diff --git a/package-lock.json b/package-lock.json index 9463a04..a894435 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "@nestjs/schedule": "^3.0.1", "@nestjs/typeorm": "^10.0.0", "axios": "^1.4.0", - "big.js": "^6.2.1", "bitcoin-address-validation": "^2.2.1", "bitcoinjs-lib": "^6.1.3", "bitcoinjs-message": "^2.2.0", @@ -44,7 +43,6 @@ "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", - "@types/big.js": "^6.1.6", "@types/cron": "^2.0.1", "@types/express": "^4.17.13", "@types/jest": "29.5.1", @@ -2219,12 +2217,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/big.js": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.1.6.tgz", - "integrity": "sha512-0r9J+Zz9rYm2hOTwiMAVkm3XFQ4u5uTK37xrQMhc9bysn/sf/okzovWMYYIBMFTn/yrEZ11pusgLEaoarTlQbA==", - "dev": true - }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -3436,18 +3428,6 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, - "node_modules/big.js": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.1.tgz", - "integrity": "sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==", - "engines": { - "node": "*" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/bigjs" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -13504,12 +13484,6 @@ "@babel/types": "^7.20.7" } }, - "@types/big.js": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@types/big.js/-/big.js-6.1.6.tgz", - "integrity": "sha512-0r9J+Zz9rYm2hOTwiMAVkm3XFQ4u5uTK37xrQMhc9bysn/sf/okzovWMYYIBMFTn/yrEZ11pusgLEaoarTlQbA==", - "dev": true - }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -14498,11 +14472,6 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, - "big.js": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.1.tgz", - "integrity": "sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ==" - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", diff --git a/package.json b/package.json index bf958cf..4699b20 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@nestjs/schedule": "^3.0.1", "@nestjs/typeorm": "^10.0.0", "axios": "^1.4.0", - "big.js": "^6.2.1", "bitcoin-address-validation": "^2.2.1", "bitcoinjs-lib": "^6.1.3", "bitcoinjs-message": "^2.2.0", @@ -55,7 +54,6 @@ "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", - "@types/big.js": "^6.1.6", "@types/cron": "^2.0.1", "@types/express": "^4.17.13", "@types/jest": "29.5.1", diff --git a/src/ORM/address-settings/address-settings.service.ts b/src/ORM/address-settings/address-settings.service.ts index 5f2a558..06e8b77 100644 --- a/src/ORM/address-settings/address-settings.service.ts +++ b/src/ORM/address-settings/address-settings.service.ts @@ -34,6 +34,24 @@ export class AddressSettingsService { return await this.addressSettingsRepository.update({ address }, { bestDifficulty, bestDifficultyUserAgent }); } + public async updateBestDifficultyIfHigher(address: string, bestDifficulty: number, bestDifficultyUserAgent: string) { + await this.addressSettingsRepository + .createQueryBuilder() + .insert() + .into(AddressSettingsEntity) + .values({ address }) + .orIgnore() + .execute(); + + return await this.addressSettingsRepository + .createQueryBuilder() + .update(AddressSettingsEntity) + .set({ bestDifficulty, bestDifficultyUserAgent }) + .where('address = :address', { address }) + .andWhere('"bestDifficulty" < :bestDifficulty', { bestDifficulty }) + .execute(); + } + public async createNew(address: string) { return await this.addressSettingsRepository.save({ address }); } diff --git a/src/ORM/client/client.service.ts b/src/ORM/client/client.service.ts index 00850e5..684d459 100644 --- a/src/ORM/client/client.service.ts +++ b/src/ORM/client/client.service.ts @@ -140,6 +140,17 @@ export class ClientService { public async updateBestDifficulty(id: string, bestDifficulty: number) { return await this.clientRepository.update({ id }, { bestDifficulty }); } + + public async updateBestDifficultyIfHigher(id: string, bestDifficulty: number) { + return await this.clientRepository + .createQueryBuilder() + .update(ClientEntity) + .set({ bestDifficulty }) + .where('id = :id', { id }) + .andWhere('"bestDifficulty" < :bestDifficulty', { bestDifficulty }) + .execute(); + } + public async connectedClientCount(): Promise { return await this.clientRepository.count(); } @@ -188,4 +199,4 @@ export class ClientService { // return result; // } -} \ No newline at end of file +} diff --git a/src/models/MiningJob.spec.ts b/src/models/MiningJob.spec.ts index 2644014..4c7e2a9 100644 --- a/src/models/MiningJob.spec.ts +++ b/src/models/MiningJob.spec.ts @@ -83,6 +83,33 @@ describe('MiningJob', () => { expect(updatedBlock.version).toBe(jobTemplate.block.version); }); + it('should build the same header as the full block update path', () => { + const versionMask = parseInt('00002000', 16); + const nonce = parseInt('ed460d91', 16); + const extraNonce1 = '57a6f098'; + const extraNonce2 = 'c708000000000000'; + const timestamp = parseInt(MockRecording1.TIME, 16); + + const updatedBlock = job.copyAndUpdateBlock( + jobTemplate, + versionMask, + nonce, + extraNonce1, + extraNonce2, + timestamp + ); + const fastHeader = job.buildHeaderBuffer( + jobTemplate, + versionMask, + nonce, + extraNonce1, + extraNonce2, + timestamp + ); + + expect(fastHeader.equals(updatedBlock.toBuffer(true))).toBe(true); + }); + it('should expose coinbase helpers for downstream validation', () => { const notify = JSON.parse(job.response(jobTemplate)); diff --git a/src/models/MiningJob.ts b/src/models/MiningJob.ts index 1fb458b..6c73832 100644 --- a/src/models/MiningJob.ts +++ b/src/models/MiningJob.ts @@ -18,6 +18,9 @@ export class MiningJob { private coinbaseTransaction: bitcoinjs.Transaction; private coinbasePart1: string; private coinbasePart2: string; + private coinbasePart1Buffer: Buffer; + private coinbasePart2Buffer: Buffer; + private merkleBranchBuffers: Buffer[]; public jobTemplateId: string; public networkDifficulty: number; @@ -34,6 +37,7 @@ export class MiningJob { this.creation = new Date().getTime(); this.jobTemplateId = jobTemplate.blockData.id; + this.merkleBranchBuffers = jobTemplate.merkle_branch.map(branch => Buffer.from(branch, 'hex')); this.coinbaseTransaction = this.createCoinbaseTransaction(payoutInformation, jobTemplate.blockData.coinbasevalue); @@ -83,6 +87,8 @@ export class MiningJob { this.coinbasePart1 = serializedCoinbaseTx.slice(0, partOneIndex - (TOTAL_EXTRANONCE_SIZE_BYTES * 2)); this.coinbasePart2 = serializedCoinbaseTx.slice(partOneIndex); + this.coinbasePart1Buffer = Buffer.from(this.coinbasePart1, 'hex'); + this.coinbasePart2Buffer = Buffer.from(this.coinbasePart2, 'hex'); } @@ -93,17 +99,42 @@ export class MiningJob { } public getCoinbasePrefixBuffer(): Buffer { - return Buffer.from(this.coinbasePart1, 'hex'); + return Buffer.from(this.coinbasePart1Buffer); } public getCoinbaseSuffixBuffer(): Buffer { - return Buffer.from(this.coinbasePart2, 'hex'); + return Buffer.from(this.coinbasePart2Buffer); } public cloneCoinbaseTransaction(): bitcoinjs.Transaction { return bitcoinjs.Transaction.fromBuffer(this.coinbaseTransaction.toBuffer()); } + public buildHeaderBuffer(jobTemplate: IJobTemplate, versionMask: number, nonce: number, extraNonce: string, extraNonce2: string, timestamp: number): Buffer { + const coinbaseBuffer = Buffer.concat([ + this.coinbasePart1Buffer, + Buffer.from(`${extraNonce}${extraNonce2}`, 'hex'), + this.coinbasePart2Buffer, + ]); + const coinbaseHash = bitcoinjs.crypto.hash256(coinbaseBuffer); + const merkleRoot = this.calculateMerkleRootHash(coinbaseHash, this.merkleBranchBuffers); + + let version = jobTemplate.block.version; + if (versionMask !== undefined && versionMask != 0) { + version = version ^ versionMask; + } + + const header = Buffer.alloc(80); + header.writeInt32LE(version, 0); + jobTemplate.block.prevHash.copy(header, 4); + merkleRoot.copy(header, 36); + header.writeUInt32LE(timestamp, 68); + header.writeUInt32LE(jobTemplate.block.bits, 72); + header.writeUInt32LE(nonce, 76); + + return header; + } + public copyAndUpdateBlock(jobTemplate: IJobTemplate, versionMask: number, nonce: number, extraNonce: string, extraNonce2: string, timestamp: number): bitcoinjs.Block { const testBlock = Object.assign(new bitcoinjs.Block(), jobTemplate.block); @@ -111,7 +142,7 @@ export class MiningJob { return Object.assign(new bitcoinjs.Transaction(), tx); }); - testBlock.transactions[0] = this.coinbaseTransaction; + testBlock.transactions[0] = this.cloneCoinbaseTransaction(); testBlock.nonce = nonce; @@ -126,7 +157,7 @@ export class MiningJob { testBlock.transactions[0].ins[0].script = Buffer.from(`${nonceScript.substring(0, nonceScript.length - (TOTAL_EXTRANONCE_SIZE_BYTES * 2))}${extraNonce}${extraNonce2}`, 'hex'); //recompute the root since we updated the coinbase script with the nonces - testBlock.merkleRoot = this.calculateMerkleRootHash(testBlock.transactions[0].getHash(false), jobTemplate.merkle_branch); + testBlock.merkleRoot = this.calculateMerkleRootHash(testBlock.transactions[0].getHash(false), this.merkleBranchBuffers); testBlock.timestamp = timestamp; @@ -135,14 +166,14 @@ export class MiningJob { } - private calculateMerkleRootHash(newRoot: Buffer, merkleBranches: string[]): Buffer { + private calculateMerkleRootHash(newRoot: Buffer, merkleBranches: Buffer[]): Buffer { const bothMerkles = Buffer.alloc(64); bothMerkles.set(newRoot); for (let i = 0; i < merkleBranches.length; i++) { - bothMerkles.set(Buffer.from(merkleBranches[i], 'hex'), 32); + bothMerkles.set(merkleBranches[i], 32); newRoot = bitcoinjs.crypto.hash256(bothMerkles); bothMerkles.set(newRoot); } diff --git a/src/models/StratumV1Client.spec.ts b/src/models/StratumV1Client.spec.ts index aa062fd..426df08 100644 --- a/src/models/StratumV1Client.spec.ts +++ b/src/models/StratumV1Client.spec.ts @@ -20,7 +20,9 @@ import { BitcoinRpcService as MockBitcoinRpcService } from '../services/bitcoin- import { NotificationService } from '../services/notification.service'; import { StratumV1JobsService } from '../services/stratum-v1-jobs.service'; import { IBlockTemplate } from './bitcoin-rpc/IBlockTemplate'; +import { MiningJob } from './MiningJob'; import { effectiveJobDifficulty, StratumV1Client } from './StratumV1Client'; +import { MiningSubmitMessage } from './stratum-messages/MiningSubmitMessage'; @@ -263,6 +265,7 @@ describe('StratumV1Client', () => { jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true)); emitMessage(MockRecording1.MINING_SUBSCRIBE); + emitMessage(`{"id": 4, "method": "mining.suggest_difficulty", "params": [0]}`); emitMessage(MockRecording1.MINING_AUTHORIZE); await new Promise((r) => setTimeout(r, 100)); emitMessage(MockRecording1.MINING_SUBMIT); @@ -312,6 +315,79 @@ describe('StratumV1Client', () => { }); + it('should use the header-only fast path for non-block submissions', async () => { + jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true)); + const buildHeaderSpy = jest.spyOn(MiningJob.prototype, 'buildHeaderBuffer'); + const fullBlockSpy = jest.spyOn(MiningJob.prototype, 'copyAndUpdateBlock'); + + emitMessage(MockRecording1.MINING_SUBSCRIBE); + emitMessage(`{"id": 4, "method": "mining.suggest_difficulty", "params": [0]}`); + emitMessage(MockRecording1.MINING_AUTHORIZE); + await new Promise((r) => setTimeout(r, 100)); + + emitMessage(MockRecording1.MINING_SUBMIT); + jest.useRealTimers(); + await new Promise((r) => setTimeout(r, 1000)); + + expect(buildHeaderSpy).toHaveBeenCalled(); + expect(fullBlockSpy).not.toHaveBeenCalled(); + }); + + it('should write accepted response before share accounting finishes', async () => { + jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true)); + + emitMessage(MockRecording1.MINING_SUBSCRIBE); + emitMessage(`{"id": 4, "method": "mining.suggest_difficulty", "params": [0]}`); + emitMessage(MockRecording1.MINING_AUTHORIZE); + await new Promise((r) => setTimeout(r, 100)); + + let finishAccounting: () => void; + const accountingPromise = new Promise((resolve) => { + finishAccounting = resolve; + }); + jest.spyOn((client as any).statistics, 'addShares').mockReturnValue(accountingPromise); + + emitMessage(MockRecording1.MINING_SUBMIT); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + + expect((client as any).write).toHaveBeenCalledWith(`{"id":5,"error":null,"result":true}\n`); + + finishAccounting(); + jest.useRealTimers(); + await new Promise((r) => setTimeout(r, 100)); + }); + + it('should update address best difficulty through the atomic path', async () => { + jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true)); + jest.spyOn(client as any, 'calculateDifficulty').mockReturnValue({ + submissionDifficulty: 1024, + submissionHash: 'share' + }); + const addressSettings = moduleRef.get(AddressSettingsService); + const getSettingsSpy = jest.spyOn(addressSettings, 'getSettings'); + const updateIfHigherSpy = jest.spyOn(addressSettings as any, 'updateBestDifficultyIfHigher').mockResolvedValue({ affected: 1 }); + const clientUpdateIfHigherSpy = jest.spyOn(clientService as any, 'updateBestDifficultyIfHigher').mockResolvedValue({ affected: 1 }); + + emitMessage(MockRecording1.MINING_SUBSCRIBE); + emitMessage(`{"id": 4, "method": "mining.suggest_difficulty", "params": [0]}`); + emitMessage(MockRecording1.MINING_AUTHORIZE); + await new Promise((r) => setTimeout(r, 100)); + + emitMessage(MockRecording1.MINING_SUBMIT); + jest.useRealTimers(); + await new Promise((r) => setTimeout(r, 1000)); + + expect(updateIfHigherSpy).toHaveBeenCalledWith( + 'tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4', + 1024, + expect.any(String) + ); + expect(clientUpdateIfHigherSpy).toHaveBeenCalledWith(expect.any(String), 1024); + expect(getSettingsSpy).not.toHaveBeenCalled(); + }); + it('should reject duplicate submissions', async () => { jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true)); @@ -330,6 +406,7 @@ describe('StratumV1Client', () => { it('should reject submissions for unknown jobs', async () => { jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true)); + const hashSpy = jest.spyOn(MiningSubmitMessage.prototype, 'hash'); emitMessage(MockRecording1.MINING_SUBSCRIBE); emitMessage(`{"id": 4, "method": "mining.suggest_difficulty", "params": [0]}`); @@ -340,6 +417,8 @@ describe('StratumV1Client', () => { await new Promise((r) => setTimeout(r, 100)); expect((client as any).write).lastCalledWith(`{"id":5,"result":null,"error":[21,"Job not found",""]}\n`); + expect(await clientService.connectedClientCount()).toBe(0); + expect(hashSpy).not.toHaveBeenCalled(); }); it('should reject submissions when the job template has expired', async () => { @@ -369,6 +448,7 @@ describe('StratumV1Client', () => { await new Promise((r) => setTimeout(r, 100)); expect((client as any).write).lastCalledWith(`{"id":5,"result":null,"error":[23,"Difficulty too low",""]}\n`); + expect(await clientService.connectedClientCount()).toBe(0); }); it('should reject submissions with short extranonce2 values', async () => { diff --git a/src/models/StratumV1Client.ts b/src/models/StratumV1Client.ts index afdd313..52df4ea 100644 --- a/src/models/StratumV1Client.ts +++ b/src/models/StratumV1Client.ts @@ -1,5 +1,4 @@ import { ConfigService } from '@nestjs/config'; -import Big from 'big.js'; import * as bitcoinjs from 'bitcoinjs-lib'; import { plainToInstance } from 'class-transformer'; import { validate, ValidatorOptions } from 'class-validator'; @@ -29,6 +28,8 @@ import { EXTRANONCE1_SIZE_BYTES } from './stratum.constants'; import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage'; import { StratumV1ClientStatistics } from './StratumV1ClientStatistics'; +const TRUE_DIFF_ONE = 2.695953529101131e67; + export function effectiveJobDifficulty( jobIdInt: number, currentDiff: number, @@ -481,46 +482,28 @@ export class StratumV1Client { } - private async handleMiningSubmission(submission: MiningSubmitMessage) { + private async ensureClientEntity() { + if (this.clientEntity != null) { + return; + } - if (this.clientEntity == null) { - if (this.creatingEntity == null) { - this.creatingEntity = new Promise(async (resolve, reject) => { - try { - this.clientEntity = await this.clientService.insert({ - sessionId: this.extraNonceAndSessionId, - address: this.clientAuthorization.address, - clientName: this.clientAuthorization.worker, - userAgent: this.clientSubscription.userAgent, - startTime: new Date(), - bestDifficulty: 0 - }); - } catch (e) { - reject(e); - } - resolve(); + if (this.creatingEntity == null) { + this.creatingEntity = (async () => { + this.clientEntity = await this.clientService.insert({ + sessionId: this.extraNonceAndSessionId, + address: this.clientAuthorization.address, + clientName: this.clientAuthorization.worker, + userAgent: this.clientSubscription.userAgent, + startTime: new Date(), + bestDifficulty: 0 }); - await this.creatingEntity; - - } else { - await this.creatingEntity; - } + })(); } - const submissionHash = submission.hash(); - if(this.miningSubmissionHashes.has(submissionHash)){ - const err = new StratumErrorMessage( - submission.id, - eStratumErrorCode.DuplicateShare, - 'Duplicate share').response(); - const success = await this.write(err); - if (!success) { - return false; - } - return false; - }else{ - this.miningSubmissionHashes.add(submissionHash); - } + await this.creatingEntity; + } + + private async handleMiningSubmission(submission: MiningSubmitMessage) { const job = this.stratumV1JobsService.getJobById(submission.jobId); @@ -565,17 +548,40 @@ export class StratumV1Client { } return false; } - - const updatedJobBlock = job.copyAndUpdateBlock( + const submissionHash = [ + submission.jobId, + submission.extraNonce2, + submission.ntime, + submission.nonce, + submission.versionMask ?? '' + ].join(':'); + if (this.miningSubmissionHashes.has(submissionHash)) { + const err = new StratumErrorMessage( + submission.id, + eStratumErrorCode.DuplicateShare, + 'Duplicate share').response(); + const success = await this.write(err); + if (!success) { + return false; + } + return false; + } else { + this.miningSubmissionHashes.add(submissionHash); + } + + const versionMask = parseInt(submission.versionMask, 16); + const nonce = parseInt(submission.nonce, 16); + const timestamp = parseInt(submission.ntime, 16); + + const header = job.buildHeaderBuffer( jobTemplate, - parseInt(submission.versionMask, 16), - parseInt(submission.nonce, 16), + versionMask, + nonce, this.extraNonceAndSessionId, submission.extraNonce2, - parseInt(submission.ntime, 16) + timestamp ); - const header = updatedJobBlock.toBuffer(true); const { submissionDifficulty } = this.calculateDifficulty(header); //console.log(`DIFF: ${submissionDifficulty} of ${this.sessionDifficulty} from ${this.clientAuthorization.worker + '.' + this.extraNonceAndSessionId}`); @@ -588,9 +594,21 @@ export class StratumV1Client { if (submissionDifficulty >= effectiveDiff) { + const success = await this.write(JSON.stringify(submission.response()) + '\n'); + if (!success) { + return false; + } if (submissionDifficulty >= jobTemplate.blockData.networkDifficulty) { console.log('!!! BLOCK FOUND !!!'); + const updatedJobBlock = job.copyAndUpdateBlock( + jobTemplate, + versionMask, + nonce, + this.extraNonceAndSessionId, + submission.extraNonce2, + timestamp + ); const blockHex = updatedJobBlock.toHex(false); const result = await this.bitcoinRpcService.SUBMIT_BLOCK(blockHex); if (result === 'SUCCESS!') { @@ -608,12 +626,13 @@ export class StratumV1Client { console.warn(`[Block submit rejected at height ${jobTemplate.blockData.height}]: ${result}`); } } + await this.ensureClientEntity(); try { await this.statistics.addShares(this.clientEntity, effectiveDiff); const now = new Date(); // only update every minute //if (this.clientEntity.updatedAt == null || now.getTime() - this.clientEntity.updatedAt.getTime() > 1000 * 60) { - await this.clientService.heartbeatBulkAsync(this.clientEntity.id, this.statistics.hashRate, now); + this.clientService.heartbeatBulkAsync(this.clientEntity.id, this.statistics.hashRate, now); this.clientEntity.updatedAt = now; //} @@ -622,11 +641,9 @@ export class StratumV1Client { } if (submissionDifficulty > this.clientEntity.bestDifficulty) { - await this.clientService.updateBestDifficulty(this.clientEntity.id, submissionDifficulty); + await this.clientService.updateBestDifficultyIfHigher(this.clientEntity.id, submissionDifficulty); this.clientEntity.bestDifficulty = submissionDifficulty; - if (submissionDifficulty > (await this.addressSettingsService.getSettings(this.clientAuthorization.address, true)).bestDifficulty) { - await this.addressSettingsService.updateBestDifficulty(this.clientAuthorization.address, submissionDifficulty, this.clientEntity.userAgent); - } + await this.addressSettingsService.updateBestDifficultyIfHigher(this.clientAuthorization.address, submissionDifficulty, this.clientEntity.userAgent); } @@ -646,7 +663,7 @@ export class StratumV1Client { } //await this.checkDifficulty(); - return true; + return false; } @@ -689,20 +706,18 @@ export class StratumV1Client { const hashResult = bitcoinjs.crypto.hash256(header); - let s64 = this.le256todouble(hashResult); - - const truediffone = Big('26959535291011309493156476344723991336010898738574164086137773096960'); - const difficulty = truediffone.div(s64.toString()); - return { submissionDifficulty: difficulty.toNumber(), submissionHash: hashResult.toString('hex') }; + const target = this.le256todouble(hashResult); + const submissionDifficulty = target === 0 ? Number.POSITIVE_INFINITY : TRUE_DIFF_ONE / target; + return { submissionDifficulty, submissionHash: hashResult.toString('hex') }; } - private le256todouble(target: Buffer): bigint { + private le256todouble(target: Buffer): number { - const number = target.reduceRight((acc, byte) => { - // Shift the number 8 bits to the left and OR with the current byte - return (acc << BigInt(8)) | BigInt(byte); - }, BigInt(0)); + let number = 0; + for (let i = target.length - 1; i >= 0; i--) { + number = number * 256 + target[i]; + } return number; }