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 Leap 5 Fork Database disk format and new Savanna disk format. #2306

Merged
merged 14 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
20 changes: 14 additions & 6 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,19 @@ R apply(const block_handle& bh, F&& f) {
}, bh.internal());
}

// applies different lambdas, depending on whether `block_handle` holds a `block_state_legacy` or a `block_state`
template <class R, class F_L, class F_S>
R apply(const block_handle& bh, F_L&& f_l, F_S&& f_s) {
if constexpr (std::is_same_v<void, R>)
std::visit<void>(overloaded{[&](const block_state_legacy_ptr& head) { std::forward<F_L>(f_l)(head); },
[&](const block_state_ptr& head) { std::forward<F_S>(f_s)(head); }
}, bh.internal());
else
return std::visit<R>(overloaded{[&](const block_state_legacy_ptr& head) -> R { return std::forward<F_L>(f_l)(head); },
[&](const block_state_ptr& head) -> R { return std::forward<F_S>(f_s)(head); }
}, bh.internal());
}

heifner marked this conversation as resolved.
Show resolved Hide resolved
// apply savanna block_state
template <class R, class F>
R apply_s(const block_handle& bh, F&& f) {
Expand Down Expand Up @@ -1024,12 +1037,7 @@ struct controller_impl {
}

void fork_db_reset_root_to_chain_head() {
fork_db.apply<void>([&](auto& forkdb) {
apply<void>(chain_head, [&](const auto& head) {
if constexpr (std::is_same_v<std::decay_t<decltype(head)>, std::decay_t<decltype(forkdb.head())>>)
forkdb.reset_root(head);
});
});
fork_db.reset_root(chain_head.internal());
}

signed_block_ptr fork_db_fetch_block_by_id( const block_id_type& id ) const {
Expand Down
263 changes: 143 additions & 120 deletions libraries/chain/fork_database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,11 @@ namespace eosio::chain {
bsp_t root;
bsp_t head;
fork_multi_index_type index;
const uint32_t magic_number;

explicit fork_database_impl(uint32_t magic_number) : magic_number(magic_number) {}
explicit fork_database_impl() = default;

void open_impl( const std::filesystem::path& fork_db_file, validator_t& validator );
void close_impl( const std::filesystem::path& fork_db_file );
void open_impl( const std::filesystem::path& fork_db_file, fc::cfile_datastream& ds, validator_t& validator );
void close_impl( std::ofstream& out );
void add_impl( const bsp_t& n, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate, bool validate, validator_t& validator );

bsp_t get_block_impl( const block_id_type& id, include_root_t include_root = include_root_t::no ) const;
Expand All @@ -147,109 +146,67 @@ namespace eosio::chain {
};

template<class BSP>
fork_database_t<BSP>::fork_database_t(uint32_t magic_number)
:my( new fork_database_impl<BSP>(magic_number) )
fork_database_t<BSP>::fork_database_t()
:my( new fork_database_impl<BSP>() )
{}

template<class BSP>
fork_database_t<BSP>::~fork_database_t() = default; // close is performed in fork_database::~fork_database()

template<class BSP>
void fork_database_t<BSP>::open( const std::filesystem::path& fork_db_file, validator_t& validator ) {
void fork_database_t<BSP>::open( const std::filesystem::path& fork_db_file, fc::cfile_datastream& ds, validator_t& validator ) {
std::lock_guard g( my->mtx );
my->open_impl( fork_db_file, validator );
my->open_impl( fork_db_file, ds, validator );
}

template<class BSP>
void fork_database_impl<BSP>::open_impl( const std::filesystem::path& fork_db_file, validator_t& validator ) {
if( std::filesystem::exists( fork_db_file ) ) {
try {
string content;
fc::read_file_contents( fork_db_file, content );

fc::datastream<const char*> ds( content.data(), content.size() );

// validate totem
uint32_t totem = 0;
fc::raw::unpack( ds, totem );
EOS_ASSERT( totem == magic_number, fork_database_exception,
"Fork database file '${filename}' has unexpected magic number: ${actual_totem}. Expected ${expected_totem}",
("filename", fork_db_file)("actual_totem", totem)("expected_totem", magic_number)
);

// validate version
uint32_t version = 0;
fc::raw::unpack( ds, version );
EOS_ASSERT( version >= fork_database::min_supported_version && version <= fork_database::max_supported_version,
fork_database_exception,
"Unsupported version of fork database file '${filename}'. "
"Fork database version is ${version} while code supports version(s) [${min},${max}]",
("filename", fork_db_file)
("version", version)
("min", fork_database::min_supported_version)
("max", fork_database::max_supported_version)
);

bsp_t state = std::make_shared<bs_t>();
fc::raw::unpack( ds, *state );
reset_root_impl( state );

unsigned_int size; fc::raw::unpack( ds, size );
for( uint32_t i = 0, n = size.value; i < n; ++i ) {
bs_t s;
fc::raw::unpack( ds, s );
// do not populate transaction_metadatas, they will be created as needed in apply_block with appropriate key recovery
s.header_exts = s.block->validate_and_extract_header_extensions();
add_impl( std::make_shared<bs_t>( std::move( s ) ), mark_valid_t::no, ignore_duplicate_t::no, true, validator );
}
block_id_type head_id;
fc::raw::unpack( ds, head_id );

if( root->id() == head_id ) {
head = root;
} else {
head = get_block_impl( head_id );
EOS_ASSERT( head, fork_database_exception,
"could not find head while reconstructing fork database from file; '${filename}' is likely corrupted",
("filename", fork_db_file) );
}
void fork_database_impl<BSP>::open_impl( const std::filesystem::path& fork_db_file, fc::cfile_datastream& ds, validator_t& validator ) {
bsp_t _root = std::make_shared<bs_t>();
fc::raw::unpack( ds, *_root );
reset_root_impl( _root );

unsigned_int size; fc::raw::unpack( ds, size );
for( uint32_t i = 0, n = size.value; i < n; ++i ) {
bs_t s;
fc::raw::unpack( ds, s );
// do not populate transaction_metadatas, they will be created as needed in apply_block with appropriate key recovery
s.header_exts = s.block->validate_and_extract_header_extensions();
add_impl( std::make_shared<bs_t>( std::move( s ) ), mark_valid_t::no, ignore_duplicate_t::no, true, validator );
}
block_id_type head_id;
fc::raw::unpack( ds, head_id );

auto candidate = index.template get<by_best_branch>().begin();
if( candidate == index.template get<by_best_branch>().end() || !bs_accessor_t::is_valid(**candidate) ) {
EOS_ASSERT( head->id() == root->id(), fork_database_exception,
"head not set to root despite no better option available; '${filename}' is likely corrupted",
("filename", fork_db_file) );
} else {
EOS_ASSERT( !first_preferred( **candidate, *head ), fork_database_exception,
"head not set to best available option available; '${filename}' is likely corrupted",
("filename", fork_db_file) );
}
} FC_CAPTURE_AND_RETHROW( (fork_db_file) )
if( root->id() == head_id ) {
head = root;
} else {
head = get_block_impl( head_id );
EOS_ASSERT( head, fork_database_exception,
"could not find head while reconstructing fork database from file; '${filename}' is likely corrupted",
("filename", fork_db_file) );
}

std::filesystem::remove( fork_db_file );
auto candidate = index.template get<by_best_branch>().begin();
if( candidate == index.template get<by_best_branch>().end() || !bs_accessor_t::is_valid(**candidate) ) {
EOS_ASSERT( head->id() == root->id(), fork_database_exception,
"head not set to root despite no better option available; '${filename}' is likely corrupted",
("filename", fork_db_file) );
} else {
EOS_ASSERT( !first_preferred( **candidate, *head ), fork_database_exception,
"head not set to best available option available; '${filename}' is likely corrupted",
("filename", fork_db_file) );
}
}

template<class BSP>
void fork_database_t<BSP>::close(const std::filesystem::path& fork_db_file) {
void fork_database_t<BSP>::close(std::ofstream& out) {
std::lock_guard g( my->mtx );
my->close_impl(fork_db_file);
my->close_impl(out);
}

template<class BSP>
void fork_database_impl<BSP>::close_impl(const std::filesystem::path& fork_db_file) {
if( !root ) {
if( index.size() > 0 ) {
heifner marked this conversation as resolved.
Show resolved Hide resolved
elog( "fork_database is in a bad state when closing; not writing out '${filename}'",
("filename", fork_db_file) );
}
return;
}

std::ofstream out( fork_db_file.generic_string().c_str(), std::ios::out | std::ios::binary | std::ofstream::trunc );
fc::raw::pack( out, magic_number );
fc::raw::pack( out, fork_database::max_supported_version ); // write out current version which is always max_supported_version
void fork_database_impl<BSP>::close_impl(std::ofstream& out) {
fc::raw::pack( out, *root );

uint32_t num_blocks_in_fork_db = index.size();
fc::raw::pack( out, unsigned_int{num_blocks_in_fork_db} );

Expand Down Expand Up @@ -288,12 +245,7 @@ namespace eosio::chain {
fc::raw::pack( out, *(*itr) );
}

if( head ) {
fc::raw::pack( out, head->id() );
} else {
elog( "head not set in fork database; '${filename}' will be corrupted",
("filename", fork_db_file) );
}
fc::raw::pack( out, head ? head->id() : digest_type());
heifner marked this conversation as resolved.
Show resolved Hide resolved

index.clear();
}
Expand Down Expand Up @@ -420,6 +372,11 @@ namespace eosio::chain {
);
}

template<class BSP>
bool fork_database_t<BSP>::in_valid_state() const {
return !!my->root;
}

template<class BSP>
bool fork_database_t<BSP>::has_root() const {
return !!my->root;
Expand Down Expand Up @@ -703,7 +660,8 @@ namespace eosio::chain {
fork_database::fork_database(const std::filesystem::path& data_dir)
: data_dir(data_dir)
// genesis starts with legacy
, fork_db_l{std::make_unique<fork_database_legacy_t>(fork_database_legacy_t::legacy_magic_number)}
, fork_db_l{std::make_unique<fork_database_legacy_t>()}
, fork_db_s{std::make_unique<fork_database_if_t>()}
heifner marked this conversation as resolved.
Show resolved Hide resolved
{
}

Expand All @@ -712,7 +670,34 @@ namespace eosio::chain {
}

void fork_database::close() {
apply<void>([&](auto& forkdb) { forkdb.close(data_dir / config::forkdb_filename); });
auto fork_db_file {data_dir / config::forkdb_filename};
bool legacy_valid = fork_db_l && fork_db_l->in_valid_state();
bool savanna_valid = fork_db_s && fork_db_s->in_valid_state();

// check that fork_dbs are in a consistent state
if (!legacy_valid && !savanna_valid) {
elog( "fork_database is in a bad state when closing; not writing out '${filename}', legacy_valid=${l}, savanna_valid=${s}",
("filename", fork_db_file)("l", legacy_valid)("s", savanna_valid) );
return;
}

std::ofstream out( fork_db_file.generic_string().c_str(), std::ios::out | std::ios::binary | std::ofstream::trunc );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a bit odd to use cfile to read and std::ofstream to write. Might be better to use cfile.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was that way before my change. But yes I agree it is a little bit inconsistent. I can change it if you think it is worthwhile.


fc::raw::pack( out, magic_number );
fc::raw::pack( out, max_supported_version ); // write out current version which is always max_supported_version
// version == 1 -> legacy
// version == 1 -> savanna (two possible fork_db, one containing `block_state_legacy`,
heifner marked this conversation as resolved.
Show resolved Hide resolved
// one containing `block_state`)

fc::raw::pack(out, static_cast<uint32_t>(in_use.load()));

fc::raw::pack(out, legacy_valid);
if (legacy_valid)
fork_db_l->close(out);

fc::raw::pack(out, savanna_valid);
if (savanna_valid)
fork_db_s->close(out);
}

void fork_database::open( validator_t& validator ) {
Expand All @@ -731,41 +716,69 @@ namespace eosio::chain {
// determine file type, validate totem
uint32_t totem = 0;
fc::raw::unpack( ds, totem );
EOS_ASSERT( totem == fork_database_legacy_t::legacy_magic_number ||
totem == fork_database_if_t::magic_number, fork_database_exception,
"Fork database file '${filename}' has unexpected magic number: ${actual_totem}. Expected ${t1} or ${t2}",
("filename", fork_db_file)
("actual_totem", totem)("t1", fork_database_legacy_t::legacy_magic_number)("t2", fork_database_if_t::magic_number)
);

if (totem == fork_database_legacy_t::legacy_magic_number) {
// fork_db_legacy created in constructor
apply_l<void>([&](auto& forkdb) {
forkdb.open(fork_db_file, validator);
});
} else {
// file is instant-finality data, so switch to fork_database_if_t
fork_db_s = std::make_unique<fork_database_if_t>(fork_database_if_t::magic_number);
legacy = false;
apply_s<void>([&](auto& forkdb) {
forkdb.open(fork_db_file, validator);
});
EOS_ASSERT( totem == magic_number, fork_database_exception,
"Fork database file '${filename}' has unexpected magic number: ${actual_totem}. Expected ${t}",
("filename", fork_db_file)("actual_totem", totem)("t", magic_number));

uint32_t version = 0;
fc::raw::unpack( ds, version );
EOS_ASSERT( version >= fork_database::min_supported_version && version <= fork_database::max_supported_version,
fork_database_exception,
"Unsupported version of fork database file '${filename}'. "
"Fork database version is ${version} while code supports version(s) [${min},${max}]",
("filename", fork_db_file)("version", version)("min", min_supported_version)("max", max_supported_version));

switch(version) {
case 1:
{
// ---------- pre-Savanna format. Just a single fork_database_l ----------------
in_use = in_use_t::legacy;
fork_db_l = std::make_unique<fork_database_legacy_t>();
fork_db_l->open(fork_db_file, ds, validator);
break;
}
} FC_CAPTURE_AND_RETHROW( (fork_db_file) )

case 2:
{
// ---------- Savanna format ----------------------------
uint32_t in_use_raw;
fc::raw::unpack( ds, in_use_raw );
in_use = static_cast<in_use_t>(in_use_raw);

bool legacy_valid { false };
fc::raw::unpack( ds, legacy_valid );
if (legacy_valid) {
fork_db_l = std::make_unique<fork_database_legacy_t>();
fork_db_l->open(fork_db_file, ds, validator);
}

bool savanna_valid { false };
fc::raw::unpack( ds, savanna_valid );
if (savanna_valid) {
fork_db_s = std::make_unique<fork_database_if_t>();
fork_db_s->open(fork_db_file, ds, validator);
}
break;
}

default:
assert(0);
break;
}
} FC_CAPTURE_AND_RETHROW( (fork_db_file) );
std::filesystem::remove( fork_db_file );
}
}

void fork_database::switch_from_legacy(const block_handle& bh) {
// no need to close fork_db because we don't want to write anything out, file is removed on open
// threads may be accessing (or locked on mutex about to access legacy forkdb) so don't delete it until program exit
assert(legacy);
assert(in_use == in_use_t::legacy);
assert(std::holds_alternative<block_state_ptr>(bh.internal()));
block_state_ptr new_head = std::get<block_state_ptr>(bh.internal());
fork_db_s = std::make_unique<fork_database_if_t>(fork_database_if_t::magic_number);
legacy = false;
apply_s<void>([&](auto& forkdb) {
forkdb.reset_root(new_head);
});
fork_db_s = std::make_unique<fork_database_if_t>();
in_use = in_use_t::savanna;
fork_db_s->reset_root(new_head);
}

block_branch_t fork_database::fetch_branch_from_head() const {
Expand All @@ -774,6 +787,16 @@ namespace eosio::chain {
});
}

void fork_database::reset_root(const block_handle::block_handle_variant_t& v) {
std::visit(overloaded{ [&](const block_state_legacy_ptr& bsp) { fork_db_l->reset_root(bsp); },
[&](const block_state_ptr& bsp) {
if (in_use == in_use_t::legacy)
in_use = in_use_t::savanna;
fork_db_s->reset_root(bsp);
} },
v);
}

// do class instantiations
template class fork_database_t<block_state_legacy_ptr>;
template class fork_database_t<block_state_ptr>;
Expand Down
4 changes: 3 additions & 1 deletion libraries/chain/include/eosio/chain/block_handle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ namespace eosio::chain {
// Created via controller::create_block_handle(const block_id_type& id, const signed_block_ptr& b)
// Valid to request id and signed_block_ptr it was created from.
struct block_handle {
using block_handle_variant_t = std::variant<block_state_legacy_ptr, block_state_ptr>;
heifner marked this conversation as resolved.
Show resolved Hide resolved

private:
std::variant<block_state_legacy_ptr, block_state_ptr> _bsp;
block_handle_variant_t _bsp;

public:
block_handle() = default;
Expand Down
Loading
Loading