diff --git a/src/app.module.ts b/src/app.module.ts index 96d57c9..7cc8c5d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,6 +7,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AddressController } from './controllers/address/address.controller'; import { ClientController } from './controllers/client/client.controller'; +import { BitcoinAddressValidator } from './models/validators/bitcoin-address.validator'; import { AddressSettingsModule } from './ORM/address-settings/address-settings.module'; import { BlocksModule } from './ORM/blocks/blocks.module'; import { ClientStatisticsModule } from './ORM/client-statistics/client-statistics.module'; @@ -56,7 +57,8 @@ const ORMModules = [ TelegramService, BitcoinRpcService, BlockTemplateService, - NotificationService + NotificationService, + BitcoinAddressValidator ], }) export class AppModule { diff --git a/src/controllers/client/client.controller.spec.ts b/src/controllers/client/client.controller.spec.ts index a4174cf..3e24797 100644 --- a/src/controllers/client/client.controller.spec.ts +++ b/src/controllers/client/client.controller.spec.ts @@ -1,4 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { ClientStatisticsModule } from '../../ORM/client-statistics/client-statistics.module'; +import { ClientModule } from '../../ORM/client/client.module'; import { ClientController } from './client.controller'; describe('ClientController', () => { @@ -6,7 +10,20 @@ describe('ClientController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ + imports: [ + TypeOrmModule.forRoot({ + type: 'sqlite', + database: './DB/public-pool.test.sqlite', + synchronize: true, + autoLoadEntities: true, + cache: true, + logging: false + }), + ClientModule, + ClientStatisticsModule + ], controllers: [ClientController], + }).compile(); controller = module.get(ClientController); diff --git a/src/controllers/client/client.controller.ts b/src/controllers/client/client.controller.ts index 95490cf..387f844 100644 --- a/src/controllers/client/client.controller.ts +++ b/src/controllers/client/client.controller.ts @@ -1,6 +1,8 @@ import { Controller, Get, NotFoundException, Param } from '@nestjs/common'; -import { ClientStatisticsService } from 'src/ORM/client-statistics/client-statistics.service'; -import { ClientService } from 'src/ORM/client/client.service'; + +import { ClientStatisticsService } from '../../ORM/client-statistics/client-statistics.service'; +import { ClientService } from '../../ORM/client/client.service'; + @Controller('client') export class ClientController { diff --git a/src/main.ts b/src/main.ts index c32f304..159c1d6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; +import { useContainer } from 'class-validator'; import { AppModule } from './app.module'; @@ -23,7 +24,7 @@ async function bootstrap() { }), ); app.enableCors(); - + useContainer(app.select(AppModule), { fallbackOnErrors: true }); await app.listen(process.env.PORT, '0.0.0.0', () => { console.log(`http listening on port ${process.env.PORT}`); }); diff --git a/src/models/MiningJob.ts b/src/models/MiningJob.ts index 9dfed89..b7349e3 100644 --- a/src/models/MiningJob.ts +++ b/src/models/MiningJob.ts @@ -21,7 +21,14 @@ export class MiningJob { public block: bitcoinjs.Block = new bitcoinjs.Block(); public networkDifficulty: number; - constructor(id: string, payoutInformation: AddressObject[], public blockTemplate: IBlockTemplate, public clean_jobs: boolean) { + constructor( + private network: bitcoinjs.networks.Network, + id: string, + payoutInformation: AddressObject[], + public blockTemplate: IBlockTemplate, + public clean_jobs: boolean) { + + console.log(JSON.stringify(blockTemplate)) this.jobId = id; this.block.prevHash = this.convertToLittleEndian(blockTemplate.previousblockhash); @@ -155,19 +162,19 @@ export class MiningJob { const addressInfo = getAddressInfo(address); switch (addressInfo.type) { case AddressType.p2wpkh: { - return bitcoinjs.payments.p2wpkh({ address }).output; + return bitcoinjs.payments.p2wpkh({ address, network: this.network }).output; } case AddressType.p2pkh: { - return bitcoinjs.payments.p2pkh({ address }).output; + return bitcoinjs.payments.p2pkh({ address, network: this.network }).output; } case AddressType.p2sh: { - return bitcoinjs.payments.p2sh({ address }).output; + return bitcoinjs.payments.p2sh({ address, network: this.network }).output; } case AddressType.p2tr: { - return bitcoinjs.payments.p2tr({ address }).output; + return bitcoinjs.payments.p2tr({ address, network: this.network }).output; } case AddressType.p2wsh: { - return bitcoinjs.payments.p2wsh({ address }).output; + return bitcoinjs.payments.p2wsh({ address, network: this.network }).output; } default: { return Buffer.alloc(0); diff --git a/src/models/StratumV1Client.spec.ts b/src/models/StratumV1Client.spec.ts new file mode 100644 index 0000000..3ea7b56 --- /dev/null +++ b/src/models/StratumV1Client.spec.ts @@ -0,0 +1,54 @@ +import { ConfigService } from '@nestjs/config'; +import { PromiseSocket } from 'promise-socket'; + +import { BlocksService } from '../ORM/blocks/blocks.service'; +import { ClientStatisticsService } from '../ORM/client-statistics/client-statistics.service'; +import { ClientService } from '../ORM/client/client.service'; +import { BitcoinRpcService } from '../services/bitcoin-rpc.service'; +import { BlockTemplateService } from '../services/block-template.service'; +import { NotificationService } from '../services/notification.service'; +import { StratumV1JobsService } from '../services/stratum-v1-jobs.service'; +import { StratumV1Client } from './StratumV1Client'; + + +describe('StratumV1Client', () => { + + let promiseSocket: PromiseSocket = new PromiseSocket(); + let stratumV1JobsService: StratumV1JobsService; + let bitcoinRpcService: BitcoinRpcService; + let blockTemplateService: BlockTemplateService; + let clientService: ClientService; + let clientStatisticsService: ClientStatisticsService; + let notificationService: NotificationService; + let blocksService: BlocksService; + let configService: ConfigService; + + let client: StratumV1Client; + + + beforeEach(async () => { + + jest.spyOn(promiseSocket.socket, 'on').mockImplementation(); + + client = new StratumV1Client( + promiseSocket, + stratumV1JobsService, + blockTemplateService, + bitcoinRpcService, + clientService, + clientStatisticsService, + notificationService, + blocksService, + configService + ); + + + }); + + + it('should parse message', () => { + expect(promiseSocket.socket.on).toHaveBeenCalled(); + }); + + +}); \ No newline at end of file diff --git a/src/models/StratumV1Client.ts b/src/models/StratumV1Client.ts index 641480d..a2bed2d 100644 --- a/src/models/StratumV1Client.ts +++ b/src/models/StratumV1Client.ts @@ -1,3 +1,4 @@ +import { ConfigService } from '@nestjs/config'; import Big from 'big.js'; import * as bitcoinjs from 'bitcoinjs-lib'; import { plainToInstance } from 'class-transformer'; @@ -6,7 +7,6 @@ import * as crypto from 'crypto'; import { Socket } from 'net'; import PromiseSocket from 'promise-socket'; import { combineLatest, firstValueFrom, interval, startWith, takeUntil } from 'rxjs'; -import { NotificationService } from 'src/services/notification.service'; import { BlocksService } from '../ORM/blocks/blocks.service'; import { ClientStatisticsService } from '../ORM/client-statistics/client-statistics.service'; @@ -14,6 +14,7 @@ import { ClientEntity } from '../ORM/client/client.entity'; import { ClientService } from '../ORM/client/client.service'; import { BitcoinRpcService } from '../services/bitcoin-rpc.service'; import { BlockTemplateService } from '../services/block-template.service'; +import { NotificationService } from '../services/notification.service'; import { StratumV1JobsService } from '../services/stratum-v1-jobs.service'; import { EasyUnsubscribe } from '../utils/EasyUnsubscribe'; import { IBlockTemplate } from './bitcoin-rpc/IBlockTemplate'; @@ -29,6 +30,7 @@ import { SubscriptionMessage } from './stratum-messages/SubscriptionMessage'; import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage'; import { StratumV1ClientStatistics } from './StratumV1ClientStatistics'; + export class StratumV1Client extends EasyUnsubscribe { private clientSubscription: SubscriptionMessage; @@ -52,7 +54,8 @@ export class StratumV1Client extends EasyUnsubscribe { private readonly clientService: ClientService, private readonly clientStatisticsService: ClientStatisticsService, private readonly notificationService: NotificationService, - private readonly blocksService: BlocksService + private readonly blocksService: BlocksService, + private readonly configService: ConfigService ) { super(); @@ -67,7 +70,6 @@ export class StratumV1Client extends EasyUnsubscribe { .filter(m => m.length > 0) .forEach(m => this.handleMessage(m)) }); - } private getRandomHexString() { @@ -91,6 +93,7 @@ export class StratumV1Client extends EasyUnsubscribe { return; } + switch (parsedMessage.method) { case eRequestMethod.SUBSCRIBE: { const subscriptionMessage = plainToInstance( @@ -312,12 +315,18 @@ export class StratumV1Client extends EasyUnsubscribe { } else { payoutInformation = [ - { address: 'bc1q99n3pu025yyu0jlywpmwzalyhm36tg5u37w20d', percent: 1.5 }, + { address: this.configService.get('DEV_FEE_ADDRESS'), percent: 1.5 }, { address: this.clientAuthorization.address, percent: 98.5 } ]; } - const job = new MiningJob(this.stratumV1JobsService.getNextId(), payoutInformation, blockTemplate, clearJobs); + const job = new MiningJob( + this.configService.get('NETWORK') === 'mainnet' ? bitcoinjs.networks.bitcoin : bitcoinjs.networks.testnet, + this.stratumV1JobsService.getNextId(), + payoutInformation, + blockTemplate, + clearJobs + ); this.stratumV1JobsService.addJob(job, clearJobs); @@ -427,7 +436,7 @@ export class StratumV1Client extends EasyUnsubscribe { } } - public calculateDifficulty(header: Buffer): { submissionDifficulty: number, submissionHash: string } { + private calculateDifficulty(header: Buffer): { submissionDifficulty: number, submissionHash: string } { const hashResult = bitcoinjs.crypto.hash256(header); diff --git a/src/models/stratum-messages/AuthorizationMessage.ts b/src/models/stratum-messages/AuthorizationMessage.ts index c3f5b2f..150c810 100644 --- a/src/models/stratum-messages/AuthorizationMessage.ts +++ b/src/models/stratum-messages/AuthorizationMessage.ts @@ -24,7 +24,7 @@ export class AuthorizationMessage extends StratumBaseMessage { @IsString() @MaxLength(64) @Transform(({ value, key, obj, type }) => { - return obj.params[0].split('.')[1]; + return obj.params[0].split('.')[1] == null ? 'worker' : obj.params[0].split('.')[1]; }) public worker: string; diff --git a/src/models/stratum-messages/MiningSubmitMessage.spec.ts b/src/models/stratum-messages/MiningSubmitMessage.spec.ts index 928256d..5c13c8e 100644 --- a/src/models/stratum-messages/MiningSubmitMessage.spec.ts +++ b/src/models/stratum-messages/MiningSubmitMessage.spec.ts @@ -1,3 +1,7 @@ +import { plainToInstance } from 'class-transformer'; + +import { MiningSubmitMessage } from './MiningSubmitMessage'; + describe('MiningSubmitMessage', () => { @@ -7,48 +11,25 @@ describe('MiningSubmitMessage', () => { }); - // describe('test nonce', () => { + describe('test message parsing', () => { - // const value = new MiningSubmitMessage().testNonceValue({ - // version: 0x20000004, - // prevhash: "0c859545a3498373a57452fac22eb7113df2a465000543520000000000000000", - // merkleRoot: "5bdc1968499c3393873edf8e07a1c3a50a97fc3a9d1a376bbf77087dd63778eb", - // ntime: 0x647025b5, - // nbits: 0x1705ae3a, - // } as unknown as MiningJob, 167943889); + const MINING_SUBMIT_MESSAGE = ' {"id": 5, "method": "mining.submit", "params": ["tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3", "1", "99020000", "64b1f10f", "2402812d", "00006000"]}' - // it('should be correct difficulty', () => { - // expect(value).toEqual(683); - // }); - // }); + const message = plainToInstance( + MiningSubmitMessage, + JSON.parse(MINING_SUBMIT_MESSAGE), + ); - // describe('test empty version', () => { + it('should parse message', () => { + expect(message.id).toEqual(5); + expect(message.userId).toEqual('tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.bitaxe3'); + expect(message.jobId).toEqual('1'); + expect(message.extraNonce2).toEqual('99020000'); + expect(message.ntime).toEqual('64b1f10f'); + expect(message.nonce).toEqual('2402812d'); + expect(message.versionMask).toEqual('00006000'); + }); + }); - // const value = new MiningSubmitMessage().testNonceValue({ - // version: 0x20000004, - // prevhash: "0c859545a3498373a57452fac22eb7113df2a465000543520000000000000000", - // merkleRoot: "5bdc1968499c3393873edf8e07a1c3a50a97fc3a9d1a376bbf77087dd63778eb", - // ntime: 0x647025b5, - // nbits: 0x1705ae3a, - // } as unknown as MiningJob, 167943889, parseInt('00000000', 16)); - // it('should be correct difficulty', () => { - // expect(value).toEqual(683); - // }); - // }); - - // describe('test high value nonce', () => { - - // const value = new MiningSubmitMessage().testNonceValue({ - // version: 0x20a00000, - // prevhash: "00000000000000000002a7b66a599d17893cb312a8ee7bc15e4015ff52774f00", - // merkleRoot: "210bbf45c85165aab889691056cfebbfd763e11b2623a261fb6135b6bab66ce3", - // ntime: 1686839100, - // target: 52350439455487, - // } as MiningJob, 0x05d69c40,); - - // it('should be correct difficulty', () => { - // expect(value).toEqual(683); - // }); - // }); }); diff --git a/src/models/validators/bitcoin-address.validator.ts b/src/models/validators/bitcoin-address.validator.ts index 7041263..bd7dac0 100644 --- a/src/models/validators/bitcoin-address.validator.ts +++ b/src/models/validators/bitcoin-address.validator.ts @@ -1,11 +1,19 @@ -import { Network, validate } from 'bitcoin-address-validation'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { validate } from 'bitcoin-address-validation'; import { registerDecorator, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator'; + @ValidatorConstraint({ name: 'bitcoinAddress', async: false }) -export class BitcoinAddress implements ValidatorConstraintInterface { +@Injectable() +export class BitcoinAddressValidator implements ValidatorConstraintInterface { + + constructor( + private configService: ConfigService + ) { } + validate(value: string): boolean { - // Implement your custom validation logic here - return validate(value, Network.mainnet); + return validate(value, this.configService.get('NETWORK')); } defaultMessage(): string { @@ -21,7 +29,7 @@ export function IsBitcoinAddress(validationOptions?: ValidationOptions) { propertyName: propertyName, constraints: [], options: validationOptions, - validator: BitcoinAddress, + validator: BitcoinAddressValidator, }); }; } \ No newline at end of file diff --git a/src/services/stratum-v1.service.ts b/src/services/stratum-v1.service.ts index f3fcc24..1a77b8c 100644 --- a/src/services/stratum-v1.service.ts +++ b/src/services/stratum-v1.service.ts @@ -1,4 +1,5 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { Server, Socket } from 'net'; import { PromiseSocket } from 'promise-socket'; @@ -21,7 +22,8 @@ export class StratumV1Service implements OnModuleInit { private readonly clientService: ClientService, private readonly clientStatisticsService: ClientStatisticsService, private readonly notificationService: NotificationService, - private readonly blocksService: BlocksService + private readonly blocksService: BlocksService, + private readonly configService: ConfigService ) { } @@ -47,7 +49,8 @@ export class StratumV1Service implements OnModuleInit { this.clientService, this.clientStatisticsService, this.notificationService, - this.blocksService + this.blocksService, + this.configService ); diff --git a/src/services/telegram.service.ts b/src/services/telegram.service.ts index 90f028c..ba3e5d0 100644 --- a/src/services/telegram.service.ts +++ b/src/services/telegram.service.ts @@ -3,7 +3,9 @@ import { ConfigService } from '@nestjs/config'; import { validate } from 'bitcoin-address-validation'; import { Block } from 'bitcoinjs-lib'; import * as TelegramBot from 'node-telegram-bot-api'; -import { TelegramSubscriptionsService } from 'src/ORM/telegram-subscriptions/telegram-subscriptions.service'; + +import { TelegramSubscriptionsService } from '../ORM/telegram-subscriptions/telegram-subscriptions.service'; + @Injectable() export class TelegramService implements OnModuleInit {