Improve Consensus interface and documentation (RIPD-1340):

- Add Consensus::Result, which represents the result of the
establish state and includes the consensus transaction set, final
proposed position and disputes.
- Add Consensus::Mode to track how we are participating in
consensus and ensures the onAccept callback can distinguish when
we entered the round with consensus versus when we recovered from
a wrong ledger during a round.
- Rename Consensus::Phase to Consensus::State and eliminate the
processing phase.  Instead, accept is a terminal phase which
notifies RCLConsensus via onAccept callbacks.  Even if clients
dispatch accepting to another thread, all future calls except to
startRound will not change the state of consensus.
- Move validate_ status from Consensus to RCLConsensus, since
generic implementation does not directly reference whether a node
is validating or not.
- Eliminate gotTxSetInternal and handle externally received
TxSets distinct from locally generated positions.
- Change ConsensusProposal::changePosition to always update the
internal close time and position even if we have bowed out. This
enforces the invariant that our proposal's position always
matches our transaction set.
This commit is contained in:
Brad Chase
2017-02-03 12:31:08 -05:00
committed by Scott Schurr
parent d5dc715d9c
commit 00c60d408a
33 changed files with 2935 additions and 2639 deletions

View File

@@ -353,6 +353,7 @@ if (WIN32 OR is_xcode)
docs/ docs/
Jamfile.v2 Jamfile.v2
boostbook.dtd boostbook.dtd
consensus.qbk
index.xml index.xml
main.qbk main.qbk
quickref.xml quickref.xml

View File

@@ -44,6 +44,15 @@ install callouts
explicit callout ; explicit callout ;
install consensus_images
:
[ glob images/consensus/*.png ]
:
<location>$(out)/html/images/consensus
;
explicit consensus_images ;
xml doc xml doc
: :
main.qbk main.qbk
@@ -60,7 +69,7 @@ boostbook boostdoc
<xsl:param>boost.root=$(broot) <xsl:param>boost.root=$(broot)
<xsl:param>chunk.first.sections=1 # Chunk the first top-level section? <xsl:param>chunk.first.sections=1 # Chunk the first top-level section?
<xsl:param>chunk.section.depth=8 # Depth to which sections should be chunked <xsl:param>chunk.section.depth=8 # Depth to which sections should be chunked
<xsl:param>generate.section.toc.level=1 # Control depth of TOC generation in sections <xsl:param>generate.section.toc.level=2 # Control depth of TOC generation in sections
<xsl:param>toc.max.depth=2 # How many levels should be created for each TOC? <xsl:param>toc.max.depth=2 # How many levels should be created for each TOC?
<xsl:param>toc.section.depth=2 # How deep should recursive sections appear in the TOC? <xsl:param>toc.section.depth=2 # How deep should recursive sections appear in the TOC?
<xsl:param>generate.toc="chapter toc section toc" <xsl:param>generate.toc="chapter toc section toc"
@@ -68,4 +77,5 @@ boostbook boostdoc
<location>temp <location>temp
<dependency>stylesheets <dependency>stylesheets
<dependency>images <dependency>images
<dependency>consensus_images
; ;

663
docs/consensus.qbk Normal file
View File

@@ -0,0 +1,663 @@
[section Consensus and Validation]
[*This section is a work in progress!!]
Consensus is the task of reaching agreement within a distributed system in the
presence of faulty or even malicious participants. This document outlines the
[@https://ripple.com/files/ripple/consensus/whitepaper.pdf Ripple Consensus
Algorithm] as implemented in [@https://github.com/ripple/rippled rippled], but
focuses on its utility as a generic consensus algorithm independent of the
detailed mechanics of the Ripple Consensus Ledger. Most notably, the algorithm
does not require fully synchronous communication between all nodes in the
network, or even a fixed network topology, but instead achieves consensus via
collectively trusted subnetworks.
[heading Distributed Agreement]
A challenge for distributed systems is reaching agreement on changes in shared
state. For the Ripple network, the shared state is the current ledger--account
information, account balances, order books and other financial data. We will
refer to shared distributed state as a /ledger/ throughout the remainder of this
document.
[$images/consensus/ledger_chain.png [width 50%] [height 50%] ]
As shown above, new ledgers are made by applying a set of transactions to the
prior ledger. For the Ripple network, transactions include payments,
modification of account settings, updates to offers and more.
In a centralized system, generating the next ledger is trivial since there is a
single unique arbiter of which transactions to include and how to apply them to
a ledger. For decentralized systems, participants must resolve disagreements on
the set of transactions to include, the order to apply those transactions, and
even the resulting ledger after applying the transactions. This is even more
difficult when some participants are faulty or malicious.
The Ripple network is a decentralized and _trust-full_ network. Anyone is free
to join and participants are free to choose a subset of peers that are
collectively trusted to not collude in an attempt to defraud the participant.
Leveraging this network of trust, the Ripple algorithm has two main components.
* /Consensus/ in which network participants agree on the transactions to apply
to a prior ledger, based on the positions of their chosen peers.
* /Validation/ in which network participants agree on what ledger was
generated, based on the ledgers generated by chosen peers.
These phases are continually repeated to process transactions submitted to the
network, generating successive ledgers and giving rise to the blockchain ledger
history depicted below. In this diagram, time is flowing to the right, but
links between ledgers point backward to the parent. Also note the alternate
Ledger 2 that was generated by some participants, but which failed validation
and was abandoned.
[$images/consensus/block_chain.png]
The remainder of this section describes the Consensus and Validation algorithms
in more detail and is meant as a companion guide to understanding the generic
implementation in =rippled=. The document *does not* discuss correctness,
fault-tolerance or liveness properties of the algorithms or the full details of
how they integrate within =rippled= to support the Ripple Consensus Ledger.
[section Consensus Overview]
[heading Definitions]
* The /ledger/ is the shared distributed state. Each ledger has a unique ID to
distinguish it from all other ledgers. During consensus, the /previous/,
/prior/ or /last-closed/ ledger is the most recent ledger seen by consensus
and is the basis upon which it will build the next ledger.
* A /transaction/ is an instruction for an atomic change in the ledger state. A
unique ID distinguishes a transaction from other transactions.
* A /transaction set/ is a set of transactions under consideration by consensus.
The goal of consensus is to reach agreement on this set. The generic
consensus algorithm does not rely on an ordering of transactions within the
set, nor does it specify how to apply a transaction set to a ledger to
generate a new ledger. A unique ID distinguishes a set of transactions from
all other sets of transactions.
* A /node/ is one of the distributed actors running the consensus algorithm. It
has a unique ID to distinguish it from all other nodes.
* A /peer/ of a node is another node that it has chosen to follow and which it
believes will not collude with other chosen peers. The choice of peers is not
symmetric, since participants can decide on their chosen sets independently.
* A /position/ is the current belief of the next ledger's transaction set and
close time. Position can refer to the node's own position or the position of a
peer.
* A /proposal/ is one of a sequence of positions a node shares during consensus.
An initial proposal contains the starting position taken by a node before it
considers any peer positions. If a node subsequently updates its position in
response to its peers, it will issue an updated proposal. A proposal is
uniquely identified by the ID of the proposing node, the ID of the position
taken, the ID of the prior ledger the proposal is for, and the sequence number
of the proposal.
* A /dispute/ is a transaction that is either not part of a node's position or
not in a peer's position. During consensus, the node will add or remove
disputed transactions from its position based on that transaction's support
amongst its peers.
Note that most types have an ID as a lightweight identifier of instances of that
type. Consensus often operates on the IDs directly since the underlying type is
potentially expensive to share over the network. For example, proposal's only
contain the ID of the position of a peer. Since many peers likely have the same
position, this reduces the need to send the full transaction set multiple times.
Instead, a node can request the transaction set from the network if necessary.
[heading Overview ]
[$images/consensus/consensus_overview.png [width 50%] [height 50%] ]
The diagram above is an overview of the consensus process from the perspective
of a single participant. Recall that during a single consensus round, a node is
trying to agree with its peers on which transactions to apply to its prior
ledger when generating the next ledger. It also attempts to agree on the
[link effective_close_time network time when the ledger closed]. There are
3 main phases to a consensus round:
* A call to =startRound= places the node in the =Open= phase. In this phase,
the node is waiting for transactions to include in its open ledger.
* At some point, the node will =Close= the open ledger and transition to the
=Establish= phase. In this phase, the node shares/receives peer proposals on
which transactions should be accepted in the closed ledger.
* At some point, the node determines it has reached consensus with its peers on
which transactions to include. It transitions to the =Accept= phase. In this
phase, the node works on applying the transactions to the prior ledger to
generate a new closed ledger. Once the new ledger is completed, the node shares
the validated ledger hash with the network and makes a call to =startRound= to
start the cycle again for the next ledger.
Throughout, a heartbeat timer calls =timerEntry= at a regular frequency to drive
the process forward. Although the =startRound= call occurs at arbitrary times
based on when the initial round began and the time it takes to apply
transactions, the transitions from =Open= to =Establish= and =Establish= to
=Accept= only occur during calls to =timerEntry=. Similarly, transactions can
arrive at arbitrary times, independent of the heartbeat timer. Transactions
received after the =Open= to =Close= transition and not part of peer proposals
won't be considered until the next consensus round. They are represented above
by the light green triangles.
Peer proposals are issued by a node during a =timerEntry= call, but since peers
do not synchronize =timerEntry= calls, they are received by other peers at
arbitrary times. Peer proposals are only considered if received prior to the
=Establish= to =Accept= transition, and only if the peer is working on the same
prior ledger. Peer proposals received after consensus is reached will not be
meaningful and are represented above by the circle with the X in it. Only
proposals from chosen peers are considered.
[#effective_close_time]
[heading Effective Close Time]
In addition to agreeing on a transaction set, each consensus round tries to
agree on the time the ledger closed. Each node calculates its own close time
when it closes the open ledger. This exact close time is rounded to the nearest
multiple of the current /effective close time resolution/. It is this
/effective close time/ that nodes seek to agree on. This allows servers to
derive a common time for a ledger without the need for perfectly synchronized
clocks. As depicted below, the 3 pink arrows represent exact close times from 3
consensus nodes that round to the same effective close time given the current
resolution. The purple arrow represents a peer whose estimate rounds to a
different effective close time given the current resolution.
[$images/consensus/EffCloseTime.png]
The effective close time is part of the node's position and is shared with peers
in its proposals. Just like the position on the consensus transaction set, a
node will update its close time position in response to its peers' effective
close time positions. Peers can agree to disagree on the close time, in which
case the effective close time is taken as 1 second past the prior close.
The close time resolution is itself dynamic, decreasing (coarser) resolution in
subsequent consensus rounds if nodes are unable to reach consensus on an
effective close time and increasing (finer) resolution if nodes consistently
reach close time consensus.
[heading Modes]
Internally, a node operates under one of the following consensus modes. Either
of the first two modes may be chosen when a consensus round starts.
* /Proposing/ indicates the node is a full-fledged consensus participant. It
takes on positions and sends proposals to its peers.
* /Observing/ indicates the node is a passive consensus participant. It
maintains a position internally, but does not propose that position to its
peers. Instead, it receives peer proposals and updates its position
to track the majority of its peers. This may be preferred if the node is only
being used to track the state of the network or during a start-up phase while
it is still synchronizing with the network.
The other two modes are set internally during the consensus round when the node
believes it is no longer working on the dominant ledger chain based on peer
validations. It checks this on every call to =timerEntry=.
* /Wrong Ledger/ indicates the node is not working on the correct prior ledger
and does not have it available. It requests that ledger from the network, but
continues to work towards consensus this round while waiting. If it had been
/proposing/, it will send a special "bowout" proposal to its peers to indicate
its change in mode for the rest of this round. For the duration of the round,
it defers to peer positions for determining the consensus outcome as if it
were just /observing/.
* /Switch Ledger/ indicates that the node has acquired the correct prior ledger
from the network. Although it now has the correct prior ledger, the fact that
it had the wrong one at some point during this round means it is likely behind
and should defer to peer positions for determining the consensus outcome.
[$images/consensus/consensus_modes.png]
Once either wrong ledger or switch ledger are reached, the node cannot
return to proposing or observing until the next consensus round. However,
the node could change its view of the correct prior ledger, so going from
switch ledger to wrong ledger and back again is possible.
The distinction between the wrong and switched ledger modes arises because a
ledger's unique identifier may be known by a node before the ledger itself. This
reflects that fact that the data corresponding to a ledger may be large and take
time to share over the network, whereas the smaller ID could be shared in a peer
validation much more quickly. Distinguishing the two states allows the node to
decide how best to generate the next ledger once it declares consensus.
[heading Phases]
As depicted in the overview diagram, consensus is best viewed as a progression
through 3 phases. There are 4 public methods of the generic consensus algorithm
that determine this progression
* =startRound= begins a consensus round.
* =timerEntry= is called at a regular frequency (=LEDGER_MIN_CLOSE=) and is the
only call to consensus that can change the phase from =Open= to =Establish=
or =Accept=.
* =peerProposal= is called whenever a peer proposal is received and is what
allows a node to update its position in a subsequent =timerEntry= call.
* =gotTxSet= is called when a transaction set is received from the network. This
is typically in response to a prior request from the node to acquire the
transaction set corresponding to a disagreeing peer's position.
The following subsections describe each consensus phase in more detail and what
actions are taken in response to these calls.
[h6 Open]
The =Open= phase is a quiescent period to allow transactions to build up in the
node's open ledger. The duration is a trade-off between latency and throughput.
A shorter window reduces the latency to generating the next ledger, but also
reduces transaction throughput due to fewer transactions accepted into the
ledger.
A call to =startRound= would forcibly begin the next consensus round, skipping
completion of the current round. This is not expected during normal operation.
Calls to =peerProposal= or =gotTxSet= simply store the proposal or transaction
set for use in the coming =Establish= phase.
A call to =timerEntry= first checks that the node is working on the correct
prior ledger. If not, it will update the mode and request the correct ledger.
Otherwise, the node checks whether to switch to the =Establish= phase and close
the ledger.
['Ledger Close]
Under normal circumstances, the open ledger period ends when one of the following
is true
* if there are transactions in the open ledger and more than =LEDGER_MIN_CLOSE=
have elapsed. This is the typical behavior.
* if there are no open transactions and a suitably longer idle interval has
elapsed. This increases the opportunity to get some transaction into
the next ledger and avoids doing useless work closing an empty ledger.
* if more than half the number of prior round peers have already closed or finished
this round. This indicates the node is falling behind and needs to catch up.
When closing the ledger, the node takes its initial position based on the
transactions in the open ledger and uses the current time as
its initial close time estimate. If in the proposing mode, the node shares its
initial position with peers. Now that the node has taken a position, it will
consider any peer positions for this round that arrived earlier. The node
generates disputed transactions for each transaction not in common with a peer's
position. The node also records the vote of each peer for each disputed
transaction.
In the example below, we suppose our node has closed with transactions 1,2 and 3. It creates disputes
for transactions 2,3 and 4, since at least one peer position differs on each.
[#disputes_image]
[$images/consensus/disputes.png [width 20%] [height 20%]]
[h6 Establish]
The establish phase is the active period of consensus in which the node
exchanges proposals with peers in an attempt to reach agreement on the consensus
transactions and effective close time.
A call to =startRound= would forcibly begin the next consensus round, skipping
completion of the current round. This is not expected during normal operation.
Calls to =peerProposal= or =gotTxSet= that reflect new positions will generate
disputed transactions for any new disagreements and will update the peer's vote
for all disputed transactions.
A call to =timerEntry= first checks that the node is working from the correct
prior ledger. If not, the node will update the mode and request the correct
ledger. Otherwise, the node updates the node's position and considers whether
to switch to the =Accepted= phase and declare consensus reached. However, at
least =LEDGER_MIN_CONSENSUS= time must have elapsed before doing either. This
allows peers an opportunity to take an initial position and share it.
['Update Position]
In order to achieve consensus, the node is looking for a transaction set that is
supported by a super-majority of peers. The node works towards this set by
adding or removing disputed transactions from its position based on an
increasing threshold for inclusion.
[$images/consensus/threshold.png [width 50%] [height 50%]]
By starting with a lower threshold, a node initially allows a wide set of
transactions into its position. If the establish round continues and the node is
"stuck", a higher threshold can focus on accepting transactions with the most
support. The constants that define the thresholds and durations at which the
thresholds change are given by `AV_XXX_CONSENSUS_PCT` and
`AV_XXX_CONSENSUS_TIME` respectively, where =XXX= is =INIT=,=MID=,=LATE= and
=STUCK=. The effective close time position is updated using the same
thresholds.
Given the [link disputes_image example disputes above] and an initial threshold
of 50%, our node would retain its position since transaction 1 was not in
dispute and transactions 2 and 3 have 75% support. Since its position did not
change, it would not need to send a new proposal to peers. Peer C would not
change either. Peer A would add transaction 3 to its position and Peer B would
remove transaction 4 from its position; both would then send an updated
position.
Conversely, if the diagram reflected a later call to =timerEntry= that occurs in
the stuck region with a threshold of say 95%, our node would remove transactions
2 and 3 from its candidate set and send an updated position. Likewise, all the
other peers would end up with only transaction 1 in their position.
Lastly, if our node were not in the proposing mode, it would not include its own
vote and just take the majority (>50%) position of its peers. In this example,
our node would maintain its position of transactions 1, 2 and 3.
['Checking Consensus]
After updating its position, the node checks for supermajority agreement with
its peers on its current position. This agreement is of the exact transaction
set, not just the support of individual transactions. That is, if our position
is a subset of a peer's position, that counts as a disagreement. Also recall
that effective close time agreement allows a supermajority of participants
agreeing to disagree.
Consensus is declared when the following 3 clauses are true:
* `LEDGER_MIN_CONSENSUS` time has elapsed in the establish phase
* At least 75% of the prior round proposers have proposed OR this establish
phase is `LEDGER_MIN_CONSENSUS` longer than the last round's establish phase
* =minimumConsensusPercentage= of ourself and our peers share the same position
The middle condition ensures slower peers have a chance to share positions, but
prevents waiting too long on peers that have disconnected. Additionally, a node
can declare that consensus has moved on if =minimumConsensusPercentage= peers
have sent validations and moved on to the next ledger. This outcome indicates
the node has fallen behind its peers and needs to catch up.
If a node is not proposing, it does not include its own position when
calculating the percent of agreeing participants but otherwise follows the above
logic.
['Accepting Consensus]
Once consensus is reached (or moved on), the node switches to the =Accept= phase
and signals to the implementing code that the round is complete. That code is
responsible for using the consensus transaction set to generate the next ledger
and calling =startRound= to begin the next round. The implementation has total
freedom on ordering transactions, deciding what to do if consensus moved on,
determining whether to retry or abandon local transactions that did not make the
consensus set and updating any internal state based on the consensus progress.
[h6 Accept]
The =Accept= phase is the terminal phase of the consensus algorithm. Calls to
=timerEntry=, =peerProposal= and =gotTxSet= will not change the internal
consensus state while in the accept phase. The expectation is that the
application specific code is working to generate the new ledger based on the
consensus outcome. Once complete, that code should make a call to =startRound=
to kick off the next consensus round. The =startRound= call includes the new
prior ledger, prior ledger ID and whether the round should begin in the
proposing or observing mode. After setting some initial state, the phase
transitions to =Open=. The node will also check if the provided prior ledger
and ID are correct, updating the mode and requesting the proper ledger from the
network if necessary.
[endsect] [/Consensus Overview]
[section Consensus Type Requirements]
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.
[heading Transaction]
The transaction type =Tx= encapsulates a single transaction under consideration
by consensus.
```
struct Tx
{
using ID = ...;
ID const & id() const;
//... implementation specific
};
```
[heading Transaction Set]
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
};
```
[heading Ledger] 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 = ...;
ID const & id() const;
// Sequence number that is 1 more than the parent ledger's seq()
std::size_t 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
};
```
[heading Generic Consensus Interface]
Following the
[@https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern CRTP]
idiom, generic =Consensus= relies on a deriving class implementing a set of
helpers and callbacks that encapsulate implementation specific details of the
algorithm. Below are excerpts of the generic consensus implementation and of
helper types that will interact with the concrete implementing class.
```
// Represents our proposed position or a peer's proposed position
template <class NodeID_t, class LedgerID_t, class Position_t> class ConsensusProposal;
// Represents a transction under dispute this round
template <class Tx_t, class NodeID_t> class DisputedTx;
template <class Derived, class Traits> class Consensus
{
protected:
enum class Mode { proposing, observing, wrongLedger, switchedLedger};
// Measure duration of phases of consensus
class Stopwatch
{
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 CloseTimes
{
std::map<NetClock::time_point, int> peers;
NetClock::time_point self;
};
// Encapsulates the result of consensus.
struct Result
{
//! The set of transactions consensus agrees go in the ledger
TxSet_t set;
//! Our proposed position on transactions/close time
Proposal_t position;
//! Transactions which are under dispute with our peers
using Dispute_t = DisputedTx<Tx_t, NodeID_t>;
hash_map<typename Tx_t::ID, Dispute_t> disputes;
// Set of TxSet ids we have already compared/created disputes
hash_set<typename TxSet_t::ID> compares;
// Measures the duration of the establish phase for this consensus round
Stopwatch roundTime;
// Indicates state in which consensus ended. Once in the accept phase
// will be either Yes or MovedOn
ConsensusState state = ConsensusState::No;
};
public:
// 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
};
```
[heading Adapting Generic Consensus]
The stub below shows the set of callback/helper functions required in the implementing class.
```
struct Traits
{
using Ledger_t = Ledger;
using TxSet_t = TxSet;
using NodeID_t = ...; // Integer-like std::uint32_t to uniquely identify a node
};
class ConsensusImp : public Consensus<ConsensusImp, Traits>
{
// 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);
// Get peers' proposed positions. Returns an iterable
// with value_type convertable to ConsensusPosition<...>
auto const & proposals(Ledger::ID const & ledgerID);
// 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,
Mode mode);
// Called when ledger closes. Implementation should generate an initial Result
// with position based on the current open ledger's transactions.
Result onClose(Ledger const &, Ledger const & prev, Mode mode);
// Called when ledger is accepted by consensus
void onAccept(Result const & result,
RCLCxLedger const & prevLedger,
NetClock::duration closeResolution,
CloseTimes const & rawCloseTimes,
Mode const & mode);
// Propose the position to peers.
void propose(ConsensusProposal<...> const & pos);
// Relay a received peer proposal on to other peer's.
void relay(ConsensusProposal<...> const & pos);
// Relay a disputed transaction to peers
void relay(TxSet::Tx const & tx);
// Realy given transaction set with peers
void relay(TxSet const &s);
//... implementation specific
};
```
The implementing class hides many details of the peer communication
model from the generic code.
* The =relay= 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=.
* The generic code does not specify how transactions are submitted by clients,
propagated through the network or stored in the open ledger. Indeed, the open
ledger is only conceptual from the perspective of the generic code---the
initial position and transaction set are opaquely generated in a
`Consensus::Result` instance returned from the =onClose= callback.
* The calls to =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.
[endsect] [/Consensus Type Requirements]
[section Validation]
Coming Soon!
[endsect] [/Validation]
[endsect] [/Consensus and Validation]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -1,5 +1,5 @@
[/ [/
Copyright (c) Copyright (c) 2012, 2013 Ripple Labs Inc. Copyright (c) Copyright (c) 2012-2017 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -16,7 +16,7 @@
[library rippled [library rippled
[quickbook 1.6] [quickbook 1.6]
[copyright 2012 - 2016 Ripple Labs Inc.] [copyright 2012 - 2017 Ripple Labs Inc.]
[purpose C++ Library] [purpose C++ Library]
[license [license
Distributed under the ISC License Distributed under the ISC License
@@ -30,6 +30,8 @@
[template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>'''] [template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>''']
[template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>'''] [template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>''']
[include consensus.qbk]
[section:ref Reference] [section:ref Reference]
[include temp/reference.qbk] [include temp/reference.qbk]
[endsect] [endsect]

View File

@@ -18,30 +18,29 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/beast/core/LexicalCast.h>
#include <ripple/app/consensus/RCLConsensus.h> #include <ripple/app/consensus/RCLConsensus.h>
#include <ripple/app/ledger/InboundTransactions.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/ledger/InboundLedgers.h> #include <ripple/app/ledger/InboundLedgers.h>
#include <ripple/overlay/Overlay.h> #include <ripple/app/ledger/InboundTransactions.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/ledger/LocalTxs.h>
#include <ripple/app/ledger/OpenLedger.h> #include <ripple/app/ledger/OpenLedger.h>
#include <ripple/protocol/digest.h>
#include <ripple/overlay/predicates.h>
#include <ripple/app/misc/AmendmentTable.h> #include <ripple/app/misc/AmendmentTable.h>
#include <ripple/app/misc/HashRouter.h> #include <ripple/app/misc/HashRouter.h>
#include <ripple/consensus/LedgerTiming.h>
#include <ripple/basics/make_lock.h>
#include <ripple/app/ledger/LocalTxs.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/tx/apply.h>
#include <ripple/protocol/Feature.h>
#include <ripple/app/misc/LoadFeeTrack.h> #include <ripple/app/misc/LoadFeeTrack.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/misc/ValidatorList.h> #include <ripple/app/misc/ValidatorList.h>
#include <ripple/app/tx/apply.h>
#include <ripple/basics/make_lock.h>
#include <ripple/beast/core/LexicalCast.h>
#include <ripple/consensus/LedgerTiming.h>
#include <ripple/overlay/Overlay.h>
#include <ripple/overlay/predicates.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/digest.h>
namespace ripple { namespace ripple {
RCLConsensus::RCLConsensus( RCLConsensus::RCLConsensus(
Application& app, Application& app,
std::unique_ptr<FeeVote>&& feeVote, std::unique_ptr<FeeVote>&& feeVote,
@@ -59,38 +58,11 @@ RCLConsensus::RCLConsensus(
, j_(journal) , j_(journal)
, nodeID_{calcNodeID(app.nodeIdentity().first)} , nodeID_{calcNodeID(app.nodeIdentity().first)}
{ {
}
void
RCLConsensus::onStartRound(RCLCxLedger const & ledger)
{
inboundTransactions_.newRound(ledger.seq());
}
// First bool is whether or not we can propose
// Second bool is whether or not we can validate
std::pair <bool, bool>
RCLConsensus::getMode ()
{
bool propose = false;
bool validate = false;
if (! app_.getOPs().isNeedNetworkLedger() && (valPublic_.size() != 0))
{
// We have a key, and we have some idea what the ledger is
validate = true;
// propose only if we're in sync with the network
propose = app_.getOPs().getOperatingMode() == NetworkOPs::omFULL;
}
return { propose, validate };
} }
boost::optional<RCLCxLedger> boost::optional<RCLCxLedger>
RCLConsensus::acquireLedger(LedgerHash const& ledger) RCLConsensus::acquireLedger(LedgerHash const& ledger)
{ {
// we need to switch the ledger we're working from // we need to switch the ledger we're working from
auto buildLCL = ledgerMaster_.getLedgerByHash(ledger); auto buildLCL = ledgerMaster_.getLedgerByHash(ledger);
if (!buildLCL) if (!buildLCL)
@@ -98,8 +70,7 @@ RCLConsensus::acquireLedger(LedgerHash const & ledger)
if (acquiringLedger_ != ledger) if (acquiringLedger_ != ledger)
{ {
// need to start acquiring the correct consensus LCL // need to start acquiring the correct consensus LCL
JLOG (j_.warn()) << JLOG(j_.warn()) << "Need consensus ledger " << ledger;
"Need consensus ledger " << ledger;
// Tell the ledger acquire system that we need the consensus ledger // Tell the ledger acquire system that we need the consensus ledger
acquiringLedger_ = ledger; acquiringLedger_ = ledger;
@@ -107,8 +78,7 @@ RCLConsensus::acquireLedger(LedgerHash const & ledger)
auto app = &app_; auto app = &app_;
auto hash = acquiringLedger_; auto hash = acquiringLedger_;
app_.getJobQueue().addJob( app_.getJobQueue().addJob(
jtADVANCE, "getConsensusLedger", jtADVANCE, "getConsensusLedger", [app, hash](Job&) {
[app, hash] (Job&) {
app->getInboundLedgers().acquire( app->getInboundLedgers().acquire(
hash, 0, InboundLedger::fcCONSENSUS); hash, 0, InboundLedger::fcCONSENSUS);
}); });
@@ -119,10 +89,12 @@ RCLConsensus::acquireLedger(LedgerHash const & ledger)
assert(!buildLCL->open() && buildLCL->isImmutable()); assert(!buildLCL->open() && buildLCL->isImmutable());
assert(buildLCL->info().hash == ledger); assert(buildLCL->info().hash == ledger);
// Notify inbound transactions of the new ledger sequence number
inboundTransactions_.newRound(buildLCL->info().seq);
return RCLCxLedger(buildLCL); return RCLCxLedger(buildLCL);
} }
std::vector<RCLCxPeerPos> std::vector<RCLCxPeerPos>
RCLConsensus::proposals(LedgerHash const& prevLedger) RCLConsensus::proposals(LedgerHash const& prevLedger)
{ {
@@ -140,9 +112,7 @@ RCLConsensus::proposals (LedgerHash const& prevLedger)
} }
void void
RCLConsensus::storeProposal ( RCLConsensus::storeProposal(RCLCxPeerPos::ref peerPos, NodeID const& nodeID)
RCLCxPeerPos::ref peerPos,
NodeID const& nodeID)
{ {
std::lock_guard<std::mutex> _(peerPositionsLock_); std::lock_guard<std::mutex> _(peerPositionsLock_);
@@ -161,10 +131,8 @@ RCLConsensus::relay(RCLCxPeerPos const & peerPos)
auto const& proposal = peerPos.proposal(); auto const& proposal = peerPos.proposal();
prop.set_proposeseq ( prop.set_proposeseq(proposal.proposeSeq());
proposal.proposeSeq ()); prop.set_closetime(proposal.closeTime().time_since_epoch().count());
prop.set_closetime (
proposal.closeTime ().time_since_epoch().count());
prop.set_currenttxhash( prop.set_currenttxhash(
proposal.position().begin(), proposal.position().size()); proposal.position().begin(), proposal.position().size());
@@ -181,10 +149,9 @@ RCLConsensus::relay(RCLCxPeerPos const & peerPos)
} }
void void
RCLConsensus::relay(DisputedTx <RCLCxTx, NodeID> const & dispute) RCLConsensus::relay(RCLCxTx const& tx)
{ {
// If we didn't relay this transaction recently, relay it to all peers // If we didn't relay this transaction recently, relay it to all peers
auto const & tx = dispute.tx();
if (app_.getHashRouter().shouldRelay(tx.id())) if (app_.getHashRouter().shouldRelay(tx.id()))
{ {
auto const slice = tx.tx_.slice(); auto const slice = tx.tx_.slice();
@@ -195,26 +162,25 @@ RCLConsensus::relay(DisputedTx <RCLCxTx, NodeID> const & dispute)
msg.set_receivetimestamp( msg.set_receivetimestamp(
app_.timeKeeper().now().time_since_epoch().count()); app_.timeKeeper().now().time_since_epoch().count());
app_.overlay().foreach (send_always( app_.overlay().foreach (send_always(
std::make_shared<Message> ( std::make_shared<Message>(msg, protocol::mtTRANSACTION)));
msg, protocol::mtTRANSACTION)));
} }
} }
void void
RCLConsensus::propose(RCLCxPeerPos::Proposal const& proposal) RCLConsensus::propose(RCLCxPeerPos::Proposal const& proposal)
{ {
JLOG (j_.trace()) << "We propose: " << JLOG(j_.trace()) << "We propose: "
(proposal.isBowOut () ? std::string ("bowOut") : << (proposal.isBowOut()
to_string (proposal.position ())); ? std::string("bowOut")
: ripple::to_string(proposal.position()));
protocol::TMProposeSet prop; protocol::TMProposeSet prop;
prop.set_currenttxhash (proposal.position().begin(), prop.set_currenttxhash(
proposal.position().size()); proposal.position().begin(), proposal.position().size());
prop.set_previousledger (proposal.prevLedger().begin(), prop.set_previousledger(
proposal.position().size()); proposal.prevLedger().begin(), proposal.position().size());
prop.set_proposeseq(proposal.proposeSeq()); prop.set_proposeseq(proposal.proposeSeq());
prop.set_closetime ( prop.set_closetime(proposal.closeTime().time_since_epoch().count());
proposal.closeTime().time_since_epoch().count());
prop.set_nodepubkey(valPublic_.data(), valPublic_.size()); prop.set_nodepubkey(valPublic_.data(), valPublic_.size());
@@ -222,10 +188,10 @@ RCLConsensus::propose (RCLCxPeerPos::Proposal const& proposal)
HashPrefix::proposal, HashPrefix::proposal,
std::uint32_t(proposal.proposeSeq()), std::uint32_t(proposal.proposeSeq()),
proposal.closeTime().time_since_epoch().count(), proposal.closeTime().time_since_epoch().count(),
proposal.prevLedger(), proposal.position()); proposal.prevLedger(),
proposal.position());
auto sig = signDigest ( auto sig = signDigest(valPublic_, valSecret_, signingHash);
valPublic_, valSecret_, signingHash);
prop.set_signature(sig.data(), sig.size()); prop.set_signature(sig.data(), sig.size());
@@ -233,10 +199,9 @@ RCLConsensus::propose (RCLCxPeerPos::Proposal const& proposal)
} }
void void
RCLConsensus::share (RCLTxSet const& set) RCLConsensus::relay(RCLTxSet const& set)
{ {
inboundTransactions_.giveSet (set.id(), inboundTransactions_.giveSet(set.id(), set.map_, false);
set.map_, false);
} }
boost::optional<RCLTxSet> boost::optional<RCLTxSet>
@@ -249,7 +214,6 @@ RCLConsensus::acquireTxSet(RCLTxSet::ID const & setId)
return boost::none; return boost::none;
} }
bool bool
RCLConsensus::hasOpenTransactions() const RCLConsensus::hasOpenTransactions() const
{ {
@@ -269,36 +233,40 @@ RCLConsensus::proposersFinished(LedgerHash const & h) const
} }
uint256 uint256
RCLConsensus::getLCL ( RCLConsensus::getPrevLedger(
uint256 const& currentLedger, uint256 ledgerID,
uint256 const& priorLedger, RCLCxLedger const& ledger,
bool believedCorrect) Mode mode)
{ {
uint256 parentID;
// Only set the parent ID if we believe ledger is the right ledger
if (mode != Mode::wrongLedger)
parentID = ledger.parentID();
// Get validators that are on our ledger, or "close" to being on // Get validators that are on our ledger, or "close" to being on
// our ledger. // our ledger.
auto vals = auto vals = app_.getValidations().getCurrentValidations(
app_.getValidations().getCurrentValidations( ledgerID, parentID, ledgerMaster_.getValidLedgerIndex());
currentLedger, priorLedger,
ledgerMaster_.getValidLedgerIndex());
uint256 netLgr = currentLedger; uint256 netLgr = ledgerID;
int netLgrCount = 0; int netLgrCount = 0;
for (auto& it : vals) for (auto& it : vals)
{ {
// Switch to ledger supported by more peers // Switch to ledger supported by more peers
// Or stick with ours on a tie // Or stick with ours on a tie
if ((it.second.first > netLgrCount) || if ((it.second.first > netLgrCount) ||
((it.second.first == netLgrCount) && (it.first == currentLedger))) ((it.second.first == netLgrCount) && (it.first == ledgerID)))
{ {
netLgr = it.first; netLgr = it.first;
netLgrCount = it.second.first; netLgrCount = it.second.first;
} }
} }
if(netLgr != currentLedger) if (netLgr != ledgerID)
{ {
if (believedCorrect) if (mode != Mode::wrongLedger)
app_.getOPs().consensusViewChange(); app_.getOPs().consensusViewChange();
if (auto stream = j_.debug()) if (auto stream = j_.debug())
{ {
for (auto& it : vals) for (auto& it : vals)
@@ -310,21 +278,19 @@ RCLConsensus::getLCL (
return netLgr; return netLgr;
} }
RCLConsensus::Result
void RCLConsensus::onClose(
RCLConsensus::onClose(RCLCxLedger const & ledger, bool haveCorrectLCL) RCLCxLedger const& ledger,
NetClock::time_point const& closeTime,
Mode mode)
{ {
notify(protocol::neCLOSING_LEDGER, ledger, haveCorrectLCL); const bool wrongLCL = mode == Mode::wrongLedger;
} const bool proposing = mode == Mode::proposing;
notify(protocol::neCLOSING_LEDGER, ledger, !wrongLCL);
auto const& prevLedger = ledger.ledger_;
std::pair <RCLTxSet, typename RCLCxPeerPos::Proposal>
RCLConsensus::makeInitialPosition (RCLCxLedger const & prevLedgerT,
bool proposing,
bool correctLCL,
NetClock::time_point closeTime,
NetClock::time_point now)
{
auto const &prevLedger = prevLedgerT.ledger_;
ledgerMaster_.applyHeldTransactions(); ledgerMaster_.applyHeldTransactions();
// Tell the ledger master not to acquire the ledger we're probably building // Tell the ledger master not to acquire the ledger we're probably building
ledgerMaster_.setBuildingLedger(prevLedger->info().seq + 1); ledgerMaster_.setBuildingLedger(prevLedger->info().seq + 1);
@@ -341,35 +307,29 @@ RCLConsensus::makeInitialPosition (RCLCxLedger const & prevLedgerT,
Serializer s(2048); Serializer s(2048);
tx.first->add(s); tx.first->add(s);
initialSet->addItem( initialSet->addItem(
SHAMapItem (tx.first->getTransactionID(), std::move (s)), true, false); SHAMapItem(tx.first->getTransactionID(), std::move(s)),
true,
false);
} }
// Add pseudo-transactions to the set // Add pseudo-transactions to the set
if ((app_.config().standalone() || (proposing && correctLCL)) if ((app_.config().standalone() || (proposing && !wrongLCL)) &&
&& ((prevLedger->info().seq % 256) == 0)) ((prevLedger->info().seq % 256) == 0))
{ {
// previous ledger was flag ledger, add pseudo-transactions // previous ledger was flag ledger, add pseudo-transactions
auto const validations = auto const validations =
app_.getValidations().getValidations ( app_.getValidations().getValidations(prevLedger->info().parentHash);
prevLedger->info().parentHash);
std::size_t const count = std::count_if( std::size_t const count = std::count_if(
validations.begin(), validations.end(), validations.begin(), validations.end(), [](auto const& v) {
[](auto const& v)
{
return v.second->isTrusted(); return v.second->isTrusted();
}); });
if (count >= app_.validators().quorum()) if (count >= app_.validators().quorum())
{ {
feeVote_->doVoting ( feeVote_->doVoting(prevLedger, validations, initialSet);
prevLedger,
validations,
initialSet);
app_.getAmendmentTable().doVoting( app_.getAmendmentTable().doVoting(
prevLedger, prevLedger, validations, initialSet);
validations,
initialSet);
} }
} }
@@ -377,101 +337,117 @@ RCLConsensus::makeInitialPosition (RCLCxLedger const & prevLedgerT,
initialSet = initialSet->snapShot(false); initialSet = initialSet->snapShot(false);
auto setHash = initialSet->getHash().as_uint256(); auto setHash = initialSet->getHash().as_uint256();
return std::make_pair<RCLTxSet, RCLCxPeerPos::Proposal> ( return Result{
std::move(initialSet), std::move(initialSet),
RCLCxPeerPos::Proposal{ RCLCxPeerPos::Proposal{
initialLedger->info().parentHash, initialLedger->info().parentHash,
RCLCxPeerPos::Proposal::seqJoin, RCLCxPeerPos::Proposal::seqJoin,
setHash, setHash,
closeTime, closeTime,
now, app_.timeKeeper().closeTime(),
nodeID_ }); nodeID_}};
} }
void void
RCLConsensus::dispatchAccept(RCLTxSet const & txSet) RCLConsensus::onForceAccept(
Result const& result,
RCLCxLedger const& prevLedger,
NetClock::duration const& closeResolution,
CloseTimes const& rawCloseTimes,
Mode const& mode)
{ {
app_.getJobQueue().addJob(jtACCEPT, "acceptLedger", doAccept(result, prevLedger, closeResolution, rawCloseTimes, mode);
[that = this->shared_from_this(), }
consensusSet = txSet]
(auto &) void
RCLConsensus::onAccept(
Result const& result,
RCLCxLedger const& prevLedger,
NetClock::duration const& closeResolution,
CloseTimes const& rawCloseTimes,
Mode const& mode)
{ {
that->accept(consensusSet); app_.getJobQueue().addJob(
jtACCEPT, "acceptLedger", [&, that = this->shared_from_this() ](auto&) {
// note that no lock is held inside this thread, which
// is fine since once a ledger is accepted, consensus
// will not touch any internal state until startRound is called
that->doAccept(
result, prevLedger, closeResolution, rawCloseTimes, mode);
that->app_.getOPs().endConsensus();
}); });
} }
bool void
RCLConsensus::accept( RCLConsensus::doAccept(
RCLTxSet const& set, Result const& result,
NetClock::time_point consensusCloseTime, RCLCxLedger const& prevLedger,
bool proposing_, NetClock::duration closeResolution,
bool validating_, CloseTimes const& rawCloseTimes,
bool haveCorrectLCL_, Mode const& mode)
bool consensusFail_,
LedgerHash const &prevLedgerHash_,
RCLCxLedger const & previousLedger_,
NetClock::duration closeResolution_,
NetClock::time_point const & now_,
std::chrono::milliseconds const & roundTime_,
hash_map<RCLCxTx::ID, DisputedTx <RCLCxTx, NodeID>> const & disputes_,
std::map <NetClock::time_point, int> closeTimes_,
NetClock::time_point const & closeTime_
)
{ {
bool closeTimeCorrect; bool closeTimeCorrect;
const bool proposing = mode == Mode::proposing;
const bool haveCorrectLCL = mode != Mode::wrongLedger;
const bool consensusFail = result.state == ConsensusState::MovedOn;
auto consensusCloseTime = result.position.closeTime();
if (consensusCloseTime == NetClock::time_point{}) if (consensusCloseTime == NetClock::time_point{})
{ {
// We agreed to disagree on the close time // We agreed to disagree on the close time
consensusCloseTime = previousLedger_.closeTime() + 1s; consensusCloseTime = prevLedger.closeTime() + 1s;
closeTimeCorrect = false; closeTimeCorrect = false;
} }
else else
{ {
// We agreed on a close time // We agreed on a close time
consensusCloseTime = effectiveCloseTime(consensusCloseTime, consensusCloseTime = effCloseTime(
closeResolution_, previousLedger_.closeTime()); consensusCloseTime, closeResolution, prevLedger.closeTime());
closeTimeCorrect = true; closeTimeCorrect = true;
} }
JLOG (j_.debug()) JLOG(j_.debug()) << "Report: Prop=" << (proposing ? "yes" : "no")
<< "Report: Prop=" << (proposing_ ? "yes" : "no")
<< " val=" << (validating_ ? "yes" : "no") << " val=" << (validating_ ? "yes" : "no")
<< " corLCL=" << (haveCorrectLCL_ ? "yes" : "no") << " corLCL=" << (haveCorrectLCL ? "yes" : "no")
<< " fail=" << (consensusFail_ ? "yes" : "no"); << " fail=" << (consensusFail ? "yes" : "no");
JLOG (j_.debug()) JLOG(j_.debug()) << "Report: Prev = " << prevLedger.id() << ":"
<< "Report: Prev = " << prevLedgerHash_ << prevLedger.seq();
<< ":" << previousLedger_.seq();
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Put transactions into a deterministic, but unpredictable, order // Put transactions into a deterministic, but unpredictable, order
CanonicalTXSet retriableTxs{ set.id() }; CanonicalTXSet retriableTxs{result.set.id()};
auto sharedLCL = buildLCL(previousLedger_, set, consensusCloseTime,
closeTimeCorrect, closeResolution_, now_, roundTime_, retriableTxs);
auto sharedLCL = buildLCL(
prevLedger,
result.set,
consensusCloseTime,
closeTimeCorrect,
closeResolution,
result.roundTime.read(),
retriableTxs);
auto const newLCLHash = sharedLCL.id(); auto const newLCLHash = sharedLCL.id();
JLOG (j_.debug()) JLOG(j_.debug()) << "Report: NewL = " << newLCLHash << ":"
<< "Report: NewL = " << newLCLHash << sharedLCL.seq();
<< ":" << sharedLCL.seq();
// Tell directly connected peers that we have a new LCL // Tell directly connected peers that we have a new LCL
notify (protocol::neACCEPTED_LEDGER, sharedLCL, haveCorrectLCL_); notify(protocol::neACCEPTED_LEDGER, sharedLCL, haveCorrectLCL);
if (validating_) if (validating_)
validating_ = ledgerMaster_.isCompatible(*sharedLCL.ledger_, validating_ = ledgerMaster_.isCompatible(
app_.journal("LedgerConsensus").warn(), "Not validating"); *sharedLCL.ledger_,
app_.journal("LedgerConsensus").warn(),
"Not validating");
if (validating_ && ! consensusFail_) if (validating_ && !consensusFail)
{ {
validate(sharedLCL, now_, proposing_); validate(sharedLCL, proposing);
JLOG (j_.info()) JLOG(j_.info()) << "CNF Val " << newLCLHash;
<< "CNF Val " << newLCLHash;
} }
else else
JLOG (j_.info()) JLOG(j_.info()) << "CNF buildLCL " << newLCLHash;
<< "CNF buildLCL " << newLCLHash;
// See if we can accept a ledger as fully-validated // See if we can accept a ledger as fully-validated
ledgerMaster_.consensusBuilt(sharedLCL.ledger_, getJson(true)); ledgerMaster_.consensusBuilt(sharedLCL.ledger_, getJson(true));
@@ -490,7 +466,7 @@ RCLConsensus::accept(
// in the previous consensus round. // in the previous consensus round.
// //
bool anyDisputes = false; bool anyDisputes = false;
for (auto& it : disputes_) for (auto& it : result.disputes)
{ {
if (!it.second.getOurVote()) if (!it.second.getOurVote())
{ {
@@ -516,10 +492,8 @@ RCLConsensus::accept(
} }
// Build new open ledger // Build new open ledger
auto lock = make_lock( auto lock = make_lock(app_.getMasterMutex(), std::defer_lock);
app_.getMasterMutex(), std::defer_lock); auto sl = make_lock(ledgerMaster_.peekMutex(), std::defer_lock);
auto sl = make_lock(
ledgerMaster_.peekMutex (), std::defer_lock);
std::lock(lock, sl); std::lock(lock, sl);
auto const lastVal = ledgerMaster_.getValidatedLedger(); auto const lastVal = ledgerMaster_.getValidatedLedger();
@@ -528,11 +502,16 @@ RCLConsensus::accept(
rules.emplace(*lastVal, app_.config().features); rules.emplace(*lastVal, app_.config().features);
else else
rules.emplace(app_.config().features); rules.emplace(app_.config().features);
app_.openLedger().accept(app_, *rules, app_.openLedger().accept(
sharedLCL.ledger_, localTxs_.getTxSet(), anyDisputes, retriableTxs, tapNONE, app_,
*rules,
sharedLCL.ledger_,
localTxs_.getTxSet(),
anyDisputes,
retriableTxs,
tapNONE,
"consensus", "consensus",
[&](OpenView& view, beast::Journal j) [&](OpenView& view, beast::Journal j) {
{
// Stuff the ledger with transactions from the queue. // Stuff the ledger with transactions from the queue.
return app_.getTxQ().accept(app_, view); return app_.getTxQ().accept(app_, view);
}); });
@@ -548,30 +527,35 @@ RCLConsensus::accept(
// Do these need to exist? // Do these need to exist?
assert(ledgerMaster_.getClosedLedger()->info().hash == sharedLCL.id()); assert(ledgerMaster_.getClosedLedger()->info().hash == sharedLCL.id());
assert (app_.openLedger().current()->info().parentHash == sharedLCL.id()); assert(
app_.openLedger().current()->info().parentHash == sharedLCL.id());
} }
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
if (haveCorrectLCL_ && ! consensusFail_)
{
// we entered the round with the network, // we entered the round with the network,
// see how close our close time is to other node's // see how close our close time is to other node's
// close time reports, and update our clock. // close time reports, and update our clock.
JLOG (j_.info()) if ((mode == Mode::proposing || mode == Mode::observing) && !consensusFail)
<< "We closed at " << closeTime_.time_since_epoch().count(); {
auto closeTime = rawCloseTimes.self;
JLOG(j_.info()) << "We closed at "
<< closeTime.time_since_epoch().count();
using usec64_t = std::chrono::duration<std::uint64_t>; using usec64_t = std::chrono::duration<std::uint64_t>;
usec64_t closeTotal = std::chrono::duration_cast<usec64_t>(closeTime_.time_since_epoch()); usec64_t closeTotal =
std::chrono::duration_cast<usec64_t>(closeTime.time_since_epoch());
int closeCount = 1; int closeCount = 1;
for (auto const& p : closeTimes_) for (auto const& p : rawCloseTimes.peers)
{ {
// FIXME: Use median, not average // FIXME: Use median, not average
JLOG(j_.info()) JLOG(j_.info())
<< std::to_string(p.second) << std::to_string(p.second) << " time votes for "
<< " time votes for "
<< std::to_string(p.first.time_since_epoch().count()); << std::to_string(p.first.time_since_epoch().count());
closeCount += p.second; closeCount += p.second;
closeTotal += std::chrono::duration_cast<usec64_t>(p.first.time_since_epoch()) * p.second; closeTotal += std::chrono::duration_cast<usec64_t>(
p.first.time_since_epoch()) *
p.second;
} }
closeTotal += usec64_t(closeCount / 2); // for round to nearest closeTotal += usec64_t(closeCount / 2); // for round to nearest
@@ -581,31 +565,20 @@ RCLConsensus::accept(
using duration = std::chrono::duration<std::int32_t>; using duration = std::chrono::duration<std::int32_t>;
using time_point = std::chrono::time_point<NetClock, duration>; using time_point = std::chrono::time_point<NetClock, duration>;
auto offset = time_point{closeTotal} - auto offset = time_point{closeTotal} -
std::chrono::time_point_cast<duration>(closeTime_); std::chrono::time_point_cast<duration>(closeTime);
JLOG (j_.info()) JLOG(j_.info()) << "Our close offset is estimated at " << offset.count()
<< "Our close offset is estimated at " << " (" << closeCount << ")";
<< offset.count() << " (" << closeCount << ")";
app_.timeKeeper().adjustCloseTime(offset); app_.timeKeeper().adjustCloseTime(offset);
} }
return validating_;
} }
void
RCLConsensus::endConsensus(bool correctLCL)
{
app_.getOPs ().endConsensus (correctLCL);
}
void void
RCLConsensus::notify( RCLConsensus::notify(
protocol::NodeEvent ne, protocol::NodeEvent ne,
RCLCxLedger const& ledger, RCLCxLedger const& ledger,
bool haveCorrectLCL) bool haveCorrectLCL)
{ {
protocol::TMStatusChange s; protocol::TMStatusChange s;
if (!haveCorrectLCL) if (!haveCorrectLCL)
@@ -615,10 +588,11 @@ RCLConsensus::notify(
s.set_ledgerseq(ledger.seq()); s.set_ledgerseq(ledger.seq());
s.set_networktime(app_.timeKeeper().now().time_since_epoch().count()); s.set_networktime(app_.timeKeeper().now().time_since_epoch().count());
s.set_ledgerhashprevious(ledger.parentID().begin (), s.set_ledgerhashprevious(
ledger.parentID().begin(),
std::decay_t<decltype(ledger.parentID())>::bytes); std::decay_t<decltype(ledger.parentID())>::bytes);
s.set_ledgerhash (ledger.id().begin (), s.set_ledgerhash(
std::decay_t<decltype(ledger.id())>::bytes); ledger.id().begin(), std::decay_t<decltype(ledger.id())>::bytes);
std::uint32_t uMin, uMax; std::uint32_t uMin, uMax;
if (!ledgerMaster_.getFullValidatedRange(uMin, uMax)) if (!ledgerMaster_.getFullValidatedRange(uMin, uMax))
@@ -633,13 +607,11 @@ RCLConsensus::notify(
} }
s.set_firstseq(uMin); s.set_firstseq(uMin);
s.set_lastseq(uMax); s.set_lastseq(uMax);
app_.overlay ().foreach (send_always ( app_.overlay().foreach (
std::make_shared <Message> ( send_always(std::make_shared<Message>(s, protocol::mtSTATUS_CHANGE)));
s, protocol::mtSTATUS_CHANGE)));
JLOG(j_.trace()) << "send status change to peer"; JLOG(j_.trace()) << "send status change to peer";
} }
/** Apply a set of transactions to a ledger. /** Apply a set of transactions to a ledger.
Typically the txFilter is used to reject transactions Typically the txFilter is used to reject transactions
@@ -663,7 +635,6 @@ applyTransactions (
auto& set = *(cSet.map_); auto& set = *(cSet.map_);
CanonicalTXSet retriableTxs(set.getHash().as_uint256()); CanonicalTXSet retriableTxs(set.getHash().as_uint256());
for (auto const& item : set) for (auto const& item : set)
{ {
if (!txFilter(item.key())) if (!txFilter(item.key()))
@@ -671,8 +642,7 @@ applyTransactions (
// The transaction wan't filtered // The transaction wan't filtered
// Add it to the set to be tried in canonical order // Add it to the set to be tried in canonical order
JLOG (j.debug()) << JLOG(j.debug()) << "Processing candidate transaction: " << item.key();
"Processing candidate transaction: " << item.key();
try try
{ {
retriableTxs.insert( retriableTxs.insert(
@@ -688,8 +658,7 @@ applyTransactions (
// Attempt to apply all of the retriable transactions // Attempt to apply all of the retriable transactions
for (int pass = 0; pass < LEDGER_TOTAL_PASSES; ++pass) for (int pass = 0; pass < LEDGER_TOTAL_PASSES; ++pass)
{ {
JLOG (j.debug()) << "Pass: " << pass << " Txns: " JLOG(j.debug()) << "Pass: " << pass << " Txns: " << retriableTxs.size()
<< retriableTxs.size ()
<< (certainRetry ? " retriable" : " final"); << (certainRetry ? " retriable" : " final");
int changes = 0; int changes = 0;
@@ -699,8 +668,8 @@ applyTransactions (
{ {
try try
{ {
switch (applyTransaction (app, view, switch (applyTransaction(
*it->second, certainRetry, tapNO_CHECK_SIGN, j)) app, view, *it->second, certainRetry, tapNO_CHECK_SIGN, j))
{ {
case ApplyResult::Success: case ApplyResult::Success:
it = retriableTxs.erase(it); it = retriableTxs.erase(it);
@@ -717,14 +686,13 @@ applyTransactions (
} }
catch (std::exception const&) catch (std::exception const&)
{ {
JLOG (j.warn()) JLOG(j.warn()) << "Transaction throws";
<< "Transaction throws";
it = retriableTxs.erase(it); it = retriableTxs.erase(it);
} }
} }
JLOG (j.debug()) << "Pass: " JLOG(j.debug()) << "Pass: " << pass << " finished " << changes
<< pass << " finished " << changes << " changes"; << " changes";
// A non-retry pass made no changes // A non-retry pass made no changes
if (!changes && !certainRetry) if (!changes && !certainRetry)
@@ -748,7 +716,6 @@ RCLConsensus::buildLCL(
NetClock::time_point closeTime, NetClock::time_point closeTime,
bool closeTimeCorrect, bool closeTimeCorrect,
NetClock::duration closeResolution, NetClock::duration closeResolution,
NetClock::time_point now,
std::chrono::milliseconds roundTime, std::chrono::milliseconds roundTime,
CanonicalTXSet& retriableTxs) CanonicalTXSet& retriableTxs)
{ {
@@ -760,14 +727,13 @@ RCLConsensus::buildLCL(
closeTimeCorrect = ((replay->closeFlags_ & sLCF_NoConsensusTime) == 0); closeTimeCorrect = ((replay->closeFlags_ & sLCF_NoConsensusTime) == 0);
} }
JLOG (j_.debug()) JLOG(j_.debug()) << "Report: TxSt = " << set.id() << ", close "
<< "Report: TxSt = " << set.id () << closeTime.time_since_epoch().count()
<< ", close " << closeTime.time_since_epoch().count()
<< (closeTimeCorrect ? "" : "X"); << (closeTimeCorrect ? "" : "X");
// Build the new last closed ledger // Build the new last closed ledger
auto buildLCL = std::make_shared<Ledger>(*previousLedger.ledger_, now); auto buildLCL =
std::make_shared<Ledger>(*previousLedger.ledger_, closeTime);
auto const v2_enabled = buildLCL->rules().enabled(featureSHAMapV2); auto const v2_enabled = buildLCL->rules().enabled(featureSHAMapV2);
@@ -780,8 +746,7 @@ RCLConsensus::buildLCL(
// Set up to write SHAMap changes to our database, // Set up to write SHAMap changes to our database,
// perform updates, extract changes // perform updates, extract changes
JLOG (j_.debug()) JLOG(j_.debug()) << "Applying consensus set transactions to the"
<< "Applying consensus set transactions to the"
<< " last closed ledger"; << " last closed ledger";
{ {
@@ -791,21 +756,19 @@ RCLConsensus::buildLCL(
{ {
// Special case, we are replaying a ledger close // Special case, we are replaying a ledger close
for (auto& tx : replay->txns_) for (auto& tx : replay->txns_)
applyTransaction (app_, accum, *tx.second, applyTransaction(
false, tapNO_CHECK_SIGN, j_); app_, accum, *tx.second, false, tapNO_CHECK_SIGN, j_);
} }
else else
{ {
// Normal case, we are not replaying a ledger close // Normal case, we are not replaying a ledger close
retriableTxs = applyTransactions (app_, set, accum, retriableTxs = applyTransactions(
[&buildLCL](uint256 const& txID) app_, set, accum, [&buildLCL](uint256 const& txID) {
{
return !buildLCL->txExists(txID); return !buildLCL->txExists(txID);
}); });
} }
// Update fee computations. // Update fee computations.
app_.getTxQ().processClosedLedger(app_, accum, app_.getTxQ().processClosedLedger(app_, accum, roundTime > 5s);
roundTime > 5s);
accum.apply(*buildLCL); accum.apply(*buildLCL);
} }
@@ -823,51 +786,42 @@ RCLConsensus::buildLCL(
hotACCOUNT_NODE, buildLCL->info().seq); hotACCOUNT_NODE, buildLCL->info().seq);
int tmf = buildLCL->txMap().flushDirty( int tmf = buildLCL->txMap().flushDirty(
hotTRANSACTION_NODE, buildLCL->info().seq); hotTRANSACTION_NODE, buildLCL->info().seq);
JLOG (j_.debug()) << "Flushed " << JLOG(j_.debug()) << "Flushed " << asf << " accounts and " << tmf
asf << " accounts and " << << " transaction nodes";
tmf << " transaction nodes";
} }
buildLCL->unshare(); buildLCL->unshare();
// Accept ledger // Accept ledger
buildLCL->setAccepted(closeTime, closeResolution, buildLCL->setAccepted(
closeTimeCorrect, app_.config()); closeTime, closeResolution, closeTimeCorrect, app_.config());
// And stash the ledger in the ledger master // And stash the ledger in the ledger master
if (ledgerMaster_.storeLedger(buildLCL)) if (ledgerMaster_.storeLedger(buildLCL))
JLOG (j_.debug()) JLOG(j_.debug()) << "Consensus built ledger we already had";
<< "Consensus built ledger we already had";
else if (app_.getInboundLedgers().find(buildLCL->info().hash)) else if (app_.getInboundLedgers().find(buildLCL->info().hash))
JLOG (j_.debug()) JLOG(j_.debug()) << "Consensus built ledger we were acquiring";
<< "Consensus built ledger we were acquiring";
else else
JLOG (j_.debug()) JLOG(j_.debug()) << "Consensus built new ledger";
<< "Consensus built new ledger";
return RCLCxLedger{std::move(buildLCL)}; return RCLCxLedger{std::move(buildLCL)};
} }
void void
RCLConsensus::validate( RCLConsensus::validate(RCLCxLedger const& ledger, bool proposing)
RCLCxLedger const & ledger,
NetClock::time_point now,
bool proposing)
{ {
auto validationTime = now; auto validationTime = app_.timeKeeper().closeTime();
if (validationTime <= lastValidationTime_) if (validationTime <= lastValidationTime_)
validationTime = lastValidationTime_ + 1s; validationTime = lastValidationTime_ + 1s;
lastValidationTime_ = validationTime; lastValidationTime_ = validationTime;
// Build validation // Build validation
auto v = std::make_shared<STValidation> (ledger.id(), auto v = std::make_shared<STValidation>(
validationTime, valPublic_, proposing); ledger.id(), validationTime, valPublic_, proposing);
v->setFieldU32(sfLedgerSequence, ledger.seq()); v->setFieldU32(sfLedgerSequence, ledger.seq());
// Add our load fee to the validation // Add our load fee to the validation
auto const& feeTrack = app_.getFeeTrack(); auto const& feeTrack = app_.getFeeTrack();
std::uint32_t fee = std::max( std::uint32_t fee =
feeTrack.getLocalFee(), std::max(feeTrack.getLocalFee(), feeTrack.getClusterFee());
feeTrack.getClusterFee());
if (fee > feeTrack.getLoadBase()) if (fee > feeTrack.getLoadBase())
v->setFieldU32(sfLoadFee, fee); v->setFieldU32(sfLoadFee, fee);
@@ -892,6 +846,14 @@ RCLConsensus::validate(
app_.overlay().send(val); app_.overlay().send(val);
} }
Json::Value
RCLConsensus::getJson(bool full) const
{
auto ret = Base::getJson(full);
ret["validating"] = validating_;
return ret;
}
PublicKey const& PublicKey const&
RCLConsensus::getValidationPublicKey() const RCLConsensus::getValidationPublicKey() const
{ {
@@ -899,11 +861,73 @@ RCLConsensus::getValidationPublicKey () const
} }
void void
RCLConsensus::setValidationKeys (SecretKey const& valSecret, RCLConsensus::setValidationKeys(
SecretKey const& valSecret,
PublicKey const& valPublic) PublicKey const& valPublic)
{ {
valSecret_ = valSecret; valSecret_ = valSecret;
valPublic_ = valPublic; valPublic_ = valPublic;
} }
void
RCLConsensus::timerEntry(NetClock::time_point const& now)
{
try
{
Base::timerEntry(now);
}
catch (SHAMapMissingNode const& mn)
{
// This should never happen
leaveConsensus();
JLOG(j_.error()) << "Missing node during consensus process " << mn;
Rethrow();
}
}
void
RCLConsensus::gotTxSet(NetClock::time_point const& now, RCLTxSet const& txSet)
{
try
{
Base::gotTxSet(now, txSet);
}
catch (SHAMapMissingNode const& mn)
{
// This should never happen
leaveConsensus();
JLOG(j_.error()) << "Missing node during consensus process " << mn;
Rethrow();
}
}
void
RCLConsensus::startRound(
NetClock::time_point const& now,
RCLCxLedger::ID const& prevLgrId,
RCLCxLedger const& prevLgr)
{
// We have a key, and we have some idea what the ledger is
validating_ =
!app_.getOPs().isNeedNetworkLedger() && (valPublic_.size() != 0);
// propose only if we're in sync with the network (and validating)
bool proposing =
validating_ && (app_.getOPs().getOperatingMode() == NetworkOPs::omFULL);
if (validating_)
{
JLOG(j_.info()) << "Entering consensus process, validating";
}
else
{
// Otherwise we just want to monitor the validation process.
JLOG(j_.info()) << "Entering consensus process, watching";
}
// Notify inbOund ledgers that we are starting a new round
inboundTransactions_.newRound(prevLgr.seq());
Base::startRound(now, prevLgrId, prevLgr, proposing);
}
} }

View File

@@ -1,4 +1,4 @@
//------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc. Copyright (c) 2012, 2013 Ripple Labs Inc.
@@ -21,19 +21,19 @@
#define RIPPLE_APP_CONSENSUS_RCLCONSENSUS_H_INCLUDED #define RIPPLE_APP_CONSENSUS_RCLCONSENSUS_H_INCLUDED
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/consensus/RCLCxLedger.h>
#include <ripple/app/consensus/RCLCxPeerPos.h>
#include <ripple/app/consensus/RCLCxTx.h>
#include <ripple/app/misc/FeeVote.h>
#include <ripple/basics/CountedObject.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/consensus/Consensus.h>
#include <ripple/core/JobQueue.h>
#include <ripple/overlay/Message.h>
#include <ripple/protocol/RippleLedgerHash.h>
#include <ripple/protocol/STValidation.h> #include <ripple/protocol/STValidation.h>
#include <ripple/shamap/SHAMap.h> #include <ripple/shamap/SHAMap.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/app/misc/FeeVote.h>
#include <ripple/protocol/RippleLedgerHash.h>
#include <ripple/app/consensus/RCLCxLedger.h>
#include <ripple/app/consensus/RCLCxTx.h>
#include <ripple/app/consensus/RCLCxPeerPos.h>
#include <ripple/core/JobQueue.h>
#include <ripple/consensus/Consensus.h>
#include <ripple/basics/CountedObject.h>
#include <ripple/overlay/Message.h>
namespace ripple { namespace ripple {
@@ -50,24 +50,20 @@ struct RCLCxTraits
using NodeID_t = NodeID; using NodeID_t = NodeID;
//! TxSet type presented to Consensus //! TxSet type presented to Consensus
using TxSet_t = RCLTxSet; using TxSet_t = RCLTxSet;
//! MissingTxException type neede by Consensus
using MissingTxException_t = SHAMapMissingNode;
}; };
/** Adapts the generic Consensus algorithm for use by RCL. /** Adapts the generic Consensus algorithm for use by RCL.
@note The enabled_shared_from_this base allows the application to properly @note The enabled_shared_from_this base allows the application to properly
create a shared instance of RCLConsensus for use in the accept logic.. create a shared instance of RCLConsensus for use in the accept logic..
*/ */
class RCLConsensus : public Consensus<RCLConsensus, RCLCxTraits> class RCLConsensus final : public Consensus<RCLConsensus, RCLCxTraits>,
, public std::enable_shared_from_this <RCLConsensus> public std::enable_shared_from_this<RCLConsensus>,
, public CountedObject <RCLConsensus> public CountedObject<RCLConsensus>
{ {
using Base = Consensus<RCLConsensus, RCLCxTraits>; using Base = Consensus<RCLConsensus, RCLCxTraits>;
using Base::accept;
public:
public:
//! Constructor //! Constructor
RCLConsensus( RCLConsensus(
Application& app, Application& app,
@@ -77,10 +73,17 @@ public:
InboundTransactions& inboundTransactions, InboundTransactions& inboundTransactions,
typename Base::clock_type const& clock, typename Base::clock_type const& clock,
beast::Journal journal); beast::Journal journal);
RCLConsensus(RCLConsensus const&) = delete;
RCLConsensus& operator=(RCLConsensus const&) = delete;
static char const* getCountedObjectName() { return "Consensus"; } RCLConsensus(RCLConsensus const&) = delete;
RCLConsensus&
operator=(RCLConsensus const&) = delete;
static char const*
getCountedObjectName()
{
return "Consensus";
}
/** Save the given consensus proposed by a peer with nodeID for later /** Save the given consensus proposed by a peer with nodeID for later
use in consensus. use in consensus.
@@ -91,6 +94,50 @@ public:
void void
storeProposal(RCLCxPeerPos::ref peerPos, NodeID const& nodeID); storeProposal(RCLCxPeerPos::ref peerPos, NodeID const& nodeID);
//! Whether we are validating consensus ledgers.
bool
validating() const
{
return validating_;
}
bool
haveCorrectLCL() const
{
return mode() != Mode::wrongLedger;
}
bool
proposing() const
{
return mode() == Mode::proposing;
}
/** Get the Json state of the consensus process.
Called by the consensus_info RPC.
@param full True if verbose response desired.
@return The Json state.
*/
Json::Value
getJson(bool full) const;
//! See Consensus::startRound
void
startRound(
NetClock::time_point const& now,
RCLCxLedger::ID const& prevLgrId,
RCLCxLedger const& prevLgr);
//! See Consensus::timerEntry
void
timerEntry(NetClock::time_point const& now);
//! See Consensus::gotTxSet
void
gotTxSet(NetClock::time_point const& now, RCLTxSet const& txSet);
/** Returns validation public key */ /** Returns validation public key */
PublicKey const& PublicKey const&
getValidationPublicKey() const; getValidationPublicKey() const;
@@ -105,17 +152,6 @@ private:
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Consensus type requirements. // Consensus type requirements.
/** Notification that a new consensus round has begun.
@param ledger The ledger we are building consensus on
*/
void
onStartRound(RCLCxLedger const & ledger);
//! @return Whether consensus should be (proposing, validating)
std::pair <bool, bool>
getMode ();
/** Attempt to acquire a specific ledger. /** Attempt to acquire a specific ledger.
If not available, asynchronously acquires from the network. If not available, asynchronously acquires from the network.
@@ -144,10 +180,10 @@ private:
Only relay if the provided transaction hasn't been shared recently. Only relay if the provided transaction hasn't been shared recently.
@param dispute The disputed transaction to relay. @param tx The disputed transaction to relay.
*/ */
void void
relay(DisputedTx <RCLCxTx, NodeID> const & dispute); relay(RCLCxTx const& tx);
/** Acquire the transaction set associated with a proposal. /** Acquire the transaction set associated with a proposal.
@@ -173,7 +209,8 @@ private:
std::size_t std::size_t
proposersValidated(LedgerHash const& h) const; proposersValidated(LedgerHash const& h) const;
/** Number of proposers that have validated a ledger descended from requested ledger. /** Number of proposers that have validated a ledger descended from
requested ledger.
@param h The hash of the ledger of interest. @param h The hash of the ledger of interest.
@return The number of validating peers that have validated a ledger @return The number of validating peers that have validated a ledger
@@ -189,115 +226,74 @@ private:
void void
propose(RCLCxPeerPos::Proposal const& proposal); propose(RCLCxPeerPos::Proposal const& proposal);
/** Share the given tx set with peers. /** Relay the given tx set to peers.
@param set The TxSet to share. @param set The TxSet to share.
*/ */
void void
share (RCLTxSet const& set); relay(RCLTxSet const& set);
/** Get the last closed ledger (LCL) seen on the network /** Get the ID of the previous ledger/last closed ledger(LCL) on the network
@param currentLedger Current ledger used in consensus @param ledgerID ID of previous ledger used by consensus
@param priorLedger Prior ledger used in consensus @param ledger Previous ledger consensus has available
@param believedCorrect Whether consensus believes currentLedger is LCL @param mode Current consensus mode
@return The id of the last closed network
@return The hash of the last closed network @note ledgerID may not match ledger.id() if we haven't acquired
the ledger matching ledgerID from the network
*/ */
uint256 uint256
getLCL ( getPrevLedger(
uint256 const& currentLedger, uint256 ledgerID,
uint256 const& priorLedger, RCLCxLedger const& ledger,
bool believedCorrect); Mode mode);
/** Close the open ledger and return initial consensus position.
/** Notification that the ledger has closed.
@param ledger the ledger we are changing to @param ledger the ledger we are changing to
@param haveCorrectLCL whether we believe this is the correct LCL @param closeTime When consensus closed the ledger
@param mode Current consensus mode
@return Tentative consensus result
*/ */
void Result
onClose(RCLCxLedger const & ledger, bool haveCorrectLCL); onClose(
RCLCxLedger const& ledger,
NetClock::time_point const& closeTime,
Mode mode);
/** Create our initial position of transactions to accept in this round /** Process the accepted ledger.
of consensus.
@param prevLedger The ledger the transactions apply to
@param isProposing Whether we are currently proposing
@param isCorrectLCL Whether we have the correct LCL
@param closeTime When we believe the ledger closed
@param now The current network adjusted time
@return Pair of (i) transactions we believe are in the ledger
(ii) the corresponding proposal of those transactions
to send to peers
*/
std::pair <RCLTxSet, typename RCLCxPeerPos::Proposal>
makeInitialPosition (
RCLCxLedger const & prevLedger,
bool isProposing,
bool isCorrectLCL,
NetClock::time_point closeTime,
NetClock::time_point now);
/** Dispatch a call to Consensus::accept
Accepting a ledger may be expensive, so this function can dispatch Accepting a ledger may be expensive, so this function can dispatch
that call to another thread if desired and must call the accept that call to another thread if desired.
method of the generic consensus algorithm.
@param txSet The transactions to accept. @param result The result of consensus
@param prevLedger The closed ledger consensus worked from
@param closeResolution The resolution used in agreeing on an effective
closeTiem
@param rawCloseTimes The unrounded closetimes of ourself and our peers
@param mode Our participating mode at the time consensus was declared
*/ */
void void
dispatchAccept(RCLTxSet const & txSet); onAccept(
Result const& result,
RCLCxLedger const& prevLedger,
NetClock::duration const & closeResolution,
CloseTimes const& rawCloseTimes,
Mode const& mode);
/** Process the accepted ledger that was a result of simulation/force
accept.
/** Accept a new ledger based on the given transactions. @ref onAccept
TODO: Too many arguments, need to group related types.
@param set The set of accepted transactions
@param consensusCloseTime Consensus agreed upon close time
@param proposing_ Whether we are proposing
@param validating_ Whether we are validating
@param haveCorrectLCL_ Whether we had the correct last closed ledger
@param consensusFail_ Whether consensus failed
@param prevLedgerHash_ The hash/id of the previous ledger
@param previousLedger_ The previous ledger
@param closeResolution_ The close time resolution used this round
@param now Current network adjsuted time
@param roundTime_ Duration of this consensus round
@param disputes_ Disputed trarnsactions from this round
@param closeTimes_ Histogram of peers close times
@param closeTime Our close time
@return Whether we should continue validating
*/
bool
accept(
RCLTxSet const& set,
NetClock::time_point consensusCloseTime,
bool proposing_,
bool validating_,
bool haveCorrectLCL_,
bool consensusFail_,
LedgerHash const &prevLedgerHash_,
RCLCxLedger const & previousLedger_,
NetClock::duration closeResolution_,
NetClock::time_point const & now,
std::chrono::milliseconds const & roundTime_,
hash_map<RCLCxTx::ID, DisputedTx <RCLCxTx, NodeID>> const & disputes_,
std::map <NetClock::time_point, int> closeTimes_,
NetClock::time_point const & closeTime
);
/** Signal the end of consensus to the application, which will start the
next round.
@param correctLCL Whether we believe we have the correct LCL
*/ */
void void
endConsensus(bool correctLCL); onForceAccept(
Result const& result,
RCLCxLedger const& prevLedger,
NetClock::duration const &closeResolution,
CloseTimes const& rawCloseTimes,
Mode const& mode);
//!------------------------------------------------------------------------- //!-------------------------------------------------------------------------
// Additional members (not directly required by Consensus interface) // Additional members (not directly required by Consensus interface)
@@ -308,7 +304,22 @@ private:
@param haveCorrectLCL Whether we believ we have the correct LCL. @param haveCorrectLCL Whether we believ we have the correct LCL.
*/ */
void void
notify(protocol::NodeEvent ne, RCLCxLedger const & ledger, bool haveCorrectLCL); notify(
protocol::NodeEvent ne,
RCLCxLedger const& ledger,
bool haveCorrectLCL);
/** Accept a new ledger based on the given transactions.
@ref onAccept
*/
void
doAccept(
Result const& result,
RCLCxLedger const& prevLedger,
NetClock::duration closeResolution,
CloseTimes const& rawCloseTimes,
Mode const& mode);
/** Build the new last closed ledger. /** Build the new last closed ledger.
@@ -323,7 +334,6 @@ private:
@param closeTime The the ledger closed @param closeTime The the ledger closed
@param closeTimeCorrect Whether consensus agreed on close time @param closeTimeCorrect Whether consensus agreed on close time
@param closeResolution Resolution used to determine consensus close time @param closeResolution Resolution used to determine consensus close time
@param now Current network adjusted time
@param roundTime Duration of this consensus rorund @param roundTime Duration of this consensus rorund
@param retriableTxs Populate with transactions to retry in next round @param retriableTxs Populate with transactions to retry in next round
@return The newly built ledger @return The newly built ledger
@@ -335,15 +345,12 @@ private:
NetClock::time_point closeTime, NetClock::time_point closeTime,
bool closeTimeCorrect, bool closeTimeCorrect,
NetClock::duration closeResolution, NetClock::duration closeResolution,
NetClock::time_point now,
std::chrono::milliseconds roundTime, std::chrono::milliseconds roundTime,
CanonicalTXSet & retriableTxs CanonicalTXSet& retriableTxs);
);
/** Validate the given ledger and share with peers as necessary /** Validate the given ledger and share with peers as necessary
@param ledger The ledger to validate @param ledger The ledger to validate
@param now Current network adjusted time
@param proposing Whether we were proposing transactions while generating @param proposing Whether we were proposing transactions while generating
this ledger. If we are not proposing, a validation this ledger. If we are not proposing, a validation
can still be sent to inform peers that we know we can still be sent to inform peers that we know we
@@ -351,10 +358,7 @@ private:
around and trying to catch up. around and trying to catch up.
*/ */
void void
validate( validate(RCLCxLedger const& ledger, bool proposing);
RCLCxLedger const & ledger,
NetClock::time_point now,
bool proposing);
//!------------------------------------------------------------------------- //!-------------------------------------------------------------------------
Application& app_; Application& app_;
@@ -376,8 +380,10 @@ private:
using PeerPositions = hash_map<NodeID, std::deque<RCLCxPeerPos::pointer>>; using PeerPositions = hash_map<NodeID, std::deque<RCLCxPeerPos::pointer>>;
PeerPositions peerPositions_; PeerPositions peerPositions_;
std::mutex peerPositionsLock_; std::mutex peerPositionsLock_;
};
bool validating_ = false;
bool simulating_ = false;
};
} }
#endif #endif

View File

@@ -21,8 +21,8 @@
#define RIPPLE_APP_CONSENSUS_RCLCXLEDGER_H_INCLUDED #define RIPPLE_APP_CONSENSUS_RCLCXLEDGER_H_INCLUDED
#include <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/ledger/ReadView.h>
#include <ripple/app/ledger/LedgerToJson.h> #include <ripple/app/ledger/LedgerToJson.h>
#include <ripple/ledger/ReadView.h>
#include <ripple/protocol/RippleLedgerHash.h> #include <ripple/protocol/RippleLedgerHash.h>
#include <memory> #include <memory>
@@ -50,7 +50,9 @@ public:
@param l The ledger to wrap. @param l The ledger to wrap.
*/ */
RCLCxLedger(std::shared_ptr<Ledger const> const & l) : ledger_{ l } {} RCLCxLedger(std::shared_ptr<Ledger const> const& l) : ledger_{l}
{
}
//! Sequence number of the ledger. //! Sequence number of the ledger.
auto const& auto const&
@@ -114,8 +116,6 @@ public:
a new ledger from a readView? a new ledger from a readView?
*/ */
std::shared_ptr<Ledger const> ledger_; std::shared_ptr<Ledger const> ledger_;
}; };
} }
#endif #endif

View File

@@ -19,11 +19,11 @@
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/consensus/RCLCxPeerPos.h> #include <ripple/app/consensus/RCLCxPeerPos.h>
#include <ripple/protocol/digest.h>
#include <ripple/core/Config.h> #include <ripple/core/Config.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/HashPrefix.h> #include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/Serializer.h> #include <ripple/protocol/Serializer.h>
#include <ripple/protocol/digest.h>
namespace ripple { namespace ripple {
@@ -38,9 +38,9 @@ RCLCxPeerPos::RCLCxPeerPos (
, publicKey_{publicKey} , publicKey_{publicKey}
, signature_{signature} , signature_{signature}
{ {
} }
uint256 RCLCxPeerPos::getSigningHash () const uint256
RCLCxPeerPos::getSigningHash() const
{ {
return sha512Half( return sha512Half(
HashPrefix::proposal, HashPrefix::proposal,
@@ -50,28 +50,25 @@ uint256 RCLCxPeerPos::getSigningHash () const
proposal().position()); proposal().position());
} }
bool RCLCxPeerPos::checkSign () const bool
RCLCxPeerPos::checkSign() const
{ {
return verifyDigest ( return verifyDigest(publicKey_, getSigningHash(), signature_, false);
publicKey_,
getSigningHash(),
signature_,
false);
} }
Json::Value RCLCxPeerPos::getJson () const Json::Value
RCLCxPeerPos::getJson() const
{ {
auto ret = proposal().getJson(); auto ret = proposal().getJson();
if (publicKey_.size()) if (publicKey_.size())
ret[jss::peer_id] = toBase58 ( ret[jss::peer_id] = toBase58(TokenType::TOKEN_NODE_PUBLIC, publicKey_);
TokenType::TOKEN_NODE_PUBLIC,
publicKey_);
return ret; return ret;
} }
uint256 proposalUniqueId ( uint256
proposalUniqueId(
uint256 const& proposeHash, uint256 const& proposeHash,
uint256 const& previousLedger, uint256 const& previousLedger,
std::uint32_t proposeSeq, std::uint32_t proposeSeq,

View File

@@ -22,12 +22,12 @@
#include <ripple/basics/CountedObject.h> #include <ripple/basics/CountedObject.h>
#include <ripple/basics/base_uint.h> #include <ripple/basics/base_uint.h>
#include <ripple/beast/hash/hash_append.h>
#include <ripple/consensus/ConsensusProposal.h>
#include <ripple/json/json_value.h> #include <ripple/json/json_value.h>
#include <ripple/protocol/HashPrefix.h> #include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/PublicKey.h> #include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SecretKey.h> #include <ripple/protocol/SecretKey.h>
#include <ripple/beast/hash/hash_append.h>
#include <ripple/consensus/ConsensusProposal.h>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
@@ -38,18 +38,20 @@ namespace ripple {
Carries a ConsensusProposal signed by a peer. Carries a ConsensusProposal signed by a peer.
*/ */
class RCLCxPeerPos class RCLCxPeerPos : public CountedObject<RCLCxPeerPos>
: public CountedObject <RCLCxPeerPos>
{ {
public: public:
static char const* getCountedObjectName () { return "RCLCxPeerPos"; } static char const*
getCountedObjectName()
{
return "RCLCxPeerPos";
}
using pointer = std::shared_ptr<RCLCxPeerPos>; using pointer = std::shared_ptr<RCLCxPeerPos>;
using ref = const pointer&; using ref = const pointer&;
//< The type of the proposed position //< The type of the proposed position
using Proposal = ConsensusProposal<NodeID, uint256, uint256>; using Proposal = ConsensusProposal<NodeID, uint256, uint256>;
/** Constructor /** Constructor
Constructs a signed peer position. Constructs a signed peer position.
@@ -67,31 +69,37 @@ public:
Proposal&& proposal); Proposal&& proposal);
//! Create the signing hash for the proposal //! Create the signing hash for the proposal
uint256 getSigningHash () const; uint256
getSigningHash() const;
//! Verify the signing hash of the proposal //! Verify the signing hash of the proposal
bool checkSign () const; bool
checkSign() const;
//! Signature of the proposal (not necessarily verified) //! Signature of the proposal (not necessarily verified)
Slice getSignature () const Slice
getSignature() const
{ {
return signature_; return signature_;
} }
//! Public key of peer that sent the proposal //! Public key of peer that sent the proposal
PublicKey const& getPublicKey () const PublicKey const&
getPublicKey() const
{ {
return publicKey_; return publicKey_;
} }
//! ????? //! ?????
uint256 const& getSuppressionID () const uint256 const&
getSuppressionID() const
{ {
return mSuppression; return mSuppression;
} }
//! The consensus proposal //! The consensus proposal
Proposal const & proposal() const Proposal const&
proposal() const
{ {
return proposal_; return proposal_;
} }
@@ -105,7 +113,8 @@ public:
/// @endcond /// @endcond
//! JSON representation of proposal //! JSON representation of proposal
Json::Value getJson () const; Json::Value
getJson() const;
private: private:
template <class Hasher> template <class Hasher>
@@ -142,7 +151,8 @@ private:
@param publicKey Signer's public key @param publicKey Signer's public key
@param signature Proposal signature @param signature Proposal signature
*/ */
uint256 proposalUniqueId ( uint256
proposalUniqueId(
uint256 const& proposeHash, uint256 const& proposeHash,
uint256 const& previousLedger, uint256 const& previousLedger,
std::uint32_t proposeSeq, std::uint32_t proposeSeq,

View File

@@ -20,10 +20,10 @@
#ifndef RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED #ifndef RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED
#define RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED #define RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED
#include <ripple/app/misc/CanonicalTXSet.h>
#include <ripple/basics/chrono.h> #include <ripple/basics/chrono.h>
#include <ripple/protocol/UintTypes.h> #include <ripple/protocol/UintTypes.h>
#include <ripple/shamap/SHAMap.h> #include <ripple/shamap/SHAMap.h>
#include <ripple/app/misc/CanonicalTXSet.h>
namespace ripple { namespace ripple {
@@ -43,7 +43,8 @@ public:
@param txn The transaction to wrap @param txn The transaction to wrap
*/ */
RCLCxTx(SHAMapItem const& txn) : tx_{txn} RCLCxTx(SHAMapItem const& txn) : tx_{txn}
{ } {
}
//! The unique identifier/hash of the transaction //! The unique identifier/hash of the transaction
ID const& ID const&
@@ -77,10 +78,8 @@ public:
std::shared_ptr<SHAMap> map_; std::shared_ptr<SHAMap> map_;
public: public:
MutableTxSet(RCLTxSet const & src) MutableTxSet(RCLTxSet const& src) : map_{src.map_->snapShot(true)}
: map_{ src.map_->snapShot(true) }
{ {
} }
/** Insert a new transaction into the set. /** Insert a new transaction into the set.
@@ -92,8 +91,7 @@ public:
insert(Tx const& t) insert(Tx const& t)
{ {
return map_->addItem( return map_->addItem(
SHAMapItem{ t.id(), t.tx_.peekData() }, SHAMapItem{t.id(), t.tx_.peekData()}, true, false);
true, false);
} }
/** Remove a transaction from the set. /** Remove a transaction from the set.
@@ -112,8 +110,7 @@ public:
@param m SHAMap to wrap @param m SHAMap to wrap
*/ */
RCLTxSet (std::shared_ptr<SHAMap> m) RCLTxSet(std::shared_ptr<SHAMap> m) : map_{std::move(m)}
: map_{ std::move(m) }
{ {
assert(map_); assert(map_);
} }
@@ -122,10 +119,8 @@ public:
@param m MutableTxSet that will become fixed @param m MutableTxSet that will become fixed
*/ */
RCLTxSet(MutableTxSet const & m) RCLTxSet(MutableTxSet const& m) : map_{m.map_->snapShot(false)}
: map_{m.map_->snapShot(false)}
{ {
} }
/** Test if a transaction is in the set. /** Test if a transaction is in the set.
@@ -163,7 +158,8 @@ public:
return map_->getHash().as_uint256(); return map_->getHash().as_uint256();
} }
/** Find transactions not in common between this and another transaction set. /** Find transactions not in common between this and another transaction
set.
@param j The set to compare with @param j The set to compare with
@return Map of transactions in this set and `j` but not both. The key @return Map of transactions in this set and `j` but not both. The key
@@ -182,7 +178,8 @@ public:
std::map<uint256, bool> ret; std::map<uint256, bool> ret;
for (auto const& item : delta) for (auto const& item : delta)
{ {
assert ( (item.second.first && ! item.second.second) || assert(
(item.second.first && !item.second.second) ||
(item.second.second && !item.second.first)); (item.second.second && !item.second.first));
ret[item.first] = static_cast<bool>(item.second.first); ret[item.first] = static_cast<bool>(item.second.first);
@@ -193,6 +190,5 @@ public:
//! The SHAMap representing the transactions. //! The SHAMap representing the transactions.
std::shared_ptr<SHAMap> map_; std::shared_ptr<SHAMap> map_;
}; };
} }
#endif #endif

View File

@@ -322,7 +322,7 @@ private:
public: public:
bool beginConsensus (uint256 const& networkClosed) override; bool beginConsensus (uint256 const& networkClosed) override;
void endConsensus (bool correctLCL) override; void endConsensus () override;
void setStandAlone () override void setStandAlone () override
{ {
setMode (omFULL); setMode (omFULL);
@@ -1525,7 +1525,7 @@ bool NetworkOPsImp::beginConsensus (uint256 const& networkClosed)
uint256 NetworkOPsImp::getConsensusLCL () uint256 NetworkOPsImp::getConsensusLCL ()
{ {
return mConsensus->LCL (); return mConsensus->prevLedgerID ();
} }
void NetworkOPsImp::processTrustedProposal ( void NetworkOPsImp::processTrustedProposal (
@@ -1565,7 +1565,7 @@ NetworkOPsImp::mapComplete (
RCLTxSet{map}); RCLTxSet{map});
} }
void NetworkOPsImp::endConsensus (bool correctLCL) void NetworkOPsImp::endConsensus ()
{ {
uint256 deadLedger = m_ledgerMaster.getClosedLedger ()->info().parentHash; uint256 deadLedger = m_ledgerMaster.getClosedLedger ()->info().parentHash;
@@ -2164,18 +2164,18 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin)
info[jss::peers] = Json::UInt (app_.overlay ().size ()); info[jss::peers] = Json::UInt (app_.overlay ().size ());
Json::Value lastClose = Json::objectValue; Json::Value lastClose = Json::objectValue;
lastClose[jss::proposers] = mConsensus->getLastCloseProposers(); lastClose[jss::proposers] = Json::UInt(mConsensus->prevProposers());
if (human) if (human)
{ {
lastClose[jss::converge_time_s] = lastClose[jss::converge_time_s] =
std::chrono::duration<double>{ std::chrono::duration<double>{
mConsensus->getLastConvergeDuration()}.count(); mConsensus->prevRoundTime()}.count();
} }
else else
{ {
lastClose[jss::converge_time] = lastClose[jss::converge_time] =
Json::Int (mConsensus->getLastConvergeDuration().count()); Json::Int (mConsensus->prevRoundTime().count());
} }
info[jss::last_close] = lastClose; info[jss::last_close] = lastClose;
@@ -2754,9 +2754,7 @@ std::uint32_t NetworkOPsImp::acceptLedger (
// FIXME Could we improve on this and remove the need for a specialized // FIXME Could we improve on this and remove the need for a specialized
// API in Consensus? // API in Consensus?
beginConsensus (m_ledgerMaster.getClosedLedger()->info().hash); beginConsensus (m_ledgerMaster.getClosedLedger()->info().hash);
mConsensus->simulate ( mConsensus->simulate (app_.timeKeeper().closeTime(), consensusDelay);
app_.timeKeeper().closeTime(),
consensusDelay);
return m_ledgerMaster.getCurrentLedger ()->info().seq; return m_ledgerMaster.getCurrentLedger ()->info().seq;
} }

View File

@@ -162,7 +162,7 @@ public:
// network state machine // network state machine
virtual bool beginConsensus (uint256 const& netLCL) = 0; virtual bool beginConsensus (uint256 const& netLCL) = 0;
virtual void endConsensus (bool correctLCL) = 0; virtual void endConsensus () = 0;
virtual void setStandAlone () = 0; virtual void setStandAlone () = 0;
virtual void setStateTimer () = 0; virtual void setStateTimer () = 0;

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/* /*
This file is part of rippled: https://github.com/ripple/rippled This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2016 Ripple Labs Inc. Copyright (c) 2012-2017 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@@ -19,13 +19,12 @@
#ifndef RIPPLE_CONSENSUS_ConsensusProposal_H_INCLUDED #ifndef RIPPLE_CONSENSUS_ConsensusProposal_H_INCLUDED
#define RIPPLE_CONSENSUS_ConsensusProposal_H_INCLUDED #define RIPPLE_CONSENSUS_ConsensusProposal_H_INCLUDED
#include <cstdint> #include <ripple/basics/chrono.h>
#include <ripple/json/json_value.h> #include <ripple/json/json_value.h>
#include <ripple/protocol/JsonFields.h> #include <ripple/protocol/JsonFields.h>
#include <ripple/basics/chrono.h> #include <cstdint>
namespace ripple namespace ripple {
{
/** Represents a proposed position taken during a round of consensus. /** Represents a proposed position taken during a round of consensus.
During consensus, peers seek agreement on a set of transactions to During consensus, peers seek agreement on a set of transactions to
@@ -49,10 +48,7 @@ namespace ripple
@tparam Position_t Type used to represent the position taken on transactions @tparam Position_t Type used to represent the position taken on transactions
under consideration during this round of consensus under consideration during this round of consensus
*/ */
template < template <class NodeID_t, class LedgerID_t, class Position_t>
class NodeID_t,
class LedgerID_t,
class Position_t>
class ConsensusProposal class ConsensusProposal
{ {
public: public:
@@ -64,7 +60,6 @@ public:
//< Sequence number when a peer wants to bow out and leave consensus //< Sequence number when a peer wants to bow out and leave consensus
static std::uint32_t const seqLeave = 0xffffffff; static std::uint32_t const seqLeave = 0xffffffff;
/** Constructor /** Constructor
@param prevLedger The previous ledger this proposal is building on. @param prevLedger The previous ledger this proposal is building on.
@@ -88,7 +83,6 @@ public:
, proposeSeq_(seq) , proposeSeq_(seq)
, nodeID_(nodeID) , nodeID_(nodeID)
{ {
} }
//! Identifying which peer took this position. //! Identifying which peer took this position.
@@ -163,29 +157,23 @@ public:
} }
/** Update the position during the consensus process. This will increment /** Update the position during the consensus process. This will increment
the proposal's sequence number. the proposal's sequence number if it has not already bowed out.
@param newPosition The new position taken. @param newPosition The new position taken.
@param newCloseTime The new close time. @param newCloseTime The new close time.
@param now the time The new position was taken. @param now the time The new position was taken
@return `true` if the position was updated or `false` if this node has
already left this consensus round.
*/ */
bool void
changePosition( changePosition(
Position_t const& newPosition, Position_t const& newPosition,
NetClock::time_point newCloseTime, NetClock::time_point newCloseTime,
NetClock::time_point now) NetClock::time_point now)
{ {
if (proposeSeq_ == seqLeave)
return false;
position_ = newPosition; position_ = newPosition;
closeTime_ = newCloseTime; closeTime_ = newCloseTime;
time_ = now; time_ = now;
if (proposeSeq_ != seqLeave)
++proposeSeq_; ++proposeSeq_;
return true;
} }
/** Leave consensus /** Leave consensus
@@ -216,13 +204,13 @@ public:
ret[jss::propose_seq] = proposeSeq(); ret[jss::propose_seq] = proposeSeq();
} }
ret[jss::close_time] = to_string(closeTime().time_since_epoch().count()); ret[jss::close_time] =
to_string(closeTime().time_since_epoch().count());
return ret; return ret;
} }
private: private:
//! Unique identifier of prior ledger this proposal is based on //! Unique identifier of prior ledger this proposal is based on
LedgerID_t previousLedger_; LedgerID_t previousLedger_;
@@ -240,23 +228,17 @@ private:
//! The identifier of the node taking this position //! The identifier of the node taking this position
NodeID_t nodeID_; NodeID_t nodeID_;
}; };
template <class NodeID_t, template <class NodeID_t, class LedgerID_t, class Position_t>
class LedgerID_t,
class Position_t>
bool bool
operator==(ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const & a, operator==(
ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const& a,
ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const& b) ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const& b)
{ {
return a.nodeID() == b.nodeID() && return a.nodeID() == b.nodeID() && a.proposeSeq() == b.proposeSeq() &&
a.proposeSeq() == b.proposeSeq() && a.prevLedger() == b.prevLedger() && a.position() == b.position() &&
a.prevLedger() == b.prevLedger() && a.closeTime() == b.closeTime() && a.seenTime() == b.seenTime();
a.position() == b.position() &&
a.closeTime() == b.closeTime() &&
a.seenTime() == b.seenTime();
} }
} }
#endif #endif

View File

@@ -17,15 +17,15 @@
*/ */
//============================================================================== //==============================================================================
#ifndef RIPPLE_APP_CONSENSUS_DISPUTEDTX_H_INCLUDED #ifndef RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
#define RIPPLE_APP_CONSENSUS_DISPUTEDTX_H_INCLUDED #define RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
#include <ripple/protocol/UintTypes.h> #include <ripple/basics/Log.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/basics/base_uint.h> #include <ripple/basics/base_uint.h>
#include <ripple/beast/utility/Journal.h> #include <ripple/beast/utility/Journal.h>
#include <ripple/consensus/LedgerTiming.h> #include <ripple/consensus/LedgerTiming.h>
#include <ripple/basics/Log.h> #include <ripple/protocol/Serializer.h>
#include <ripple/protocol/UintTypes.h>
#include <memory> #include <memory>
namespace ripple { namespace ripple {
@@ -48,6 +48,7 @@ template <class Tx_t, class NodeID_t>
class DisputedTx class DisputedTx
{ {
using TxID_t = typename Tx_t::ID; using TxID_t = typename Tx_t::ID;
public: public:
/** Constructor /** Constructor
@@ -55,20 +56,14 @@ public:
@param ourVote Our vote on whether tx should be included @param ourVote Our vote on whether tx should be included
@param j Journal for debugging @param j Journal for debugging
*/ */
DisputedTx (Tx_t const& tx, DisputedTx(Tx_t const& tx, bool ourVote, beast::Journal j)
bool ourVote, : yays_(0), nays_(0), ourVote_(ourVote), tx_(tx), j_(j)
beast::Journal j)
: yays_ (0)
, nays_ (0)
, ourVote_ (ourVote)
, tx_ (tx)
, j_ (j)
{ {
} }
//! The unique id/hash of the disputed transaction. //! The unique id/hash of the disputed transaction.
TxID_t TxID_t const&
const& ID () const ID() const
{ {
return tx_.id(); return tx_.id();
} }
@@ -81,8 +76,8 @@ public:
} }
//! The disputed transaction. //! The disputed transaction.
Tx_t Tx_t const&
const& tx () const tx() const
{ {
return tx_; return tx_;
} }
@@ -138,7 +133,8 @@ private:
// Track a peer's yes/no vote on a particular disputed tx_ // Track a peer's yes/no vote on a particular disputed tx_
template <class Tx_t, class NodeID_t> template <class Tx_t, class NodeID_t>
void DisputedTx<Tx_t, NodeID_t>::setVote (NodeID_t const& peer, bool votesYes) void
DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
{ {
auto res = votes_.insert(std::make_pair(peer, votesYes)); auto res = votes_.insert(std::make_pair(peer, votesYes));
@@ -147,22 +143,19 @@ void DisputedTx<Tx_t, NodeID_t>::setVote (NodeID_t const& peer, bool votesYes)
{ {
if (votesYes) if (votesYes)
{ {
JLOG (j_.debug()) JLOG(j_.debug()) << "Peer " << peer << " votes YES on " << tx_.id();
<< "Peer " << peer << " votes YES on " << tx_.id();
++yays_; ++yays_;
} }
else else
{ {
JLOG (j_.debug()) JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
<< "Peer " << peer << " votes NO on " << tx_.id();
++nays_; ++nays_;
} }
} }
// changes vote to yes // changes vote to yes
else if (votesYes && !res.first->second) else if (votesYes && !res.first->second)
{ {
JLOG (j_.debug()) JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id();
<< "Peer " << peer << " now votes YES on " << tx_.id();
--nays_; --nays_;
++yays_; ++yays_;
res.first->second = true; res.first->second = true;
@@ -170,8 +163,7 @@ void DisputedTx<Tx_t, NodeID_t>::setVote (NodeID_t const& peer, bool votesYes)
// changes vote to no // changes vote to no
else if (!votesYes && res.first->second) else if (!votesYes && res.first->second)
{ {
JLOG (j_.debug()) JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id();
<< "Peer " << peer << " now votes NO on " << tx_.id();
++nays_; ++nays_;
--yays_; --yays_;
res.first->second = false; res.first->second = false;
@@ -180,7 +172,8 @@ void DisputedTx<Tx_t, NodeID_t>::setVote (NodeID_t const& peer, bool votesYes)
// Remove a peer's vote on this disputed transasction // Remove a peer's vote on this disputed transasction
template <class Tx_t, class NodeID_t> template <class Tx_t, class NodeID_t>
void DisputedTx<Tx_t, NodeID_t>::unVote (NodeID_t const& peer) void
DisputedTx<Tx_t, NodeID_t>::unVote(NodeID_t const& peer)
{ {
auto it = votes_.find(peer); auto it = votes_.find(peer);
@@ -196,7 +189,8 @@ void DisputedTx<Tx_t, NodeID_t>::unVote (NodeID_t const& peer)
} }
template <class Tx_t, class NodeID_t> template <class Tx_t, class NodeID_t>
bool DisputedTx<Tx_t, NodeID_t>::updateVote (int percentTime, bool proposing) bool
DisputedTx<Tx_t, NodeID_t>::updateVote(int percentTime, bool proposing)
{ {
if (ourVote_ && (nays_ == 0)) if (ourVote_ && (nays_ == 0))
return false; return false;
@@ -236,23 +230,23 @@ bool DisputedTx<Tx_t, NodeID_t>::updateVote (int percentTime, bool proposing)
if (newPosition == ourVote_) if (newPosition == ourVote_)
{ {
JLOG (j_.info()) JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO")
<< "No change (" << (ourVote_ ? "YES" : "NO") << ") : weight " << ") : weight " << weight << ", percent "
<< weight << ", percent " << percentTime; << percentTime;
JLOG(j_.debug()) << getJson(); JLOG(j_.debug()) << getJson();
return false; return false;
} }
ourVote_ = newPosition; ourVote_ = newPosition;
JLOG (j_.debug()) JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
<< "We now vote " << (ourVote_ ? "YES" : "NO") << tx_.id();
<< " on " << tx_.id();
JLOG(j_.debug()) << getJson(); JLOG(j_.debug()) << getJson();
return true; return true;
} }
template <class Tx_t, class NodeID_t> template <class Tx_t, class NodeID_t>
Json::Value DisputedTx<Tx_t, NodeID_t>::getJson () const Json::Value
DisputedTx<Tx_t, NodeID_t>::getJson() const
{ {
using std::to_string; using std::to_string;

View File

@@ -18,8 +18,8 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/consensus/LedgerTiming.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/consensus/LedgerTiming.h>
#include <algorithm> #include <algorithm>
#include <iterator> #include <iterator>
@@ -28,29 +28,29 @@ namespace ripple {
bool bool
shouldCloseLedger( shouldCloseLedger(
bool anyTransactions, bool anyTransactions,
std::size_t previousProposers, std::size_t prevProposers,
std::size_t proposersClosed, std::size_t proposersClosed,
std::size_t proposersValidated, std::size_t proposersValidated,
std::chrono::milliseconds previousTime, std::chrono::milliseconds prevRoundTime,
std::chrono::milliseconds currentTime, // Time since last ledger's close time std::chrono::milliseconds
timeSincePrevClose, // Time since last ledger's close time
std::chrono::milliseconds openTime, // Time waiting to close this ledger std::chrono::milliseconds openTime, // Time waiting to close this ledger
std::chrono::seconds idleInterval, std::chrono::seconds idleInterval,
beast::Journal j) beast::Journal j)
{ {
using namespace std::chrono_literals; using namespace std::chrono_literals;
if ((previousTime < -1s) || (previousTime > 10min) || if ((prevRoundTime < -1s) || (prevRoundTime > 10min) || (timeSincePrevClose > 10min))
(currentTime > 10min))
{ {
// These are unexpected cases, we just close the ledger // These are unexpected cases, we just close the ledger
JLOG (j.warn()) << JLOG(j.warn()) << "shouldCloseLedger Trans="
"shouldCloseLedger Trans=" << (anyTransactions ? "yes" : "no") << << (anyTransactions ? "yes" : "no")
" Prop: " << previousProposers << "/" << proposersClosed << << " Prop: " << prevProposers << "/" << proposersClosed
" Secs: " << currentTime.count() << " (last: " << << " Secs: " << timeSincePrevClose.count()
previousTime.count() << ")"; << " (last: " << prevRoundTime.count() << ")";
return true; return true;
} }
if ((proposersClosed + proposersValidated) > (previousProposers / 2)) if ((proposersClosed + proposersValidated) > (prevProposers / 2))
{ {
// If more than half of the network has closed, we close // If more than half of the network has closed, we close
JLOG(j.trace()) << "Others have closed"; JLOG(j.trace()) << "Others have closed";
@@ -60,24 +60,22 @@ shouldCloseLedger (
if (!anyTransactions) if (!anyTransactions)
{ {
// Only close at the end of the idle interval // Only close at the end of the idle interval
return currentTime >= idleInterval; // normal idle return timeSincePrevClose >= idleInterval; // normal idle
} }
// Preserve minimum ledger open time // Preserve minimum ledger open time
if (openTime < LEDGER_MIN_CLOSE) if (openTime < LEDGER_MIN_CLOSE)
{ {
JLOG (j.debug()) << JLOG(j.debug()) << "Must wait minimum time before closing";
"Must wait minimum time before closing";
return false; return false;
} }
// Don't let this ledger close more than twice as fast as the previous // Don't let this ledger close more than twice as fast as the previous
// ledger reached consensus so that slower validators can slow down // ledger reached consensus so that slower validators can slow down
// the network // the network
if (openTime < (previousTime / 2)) if (openTime < (prevRoundTime / 2))
{ {
JLOG (j.debug()) << JLOG(j.debug()) << "Ledger has not been open long enough";
"Ledger has not been open long enough";
return false; return false;
} }
@@ -86,10 +84,7 @@ shouldCloseLedger (
} }
bool bool
checkConsensusReached ( checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self)
std::size_t agreeing,
std::size_t total,
bool count_self)
{ {
// If we are alone, we have a consensus // If we are alone, we have a consensus
if (total == 0) if (total == 0)
@@ -108,7 +103,7 @@ checkConsensusReached (
ConsensusState ConsensusState
checkConsensus( checkConsensus(
std::size_t previousProposers, std::size_t prevProposers,
std::size_t currentProposers, std::size_t currentProposers,
std::size_t currentAgree, std::size_t currentAgree,
std::size_t currentFinished, std::size_t currentFinished,
@@ -117,23 +112,22 @@ checkConsensus (
bool proposing, bool proposing,
beast::Journal j) beast::Journal j)
{ {
JLOG (j.trace()) << JLOG(j.trace()) << "checkConsensus: prop=" << currentProposers << "/"
"checkConsensus: prop=" << currentProposers << << prevProposers << " agree=" << currentAgree
"/" << previousProposers << << " validated=" << currentFinished
" agree=" << currentAgree << " validated=" << currentFinished << << " time=" << currentAgreeTime.count() << "/"
" time=" << currentAgreeTime.count() << "/" << previousAgreeTime.count(); << previousAgreeTime.count();
if (currentAgreeTime <= LEDGER_MIN_CONSENSUS) if (currentAgreeTime <= LEDGER_MIN_CONSENSUS)
return ConsensusState::No; return ConsensusState::No;
if (currentProposers < (previousProposers * 3 / 4)) if (currentProposers < (prevProposers * 3 / 4))
{ {
// Less than 3/4 of the last ledger's proposers are present; don't // Less than 3/4 of the last ledger's proposers are present; don't
// rush: we may need more time. // rush: we may need more time.
if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS)) if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS))
{ {
JLOG (j.trace()) << JLOG(j.trace()) << "too fast, not enough proposers";
"too fast, not enough proposers";
return ConsensusState::No; return ConsensusState::No;
} }
} }
@@ -150,8 +144,7 @@ checkConsensus (
// to declare consensus? // to declare consensus?
if (checkConsensusReached(currentFinished, currentProposers, false)) if (checkConsensusReached(currentFinished, currentProposers, false))
{ {
JLOG (j.warn()) << JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on";
"We see no consensus, but 80% of nodes have moved on";
return ConsensusState::MovedOn; return ConsensusState::MovedOn;
} }

View File

@@ -20,14 +20,13 @@
#ifndef RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED #ifndef RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
#define RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
#include <chrono>
#include <cstdint>
#include <ripple/basics/chrono.h> #include <ripple/basics/chrono.h>
#include <ripple/beast/utility/Journal.h> #include <ripple/beast/utility/Journal.h>
#include <chrono>
#include <cstdint>
namespace ripple { namespace ripple {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// These are protocol parameters used to control the behavior of the system and // These are protocol parameters used to control the behavior of the system and
// they should not be changed arbitrarily. // they should not be changed arbitrarily.
@@ -44,14 +43,8 @@ using namespace std::chrono_literals;
std::chrono::seconds constexpr ledgerPossibleTimeResolutions[] = std::chrono::seconds constexpr ledgerPossibleTimeResolutions[] =
{10s, 20s, 30s, 60s, 90s, 120s}; {10s, 20s, 30s, 60s, 90s, 120s};
#ifndef _MSC_VER
//! Initial resolution of ledger close time. //! Initial resolution of ledger close time.
auto constexpr ledgerDefaultTimeResolution = ledgerPossibleTimeResolutions[2]; auto constexpr ledgerDefaultTimeResolution = ledgerPossibleTimeResolutions[2];
#else
// HH Remove this workaround of a VS bug when possible
//! Initial resolution of ledger close time.
auto constexpr ledgerDefaultTimeResolution = 30s;
#endif
//! How often we increase the close time resolution (in numbers of ledgers) //! How often we increase the close time resolution (in numbers of ledgers)
auto constexpr increaseLedgerTimeResolutionEvery = 8; auto constexpr increaseLedgerTimeResolutionEvery = 8;
@@ -165,8 +158,10 @@ getNextLedgerTimeResolution(
using namespace std::chrono; using namespace std::chrono;
// Find the current resolution: // Find the current resolution:
auto iter = std::find (std::begin (ledgerPossibleTimeResolutions), auto iter = std::find(
std::end (ledgerPossibleTimeResolutions), previousResolution); std::begin(ledgerPossibleTimeResolutions),
std::end(ledgerPossibleTimeResolutions),
previousResolution);
assert(iter != std::end(ledgerPossibleTimeResolutions)); assert(iter != std::end(ledgerPossibleTimeResolutions));
// This should never happen, but just as a precaution // This should never happen, but just as a precaution
@@ -212,7 +207,6 @@ roundCloseTime(
return closeTime - (closeTime.time_since_epoch() % closeResolution); return closeTime - (closeTime.time_since_epoch() % closeResolution);
} }
/** Calculate the effective ledger close time /** Calculate the effective ledger close time
After adjusting the ledger close time based on the current resolution, also After adjusting the ledger close time based on the current resolution, also
@@ -223,7 +217,9 @@ roundCloseTime(
@param priorCloseTime The close time of the prior ledger @param priorCloseTime The close time of the prior ledger
*/ */
template <class time_point> template <class time_point>
time_point effectiveCloseTime(time_point closeTime, time_point
effCloseTime(
time_point closeTime,
typename time_point::duration const resolution, typename time_point::duration const resolution,
time_point priorCloseTime) time_point priorCloseTime)
{ {
@@ -231,8 +227,7 @@ time_point effectiveCloseTime(time_point closeTime,
return closeTime; return closeTime;
return std::max<time_point>( return std::max<time_point>(
roundCloseTime (closeTime, resolution), roundCloseTime(closeTime, resolution), (priorCloseTime + 1s));
(priorCloseTime + 1s));
} }
/** Determines whether the current ledger should close at this time. /** Determines whether the current ledger should close at this time.
@@ -241,30 +236,29 @@ time_point effectiveCloseTime(time_point closeTime,
in progress, or when a transaction is received and no close is in progress. in progress, or when a transaction is received and no close is in progress.
@param anyTransactions indicates whether any transactions have been received @param anyTransactions indicates whether any transactions have been received
@param previousProposers proposers in the last closing @param prevProposers proposers in the last closing
@param proposersClosed proposers who have currently closed this ledger @param proposersClosed proposers who have currently closed this ledger
@param proposersValidated proposers who have validated the last closed @param proposersValidated proposers who have validated the last closed
ledger ledger
@param previousTime time for the previous ledger to reach consensus @param prevRoundTime time for the previous ledger to reach consensus
@param currentTime time since the previous ledger's @param timeSincePrevClose time since the previous ledger's (possibly rounded)
(possibly rounded) close time close time
@param openTime time waiting to close this ledger @param openTime duration this ledger has been open
@param idleInterval the network's desired idle interval @param idleInterval the network's desired idle interval
@param j journal for logging @param j journal for logging
*/ */
bool bool
shouldCloseLedger( shouldCloseLedger(
bool anyTransactions, bool anyTransactions,
std::size_t previousProposers, std::size_t prevProposers,
std::size_t proposersClosed, std::size_t proposersClosed,
std::size_t proposersValidated, std::size_t proposersValidated,
std::chrono::milliseconds previousTime, std::chrono::milliseconds prevRoundTime,
std::chrono::milliseconds currentTime, // Time since last ledger's close time std::chrono::milliseconds timeSincePrevClose,
std::chrono::milliseconds openTime, // Time waiting to close this ledger std::chrono::milliseconds openTime,
std::chrono::seconds idleInterval, std::chrono::seconds idleInterval,
beast::Journal j); beast::Journal j);
/** Determine if a consensus has been reached /** Determine if a consensus has been reached
This function determines if a consensus has been reached This function determines if a consensus has been reached
@@ -275,14 +269,10 @@ shouldCloseLedger (
@return True if a consensus has been reached @return True if a consensus has been reached
*/ */
bool bool
checkConsensusReached ( checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self);
std::size_t agreeing,
std::size_t total,
bool count_self);
/** Whether we have or don't have a consensus */ /** Whether we have or don't have a consensus */
enum class ConsensusState enum class ConsensusState {
{
No, //!< We do not have consensus No, //!< We do not have consensus
MovedOn, //!< The network has consensus without us MovedOn, //!< The network has consensus without us
Yes //!< We have consensus along with the network Yes //!< We have consensus along with the network
@@ -290,7 +280,7 @@ enum class ConsensusState
/** Determine whether the network reached consensus and whether we joined. /** Determine whether the network reached consensus and whether we joined.
@param previousProposers proposers in the last closing (not including us) @param prevProposers proposers in the last closing (not including us)
@param currentProposers proposers in this closing so far (not including us) @param currentProposers proposers in this closing so far (not including us)
@param currentAgree proposers who agree with us @param currentAgree proposers who agree with us
@param currentFinished proposers who have validated a ledger after this one @param currentFinished proposers who have validated a ledger after this one
@@ -303,7 +293,7 @@ enum class ConsensusState
*/ */
ConsensusState ConsensusState
checkConsensus( checkConsensus(
std::size_t previousProposers, std::size_t prevProposers,
std::size_t currentProposers, std::size_t currentProposers,
std::size_t currentAgree, std::size_t currentAgree,
std::size_t currentFinished, std::size_t currentFinished,

View File

@@ -17,22 +17,20 @@
*/ */
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/beast/clock/manual_clock.h>
#include <ripple/beast/unit_test.h> #include <ripple/beast/unit_test.h>
#include <ripple/consensus/Consensus.h> #include <ripple/consensus/Consensus.h>
#include <ripple/consensus/ConsensusProposal.h> #include <ripple/consensus/ConsensusProposal.h>
#include <ripple/beast/clock/manual_clock.h>
#include <boost/function_output_iterator.hpp> #include <boost/function_output_iterator.hpp>
#include <test/csf.h> #include <test/csf.h>
#include <utility> #include <utility>
namespace ripple { namespace ripple {
namespace test { namespace test {
class Consensus_test : public beast::unit_test::suite class Consensus_test : public beast::unit_test::suite
{ {
public: public:
void void
testStandalone() testStandalone()
{ {
@@ -50,12 +48,13 @@ public:
s.net.step(); s.net.step();
// Inspect that the proper ledger was created // Inspect that the proper ledger was created
BEAST_EXPECT(p.LCL().seq == 1); BEAST_EXPECT(p.prevLedgerID().seq == 1);
BEAST_EXPECT(p.LCL() == p.lastClosedLedger.id()); BEAST_EXPECT(p.prevLedgerID() == p.lastClosedLedger.id());
BEAST_EXPECT(p.lastClosedLedger.id().txs.size() == 1); BEAST_EXPECT(p.lastClosedLedger.id().txs.size() == 1);
BEAST_EXPECT(p.lastClosedLedger.id().txs.find(Tx{ 1 }) BEAST_EXPECT(
!= p.lastClosedLedger.id().txs.end()); p.lastClosedLedger.id().txs.find(Tx{1}) !=
BEAST_EXPECT(p.getLastCloseProposers() == 0); p.lastClosedLedger.id().txs.end());
BEAST_EXPECT(p.prevProposers() == 0);
} }
void void
@@ -65,7 +64,8 @@ public:
using namespace std::chrono; using namespace std::chrono;
auto tg = TrustGraph::makeComplete(5); auto tg = TrustGraph::makeComplete(5);
Sim sim(tg, Sim sim(
tg,
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)})); topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
// everyone submits their own ID as a TX and relay it to peers // everyone submits their own ID as a TX and relay it to peers
@@ -76,13 +76,13 @@ public:
sim.run(1); sim.run(1);
for (auto& p : sim.peers) for (auto& p : sim.peers)
{ {
auto const &lgrID = p.LCL(); auto const& lgrID = p.prevLedgerID();
BEAST_EXPECT(lgrID.seq == 1); BEAST_EXPECT(lgrID.seq == 1);
BEAST_EXPECT(p.getLastCloseProposers() == sim.peers.size() - 1); BEAST_EXPECT(p.prevProposers() == sim.peers.size() - 1);
for (std::uint32_t i = 0; i < sim.peers.size(); ++i) for (std::uint32_t i = 0; i < sim.peers.size(); ++i)
BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end()); BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end());
// Matches peer 0 ledger // Matches peer 0 ledger
BEAST_EXPECT(lgrID.txs == sim.peers[0].LCL().txs); BEAST_EXPECT(lgrID.txs == sim.peers[0].prevLedgerID().txs);
} }
} }
@@ -100,15 +100,16 @@ public:
{ {
auto tg = TrustGraph::makeComplete(5); auto tg = TrustGraph::makeComplete(5);
Sim sim(tg, topology(tg,[](PeerID i, PeerID j) Sim sim(tg, topology(tg, [](PeerID i, PeerID j) {
{
auto delayFactor = (i == 0 || j == 0) ? 1.1 : 0.2; auto delayFactor = (i == 0 || j == 0) ? 1.1 : 0.2;
return round<milliseconds>(delayFactor* LEDGER_GRANULARITY); return round<milliseconds>(
delayFactor * LEDGER_GRANULARITY);
})); }));
sim.peers[0].proposing = sim.peers[0].validating = isParticipant; sim.peers[0].proposing_ = sim.peers[0].validating_ = isParticipant;
// All peers submit their own ID as a transaction and relay it to peers // All peers submit their own ID as a transaction and relay it to
// peers
for (auto& p : sim.peers) for (auto& p : sim.peers)
{ {
p.submit(Tx{p.id}); p.submit(Tx{p.id});
@@ -116,49 +117,47 @@ public:
sim.run(1); sim.run(1);
// Verify all peers have same LCL but are missing transaction 0 which // Verify all peers have same LCL but are missing transaction 0
// was not received by all peers before the ledger closed // which was not received by all peers before the ledger closed
for (auto& p : sim.peers) for (auto& p : sim.peers)
{ {
auto const &lgrID = p.LCL(); auto const& lgrID = p.prevLedgerID();
BEAST_EXPECT(lgrID.seq == 1); BEAST_EXPECT(lgrID.seq == 1);
// If peer 0 is participating // If peer 0 is participating
if (isParticipant) if (isParticipant)
{ {
BEAST_EXPECT(p.getLastCloseProposers() BEAST_EXPECT(p.prevProposers() == sim.peers.size() - 1);
== sim.peers.size() - 1); // Peer 0 closes first because it sees a quorum of agreeing
// Peer 0 closes first because it sees a quorum of agreeing positions // positions from all other peers in one hop (1->0, 2->0,
// from all other peers in one hop (1->0, 2->0, ..) // ..) The other peers take an extra timer period before
// The other peers take an extra timer period before they find that // they find that Peer 0 agrees with them ( 1->0->1,
// Peer 0 agrees with them ( 1->0->1, 2->0->2, ...) // 2->0->2, ...)
if (p.id != 0) if (p.id != 0)
BEAST_EXPECT(p.getLastConvergeDuration() BEAST_EXPECT(
> sim.peers[0].getLastConvergeDuration()); p.prevRoundTime() > sim.peers[0].prevRoundTime());
} }
else // peer 0 is not participating else // peer 0 is not participating
{ {
auto const proposers = p.getLastCloseProposers(); auto const proposers = p.prevProposers();
if (p.id == 0) if (p.id == 0)
BEAST_EXPECT(proposers == sim.peers.size() - 1); BEAST_EXPECT(proposers == sim.peers.size() - 1);
else else
BEAST_EXPECT(proposers == sim.peers.size() - 2); BEAST_EXPECT(proposers == sim.peers.size() - 2);
// so all peers should have closed together // so all peers should have closed together
BEAST_EXPECT(p.getLastConvergeDuration() BEAST_EXPECT(
== sim.peers[0].getLastConvergeDuration()); p.prevRoundTime() == sim.peers[0].prevRoundTime());
} }
BEAST_EXPECT(lgrID.txs.find(Tx{0}) == lgrID.txs.end()); BEAST_EXPECT(lgrID.txs.find(Tx{0}) == lgrID.txs.end());
for (std::uint32_t i = 1; i < sim.peers.size(); ++i) for (std::uint32_t i = 1; i < sim.peers.size(); ++i)
BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end()); BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end());
// Matches peer 0 ledger // Matches peer 0 ledger
BEAST_EXPECT(lgrID.txs == sim.peers[0].LCL().txs); BEAST_EXPECT(lgrID.txs == sim.peers[0].prevLedgerID().txs);
} }
BEAST_EXPECT(sim.peers[0].openTxs.find(Tx{ 0 }) BEAST_EXPECT(
!= sim.peers[0].openTxs.end()); sim.peers[0].openTxs.find(Tx{0}) != sim.peers[0].openTxs.end());
} }
} }
@@ -185,17 +184,20 @@ public:
// Complicating this matter is that nodes will ignore proposals // Complicating this matter is that nodes will ignore proposals
// with times more than PROPOSE_FRESHNESS =20s in the past. So at // with times more than PROPOSE_FRESHNESS =20s in the past. So at
// the minimum granularity, we have at most 3 types of skews (0s,10s,20s). // the minimum granularity, we have at most 3 types of skews
// (0s,10s,20s).
// This test therefore has 6 nodes, with 2 nodes having each type of // This test therefore has 6 nodes, with 2 nodes having each type of
// skew. Then no majority (1/3 < 1/2) of nodes will agree on an // skew. Then no majority (1/3 < 1/2) of nodes will agree on an
// actual close time. // actual close time.
auto tg = TrustGraph::makeComplete(6); auto tg = TrustGraph::makeComplete(6);
Sim sim(tg, Sim sim(
tg,
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)})); topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
// Run consensus without skew until we have a short close time resolution // Run consensus without skew until we have a short close time
// resolution
while (sim.peers.front().lastClosedLedger.closeTimeResolution() >= while (sim.peers.front().lastClosedLedger.closeTimeResolution() >=
PROPOSE_FRESHNESS) PROPOSE_FRESHNESS)
sim.run(1); sim.run(1);
@@ -226,7 +228,6 @@ public:
// the wrong LCL at different phases of consensus // the wrong LCL at different phases of consensus
for (auto validationDelay : {0s, LEDGER_MIN_CLOSE}) for (auto validationDelay : {0s, LEDGER_MIN_CLOSE})
{ {
// Consider 10 peers: // Consider 10 peers:
// 0 1 2 3 4 5 6 7 8 9 // 0 1 2 3 4 5 6 7 8 9
// //
@@ -241,7 +242,8 @@ public:
// since nodes 2-4 will validate a different ledger. // since nodes 2-4 will validate a different ledger.
// Nodes 0-1 will acquire the proper ledger from the network and // Nodes 0-1 will acquire the proper ledger from the network and
// resume consensus and eventually generate the dominant network ledger // resume consensus and eventually generate the dominant network
// ledger
std::vector<UNL> unls; std::vector<UNL> unls;
unls.push_back({2, 3, 4, 5, 6, 7, 8, 9}); unls.push_back({2, 3, 4, 5, 6, 7, 8, 9});
@@ -252,7 +254,8 @@ public:
TrustGraph tg{unls, membership}; TrustGraph tg{unls, membership};
// This topology can fork, which is why we are using it for this test. // This topology can fork, which is why we are using it for this
// test.
BEAST_EXPECT(tg.canFork(minimumConsensusPercentage / 100.)); BEAST_EXPECT(tg.canFork(minimumConsensusPercentage / 100.));
auto netDelay = round<milliseconds>(0.2 * LEDGER_GRANULARITY); auto netDelay = round<milliseconds>(0.2 * LEDGER_GRANULARITY);
@@ -261,7 +264,8 @@ public:
// initial round to set prior state // initial round to set prior state
sim.run(1); sim.run(1);
// Nodes in smaller UNL have seen tx 0, nodes in other unl have seen tx 1 // Nodes in smaller UNL have seen tx 0, nodes in other unl have seen
// tx 1
for (auto& p : sim.peers) for (auto& p : sim.peers)
{ {
p.validationDelay = validationDelay; p.validationDelay = validationDelay;
@@ -272,11 +276,24 @@ public:
p.openTxs.insert(Tx{1}); p.openTxs.insert(Tx{1});
} }
// Run for 2 additional rounds // Run for additional rounds
// - One round to generate different ledgers // With no validation delay, only 2 more rounds are needed.
// - One round to detect different prior ledgers (but still generate // 1. Round to generate different ledgers
// wrong ones) and recover // 2. Round to detect different prior ledgers (but still generate
sim.run(2); // wrong ones) and recover within that round since wrong LCL
// is detected before we close
//
// With a validation delay of LEDGER_MIN_CLOSE, we need 3 more
// rounds.
// 1. Round to generate different ledgers
// 2. Round to detect different prior ledgers (but still generate
// wrong ones) but end up declaring consensus on wrong LCL (but
// with the right transaction set!). This is because we detect
// the wrong LCL after we have closed the ledger, so we declare
// consensus based solely on our peer proposals. But we haven't
// had time to acquire the right LCL
// 3. Round to correct
sim.run(3);
bc::flat_map<int, bc::flat_set<Ledger::ID>> ledgers; bc::flat_map<int, bc::flat_set<Ledger::ID>> ledgers;
for (auto& p : sim.peers) for (auto& p : sim.peers)
@@ -289,12 +306,19 @@ public:
BEAST_EXPECT(ledgers[0].size() == 1); BEAST_EXPECT(ledgers[0].size() == 1);
BEAST_EXPECT(ledgers[1].size() == 1); BEAST_EXPECT(ledgers[1].size() == 1);
if (validationDelay == 0s)
{
BEAST_EXPECT(ledgers[2].size() == 2); BEAST_EXPECT(ledgers[2].size() == 2);
BEAST_EXPECT(ledgers[3].size() == 1); BEAST_EXPECT(ledgers[3].size() == 1);
BEAST_EXPECT(ledgers[4].size() == 1);
}
else
{
BEAST_EXPECT(ledgers[2].size() == 2);
BEAST_EXPECT(ledgers[3].size() == 2);
BEAST_EXPECT(ledgers[4].size() == 1);
}
} }
// Additional test engineered to switch LCL during the establish phase. // Additional test engineered to switch LCL during the establish phase.
// This was added to trigger a scenario that previously crashed, in which // This was added to trigger a scenario that previously crashed, in which
// switchLCL switched from establish to open phase, but still processed // switchLCL switched from establish to open phase, but still processed
@@ -330,7 +354,7 @@ public:
// Check all peers recovered // Check all peers recovered
for (auto &p : sim.peers) for (auto &p : sim.peers)
BEAST_EXPECT(p.LCL() == sim.peers[0].LCL()); BEAST_EXPECT(p.prevLedgerID() == sim.peers[0].prevLedgerID());
} }
} }
@@ -344,8 +368,10 @@ public:
for (int overlap = 0; overlap <= numPeers; ++overlap) for (int overlap = 0; overlap <= numPeers; ++overlap)
{ {
auto tg = TrustGraph::makeClique(numPeers, overlap); auto tg = TrustGraph::makeClique(numPeers, overlap);
Sim sim(tg, topology(tg, Sim sim(
fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)})); tg,
topology(
tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
// Initial round to set prior state // Initial round to set prior state
sim.run(1); sim.run(1);
@@ -358,12 +384,11 @@ public:
} }
sim.run(1); sim.run(1);
// See if the network forked // See if the network forked
bc::flat_set<Ledger::ID> ledgers; bc::flat_set<Ledger::ID> ledgers;
for (auto& p : sim.peers) for (auto& p : sim.peers)
{ {
ledgers.insert(p.LCL()); ledgers.insert(p.prevLedgerID());
} }
// Fork should not happen for 40% or greater overlap // Fork should not happen for 40% or greater overlap
@@ -377,7 +402,6 @@ public:
} }
} }
void void
simClockSkew() simClockSkew()
{ {
@@ -396,17 +420,13 @@ public:
// Disabled while continuing to understand testt. // Disabled while continuing to understand testt.
for (auto stagger : {800ms, 1600ms, 3200ms, 30000ms, 45000ms, 300000ms}) for (auto stagger : {800ms, 1600ms, 3200ms, 30000ms, 45000ms, 300000ms})
{ {
auto tg = TrustGraph::makeComplete(5); auto tg = TrustGraph::makeComplete(5);
Sim sim(tg, topology(tg, [](PeerID i, PeerID) Sim sim(tg, topology(tg, [](PeerID i, PeerID) {
{
return 200ms * (i + 1); return 200ms * (i + 1);
})); }));
// all transactions submitted before starting // all transactions submitted before starting
// Initial round to set prior state // Initial round to set prior state
sim.run(1); sim.run(1);
@@ -415,7 +435,6 @@ public:
{ {
p.openTxs.insert(Tx{0}); p.openTxs.insert(Tx{0});
p.targetLedgers = p.completedLedgers + 1; p.targetLedgers = p.completedLedgers + 1;
} }
// stagger start of consensus // stagger start of consensus
@@ -426,11 +445,10 @@ public:
} }
// run until all peers have accepted all transactions // run until all peers have accepted all transactions
sim.net.step_while([&]() sim.net.step_while([&]() {
{
for (auto& p : sim.peers) for (auto& p : sim.peers)
{ {
if(p.LCL().txs.size() != 1) if (p.prevLedgerID().txs.size() != 1)
{ {
return true; return true;
} }
@@ -440,8 +458,6 @@ public:
} }
} }
void void
simScaleFree() simScaleFree()
{ {
@@ -459,12 +475,16 @@ public:
std::mt19937_64 rng; std::mt19937_64 rng;
auto tg = TrustGraph::makeRandomRanked(N, numUNLs, auto tg = TrustGraph::makeRandomRanked(
N,
numUNLs,
PowerLawDistribution{1, 3}, PowerLawDistribution{1, 3},
std::uniform_int_distribution<>{minUNLSize, maxUNLSize}, std::uniform_int_distribution<>{minUNLSize, maxUNLSize},
rng); rng);
Sim sim{tg, topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)})}; Sim sim{
tg,
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)})};
// Initial round to set prior state // Initial round to set prior state
sim.run(1); sim.run(1);
@@ -475,20 +495,17 @@ public:
// 50-50 chance to have seen a transaction // 50-50 chance to have seen a transaction
if (u(rng) >= transProb) if (u(rng) >= transProb)
p.openTxs.insert(Tx{0}); p.openTxs.insert(Tx{0});
} }
sim.run(1); sim.run(1);
// See if the network forked // See if the network forked
bc::flat_set<Ledger::ID> ledgers; bc::flat_set<Ledger::ID> ledgers;
for (auto& p : sim.peers) for (auto& p : sim.peers)
{ {
ledgers.insert(p.LCL()); ledgers.insert(p.prevLedgerID());
} }
BEAST_EXPECT(ledgers.size() == 1); BEAST_EXPECT(ledgers.size() == 1);
} }
void void

View File

@@ -30,11 +30,11 @@
#include <boost/range/adaptor/transformed.hpp> #include <boost/range/adaptor/transformed.hpp>
#include <boost/range/iterator_range.hpp> #include <boost/range/iterator_range.hpp>
#include <boost/tuple/tuple.hpp> #include <boost/tuple/tuple.hpp>
#include <deque>
#include <memory>
#include <cassert> #include <cassert>
#include <cstdint> #include <cstdint>
#include <deque>
#include <iomanip> #include <iomanip>
#include <memory>
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
@@ -98,52 +98,48 @@ class BasicNetwork
public: public:
using peer_type = Peer; using peer_type = Peer;
using clock_type = using clock_type = beast::manual_clock<std::chrono::steady_clock>;
beast::manual_clock<
std::chrono::steady_clock>;
using duration = using duration = typename clock_type::duration;
typename clock_type::duration;
using time_point = using time_point = typename clock_type::time_point;
typename clock_type::time_point;
private: private:
struct by_to_tag {}; struct by_to_tag
struct by_from_tag {}; {
struct by_when_tag {}; };
struct by_from_tag
{
};
struct by_when_tag
{
};
using by_to_hook = using by_to_hook = boost::intrusive::list_base_hook<
boost::intrusive::list_base_hook< boost::intrusive::link_mode<boost::intrusive::normal_link>,
boost::intrusive::link_mode<
boost::intrusive::normal_link>,
boost::intrusive::tag<by_to_tag>>; boost::intrusive::tag<by_to_tag>>;
using by_from_hook = using by_from_hook = boost::intrusive::list_base_hook<
boost::intrusive::list_base_hook< boost::intrusive::link_mode<boost::intrusive::normal_link>,
boost::intrusive::link_mode<
boost::intrusive::normal_link>,
boost::intrusive::tag<by_from_tag>>; boost::intrusive::tag<by_from_tag>>;
using by_when_hook = using by_when_hook = boost::intrusive::set_base_hook<
boost::intrusive::set_base_hook< boost::intrusive::link_mode<boost::intrusive::normal_link>>;
boost::intrusive::link_mode<
boost::intrusive::normal_link>>;
struct msg struct msg : by_to_hook, by_from_hook, by_when_hook
: by_to_hook, by_from_hook, by_when_hook
{ {
Peer to; Peer to;
Peer from; Peer from;
time_point when; time_point when;
msg(msg const&) = delete; msg(msg const&) = delete;
msg& operator= (msg const&) = delete; msg&
operator=(msg const&) = delete;
virtual ~msg() = default; virtual ~msg() = default;
virtual void operator()() const = 0; virtual void
operator()() const = 0;
msg (Peer const& from_, Peer const& to_, msg(Peer const& from_, Peer const& to_, time_point when_)
time_point when_)
: to(to_), from(from_), when(when_) : to(to_), from(from_), when(when_)
{ {
} }
@@ -163,23 +159,29 @@ private:
public: public:
msg_impl(msg_impl const&) = delete; msg_impl(msg_impl const&) = delete;
msg_impl& operator= (msg_impl const&) = delete; msg_impl&
operator=(msg_impl const&) = delete;
msg_impl (Peer const& from_, Peer const& to_, msg_impl(
time_point when_, Handler&& h) Peer const& from_,
: msg (from_, to_, when_) Peer const& to_,
, h_ (std::move(h)) time_point when_,
Handler&& h)
: msg(from_, to_, when_), h_(std::move(h))
{ {
} }
msg_impl (Peer const& from_, Peer const& to_, msg_impl(
time_point when_, Handler const& h) Peer const& from_,
: msg (from_, to_, when_) Peer const& to_,
, h_ (h) time_point when_,
Handler const& h)
: msg(from_, to_, when_), h_(h)
{ {
} }
void operator()() const override void
operator()() const override
{ {
h_(); h_();
} }
@@ -188,18 +190,18 @@ private:
class queue_type class queue_type
{ {
private: private:
using by_to_list = typename using by_to_list = typename boost::intrusive::make_list<
boost::intrusive::make_list<msg, msg,
boost::intrusive::base_hook<by_to_hook>, boost::intrusive::base_hook<by_to_hook>,
boost::intrusive::constant_time_size<false>>::type; boost::intrusive::constant_time_size<false>>::type;
using by_from_list = typename using by_from_list = typename boost::intrusive::make_list<
boost::intrusive::make_list<msg, msg,
boost::intrusive::base_hook<by_from_hook>, boost::intrusive::base_hook<by_from_hook>,
boost::intrusive::constant_time_size<false>>::type; boost::intrusive::constant_time_size<false>>::type;
using by_when_set = typename using by_when_set = typename boost::intrusive::make_multiset<
boost::intrusive::make_multiset<msg, msg,
boost::intrusive::constant_time_size<false>>::type; boost::intrusive::constant_time_size<false>>::type;
qalloc alloc_; qalloc alloc_;
@@ -208,14 +210,13 @@ private:
std::unordered_map<Peer, by_from_list> by_from_; std::unordered_map<Peer, by_from_list> by_from_;
public: public:
using iterator = using iterator = typename by_when_set::iterator;
typename by_when_set::iterator;
queue_type(queue_type const&) = delete; queue_type(queue_type const&) = delete;
queue_type& operator= (queue_type const&) = delete; queue_type&
operator=(queue_type const&) = delete;
explicit explicit queue_type(qalloc const& alloc);
queue_type (qalloc const& alloc);
~queue_type(); ~queue_type();
@@ -230,8 +231,7 @@ private:
template <class Handler> template <class Handler>
typename by_when_set::iterator typename by_when_set::iterator
emplace (Peer const& from, Peer const& to, emplace(Peer const& from, Peer const& to, time_point when, Handler&& h);
time_point when, Handler&& h);
void void
erase(iterator iter); erase(iterator iter);
@@ -246,14 +246,12 @@ private:
duration delay; duration delay;
link_type(bool inbound_, duration delay_) link_type(bool inbound_, duration delay_)
: inbound (inbound_) : inbound(inbound_), delay(delay_)
, delay (delay_)
{ {
} }
}; };
using links_type = using links_type = boost::container::flat_map<Peer, link_type>;
boost::container::flat_map<Peer, link_type>;
class link_transform; class link_transform;
@@ -266,7 +264,8 @@ private:
public: public:
BasicNetwork(BasicNetwork const&) = delete; BasicNetwork(BasicNetwork const&) = delete;
BasicNetwork& operator= (BasicNetwork const&) = delete; BasicNetwork&
operator=(BasicNetwork const&) = delete;
BasicNetwork(); BasicNetwork();
@@ -308,7 +307,9 @@ public:
@return `true` if a new connection was established @return `true` if a new connection was established
*/ */
bool bool
connect (Peer const& from, Peer const& to, connect(
Peer const& from,
Peer const& to,
duration const& delay = std::chrono::seconds{0}); duration const& delay = std::chrono::seconds{0});
/** Break a link. /** Break a link.
@@ -330,8 +331,7 @@ public:
@return A random access range. @return A random access range.
*/ */
boost::transformed_range< boost::transformed_range<link_transform, links_type>
link_transform, links_type>
links(Peer const& from); links(Peer const& from);
/** Send a message to a peer. /** Send a message to a peer.
@@ -354,8 +354,7 @@ public:
*/ */
template <class Function> template <class Function>
void void
send (Peer const& from, Peer const& to, send(Peer const& from, Peer const& to, Function&& f);
Function&& f);
// Used to cancel timers // Used to cancel timers
struct cancel_token; struct cancel_token;
@@ -370,8 +369,7 @@ public:
*/ */
template <class Function> template <class Function>
cancel_token cancel_token
timer (time_point const& when, timer(time_point const& when, Function&& f);
Function&& f);
/** Deliver a timer notification. /** Deliver a timer notification.
@@ -383,8 +381,7 @@ public:
*/ */
template <class Function> template <class Function>
cancel_token cancel_token
timer (duration const& delay, timer(duration const& delay, Function&& f);
Function&& f);
/** Cancel a timer. /** Cancel a timer.
@@ -473,15 +470,13 @@ public:
*/ */
template <class Period, class Rep> template <class Period, class Rep>
bool bool
step_for (std::chrono::duration< step_for(std::chrono::duration<Period, Rep> const& amount);
Period, Rep> const& amount);
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template <class Peer> template <class Peer>
BasicNetwork<Peer>::queue_type::queue_type( BasicNetwork<Peer>::queue_type::queue_type(qalloc const& alloc)
qalloc const& alloc)
: alloc_(alloc) : alloc_(alloc)
{ {
} }
@@ -489,8 +484,7 @@ BasicNetwork<Peer>::queue_type::queue_type(
template <class Peer> template <class Peer>
BasicNetwork<Peer>::queue_type::~queue_type() BasicNetwork<Peer>::queue_type::~queue_type()
{ {
for(auto iter = by_when_.begin(); for (auto iter = by_when_.begin(); iter != by_when_.end();)
iter != by_when_.end();)
{ {
auto m = &*iter; auto m = &*iter;
++iter; ++iter;
@@ -500,27 +494,22 @@ BasicNetwork<Peer>::queue_type::~queue_type()
} }
template <class Peer> template <class Peer>
inline inline bool
bool
BasicNetwork<Peer>::queue_type::empty() const BasicNetwork<Peer>::queue_type::empty() const
{ {
return by_when_.empty(); return by_when_.empty();
} }
template <class Peer> template <class Peer>
inline inline auto
auto BasicNetwork<Peer>::queue_type::begin() -> iterator
BasicNetwork<Peer>::queue_type::begin() ->
iterator
{ {
return by_when_.begin(); return by_when_.begin();
} }
template <class Peer> template <class Peer>
inline inline auto
auto BasicNetwork<Peer>::queue_type::end() -> iterator
BasicNetwork<Peer>::queue_type::end() ->
iterator
{ {
return by_when_.end(); return by_when_.end();
} }
@@ -529,15 +518,14 @@ template <class Peer>
template <class Handler> template <class Handler>
auto auto
BasicNetwork<Peer>::queue_type::emplace( BasicNetwork<Peer>::queue_type::emplace(
Peer const& from, Peer const& to, time_point when, Peer const& from,
Handler&& h) -> Peer const& to,
typename by_when_set::iterator time_point when,
Handler&& h) -> typename by_when_set::iterator
{ {
using msg_type = msg_impl< using msg_type = msg_impl<std::decay_t<Handler>>;
std::decay_t<Handler>>;
auto const p = alloc_.alloc<msg_type>(1); auto const p = alloc_.alloc<msg_type>(1);
auto& m = *new(p) msg_type(from, to, auto& m = *new (p) msg_type(from, to, when, std::forward<Handler>(h));
when, std::forward<Handler>(h));
if (to) if (to)
by_to_[to].push_back(m); by_to_[to].push_back(m);
if (from) if (from)
@@ -547,8 +535,7 @@ BasicNetwork<Peer>::queue_type::emplace(
template <class Peer> template <class Peer>
void void
BasicNetwork<Peer>::queue_type::erase( BasicNetwork<Peer>::queue_type::erase(iterator iter)
iterator iter)
{ {
auto& m = *iter; auto& m = *iter;
if (iter->to) if (iter->to)
@@ -568,13 +555,11 @@ BasicNetwork<Peer>::queue_type::erase(
template <class Peer> template <class Peer>
void void
BasicNetwork<Peer>::queue_type::remove( BasicNetwork<Peer>::queue_type::remove(Peer const& from, Peer const& to)
Peer const& from, Peer const& to)
{ {
{ {
auto& list = by_to_[to]; auto& list = by_to_[to];
for(auto iter = list.begin(); for (auto iter = list.begin(); iter != list.end();)
iter != list.end();)
{ {
auto& m = *iter++; auto& m = *iter++;
if (m.from == from) if (m.from == from)
@@ -583,8 +568,7 @@ BasicNetwork<Peer>::queue_type::remove(
} }
{ {
auto& list = by_to_[from]; auto& list = by_to_[from];
for(auto iter = list.begin(); for (auto iter = list.begin(); iter != list.end();)
iter != list.end();)
{ {
auto& m = *iter++; auto& m = *iter++;
if (m.from == to) if (m.from == to)
@@ -603,8 +587,7 @@ private:
Peer from_; Peer from_;
public: public:
using argument_type = using argument_type = typename links_type::value_type;
typename links_type::value_type;
class result_type class result_type
{ {
@@ -614,13 +597,12 @@ public:
result_type(result_type const&) = default; result_type(result_type const&) = default;
result_type (BasicNetwork& net, result_type(
Peer const& from, Peer const& to_, BasicNetwork& net,
Peer const& from,
Peer const& to_,
bool inbound_) bool inbound_)
: to(to_) : to(to_), inbound(inbound_), net_(net), from_(from)
, inbound(inbound_)
, net_(net)
, from_(from)
{ {
} }
@@ -641,18 +623,14 @@ public:
Peer from_; Peer from_;
}; };
link_transform (BasicNetwork& net, link_transform(BasicNetwork& net, Peer const& from) : net_(net), from_(from)
Peer const& from)
: net_(net)
, from_(from)
{ {
} }
result_type const result_type const
operator()(argument_type const& v) const operator()(argument_type const& v) const
{ {
return result_type(net_, from_, return result_type(net_, from_, v.first, v.second.inbound);
v.first, v.second.inbound);
} }
}; };
@@ -667,13 +645,12 @@ private:
public: public:
cancel_token() = delete; cancel_token() = delete;
cancel_token(cancel_token const&) = default; cancel_token(cancel_token const&) = default;
cancel_token& operator= (cancel_token const&) = default; cancel_token&
operator=(cancel_token const&) = default;
private: private:
friend class BasicNetwork; friend class BasicNetwork;
cancel_token(typename cancel_token(typename queue_type::iterator iter) : iter_(iter)
queue_type::iterator iter)
: iter_ (iter)
{ {
} }
}; };
@@ -681,33 +658,27 @@ private:
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
template <class Peer> template <class Peer>
BasicNetwork<Peer>::BasicNetwork() BasicNetwork<Peer>::BasicNetwork() : queue_(alloc_)
: queue_ (alloc_)
{ {
} }
template <class Peer> template <class Peer>
inline inline qalloc const&
qalloc const&
BasicNetwork<Peer>::alloc() const BasicNetwork<Peer>::alloc() const
{ {
return alloc_; return alloc_;
} }
template <class Peer> template <class Peer>
inline inline auto
auto BasicNetwork<Peer>::clock() const -> clock_type&
BasicNetwork<Peer>::clock() const ->
clock_type&
{ {
return clock_; return clock_;
} }
template <class Peer> template <class Peer>
inline inline auto
auto BasicNetwork<Peer>::now() const -> time_point
BasicNetwork<Peer>::now() const ->
time_point
{ {
return clock_.now(); return clock_.now();
} }
@@ -715,17 +686,16 @@ BasicNetwork<Peer>::now() const ->
template <class Peer> template <class Peer>
bool bool
BasicNetwork<Peer>::connect( BasicNetwork<Peer>::connect(
Peer const& from, Peer const& to, Peer const& from,
Peer const& to,
duration const& delay) duration const& delay)
{ {
if (to == from) if (to == from)
return false; return false;
using namespace std; using namespace std;
if (! links_[from].emplace(to, if (!links_[from].emplace(to, link_type{false, delay}).second)
link_type{ false, delay }).second)
return false; return false;
auto const result = links_[to].emplace( auto const result = links_[to].emplace(from, link_type{true, delay});
from, link_type{ true, delay });
(void)result; (void)result;
assert(result.second); assert(result.second);
return true; return true;
@@ -733,13 +703,11 @@ BasicNetwork<Peer>::connect(
template <class Peer> template <class Peer>
bool bool
BasicNetwork<Peer>::disconnect( BasicNetwork<Peer>::disconnect(Peer const& peer1, Peer const& peer2)
Peer const& peer1, Peer const& peer2)
{ {
if (links_[peer1].erase(peer2) == 0) if (links_[peer1].erase(peer2) == 0)
return false; return false;
auto const n = auto const n = links_[peer2].erase(peer1);
links_[peer2].erase(peer1);
(void)n; (void)n;
assert(n); assert(n);
queue_.remove(peer1, peer2); queue_.remove(peer1, peer2);
@@ -747,64 +715,45 @@ BasicNetwork<Peer>::disconnect(
} }
template <class Peer> template <class Peer>
inline inline auto
auto BasicNetwork<Peer>::links(Peer const& from)
BasicNetwork<Peer>::links(Peer const& from) -> -> boost::transformed_range<link_transform, links_type>
boost::transformed_range<
link_transform, links_type>
{ {
return boost::adaptors::transform( return boost::adaptors::transform(
links_[from], links_[from], link_transform{*this, from});
link_transform{ *this, from });
} }
template <class Peer> template <class Peer>
template <class Function> template <class Function>
inline inline void
void BasicNetwork<Peer>::send(Peer const& from, Peer const& to, Function&& f)
BasicNetwork<Peer>::send(
Peer const& from, Peer const& to,
Function&& f)
{ {
using namespace std; using namespace std;
auto const iter = auto const iter = links_[from].find(to);
links_[from].find(to); queue_.emplace(
queue_.emplace(from, to, from, to, clock_.now() + iter->second.delay, forward<Function>(f));
clock_.now() + iter->second.delay,
forward<Function>(f));
} }
template <class Peer> template <class Peer>
template <class Function> template <class Function>
inline inline auto
auto BasicNetwork<Peer>::timer(time_point const& when, Function&& f) -> cancel_token
BasicNetwork<Peer>::timer(
time_point const& when, Function&& f) ->
cancel_token
{ {
using namespace std; using namespace std;
return queue_.emplace( return queue_.emplace(nullptr, nullptr, when, forward<Function>(f));
nullptr, nullptr, when,
forward<Function>(f));
} }
template <class Peer> template <class Peer>
template <class Function> template <class Function>
inline inline auto
auto BasicNetwork<Peer>::timer(duration const& delay, Function&& f) -> cancel_token
BasicNetwork<Peer>::timer(
duration const& delay, Function&& f) ->
cancel_token
{ {
return timer(clock_.now() + delay, return timer(clock_.now() + delay, std::forward<Function>(f));
std::forward<Function>(f));
} }
template <class Peer> template <class Peer>
inline inline void
void BasicNetwork<Peer>::cancel(cancel_token const& token)
BasicNetwork<Peer>::cancel(
cancel_token const& token)
{ {
queue_.erase(token.iter_); queue_.erase(token.iter_);
} }
@@ -847,8 +796,7 @@ BasicNetwork<Peer>::step_while(Function && f)
template <class Peer> template <class Peer>
bool bool
BasicNetwork<Peer>::step_until( BasicNetwork<Peer>::step_until(time_point const& until)
time_point const& until)
{ {
// VFALCO This routine needs optimizing // VFALCO This routine needs optimizing
if (queue_.empty()) if (queue_.empty())
@@ -866,19 +814,15 @@ BasicNetwork<Peer>::step_until(
{ {
step_one(); step_one();
iter = queue_.begin(); iter = queue_.begin();
} } while (iter != queue_.end() && iter->when <= until);
while(iter != queue_.end() &&
iter->when <= until);
clock_.set(until); clock_.set(until);
return iter != queue_.end(); return iter != queue_.end();
} }
template <class Peer> template <class Peer>
template <class Period, class Rep> template <class Period, class Rep>
inline inline bool
bool BasicNetwork<Peer>::step_for(std::chrono::duration<Period, Rep> const& amount)
BasicNetwork<Peer>::step_for(
std::chrono::duration<Period, Rep> const& amount)
{ {
return step_until(now() + amount); return step_until(now() + amount);
} }
@@ -886,8 +830,7 @@ BasicNetwork<Peer>::step_for(
template <class Peer> template <class Peer>
template <class Function> template <class Function>
void void
BasicNetwork<Peer>::bfs( BasicNetwork<Peer>::bfs(Peer const& start, Function&& f)
Peer const& start, Function&& f)
{ {
std::deque<std::pair<Peer, std::size_t>> q; std::deque<std::pair<Peer, std::size_t>> q;
std::unordered_set<Peer> seen; std::unordered_set<Peer> seen;

View File

@@ -18,15 +18,14 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <test/csf/BasicNetwork.h>
#include <ripple/beast/unit_test.h> #include <ripple/beast/unit_test.h>
#include <set> #include <set>
#include <test/csf/BasicNetwork.h>
#include <vector> #include <vector>
namespace ripple { namespace ripple {
namespace test { namespace test {
class BasicNetwork_test : public beast::unit_test::suite class BasicNetwork_test : public beast::unit_test::suite
{ {
public: public:
@@ -38,23 +37,20 @@ public:
Peer(Peer const&) = default; Peer(Peer const&) = default;
Peer(Peer&&) = default; Peer(Peer&&) = default;
explicit Peer(int id_) explicit Peer(int id_) : id(id_)
: id(id_)
{ {
} }
template <class Net> template <class Net>
void start(Net& net) void
start(Net& net)
{ {
using namespace std::chrono_literals; using namespace std::chrono_literals;
auto t = net.timer(1s, auto t = net.timer(1s, [&] { set.insert(0); });
[&]{ set.insert(0); });
if (id == 0) if (id == 0)
{ {
for (auto const& link : net.links(this)) for (auto const& link : net.links(this))
net.send(this, link.to, net.send(this, link.to, [&, to = link.to ] {
[&, to = link.to]
{
to->receive(net, this, 1); to->receive(net, this, 1);
}); });
} }
@@ -65,23 +61,23 @@ public:
} }
template <class Net> template <class Net>
void receive(Net& net, Peer* from, int m) void
receive(Net& net, Peer* from, int m)
{ {
set.insert(m); set.insert(m);
++m; ++m;
if (m < 5) if (m < 5)
{ {
for (auto const& link : net.links(this)) for (auto const& link : net.links(this))
net.send(this, link.to, net.send(this, link.to, [&, mm = m, to = link.to ] {
[&, mm = m, to = link.to]
{
to->receive(net, this, mm); to->receive(net, this, mm);
}); });
} }
} }
}; };
void run() override void
run() override
{ {
using namespace std::chrono_literals; using namespace std::chrono_literals;
std::vector<Peer> pv; std::vector<Peer> pv;
@@ -94,9 +90,8 @@ public:
BEAST_EXPECT(net.connect(&pv[1], &pv[2], 1s)); BEAST_EXPECT(net.connect(&pv[1], &pv[2], 1s));
BEAST_EXPECT(!net.connect(&pv[0], &pv[1])); BEAST_EXPECT(!net.connect(&pv[0], &pv[1]));
std::size_t diameter = 0; std::size_t diameter = 0;
net.bfs(&pv[0], net.bfs(
[&](auto d, Peer*) &pv[0], [&](auto d, Peer*) { diameter = std::max(d, diameter); });
{ diameter = std::max(d, diameter); });
BEAST_EXPECT(diameter == 2); BEAST_EXPECT(diameter == 2);
for (auto& peer : pv) for (auto& peer : pv)
peer.start(net); peer.start(net);
@@ -116,12 +111,9 @@ public:
break; break;
BEAST_EXPECT(links[0].disconnect()); BEAST_EXPECT(links[0].disconnect());
} }
BEAST_EXPECT(pv[0].set == BEAST_EXPECT(pv[0].set == std::set<int>({0, 2, 4}));
std::set<int>({0, 2, 4})); BEAST_EXPECT(pv[1].set == std::set<int>({1, 3}));
BEAST_EXPECT(pv[1].set == BEAST_EXPECT(pv[2].set == std::set<int>({2, 4}));
std::set<int>({1, 3}));
BEAST_EXPECT(pv[2].set ==
std::set<int>({2, 4}));
net.timer(0s, [] {}); net.timer(0s, [] {});
} }
}; };
@@ -130,4 +122,3 @@ BEAST_DEFINE_TESTSUITE(BasicNetwork, test, ripple);
} // test } // test
} // ripple } // ripple

View File

@@ -46,31 +46,31 @@ namespace csf {
class Ledger class Ledger
{ {
public: public:
struct ID struct ID
{ {
std::uint32_t seq = 0; std::uint32_t seq = 0;
TxSetType txs = TxSetType{}; TxSetType txs = TxSetType{};
bool operator==(ID const & o) const bool
operator==(ID const& o) const
{ {
return seq == o.seq && txs == o.txs; return seq == o.seq && txs == o.txs;
} }
bool operator!=(ID const & o) const bool
operator!=(ID const& o) const
{ {
return !(*this == o); return !(*this == o);
} }
bool operator<(ID const & o) const bool
operator<(ID const& o) const
{ {
return std::tie(seq, txs) < std::tie(o.seq, o.txs); return std::tie(seq, txs) < std::tie(o.seq, o.txs);
} }
}; };
auto const& auto const&
id() const id() const
{ {
@@ -127,10 +127,10 @@ public:
return res; return res;
} }
//! Apply the given transactions to this ledger //! Apply the given transactions to this ledger
Ledger Ledger
close(TxSetType const & txs, close(
TxSetType const& txs,
NetClock::duration closeTimeResolution, NetClock::duration closeTimeResolution,
NetClock::time_point const& consensusCloseTime, NetClock::time_point const& consensusCloseTime,
bool closeTimeAgree) const bool closeTimeAgree) const
@@ -140,17 +140,15 @@ public:
res.id_.seq = seq() + 1; res.id_.seq = seq() + 1;
res.closeTimeResolution_ = closeTimeResolution; res.closeTimeResolution_ = closeTimeResolution;
res.actualCloseTime_ = consensusCloseTime; res.actualCloseTime_ = consensusCloseTime;
res.closeTime_ = effectiveCloseTime(consensusCloseTime, res.closeTime_ = effCloseTime(
closeTimeResolution, parentCloseTime_); consensusCloseTime, closeTimeResolution, parentCloseTime_);
res.closeTimeAgree_ = closeTimeAgree; res.closeTimeAgree_ = closeTimeAgree;
res.parentCloseTime_ = closeTime(); res.parentCloseTime_ = closeTime();
res.parentID_ = id(); res.parentID_ = id();
return res; return res;
} }
private: private:
//! Unique identifier of ledger is combination of sequence number and id //! Unique identifier of ledger is combination of sequence number and id
ID id_; ID id_;
@@ -171,18 +169,15 @@ private:
//! Close time unadjusted by closeTimeResolution //! Close time unadjusted by closeTimeResolution
NetClock::time_point actualCloseTime_; NetClock::time_point actualCloseTime_;
}; };
inline inline std::ostream&
std::ostream &
operator<<(std::ostream& o, Ledger::ID const& id) operator<<(std::ostream& o, Ledger::ID const& id)
{ {
return o << id.seq << "," << id.txs; return o << id.seq << "," << id.txs;
} }
inline inline std::string
std::string
to_string(Ledger::ID const& id) to_string(Ledger::ID const& id)
{ {
std::stringstream ss; std::stringstream ss;

View File

@@ -22,15 +22,14 @@
#include <boost/container/flat_map.hpp> #include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp> #include <boost/container/flat_set.hpp>
#include <test/csf/Tx.h>
#include <test/csf/Ledger.h> #include <test/csf/Ledger.h>
#include <test/csf/Tx.h>
#include <test/csf/UNL.h> #include <test/csf/UNL.h>
namespace ripple { namespace ripple {
namespace test { namespace test {
namespace csf { namespace csf {
/** Store validations reached by peers */ /** Store validations reached by peers */
struct Validation struct Validation
{ {
@@ -47,7 +46,9 @@ class Validations
//< created) //< created)
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromLedger; bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromLedger;
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromPrevLedger; bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromPrevLedger;
bc::flat_map<Ledger::ID, bc::flat_map<Ledger::ID, std::size_t>> childLedgers; bc::flat_map<Ledger::ID, bc::flat_map<Ledger::ID, std::size_t>>
childLedgers;
public: public:
void void
update(Validation const& v) update(Validation const& v)
@@ -86,12 +87,10 @@ public:
/** Returns the ledger starting from prevLedger with the most validations. /** Returns the ledger starting from prevLedger with the most validations.
*/ */
Ledger::ID Ledger::ID
getBestLCL(Ledger::ID const & currLedger, getBestLCL(Ledger::ID const& currLedger, Ledger::ID const& prevLedger) const
Ledger::ID const & prevLedger) const
{ {
auto it = childLedgers.find(prevLedger); auto it = childLedgers.find(prevLedger);
if (it != childLedgers.end() && if (it != childLedgers.end() && !it->second.empty())
! it->second.empty())
{ {
std::size_t bestCount = 0; std::size_t bestCount = 0;
Ledger::ID bestLedger; Ledger::ID bestLedger;
@@ -122,11 +121,8 @@ struct Traits
using Ledger_t = Ledger; using Ledger_t = Ledger;
using NodeID_t = PeerID; using NodeID_t = PeerID;
using TxSet_t = TxSet; using TxSet_t = TxSet;
using MissingTxException_t = MissingTx;
}; };
/** Represents a single node participating in the consensus process. /** Represents a single node participating in the consensus process.
It implements the Callbacks required by Consensus. It implements the Callbacks required by Consensus.
*/ */
@@ -173,8 +169,8 @@ struct Peer : public Consensus<Peer, Traits>
//! Delay in acquiring missing ledger from the network //! Delay in acquiring missing ledger from the network
std::chrono::milliseconds missingLedgerDelay{0}; std::chrono::milliseconds missingLedgerDelay{0};
bool validating = true; bool validating_ = true;
bool proposing = true; bool proposing_ = true;
//! All peers start from the default constructed ledger //! All peers start from the default constructed ledger
Peer(PeerID i, BasicNetwork<Peer*>& n, UNL const& u) Peer(PeerID i, BasicNetwork<Peer*>& n, UNL const& u)
@@ -186,17 +182,6 @@ struct Peer : public Consensus<Peer, Traits>
ledgers[lastClosedLedger.id()] = lastClosedLedger; ledgers[lastClosedLedger.id()] = lastClosedLedger;
} }
// @return whether we are proposing,validating
// TODO: Bit akward that this is in callbacks, would be nice to extract
std::pair<bool, bool>
getMode()
{
// in RCL this hits NetworkOps to decide whether we are proposing
// validating
return{ proposing, validating };
}
Ledger const* Ledger const*
acquireLedger(Ledger::ID const& ledgerHash) acquireLedger(Ledger::ID const& ledgerHash)
{ {
@@ -212,9 +197,9 @@ struct Peer : public Consensus<Peer, Traits>
auto it = p.ledgers.find(ledgerHash); auto it = p.ledgers.find(ledgerHash);
if (it != p.ledgers.end()) if (it != p.ledgers.end())
{ {
schedule(missingLedgerDelay, schedule(
[this, ledgerHash, ledger = it->second]() missingLedgerDelay,
{ [ this, ledgerHash, ledger = it->second ]() {
ledgers.emplace(ledgerHash, ledger); ledgers.emplace(ledgerHash, ledger);
}); });
if (missingLedgerDelay == 0ms) if (missingLedgerDelay == 0ms)
@@ -241,7 +226,6 @@ struct Peer : public Consensus<Peer, Traits>
return nullptr; return nullptr;
} }
bool bool
hasOpenTransactions() const hasOpenTransactions() const
{ {
@@ -260,103 +244,57 @@ struct Peer : public Consensus<Peer, Traits>
return peerValidations.proposersFinished(prevLedger); return peerValidations.proposersFinished(prevLedger);
} }
void Result
onStartRound(Ledger const &) {} onClose(Ledger const& prevLedger, NetClock::time_point closeTime, Mode mode)
void
onClose(Ledger const &, bool ) {}
// don't really offload
void
dispatchAccept(TxSet const & f)
{
Base::accept(f);
}
void
share(TxSet const &s)
{
relay(s);
}
Ledger::ID
getLCL(Ledger::ID const & currLedger,
Ledger::ID const & priorLedger,
bool haveCorrectLCL)
{
// TODO: Use generic validation code
if(currLedger.seq > 0 && priorLedger.seq > 0)
return peerValidations.getBestLCL(currLedger, priorLedger);
return currLedger;
}
void
propose(Proposal const & pos)
{
if(proposing)
relay(pos);
}
void
relay(DisputedTx<Tx, PeerID> const & dispute)
{
relay(dispute.tx());
}
std::pair <TxSet, Proposal>
makeInitialPosition(
Ledger const & prevLedger,
bool isProposing,
bool isCorrectLCL,
NetClock::time_point closeTime,
NetClock::time_point now)
{ {
TxSet res{openTxs}; TxSet res{openTxs};
return { res, return Result{TxSet{openTxs},
Proposal{prevLedger.id(), Proposal::seqJoin, res.id(), closeTime, now, id} }; Proposal{prevLedger.id(),
Proposal::seqJoin,
res.id(),
closeTime,
now(),
id}};
} }
// Process the accepted transaction set, generating the newly closed ledger void
// and clearing out the openTxs that were included. onForceAccept(
// TODO: Kinda nasty it takes so many arguments . . . sign of bad coupling Result const& result,
bool Ledger const& prevLedger,
accept(TxSet const& set, NetClock::duration const& closeResolution,
NetClock::time_point consensusCloseTime, CloseTimes const& rawCloseTimes,
bool proposing_, Mode const& mode)
bool validating_,
bool haveCorrectLCL_,
bool consensusFail_,
Ledger::ID const & prevLedgerHash_,
Ledger const & previousLedger_,
NetClock::duration closeResolution_,
NetClock::time_point const & now,
std::chrono::milliseconds const & roundTime_,
hash_map<Tx::ID, DisputedTx <Tx, PeerID>> const & disputes_,
std::map <NetClock::time_point, int> closeTimes_,
NetClock::time_point const & closeTime)
{ {
auto newLedger = previousLedger_.close(set.txs_, closeResolution_, onAccept(result, prevLedger, closeResolution, rawCloseTimes, mode);
closeTime, consensusCloseTime != NetClock::time_point{}); }
void
onAccept(
Result const& result,
Ledger const& prevLedger,
NetClock::duration const& closeResolution,
CloseTimes const& rawCloseTimes,
Mode const& mode)
{
auto newLedger = prevLedger.close(
result.set.txs_,
closeResolution,
rawCloseTimes.self,
result.position.closeTime() != NetClock::time_point{});
ledgers[newLedger.id()] = newLedger; ledgers[newLedger.id()] = newLedger;
lastClosedLedger = newLedger; lastClosedLedger = newLedger;
auto it = std::remove_if(openTxs.begin(), openTxs.end(), auto it =
[&](Tx const & tx) std::remove_if(openTxs.begin(), openTxs.end(), [&](Tx const& tx) {
{ return result.set.exists(tx.id());
return set.exists(tx.id());
}); });
openTxs.erase(it, openTxs.end()); openTxs.erase(it, openTxs.end());
if(validating) if (validating_)
relay(Validation{id, newLedger.id(), newLedger.parentID()}); relay(Validation{id, newLedger.id(), newLedger.parentID()});
return validating_;
}
void
endConsensus(bool correct)
{
// kick off the next round... // kick off the next round...
// in the actual implementation, this passes back through // in the actual implementation, this passes back through
// network ops // network ops
@@ -366,11 +304,28 @@ struct Peer : public Consensus<Peer, Traits>
// TODO: reconsider this and instead just save LCL generated here? // TODO: reconsider this and instead just save LCL generated here?
if (completedLedgers <= targetLedgers) if (completedLedgers <= targetLedgers)
{ {
startRound(now(), lastClosedLedger.id(), startRound(
lastClosedLedger); now(), lastClosedLedger.id(), lastClosedLedger, proposing_);
} }
} }
Ledger::ID
getPrevLedger(Ledger::ID const& ledgerID, Ledger const& ledger, Mode mode)
{
// TODO: Use generic validation code
if (mode != Mode::wrongLedger && ledgerID.seq > 0 &&
ledger.id().seq > 0)
return peerValidations.getBestLCL(ledgerID, ledger.parentID());
return ledgerID;
}
void
propose(Proposal const& pos)
{
if (proposing_)
relay(pos);
}
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// non-callback helpers // non-callback helpers
void void
@@ -383,9 +338,9 @@ struct Peer : public Consensus<Peer, Traits>
auto& dest = peerPositions_[p.prevLedger()]; auto& dest = peerPositions_[p.prevLedger()];
if (std::find(dest.begin(), dest.end(), p) != dest.end()) if (std::find(dest.begin(), dest.end(), p) != dest.end())
return; return;
dest.push_back(p); dest.push_back(p);
peerProposal(now(), p); peerProposal(now(), p);
} }
void void
@@ -413,11 +368,7 @@ struct Peer : public Consensus<Peer, Traits>
{ {
if (unl.find(v.id) != unl.end()) if (unl.find(v.id) != unl.end())
{ {
schedule(validationDelay, schedule(validationDelay, [&, v]() { peerValidations.update(v); });
[&, v]()
{
peerValidations.update(v);
});
} }
} }
@@ -426,11 +377,8 @@ struct Peer : public Consensus<Peer, Traits>
relay(T const& t) relay(T const& t)
{ {
for (auto const& link : net.links(this)) for (auto const& link : net.links(this))
net.send(this, link.to, net.send(
[msg = t, to = link.to] this, link.to, [ msg = t, to = link.to ] { to->receive(msg); });
{
to->receive(msg);
});
} }
// Receive and relay locally submitted transaction // Receive and relay locally submitted transaction
@@ -456,10 +404,9 @@ struct Peer : public Consensus<Peer, Traits>
// The ID is the one we have seen the most validations for // The ID is the one we have seen the most validations for
// In practice, we might not actually have that ledger itself yet, // In practice, we might not actually have that ledger itself yet,
// so there is no gaurantee that bestLCL == lastClosedLedger.id() // so there is no gaurantee that bestLCL == lastClosedLedger.id()
auto bestLCL = peerValidations.getBestLCL(lastClosedLedger.id(), auto bestLCL = peerValidations.getBestLCL(
lastClosedLedger.parentID()); lastClosedLedger.id(), lastClosedLedger.parentID());
startRound(now(), bestLCL, startRound(now(), bestLCL, lastClosedLedger, proposing_);
lastClosedLedger);
} }
NetClock::time_point NetClock::time_point
@@ -470,14 +417,15 @@ struct Peer : public Consensus<Peer, Traits>
// any subtractions of two NetClock::time_point in the consensu // any subtractions of two NetClock::time_point in the consensu
// code are positive. (e.g. PROPOSE_FRESHNESS) // code are positive. (e.g. PROPOSE_FRESHNESS)
using namespace std::chrono; using namespace std::chrono;
return NetClock::time_point(duration_cast<NetClock::duration> return NetClock::time_point(duration_cast<NetClock::duration>(
(net.now().time_since_epoch()+ 86400s + clockSkew)); net.now().time_since_epoch() + 86400s + clockSkew));
} }
// Schedule the provided callback in `when` duration, but if // Schedule the provided callback in `when` duration, but if
// `when` is 0, call immediately // `when` is 0, call immediately
template <class T> template <class T>
void schedule(std::chrono::nanoseconds when, T && what) void
schedule(std::chrono::nanoseconds when, T&& what)
{ {
if (when == 0ns) if (when == 0ns)
what(); what();

View File

@@ -20,8 +20,8 @@
#ifndef RIPPLE_TEST_CSF_SIM_H_INCLUDED #ifndef RIPPLE_TEST_CSF_SIM_H_INCLUDED
#define RIPPLE_TEST_CSF_SIM_H_INCLUDED #define RIPPLE_TEST_CSF_SIM_H_INCLUDED
#include <test/csf/UNL.h>
#include <test/csf/BasicNetwork.h> #include <test/csf/BasicNetwork.h>
#include <test/csf/UNL.h>
namespace ripple { namespace ripple {
namespace test { namespace test {
@@ -84,7 +84,7 @@ public:
for (auto& p : peers) for (auto& p : peers)
{ {
if (p.completedLedgers == 0) if (p.completedLedgers == 0)
p.relay(Validation{p.id, p.LCL(), p.LCL()}); p.relay(Validation{p.id, p.prevLedgerID(), p.prevLedgerID()});
p.targetLedgers = p.completedLedgers + ledgers; p.targetLedgers = p.completedLedgers + ledgers;
p.start(); p.start();
} }
@@ -93,10 +93,8 @@ public:
std::vector<Peer> peers; std::vector<Peer> peers;
BasicNetwork<Peer*> net; BasicNetwork<Peer*> net;
}; };
} // csf } // csf
} // test } // test
} // ripple } // ripple

View File

@@ -21,9 +21,9 @@
#include <ripple/beast/hash/hash_append.h> #include <ripple/beast/hash/hash_append.h>
#include <boost/container/flat_set.hpp> #include <boost/container/flat_set.hpp>
#include <map>
#include <ostream> #include <ostream>
#include <string> #include <string>
#include <map>
namespace ripple { namespace ripple {
namespace test { namespace test {
@@ -35,7 +35,9 @@ class Tx
public: public:
using ID = std::uint32_t; using ID = std::uint32_t;
Tx(ID i) : id_{ i } {} Tx(ID i) : id_{i}
{
}
ID ID
id() const id() const
@@ -55,10 +57,8 @@ public:
return id_ == o.id_; return id_ == o.id_;
} }
private: private:
ID id_; ID id_;
}; };
//!------------------------------------------------------------------------- //!-------------------------------------------------------------------------
@@ -74,7 +74,9 @@ public:
using MutableTxSet = TxSet; using MutableTxSet = TxSet;
TxSet() = default; TxSet() = default;
TxSet(TxSetType const & s) : txs_{ s } {} TxSet(TxSetType const& s) : txs_{s}
{
}
bool bool
insert(Tx const& t) insert(Tx const& t)
@@ -119,19 +121,14 @@ public:
{ {
std::map<Tx::ID, bool> res; std::map<Tx::ID, bool> res;
auto populate_diffs = [&res](auto const & a, auto const & b, bool s) auto populate_diffs = [&res](auto const& a, auto const& b, bool s) {
{ auto populator = [&](auto const& tx) { res[tx.id()] = s; };
auto populator = [&](auto const & tx)
{
res[tx.id()] = s;
};
std::set_difference( std::set_difference(
a.begin(), a.end(), a.begin(),
b.begin(), b.end(), a.end(),
boost::make_function_output_iterator( b.begin(),
std::ref(populator) b.end(),
) boost::make_function_output_iterator(std::ref(populator)));
);
}; };
populate_diffs(txs_, other.txs_, true); populate_diffs(txs_, other.txs_, true);
@@ -143,33 +140,17 @@ public:
TxSetType txs_; TxSetType txs_;
}; };
/** The RCL consensus process catches missing node SHAMap error
in several points. This exception is meant to represent a similar
case for the unit test.
*/
class MissingTx : public std::runtime_error
{
public:
MissingTx()
: std::runtime_error("MissingTx")
{}
};
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helper functions for debug printing // Helper functions for debug printing
inline inline std::ostream&
std::ostream&
operator<<(std::ostream& o, const Tx& t) operator<<(std::ostream& o, const Tx& t)
{ {
return o << t.id(); return o << t.id();
} }
template <class T> template <class T>
inline inline std::ostream&
std::ostream&
operator<<(std::ostream& o, boost::container::flat_set<T> const& ts) operator<<(std::ostream& o, boost::container::flat_set<T> const& ts)
{ {
o << "{ "; o << "{ ";
@@ -181,16 +162,12 @@ operator<<(std::ostream & o, boost::container::flat_set<T> const & ts)
else else
do_comma = true; do_comma = true;
o << t; o << t;
} }
o << " }"; o << " }";
return o; return o;
} }
inline inline std::string
std::string
to_string(TxSetType const& txs) to_string(TxSetType const& txs)
{ {
std::stringstream ss; std::stringstream ss;
@@ -199,21 +176,13 @@ to_string(TxSetType const & txs)
} }
template <class Hasher> template <class Hasher>
inline inline void
void
hash_append(Hasher& h, Tx const& tx) hash_append(Hasher& h, Tx const& tx)
{ {
using beast::hash_append; using beast::hash_append;
hash_append(h, tx.id()); hash_append(h, tx.id());
} }
std::ostream&
operator<<(std::ostream & o, MissingTx const &m)
{
return o << m.what();
}
} // csf } // csf
} // test } // test
} // ripple } // ripple

View File

@@ -22,10 +22,10 @@
#include <boost/container/flat_set.hpp> #include <boost/container/flat_set.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <vector>
#include <random>
#include <numeric>
#include <chrono> #include <chrono>
#include <numeric>
#include <random>
#include <vector>
namespace ripple { namespace ripple {
namespace test { namespace test {
@@ -57,7 +57,6 @@ random_weighted_shuffle(std::vector<T> v, std::vector<double> w, G & g)
return v; return v;
} }
/** Power-law distribution with PDF /** Power-law distribution with PDF
P(x) = (x/xmin)^-a P(x) = (x/xmin)^-a
@@ -72,22 +71,19 @@ class PowerLawDistribution
std::uniform_real_distribution<double> uf_{0, 1}; std::uniform_real_distribution<double> uf_{0, 1};
public: public:
PowerLawDistribution(double xmin, double a) PowerLawDistribution(double xmin, double a) : xmin_{xmin}, a_{a}
: xmin_{xmin}, a_{a}
{ {
inv_ = 1.0 / (1.0 - a_); inv_ = 1.0 / (1.0 - a_);
} }
template <class Generator> template <class Generator>
inline inline double
double
operator()(Generator& g) operator()(Generator& g)
{ {
// use inverse transform of CDF to sample // use inverse transform of CDF to sample
// CDF is P(X <= x): 1 - (x/xmin)^(1-a) // CDF is P(X <= x): 1 - (x/xmin)^(1-a)
return xmin_ * std::pow(1 - uf_(g), inv_); return xmin_ * std::pow(1 - uf_(g), inv_);
} }
}; };
//< Unique identifier for each node in the network //< Unique identifier for each node in the network
@@ -110,25 +106,23 @@ class TrustGraph
std::vector<UNL> UNLs_; std::vector<UNL> UNLs_;
std::vector<int> assignment_; std::vector<int> assignment_;
public:
public:
//< Constructor //< Constructor
TrustGraph(std::vector<UNL> UNLs, std::vector<int> assignment) TrustGraph(std::vector<UNL> UNLs, std::vector<int> assignment)
: UNLs_{UNLs} : UNLs_{UNLs}, assignment_{assignment}
, assignment_{assignment} {
{} }
//< Whether node `i` trusts node `j` //< Whether node `i` trusts node `j`
inline inline bool
bool
trusts(PeerID i, PeerID j) const trusts(PeerID i, PeerID j) const
{ {
return unl(i).find(j) != unl(i).end(); return unl(i).find(j) != unl(i).end();
} }
//< Get the UNL for node `i` //< Get the UNL for node `i`
inline inline UNL const&
UNL const &
unl(PeerID i) const unl(PeerID i) const
{ {
return UNLs_[assignment_[i]]; return UNLs_[assignment_[i]];
@@ -138,7 +132,6 @@ public:
bool bool
canFork(double quorum) const; canFork(double quorum) const;
auto auto
numPeers() const numPeers() const
{ {
@@ -176,28 +169,23 @@ public:
*/ */
template <class RankPDF, class SizePDF, class Generator> template <class RankPDF, class SizePDF, class Generator>
static static TrustGraph
TrustGraph makeRandomRanked(
makeRandomRanked(int size, int size,
int numUNLs, int numUNLs,
RankPDF rankPDF, RankPDF rankPDF,
SizePDF unlSizePDF, SizePDF unlSizePDF,
Generator& g) Generator& g)
{ {
// 1. Generate ranks // 1. Generate ranks
std::vector<double> weights(size); std::vector<double> weights(size);
std::generate(weights.begin(), weights.end(), [&]() std::generate(
{ weights.begin(), weights.end(), [&]() { return rankPDF(g); });
return rankPDF(g);
});
// 2. Generate UNLs based on sampling without replacement according // 2. Generate UNLs based on sampling without replacement according
// to weights // to weights
std::vector<UNL> unls(numUNLs); std::vector<UNL> unls(numUNLs);
std::generate(unls.begin(), unls.end(), [&]() std::generate(unls.begin(), unls.end(), [&]() {
{
std::vector<PeerID> ids(size); std::vector<PeerID> ids(size);
std::iota(ids.begin(), ids.end(), 0); std::iota(ids.begin(), ids.end(), 0);
auto res = random_weighted_shuffle(ids, weights, g); auto res = random_weighted_shuffle(ids, weights, g);
@@ -207,11 +195,8 @@ public:
// 3. Assign membership // 3. Assign membership
std::vector<int> assignment(size); std::vector<int> assignment(size);
std::uniform_int_distribution<int> u(0, numUNLs - 1); std::uniform_int_distribution<int> u(0, numUNLs - 1);
std::generate(assignment.begin(), assignment.end(), std::generate(
[&]() assignment.begin(), assignment.end(), [&]() { return u(g); });
{
return u(g);
});
return TrustGraph(unls, assignment); return TrustGraph(unls, assignment);
} }
@@ -226,8 +211,7 @@ public:
@param size The number of nodes in the trust graph @param size The number of nodes in the trust graph
@param overlap The number of nodes trusting both cliques @param overlap The number of nodes trusting both cliques
*/ */
static static TrustGraph
TrustGraph
makeClique(int size, int overlap); makeClique(int size, int overlap);
/** Generate a complete (fully-connect) trust graph /** Generate a complete (fully-connect) trust graph
@@ -237,20 +221,16 @@ public:
@param size The number of nodes in the trust graph @param size The number of nodes in the trust graph
*/ */
static static TrustGraph
TrustGraph
makeComplete(int size); makeComplete(int size);
}; };
//< Make the TrustGraph into a topology with delays given by DelayModel //< Make the TrustGraph into a topology with delays given by DelayModel
template <class DelayModel> template <class DelayModel>
auto auto
topology(TrustGraph const& tg, DelayModel const& d) topology(TrustGraph const& tg, DelayModel const& d)
{ {
return [&](PeerID i, PeerID j) return [&](PeerID i, PeerID j) {
{
return tg.trusts(i, j) ? boost::make_optional(d(i, j)) : boost::none; return tg.trusts(i, j) ? boost::make_optional(d(i, j)) : boost::none;
}; };
} }
@@ -260,10 +240,11 @@ class fixed
std::chrono::nanoseconds d_; std::chrono::nanoseconds d_;
public: public:
fixed(std::chrono::nanoseconds const & d) : d_{d} {} fixed(std::chrono::nanoseconds const& d) : d_{d}
{
}
inline inline std::chrono::nanoseconds
std::chrono::nanoseconds
operator()(PeerID const& i, PeerID const& j) const operator()(PeerID const& i, PeerID const& j) const
{ {
return d_; return d_;

View File

@@ -16,10 +16,10 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
//============================================================================== //==============================================================================
#include <test/csf/UNL.h>
#include <boost/iterator/counting_iterator.hpp> #include <boost/iterator/counting_iterator.hpp>
#include <fstream>
#include <algorithm> #include <algorithm>
#include <fstream>
#include <test/csf/UNL.h>
namespace ripple { namespace ripple {
namespace test { namespace test {
@@ -55,12 +55,11 @@ TrustGraph::canFork(double quorum) const
auto const& unlA = uniqueUNLs[i]; auto const& unlA = uniqueUNLs[i];
auto const& unlB = uniqueUNLs[j]; auto const& unlB = uniqueUNLs[j];
double rhs = 2.0*(1.-quorum) * double rhs =
std::max(unlA.size(), unlB.size() ); 2.0 * (1. - quorum) * std::max(unlA.size(), unlB.size());
int intersectionSize = std::count_if(unlA.begin(), unlA.end(), int intersectionSize =
[&](PeerID id) std::count_if(unlA.begin(), unlA.end(), [&](PeerID id) {
{
return unlB.find(id) != unlB.end(); return unlB.find(id) != unlB.end();
}); });
@@ -100,7 +99,6 @@ TrustGraph::makeClique(int size, int overlap)
assignment[i] = 2; assignment[i] = 2;
} }
return TrustGraph(unls, assignment); return TrustGraph(unls, assignment);
} }
@@ -110,11 +108,11 @@ TrustGraph::makeComplete(int size)
UNL all{boost::counting_iterator<PeerID>(0), UNL all{boost::counting_iterator<PeerID>(0),
boost::counting_iterator<PeerID>(size)}; boost::counting_iterator<PeerID>(size)};
return TrustGraph(std::vector<UNL>(1,all), return TrustGraph(std::vector<UNL>(1, all), std::vector<int>(size, 0));
std::vector<int>(size, 0));
} }
inline void TrustGraph::save_dot(std::string const & fileName) inline void
TrustGraph::save_dot(std::string const& fileName)
{ {
std::ofstream out(fileName); std::ofstream out(fileName);
out << "digraph {\n"; out << "digraph {\n";
@@ -124,10 +122,8 @@ inline void TrustGraph::save_dot(std::string const & fileName)
{ {
out << i << " -> " << j << ";\n"; out << i << " -> " << j << ";\n";
} }
} }
out << "}\n"; out << "}\n";
} }
} // csf } // csf