Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IF: Support irreversible read-mode under Savanna #2335

Merged
merged 11 commits into from
Mar 28, 2024
92 changes: 69 additions & 23 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -997,8 +997,8 @@ struct controller_impl {
}

template <typename ForkDB>
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.
Expand All @@ -1010,17 +1010,17 @@ struct controller_impl {

uint32_t fork_db_head_block_num() const {
return fork_db.apply<uint32_t>(
[&](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<block_id_type>(
[&](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<uint32_t>(
[&](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 ----------------------------------------------------------------------
Expand Down Expand Up @@ -1259,14 +1259,64 @@ struct controller_impl {
}
}

template<typename ForkDB, typename BSP>
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<block_state_legacy_ptr, std::decay_t<decltype(bsp)>>) {
// 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<void>(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<void>([&](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<std::function<void()>> 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
fork_database_legacy_t::branch_t legacy_branch;
block_state_legacy_ptr legacy_root;
fork_db.apply_l<void>([&](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);
Expand All @@ -1276,33 +1326,29 @@ struct controller_impl {
fork_db.switch_from_legacy(new_root);
fork_db.apply_s<void>([&](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<block_state>(
*prev,
(*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),
Expand Down Expand Up @@ -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
Expand All @@ -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()) );

Expand All @@ -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& ) {
Expand Down Expand Up @@ -3077,6 +3121,7 @@ struct controller_impl {

if (s != controller::block_status::irreversible) {
auto add_completed_block = [&](auto& forkdb) {
assert(std::holds_alternative<std::decay_t<decltype(forkdb.head())>>(cb.bsp.internal()));
const auto& bsp = std::get<std::decay_t<decltype(forkdb.head())>>(cb.bsp.internal());
if( s == controller::block_status::incomplete ) {
forkdb.add( bsp, mark_valid_t::yes, ignore_duplicate_t::no );
Expand Down Expand Up @@ -3104,6 +3149,7 @@ struct controller_impl {

if( s == controller::block_status::incomplete ) {
fork_db.apply_s<void>([&](auto& forkdb) {
assert(std::holds_alternative<std::decay_t<decltype(forkdb.head())>>(cb.bsp.internal()));
const auto& bsp = std::get<std::decay_t<decltype(forkdb.head())>>(cb.bsp.internal());

uint16_t if_ext_id = instant_finality_extension::extension_id();
Expand Down
5 changes: 0 additions & 5 deletions libraries/chain/fork_database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<block_branch_t>([&](auto& forkdb) {
return forkdb.fetch_block_branch(forkdb.head()->id());
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/include/eosio/chain/fork_database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }

Expand Down
2 changes: 1 addition & 1 deletion plugins/net_plugin/net_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions plugins/producer_plugin/producer_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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\"");

Expand Down
11 changes: 4 additions & 7 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions tests/nodeos_irreversible_mode_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 9 additions & 5 deletions tests/transition_to_if.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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:
Expand Down
Loading