Skip to content

Commit

Permalink
GH-3 Add unittest for vote_processor. Modify vote_processor to make i…
Browse files Browse the repository at this point in the history
…t easier to test.
  • Loading branch information
heifner committed Apr 11, 2024
1 parent 1dcf7c1 commit 295f7d4
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 13 deletions.
7 changes: 6 additions & 1 deletion libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,12 @@ struct controller_impl {
signal<void(std::tuple<const transaction_trace_ptr&, const packed_transaction_ptr&>)> applied_transaction;
signal<void(const vote_signal_params&)> voted_block;

vote_processor_t vote_processor{fork_db, voted_block};
vote_processor_t vote_processor{voted_block,
[this](const block_id_type& id) -> block_state_ptr {
return fork_db.apply_s<block_state_ptr>([&](const auto& forkdb) {
return forkdb.get_block(id);
});
}};

int64_t set_proposed_producers( vector<producer_authority> producers );
int64_t set_proposed_producers_legacy( vector<producer_authority> producers );
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/include/eosio/chain/block_header.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ namespace eosio::chain {
// When block header is validated in block_header_state's next(),
// it is already validate if schedule_version == proper_svnn_schedule_version,
// finality extension must exist.
bool is_proper_svnn_block() const { return ( schedule_version == proper_svnn_schedule_version ); }
bool is_proper_svnn_block() const { return ( schedule_version == proper_svnn_schedule_version ); }

header_extension_multimap validate_and_extract_header_extensions()const;
std::optional<block_header_extension> extract_header_extension(uint16_t extension_id)const;
Expand Down
29 changes: 19 additions & 10 deletions libraries/chain/include/eosio/chain/vote_processor.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <eosio/chain/hotstuff/hotstuff.hpp>
#include <eosio/chain/block_state.hpp>

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/composite_key.hpp>
Expand Down Expand Up @@ -32,7 +33,7 @@ class vote_processor_t {
using vote_ptr = std::shared_ptr<vote>;
using vote_signal_type = decltype(controller({},chain_id_type::empty_chain_id()).voted_block());

typedef multi_index_container< vote_ptr,
using vote_index_type = boost::multi_index_container< vote_ptr,
indexed_by<
ordered_non_unique<tag<by_block_num>,
composite_key<vote,
Expand All @@ -43,17 +44,21 @@ class vote_processor_t {
ordered_non_unique< tag<by_connection>, member<vote, uint32_t, &vote::connection_id> >,
ordered_unique< tag<by_vote>, member<vote, vote_message, &vote::msg> >
>
> vote_index_type;
>;

using fetch_block_func_t = std::function<block_state_ptr(const block_id_type&)>;

vote_signal_type& vote_signal;
fetch_block_func_t fetch_block_func;

fork_database& fork_db;
std::mutex mtx;
std::condition_variable cv;
vote_index_type index;
// connection, count of messages
std::map<uint32_t, uint16_t> num_messages;

std::atomic<block_num_type> lib{0};
std::atomic<bool> stopped{false};
vote_signal_type& vote_signal;
named_thread_pool<vote> thread_pool;

private:
Expand Down Expand Up @@ -112,16 +117,22 @@ class vote_processor_t {
}

public:
explicit vote_processor_t(fork_database& forkdb, vote_signal_type& vote_signal)
: fork_db(forkdb)
, vote_signal(vote_signal) {}
explicit vote_processor_t(vote_signal_type& vote_signal, fetch_block_func_t&& get_block)
: vote_signal(vote_signal)
, fetch_block_func(get_block)
{}

~vote_processor_t() {
stopped = true;
std::lock_guard g(mtx);
cv.notify_one();
}

size_t size() {
std::lock_guard g(mtx);
return index.size();
}

void start(size_t num_threads, decltype(thread_pool)::on_except_t&& on_except) {
assert(num_threads > 1); // need at least two as one is used for coordinatation
thread_pool.start( num_threads, std::move(on_except));
Expand Down Expand Up @@ -151,9 +162,7 @@ class vote_processor_t {
}
for (auto i = idx.begin(); i != idx.end();) {
auto& vt = *i;
block_state_ptr bsp = fork_db.apply_s<block_state_ptr>([&](const auto& forkdb) {
return forkdb.get_block(vt->id());
});
block_state_ptr bsp = fetch_block_func(vt->id());
if (bsp) {
if (!bsp->is_proper_svnn_block()) {
if (remove_all_for_block(idx, i, bsp->id()))
Expand Down
2 changes: 1 addition & 1 deletion unittests/subjective_billing_tests.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <boost/test/unit_test.hpp>

#include "eosio/chain/subjective_billing.hpp"
#include <eosio/chain/subjective_billing.hpp>
#include <eosio/testing/tester.hpp>
#include <fc/time.hpp>

Expand Down
228 changes: 228 additions & 0 deletions unittests/vote_processor_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
#include <boost/test/unit_test.hpp>

#include <eosio/chain/controller.hpp>
#include <eosio/chain/vote_processor.hpp>
#include <eosio/testing/tester.hpp>
#include <fc/bitutil.hpp>
#include <boost/signals2/signal.hpp>
#include <latch>

namespace std {
std::ostream& operator<<(std::ostream& os, const eosio::chain::vote_message& v) {
os << "vote_message{" << v.block_id << std::endl;
return os;
}
std::ostream& operator<<(std::ostream& os, const eosio::chain::vote_status& v) {
os << fc::reflector<eosio::chain::vote_status>::to_string(v) << std::endl;
return os;
}
}

namespace {

using namespace eosio;
using namespace eosio::chain;

block_id_type make_block_id(uint32_t block_num) {
block_id_type block_id;
block_id._hash[0] &= 0xffffffff00000000;
block_id._hash[0] += fc::endian_reverse_u32(block_num);
return block_id;
}

bls_private_key bls_priv_key_0 = bls_private_key::generate();
bls_private_key bls_priv_key_1 = bls_private_key::generate();
bls_private_key bls_priv_key_2 = bls_private_key::generate();
std::vector<bls_private_key> bls_priv_keys{bls_priv_key_0, bls_priv_key_1, bls_priv_key_2};

auto create_genesis_block_state() { // block 2
signed_block_ptr block = std::make_shared<signed_block>();

block->producer = eosio::chain::config::system_account_name;
auto pub_key = eosio::testing::base_tester::get_public_key( block->producer, "active" );

std::vector<finalizer_authority> finalizers;
finalizers.push_back(finalizer_authority{.description = "first", .weight = 1, .public_key = bls_priv_keys.at(0).get_public_key()});
finalizers.push_back(finalizer_authority{.description = "first", .weight = 1, .public_key = bls_priv_keys.at(1).get_public_key()});
finalizers.push_back(finalizer_authority{.description = "first", .weight = 1, .public_key = bls_priv_keys.at(2).get_public_key()});
finalizer_policy new_finalizer_policy{.finalizers = finalizers};
qc_claim_t initial_if_claim { .block_num = 2,
.is_strong_qc = false };
emplace_extension(block->header_extensions, instant_finality_extension::extension_id(),
fc::raw::pack(instant_finality_extension{ initial_if_claim, new_finalizer_policy, {} }));

producer_authority_schedule schedule = { 0, { producer_authority{block->producer, block_signing_authority_v0{ 1, {{pub_key, 1}} } } } };
auto genesis = std::make_shared<block_state>();
genesis->block = block;
genesis->active_finalizer_policy = std::make_shared<finalizer_policy>(new_finalizer_policy);
genesis->block->previous = make_block_id(1);
genesis->active_proposer_policy = std::make_shared<proposer_policy>(proposer_policy{.proposer_schedule = schedule});
genesis->core = finality_core::create_core_for_genesis_block(1);
genesis->block_id = genesis->block->calculate_id();
return genesis;
}

auto create_test_block_state(const block_state_ptr& prev) {
static block_timestamp_type timestamp;
timestamp = timestamp.next(); // each test block state will be unique
signed_block_ptr block = std::make_shared<signed_block>(prev->block->clone());
block->producer = eosio::chain::config::system_account_name;
block->previous = prev->id();
block->timestamp = timestamp;

auto priv_key = eosio::testing::base_tester::get_private_key( block->producer, "active" );
auto pub_key = eosio::testing::base_tester::get_public_key( block->producer, "active" );

auto sig_digest = digest_type::hash("something");
block->producer_signature = priv_key.sign( sig_digest );

vector<private_key_type> signing_keys;
signing_keys.emplace_back( std::move( priv_key ) );

auto signer = [&]( digest_type d ) {
std::vector<signature_type> result;
result.reserve(signing_keys.size());
for (const auto& k: signing_keys)
result.emplace_back(k.sign(d));
return result;
};
block_header_state bhs = *prev;
bhs.header = *block;
bhs.header.timestamp = timestamp;
bhs.header.previous = prev->id();
bhs.header.schedule_version = block_header::proper_svnn_schedule_version;
bhs.block_id = block->calculate_id();

auto bsp = std::make_shared<block_state>(bhs,
deque<transaction_metadata_ptr>{},
deque<transaction_receipt>{},
std::optional<valid_t>{},
std::optional<quorum_certificate>{},
signer,
block_signing_authority_v0{ 1, {{pub_key, 1}} },
digest_type{});

return bsp;
}

vote_message make_empty_message(const block_id_type& id) {
vote_message vm;
vm.block_id = id;
return vm;
}

vote_message make_vote_message(const block_state_ptr& bsp) {
vote_message vm;
vm.block_id = bsp->id();
vm.strong = true;
size_t i = bsp->block_num() % bls_priv_keys.size();
vm.finalizer_key = bls_priv_keys.at(i).get_public_key();
vm.sig = bls_priv_keys.at(i).sign({(uint8_t*)bsp->strong_digest.data(), (uint8_t*)bsp->strong_digest.data() + bsp->strong_digest.data_size()});
return vm;
}

BOOST_AUTO_TEST_SUITE(vote_processor_tests)

BOOST_AUTO_TEST_CASE( vote_processor_test ) {
boost::signals2::signal<void(const vote_signal_params&)> voted_block;

uint32_t received_connection_id = 0;
vote_status received_vote_status = vote_status::unknown_block;
vote_message received_vote_message{};

std::unique_ptr<std::latch> signaled;
std::mutex forkdb_mtx;
std::map<block_id_type, block_state_ptr> forkdb;
auto add_to_forkdb = [&](const block_state_ptr& bsp) {
std::lock_guard g(forkdb_mtx);
forkdb[bsp->id()] = bsp;
};

voted_block.connect( [&]( const vote_signal_params& vote_signal ) {
received_connection_id = std::get<0>(vote_signal);
received_vote_status = std::get<1>(vote_signal);
received_vote_message = std::get<2>(vote_signal);
signaled->count_down();
} );

vote_processor_t vp{voted_block, [&](const block_id_type& id) -> block_state_ptr {
std::lock_guard g(forkdb_mtx);
return forkdb[id];
}};
vp.start(2, [](const fc::exception& e) {
edump((e));
BOOST_REQUIRE(false);
});

{ // empty fork db, block never found, never signaled
vote_message vm1 = make_empty_message(make_block_id(1));
signaled = std::make_unique<std::latch>(1);
vp.process_vote_message(1, vm1);
for (size_t i = 0; i < 5; ++i) {
BOOST_CHECK(!signaled->try_wait()); // not signaled because no block
std::this_thread::sleep_for(std::chrono::milliseconds{5});
}
BOOST_CHECK(vp.size() == 1);
// move lib past block
vp.notify_lib(2);
for (size_t i = 0; i < 5; ++i) {
if (vp.size() == 0) break;
std::this_thread::sleep_for(std::chrono::milliseconds{5});
}
BOOST_CHECK(vp.size() == 0);
}
{ // process a valid vote
signaled = std::make_unique<std::latch>(1);
auto gensis = create_genesis_block_state();
auto bsp = create_test_block_state(gensis);
BOOST_CHECK_EQUAL(bsp->block_num(), 3);
vote_message m1 = make_vote_message(bsp);
add_to_forkdb(bsp);
vp.process_vote_message(1, m1);
// duplicate ignored
vp.process_vote_message(1, m1);
signaled->wait();
BOOST_CHECK(1 == received_connection_id);
BOOST_CHECK(vote_status::success == received_vote_status);
BOOST_CHECK(m1 == received_vote_message);
}
{ // process an invalid signature vote
signaled = std::make_unique<std::latch>(1);
auto gensis = create_genesis_block_state();
auto bsp = create_test_block_state(gensis);
BOOST_CHECK_EQUAL(bsp->block_num(), 3);
vote_message m1 = make_vote_message(bsp);
m1.strong = false; // signed with strong_digest
add_to_forkdb(bsp);
vp.process_vote_message(1, m1);
signaled->wait();
BOOST_CHECK(1 == received_connection_id);
BOOST_CHECK(vote_status::invalid_signature == received_vote_status);
BOOST_CHECK(m1 == received_vote_message);
}
{ // process two diff block votes
signaled = std::make_unique<std::latch>(2);
auto gensis = create_genesis_block_state();
auto bsp = create_test_block_state(gensis);
auto bsp2 = create_test_block_state(bsp);
vote_message m1 = make_vote_message(bsp);
vote_message m2 = make_vote_message(bsp2);
vp.process_vote_message(2, m1);
vp.process_vote_message(2, m2);
for (size_t i = 0; i < 5; ++i) {
if (vp.size() == 2) break;
std::this_thread::sleep_for(std::chrono::milliseconds{5});
}
BOOST_CHECK(vp.size() == 2);
add_to_forkdb(bsp);
add_to_forkdb(bsp2);
signaled->wait();
BOOST_CHECK(2 == received_connection_id);
BOOST_CHECK(vote_status::success == received_vote_status);
BOOST_CHECK(m1 == received_vote_message || m2 == received_vote_message);
}
}

BOOST_AUTO_TEST_SUITE_END()

}

0 comments on commit 295f7d4

Please sign in to comment.