Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/hotstuff_integration' into GH-2125
Browse files Browse the repository at this point in the history
…-forkdb
  • Loading branch information
heifner committed Feb 14, 2024
2 parents 5d2a839 + e492b4b commit 29ddf49
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 103 deletions.
4 changes: 2 additions & 2 deletions libraries/chain/block_header_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ block_header_state block_header_state::next(block_header_state_input& input) con

if(!proposer_policies.empty()) {
auto it = proposer_policies.begin();
// -1 since this is called after the block is built, this will be the active schedule for the next block
if (it->first.slot <= input.timestamp.slot - 1) {
// +1 since this is called after the block is built, this will be the active schedule for the next block
if (it->first.slot <= input.timestamp.slot + 1) {
result.active_proposer_policy = it->second;
result.header.schedule_version = header.schedule_version + 1;
result.active_proposer_policy->proposer_schedule.version = result.header.schedule_version;
Expand Down
89 changes: 6 additions & 83 deletions libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#pragma once
#include <eosio/chain/block_header.hpp>
#include <fc/bitutil.hpp>

#include <fc/crypto/bls_private_key.hpp>
#include <fc/crypto/bls_public_key.hpp>
#include <fc/crypto/bls_signature.hpp>
Expand All @@ -14,84 +13,13 @@ namespace eosio::chain {
using hs_bitset = boost::dynamic_bitset<uint32_t>;
using bls_key_map_t = std::map<fc::crypto::blslib::bls_public_key, fc::crypto::blslib::bls_private_key>;

inline digest_type get_digest_to_sign(const block_id_type& block_id, uint8_t phase_counter, const fc::sha256& final_on_qc) {
digest_type h1 = digest_type::hash( std::make_pair( std::cref(block_id), phase_counter ) );
digest_type h2 = digest_type::hash( std::make_pair( std::cref(h1), std::cref(final_on_qc) ) );
return h2;
}

inline uint64_t compute_height(uint32_t block_height, uint32_t phase_counter) {
return (uint64_t{block_height} << 32) | phase_counter;
}

struct view_number {
view_number() : bheight(0), pcounter(0) {}
explicit view_number(uint32_t block_height, uint8_t phase_counter) : bheight(block_height), pcounter(phase_counter) {}
auto operator<=>(const view_number&) const = default;
friend std::ostream& operator<<(std::ostream& os, const view_number& vn) {
os << "view_number(" << vn.bheight << ", " << vn.pcounter << ")\n";
return os;
}

uint32_t block_height() const { return bheight; }
uint8_t phase_counter() const { return pcounter; }
uint64_t get_key() const { return compute_height(bheight, pcounter); }
std::string to_string() const { return std::to_string(bheight) + "::" + std::to_string(pcounter); }

uint32_t bheight;
uint8_t pcounter;
};

struct quorum_certificate_message {
fc::sha256 proposal_id;
std::vector<uint32_t> strong_votes; //bitset encoding, following canonical order
std::vector<uint32_t> weak_votes; //bitset encoding, following canonical order
fc::crypto::blslib::bls_signature active_agg_sig;
};

struct vote_message {
fc::sha256 proposal_id; //vote on proposal
bool strong{false};
fc::crypto::blslib::bls_public_key finalizer_key;
fc::crypto::blslib::bls_signature sig;
};

struct hs_proposal_message {
fc::sha256 proposal_id; //vote on proposal
block_id_type block_id;
fc::sha256 parent_id; //new proposal
fc::sha256 final_on_qc;
quorum_certificate_message justify; //justification
uint8_t phase_counter = 0;
mutable std::optional<digest_type> digest;

digest_type get_proposal_digest() const {
if (!digest)
digest.emplace(get_digest_to_sign(block_id, phase_counter, final_on_qc));
return *digest;
};

uint32_t block_num() const { return block_header::num_from_id(block_id); }
uint64_t get_key() const { return compute_height(block_header::num_from_id(block_id), phase_counter); };

view_number get_view_number() const { return view_number(block_header::num_from_id(block_id), phase_counter); };
};

struct hs_new_view_message {
quorum_certificate_message high_qc; //justification
};

struct hs_message {
std::variant<vote_message, hs_proposal_message, hs_new_view_message> msg;
};

enum class hs_message_warning {
discarded, // default code for dropped messages (irrelevant, redundant, ...)
duplicate_signature, // same message signature already seen
invalid_signature, // invalid message signature
invalid // invalid message (other reason)
};

enum class vote_status {
success,
duplicate,
Expand All @@ -104,7 +32,7 @@ namespace eosio::chain {
using bls_signature = fc::crypto::blslib::bls_signature;
using bls_private_key = fc::crypto::blslib::bls_private_key;

// -------------------- valid_quorum_certificate -------------------------------------------------
// valid_quorum_certificate
class valid_quorum_certificate {
public:
valid_quorum_certificate(const std::vector<uint32_t>& strong_votes, //bitset encoding, following canonical order
Expand All @@ -123,14 +51,14 @@ namespace eosio::chain {
bls_signature _sig;
};

// -------------------- quorum_certificate -------------------------------------------------------
// quorum_certificate
struct quorum_certificate {
uint32_t block_num;
valid_quorum_certificate qc;
};


// -------------------- pending_quorum_certificate -------------------------------------------------
// pending_quorum_certificate
class pending_quorum_certificate {
public:
enum class state_t {
Expand Down Expand Up @@ -159,14 +87,14 @@ namespace eosio::chain {
explicit pending_quorum_certificate(size_t num_finalizers, uint64_t quorum, uint64_t max_weak_sum_before_weak_final);

// thread safe
bool is_quorum_met() const;
bool is_quorum_met() const;

// thread safe
std::pair<vote_status, state_t>
add_vote(bool strong, const std::vector<uint8_t>& proposal_digest, size_t index,
const bls_public_key& pubkey, const bls_signature& sig, uint64_t weight);

state_t state() const { std::lock_guard g(*_mtx); return _state; };
state_t state() const { std::lock_guard g(*_mtx); return _state; };
valid_quorum_certificate to_valid_quorum_certificate() const;

private:
Expand Down Expand Up @@ -198,12 +126,7 @@ namespace eosio::chain {
} //eosio::chain


FC_REFLECT(eosio::chain::view_number, (bheight)(pcounter));
FC_REFLECT(eosio::chain::quorum_certificate_message, (proposal_id)(strong_votes)(weak_votes)(active_agg_sig));
FC_REFLECT(eosio::chain::vote_message, (proposal_id)(strong)(finalizer_key)(sig));
FC_REFLECT(eosio::chain::hs_proposal_message, (proposal_id)(block_id)(parent_id)(final_on_qc)(justify)(phase_counter));
FC_REFLECT(eosio::chain::hs_new_view_message, (high_qc));
FC_REFLECT(eosio::chain::hs_message, (msg));
FC_REFLECT(eosio::chain::valid_quorum_certificate, (_strong_votes)(_weak_votes)(_sig));
FC_REFLECT(eosio::chain::pending_quorum_certificate, (_quorum)(_max_weak_sum_before_weak_final)(_state)(_strong_sum)(_weak_sum)(_weak_votes)(_strong_votes));
FC_REFLECT(eosio::chain::pending_quorum_certificate::votes_t, (_bitset)(_sig));
Expand Down
8 changes: 8 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/leap_util_bls_test.py ${CMAKE_CURRENT
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sample-cluster-map.json ${CMAKE_CURRENT_BINARY_DIR}/sample-cluster-map.json COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/restart-scenarios-test.py ${CMAKE_CURRENT_BINARY_DIR}/restart-scenarios-test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/terminate-scenarios-test.py ${CMAKE_CURRENT_BINARY_DIR}/terminate-scenarios-test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/liveness_test.py ${CMAKE_CURRENT_BINARY_DIR}/liveness_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_startup_catchup.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_startup_catchup.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_snapshot_diff_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_snapshot_diff_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_snapshot_forked_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_snapshot_forked_test.py COPYONLY)
Expand Down Expand Up @@ -63,6 +64,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/subjective_billing_test.py ${CMAKE_CU
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/get_account_test.py ${CMAKE_CURRENT_BINARY_DIR}/get_account_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_high_transaction_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_high_transaction_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_retry_transaction_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_retry_transaction_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/transition_to_if.py ${CMAKE_CURRENT_BINARY_DIR}/transition_to_if.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/trx_finality_status_test.py ${CMAKE_CURRENT_BINARY_DIR}/trx_finality_status_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/trx_finality_status_forked_test.py ${CMAKE_CURRENT_BINARY_DIR}/trx_finality_status_forked_test.py COPYONLY)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/plugin_http_api_test.py ${CMAKE_CURRENT_BINARY_DIR}/plugin_http_api_test.py COPYONLY)
Expand Down Expand Up @@ -132,6 +134,9 @@ set_property(TEST cluster_launcher PROPERTY LABELS nonparallelizable_tests)
add_test(NAME cluster_launcher_if COMMAND tests/cluster_launcher.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST cluster_launcher_if PROPERTY LABELS nonparallelizable_tests)

add_test(NAME transition_to_if COMMAND tests/transition_to_if.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST transition_to_if PROPERTY LABELS nonparallelizable_tests)

add_test(NAME ship_test COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST ship_test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME ship_test_unix COMMAND tests/ship_test.py -v --num-clients 10 --num-requests 5000 ${UNSHARE} --unix-socket WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
Expand Down Expand Up @@ -287,6 +292,9 @@ set_property(TEST nodeos_read_terminate_at_block_lr_test PROPERTY LABELS long_ru
#add_test(NAME nodeos_read_terminate_at_block_if_lr_test COMMAND tests/nodeos_read_terminate_at_block_test.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
#set_property(TEST nodeos_read_terminate_at_block_if_lr_test PROPERTY LABELS long_running_tests)

add_test(NAME liveness_test COMMAND tests/liveness_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST liveness_test PROPERTY LABELS nonparallelizable_tests)

add_test(NAME nodeos_chainbase_allocation_test COMMAND tests/nodeos_chainbase_allocation_test.py -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_property(TEST nodeos_chainbase_allocation_test PROPERTY LABELS nonparallelizable_tests)
add_test(NAME nodeos_chainbase_allocation_if_test COMMAND tests/nodeos_chainbase_allocation_test.py --activate-if -v ${UNSHARE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
Expand Down
55 changes: 42 additions & 13 deletions tests/TestHarness/Cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ def connectGroup(group, producerNodes, bridgeNodes) :
node = Node(self.host, self.port + nodeNum, nodeNum, Path(instance.data_dir_name),
Path(instance.config_dir_name), eosdcmd, unstarted=instance.dont_start,
launch_time=launcher.launch_time, walletMgr=self.walletMgr, nodeosVers=self.nodeosVers)
node.keys = instance.keys
node.isProducer = len(instance.producers) > 0
if nodeNum == Node.biosNodeId:
self.biosNode = node
else:
Expand Down Expand Up @@ -993,34 +995,33 @@ def parseClusterKeys(totalNodes):
Utils.Print(f'Found {len(producerKeys)} producer keys')
return producerKeys

def activateInstantFinality(self, launcher, biosFinalizer, pnodes):
def activateInstantFinality(self, biosFinalizer=True):
# call setfinalizer
numFins = 0
for n in launcher.network.nodes.values():
if not n.keys or not n.keys[0].blspubkey:
for n in (self.nodes + [self.biosNode]):
if not n or not n.keys or not n.keys[0].blspubkey:
continue
if not n.producers:
if not n.isProducer:
continue
if n.index == Node.biosNodeId and not biosFinalizer:
if n.nodeId == 'bios' and not biosFinalizer:
continue
numFins = numFins + 1

threshold = int(numFins * 2 / 3 + 1)
if threshold > 2 and threshold == numFins:
# nodes are often stopped, so do not require all node votes
threshold = threshold - 1
# pnodes does not include biosNode
if Utils.Debug: Utils.Print(f"threshold: {threshold}, numFins: {numFins}, pnodes: {pnodes}")
if Utils.Debug: Utils.Print(f"threshold: {threshold}, numFins: {numFins}")
setFinStr = f'{{"finalizer_policy": {{'
setFinStr += f' "threshold": {threshold}, '
setFinStr += f' "finalizers": ['
finNum = 1
for n in launcher.network.nodes.values():
if n.index == Node.biosNodeId and not biosFinalizer:
for n in (self.nodes + [self.biosNode]):
if not n or not n.keys or not n.keys[0].blspubkey:
continue
if not n.keys or not n.keys[0].blspubkey:
if not n.isProducer:
continue
if not n.producers:
if n.nodeId == 'bios' and not biosFinalizer:
continue
setFinStr += f' {{"description": "finalizer #{finNum}", '
setFinStr += f' "weight":1, '
Expand All @@ -1044,6 +1045,7 @@ def activateInstantFinality(self, launcher, biosFinalizer, pnodes):
if not self.biosNode.waitForTransFinalization(transId, timeout=21*12*3):
Utils.Print("ERROR: Failed to validate transaction %s got rolled into a LIB block on server port %d." % (transId, biosNode.port))
return None
return True

def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers, pfSetupPolicy, onlyBios=False, onlySetProds=False, loadSystemContract=True, activateIF=False, biosFinalizer=True):
"""Create 'prodCount' init accounts and deposits 10000000000 SYS in each. If prodCount is -1 will initialize all possible producers.
Expand Down Expand Up @@ -1109,7 +1111,9 @@ def bootstrap(self, launcher, biosNode, totalNodes, prodCount, totalProducers,
return None

if activateIF:
self.activateInstantFinality(launcher, biosFinalizer, self.productionNodesCount)
if not self.activateInstantFinality(biosFinalizer=biosFinalizer):
Utils.Print("ERROR: Activate instant finality failed")
return None

Utils.Print("Creating accounts: %s " % ", ".join(producerKeys.keys()))
producerKeys.pop(eosioName)
Expand Down Expand Up @@ -1208,7 +1212,7 @@ def createSystemAccount(accountName):
#
# Could activate instant finality here, but have to wait for finality which with all the producers takes a long time
# if activateIF:
# self.activateInstantFinality(launcher)
# self.activateInstantFinality()

eosioTokenAccount = copy.deepcopy(eosioAccount)
eosioTokenAccount.name = 'eosio.token'
Expand Down Expand Up @@ -1456,6 +1460,31 @@ def cleanup(self):
for f in self.filesToCleanup:
os.remove(f)

def setProds(self, producers):
"""Call setprods with list of producers"""
setProdsStr = '{"schedule": ['
firstTime = True
for name in producers:
if firstTime:
firstTime = False
else:
setProdsStr += ','
if not self.defProducerAccounts[name]:
Utils.Print(f"ERROR: no account key for {name}")
return None
key = self.defProducerAccounts[name].activePublicKey
setProdsStr += '{"producer_name":' + name + ',"authority": ["block_signing_authority_v0", {"threshold":1, "keys":[{"key":' + key + ', "weight":1}]}]}'

setProdsStr += ' ] }'
Utils.Print("setprods: %s" % (setProdsStr))
opts = "--permission eosio@active"
# pylint: disable=redefined-variable-type
trans = self.biosNode.pushMessage("eosio", "setprods", setProdsStr, opts)
if trans is None or not trans[0]:
Utils.Print("ERROR: Failed to set producer with cmd %s" % (setProdsStr))
return None
return True

# Create accounts, if account does not already exist, and validates that the last transaction is received on root node
def createAccounts(self, creator, waitForTransBlock=True, stakedDeposit=1000, validationNodeIndex=-1):
if self.accounts is None:
Expand Down
10 changes: 10 additions & 0 deletions tests/TestHarness/Node.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import sys
from pathlib import Path
from typing import List
from dataclasses import InitVar, dataclass, field, is_dataclass, asdict

from datetime import datetime
from datetime import timedelta
Expand All @@ -21,6 +22,14 @@
from .testUtils import unhandledEnumType
from .testUtils import ReturnType

@dataclass
class KeyStrings(object):
pubkey: str
privkey: str
blspubkey: str = None
blsprivkey: str = None
blspop: str = None

# pylint: disable=too-many-public-methods
class Node(Transactions):
# Node number is used as an addend to determine the node listen ports.
Expand Down Expand Up @@ -66,6 +75,7 @@ def __init__(self, host, port, nodeId: int, data_dir: Path, config_dir: Path, cm
self.config_dir=config_dir
self.launch_time=launch_time
self.isProducer=False
self.keys: List[KeyStrings] = field(default_factory=list)
self.configureVersion()

def configureVersion(self):
Expand Down
Loading

0 comments on commit 29ddf49

Please sign in to comment.