import { BN , keccak , keccak256 , rlphash , zeros , TWO_POW256 } from 'ethereumjs-util' ; import { params , fnv , fnvBuffer , bufReverse , getEpoc , getCacheSize , getFullSize , getSeed , } from './util' ; import type { LevelUp } from 'levelup' ; import { Block , BlockData , BlockHeader , HeaderData } from '@ethereumjs/block' ; const xor = require ( 'buffer-xor' ) ; export type Solution = { mixHash : Buffer ; nonce : Buffer ; } ; export class Miner { private blockHeader : BlockHeader ; private block ? : Block ; private ethash : Ethash ; public solution ? : Solution ; private currentNonce : BN ; private headerHash ? : Buffer ; private stopMining : boolean ; param mineObject param ethash constructor ( mineObject , ethash ) { if ( mineObject instanceof BlockHeader ) { this . blockHeader = mineObject ; } else if ( mineObject instanceof Block ) { this . block = mineObject ; this . blockHeader = mineObject . header ; } else { throw new Error ( 'unsupported mineObject' ) ; } this . currentNonce = new BN ( 0 ) ; this . ethash = ethash ; this . stopMining = false ; } /**
     * Stop the miner on the next iteration
     */ stop ( ) { this . stopMining = true ; } param iterations returns async mine ( iterations = 0 ) { const solution = await this . iterate ( iterations ) ; if ( solution ) { if ( this . block ) { const data = < BlockData > this . block . toJSON ( ) ; data . header ! . mixHash = solution . mixHash ; data . header ! . nonce = solution . nonce ; return Block . fromBlockData ( data , { common : this . block . _common } ) ; } else { const data = < HeaderData > this . blockHeader . toJSON ( ) ; data . mixHash = solution . mixHash ; data . nonce = solution . nonce ; return BlockHeader . fromHeaderData ( data , { common : this . blockHeader . _common } ) ; } } } param iterations returns async iterate ( iterations = 0 ) { if ( this . solution ) { return this . solution ; } if ( ! this . headerHash ) { this . headerHash = this . ethash . headerHash ( this . blockHeader . raw ( ) ) ; } const headerHash = this . headerHash ; const { number , difficulty } = this . blockHeader ; await this . ethash . loadEpoc ( number . toNumber ( ) ) ; const self = this ; while ( iterations != 0 && ! this . stopMining ) { const solution = await new Promise ( ( resolve ) => { setTimeout ( function ( ) { const nonce = self . currentNonce . toBuffer ( undefined , 8 ) ; const a = self . ethash . run ( headerHash , nonce ) ; const result = new BN ( a . hash ) ; if ( TWO_POW256 . div ( difficulty ) . cmp ( result ) === 1 ) { const solution = { mixHash : < Buffer > a . mix , nonce , } ; self . solution = solution ; resolve ( solution ) ; return ; } self . currentNonce . iaddn ( 1 ) ; iterations -- ; resolve ( null ) ; } , 0 ) ; } ) ; if ( solution ) { return < Solution > solution ; } } } } export default class Ethash { dbOpts : Object ; cacheDB ? : LevelUp ; cache : Buffer [ ] ; epoc ? : number ; fullSize ? : number ; cacheSize ? : number ; seed ? : Buffer ; constructor ( cacheDB ? ) { this . dbOpts = { valueEncoding : 'json' , } ; this . cacheDB = cacheDB ; this . cache = [ ] ; } mkcache ( cacheSize , seed ) { const n = Math . floor ( cacheSize / params . HASH_BYTES ) ; const o = [ keccak ( seed , 512 ) ] ; let i ; for ( i = 1 ; i < n ; i ++ ) { o . push ( keccak ( o [ o . length - 1 ] , 512 ) ) ; } for ( let _ = 0 ; _ < params . CACHE_ROUNDS ; _ ++ ) { for ( i = 0 ; i < n ; i ++ ) { const v = o [ i ] . readUInt32LE ( 0 ) % n ; o [ i ] = keccak ( xor ( o [ ( i - 1 + n ) % n ] , o [ v ] ) , 512 ) ; } } this . cache = o ; return this . cache ; } calcDatasetItem ( i ) { const n = this . cache . length ; const r = Math . floor ( params . HASH_BYTES / params . WORD_BYTES ) ; let mix = Buffer . from ( this . cache [ i % n ] ) ; mix . writeInt32LE ( mix . readUInt32LE ( 0 ) ^ i , 0 ) ; mix = keccak ( mix , 512 ) ; for ( let j = 0 ; j < params . DATASET_PARENTS ; j ++ ) { const cacheIndex = fnv ( i ^ j , mix . readUInt32LE ( ( j % r ) * 4 ) ) ; mix = fnvBuffer ( mix , this . cache [ cacheIndex % n ] ) ; } return keccak ( mix , 512 ) ; } run ( val , nonce , fullSize ? ) { if ( ! fullSize && this . fullSize ) { fullSize = this . fullSize ; } if ( ! fullSize ) { throw new Error ( 'fullSize needed' ) ; } const n = Math . floor ( fullSize / params . HASH_BYTES ) ; const w = Math . floor ( params . MIX_BYTES / params . WORD_BYTES ) ; const s = keccak ( Buffer . concat ( [ val , bufReverse ( nonce ) ] ) , 512 ) ; const mixhashes = Math . floor ( params . MIX_BYTES / params . HASH_BYTES ) ; let mix = Buffer . concat ( Array ( mixhashes ) . fill ( s ) ) ; let i ; for ( i = 0 ; i < params . ACCESSES ; i ++ ) { const p = ( fnv ( i ^ s . readUInt32LE ( 0 ) , mix . readUInt32LE ( ( i % w ) * 4 ) ) % Math . floor ( n / mixhashes ) ) * mixhashes ; const newdata = [ ] ; for ( let j = 0 ; j < mixhashes ; j ++ ) { newdata . push ( this . calcDatasetItem ( p + j ) ) ; } mix = fnvBuffer ( mix , Buffer . concat ( newdata ) ) ; } const cmix = Buffer . alloc ( mix . length / 4 ) ; for ( i = 0 ; i < mix . length / 4 ; i = i + 4 ) { const a = fnv ( mix . readUInt32LE ( i * 4 ) , mix . readUInt32LE ( ( i + 1 ) * 4 ) ) ; const b = fnv ( a , mix . readUInt32LE ( ( i + 2 ) * 4 ) ) ; const c = fnv ( b , mix . readUInt32LE ( ( i + 3 ) * 4 ) ) ; cmix . writeUInt32LE ( c , i ) ; } return { mix : cmix , hash : keccak256 ( Buffer . concat ( [ s , cmix ] ) ) , } ; } cacheHash ( ) { return keccak256 ( Buffer . concat ( this . cache ) ) ; } headerHash ( rawHeader ) { return rlphash ( rawHeader . slice ( 0 , - 2 ) ) ; } /**
     * Loads the seed and cache given a block number.
     */ async loadEpoc ( number ) { const epoc = getEpoc ( number ) ; if ( this . epoc === epoc ) { return ; } this . epoc = epoc ; if ( ! this . cacheDB ) { throw new Error ( 'cacheDB needed' ) ; } const findLastSeed = async ( epoc ) : Promise < [ Buffer , number ] > => { if ( epoc === 0 ) { return [ zeros ( 32 ) , 0 ] ; } let data ; try { data = await this . cacheDB ! . get ( epoc , this . dbOpts ) ; } catch ( error ) { if ( error . type !== 'NotFoundError' ) { throw error ; } } if ( data ) { return [ data . seed , epoc ] ; } else { return findLastSeed ( epoc - 1 ) ; } } ; let data ; try { data = await this . cacheDB ! . get ( epoc , this . dbOpts ) ; } catch ( error ) { if ( error . type !== 'NotFoundError' ) { throw error ; } } if ( ! data ) { this . cacheSize = getCacheSize ( epoc ) ; this . fullSize = getFullSize ( epoc ) ; const [ seed , foundEpoc ] = await findLastSeed ( epoc ) ; this . seed = getSeed ( seed , foundEpoc , epoc ) ; const cache = this . mkcache ( this . cacheSize ! , this . seed ! ) ; await this . cacheDB ! . put ( epoc , { cacheSize : this . cacheSize , fullSize : this . fullSize , seed : this . seed , cache : cache , } , this . dbOpts ) ; } else { this . cache = data . cache . map ( ( a ) => { return Buffer . from ( a ) ; } ) ; this . cacheSize = data . cacheSize ; this . fullSize = data . fullSize ; this . seed = Buffer . from ( data . seed ) ; } } param mineObject returns getMiner ( mineObject ) { return new Miner ( mineObject , this ) ; } async _verifyPOW ( header ) { const headerHash = this . headerHash ( header . raw ( ) ) ; const { number , difficulty , mixHash , nonce } = header ; await this . loadEpoc ( number . toNumber ( ) ) ; const a = this . run ( headerHash , nonce ) ; const result = new BN ( a . hash ) ; return a . mix . equals ( mixHash ) && TWO_POW256 . div ( difficulty ) . cmp ( result ) === 1 ; } async verifyPOW ( block ) { if ( block . header . isGenesis ( ) ) { return true ; } const valid = await this . _verifyPOW ( block . header ) ; if ( ! valid ) { return false ; } for ( let index = 0 ; index < block . uncleHeaders . length ; index ++ ) { const valid = await this . _verifyPOW ( block . uncleHeaders [ index ] ) ; if ( ! valid ) { return false ; } } return true ; } }