11import { EpochCache } from '@aztec/epoch-cache' ;
2- import { BlockNumber , CheckpointNumber , EpochNumber } from '@aztec/foundation/branded-types' ;
3- import { merge , pick } from '@aztec/foundation/collection' ;
2+ import { BlockNumber , EpochNumber } from '@aztec/foundation/branded-types' ;
3+ import { chunkBy , merge , pick } from '@aztec/foundation/collection' ;
44import type { Fr } from '@aztec/foundation/curves/bn254' ;
55import { type Logger , createLogger } from '@aztec/foundation/log' ;
66import {
@@ -12,6 +12,7 @@ import {
1212} from '@aztec/stdlib/block' ;
1313import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers' ;
1414import type {
15+ ICheckpointBlockBuilder ,
1516 ICheckpointsBuilder ,
1617 ITxProvider ,
1718 MerkleTreeWriteOperations ,
@@ -106,7 +107,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
106107 { blocks : epochBlocks . map ( b => b . toBlockInfo ( ) ) } ,
107108 ) ;
108109
109- await this . validateBlocks ( epochBlocks ) ;
110+ await this . validateBlocks ( epochBlocks , epochNumber ) ;
110111 this . log . info ( `Pruned epoch ${ epochNumber } was valid. Want to slash committee for not having it proven.` ) ;
111112 await this . emitSlashForEpoch ( OffenseType . VALID_EPOCH_PRUNED , epochNumber ) ;
112113 } catch ( error ) {
@@ -121,45 +122,52 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
121122 }
122123 }
123124
124- public async validateBlocks ( blocks : L2Block [ ] ) : Promise < void > {
125+ public async validateBlocks ( blocks : L2Block [ ] , epochNumber : EpochNumber ) : Promise < void > {
125126 if ( blocks . length === 0 ) {
126127 return ;
127128 }
128129
129- let previousCheckpointOutHashes : Fr [ ] = [ ] ;
130- const fork = await this . checkpointsBuilder . getFork ( BlockNumber ( blocks [ 0 ] . header . globalVariables . blockNumber - 1 ) ) ;
130+ // Sort blocks by block number and group by checkpoint
131+ const sortedBlocks = [ ...blocks ] . sort ( ( a , b ) => a . number - b . number ) ;
132+ const blocksByCheckpoint = chunkBy ( sortedBlocks , b => b . checkpointNumber ) ;
133+
134+ // Get prior checkpoints in the epoch (in case this was a partial prune) to extract the out hashes
135+ const priorCheckpointOutHashes = ( await this . l2BlockSource . getCheckpointsForEpoch ( epochNumber ) )
136+ . filter ( c => c . number < sortedBlocks [ 0 ] . checkpointNumber )
137+ . map ( c => c . getCheckpointOutHash ( ) ) ;
138+ let previousCheckpointOutHashes : Fr [ ] = [ ...priorCheckpointOutHashes ] ;
139+
140+ const fork = await this . checkpointsBuilder . getFork (
141+ BlockNumber ( sortedBlocks [ 0 ] . header . globalVariables . blockNumber - 1 ) ,
142+ ) ;
131143 try {
132- for ( const block of blocks ) {
133- await this . validateBlock ( block , previousCheckpointOutHashes , fork ) ;
144+ for ( const checkpointBlocks of blocksByCheckpoint ) {
145+ await this . validateCheckpoint ( checkpointBlocks , previousCheckpointOutHashes , fork ) ;
134146
135- // TODO(mbps): This assumes one block per checkpoint, which is only true for now.
136- const checkpointOutHash = computeCheckpointOutHash ( [ block . body . txEffects . map ( tx => tx . l2ToL1Msgs ) ] ) ;
147+ // Compute checkpoint out hash from all blocks in this checkpoint
148+ const checkpointOutHash = computeCheckpointOutHash (
149+ checkpointBlocks . map ( b => b . body . txEffects . map ( tx => tx . l2ToL1Msgs ) ) ,
150+ ) ;
137151 previousCheckpointOutHashes = [ ...previousCheckpointOutHashes , checkpointOutHash ] ;
138152 }
139153 } finally {
140154 await fork . close ( ) ;
141155 }
142156 }
143157
144- public async validateBlock (
145- blockFromL1 : L2Block ,
158+ private async validateCheckpoint (
159+ checkpointBlocks : L2Block [ ] ,
146160 previousCheckpointOutHashes : Fr [ ] ,
147161 fork : MerkleTreeWriteOperations ,
148162 ) : Promise < void > {
149- this . log . debug ( `Validating pruned block ${ blockFromL1 . header . globalVariables . blockNumber } ` ) ;
150- const txHashes = blockFromL1 . body . txEffects . map ( txEffect => txEffect . txHash ) ;
151- // We load txs from the mempool directly, since the TxCollector running in the background has already been
152- // trying to fetch them from nodes or via reqresp. If we haven't managed to collect them by now,
153- // it's likely that they are not available in the network at all.
154- const { txs, missingTxs } = await this . txProvider . getAvailableTxs ( txHashes ) ;
155-
156- if ( missingTxs && missingTxs . length > 0 ) {
157- throw new TransactionsNotAvailableError ( missingTxs ) ;
158- }
163+ const checkpointNumber = checkpointBlocks [ 0 ] . checkpointNumber ;
164+ this . log . debug ( `Validating pruned checkpoint ${ checkpointNumber } with ${ checkpointBlocks . length } blocks` ) ;
159165
160- const checkpointNumber = CheckpointNumber . fromBlockNumber ( blockFromL1 . number ) ;
166+ // Get L1ToL2Messages once for the entire checkpoint
161167 const l1ToL2Messages = await this . l1ToL2MessageSource . getL1ToL2Messages ( checkpointNumber ) ;
162- const gv = blockFromL1 . header . globalVariables ;
168+
169+ // Build checkpoint constants from first block's global variables
170+ const gv = checkpointBlocks [ 0 ] . header . globalVariables ;
163171 const constants : CheckpointGlobalVariables = {
164172 chainId : gv . chainId ,
165173 version : gv . version ,
@@ -169,7 +177,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
169177 gasFees : gv . gasFees ,
170178 } ;
171179
172- // Use checkpoint builder to validate the block
180+ // Start checkpoint builder once for all blocks in this checkpoint
173181 const checkpointBuilder = await this . checkpointsBuilder . startCheckpoint (
174182 checkpointNumber ,
175183 constants ,
@@ -179,6 +187,28 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter
179187 this . log . getBindings ( ) ,
180188 ) ;
181189
190+ // Validate all blocks in the checkpoint sequentially
191+ for ( const block of checkpointBlocks ) {
192+ await this . validateBlockInCheckpoint ( block , checkpointBuilder ) ;
193+ }
194+ }
195+
196+ private async validateBlockInCheckpoint (
197+ blockFromL1 : L2Block ,
198+ checkpointBuilder : ICheckpointBlockBuilder ,
199+ ) : Promise < void > {
200+ this . log . debug ( `Validating pruned block ${ blockFromL1 . header . globalVariables . blockNumber } ` ) ;
201+ const txHashes = blockFromL1 . body . txEffects . map ( txEffect => txEffect . txHash ) ;
202+ // We load txs from the mempool directly, since the TxCollector running in the background has already been
203+ // trying to fetch them from nodes or via reqresp. If we haven't managed to collect them by now,
204+ // it's likely that they are not available in the network at all.
205+ const { txs, missingTxs } = await this . txProvider . getAvailableTxs ( txHashes ) ;
206+
207+ if ( missingTxs && missingTxs . length > 0 ) {
208+ throw new TransactionsNotAvailableError ( missingTxs ) ;
209+ }
210+
211+ const gv = blockFromL1 . header . globalVariables ;
182212 const { block, failedTxs, numTxs } = await checkpointBuilder . buildBlock ( txs , gv . blockNumber , gv . timestamp , { } ) ;
183213
184214 if ( numTxs !== txs . length ) {
0 commit comments