@@ -120,7 +120,6 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
120120
121121 private l1BlockNumber : bigint | undefined ;
122122 private l1Timestamp : bigint | undefined ;
123- private pendingChainValidationStatus : ValidateBlockResult = { valid : true } ;
124123 private initialSyncComplete : boolean = false ;
125124
126125 public readonly tracer : Tracer ;
@@ -342,7 +341,8 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
342341
343342 // ********** Events that are processed per L2 block **********
344343 if ( currentL1BlockNumber > blocksSynchedTo ) {
345- // First we retrieve new L2 blocks
344+ // First we retrieve new L2 blocks and store them in the DB. This will also update the
345+ // pending chain validation status, proven block number, and synched L1 block number.
346346 const rollupStatus = await this . handleL2blocks ( blocksSynchedTo , currentL1BlockNumber ) ;
347347 // Then we prune the current epoch if it'd reorg on next submission.
348348 // Note that we don't do this before retrieving L2 blocks because we may need to retrieve
@@ -355,21 +355,11 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
355355 currentL1Timestamp ,
356356 ) ;
357357
358- // Update the pending chain validation status with the last block validation result.
359- // Again, we only update if validation status changed, so in a sequence of invalid blocks
360- // we keep track of the first invalid block so we can invalidate that one if needed.
361- if (
362- rollupStatus . validationResult &&
363- rollupStatus . validationResult ?. valid !== this . pendingChainValidationStatus . valid
364- ) {
365- this . pendingChainValidationStatus = rollupStatus . validationResult ;
366- }
367-
368358 // And lastly we check if we are missing any L2 blocks behind us due to a possible L1 reorg.
369359 // We only do this if rollup cant prune on the next submission. Otherwise we will end up
370360 // re-syncing the blocks we have just unwound above. We also dont do this if the last block is invalid,
371361 // since the archiver will rightfully refuse to sync up to it.
372- if ( ! rollupCanPrune && this . pendingChainValidationStatus . valid ) {
362+ if ( ! rollupCanPrune && rollupStatus . validationResult ? .valid ) {
373363 await this . checkForNewBlocksBeforeL1SyncPoint ( rollupStatus , blocksSynchedTo , currentL1BlockNumber ) ;
374364 }
375365
@@ -623,14 +613,15 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
623613
624614 private async handleL2blocks ( blocksSynchedTo : bigint , currentL1BlockNumber : bigint ) {
625615 const localPendingBlockNumber = await this . getBlockNumber ( ) ;
616+ const initialValidationResult : ValidateBlockResult | undefined = await this . store . getPendingChainValidationStatus ( ) ;
626617 const [ provenBlockNumber , provenArchive , pendingBlockNumber , pendingArchive , archiveForLocalPendingBlockNumber ] =
627618 await this . rollup . status ( BigInt ( localPendingBlockNumber ) , { blockNumber : currentL1BlockNumber } ) ;
628619 const rollupStatus = {
629620 provenBlockNumber : Number ( provenBlockNumber ) ,
630621 provenArchive,
631622 pendingBlockNumber : Number ( pendingBlockNumber ) ,
632623 pendingArchive,
633- validationResult : undefined as ValidateBlockResult | undefined ,
624+ validationResult : initialValidationResult ,
634625 } ;
635626 this . log . trace ( `Retrieved rollup status at current L1 block ${ currentL1BlockNumber } .` , {
636627 localPendingBlockNumber,
@@ -809,7 +800,15 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
809800
810801 // Only update the validation result if it has changed, so we can keep track of the first invalid block
811802 // in case there is a sequence of more than one invalid block, as we need to invalidate the first one.
812- if ( rollupStatus . validationResult ?. valid !== validationResult . valid ) {
803+ // There is an exception though: if an invalid block is invalidated and replaced with another invalid block,
804+ // we need to update the validation result, since we need to be able to invalidate the new one.
805+ // See test 'chain progresses if an invalid block is invalidated with an invalid one' for more info.
806+ if (
807+ rollupStatus . validationResult ?. valid !== validationResult . valid ||
808+ ( ! rollupStatus . validationResult . valid &&
809+ ! validationResult . valid &&
810+ rollupStatus . validationResult . block . blockNumber === validationResult . block . blockNumber )
811+ ) {
813812 rollupStatus . validationResult = validationResult ;
814813 }
815814
@@ -828,6 +827,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
828827
829828 // We keep consuming blocks if we find an invalid one, since we do not listen for BlockInvalidated events
830829 // We just pretend the invalid ones are not there and keep consuming the next blocks
830+ // Note that this breaks if the committee ever attests to a descendant of an invalid block
831831 continue ;
832832 }
833833
@@ -841,7 +841,9 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
841841 }
842842
843843 try {
844- const [ processDuration ] = await elapsed ( ( ) => this . store . addBlocks ( validBlocks ) ) ;
844+ const updatedValidationResult =
845+ rollupStatus . validationResult === initialValidationResult ? undefined : rollupStatus . validationResult ;
846+ const [ processDuration ] = await elapsed ( ( ) => this . store . addBlocks ( validBlocks , updatedValidationResult ) ) ;
845847 this . instrumentation . processNewBlocks (
846848 processDuration / validBlocks . length ,
847849 validBlocks . map ( b => b . block ) ,
@@ -1228,12 +1230,12 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
12281230 return this . store . getDebugFunctionName ( address , selector ) ;
12291231 }
12301232
1231- getPendingChainValidationStatus ( ) : Promise < ValidateBlockResult > {
1232- return Promise . resolve ( this . pendingChainValidationStatus ) ;
1233+ async getPendingChainValidationStatus ( ) : Promise < ValidateBlockResult > {
1234+ return ( await this . store . getPendingChainValidationStatus ( ) ) ?? { valid : true } ;
12331235 }
12341236
12351237 isPendingChainInvalid ( ) : Promise < boolean > {
1236- return Promise . resolve ( this . pendingChainValidationStatus . valid === false ) ;
1238+ return this . getPendingChainValidationStatus ( ) . then ( status => ! status . valid ) ;
12371239 }
12381240
12391241 async getL2Tips ( ) : Promise < L2Tips > {
@@ -1351,6 +1353,7 @@ export class ArchiverStoreHelper
13511353 | 'backupTo'
13521354 | 'close'
13531355 | 'transactionAsync'
1356+ | 'addBlocks'
13541357 >
13551358{
13561359 #log = createLogger ( 'archiver:block-helper' ) ;
@@ -1493,13 +1496,16 @@ export class ArchiverStoreHelper
14931496 return true ;
14941497 }
14951498
1496- public addBlocks ( blocks : PublishedL2Block [ ] ) : Promise < boolean > {
1499+ public addBlocks ( blocks : PublishedL2Block [ ] , pendingChainValidationStatus ?: ValidateBlockResult ) : Promise < boolean > {
14971500 // Add the blocks to the store. Store will throw if the blocks are not in order, there are gaps,
14981501 // or if the previous block is not in the store.
14991502 return this . store . transactionAsync ( async ( ) => {
15001503 await this . store . addBlocks ( blocks ) ;
15011504
15021505 const opResults = await Promise . all ( [
1506+ // Update the pending chain validation status if provided
1507+ pendingChainValidationStatus && this . store . setPendingChainValidationStatus ( pendingChainValidationStatus ) ,
1508+ // Add any logs emitted during the retrieved blocks
15031509 this . store . addLogs ( blocks . map ( block => block . block ) ) ,
15041510 // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
15051511 ...blocks . map ( async block => {
@@ -1539,6 +1545,8 @@ export class ArchiverStoreHelper
15391545 const blocks = await this . getPublishedBlocks ( from - blocksToUnwind + 1 , blocksToUnwind ) ;
15401546
15411547 const opResults = await Promise . all ( [
1548+ // Prune rolls back to the last proven block, which is by definition valid
1549+ this . store . setPendingChainValidationStatus ( { valid : true } ) ,
15421550 // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
15431551 ...blocks . map ( async block => {
15441552 const contractClassLogs = block . block . body . txEffects . flatMap ( txEffect => txEffect . contractClassLogs ) ;
@@ -1656,4 +1664,11 @@ export class ArchiverStoreHelper
16561664 getLastL1ToL2Message ( ) : Promise < InboxMessage | undefined > {
16571665 return this . store . getLastL1ToL2Message ( ) ;
16581666 }
1667+ getPendingChainValidationStatus ( ) : Promise < ValidateBlockResult | undefined > {
1668+ return this . store . getPendingChainValidationStatus ( ) ;
1669+ }
1670+ setPendingChainValidationStatus ( status : ValidateBlockResult | undefined ) : Promise < void > {
1671+ this . #log. debug ( `Setting pending chain validation status to valid ${ status ?. valid } ` , status ) ;
1672+ return this . store . setPendingChainValidationStatus ( status ) ;
1673+ }
16591674}
0 commit comments