mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
extranonce2 8 bytes
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity } from 'typeorm';
|
||||||
|
|
||||||
|
import { PrimaryGeneratedBigIntColumn } from '../utils/PrimaryGeneratedBigIntColumn';
|
||||||
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class BlocksEntity extends TrackedEntity {
|
export class BlocksEntity extends TrackedEntity {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn({type: 'bigint'})
|
@PrimaryGeneratedBigIntColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, Index, ManyToOne } from 'typeorm';
|
||||||
|
|
||||||
import { ClientEntity } from '../client/client.entity';
|
import { ClientEntity } from '../client/client.entity';
|
||||||
|
import { PrimaryGeneratedBigIntColumn } from '../utils/PrimaryGeneratedBigIntColumn';
|
||||||
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@@ -8,7 +9,7 @@ import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
|||||||
@Index(["clientId", "time"])
|
@Index(["clientId", "time"])
|
||||||
export class ClientStatisticsEntity extends TrackedEntity {
|
export class ClientStatisticsEntity extends TrackedEntity {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn({type: 'bigint'})
|
@PrimaryGeneratedBigIntColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@Column({ length: 62, type: 'varchar' })
|
@Column({ length: 62, type: 'varchar' })
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class ClientEntity extends TrackedEntity {
|
|||||||
userAgent: string;
|
userAgent: string;
|
||||||
|
|
||||||
|
|
||||||
@Column({ type: 'timestamp' })
|
@Column()
|
||||||
startTime: Date;
|
startTime: Date;
|
||||||
|
|
||||||
@Column({ type: 'decimal', default: 0 })
|
@Column({ type: 'decimal', default: 0 })
|
||||||
@@ -46,4 +46,3 @@ export class ClientEntity extends TrackedEntity {
|
|||||||
statistics: ClientStatisticsEntity[]
|
statistics: ClientStatisticsEntity[]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity } from 'typeorm';
|
||||||
|
|
||||||
|
import { PrimaryGeneratedBigIntColumn } from '../utils/PrimaryGeneratedBigIntColumn';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class HomeGraphEntity {
|
export class HomeGraphEntity {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn({type: 'bigint'})
|
@PrimaryGeneratedBigIntColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@Column({ type: 'bigint' })
|
@Column({ type: 'bigint' })
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, Index } from 'typeorm';
|
||||||
|
|
||||||
|
import { PrimaryGeneratedBigIntColumn } from '../utils/PrimaryGeneratedBigIntColumn';
|
||||||
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
import { TrackedEntity } from '../utils/TrackedEntity.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class TelegramSubscriptionsEntity extends TrackedEntity {
|
export class TelegramSubscriptionsEntity extends TrackedEntity {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn({type: 'bigint'})
|
@PrimaryGeneratedBigIntColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
export const PrimaryGeneratedBigIntColumn = () => PrimaryGeneratedColumn({
|
||||||
|
type: process.env.NODE_ENV === 'test' ? 'integer' : 'bigint'
|
||||||
|
});
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { CreateDateColumn, DeleteDateColumn, UpdateDateColumn } from 'typeorm';
|
import { CreateDateColumn, DeleteDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
export abstract class TrackedEntity {
|
export abstract class TrackedEntity {
|
||||||
@DeleteDateColumn({ nullable: true, type: 'timestamp' })
|
@DeleteDateColumn({ nullable: true })
|
||||||
public deletedAt?: Date;
|
public deletedAt?: Date;
|
||||||
|
|
||||||
@CreateDateColumn({ type: 'timestamp' })
|
@CreateDateColumn()
|
||||||
public createdAt?: Date
|
public createdAt?: Date
|
||||||
|
|
||||||
@UpdateDateColumn({ type: 'timestamp' })
|
@UpdateDateColumn()
|
||||||
public updatedAt?: Date
|
public updatedAt?: Date
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ describe('ClientController', () => {
|
|||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forRoot({
|
TypeOrmModule.forRoot({
|
||||||
type: 'sqlite',
|
type: 'sqlite',
|
||||||
database: './DB/public-pool.test.sqlite',
|
database: ':memory:',
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
autoLoadEntities: true,
|
autoLoadEntities: true,
|
||||||
cache: true,
|
cache: true,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import * as bitcoinjs from 'bitcoinjs-lib';
|
|||||||
import { IJobTemplate } from '../services/stratum-v1-jobs.service';
|
import { IJobTemplate } from '../services/stratum-v1-jobs.service';
|
||||||
import { eResponseMethod } from './enums/eResponseMethod';
|
import { eResponseMethod } from './enums/eResponseMethod';
|
||||||
import { IMiningNotify } from './stratum-messages/IMiningNotify';
|
import { IMiningNotify } from './stratum-messages/IMiningNotify';
|
||||||
|
import { TOTAL_EXTRANONCE_SIZE_BYTES } from './stratum.constants';
|
||||||
|
|
||||||
|
|
||||||
interface AddressObject {
|
interface AddressObject {
|
||||||
@@ -50,7 +51,7 @@ export class MiningJob {
|
|||||||
const blockHeightLengthByte = Buffer.from([blockHeightEncoded.length]);
|
const blockHeightLengthByte = Buffer.from([blockHeightEncoded.length]);
|
||||||
|
|
||||||
// generate padding and take length of encode blockHeight into account
|
// generate padding and take length of encode blockHeight into account
|
||||||
const padding = Buffer.alloc(8 + (3 - blockHeightEncoded.length), 0)
|
const padding = Buffer.alloc(TOTAL_EXTRANONCE_SIZE_BYTES + (3 - blockHeightEncoded.length), 0)
|
||||||
|
|
||||||
// build the script
|
// build the script
|
||||||
this.coinbaseTransaction.ins[0].script = Buffer.concat([blockHeightLengthByte, blockHeightEncoded, extra, padding])
|
this.coinbaseTransaction.ins[0].script = Buffer.concat([blockHeightLengthByte, blockHeightEncoded, extra, padding])
|
||||||
@@ -65,7 +66,7 @@ export class MiningJob {
|
|||||||
|
|
||||||
const partOneIndex = serializedCoinbaseTx.indexOf(inputScript) + inputScript.length;
|
const partOneIndex = serializedCoinbaseTx.indexOf(inputScript) + inputScript.length;
|
||||||
|
|
||||||
this.coinbasePart1 = serializedCoinbaseTx.slice(0, partOneIndex - 16);
|
this.coinbasePart1 = serializedCoinbaseTx.slice(0, partOneIndex - (TOTAL_EXTRANONCE_SIZE_BYTES * 2));
|
||||||
this.coinbasePart2 = serializedCoinbaseTx.slice(partOneIndex);
|
this.coinbasePart2 = serializedCoinbaseTx.slice(partOneIndex);
|
||||||
|
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ export class MiningJob {
|
|||||||
// set the nonces
|
// set the nonces
|
||||||
const nonceScript = testBlock.transactions[0].ins[0].script.toString('hex');
|
const nonceScript = testBlock.transactions[0].ins[0].script.toString('hex');
|
||||||
|
|
||||||
testBlock.transactions[0].ins[0].script = Buffer.from(`${nonceScript.substring(0, nonceScript.length - 16)}${extraNonce}${extraNonce2}`, 'hex');
|
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
|
//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), jobTemplate.merkle_branch);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { ClientService } from '../ORM/client/client.service';
|
|||||||
import { BitcoinRpcService as MockBitcoinRpcService } from '../services/bitcoin-rpc.service';
|
import { BitcoinRpcService as MockBitcoinRpcService } from '../services/bitcoin-rpc.service';
|
||||||
import { NotificationService } from '../services/notification.service';
|
import { NotificationService } from '../services/notification.service';
|
||||||
import { StratumV1JobsService } from '../services/stratum-v1-jobs.service';
|
import { StratumV1JobsService } from '../services/stratum-v1-jobs.service';
|
||||||
import { IMiningInfo } from './bitcoin-rpc/IMiningInfo';
|
import { IBlockTemplate } from './bitcoin-rpc/IBlockTemplate';
|
||||||
import { StratumV1Client } from './StratumV1Client';
|
import { StratumV1Client } from './StratumV1Client';
|
||||||
|
|
||||||
|
|
||||||
@@ -50,8 +50,9 @@ describe('StratumV1Client', () => {
|
|||||||
let client: StratumV1Client;
|
let client: StratumV1Client;
|
||||||
|
|
||||||
let socketEmitter: (...args: any[]) => void;
|
let socketEmitter: (...args: any[]) => void;
|
||||||
|
const emitMessage = (message: string) => socketEmitter(Buffer.from(`${message}\n`));
|
||||||
|
|
||||||
let newBlockEmitter: BehaviorSubject<IMiningInfo> = new BehaviorSubject(null);
|
let newBlockEmitter: BehaviorSubject<IBlockTemplate> = new BehaviorSubject(MockRecording1.BLOCK_TEMPLATE);
|
||||||
|
|
||||||
let moduleRef: TestingModule;
|
let moduleRef: TestingModule;
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ describe('StratumV1Client', () => {
|
|||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forRoot({
|
TypeOrmModule.forRoot({
|
||||||
type: 'sqlite',
|
type: 'sqlite',
|
||||||
database: './DB/public-pool.test.sqlite',
|
database: ':memory:',
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
autoLoadEntities: true,
|
autoLoadEntities: true,
|
||||||
cache: true,
|
cache: true,
|
||||||
@@ -94,23 +95,26 @@ describe('StratumV1Client', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
|
||||||
console.log('NEW TEST')
|
jest.useFakeTimers({ advanceTimers: true })
|
||||||
|
jest.setSystemTime(new Date(parseInt(MockRecording1.TIME, 16) * 1000));
|
||||||
|
|
||||||
clientService = moduleRef.get<ClientService>(ClientService);
|
clientService = moduleRef.get<ClientService>(ClientService);
|
||||||
|
|
||||||
const dataSource = moduleRef.get<DataSource>(DataSource);
|
const dataSource = moduleRef.get<DataSource>(DataSource);
|
||||||
|
|
||||||
dataSource.getRepository(ClientEntity).delete({});
|
await dataSource.getRepository(ClientStatisticsEntity).delete({});
|
||||||
dataSource.getRepository(ClientStatisticsEntity).delete({});
|
await dataSource.getRepository(ClientEntity).delete({});
|
||||||
|
|
||||||
|
|
||||||
clientStatisticsService = moduleRef.get<ClientStatisticsService>(ClientStatisticsService);
|
clientStatisticsService = moduleRef.get<ClientStatisticsService>(ClientStatisticsService);
|
||||||
|
|
||||||
configService = moduleRef.get<ConfigService>(ConfigService);
|
configService = moduleRef.get<ConfigService>(ConfigService);
|
||||||
|
|
||||||
bitcoinRpcService = new MockBitcoinRpcService(null);
|
bitcoinRpcService = {
|
||||||
jest.spyOn(bitcoinRpcService, 'getBlockTemplate').mockReturnValue(Promise.resolve(MockRecording1.BLOCK_TEMPLATE));
|
newBlockTemplate$: newBlockEmitter.asObservable(),
|
||||||
bitcoinRpcService.newBlock$ = newBlockEmitter.asObservable();
|
miningInfo: { blocks: MockRecording1.BLOCK_TEMPLATE.height },
|
||||||
|
SUBMIT_BLOCK: jest.fn().mockResolvedValue(null)
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
|
||||||
stratumV1JobsService = new StratumV1JobsService(bitcoinRpcService);
|
stratumV1JobsService = new StratumV1JobsService(bitcoinRpcService);
|
||||||
@@ -143,8 +147,8 @@ describe('StratumV1Client', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
client.extraNonceAndSessionId = MockRecording1.EXTRA_NONCE;
|
client.extraNonceAndSessionId = MockRecording1.EXTRA_NONCE;
|
||||||
|
jest.spyOn(client as any, 'getRandomHexString').mockReturnValue(MockRecording1.EXTRA_NONCE);
|
||||||
|
|
||||||
jest.useFakeTimers({ advanceTimers: true })
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -158,7 +162,7 @@ describe('StratumV1Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should close socket on invalid JSON', () => {
|
it('should close socket on invalid JSON', () => {
|
||||||
socketEmitter(Buffer.from('INVALID'));
|
emitMessage('INVALID');
|
||||||
jest.spyOn(socket, 'destroy');
|
jest.spyOn(socket, 'destroy');
|
||||||
expect(socket.on).toHaveBeenCalled();
|
expect(socket.on).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -167,11 +171,11 @@ describe('StratumV1Client', () => {
|
|||||||
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
||||||
|
|
||||||
expect(socket.on).toHaveBeenCalled();
|
expect(socket.on).toHaveBeenCalled();
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUBSCRIBE));
|
emitMessage(MockRecording1.MINING_SUBSCRIBE);
|
||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 1));
|
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));
|
expect(socket.write).toHaveBeenCalledWith(`{"id":1,"error":null,"result":[[["mining.notify","${client.extraNonceAndSessionId}"]],"${client.extraNonceAndSessionId}",8]}\n`, expect.any(Function));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -181,7 +185,7 @@ describe('StratumV1Client', () => {
|
|||||||
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
||||||
|
|
||||||
expect(socket.on).toHaveBeenCalled();
|
expect(socket.on).toHaveBeenCalled();
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_CONFIGURE));
|
emitMessage(MockRecording1.MINING_CONFIGURE);
|
||||||
await new Promise((r) => setTimeout(r, 1));
|
await new Promise((r) => setTimeout(r, 1));
|
||||||
expect(socket.write).toHaveBeenCalledWith(`{"id":2,"error":null,"result":{"version-rolling":true,"version-rolling.mask":"1fffe000"}}\n`, expect.any(Function));
|
expect(socket.write).toHaveBeenCalledWith(`{"id":2,"error":null,"result":{"version-rolling":true,"version-rolling.mask":"1fffe000"}}\n`, expect.any(Function));
|
||||||
});
|
});
|
||||||
@@ -191,7 +195,7 @@ describe('StratumV1Client', () => {
|
|||||||
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
||||||
|
|
||||||
expect(socket.on).toHaveBeenCalled();
|
expect(socket.on).toHaveBeenCalled();
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_AUTHORIZE));
|
emitMessage(MockRecording1.MINING_AUTHORIZE);
|
||||||
await new Promise((r) => setTimeout(r, 1));
|
await new Promise((r) => setTimeout(r, 1));
|
||||||
expect(socket.write).toHaveBeenCalledWith('{"id":3,"error":null,"result":true}\n', expect.any(Function));
|
expect(socket.write).toHaveBeenCalledWith('{"id":3,"error":null,"result":true}\n', expect.any(Function));
|
||||||
});
|
});
|
||||||
@@ -200,7 +204,7 @@ describe('StratumV1Client', () => {
|
|||||||
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
||||||
|
|
||||||
expect(socket.on).toHaveBeenCalled();
|
expect(socket.on).toHaveBeenCalled();
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUGGEST_DIFFICULTY));
|
emitMessage(MockRecording1.MINING_SUGGEST_DIFFICULTY);
|
||||||
await new Promise((r) => setTimeout(r, 1));
|
await new Promise((r) => setTimeout(r, 1));
|
||||||
expect(socket.write).toHaveBeenCalledWith(`{"id":null,"method":"mining.set_difficulty","params":[512]}\n`, expect.any(Function));
|
expect(socket.write).toHaveBeenCalledWith(`{"id":null,"method":"mining.set_difficulty","params":[512]}\n`, expect.any(Function));
|
||||||
});
|
});
|
||||||
@@ -208,20 +212,21 @@ describe('StratumV1Client', () => {
|
|||||||
it('should set difficulty', async () => {
|
it('should set difficulty', async () => {
|
||||||
jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true));
|
jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true));
|
||||||
|
|
||||||
console.log('should set difficulty')
|
emitMessage(MockRecording1.MINING_SUBSCRIBE);
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUBSCRIBE));
|
emitMessage(MockRecording1.MINING_AUTHORIZE);
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_AUTHORIZE));
|
|
||||||
await new Promise((r) => setTimeout(r, 100));
|
await new Promise((r) => setTimeout(r, 100));
|
||||||
|
|
||||||
expect((client as any).write).toHaveBeenCalledWith(`{"id":null,"method":"mining.set_difficulty","params":[16384]}\n`);
|
expect((client as any).write).toHaveBeenCalledWith(`{"id":null,"method":"mining.set_difficulty","params":[100000]}\n`);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should save client', async () => {
|
it('should save client', async () => {
|
||||||
jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true));
|
jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true));
|
||||||
|
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUBSCRIBE));
|
emitMessage(MockRecording1.MINING_SUBSCRIBE);
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_AUTHORIZE));
|
emitMessage(MockRecording1.MINING_AUTHORIZE);
|
||||||
|
await new Promise((r) => setTimeout(r, 100));
|
||||||
|
emitMessage(MockRecording1.MINING_SUBMIT);
|
||||||
await new Promise((r) => setTimeout(r, 100));
|
await new Promise((r) => setTimeout(r, 100));
|
||||||
|
|
||||||
const clientCount = await clientService.connectedClientCount();
|
const clientCount = await clientService.connectedClientCount();
|
||||||
@@ -244,9 +249,9 @@ describe('StratumV1Client', () => {
|
|||||||
jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true));
|
jest.spyOn(client as any, 'write').mockImplementation((data) => Promise.resolve(true));
|
||||||
|
|
||||||
|
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUBSCRIBE));
|
emitMessage(MockRecording1.MINING_SUBSCRIBE);
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUGGEST_DIFFICULTY));
|
emitMessage(`{"id": 4, "method": "mining.suggest_difficulty", "params": [0]}`);
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_AUTHORIZE));
|
emitMessage(MockRecording1.MINING_AUTHORIZE);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -255,10 +260,10 @@ describe('StratumV1Client', () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
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`);
|
expect((client as any).write).lastCalledWith(`{"id":null,"method":"mining.notify","params":["1","171592f223740e92d223f6e68bff25279af7ac4f2246451e0000000200000000","02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1b03c943255075626c69632d506f6f6c","ffffffff02b59f250000000000160014e6f22ca44dc800e9d049621a3b9a42c509f1c4bc0000000000000000266a24aa21a9edbd3d1d916aa0b57326a2d88ebe1b68a1d7c48585f26d8335fe6a94b62755f64c00000000",["175335649d5e8746982969ec88f52e85ac9917106fba5468e699c8879ab974a1","d5644ab3e708c54cd68dc5aedc92b8d3037449687f92ec41ed6e37673d969d4a","5c9ec187517edc0698556cca5ce27e54c96acb014770599ed9df4d4937fbf2b0"],"20000000","192495f8","${MockRecording1.TIME}",true]}\n`);
|
||||||
|
|
||||||
|
|
||||||
socketEmitter(Buffer.from(MockRecording1.MINING_SUBMIT));
|
emitMessage(MockRecording1.MINING_SUBMIT);
|
||||||
|
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
await new Promise((r) => setTimeout(r, 1000));
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { ConfigurationMessage } from './stratum-messages/ConfigurationMessage';
|
|||||||
import { MiningSubmitMessage } from './stratum-messages/MiningSubmitMessage';
|
import { MiningSubmitMessage } from './stratum-messages/MiningSubmitMessage';
|
||||||
import { StratumErrorMessage } from './stratum-messages/StratumErrorMessage';
|
import { StratumErrorMessage } from './stratum-messages/StratumErrorMessage';
|
||||||
import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage';
|
import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage';
|
||||||
|
import { EXTRANONCE1_SIZE_BYTES } from './stratum.constants';
|
||||||
import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
||||||
import { StratumV1ClientStatistics } from './StratumV1ClientStatistics';
|
import { StratumV1ClientStatistics } from './StratumV1ClientStatistics';
|
||||||
|
|
||||||
@@ -103,10 +104,8 @@ export class StratumV1Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getRandomHexString() {
|
private getRandomHexString() {
|
||||||
const randomBytes = crypto.randomBytes(4); // 4 bytes = 32 bits
|
const randomBytes = crypto.randomBytes(EXTRANONCE1_SIZE_BYTES);
|
||||||
const randomNumber = randomBytes.readUInt32BE(0); // Convert bytes to a 32-bit unsigned integer
|
return randomBytes.toString('hex');
|
||||||
const hexString = randomNumber.toString(16).padStart(8, '0'); // Convert to hex and pad with zeros
|
|
||||||
return hexString;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('MiningSubmitMessage', () => {
|
|||||||
|
|
||||||
describe('test message parsing', () => {
|
describe('test message parsing', () => {
|
||||||
|
|
||||||
const MINING_SUBMIT_MESSAGE = ' {"id": 5, "method": "mining.submit", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "1", "99020000", "64b1f10f", "2402812d", "00006000"]}'
|
const MINING_SUBMIT_MESSAGE = ' {"id": 5, "method": "mining.submit", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "1", "9902000000000000", "64b1f10f", "2402812d", "00006000"]}'
|
||||||
|
|
||||||
const message = plainToInstance(
|
const message = plainToInstance(
|
||||||
MiningSubmitMessage,
|
MiningSubmitMessage,
|
||||||
@@ -24,7 +24,7 @@ describe('MiningSubmitMessage', () => {
|
|||||||
expect(message.id).toEqual(5);
|
expect(message.id).toEqual(5);
|
||||||
expect(message.userId).toEqual('tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3');
|
expect(message.userId).toEqual('tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3');
|
||||||
expect(message.jobId).toEqual('1');
|
expect(message.jobId).toEqual('1');
|
||||||
expect(message.extraNonce2).toEqual('99020000');
|
expect(message.extraNonce2).toEqual('9902000000000000');
|
||||||
expect(message.ntime).toEqual('64b1f10f');
|
expect(message.ntime).toEqual('64b1f10f');
|
||||||
expect(message.nonce).toEqual('2402812d');
|
expect(message.nonce).toEqual('2402812d');
|
||||||
expect(message.versionMask).toEqual('00006000');
|
expect(message.versionMask).toEqual('00006000');
|
||||||
|
|||||||
@@ -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, Length } from 'class-validator';
|
||||||
|
|
||||||
import { eRequestMethod } from '../enums/eRequestMethod';
|
import { eRequestMethod } from '../enums/eRequestMethod';
|
||||||
|
import { EXTRANONCE2_SIZE_BYTES } from '../stratum.constants';
|
||||||
import { StratumBaseMessage } from './StratumBaseMessage';
|
import { StratumBaseMessage } from './StratumBaseMessage';
|
||||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ export class MiningSubmitMessage extends StratumBaseMessage {
|
|||||||
public jobId: string;
|
public jobId: string;
|
||||||
@Expose()
|
@Expose()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@Length(EXTRANONCE2_SIZE_BYTES * 2, EXTRANONCE2_SIZE_BYTES * 2)
|
||||||
@Transform(({ value, key, obj, type }) => {
|
@Transform(({ value, key, obj, type }) => {
|
||||||
return obj.params[2];
|
return obj.params[2];
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Expose, Transform } from 'class-transformer';
|
|||||||
import { IsArray, IsString, MaxLength } from 'class-validator';
|
import { IsArray, IsString, MaxLength } from 'class-validator';
|
||||||
|
|
||||||
import { eRequestMethod } from '../enums/eRequestMethod';
|
import { eRequestMethod } from '../enums/eRequestMethod';
|
||||||
|
import { EXTRANONCE2_SIZE_BYTES } from '../stratum.constants';
|
||||||
import { StratumBaseMessage } from './StratumBaseMessage';
|
import { StratumBaseMessage } from './StratumBaseMessage';
|
||||||
|
|
||||||
export class SubscriptionMessage extends StratumBaseMessage {
|
export class SubscriptionMessage extends StratumBaseMessage {
|
||||||
@@ -32,7 +33,7 @@ export class SubscriptionMessage extends StratumBaseMessage {
|
|||||||
['mining.notify', clientId]
|
['mining.notify', clientId]
|
||||||
],
|
],
|
||||||
clientId, //Extranonce1 - Hex-encoded, per-connection unique string which will be used for coinbase serialization later. Keep it safe!
|
clientId, //Extranonce1 - Hex-encoded, per-connection unique string which will be used for coinbase serialization later. Keep it safe!
|
||||||
4 //Extranonce2_size - Represents expected length of extranonce2 which will be generated by the miner.
|
EXTRANONCE2_SIZE_BYTES //Extranonce2_size - Represents expected length of extranonce2 which will be generated by the miner.
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const EXTRANONCE1_SIZE_BYTES = 4;
|
||||||
|
export const EXTRANONCE2_SIZE_BYTES = 8;
|
||||||
|
export const TOTAL_EXTRANONCE_SIZE_BYTES = EXTRANONCE1_SIZE_BYTES + EXTRANONCE2_SIZE_BYTES;
|
||||||
@@ -2,7 +2,7 @@ import { Injectable, OnModuleInit } from '@nestjs/common';
|
|||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { RPCClient } from 'rpc-bitcoin';
|
import { RPCClient } from 'rpc-bitcoin';
|
||||||
import { asyncScheduler, BehaviorSubject, delay, filter, from, interval, scheduled, shareReplay, startWith, Subject, switchMap } from 'rxjs';
|
import { asyncScheduler, BehaviorSubject, delay, filter, from, interval, scheduled, shareReplay, startWith, Subject, switchMap } from 'rxjs';
|
||||||
import { RpcBlockService } from 'src/ORM/rpc-block/rpc-block.service';
|
import { RpcBlockService } from '../ORM/rpc-block/rpc-block.service';
|
||||||
import * as zmq from 'zeromq';
|
import * as zmq from 'zeromq';
|
||||||
|
|
||||||
import { IBlockTemplate } from '../models/bitcoin-rpc/IBlockTemplate';
|
import { IBlockTemplate } from '../models/bitcoin-rpc/IBlockTemplate';
|
||||||
@@ -159,4 +159,3 @@ export class BitcoinRpcService implements OnModuleInit {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ export class MockRecording1 {
|
|||||||
public static MINING_AUTHORIZE = `{"id": 3, "method": "mining.authorize", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "x"]}`;
|
public static MINING_AUTHORIZE = `{"id": 3, "method": "mining.authorize", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "x"]}`;
|
||||||
public static MINING_SUGGEST_DIFFICULTY = `{"id": 4, "method": "mining.suggest_difficulty", "params": [512]}`;
|
public static MINING_SUGGEST_DIFFICULTY = `{"id": 4, "method": "mining.suggest_difficulty", "params": [512]}`;
|
||||||
public static BLOCK_TEMPLATE: IBlockTemplate = { "capabilities": ["proposal"], "version": 536870912, "rules": ["csv", "!segwit", "taproot"], "vbavailable": {}, "vbrequired": 0, "previousblockhash": "00000000000000022246451e9af7ac4f8bff2527d223f6e623740e92171592f2", "transactions": [{ "data": "02000000000101c3efa41c94ee24dd6c6aaccab0f27e9d2baf92e5112bff15cc463b600e4e3a3c0100000000fdffffff024ea319000000000017a914a5b8d32a6388f02204a1445bd623150ee3b851d18765d67b000000000017a914a316e3a4eaeee9cbee9c254c3292830281a8c9c38702473044022065fbcd2918e8b52c3af8c4ae21b3646d7a12f66ddcaa73a68273f5806527c0da0220291f555dd120bb9eadb144ccf4203db659b78d79bffcd37a29016da068f3abb9012102baab4d464b57771f5108f40ffc31b7242c5b430a61d4bfead024d715c441f3f3c7432500", "txid": "a174b99a87c899e66854ba6f101799ac852ef588ec69299846875e9d64355317", "hash": "748f4db5e45dab6a9903f50e7e760470ce80ac219c637d61418667a0ccb6c536", "depends": [], "fee": 14300, "sigops": 1, "weight": 569 }, { "data": "01000000018985719caa78472c8fad7efa88fc109c6faf6df3dc3a690656479e53c6b10fe9030000006b483045022100e7abb2f2f7e6cfad680efe10a7fc218d685a9cce929b1a4e62fcb726228ac9b50220538f3d2eb36d277bcadf00f3ac2abbaf80395c389e1f8048dc95fdf6204cf1840121037435c194e9b01b3d7f7a2802d6684a3af68d05bbf4ec8f17021980d777691f1dfdffffff040000000000000000536a4c5054325b0b15aaff5c59aab47ce39829055c3007922e2934d3b7c9f51238f8d4a98718da4f9c99a6692adacd0e324b7775eebe151c4f3ad37fcc5f9303eed491a02638b8002543c70002002543a7000a4c10270000000000001976a914000000000000000000000000000000000000000088ac10270000000000001976a914000000000000000000000000000000000000000088aca99f6412000000001976a914ba27f99e007c7f605a8305e318c1abde3cd220ac88ac00000000", "txid": "c34ac3f822a0b49f298be1a25fc9e9dd375c23985ed2656f7cf7d87b67fee1c0", "hash": "c34ac3f822a0b49f298be1a25fc9e9dd375c23985ed2656f7cf7d87b67fee1c0", "depends": [], "fee": 9153, "sigops": 12, "weight": 1408 }, { "data": "020000000001013f8e5ebf5620f2c62c9c4e4f77bb4eb903699620da372ad44f666a552e5b9fc801000000171600148442ee1fe3742153a6b40623d363a22ceed677c3ffffffff02bc0200000000000017a91470b93cf7ead31ef24b71159388c2e8e147f755b987aa0415000000000017a9145506e665bcf63e1225100d7c8613a640699fd5d087024730440220215cd74141ebcde3aee383f232dfd2c6b011645012f35714781d28d4a6959829022017ea40a15f4116bbf0a6f86dc0af08758d9a74673498a61164ed13e9d1c712e501210294184e8093958861740c4ce32e724a24b7df7eeb04702cae274652e86b01f83a00000000", "txid": "5aaf2fa707da25c24374b87d89c420c9e4c51022a4bbed4cc91614831f359366", "hash": "2c19268e65cc223c5fac9666883ecc1d10e680c54a4fdbbb3e4775f886b0feec", "depends": [], "fee": 491, "sigops": 1, "weight": 661 }, { "data": "020000000001012c53f5b0a12cadb57602466e703e60272183b75f3366073f4841dac8e616f2740000000000feffffff02e80300000000000016001496e381bd3fe4bae631adc5a5a420fb9124e5480af0a8100000000000160014cd06fc96fea44c6c5e074ba16dd20b222a0a9b32024730440220440ba2252ea6f70f1b8106897984330a997ef504f7f0dbb89679e44309026f2b02207074ad7dbd9a2bd37a41352527b7f3e68612956fe2085a0eb0b41f97f6a3599d012103b3ecf0f44126019a94a818245e35ecff1582c2900a38487a306fcd91bdb28003c8432500", "txid": "69770cf296b64329e784a5666fb0eab034fbb1780bf067f4e1606874c214f01d", "hash": "fadcaa8de64274e63c093592ed6d8c6b7584bb92c0e8d9c49f02a4df11f6f69d", "depends": [], "fee": 141, "sigops": 1, "weight": 561 }, { "data": "01000000019e03cd78895f6876932501b66eab1e44611d48b4c5029d250b7d2d6073b44b68000000006b48304502210088dfbeb842d54893987e5afb87b70fe70e7b91ebad740782515c44464b19a74202205a6bc47ad6c925f85aa29b2be1a174efcc30a4ad8ed6cfe6236b66d7a4f8d96f01210259530ee281106e237ac3aa801d1f7699ada05501186401da78b7d88849bbda8bffffffff0204ce3900000000001976a91419803f0d83a1b58348a4617c743638bbf9ec235588ac44ce5c01000000001976a9149e38aebe02be7cecacdb521f514cffe445c5533788ac00000000", "txid": "2137ca428d6233e680cea25c428b984391314d872a1a0f5f7b8dc8cfc4474c44", "hash": "2137ca428d6233e680cea25c428b984391314d872a1a0f5f7b8dc8cfc4474c44", "depends": [], "fee": 226, "sigops": 8, "weight": 904 }], "coinbaseaux": {}, "coinbasevalue": 2465717, "longpollid": "00000000000000022246451e9af7ac4f8bff2527d223f6e623740e92171592f29370", "target": "000000000000002495f800000000000000000000000000000000000000000000", "mintime": 1689512405, "mutable": ["time", "transactions", "prevblock"], "noncerange": "00000000ffffffff", "sigoplimit": 80000, "sizelimit": 4000000, "weightlimit": 4000000, "curtime": 1689514989, "bits": "192495f8", "height": 2442185, "default_witness_commitment": "6a24aa21a9edbd3d1d916aa0b57326a2d88ebe1b68a1d7c48585f26d8335fe6a94b62755f64c" };
|
public static BLOCK_TEMPLATE: IBlockTemplate = { "capabilities": ["proposal"], "version": 536870912, "rules": ["csv", "!segwit", "taproot"], "vbavailable": {}, "vbrequired": 0, "previousblockhash": "00000000000000022246451e9af7ac4f8bff2527d223f6e623740e92171592f2", "transactions": [{ "data": "02000000000101c3efa41c94ee24dd6c6aaccab0f27e9d2baf92e5112bff15cc463b600e4e3a3c0100000000fdffffff024ea319000000000017a914a5b8d32a6388f02204a1445bd623150ee3b851d18765d67b000000000017a914a316e3a4eaeee9cbee9c254c3292830281a8c9c38702473044022065fbcd2918e8b52c3af8c4ae21b3646d7a12f66ddcaa73a68273f5806527c0da0220291f555dd120bb9eadb144ccf4203db659b78d79bffcd37a29016da068f3abb9012102baab4d464b57771f5108f40ffc31b7242c5b430a61d4bfead024d715c441f3f3c7432500", "txid": "a174b99a87c899e66854ba6f101799ac852ef588ec69299846875e9d64355317", "hash": "748f4db5e45dab6a9903f50e7e760470ce80ac219c637d61418667a0ccb6c536", "depends": [], "fee": 14300, "sigops": 1, "weight": 569 }, { "data": "01000000018985719caa78472c8fad7efa88fc109c6faf6df3dc3a690656479e53c6b10fe9030000006b483045022100e7abb2f2f7e6cfad680efe10a7fc218d685a9cce929b1a4e62fcb726228ac9b50220538f3d2eb36d277bcadf00f3ac2abbaf80395c389e1f8048dc95fdf6204cf1840121037435c194e9b01b3d7f7a2802d6684a3af68d05bbf4ec8f17021980d777691f1dfdffffff040000000000000000536a4c5054325b0b15aaff5c59aab47ce39829055c3007922e2934d3b7c9f51238f8d4a98718da4f9c99a6692adacd0e324b7775eebe151c4f3ad37fcc5f9303eed491a02638b8002543c70002002543a7000a4c10270000000000001976a914000000000000000000000000000000000000000088ac10270000000000001976a914000000000000000000000000000000000000000088aca99f6412000000001976a914ba27f99e007c7f605a8305e318c1abde3cd220ac88ac00000000", "txid": "c34ac3f822a0b49f298be1a25fc9e9dd375c23985ed2656f7cf7d87b67fee1c0", "hash": "c34ac3f822a0b49f298be1a25fc9e9dd375c23985ed2656f7cf7d87b67fee1c0", "depends": [], "fee": 9153, "sigops": 12, "weight": 1408 }, { "data": "020000000001013f8e5ebf5620f2c62c9c4e4f77bb4eb903699620da372ad44f666a552e5b9fc801000000171600148442ee1fe3742153a6b40623d363a22ceed677c3ffffffff02bc0200000000000017a91470b93cf7ead31ef24b71159388c2e8e147f755b987aa0415000000000017a9145506e665bcf63e1225100d7c8613a640699fd5d087024730440220215cd74141ebcde3aee383f232dfd2c6b011645012f35714781d28d4a6959829022017ea40a15f4116bbf0a6f86dc0af08758d9a74673498a61164ed13e9d1c712e501210294184e8093958861740c4ce32e724a24b7df7eeb04702cae274652e86b01f83a00000000", "txid": "5aaf2fa707da25c24374b87d89c420c9e4c51022a4bbed4cc91614831f359366", "hash": "2c19268e65cc223c5fac9666883ecc1d10e680c54a4fdbbb3e4775f886b0feec", "depends": [], "fee": 491, "sigops": 1, "weight": 661 }, { "data": "020000000001012c53f5b0a12cadb57602466e703e60272183b75f3366073f4841dac8e616f2740000000000feffffff02e80300000000000016001496e381bd3fe4bae631adc5a5a420fb9124e5480af0a8100000000000160014cd06fc96fea44c6c5e074ba16dd20b222a0a9b32024730440220440ba2252ea6f70f1b8106897984330a997ef504f7f0dbb89679e44309026f2b02207074ad7dbd9a2bd37a41352527b7f3e68612956fe2085a0eb0b41f97f6a3599d012103b3ecf0f44126019a94a818245e35ecff1582c2900a38487a306fcd91bdb28003c8432500", "txid": "69770cf296b64329e784a5666fb0eab034fbb1780bf067f4e1606874c214f01d", "hash": "fadcaa8de64274e63c093592ed6d8c6b7584bb92c0e8d9c49f02a4df11f6f69d", "depends": [], "fee": 141, "sigops": 1, "weight": 561 }, { "data": "01000000019e03cd78895f6876932501b66eab1e44611d48b4c5029d250b7d2d6073b44b68000000006b48304502210088dfbeb842d54893987e5afb87b70fe70e7b91ebad740782515c44464b19a74202205a6bc47ad6c925f85aa29b2be1a174efcc30a4ad8ed6cfe6236b66d7a4f8d96f01210259530ee281106e237ac3aa801d1f7699ada05501186401da78b7d88849bbda8bffffffff0204ce3900000000001976a91419803f0d83a1b58348a4617c743638bbf9ec235588ac44ce5c01000000001976a9149e38aebe02be7cecacdb521f514cffe445c5533788ac00000000", "txid": "2137ca428d6233e680cea25c428b984391314d872a1a0f5f7b8dc8cfc4474c44", "hash": "2137ca428d6233e680cea25c428b984391314d872a1a0f5f7b8dc8cfc4474c44", "depends": [], "fee": 226, "sigops": 8, "weight": 904 }], "coinbaseaux": {}, "coinbasevalue": 2465717, "longpollid": "00000000000000022246451e9af7ac4f8bff2527d223f6e623740e92171592f29370", "target": "000000000000002495f800000000000000000000000000000000000000000000", "mintime": 1689512405, "mutable": ["time", "transactions", "prevblock"], "noncerange": "00000000ffffffff", "sigoplimit": 80000, "sizelimit": 4000000, "weightlimit": 4000000, "curtime": 1689514989, "bits": "192495f8", "height": 2442185, "default_witness_commitment": "6a24aa21a9edbd3d1d916aa0b57326a2d88ebe1b68a1d7c48585f26d8335fe6a94b62755f64c" };
|
||||||
public static MINING_SUBMIT = `{"id": 5, "method": "mining.submit", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "1", "c7080000", "64b3f3ec", "ed460d91", "00002000"]}`;
|
public static MINING_SUBMIT = `{"id": 5, "method": "mining.submit", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "1", "c708000000000000", "64b3f3ec", "ed460d91", "00002000"]}`;
|
||||||
public static TIME = '64b3f3ec';
|
public static TIME = '64b3f3ec';
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user