diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 241708d817..4d2fff374c 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -997,8 +997,8 @@ struct controller_impl { } template - typename ForkDB::bsp_t fork_db_head(const ForkDB& forkdb, bool irreversible_mode) const { - if (irreversible_mode) { + typename ForkDB::bsp_t fork_db_head_or_pending(const ForkDB& forkdb) const { + if (irreversible_mode()) { // When in IRREVERSIBLE mode fork_db blocks are marked valid when they become irreversible so that // fork_db.head() returns irreversible block // Use pending_head since this method should return the chain head and not last irreversible. @@ -1010,17 +1010,17 @@ struct controller_impl { uint32_t fork_db_head_block_num() const { return fork_db.apply( - [&](const auto& forkdb) { return fork_db_head(forkdb, irreversible_mode())->block_num(); }); + [&](const auto& forkdb) { return fork_db_head_or_pending(forkdb)->block_num(); }); } block_id_type fork_db_head_block_id() const { return fork_db.apply( - [&](const auto& forkdb) { return fork_db_head(forkdb, irreversible_mode())->id(); }); + [&](const auto& forkdb) { return fork_db_head_or_pending(forkdb)->id(); }); } uint32_t fork_db_head_irreversible_blocknum() const { return fork_db.apply( - [&](const auto& forkdb) { return fork_db_head(forkdb, irreversible_mode())->irreversible_blocknum(); }); + [&](const auto& forkdb) { return fork_db_head_or_pending(forkdb)->irreversible_blocknum(); }); } // --------------- access fork_db root ---------------------------------------------------------------------- @@ -1259,6 +1259,56 @@ struct controller_impl { } } + template + void apply_irreversible_block(ForkDB& forkdb, const BSP& bsp) { + if (read_mode != db_read_mode::IRREVERSIBLE) + return; + controller::block_report br; + if constexpr (std::is_same_v>) { + // before transition to savanna + apply_block(br, bsp, controller::block_status::complete, trx_meta_cache_lookup{}); + } else { + assert(bsp->block); + if (bsp->block->is_proper_svnn_block()) { + apply_l(chain_head, [&](const auto&) { + // if chain_head is legacy, update to non-legacy chain_head, this is needed so that the correct block_state is created in apply_block + block_state_ptr prev = forkdb.get_block(bsp->previous(), include_root_t::yes); + assert(prev); + chain_head = block_handle{prev}; + }); + apply_block(br, bsp, controller::block_status::complete, trx_meta_cache_lookup{}); + } else { + // only called during transition when not a proper savanna block + fork_db.apply_l([&](const auto& forkdb_l) { + block_state_legacy_ptr legacy = forkdb_l.get_block(bsp->id()); + fork_db.switch_to(fork_database::in_use_t::legacy); // apply block uses to know what types to create + fc::scoped_exit> e([&]{fork_db.switch_to(fork_database::in_use_t::both);}); + apply_block(br, legacy, controller::block_status::complete, trx_meta_cache_lookup{}); + // irreversible apply was just done, calculate new_valid here instead of in transition_to_savanna() + assert(legacy->action_receipt_digests_savanna); + block_state_ptr prev = forkdb.get_block(legacy->previous(), include_root_t::yes); + assert(prev); + transition_add_to_savanna_fork_db(forkdb, legacy, bsp, prev); + }); + } + } + } + + void transition_add_to_savanna_fork_db(fork_database_if_t& forkdb, + const block_state_legacy_ptr& legacy, const block_state_ptr& new_bsp, + const block_state_ptr& prev) { + // legacy_branch is from head, all will be validated unless irreversible_mode(), + // IRREVERSIBLE applies (validates) blocks when irreversible, new_valid will be done after apply in log_irreversible + assert(read_mode == db_read_mode::IRREVERSIBLE || legacy->action_receipt_digests_savanna); + if (legacy->action_receipt_digests_savanna) { + auto digests = *legacy->action_receipt_digests_savanna; + auto action_mroot = calculate_merkle(std::move(digests)); + // Create the valid structure for producing + new_bsp->valid = prev->new_valid(*new_bsp, action_mroot); + } + forkdb.add(new_bsp, legacy->is_valid() ? mark_valid_t::yes : mark_valid_t::no, ignore_duplicate_t::yes); + } + void transition_to_savanna() { assert(chain_head.header().contains_header_extension(instant_finality_extension::extension_id())); // copy head branch branch from legacy forkdb legacy to savanna forkdb @@ -1266,7 +1316,7 @@ struct controller_impl { block_state_legacy_ptr legacy_root; fork_db.apply_l([&](const auto& forkdb) { legacy_root = forkdb.root(); - legacy_branch = forkdb.fetch_branch(forkdb.head()->id()); + legacy_branch = forkdb.fetch_branch(fork_db_head_or_pending(forkdb)->id()); }); assert(!!legacy_root); @@ -1276,6 +1326,7 @@ struct controller_impl { fork_db.switch_from_legacy(new_root); fork_db.apply_s([&](auto& forkdb) { block_state_ptr prev = forkdb.root(); + assert(prev); for (auto bitr = legacy_branch.rbegin(); bitr != legacy_branch.rend(); ++bitr) { const bool skip_validate_signee = true; // validated already auto new_bsp = std::make_shared( @@ -1283,26 +1334,21 @@ struct controller_impl { (*bitr)->block, protocol_features.get_protocol_feature_set(), validator_t{}, skip_validate_signee); - // legacy_branch is from head, all should be validated - assert((*bitr)->action_receipt_digests_savanna); - auto digests = *(*bitr)->action_receipt_digests_savanna; - auto action_mroot = calculate_merkle(std::move(digests)); - // Create the valid structure for producing - new_bsp->valid = prev->new_valid(*new_bsp, action_mroot); - forkdb.add(new_bsp, (*bitr)->is_valid() ? mark_valid_t::yes : mark_valid_t::no, ignore_duplicate_t::yes); + transition_add_to_savanna_fork_db(forkdb, *bitr, new_bsp, prev); prev = new_bsp; } assert(read_mode == db_read_mode::IRREVERSIBLE || forkdb.head()->id() == legacy_branch.front()->id()); - chain_head = block_handle{forkdb.head()}; + if (read_mode != db_read_mode::IRREVERSIBLE) + chain_head = block_handle{forkdb.head()}; + ilog("Transition to instant finality happening after block ${b}, First IF Proper Block ${pb}", ("b", prev->block_num())("pb", prev->block_num()+1)); }); - ilog("Transition to instant finality happening after block ${b}, First IF Proper Block ${pb}", ("b", chain_head.block_num())("pb", chain_head.block_num()+1)); { // If Leap started at a block prior to the IF transition, it needs to provide a default safety // information for those finalizers that don't already have one. This typically should be done when // we create the non-legacy fork_db, as from this point we may need to cast votes to participate // to the IF consensus. See https://github.com/AntelopeIO/leap/issues/2070#issuecomment-1941901836 - auto start_block = chain_head; + auto start_block = chain_head; // doesn't matter this is not updated for IRREVERSIBLE, can be in irreversible mode and be a finalizer auto lib_block = chain_head; my_finalizers.set_default_safety_information( finalizer_safety_information{ .last_vote_range_start = block_timestamp_type(0), @@ -1339,7 +1385,7 @@ struct controller_impl { bool savanna_transistion_required = false; auto mark_branch_irreversible = [&, this](auto& forkdb) { auto branch = (if_lib_num > 0) ? forkdb.fetch_branch( if_irreversible_block_id, new_lib_num) - : forkdb.fetch_branch( fork_db_head(forkdb, irreversible_mode())->id(), new_lib_num ); + : forkdb.fetch_branch( fork_db_head_or_pending(forkdb)->id(), new_lib_num ); try { auto should_process = [&](auto& bsp) { // Only make irreversible blocks that have been validated. Blocks in the fork database may not be on our current best head @@ -1359,10 +1405,7 @@ struct controller_impl { auto it = v.begin(); for( auto bitr = branch.rbegin(); bitr != branch.rend() && should_process(*bitr); ++bitr ) { - if( read_mode == db_read_mode::IRREVERSIBLE ) { - controller::block_report br; - apply_block( br, *bitr, controller::block_status::complete, trx_meta_cache_lookup{} ); - } + apply_irreversible_block(forkdb, *bitr); emit( irreversible_block, std::tie((*bitr)->block, (*bitr)->id()) ); @@ -1380,8 +1423,9 @@ struct controller_impl { // Do not advance irreversible past IF Genesis Block break; } - } else if ((*bitr)->block->is_proper_svnn_block()) { - fork_db.switch_to_savanna(); + } else if ((*bitr)->block->is_proper_svnn_block() && fork_db.version_in_use() == fork_database::in_use_t::both) { + fork_db.switch_to(fork_database::in_use_t::savanna); + break; } } } catch( std::exception& ) { @@ -3077,6 +3121,7 @@ struct controller_impl { if (s != controller::block_status::irreversible) { auto add_completed_block = [&](auto& forkdb) { + assert(std::holds_alternative>(cb.bsp.internal())); const auto& bsp = std::get>(cb.bsp.internal()); if( s == controller::block_status::incomplete ) { forkdb.add( bsp, mark_valid_t::yes, ignore_duplicate_t::no ); @@ -3104,6 +3149,7 @@ struct controller_impl { if( s == controller::block_status::incomplete ) { fork_db.apply_s([&](auto& forkdb) { + assert(std::holds_alternative>(cb.bsp.internal())); const auto& bsp = std::get>(cb.bsp.internal()); uint16_t if_ext_id = instant_finality_extension::extension_id(); diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 5bafbd03e9..5cda182450 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -799,11 +799,6 @@ namespace eosio::chain { } } - // only called from the main thread - void fork_database::switch_to_savanna() { - in_use = in_use_t::savanna; - } - block_branch_t fork_database::fetch_branch_from_head() const { return apply([&](auto& forkdb) { return forkdb.fetch_block_branch(forkdb.head()->id()); diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 4795cf5fa8..4f26eb547a 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -156,7 +156,7 @@ namespace eosio::chain { // switches to using both legacy and savanna during transition void switch_from_legacy(const block_state_ptr& root); - void switch_to_savanna(); + void switch_to(in_use_t v) { in_use = v; } in_use_t version_in_use() const { return in_use.load(); } diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index ccef14248f..2af7109e0e 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -3854,7 +3854,7 @@ namespace eosio { fc::microseconds age( fc::time_point::now() - block->timestamp); fc_dlog( logger, "received signed_block: #${n} block age in secs = ${age}, connection ${cid}, ${v}, lib #${lib}", - ("n", blk_num)("age", age.to_seconds())("cid", c->connection_id)("v", obt ? "pre-validated" : "validation pending")("lib", lib) ); + ("n", blk_num)("age", age.to_seconds())("cid", c->connection_id)("v", obt ? "header validated" : "header validation pending")("lib", lib) ); go_away_reason reason = no_reason; bool accepted = false; diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 8edbb8b55d..f43668489d 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1347,6 +1347,9 @@ void producer_plugin_impl::plugin_startup() { EOS_ASSERT(_producers.empty() || chain.get_read_mode() != chain::db_read_mode::IRREVERSIBLE, plugin_config_exception, "node cannot have any producer-name configured because block production is impossible when read_mode is \"irreversible\""); + EOS_ASSERT(_finalizer_keys.empty() || chain.get_read_mode() != chain::db_read_mode::IRREVERSIBLE, plugin_config_exception, + "node cannot have any finalizers configured because finalization is impossible when read_mode is \"irreversible\""); + EOS_ASSERT(_producers.empty() || chain.get_validation_mode() == chain::validation_mode::FULL, plugin_config_exception, "node cannot have any producer-name configured because block production is not safe when validation_mode is not \"full\""); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4a12e8a90b..e9da8bb115 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -219,7 +219,6 @@ add_test(NAME keosd_auto_launch_test COMMAND tests/keosd_auto_launch_test.py WOR set_property(TEST keosd_auto_launch_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_snapshot_diff_test COMMAND tests/nodeos_snapshot_diff_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_snapshot_diff_test PROPERTY LABELS nonparallelizable_tests) -# requires https://github.com/AntelopeIO/leap/issues/1558 add_test(NAME nodeos_snapshot_diff_if_test COMMAND tests/nodeos_snapshot_diff_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_snapshot_diff_if_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME nodeos_snapshot_forked_test COMMAND tests/nodeos_snapshot_forked_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -287,15 +286,13 @@ set_property(TEST nodeos_under_min_avail_ram_if_lr_test PROPERTY LABELS long_run add_test(NAME nodeos_irreversible_mode_lr_test COMMAND tests/nodeos_irreversible_mode_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_irreversible_mode_lr_test PROPERTY LABELS long_running_tests) -# requires https://github.com/AntelopeIO/leap/issues/2286 -#add_test(NAME nodeos_irreversible_mode_if_lr_test COMMAND tests/nodeos_irreversible_mode_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -#set_property(TEST nodeos_irreversible_mode_if_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_irreversible_mode_if_lr_test COMMAND tests/nodeos_irreversible_mode_test.py -v --activate-if ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_irreversible_mode_if_lr_test PROPERTY LABELS long_running_tests) add_test(NAME nodeos_read_terminate_at_block_lr_test COMMAND tests/nodeos_read_terminate_at_block_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST nodeos_read_terminate_at_block_lr_test PROPERTY LABELS long_running_tests) -# requires https://github.com/AntelopeIO/leap/issues/2057 because running in irreversible mode currently switches different than non-irreversible mode -#add_test(NAME nodeos_read_terminate_at_block_if_lr_test COMMAND tests/nodeos_read_terminate_at_block_test.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -#set_property(TEST nodeos_read_terminate_at_block_if_lr_test PROPERTY LABELS long_running_tests) +add_test(NAME nodeos_read_terminate_at_block_if_lr_test COMMAND tests/nodeos_read_terminate_at_block_test.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST nodeos_read_terminate_at_block_if_lr_test PROPERTY LABELS long_running_tests) add_test(NAME liveness_test COMMAND tests/liveness_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST liveness_test PROPERTY LABELS nonparallelizable_tests) diff --git a/tests/nodeos_irreversible_mode_test.py b/tests/nodeos_irreversible_mode_test.py index dd60f6e1f9..144e8793c6 100755 --- a/tests/nodeos_irreversible_mode_test.py +++ b/tests/nodeos_irreversible_mode_test.py @@ -166,6 +166,7 @@ def relaunchNode(node: Node, chainArg="", addSwapFlags=None, relaunchAssertMessa totalNodes=totalNodes, pnodes=1, activateIF=activateIF, + biosFinalizer=False, topo="mesh", specificExtraNodeosArgs={ 0:"--enable-stale-production", diff --git a/tests/transition_to_if.py b/tests/transition_to_if.py index 0263fb44f0..3df75ac27a 100755 --- a/tests/transition_to_if.py +++ b/tests/transition_to_if.py @@ -22,7 +22,7 @@ topo=args.s debug=args.v prod_count = 1 # per node prod count -total_nodes=pnodes +total_nodes=pnodes+1 dumpErrorDetails=args.dump_error_details Utils.Debug=debug @@ -41,8 +41,9 @@ numTrxGenerators=2 Print("Stand up cluster") # For now do not load system contract as it does not support setfinalizer + specificExtraNodeosArgs = { 4: "--read-mode irreversible"} if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, prodCount=prod_count, maximumP2pPerHost=total_nodes+numTrxGenerators, topo=topo, delay=delay, loadSystemContract=False, - activateIF=False) is False: + activateIF=False, specificExtraNodeosArgs=specificExtraNodeosArgs) is False: errorExit("Failed to stand up eos cluster.") assert cluster.biosNode.getInfo(exitOnError=True)["head_block_producer"] != "eosio", "launch should have waited for production to change" @@ -62,8 +63,10 @@ assert cluster.biosNode.waitForLibToAdvance(), "Lib should advance after instant finality activated" assert cluster.biosNode.waitForProducer("defproducera"), "Did not see defproducera" - assert cluster.biosNode.waitForHeadToAdvance(blocksToAdvance=13) # into next producer - assert cluster.biosNode.waitForLibToAdvance(), "Lib stopped advancing" + assert cluster.biosNode.waitForHeadToAdvance(blocksToAdvance=13), "Head did not advance 13 blocks to next producer" + assert cluster.biosNode.waitForLibToAdvance(), "Lib stopped advancing on biosNode" + assert cluster.getNode(1).waitForLibToAdvance(), "Lib stopped advancing on Node 1" + assert cluster.getNode(4).waitForLibToAdvance(), "Lib stopped advancing on Node 4, irreversible node" info = cluster.biosNode.getInfo(exitOnError=True) assert (info["head_block_num"] - info["last_irreversible_block_num"]) < 9, "Instant finality enabled LIB diff should be small" @@ -76,8 +79,9 @@ # should take effect in first block of defproducerg slot (so defproducerh) assert cluster.setProds(["defproducerb", "defproducerh", "defproducerm", "defproducerr"]), "setprods failed" setProdsBlockNum = cluster.biosNode.getBlockNum() - cluster.biosNode.waitForBlock(setProdsBlockNum+12+12+1) + assert cluster.biosNode.waitForBlock(setProdsBlockNum+12+12+1), "Block of new producers not reached" assert cluster.biosNode.getInfo(exitOnError=True)["head_block_producer"] == "defproducerh", "setprods should have taken effect" + assert cluster.getNode(4).waitForBlock(setProdsBlockNum + 12 + 12 + 1), "Block of new producers not reached on irreversible node" testSuccessful=True finally: