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

[1.0 -> main] Replay from retained directory with no block log #597

Merged
merged 15 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libraries/chain/block_log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -956,7 +956,7 @@ namespace eosio { namespace chain {
open(log_dir);
const auto log_size = std::filesystem::file_size(block_file.get_file_path());

if (log_size == 0 && !catalog.empty()) {
if ((log_size == 0 || !head) && !catalog.empty()) {
basic_block_log::reset(catalog.verifier.chain_id, catalog.last_block_num() + 1);
update_head(read_block_by_num(catalog.last_block_num()));
} else {
Expand Down
216 changes: 114 additions & 102 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1583,109 +1583,119 @@ struct controller_impl {

enum class startup_t { genesis, snapshot, existing_state };

std::exception_ptr replay_block_log() {
bool should_replay_block_log() const {
auto blog_head = blog.head();
if (!blog_head) {
ilog( "no block log found" );
return {};
return false;
}

auto start_block_num = chain_head.block_num() + 1;

bool should_replay = start_block_num <= blog_head->block_num();
if (!should_replay) {
ilog( "no irreversible blocks need to be replayed" );
}
return should_replay;
}

std::exception_ptr replay_block_log() {
auto blog_head = blog.head();
assert(blog_head);

auto start_block_num = chain_head.block_num() + 1;
auto start = fc::time_point::now();

assert(start_block_num <= blog_head->block_num());

std::exception_ptr except_ptr;
if( start_block_num <= blog_head->block_num() ) {
ilog( "existing block log, attempting to replay from ${s} to ${n} blocks", ("s", start_block_num)("n", blog_head->block_num()) );
try {
std::vector<block_state_legacy_ptr> legacy_branch; // for blocks that will need to be converted to IF blocks
while( auto next = blog.read_block_by_num( chain_head.block_num() + 1 ) ) {
block_handle_accessor::apply_l<void>(chain_head, [&](const auto& head) {
if (next->is_proper_svnn_block()) {
const bool skip_validate_signee = true; // validated already or not in replay_push_block according to conf.force_all_checks;
assert(!legacy_branch.empty()); // should have started with a block_state chain_head or we transition during replay
// transition to savanna
block_state_ptr prev = chain_head_trans_svnn_block;
bool replay_not_from_snapshot = !chain_head_trans_svnn_block;
for (size_t i = 0; i < legacy_branch.size(); ++i) {
if (i == 0 && replay_not_from_snapshot) {
assert(!prev);
prev = block_state::create_if_genesis_block(*legacy_branch[0]);
} else {
const auto& bspl = legacy_branch[i];
assert(read_mode == db_read_mode::IRREVERSIBLE || bspl->action_mroot_savanna.has_value());
auto new_bsp = block_state::create_transition_block(
*prev,
bspl->block,
protocol_features.get_protocol_feature_set(),
validator_t{}, skip_validate_signee,
bspl->action_mroot_savanna);
prev = new_bsp;
}
}
chain_head = block_handle{ prev }; // apply_l will not execute again after this
{
// 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
my_finalizers.set_default_safety_information(
finalizer_safety_information{.last_vote = prev->make_block_ref(),
.lock = prev->make_block_ref(),
.votes_forked_since_latest_strong_vote = false});
ilog( "existing block log, attempting to replay from ${s} to ${n} blocks", ("s", start_block_num)("n", blog_head->block_num()) );
try {
std::vector<block_state_legacy_ptr> legacy_branch; // for blocks that will need to be converted to IF blocks
while( auto next = blog.read_block_by_num( chain_head.block_num() + 1 ) ) {
block_handle_accessor::apply_l<void>(chain_head, [&](const auto& head) {
if (next->is_proper_svnn_block()) {
const bool skip_validate_signee = true; // validated already or not in replay_push_block according to conf.force_all_checks;
assert(!legacy_branch.empty()); // should have started with a block_state chain_head or we transition during replay
// transition to savanna
block_state_ptr prev = chain_head_trans_svnn_block;
bool replay_not_from_snapshot = !chain_head_trans_svnn_block;
for (size_t i = 0; i < legacy_branch.size(); ++i) {
if (i == 0 && replay_not_from_snapshot) {
assert(!prev);
prev = block_state::create_if_genesis_block(*legacy_branch[0]);
} else {
const auto& bspl = legacy_branch[i];
assert(read_mode == db_read_mode::IRREVERSIBLE || bspl->action_mroot_savanna.has_value());
auto new_bsp = block_state::create_transition_block(
*prev,
bspl->block,
protocol_features.get_protocol_feature_set(),
validator_t{}, skip_validate_signee,
bspl->action_mroot_savanna);
prev = new_bsp;
}
}
});
block_handle_accessor::apply<void>(chain_head, [&]<typename T>(const T&) {
replay_push_block<T>( next, controller::block_status::irreversible );
});
block_handle_accessor::apply_l<void>(chain_head, [&](const auto& head) { // chain_head is updated via replay_push_block
assert(!next->is_proper_svnn_block());
if (next->contains_header_extension(finality_extension::extension_id())) {
assert(legacy_branch.empty() || head->block->previous == legacy_branch.back()->block->calculate_id());
legacy_branch.push_back(head);
// note if is_proper_svnn_block is not reached then transistion will happen live
chain_head = block_handle{ prev }; // apply_l will not execute again after this
{
// 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
my_finalizers.set_default_safety_information(
finalizer_safety_information{.last_vote = prev->make_block_ref(),
.lock = prev->make_block_ref(),
.votes_forked_since_latest_strong_vote = false});
}
});
if( check_shutdown() ) { // needed on every loop for terminate-at-block
ilog( "quitting from replay_block_log because of shutdown" );
break;
}
if( next->block_num() % 500 == 0 ) {
ilog( "${n} of ${head}", ("n", next->block_num())("head", blog_head->block_num()) );
});
block_handle_accessor::apply<void>(chain_head, [&]<typename T>(const T&) {
replay_push_block<T>( next, controller::block_status::irreversible );
});
block_handle_accessor::apply_l<void>(chain_head, [&](const auto& head) { // chain_head is updated via replay_push_block
assert(!next->is_proper_svnn_block());
if (next->contains_header_extension(finality_extension::extension_id())) {
assert(legacy_branch.empty() || head->block->previous == legacy_branch.back()->block->calculate_id());
legacy_branch.push_back(head);
// note if is_proper_svnn_block is not reached then transistion will happen live
}
});
if( check_shutdown() ) { // needed on every loop for terminate-at-block
ilog( "quitting from replay_block_log because of shutdown" );
break;
}
if( next->block_num() % 500 == 0 ) {
ilog( "${n} of ${head}", ("n", next->block_num())("head", blog_head->block_num()) );
}
} catch( const database_guard_exception& e ) {
except_ptr = std::current_exception();
}
auto end = fc::time_point::now();
ilog( "${n} irreversible blocks replayed", ("n", 1 + chain_head.block_num() - start_block_num) );
ilog( "replayed ${n} blocks in ${duration} seconds, ${mspb} ms/block",
("n", chain_head.block_num() + 1 - start_block_num)("duration", (end-start).count()/1000000)
("mspb", ((end-start).count()/1000.0)/(chain_head.block_num()-start_block_num)) );

// if the irreverible log is played without undo sessions enabled, we need to sync the
// revision ordinal to the appropriate expected value here.
if( skip_db_sessions( controller::block_status::irreversible ) )
db.set_revision( chain_head.block_num() );
} else {
ilog( "no irreversible blocks need to be replayed" );
} catch( const database_guard_exception& e ) {
except_ptr = std::current_exception();
}
auto end = fc::time_point::now();
ilog( "${n} irreversible blocks replayed", ("n", 1 + chain_head.block_num() - start_block_num) );
ilog( "replayed ${n} blocks in ${duration} seconds, ${mspb} ms/block",
("n", chain_head.block_num() + 1 - start_block_num)("duration", (end-start).count()/1000000)
("mspb", ((end-start).count()/1000.0)/(chain_head.block_num()-start_block_num)) );

// if the irreverible log is played without undo sessions enabled, we need to sync the
// revision ordinal to the appropriate expected value here.
if( skip_db_sessions( controller::block_status::irreversible ) )
db.set_revision( chain_head.block_num() );

return except_ptr;
}

void replay(startup_t startup) {
replaying = true;

bool replay_block_log_needed = should_replay_block_log();

auto blog_head = blog.head();
auto start_block_num = chain_head.block_num() + 1;
std::exception_ptr except_ptr;

if (blog_head) {
if (replay_block_log_needed)
except_ptr = replay_block_log();
} else {
ilog( "no block log found" );
}

if( check_shutdown() ) {
ilog( "quitting from replay because of shutdown" );
Expand All @@ -1699,38 +1709,40 @@ struct controller_impl {
}

if (startup == startup_t::existing_state) {
EOS_ASSERT(fork_db_has_root(), fork_database_exception,
"No existing fork database despite existing chain state. Replay required." );
uint32_t lib_num = fork_db_root_block_num();
auto first_block_num = blog.first_block_num();
if(blog_head) {
EOS_ASSERT( first_block_num <= lib_num && lib_num <= blog_head->block_num(),
block_log_exception,
"block log (ranging from ${block_log_first_num} to ${block_log_last_num}) does not contain the last irreversible block (${fork_db_lib})",
("block_log_first_num", first_block_num)
("block_log_last_num", blog_head->block_num())
("fork_db_lib", lib_num)
);
lib_num = blog_head->block_num();
} else {
if( first_block_num != (lib_num + 1) ) {
blog.reset( chain_id, lib_num + 1 );
if (!replay_block_log_needed) {
EOS_ASSERT(fork_db_has_root(), fork_database_exception,
"No existing fork database despite existing chain state. Replay required." );
uint32_t lib_num = fork_db_root_block_num();
auto first_block_num = blog.first_block_num();
if(blog_head) {
EOS_ASSERT( first_block_num <= lib_num && lib_num <= blog_head->block_num(),
block_log_exception,
"block log (ranging from ${block_log_first_num} to ${block_log_last_num}) does not contain the last irreversible block (${fork_db_lib})",
("block_log_first_num", first_block_num)
("block_log_last_num", blog_head->block_num())
("fork_db_lib", lib_num)
);
lib_num = blog_head->block_num();
} else {
if( first_block_num != (lib_num + 1) ) {
blog.reset( chain_id, lib_num + 1 );
}
}
}

auto do_startup = [&](auto& forkdb) {
if( read_mode == db_read_mode::IRREVERSIBLE) {
auto root = forkdb.root();
if (root && chain_head.id() != root->id()) {
chain_head = block_handle{forkdb.root()};
// rollback db to LIB
while( db.revision() > chain_head.block_num() ) {
db.undo();
auto do_startup = [&](auto& forkdb) {
if( read_mode == db_read_mode::IRREVERSIBLE) {
auto root = forkdb.root();
if (root && chain_head.id() != root->id()) {
chain_head = block_handle{forkdb.root()};
// rollback db to LIB
while( db.revision() > chain_head.block_num() ) {
db.undo();
}
}
}
}
};
fork_db.apply<void>(do_startup);
};
fork_db.apply<void>(do_startup);
}
}

auto fork_db_reset_root_to_chain_head = [&]() {
Expand Down
25 changes: 16 additions & 9 deletions unittests/blocks_log_replay_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct blog_replay_fixture {
}

// Stop replay at block number `stop_at` via simulated ctrl-c and resume the replay afterward
void stop_and_resume_replay(uint32_t stop_at) try {
void stop_and_resume_replay(uint32_t stop_at, bool remove_fork_db = false) try {
controller::config copied_config = chain.get_config();

auto genesis = block_log::extract_genesis_state(copied_config.blocks_dir);
Expand Down Expand Up @@ -77,20 +77,17 @@ struct blog_replay_fixture {
// Make sure reversible fork_db still exists
BOOST_CHECK(std::filesystem::exists(copied_config.blocks_dir / config::reversible_blocks_dir_name / "fork_db.dat"));

if (remove_fork_db) {
std::filesystem::remove(copied_config.blocks_dir / config::reversible_blocks_dir_name / "fork_db.dat");
}

// Prepare resuming replay
controller::config copied_config_1 = replay_chain.get_config();

// Resume replay
eosio::testing::tester replay_chain_1(copied_config_1, *genesis, call_startup_t::no);
replay_chain_1.control->startup( [](){}, []()->bool{ return false; } );

replay_chain_1.control->accepted_block().connect([&](const block_signal_params& t) {
const auto& [ block, id ] = t;
BOOST_TEST(block->block_num() > stop_at);
static uint32_t first = block->block_num();
BOOST_TEST(first == stop_at);
});

// Make sure new chain contain the account created by original chain
BOOST_REQUIRE_NO_THROW(replay_chain_1.get_account("replay1"_n));
BOOST_REQUIRE_NO_THROW(replay_chain_1.get_account("replay2"_n));
Expand All @@ -99,7 +96,11 @@ struct blog_replay_fixture {
// Make sure replayed irreversible_block_num and head_block_num match
// with last_irreversible_block_num and last_head_block_num
BOOST_CHECK(replay_chain_1.last_irreversible_block_num() == last_irreversible_block_num);
BOOST_CHECK(replay_chain_1.head().block_num() == last_head_block_num);
if (!remove_fork_db) {
BOOST_CHECK(replay_chain_1.head().block_num() == last_head_block_num);
} else {
BOOST_CHECK(replay_chain_1.head().block_num() == last_irreversible_block_num);
}
} FC_LOG_AND_RETHROW()

void remove_existing_states(std::filesystem::path& state_path) {
Expand Down Expand Up @@ -136,6 +137,12 @@ BOOST_FIXTURE_TEST_CASE(replay_stop_in_middle, blog_replay_fixture) try {
stop_and_resume_replay(last_irreversible_block_num - 1);
} FC_LOG_AND_RETHROW()

// Test replay stopping in the middle of blocks log and resuming without forkdb
BOOST_FIXTURE_TEST_CASE(replay_stop_in_middle_rm_forkdb, blog_replay_fixture) try {
// block `last_irreversible_block_num - 1` is within blocks log
stop_and_resume_replay(last_irreversible_block_num - 1, true);
} FC_LOG_AND_RETHROW()

// Test replay stopping in the middle of reversible blocks and resuming
BOOST_FIXTURE_TEST_CASE(replay_stop_in_reversible_blocks, blog_replay_fixture) try {
// block `last_head_block_num - 1` is within reversible_blocks, since in Savanna
Expand Down
Loading