Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -867,17 +867,17 @@ void ContentAddressedCachedTreeStore<LeafValueType>::advance_finalized_block(con
ReadTransactionPtr readTx = create_read_transaction();
get_meta(uncommittedMeta);
get_meta(committedMeta, *readTx, false);
// do nothing if the block is already finalized
if (committedMeta.finalizedBlockHeight >= blockNumber) {
return;
}
if (!dataStore_->read_block_data(blockNumber, blockPayload, *readTx)) {
throw std::runtime_error(format("Unable to advance finalized block: ",
blockNumber,
". Failed to read block data. Tree name: ",
forkConstantData_.name_));
}
}
// do nothing if the block is already finalized
if (committedMeta.finalizedBlockHeight >= blockNumber) {
return;
}

// can currently only finalize up to the unfinalized block height
if (committedMeta.finalizedBlockHeight > committedMeta.unfinalizedBlockHeight) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,20 @@ export class ServerWorldStateSynchronizer

private async handleChainFinalized(blockNumber: BlockNumber) {
this.log.verbose(`Finalized chain is now at block ${blockNumber}`);
// Clamp to the oldest block still available in world state. The finalized block number can
// jump backwards (e.g. when the finalization heuristic changes) and try to read block data
// that has already been pruned, which causes the native world state to throw.
const currentSummary = await this.merkleTreeDb.getStatusSummary();
if (blockNumber < currentSummary.oldestHistoricalBlock) {
this.log.warn(
`Finalized block ${blockNumber} is older than the oldest available block ${currentSummary.oldestHistoricalBlock}. ` +
`Clamping to oldest available block.`,
);
blockNumber = currentSummary.oldestHistoricalBlock;
}
if (blockNumber < 1) {
return;
}
const summary = await this.merkleTreeDb.setFinalized(blockNumber);
if (this.historyToKeep === undefined) {
return;
Expand Down
38 changes: 38 additions & 0 deletions yarn-project/world-state/src/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,44 @@ describe('world-state integration', () => {
await awaitSync(5, 4);
await expectSynchedToBlock(5, 4);
});

it('does not throw when finalized block jumps backwards past pruned blocks', async () => {
// Create 20 blocks and sync them all
await archiver.createBlocks(MAX_CHECKPOINT_COUNT);
await synchronizer.start();
await awaitSync(MAX_CHECKPOINT_COUNT);
await expectSynchedToBlock(MAX_CHECKPOINT_COUNT);

// Manually finalize to block 15 and prune historical blocks up to block 10
// to simulate world-state having pruned old data.
await db.setFinalized(BlockNumber(15));
await db.removeHistoricalBlocks(BlockNumber(10));

const summary = await db.getStatusSummary();
log.info(
`After manual finalize+prune: oldest=${summary.oldestHistoricalBlock}, finalized=${summary.finalizedBlockNumber}`,
);
expect(summary.oldestHistoricalBlock).toBe(10);
expect(summary.finalizedBlockNumber).toBe(15);

// Now simulate the scenario from PR #21597: finalized block jumps backwards
// to a block M that is older than oldestHistoricalBlock.
// This should NOT throw — the clamping logic should handle it.
const backwardsFinalized = BlockNumber(5);
log.info(
`Sending chain-finalized for block ${backwardsFinalized} (below oldest ${summary.oldestHistoricalBlock})`,
);
await expect(
synchronizer.handleBlockStreamEvent({
type: 'chain-finalized',
block: { number: backwardsFinalized, hash: '' },
}),
).resolves.not.toThrow();

// Finalized block should remain at 15 (unchanged by the backwards event)
const afterSummary = await db.getStatusSummary();
expect(afterSummary.finalizedBlockNumber).toBe(15);
});
});
});

Expand Down
Loading