import { DB } from '@pigi/core-db' ; import { getLogger , logError } from '@pigi/core-utils' ; import { Contract } from 'ethers' ; import { abiEncodeTransition , parseTransitionFromABI , } from '../common/serialization' ; import { RollupBlock , RollupBlockSubmitter } from '../types' ; const log = getLogger ( 'rollup-block-submitter' ) ; export class DefaultRollupBlockSubmitter implements RollupBlockSubmitter { public static readonly LAST_CONFIRMED_KEY : Buffer = Buffer . from ( 'last_confirmed' ) ; public static readonly LAST_SUBMITTED_KEY : Buffer = Buffer . from ( 'last_submitted' ) ; public static readonly LAST_QUEUED_KEY : Buffer = Buffer . from ( 'last_queued' ) ; private lastSubmitted : number ; private lastConfirmed : number ; private lastQueued : number ; private blockQueue : RollupBlock [ ] ; public static async create ( db , rollupContract ) { const submitter = new DefaultRollupBlockSubmitter ( db , rollupContract ) ; await submitter . init ( ) ; return submitter ; } private constructor ( private db , private readonly rollupContract ) { this . blockQueue = [ ] ; } private async init ( ) { const [ lastSubmittedBuffer , lastConfirmedBuffer , lastQueuedBuffer , ] = await Promise . all ( [ this . db . get ( DefaultRollupBlockSubmitter . LAST_SUBMITTED_KEY ) , this . db . get ( DefaultRollupBlockSubmitter . LAST_CONFIRMED_KEY ) , this . db . get ( DefaultRollupBlockSubmitter . LAST_QUEUED_KEY ) , ] ) ; this . lastSubmitted = ! ! lastSubmittedBuffer ? parseInt ( lastSubmittedBuffer . toString ( ) , 10 ) : 0 ; this . lastConfirmed = ! ! lastConfirmedBuffer ? parseInt ( lastConfirmedBuffer . toString ( ) , 10 ) : 0 ; this . lastQueued = ! ! lastQueuedBuffer ? parseInt ( lastQueuedBuffer . toString ( ) , 10 ) : 0 ; if ( this . lastSubmitted === this . lastConfirmed && this . lastConfirmed === this . lastQueued ) { return ; } if ( this . lastConfirmed !== this . lastQueued ) { let i = this . lastConfirmed + 1 ; const promises = [ ] ; for ( ; i <= this . lastQueued ; i ++ ) { promises . push ( this . db . get ( DefaultRollupBlockSubmitter . getBlockKey ( i ) ) ) ; } const blocks = await Promise . all ( promises ) ; this . blockQueue = blocks . map ( ( x ) => DefaultRollupBlockSubmitter . deserializeRollupBlockFromStorage ( x ) ) ; } await this . trySubmitNextBlock ( ) ; } public async submitBlock ( rollupBlock ) { if ( rollupBlock . blockNumber <= this . lastQueued ) { log . error ( `submitBlock(...) called on old block. Last Queued: ${ this . lastQueued }, received: ${ JSON . stringify ( rollupBlock ) }` ) ; return ; } log . info ( `Queueing rollup block: ${ JSON . stringify ( rollupBlock ) }}` ) ; this . blockQueue . push ( rollupBlock ) ; await this . db . put ( DefaultRollupBlockSubmitter . getBlockKey ( rollupBlock . blockNumber ) , DefaultRollupBlockSubmitter . serializeRollupBlockForStorage ( rollupBlock ) ) ; this . lastQueued = rollupBlock . blockNumber ; await this . db . put ( DefaultRollupBlockSubmitter . LAST_QUEUED_KEY , this . numberToBuffer ( this . lastQueued ) ) ; await this . trySubmitNextBlock ( ) ; } public async handleNewRollupBlock ( rollupBlockNumber ) { if ( ! this . blockQueue . length ) { log . error ( `Received block when no blocks pending. Block #: ${ JSON . stringify ( rollupBlockNumber ) }` ) ; return ; } if ( rollupBlockNumber === this . blockQueue [ 0 ] . blockNumber ) { log . info ( `Received confirmation for block ${ rollupBlockNumber }!` ) ; this . blockQueue . shift ( ) ; this . lastConfirmed = rollupBlockNumber ; await this . db . put ( DefaultRollupBlockSubmitter . LAST_CONFIRMED_KEY , this . numberToBuffer ( this . lastConfirmed ) ) ; if ( this . lastSubmitted < this . lastConfirmed ) { this . lastSubmitted = rollupBlockNumber ; await this . db . put ( DefaultRollupBlockSubmitter . LAST_SUBMITTED_KEY , this . numberToBuffer ( this . lastSubmitted ) ) ; } await this . trySubmitNextBlock ( ) ; } else { log . error ( `Received confirmation for future block ${ rollupBlockNumber }! First in queue is ${ this . blockQueue [ 0 ] . blockNumber }.` ) ; } } private async trySubmitNextBlock ( ) { if ( this . lastSubmitted > this . lastConfirmed || this . lastSubmitted >= this . lastQueued || ! this . blockQueue . length ) { if ( ! this . blockQueue . length ) { log . info ( `No blocks queued for submission.` ) ; } else { log . debug ( `Next block queued but not submitted because block ${ this . lastSubmitted } was submitted but not yet confirmed.` ) ; } return ; } const block = this . blockQueue [ 0 ] ; log . info ( `Submitting block number ${ block . blockNumber }: ${ JSON . stringify ( block ) }.` ) ; try { const receipt = await this . rollupContract . submitBlock ( DefaultRollupBlockSubmitter . serializeRollupBlockForSubmission ( block ) ) ; } catch ( e ) { logError ( log , `Error submitting rollup block: ${ JSON . stringify ( block ) }` , e ) ; throw e ; } this . lastSubmitted = block . blockNumber ; await this . db . put ( DefaultRollupBlockSubmitter . LAST_SUBMITTED_KEY , this . numberToBuffer ( this . lastSubmitted ) ) ; } public static serializeRollupBlockForSubmission ( block ) { return block . transitions . map ( ( x ) => abiEncodeTransition ( x ) ) ; } public static serializeRollupBlockForStorage ( rollupBlock ) { const encodedTransitions = rollupBlock . transitions . map ( ( x ) => abiEncodeTransition ( x ) ) ; return Buffer . from ( `${ rollupBlock . blockNumber . toString ( 10 ) }|${ JSON . stringify ( encodedTransitions ) }` ) ; } public static deserializeRollupBlockFromStorage ( rollupBlockBuffer ) { const [ blockNumber , json ] = rollupBlockBuffer . toString ( ) . split ( '|' ) ; return { blockNumber : parseInt ( blockNumber , 10 ) , transitions : JSON . parse ( json ) . map ( ( x ) => parseTransitionFromABI ( x ) ) , } ; } public static getBlockKey ( blockNumber ) { return Buffer . from ( `BLOCK_${ blockNumber . toString ( ) }` ) ; } private numberToBuffer ( num ) { return Buffer . from ( num . toString ( 10 ) ) ; } /***********
     * GETTERS *
     ***********/ public getLastSubmitted ( ) { return this . lastSubmitted ; } public getLastConfirmed ( ) { return this . lastConfirmed ; } public getLastQueued ( ) { return this . lastQueued ; } }