From 314e8a0030f93b6b94a17dfa2235e177066e6153 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 28 Sep 2023 10:03:27 -0300 Subject: [PATCH] chore: Check tree roots in world state sync (#2543) Checks that the resulting roots on the world state sync match the ones on the retrieved L2 block after applying all tree changes. This is not fixing any issues I could reproduce, but is meant to be an extra sanity check to fail loudly if we hit it. As a somewhat related change, this PR also forces the sequencer to wait on the L2 block and L1toL2 message sources to be synced before proceeding. --- .../src/sequencer/sequencer.ts | 2 ++ yarn-project/types/src/contract_data.ts | 6 ++++ yarn-project/types/src/l1_to_l2_message.ts | 6 ++++ yarn-project/types/src/logs/l2_logs_source.ts | 6 ++++ .../src/world-state-db/merkle_trees.ts | 33 ++++++++++++------- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 47ceb34799b..f4cbe839c76 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -262,6 +262,8 @@ export class Sequencer { const syncedBlocks = await Promise.all([ this.worldState.status().then((s: WorldStateStatus) => s.syncedToL2Block), this.p2pClient.getStatus().then(s => s.syncedToL2Block), + this.l2BlockSource.getBlockNumber(), + this.l1ToL2MessageSource.getBlockNumber(), ]); const min = Math.min(...syncedBlocks); return min >= this.lastPublishedBlock; diff --git a/yarn-project/types/src/contract_data.ts b/yarn-project/types/src/contract_data.ts index 933b8322b99..af62d0aa3f0 100644 --- a/yarn-project/types/src/contract_data.ts +++ b/yarn-project/types/src/contract_data.ts @@ -55,6 +55,12 @@ export interface ContractDataSource { * @returns The function's data. */ getPublicFunction(address: AztecAddress, selector: FunctionSelector): Promise; + + /** + * Gets the number of the latest L2 block processed by the implementation. + * @returns The number of the latest L2 block processed by the implementation. + */ + getBlockNumber(): Promise; } /** diff --git a/yarn-project/types/src/l1_to_l2_message.ts b/yarn-project/types/src/l1_to_l2_message.ts index bd4397d54b8..03493c09d29 100644 --- a/yarn-project/types/src/l1_to_l2_message.ts +++ b/yarn-project/types/src/l1_to_l2_message.ts @@ -21,6 +21,12 @@ export interface L1ToL2MessageSource { * @returns The confirmed L1 to L2 message (throws if not found) */ getConfirmedL1ToL2Message(messageKey: Fr): Promise; + + /** + * Gets the number of the latest L2 block processed by the implementation. + * @returns The number of the latest L2 block processed by the implementation. + */ + getBlockNumber(): Promise; } /** diff --git a/yarn-project/types/src/logs/l2_logs_source.ts b/yarn-project/types/src/logs/l2_logs_source.ts index 50cf9cb4ae2..a21d5ccedd3 100644 --- a/yarn-project/types/src/logs/l2_logs_source.ts +++ b/yarn-project/types/src/logs/l2_logs_source.ts @@ -26,4 +26,10 @@ export interface L2LogsSource { * @returns A promise signalling completion of the stop process. */ stop(): Promise; + + /** + * Gets the number of the latest L2 block processed by the implementation. + * @returns The number of the latest L2 block processed by the implementation. + */ + getBlockNumber(): Promise; } diff --git a/yarn-project/world-state/src/world-state-db/merkle_trees.ts b/yarn-project/world-state/src/world-state-db/merkle_trees.ts index 0d04eee180a..b335a0eb947 100644 --- a/yarn-project/world-state/src/world-state-db/merkle_trees.ts +++ b/yarn-project/world-state/src/world-state-db/merkle_trees.ts @@ -527,19 +527,19 @@ export class MerkleTrees implements MerkleTreeDb { * @param l2Block - The L2 block to handle. */ private async _handleL2Block(l2Block: L2Block) { + const treeRootWithIdPairs = [ + [l2Block.endContractTreeSnapshot.root, MerkleTreeId.CONTRACT_TREE], + [l2Block.endNullifierTreeSnapshot.root, MerkleTreeId.NULLIFIER_TREE], + [l2Block.endPrivateDataTreeSnapshot.root, MerkleTreeId.PRIVATE_DATA_TREE], + [l2Block.endPublicDataTreeRoot, MerkleTreeId.PUBLIC_DATA_TREE], + [l2Block.endL1ToL2MessagesTreeSnapshot.root, MerkleTreeId.L1_TO_L2_MESSAGES_TREE], + [l2Block.endHistoricBlocksTreeSnapshot.root, MerkleTreeId.BLOCKS_TREE], + ] as const; const compareRoot = (root: Fr, treeId: MerkleTreeId) => { const treeRoot = this.trees[treeId].getRoot(true); return treeRoot.equals(root.toBuffer()); }; - const rootChecks = [ - compareRoot(l2Block.endContractTreeSnapshot.root, MerkleTreeId.CONTRACT_TREE), - compareRoot(l2Block.endNullifierTreeSnapshot.root, MerkleTreeId.NULLIFIER_TREE), - compareRoot(l2Block.endPrivateDataTreeSnapshot.root, MerkleTreeId.PRIVATE_DATA_TREE), - compareRoot(l2Block.endPublicDataTreeRoot, MerkleTreeId.PUBLIC_DATA_TREE), - compareRoot(l2Block.endL1ToL2MessagesTreeSnapshot.root, MerkleTreeId.L1_TO_L2_MESSAGES_TREE), - compareRoot(l2Block.endHistoricBlocksTreeSnapshot.root, MerkleTreeId.BLOCKS_TREE), - ]; - const ourBlock = rootChecks.every(x => x); + const ourBlock = treeRootWithIdPairs.every(([root, id]) => compareRoot(root, id)); if (ourBlock) { this.log(`Block ${l2Block.number} is ours, committing world state..`); await this._commit(); @@ -582,9 +582,20 @@ export class MerkleTrees implements MerkleTreeDb { await this._commit(); } - for (const treeId of merkleTreeIds()) { + + for (const [root, treeId] of treeRootWithIdPairs) { + const treeName = MerkleTreeId[treeId]; const info = await this._getTreeInfo(treeId, false); - this.log(`Tree ${MerkleTreeId[treeId]} synched with size ${info.size} root ${info.root.toString('hex')}`); + const syncedStr = '0x' + info.root.toString('hex'); + const rootStr = root.toString(); + // Sanity check that the rebuilt trees match the roots published by the L2 block + if (!info.root.equals(root.toBuffer())) { + throw new Error( + `Synced tree root ${treeName} does not match published L2 block root: ${syncedStr} != ${rootStr}`, + ); + } else { + this.log(`Tree ${treeName} synched with size ${info.size} root ${rootStr}`); + } } } }