Skip to content

Commit

Permalink
chore: Check tree roots in world state sync (#2543)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
spalladino authored Sep 28, 2023
1 parent ecffc36 commit 314e8a0
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 11 deletions.
2 changes: 2 additions & 0 deletions yarn-project/sequencer-client/src/sequencer/sequencer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/types/src/contract_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ export interface ContractDataSource {
* @returns The function's data.
*/
getPublicFunction(address: AztecAddress, selector: FunctionSelector): Promise<EncodedContractFunction | undefined>;

/**
* 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<number>;
}

/**
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/types/src/l1_to_l2_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export interface L1ToL2MessageSource {
* @returns The confirmed L1 to L2 message (throws if not found)
*/
getConfirmedL1ToL2Message(messageKey: Fr): Promise<L1ToL2Message>;

/**
* 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<number>;
}

/**
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/types/src/logs/l2_logs_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,10 @@ export interface L2LogsSource {
* @returns A promise signalling completion of the stop process.
*/
stop(): Promise<void>;

/**
* 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<number>;
}
33 changes: 22 additions & 11 deletions yarn-project/world-state/src/world-state-db/merkle_trees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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}`);
}
}
}
}

0 comments on commit 314e8a0

Please sign in to comment.