Merged
Conversation
- Parallelize transaction and action merkle computation in assemble_block using post_async_task. - Modify extract_qc_data to use block_state's cached header_exts instead of re-parsing block header extensions, and use contains_extension/extract_extension for block extensions to avoid redundant validate_and_extract_extensions call.
libraries/chain/controller.cpp
Outdated
| return std::make_pair(calculate_merkle(trx_receipts), | ||
| calculate_merkle(action_receipts.digests_s)); | ||
| auto trx_f = post_async_task(ioc, [&]() { return calculate_merkle(trx_receipts); }); | ||
| auto act_f = post_async_task(ioc, [&]() { return calculate_merkle(action_receipts.digests_s); }); |
Contributor
There was a problem hiding this comment.
I think we should only do one of the branches on a new thread, and the other should be processed on the calling thread, to avoid the context switch.
- finish_next() called prev.make_block_ref() which dispatched to the base class block_header_state::make_block_ref(), recomputing compute_finality_digest() from scratch every block. This is expensive: O(n) SHA-256 hashes for the reversible blocks merkle, full serialization of header/core/policies for compute_base_digest(), and 3 additional SHA-256 rounds. All callers already have a block_state with the finality digest cached as strong_digest. Pass the parent's pre-computed block_ref through the call chain (next() -> finish_next()) instead of recomputing it. Also remove the now-unused block_header_state::make_block_ref() to avoid confusion with block_state::make_block_ref() which uses the cached digest.
During sync, transactions in blocks behind the LIB have already expired relative to the pending savanna LIB timestamp. Recording them in the dedup database is unnecessary since they can never appear in future blocks and would be immediately cleaned by clear_expired_input_transactions. Cache pending_savanna_lib_timestamp alongside the ID in fork_database, serialize it to the fork_db file, and expose it via controller::pending_lib_time(). In transaction_context::init_for_input_trx(), skip record_transaction() when the transaction's expiration is before the LIB timestamp.
jglanz
approved these changes
Feb 12, 2026
Collaborator
jglanz
left a comment
There was a problem hiding this comment.
Looks good, but as with the other PR, please have @brianjohnson5972 take a quick peek as he has historical context
brianjohnson5972
approved these changes
Feb 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Parallelize transaction and action merkle computation in assemble_block using post_async_task.
Modify extract_qc_data to use block_state's cached header_exts instead of re-parsing block header extensions, and use contains_extension/extract_extension for block extensions to avoid redundant validate_and_extract_extensions call.
Avoid get_cpu_limit lookup when not needed
Avoid redundant compute_finality_digest in block_header_state::next
Skip record_transaction for finalized transactions during sync
During sync, transactions in blocks behind the pending savanna LIB have
already expired. Recording them in the dedup database is unnecessary
since they can never appear in future blocks and would be immediately
cleaned by clear_expired_input_transactions.
Cache pending_savanna_lib_timestamp alongside the ID in fork_database,
expose it via controller::pending_lib_time(), and skip record_transaction()
when the transaction's expiration is before the LIB timestamp.
Snapshot interaction
During sync, pending_savanna_lib in the fork database can be far ahead
of chain_head because blocks are added to fork_db (advancing pending LIB
via QC claims) before being applied to chain state. For example, a syncing
node may have pending_savanna_lib at block 3000 while chain_head is still
at block 1000. This means the dedup database will be missing entries for
transactions with expiration between chain_head's timestamp and
pending_savanna_lib's timestamp.
If a snapshot is taken at chain_head=1000 in this state, the dedup DB in
the snapshot will not contain those entries. This was analyzed and
determined to be a non-issue:
fork database with pending_savanna_lib at the root. As blocks are
re-synced from the network, all transactions are recorded correctly
during the new sync.
block 1000, there are no blocks beyond 1000 in the network, so
pending_savanna_lib cannot have advanced to 3000 in the first place.
The scenario where pending_savanna_lib is far ahead of chain_head only
occurs when syncing from a live network — in which case the chain will
continue building on top of the pending LIB, and a restarting node
will always need to re-sync those blocks anyway.