Skip to content

perf(spf): index the Tempo node pool once per batch - #1390

Open
mattsse wants to merge 1 commit into
mainfrom
mattsse/perf-spf-reuse-tempo-trie
Open

perf(spf): index the Tempo node pool once per batch#1390
mattsse wants to merge 1 commit into
mainfrom
mattsse/perf-spf-reuse-tempo-trie

Conversation

@mattsse

@mattsse mattsse commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

prove_zone_batch imports a Tempo checkpoint for every zone block, and each import rebuilt the sparse trie from the raw node pool, keccak-hashing and indexing every node again. The pool is fixed for the batch, so the Tempo side of replay was quadratic in batch length.

The pool is now hashed and indexed once in from_tempo_state_witness and shared as an Arc<B256Map<Bytes>>; each checkpoint only reveals the proof reachable from its own state root, and a checkpoint carrying the previous root reuses the revealed trie. Error behaviour is preserved: the initial header is still decoded before indexing, so InvalidTempoHeader still precedes DuplicateNodeHash, and MissingStateRootNode still leaves the reader inactive.

Measured with a throwaway harness replaying the Tempo side of prove_zone_batch over a pool with one distinct checkpoint root per block, dev profile: a 256-block batch with an 8257-node, 4.3 MB pool goes from 142 s to 255 ms, a 64-block batch with a 2065-node, 1.1 MB pool from 7.95 s to 64 ms. In an optimized build, indexing the 8257-node pool costs 11.2 ms, which is what every block paid before.

`prove_zone_batch` imports a Tempo checkpoint for every Zone block, and
`with_imported_checkpoint` rebuilt the sparse trie from the raw node pool
each time. Because `StatelessSparseTrie::new` starts by keccak-hashing and
indexing every node in the pool, a batch of N blocks re-hashed the entire
Tempo witness N times, making the Tempo side of replay quadratic in batch
length even though the pool is fixed for the whole batch.

The pool is now hashed and indexed once in `from_tempo_state_witness` and
shared as an `Arc<B256Map<Bytes>>`, so each checkpoint only walks the
proof reachable from its own state root. A checkpoint whose state root
equals the previous one also reuses the already revealed trie instead of
revealing it again.

Error behaviour is unchanged for the witnesses that can reach this code:
the initial header is still decoded before the pool is indexed, so
`InvalidTempoHeader` still precedes `DuplicateNodeHash`, and
`MissingStateRootNode` still maps to an inactive reader.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant