import { ChildProcess , execFile } from 'child_process' ; import EventEmitter from 'events' ; import { resolve } from 'path' ; import { BitString , Cell } from 'ton' ; import log from './logger' ; import { GPU } from './read-gpus' ; interface MinedBody { op ? : bigint ; flags ? : bigint ; expire ? : bigint ; whom ? : bigint ; rdata1 ? : bigint ; rseed ? : bigint ; rdata2 ? : bigint ; } const getBitStringSlices = ( bits , slices ) : bigint [ ] => { if ( ! slices . length || ! bits . length ) return [ ] ; let from = 0 ; return slices . map ( ( slice ) => getBitStringSliceBigInt ( bits , from , ( from += slice ) ) ) ; } ; const getBitStringSliceBigInt = ( bits , from , to ) : bigint => { const slice = bits . toString ( ) . slice ( from , to ) ; return BigInt ( `0b${ slice || 0 }` ) ; } ; const parseMinedBody = ( bits ) : MinedBody => { const slices = [ 32 , 8 , 32 , 256 , 256 , 128 , 256 ] ; const [ op , flags , expire , whom , rdata1 , rseed , rdata2 ] = getBitStringSlices ( bits , slices ) ; const parsed = { op , flags , expire , whom , rdata1 , rseed , rdata2 } ; return parsed ; } ; export interface Benchmark extends EventEmitter { on ( event , listener ) : this ; on ( event , listener ) : this ; once ( event , listener ) : this ; once ( event , listener ) : this ; } interface Miner { emit ( event , error ) : boolean ; emit ( event , hashrate ) : boolean ; emit ( event , solution ) : boolean ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; once ( event , listener ) : this ; once ( event , listener ) : this ; once ( event , listener ) : this ; } class Miner extends EventEmitter { public id : string ; protected expired = '' ; protected complexity = '' ; protected giver = '' ; protected gpu : GPU ; protected iterations : string ; protected ref ? : ChildProcess = undefined ; protected seed = '' ; protected solutionPath : string ; protected stopped = false ; protected wallet : string ; constructor ( gpu , wallet , dataDir , iterations ) { super ( ) ; this . gpu = gpu ; this . id = gpu . id ; this . iterations = iterations ; this . solutionPath = resolve ( dataDir , `${ this . id }-mined.boc` ) ; this . wallet = wallet ; } static benchmark ( bin , id , timeout = 10 ) { const events = new EventEmitter ( ) ; const child = execFile ( resolve ( __dirname , '..' , '..' , 'bin' , bin ) , [ ... [ '-vv' , '-g' , id . toString ( 10 ) , '-B' , '-t' , timeout . toString ( 10 ) ] , 'kQBWkNKqzCAwA9vjMwRmg7aY75Rf8lByPA9zKXoqGkHi8SM7' , '229760179690128740373110445116482216837' , '5391989333430127958933403017403926134727428884508114496220722049840' , '9223372036854775807' ] , ( error , stdout , stderr ) => { if ( error ) { const lines = error . message . trim ( ) . split ( '\n' ) ; const rawLine = ( lines [ lines . length - 1 ] || 'unknown error' ) . replace ( '\u001b[1;36m' , '' ) . replace ( '\u001b[1;31m' , '' ) . replace ( '\u001b[0m' , '' ) . replace ( /^\[[^\]]+\]\[[^\]]+\]\[[^\]]+\]/ , '' ) ; return events . emit ( 'done' , new Error ( rawLine ) ) ; } const [ , boost = '16' ] = stderr . match ( /best boost factor: +(\d+)/ ) || [ ] ; const [ , hashrate = '0' ] = stderr . match ( /best speed: +(\d+)/ ) || [ ] ; return events . emit ( 'done' , null , [ Number . parseInt ( boost , 10 ) , Number . parseInt ( hashrate , 10 ) ] ) ; } ) ; if ( child . stderr ) { const step = /cuda/ . test ( bin ) ? 6 : 9 ; let attempt = 0 ; child . stderr . on ( 'data' , ( line ) => { if ( /START MINER/ . test ( line ) ) { events . emit ( 'progress' , step * attempt ++ ) ; } } ) ; } return events ; } setComplexity ( complexity ) { this . complexity = complexity ; } setExpire ( expire ) { this . expired = expire ; if ( ! this . ref ) { this . run ( ) ; } } setTarget ( seed , expired , giver , wallet ) { this . seed = seed ; this . expired = expired ; this . giver = giver ; this . wallet = wallet ; this . ref ? this . ref . kill ( ) : this . run ( ) ; } protected run ( ) { if ( this . stopped ) { return undefined ; } const currentExpire = this . expired ; log . debug ( `[${ this . id }] miner.run` ) ; this . ref = execFile ( this . gpu . minerPath , [ '-vv' , ... [ '-g' , this . gpu . deviceId . toString ( ) ] , ... ( this . gpu . type === 'OpenCL' ? [ '-p' , this . gpu . platformId . toString ( ) ] : [ ] ) , ... [ '-F' , this . gpu . boost . toString ( ) ] , ... [ '-e' , currentExpire ] , this . wallet , ... [ this . seed , this . complexity , this . iterations , this . giver ] , this . solutionPath ] , { timeout : 0 } , ( error , stdout , stderr ) => { log . debug ( `[${ this . id }] miner.run done ${ this . ref ?. killed } ${ error ?. code } ${ error ?. signal }` ) ; this . ref = undefined ; if ( this . stopped ) { return undefined ; } if ( error && /expire_base [<>]=/ . test ( error . message ) ) { if ( currentExpire !== this . expired ) { return this . run ( ) ; } const offset = Number . parseInt ( currentExpire ) - Math . floor ( Date . now ( ) / 1000 ) ; const signedOffset = `${ offset >= 0 ? '+' : '' }${ offset }` ; this . emit ( "error" , new Error ( `[${ this . id }] miner error: invalid expire ${ signedOffset }, waiting for new one...` ) ) ; return undefined ; } if ( error && ( error . code || error . signal === 'SIGABRT' ) ) { this . emit ( "error" , new Error ( `[${ this . id }] miner error ${ error . code || 'null' } ${ error . signal || 'null' }:\n${ stderr . trim ( ) }` ) ) ; } return this . run ( ) ; } ) ; this . ref . stderr ?. on ( 'data' , ( line ) : void => { const [ , solution ] = line . match ( /FOUND BOC: (\w+)/ ) || [ ] ; if ( solution ) { try { const mined = Cell . fromBoc ( solution ) ; const body = mined [ 0 ] ?. refs [ 0 ] ?. bits ; if ( ! body ) { return void this . emit ( 'error' , new Error ( 'parsed invalid boc' ) ) ; } const { expire , rdata1 , rseed } = parseMinedBody ( body ) ; return void this . emit ( 'success' , [ expire ?. toString ( ) || '' , rdata1 ?. toString ( ) || '' , rseed ?. toString ( ) || '' , '' ] ) ; } catch ( error ) { return void this . emit ( 'error' , new Error ( `error parsing boc: ${ ( error as Error ) . message }` ) ) ; } } if ( /\[ mining in progress, / . test ( line ) ) { const [ , hashrate ] = line . match ( /instant speed: (\d+\.?\d*) Mhash\/s/ ) || [ ] ; if ( ! hashrate ) { return undefined ; } const value = BigInt ( Math . round ( Number . parseFloat ( hashrate ) ) ) * BigInt ( 1e6 ) ; return void this . emit ( 'hashrate' , value . toString ( ) ) ; } } ) ; } start ( ) { this . stopped = false ; } stop ( ) { if ( this . stopped ) { return undefined ; } this . stopped = true ; this . ref ?. kill ( ) ; } } export default Miner ;