mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
adjust client difficulty
This commit is contained in:
@@ -1,23 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
|
|
||||||
import { AppController } from './app.controller';
|
|
||||||
import { AppService } from './app.service';
|
|
||||||
|
|
||||||
describe('AppController', () => {
|
|
||||||
let appController: AppController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const app: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [AppController],
|
|
||||||
providers: [AppService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
appController = app.get<AppController>(AppController);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('root', () => {
|
|
||||||
it('should return "Hello World!"', () => {
|
|
||||||
// expect(appController.getHello()).toBe('Hello World!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -12,12 +12,6 @@ export class AppController {
|
|||||||
private readonly clientStatisticsService: ClientStatisticsService
|
private readonly clientStatisticsService: ClientStatisticsService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@Get()
|
|
||||||
async getInfo() {
|
|
||||||
return {
|
|
||||||
clients: await this.clientService.connectedClientCount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('client/:address')
|
@Get('client/:address')
|
||||||
async getClientInfo(@Param('address') address: string) {
|
async getClientInfo(@Param('address') address: string) {
|
||||||
|
|||||||
@@ -158,13 +158,13 @@ export class MiningJob {
|
|||||||
return bitcoinjs.payments.p2wpkh({ address, network: bitcoinjs.networks.testnet }).output;
|
return bitcoinjs.payments.p2wpkh({ address, network: bitcoinjs.networks.testnet }).output;
|
||||||
}
|
}
|
||||||
case AddressType.p2pkh: {
|
case AddressType.p2pkh: {
|
||||||
return bitcoinjs.payments.p2pkh({ address }).output;;
|
return bitcoinjs.payments.p2pkh({ address }).output;
|
||||||
}
|
}
|
||||||
case AddressType.p2sh: {
|
case AddressType.p2sh: {
|
||||||
return bitcoinjs.payments.p2sh({ address }).output;;
|
return bitcoinjs.payments.p2sh({ address }).output;
|
||||||
}
|
}
|
||||||
case AddressType.p2tr: {
|
case AddressType.p2tr: {
|
||||||
return bitcoinjs.payments.p2tr({ address }).output;;
|
return bitcoinjs.payments.p2tr({ address }).output;
|
||||||
}
|
}
|
||||||
case AddressType.p2wsh: {
|
case AddressType.p2wsh: {
|
||||||
return bitcoinjs.payments.p2wsh({ address }).output;
|
return bitcoinjs.payments.p2wsh({ address }).output;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { plainToInstance } from 'class-transformer';
|
|||||||
import { validate, ValidatorOptions } from 'class-validator';
|
import { validate, ValidatorOptions } from 'class-validator';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { Socket } from 'net';
|
import { Socket } from 'net';
|
||||||
import { combineLatest, interval, startWith } from 'rxjs';
|
import { combineLatest, interval, startWith, take } from 'rxjs';
|
||||||
|
|
||||||
import { ClientStatisticsService } from '../ORM/client-statistics/client-statistics.service';
|
import { ClientStatisticsService } from '../ORM/client-statistics/client-statistics.service';
|
||||||
import { ClientEntity } from '../ORM/client/client.entity';
|
import { ClientEntity } from '../ORM/client/client.entity';
|
||||||
@@ -12,7 +12,9 @@ import { BitcoinRpcService } from '../services/bitcoin-rpc.service';
|
|||||||
import { BlockTemplateService } from '../services/block-template.service';
|
import { BlockTemplateService } from '../services/block-template.service';
|
||||||
import { StratumV1JobsService } from '../services/stratum-v1-jobs.service';
|
import { StratumV1JobsService } from '../services/stratum-v1-jobs.service';
|
||||||
import { EasyUnsubscribe } from '../utils/AutoUnsubscribe';
|
import { EasyUnsubscribe } from '../utils/AutoUnsubscribe';
|
||||||
|
import { IBlockTemplate } from './bitcoin-rpc/IBlockTemplate';
|
||||||
import { eRequestMethod } from './enums/eRequestMethod';
|
import { eRequestMethod } from './enums/eRequestMethod';
|
||||||
|
import { eResponseMethod } from './enums/eResponseMethod';
|
||||||
import { MiningJob } from './MiningJob';
|
import { MiningJob } from './MiningJob';
|
||||||
import { AuthorizationMessage } from './stratum-messages/AuthorizationMessage';
|
import { AuthorizationMessage } from './stratum-messages/AuthorizationMessage';
|
||||||
import { ConfigurationMessage } from './stratum-messages/ConfigurationMessage';
|
import { ConfigurationMessage } from './stratum-messages/ConfigurationMessage';
|
||||||
@@ -32,7 +34,8 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
|
|
||||||
private statistics: StratumV1ClientStatistics;
|
private statistics: StratumV1ClientStatistics;
|
||||||
private stratumInitialized = false;
|
private stratumInitialized = false;
|
||||||
private clientDifficulty: number = 512;
|
private usedSuggestedDifficulty = false;
|
||||||
|
private sessionDifficulty: number = 524288;
|
||||||
private entity: ClientEntity;
|
private entity: ClientEntity;
|
||||||
|
|
||||||
public extraNonce: string;
|
public extraNonce: string;
|
||||||
@@ -157,6 +160,9 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case eRequestMethod.SUGGEST_DIFFICULTY: {
|
case eRequestMethod.SUGGEST_DIFFICULTY: {
|
||||||
|
if (this.usedSuggestedDifficulty == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const suggestDifficultyMessage = plainToInstance(
|
const suggestDifficultyMessage = plainToInstance(
|
||||||
SuggestDifficulty,
|
SuggestDifficulty,
|
||||||
@@ -173,8 +179,9 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
|
|
||||||
this.clientSuggestedDifficulty = suggestDifficultyMessage;
|
this.clientSuggestedDifficulty = suggestDifficultyMessage;
|
||||||
this.clientDifficulty = suggestDifficultyMessage.suggestedDifficulty;
|
this.sessionDifficulty = suggestDifficultyMessage.suggestedDifficulty;
|
||||||
socket.write(JSON.stringify(this.clientSuggestedDifficulty.response(this.clientDifficulty)) + '\n');
|
socket.write(JSON.stringify(this.clientSuggestedDifficulty.response(this.sessionDifficulty)) + '\n');
|
||||||
|
this.usedSuggestedDifficulty = true;
|
||||||
} else {
|
} else {
|
||||||
console.error(errors);
|
console.error(errors);
|
||||||
}
|
}
|
||||||
@@ -196,6 +203,8 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
await this.handleMiningSubmission(miningSubmitMessage);
|
await this.handleMiningSubmission(miningSubmitMessage);
|
||||||
socket.write(JSON.stringify(miningSubmitMessage.response()) + '\n');
|
socket.write(JSON.stringify(miningSubmitMessage.response()) + '\n');
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.error(errors);
|
console.error(errors);
|
||||||
}
|
}
|
||||||
@@ -227,22 +236,44 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
}
|
}
|
||||||
lastIntervalCount = interValCount;
|
lastIntervalCount = interValCount;
|
||||||
|
|
||||||
const job = new MiningJob(this.stratumV1JobsService.getNextId(), [{ address: this.clientAuthorization.address, percent: 100 }], blockTemplate, clearJobs);
|
this.sendNewMiningJob(blockTemplate, clearJobs);
|
||||||
|
|
||||||
this.stratumV1JobsService.addJob(job, clearJobs);
|
this.checkDifficulty();
|
||||||
|
|
||||||
this.socket.write(job.response + '\n');
|
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sendNewMiningJob(blockTemplate: IBlockTemplate, clearJobs: boolean) {
|
||||||
|
|
||||||
|
// const payoutInformation = [
|
||||||
|
// { address: 'bc1q99n3pu025yyu0jlywpmwzalyhm36tg5u37w20d', percent: 1.8 },
|
||||||
|
// { address: this.clientAuthorization.address, percent: 98.2 }
|
||||||
|
// ];
|
||||||
|
|
||||||
|
const payoutInformation = [
|
||||||
|
{ address: this.clientAuthorization.address, percent: 100 }
|
||||||
|
];
|
||||||
|
|
||||||
|
const job = new MiningJob(this.stratumV1JobsService.getNextId(), payoutInformation, blockTemplate, clearJobs);
|
||||||
|
|
||||||
|
this.stratumV1JobsService.addJob(job, clearJobs);
|
||||||
|
|
||||||
|
this.socket.write(job.response + '\n');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private async handleMiningSubmission(submission: MiningSubmitMessage) {
|
private async handleMiningSubmission(submission: MiningSubmitMessage) {
|
||||||
|
|
||||||
const job = this.stratumV1JobsService.getJobById(submission.jobId);
|
const job = this.stratumV1JobsService.getJobById(submission.jobId);
|
||||||
// a miner may submit a job that doesn't exist anymore if it was removed by a new block notification
|
// a miner may submit a job that doesn't exist anymore if it was removed by a new block notification
|
||||||
if (job == null) {
|
if (job == null) {
|
||||||
|
console.log('job not found')
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const updatedJobBlock = job.copyAndUpdateBlock(
|
const updatedJobBlock = job.copyAndUpdateBlock(
|
||||||
@@ -251,26 +282,56 @@ export class StratumV1Client extends EasyUnsubscribe {
|
|||||||
this.extraNonce,
|
this.extraNonce,
|
||||||
submission.extraNonce2
|
submission.extraNonce2
|
||||||
);
|
);
|
||||||
const diff = this.calculateDifficulty(updatedJobBlock.toBuffer(true));
|
const submissionDifficulty = this.calculateDifficulty(updatedJobBlock.toBuffer(true));
|
||||||
console.log(`DIFF: ${diff}`);
|
|
||||||
|
|
||||||
if (diff >= this.clientDifficulty) {
|
console.log(`DIFF: ${submissionDifficulty} of ${this.sessionDifficulty}`);
|
||||||
|
|
||||||
if (diff >= (job.networkDifficulty / 2)) {
|
if (submissionDifficulty >= this.sessionDifficulty) {
|
||||||
|
|
||||||
|
if (submissionDifficulty >= (job.networkDifficulty / 2)) {
|
||||||
console.log('!!! BOCK FOUND !!!');
|
console.log('!!! BOCK FOUND !!!');
|
||||||
const blockHex = updatedJobBlock.toHex(false);
|
const blockHex = updatedJobBlock.toHex(false);
|
||||||
this.bitcoinRpcService.SUBMIT_BLOCK(blockHex);
|
this.bitcoinRpcService.SUBMIT_BLOCK(blockHex);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.statistics.addSubmission(this.entity, this.clientDifficulty);
|
await this.statistics.addSubmission(this.entity, this.sessionDifficulty);
|
||||||
if (diff > this.entity.bestDifficulty) {
|
if (submissionDifficulty > this.entity.bestDifficulty) {
|
||||||
await this.clientService.updateBestDifficulty(this.extraNonce, diff);
|
await this.clientService.updateBestDifficulty(this.extraNonce, submissionDifficulty);
|
||||||
this.entity.bestDifficulty = diff;
|
this.entity.bestDifficulty = submissionDifficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.log(`Difficulty too low`);
|
console.log(`Difficulty too low`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkDifficulty();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkDifficulty() {
|
||||||
|
const targetDiff = this.statistics.getSuggestedDifficulty(this.sessionDifficulty);
|
||||||
|
if (targetDiff == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetDiff != this.sessionDifficulty) {
|
||||||
|
console.log(`Adjusting difficulty from ${this.sessionDifficulty} to ${targetDiff}`);
|
||||||
|
this.sessionDifficulty = targetDiff;
|
||||||
|
this.socket.write(JSON.stringify(
|
||||||
|
{
|
||||||
|
id: null,
|
||||||
|
method: eResponseMethod.SET_DIFFICULTY,
|
||||||
|
params: [targetDiff]
|
||||||
|
}
|
||||||
|
) + '\n');
|
||||||
|
|
||||||
|
// we need to clear the jobs so that the difficulty set takes effect. Otherwise the different miner implementations can cause issues
|
||||||
|
this.blockTemplateService.currentBlockTemplate$.pipe(take(1)).subscribe(({ blockTemplate }) => {
|
||||||
|
this.sendNewMiningJob(blockTemplate, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public calculateDifficulty(header: Buffer): number {
|
public calculateDifficulty(header: Buffer): number {
|
||||||
|
|||||||
@@ -1,25 +1,77 @@
|
|||||||
import { ClientStatisticsService } from '../ORM/client-statistics/client-statistics.service';
|
import { ClientStatisticsService } from '../ORM/client-statistics/client-statistics.service';
|
||||||
import { ClientEntity } from '../ORM/client/client.entity';
|
import { ClientEntity } from '../ORM/client/client.entity';
|
||||||
|
|
||||||
|
const CACHE_SIZE = 30;
|
||||||
|
const TARGET_SUBMISSION_PER_SECOND = 30;
|
||||||
export class StratumV1ClientStatistics {
|
export class StratumV1ClientStatistics {
|
||||||
|
|
||||||
|
private submissionCacheStart: Date;
|
||||||
|
private submissionCache = [];
|
||||||
|
|
||||||
constructor(private readonly clientStatisticsService: ClientStatisticsService) {
|
constructor(private readonly clientStatisticsService: ClientStatisticsService) {
|
||||||
|
this.submissionCacheStart = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addSubmission(client: ClientEntity, targetDifficulty: number) {
|
public async addSubmission(client: ClientEntity, targetDifficulty: number) {
|
||||||
|
|
||||||
|
if (this.submissionCache.length > CACHE_SIZE) {
|
||||||
|
this.submissionCache.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.submissionCache.push({
|
||||||
|
time: new Date(),
|
||||||
|
difficulty: targetDifficulty,
|
||||||
|
});
|
||||||
|
|
||||||
await this.clientStatisticsService.save({
|
await this.clientStatisticsService.save({
|
||||||
time: new Date(),
|
time: new Date(),
|
||||||
difficulty: targetDifficulty,
|
difficulty: targetDifficulty,
|
||||||
address: client.address,
|
address: client.address,
|
||||||
clientName: client.clientName,
|
clientName: client.clientName,
|
||||||
sessionId: client.sessionId,
|
sessionId: client.sessionId,
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSuggestedDifficulty(clientDifficulty: number) {
|
||||||
|
|
||||||
|
// miner hasn't submitted shares in one minute
|
||||||
|
if (this.submissionCache.length == 0 && (new Date().getTime() - this.submissionCacheStart.getTime()) / 1000 > 60) {
|
||||||
|
return this.blpo2(clientDifficulty >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.submissionCache.length < CACHE_SIZE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sum = this.submissionCache.reduce((pre, cur) => {
|
||||||
|
pre += cur.difficulty;
|
||||||
|
return pre;
|
||||||
|
}, 0);
|
||||||
|
const diffSeconds = (this.submissionCache[this.submissionCache.length - 1].time.getTime() - this.submissionCache[0].time.getTime()) / 1000;
|
||||||
|
|
||||||
|
const difficultyPerSecond = sum / diffSeconds;
|
||||||
|
|
||||||
|
const targetDifficulty = difficultyPerSecond * TARGET_SUBMISSION_PER_SECOND;
|
||||||
|
|
||||||
|
if (clientDifficulty << 1 < targetDifficulty || clientDifficulty >> 1 > targetDifficulty) {
|
||||||
|
return this.blpo2(targetDifficulty)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private blpo2(x) {
|
||||||
|
x = x | (x >> 1);
|
||||||
|
x = x | (x >> 2);
|
||||||
|
x = x | (x >> 4);
|
||||||
|
x = x | (x >> 8);
|
||||||
|
x = x | (x >> 16);
|
||||||
|
x = x | (x >> 32);
|
||||||
|
return x - (x >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Expose, Transform } from 'class-transformer';
|
import { Expose, Transform } from 'class-transformer';
|
||||||
import { ArrayMaxSize, ArrayMinSize, IsArray, IsString } from 'class-validator';
|
import { ArrayMaxSize, ArrayMinSize, IsArray, IsString, MaxLength } from 'class-validator';
|
||||||
|
|
||||||
import { eRequestMethod } from '../enums/eRequestMethod';
|
import { eRequestMethod } from '../enums/eRequestMethod';
|
||||||
|
import { IsBitcoinAddress } from '../validators/bitcoin-address.validator';
|
||||||
import { StratumBaseMessage } from './StratumBaseMessage';
|
import { StratumBaseMessage } from './StratumBaseMessage';
|
||||||
|
|
||||||
export class AuthorizationMessage extends StratumBaseMessage {
|
export class AuthorizationMessage extends StratumBaseMessage {
|
||||||
@@ -16,10 +17,12 @@ export class AuthorizationMessage extends StratumBaseMessage {
|
|||||||
@Transform(({ value, key, obj, type }) => {
|
@Transform(({ value, key, obj, type }) => {
|
||||||
return obj.params[0].split('.')[0];
|
return obj.params[0].split('.')[0];
|
||||||
})
|
})
|
||||||
|
@IsBitcoinAddress()
|
||||||
public address: string;
|
public address: string;
|
||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@MaxLength(64)
|
||||||
@Transform(({ value, key, obj, type }) => {
|
@Transform(({ value, key, obj, type }) => {
|
||||||
return obj.params[0].split('.')[1];
|
return obj.params[0].split('.')[1];
|
||||||
})
|
})
|
||||||
@@ -31,6 +34,7 @@ export class AuthorizationMessage extends StratumBaseMessage {
|
|||||||
@Transform(({ value, key, obj, type }) => {
|
@Transform(({ value, key, obj, type }) => {
|
||||||
return obj.params[1];
|
return obj.params[1];
|
||||||
})
|
})
|
||||||
|
@MaxLength(64)
|
||||||
public password: string;
|
public password: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { validate } from 'bitcoin-address-validation';
|
||||||
|
import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
|
||||||
|
|
||||||
|
@ValidatorConstraint({ name: 'bitcoinAddress', async: false })
|
||||||
|
export class BitcoinAddress implements ValidatorConstraintInterface {
|
||||||
|
validate(value: string): boolean {
|
||||||
|
// Implement your custom validation logic here
|
||||||
|
return validate(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultMessage(): string {
|
||||||
|
return 'Must be a bitcoin address';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IsBitcoinAddress(validationOptions?: ValidationOptions) {
|
||||||
|
return function (object: Object, propertyName: string) {
|
||||||
|
registerDecorator({
|
||||||
|
name: 'isBitcoinAddress',
|
||||||
|
target: object.constructor,
|
||||||
|
propertyName: propertyName,
|
||||||
|
constraints: [],
|
||||||
|
options: validationOptions,
|
||||||
|
validator: BitcoinAddress,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user