Skip to content

Commit d4afda5

Browse files
committed
fix(fast-inbox): resolve message-sent L1 block timestamps by hash (A-1379)
A message's bucket timestamp must come from the same fork as the message itself. Resolving the emitting L1 block by number returns whatever block sits at that height when the lookup runs, so a reorg between the log query and the timestamp fetch silently stores a cross-fork timestamp. Resolving by the log's block hash pins the fork and turns that race into a failed lookup, which the archiver's L1 synchronizer retries on its next poll.
1 parent 3630ec2 commit d4afda5

1 file changed

Lines changed: 15 additions & 13 deletions

File tree

  • yarn-project/ethereum/src/contracts

yarn-project/ethereum/src/contracts/inbox.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ export class InboxContract {
8888
const logs = (await this.inbox.getEvents.MessageSent({}, { fromBlock, toBlock })).filter(
8989
log => log.blockNumber! >= fromBlock && log.blockNumber! <= toBlock,
9090
);
91-
const timestamps = await this.getBlockTimestamps(logs.map(log => log.blockNumber!));
92-
return logs.map(log => this.mapMessageSentLog(log, timestamps.get(log.blockNumber!)!));
91+
const timestamps = await this.getBlockTimestamps(logs.map(log => log.blockHash!));
92+
return logs.map(log => this.mapMessageSentLog(log, timestamps.get(log.blockHash!)!));
9393
}
9494

9595
/** Fetches MessageSent events for a specific message hash around a specific block. */
@@ -105,22 +105,24 @@ export class InboxContract {
105105
if (!log) {
106106
return log as unknown as MessageSentLog;
107107
}
108-
const [timestamp] = (await this.getBlockTimestamps([log.blockNumber!])).values();
108+
const [timestamp] = (await this.getBlockTimestamps([log.blockHash!])).values();
109109
return this.mapMessageSentLog(log, timestamp);
110110
}
111111

112112
/**
113-
* Fetches the timestamp of each distinct L1 block number, so each MessageSent log can carry its bucket key.
114-
* Fetched with bounded concurrency to keep a large sync batch from fanning out unbounded RPC requests. Blocks
115-
* are resolved by number rather than hash so a concurrent L1 reorg does not throw; a resulting cross-fork
116-
* timestamp is transient and re-corrected by the archiver's rolling-hash reorg detection on the next sync.
113+
* Fetches the timestamp of each distinct L1 block, so each MessageSent log can carry its bucket key. Blocks are
114+
* resolved by hash, which pins them to the same fork the logs were read from: resolving by number would silently
115+
* return the same-height block of another fork if the chain reorgs between the log query and this lookup, storing a
116+
* timestamp that never applied to the message. By hash, such a reorg fails the lookup instead, and the caller
117+
* retries against the reorged chain. Fetched with bounded concurrency to keep a large sync batch from fanning out
118+
* unbounded RPC requests.
117119
*/
118-
private async getBlockTimestamps(blockNumbers: bigint[]): Promise<Map<bigint, bigint>> {
119-
const uniqueBlockNumbers = [...new Set(blockNumbers)];
120-
const timestamps = new Map<bigint, bigint>();
121-
await asyncPool(10, uniqueBlockNumbers, async blockNumber => {
122-
const block = await this.client.getBlock({ blockNumber, includeTransactions: false });
123-
timestamps.set(blockNumber, block.timestamp);
120+
private async getBlockTimestamps(blockHashes: Hex[]): Promise<Map<Hex, bigint>> {
121+
const uniqueBlockHashes = [...new Set(blockHashes)];
122+
const timestamps = new Map<Hex, bigint>();
123+
await asyncPool(10, uniqueBlockHashes, async blockHash => {
124+
const block = await this.client.getBlock({ blockHash, includeTransactions: false });
125+
timestamps.set(blockHash, block.timestamp);
124126
});
125127
return timestamps;
126128
}

0 commit comments

Comments
 (0)