Isolate API worker module

This commit is contained in:
Ben
2026-08-03 10:27:03 -04:00
parent f31cdef7f0
commit 3cb4cbfbdf
6 changed files with 110 additions and 7 deletions
+62
View File
@@ -0,0 +1,62 @@
import { CacheModule } from '@nestjs/cache-manager';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AddressController } from './controllers/address/address.controller';
import { ClientController } from './controllers/client/client.controller';
import { UserAgentReportModule } from './ORM/_views/user-agent-report/user-agent-report.module';
import { AddressSettingsModule } from './ORM/address-settings/address-settings.module';
import { BlocksModule } from './ORM/blocks/blocks.module';
import { ClientStatisticsModule } from './ORM/client-statistics/client-statistics.module';
import { ClientModule } from './ORM/client/client.module';
import { PayoutSnapshotModule } from './ORM/payout-snapshot/payout-snapshot.module';
import { RpcBlocksModule } from './ORM/rpc-block/rpc-block.module';
import { ShareAccountingModule } from './ORM/share-accounting/share-accounting.module';
import { createDatabaseOptions } from './database.config';
import { BitcoinAddressValidator } from './models/validators/bitcoin-address.validator';
import { BitcoinRpcService } from './services/bitcoin-rpc.service';
import { RedisMessagingModule } from './services/redis-messaging.module';
import { Sv2AuthorityService } from './services/sv2-authority.service';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forRootAsync({
useFactory: (configService: ConfigService) => createDatabaseOptions({
...process.env,
DB_HOST: configService.get('DB_HOST'),
DB_PORT: configService.get('DB_PORT'),
DB_USERNAME: configService.get('DB_USERNAME'),
DB_PASSWORD: configService.get('DB_PASSWORD'),
DB_DATABASE: configService.get('DB_DATABASE'),
DB_LOGGING: configService.get('DB_LOGGING'),
DB_POOL_SIZE: configService.get('DB_POOL_SIZE'),
}),
imports: [ConfigModule],
inject: [ConfigService],
}),
CacheModule.register(),
RedisMessagingModule,
ClientStatisticsModule,
ClientModule,
AddressSettingsModule,
BlocksModule,
RpcBlocksModule,
UserAgentReportModule,
ShareAccountingModule,
PayoutSnapshotModule,
],
controllers: [
AppController,
ClientController,
AddressController,
],
providers: [
BitcoinRpcService,
BitcoinAddressValidator,
Sv2AuthorityService,
],
})
export class ApiModule {}
+2 -2
View File
@@ -6,7 +6,7 @@ describe('AppController', () => {
blocksService?: any; blocksService?: any;
addressSettingsService?: any; addressSettingsService?: any;
userAgentReportService?: any; userAgentReportService?: any;
stratumV2Service?: any; sv2AuthorityService?: any;
redisMessagingService?: any; redisMessagingService?: any;
} = {}) => { } = {}) => {
return new AppController( return new AppController(
@@ -26,7 +26,7 @@ describe('AppController', () => {
overrides.userAgentReportService ?? { overrides.userAgentReportService ?? {
getReport: jest.fn().mockResolvedValue([]), getReport: jest.fn().mockResolvedValue([]),
}, },
overrides.stratumV2Service ?? { overrides.sv2AuthorityService ?? {
getPoolAuthorityPublicKey: jest.fn().mockResolvedValue({ getPoolAuthorityPublicKey: jest.fn().mockResolvedValue({
publicKey: '', publicKey: '',
configured: false, configured: false,
+3 -3
View File
@@ -10,9 +10,9 @@ import { ClientStatisticsService } from './ORM/client-statistics/client-statisti
import { ClientService } from './ORM/client/client.service'; import { ClientService } from './ORM/client/client.service';
import { BitcoinRpcService } from './services/bitcoin-rpc.service'; import { BitcoinRpcService } from './services/bitcoin-rpc.service';
import { UserAgentReportView } from './ORM/_views/user-agent-report/user-agent-report.view'; import { UserAgentReportView } from './ORM/_views/user-agent-report/user-agent-report.view';
import { StratumV2Service } from './services/stratum-v2.service';
import { ShareAccountingService } from './ORM/share-accounting/share-accounting.service'; import { ShareAccountingService } from './ORM/share-accounting/share-accounting.service';
import { RedisMessagingService } from './services/redis-messaging.service'; import { RedisMessagingService } from './services/redis-messaging.service';
import { Sv2AuthorityService } from './services/sv2-authority.service';
import { normalizePayoutMode } from './types/payout-mode'; import { normalizePayoutMode } from './types/payout-mode';
@Controller() @Controller()
@@ -30,7 +30,7 @@ export class AppController {
private readonly bitcoinRpcService: BitcoinRpcService, private readonly bitcoinRpcService: BitcoinRpcService,
private readonly addressSettingsService: AddressSettingsService, private readonly addressSettingsService: AddressSettingsService,
private readonly userAgentReportService: UserAgentReportService, private readonly userAgentReportService: UserAgentReportService,
private readonly stratumV2Service: StratumV2Service, private readonly sv2AuthorityService: Sv2AuthorityService,
private readonly shareAccountingService: ShareAccountingService, private readonly shareAccountingService: ShareAccountingService,
private readonly redisMessagingService: RedisMessagingService private readonly redisMessagingService: RedisMessagingService
) { } ) { }
@@ -78,7 +78,7 @@ export class AppController {
const [blockData, highScores, poolAuthority, userAgentReport] = await Promise.all([ const [blockData, highScores, poolAuthority, userAgentReport] = await Promise.all([
withInfoTimeout('found blocks', this.blocksService.getFoundBlocks(), staleInfo?.blockData ?? []), withInfoTimeout('found blocks', this.blocksService.getFoundBlocks(), staleInfo?.blockData ?? []),
withInfoTimeout('high scores', this.addressSettingsService.getHighScores(), staleInfo?.highScores ?? []), withInfoTimeout('high scores', this.addressSettingsService.getHighScores(), staleInfo?.highScores ?? []),
withInfoTimeout('SV2 authority', this.stratumV2Service.getPoolAuthorityPublicKey(), { withInfoTimeout('SV2 authority', this.sv2AuthorityService.getPoolAuthorityPublicKey(), {
publicKey: staleInfo?.sv2?.poolAuthorityPublicKey ?? '', publicKey: staleInfo?.sv2?.poolAuthorityPublicKey ?? '',
configured: staleInfo?.sv2?.authorityKeyConfigured ?? false configured: staleInfo?.sv2?.authorityKeyConfigured ?? false
}), }),
+2
View File
@@ -33,6 +33,7 @@ import { StratumV1Service } from './services/stratum-v1.service';
import { Sv2JobDeclarationRegistryService } from './services/sv2-job-declaration-registry.service'; import { Sv2JobDeclarationRegistryService } from './services/sv2-job-declaration-registry.service';
import { Sv2JobDeclarationService } from './services/sv2-job-declaration.service'; import { Sv2JobDeclarationService } from './services/sv2-job-declaration.service';
import { Sv2TemplateDistributionService } from './services/sv2-template-distribution.service'; import { Sv2TemplateDistributionService } from './services/sv2-template-distribution.service';
import { Sv2AuthorityService } from './services/sv2-authority.service';
import { StratumV2Service } from './services/stratum-v2.service'; import { StratumV2Service } from './services/stratum-v2.service';
import { TelegramService } from './services/telegram.service'; import { TelegramService } from './services/telegram.service';
import { TemplateProviderService } from './services/template-provider.service'; import { TemplateProviderService } from './services/template-provider.service';
@@ -94,6 +95,7 @@ const ORMModules = [
Sv2JobDeclarationRegistryService, Sv2JobDeclarationRegistryService,
Sv2JobDeclarationService, Sv2JobDeclarationService,
Sv2TemplateDistributionService, Sv2TemplateDistributionService,
Sv2AuthorityService,
CustomWorkService, CustomWorkService,
DatumService, DatumService,
BTCPayService, BTCPayService,
+4 -2
View File
@@ -7,6 +7,7 @@ import { readFileSync, watch } from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as ecc from 'tiny-secp256k1'; import * as ecc from 'tiny-secp256k1';
import { ApiModule } from './api.module';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
async function bootstrap() { async function bootstrap() {
@@ -32,7 +33,8 @@ async function bootstrap() {
}; };
} }
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter(options)); const appModule = process.env.API_ONLY === 'true' ? ApiModule : AppModule;
const app = await NestFactory.create<NestFastifyApplication>(appModule, new FastifyAdapter(options));
app.setGlobalPrefix('api'); app.setGlobalPrefix('api');
app.useGlobalPipes( app.useGlobalPipes(
new ValidationPipe({ new ValidationPipe({
@@ -54,7 +56,7 @@ async function bootstrap() {
}); });
app.enableCors(); app.enableCors();
useContainer(app.select(AppModule), { fallbackOnErrors: true }); useContainer(app.select(appModule), { fallbackOnErrors: true });
// Taproot // Taproot
bitcoinjs.initEccLib(ecc); bitcoinjs.initEccLib(ecc);
+37
View File
@@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as crypto from 'crypto';
import { encodeSv2AuthorityPublicKey } from '../models/sv2/sv2-authority-key';
import { xOnlyPubKeyFromPriv } from '../models/sv2/sv2-noise';
@Injectable()
export class Sv2AuthorityService {
private authorityPublicKey: string | null = null;
private authorityKeyConfigured = false;
constructor(
private readonly configService: ConfigService,
) {}
public async getPoolAuthorityPublicKey(): Promise<{ publicKey: string; configured: boolean }> {
if (this.authorityPublicKey != null) {
return {
publicKey: this.authorityPublicKey,
configured: this.authorityKeyConfigured,
};
}
const configuredAuthorityKey = this.configService.get<string>('SV2_AUTHORITY_PRIVKEY');
this.authorityKeyConfigured = configuredAuthorityKey?.length === 64;
const authorityPrivKey = configuredAuthorityKey?.length === 64
? Buffer.from(configuredAuthorityKey, 'hex')
: crypto.randomBytes(32);
this.authorityPublicKey = encodeSv2AuthorityPublicKey(xOnlyPubKeyFromPriv(authorityPrivKey));
return {
publicKey: this.authorityPublicKey,
configured: this.authorityKeyConfigured,
};
}
}