import { Net } from './interfaces' ; import { PromInt , InterruptedError } from './promint' ; import { Cache } from './cache' ; import { blake2b256 } from 'thor-devkit' ; import { sleep } from './common' ; /** class implements Connex.Driver leaves out Vendor related methods */ export class DriverNoVendor implements Connex . Driver { public head : Connex . Thor . Status [ 'head' ] ; private headResolvers = [ ] as Array < ( ) => void > ; private readonly int = new PromInt ( ) ; private readonly cache = new Cache ( ) ; private readonly pendingRequests : Record < string , Promise < any > > = { } ; constructor ( protected readonly net , readonly genesis , initialHead ? ) { if ( initialHead ) { this . head = initialHead ; } else { this . head = { id : genesis . id , number : genesis . number , timestamp : genesis . timestamp , parentID : genesis . parentID , txsFeatures : genesis . txsFeatures , gasLimit : genesis . gasLimit } ; } void this . headTrackerLoop ( ) ; } public close ( ) { this . int . interrupt ( ) ; } public pollHead ( ) { return this . int . wrap ( new Promise < Connex . Thor . Status [ 'head' ] > ( resolve => { this . headResolvers . push ( ( ) => resolve ( this . head ) ) ; } ) ) ; } public getBlock ( revision ) { return this . cache . getBlock ( revision , ( ) => this . httpGet ( `blocks/${ revision }` ) ) ; } public getTransaction ( id , allowPending ) { return this . cache . getTx ( id , ( ) => { const query = { head : this . head . id } ; if ( allowPending ) { query . pending = 'true' ; } return this . httpGet ( `transactions/${ id }` , query ) ; } ) ; } public getReceipt ( id ) { return this . cache . getReceipt ( id , ( ) => this . httpGet ( `transactions/${ id }/receipt` , { head : this . head . id } ) ) ; } public getAccount ( addr , revision ) { return this . cache . getAccount ( addr , revision , ( ) => this . httpGet ( `accounts/${ addr }` , { revision } ) ) ; } public getCode ( addr , revision ) { return this . cache . getTied ( `code-${ addr }` , revision , ( ) => this . httpGet ( `accounts/${ addr }/code` , { revision } ) ) ; } public getStorage ( addr , key , revision ) { return this . cache . getTied ( `storage-${ addr }-${ key }` , revision , ( ) => this . httpGet ( `accounts/${ addr }/storage/${ key }` , { revision } ) ) ; } public explain ( arg , revision , cacheHints ? ) { const cacheKey = `explain-${ blake2b256 ( JSON . stringify ( arg ) ) . toString ( 'hex' ) }` ; return this . cache . getTied ( cacheKey , revision , ( ) => this . httpPost ( 'accounts/*' , arg , { revision } ) , cacheHints ) ; } public filterEventLogs ( arg , cacheHints ? ) { const cacheKey = `event-${ blake2b256 ( JSON . stringify ( arg ) ) . toString ( 'hex' ) }` ; return this . cache . getTied ( cacheKey , this . head . id , ( ) => this . httpPost ( 'logs/event' , arg ) , cacheHints ) ; } public filterTransferLogs ( arg , cacheHints ? ) { const cacheKey = `transfer-${ blake2b256 ( JSON . stringify ( arg ) ) . toString ( 'hex' ) }` ; return this . cache . getTied ( cacheKey , this . head . id , ( ) => this . httpPost ( 'logs/transfer' , arg ) , cacheHints ) ; } public signTx ( msg , options ) { throw new Error ( 'not implemented' ) ; } public signCert ( msg , options ) { throw new Error ( 'not implemented' ) ; } protected mergeRequest ( req , ... keyParts ) { const key = JSON . stringify ( keyParts ) ; const pending = this . pendingRequests [ key ] ; if ( pending ) { return pending ; } return this . pendingRequests [ key ] = ( async ( ) => { try { return await req ( ) ; } finally { delete this . pendingRequests [ key ] ; } } ) ( ) ; } protected httpGet ( path , query ? ) { return this . mergeRequest ( ( ) => { return this . net . http ( 'GET' , path , { query , validateResponseHeader : this . headerValidator } ) ; } , path , query || '' ) ; } protected httpPost ( path , body , query ? ) { return this . mergeRequest ( ( ) => { return this . net . http ( 'POST' , path , { query , body , validateResponseHeader : this . headerValidator } ) ; } , path , query || '' , body || '' ) ; } private get headerValidator ( ) { return ( headers ) => { const xgid = headers [ 'x-genesis-id' ] ; if ( xgid && xgid !== this . genesis . id ) { throw new Error ( `responded 'x-genesis-id' not matched` ) ; } } ; } private emitNewHead ( ) { const resolvers = this . headResolvers ; this . headResolvers = [ ] ; resolvers . forEach ( r => r ( ) ) ; } private async headTrackerLoop ( ) { for ( ; ; ) { let attemptWs = false ; try { const best = await this . int . wrap < Connex . Thor . Block > ( this . httpGet ( 'blocks/best' ) ) ; if ( best . id !== this . head . id && best . number >= this . head . number ) { this . head = { id : best . id , number : best . number , timestamp : best . timestamp , parentID : best . parentID , txsFeatures : best . txsFeatures , gasLimit : best . gasLimit } ; this . cache . handleNewBlock ( this . head , undefined , best ) ; this . emitNewHead ( ) ; if ( Date . now ( ) - this . head . timestamp * 1000 < 60 * 1000 ) { attemptWs = true ; } } } catch ( err ) { if ( err instanceof InterruptedError ) { break ; } } if ( attemptWs ) { try { await this . trackWs ( ) ; } catch ( err ) { if ( err instanceof InterruptedError ) { break ; } } } try { await this . int . wrap ( sleep ( 8 * 1000 ) ) ; } catch { break ; } } } private async trackWs ( ) { const wsPath = `subscriptions/beat2?pos=${ this . head . parentID }` ; const wsr = this . net . openWebSocketReader ( wsPath ) ; try { for ( ; ; ) { const data = await this . int . wrap ( wsr . read ( ) ) ; const beat = JSON . parse ( data ) ; if ( ! beat . obsolete && beat . id !== this . head . id && beat . number >= this . head . number ) { this . head = { id : beat . id , number : beat . number , timestamp : beat . timestamp , parentID : beat . parentID , txsFeatures : beat . txsFeatures , gasLimit : beat . gasLimit } ; this . cache . handleNewBlock ( this . head , { k : beat . k , bits : beat . bloom } ) ; this . emitNewHead ( ) ; } } } finally { wsr . close ( ) ; } } } interface Beat2 { number : number ; id : string ; parentID : string ; timestamp : number ; gasLimit : number ; bloom : string ; k : number ; txsFeatures ? : number ; obsolete : boolean ; }