-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/hotstuff_integration' into GH-2125…
…-forkdb
- Loading branch information
Showing
7 changed files
with
857 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
#include "finality_test_cluster.hpp" | ||
|
||
// Construct a test network and activate IF. | ||
finality_test_cluster::finality_test_cluster() { | ||
using namespace eosio::testing; | ||
|
||
setup_node(node0, "node0"_n); | ||
setup_node(node1, "node1"_n); | ||
setup_node(node2, "node2"_n); | ||
|
||
// collect node1's votes | ||
node1.node.control->voted_block().connect( [&]( const eosio::chain::vote_message& vote ) { | ||
node1.votes.emplace_back(vote); | ||
}); | ||
// collect node2's votes | ||
node2.node.control->voted_block().connect( [&]( const eosio::chain::vote_message& vote ) { | ||
node2.votes.emplace_back(vote); | ||
}); | ||
|
||
// form a 3-chain to make LIB advacing on node0 | ||
// node0's vote (internal voting) and node1's vote make the quorum | ||
for (auto i = 0; i < 3; ++i) { | ||
produce_and_push_block(); | ||
process_node1_vote(); | ||
} | ||
FC_ASSERT(node0_lib_advancing(), "LIB has not advanced on node0"); | ||
|
||
// QC extension in the block sent to node1 and node2 makes them LIB advancing | ||
produce_and_push_block(); | ||
process_node1_vote(); | ||
FC_ASSERT(node1_lib_advancing(), "LIB has not advanced on node1"); | ||
FC_ASSERT(node2_lib_advancing(), "LIB has not advanced on node2"); | ||
|
||
// clean up processed votes | ||
for (auto& n : nodes) { | ||
n.votes.clear(); | ||
n.prev_lib_num = n.node.control->if_irreversible_block_num(); | ||
} | ||
} | ||
|
||
// node0 produces a block and pushes it to node1 and node2 | ||
void finality_test_cluster::produce_and_push_block() { | ||
auto b = node0.node.produce_block(); | ||
node1.node.push_block(b); | ||
node2.node.push_block(b); | ||
} | ||
|
||
// send node1's vote identified by "vote_index" in the collected votes | ||
eosio::chain::vote_status finality_test_cluster::process_node1_vote(uint32_t vote_index, vote_mode mode) { | ||
return process_vote( node1, vote_index, mode ); | ||
} | ||
|
||
// send node1's latest vote | ||
eosio::chain::vote_status finality_test_cluster::process_node1_vote(vote_mode mode) { | ||
return process_vote( node1, mode ); | ||
} | ||
|
||
// send node2's vote identified by "vote_index" in the collected votes | ||
eosio::chain::vote_status finality_test_cluster::process_node2_vote(uint32_t vote_index, vote_mode mode) { | ||
return process_vote( node2, vote_index, mode ); | ||
} | ||
|
||
// send node2's latest vote | ||
eosio::chain::vote_status finality_test_cluster::process_node2_vote(vote_mode mode) { | ||
return process_vote( node2, mode ); | ||
} | ||
|
||
// returns true if node0's LIB has advanced | ||
bool finality_test_cluster::node0_lib_advancing() { | ||
return lib_advancing(node0); | ||
} | ||
|
||
// returns true if node1's LIB has advanced | ||
bool finality_test_cluster::node1_lib_advancing() { | ||
return lib_advancing(node1); | ||
} | ||
|
||
// returns true if node2's LIB has advanced | ||
bool finality_test_cluster::node2_lib_advancing() { | ||
return lib_advancing(node2); | ||
} | ||
|
||
// Produces a number of blocks and returns true if LIB is advancing. | ||
// This function can be only used at the end of a test as it clears | ||
// node1_votes and node2_votes when starting. | ||
bool finality_test_cluster::produce_blocks_and_verify_lib_advancing() { | ||
// start from fresh | ||
node1.votes.clear(); | ||
node2.votes.clear(); | ||
|
||
for (auto i = 0; i < 3; ++i) { | ||
produce_and_push_block(); | ||
process_node1_vote(); | ||
if (!node0_lib_advancing() || !node1_lib_advancing() || !node2_lib_advancing()) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
void finality_test_cluster::node1_corrupt_vote_proposal_id() { | ||
node1_orig_vote = node1.votes[0]; | ||
|
||
if( node1.votes[0].proposal_id.data()[0] == 'a' ) { | ||
node1.votes[0].proposal_id.data()[0] = 'b'; | ||
} else { | ||
node1.votes[0].proposal_id.data()[0] = 'a'; | ||
} | ||
} | ||
|
||
void finality_test_cluster::node1_corrupt_vote_finalizer_key() { | ||
node1_orig_vote = node1.votes[0]; | ||
|
||
// corrupt the finalizer_key | ||
if( node1.votes[0].finalizer_key._pkey.x.d[0] == 1 ) { | ||
node1.votes[0].finalizer_key._pkey.x.d[0] = 2; | ||
} else { | ||
node1.votes[0].finalizer_key._pkey.x.d[0] = 1; | ||
} | ||
} | ||
|
||
void finality_test_cluster::node1_corrupt_vote_signature() { | ||
node1_orig_vote = node1.votes[0]; | ||
|
||
// corrupt the signature | ||
if( node1.votes[0].sig._sig.x.c0.d[0] == 1 ) { | ||
node1.votes[0].sig._sig.x.c0.d[0] = 2; | ||
} else { | ||
node1.votes[0].sig._sig.x.c0.d[0] = 1; | ||
} | ||
} | ||
|
||
void finality_test_cluster::node1_restore_to_original_vote() { | ||
node1.votes[0] = node1_orig_vote; | ||
} | ||
|
||
bool finality_test_cluster::lib_advancing(node_info& node) { | ||
auto curr_lib_num = node.node.control->if_irreversible_block_num(); | ||
auto advancing = curr_lib_num > node.prev_lib_num; | ||
// update pre_lib_num for next time check | ||
node.prev_lib_num = curr_lib_num; | ||
return advancing; | ||
} | ||
|
||
// private methods follow | ||
void finality_test_cluster::setup_node(node_info& node, eosio::chain::account_name local_finalizer) { | ||
using namespace eosio::testing; | ||
|
||
node.node.produce_block(); | ||
node.node.produce_block(); | ||
|
||
// activate hotstuff | ||
eosio::testing::base_tester::finalizer_policy_input policy_input = { | ||
.finalizers = { {.name = "node0"_n, .weight = 1}, | ||
{.name = "node1"_n, .weight = 1}, | ||
{.name = "node2"_n, .weight = 1}}, | ||
.threshold = 2, | ||
.local_finalizers = {local_finalizer} | ||
}; | ||
node.node.set_finalizers(policy_input); | ||
auto block = node.node.produce_block(); | ||
|
||
// this block contains the header extension for the instant finality | ||
std::optional<eosio::chain::block_header_extension> ext = block->extract_header_extension(eosio::chain::instant_finality_extension::extension_id()); | ||
BOOST_TEST(!!ext); | ||
std::optional<eosio::chain::finalizer_policy> fin_policy = std::get<eosio::chain::instant_finality_extension>(*ext).new_finalizer_policy; | ||
BOOST_TEST(!!fin_policy); | ||
BOOST_TEST(fin_policy->finalizers.size() == 3); | ||
BOOST_TEST(fin_policy->generation == 1); | ||
} | ||
|
||
// send a vote to node0 | ||
eosio::chain::vote_status finality_test_cluster::process_vote(node_info& node, size_t vote_index, vote_mode mode) { | ||
FC_ASSERT( vote_index < node.votes.size(), "out of bound index in process_vote" ); | ||
auto& vote = node.votes[vote_index]; | ||
if( mode == vote_mode::strong ) { | ||
vote.strong = true; | ||
} else { | ||
vote.strong = false; | ||
} | ||
return node0.node.control->process_vote_message( vote ); | ||
} | ||
|
||
eosio::chain::vote_status finality_test_cluster::process_vote(node_info& node, vote_mode mode) { | ||
auto vote_index = node.votes.size() - 1; | ||
return process_vote( node, vote_index, mode ); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
#pragma once | ||
|
||
#include <eosio/chain/hotstuff/finalizer_authority.hpp> | ||
|
||
#pragma GCC diagnostic push | ||
#pragma GCC diagnostic ignored "-Wsign-compare" | ||
#include <boost/test/unit_test.hpp> | ||
#pragma GCC diagnostic pop | ||
#include <eosio/testing/tester.hpp> | ||
|
||
// Set up a test network which consists of 3 nodes: | ||
// * node0 produces blocks and pushes them to node1 and node2; | ||
// node0 votes the blocks it produces internally. | ||
// * node1 votes on the proposal sent by node0 | ||
// * node2 votes on the proposal sent by node0 | ||
// Each node has one finalizer: node0 -- "node0"_n, node1 -- "node1"_n, node2 -- "node2"_n. | ||
// Quorum is set to 2. | ||
// After starup up, IF are activated on both nodes. | ||
// | ||
// APIs are provided to modify/delay/reoder/remove votes from node1 and node2 to node0. | ||
|
||
|
||
class finality_test_cluster { | ||
public: | ||
|
||
enum class vote_mode { | ||
strong, | ||
weak, | ||
}; | ||
|
||
// Construct a test network and activate IF. | ||
finality_test_cluster(); | ||
|
||
// node0 produces a block and pushes it to node1 and node2 | ||
void produce_and_push_block(); | ||
|
||
// send node1's vote identified by "index" in the collected votes | ||
eosio::chain::vote_status process_node1_vote(uint32_t vote_index, vote_mode mode = vote_mode::strong); | ||
|
||
// send node1's latest vote | ||
eosio::chain::vote_status process_node1_vote(vote_mode mode = vote_mode::strong); | ||
|
||
// send node2's vote identified by "index" in the collected votes | ||
eosio::chain::vote_status process_node2_vote(uint32_t vote_index, vote_mode mode = vote_mode::strong); | ||
|
||
// send node2's latest vote | ||
eosio::chain::vote_status process_node2_vote(vote_mode mode = vote_mode::strong); | ||
|
||
// returns true if node0's LIB has advanced | ||
bool node0_lib_advancing(); | ||
|
||
// returns true if node1's LIB has advanced | ||
bool node1_lib_advancing(); | ||
|
||
// returns true if node2's LIB has advanced | ||
bool node2_lib_advancing(); | ||
|
||
// Produces a number of blocks and returns true if LIB is advancing. | ||
// This function can be only used at the end of a test as it clears | ||
// node1_votes and node2_votes when starting. | ||
bool produce_blocks_and_verify_lib_advancing(); | ||
|
||
// Intentionally corrupt node1's vote's proposal_id and save the original vote | ||
void node1_corrupt_vote_proposal_id(); | ||
|
||
// Intentionally corrupt node1's vote's finalizer_key and save the original vote | ||
void node1_corrupt_vote_finalizer_key(); | ||
|
||
// Intentionally corrupt node1's vote's signature and save the original vote | ||
void node1_corrupt_vote_signature(); | ||
|
||
// Restore node1's original vote | ||
void node1_restore_to_original_vote(); | ||
|
||
private: | ||
|
||
struct node_info { | ||
eosio::testing::tester node; | ||
uint32_t prev_lib_num{0}; | ||
std::vector<eosio::chain::vote_message> votes; | ||
}; | ||
|
||
std::array<node_info, 3> nodes; | ||
node_info& node0 = nodes[0]; | ||
node_info& node1 = nodes[1]; | ||
node_info& node2 = nodes[2]; | ||
|
||
eosio::chain::vote_message node1_orig_vote; | ||
|
||
// sets up "node_index" node | ||
void setup_node(node_info& node, eosio::chain::account_name local_finalizer); | ||
|
||
// returns true if LIB advances on "node_index" node | ||
bool lib_advancing(node_info& node); | ||
|
||
// send "vote_index" vote on node to node0 | ||
eosio::chain::vote_status process_vote(node_info& node, size_t vote_index, vote_mode mode); | ||
|
||
// send the latest vote on "node_index" node to node0 | ||
eosio::chain::vote_status process_vote(node_info& node, vote_mode mode); | ||
}; |
Oops, something went wrong.