From f7b2216b0e8004430672892e358ff5a2bfc1a691 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 29 May 2025 15:00:26 -0500 Subject: [PATCH 1/7] GH-1492 Add is_child_of and child_of_pending_savanna_lib --- libraries/chain/fork_database.cpp | 41 +++++++++++++++++++ .../include/eosio/chain/fork_database.hpp | 14 +++++++ unittests/fork_db_tests.cpp | 21 ++++++++++ 3 files changed, 76 insertions(+) diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 8271e6be17..d34d97f0e6 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -106,6 +106,8 @@ namespace eosio::chain { void remove_impl( block_num_type block_num ); bsp_t head_impl(include_root_t include_root) const; bool set_pending_savanna_lib_id_impl(const block_id_type& id); + bool child_of_pending_savanna_lib_impl(const block_id_type& id) const; + bool is_child_of_impl(const block_id_type& ancestor, const block_id_type& descendant) const; branch_t fetch_branch_impl( const block_id_type& h, uint32_t trim_after_block_num ) const; block_branch_t fetch_block_branch_impl( const block_id_type& h, uint32_t trim_after_block_num ) const; branch_t fetch_branch_impl( const block_id_type& h, const block_id_type& b ) const; @@ -375,6 +377,45 @@ namespace eosio::chain { return false; } + template + bool fork_database_t::child_of_pending_savanna_lib( const block_id_type& id ) const { + std::lock_guard g( my->mtx ); + return my->child_of_pending_savanna_lib_impl(id); + } + + template + bool fork_database_impl::child_of_pending_savanna_lib_impl(const block_id_type& id) const { + if (pending_savanna_lib_id == id) + return true; + return is_child_of_impl(pending_savanna_lib_id, id); + } + + template + bool fork_database_t::is_child_of(const block_id_type& ancestor, const block_id_type& descendant) const { + std::lock_guard g( my->mtx ); + return my->is_child_of_impl(ancestor, descendant); + } + + template + bool fork_database_impl::is_child_of_impl(const block_id_type& ancestor, const block_id_type& descendant) const { + block_num_type ancestor_block_num = block_header::num_from_id(ancestor); + if (ancestor_block_num >= block_header::num_from_id(descendant)) + return false; + + auto i = index.find(descendant); + for (; i != index.end(); i = index.find((*i)->previous())) { + if ((*i)->previous() == ancestor) + return true; + if ((*i)->block_num() <= ancestor_block_num+1) + return false; + } + + // At this point descendant is not found in ancestor, but root has not been checked. + // However, root is either the ancestor or we can't make determination if descendant is a child because + // ancestor < root. Therefore, no reason to check root. + return false; + } + template eosio::chain::fork_database_t::branch_t fork_database_t::fetch_branch(const block_id_type& h, uint32_t trim_after_block_num) const { diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 7d31c73aab..2d27b8b96c 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -110,6 +110,20 @@ namespace eosio::chain { block_id_type pending_savanna_lib_id() const; bool set_pending_savanna_lib_id( const block_id_type& id ); + /** + * @return true if id is built on top of pending savanna lib or id == pending_savanna_lib + */ + bool child_of_pending_savanna_lib( const block_id_type& id ) const; + + /** + * @param ancestor the id of a possible ancestor block + * @param descendant the id of a possible descendant block + * @return false if either ancestor or descendant not found. + * true if any descendant->previous.. == ancestor. + * false if unable to find ancestor in any descendant->previous.. + */ + bool is_child_of(const block_id_type& ancestor, const block_id_type& descendant) const; + /** * Returns the sequence of block states resulting from trimming the branch from the * root block (exclusive) to the block with an id of `h` (inclusive) by removing any diff --git a/unittests/fork_db_tests.cpp b/unittests/fork_db_tests.cpp index 1556f5cc0c..554cee3455 100644 --- a/unittests/fork_db_tests.cpp +++ b/unittests/fork_db_tests.cpp @@ -228,4 +228,25 @@ BOOST_FIXTURE_TEST_CASE(validated_block_exists, generate_fork_db_state) try { } FC_LOG_AND_RETHROW(); +// test `fork_database_t::is_child_of() const` member +// ------------------------------------------------------------- +BOOST_FIXTURE_TEST_CASE(is_child_of, generate_fork_db_state) try { + + BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp14b->id(), bsp14b->id())); + BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp14b->id(), bsp13b->id())); + BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp14b->id(), bsp12b->id())); + BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp14b->id(), bsp11b->id())); + + BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(bsp13b->id(), bsp14b->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(bsp12b->id(), bsp14b->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(bsp11b->id(), bsp14b->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(root->id(), bsp11a->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(root->id(), bsp12a->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(root->id(), bsp14b->id())); + + BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp12b->id(), bsp13a->id())); + BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp11b->id(), bsp13a->id())); + +} FC_LOG_AND_RETHROW(); + BOOST_AUTO_TEST_SUITE_END() From f0846098c152d5d5fb3c1042f0e7982939d65cfc Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 29 May 2025 15:01:31 -0500 Subject: [PATCH 2/7] GH-1492 Add debug log of interrupt_timer interrupting a currently running timer --- libraries/chain/platform_timer_posix.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/chain/platform_timer_posix.cpp b/libraries/chain/platform_timer_posix.cpp index 45aa9c0201..c6338d67bb 100644 --- a/libraries/chain/platform_timer_posix.cpp +++ b/libraries/chain/platform_timer_posix.cpp @@ -84,6 +84,7 @@ void platform_timer::expire_now() { void platform_timer::interrupt_timer() { state_t expected = state_t::running; if (_state.compare_exchange_strong(expected, state_t::interrupted)) { + dlog("interrupted timer"); call_expiration_callback(); } } From 7b3304a84c2c5a2b51a224ae324a379cb35a65dd Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 29 May 2025 15:02:45 -0500 Subject: [PATCH 3/7] GH-1492 Add head_child_of_pending_lib --- libraries/chain/controller.cpp | 17 +++++++++++++++++ .../chain/include/eosio/chain/controller.hpp | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 49426ba223..5bc5e90dc5 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -4892,6 +4892,18 @@ struct controller_impl { return is_trx_transient ? nullptr : deep_mind_logger; } + bool head_child_of_pending_lib() const { + return fork_db_.apply( + [&](const fork_database_legacy_t& fork_db) -> bool { + // there is no pending lib in legacy + return true; + }, + [&](const fork_database_if_t& fork_db) -> bool { + return fork_db.child_of_pending_savanna_lib(chain_head.id()); + } + ); + } + void set_savanna_lib_id(const block_id_type& id) { fork_db_.apply_s([&](auto& fork_db) { fork_db.set_pending_savanna_lib_id(id); @@ -5581,6 +5593,11 @@ std::optional controller::pending_producer_block_id()const { return my->pending_producer_block_id(); } +bool controller::head_child_of_pending_lib() const { + return my->head_child_of_pending_lib(); +} + + void controller::set_savanna_lib_id(const block_id_type& id) { my->set_savanna_lib_id(id); } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 80246e7a58..302b0cefc4 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -339,6 +339,10 @@ namespace eosio::chain { // thread-safe qc_vote_metrics_t::fin_auth_set_t missing_votes(const block_id_type& id, const qc_t& qc) const; + // not thread-safe + bool head_child_of_pending_lib() const; + + // thread-safe void set_savanna_lib_id(const block_id_type& id); // thread-safe From 6c00b9ee885840743bfe1739acc967957393f63f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 29 May 2025 15:03:40 -0500 Subject: [PATCH 4/7] GH-1492 Add processing of blocks as a producer in start_block if head is not child of pending lib --- libraries/chain/controller.cpp | 10 +-- .../chain/include/eosio/chain/controller.hpp | 1 + plugins/producer_plugin/producer_plugin.cpp | 87 +++++++++++-------- 3 files changed, 56 insertions(+), 42 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 5bc5e90dc5..ad06b4ae81 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1432,7 +1432,7 @@ struct controller_impl { block_state_ptr prev = fork_db.get_block(legacy->previous(), include_root_t::yes); assert(prev); controller::apply_blocks_result r = apply_block(legacy, controller::block_status::complete, trx_meta_cache_lookup{}); - if( r == controller::apply_blocks_result::complete) { + if (r == controller::apply_blocks_result::complete || r == controller::apply_blocks_result::none) { fc::scoped_exit> e([&]{fork_db_.switch_to(fork_database::in_use_t::both);}); // irreversible apply was just done, calculate new_valid here instead of in transition_to_savanna() assert(legacy->action_mroot_savanna); @@ -1590,7 +1590,7 @@ struct controller_impl { const block_id_type new_lib_id = pending_lib_id(); const block_num_type new_lib_num = block_header::num_from_id(new_lib_id); - controller::apply_blocks_result result = controller::apply_blocks_result::complete; + controller::apply_blocks_result result = controller::apply_blocks_result::none; if( new_lib_num <= lib_num ) return result; @@ -1622,7 +1622,7 @@ struct controller_impl { for( auto bitr = branch.rbegin(); bitr != branch.rend() && should_process(*bitr); ++bitr ) { if (irreversible_mode()) { result = apply_irreversible_block(fork_db, *bitr); - if (result != controller::apply_blocks_result::complete) + if (result != controller::apply_blocks_result::complete && result != controller::apply_blocks_result::none) break; } @@ -4486,7 +4486,7 @@ struct controller_impl { controller::apply_blocks_result maybe_apply_blocks( const forked_callback_t& forked_cb, const trx_meta_cache_lookup& trx_lookup ) { - controller::apply_blocks_result result = controller::apply_blocks_result::complete; + controller::apply_blocks_result result = controller::apply_blocks_result::none; auto do_apply_blocks = [&](auto& fork_db) { auto new_head = fork_db.head(); // use best head if (!new_head) @@ -4619,7 +4619,7 @@ struct controller_impl { // irreversible can change even if block not applied to head, integrated qc can move LIB auto log_result = log_irreversible(); transition_to_savanna_if_needed(); - if (log_result != controller::apply_blocks_result::complete) + if (log_result != controller::apply_blocks_result::complete && log_result != controller::apply_blocks_result::none) result = log_result; }; diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 302b0cefc4..36c5614acc 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -247,6 +247,7 @@ namespace eosio::chain { /// Apply any blocks that are ready from the fork_db enum class apply_blocks_result { + none, // no blocks are currently available to process in forkdb complete, // all ready blocks in forkdb have been applied incomplete, // time limit reached, additional blocks may be available in forkdb to process paused // apply blocks currently paused diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 19a8baa5b5..d12fe7f019 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -926,14 +926,14 @@ class producer_plugin_impl : public std::enable_shared_from_this controller::apply_blocks_result { - try { - return chain.apply_blocks([this](const transaction_metadata_ptr& trx) { - fc_dlog(_trx_log, "adding forked trx ${id} to unapplied queue", ("id", trx->id())); - _unapplied_transactions.add_forked(trx); - }, - [this](const transaction_id_type& id) { return _unapplied_transactions.get_trx(id); }); - } catch (...) {} // errors logged in apply_blocks - return controller::apply_blocks_result::incomplete; - }; - - // producers need to be able to start producing on schedule, do not apply blocks as it might take a long time to apply - if (!is_configured_producer()) { - auto r = apply_blocks(); - if (r != controller::apply_blocks_result::complete) - return start_block_result::waiting_for_block; - } - - if (chain.should_terminate()) { - app().quit(); - return start_block_result::failed; - } - - _time_tracker.clear(); // make sure we start tracking block time after `apply_blocks()` - - block_handle head = chain.head(); + block_handle head = chain.head(); if (head.block_num() == chain.get_pause_at_block_num()) return start_block_result::waiting_for_block; @@ -2232,13 +2209,32 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { if (r != start_block_result::succeeded) return r; - if (is_configured_producer() && in_speculating_mode()) { - // if not producing right now, see if any blocks have come in that need to be applied - const block_id_type head_id = head.id(); - schedule_delayed_production_loop(weak_from_this(), _pending_block_deadline); // interrupt apply_blocks at deadline - apply_blocks(); - head = chain.head(); - if (head_id != head.id()) { // blocks were applied + auto process_pending_blocks = [&]() -> start_block_result { + auto apply_blocks = [&]() -> controller::apply_blocks_result { + try { + return chain.apply_blocks([this](const transaction_metadata_ptr& trx) { + fc_dlog(_trx_log, "adding forked trx ${id} to unapplied queue", ("id", trx->id())); + _unapplied_transactions.add_forked(trx); + }, + [this](const transaction_id_type& id) { return _unapplied_transactions.get_trx(id); }); + } catch (...) {} // errors logged in apply_blocks + return controller::apply_blocks_result::incomplete; + }; + + // producers need to be able to start producing on schedule, do not apply blocks as it might take a long time to apply + // unless head not a child of pending lib, as there is no reason ever to produce on a branch that is not a child of pending lib + while (in_speculating_mode() || !chain.head_child_of_pending_lib()) { + if (is_configured_producer()) + schedule_delayed_production_loop(weak_from_this(), _pending_block_deadline); // interrupt apply_blocks at deadline + + auto result = apply_blocks(); + if (result == controller::apply_blocks_result::none) + return start_block_result::succeeded; + + head = chain.head(); + if (head.block_num() == chain.get_pause_at_block_num()) + return start_block_result::waiting_for_block; + now = fc::time_point::now(); block_time = calculate_pending_block_time(); scheduled_producer = chain.head_active_producers(block_time).get_scheduled_producer(block_time); @@ -2246,9 +2242,26 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { r = determine_pending_block_mode(now, head, block_time, scheduled_producer); if (r != start_block_result::succeeded) return r; + + if (in_speculating_mode()) { + if (result != controller::apply_blocks_result::complete) // none checked above + return start_block_result::waiting_for_block; + return start_block_result::succeeded; + } } + return start_block_result::succeeded; + }; + r = process_pending_blocks(); + if (r != start_block_result::succeeded) + return r; + + if (chain.should_terminate()) { + app().quit(); + return start_block_result::failed; } + _time_tracker.clear(); // make sure we start tracking block time after `apply_blocks()` + const auto& preprocess_deadline = _pending_block_deadline; const block_num_type head_block_num = head.block_num(); From cded7e0f44afdfb1f5bdc1f6ca517d798600a707 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 6 Jun 2025 15:19:27 -0500 Subject: [PATCH 5/7] GH-1492 Use a struct with num_blocks_applied instead of none enum type --- libraries/chain/controller.cpp | 81 ++++++++++--------- .../chain/include/eosio/chain/controller.hpp | 15 ++-- libraries/testing/tester.cpp | 2 +- .../include/eosio/chain/plugin_interface.hpp | 2 +- .../eosio/producer_plugin/producer_plugin.hpp | 2 +- plugins/producer_plugin/producer_plugin.cpp | 26 +++--- 6 files changed, 68 insertions(+), 60 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index ad06b4ae81..45f1f11b4c 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1410,7 +1410,7 @@ struct controller_impl { // When in IRREVERSIBLE mode fork_db blocks are applied and marked valid when they become irreversible template - controller::apply_blocks_result apply_irreversible_block(ForkDB& fork_db, const BSP& bsp) { + controller::apply_blocks_result_t::status_t apply_irreversible_block(ForkDB& fork_db, const BSP& bsp) { if constexpr (std::is_same_v>) { // before transition to savanna return apply_block(bsp, controller::block_status::complete, trx_meta_cache_lookup{}); @@ -1426,13 +1426,13 @@ struct controller_impl { return apply_block(bsp, controller::block_status::complete, trx_meta_cache_lookup{}); } // only called during transition when not a proper savanna block - return fork_db_.apply_l([&](const auto& fork_db_l) { + return fork_db_.apply_l([&](const auto& fork_db_l) { block_state_legacy_ptr legacy = fork_db_l.get_block(bsp->id()); fork_db_.switch_to(fork_database::in_use_t::legacy); // apply block uses to know what types to create block_state_ptr prev = fork_db.get_block(legacy->previous(), include_root_t::yes); assert(prev); - controller::apply_blocks_result r = apply_block(legacy, controller::block_status::complete, trx_meta_cache_lookup{}); - if (r == controller::apply_blocks_result::complete || r == controller::apply_blocks_result::none) { + controller::apply_blocks_result_t::status_t r = apply_block(legacy, controller::block_status::complete, trx_meta_cache_lookup{}); + if (r == controller::apply_blocks_result_t::status_t::complete) { fc::scoped_exit> e([&]{fork_db_.switch_to(fork_database::in_use_t::both);}); // irreversible apply was just done, calculate new_valid here instead of in transition_to_savanna() assert(legacy->action_mroot_savanna); @@ -1550,7 +1550,7 @@ struct controller_impl { } } - controller::apply_blocks_result log_irreversible() { + controller::apply_blocks_result_t log_irreversible() { EOS_ASSERT( fork_db_has_root(), fork_database_exception, "fork database not properly initialized" ); const std::optional log_head_id = blog.head_id(); @@ -1590,14 +1590,14 @@ struct controller_impl { const block_id_type new_lib_id = pending_lib_id(); const block_num_type new_lib_num = block_header::num_from_id(new_lib_id); - controller::apply_blocks_result result = controller::apply_blocks_result::none; if( new_lib_num <= lib_num ) - return result; + return controller::apply_blocks_result_t{}; const fc::time_point start = fc::time_point::now(); - auto mark_branch_irreversible = [&, this](auto& fork_db) { + auto mark_branch_irreversible = [&, this](auto& fork_db) -> controller::apply_blocks_result_t { + controller::apply_blocks_result_t result; assert(!irreversible_mode() || fork_db.head()); const auto& head_id = irreversible_mode() ? fork_db.head()->id() : chain_head.id(); const auto head_num = block_header::num_from_id(head_id); @@ -1621,9 +1621,12 @@ struct controller_impl { for( auto bitr = branch.rbegin(); bitr != branch.rend() && should_process(*bitr); ++bitr ) { if (irreversible_mode()) { - result = apply_irreversible_block(fork_db, *bitr); - if (result != controller::apply_blocks_result::complete && result != controller::apply_blocks_result::none) + controller::apply_blocks_result_t::status_t r = apply_irreversible_block(fork_db, *bitr); + if (r != controller::apply_blocks_result_t::status_t::complete) { + result.status = r; break; + } + ++result.num_blocks_applied; } emit( irreversible_block, std::tie((*bitr)->block, (*bitr)->id()), __FILE__, __LINE__ ); @@ -1643,7 +1646,7 @@ struct controller_impl { // In irreversible mode, break every ~500ms to allow other tasks (e.g. get_info, SHiP) opportunity to run const bool more_blocks_to_process = bitr + 1 != branch.rend(); if (!replaying && more_blocks_to_process && fc::time_point::now() - start > fc::milliseconds(500)) { - result = controller::apply_blocks_result::incomplete; + result.status = controller::apply_blocks_result_t::status_t::incomplete; break; } } @@ -1672,11 +1675,11 @@ struct controller_impl { // delete branch in thread pool boost::asio::post( thread_pool.get_executor(), [branch{std::move(branch)}]() {} ); - }; - fork_db_.apply(mark_branch_irreversible); + return result; + }; - return result; + return fork_db_.apply(mark_branch_irreversible); } void initialize_blockchain_state(const genesis_state& genesis) { @@ -3793,16 +3796,16 @@ struct controller_impl { } template - controller::apply_blocks_result apply_block( const BSP& bsp, controller::block_status s, - const trx_meta_cache_lookup& trx_lookup ) { + controller::apply_blocks_result_t::status_t apply_block( const BSP& bsp, controller::block_status s, + const trx_meta_cache_lookup& trx_lookup ) { try { try { if (should_terminate()) { shutdown(); - return controller::apply_blocks_result::incomplete; + return controller::apply_blocks_result_t::status_t::incomplete; } if (should_pause()) { - return controller::apply_blocks_result::paused; + return controller::apply_blocks_result_t::status_t::paused; } auto start = fc::time_point::now(); // want to report total time of applying a block @@ -3959,7 +3962,7 @@ struct controller_impl { commit_block(s); - return controller::apply_blocks_result::complete; + return controller::apply_blocks_result_t::status_t::complete; } catch ( const std::bad_alloc& ) { throw; } catch ( const boost::interprocess::bad_alloc& ) { @@ -4448,7 +4451,7 @@ struct controller_impl { BSP bsp = std::make_shared(*head, b, protocol_features.get_protocol_feature_set(), validator, skip_validate_signee); - if (apply_block(bsp, controller::block_status::irreversible, trx_meta_cache_lookup{}) == controller::apply_blocks_result::complete) { + if (apply_block(bsp, controller::block_status::irreversible, trx_meta_cache_lookup{}) == controller::apply_blocks_result_t::status_t::complete) { // On replay, log_irreversible is not called and so no irreversible_block signal is emitted. // So emit it explicitly here. emit( irreversible_block, std::tie(bsp->block, bsp->id()), __FILE__, __LINE__ ); @@ -4464,7 +4467,7 @@ struct controller_impl { } FC_LOG_AND_RETHROW( ) } - controller::apply_blocks_result apply_blocks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup) { + controller::apply_blocks_result_t apply_blocks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup) { try { if( !irreversible_mode() ) { return maybe_apply_blocks( cb, trx_lookup ); @@ -4484,13 +4487,13 @@ struct controller_impl { } } - controller::apply_blocks_result maybe_apply_blocks( const forked_callback_t& forked_cb, const trx_meta_cache_lookup& trx_lookup ) + controller::apply_blocks_result_t maybe_apply_blocks( const forked_callback_t& forked_cb, const trx_meta_cache_lookup& trx_lookup ) { - controller::apply_blocks_result result = controller::apply_blocks_result::none; - auto do_apply_blocks = [&](auto& fork_db) { + auto do_apply_blocks = [&](auto& fork_db) -> controller::apply_blocks_result_t { + controller::apply_blocks_result_t result; auto new_head = fork_db.head(); // use best head if (!new_head) - return;// nothing to do, fork_db at root + return result;// nothing to do, fork_db at root auto [new_head_branch, old_head_branch] = fork_db.fetch_branch_from( new_head->id(), chain_head.id() ); bool switch_fork = !old_head_branch.empty(); @@ -4538,25 +4541,29 @@ struct controller_impl { auto except = std::exception_ptr{}; const auto& bsp = *ritr; try { - result = apply_block( bsp, bsp->is_valid() ? controller::block_status::validated - : controller::block_status::complete, trx_lookup ); + controller::apply_blocks_result_t::status_t r = + apply_block( bsp, bsp->is_valid() ? controller::block_status::validated + : controller::block_status::complete, trx_lookup ); + if (r == controller::apply_blocks_result_t::status_t::complete) + ++result.num_blocks_applied; + if (!switch_fork) { if (check_shutdown()) { shutdown(); - result = controller::apply_blocks_result::incomplete; // doesn't really matter since we are shutting down + result.status = controller::apply_blocks_result_t::status_t::incomplete; // doesn't really matter since we are shutting down break; } - if (result == controller::apply_blocks_result::complete) { + if (r == controller::apply_blocks_result_t::status_t::complete) { // Break every ~500ms to allow other tasks (e.g. get_info, SHiP) opportunity to run. User expected // to call apply_blocks again if this returns incomplete. const bool more_blocks_to_process = ritr + 1 != new_head_branch.rend(); if (!replaying && more_blocks_to_process && fc::time_point::now() - start_apply_blocks_loop > fc::milliseconds(500)) { - result = controller::apply_blocks_result::incomplete; + result.status = controller::apply_blocks_result_t::status_t::incomplete; break; } } } - if (result != controller::apply_blocks_result::complete) { + if (r != controller::apply_blocks_result_t::status_t::complete) { break; } } catch ( const std::bad_alloc& ) { @@ -4617,15 +4624,13 @@ struct controller_impl { } // irreversible can change even if block not applied to head, integrated qc can move LIB - auto log_result = log_irreversible(); + log_irreversible(); transition_to_savanna_if_needed(); - if (log_result != controller::apply_blocks_result::complete && log_result != controller::apply_blocks_result::none) - result = log_result; - }; - fork_db_.apply(do_apply_blocks); + return result; + }; - return result; + return fork_db_.apply(do_apply_blocks); } deque abort_block() { @@ -5429,7 +5434,7 @@ void controller::set_async_aggregation(async_t val) { my->async_aggregation = val; } -controller::apply_blocks_result controller::apply_blocks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup) { +controller::apply_blocks_result_t controller::apply_blocks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup) { validate_db_available_size(); return my->apply_blocks(cb, trx_lookup); } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 36c5614acc..5d971285fa 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -246,13 +246,16 @@ namespace eosio::chain { accepted_block_result accept_block( const block_id_type& id, const signed_block_ptr& b ) const; /// Apply any blocks that are ready from the fork_db - enum class apply_blocks_result { - none, // no blocks are currently available to process in forkdb - complete, // all ready blocks in forkdb have been applied - incomplete, // time limit reached, additional blocks may be available in forkdb to process - paused // apply blocks currently paused + struct apply_blocks_result_t { + enum class status_t { + complete, // all ready blocks in forkdb have been applied + incomplete, // time limit reached, additional blocks may be available in forkdb to process + paused // apply blocks currently paused + }; + status_t status = status_t::complete; + size_t num_blocks_applied = 0; }; - apply_blocks_result apply_blocks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup); + apply_blocks_result_t apply_blocks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup); boost::asio::io_context& get_thread_pool(); diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 96011aa39f..1fd3549205 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -465,7 +465,7 @@ namespace eosio::testing { } void base_tester::apply_blocks() { - while (control->apply_blocks( {}, {} ) == controller::apply_blocks_result::incomplete) + while (control->apply_blocks( {}, {} ).status == controller::apply_blocks_result_t::status_t::incomplete) ; } diff --git a/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp b/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp index a6da2398f6..407cda0756 100644 --- a/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp +++ b/plugins/chain_interface/include/eosio/chain/plugin_interface.hpp @@ -28,7 +28,7 @@ namespace eosio::chain::plugin_interface { namespace incoming { namespace methods { // synchronously push a block/trx to a single provider, block_state_legacy_ptr may be null - using block_sync = method_decl; + using block_sync = method_decl; using transaction_async = method_decl), first_provider_policy>; } } diff --git a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp index 938b966d6e..99671001bc 100644 --- a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp +++ b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp @@ -81,7 +81,7 @@ class producer_plugin : public appbase::plugin { virtual void plugin_shutdown(); void handle_sighup() override; - controller::apply_blocks_result on_incoming_block(); + controller::apply_blocks_result_t on_incoming_block(); struct pause_at_block_params { chain::block_num_type block_num{0}; // block height to pause block evaluation/production diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index d12fe7f019..09cbe3aa95 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -913,7 +913,7 @@ class producer_plugin_impl : public std::enable_shared_from_thison_incoming_block(); } @@ -2210,7 +2210,7 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { return r; auto process_pending_blocks = [&]() -> start_block_result { - auto apply_blocks = [&]() -> controller::apply_blocks_result { + auto apply_blocks = [&]() -> controller::apply_blocks_result_t { try { return chain.apply_blocks([this](const transaction_metadata_ptr& trx) { fc_dlog(_trx_log, "adding forked trx ${id} to unapplied queue", ("id", trx->id())); @@ -2218,7 +2218,7 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { }, [this](const transaction_id_type& id) { return _unapplied_transactions.get_trx(id); }); } catch (...) {} // errors logged in apply_blocks - return controller::apply_blocks_result::incomplete; + return controller::apply_blocks_result_t{.status = controller::apply_blocks_result_t::status_t::incomplete}; }; // producers need to be able to start producing on schedule, do not apply blocks as it might take a long time to apply @@ -2228,7 +2228,7 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { schedule_delayed_production_loop(weak_from_this(), _pending_block_deadline); // interrupt apply_blocks at deadline auto result = apply_blocks(); - if (result == controller::apply_blocks_result::none) + if (result.status == controller::apply_blocks_result_t::status_t::complete && result.num_blocks_applied == 0) return start_block_result::succeeded; head = chain.head(); @@ -2244,7 +2244,7 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { return r; if (in_speculating_mode()) { - if (result != controller::apply_blocks_result::complete) // none checked above + if (result.status != controller::apply_blocks_result_t::status_t::complete) // no block applied checked above return start_block_result::waiting_for_block; return start_block_result::succeeded; } @@ -3074,7 +3074,7 @@ void producer_plugin::process_blocks() { auto process_incoming_blocks = [this](auto self) -> void { try { auto r = on_incoming_block(); - if (r == controller::apply_blocks_result::incomplete) { + if (r.status == controller::apply_blocks_result_t::status_t::incomplete) { app().executor().post(handler_id::process_incoming_block, priority::medium, exec_queue::read_write, [self]() { self(self); }); From 6c571b247c4650a839cf13028bff67e5ea29bc60 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 6 Jun 2025 16:56:37 -0500 Subject: [PATCH 6/7] GH-1492 Work around compiler bug --- libraries/chain/controller.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 45f1f11b4c..aabe13b738 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1596,8 +1596,8 @@ struct controller_impl { const fc::time_point start = fc::time_point::now(); - auto mark_branch_irreversible = [&, this](auto& fork_db) -> controller::apply_blocks_result_t { - controller::apply_blocks_result_t result; + controller::apply_blocks_result_t result; + auto mark_branch_irreversible = [&, this](auto& fork_db) { assert(!irreversible_mode() || fork_db.head()); const auto& head_id = irreversible_mode() ? fork_db.head()->id() : chain_head.id(); const auto head_num = block_header::num_from_id(head_id); @@ -1675,11 +1675,11 @@ struct controller_impl { // delete branch in thread pool boost::asio::post( thread_pool.get_executor(), [branch{std::move(branch)}]() {} ); - - return result; }; - return fork_db_.apply(mark_branch_irreversible); + fork_db_.apply(mark_branch_irreversible); + + return result; } void initialize_blockchain_state(const genesis_state& genesis) { From 57cecf9c77d767f1448245365ce19c3b1fb62c6b Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 26 Jun 2025 10:59:34 -0500 Subject: [PATCH 7/7] GH-1492 Rename methods from child to descendant --- libraries/chain/controller.cpp | 8 +++--- libraries/chain/fork_database.cpp | 20 +++++++------- .../chain/include/eosio/chain/controller.hpp | 2 +- .../include/eosio/chain/fork_database.hpp | 4 +-- plugins/producer_plugin/producer_plugin.cpp | 2 +- unittests/fork_db_tests.cpp | 26 +++++++++---------- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index aabe13b738..aa5444b7d2 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -4897,14 +4897,14 @@ struct controller_impl { return is_trx_transient ? nullptr : deep_mind_logger; } - bool head_child_of_pending_lib() const { + bool is_head_descendant_of_pending_lib() const { return fork_db_.apply( [&](const fork_database_legacy_t& fork_db) -> bool { // there is no pending lib in legacy return true; }, [&](const fork_database_if_t& fork_db) -> bool { - return fork_db.child_of_pending_savanna_lib(chain_head.id()); + return fork_db.is_descendant_of_pending_savanna_lib(chain_head.id()); } ); } @@ -5598,8 +5598,8 @@ std::optional controller::pending_producer_block_id()const { return my->pending_producer_block_id(); } -bool controller::head_child_of_pending_lib() const { - return my->head_child_of_pending_lib(); +bool controller::is_head_descendant_of_pending_lib() const { + return my->is_head_descendant_of_pending_lib(); } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index d34d97f0e6..f1cb2664fc 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -106,8 +106,8 @@ namespace eosio::chain { void remove_impl( block_num_type block_num ); bsp_t head_impl(include_root_t include_root) const; bool set_pending_savanna_lib_id_impl(const block_id_type& id); - bool child_of_pending_savanna_lib_impl(const block_id_type& id) const; - bool is_child_of_impl(const block_id_type& ancestor, const block_id_type& descendant) const; + bool is_descendant_of_pending_savanna_lib_impl(const block_id_type& id) const; + bool is_descendant_of_impl(const block_id_type& ancestor, const block_id_type& descendant) const; branch_t fetch_branch_impl( const block_id_type& h, uint32_t trim_after_block_num ) const; block_branch_t fetch_block_branch_impl( const block_id_type& h, uint32_t trim_after_block_num ) const; branch_t fetch_branch_impl( const block_id_type& h, const block_id_type& b ) const; @@ -378,26 +378,26 @@ namespace eosio::chain { } template - bool fork_database_t::child_of_pending_savanna_lib( const block_id_type& id ) const { + bool fork_database_t::is_descendant_of_pending_savanna_lib( const block_id_type& id ) const { std::lock_guard g( my->mtx ); - return my->child_of_pending_savanna_lib_impl(id); + return my->is_descendant_of_pending_savanna_lib_impl(id); } template - bool fork_database_impl::child_of_pending_savanna_lib_impl(const block_id_type& id) const { + bool fork_database_impl::is_descendant_of_pending_savanna_lib_impl(const block_id_type& id) const { if (pending_savanna_lib_id == id) return true; - return is_child_of_impl(pending_savanna_lib_id, id); + return is_descendant_of_impl(pending_savanna_lib_id, id); } template - bool fork_database_t::is_child_of(const block_id_type& ancestor, const block_id_type& descendant) const { + bool fork_database_t::is_descendant_of(const block_id_type& ancestor, const block_id_type& descendant) const { std::lock_guard g( my->mtx ); - return my->is_child_of_impl(ancestor, descendant); + return my->is_descendant_of_impl(ancestor, descendant); } template - bool fork_database_impl::is_child_of_impl(const block_id_type& ancestor, const block_id_type& descendant) const { + bool fork_database_impl::is_descendant_of_impl(const block_id_type& ancestor, const block_id_type& descendant) const { block_num_type ancestor_block_num = block_header::num_from_id(ancestor); if (ancestor_block_num >= block_header::num_from_id(descendant)) return false; @@ -406,7 +406,7 @@ namespace eosio::chain { for (; i != index.end(); i = index.find((*i)->previous())) { if ((*i)->previous() == ancestor) return true; - if ((*i)->block_num() <= ancestor_block_num+1) + if ((*i)->block_num() <= ancestor_block_num+1) // +1 since comparison of previous() already done return false; } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 5d971285fa..f653b61db1 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -344,7 +344,7 @@ namespace eosio::chain { qc_vote_metrics_t::fin_auth_set_t missing_votes(const block_id_type& id, const qc_t& qc) const; // not thread-safe - bool head_child_of_pending_lib() const; + bool is_head_descendant_of_pending_lib() const; // thread-safe void set_savanna_lib_id(const block_id_type& id); diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 2d27b8b96c..87d4968624 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -113,7 +113,7 @@ namespace eosio::chain { /** * @return true if id is built on top of pending savanna lib or id == pending_savanna_lib */ - bool child_of_pending_savanna_lib( const block_id_type& id ) const; + bool is_descendant_of_pending_savanna_lib( const block_id_type& id ) const; /** * @param ancestor the id of a possible ancestor block @@ -122,7 +122,7 @@ namespace eosio::chain { * true if any descendant->previous.. == ancestor. * false if unable to find ancestor in any descendant->previous.. */ - bool is_child_of(const block_id_type& ancestor, const block_id_type& descendant) const; + bool is_descendant_of(const block_id_type& ancestor, const block_id_type& descendant) const; /** * Returns the sequence of block states resulting from trimming the branch from the diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 09cbe3aa95..90319d0b80 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -2223,7 +2223,7 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { // producers need to be able to start producing on schedule, do not apply blocks as it might take a long time to apply // unless head not a child of pending lib, as there is no reason ever to produce on a branch that is not a child of pending lib - while (in_speculating_mode() || !chain.head_child_of_pending_lib()) { + while (in_speculating_mode() || !chain.is_head_descendant_of_pending_lib()) { if (is_configured_producer()) schedule_delayed_production_loop(weak_from_this(), _pending_block_deadline); // interrupt apply_blocks at deadline diff --git a/unittests/fork_db_tests.cpp b/unittests/fork_db_tests.cpp index 554cee3455..57ec6d9b0d 100644 --- a/unittests/fork_db_tests.cpp +++ b/unittests/fork_db_tests.cpp @@ -228,24 +228,24 @@ BOOST_FIXTURE_TEST_CASE(validated_block_exists, generate_fork_db_state) try { } FC_LOG_AND_RETHROW(); -// test `fork_database_t::is_child_of() const` member +// test `fork_database_t::is_descendant_of() const` member // ------------------------------------------------------------- BOOST_FIXTURE_TEST_CASE(is_child_of, generate_fork_db_state) try { - BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp14b->id(), bsp14b->id())); - BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp14b->id(), bsp13b->id())); - BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp14b->id(), bsp12b->id())); - BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp14b->id(), bsp11b->id())); + BOOST_REQUIRE_EQUAL(false, fork_db.is_descendant_of(bsp14b->id(), bsp14b->id())); + BOOST_REQUIRE_EQUAL(false, fork_db.is_descendant_of(bsp14b->id(), bsp13b->id())); + BOOST_REQUIRE_EQUAL(false, fork_db.is_descendant_of(bsp14b->id(), bsp12b->id())); + BOOST_REQUIRE_EQUAL(false, fork_db.is_descendant_of(bsp14b->id(), bsp11b->id())); - BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(bsp13b->id(), bsp14b->id())); - BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(bsp12b->id(), bsp14b->id())); - BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(bsp11b->id(), bsp14b->id())); - BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(root->id(), bsp11a->id())); - BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(root->id(), bsp12a->id())); - BOOST_REQUIRE_EQUAL(true, fork_db.is_child_of(root->id(), bsp14b->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_descendant_of(bsp13b->id(), bsp14b->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_descendant_of(bsp12b->id(), bsp14b->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_descendant_of(bsp11b->id(), bsp14b->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_descendant_of(root->id(), bsp11a->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_descendant_of(root->id(), bsp12a->id())); + BOOST_REQUIRE_EQUAL(true, fork_db.is_descendant_of(root->id(), bsp14b->id())); - BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp12b->id(), bsp13a->id())); - BOOST_REQUIRE_EQUAL(false, fork_db.is_child_of(bsp11b->id(), bsp13a->id())); + BOOST_REQUIRE_EQUAL(false, fork_db.is_descendant_of(bsp12b->id(), bsp13a->id())); + BOOST_REQUIRE_EQUAL(false, fork_db.is_descendant_of(bsp11b->id(), bsp13a->id())); } FC_LOG_AND_RETHROW();