diff --git a/.env.example b/.env.example index 21dbe8c..e605dce 100644 --- a/.env.example +++ b/.env.example @@ -30,6 +30,8 @@ STRATUM_PORT=3333 #optional DEV_FEE_ADDRESS= +DEV_FEE=1.5 + # mainnet | testnet NETWORK=mainnet diff --git a/src/models/StratumV1Client.spec.ts b/src/models/StratumV1Client.spec.ts index d04e918..a9bb39d 100644 --- a/src/models/StratumV1Client.spec.ts +++ b/src/models/StratumV1Client.spec.ts @@ -21,10 +21,6 @@ import { StratumV1JobsService } from '../services/stratum-v1-jobs.service'; import { IMiningInfo } from './bitcoin-rpc/IMiningInfo'; import { StratumV1Client } from './StratumV1Client'; - - - - jest.mock('../services/bitcoin-rpc.service') jest.mock('./validators/bitcoin-address.validator', () => ({ @@ -33,10 +29,7 @@ jest.mock('./validators/bitcoin-address.validator', () => ({ }, })); - describe('StratumV1Client', () => { - - let socket: Socket; let stratumV1JobsService: StratumV1JobsService; let bitcoinRpcService: MockBitcoinRpcService; @@ -87,13 +80,9 @@ describe('StratumV1Client', () => { } ], }).compile(); - - - }) - - + }); + beforeEach(async () => { - console.log('NEW TEST') clientService = moduleRef.get(ClientService); @@ -102,13 +91,10 @@ describe('StratumV1Client', () => { dataSource.getRepository(ClientEntity).delete({}); dataSource.getRepository(ClientStatisticsEntity).delete({}); - - + clientStatisticsService = moduleRef.get(ClientStatisticsService); - configService = moduleRef.get(ConfigService); - bitcoinRpcService = new MockBitcoinRpcService(configService,null); jest.spyOn(bitcoinRpcService, 'getBlockTemplate').mockReturnValue(Promise.resolve(MockRecording1.BLOCK_TEMPLATE)); bitcoinRpcService.newBlock$ = newBlockEmitter.asObservable(); @@ -130,7 +116,6 @@ describe('StratumV1Client', () => { const addressSettings = moduleRef.get(AddressSettingsService); - client = new StratumV1Client( socket, stratumV1JobsService, @@ -153,7 +138,6 @@ describe('StratumV1Client', () => { jest.useRealTimers(); }) - it('should subscribe to socket', () => { expect(socket.on).toHaveBeenCalled(); }); @@ -173,10 +157,8 @@ describe('StratumV1Client', () => { await new Promise((r) => setTimeout(r, 1)); expect(socket.write).toHaveBeenCalledWith(`{"id":1,"error":null,"result":[[["mining.notify","${client.extraNonceAndSessionId}"]],"${client.extraNonceAndSessionId}",4]}\n`, expect.any(Function)); - }); - it('should respond to mining.configure', async () => { jest.spyOn(socket, 'write').mockImplementation((data) => true); @@ -227,48 +209,28 @@ describe('StratumV1Client', () => { const clientCount = await clientService.connectedClientCount(); expect(clientCount).toBe(1); - }); - - - + it('should send job and accept submission', async () => { - - - const date = new Date(parseInt(MockRecording1.TIME, 16) * 1000); - - + jest.setSystemTime(date); - jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true)); - - + socketEmitter(Buffer.from(MockRecording1.MINING_SUBSCRIBE)); socketEmitter(Buffer.from(MockRecording1.MINING_SUGGEST_DIFFICULTY)); socketEmitter(Buffer.from(MockRecording1.MINING_AUTHORIZE)); - - - + await new Promise((r) => setTimeout(r, 100)); - - - - + expect((client as any).write).lastCalledWith(`{"id":null,"method":"mining.notify","params":["1","171592f223740e92d223f6e68bff25279af7ac4f2246451e0000000200000000","02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1903c943255c7075626c69632d706f6f6c5c","ffffffff037a90000000000000160014e6f22ca44dc800e9d049621a3b9a42c509f1c4bc3b0f250000000000160014e6f22ca44dc800e9d049621a3b9a42c509f1c4bc0000000000000000266a24aa21a9edbd3d1d916aa0b57326a2d88ebe1b68a1d7c48585f26d8335fe6a94b62755f64c00000000",["175335649d5e8746982969ec88f52e85ac9917106fba5468e699c8879ab974a1","d5644ab3e708c54cd68dc5aedc92b8d3037449687f92ec41ed6e37673d969d4a","5c9ec187517edc0698556cca5ce27e54c96acb014770599ed9df4d4937fbf2b0"],"20000000","192495f8","${MockRecording1.TIME}",false]}\n`); - - + socketEmitter(Buffer.from(MockRecording1.MINING_SUBMIT)); jest.useRealTimers(); await new Promise((r) => setTimeout(r, 1000)); expect((client as any).write).lastCalledWith(`{\"id\":5,\"error\":null,\"result\":true}\n`); - - }); - - - }); diff --git a/src/models/StratumV1Client.ts b/src/models/StratumV1Client.ts index bd2efc9..bc63f91 100644 --- a/src/models/StratumV1Client.ts +++ b/src/models/StratumV1Client.ts @@ -49,7 +49,7 @@ export class StratumV1Client { public extraNonceAndSessionId: string; public sessionStart: Date; - public noFee: boolean; + public shouldApplyFee: boolean = false; public hashRate: number = 0; private buffer: string = ''; @@ -82,8 +82,6 @@ export class StratumV1Client { } }); }); - - } public async destroy() { @@ -121,9 +119,7 @@ export class StratumV1Client { await this.socket.end(); return; } - - - + switch (parsedMessage.method) { case eRequestMethod.SUBSCRIBE: { const subscriptionMessage = plainToInstance( @@ -139,7 +135,6 @@ export class StratumV1Client { const errors = await validate(subscriptionMessage, validatorOptions); if (errors.length === 0) { - if (this.sessionStart == null) { this.sessionStart = new Date(); this.statistics = new StratumV1ClientStatistics(this.clientStatisticsService); @@ -168,8 +163,8 @@ export class StratumV1Client { break; } + case eRequestMethod.CONFIGURE: { - const configurationMessage = plainToInstance( ConfigurationMessage, parsedMessage, @@ -206,8 +201,8 @@ export class StratumV1Client { break; } + case eRequestMethod.AUTHORIZE: { - const authorizationMessage = plainToInstance( AuthorizationMessage, parsedMessage, @@ -219,7 +214,6 @@ export class StratumV1Client { }; const errors = await validate(authorizationMessage, validatorOptions); - if (errors.length === 0) { this.clientAuthorization = authorizationMessage; const success = await this.write(JSON.stringify(this.clientAuthorization.response()) + '\n'); @@ -242,6 +236,7 @@ export class StratumV1Client { break; } + case eRequestMethod.SUGGEST_DIFFICULTY: { if (this.usedSuggestedDifficulty == true) { return; @@ -258,7 +253,6 @@ export class StratumV1Client { }; const errors = await validate(suggestDifficultyMessage, validatorOptions); - if (errors.length === 0) { this.clientSuggestedDifficulty = suggestDifficultyMessage; @@ -284,7 +278,6 @@ export class StratumV1Client { break; } case eRequestMethod.SUBMIT: { - if (this.stratumInitialized == false) { console.log('Submit before initalized'); await this.socket.end(); @@ -303,7 +296,6 @@ export class StratumV1Client { }; const errors = await validate(miningSubmitMessage, validatorOptions); - if (errors.length === 0 && this.stratumInitialized == true) { const result = await this.handleMiningSubmission(miningSubmitMessage); if (result == true) { @@ -312,8 +304,6 @@ export class StratumV1Client { return; } } - - } else { console.log('Mining Submit validation error'); const err = new StratumErrorMessage( @@ -336,14 +326,11 @@ export class StratumV1Client { // return; // } } - - + if (this.clientSubscription != null && this.clientAuthorization != null && this.stratumInitialized == false) { - await this.initStratum(); - } } @@ -381,26 +368,26 @@ export class StratumV1Client { ); } - + private async sendNewMiningJob(jobTemplate: IJobTemplate) { - let payoutInformation; + const devFee = this.configService.get('DEV_FEE'); const devFeeAddress = this.configService.get('DEV_FEE_ADDRESS'); - //50Th/s - this.noFee = false; + if (this.entity) { this.hashRate = await this.clientStatisticsService.getHashRateForSession(this.clientAuthorization.address, this.clientAuthorization.worker, this.extraNonceAndSessionId); - this.noFee = this.hashRate != 0 && this.hashRate < 50000000000000; + this.shouldApplyFee = this.hashRate != 0 && this.hashRate > 50000000000000; // 50Th/s } - if (this.noFee || devFeeAddress == null || devFeeAddress.length < 1) { + + const applyDevFee = this.shouldApplyFee && devFeeAddress && devFeeAddress.length > 0 && !isNaN(devFee) && devFee > 0; + if (applyDevFee) { payoutInformation = [ - { address: this.clientAuthorization.address, percent: 100 } + { address: devFeeAddress, percent: devFee }, + { address: this.clientAuthorization.address, percent: this.calculateMinerFeeWithDevFee(devFee) } ]; - } else { payoutInformation = [ - { address: devFeeAddress, percent: 1.5 }, - { address: this.clientAuthorization.address, percent: 98.5 } + { address: this.clientAuthorization.address, percent: 100 } ]; } @@ -426,18 +413,24 @@ export class StratumV1Client { ); this.stratumV1JobsService.addJob(job); - - + const success = await this.write(job.response(jobTemplate)); if (!success) { return; } - - + //console.log(`Sent new job to ${this.clientAuthorization.worker}.${this.extraNonceAndSessionId}. (clearJobs: ${jobTemplate.blockData.clearJobs}, fee?: ${!this.noFee})`) - } + + private calculateMinerFeeWithDevFee(devFee: number): number { + const maxFee = 100; + const defaultFee = 1.5; + + // Ensure the devFee is below 100, otherwise default to 1.5 + const feeToUse = devFee > maxFee ? defaultFee : devFee; + return maxFee - feeToUse; + } private async handleMiningSubmission(submission: MiningSubmitMessage) {