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] Add testcase validating the new fsi design from issue #660

Merged
merged 28 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7b0f1e7
Start on `gh_534_liveness_issue` testcase
greg7mdp Aug 23, 2024
994a9a0
Merge branch 'gh_544' of github.com:AntelopeIO/spring into gh_621
greg7mdp Aug 23, 2024
3a9d896
Some progress on `gh_534_liveness_issue` testcase
greg7mdp Aug 23, 2024
fe23716
Merge branch 'gh_544' of github.com:AntelopeIO/spring into gh_621
greg7mdp Aug 24, 2024
baad9f2
Minor cleanup
greg7mdp Aug 26, 2024
040d4b2
Implement vote delay for `savanna_cluster`
greg7mdp Aug 26, 2024
0391637
Finish `gh_534_liveness_issue` testcase
greg7mdp Aug 26, 2024
74d1668
Validate testcase `gh_534_liveness_issue` with fsi changes.
greg7mdp Aug 26, 2024
bf5f7cd
Comment out new testcase as it fails with current fsi implementation
greg7mdp Aug 26, 2024
1d19e44
Add big block comment copied from issue #621.
greg7mdp Aug 26, 2024
5cbed82
Merge branch 'release/1.0' of github.com:AntelopeIO/spring into gh_621
greg7mdp Aug 26, 2024
b8d832c
Address PR comment.
greg7mdp Aug 26, 2024
27cbf08
Merge branch 'release/1.0' of github.com:AntelopeIO/spring into gh_621
greg7mdp Aug 26, 2024
8f383b6
Address PR comments, incl. not skipping slots for b5 to b8.
greg7mdp Aug 27, 2024
3a5f697
Make the checks a little cleaner by using `strong_qc/weak_qc` and sam…
greg7mdp Aug 27, 2024
9153e40
Forgot this file!
greg7mdp Aug 27, 2024
8a0163c
Merge branch 'release/1.0' of github.com:AntelopeIO/spring into gh_621
greg7mdp Aug 27, 2024
511c2a1
Remove `#ifdef`, align comments.
greg7mdp Aug 27, 2024
d873f75
Merge branch 'release/1.0' of github.com:AntelopeIO/spring into gh_621
greg7mdp Aug 27, 2024
d3bb815
Add `fsi` checks.
greg7mdp Aug 27, 2024
4d5f1cb
Make `check_fsi` checks a bit clearer.
greg7mdp Aug 27, 2024
81df896
Make `get_node_finalizers` private and fix test.
greg7mdp Aug 27, 2024
7db6083
Add `Boost::crc` as a controller dependency.
greg7mdp Aug 27, 2024
9758307
Move `check_fsi()` to `savanna_cluster` so it can be re-used.
greg7mdp Aug 27, 2024
cd44dc7
Add `Boost::crc` to `EosioTester.cmake.in` and `EosioTesterBuild.cmak…
greg7mdp Aug 27, 2024
b4fbc56
Merge pull request #636 from AntelopeIO/gh_621
greg7mdp Aug 28, 2024
0e7392d
Merge remote-tracking branch 'origin/release/1.0' into gh_621_main
greg7mdp Aug 28, 2024
23d80b0
Merge branch 'main' of github.com:AntelopeIO/spring into gh_621_main
greg7mdp Aug 28, 2024
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
1 change: 1 addition & 0 deletions CMakeModules/EosioTester.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ target_link_libraries(EosioChain INTERFACE
Boost::interprocess
Boost::asio
Boost::beast
Boost::crc
Boost::signals2
Boost::iostreams
"-lz" # Needed by Boost iostreams
Expand Down
1 change: 1 addition & 0 deletions CMakeModules/EosioTesterBuild.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ target_link_libraries(EosioChain INTERFACE
Boost::interprocess
Boost::asio
Boost::beast
Boost::crc
Boost::signals2
Boost::iostreams
"-lz" # Needed by Boost iostreams
Expand Down
4 changes: 4 additions & 0 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6001,6 +6001,10 @@ bool controller::is_node_finalizer_key(const bls_public_key& key) const {
return my->my_finalizers.contains(key);
}

const my_finalizers_t& controller::get_node_finalizers() const {
return my->my_finalizers;
}

/// Protocol feature activation handlers:

template<>
Expand Down
9 changes: 9 additions & 0 deletions libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <eosio/chain/protocol_feature_manager.hpp>
#include <eosio/chain/webassembly/eos-vm-oc/config.hpp>
#include <eosio/chain/finality/vote_message.hpp>
#include <eosio/chain/finality/finalizer.hpp>

#include <chainbase/pinnable_mapped_file.hpp>

Expand All @@ -22,6 +23,10 @@ namespace boost::asio {
class thread_pool;
}

namespace savanna_cluster {
class node_t;
}

namespace eosio::vm { class wasm_allocator; }

namespace eosio::chain {
Expand Down Expand Up @@ -452,9 +457,13 @@ namespace eosio::chain {
// is the bls key a registered finalizer key of this node, thread safe
bool is_node_finalizer_key(const bls_public_key& key) const;


private:
const my_finalizers_t& get_node_finalizers() const; // used for tests (purpose is inspecting fsi).

friend class apply_context;
friend class transaction_context;
friend class savanna_cluster::node_t;

chainbase::database& mutable_db()const;

Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/include/eosio/chain/finality/finalizer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ namespace eosio::chain {
fsi_map load_finalizer_safety_info();

// for testing purposes only, not thread safe
const fsi_t& get_fsi(const bls_public_key& k) { return finalizers[k].fsi; }
const fsi_t& get_fsi(const bls_public_key& k) const { return finalizers.at(k).fsi; }
void set_fsi(const bls_public_key& k, const fsi_t& fsi) { finalizers[k].fsi = fsi; }

private:
Expand Down
3 changes: 1 addition & 2 deletions unittests/finalizer_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,7 @@ BOOST_AUTO_TEST_CASE( corrupt_finalizer_safety_file ) try {
finalizer_safety_exception);

// make sure the safety info for our finalizer that we saved above is restored correctly
BOOST_CHECK_NE(fset.get_fsi(k.pubkey), fsi);
BOOST_CHECK_EQUAL(fset.get_fsi(k.pubkey), fsi_t());
BOOST_CHECK(!fset.contains(k.pubkey));
}

} FC_LOG_AND_RETHROW()
Expand Down
32 changes: 22 additions & 10 deletions unittests/savanna_cluster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,50 @@ namespace savanna_cluster {

node_t::node_t(size_t node_idx, cluster_t& cluster, setup_policy policy /* = setup_policy::none */)
: tester(policy)
, node_idx(node_idx) {
, _node_idx(node_idx)
, _last_vote({}, false)
{

// since we are creating forks, finalizers may be locked on another fork and unable to vote.
do_check_for_votes(false);

voted_block_cb = [&, node_idx](const eosio::chain::vote_signal_params& v) {
_voted_block_cb = [&, node_idx](const eosio::chain::vote_signal_params& v) {
// no mutex needed because controller is set in tester (via `disable_async_voting(true)`)
// to vote (and emit the `voted_block` signal) synchronously.
// --------------------------------------------------------------------------------------
vote_result_t status = std::get<1>(v);

if (status == vote_result_t::success) {
vote_message_ptr vote_msg = std::get<2>(v);
last_vote = vote_t(vote_msg);
if (propagate_votes)
cluster.dispatch_vote_to_peers(node_idx, skip_self_t::yes, std::get<2>(v));
_last_vote = vote_t(vote_msg->block_id, vote_msg->strong);

if (_propagate_votes) {
if (_vote_delay)
_delayed_votes.push_back(std::move(vote_msg));
while (_delayed_votes.size() > _vote_delay) {
vote_message_ptr vote = _delayed_votes.front();
_delayed_votes.erase(_delayed_votes.cbegin());
cluster.dispatch_vote_to_peers(node_idx, skip_self_t::yes, vote);
}
if (!_vote_delay)
cluster.dispatch_vote_to_peers(node_idx, skip_self_t::yes, vote_msg);
}
}
};

// called on `commit_block`, for both blocks received from `push_block` and produced blocks
accepted_block_cb = [&, node_idx](const eosio::chain::block_signal_params& p) {
if (!pushing_a_block) {
_accepted_block_cb = [&, node_idx](const eosio::chain::block_signal_params& p) {
if (!_pushing_a_block) {
// we want to propagate only blocks we produce, not the ones we receive from the network
auto& b = std::get<0>(p);
cluster.push_block_to_peers(node_idx, skip_self_t::yes, b);
}
};

auto node_initialization_fn = [&, node_idx]() {
[[maybe_unused]] auto _a = control->voted_block().connect(voted_block_cb);
[[maybe_unused]] auto _b = control->accepted_block().connect(accepted_block_cb);
tester::set_node_finalizers(node_finalizers);
[[maybe_unused]] auto _a = control->voted_block().connect(_voted_block_cb);
[[maybe_unused]] auto _b = control->accepted_block().connect(_accepted_block_cb);
tester::set_node_finalizers(_node_finalizers);
cluster.get_new_blocks_from_peers(node_idx);
};

Expand Down
145 changes: 94 additions & 51 deletions unittests/savanna_cluster.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <eosio/chain/finality/finalizer_authority.hpp>
#include <eosio/chain/finality/finalizer.hpp>
#include <fc/crypto/bls_private_key.hpp>

#include <eosio/testing/tester.hpp>
Expand All @@ -22,6 +23,7 @@ namespace savanna_cluster {
using block_header = eosio::chain::block_header;
using tester = eosio::testing::tester;
using setup_policy = eosio::testing::setup_policy;
using fsi_t = eosio::chain::finalizer_safety_information;

class cluster_t;

Expand Down Expand Up @@ -51,34 +53,68 @@ namespace savanna_cluster {
vector<bls_private_key> privkeys;
};

// two classes for comparisons in BOOST_REQUIRE_EQUAL
// --------------------------------------------------
struct vote_t {
friend std::ostream& operator<<(std::ostream& s, const vote_t& v) {
s << "vote_t(" << v.id.str().substr(8, 16) << ", " << (v.strong ? "strong" : "weak") << ")";
return s;
}
bool operator==(const vote_t&) const = default;

block_id_type id;
bool strong;
};

struct strong_vote : public vote_t {
explicit strong_vote(const signed_block_ptr& p) : vote_t(p->calculate_id(), true) {}
};
struct weak_vote : public vote_t {
explicit weak_vote(const signed_block_ptr& p) : vote_t(p->calculate_id(), false) {}
};


struct qc_s {
explicit qc_s(uint32_t block_num, bool strong) : block_num(block_num), strong(strong) {}
explicit qc_s(const std::optional<qc_t>& qc) : block_num(qc->block_num), strong(qc->is_strong()) {}

friend std::ostream& operator<<(std::ostream& s, const qc_s& v) {
s << "qc_s(" << v.block_num << ", " << (v.strong ? "strong" : "weak") << ")";
return s;
}
bool operator==(const qc_s&) const = default;

uint32_t block_num; // claimed block
bool strong;
};

struct strong_qc : public qc_s {
explicit strong_qc(const signed_block_ptr& p) : qc_s(p->block_num(), true) {}
};
struct weak_qc : public qc_s {
explicit weak_qc(const signed_block_ptr& p) : qc_s(p->block_num(), false) {}
};

struct fsi_expect {
const signed_block_ptr& last_vote;
const signed_block_ptr& lock;
block_timestamp_type other_branch_latest_time;
};

// ----------------------------------------------------------------------------
class node_t : public tester {
private:
size_t node_idx;
bool pushing_a_block{false};

std::function<void(const block_signal_params&)> accepted_block_cb;
std::function<void(const vote_signal_params&)> voted_block_cb;

public:
struct vote_t {
vote_t() : strong(false) {}
explicit vote_t(const vote_message_ptr& p) : id(p->block_id), strong(p->strong) {}
explicit vote_t(const signed_block_ptr& p, bool strong) : id(p->calculate_id()), strong(strong) {}

friend std::ostream& operator<<(std::ostream& s, const vote_t& v) {
s << "vote_t(" << v.id.str().substr(8, 16) << ", " << (v.strong ? "strong" : "weak") << ")";
return s;
}
bool operator==(const vote_t&) const = default;
size_t _node_idx;
bool _pushing_a_block{false};
bool _propagate_votes{true}; // if false, votes are dropped
vote_t _last_vote;
std::vector<account_name> _node_finalizers;

block_id_type id;
bool strong;
};
size_t _vote_delay{0}; // delay vote propagation by this much
std::vector<vote_message_ptr> _delayed_votes;

bool propagate_votes{true};
vote_t last_vote;
std::vector<account_name> node_finalizers;
std::function<void(const block_signal_params&)> _accepted_block_cb;
std::function<void(const vote_signal_params&)> _voted_block_cb;

public:
node_t(size_t node_idx, cluster_t& cluster, setup_policy policy = setup_policy::none);
Expand All @@ -87,9 +123,18 @@ namespace savanna_cluster {

node_t(node_t&&) = default;

bool& propagate_votes() { return _propagate_votes; }

size_t& vote_delay() { return _vote_delay; }

const vote_t& last_vote() const { return _last_vote; }

void set_node_finalizers(std::span<const account_name> names) {
node_finalizers = std::vector<account_name>{ names.begin(), names.end() };
tester::set_node_finalizers(node_finalizers);
_node_finalizers = std::vector<account_name>{ names.begin(), names.end() };
if (control) {
// node is "open", se we can update the tester immediately
tester::set_node_finalizers(_node_finalizers);
}
}

void transition_to_savanna(std::span<const account_name> finalizer_policy_names) {
Expand Down Expand Up @@ -167,8 +212,8 @@ namespace savanna_cluster {

void push_block(const signed_block_ptr& b) {
if (is_open() && !fetch_block_by_id(b->calculate_id())) {
assert(!pushing_a_block);
fc::scoped_set_value set_pushing_a_block(pushing_a_block, true);
assert(!_pushing_a_block);
fc::scoped_set_value set_pushing_a_block(_pushing_a_block, true);
tester::push_block(b);
}
}
Expand All @@ -189,19 +234,19 @@ namespace savanna_cluster {
}

std::string snapshot() const {
dlog("node ${i} - taking snapshot", ("i", node_idx));
dlog("node ${i} - taking snapshot", ("i", _node_idx));
auto writer = buffered_snapshot_suite::get_writer();
control->write_snapshot(writer);
return buffered_snapshot_suite::finalize(writer);
}

void open_from_snapshot(const std::string& snapshot) {
dlog("node ${i} - restoring from snapshot", ("i", node_idx));
dlog("node ${i} - restoring from snapshot", ("i", _node_idx));
open(buffered_snapshot_suite::get_reader(snapshot));
}

std::vector<uint8_t> save_fsi() const {
dlog("node ${i} - saving fsi", ("i", node_idx));
dlog("node ${i} - saving fsi", ("i", _node_idx));
auto finalizer_path = get_fsi_path();
std::ifstream file(finalizer_path.generic_string(), std::ios::binary | std::ios::ate);
std::streamsize size = file.tellg();
Expand All @@ -214,21 +259,21 @@ namespace savanna_cluster {
}

void overwrite_fsi(const std::vector<uint8_t>& fsi) const {
dlog("node ${i} - overwriting fsi", ("i", node_idx));
dlog("node ${i} - overwriting fsi", ("i", _node_idx));
auto finalizer_path = get_fsi_path();
std::ofstream file(finalizer_path.generic_string(), std::ios::binary);
assert(!fsi.empty());
file.write((const char *)fsi.data(), fsi.size());
}

void remove_fsi() {
dlog("node ${i} - removing fsi", ("i", node_idx));
dlog("node ${i} - removing fsi", ("i", _node_idx));
remove_all(get_fsi_path());
}

void remove_state() {
auto state_path = cfg.state_dir;
dlog("node ${i} - removing state data from: ${state_path}", ("i", node_idx)("${state_path}", state_path));
dlog("node ${i} - removing state data from: ${state_path}", ("i", _node_idx)("${state_path}", state_path));
remove_all(state_path);
fs::create_directories(state_path);
}
Expand All @@ -246,20 +291,34 @@ namespace savanna_cluster {
for (auto const& dir_entry : std::filesystem::directory_iterator{path}) {
auto path = dir_entry.path();
if (path.filename().generic_string() != "reversible") {
dlog("node ${i} - removing : ${path}", ("i", node_idx)("${path}", path));
dlog("node ${i} - removing : ${path}", ("i", _node_idx)("${path}", path));
remove_all(path);
}
}
}

const fsi_t& get_fsi(size_t idx = 0) const {
assert(control);
assert(idx < _node_finalizers.size());
auto [privkey, pubkey, pop] = get_bls_key(_node_finalizers[idx]);
return control->get_node_finalizers().get_fsi(pubkey);
}

void check_fsi(const fsi_expect& expected) {
const fsi_t& fsi = get_fsi();
BOOST_REQUIRE_EQUAL(fsi.last_vote.block_id, expected.last_vote->calculate_id());
BOOST_REQUIRE_EQUAL(fsi.lock.block_id, expected.lock->calculate_id());
BOOST_REQUIRE_EQUAL(fsi.other_branch_latest_time, expected.other_branch_latest_time);
}

private:
// always removes reversible data (`blocks/reversible`)
// optionally remove the blocks log as well by deleting the whole `blocks` directory
// ---------------------------------------------------------------------------------
void remove_blocks(bool rm_blocks_log) {
auto reversible_path = cfg.blocks_dir / config::reversible_blocks_dir_name;
auto& path = rm_blocks_log ? cfg.blocks_dir : reversible_path;
dlog("node ${i} - removing : ${path}", ("i", node_idx)("${path}", path));
dlog("node ${i} - removing : ${path}", ("i", _node_idx)("${path}", path));
remove_all(path);
fs::create_directories(reversible_path);
}
Expand Down Expand Up @@ -471,22 +530,6 @@ namespace savanna_cluster {

size_t num_nodes() const { return _num_nodes; }

// Class for comparisons in BOOST_REQUIRE_EQUAL
// --------------------------------------------
struct qc_s {
explicit qc_s(const signed_block_ptr& p, bool strong) : block_num(p->block_num()), strong(strong) {}
explicit qc_s(const std::optional<qc_t>& qc) : block_num(qc->block_num), strong(qc->is_strong()) {}

friend std::ostream& operator<<(std::ostream& s, const qc_s& v) {
s << "qc_s(" << v.block_num << ", " << (v.strong ? "strong" : "weak") << ")";
return s;
}
bool operator==(const qc_s&) const = default;

uint32_t block_num; // claimed block
bool strong;
};

static qc_claim_t qc_claim(const signed_block_ptr& b) {
return b->extract_header_extension<finality_extension>().qc_claim;
}
Expand Down
Loading