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.
@@ -353,6 +353,7 @@ if (WIN32 OR is_xcode)
|
||||
docs/
|
||||
Jamfile.v2
|
||||
boostbook.dtd
|
||||
consensus.qbk
|
||||
index.xml
|
||||
main.qbk
|
||||
quickref.xml
|
||||
|
||||
@@ -44,6 +44,15 @@ install callouts
|
||||
|
||||
explicit callout ;
|
||||
|
||||
install consensus_images
|
||||
:
|
||||
[ glob images/consensus/*.png ]
|
||||
:
|
||||
<location>$(out)/html/images/consensus
|
||||
;
|
||||
|
||||
explicit consensus_images ;
|
||||
|
||||
xml doc
|
||||
:
|
||||
main.qbk
|
||||
@@ -60,7 +69,7 @@ boostbook boostdoc
|
||||
<xsl:param>boost.root=$(broot)
|
||||
<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>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.section.depth=2 # How deep should recursive sections appear in the TOC?
|
||||
<xsl:param>generate.toc="chapter toc section toc"
|
||||
@@ -68,4 +77,5 @@ boostbook boostdoc
|
||||
<location>temp
|
||||
<dependency>stylesheets
|
||||
<dependency>images
|
||||
<dependency>consensus_images
|
||||
;
|
||||
|
||||
663
docs/consensus.qbk
Normal 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]
|
||||
BIN
docs/images/consensus/EffCloseTime.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
docs/images/consensus/block_chain.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
docs/images/consensus/consensus_modes.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/images/consensus/consensus_overview.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/images/consensus/disputes.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/images/consensus/ledger_chain.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/images/consensus/threshold.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
@@ -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
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
[library rippled
|
||||
[quickbook 1.6]
|
||||
[copyright 2012 - 2016 Ripple Labs Inc.]
|
||||
[copyright 2012 - 2017 Ripple Labs Inc.]
|
||||
[purpose C++ Library]
|
||||
[license
|
||||
Distributed under the ISC License
|
||||
@@ -30,6 +30,8 @@
|
||||
[template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>''']
|
||||
[template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>''']
|
||||
|
||||
[include consensus.qbk]
|
||||
|
||||
[section:ref Reference]
|
||||
[include temp/reference.qbk]
|
||||
[endsect]
|
||||
|
||||
@@ -18,30 +18,29 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/beast/core/LexicalCast.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/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/protocol/digest.h>
|
||||
#include <ripple/overlay/predicates.h>
|
||||
#include <ripple/app/misc/AmendmentTable.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/NetworkOPs.h>
|
||||
#include <ripple/app/misc/TxQ.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 {
|
||||
|
||||
|
||||
RCLConsensus::RCLConsensus(
|
||||
Application& app,
|
||||
std::unique_ptr<FeeVote>&& feeVote,
|
||||
@@ -59,38 +58,11 @@ RCLConsensus::RCLConsensus(
|
||||
, j_(journal)
|
||||
, 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>
|
||||
RCLConsensus::acquireLedger(LedgerHash const& ledger)
|
||||
{
|
||||
|
||||
// we need to switch the ledger we're working from
|
||||
auto buildLCL = ledgerMaster_.getLedgerByHash(ledger);
|
||||
if (!buildLCL)
|
||||
@@ -98,8 +70,7 @@ RCLConsensus::acquireLedger(LedgerHash const & ledger)
|
||||
if (acquiringLedger_ != ledger)
|
||||
{
|
||||
// need to start acquiring the correct consensus LCL
|
||||
JLOG (j_.warn()) <<
|
||||
"Need consensus ledger " << ledger;
|
||||
JLOG(j_.warn()) << "Need consensus ledger " << ledger;
|
||||
|
||||
// Tell the ledger acquire system that we need the consensus ledger
|
||||
acquiringLedger_ = ledger;
|
||||
@@ -107,8 +78,7 @@ RCLConsensus::acquireLedger(LedgerHash const & ledger)
|
||||
auto app = &app_;
|
||||
auto hash = acquiringLedger_;
|
||||
app_.getJobQueue().addJob(
|
||||
jtADVANCE, "getConsensusLedger",
|
||||
[app, hash] (Job&) {
|
||||
jtADVANCE, "getConsensusLedger", [app, hash](Job&) {
|
||||
app->getInboundLedgers().acquire(
|
||||
hash, 0, InboundLedger::fcCONSENSUS);
|
||||
});
|
||||
@@ -119,10 +89,12 @@ RCLConsensus::acquireLedger(LedgerHash const & ledger)
|
||||
assert(!buildLCL->open() && buildLCL->isImmutable());
|
||||
assert(buildLCL->info().hash == ledger);
|
||||
|
||||
// Notify inbound transactions of the new ledger sequence number
|
||||
inboundTransactions_.newRound(buildLCL->info().seq);
|
||||
|
||||
return RCLCxLedger(buildLCL);
|
||||
}
|
||||
|
||||
|
||||
std::vector<RCLCxPeerPos>
|
||||
RCLConsensus::proposals(LedgerHash const& prevLedger)
|
||||
{
|
||||
@@ -140,9 +112,7 @@ RCLConsensus::proposals (LedgerHash const& prevLedger)
|
||||
}
|
||||
|
||||
void
|
||||
RCLConsensus::storeProposal (
|
||||
RCLCxPeerPos::ref peerPos,
|
||||
NodeID const& nodeID)
|
||||
RCLConsensus::storeProposal(RCLCxPeerPos::ref peerPos, NodeID const& nodeID)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(peerPositionsLock_);
|
||||
|
||||
@@ -161,10 +131,8 @@ RCLConsensus::relay(RCLCxPeerPos const & peerPos)
|
||||
|
||||
auto const& proposal = peerPos.proposal();
|
||||
|
||||
prop.set_proposeseq (
|
||||
proposal.proposeSeq ());
|
||||
prop.set_closetime (
|
||||
proposal.closeTime ().time_since_epoch().count());
|
||||
prop.set_proposeseq(proposal.proposeSeq());
|
||||
prop.set_closetime(proposal.closeTime().time_since_epoch().count());
|
||||
|
||||
prop.set_currenttxhash(
|
||||
proposal.position().begin(), proposal.position().size());
|
||||
@@ -181,10 +149,9 @@ RCLConsensus::relay(RCLCxPeerPos const & peerPos)
|
||||
}
|
||||
|
||||
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
|
||||
auto const & tx = dispute.tx();
|
||||
if (app_.getHashRouter().shouldRelay(tx.id()))
|
||||
{
|
||||
auto const slice = tx.tx_.slice();
|
||||
@@ -195,26 +162,25 @@ RCLConsensus::relay(DisputedTx <RCLCxTx, NodeID> const & dispute)
|
||||
msg.set_receivetimestamp(
|
||||
app_.timeKeeper().now().time_since_epoch().count());
|
||||
app_.overlay().foreach (send_always(
|
||||
std::make_shared<Message> (
|
||||
msg, protocol::mtTRANSACTION)));
|
||||
std::make_shared<Message>(msg, protocol::mtTRANSACTION)));
|
||||
}
|
||||
}
|
||||
void
|
||||
RCLConsensus::propose(RCLCxPeerPos::Proposal const& proposal)
|
||||
{
|
||||
JLOG (j_.trace()) << "We propose: " <<
|
||||
(proposal.isBowOut () ? std::string ("bowOut") :
|
||||
to_string (proposal.position ()));
|
||||
JLOG(j_.trace()) << "We propose: "
|
||||
<< (proposal.isBowOut()
|
||||
? std::string("bowOut")
|
||||
: ripple::to_string(proposal.position()));
|
||||
|
||||
protocol::TMProposeSet prop;
|
||||
|
||||
prop.set_currenttxhash (proposal.position().begin(),
|
||||
proposal.position().size());
|
||||
prop.set_previousledger (proposal.prevLedger().begin(),
|
||||
proposal.position().size());
|
||||
prop.set_currenttxhash(
|
||||
proposal.position().begin(), proposal.position().size());
|
||||
prop.set_previousledger(
|
||||
proposal.prevLedger().begin(), proposal.position().size());
|
||||
prop.set_proposeseq(proposal.proposeSeq());
|
||||
prop.set_closetime (
|
||||
proposal.closeTime().time_since_epoch().count());
|
||||
prop.set_closetime(proposal.closeTime().time_since_epoch().count());
|
||||
|
||||
prop.set_nodepubkey(valPublic_.data(), valPublic_.size());
|
||||
|
||||
@@ -222,10 +188,10 @@ RCLConsensus::propose (RCLCxPeerPos::Proposal const& proposal)
|
||||
HashPrefix::proposal,
|
||||
std::uint32_t(proposal.proposeSeq()),
|
||||
proposal.closeTime().time_since_epoch().count(),
|
||||
proposal.prevLedger(), proposal.position());
|
||||
proposal.prevLedger(),
|
||||
proposal.position());
|
||||
|
||||
auto sig = signDigest (
|
||||
valPublic_, valSecret_, signingHash);
|
||||
auto sig = signDigest(valPublic_, valSecret_, signingHash);
|
||||
|
||||
prop.set_signature(sig.data(), sig.size());
|
||||
|
||||
@@ -233,10 +199,9 @@ RCLConsensus::propose (RCLCxPeerPos::Proposal const& proposal)
|
||||
}
|
||||
|
||||
void
|
||||
RCLConsensus::share (RCLTxSet const& set)
|
||||
RCLConsensus::relay(RCLTxSet const& set)
|
||||
{
|
||||
inboundTransactions_.giveSet (set.id(),
|
||||
set.map_, false);
|
||||
inboundTransactions_.giveSet(set.id(), set.map_, false);
|
||||
}
|
||||
|
||||
boost::optional<RCLTxSet>
|
||||
@@ -249,7 +214,6 @@ RCLConsensus::acquireTxSet(RCLTxSet::ID const & setId)
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
RCLConsensus::hasOpenTransactions() const
|
||||
{
|
||||
@@ -269,36 +233,40 @@ RCLConsensus::proposersFinished(LedgerHash const & h) const
|
||||
}
|
||||
|
||||
uint256
|
||||
RCLConsensus::getLCL (
|
||||
uint256 const& currentLedger,
|
||||
uint256 const& priorLedger,
|
||||
bool believedCorrect)
|
||||
RCLConsensus::getPrevLedger(
|
||||
uint256 ledgerID,
|
||||
RCLCxLedger const& ledger,
|
||||
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
|
||||
// our ledger.
|
||||
auto vals =
|
||||
app_.getValidations().getCurrentValidations(
|
||||
currentLedger, priorLedger,
|
||||
ledgerMaster_.getValidLedgerIndex());
|
||||
auto vals = app_.getValidations().getCurrentValidations(
|
||||
ledgerID, parentID, ledgerMaster_.getValidLedgerIndex());
|
||||
|
||||
uint256 netLgr = currentLedger;
|
||||
uint256 netLgr = ledgerID;
|
||||
int netLgrCount = 0;
|
||||
for (auto& it : vals)
|
||||
{
|
||||
// Switch to ledger supported by more peers
|
||||
// Or stick with ours on a tie
|
||||
if ((it.second.first > netLgrCount) ||
|
||||
((it.second.first == netLgrCount) && (it.first == currentLedger)))
|
||||
((it.second.first == netLgrCount) && (it.first == ledgerID)))
|
||||
{
|
||||
netLgr = it.first;
|
||||
netLgrCount = it.second.first;
|
||||
}
|
||||
}
|
||||
|
||||
if(netLgr != currentLedger)
|
||||
if (netLgr != ledgerID)
|
||||
{
|
||||
if (believedCorrect)
|
||||
if (mode != Mode::wrongLedger)
|
||||
app_.getOPs().consensusViewChange();
|
||||
|
||||
if (auto stream = j_.debug())
|
||||
{
|
||||
for (auto& it : vals)
|
||||
@@ -310,21 +278,19 @@ RCLConsensus::getLCL (
|
||||
return netLgr;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
RCLConsensus::onClose(RCLCxLedger const & ledger, bool haveCorrectLCL)
|
||||
RCLConsensus::Result
|
||||
RCLConsensus::onClose(
|
||||
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();
|
||||
// Tell the ledger master not to acquire the ledger we're probably building
|
||||
ledgerMaster_.setBuildingLedger(prevLedger->info().seq + 1);
|
||||
@@ -341,35 +307,29 @@ RCLConsensus::makeInitialPosition (RCLCxLedger const & prevLedgerT,
|
||||
Serializer s(2048);
|
||||
tx.first->add(s);
|
||||
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
|
||||
if ((app_.config().standalone() || (proposing && correctLCL))
|
||||
&& ((prevLedger->info().seq % 256) == 0))
|
||||
if ((app_.config().standalone() || (proposing && !wrongLCL)) &&
|
||||
((prevLedger->info().seq % 256) == 0))
|
||||
{
|
||||
// previous ledger was flag ledger, add pseudo-transactions
|
||||
auto const validations =
|
||||
app_.getValidations().getValidations (
|
||||
prevLedger->info().parentHash);
|
||||
app_.getValidations().getValidations(prevLedger->info().parentHash);
|
||||
|
||||
std::size_t const count = std::count_if(
|
||||
validations.begin(), validations.end(),
|
||||
[](auto const& v)
|
||||
{
|
||||
validations.begin(), validations.end(), [](auto const& v) {
|
||||
return v.second->isTrusted();
|
||||
});
|
||||
|
||||
if (count >= app_.validators().quorum())
|
||||
{
|
||||
feeVote_->doVoting (
|
||||
prevLedger,
|
||||
validations,
|
||||
initialSet);
|
||||
feeVote_->doVoting(prevLedger, validations, initialSet);
|
||||
app_.getAmendmentTable().doVoting(
|
||||
prevLedger,
|
||||
validations,
|
||||
initialSet);
|
||||
prevLedger, validations, initialSet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,101 +337,117 @@ RCLConsensus::makeInitialPosition (RCLCxLedger const & prevLedgerT,
|
||||
initialSet = initialSet->snapShot(false);
|
||||
auto setHash = initialSet->getHash().as_uint256();
|
||||
|
||||
return std::make_pair<RCLTxSet, RCLCxPeerPos::Proposal> (
|
||||
return Result{
|
||||
std::move(initialSet),
|
||||
RCLCxPeerPos::Proposal{
|
||||
initialLedger->info().parentHash,
|
||||
RCLCxPeerPos::Proposal::seqJoin,
|
||||
setHash,
|
||||
closeTime,
|
||||
now,
|
||||
nodeID_ });
|
||||
app_.timeKeeper().closeTime(),
|
||||
nodeID_}};
|
||||
}
|
||||
|
||||
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",
|
||||
[that = this->shared_from_this(),
|
||||
consensusSet = txSet]
|
||||
(auto &)
|
||||
doAccept(result, prevLedger, closeResolution, rawCloseTimes, mode);
|
||||
}
|
||||
|
||||
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
|
||||
RCLConsensus::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_
|
||||
)
|
||||
void
|
||||
RCLConsensus::doAccept(
|
||||
Result const& result,
|
||||
RCLCxLedger const& prevLedger,
|
||||
NetClock::duration closeResolution,
|
||||
CloseTimes const& rawCloseTimes,
|
||||
Mode const& mode)
|
||||
{
|
||||
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{})
|
||||
{
|
||||
// We agreed to disagree on the close time
|
||||
consensusCloseTime = previousLedger_.closeTime() + 1s;
|
||||
consensusCloseTime = prevLedger.closeTime() + 1s;
|
||||
closeTimeCorrect = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We agreed on a close time
|
||||
consensusCloseTime = effectiveCloseTime(consensusCloseTime,
|
||||
closeResolution_, previousLedger_.closeTime());
|
||||
consensusCloseTime = effCloseTime(
|
||||
consensusCloseTime, closeResolution, prevLedger.closeTime());
|
||||
closeTimeCorrect = true;
|
||||
}
|
||||
|
||||
JLOG (j_.debug())
|
||||
<< "Report: Prop=" << (proposing_ ? "yes" : "no")
|
||||
JLOG(j_.debug()) << "Report: Prop=" << (proposing ? "yes" : "no")
|
||||
<< " val=" << (validating_ ? "yes" : "no")
|
||||
<< " corLCL=" << (haveCorrectLCL_ ? "yes" : "no")
|
||||
<< " fail=" << (consensusFail_ ? "yes" : "no");
|
||||
JLOG (j_.debug())
|
||||
<< "Report: Prev = " << prevLedgerHash_
|
||||
<< ":" << previousLedger_.seq();
|
||||
<< " corLCL=" << (haveCorrectLCL ? "yes" : "no")
|
||||
<< " fail=" << (consensusFail ? "yes" : "no");
|
||||
JLOG(j_.debug()) << "Report: Prev = " << prevLedger.id() << ":"
|
||||
<< prevLedger.seq();
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Put transactions into a deterministic, but unpredictable, order
|
||||
CanonicalTXSet retriableTxs{ set.id() };
|
||||
|
||||
auto sharedLCL = buildLCL(previousLedger_, set, consensusCloseTime,
|
||||
closeTimeCorrect, closeResolution_, now_, roundTime_, retriableTxs);
|
||||
CanonicalTXSet retriableTxs{result.set.id()};
|
||||
|
||||
auto sharedLCL = buildLCL(
|
||||
prevLedger,
|
||||
result.set,
|
||||
consensusCloseTime,
|
||||
closeTimeCorrect,
|
||||
closeResolution,
|
||||
result.roundTime.read(),
|
||||
retriableTxs);
|
||||
|
||||
auto const newLCLHash = sharedLCL.id();
|
||||
JLOG (j_.debug())
|
||||
<< "Report: NewL = " << newLCLHash
|
||||
<< ":" << sharedLCL.seq();
|
||||
JLOG(j_.debug()) << "Report: NewL = " << newLCLHash << ":"
|
||||
<< sharedLCL.seq();
|
||||
|
||||
// Tell directly connected peers that we have a new LCL
|
||||
notify (protocol::neACCEPTED_LEDGER, sharedLCL, haveCorrectLCL_);
|
||||
notify(protocol::neACCEPTED_LEDGER, sharedLCL, haveCorrectLCL);
|
||||
|
||||
if (validating_)
|
||||
validating_ = ledgerMaster_.isCompatible(*sharedLCL.ledger_,
|
||||
app_.journal("LedgerConsensus").warn(), "Not validating");
|
||||
validating_ = ledgerMaster_.isCompatible(
|
||||
*sharedLCL.ledger_,
|
||||
app_.journal("LedgerConsensus").warn(),
|
||||
"Not validating");
|
||||
|
||||
if (validating_ && ! consensusFail_)
|
||||
if (validating_ && !consensusFail)
|
||||
{
|
||||
validate(sharedLCL, now_, proposing_);
|
||||
JLOG (j_.info())
|
||||
<< "CNF Val " << newLCLHash;
|
||||
validate(sharedLCL, proposing);
|
||||
JLOG(j_.info()) << "CNF Val " << newLCLHash;
|
||||
}
|
||||
else
|
||||
JLOG (j_.info())
|
||||
<< "CNF buildLCL " << newLCLHash;
|
||||
JLOG(j_.info()) << "CNF buildLCL " << newLCLHash;
|
||||
|
||||
// See if we can accept a ledger as fully-validated
|
||||
ledgerMaster_.consensusBuilt(sharedLCL.ledger_, getJson(true));
|
||||
@@ -490,7 +466,7 @@ RCLConsensus::accept(
|
||||
// in the previous consensus round.
|
||||
//
|
||||
bool anyDisputes = false;
|
||||
for (auto& it : disputes_)
|
||||
for (auto& it : result.disputes)
|
||||
{
|
||||
if (!it.second.getOurVote())
|
||||
{
|
||||
@@ -516,10 +492,8 @@ RCLConsensus::accept(
|
||||
}
|
||||
|
||||
// Build new open ledger
|
||||
auto lock = make_lock(
|
||||
app_.getMasterMutex(), std::defer_lock);
|
||||
auto sl = make_lock(
|
||||
ledgerMaster_.peekMutex (), std::defer_lock);
|
||||
auto lock = make_lock(app_.getMasterMutex(), std::defer_lock);
|
||||
auto sl = make_lock(ledgerMaster_.peekMutex(), std::defer_lock);
|
||||
std::lock(lock, sl);
|
||||
|
||||
auto const lastVal = ledgerMaster_.getValidatedLedger();
|
||||
@@ -528,11 +502,16 @@ RCLConsensus::accept(
|
||||
rules.emplace(*lastVal, app_.config().features);
|
||||
else
|
||||
rules.emplace(app_.config().features);
|
||||
app_.openLedger().accept(app_, *rules,
|
||||
sharedLCL.ledger_, localTxs_.getTxSet(), anyDisputes, retriableTxs, tapNONE,
|
||||
app_.openLedger().accept(
|
||||
app_,
|
||||
*rules,
|
||||
sharedLCL.ledger_,
|
||||
localTxs_.getTxSet(),
|
||||
anyDisputes,
|
||||
retriableTxs,
|
||||
tapNONE,
|
||||
"consensus",
|
||||
[&](OpenView& view, beast::Journal j)
|
||||
{
|
||||
[&](OpenView& view, beast::Journal j) {
|
||||
// Stuff the ledger with transactions from the queue.
|
||||
return app_.getTxQ().accept(app_, view);
|
||||
});
|
||||
@@ -548,30 +527,35 @@ RCLConsensus::accept(
|
||||
|
||||
// Do these need to exist?
|
||||
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,
|
||||
// see how close our close time is to other node's
|
||||
// close time reports, and update our clock.
|
||||
JLOG (j_.info())
|
||||
<< "We closed at " << closeTime_.time_since_epoch().count();
|
||||
if ((mode == Mode::proposing || mode == Mode::observing) && !consensusFail)
|
||||
{
|
||||
auto closeTime = rawCloseTimes.self;
|
||||
|
||||
JLOG(j_.info()) << "We closed at "
|
||||
<< closeTime.time_since_epoch().count();
|
||||
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;
|
||||
|
||||
for (auto const& p : closeTimes_)
|
||||
for (auto const& p : rawCloseTimes.peers)
|
||||
{
|
||||
// FIXME: Use median, not average
|
||||
JLOG(j_.info())
|
||||
<< std::to_string(p.second)
|
||||
<< " time votes for "
|
||||
<< std::to_string(p.second) << " time votes for "
|
||||
<< std::to_string(p.first.time_since_epoch().count());
|
||||
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
|
||||
@@ -581,31 +565,20 @@ RCLConsensus::accept(
|
||||
using duration = std::chrono::duration<std::int32_t>;
|
||||
using time_point = std::chrono::time_point<NetClock, duration>;
|
||||
auto offset = time_point{closeTotal} -
|
||||
std::chrono::time_point_cast<duration>(closeTime_);
|
||||
JLOG (j_.info())
|
||||
<< "Our close offset is estimated at "
|
||||
<< offset.count() << " (" << closeCount << ")";
|
||||
std::chrono::time_point_cast<duration>(closeTime);
|
||||
JLOG(j_.info()) << "Our close offset is estimated at " << offset.count()
|
||||
<< " (" << closeCount << ")";
|
||||
|
||||
app_.timeKeeper().adjustCloseTime(offset);
|
||||
}
|
||||
|
||||
return validating_;
|
||||
}
|
||||
|
||||
void
|
||||
RCLConsensus::endConsensus(bool correctLCL)
|
||||
{
|
||||
app_.getOPs ().endConsensus (correctLCL);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
RCLConsensus::notify(
|
||||
protocol::NodeEvent ne,
|
||||
RCLCxLedger const& ledger,
|
||||
bool haveCorrectLCL)
|
||||
{
|
||||
|
||||
protocol::TMStatusChange s;
|
||||
|
||||
if (!haveCorrectLCL)
|
||||
@@ -615,10 +588,11 @@ RCLConsensus::notify(
|
||||
|
||||
s.set_ledgerseq(ledger.seq());
|
||||
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);
|
||||
s.set_ledgerhash (ledger.id().begin (),
|
||||
std::decay_t<decltype(ledger.id())>::bytes);
|
||||
s.set_ledgerhash(
|
||||
ledger.id().begin(), std::decay_t<decltype(ledger.id())>::bytes);
|
||||
|
||||
std::uint32_t uMin, uMax;
|
||||
if (!ledgerMaster_.getFullValidatedRange(uMin, uMax))
|
||||
@@ -633,13 +607,11 @@ RCLConsensus::notify(
|
||||
}
|
||||
s.set_firstseq(uMin);
|
||||
s.set_lastseq(uMax);
|
||||
app_.overlay ().foreach (send_always (
|
||||
std::make_shared <Message> (
|
||||
s, protocol::mtSTATUS_CHANGE)));
|
||||
app_.overlay().foreach (
|
||||
send_always(std::make_shared<Message>(s, protocol::mtSTATUS_CHANGE)));
|
||||
JLOG(j_.trace()) << "send status change to peer";
|
||||
}
|
||||
|
||||
|
||||
/** Apply a set of transactions to a ledger.
|
||||
|
||||
Typically the txFilter is used to reject transactions
|
||||
@@ -663,7 +635,6 @@ applyTransactions (
|
||||
auto& set = *(cSet.map_);
|
||||
CanonicalTXSet retriableTxs(set.getHash().as_uint256());
|
||||
|
||||
|
||||
for (auto const& item : set)
|
||||
{
|
||||
if (!txFilter(item.key()))
|
||||
@@ -671,8 +642,7 @@ applyTransactions (
|
||||
|
||||
// The transaction wan't filtered
|
||||
// Add it to the set to be tried in canonical order
|
||||
JLOG (j.debug()) <<
|
||||
"Processing candidate transaction: " << item.key();
|
||||
JLOG(j.debug()) << "Processing candidate transaction: " << item.key();
|
||||
try
|
||||
{
|
||||
retriableTxs.insert(
|
||||
@@ -688,8 +658,7 @@ applyTransactions (
|
||||
// Attempt to apply all of the retriable transactions
|
||||
for (int pass = 0; pass < LEDGER_TOTAL_PASSES; ++pass)
|
||||
{
|
||||
JLOG (j.debug()) << "Pass: " << pass << " Txns: "
|
||||
<< retriableTxs.size ()
|
||||
JLOG(j.debug()) << "Pass: " << pass << " Txns: " << retriableTxs.size()
|
||||
<< (certainRetry ? " retriable" : " final");
|
||||
int changes = 0;
|
||||
|
||||
@@ -699,8 +668,8 @@ applyTransactions (
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (applyTransaction (app, view,
|
||||
*it->second, certainRetry, tapNO_CHECK_SIGN, j))
|
||||
switch (applyTransaction(
|
||||
app, view, *it->second, certainRetry, tapNO_CHECK_SIGN, j))
|
||||
{
|
||||
case ApplyResult::Success:
|
||||
it = retriableTxs.erase(it);
|
||||
@@ -717,14 +686,13 @@ applyTransactions (
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
JLOG (j.warn())
|
||||
<< "Transaction throws";
|
||||
JLOG(j.warn()) << "Transaction throws";
|
||||
it = retriableTxs.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
JLOG (j.debug()) << "Pass: "
|
||||
<< pass << " finished " << changes << " changes";
|
||||
JLOG(j.debug()) << "Pass: " << pass << " finished " << changes
|
||||
<< " changes";
|
||||
|
||||
// A non-retry pass made no changes
|
||||
if (!changes && !certainRetry)
|
||||
@@ -748,7 +716,6 @@ RCLConsensus::buildLCL(
|
||||
NetClock::time_point closeTime,
|
||||
bool closeTimeCorrect,
|
||||
NetClock::duration closeResolution,
|
||||
NetClock::time_point now,
|
||||
std::chrono::milliseconds roundTime,
|
||||
CanonicalTXSet& retriableTxs)
|
||||
{
|
||||
@@ -760,14 +727,13 @@ RCLConsensus::buildLCL(
|
||||
closeTimeCorrect = ((replay->closeFlags_ & sLCF_NoConsensusTime) == 0);
|
||||
}
|
||||
|
||||
JLOG (j_.debug())
|
||||
<< "Report: TxSt = " << set.id ()
|
||||
<< ", close " << closeTime.time_since_epoch().count()
|
||||
JLOG(j_.debug()) << "Report: TxSt = " << set.id() << ", close "
|
||||
<< closeTime.time_since_epoch().count()
|
||||
<< (closeTimeCorrect ? "" : "X");
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -780,8 +746,7 @@ RCLConsensus::buildLCL(
|
||||
|
||||
// Set up to write SHAMap changes to our database,
|
||||
// perform updates, extract changes
|
||||
JLOG (j_.debug())
|
||||
<< "Applying consensus set transactions to the"
|
||||
JLOG(j_.debug()) << "Applying consensus set transactions to the"
|
||||
<< " last closed ledger";
|
||||
|
||||
{
|
||||
@@ -791,21 +756,19 @@ RCLConsensus::buildLCL(
|
||||
{
|
||||
// Special case, we are replaying a ledger close
|
||||
for (auto& tx : replay->txns_)
|
||||
applyTransaction (app_, accum, *tx.second,
|
||||
false, tapNO_CHECK_SIGN, j_);
|
||||
applyTransaction(
|
||||
app_, accum, *tx.second, false, tapNO_CHECK_SIGN, j_);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal case, we are not replaying a ledger close
|
||||
retriableTxs = applyTransactions (app_, set, accum,
|
||||
[&buildLCL](uint256 const& txID)
|
||||
{
|
||||
retriableTxs = applyTransactions(
|
||||
app_, set, accum, [&buildLCL](uint256 const& txID) {
|
||||
return !buildLCL->txExists(txID);
|
||||
});
|
||||
}
|
||||
// Update fee computations.
|
||||
app_.getTxQ().processClosedLedger(app_, accum,
|
||||
roundTime > 5s);
|
||||
app_.getTxQ().processClosedLedger(app_, accum, roundTime > 5s);
|
||||
accum.apply(*buildLCL);
|
||||
}
|
||||
|
||||
@@ -823,51 +786,42 @@ RCLConsensus::buildLCL(
|
||||
hotACCOUNT_NODE, buildLCL->info().seq);
|
||||
int tmf = buildLCL->txMap().flushDirty(
|
||||
hotTRANSACTION_NODE, buildLCL->info().seq);
|
||||
JLOG (j_.debug()) << "Flushed " <<
|
||||
asf << " accounts and " <<
|
||||
tmf << " transaction nodes";
|
||||
JLOG(j_.debug()) << "Flushed " << asf << " accounts and " << tmf
|
||||
<< " transaction nodes";
|
||||
}
|
||||
buildLCL->unshare();
|
||||
|
||||
// Accept ledger
|
||||
buildLCL->setAccepted(closeTime, closeResolution,
|
||||
closeTimeCorrect, app_.config());
|
||||
buildLCL->setAccepted(
|
||||
closeTime, closeResolution, closeTimeCorrect, app_.config());
|
||||
|
||||
// And stash the ledger in the ledger master
|
||||
if (ledgerMaster_.storeLedger(buildLCL))
|
||||
JLOG (j_.debug())
|
||||
<< "Consensus built ledger we already had";
|
||||
JLOG(j_.debug()) << "Consensus built ledger we already had";
|
||||
else if (app_.getInboundLedgers().find(buildLCL->info().hash))
|
||||
JLOG (j_.debug())
|
||||
<< "Consensus built ledger we were acquiring";
|
||||
JLOG(j_.debug()) << "Consensus built ledger we were acquiring";
|
||||
else
|
||||
JLOG (j_.debug())
|
||||
<< "Consensus built new ledger";
|
||||
JLOG(j_.debug()) << "Consensus built new ledger";
|
||||
return RCLCxLedger{std::move(buildLCL)};
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
RCLConsensus::validate(
|
||||
RCLCxLedger const & ledger,
|
||||
NetClock::time_point now,
|
||||
bool proposing)
|
||||
RCLConsensus::validate(RCLCxLedger const& ledger, bool proposing)
|
||||
{
|
||||
auto validationTime = now;
|
||||
auto validationTime = app_.timeKeeper().closeTime();
|
||||
if (validationTime <= lastValidationTime_)
|
||||
validationTime = lastValidationTime_ + 1s;
|
||||
lastValidationTime_ = validationTime;
|
||||
|
||||
// Build validation
|
||||
auto v = std::make_shared<STValidation> (ledger.id(),
|
||||
validationTime, valPublic_, proposing);
|
||||
auto v = std::make_shared<STValidation>(
|
||||
ledger.id(), validationTime, valPublic_, proposing);
|
||||
v->setFieldU32(sfLedgerSequence, ledger.seq());
|
||||
|
||||
// Add our load fee to the validation
|
||||
auto const& feeTrack = app_.getFeeTrack();
|
||||
std::uint32_t fee = std::max(
|
||||
feeTrack.getLocalFee(),
|
||||
feeTrack.getClusterFee());
|
||||
std::uint32_t fee =
|
||||
std::max(feeTrack.getLocalFee(), feeTrack.getClusterFee());
|
||||
|
||||
if (fee > feeTrack.getLoadBase())
|
||||
v->setFieldU32(sfLoadFee, fee);
|
||||
@@ -892,6 +846,14 @@ RCLConsensus::validate(
|
||||
app_.overlay().send(val);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
RCLConsensus::getJson(bool full) const
|
||||
{
|
||||
auto ret = Base::getJson(full);
|
||||
ret["validating"] = validating_;
|
||||
return ret;
|
||||
}
|
||||
|
||||
PublicKey const&
|
||||
RCLConsensus::getValidationPublicKey() const
|
||||
{
|
||||
@@ -899,11 +861,73 @@ RCLConsensus::getValidationPublicKey () const
|
||||
}
|
||||
|
||||
void
|
||||
RCLConsensus::setValidationKeys (SecretKey const& valSecret,
|
||||
RCLConsensus::setValidationKeys(
|
||||
SecretKey const& valSecret,
|
||||
PublicKey const& valPublic)
|
||||
{
|
||||
valSecret_ = valSecret;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
@@ -21,19 +21,19 @@
|
||||
#define RIPPLE_APP_CONSENSUS_RCLCONSENSUS_H_INCLUDED
|
||||
|
||||
#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/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/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 {
|
||||
|
||||
@@ -50,24 +50,20 @@ struct RCLCxTraits
|
||||
using NodeID_t = NodeID;
|
||||
//! TxSet type presented to Consensus
|
||||
using TxSet_t = RCLTxSet;
|
||||
//! MissingTxException type neede by Consensus
|
||||
using MissingTxException_t = SHAMapMissingNode;
|
||||
};
|
||||
|
||||
|
||||
/** Adapts the generic Consensus algorithm for use by RCL.
|
||||
|
||||
@note The enabled_shared_from_this base allows the application to properly
|
||||
create a shared instance of RCLConsensus for use in the accept logic..
|
||||
*/
|
||||
class RCLConsensus : public Consensus<RCLConsensus, RCLCxTraits>
|
||||
, public std::enable_shared_from_this <RCLConsensus>
|
||||
, public CountedObject <RCLConsensus>
|
||||
class RCLConsensus final : public Consensus<RCLConsensus, RCLCxTraits>,
|
||||
public std::enable_shared_from_this<RCLConsensus>,
|
||||
public CountedObject<RCLConsensus>
|
||||
{
|
||||
using Base = Consensus<RCLConsensus, RCLCxTraits>;
|
||||
using Base::accept;
|
||||
public:
|
||||
|
||||
public:
|
||||
//! Constructor
|
||||
RCLConsensus(
|
||||
Application& app,
|
||||
@@ -77,10 +73,17 @@ public:
|
||||
InboundTransactions& inboundTransactions,
|
||||
typename Base::clock_type const& clock,
|
||||
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
|
||||
use in consensus.
|
||||
@@ -91,6 +94,50 @@ public:
|
||||
void
|
||||
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 */
|
||||
PublicKey const&
|
||||
getValidationPublicKey() const;
|
||||
@@ -105,17 +152,6 @@ private:
|
||||
//-------------------------------------------------------------------------
|
||||
// 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.
|
||||
|
||||
If not available, asynchronously acquires from the network.
|
||||
@@ -144,10 +180,10 @@ private:
|
||||
|
||||
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
|
||||
relay(DisputedTx <RCLCxTx, NodeID> const & dispute);
|
||||
relay(RCLCxTx const& tx);
|
||||
|
||||
/** Acquire the transaction set associated with a proposal.
|
||||
|
||||
@@ -173,7 +209,8 @@ private:
|
||||
std::size_t
|
||||
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.
|
||||
@return The number of validating peers that have validated a ledger
|
||||
@@ -189,115 +226,74 @@ private:
|
||||
void
|
||||
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.
|
||||
*/
|
||||
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 priorLedger Prior ledger used in consensus
|
||||
@param believedCorrect Whether consensus believes currentLedger is LCL
|
||||
@param ledgerID ID of previous ledger used by consensus
|
||||
@param ledger Previous ledger consensus has available
|
||||
@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
|
||||
getLCL (
|
||||
uint256 const& currentLedger,
|
||||
uint256 const& priorLedger,
|
||||
bool believedCorrect);
|
||||
getPrevLedger(
|
||||
uint256 ledgerID,
|
||||
RCLCxLedger const& ledger,
|
||||
Mode mode);
|
||||
|
||||
|
||||
/** Notification that the ledger has closed.
|
||||
/** Close the open ledger and return initial consensus position.
|
||||
|
||||
@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
|
||||
onClose(RCLCxLedger const & ledger, bool haveCorrectLCL);
|
||||
Result
|
||||
onClose(
|
||||
RCLCxLedger const& ledger,
|
||||
NetClock::time_point const& closeTime,
|
||||
Mode mode);
|
||||
|
||||
/** Create our initial position of transactions to accept in this round
|
||||
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
|
||||
/** Process the accepted ledger.
|
||||
|
||||
Accepting a ledger may be expensive, so this function can dispatch
|
||||
that call to another thread if desired and must call the accept
|
||||
method of the generic consensus algorithm.
|
||||
that call to another thread if desired.
|
||||
|
||||
@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
|
||||
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.
|
||||
|
||||
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
|
||||
@ref onAccept
|
||||
*/
|
||||
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)
|
||||
@@ -308,7 +304,22 @@ private:
|
||||
@param haveCorrectLCL Whether we believ we have the correct LCL.
|
||||
*/
|
||||
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.
|
||||
|
||||
@@ -323,7 +334,6 @@ private:
|
||||
@param closeTime The the ledger closed
|
||||
@param closeTimeCorrect Whether consensus agreed on 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 retriableTxs Populate with transactions to retry in next round
|
||||
@return The newly built ledger
|
||||
@@ -335,15 +345,12 @@ private:
|
||||
NetClock::time_point closeTime,
|
||||
bool closeTimeCorrect,
|
||||
NetClock::duration closeResolution,
|
||||
NetClock::time_point now,
|
||||
std::chrono::milliseconds roundTime,
|
||||
CanonicalTXSet & retriableTxs
|
||||
);
|
||||
CanonicalTXSet& retriableTxs);
|
||||
|
||||
/** Validate the given ledger and share with peers as necessary
|
||||
|
||||
@param ledger The ledger to validate
|
||||
@param now Current network adjusted time
|
||||
@param proposing Whether we were proposing transactions while generating
|
||||
this ledger. If we are not proposing, a validation
|
||||
can still be sent to inform peers that we know we
|
||||
@@ -351,10 +358,7 @@ private:
|
||||
around and trying to catch up.
|
||||
*/
|
||||
void
|
||||
validate(
|
||||
RCLCxLedger const & ledger,
|
||||
NetClock::time_point now,
|
||||
bool proposing);
|
||||
validate(RCLCxLedger const& ledger, bool proposing);
|
||||
|
||||
//!-------------------------------------------------------------------------
|
||||
Application& app_;
|
||||
@@ -376,8 +380,10 @@ private:
|
||||
using PeerPositions = hash_map<NodeID, std::deque<RCLCxPeerPos::pointer>>;
|
||||
PeerPositions peerPositions_;
|
||||
std::mutex peerPositionsLock_;
|
||||
};
|
||||
|
||||
bool validating_ = false;
|
||||
bool simulating_ = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
#define RIPPLE_APP_CONSENSUS_RCLCXLEDGER_H_INCLUDED
|
||||
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <ripple/app/ledger/LedgerToJson.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <ripple/protocol/RippleLedgerHash.h>
|
||||
#include <memory>
|
||||
|
||||
@@ -50,7 +50,9 @@ public:
|
||||
|
||||
@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.
|
||||
auto const&
|
||||
@@ -114,8 +116,6 @@ public:
|
||||
a new ledger from a readView?
|
||||
*/
|
||||
std::shared_ptr<Ledger const> ledger_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/consensus/RCLCxPeerPos.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/HashPrefix.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/Serializer.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -38,9 +38,9 @@ RCLCxPeerPos::RCLCxPeerPos (
|
||||
, publicKey_{publicKey}
|
||||
, signature_{signature}
|
||||
{
|
||||
|
||||
}
|
||||
uint256 RCLCxPeerPos::getSigningHash () const
|
||||
uint256
|
||||
RCLCxPeerPos::getSigningHash() const
|
||||
{
|
||||
return sha512Half(
|
||||
HashPrefix::proposal,
|
||||
@@ -50,28 +50,25 @@ uint256 RCLCxPeerPos::getSigningHash () const
|
||||
proposal().position());
|
||||
}
|
||||
|
||||
bool RCLCxPeerPos::checkSign () const
|
||||
bool
|
||||
RCLCxPeerPos::checkSign() const
|
||||
{
|
||||
return verifyDigest (
|
||||
publicKey_,
|
||||
getSigningHash(),
|
||||
signature_,
|
||||
false);
|
||||
return verifyDigest(publicKey_, getSigningHash(), signature_, false);
|
||||
}
|
||||
|
||||
Json::Value RCLCxPeerPos::getJson () const
|
||||
Json::Value
|
||||
RCLCxPeerPos::getJson() const
|
||||
{
|
||||
auto ret = proposal().getJson();
|
||||
|
||||
if (publicKey_.size())
|
||||
ret[jss::peer_id] = toBase58 (
|
||||
TokenType::TOKEN_NODE_PUBLIC,
|
||||
publicKey_);
|
||||
ret[jss::peer_id] = toBase58(TokenType::TOKEN_NODE_PUBLIC, publicKey_);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint256 proposalUniqueId (
|
||||
uint256
|
||||
proposalUniqueId(
|
||||
uint256 const& proposeHash,
|
||||
uint256 const& previousLedger,
|
||||
std::uint32_t proposeSeq,
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
#include <ripple/basics/CountedObject.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/protocol/HashPrefix.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/SecretKey.h>
|
||||
#include <ripple/beast/hash/hash_append.h>
|
||||
#include <ripple/consensus/ConsensusProposal.h>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -38,18 +38,20 @@ namespace ripple {
|
||||
|
||||
Carries a ConsensusProposal signed by a peer.
|
||||
*/
|
||||
class RCLCxPeerPos
|
||||
: public CountedObject <RCLCxPeerPos>
|
||||
class RCLCxPeerPos : public CountedObject<RCLCxPeerPos>
|
||||
{
|
||||
public:
|
||||
static char const* getCountedObjectName () { return "RCLCxPeerPos"; }
|
||||
static char const*
|
||||
getCountedObjectName()
|
||||
{
|
||||
return "RCLCxPeerPos";
|
||||
}
|
||||
using pointer = std::shared_ptr<RCLCxPeerPos>;
|
||||
using ref = const pointer&;
|
||||
|
||||
//< The type of the proposed position
|
||||
using Proposal = ConsensusProposal<NodeID, uint256, uint256>;
|
||||
|
||||
|
||||
/** Constructor
|
||||
|
||||
Constructs a signed peer position.
|
||||
@@ -67,31 +69,37 @@ public:
|
||||
Proposal&& proposal);
|
||||
|
||||
//! Create the signing hash for the proposal
|
||||
uint256 getSigningHash () const;
|
||||
uint256
|
||||
getSigningHash() const;
|
||||
|
||||
//! Verify the signing hash of the proposal
|
||||
bool checkSign () const;
|
||||
bool
|
||||
checkSign() const;
|
||||
|
||||
//! Signature of the proposal (not necessarily verified)
|
||||
Slice getSignature () const
|
||||
Slice
|
||||
getSignature() const
|
||||
{
|
||||
return signature_;
|
||||
}
|
||||
|
||||
//! Public key of peer that sent the proposal
|
||||
PublicKey const& getPublicKey () const
|
||||
PublicKey const&
|
||||
getPublicKey() const
|
||||
{
|
||||
return publicKey_;
|
||||
}
|
||||
|
||||
//! ?????
|
||||
uint256 const& getSuppressionID () const
|
||||
uint256 const&
|
||||
getSuppressionID() const
|
||||
{
|
||||
return mSuppression;
|
||||
}
|
||||
|
||||
//! The consensus proposal
|
||||
Proposal const & proposal() const
|
||||
Proposal const&
|
||||
proposal() const
|
||||
{
|
||||
return proposal_;
|
||||
}
|
||||
@@ -105,7 +113,8 @@ public:
|
||||
/// @endcond
|
||||
|
||||
//! JSON representation of proposal
|
||||
Json::Value getJson () const;
|
||||
Json::Value
|
||||
getJson() const;
|
||||
|
||||
private:
|
||||
template <class Hasher>
|
||||
@@ -142,7 +151,8 @@ private:
|
||||
@param publicKey Signer's public key
|
||||
@param signature Proposal signature
|
||||
*/
|
||||
uint256 proposalUniqueId (
|
||||
uint256
|
||||
proposalUniqueId(
|
||||
uint256 const& proposeHash,
|
||||
uint256 const& previousLedger,
|
||||
std::uint32_t proposeSeq,
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
#ifndef 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/protocol/UintTypes.h>
|
||||
#include <ripple/shamap/SHAMap.h>
|
||||
#include <ripple/app/misc/CanonicalTXSet.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -43,7 +43,8 @@ public:
|
||||
@param txn The transaction to wrap
|
||||
*/
|
||||
RCLCxTx(SHAMapItem const& txn) : tx_{txn}
|
||||
{ }
|
||||
{
|
||||
}
|
||||
|
||||
//! The unique identifier/hash of the transaction
|
||||
ID const&
|
||||
@@ -77,10 +78,8 @@ public:
|
||||
std::shared_ptr<SHAMap> map_;
|
||||
|
||||
public:
|
||||
MutableTxSet(RCLTxSet const & src)
|
||||
: map_{ src.map_->snapShot(true) }
|
||||
MutableTxSet(RCLTxSet const& src) : map_{src.map_->snapShot(true)}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/** Insert a new transaction into the set.
|
||||
@@ -92,8 +91,7 @@ public:
|
||||
insert(Tx const& t)
|
||||
{
|
||||
return map_->addItem(
|
||||
SHAMapItem{ t.id(), t.tx_.peekData() },
|
||||
true, false);
|
||||
SHAMapItem{t.id(), t.tx_.peekData()}, true, false);
|
||||
}
|
||||
|
||||
/** Remove a transaction from the set.
|
||||
@@ -112,8 +110,7 @@ public:
|
||||
|
||||
@param m SHAMap to wrap
|
||||
*/
|
||||
RCLTxSet (std::shared_ptr<SHAMap> m)
|
||||
: map_{ std::move(m) }
|
||||
RCLTxSet(std::shared_ptr<SHAMap> m) : map_{std::move(m)}
|
||||
{
|
||||
assert(map_);
|
||||
}
|
||||
@@ -122,10 +119,8 @@ public:
|
||||
|
||||
@param m MutableTxSet that will become fixed
|
||||
*/
|
||||
RCLTxSet(MutableTxSet const & m)
|
||||
: map_{m.map_->snapShot(false)}
|
||||
RCLTxSet(MutableTxSet const& m) : map_{m.map_->snapShot(false)}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/** Test if a transaction is in the set.
|
||||
@@ -163,7 +158,8 @@ public:
|
||||
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
|
||||
@return Map of transactions in this set and `j` but not both. The key
|
||||
@@ -182,7 +178,8 @@ public:
|
||||
std::map<uint256, bool> ret;
|
||||
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));
|
||||
|
||||
ret[item.first] = static_cast<bool>(item.second.first);
|
||||
@@ -193,6 +190,5 @@ public:
|
||||
//! The SHAMap representing the transactions.
|
||||
std::shared_ptr<SHAMap> map_;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -322,7 +322,7 @@ private:
|
||||
|
||||
public:
|
||||
bool beginConsensus (uint256 const& networkClosed) override;
|
||||
void endConsensus (bool correctLCL) override;
|
||||
void endConsensus () override;
|
||||
void setStandAlone () override
|
||||
{
|
||||
setMode (omFULL);
|
||||
@@ -1525,7 +1525,7 @@ bool NetworkOPsImp::beginConsensus (uint256 const& networkClosed)
|
||||
|
||||
uint256 NetworkOPsImp::getConsensusLCL ()
|
||||
{
|
||||
return mConsensus->LCL ();
|
||||
return mConsensus->prevLedgerID ();
|
||||
}
|
||||
|
||||
void NetworkOPsImp::processTrustedProposal (
|
||||
@@ -1565,7 +1565,7 @@ NetworkOPsImp::mapComplete (
|
||||
RCLTxSet{map});
|
||||
}
|
||||
|
||||
void NetworkOPsImp::endConsensus (bool correctLCL)
|
||||
void NetworkOPsImp::endConsensus ()
|
||||
{
|
||||
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 ());
|
||||
|
||||
Json::Value lastClose = Json::objectValue;
|
||||
lastClose[jss::proposers] = mConsensus->getLastCloseProposers();
|
||||
lastClose[jss::proposers] = Json::UInt(mConsensus->prevProposers());
|
||||
|
||||
if (human)
|
||||
{
|
||||
lastClose[jss::converge_time_s] =
|
||||
std::chrono::duration<double>{
|
||||
mConsensus->getLastConvergeDuration()}.count();
|
||||
mConsensus->prevRoundTime()}.count();
|
||||
}
|
||||
else
|
||||
{
|
||||
lastClose[jss::converge_time] =
|
||||
Json::Int (mConsensus->getLastConvergeDuration().count());
|
||||
Json::Int (mConsensus->prevRoundTime().count());
|
||||
}
|
||||
|
||||
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
|
||||
// API in Consensus?
|
||||
beginConsensus (m_ledgerMaster.getClosedLedger()->info().hash);
|
||||
mConsensus->simulate (
|
||||
app_.timeKeeper().closeTime(),
|
||||
consensusDelay);
|
||||
mConsensus->simulate (app_.timeKeeper().closeTime(), consensusDelay);
|
||||
return m_ledgerMaster.getCurrentLedger ()->info().seq;
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ public:
|
||||
|
||||
// network state machine
|
||||
virtual bool beginConsensus (uint256 const& netLCL) = 0;
|
||||
virtual void endConsensus (bool correctLCL) = 0;
|
||||
virtual void endConsensus () = 0;
|
||||
virtual void setStandAlone () = 0;
|
||||
virtual void setStateTimer () = 0;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -19,13 +19,12 @@
|
||||
#ifndef 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/protocol/JsonFields.h>
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple
|
||||
{
|
||||
namespace ripple {
|
||||
/** Represents a proposed position taken during a round of consensus.
|
||||
|
||||
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
|
||||
under consideration during this round of consensus
|
||||
*/
|
||||
template <
|
||||
class NodeID_t,
|
||||
class LedgerID_t,
|
||||
class Position_t>
|
||||
template <class NodeID_t, class LedgerID_t, class Position_t>
|
||||
class ConsensusProposal
|
||||
{
|
||||
public:
|
||||
@@ -64,7 +60,6 @@ public:
|
||||
//< Sequence number when a peer wants to bow out and leave consensus
|
||||
static std::uint32_t const seqLeave = 0xffffffff;
|
||||
|
||||
|
||||
/** Constructor
|
||||
|
||||
@param prevLedger The previous ledger this proposal is building on.
|
||||
@@ -88,7 +83,6 @@ public:
|
||||
, proposeSeq_(seq)
|
||||
, nodeID_(nodeID)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//! Identifying which peer took this position.
|
||||
@@ -163,29 +157,23 @@ public:
|
||||
}
|
||||
|
||||
/** 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 newCloseTime The new close time.
|
||||
@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.
|
||||
@param now the time The new position was taken
|
||||
*/
|
||||
bool
|
||||
void
|
||||
changePosition(
|
||||
Position_t const& newPosition,
|
||||
NetClock::time_point newCloseTime,
|
||||
NetClock::time_point now)
|
||||
{
|
||||
if (proposeSeq_ == seqLeave)
|
||||
return false;
|
||||
|
||||
position_ = newPosition;
|
||||
closeTime_ = newCloseTime;
|
||||
time_ = now;
|
||||
if (proposeSeq_ != seqLeave)
|
||||
++proposeSeq_;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Leave consensus
|
||||
@@ -216,13 +204,13 @@ public:
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
//! Unique identifier of prior ledger this proposal is based on
|
||||
LedgerID_t previousLedger_;
|
||||
|
||||
@@ -240,23 +228,17 @@ private:
|
||||
|
||||
//! The identifier of the node taking this position
|
||||
NodeID_t nodeID_;
|
||||
|
||||
};
|
||||
|
||||
template <class NodeID_t,
|
||||
class LedgerID_t,
|
||||
class Position_t>
|
||||
template <class NodeID_t, class LedgerID_t, class Position_t>
|
||||
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)
|
||||
{
|
||||
return a.nodeID() == b.nodeID() &&
|
||||
a.proposeSeq() == b.proposeSeq() &&
|
||||
a.prevLedger() == b.prevLedger() &&
|
||||
a.position() == b.position() &&
|
||||
a.closeTime() == b.closeTime() &&
|
||||
a.seenTime() == b.seenTime();
|
||||
return a.nodeID() == b.nodeID() && a.proposeSeq() == b.proposeSeq() &&
|
||||
a.prevLedger() == b.prevLedger() && a.position() == b.position() &&
|
||||
a.closeTime() == b.closeTime() && a.seenTime() == b.seenTime();
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_APP_CONSENSUS_DISPUTEDTX_H_INCLUDED
|
||||
#define RIPPLE_APP_CONSENSUS_DISPUTEDTX_H_INCLUDED
|
||||
#ifndef RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
|
||||
#define RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
|
||||
|
||||
#include <ripple/protocol/UintTypes.h>
|
||||
#include <ripple/protocol/Serializer.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/base_uint.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <ripple/consensus/LedgerTiming.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/protocol/Serializer.h>
|
||||
#include <ripple/protocol/UintTypes.h>
|
||||
#include <memory>
|
||||
|
||||
namespace ripple {
|
||||
@@ -48,6 +48,7 @@ template <class Tx_t, class NodeID_t>
|
||||
class DisputedTx
|
||||
{
|
||||
using TxID_t = typename Tx_t::ID;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
|
||||
@@ -55,20 +56,14 @@ public:
|
||||
@param ourVote Our vote on whether tx should be included
|
||||
@param j Journal for debugging
|
||||
*/
|
||||
DisputedTx (Tx_t const& tx,
|
||||
bool ourVote,
|
||||
beast::Journal j)
|
||||
: yays_ (0)
|
||||
, nays_ (0)
|
||||
, ourVote_ (ourVote)
|
||||
, tx_ (tx)
|
||||
, j_ (j)
|
||||
DisputedTx(Tx_t const& tx, bool ourVote, beast::Journal j)
|
||||
: yays_(0), nays_(0), ourVote_(ourVote), tx_(tx), j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
//! The unique id/hash of the disputed transaction.
|
||||
TxID_t
|
||||
const& ID () const
|
||||
TxID_t const&
|
||||
ID() const
|
||||
{
|
||||
return tx_.id();
|
||||
}
|
||||
@@ -81,8 +76,8 @@ public:
|
||||
}
|
||||
|
||||
//! The disputed transaction.
|
||||
Tx_t
|
||||
const& tx () const
|
||||
Tx_t const&
|
||||
tx() const
|
||||
{
|
||||
return tx_;
|
||||
}
|
||||
@@ -138,7 +133,8 @@ private:
|
||||
|
||||
// Track a peer's yes/no vote on a particular disputed tx_
|
||||
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));
|
||||
|
||||
@@ -147,22 +143,19 @@ void DisputedTx<Tx_t, NodeID_t>::setVote (NodeID_t const& peer, bool votesYes)
|
||||
{
|
||||
if (votesYes)
|
||||
{
|
||||
JLOG (j_.debug())
|
||||
<< "Peer " << peer << " votes YES on " << tx_.id();
|
||||
JLOG(j_.debug()) << "Peer " << peer << " votes YES on " << tx_.id();
|
||||
++yays_;
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG (j_.debug())
|
||||
<< "Peer " << peer << " votes NO on " << tx_.id();
|
||||
JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
|
||||
++nays_;
|
||||
}
|
||||
}
|
||||
// changes vote to yes
|
||||
else if (votesYes && !res.first->second)
|
||||
{
|
||||
JLOG (j_.debug())
|
||||
<< "Peer " << peer << " now votes YES on " << tx_.id();
|
||||
JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id();
|
||||
--nays_;
|
||||
++yays_;
|
||||
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
|
||||
else if (!votesYes && res.first->second)
|
||||
{
|
||||
JLOG (j_.debug())
|
||||
<< "Peer " << peer << " now votes NO on " << tx_.id();
|
||||
JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id();
|
||||
++nays_;
|
||||
--yays_;
|
||||
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
|
||||
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);
|
||||
|
||||
@@ -196,7 +189,8 @@ void DisputedTx<Tx_t, NodeID_t>::unVote (NodeID_t const& peer)
|
||||
}
|
||||
|
||||
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))
|
||||
return false;
|
||||
@@ -236,23 +230,23 @@ bool DisputedTx<Tx_t, NodeID_t>::updateVote (int percentTime, bool proposing)
|
||||
|
||||
if (newPosition == ourVote_)
|
||||
{
|
||||
JLOG (j_.info())
|
||||
<< "No change (" << (ourVote_ ? "YES" : "NO") << ") : weight "
|
||||
<< weight << ", percent " << percentTime;
|
||||
JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO")
|
||||
<< ") : weight " << weight << ", percent "
|
||||
<< percentTime;
|
||||
JLOG(j_.debug()) << getJson();
|
||||
return false;
|
||||
}
|
||||
|
||||
ourVote_ = newPosition;
|
||||
JLOG (j_.debug())
|
||||
<< "We now vote " << (ourVote_ ? "YES" : "NO")
|
||||
<< " on " << tx_.id();
|
||||
JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
|
||||
<< tx_.id();
|
||||
JLOG(j_.debug()) << getJson();
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/consensus/LedgerTiming.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/consensus/LedgerTiming.h>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
@@ -28,29 +28,29 @@ namespace ripple {
|
||||
bool
|
||||
shouldCloseLedger(
|
||||
bool anyTransactions,
|
||||
std::size_t previousProposers,
|
||||
std::size_t prevProposers,
|
||||
std::size_t proposersClosed,
|
||||
std::size_t proposersValidated,
|
||||
std::chrono::milliseconds previousTime,
|
||||
std::chrono::milliseconds currentTime, // Time since last ledger's close time
|
||||
std::chrono::milliseconds prevRoundTime,
|
||||
std::chrono::milliseconds
|
||||
timeSincePrevClose, // Time since last ledger's close time
|
||||
std::chrono::milliseconds openTime, // Time waiting to close this ledger
|
||||
std::chrono::seconds idleInterval,
|
||||
beast::Journal j)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
if ((previousTime < -1s) || (previousTime > 10min) ||
|
||||
(currentTime > 10min))
|
||||
if ((prevRoundTime < -1s) || (prevRoundTime > 10min) || (timeSincePrevClose > 10min))
|
||||
{
|
||||
// These are unexpected cases, we just close the ledger
|
||||
JLOG (j.warn()) <<
|
||||
"shouldCloseLedger Trans=" << (anyTransactions ? "yes" : "no") <<
|
||||
" Prop: " << previousProposers << "/" << proposersClosed <<
|
||||
" Secs: " << currentTime.count() << " (last: " <<
|
||||
previousTime.count() << ")";
|
||||
JLOG(j.warn()) << "shouldCloseLedger Trans="
|
||||
<< (anyTransactions ? "yes" : "no")
|
||||
<< " Prop: " << prevProposers << "/" << proposersClosed
|
||||
<< " Secs: " << timeSincePrevClose.count()
|
||||
<< " (last: " << prevRoundTime.count() << ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((proposersClosed + proposersValidated) > (previousProposers / 2))
|
||||
if ((proposersClosed + proposersValidated) > (prevProposers / 2))
|
||||
{
|
||||
// If more than half of the network has closed, we close
|
||||
JLOG(j.trace()) << "Others have closed";
|
||||
@@ -60,24 +60,22 @@ shouldCloseLedger (
|
||||
if (!anyTransactions)
|
||||
{
|
||||
// Only close at the end of the idle interval
|
||||
return currentTime >= idleInterval; // normal idle
|
||||
return timeSincePrevClose >= idleInterval; // normal idle
|
||||
}
|
||||
|
||||
// Preserve minimum ledger open time
|
||||
if (openTime < LEDGER_MIN_CLOSE)
|
||||
{
|
||||
JLOG (j.debug()) <<
|
||||
"Must wait minimum time before closing";
|
||||
JLOG(j.debug()) << "Must wait minimum time before closing";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't let this ledger close more than twice as fast as the previous
|
||||
// ledger reached consensus so that slower validators can slow down
|
||||
// the network
|
||||
if (openTime < (previousTime / 2))
|
||||
if (openTime < (prevRoundTime / 2))
|
||||
{
|
||||
JLOG (j.debug()) <<
|
||||
"Ledger has not been open long enough";
|
||||
JLOG(j.debug()) << "Ledger has not been open long enough";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -86,10 +84,7 @@ shouldCloseLedger (
|
||||
}
|
||||
|
||||
bool
|
||||
checkConsensusReached (
|
||||
std::size_t agreeing,
|
||||
std::size_t total,
|
||||
bool count_self)
|
||||
checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self)
|
||||
{
|
||||
// If we are alone, we have a consensus
|
||||
if (total == 0)
|
||||
@@ -108,7 +103,7 @@ checkConsensusReached (
|
||||
|
||||
ConsensusState
|
||||
checkConsensus(
|
||||
std::size_t previousProposers,
|
||||
std::size_t prevProposers,
|
||||
std::size_t currentProposers,
|
||||
std::size_t currentAgree,
|
||||
std::size_t currentFinished,
|
||||
@@ -117,23 +112,22 @@ checkConsensus (
|
||||
bool proposing,
|
||||
beast::Journal j)
|
||||
{
|
||||
JLOG (j.trace()) <<
|
||||
"checkConsensus: prop=" << currentProposers <<
|
||||
"/" << previousProposers <<
|
||||
" agree=" << currentAgree << " validated=" << currentFinished <<
|
||||
" time=" << currentAgreeTime.count() << "/" << previousAgreeTime.count();
|
||||
JLOG(j.trace()) << "checkConsensus: prop=" << currentProposers << "/"
|
||||
<< prevProposers << " agree=" << currentAgree
|
||||
<< " validated=" << currentFinished
|
||||
<< " time=" << currentAgreeTime.count() << "/"
|
||||
<< previousAgreeTime.count();
|
||||
|
||||
if (currentAgreeTime <= LEDGER_MIN_CONSENSUS)
|
||||
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
|
||||
// rush: we may need more time.
|
||||
if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS))
|
||||
{
|
||||
JLOG (j.trace()) <<
|
||||
"too fast, not enough proposers";
|
||||
JLOG(j.trace()) << "too fast, not enough proposers";
|
||||
return ConsensusState::No;
|
||||
}
|
||||
}
|
||||
@@ -150,8 +144,7 @@ checkConsensus (
|
||||
// to declare consensus?
|
||||
if (checkConsensusReached(currentFinished, currentProposers, false))
|
||||
{
|
||||
JLOG (j.warn()) <<
|
||||
"We see no consensus, but 80% of nodes have moved on";
|
||||
JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on";
|
||||
return ConsensusState::MovedOn;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,13 @@
|
||||
#ifndef RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
|
||||
#define RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// These are protocol parameters used to control the behavior of the system and
|
||||
// they should not be changed arbitrarily.
|
||||
@@ -44,14 +43,8 @@ using namespace std::chrono_literals;
|
||||
std::chrono::seconds constexpr ledgerPossibleTimeResolutions[] =
|
||||
{10s, 20s, 30s, 60s, 90s, 120s};
|
||||
|
||||
#ifndef _MSC_VER
|
||||
//! Initial resolution of ledger close time.
|
||||
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)
|
||||
auto constexpr increaseLedgerTimeResolutionEvery = 8;
|
||||
@@ -165,8 +158,10 @@ getNextLedgerTimeResolution(
|
||||
|
||||
using namespace std::chrono;
|
||||
// Find the current resolution:
|
||||
auto iter = std::find (std::begin (ledgerPossibleTimeResolutions),
|
||||
std::end (ledgerPossibleTimeResolutions), previousResolution);
|
||||
auto iter = std::find(
|
||||
std::begin(ledgerPossibleTimeResolutions),
|
||||
std::end(ledgerPossibleTimeResolutions),
|
||||
previousResolution);
|
||||
assert(iter != std::end(ledgerPossibleTimeResolutions));
|
||||
|
||||
// This should never happen, but just as a precaution
|
||||
@@ -212,7 +207,6 @@ roundCloseTime(
|
||||
return closeTime - (closeTime.time_since_epoch() % closeResolution);
|
||||
}
|
||||
|
||||
|
||||
/** Calculate the effective ledger close time
|
||||
|
||||
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
|
||||
*/
|
||||
template <class time_point>
|
||||
time_point effectiveCloseTime(time_point closeTime,
|
||||
time_point
|
||||
effCloseTime(
|
||||
time_point closeTime,
|
||||
typename time_point::duration const resolution,
|
||||
time_point priorCloseTime)
|
||||
{
|
||||
@@ -231,8 +227,7 @@ time_point effectiveCloseTime(time_point closeTime,
|
||||
return closeTime;
|
||||
|
||||
return std::max<time_point>(
|
||||
roundCloseTime (closeTime, resolution),
|
||||
(priorCloseTime + 1s));
|
||||
roundCloseTime(closeTime, resolution), (priorCloseTime + 1s));
|
||||
}
|
||||
|
||||
/** 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.
|
||||
|
||||
@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 proposersValidated proposers who have validated the last closed
|
||||
ledger
|
||||
@param previousTime time for the previous ledger to reach consensus
|
||||
@param currentTime time since the previous ledger's
|
||||
(possibly rounded) close time
|
||||
@param openTime time waiting to close this ledger
|
||||
@param prevRoundTime time for the previous ledger to reach consensus
|
||||
@param timeSincePrevClose time since the previous ledger's (possibly rounded)
|
||||
close time
|
||||
@param openTime duration this ledger has been open
|
||||
@param idleInterval the network's desired idle interval
|
||||
@param j journal for logging
|
||||
*/
|
||||
bool
|
||||
shouldCloseLedger(
|
||||
bool anyTransactions,
|
||||
std::size_t previousProposers,
|
||||
std::size_t prevProposers,
|
||||
std::size_t proposersClosed,
|
||||
std::size_t proposersValidated,
|
||||
std::chrono::milliseconds previousTime,
|
||||
std::chrono::milliseconds currentTime, // Time since last ledger's close time
|
||||
std::chrono::milliseconds openTime, // Time waiting to close this ledger
|
||||
std::chrono::milliseconds prevRoundTime,
|
||||
std::chrono::milliseconds timeSincePrevClose,
|
||||
std::chrono::milliseconds openTime,
|
||||
std::chrono::seconds idleInterval,
|
||||
beast::Journal j);
|
||||
|
||||
|
||||
/** Determine 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
|
||||
*/
|
||||
bool
|
||||
checkConsensusReached (
|
||||
std::size_t agreeing,
|
||||
std::size_t total,
|
||||
bool count_self);
|
||||
checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self);
|
||||
|
||||
/** Whether we have or don't have a consensus */
|
||||
enum class ConsensusState
|
||||
{
|
||||
enum class ConsensusState {
|
||||
No, //!< We do not have consensus
|
||||
MovedOn, //!< The network has consensus without us
|
||||
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.
|
||||
|
||||
@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 currentAgree proposers who agree with us
|
||||
@param currentFinished proposers who have validated a ledger after this one
|
||||
@@ -303,7 +293,7 @@ enum class ConsensusState
|
||||
*/
|
||||
ConsensusState
|
||||
checkConsensus(
|
||||
std::size_t previousProposers,
|
||||
std::size_t prevProposers,
|
||||
std::size_t currentProposers,
|
||||
std::size_t currentAgree,
|
||||
std::size_t currentFinished,
|
||||
|
||||
@@ -17,22 +17,20 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/beast/clock/manual_clock.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <ripple/consensus/Consensus.h>
|
||||
#include <ripple/consensus/ConsensusProposal.h>
|
||||
#include <ripple/beast/clock/manual_clock.h>
|
||||
#include <boost/function_output_iterator.hpp>
|
||||
#include <test/csf.h>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class Consensus_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
|
||||
void
|
||||
testStandalone()
|
||||
{
|
||||
@@ -50,12 +48,13 @@ public:
|
||||
s.net.step();
|
||||
|
||||
// Inspect that the proper ledger was created
|
||||
BEAST_EXPECT(p.LCL().seq == 1);
|
||||
BEAST_EXPECT(p.LCL() == p.lastClosedLedger.id());
|
||||
BEAST_EXPECT(p.prevLedgerID().seq == 1);
|
||||
BEAST_EXPECT(p.prevLedgerID() == p.lastClosedLedger.id());
|
||||
BEAST_EXPECT(p.lastClosedLedger.id().txs.size() == 1);
|
||||
BEAST_EXPECT(p.lastClosedLedger.id().txs.find(Tx{ 1 })
|
||||
!= p.lastClosedLedger.id().txs.end());
|
||||
BEAST_EXPECT(p.getLastCloseProposers() == 0);
|
||||
BEAST_EXPECT(
|
||||
p.lastClosedLedger.id().txs.find(Tx{1}) !=
|
||||
p.lastClosedLedger.id().txs.end());
|
||||
BEAST_EXPECT(p.prevProposers() == 0);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -65,7 +64,8 @@ public:
|
||||
using namespace std::chrono;
|
||||
|
||||
auto tg = TrustGraph::makeComplete(5);
|
||||
Sim sim(tg,
|
||||
Sim sim(
|
||||
tg,
|
||||
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
||||
|
||||
// everyone submits their own ID as a TX and relay it to peers
|
||||
@@ -76,13 +76,13 @@ public:
|
||||
sim.run(1);
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
auto const &lgrID = p.LCL();
|
||||
auto const& lgrID = p.prevLedgerID();
|
||||
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)
|
||||
BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end());
|
||||
// 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);
|
||||
|
||||
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;
|
||||
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)
|
||||
{
|
||||
p.submit(Tx{p.id});
|
||||
@@ -116,49 +117,47 @@ public:
|
||||
|
||||
sim.run(1);
|
||||
|
||||
// Verify all peers have same LCL but are missing transaction 0 which
|
||||
// was not received by all peers before the ledger closed
|
||||
// Verify all peers have same LCL but are missing transaction 0
|
||||
// which was not received by all peers before the ledger closed
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
auto const &lgrID = p.LCL();
|
||||
auto const& lgrID = p.prevLedgerID();
|
||||
BEAST_EXPECT(lgrID.seq == 1);
|
||||
|
||||
|
||||
// If peer 0 is participating
|
||||
if (isParticipant)
|
||||
{
|
||||
BEAST_EXPECT(p.getLastCloseProposers()
|
||||
== sim.peers.size() - 1);
|
||||
// Peer 0 closes first because it sees a quorum of agreeing positions
|
||||
// from all other peers in one hop (1->0, 2->0, ..)
|
||||
// The other peers take an extra timer period before they find that
|
||||
// Peer 0 agrees with them ( 1->0->1, 2->0->2, ...)
|
||||
BEAST_EXPECT(p.prevProposers() == sim.peers.size() - 1);
|
||||
// Peer 0 closes first because it sees a quorum of agreeing
|
||||
// positions from all other peers in one hop (1->0, 2->0,
|
||||
// ..) The other peers take an extra timer period before
|
||||
// they find that Peer 0 agrees with them ( 1->0->1,
|
||||
// 2->0->2, ...)
|
||||
if (p.id != 0)
|
||||
BEAST_EXPECT(p.getLastConvergeDuration()
|
||||
> sim.peers[0].getLastConvergeDuration());
|
||||
BEAST_EXPECT(
|
||||
p.prevRoundTime() > sim.peers[0].prevRoundTime());
|
||||
}
|
||||
else // peer 0 is not participating
|
||||
{
|
||||
auto const proposers = p.getLastCloseProposers();
|
||||
auto const proposers = p.prevProposers();
|
||||
if (p.id == 0)
|
||||
BEAST_EXPECT(proposers == sim.peers.size() - 1);
|
||||
else
|
||||
BEAST_EXPECT(proposers == sim.peers.size() - 2);
|
||||
|
||||
// so all peers should have closed together
|
||||
BEAST_EXPECT(p.getLastConvergeDuration()
|
||||
== sim.peers[0].getLastConvergeDuration());
|
||||
BEAST_EXPECT(
|
||||
p.prevRoundTime() == sim.peers[0].prevRoundTime());
|
||||
}
|
||||
|
||||
|
||||
BEAST_EXPECT(lgrID.txs.find(Tx{0}) == lgrID.txs.end());
|
||||
for (std::uint32_t i = 1; i < sim.peers.size(); ++i)
|
||||
BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end());
|
||||
// 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 })
|
||||
!= sim.peers[0].openTxs.end());
|
||||
BEAST_EXPECT(
|
||||
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
|
||||
// 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
|
||||
// skew. Then no majority (1/3 < 1/2) of nodes will agree on an
|
||||
// actual close time.
|
||||
|
||||
auto tg = TrustGraph::makeComplete(6);
|
||||
Sim sim(tg,
|
||||
Sim sim(
|
||||
tg,
|
||||
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() >=
|
||||
PROPOSE_FRESHNESS)
|
||||
sim.run(1);
|
||||
@@ -226,7 +228,6 @@ public:
|
||||
// the wrong LCL at different phases of consensus
|
||||
for (auto validationDelay : {0s, LEDGER_MIN_CLOSE})
|
||||
{
|
||||
|
||||
// Consider 10 peers:
|
||||
// 0 1 2 3 4 5 6 7 8 9
|
||||
//
|
||||
@@ -241,7 +242,8 @@ public:
|
||||
// since nodes 2-4 will validate a different ledger.
|
||||
|
||||
// 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;
|
||||
unls.push_back({2, 3, 4, 5, 6, 7, 8, 9});
|
||||
@@ -252,7 +254,8 @@ public:
|
||||
|
||||
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.));
|
||||
|
||||
auto netDelay = round<milliseconds>(0.2 * LEDGER_GRANULARITY);
|
||||
@@ -261,7 +264,8 @@ public:
|
||||
// initial round to set prior state
|
||||
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)
|
||||
{
|
||||
p.validationDelay = validationDelay;
|
||||
@@ -272,11 +276,24 @@ public:
|
||||
p.openTxs.insert(Tx{1});
|
||||
}
|
||||
|
||||
// Run for 2 additional rounds
|
||||
// - One round to generate different ledgers
|
||||
// - One round to detect different prior ledgers (but still generate
|
||||
// wrong ones) and recover
|
||||
sim.run(2);
|
||||
// Run for additional rounds
|
||||
// With no validation delay, only 2 more rounds are needed.
|
||||
// 1. Round to generate different ledgers
|
||||
// 2. Round to detect different prior ledgers (but still generate
|
||||
// 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;
|
||||
for (auto& p : sim.peers)
|
||||
@@ -289,12 +306,19 @@ public:
|
||||
|
||||
BEAST_EXPECT(ledgers[0].size() == 1);
|
||||
BEAST_EXPECT(ledgers[1].size() == 1);
|
||||
if (validationDelay == 0s)
|
||||
{
|
||||
BEAST_EXPECT(ledgers[2].size() == 2);
|
||||
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.
|
||||
// This was added to trigger a scenario that previously crashed, in which
|
||||
// switchLCL switched from establish to open phase, but still processed
|
||||
@@ -330,7 +354,7 @@ public:
|
||||
|
||||
// Check all peers recovered
|
||||
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)
|
||||
{
|
||||
auto tg = TrustGraph::makeClique(numPeers, overlap);
|
||||
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
|
||||
sim.run(1);
|
||||
@@ -358,12 +384,11 @@ public:
|
||||
}
|
||||
sim.run(1);
|
||||
|
||||
|
||||
// See if the network forked
|
||||
bc::flat_set<Ledger::ID> ledgers;
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
ledgers.insert(p.LCL());
|
||||
ledgers.insert(p.prevLedgerID());
|
||||
}
|
||||
|
||||
// Fork should not happen for 40% or greater overlap
|
||||
@@ -377,7 +402,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
simClockSkew()
|
||||
{
|
||||
@@ -396,17 +420,13 @@ public:
|
||||
|
||||
// Disabled while continuing to understand testt.
|
||||
|
||||
|
||||
for (auto stagger : {800ms, 1600ms, 3200ms, 30000ms, 45000ms, 300000ms})
|
||||
{
|
||||
|
||||
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);
|
||||
}));
|
||||
|
||||
|
||||
// all transactions submitted before starting
|
||||
// Initial round to set prior state
|
||||
sim.run(1);
|
||||
@@ -415,7 +435,6 @@ public:
|
||||
{
|
||||
p.openTxs.insert(Tx{0});
|
||||
p.targetLedgers = p.completedLedgers + 1;
|
||||
|
||||
}
|
||||
|
||||
// stagger start of consensus
|
||||
@@ -426,11 +445,10 @@ public:
|
||||
}
|
||||
|
||||
// run until all peers have accepted all transactions
|
||||
sim.net.step_while([&]()
|
||||
{
|
||||
sim.net.step_while([&]() {
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
if(p.LCL().txs.size() != 1)
|
||||
if (p.prevLedgerID().txs.size() != 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -440,8 +458,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
simScaleFree()
|
||||
{
|
||||
@@ -459,12 +475,16 @@ public:
|
||||
|
||||
std::mt19937_64 rng;
|
||||
|
||||
auto tg = TrustGraph::makeRandomRanked(N, numUNLs,
|
||||
auto tg = TrustGraph::makeRandomRanked(
|
||||
N,
|
||||
numUNLs,
|
||||
PowerLawDistribution{1, 3},
|
||||
std::uniform_int_distribution<>{minUNLSize, maxUNLSize},
|
||||
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
|
||||
sim.run(1);
|
||||
@@ -475,20 +495,17 @@ public:
|
||||
// 50-50 chance to have seen a transaction
|
||||
if (u(rng) >= transProb)
|
||||
p.openTxs.insert(Tx{0});
|
||||
|
||||
}
|
||||
sim.run(1);
|
||||
|
||||
|
||||
// See if the network forked
|
||||
bc::flat_set<Ledger::ID> ledgers;
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
ledgers.insert(p.LCL());
|
||||
ledgers.insert(p.prevLedgerID());
|
||||
}
|
||||
|
||||
BEAST_EXPECT(ledgers.size() == 1);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -98,52 +98,48 @@ class BasicNetwork
|
||||
public:
|
||||
using peer_type = Peer;
|
||||
|
||||
using clock_type =
|
||||
beast::manual_clock<
|
||||
std::chrono::steady_clock>;
|
||||
using clock_type = beast::manual_clock<std::chrono::steady_clock>;
|
||||
|
||||
using duration =
|
||||
typename clock_type::duration;
|
||||
using duration = typename clock_type::duration;
|
||||
|
||||
using time_point =
|
||||
typename clock_type::time_point;
|
||||
using time_point = typename clock_type::time_point;
|
||||
|
||||
private:
|
||||
struct by_to_tag {};
|
||||
struct by_from_tag {};
|
||||
struct by_when_tag {};
|
||||
struct by_to_tag
|
||||
{
|
||||
};
|
||||
struct by_from_tag
|
||||
{
|
||||
};
|
||||
struct by_when_tag
|
||||
{
|
||||
};
|
||||
|
||||
using by_to_hook =
|
||||
boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<
|
||||
boost::intrusive::normal_link>,
|
||||
using by_to_hook = boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<boost::intrusive::normal_link>,
|
||||
boost::intrusive::tag<by_to_tag>>;
|
||||
|
||||
using by_from_hook =
|
||||
boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<
|
||||
boost::intrusive::normal_link>,
|
||||
using by_from_hook = boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<boost::intrusive::normal_link>,
|
||||
boost::intrusive::tag<by_from_tag>>;
|
||||
|
||||
using by_when_hook =
|
||||
boost::intrusive::set_base_hook<
|
||||
boost::intrusive::link_mode<
|
||||
boost::intrusive::normal_link>>;
|
||||
using by_when_hook = boost::intrusive::set_base_hook<
|
||||
boost::intrusive::link_mode<boost::intrusive::normal_link>>;
|
||||
|
||||
struct msg
|
||||
: by_to_hook, by_from_hook, by_when_hook
|
||||
struct msg : by_to_hook, by_from_hook, by_when_hook
|
||||
{
|
||||
Peer to;
|
||||
Peer from;
|
||||
time_point when;
|
||||
|
||||
msg(msg const&) = delete;
|
||||
msg& operator= (msg const&) = delete;
|
||||
msg&
|
||||
operator=(msg const&) = delete;
|
||||
virtual ~msg() = default;
|
||||
virtual void operator()() const = 0;
|
||||
virtual void
|
||||
operator()() const = 0;
|
||||
|
||||
msg (Peer const& from_, Peer const& to_,
|
||||
time_point when_)
|
||||
msg(Peer const& from_, Peer const& to_, time_point when_)
|
||||
: to(to_), from(from_), when(when_)
|
||||
{
|
||||
}
|
||||
@@ -163,23 +159,29 @@ private:
|
||||
|
||||
public:
|
||||
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_,
|
||||
time_point when_, Handler&& h)
|
||||
: msg (from_, to_, when_)
|
||||
, h_ (std::move(h))
|
||||
msg_impl(
|
||||
Peer const& from_,
|
||||
Peer const& to_,
|
||||
time_point when_,
|
||||
Handler&& h)
|
||||
: msg(from_, to_, when_), h_(std::move(h))
|
||||
{
|
||||
}
|
||||
|
||||
msg_impl (Peer const& from_, Peer const& to_,
|
||||
time_point when_, Handler const& h)
|
||||
: msg (from_, to_, when_)
|
||||
, h_ (h)
|
||||
msg_impl(
|
||||
Peer const& from_,
|
||||
Peer const& to_,
|
||||
time_point when_,
|
||||
Handler const& h)
|
||||
: msg(from_, to_, when_), h_(h)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()() const override
|
||||
void
|
||||
operator()() const override
|
||||
{
|
||||
h_();
|
||||
}
|
||||
@@ -188,18 +190,18 @@ private:
|
||||
class queue_type
|
||||
{
|
||||
private:
|
||||
using by_to_list = typename
|
||||
boost::intrusive::make_list<msg,
|
||||
using by_to_list = typename boost::intrusive::make_list<
|
||||
msg,
|
||||
boost::intrusive::base_hook<by_to_hook>,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
|
||||
using by_from_list = typename
|
||||
boost::intrusive::make_list<msg,
|
||||
using by_from_list = typename boost::intrusive::make_list<
|
||||
msg,
|
||||
boost::intrusive::base_hook<by_from_hook>,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
|
||||
using by_when_set = typename
|
||||
boost::intrusive::make_multiset<msg,
|
||||
using by_when_set = typename boost::intrusive::make_multiset<
|
||||
msg,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
|
||||
qalloc alloc_;
|
||||
@@ -208,14 +210,13 @@ private:
|
||||
std::unordered_map<Peer, by_from_list> by_from_;
|
||||
|
||||
public:
|
||||
using iterator =
|
||||
typename by_when_set::iterator;
|
||||
using iterator = typename by_when_set::iterator;
|
||||
|
||||
queue_type(queue_type const&) = delete;
|
||||
queue_type& operator= (queue_type const&) = delete;
|
||||
queue_type&
|
||||
operator=(queue_type const&) = delete;
|
||||
|
||||
explicit
|
||||
queue_type (qalloc const& alloc);
|
||||
explicit queue_type(qalloc const& alloc);
|
||||
|
||||
~queue_type();
|
||||
|
||||
@@ -230,8 +231,7 @@ private:
|
||||
|
||||
template <class Handler>
|
||||
typename by_when_set::iterator
|
||||
emplace (Peer const& from, Peer const& to,
|
||||
time_point when, Handler&& h);
|
||||
emplace(Peer const& from, Peer const& to, time_point when, Handler&& h);
|
||||
|
||||
void
|
||||
erase(iterator iter);
|
||||
@@ -246,14 +246,12 @@ private:
|
||||
duration delay;
|
||||
|
||||
link_type(bool inbound_, duration delay_)
|
||||
: inbound (inbound_)
|
||||
, delay (delay_)
|
||||
: inbound(inbound_), delay(delay_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using links_type =
|
||||
boost::container::flat_map<Peer, link_type>;
|
||||
using links_type = boost::container::flat_map<Peer, link_type>;
|
||||
|
||||
class link_transform;
|
||||
|
||||
@@ -266,7 +264,8 @@ private:
|
||||
|
||||
public:
|
||||
BasicNetwork(BasicNetwork const&) = delete;
|
||||
BasicNetwork& operator= (BasicNetwork const&) = delete;
|
||||
BasicNetwork&
|
||||
operator=(BasicNetwork const&) = delete;
|
||||
|
||||
BasicNetwork();
|
||||
|
||||
@@ -308,7 +307,9 @@ public:
|
||||
@return `true` if a new connection was established
|
||||
*/
|
||||
bool
|
||||
connect (Peer const& from, Peer const& to,
|
||||
connect(
|
||||
Peer const& from,
|
||||
Peer const& to,
|
||||
duration const& delay = std::chrono::seconds{0});
|
||||
|
||||
/** Break a link.
|
||||
@@ -330,8 +331,7 @@ public:
|
||||
|
||||
@return A random access range.
|
||||
*/
|
||||
boost::transformed_range<
|
||||
link_transform, links_type>
|
||||
boost::transformed_range<link_transform, links_type>
|
||||
links(Peer const& from);
|
||||
|
||||
/** Send a message to a peer.
|
||||
@@ -354,8 +354,7 @@ public:
|
||||
*/
|
||||
template <class Function>
|
||||
void
|
||||
send (Peer const& from, Peer const& to,
|
||||
Function&& f);
|
||||
send(Peer const& from, Peer const& to, Function&& f);
|
||||
|
||||
// Used to cancel timers
|
||||
struct cancel_token;
|
||||
@@ -370,8 +369,7 @@ public:
|
||||
*/
|
||||
template <class Function>
|
||||
cancel_token
|
||||
timer (time_point const& when,
|
||||
Function&& f);
|
||||
timer(time_point const& when, Function&& f);
|
||||
|
||||
/** Deliver a timer notification.
|
||||
|
||||
@@ -383,8 +381,7 @@ public:
|
||||
*/
|
||||
template <class Function>
|
||||
cancel_token
|
||||
timer (duration const& delay,
|
||||
Function&& f);
|
||||
timer(duration const& delay, Function&& f);
|
||||
|
||||
/** Cancel a timer.
|
||||
|
||||
@@ -473,15 +470,13 @@ public:
|
||||
*/
|
||||
template <class Period, class Rep>
|
||||
bool
|
||||
step_for (std::chrono::duration<
|
||||
Period, Rep> const& amount);
|
||||
step_for(std::chrono::duration<Period, Rep> const& amount);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Peer>
|
||||
BasicNetwork<Peer>::queue_type::queue_type(
|
||||
qalloc const& alloc)
|
||||
BasicNetwork<Peer>::queue_type::queue_type(qalloc const& alloc)
|
||||
: alloc_(alloc)
|
||||
{
|
||||
}
|
||||
@@ -489,8 +484,7 @@ BasicNetwork<Peer>::queue_type::queue_type(
|
||||
template <class Peer>
|
||||
BasicNetwork<Peer>::queue_type::~queue_type()
|
||||
{
|
||||
for(auto iter = by_when_.begin();
|
||||
iter != by_when_.end();)
|
||||
for (auto iter = by_when_.begin(); iter != by_when_.end();)
|
||||
{
|
||||
auto m = &*iter;
|
||||
++iter;
|
||||
@@ -500,27 +494,22 @@ BasicNetwork<Peer>::queue_type::~queue_type()
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
bool
|
||||
inline bool
|
||||
BasicNetwork<Peer>::queue_type::empty() const
|
||||
{
|
||||
return by_when_.empty();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::queue_type::begin() ->
|
||||
iterator
|
||||
inline auto
|
||||
BasicNetwork<Peer>::queue_type::begin() -> iterator
|
||||
{
|
||||
return by_when_.begin();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::queue_type::end() ->
|
||||
iterator
|
||||
inline auto
|
||||
BasicNetwork<Peer>::queue_type::end() -> iterator
|
||||
{
|
||||
return by_when_.end();
|
||||
}
|
||||
@@ -529,15 +518,14 @@ template <class Peer>
|
||||
template <class Handler>
|
||||
auto
|
||||
BasicNetwork<Peer>::queue_type::emplace(
|
||||
Peer const& from, Peer const& to, time_point when,
|
||||
Handler&& h) ->
|
||||
typename by_when_set::iterator
|
||||
Peer const& from,
|
||||
Peer const& to,
|
||||
time_point when,
|
||||
Handler&& h) -> typename by_when_set::iterator
|
||||
{
|
||||
using msg_type = msg_impl<
|
||||
std::decay_t<Handler>>;
|
||||
using msg_type = msg_impl<std::decay_t<Handler>>;
|
||||
auto const p = alloc_.alloc<msg_type>(1);
|
||||
auto& m = *new(p) msg_type(from, to,
|
||||
when, std::forward<Handler>(h));
|
||||
auto& m = *new (p) msg_type(from, to, when, std::forward<Handler>(h));
|
||||
if (to)
|
||||
by_to_[to].push_back(m);
|
||||
if (from)
|
||||
@@ -547,8 +535,7 @@ BasicNetwork<Peer>::queue_type::emplace(
|
||||
|
||||
template <class Peer>
|
||||
void
|
||||
BasicNetwork<Peer>::queue_type::erase(
|
||||
iterator iter)
|
||||
BasicNetwork<Peer>::queue_type::erase(iterator iter)
|
||||
{
|
||||
auto& m = *iter;
|
||||
if (iter->to)
|
||||
@@ -568,13 +555,11 @@ BasicNetwork<Peer>::queue_type::erase(
|
||||
|
||||
template <class Peer>
|
||||
void
|
||||
BasicNetwork<Peer>::queue_type::remove(
|
||||
Peer const& from, Peer const& to)
|
||||
BasicNetwork<Peer>::queue_type::remove(Peer const& from, Peer const& to)
|
||||
{
|
||||
{
|
||||
auto& list = by_to_[to];
|
||||
for(auto iter = list.begin();
|
||||
iter != list.end();)
|
||||
for (auto iter = list.begin(); iter != list.end();)
|
||||
{
|
||||
auto& m = *iter++;
|
||||
if (m.from == from)
|
||||
@@ -583,8 +568,7 @@ BasicNetwork<Peer>::queue_type::remove(
|
||||
}
|
||||
{
|
||||
auto& list = by_to_[from];
|
||||
for(auto iter = list.begin();
|
||||
iter != list.end();)
|
||||
for (auto iter = list.begin(); iter != list.end();)
|
||||
{
|
||||
auto& m = *iter++;
|
||||
if (m.from == to)
|
||||
@@ -603,8 +587,7 @@ private:
|
||||
Peer from_;
|
||||
|
||||
public:
|
||||
using argument_type =
|
||||
typename links_type::value_type;
|
||||
using argument_type = typename links_type::value_type;
|
||||
|
||||
class result_type
|
||||
{
|
||||
@@ -614,13 +597,12 @@ public:
|
||||
|
||||
result_type(result_type const&) = default;
|
||||
|
||||
result_type (BasicNetwork& net,
|
||||
Peer const& from, Peer const& to_,
|
||||
result_type(
|
||||
BasicNetwork& net,
|
||||
Peer const& from,
|
||||
Peer const& to_,
|
||||
bool inbound_)
|
||||
: to(to_)
|
||||
, inbound(inbound_)
|
||||
, net_(net)
|
||||
, from_(from)
|
||||
: to(to_), inbound(inbound_), net_(net), from_(from)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -641,18 +623,14 @@ public:
|
||||
Peer from_;
|
||||
};
|
||||
|
||||
link_transform (BasicNetwork& net,
|
||||
Peer const& from)
|
||||
: net_(net)
|
||||
, from_(from)
|
||||
link_transform(BasicNetwork& net, Peer const& from) : net_(net), from_(from)
|
||||
{
|
||||
}
|
||||
|
||||
result_type const
|
||||
operator()(argument_type const& v) const
|
||||
{
|
||||
return result_type(net_, from_,
|
||||
v.first, v.second.inbound);
|
||||
return result_type(net_, from_, v.first, v.second.inbound);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -667,13 +645,12 @@ private:
|
||||
public:
|
||||
cancel_token() = delete;
|
||||
cancel_token(cancel_token const&) = default;
|
||||
cancel_token& operator= (cancel_token const&) = default;
|
||||
cancel_token&
|
||||
operator=(cancel_token const&) = default;
|
||||
|
||||
private:
|
||||
friend class BasicNetwork;
|
||||
cancel_token(typename
|
||||
queue_type::iterator iter)
|
||||
: iter_ (iter)
|
||||
cancel_token(typename queue_type::iterator iter) : iter_(iter)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -681,33 +658,27 @@ private:
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Peer>
|
||||
BasicNetwork<Peer>::BasicNetwork()
|
||||
: queue_ (alloc_)
|
||||
BasicNetwork<Peer>::BasicNetwork() : queue_(alloc_)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
qalloc const&
|
||||
inline qalloc const&
|
||||
BasicNetwork<Peer>::alloc() const
|
||||
{
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::clock() const ->
|
||||
clock_type&
|
||||
inline auto
|
||||
BasicNetwork<Peer>::clock() const -> clock_type&
|
||||
{
|
||||
return clock_;
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::now() const ->
|
||||
time_point
|
||||
inline auto
|
||||
BasicNetwork<Peer>::now() const -> time_point
|
||||
{
|
||||
return clock_.now();
|
||||
}
|
||||
@@ -715,17 +686,16 @@ BasicNetwork<Peer>::now() const ->
|
||||
template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::connect(
|
||||
Peer const& from, Peer const& to,
|
||||
Peer const& from,
|
||||
Peer const& to,
|
||||
duration const& delay)
|
||||
{
|
||||
if (to == from)
|
||||
return false;
|
||||
using namespace std;
|
||||
if (! links_[from].emplace(to,
|
||||
link_type{ false, delay }).second)
|
||||
if (!links_[from].emplace(to, link_type{false, delay}).second)
|
||||
return false;
|
||||
auto const result = links_[to].emplace(
|
||||
from, link_type{ true, delay });
|
||||
auto const result = links_[to].emplace(from, link_type{true, delay});
|
||||
(void)result;
|
||||
assert(result.second);
|
||||
return true;
|
||||
@@ -733,13 +703,11 @@ BasicNetwork<Peer>::connect(
|
||||
|
||||
template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::disconnect(
|
||||
Peer const& peer1, Peer const& peer2)
|
||||
BasicNetwork<Peer>::disconnect(Peer const& peer1, Peer const& peer2)
|
||||
{
|
||||
if (links_[peer1].erase(peer2) == 0)
|
||||
return false;
|
||||
auto const n =
|
||||
links_[peer2].erase(peer1);
|
||||
auto const n = links_[peer2].erase(peer1);
|
||||
(void)n;
|
||||
assert(n);
|
||||
queue_.remove(peer1, peer2);
|
||||
@@ -747,64 +715,45 @@ BasicNetwork<Peer>::disconnect(
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::links(Peer const& from) ->
|
||||
boost::transformed_range<
|
||||
link_transform, links_type>
|
||||
inline auto
|
||||
BasicNetwork<Peer>::links(Peer const& from)
|
||||
-> boost::transformed_range<link_transform, links_type>
|
||||
{
|
||||
return boost::adaptors::transform(
|
||||
links_[from],
|
||||
link_transform{ *this, from });
|
||||
links_[from], link_transform{*this, from});
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
inline
|
||||
void
|
||||
BasicNetwork<Peer>::send(
|
||||
Peer const& from, Peer const& to,
|
||||
Function&& f)
|
||||
inline void
|
||||
BasicNetwork<Peer>::send(Peer const& from, Peer const& to, Function&& f)
|
||||
{
|
||||
using namespace std;
|
||||
auto const iter =
|
||||
links_[from].find(to);
|
||||
queue_.emplace(from, to,
|
||||
clock_.now() + iter->second.delay,
|
||||
forward<Function>(f));
|
||||
auto const iter = links_[from].find(to);
|
||||
queue_.emplace(
|
||||
from, to, clock_.now() + iter->second.delay, forward<Function>(f));
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::timer(
|
||||
time_point const& when, Function&& f) ->
|
||||
cancel_token
|
||||
inline auto
|
||||
BasicNetwork<Peer>::timer(time_point const& when, Function&& f) -> cancel_token
|
||||
{
|
||||
using namespace std;
|
||||
return queue_.emplace(
|
||||
nullptr, nullptr, when,
|
||||
forward<Function>(f));
|
||||
return queue_.emplace(nullptr, nullptr, when, forward<Function>(f));
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::timer(
|
||||
duration const& delay, Function&& f) ->
|
||||
cancel_token
|
||||
inline auto
|
||||
BasicNetwork<Peer>::timer(duration const& delay, Function&& f) -> cancel_token
|
||||
{
|
||||
return timer(clock_.now() + delay,
|
||||
std::forward<Function>(f));
|
||||
return timer(clock_.now() + delay, std::forward<Function>(f));
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
void
|
||||
BasicNetwork<Peer>::cancel(
|
||||
cancel_token const& token)
|
||||
inline void
|
||||
BasicNetwork<Peer>::cancel(cancel_token const& token)
|
||||
{
|
||||
queue_.erase(token.iter_);
|
||||
}
|
||||
@@ -847,8 +796,7 @@ BasicNetwork<Peer>::step_while(Function && f)
|
||||
|
||||
template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::step_until(
|
||||
time_point const& until)
|
||||
BasicNetwork<Peer>::step_until(time_point const& until)
|
||||
{
|
||||
// VFALCO This routine needs optimizing
|
||||
if (queue_.empty())
|
||||
@@ -866,19 +814,15 @@ BasicNetwork<Peer>::step_until(
|
||||
{
|
||||
step_one();
|
||||
iter = queue_.begin();
|
||||
}
|
||||
while(iter != queue_.end() &&
|
||||
iter->when <= until);
|
||||
} while (iter != queue_.end() && iter->when <= until);
|
||||
clock_.set(until);
|
||||
return iter != queue_.end();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Period, class Rep>
|
||||
inline
|
||||
bool
|
||||
BasicNetwork<Peer>::step_for(
|
||||
std::chrono::duration<Period, Rep> const& amount)
|
||||
inline bool
|
||||
BasicNetwork<Peer>::step_for(std::chrono::duration<Period, Rep> const& amount)
|
||||
{
|
||||
return step_until(now() + amount);
|
||||
}
|
||||
@@ -886,8 +830,7 @@ BasicNetwork<Peer>::step_for(
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
void
|
||||
BasicNetwork<Peer>::bfs(
|
||||
Peer const& start, Function&& f)
|
||||
BasicNetwork<Peer>::bfs(Peer const& start, Function&& f)
|
||||
{
|
||||
std::deque<std::pair<Peer, std::size_t>> q;
|
||||
std::unordered_set<Peer> seen;
|
||||
|
||||
@@ -18,15 +18,14 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <test/csf/BasicNetwork.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <set>
|
||||
#include <test/csf/BasicNetwork.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
|
||||
class BasicNetwork_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
@@ -38,23 +37,20 @@ public:
|
||||
Peer(Peer const&) = default;
|
||||
Peer(Peer&&) = default;
|
||||
|
||||
explicit Peer(int id_)
|
||||
: id(id_)
|
||||
explicit Peer(int id_) : id(id_)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Net>
|
||||
void start(Net& net)
|
||||
void
|
||||
start(Net& net)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
auto t = net.timer(1s,
|
||||
[&]{ set.insert(0); });
|
||||
auto t = net.timer(1s, [&] { set.insert(0); });
|
||||
if (id == 0)
|
||||
{
|
||||
for (auto const& link : net.links(this))
|
||||
net.send(this, link.to,
|
||||
[&, to = link.to]
|
||||
{
|
||||
net.send(this, link.to, [&, to = link.to ] {
|
||||
to->receive(net, this, 1);
|
||||
});
|
||||
}
|
||||
@@ -65,23 +61,23 @@ public:
|
||||
}
|
||||
|
||||
template <class Net>
|
||||
void receive(Net& net, Peer* from, int m)
|
||||
void
|
||||
receive(Net& net, Peer* from, int m)
|
||||
{
|
||||
set.insert(m);
|
||||
++m;
|
||||
if (m < 5)
|
||||
{
|
||||
for (auto const& link : net.links(this))
|
||||
net.send(this, link.to,
|
||||
[&, mm = m, to = link.to]
|
||||
{
|
||||
net.send(this, link.to, [&, mm = m, to = link.to ] {
|
||||
to->receive(net, this, mm);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void run() override
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
std::vector<Peer> pv;
|
||||
@@ -94,9 +90,8 @@ public:
|
||||
BEAST_EXPECT(net.connect(&pv[1], &pv[2], 1s));
|
||||
BEAST_EXPECT(!net.connect(&pv[0], &pv[1]));
|
||||
std::size_t diameter = 0;
|
||||
net.bfs(&pv[0],
|
||||
[&](auto d, Peer*)
|
||||
{ diameter = std::max(d, diameter); });
|
||||
net.bfs(
|
||||
&pv[0], [&](auto d, Peer*) { diameter = std::max(d, diameter); });
|
||||
BEAST_EXPECT(diameter == 2);
|
||||
for (auto& peer : pv)
|
||||
peer.start(net);
|
||||
@@ -116,12 +111,9 @@ public:
|
||||
break;
|
||||
BEAST_EXPECT(links[0].disconnect());
|
||||
}
|
||||
BEAST_EXPECT(pv[0].set ==
|
||||
std::set<int>({0, 2, 4}));
|
||||
BEAST_EXPECT(pv[1].set ==
|
||||
std::set<int>({1, 3}));
|
||||
BEAST_EXPECT(pv[2].set ==
|
||||
std::set<int>({2, 4}));
|
||||
BEAST_EXPECT(pv[0].set == std::set<int>({0, 2, 4}));
|
||||
BEAST_EXPECT(pv[1].set == std::set<int>({1, 3}));
|
||||
BEAST_EXPECT(pv[2].set == std::set<int>({2, 4}));
|
||||
net.timer(0s, [] {});
|
||||
}
|
||||
};
|
||||
@@ -130,4 +122,3 @@ BEAST_DEFINE_TESTSUITE(BasicNetwork, test, ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
|
||||
@@ -46,31 +46,31 @@ namespace csf {
|
||||
|
||||
class Ledger
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
struct ID
|
||||
{
|
||||
std::uint32_t seq = 0;
|
||||
TxSetType txs = TxSetType{};
|
||||
|
||||
bool operator==(ID const & o) const
|
||||
bool
|
||||
operator==(ID const& o) const
|
||||
{
|
||||
return seq == o.seq && txs == o.txs;
|
||||
}
|
||||
|
||||
bool operator!=(ID const & o) const
|
||||
bool
|
||||
operator!=(ID const& o) const
|
||||
{
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
auto const&
|
||||
id() const
|
||||
{
|
||||
@@ -127,10 +127,10 @@ public:
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
//! Apply the given transactions to this ledger
|
||||
Ledger
|
||||
close(TxSetType const & txs,
|
||||
close(
|
||||
TxSetType const& txs,
|
||||
NetClock::duration closeTimeResolution,
|
||||
NetClock::time_point const& consensusCloseTime,
|
||||
bool closeTimeAgree) const
|
||||
@@ -140,17 +140,15 @@ public:
|
||||
res.id_.seq = seq() + 1;
|
||||
res.closeTimeResolution_ = closeTimeResolution;
|
||||
res.actualCloseTime_ = consensusCloseTime;
|
||||
res.closeTime_ = effectiveCloseTime(consensusCloseTime,
|
||||
closeTimeResolution, parentCloseTime_);
|
||||
res.closeTime_ = effCloseTime(
|
||||
consensusCloseTime, closeTimeResolution, parentCloseTime_);
|
||||
res.closeTimeAgree_ = closeTimeAgree;
|
||||
res.parentCloseTime_ = closeTime();
|
||||
res.parentID_ = id();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
//! Unique identifier of ledger is combination of sequence number and id
|
||||
ID id_;
|
||||
|
||||
@@ -171,18 +169,15 @@ private:
|
||||
|
||||
//! Close time unadjusted by closeTimeResolution
|
||||
NetClock::time_point actualCloseTime_;
|
||||
|
||||
};
|
||||
|
||||
inline
|
||||
std::ostream &
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& o, Ledger::ID const& id)
|
||||
{
|
||||
return o << id.seq << "," << id.txs;
|
||||
}
|
||||
|
||||
inline
|
||||
std::string
|
||||
inline std::string
|
||||
to_string(Ledger::ID const& id)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
@@ -22,15 +22,14 @@
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
|
||||
#include <test/csf/Tx.h>
|
||||
#include <test/csf/Ledger.h>
|
||||
#include <test/csf/Tx.h>
|
||||
#include <test/csf/UNL.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
|
||||
/** Store validations reached by peers */
|
||||
struct Validation
|
||||
{
|
||||
@@ -47,7 +46,9 @@ class Validations
|
||||
//< created)
|
||||
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_map<Ledger::ID, std::size_t>> childLedgers;
|
||||
bc::flat_map<Ledger::ID, bc::flat_map<Ledger::ID, std::size_t>>
|
||||
childLedgers;
|
||||
|
||||
public:
|
||||
void
|
||||
update(Validation const& v)
|
||||
@@ -86,12 +87,10 @@ public:
|
||||
/** Returns the ledger starting from prevLedger with the most validations.
|
||||
*/
|
||||
Ledger::ID
|
||||
getBestLCL(Ledger::ID const & currLedger,
|
||||
Ledger::ID const & prevLedger) const
|
||||
getBestLCL(Ledger::ID const& currLedger, Ledger::ID const& prevLedger) const
|
||||
{
|
||||
auto it = childLedgers.find(prevLedger);
|
||||
if (it != childLedgers.end() &&
|
||||
! it->second.empty())
|
||||
if (it != childLedgers.end() && !it->second.empty())
|
||||
{
|
||||
std::size_t bestCount = 0;
|
||||
Ledger::ID bestLedger;
|
||||
@@ -122,11 +121,8 @@ struct Traits
|
||||
using Ledger_t = Ledger;
|
||||
using NodeID_t = PeerID;
|
||||
using TxSet_t = TxSet;
|
||||
using MissingTxException_t = MissingTx;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** Represents a single node participating in the consensus process.
|
||||
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
|
||||
std::chrono::milliseconds missingLedgerDelay{0};
|
||||
|
||||
bool validating = true;
|
||||
bool proposing = true;
|
||||
bool validating_ = true;
|
||||
bool proposing_ = true;
|
||||
|
||||
//! All peers start from the default constructed ledger
|
||||
Peer(PeerID i, BasicNetwork<Peer*>& n, UNL const& u)
|
||||
@@ -186,17 +182,6 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
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*
|
||||
acquireLedger(Ledger::ID const& ledgerHash)
|
||||
{
|
||||
@@ -212,9 +197,9 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
auto it = p.ledgers.find(ledgerHash);
|
||||
if (it != p.ledgers.end())
|
||||
{
|
||||
schedule(missingLedgerDelay,
|
||||
[this, ledgerHash, ledger = it->second]()
|
||||
{
|
||||
schedule(
|
||||
missingLedgerDelay,
|
||||
[ this, ledgerHash, ledger = it->second ]() {
|
||||
ledgers.emplace(ledgerHash, ledger);
|
||||
});
|
||||
if (missingLedgerDelay == 0ms)
|
||||
@@ -241,7 +226,6 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
hasOpenTransactions() const
|
||||
{
|
||||
@@ -260,103 +244,57 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
return peerValidations.proposersFinished(prevLedger);
|
||||
}
|
||||
|
||||
void
|
||||
onStartRound(Ledger const &) {}
|
||||
|
||||
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)
|
||||
Result
|
||||
onClose(Ledger const& prevLedger, NetClock::time_point closeTime, Mode mode)
|
||||
{
|
||||
TxSet res{openTxs};
|
||||
|
||||
return { res,
|
||||
Proposal{prevLedger.id(), Proposal::seqJoin, res.id(), closeTime, now, id} };
|
||||
return Result{TxSet{openTxs},
|
||||
Proposal{prevLedger.id(),
|
||||
Proposal::seqJoin,
|
||||
res.id(),
|
||||
closeTime,
|
||||
now(),
|
||||
id}};
|
||||
}
|
||||
|
||||
// Process the accepted transaction set, generating the newly closed ledger
|
||||
// and clearing out the openTxs that were included.
|
||||
// TODO: Kinda nasty it takes so many arguments . . . sign of bad coupling
|
||||
bool
|
||||
accept(TxSet const& set,
|
||||
NetClock::time_point consensusCloseTime,
|
||||
bool proposing_,
|
||||
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)
|
||||
void
|
||||
onForceAccept(
|
||||
Result const& result,
|
||||
Ledger const& prevLedger,
|
||||
NetClock::duration const& closeResolution,
|
||||
CloseTimes const& rawCloseTimes,
|
||||
Mode const& mode)
|
||||
{
|
||||
auto newLedger = previousLedger_.close(set.txs_, closeResolution_,
|
||||
closeTime, consensusCloseTime != NetClock::time_point{});
|
||||
onAccept(result, prevLedger, closeResolution, rawCloseTimes, mode);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
lastClosedLedger = newLedger;
|
||||
|
||||
auto it = std::remove_if(openTxs.begin(), openTxs.end(),
|
||||
[&](Tx const & tx)
|
||||
{
|
||||
return set.exists(tx.id());
|
||||
auto it =
|
||||
std::remove_if(openTxs.begin(), openTxs.end(), [&](Tx const& tx) {
|
||||
return result.set.exists(tx.id());
|
||||
});
|
||||
openTxs.erase(it, openTxs.end());
|
||||
|
||||
if(validating)
|
||||
if (validating_)
|
||||
relay(Validation{id, newLedger.id(), newLedger.parentID()});
|
||||
return validating_;
|
||||
}
|
||||
|
||||
void
|
||||
endConsensus(bool correct)
|
||||
{
|
||||
// kick off the next round...
|
||||
// in the actual implementation, this passes back through
|
||||
// network ops
|
||||
@@ -366,11 +304,28 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
// TODO: reconsider this and instead just save LCL generated here?
|
||||
if (completedLedgers <= targetLedgers)
|
||||
{
|
||||
startRound(now(), lastClosedLedger.id(),
|
||||
lastClosedLedger);
|
||||
startRound(
|
||||
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
|
||||
void
|
||||
@@ -383,9 +338,9 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
auto& dest = peerPositions_[p.prevLedger()];
|
||||
if (std::find(dest.begin(), dest.end(), p) != dest.end())
|
||||
return;
|
||||
|
||||
dest.push_back(p);
|
||||
peerProposal(now(), p);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
@@ -413,11 +368,7 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
{
|
||||
if (unl.find(v.id) != unl.end())
|
||||
{
|
||||
schedule(validationDelay,
|
||||
[&, v]()
|
||||
{
|
||||
peerValidations.update(v);
|
||||
});
|
||||
schedule(validationDelay, [&, v]() { peerValidations.update(v); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,11 +377,8 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
relay(T const& t)
|
||||
{
|
||||
for (auto const& link : net.links(this))
|
||||
net.send(this, link.to,
|
||||
[msg = t, to = link.to]
|
||||
{
|
||||
to->receive(msg);
|
||||
});
|
||||
net.send(
|
||||
this, link.to, [ msg = t, to = link.to ] { to->receive(msg); });
|
||||
}
|
||||
|
||||
// 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
|
||||
// In practice, we might not actually have that ledger itself yet,
|
||||
// so there is no gaurantee that bestLCL == lastClosedLedger.id()
|
||||
auto bestLCL = peerValidations.getBestLCL(lastClosedLedger.id(),
|
||||
lastClosedLedger.parentID());
|
||||
startRound(now(), bestLCL,
|
||||
lastClosedLedger);
|
||||
auto bestLCL = peerValidations.getBestLCL(
|
||||
lastClosedLedger.id(), lastClosedLedger.parentID());
|
||||
startRound(now(), bestLCL, lastClosedLedger, proposing_);
|
||||
}
|
||||
|
||||
NetClock::time_point
|
||||
@@ -470,14 +417,15 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
// any subtractions of two NetClock::time_point in the consensu
|
||||
// code are positive. (e.g. PROPOSE_FRESHNESS)
|
||||
using namespace std::chrono;
|
||||
return NetClock::time_point(duration_cast<NetClock::duration>
|
||||
(net.now().time_since_epoch()+ 86400s + clockSkew));
|
||||
return NetClock::time_point(duration_cast<NetClock::duration>(
|
||||
net.now().time_since_epoch() + 86400s + clockSkew));
|
||||
}
|
||||
|
||||
// Schedule the provided callback in `when` duration, but if
|
||||
// `when` is 0, call immediately
|
||||
template <class T>
|
||||
void schedule(std::chrono::nanoseconds when, T && what)
|
||||
void
|
||||
schedule(std::chrono::nanoseconds when, T&& what)
|
||||
{
|
||||
if (when == 0ns)
|
||||
what();
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
#ifndef 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/UNL.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -84,7 +84,7 @@ public:
|
||||
for (auto& p : peers)
|
||||
{
|
||||
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.start();
|
||||
}
|
||||
@@ -93,10 +93,8 @@ public:
|
||||
|
||||
std::vector<Peer> peers;
|
||||
BasicNetwork<Peer*> net;
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
#include <ripple/beast/hash/hash_append.h>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -35,7 +35,9 @@ class Tx
|
||||
public:
|
||||
using ID = std::uint32_t;
|
||||
|
||||
Tx(ID i) : id_{ i } {}
|
||||
Tx(ID i) : id_{i}
|
||||
{
|
||||
}
|
||||
|
||||
ID
|
||||
id() const
|
||||
@@ -55,10 +57,8 @@ public:
|
||||
return id_ == o.id_;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
ID id_;
|
||||
|
||||
};
|
||||
|
||||
//!-------------------------------------------------------------------------
|
||||
@@ -74,7 +74,9 @@ public:
|
||||
using MutableTxSet = TxSet;
|
||||
|
||||
TxSet() = default;
|
||||
TxSet(TxSetType const & s) : txs_{ s } {}
|
||||
TxSet(TxSetType const& s) : txs_{s}
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
insert(Tx const& t)
|
||||
@@ -119,19 +121,14 @@ public:
|
||||
{
|
||||
std::map<Tx::ID, bool> res;
|
||||
|
||||
auto populate_diffs = [&res](auto const & a, auto const & b, bool s)
|
||||
{
|
||||
auto populator = [&](auto const & tx)
|
||||
{
|
||||
res[tx.id()] = s;
|
||||
};
|
||||
auto populate_diffs = [&res](auto const& a, auto const& b, bool s) {
|
||||
auto populator = [&](auto const& tx) { res[tx.id()] = s; };
|
||||
std::set_difference(
|
||||
a.begin(), a.end(),
|
||||
b.begin(), b.end(),
|
||||
boost::make_function_output_iterator(
|
||||
std::ref(populator)
|
||||
)
|
||||
);
|
||||
a.begin(),
|
||||
a.end(),
|
||||
b.begin(),
|
||||
b.end(),
|
||||
boost::make_function_output_iterator(std::ref(populator)));
|
||||
};
|
||||
|
||||
populate_diffs(txs_, other.txs_, true);
|
||||
@@ -143,33 +140,17 @@ public:
|
||||
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
|
||||
|
||||
inline
|
||||
std::ostream&
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& o, const Tx& t)
|
||||
{
|
||||
return o << t.id();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline
|
||||
std::ostream&
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& o, boost::container::flat_set<T> const& ts)
|
||||
{
|
||||
o << "{ ";
|
||||
@@ -181,16 +162,12 @@ operator<<(std::ostream & o, boost::container::flat_set<T> const & ts)
|
||||
else
|
||||
do_comma = true;
|
||||
o << t;
|
||||
|
||||
|
||||
}
|
||||
o << " }";
|
||||
return o;
|
||||
|
||||
}
|
||||
|
||||
inline
|
||||
std::string
|
||||
inline std::string
|
||||
to_string(TxSetType const& txs)
|
||||
{
|
||||
std::stringstream ss;
|
||||
@@ -199,21 +176,13 @@ to_string(TxSetType const & txs)
|
||||
}
|
||||
|
||||
template <class Hasher>
|
||||
inline
|
||||
void
|
||||
inline void
|
||||
hash_append(Hasher& h, Tx const& tx)
|
||||
{
|
||||
using beast::hash_append;
|
||||
hash_append(h, tx.id());
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream & o, MissingTx const &m)
|
||||
{
|
||||
return o << m.what();
|
||||
}
|
||||
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <numeric>
|
||||
#include <chrono>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -57,7 +57,6 @@ random_weighted_shuffle(std::vector<T> v, std::vector<double> w, G & g)
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
/** Power-law distribution with PDF
|
||||
|
||||
P(x) = (x/xmin)^-a
|
||||
@@ -72,22 +71,19 @@ class PowerLawDistribution
|
||||
std::uniform_real_distribution<double> uf_{0, 1};
|
||||
|
||||
public:
|
||||
PowerLawDistribution(double xmin, double a)
|
||||
: xmin_{xmin}, a_{a}
|
||||
PowerLawDistribution(double xmin, double a) : xmin_{xmin}, a_{a}
|
||||
{
|
||||
inv_ = 1.0 / (1.0 - a_);
|
||||
}
|
||||
|
||||
template <class Generator>
|
||||
inline
|
||||
double
|
||||
inline double
|
||||
operator()(Generator& g)
|
||||
{
|
||||
// use inverse transform of CDF to sample
|
||||
// CDF is P(X <= x): 1 - (x/xmin)^(1-a)
|
||||
return xmin_ * std::pow(1 - uf_(g), inv_);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//< Unique identifier for each node in the network
|
||||
@@ -110,25 +106,23 @@ class TrustGraph
|
||||
std::vector<UNL> UNLs_;
|
||||
|
||||
std::vector<int> assignment_;
|
||||
public:
|
||||
|
||||
public:
|
||||
//< Constructor
|
||||
TrustGraph(std::vector<UNL> UNLs, std::vector<int> assignment)
|
||||
: UNLs_{UNLs}
|
||||
, assignment_{assignment}
|
||||
{}
|
||||
: UNLs_{UNLs}, assignment_{assignment}
|
||||
{
|
||||
}
|
||||
|
||||
//< Whether node `i` trusts node `j`
|
||||
inline
|
||||
bool
|
||||
inline bool
|
||||
trusts(PeerID i, PeerID j) const
|
||||
{
|
||||
return unl(i).find(j) != unl(i).end();
|
||||
}
|
||||
|
||||
//< Get the UNL for node `i`
|
||||
inline
|
||||
UNL const &
|
||||
inline UNL const&
|
||||
unl(PeerID i) const
|
||||
{
|
||||
return UNLs_[assignment_[i]];
|
||||
@@ -138,7 +132,6 @@ public:
|
||||
bool
|
||||
canFork(double quorum) const;
|
||||
|
||||
|
||||
auto
|
||||
numPeers() const
|
||||
{
|
||||
@@ -176,28 +169,23 @@ public:
|
||||
|
||||
*/
|
||||
template <class RankPDF, class SizePDF, class Generator>
|
||||
static
|
||||
TrustGraph
|
||||
makeRandomRanked(int size,
|
||||
static TrustGraph
|
||||
makeRandomRanked(
|
||||
int size,
|
||||
int numUNLs,
|
||||
RankPDF rankPDF,
|
||||
SizePDF unlSizePDF,
|
||||
Generator& g)
|
||||
{
|
||||
|
||||
|
||||
// 1. Generate ranks
|
||||
std::vector<double> weights(size);
|
||||
std::generate(weights.begin(), weights.end(), [&]()
|
||||
{
|
||||
return rankPDF(g);
|
||||
});
|
||||
std::generate(
|
||||
weights.begin(), weights.end(), [&]() { return rankPDF(g); });
|
||||
|
||||
// 2. Generate UNLs based on sampling without replacement according
|
||||
// to weights
|
||||
std::vector<UNL> unls(numUNLs);
|
||||
std::generate(unls.begin(), unls.end(), [&]()
|
||||
{
|
||||
std::generate(unls.begin(), unls.end(), [&]() {
|
||||
std::vector<PeerID> ids(size);
|
||||
std::iota(ids.begin(), ids.end(), 0);
|
||||
auto res = random_weighted_shuffle(ids, weights, g);
|
||||
@@ -207,11 +195,8 @@ public:
|
||||
// 3. Assign membership
|
||||
std::vector<int> assignment(size);
|
||||
std::uniform_int_distribution<int> u(0, numUNLs - 1);
|
||||
std::generate(assignment.begin(), assignment.end(),
|
||||
[&]()
|
||||
{
|
||||
return u(g);
|
||||
});
|
||||
std::generate(
|
||||
assignment.begin(), assignment.end(), [&]() { return u(g); });
|
||||
|
||||
return TrustGraph(unls, assignment);
|
||||
}
|
||||
@@ -226,8 +211,7 @@ public:
|
||||
@param size The number of nodes in the trust graph
|
||||
@param overlap The number of nodes trusting both cliques
|
||||
*/
|
||||
static
|
||||
TrustGraph
|
||||
static TrustGraph
|
||||
makeClique(int size, int overlap);
|
||||
|
||||
/** Generate a complete (fully-connect) trust graph
|
||||
@@ -237,20 +221,16 @@ public:
|
||||
|
||||
@param size The number of nodes in the trust graph
|
||||
*/
|
||||
static
|
||||
TrustGraph
|
||||
static TrustGraph
|
||||
makeComplete(int size);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//< Make the TrustGraph into a topology with delays given by DelayModel
|
||||
template <class DelayModel>
|
||||
auto
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -260,10 +240,11 @@ class fixed
|
||||
std::chrono::nanoseconds d_;
|
||||
|
||||
public:
|
||||
fixed(std::chrono::nanoseconds const & d) : d_{d} {}
|
||||
fixed(std::chrono::nanoseconds const& d) : d_{d}
|
||||
{
|
||||
}
|
||||
|
||||
inline
|
||||
std::chrono::nanoseconds
|
||||
inline std::chrono::nanoseconds
|
||||
operator()(PeerID const& i, PeerID const& j) const
|
||||
{
|
||||
return d_;
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
#include <test/csf/UNL.h>
|
||||
#include <boost/iterator/counting_iterator.hpp>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <test/csf/UNL.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -55,12 +55,11 @@ TrustGraph::canFork(double quorum) const
|
||||
auto const& unlA = uniqueUNLs[i];
|
||||
auto const& unlB = uniqueUNLs[j];
|
||||
|
||||
double rhs = 2.0*(1.-quorum) *
|
||||
std::max(unlA.size(), unlB.size() );
|
||||
double rhs =
|
||||
2.0 * (1. - quorum) * std::max(unlA.size(), unlB.size());
|
||||
|
||||
int intersectionSize = std::count_if(unlA.begin(), unlA.end(),
|
||||
[&](PeerID id)
|
||||
{
|
||||
int intersectionSize =
|
||||
std::count_if(unlA.begin(), unlA.end(), [&](PeerID id) {
|
||||
return unlB.find(id) != unlB.end();
|
||||
});
|
||||
|
||||
@@ -100,7 +99,6 @@ TrustGraph::makeClique(int size, int overlap)
|
||||
assignment[i] = 2;
|
||||
}
|
||||
|
||||
|
||||
return TrustGraph(unls, assignment);
|
||||
}
|
||||
|
||||
@@ -110,11 +108,11 @@ TrustGraph::makeComplete(int size)
|
||||
UNL all{boost::counting_iterator<PeerID>(0),
|
||||
boost::counting_iterator<PeerID>(size)};
|
||||
|
||||
return TrustGraph(std::vector<UNL>(1,all),
|
||||
std::vector<int>(size, 0));
|
||||
return TrustGraph(std::vector<UNL>(1, all), 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);
|
||||
out << "digraph {\n";
|
||||
@@ -124,10 +122,8 @@ inline void TrustGraph::save_dot(std::string const & fileName)
|
||||
{
|
||||
out << i << " -> " << j << ";\n";
|
||||
}
|
||||
|
||||
}
|
||||
out << "}\n";
|
||||
|
||||
}
|
||||
|
||||
} // csf
|
||||
|
||||