import { toBufferBE } from 'bigint-buffer' ; import { EventEmitter } from 'events' ; import levelup , { LevelUp } from 'levelup' ; import { Address } from 'web3x/address' ; import { BlockHeaderResponse , fromRawCallRequest , fromRawLogRequest , fromRawTransactionRequest , LogResponse , toRawBlockHeaderResponse , toRawLogResponse , } from 'web3x/formatters' ; import { EthereumProvider , EthereumProviderNotifications } from 'web3x/providers' ; import { bufferToHex , numberToHex } from 'web3x/utils' ; import { Wallet } from 'web3x/wallet' ; import { Blockchain , BlockHeader , deserializeBlockState } from '../blockchain' ; import { validateBlock } from '../blockchain/validate-block' ; import { getAccountCode } from '../vm' ; import { getAccountTransactions } from '../vm/get-account-transactions' ; import { WorldState } from '../world' ; import { handleCall } from './handle-call' ; import { getLogs } from './handle-get-logs' ; import { handleGetTransactionByHash } from './handle-get-transaction' ; import { handleGetTransactionReceipt } from './handle-get-transaction-receipt' ; import { handleSendTransaction } from './handle-send-transaction' ; const newBlockChannel = typeof BroadcastChannel !== 'undefined' ? new BroadcastChannel ( 'newBlock' ) : undefined ; export interface EvmProviderOptions { blockDelay ? : number ; wallet ? : Wallet ; listenForBlocks ? : boolean ; } export class EvmProvider extends EventEmitter implements EthereumProvider { public wallet ? : Wallet ; private subscriptions : { [ id ] : any ; } = { } ; private nextSubscriptionId = 0 ; private blockHandler = e => this . handleBlock ( new Buffer ( e . data ) ) ; constructor ( public readonly worldState , private readonly blockchain , private options = { } ) { super ( ) ; this . wallet = options . wallet ; if ( options . listenForBlocks && newBlockChannel ) { newBlockChannel . onmessage = this . blockHandler ; } } public static fromEvmProvider ( provider , options ? ) { return new EvmProvider ( provider . worldState , provider . blockchain , { wallet : provider . wallet , ... options } ) ; } public static async fromDb ( db , options ? ) { const worldState = await WorldState . fromDb ( db ) ; const blockchain = await Blockchain . fromDb ( db ) ; return new EvmProvider ( worldState , blockchain , options ) ; } public static async fromLocalDb ( name , options ? ) { const leveljs = require ( 'level-js' ) ; return await EvmProvider . fromDb ( levelup ( leveljs ( name ) ) , options ) ; } public static async eraseLocalDb ( name ) { return new Promise ( ( resolve , reject ) => { const req = indexedDB . deleteDatabase ( `level-js-${ name }` ) ; req . onsuccess = resolve ; req . onerror = reject ; req . onblocked = reject ; } ) ; } public async loadWallet ( wallet , amount = BigInt ( 10 ) * BigInt ( 10 ) ** BigInt ( 18 ) ) { this . worldState . checkpoint ( ) ; for ( const address of wallet . currentAddresses ( ) ) { if ( ! ( await this . worldState . accountExists ( address ) ) ) { await this . worldState . createAccount ( address , amount ) ; } } await this . worldState . commit ( ) ; this . wallet = wallet ; } public setBlockDelay ( blockDelay ) { this . options . blockDelay = blockDelay ; } public async send ( method , params ? ) { switch ( method ) { case 'eth_accounts' : return this . wallet ? this . wallet . currentAddresses ( ) : [ ] ; case 'eth_gasPrice' : return numberToHex ( 50000 ) ; case 'net_version' : return numberToHex ( 666 ) ; } if ( ! params || ! params [ 0 ] ) { throw new Error ( ) ; } switch ( method ) { case 'eth_sendTransaction' : if ( ! this . wallet ) { throw new Error ( 'No wallet available for signing transactions.' ) ; } return await handleSendTransaction ( this . worldState , this . blockchain , fromRawTransactionRequest ( params [ 0 ] ) , this . wallet , newBlockChannel , this . options . blockDelay ) ; case 'eth_call' : return bufferToHex ( await handleCall ( this . worldState , this . blockchain , fromRawCallRequest ( params [ 0 ] ) ) ) ; case 'eth_getTransactionCount' : return numberToHex ( await getAccountTransactions ( this . worldState , Address . fromString ( params ! [ 0 ] ) ) ) ; case 'eth_getTransactionReceipt' : return await handleGetTransactionReceipt ( this . blockchain , params [ 0 ] ) ; case 'eth_getTransactionByHash' : return await handleGetTransactionByHash ( this . blockchain , params [ 0 ] ) ; case 'eth_getCode' : return bufferToHex ( await getAccountCode ( this . worldState , Address . fromString ( params ! [ 0 ] ) ) ) ; case 'eth_getLogs' : return ( await getLogs ( this . blockchain , fromRawLogRequest ( params ! [ 0 ] ) ) ) . map ( toRawLogResponse ) ; case 'eth_subscribe' : return numberToHex ( this . subscribe ( params [ 0 ] , params [ 1 ] ) ) ; case 'eth_unsubscribe' : return this . unsubscribe ( params [ 0 ] ) ; default : throw new Error ( `Unsupported method ${ method }` ) ; } } private subscribe ( event , params ) { const id = numberToHex ( this . nextSubscriptionId ++ ) ; if ( event === 'logs' ) { const listener = logs => this . handleLogs ( id , logs ) ; this . blockchain . on ( 'logs' , listener ) ; this . subscriptions [ id ] = { event , params , listener } ; } if ( event === 'newHeads' ) { const listener = ( block , hash ) => this . handleNewHead ( id , block , hash ) ; this . blockchain . on ( 'newHeads' , listener ) ; this . subscriptions [ id ] = { event , params , listener } ; } return id ; } private unsubscribe ( id ) { const sub = this . subscriptions [ id ] ; if ( ! sub ) { return false ; } delete this . subscriptions [ id ] ; this . blockchain . removeListener ( sub . event , sub . listener ) ; return true ; } private handleLogs ( subscription , logResponse ) { this . emit ( 'notification' , { subscription , result : toRawLogResponse ( logResponse ) } ) ; } private handleNewHead ( subscription , block , hash ) { const blockResponse = { hash , ... block , difficulty : block . difficulty . toString ( ) , gasLimit : Number ( block . gasLimit ) , gasUsed : Number ( block . gasUsed ) , nonce : toBufferBE ( BigInt ( block . nonce ) , 8 ) , } ; this . emit ( 'notification' , { subscription , result : toRawBlockHeaderResponse ( blockResponse ) } ) ; } private async handleBlock ( serializedBlockState ) { const blockState = deserializeBlockState ( serializedBlockState ) ; await validateBlock ( this . worldState , this . blockchain , blockState ) ; } public on ( notification , listener ) ; public on ( notification , listener ) ; public on ( notification , listener ) ; public on ( notification , listener ) ; public on ( notification , listener ) ; public on ( notification , listener ) { return super . on ( notification , listener ) ; } public removeListener ( notification , listener ) ; public removeListener ( notification , listener ) ; public removeListener ( notification , listener ) ; public removeListener ( notification , listener ) ; public removeListener ( notification , listener ) ; public removeListener ( notification , listener ) { return super . removeListener ( notification , listener ) ; } public removeAllListeners ( notification ) { return super . removeAllListeners ( notification ) ; } public close ( ) { if ( newBlockChannel ) { newBlockChannel . removeEventListener ( 'message' , this . blockHandler ) ; } } }