diff options
Diffstat (limited to 'include/hotstuff')
-rw-r--r-- | include/hotstuff/hotstuff.h | 7 | ||||
-rw-r--r-- | include/hotstuff/liveness.h | 285 |
2 files changed, 10 insertions, 282 deletions
diff --git a/include/hotstuff/hotstuff.h b/include/hotstuff/hotstuff.h index ffc5e3d..291f9a7 100644 --- a/include/hotstuff/hotstuff.h +++ b/include/hotstuff/hotstuff.h @@ -27,7 +27,6 @@ #include "salticidae/msg.h" #include "hotstuff/util.h" #include "hotstuff/consensus.h" -#include "hotstuff/liveness.h" namespace hotstuff { @@ -82,6 +81,7 @@ struct MsgRespBlock { using promise::promise_t; class HotStuffBase; +using pacemaker_bt = BoxObj<class PaceMaker>; template<EntityType ent_type> class FetchContext: public promise_t { @@ -140,6 +140,7 @@ class HotStuffBase: public HotStuffCore { size_t blk_size; /** libevent handle */ EventContext ec; + salticidae::ThreadCall tcall; VeriPool vpool; std::vector<NetAddr> peers; @@ -223,8 +224,10 @@ class HotStuffBase: public HotStuffCore { bool ec_loop = false); size_t size() const { return peers.size(); } - PaceMaker &get_pace_maker() { return *pmaker; } + auto get_decision_waiting() const { return decision_waiting; } + PaceMaker *get_pace_maker() { return pmaker.get(); } void print_stat() const; + virtual void do_elected(); //#ifdef HOTSTUFF_AUTOCLI // virtual void do_demand_commands(size_t) {} //#endif diff --git a/include/hotstuff/liveness.h b/include/hotstuff/liveness.h index cdefdeb..1286c45 100644 --- a/include/hotstuff/liveness.h +++ b/include/hotstuff/liveness.h @@ -19,7 +19,7 @@ #define _HOTSTUFF_LIVENESS_H #include "salticidae/util.h" -#include "hotstuff/consensus.h" +#include "hotstuff/hotstuff.h" namespace hotstuff { @@ -224,280 +224,6 @@ class PaceMakerDummyFixed: public PaceMakerDummy { }; /** - * Simple long-standing proposer liveness gadget (with randomization). - * There are three roles for each replica: proposer, candidate and follower. - * - * For a proposer, it proposes a new block and refrains itself from proposing - * the next block unless it receives the QC for the previous block. It will - * give up the leadership and turn into a candidate when it sees QC for a - * higher block or being impeached. - * - * For a follower, it never proposes any block, but keeps a timer for the QC - * for the block last proposed by the proposer (the proposer it believes to - * be). When it times out without seeing such QC or the proposer is impeached, - * the follower turns into a candidate. - * - * For a candidate, it periodically proposes empty blocks to synchronize the - * preferred branch, with randomized timeout, and check for any new QC. Once it - * sees such new QC, if the QC is given by itself, it becomes the proposer, - * otherwise yields to the creator of the QC as a follower. - * - * CAUTIONS: This pace maker does not guarantee liveness when a Byzantine node - * tries to contend with correct nodes and always proposes higher blocks to - * grab the leadership. If you want to use this for your system, please make - * sure you introduce mechanism to detect and ban such behavior, or use the - * round-robin style pace maker instead. - */ -class PMStickyProposer: virtual public PaceMaker { - enum { - PROPOSER, - FOLLOWER, - CANDIDATE - } role; - double qc_timeout; - double candidate_timeout; - EventContext ec; - /** QC timer or randomized timeout */ - TimerEvent timer; - TimerEvent ev_imp; - block_t last_proposed; - /** the proposer it believes */ - ReplicaID proposer; - - /* extra state needed for a proposer */ - std::queue<promise_t> pending_beats; - bool locked; - - /* extra state needed for a candidate */ - std::unordered_map<ReplicaID, promise_t> last_proposed_by; - - promise_t pm_wait_receive_proposal; - promise_t pm_wait_propose; - promise_t pm_qc_finish; - - void clear_promises() { - pm_wait_receive_proposal.reject(); - pm_wait_propose.reject(); - pm_qc_finish.reject(); - for (auto &p: last_proposed_by) - p.second.reject(); - last_proposed_by.clear(); - } - - /* helper functions for a follower */ - - void reg_follower_receive_proposal() { - pm_wait_receive_proposal.reject(); - (pm_wait_receive_proposal = hsc->async_wait_receive_proposal()) - .then( - salticidae::generic_bind( - &PMStickyProposer::follower_receive_proposal, this, _1)); - } - - void follower_receive_proposal(const Proposal &prop) { - if (prop.proposer == proposer) - { - auto &qc_ref = prop.blk->get_qc_ref(); - if (last_proposed && qc_ref != last_proposed) - { - HOTSTUFF_LOG_INFO("proposer misbehave"); - to_candidate(); /* proposer misbehave */ - return; - } - HOTSTUFF_LOG_PROTO("proposer emits new QC"); - last_proposed = prop.blk; - } - reg_follower_receive_proposal(); - } - - /* helper functions for a proposer */ - - void proposer_schedule_next() { - if (!pending_beats.empty() && !locked) - { - auto pm = pending_beats.front(); - pending_beats.pop(); - pm_qc_finish.reject(); - (pm_qc_finish = hsc->async_qc_finish(last_proposed)) - .then([this, pm]() { - timer.del(); - pm.resolve(proposer); - timer.add(qc_timeout); - HOTSTUFF_LOG_PROTO("QC timer reset"); - }); - locked = true; - } - } - - void reg_proposer_propose() { - pm_wait_propose.reject(); - (pm_wait_propose = hsc->async_wait_proposal()).then( - salticidae::generic_bind( - &PMStickyProposer::proposer_propose, this, _1)); - } - - void proposer_propose(const Proposal &prop) { - last_proposed = prop.blk; - locked = false; - proposer_schedule_next(); - reg_proposer_propose(); - } - - void propose_elect_block() { - DataStream s; - /* FIXME: should extra data be the voter's id? */ - s << hsc->get_id(); - /* propose a block for leader election */ - hsc->on_propose(std::vector<uint256_t>{}, - get_parents(), std::move(s)); - } - - /* helper functions for a candidate */ - - void candidate_qc_timeout() { - pm_qc_finish.reject(); - pm_wait_propose.reject(); - (pm_wait_propose = hsc->async_wait_proposal()).then([this](const Proposal &prop) { - const auto &blk = prop.blk; - (pm_qc_finish = hsc->async_qc_finish(blk)).then([this, blk]() { - HOTSTUFF_LOG_INFO("collected QC for %s", std::string(*blk).c_str()); - /* managed to collect a QC */ - to_proposer(); - propose_elect_block(); - }); - }); - double t = salticidae::gen_rand_timeout(candidate_timeout); - timer.del(); - timer.add(t); - HOTSTUFF_LOG_INFO("candidate next try in %.2fs", t); - propose_elect_block(); - } - - void reg_cp_receive_proposal() { - pm_wait_receive_proposal.reject(); - (pm_wait_receive_proposal = hsc->async_wait_receive_proposal()) - .then( - salticidae::generic_bind( - &PMStickyProposer::cp_receive_proposal, this, _1)); - } - - void cp_receive_proposal(const Proposal &prop) { - auto _proposer = prop.proposer; - auto &p = last_proposed_by[_proposer]; - HOTSTUFF_LOG_PROTO("got block %s from %d", std::string(*prop.blk).c_str(), _proposer); - p.reject(); - (p = hsc->async_qc_finish(prop.blk)).then([this, blk=prop.blk, _proposer]() { - if (hsc->get_hqc() == blk) - to_follower(_proposer); - }); - reg_cp_receive_proposal(); - } - - /* role transitions */ - - void to_follower(ReplicaID new_proposer) { - HOTSTUFF_LOG_INFO("new role: follower"); - clear_promises(); - role = FOLLOWER; - proposer = new_proposer; - last_proposed = nullptr; - hsc->set_vote_disabled(false); - timer.clear(); - /* redirect all pending cmds to the new proposer */ - while (!pending_beats.empty()) - { - pending_beats.front().resolve(proposer); - pending_beats.pop(); - } - reg_follower_receive_proposal(); - } - - void to_proposer() { - HOTSTUFF_LOG_INFO("new role: proposer"); - clear_promises(); - role = PROPOSER; - proposer = hsc->get_id(); - last_proposed = nullptr; - hsc->set_vote_disabled(true); - timer = TimerEvent(ec, [this](TimerEvent &) { - /* proposer unable to get a QC in time */ - to_candidate(); - }); - reg_cp_receive_proposal(); - proposer_propose(Proposal(-1, hsc->get_genesis(), nullptr)); - } - - void to_candidate() { - HOTSTUFF_LOG_INFO("new role: candidate"); - clear_promises(); - role = CANDIDATE; - proposer = hsc->get_id(); - last_proposed = nullptr; - hsc->set_vote_disabled(false); - timer = TimerEvent(ec, [this](TimerEvent &) { - candidate_qc_timeout(); - }); - candidate_timeout = qc_timeout; - reg_cp_receive_proposal(); - candidate_qc_timeout(); - } - - protected: - void impeach() override { - if (role == CANDIDATE) return; - ev_imp = TimerEvent(ec, [this](TimerEvent &) { - to_candidate(); - }); - ev_imp.add(0); - HOTSTUFF_LOG_INFO("schedule to impeach the proposer"); - } - - public: - PMStickyProposer(double qc_timeout, const EventContext &ec): - qc_timeout(qc_timeout), ec(ec) {} - - size_t get_pending_size() override { return pending_beats.size(); } - - void init() { to_candidate(); } - - ReplicaID get_proposer() override { - return proposer; - } - - promise_t beat() override { - if (role != FOLLOWER) - { - promise_t pm; - pending_beats.push(pm); - if (role == PROPOSER) - proposer_schedule_next(); - return std::move(pm); - } - else - return promise_t([proposer=proposer](promise_t &pm) { - pm.resolve(proposer); - }); - } - - promise_t beat_resp(ReplicaID last_proposer) override { - return promise_t([this, last_proposer](promise_t &pm) { - pm.resolve(last_proposer); - }); - } -}; - -struct PaceMakerSticky: public PMHighTail, public PMStickyProposer { - PaceMakerSticky(int32_t parent_limit, double qc_timeout, EventContext eb): - PMHighTail(parent_limit), PMStickyProposer(qc_timeout, eb) {} - - void init(HotStuffCore *hsc) override { - PaceMaker::init(hsc); - PMHighTail::init(); - PMStickyProposer::init(); - } -}; - -/** * Simple long-standing round-robin style proposer liveness gadget. */ class PMRoundRobinProposer: virtual public PaceMaker { @@ -610,15 +336,14 @@ class PMRoundRobinProposer: virtual public PaceMaker { locked = false; last_proposed = hsc->get_genesis(); proposer_update_last_proposed(); + if (proposer == hsc->get_id()) + static_cast<hotstuff::HotStuffBase *>(hsc)->do_elected(); } protected: void on_consensus(const block_t &blk) override { - if (!rotating) - { - timer.del(); - exp_timeout = base_timeout; - } + timer.del(); + exp_timeout = base_timeout; if (prop_blk[proposer] == blk) stop_rotate(); } |