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:Unification: Integrate vote message signatures into new fork database #2067

Closed
wants to merge 12 commits into from
Closed
1 change: 1 addition & 0 deletions libraries/chain/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ set(CHAIN_HOTSTUFF_SOURCES
hotstuff/instant_finality_extension.cpp
hotstuff/qc_chain.cpp
hotstuff/hotstuff.cpp
hotstuff/finality_controller.cpp
)

add_library(eosio_rapidjson INTERFACE)
Expand Down
30 changes: 24 additions & 6 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <eosio/chain/hotstuff/finalizer_authority.hpp>
#include <eosio/chain/hotstuff/hotstuff.hpp>
#include <eosio/chain/hotstuff/chain_pacemaker.hpp>
#include <eosio/chain/hotstuff/finality_controller.hpp>

#include <chainbase/chainbase.hpp>
#include <eosio/vm/allocator.hpp>
Expand Down Expand Up @@ -764,6 +765,17 @@ struct controller_impl {
return std::visit(overloaded{[&](block_data_legacy_t& bd) -> R { return bd.template apply<R>(f); },
[&](block_data_new_t& bd) -> R { return {}; }}, v);
}

template <class R, class F>
R apply_block_data_new(F& f) {
if constexpr (std::is_same_v<void, R>)
std::visit(overloaded{[&](block_data_legacy_t& bd) {},
[&](block_data_new_t& bd) { bd.template apply<R>(f); }}, v);
else
return std::visit(overloaded{[&](block_data_legacy_t& bd) -> R { return {}; },
[&](block_data_new_t& bd) -> R { return bd.template apply<R>(f); }}, v);
}

};

reset_new_handler rnh; // placed here to allow for this to be set before constructing the other fields
Expand All @@ -774,6 +786,7 @@ struct controller_impl {
std::optional<pending_state> pending;
block_data_t block_data; // includes `head` aand `fork_db`
std::optional<chain_pacemaker> pacemaker;
finality_controller finality_control;
std::atomic<uint32_t> hs_irreversible_block_num{0};
resource_limits_manager resource_limits;
subjective_billing subjective_bill;
Expand Down Expand Up @@ -3856,10 +3869,10 @@ std::optional<signed_block_header> controller::fetch_block_header_by_number( uin
} FC_CAPTURE_AND_RETHROW( (block_num) ) }


block_state_legacy_ptr controller::fetch_block_state_by_id( block_id_type id )const {
// returns nullptr when in IF mode
auto get_block_state = [&](auto& fork_db, auto& head) -> block_state_legacy_ptr { return fork_db.get_block(id); };
return my->block_data.apply_dpos<block_state_legacy_ptr>(get_block_state);
block_state_ptr controller::fetch_block_state_by_id( block_id_type id )const {
// returns nullptr when in legacy mode
auto get_block_state = [&](auto& fork_db, auto& head) -> block_state_ptr { return fork_db.get_block(id); };
return my->block_data.apply_block_data_new<block_state_ptr>(get_block_state);
}

block_state_legacy_ptr controller::fetch_block_state_by_number( uint32_t block_num )const {
Expand Down Expand Up @@ -3975,8 +3988,13 @@ void controller::get_finalizer_state( finalizer_state& fs ) const {
}

// called from net threads
void controller::notify_hs_message( const uint32_t connection_id, const hs_message& msg ) {
my->pacemaker->on_hs_msg(connection_id, msg);
void controller::notify_vote_message( const hs_vote_message& vote ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please change the function name to process_vote_message or aggregate_vote_message or something like that.

auto bsp = fetch_block_state_by_id(vote.proposal_id);
if( bsp ) {
my->finality_control.aggregate_vote(bsp, vote);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of adding fetch_block_state_by_id and apply_block_data_new, I suggest you add a new method in block_data_t named:

void aggregate_vote(const hs_vote_message& vote) which would do nothing for the dpos case, and would fetch the block_state and apply the vote in the if case.

Copy link
Member Author

Choose a reason for hiding this comment

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

Is block_data_t a general class and its main purpose to service fork_db APIs? aggregate_vote seems too specific for block_data_t.

Copy link
Contributor

Choose a reason for hiding this comment

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

Block data and the other structs with variants should contain, as much as possible, any code that does something different for dpos and if, or accesses the block_state_* structs, so that the rest of controller doesn't see these differences.

It is perfectly fine to add new members like aggregate_vote which behave differently for dpos and if.

The apply functions were mostly present to make controller work in the dpos mode while changes were not completed, ideally they should go away.

wlog( "no block exists for the vote (proposal_id: ${id}", ("id", vote.proposal_id) );
}
};

const producer_authority_schedule& controller::active_producers()const {
Expand Down
38 changes: 38 additions & 0 deletions libraries/chain/hotstuff/finality_controller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include <eosio/chain/hotstuff/finality_controller.hpp>

namespace eosio::chain {

struct finality_controller_impl {
bool aggregate_vote(const block_state_ptr& bsp, const hs_vote_message& vote) {
const auto& finalizers = bsp->finalizer_policy->finalizers;
auto it = std::find_if(finalizers.begin(),
finalizers.end(),
[&](const auto& finalizer) { return finalizer.public_key == vote.finalizer_key; });

if (it != finalizers.end()) {
auto index = std::distance(finalizers.begin(), it);
return bsp->pending_qc.add_vote( vote.strong,
index,
vote.finalizer_key,
vote.sig );
} else {
wlog( "finalizer_key (${k}) in vote is not in finalizer policy", ("k", vote.finalizer_key) );
return false;
}
}
}; // finality_controller_impl

finality_controller::finality_controller()
:my( new finality_controller_impl() )
{
}

finality_controller::~finality_controller()
{
}

bool finality_controller::aggregate_vote(const block_state_ptr& bsp, const hs_vote_message& vote) {
return my->aggregate_vote(bsp, vote);
}

} // namespace eosio::chain
24 changes: 14 additions & 10 deletions libraries/chain/hotstuff/hotstuff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ inline std::vector<uint32_t> bitset_to_vector(const hs_bitset& bs) {

bool pending_quorum_certificate::votes_t::add_vote(const std::vector<uint8_t>& proposal_digest, size_t index,
const bls_public_key& pubkey, const bls_signature& new_sig) {
if (_bitset[index])
if (_bitset[index]) {
wlog( "finalizer ${i} has already voted", ("i", index) );
return false; // shouldn't be already present
if (!fc::crypto::blslib::verify(pubkey, proposal_digest, new_sig))
}
if (!fc::crypto::blslib::verify(pubkey, proposal_digest, new_sig)) {
wlog( "sinature from finalizer ${i} cannot be verified", ("i", index) );
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
wlog( "sinature from finalizer ${i} cannot be verified", ("i", index) );
wlog( "signature from finalizer ${i} cannot be verified", ("i", index) );

return false;
}
_bitset.set(index);
_sig = fc::crypto::blslib::aggregate({_sig, new_sig}); // works even if _sig is default initialized (fp2::zero())
return true;
Expand Down Expand Up @@ -68,10 +72,10 @@ void pending_quorum_certificate::reset(const fc::sha256& proposal_id, const dige
_state = state_t::unrestricted;
}

bool pending_quorum_certificate::add_strong_vote(const std::vector<uint8_t>& proposal_digest, size_t index,
bool pending_quorum_certificate::add_strong_vote(size_t index,
const bls_public_key& pubkey, const bls_signature& sig) {
assert(index < _num_finalizers);
if (!_strong_votes.add_vote(proposal_digest, index, pubkey, sig))
if (!_strong_votes.add_vote(_proposal_digest, index, pubkey, sig))
return false;
size_t weak = num_weak();
size_t strong = num_strong();
Expand Down Expand Up @@ -99,10 +103,10 @@ bool pending_quorum_certificate::add_strong_vote(const std::vector<uint8_t>& pro
return true;
}

bool pending_quorum_certificate::add_weak_vote(const std::vector<uint8_t>& proposal_digest, size_t index,
bool pending_quorum_certificate::add_weak_vote(size_t index,
const bls_public_key& pubkey, const bls_signature& sig) {
assert(index < _num_finalizers);
if (!_weak_votes.add_vote(proposal_digest, index, pubkey, sig))
if (!_weak_votes.add_vote(_proposal_digest, index, pubkey, sig))
return false;
size_t weak = num_weak();
size_t strong = num_strong();
Expand Down Expand Up @@ -134,10 +138,10 @@ bool pending_quorum_certificate::add_weak_vote(const std::vector<uint8_t>& propo
return true;
}

bool pending_quorum_certificate::add_vote(bool strong, const std::vector<uint8_t>& proposal_digest, size_t index,
bool pending_quorum_certificate::add_vote(bool strong, size_t index,
const bls_public_key& pubkey, const bls_signature& sig) {
return strong ? add_strong_vote(proposal_digest, index, pubkey, sig)
: add_weak_vote(proposal_digest, index, pubkey, sig);
return strong ? add_strong_vote(index, pubkey, sig)
: add_weak_vote(index, pubkey, sig);
Comment on lines +141 to +144
Copy link
Contributor

@greg7mdp greg7mdp Jan 10, 2024

Choose a reason for hiding this comment

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

as discussed in the meeting, you'll have to pass the finalizer_digest from the block_state, and we can remove the _proposal_digest member.

}

// ================== begin compatibility functions =======================
Expand Down Expand Up @@ -192,4 +196,4 @@ quorum_certificate_message valid_quorum_certificate::to_msg() const {
};
}

} // namespace eosio::chain
} // namespace eosio::chain
2 changes: 1 addition & 1 deletion libraries/chain/hotstuff/qc_chain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ namespace eosio::chain {
digest_type digest = p->get_proposal_digest();
for (size_t i=0; i<finalizers.size(); ++i) {
if (finalizers[i].public_key == vote.finalizer_key) {
if (_current_qc.add_vote(vote.strong, std::vector<uint8_t>(digest.data(), digest.data() + 32),
if (_current_qc.add_vote(vote.strong,
i, vote.finalizer_key, vote.sig)) {
// fc_tlog(_logger, " === update bitset ${value} ${finalizer_key}",
// ("value", _current_qc.get_active_finalizers_string())("finalizer_key", vote.finalizer_key));
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/hotstuff/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
add_executable( test_hotstuff test_hotstuff.cpp test_hotstuff_state.cpp hotstuff_tools.cpp test_pacemaker.cpp)
add_executable( test_hotstuff test_hotstuff.cpp test_finality_controller.cpp test_hotstuff_state.cpp hotstuff_tools.cpp test_pacemaker.cpp)
target_link_libraries( test_hotstuff eosio_chain fc Boost::unit_test_framework)

add_test(NAME test_hotstuff COMMAND test_hotstuff WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
Expand Down
17 changes: 10 additions & 7 deletions libraries/chain/hotstuff/test/hotstuff_tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,12 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try {
using namespace fc::crypto::blslib;
using state_t = pending_quorum_certificate::state_t;


digest_type d(fc::sha256("0000000000000000000000000000001"));
std::vector<uint8_t> digest(d.data(), d.data() + d.data_size());

fc::sha256 id(fc::sha256("0000000000000000000000000000002"));

std::vector<bls_private_key> sk {
bls_private_key("PVT_BLS_r4ZpChd87ooyzl6MIkw23k7PRX8xptp7TczLJHCIIW88h/hS"),
bls_private_key("PVT_BLS_/l7xzXANaB+GrlTsbZEuTiSOiWTtpBoog+TZnirxUUSaAfCo"),
Expand All @@ -100,15 +103,15 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try {
pubkey.push_back(k.get_public_key());

auto weak_vote = [&](pending_quorum_certificate& qc, const std::vector<uint8_t>& digest, size_t index) {
return qc.add_weak_vote(digest, index, pubkey[index], sk[index].sign(digest));
return qc.add_weak_vote(index, pubkey[index], sk[index].sign(digest));
};

auto strong_vote = [&](pending_quorum_certificate& qc, const std::vector<uint8_t>& digest, size_t index) {
return qc.add_strong_vote(digest, index, pubkey[index], sk[index].sign(digest));
return qc.add_strong_vote(index, pubkey[index], sk[index].sign(digest));
};

{
pending_quorum_certificate qc(2, 1); // 2 finalizers, quorum = 1
pending_quorum_certificate qc(id, d, 2, 1); // 2 finalizers, quorum = 1
BOOST_CHECK_EQUAL(qc._state, state_t::unrestricted);

// add one weak vote
Expand All @@ -131,7 +134,7 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try {
}

{
pending_quorum_certificate qc(2, 1); // 2 finalizers, quorum = 1
pending_quorum_certificate qc(id, d, 2, 1); // 2 finalizers, quorum = 1
BOOST_CHECK_EQUAL(qc._state, state_t::unrestricted);

// add a weak vote
Expand All @@ -148,7 +151,7 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try {
}

{
pending_quorum_certificate qc(2, 1); // 2 finalizers, quorum = 1
pending_quorum_certificate qc(id, d, 2, 1); // 2 finalizers, quorum = 1
BOOST_CHECK_EQUAL(qc._state, state_t::unrestricted);

// add a strong vote
Expand All @@ -165,7 +168,7 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try {
}

{
pending_quorum_certificate qc(3, 2); // 3 finalizers, quorum = 2
pending_quorum_certificate qc(id, d, 3, 2); // 3 finalizers, quorum = 2

// add a weak vote
// ---------------
Expand Down Expand Up @@ -201,7 +204,7 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try {
}

{
pending_quorum_certificate qc(3, 2); // 3 finalizers, quorum = 2
pending_quorum_certificate qc(id, d, 3, 2); // 3 finalizers, quorum = 2

// add a weak vote
// ---------------
Expand Down
83 changes: 83 additions & 0 deletions libraries/chain/hotstuff/test/test_finality_controller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include <eosio/chain/hotstuff/finality_controller.hpp>

#include <fc/exception/exception.hpp>
#include <fc/crypto/bls_private_key.hpp>
#include <fc/crypto/bls_utils.hpp>

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(finality_controller_tests)

BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
using namespace eosio::chain;
using namespace fc::crypto::blslib;

digest_type proposal_id(fc::sha256("0000000000000000000000000000001"));

digest_type proposal_digest(fc::sha256("0000000000000000000000000000002"));
std::vector<uint8_t> proposal_digest_data(proposal_digest.data(), proposal_digest.data() + proposal_digest.data_size());

const size_t num_finalizers = 3;

// initialize a set of private keys
std::vector<bls_private_key> private_key {
bls_private_key("PVT_BLS_r4ZpChd87ooyzl6MIkw23k7PRX8xptp7TczLJHCIIW88h/hS"),
bls_private_key("PVT_BLS_/l7xzXANaB+GrlTsbZEuTiSOiWTtpBoog+TZnirxUUSaAfCo"),
bls_private_key("PVT_BLS_3FoY73Q/gED3ejyg8cvnGqHrMmx4cLKwh/e0sbcsCxpCeqn3"),
};

// construct finalizers
std::vector<bls_public_key> public_key(num_finalizers);
std::vector<finalizer_authority> finalizers(num_finalizers);
for (size_t i = 0; i < num_finalizers; ++i) {
public_key[i] = private_key[i].get_public_key();
finalizers[i] = finalizer_authority{ "test", 1, public_key[i] };
}

finality_controller finality_control;

{ // all finalizers can aggregate votes
block_state_ptr bsp = std::make_shared<block_state>();
bsp->finalizer_policy = std::make_shared<finalizer_policy>( 10, 15, finalizers );
bsp->pending_qc = pending_quorum_certificate{ proposal_id, proposal_digest, num_finalizers, 1 };

for (size_t i = 0; i < num_finalizers; ++i) {
bool strong = (i % 2 == 0); // alternate strong and weak
hs_vote_message vote {proposal_id, strong, public_key[i], private_key[i].sign(proposal_digest_data) };
BOOST_REQUIRE(finality_control.aggregate_vote(bsp, vote));
}
}

{ // public and private keys mismatched
block_state_ptr bsp = std::make_shared<block_state>();
bsp->finalizer_policy = std::make_shared<finalizer_policy>( 10, 15, finalizers );
bsp->pending_qc = pending_quorum_certificate{ proposal_id, proposal_digest, num_finalizers, 1 };

hs_vote_message vote {proposal_id, true, public_key[0], private_key[1].sign(proposal_digest_data) };
BOOST_REQUIRE(!finality_control.aggregate_vote(bsp, vote));
}

{ // duplicate votes
block_state_ptr bsp = std::make_shared<block_state>();
bsp->finalizer_policy = std::make_shared<finalizer_policy>( 10, 15, finalizers );
bsp->pending_qc = pending_quorum_certificate{ proposal_id, proposal_digest, num_finalizers, 1 };

hs_vote_message vote {proposal_id, true, public_key[0], private_key[0].sign(proposal_digest_data) };
BOOST_REQUIRE(finality_control.aggregate_vote(bsp, vote));
BOOST_REQUIRE(!finality_control.aggregate_vote(bsp, vote));
}

{ // public key does not exit in finalizer set
block_state_ptr bsp = std::make_shared<block_state>();
bsp->finalizer_policy = std::make_shared<finalizer_policy>( 10, 15, finalizers );
bsp->pending_qc = pending_quorum_certificate{ proposal_id, proposal_digest, num_finalizers, 1 };

bls_private_key new_private_key{ "PVT_BLS_warwI76e+pPX9wLFZKPFagngeFM8bm6J8D5w0iiHpxW7PiId" };
bls_public_key new_public_key{ new_private_key.get_public_key() };

hs_vote_message vote {proposal_id, true, new_public_key, private_key[0].sign(proposal_digest_data) };
BOOST_REQUIRE(!finality_control.aggregate_vote(bsp, vote));
}
} FC_LOG_AND_RETHROW();

BOOST_AUTO_TEST_SUITE_END()
6 changes: 3 additions & 3 deletions libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,8 @@ namespace eosio::chain {
std::optional<signed_block_header> fetch_block_header_by_id( const block_id_type& id )const;
// return block_state_legacy from forkdb, thread-safe
block_state_legacy_ptr fetch_block_state_by_number( uint32_t block_num )const;
// return block_state_legacy from forkdb, thread-safe
block_state_legacy_ptr fetch_block_state_by_id( block_id_type id )const;
// return block_state_ptr from forkdb in IF mode and nullptr in legacy mode, thread-safe
block_state_ptr fetch_block_state_by_id( block_id_type id )const;
// thread-safe
block_id_type get_block_id_for_num( uint32_t block_num )const;

Expand Down Expand Up @@ -316,7 +316,7 @@ namespace eosio::chain {
void set_proposed_finalizers( const finalizer_policy& fin_set );
void get_finalizer_state( finalizer_state& fs ) const;
// called from net threads
void notify_hs_message( const uint32_t connection_id, const hs_message& msg );
void notify_vote_message( const hs_vote_message& vote );

bool light_validation_allowed() const;
bool skip_auth_check()const;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

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

namespace eosio::chain {
struct finality_controller_impl;

class finality_controller {
public:
finality_controller();
~finality_controller();

bool aggregate_vote(const block_state_ptr& bsp, const hs_vote_message& vote);

private:
std::unique_ptr<finality_controller_impl> my;
};

} // eosio::chain
Loading