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 load of fork database after instant-finality is enabled #2113

Merged
merged 12 commits into from
Jan 21, 2024
5 changes: 5 additions & 0 deletions libraries/chain/block_header_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@

namespace eosio::chain {

// moved this warning out of header so it only uses once
#warning TDDO https://github.com/AntelopeIO/leap/issues/2080
Copy link
Member

Choose a reason for hiding this comment

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

Now it occurs to me, if we already have an issue, we don't need a warning, because the issue will remind us.

// digest_type compute_finalizer_digest() const { return id; };


producer_authority block_header_state::get_scheduled_producer(block_timestamp_type t) const {
return detail::get_scheduled_producer(active_proposer_policy->proposer_schedule.producers, t);
}
Expand Down
1,035 changes: 491 additions & 544 deletions libraries/chain/controller.cpp

Large diffs are not rendered by default.

249 changes: 145 additions & 104 deletions libraries/chain/fork_database.cpp

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,6 @@ namespace eosio::chain {

const chainbase::database& db()const;

const fork_database_legacy& fork_db()const;

const account_object& get_account( account_name n )const;
const global_property_object& get_global_properties()const;
const dynamic_global_property_object& get_dynamic_global_properties()const;
Expand Down Expand Up @@ -249,7 +247,7 @@ namespace eosio::chain {
block_state_legacy_ptr head_block_state_legacy()const;

uint32_t fork_db_head_block_num()const;
const block_id_type& fork_db_head_block_id()const;
block_id_type fork_db_head_block_id()const;

time_point pending_block_time()const;
block_timestamp_type pending_block_timestamp()const;
Expand Down
120 changes: 104 additions & 16 deletions libraries/chain/include/eosio/chain/fork_database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,35 @@ namespace eosio::chain {
struct fork_database_impl;

/**
* @class fork_database
* @class fork_database_t
* @brief manages light-weight state for all potential unconfirmed forks
*
* As new blocks are received, they are pushed into the fork database. The fork
* database tracks the longest chain and the last irreversible block number. All
* blocks older than the last irreversible block are freed after emitting the
* irreversible signal.
*
* An internal mutex is used to provide thread-safety.
* Not thread safe, thread safety provided by fork_database below.
* fork_database should be used instead of fork_database_t directly as it manages
* the different supported types.
*/
template<class bsp> // either block_state_legacy_ptr or block_state_ptr
class fork_database {
class fork_database_t {
public:
static constexpr uint32_t legacy_magic_number = 0x30510FDB;
static constexpr uint32_t magic_number = 0x4242FDB;
linh2931 marked this conversation as resolved.
Show resolved Hide resolved

using bs = bsp::element_type;
using bhsp = bs::bhsp_t;
using bhs = bhsp::element_type;
using bsp_t = bsp;
using branch_type = deque<bsp>;
using branch_type_pair = pair<branch_type, branch_type>;

explicit fork_database( const std::filesystem::path& data_dir );
~fork_database();

std::filesystem::path get_data_dir() const;
explicit fork_database_t(uint32_t magic_number = legacy_magic_number);

void open( validator_t& validator );
void close();
void open( const std::filesystem::path& fork_db_file, validator_t& validator );
void close( const std::filesystem::path& fork_db_file );

bhsp get_block_header( const block_id_type& id ) const;
bsp get_block( const block_id_type& id ) const;
Expand Down Expand Up @@ -70,6 +72,9 @@ namespace eosio::chain {
bsp head() const;
bsp pending_head() const;

// only accessed by main thread, no mutex protection
bsp chain_head;

/**
* 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
Expand All @@ -95,15 +100,98 @@ namespace eosio::chain {

void mark_valid( const bsp& h );

static const uint32_t magic_number;

static const uint32_t min_supported_version;
static const uint32_t max_supported_version;

private:
unique_ptr<fork_database_impl<bsp>> my;
};

using fork_database_legacy = fork_database<block_state_legacy_ptr>;

using fork_database_legacy_t = fork_database_t<block_state_legacy_ptr>;
using fork_database_if_t = fork_database_t<block_state_ptr>;

/**
* Provides thread safety on fork_database_t and provide mechanism for opening the correct type
* as well as switching from legacy (old dpos) to instant-finality.
*
* All methods assert until open() is closed.
*/
class fork_database {
mutable std::recursive_mutex m;
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not a fan of recursive mutexes. However, currently controller uses nested calls of fork_database.apply. We can look at refactoring those so that no apply calls functions which also calls apply but that will take some work.

const std::filesystem::path data_dir;
std::variant<fork_database_t<block_state_legacy_ptr>, fork_database_t<block_state_ptr>> vforkdb;
public:
explicit fork_database(const std::filesystem::path& data_dir);
~fork_database(); // close on destruction

void open( validator_t& validator );
void close();

void switch_from_legacy();

// see fork_database_t::fetch_branch(forkdb->head()->id())
std::vector<signed_block_ptr> fetch_branch_from_head();

template <class R, class F>
R apply(const F& f) {
std::lock_guard g(m);
if constexpr (std::is_same_v<void, R>)
std::visit([&](auto& forkdb) { f(forkdb); }, vforkdb);
else
return std::visit([&](auto& forkdb) -> R { return f(forkdb); }, vforkdb);
}

template <class R, class F>
R apply(const F& f) const {
std::lock_guard g(m);
if constexpr (std::is_same_v<void, R>)
std::visit([&](const auto& forkdb) { f(forkdb); }, vforkdb);
else
return std::visit([&](const auto& forkdb) -> R { return f(forkdb); }, vforkdb);
}

/// Apply for when only need lambda executed when in instant-finality mode
template <class R, class F>
R apply_if(const F& f) {
std::lock_guard g(m);
if constexpr (std::is_same_v<void, R>)
std::visit(overloaded{[&](fork_database_legacy_t&) {},
[&](fork_database_if_t& forkdb) { f(forkdb); }},
vforkdb);
else
return std::visit(overloaded{[&](fork_database_legacy_t&) -> R { return {}; },
[&](fork_database_if_t& forkdb) -> R { return f(forkdb); }},
vforkdb);
}

/// Apply for when only need lambda executed when in legacy mode
template <class R, class F>
R apply_legacy(const F& f) {
std::lock_guard g(m);
if constexpr (std::is_same_v<void, R>)
std::visit(overloaded{[&](fork_database_legacy_t& forkdb) { f(forkdb); },
[&](fork_database_if_t&) {}},
vforkdb);
else
return std::visit(overloaded{[&](fork_database_legacy_t& forkdb) -> R { return f(forkdb); },
[&](fork_database_if_t&) -> R { return {}; }},
vforkdb);
}

/// @param legacy_f the lambda to execute if in legacy mode
/// @param if_f the lambda to execute if in instant-finality mode
template <class R, class LegacyF, class IfF>
R apply(const LegacyF& legacy_f, const IfF& if_f) {
std::lock_guard g(m);
if constexpr (std::is_same_v<void, R>)
std::visit(overloaded{[&](fork_database_legacy_t& forkdb) { legacy_f(forkdb); },
[&](fork_database_if_t& forkdb) { if_f(forkdb); }},
vforkdb);
else
return std::visit(overloaded{[&](fork_database_legacy_t& forkdb) -> R { return legacy_f(forkdb); },
[&](fork_database_if_t& forkdb) -> R { return if_f(forkdb); }},
vforkdb);
}

// if we ever support more than one version then need to save min/max in fork_database_t
static constexpr uint32_t min_supported_version = 1;
static constexpr uint32_t max_supported_version = 1;
};
} /// eosio::chain
9 changes: 4 additions & 5 deletions programs/leap-util/actions/blocklog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,18 +266,17 @@ int blocklog_actions::read_log() {
opt->first_block = block_logger.first_block_num();
}

using fork_database_t = fork_database_legacy; // [greg todo] what is it is not a legacy fork_db?
fork_database_t::branch_type fork_db_branch;
std::vector<signed_block_ptr> fork_db_branch;

if(std::filesystem::exists(std::filesystem::path(opt->blocks_dir) / config::reversible_blocks_dir_name / config::forkdb_filename)) {
ilog("opening fork_db");
fork_database_t fork_db(std::filesystem::path(opt->blocks_dir) / config::reversible_blocks_dir_name);
fork_database fork_db(std::filesystem::path(opt->blocks_dir) / config::reversible_blocks_dir_name);

fork_db.open([](block_timestamp_type timestamp,
const flat_set<digest_type>& cur_features,
const vector<digest_type>& new_features) {});

fork_db_branch = fork_db.fetch_branch(fork_db.head()->id());
fork_db_branch = fork_db.fetch_branch_from_head();
if(fork_db_branch.empty()) {
elog("no blocks available in reversible block database: only block_log blocks are available");
} else {
Expand Down Expand Up @@ -336,7 +335,7 @@ int blocklog_actions::read_log() {
for(auto bitr = fork_db_branch.rbegin(); bitr != fork_db_branch.rend() && block_num <= opt->last_block; ++bitr) {
if(opt->as_json_array && contains_obj)
*out << ",";
auto next = (*bitr)->block;
auto& next = *bitr;
print_block(next);
++block_num;
contains_obj = true;
Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ set_property(TEST nodeos_chainbase_allocation_test PROPERTY LABELS nonparalleliz
add_test(NAME nodeos_startup_catchup_lr_test COMMAND tests/nodeos_startup_catchup.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST nodeos_startup_catchup_lr_test PROPERTY LABELS long_running_tests)

add_test(NAME nodeos_startup_catchup_if_lr_test COMMAND tests/nodeos_startup_catchup.py -p3 --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST nodeos_startup_catchup_if_lr_test PROPERTY LABELS long_running_tests)

add_test(NAME nodeos_short_fork_take_over_test COMMAND tests/nodeos_short_fork_take_over_test.py -v --wallet-port 9905 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST nodeos_short_fork_take_over_test PROPERTY LABELS nonparallelizable_tests)

Expand Down
5 changes: 3 additions & 2 deletions tests/nodeos_startup_catchup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
extraArgs = appArgs.add(flag="--catchup-count", type=int, help="How many catchup-nodes to launch", default=10)
extraArgs = appArgs.add(flag="--txn-gen-nodes", type=int, help="How many transaction generator nodes", default=2)
args = TestHelper.parse_args({"--dump-error-details","--keep-logs","-v","--leave-running",
"-p","--wallet-port","--unshared"}, applicationSpecificArgs=appArgs)
"--activate-if","-p","--wallet-port","--unshared"}, applicationSpecificArgs=appArgs)
Utils.Debug=args.v
pnodes=args.p if args.p > 0 else 1
startedNonProdNodes = args.txn_gen_nodes if args.txn_gen_nodes >= 2 else 2
Expand All @@ -43,6 +43,7 @@
walletPort=args.wallet_port
catchupCount=args.catchup_count if args.catchup_count > 0 else 1
totalNodes=startedNonProdNodes+pnodes+catchupCount
activateIF=args.activate_if

walletMgr=WalletMgr(True, port=walletPort)
testSuccessful=False
Expand All @@ -56,7 +57,7 @@
cluster.setWalletMgr(walletMgr)

Print("Stand up cluster")
if cluster.launch(prodCount=prodCount, onlyBios=False, pnodes=pnodes, totalNodes=totalNodes, totalProducers=pnodes*prodCount,
if cluster.launch(prodCount=prodCount, activateIF=activateIF, onlyBios=False, pnodes=pnodes, totalNodes=totalNodes, totalProducers=pnodes*prodCount,
unstartedNodes=catchupCount, loadSystemContract=True,
maximumP2pPerHost=totalNodes+trxGeneratorCnt) is False:
Utils.errorExit("Failed to stand up eos cluster.")
Expand Down
2 changes: 1 addition & 1 deletion unittests/unapplied_transaction_queue_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ auto create_test_block_state( deque<transaction_metadata_ptr> trx_metas ) {
return bsp;
}

using branch_type_legacy = fork_database<block_state_legacy_ptr>::branch_type;
using branch_type_legacy = fork_database_t<block_state_legacy_ptr>::branch_type;

template<class BRANCH_TYPE>
void add_forked( unapplied_transaction_queue& queue, const BRANCH_TYPE& forked_branch ) {
Expand Down
Loading