The consensus type requirements are given below as minimal implementation stubs. Actual implementations would augment these stubs with members appropriate for managing the details of transactions and ledgers within the larger application framework.
The transaction type Tx
encapsulates a single transaction
under consideration by consensus.
struct Tx { using ID = ...; ID const & id() const; //... implementation specific };
The transaction set type TxSet
represents a set of Tx
s
that are collectively under consideration by consensus. A TxSet
can be compared against other TxSet
s (typically from peers)
and can be modified to add or remove transactions via the mutable subtype.
struct TxSet { using Tx = Tx; using ID = ...; ID const & id() const; bool exists(Tx::ID const &) const; Tx const * find(Tx::ID const &) const ; // Return set of transactions that are not common with another set // Bool in map is true if in our set, false if in other std::map<Tx::ID, bool> compare(TxSet const & other) const; // A mutable view that allows changing transactions in the set struct MutableTxSet { MutableTxSet(TxSet const &); bool insert(Tx const &); bool erase(Tx::ID const &); }; // Construct from a mutable view. TxSet(MutableTxSet const &); // Alternatively, if the TxSet is itself mutable // just alias MutableTxSet = TxSet //... implementation specific };
The Ledger
type represents the state shared amongst the
distributed participants. Notice that the details of how the next ledger
is generated from the prior ledger and the consensus accepted transaction
set is not part of the interface. Within the generic code, this type is primarily
used to know that peers are working on the same tip of the ledger chain and
to provide some basic timing data for consensus.
struct Ledger { using ID = ...; using Seq = //std::uint32_t?...; ID const & id() const; // Sequence number that is 1 more than the parent ledger's seq() Seq seq() const; // Whether the ledger's close time was a non-trivial consensus result bool closeAgree() const; // The close time resolution used in determing the close time NetClock::duration closeTimeResolution() const; // The (effective) close time, based on the closeTimeResolution NetClock::time_point closeTime() const; // The parent ledger's close time NetClock::time_point parentCloseTime() const; Json::Value getJson() const; //... implementation specific };
The PeerProposal
type represents the signed position taken
by a peer during consensus. The only type requirement is owning an instance
of a generic ConsensusProposal
.
// Represents our proposed position or a peer's proposed position // and is provided with the generic code template <class NodeID_t, class LedgerID_t, class Position_t> class ConsensusProposal; struct PeerPosition { ConsensusProposal< NodeID_t, typename Ledger::ID, typename TxSet::ID> const & proposal() const; // ... implementation specific };
The generic Consensus
relies on Adaptor
template class to implement a set of helper functions that plug the consensus
algorithm into a specific application. The Adaptor
class
also defines the types above needed by the algorithm. Below are excerpts
of the generic consensus implementation and of helper types that will interact
with the concrete implementing class.
// Represents a transction under dispute this round template <class Tx_t, class NodeID_t> class DisputedTx; // Represents how the node participates in Consensus this round enum class ConsensusMode { proposing, observing, wrongLedger, switchedLedger}; // Measure duration of phases of consensus class ConsensusTimer { public: std::chrono::milliseconds read() const; // details omitted ... }; // Initial ledger close times, not rounded by closeTimeResolution // Used to gauge degree of synchronization between a node and its peers struct ConsensusCloseTimes { std::map<NetClock::time_point, int> peers; NetClock::time_point self; }; // Encapsulates the result of consensus. template <class Adaptor> struct ConsensusResult { //! The set of transactions consensus agrees go in the ledger Adaptor::TxSet_t set; //! Our proposed position on transactions/close time ConsensusProposal<...> position; //! Transactions which are under dispute with our peers hash_map<Adaptor::Tx_t::ID, DisputedTx<...>> disputes; // Set of TxSet ids we have already compared/created disputes hash_set<typename Adaptor::TxSet_t::ID> compares; // Measures the duration of the establish phase for this consensus round ConsensusTimer roundTime; // Indicates state in which consensus ended. Once in the accept phase // will be either Yes or MovedOn ConsensusState state = ConsensusState::No; }; template <class Adaptor> class Consensus { public: Consensus(clock_type, Adaptor &, beast::journal); // Kick-off the next round of consensus. void startRound( NetClock::time_point const& now, typename Ledger_t::ID const& prevLedgerID, Ledger_t const& prevLedger, bool proposing); // Call periodically to drive consensus forward. void timerEntry(NetClock::time_point const& now); // A peer has proposed a new position, adjust our tracking. Return true if the proposal // was used. bool peerProposal(NetClock::time_point const& now, Proposal_t const& newProposal); // Process a transaction set acquired from the network void gotTxSet(NetClock::time_point const& now, TxSet_t const& txSet); // ... details };
The stub below shows the set of callback/helper functions required in the implementing class.
struct Adaptor { using Ledger_t = Ledger; using TxSet_t = TxSet; using PeerProposal_t = PeerProposal; using NodeID_t = ...; // Integer-like std::uint32_t to uniquely identify a node // Attempt to acquire a specific ledger from the network. boost::optional<Ledger> acquireLedger(Ledger::ID const & ledgerID); // Acquire the transaction set associated with a proposed position. boost::optional<TxSet> acquireTxSet(TxSet::ID const & setID); // Whether any transactions are in the open ledger bool hasOpenTransactions() const; // Number of proposers that have validated the given ledger std::size_t proposersValidated(Ledger::ID const & prevLedger) const; // Number of proposers that have validated a ledger descended from the // given ledger std::size_t proposersFinished(Ledger::ID const & prevLedger) const; // Return the ID of the last closed (and validated) ledger that the // application thinks consensus should use as the prior ledger. Ledger::ID getPrevLedger(Ledger::ID const & prevLedgerID, Ledger const & prevLedger, ConsensusMode mode); // Called when consensus operating mode changes void onModeChange(ConsensuMode before, ConsensusMode after); // Called when ledger closes. Implementation should generate an initial Result // with position based on the current open ledger's transactions. ConsensusResult onClose(Ledger const &, Ledger const & prev, ConsensusMode mode); // Called when ledger is accepted by consensus void onAccept(ConsensusResult const & result, RCLCxLedger const & prevLedger, NetClock::duration closeResolution, ConsensusCloseTimes const & rawCloseTimes, ConsensusMode const & mode); // Propose the position to peers. void propose(ConsensusProposal<...> const & pos); // Share a received peer proposal with other peers. void share(PeerPosition_t const & pos); // Share a disputed transaction with peers void share(TxSet::Tx const & tx); // Share given transaction set with peers void share(TxSet const &s); //... implementation specific };
The implementing class hides many details of the peer communication model from the generic code.
share
member functions are responsible for sharing
the given type with a node's peers, but are agnostic to the mechanism.
Ideally, messages are delivered faster than LEDGER_GRANULARITY
.
Consensus::Result
instance returned from the
onClose
callback.
acquireLedger
and acquireTxSet
only have non-trivial return if the ledger or transaction set of interest
is available. The implementing class is free to block while acquiring,
or return the empty option while servicing the request asynchronously.
Due to legacy reasons, the two calls are not symmetric. acquireTxSet
requires the host application to call gotTxSet
when
an asynchronous acquire
completes. Conversely, acquireLedger
will be called again later by the consensus code if it still desires
the ledger with the hope that the asynchronous acquisition is complete.