From 5df73f3b6268bae38e357e1f73850eafe54a56a5 Mon Sep 17 00:00:00 2001 From: Vicky Chen Date: Thu, 13 Aug 2026 12:28:20 -0400 Subject: [PATCH 1/2] [runloop] allow for dual-db commit in runloop_monad_ethblocks where secondary_db is optional --- .../runloop/runloop_monad_ethblocks.cpp | 53 ++++++++++++++----- .../runloop/runloop_monad_ethblocks.hpp | 3 +- cmd/monad/main.cpp | 35 ++++++------ 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/category/execution/runloop/runloop_monad_ethblocks.cpp b/category/execution/runloop/runloop_monad_ethblocks.cpp index 0afed6c069..ffd1f427e4 100644 --- a/category/execution/runloop/runloop_monad_ethblocks.cpp +++ b/category/execution/runloop/runloop_monad_ethblocks.cpp @@ -119,7 +119,7 @@ void get_block_with_retry( template requires is_monad_trait_v Result process_monad_block( - MonadChain const &chain, Db &db, vm::VM &vm, + MonadChain const &chain, Db &db, Db *const secondary_db, vm::VM &vm, BlockHashBufferFinalized &block_hash_buffer, fiber::PriorityPool &priority_pool, Block &block, bytes32_t const &block_id, bytes32_t const &parent_block_id, bool const enable_tracing, @@ -210,11 +210,15 @@ Result process_monad_block( // Core execution: transaction-level EVM execution that tracks state // changes but does not commit them db.set_block_and_prefix(block.header.number - 1, parent_block_id); + if (secondary_db != nullptr) { + secondary_db->set_block_and_prefix( + block.header.number - 1, parent_block_id); + } block.header.parent_hash = to_bytes(keccak256(rlp::encode_block_header(db.read_eth_header()))); BlockMetrics block_metrics; - BlockState block_state(db, vm); + BlockState block_state(db, vm, secondary_db); record_block_marker_event(exec_recorder, MONAD_EXEC_BLOCK_PERF_EVM_ENTER); BOOST_OUTCOME_TRY( auto const receipts, @@ -247,7 +251,7 @@ Result process_monad_block( .call_frames = call_frames, .ommers = block.ommers, .withdrawals = block.withdrawals}; - commit_block(db, nullptr, block_id, block.header, *state, anc); + commit_block(db, secondary_db, block_id, block.header, *state, anc); [[maybe_unused]] auto const commit_time = std::chrono::duration_cast( @@ -268,12 +272,32 @@ Result process_monad_block( // block hash to append to the circular hash buffer db.finalize(block.header.number, block_id); db.update_verified_block(block.header.number); + if (secondary_db != nullptr) { + secondary_db->finalize(block.header.number, block_id); + secondary_db->update_verified_block(block.header.number); + } exec_output.eth_block_hash = to_bytes(keccak256(rlp::encode_block_header(exec_output.eth_header))); block_hash_buffer.set( exec_output.eth_header.number, exec_output.eth_block_hash); (void)record_block_result(exec_recorder, exec_output); + if (secondary_db != nullptr) { + LOG_INFO( + "block={}, block_id={} state_root primary={} secondary={}", + block.header.number, + block_id, + db.state_root(), + secondary_db->state_root()); + } + else { + LOG_INFO( + "block={}, block_id={} state_root primary={}", + block.header.number, + block_id, + db.state_root()); + } + // Emit the block metrics log line [[maybe_unused]] auto const block_time = std::chrono::duration_cast( @@ -321,7 +345,7 @@ Result> runloop_monad_ethblocks( fiber::PriorityPool &priority_pool, uint64_t &finalized_block_num, uint64_t const end_block_num, sig_atomic_t const volatile &stop, bool const enable_tracing, std::chrono::seconds const block_db_timeout, - ExecutionEventRecorder *const exec_recorder) + ExecutionEventRecorder *const exec_recorder, Db *const secondary_db) { uint64_t const batch_size = end_block_num == std::numeric_limits::max() ? 1 : 1000; @@ -414,14 +438,18 @@ Result> runloop_monad_ethblocks( // matches the TrieDbImpl backing `db`). If the // replay crosses the mip-8 cutoff in either direction, the // encoding the caller picked at startup no longer matches what - // this block's revision expects. - MONAD_ASSERT_PRINTF( - mip_8_active(rev) == db.is_page_encoded(), - "monad revision %d at block %lu crosses mip-8 cutoff " - "but db was opened with page_encoded=%d", - rev, - block.header.number, - db.is_page_encoded()); + // this block's revision expects. With a secondary db both + // encodings are present (commit_block asserts slot primary + + // page secondary), so any revision is fine. + if (secondary_db == nullptr) { + MONAD_ASSERT_PRINTF( + mip_8_active(rev) == db.is_page_encoded(), + "monad revision %d at block %lu crosses mip-8 cutoff " + "but db was opened with page_encoded=%d", + rev, + block.header.number, + db.is_page_encoded()); + } ankerl::unordered_dense::segmented_set
senders_and_authorities; BOOST_OUTCOME_TRY([&] { @@ -429,6 +457,7 @@ Result> runloop_monad_ethblocks( process_monad_block, chain, db, + secondary_db, vm, block_hash_buffer, priority_pool, diff --git a/category/execution/runloop/runloop_monad_ethblocks.hpp b/category/execution/runloop/runloop_monad_ethblocks.hpp index 1849f4cb1c..6d501d6e32 100644 --- a/category/execution/runloop/runloop_monad_ethblocks.hpp +++ b/category/execution/runloop/runloop_monad_ethblocks.hpp @@ -43,6 +43,7 @@ Result> runloop_monad_ethblocks( MonadChain const &, std::filesystem::path const &, Db &, vm::VM &, BlockHashBufferFinalized &, fiber::PriorityPool &, uint64_t &, uint64_t, sig_atomic_t const volatile &, bool enable_tracing, - std::chrono::seconds block_db_timeout, ExecutionEventRecorder *); + std::chrono::seconds block_db_timeout, ExecutionEventRecorder *, + Db *secondary_db); MONAD_NAMESPACE_END diff --git a/cmd/monad/main.cpp b/cmd/monad/main.cpp index e3e41a6e78..2fca28bcfe 100644 --- a/cmd/monad/main.cpp +++ b/cmd/monad/main.cpp @@ -479,7 +479,21 @@ try { chain_rlp_path); case CHAIN_CONFIG_MONAD_DEVNET: case CHAIN_CONFIG_MONAD_TESTNET: - case CHAIN_CONFIG_MONAD_MAINNET: + case CHAIN_CONFIG_MONAD_MAINNET: { + std::optional secondary_db; + std::optional secondary_triedb; + if (raw_db.timeline_active(monad::mpt::timeline_id::secondary)) { + secondary_db = raw_db.open_secondary_timeline(); + MONAD_ASSERT(secondary_db.has_value()); + secondary_triedb.emplace(*secondary_db); + MONAD_ASSERT( + secondary_triedb->is_page_encoded(), + "secondary timeline must be page-encoded"); + MONAD_ASSERT( + !db.is_page_encoded(), + "primary must be slot-encoded when the secondary " + "timeline is active"); + } if (as_eth_blocks) { return runloop_monad_ethblocks( dynamic_cast(*chain), @@ -493,7 +507,9 @@ try { stop, trace_calls, block_db_timeout, - exec_recorder); + exec_recorder, + secondary_triedb.has_value() ? &*secondary_triedb + : nullptr); } else { // TODO: Remove this check once dual-db is deprecated. @@ -502,8 +518,7 @@ try { if (chain_config == CHAIN_CONFIG_MONAD_TESTNET || chain_config == CHAIN_CONFIG_MONAD_MAINNET) { bool const primary_is_page = db.is_page_encoded(); - bool const secondary_active = raw_db.timeline_active( - monad::mpt::timeline_id::secondary); + bool const secondary_active = secondary_triedb.has_value(); MONAD_ASSERT_PRINTF( primary_is_page || secondary_active, "live monad requires a page-encoded timeline " @@ -516,17 +531,6 @@ try { secondary_active); } - std::optional secondary_db; - std::optional secondary_triedb; - if (raw_db.timeline_active( - monad::mpt::timeline_id::secondary)) { - secondary_db = raw_db.open_secondary_timeline(); - MONAD_ASSERT(secondary_db.has_value()); - secondary_triedb.emplace(*secondary_db); - MONAD_ASSERT( - secondary_triedb->is_page_encoded(), - "secondary timeline must be page-encoded"); - } return runloop_monad( dynamic_cast(*chain), block_db_path, @@ -544,6 +548,7 @@ try { : nullptr); } } + } MONAD_ABORT_PRINTF("Unsupported chain"); }(); From daf6e0d7b0b782c4c1529b640b80f26f0b476979 Mon Sep 17 00:00:00 2001 From: Vicky Chen Date: Thu, 13 Aug 2026 12:29:22 -0400 Subject: [PATCH 2/2] [runloop] consolidate dual-db mirroring with for_each_db --- .../monad/db/commit_block_migration.hpp | 10 ++++ category/execution/runloop/runloop_monad.cpp | 60 +++++++------------ .../runloop/runloop_monad_ethblocks.cpp | 41 +++++-------- 3 files changed, 47 insertions(+), 64 deletions(-) diff --git a/category/execution/monad/db/commit_block_migration.hpp b/category/execution/monad/db/commit_block_migration.hpp index 95bcb1689b..59378ecd07 100644 --- a/category/execution/monad/db/commit_block_migration.hpp +++ b/category/execution/monad/db/commit_block_migration.hpp @@ -55,4 +55,14 @@ void commit_block( BlockHeader const &header, StateDeltas const &state, BlockCommitAncillaries const &anc); +// apply f to both dbs that are present +template +void for_each_db(Db &db, Db *const secondary_db, F &&f) +{ + f(db); + if (secondary_db != nullptr) { + f(*secondary_db); + } +} + MONAD_NAMESPACE_END diff --git a/category/execution/runloop/runloop_monad.cpp b/category/execution/runloop/runloop_monad.cpp index 7f06a612cd..f1a194fbcb 100644 --- a/category/execution/runloop/runloop_monad.cpp +++ b/category/execution/runloop/runloop_monad.cpp @@ -282,14 +282,11 @@ Result propose_block( // Core execution: transaction-level EVM execution that tracks state // changes but does not commit them - db.set_block_and_prefix( - block.header.number - 1, - is_first_block ? bytes32_t{} : consensus_header.parent_id()); - if (secondary_db != nullptr) { - secondary_db->set_block_and_prefix( + for_each_db(db, secondary_db, [&](Db &d) { + d.set_block_and_prefix( block.header.number - 1, is_first_block ? bytes32_t{} : consensus_header.parent_id()); - } + }); block.header.parent_hash = to_bytes(keccak256(rlp::encode_block_header(db.read_eth_header()))); @@ -368,21 +365,14 @@ Result propose_block( // Dual-db migration: log both timelines' state roots (slot primary vs // page secondary) so a divergence is visible per block. - if (secondary_db != nullptr) { - LOG_INFO( - "block={}, block_id={} state_root primary={} secondary={}", - block.header.number, - block_id, - db.state_root(), - secondary_db->state_root()); - } - else { - LOG_INFO( - "block={}, block_id={} state_root primary={}", - block.header.number, - block_id, - db.state_root()); - } + LOG_INFO( + "block={}, block_id={} state_root primary={}{}", + block.header.number, + block_id, + db.state_root(), + secondary_db != nullptr + ? fmt::format(" secondary={}", secondary_db->state_root()) + : std::string{}); // Emit the block metrics log line [[maybe_unused]] auto const block_time = @@ -668,11 +658,9 @@ Result> runloop_monad( auto const &header) -> Result> { auto const block_time_start = std::chrono::steady_clock::now(); - db.update_voted_metadata(header.seqno - 1, header.parent_id()); - if (secondary_db != nullptr) { - secondary_db->update_voted_metadata( - header.seqno - 1, header.parent_id()); - } + for_each_db(db, secondary_db, [&](Db &d) { + d.update_voted_metadata(header.seqno - 1, header.parent_id()); + }); record_block_qc(exec_recorder, header, last_finalized_block_number); uint64_t const block_number = header.execution_inputs.number; @@ -733,10 +721,9 @@ Result> runloop_monad( BlockExecOutput const exec_output, record_block_result(exec_recorder, propose_dispatch())); - db.update_proposed_metadata(header.seqno, block_id); - if (secondary_db != nullptr) { - secondary_db->update_proposed_metadata(header.seqno, block_id); - } + for_each_db(db, secondary_db, [&](Db &d) { + d.update_proposed_metadata(header.seqno, block_id); + }); log_tps( block_number, @@ -761,20 +748,17 @@ Result> runloop_monad( "Processing finalization for block {} with block_id {}", block, block_id); - db.finalize(block, block_id); - if (secondary_db != nullptr) { - secondary_db->finalize(block, block_id); - } + for_each_db( + db, secondary_db, [&](Db &d) { d.finalize(block, block_id); }); block_hash_chain.finalize(block_id); record_block_finalized(exec_recorder, block_id, block); finalized_block_num = block; if (!verified_blocks.empty() && verified_blocks.back() != mpt::INVALID_BLOCK_NUM) { - db.update_verified_block(verified_blocks.back()); - if (secondary_db != nullptr) { - secondary_db->update_verified_block(verified_blocks.back()); - } + for_each_db(db, secondary_db, [&](Db &d) { + d.update_verified_block(verified_blocks.back()); + }); } record_block_verified(exec_recorder, verified_blocks); } diff --git a/category/execution/runloop/runloop_monad_ethblocks.cpp b/category/execution/runloop/runloop_monad_ethblocks.cpp index ffd1f427e4..f6135790b1 100644 --- a/category/execution/runloop/runloop_monad_ethblocks.cpp +++ b/category/execution/runloop/runloop_monad_ethblocks.cpp @@ -209,11 +209,9 @@ Result process_monad_block( // Core execution: transaction-level EVM execution that tracks state // changes but does not commit them - db.set_block_and_prefix(block.header.number - 1, parent_block_id); - if (secondary_db != nullptr) { - secondary_db->set_block_and_prefix( - block.header.number - 1, parent_block_id); - } + for_each_db(db, secondary_db, [&](Db &d) { + d.set_block_and_prefix(block.header.number - 1, parent_block_id); + }); block.header.parent_hash = to_bytes(keccak256(rlp::encode_block_header(db.read_eth_header()))); @@ -270,33 +268,24 @@ Result process_monad_block( // Commit prologue: database finalization, computation of the Ethereum // block hash to append to the circular hash buffer - db.finalize(block.header.number, block_id); - db.update_verified_block(block.header.number); - if (secondary_db != nullptr) { - secondary_db->finalize(block.header.number, block_id); - secondary_db->update_verified_block(block.header.number); - } + for_each_db(db, secondary_db, [&](Db &d) { + d.finalize(block.header.number, block_id); + d.update_verified_block(block.header.number); + }); exec_output.eth_block_hash = to_bytes(keccak256(rlp::encode_block_header(exec_output.eth_header))); block_hash_buffer.set( exec_output.eth_header.number, exec_output.eth_block_hash); (void)record_block_result(exec_recorder, exec_output); - if (secondary_db != nullptr) { - LOG_INFO( - "block={}, block_id={} state_root primary={} secondary={}", - block.header.number, - block_id, - db.state_root(), - secondary_db->state_root()); - } - else { - LOG_INFO( - "block={}, block_id={} state_root primary={}", - block.header.number, - block_id, - db.state_root()); - } + LOG_INFO( + "block={}, block_id={} state_root primary={}{}", + block.header.number, + block_id, + db.state_root(), + secondary_db != nullptr + ? fmt::format(" secondary={}", secondary_db->state_root()) + : std::string{}); // Emit the block metrics log line [[maybe_unused]] auto const block_time =