From b9af79978fe7f5fed4d904fe360d62ab5be4f381 Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 24 Nov 2023 14:56:35 -0500 Subject: [PATCH] RPC load reduction --- .vscode/settings.json | 3 +- src/ORM/rpc-block/rpc-block.entity.ts | 14 +++++ src/ORM/rpc-block/rpc-block.module.ts | 14 +++++ src/ORM/rpc-block/rpc-block.service.ts | 28 +++++++++ src/app.module.ts | 5 +- src/models/StratumV1Client.ts | 3 +- src/services/bitcoin-rpc.service.ts | 75 +++++++++++++++++++------ src/services/stratum-v1-jobs.service.ts | 2 +- 8 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 src/ORM/rpc-block/rpc-block.entity.ts create mode 100644 src/ORM/rpc-block/rpc-block.module.ts create mode 100644 src/ORM/rpc-block/rpc-block.service.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index ac83d76..e0eba1d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,5 +15,6 @@ "Satoshis", "submitblock", "Tempalte" - ] + ], + "idf.portWin": "COM30" } \ No newline at end of file diff --git a/src/ORM/rpc-block/rpc-block.entity.ts b/src/ORM/rpc-block/rpc-block.entity.ts new file mode 100644 index 0000000..2f65850 --- /dev/null +++ b/src/ORM/rpc-block/rpc-block.entity.ts @@ -0,0 +1,14 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class RpcBlockEntity { + + @PrimaryColumn() + blockHeight: number; + + @Column({ nullable: true }) + lockedBy?: string; + + @Column({ nullable: true }) + data?: string; +} \ No newline at end of file diff --git a/src/ORM/rpc-block/rpc-block.module.ts b/src/ORM/rpc-block/rpc-block.module.ts new file mode 100644 index 0000000..8a4460b --- /dev/null +++ b/src/ORM/rpc-block/rpc-block.module.ts @@ -0,0 +1,14 @@ +import { Global, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { RpcBlockEntity } from './rpc-block.entity'; +import { RpcBlockService } from './rpc-block.service'; + + +@Global() +@Module({ + imports: [TypeOrmModule.forFeature([RpcBlockEntity])], + providers: [RpcBlockService], + exports: [TypeOrmModule, RpcBlockService], +}) +export class RpcBlocksModule { } diff --git a/src/ORM/rpc-block/rpc-block.service.ts b/src/ORM/rpc-block/rpc-block.service.ts new file mode 100644 index 0000000..71b5b6c --- /dev/null +++ b/src/ORM/rpc-block/rpc-block.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { RpcBlockEntity } from './rpc-block.entity'; + +@Injectable() +export class RpcBlockService { + constructor( + @InjectRepository(RpcBlockEntity) + private rpcBlockRepository: Repository + ) { + + } + public getBlock(blockHeight: number) { + return this.rpcBlockRepository.findOne({ + where: { blockHeight } + }); + } + + public lockBlock(blockHeight: number, process: string) { + return this.rpcBlockRepository.save({ blockHeight, data: null, lockedBy: process }); + } + + public saveBlock(blockHeight: number, data: string) { + return this.rpcBlockRepository.update(blockHeight, { data }) + } +} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index c19d0dc..3faba65 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,6 +13,7 @@ import { AddressSettingsModule } from './ORM/address-settings/address-settings.m import { BlocksModule } from './ORM/blocks/blocks.module'; import { ClientStatisticsModule } from './ORM/client-statistics/client-statistics.module'; import { ClientModule } from './ORM/client/client.module'; +import { RpcBlocksModule } from './ORM/rpc-block/rpc-block.module'; import { TelegramSubscriptionsModule } from './ORM/telegram-subscriptions/telegram-subscriptions.module'; import { AppService } from './services/app.service'; import { BitcoinRpcService } from './services/bitcoin-rpc.service'; @@ -25,12 +26,14 @@ import { StratumV1JobsService } from './services/stratum-v1-jobs.service'; import { StratumV1Service } from './services/stratum-v1.service'; import { TelegramService } from './services/telegram.service'; + const ORMModules = [ ClientStatisticsModule, ClientModule, AddressSettingsModule, TelegramSubscriptionsModule, - BlocksModule + BlocksModule, + RpcBlocksModule ] @Module({ diff --git a/src/models/StratumV1Client.ts b/src/models/StratumV1Client.ts index 7d94b2e..789d90c 100644 --- a/src/models/StratumV1Client.ts +++ b/src/models/StratumV1Client.ts @@ -352,8 +352,7 @@ export class StratumV1Client { } - this.stratumSubscription = this.stratumV1JobsService.newMiningJob$.pipe( - ).subscribe(async (jobTemplate) => { + this.stratumSubscription = this.stratumV1JobsService.newMiningJob$.subscribe(async (jobTemplate) => { try { await this.sendNewMiningJob(jobTemplate); } catch (e) { diff --git a/src/services/bitcoin-rpc.service.ts b/src/services/bitcoin-rpc.service.ts index d2bee67..c1deeb5 100644 --- a/src/services/bitcoin-rpc.service.ts +++ b/src/services/bitcoin-rpc.service.ts @@ -2,10 +2,12 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { RPCClient } from 'rpc-bitcoin'; import { BehaviorSubject, filter, shareReplay } from 'rxjs'; +import { RpcBlockService } from 'src/ORM/rpc-block/rpc-block.service'; import { IBlockTemplate } from '../models/bitcoin-rpc/IBlockTemplate'; import { IMiningInfo } from '../models/bitcoin-rpc/IMiningInfo'; -import * as zmq from 'zeromq'; + +// import * as zmq from 'zeromq'; @Injectable() export class BitcoinRpcService { @@ -15,7 +17,11 @@ export class BitcoinRpcService { private _newBlock$: BehaviorSubject = new BehaviorSubject(undefined); public newBlock$ = this._newBlock$.pipe(filter(block => block != null), shareReplay({ refCount: true, bufferSize: 1 })); - constructor(private readonly configService: ConfigService) { + constructor( + private readonly configService: ConfigService, + private rpcBlockService: RpcBlockService + ) { + const url = this.configService.get('BITCOIN_RPC_URL'); const user = this.configService.get('BITCOIN_RPC_USER'); const pass = this.configService.get('BITCOIN_RPC_PASSWORD'); @@ -27,13 +33,13 @@ export class BitcoinRpcService { console.log('Bitcoin RPC connected'); if (this.configService.get('BITCOIN_ZMQ_HOST')) { - const sock = zmq.socket("sub"); - sock.connect(this.configService.get('BITCOIN_ZMQ_HOST')); - sock.subscribe("rawblock"); - sock.on("message", async (topic: Buffer, message: Buffer) => { - console.log("new block zmq"); - this.pollMiningInfo(); - }); + // const sock = zmq.socket("sub"); + // sock.connect(this.configService.get('BITCOIN_ZMQ_HOST')); + // sock.subscribe("rawblock"); + // sock.on("message", async (topic: Buffer, message: Buffer) => { + // console.log("new block zmq"); + // this.pollMiningInfo(); + // }); this.pollMiningInfo(); } else { setInterval(this.pollMiningInfo.bind(this), 500); @@ -49,18 +55,53 @@ export class BitcoinRpcService { } } - public async getBlockTemplate(): Promise { + private async waitForBlock(blockHeight: number) { + while (true) { + await new Promise(r => setTimeout(r, 100)); + + const block = await this.rpcBlockService.getBlock(blockHeight); + if (block != null && block.data != null) { + console.log('promise loop resolved'); + return Promise.resolve(JSON.parse(block.data)); + } + console.log('promise loop'); + } + } + + public async getBlockTemplate(blockHeight: number): Promise { let result: IBlockTemplate; try { - result = await this.client.getblocktemplate({ - template_request: { - rules: ['segwit'], - mode: 'template', - capabilities: ['serverlist', 'proposal'] + + const block = await this.rpcBlockService.getBlock(blockHeight); + + if (block != null && block.data != null) { + return Promise.resolve(JSON.parse(block.data)); + } else if (block == null) { + const { lockedBy } = await this.rpcBlockService.lockBlock(blockHeight, process.env.NODE_APP_INSTANCE); + + if (lockedBy != process.env.NODE_APP_INSTANCE) { + await this.waitForBlock(blockHeight); + return; } - }); + + result = await this.client.getblocktemplate({ + template_request: { + rules: ['segwit'], + mode: 'template', + capabilities: ['serverlist', 'proposal'] + } + }); + await this.rpcBlockService.saveBlock(blockHeight, JSON.stringify(result)); + } else { + //wait for block + await this.waitForBlock(blockHeight); + + } + + + } catch (e) { - console.error('Error getblocktemplate:' ,e.message); + console.error('Error getblocktemplate:', e.message); throw new Error('Error getblocktemplate'); } console.log(`getblocktemplate tx count: ${result.transactions.length}`); diff --git a/src/services/stratum-v1-jobs.service.ts b/src/services/stratum-v1-jobs.service.ts index e4dc35d..864608b 100644 --- a/src/services/stratum-v1-jobs.service.ts +++ b/src/services/stratum-v1-jobs.service.ts @@ -44,7 +44,7 @@ export class StratumV1JobsService { this.newMiningJob$ = combineLatest([this.bitcoinRpcService.newBlock$, interval(60000).pipe(delay(this.delay), startWith(-1))]).pipe( switchMap(([miningInfo, interval]) => { - return from(this.bitcoinRpcService.getBlockTemplate()).pipe(map((blockTemplate) => { + return from(this.bitcoinRpcService.getBlockTemplate(miningInfo.blocks)).pipe(map((blockTemplate) => { return { blockTemplate, interval