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: accumulate the weight in vote calculation #2131

Merged
merged 9 commits into from
Jan 30, 2024
4 changes: 2 additions & 2 deletions libraries/chain/block_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ block_state::block_state(const block_header_state& prev, signed_block_ptr b, con
, block(std::move(b))
, strong_digest(compute_finalizer_digest())
, weak_digest(compute_finalizer_digest())
, pending_qc(prev.active_finalizer_policy->finalizers.size(), prev.active_finalizer_policy->threshold)
, pending_qc(prev.active_finalizer_policy->finalizers.size(), prev.active_finalizer_policy->finalizer_weights(), prev.active_finalizer_policy->threshold)
{}

block_state::block_state(const block_header_state& bhs, deque<transaction_metadata_ptr>&& trx_metas,
Expand All @@ -20,7 +20,7 @@ block_state::block_state(const block_header_state& bhs, deque<transaction_metada
, block(std::make_shared<signed_block>(signed_block_header{bhs.header})) // [greg todo] do we need signatures?
, strong_digest(compute_finalizer_digest())
, weak_digest(compute_finalizer_digest())
, pending_qc(bhs.active_finalizer_policy->finalizers.size(), bhs.active_finalizer_policy->threshold)
, pending_qc(bhs.active_finalizer_policy->finalizers.size(), bhs.active_finalizer_policy->finalizer_weights(), bhs.active_finalizer_policy->threshold)
, pub_keys_recovered(true) // probably not needed
, cached_trxs(std::move(trx_metas))
{
Expand Down
29 changes: 10 additions & 19 deletions libraries/chain/hotstuff/hotstuff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,15 @@ pending_quorum_certificate::pending_quorum_certificate()
: _mtx(std::make_unique<std::mutex>()) {
}

pending_quorum_certificate::pending_quorum_certificate(size_t num_finalizers, size_t quorum)
pending_quorum_certificate::pending_quorum_certificate(size_t num_finalizers, std::vector<uint64_t>&& weights, uint64_t quorum)
: _num_finalizers(num_finalizers)
, _quorum(quorum)
, _finalizer_weights(std::move(weights))
, _mtx(std::make_unique<std::mutex>()) {
_weak_votes.resize(num_finalizers);
_strong_votes.resize(num_finalizers);
}

pending_quorum_certificate::pending_quorum_certificate(const fc::sha256& proposal_id,
const digest_type& proposal_digest, size_t num_finalizers,
size_t quorum)
: pending_quorum_certificate(num_finalizers, quorum) {
_proposal_id = proposal_id;
_proposal_digest.assign(proposal_digest.data(), proposal_digest.data() + 32);
}

bool pending_quorum_certificate::is_quorum_met() const {
std::lock_guard g(*_mtx);
return _state == state_t::weak_achieved || _state == state_t::weak_final || _state == state_t::strong;
Expand All @@ -84,21 +77,20 @@ bool pending_quorum_certificate::add_strong_vote(const std::vector<uint8_t>& pro
assert(index < _num_finalizers);
if (!_strong_votes.add_vote(proposal_digest, index, pubkey, sig))
return false;
size_t weak = num_weak();
size_t strong = num_strong();
_strong_accumulated_weight += _finalizer_weights[index];

switch (_state) {
case state_t::unrestricted:
case state_t::restricted:
if (strong >= _quorum) {
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
if (_strong_accumulated_weight >= _quorum) {
assert(_state != state_t::restricted);
_state = state_t::strong;
} else if (weak + strong >= _quorum)
} else if (_weak_accumulated_weight + _strong_accumulated_weight >= _quorum)
_state = (_state == state_t::restricted) ? state_t::weak_final : state_t::weak_achieved;
break;

case state_t::weak_achieved:
if (strong >= _quorum)
if (_strong_accumulated_weight >= _quorum)
_state = state_t::strong;
break;

Expand All @@ -116,16 +108,15 @@ bool pending_quorum_certificate::add_weak_vote(const std::vector<uint8_t>& propo
assert(index < _num_finalizers);
if (!_weak_votes.add_vote(proposal_digest, index, pubkey, sig))
return false;
size_t weak = num_weak();
size_t strong = num_strong();
_weak_accumulated_weight += _finalizer_weights[index];

switch (_state) {
case state_t::unrestricted:
case state_t::restricted:
if (weak + strong >= _quorum)
if (_weak_accumulated_weight + _strong_accumulated_weight >= _quorum)
_state = state_t::weak_achieved;

if (weak > (_num_finalizers - _quorum)) {
if (_weak_accumulated_weight > (_num_finalizers - _quorum)) {
if (_state == state_t::weak_achieved)
_state = state_t::weak_final;
else if (_state == state_t::unrestricted)
Expand All @@ -134,7 +125,7 @@ bool pending_quorum_certificate::add_weak_vote(const std::vector<uint8_t>& propo
break;

case state_t::weak_achieved:
if (weak >= (_num_finalizers - _quorum))
if (_weak_accumulated_weight >= (_num_finalizers - _quorum))
_state = state_t::weak_final;
break;

Expand Down
14 changes: 7 additions & 7 deletions libraries/chain/hotstuff/test/hotstuff_tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try {
};

{
pending_quorum_certificate qc(2, 1); // 2 finalizers, quorum = 1
pending_quorum_certificate qc(2, {1, 1}, 1); // 2 finalizers, quorum = 1
BOOST_CHECK_EQUAL(qc.state(), state_t::unrestricted);

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

{
pending_quorum_certificate qc(2, 1); // 2 finalizers, quorum = 1
pending_quorum_certificate qc(2, {1, 1}, 1); // 2 finalizers, quorum = 1
BOOST_CHECK_EQUAL(qc.state(), state_t::unrestricted);

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

{
pending_quorum_certificate qc(2, 1); // 2 finalizers, quorum = 1
pending_quorum_certificate qc(2, {1, 1}, 1); // 2 finalizers, quorum = 1
BOOST_CHECK_EQUAL(qc.state(), state_t::unrestricted);

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

{
pending_quorum_certificate qc(3, 2); // 3 finalizers, quorum = 2
pending_quorum_certificate qc(3, {1, 1, 1}, 2); // 3 finalizers, quorum = 2

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

{
pending_quorum_certificate qc(3, 2); // 3 finalizers, quorum = 2
pending_quorum_certificate qc(3, {1, 1, 1}, 2); // 3 finalizers, quorum = 2

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

{
pending_quorum_certificate qc(3, 2); // 3 finalizers, quorum = 2
pending_quorum_certificate qc(3, {1, 1, 1}, 2); // 3 finalizers, quorum = 2

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

{
pending_quorum_certificate qc(3, 2); // 3 finalizers, quorum = 2
pending_quorum_certificate qc(3, {1, 1, 1}, 2); // 3 finalizers, quorum = 2

// add a weak vote
// ---------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ namespace eosio::chain {
uint32_t generation = 0; ///< sequentially incrementing version number
uint64_t threshold = 0; ///< vote weight threshold to finalize blocks
std::vector<finalizer_authority> finalizers; ///< Instant Finality voter set

std::vector<uint64_t> finalizer_weights() const {
auto n = finalizers.size();
std::vector<uint64_t> weights(n);
for( size_t i = 0; i < n; ++i ) {
heifner marked this conversation as resolved.
Show resolved Hide resolved
weights[i] = finalizers[i].weight;
}
return weights;
}
};

using finalizer_policy_ptr = std::shared_ptr<finalizer_policy>;
Expand Down
18 changes: 6 additions & 12 deletions libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,18 +185,13 @@ namespace eosio::chain {

pending_quorum_certificate();

explicit pending_quorum_certificate(size_t num_finalizers, size_t quorum);

explicit pending_quorum_certificate(const fc::sha256& proposal_id,
const digest_type& proposal_digest,
size_t num_finalizers,
size_t quorum);
explicit pending_quorum_certificate(size_t num_finalizers, std::vector<uint64_t>&& weights, uint64_t quorum);

// thread safe
bool is_quorum_met() const;

// thread safe
void reset(const fc::sha256& proposal_id, const digest_type& proposal_digest, size_t num_finalizers, size_t quorum);
void reset(const fc::sha256& proposal_id, const digest_type& proposal_digest, size_t num_finalizers, uint64_t quorum);

// thread safe
std::pair<bool, bool> add_vote(bool strong,
Expand All @@ -223,15 +218,14 @@ namespace eosio::chain {
std::vector<uint8_t> _proposal_digest;
state_t _state { state_t::unrestricted };
size_t _num_finalizers {0};
size_t _quorum {0};
std::vector<uint64_t> _finalizer_weights; // weight of each finalizer
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
uint64_t _strong_accumulated_weight {0}; // accumulated weight of strong votes far
uint64_t _weak_accumulated_weight {0}; // accumulated weight of weak votes so far
uint64_t _quorum {0};
std::unique_ptr<std::mutex> _mtx; // protect both _strong_votes and _weak_votes
votes_t _weak_votes;
votes_t _strong_votes;

// num_weak and num_strong are protected by mutex by add_vote
size_t num_weak() const { return _weak_votes.count(); }
size_t num_strong() const { return _strong_votes.count(); }

// called by add_vote, already protected by mutex
bool add_strong_vote(const std::vector<uint8_t>& proposal_digest,
size_t index,
Expand Down
103 changes: 99 additions & 4 deletions unittests/block_state_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
bsp->active_finalizer_policy = std::make_shared<finalizer_policy>( 10, 15, finalizers );
bsp->strong_digest = strong_digest;
bsp->weak_digest = weak_digest;
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1 };
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, bsp->active_finalizer_policy->finalizer_weights(), threshold };

for (size_t i = 0; i < num_finalizers; ++i) {
bool strong = (i % 2 == 0); // alternate strong and weak
Expand All @@ -56,7 +56,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
block_state_ptr bsp = std::make_shared<block_state>();
bsp->active_finalizer_policy = std::make_shared<finalizer_policy>( 10, 15, finalizers );
bsp->strong_digest = strong_digest;
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1 };
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, bsp->active_finalizer_policy->finalizer_weights(), 1 };

vote_message vote {block_id, true, public_key[0], private_key[1].sign(strong_digest_data) };
BOOST_REQUIRE(!bsp->aggregate_vote(vote).first);
Expand All @@ -66,7 +66,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
block_state_ptr bsp = std::make_shared<block_state>();
bsp->active_finalizer_policy = std::make_shared<finalizer_policy>( 10, 15, finalizers );
bsp->strong_digest = strong_digest;
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1 };
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, bsp->active_finalizer_policy->finalizer_weights(), 1 };

vote_message vote {block_id, true, public_key[0], private_key[0].sign(strong_digest_data) };
BOOST_REQUIRE(bsp->aggregate_vote(vote).first);
Expand All @@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
block_state_ptr bsp = std::make_shared<block_state>();
bsp->active_finalizer_policy = std::make_shared<finalizer_policy>( 10, 15, finalizers );
bsp->strong_digest = strong_digest;
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1 };
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, bsp->active_finalizer_policy->finalizer_weights(), 1 };

bls_private_key new_private_key{ "PVT_BLS_warwI76e+pPX9wLFZKPFagngeFM8bm6J8D5w0iiHpxW7PiId" };
bls_public_key new_public_key{ new_private_key.get_public_key() };
Expand All @@ -87,4 +87,99 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
}
} FC_LOG_AND_RETHROW();

void do_quorum_test(const std::vector<uint64_t>& weights,
uint64_t threshold,
bool strong,
const std::vector<bool>& to_vote,
bool expected_quorum) {
using namespace eosio::chain;
using namespace fc::crypto::blslib;

digest_type block_id(fc::sha256("0000000000000000000000000000001"));
digest_type strong_digest(fc::sha256("0000000000000000000000000000002"));
std::vector<uint8_t> strong_digest_data(strong_digest.data(), strong_digest.data() + strong_digest.data_size());
digest_type weak_digest(fc::sha256("0000000000000000000000000000003"));
std::vector<uint8_t> weak_digest_data(weak_digest.data(), weak_digest.data() + weak_digest.data_size());

// 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"),
};
const size_t num_finalizers = private_key.size();

// 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", weights[i], public_key[i] };
}

block_state_ptr bsp = std::make_shared<block_state>();
constexpr uint32_t generation = 1;
bsp->active_finalizer_policy = std::make_shared<finalizer_policy>( generation, threshold, finalizers );
bsp->strong_digest = strong_digest;
bsp->weak_digest = weak_digest;
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, bsp->active_finalizer_policy->finalizer_weights(), threshold };

for (size_t i = 0; i < num_finalizers; ++i) {
if( to_vote[i] ) {
auto sig = strong ? private_key[i].sign(strong_digest_data) : private_key[i].sign(weak_digest_data);
vote_message vote{ block_id, strong, public_key[i], sig };
BOOST_REQUIRE(bsp->aggregate_vote(vote).first);
}
}

BOOST_REQUIRE_EQUAL(bsp->pending_qc.is_quorum_met(), expected_quorum);
}

BOOST_AUTO_TEST_CASE(quorum_test) try {
std::vector<uint64_t> weights{1, 3, 5};
constexpr uint64_t threshold = 4;

{ // 1 strong vote, quorum not met
constexpr bool strong = true;
std::vector<bool> to_vote{true, false, false}; // finalizer 0 voting
constexpr bool expected_quorum_met = false;
do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met );
}

{ // 2 strong votes, quorum met
constexpr bool strong = true;
std::vector<bool> to_vote{true, true, false}; // finalizers 0 and 1 voting
constexpr bool expected_quorum_met = true;
do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met );
}

{ // 1 strong vote, quorum met
constexpr bool strong = true;
std::vector<bool> to_vote{false, false, true}; // finalizer 2 voting
constexpr bool expected_quorum_met = true;
do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met );
}

{ // 1 weak vote, quorum not met
constexpr bool strong = false;
std::vector<bool> to_vote{true, false, false}; // finalizer 0 voting
constexpr bool expected_quorum_met = false;
do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met );
}

{ // 2 weak votes, quorum met
constexpr bool strong = false;
std::vector<bool> to_vote{true, true, false}; // finalizers 0 and 1 voting
constexpr bool expected_quorum_met = true;
do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met );
}

{ // 1 weak vote, quorum met
constexpr bool strong = false;
std::vector<bool> to_vote{false, false, true}; // finalizer 2 voting
constexpr bool expected_quorum_met = true;
do_quorum_test( weights, threshold, strong, to_vote, expected_quorum_met );
}
} FC_LOG_AND_RETHROW();

BOOST_AUTO_TEST_SUITE_END()