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/
|
docs/
|
||||||
Jamfile.v2
|
Jamfile.v2
|
||||||
boostbook.dtd
|
boostbook.dtd
|
||||||
|
consensus.qbk
|
||||||
index.xml
|
index.xml
|
||||||
main.qbk
|
main.qbk
|
||||||
quickref.xml
|
quickref.xml
|
||||||
|
|||||||
@@ -44,6 +44,15 @@ install callouts
|
|||||||
|
|
||||||
explicit callout ;
|
explicit callout ;
|
||||||
|
|
||||||
|
install consensus_images
|
||||||
|
:
|
||||||
|
[ glob images/consensus/*.png ]
|
||||||
|
:
|
||||||
|
<location>$(out)/html/images/consensus
|
||||||
|
;
|
||||||
|
|
||||||
|
explicit consensus_images ;
|
||||||
|
|
||||||
xml doc
|
xml doc
|
||||||
:
|
:
|
||||||
main.qbk
|
main.qbk
|
||||||
@@ -60,7 +69,7 @@ boostbook boostdoc
|
|||||||
<xsl:param>boost.root=$(broot)
|
<xsl:param>boost.root=$(broot)
|
||||||
<xsl:param>chunk.first.sections=1 # Chunk the first top-level section?
|
<xsl:param>chunk.first.sections=1 # Chunk the first top-level section?
|
||||||
<xsl:param>chunk.section.depth=8 # Depth to which sections should be chunked
|
<xsl:param>chunk.section.depth=8 # Depth to which sections should be chunked
|
||||||
<xsl:param>generate.section.toc.level=1 # Control depth of TOC generation in sections
|
<xsl:param>generate.section.toc.level=2 # Control depth of TOC generation in sections
|
||||||
<xsl:param>toc.max.depth=2 # How many levels should be created for each TOC?
|
<xsl:param>toc.max.depth=2 # How many levels should be created for each TOC?
|
||||||
<xsl:param>toc.section.depth=2 # How deep should recursive sections appear in the TOC?
|
<xsl:param>toc.section.depth=2 # How deep should recursive sections appear in the TOC?
|
||||||
<xsl:param>generate.toc="chapter toc section toc"
|
<xsl:param>generate.toc="chapter toc section toc"
|
||||||
@@ -68,4 +77,5 @@ boostbook boostdoc
|
|||||||
<location>temp
|
<location>temp
|
||||||
<dependency>stylesheets
|
<dependency>stylesheets
|
||||||
<dependency>images
|
<dependency>images
|
||||||
|
<dependency>consensus_images
|
||||||
;
|
;
|
||||||
|
|||||||
663
docs/consensus.qbk
Normal file
@@ -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
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
[library rippled
|
[library rippled
|
||||||
[quickbook 1.6]
|
[quickbook 1.6]
|
||||||
[copyright 2012 - 2016 Ripple Labs Inc.]
|
[copyright 2012 - 2017 Ripple Labs Inc.]
|
||||||
[purpose C++ Library]
|
[purpose C++ Library]
|
||||||
[license
|
[license
|
||||||
Distributed under the ISC License
|
Distributed under the ISC License
|
||||||
@@ -30,6 +30,8 @@
|
|||||||
[template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>''']
|
[template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>''']
|
||||||
[template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>''']
|
[template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>''']
|
||||||
|
|
||||||
|
[include consensus.qbk]
|
||||||
|
|
||||||
[section:ref Reference]
|
[section:ref Reference]
|
||||||
[include temp/reference.qbk]
|
[include temp/reference.qbk]
|
||||||
[endsect]
|
[endsect]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||||
@@ -21,19 +21,19 @@
|
|||||||
#define RIPPLE_APP_CONSENSUS_RCLCONSENSUS_H_INCLUDED
|
#define RIPPLE_APP_CONSENSUS_RCLCONSENSUS_H_INCLUDED
|
||||||
|
|
||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
|
#include <ripple/app/consensus/RCLCxLedger.h>
|
||||||
|
#include <ripple/app/consensus/RCLCxPeerPos.h>
|
||||||
|
#include <ripple/app/consensus/RCLCxTx.h>
|
||||||
|
#include <ripple/app/misc/FeeVote.h>
|
||||||
|
#include <ripple/basics/CountedObject.h>
|
||||||
#include <ripple/basics/Log.h>
|
#include <ripple/basics/Log.h>
|
||||||
|
#include <ripple/beast/utility/Journal.h>
|
||||||
|
#include <ripple/consensus/Consensus.h>
|
||||||
|
#include <ripple/core/JobQueue.h>
|
||||||
|
#include <ripple/overlay/Message.h>
|
||||||
|
#include <ripple/protocol/RippleLedgerHash.h>
|
||||||
#include <ripple/protocol/STValidation.h>
|
#include <ripple/protocol/STValidation.h>
|
||||||
#include <ripple/shamap/SHAMap.h>
|
#include <ripple/shamap/SHAMap.h>
|
||||||
#include <ripple/beast/utility/Journal.h>
|
|
||||||
#include <ripple/app/misc/FeeVote.h>
|
|
||||||
#include <ripple/protocol/RippleLedgerHash.h>
|
|
||||||
#include <ripple/app/consensus/RCLCxLedger.h>
|
|
||||||
#include <ripple/app/consensus/RCLCxTx.h>
|
|
||||||
#include <ripple/app/consensus/RCLCxPeerPos.h>
|
|
||||||
#include <ripple/core/JobQueue.h>
|
|
||||||
#include <ripple/consensus/Consensus.h>
|
|
||||||
#include <ripple/basics/CountedObject.h>
|
|
||||||
#include <ripple/overlay/Message.h>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
@@ -50,37 +50,40 @@ struct RCLCxTraits
|
|||||||
using NodeID_t = NodeID;
|
using NodeID_t = NodeID;
|
||||||
//! TxSet type presented to Consensus
|
//! TxSet type presented to Consensus
|
||||||
using TxSet_t = RCLTxSet;
|
using TxSet_t = RCLTxSet;
|
||||||
//! MissingTxException type neede by Consensus
|
|
||||||
using MissingTxException_t = SHAMapMissingNode;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** Adapts the generic Consensus algorithm for use by RCL.
|
/** Adapts the generic Consensus algorithm for use by RCL.
|
||||||
|
|
||||||
@note The enabled_shared_from_this base allows the application to properly
|
@note The enabled_shared_from_this base allows the application to properly
|
||||||
create a shared instance of RCLConsensus for use in the accept logic..
|
create a shared instance of RCLConsensus for use in the accept logic..
|
||||||
*/
|
*/
|
||||||
class RCLConsensus : public Consensus<RCLConsensus, RCLCxTraits>
|
class RCLConsensus final : public Consensus<RCLConsensus, RCLCxTraits>,
|
||||||
, public std::enable_shared_from_this <RCLConsensus>
|
public std::enable_shared_from_this<RCLConsensus>,
|
||||||
, public CountedObject <RCLConsensus>
|
public CountedObject<RCLConsensus>
|
||||||
{
|
{
|
||||||
using Base = Consensus<RCLConsensus, RCLCxTraits>;
|
using Base = Consensus<RCLConsensus, RCLCxTraits>;
|
||||||
using Base::accept;
|
|
||||||
public:
|
|
||||||
|
|
||||||
|
public:
|
||||||
//! Constructor
|
//! Constructor
|
||||||
RCLConsensus(
|
RCLConsensus(
|
||||||
Application& app,
|
Application& app,
|
||||||
std::unique_ptr<FeeVote> && feeVote,
|
std::unique_ptr<FeeVote>&& feeVote,
|
||||||
LedgerMaster& ledgerMaster,
|
LedgerMaster& ledgerMaster,
|
||||||
LocalTxs& localTxs,
|
LocalTxs& localTxs,
|
||||||
InboundTransactions& inboundTransactions,
|
InboundTransactions& inboundTransactions,
|
||||||
typename Base::clock_type const & clock,
|
typename Base::clock_type const& clock,
|
||||||
beast::Journal journal);
|
beast::Journal journal);
|
||||||
RCLConsensus(RCLConsensus const&) = delete;
|
|
||||||
RCLConsensus& operator=(RCLConsensus const&) = delete;
|
|
||||||
|
|
||||||
static char const* getCountedObjectName() { return "Consensus"; }
|
RCLConsensus(RCLConsensus const&) = delete;
|
||||||
|
|
||||||
|
RCLConsensus&
|
||||||
|
operator=(RCLConsensus const&) = delete;
|
||||||
|
|
||||||
|
static char const*
|
||||||
|
getCountedObjectName()
|
||||||
|
{
|
||||||
|
return "Consensus";
|
||||||
|
}
|
||||||
|
|
||||||
/** Save the given consensus proposed by a peer with nodeID for later
|
/** Save the given consensus proposed by a peer with nodeID for later
|
||||||
use in consensus.
|
use in consensus.
|
||||||
@@ -89,15 +92,59 @@ public:
|
|||||||
@param nodeID ID of peer
|
@param nodeID ID of peer
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
storeProposal( RCLCxPeerPos::ref peerPos, NodeID const& nodeID);
|
storeProposal(RCLCxPeerPos::ref peerPos, NodeID const& nodeID);
|
||||||
|
|
||||||
|
//! Whether we are validating consensus ledgers.
|
||||||
|
bool
|
||||||
|
validating() const
|
||||||
|
{
|
||||||
|
return validating_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
haveCorrectLCL() const
|
||||||
|
{
|
||||||
|
return mode() != Mode::wrongLedger;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
proposing() const
|
||||||
|
{
|
||||||
|
return mode() == Mode::proposing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the Json state of the consensus process.
|
||||||
|
|
||||||
|
Called by the consensus_info RPC.
|
||||||
|
|
||||||
|
@param full True if verbose response desired.
|
||||||
|
@return The Json state.
|
||||||
|
*/
|
||||||
|
Json::Value
|
||||||
|
getJson(bool full) const;
|
||||||
|
|
||||||
|
//! See Consensus::startRound
|
||||||
|
void
|
||||||
|
startRound(
|
||||||
|
NetClock::time_point const& now,
|
||||||
|
RCLCxLedger::ID const& prevLgrId,
|
||||||
|
RCLCxLedger const& prevLgr);
|
||||||
|
|
||||||
|
//! See Consensus::timerEntry
|
||||||
|
void
|
||||||
|
timerEntry(NetClock::time_point const& now);
|
||||||
|
|
||||||
|
//! See Consensus::gotTxSet
|
||||||
|
void
|
||||||
|
gotTxSet(NetClock::time_point const& now, RCLTxSet const& txSet);
|
||||||
|
|
||||||
/** Returns validation public key */
|
/** Returns validation public key */
|
||||||
PublicKey const&
|
PublicKey const&
|
||||||
getValidationPublicKey () const;
|
getValidationPublicKey() const;
|
||||||
|
|
||||||
/** Set validation private and public key pair. */
|
/** Set validation private and public key pair. */
|
||||||
void
|
void
|
||||||
setValidationKeys (SecretKey const& valSecret, PublicKey const& valPublic);
|
setValidationKeys(SecretKey const& valSecret, PublicKey const& valPublic);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Consensus<RCLConsensus, RCLCxTraits>;
|
friend class Consensus<RCLConsensus, RCLCxTraits>;
|
||||||
@@ -105,17 +152,6 @@ private:
|
|||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// Consensus type requirements.
|
// Consensus type requirements.
|
||||||
|
|
||||||
/** Notification that a new consensus round has begun.
|
|
||||||
|
|
||||||
@param ledger The ledger we are building consensus on
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
onStartRound(RCLCxLedger const & ledger);
|
|
||||||
|
|
||||||
//! @return Whether consensus should be (proposing, validating)
|
|
||||||
std::pair <bool, bool>
|
|
||||||
getMode ();
|
|
||||||
|
|
||||||
/** Attempt to acquire a specific ledger.
|
/** Attempt to acquire a specific ledger.
|
||||||
|
|
||||||
If not available, asynchronously acquires from the network.
|
If not available, asynchronously acquires from the network.
|
||||||
@@ -124,44 +160,44 @@ private:
|
|||||||
@return Optional ledger, will be seated if we locally had the ledger
|
@return Optional ledger, will be seated if we locally had the ledger
|
||||||
*/
|
*/
|
||||||
boost::optional<RCLCxLedger>
|
boost::optional<RCLCxLedger>
|
||||||
acquireLedger(LedgerHash const & ledger);
|
acquireLedger(LedgerHash const& ledger);
|
||||||
|
|
||||||
/** Get peers' proposed positions.
|
/** Get peers' proposed positions.
|
||||||
@param prevLedger The base ledger which proposals are based on
|
@param prevLedger The base ledger which proposals are based on
|
||||||
@return The set of proposals
|
@return The set of proposals
|
||||||
*/
|
*/
|
||||||
std::vector<RCLCxPeerPos>
|
std::vector<RCLCxPeerPos>
|
||||||
proposals (LedgerHash const& prevLedger);
|
proposals(LedgerHash const& prevLedger);
|
||||||
|
|
||||||
/** Relay the given proposal to all peers
|
/** Relay the given proposal to all peers
|
||||||
|
|
||||||
@param peerPos The peer position to relay.
|
@param peerPos The peer position to relay.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
relay(RCLCxPeerPos const & peerPos);
|
relay(RCLCxPeerPos const& peerPos);
|
||||||
|
|
||||||
/** Relay disputed transacction to peers.
|
/** Relay disputed transacction to peers.
|
||||||
|
|
||||||
Only relay if the provided transaction hasn't been shared recently.
|
Only relay if the provided transaction hasn't been shared recently.
|
||||||
|
|
||||||
@param dispute The disputed transaction to relay.
|
@param tx The disputed transaction to relay.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
relay(DisputedTx <RCLCxTx, NodeID> const & dispute);
|
relay(RCLCxTx const& tx);
|
||||||
|
|
||||||
/** Acquire the transaction set associated with a proposal.
|
/** Acquire the transaction set associated with a proposal.
|
||||||
|
|
||||||
If the transaction set is not available locally, will attempt acquire it
|
If the transaction set is not available locally, will attempt acquire it
|
||||||
from the network.
|
from the network.
|
||||||
|
|
||||||
@param setId The transaction set ID associated with the proposal
|
@param setId The transaction set ID associated with the proposal
|
||||||
@return Optional set of transactions, seated if available.
|
@return Optional set of transactions, seated if available.
|
||||||
*/
|
*/
|
||||||
boost::optional<RCLTxSet>
|
boost::optional<RCLTxSet>
|
||||||
acquireTxSet(RCLTxSet::ID const & setId);
|
acquireTxSet(RCLTxSet::ID const& setId);
|
||||||
|
|
||||||
/** Whether the open ledger has any transactions
|
/** Whether the open ledger has any transactions
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
hasOpenTransactions() const;
|
hasOpenTransactions() const;
|
||||||
|
|
||||||
@@ -171,133 +207,93 @@ private:
|
|||||||
@return the number of proposers that validated a ledger
|
@return the number of proposers that validated a ledger
|
||||||
*/
|
*/
|
||||||
std::size_t
|
std::size_t
|
||||||
proposersValidated(LedgerHash const & h) const;
|
proposersValidated(LedgerHash const& h) const;
|
||||||
|
|
||||||
/** Number of proposers that have validated a ledger descended from requested ledger.
|
/** Number of proposers that have validated a ledger descended from
|
||||||
|
requested ledger.
|
||||||
|
|
||||||
@param h The hash of the ledger of interest.
|
@param h The hash of the ledger of interest.
|
||||||
@return The number of validating peers that have validated a ledger
|
@return The number of validating peers that have validated a ledger
|
||||||
succeeding the one provided.
|
succeeding the one provided.
|
||||||
*/
|
*/
|
||||||
std::size_t
|
std::size_t
|
||||||
proposersFinished(LedgerHash const & h) const;
|
proposersFinished(LedgerHash const& h) const;
|
||||||
|
|
||||||
/** Propose the given position to my peers.
|
/** Propose the given position to my peers.
|
||||||
|
|
||||||
@param proposal Our proposed position
|
@param proposal Our proposed position
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
propose (RCLCxPeerPos::Proposal const& proposal);
|
propose(RCLCxPeerPos::Proposal const& proposal);
|
||||||
|
|
||||||
/** Share the given tx set with peers.
|
/** Relay the given tx set to peers.
|
||||||
|
|
||||||
@param set The TxSet to share.
|
@param set The TxSet to share.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
share (RCLTxSet const& set);
|
relay(RCLTxSet const& set);
|
||||||
|
|
||||||
/** Get the last closed ledger (LCL) seen on the network
|
/** Get the ID of the previous ledger/last closed ledger(LCL) on the network
|
||||||
|
|
||||||
@param currentLedger Current ledger used in consensus
|
@param ledgerID ID of previous ledger used by consensus
|
||||||
@param priorLedger Prior ledger used in consensus
|
@param ledger Previous ledger consensus has available
|
||||||
@param believedCorrect Whether consensus believes currentLedger is LCL
|
@param mode Current consensus mode
|
||||||
|
@return The id of the last closed network
|
||||||
|
|
||||||
@return The hash of the last closed network
|
@note ledgerID may not match ledger.id() if we haven't acquired
|
||||||
|
the ledger matching ledgerID from the network
|
||||||
*/
|
*/
|
||||||
uint256
|
uint256
|
||||||
getLCL (
|
getPrevLedger(
|
||||||
uint256 const& currentLedger,
|
uint256 ledgerID,
|
||||||
uint256 const& priorLedger,
|
RCLCxLedger const& ledger,
|
||||||
bool believedCorrect);
|
Mode mode);
|
||||||
|
|
||||||
|
/** Close the open ledger and return initial consensus position.
|
||||||
/** Notification that the ledger has closed.
|
|
||||||
|
|
||||||
@param ledger the ledger we are changing to
|
@param ledger the ledger we are changing to
|
||||||
@param haveCorrectLCL whether we believe this is the correct LCL
|
@param closeTime When consensus closed the ledger
|
||||||
|
@param mode Current consensus mode
|
||||||
|
@return Tentative consensus result
|
||||||
*/
|
*/
|
||||||
void
|
Result
|
||||||
onClose(RCLCxLedger const & ledger, bool haveCorrectLCL);
|
onClose(
|
||||||
|
RCLCxLedger const& ledger,
|
||||||
|
NetClock::time_point const& closeTime,
|
||||||
|
Mode mode);
|
||||||
|
|
||||||
/** Create our initial position of transactions to accept in this round
|
/** Process the accepted ledger.
|
||||||
of consensus.
|
|
||||||
|
|
||||||
@param prevLedger The ledger the transactions apply to
|
|
||||||
@param isProposing Whether we are currently proposing
|
|
||||||
@param isCorrectLCL Whether we have the correct LCL
|
|
||||||
@param closeTime When we believe the ledger closed
|
|
||||||
@param now The current network adjusted time
|
|
||||||
|
|
||||||
@return Pair of (i) transactions we believe are in the ledger
|
|
||||||
(ii) the corresponding proposal of those transactions
|
|
||||||
to send to peers
|
|
||||||
*/
|
|
||||||
std::pair <RCLTxSet, typename RCLCxPeerPos::Proposal>
|
|
||||||
makeInitialPosition (
|
|
||||||
RCLCxLedger const & prevLedger,
|
|
||||||
bool isProposing,
|
|
||||||
bool isCorrectLCL,
|
|
||||||
NetClock::time_point closeTime,
|
|
||||||
NetClock::time_point now);
|
|
||||||
|
|
||||||
|
|
||||||
/** Dispatch a call to Consensus::accept
|
|
||||||
|
|
||||||
Accepting a ledger may be expensive, so this function can dispatch
|
Accepting a ledger may be expensive, so this function can dispatch
|
||||||
that call to another thread if desired and must call the accept
|
that call to another thread if desired.
|
||||||
method of the generic consensus algorithm.
|
|
||||||
|
|
||||||
@param txSet The transactions to accept.
|
@param result The result of consensus
|
||||||
|
@param prevLedger The closed ledger consensus worked from
|
||||||
|
@param closeResolution The resolution used in agreeing on an effective
|
||||||
|
closeTiem
|
||||||
|
@param rawCloseTimes The unrounded closetimes of ourself and our peers
|
||||||
|
@param mode Our participating mode at the time consensus was declared
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
dispatchAccept(RCLTxSet const & txSet);
|
onAccept(
|
||||||
|
Result const& result,
|
||||||
|
RCLCxLedger const& prevLedger,
|
||||||
|
NetClock::duration const & closeResolution,
|
||||||
|
CloseTimes const& rawCloseTimes,
|
||||||
|
Mode const& mode);
|
||||||
|
|
||||||
|
/** Process the accepted ledger that was a result of simulation/force
|
||||||
|
accept.
|
||||||
|
|
||||||
/** Accept a new ledger based on the given transactions.
|
@ref onAccept
|
||||||
|
|
||||||
TODO: Too many arguments, need to group related types.
|
|
||||||
|
|
||||||
@param set The set of accepted transactions
|
|
||||||
@param consensusCloseTime Consensus agreed upon close time
|
|
||||||
@param proposing_ Whether we are proposing
|
|
||||||
@param validating_ Whether we are validating
|
|
||||||
@param haveCorrectLCL_ Whether we had the correct last closed ledger
|
|
||||||
@param consensusFail_ Whether consensus failed
|
|
||||||
@param prevLedgerHash_ The hash/id of the previous ledger
|
|
||||||
@param previousLedger_ The previous ledger
|
|
||||||
@param closeResolution_ The close time resolution used this round
|
|
||||||
@param now Current network adjsuted time
|
|
||||||
@param roundTime_ Duration of this consensus round
|
|
||||||
@param disputes_ Disputed trarnsactions from this round
|
|
||||||
@param closeTimes_ Histogram of peers close times
|
|
||||||
@param closeTime Our close time
|
|
||||||
@return Whether we should continue validating
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
accept(
|
|
||||||
RCLTxSet const& set,
|
|
||||||
NetClock::time_point consensusCloseTime,
|
|
||||||
bool proposing_,
|
|
||||||
bool validating_,
|
|
||||||
bool haveCorrectLCL_,
|
|
||||||
bool consensusFail_,
|
|
||||||
LedgerHash const &prevLedgerHash_,
|
|
||||||
RCLCxLedger const & previousLedger_,
|
|
||||||
NetClock::duration closeResolution_,
|
|
||||||
NetClock::time_point const & now,
|
|
||||||
std::chrono::milliseconds const & roundTime_,
|
|
||||||
hash_map<RCLCxTx::ID, DisputedTx <RCLCxTx, NodeID>> const & disputes_,
|
|
||||||
std::map <NetClock::time_point, int> closeTimes_,
|
|
||||||
NetClock::time_point const & closeTime
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Signal the end of consensus to the application, which will start the
|
|
||||||
next round.
|
|
||||||
|
|
||||||
@param correctLCL Whether we believe we have the correct LCL
|
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
endConsensus(bool correctLCL);
|
onForceAccept(
|
||||||
|
Result const& result,
|
||||||
|
RCLCxLedger const& prevLedger,
|
||||||
|
NetClock::duration const &closeResolution,
|
||||||
|
CloseTimes const& rawCloseTimes,
|
||||||
|
Mode const& mode);
|
||||||
|
|
||||||
//!-------------------------------------------------------------------------
|
//!-------------------------------------------------------------------------
|
||||||
// Additional members (not directly required by Consensus interface)
|
// Additional members (not directly required by Consensus interface)
|
||||||
@@ -308,42 +304,53 @@ private:
|
|||||||
@param haveCorrectLCL Whether we believ we have the correct LCL.
|
@param haveCorrectLCL Whether we believ we have the correct LCL.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
notify(protocol::NodeEvent ne, RCLCxLedger const & ledger, bool haveCorrectLCL);
|
notify(
|
||||||
|
protocol::NodeEvent ne,
|
||||||
|
RCLCxLedger const& ledger,
|
||||||
|
bool haveCorrectLCL);
|
||||||
|
|
||||||
/** Build the new last closed ledger.
|
/** Accept a new ledger based on the given transactions.
|
||||||
|
|
||||||
Accept the given the provided set of consensus transactions and build
|
@ref onAccept
|
||||||
the last closed ledger. Since consensus just agrees on which
|
*/
|
||||||
transactions to apply, but not whether they make it into the closed
|
void
|
||||||
ledger, this function also populates retriableTxs with those that can
|
doAccept(
|
||||||
be retried in the next round.
|
Result const& result,
|
||||||
|
RCLCxLedger const& prevLedger,
|
||||||
|
NetClock::duration closeResolution,
|
||||||
|
CloseTimes const& rawCloseTimes,
|
||||||
|
Mode const& mode);
|
||||||
|
|
||||||
@param previousLedger Prior ledger building upon
|
/** Build the new last closed ledger.
|
||||||
@param set The set of transactions to apply to the ledger
|
|
||||||
@param closeTime The the ledger closed
|
Accept the given the provided set of consensus transactions and build
|
||||||
@param closeTimeCorrect Whether consensus agreed on close time
|
the last closed ledger. Since consensus just agrees on which
|
||||||
@param closeResolution Resolution used to determine consensus close time
|
transactions to apply, but not whether they make it into the closed
|
||||||
@param now Current network adjusted time
|
ledger, this function also populates retriableTxs with those that can
|
||||||
@param roundTime Duration of this consensus rorund
|
be retried in the next round.
|
||||||
@param retriableTxs Populate with transactions to retry in next round
|
|
||||||
@return The newly built ledger
|
@param previousLedger Prior ledger building upon
|
||||||
*/
|
@param set The set of transactions to apply to the ledger
|
||||||
|
@param closeTime The the ledger closed
|
||||||
|
@param closeTimeCorrect Whether consensus agreed on close time
|
||||||
|
@param closeResolution Resolution used to determine consensus close time
|
||||||
|
@param roundTime Duration of this consensus rorund
|
||||||
|
@param retriableTxs Populate with transactions to retry in next round
|
||||||
|
@return The newly built ledger
|
||||||
|
*/
|
||||||
RCLCxLedger
|
RCLCxLedger
|
||||||
buildLCL(
|
buildLCL(
|
||||||
RCLCxLedger const & previousLedger,
|
RCLCxLedger const& previousLedger,
|
||||||
RCLTxSet const & set,
|
RCLTxSet const& set,
|
||||||
NetClock::time_point closeTime,
|
NetClock::time_point closeTime,
|
||||||
bool closeTimeCorrect,
|
bool closeTimeCorrect,
|
||||||
NetClock::duration closeResolution,
|
NetClock::duration closeResolution,
|
||||||
NetClock::time_point now,
|
|
||||||
std::chrono::milliseconds roundTime,
|
std::chrono::milliseconds roundTime,
|
||||||
CanonicalTXSet & retriableTxs
|
CanonicalTXSet& retriableTxs);
|
||||||
);
|
|
||||||
|
|
||||||
/** Validate the given ledger and share with peers as necessary
|
/** Validate the given ledger and share with peers as necessary
|
||||||
|
|
||||||
@param ledger The ledger to validate
|
@param ledger The ledger to validate
|
||||||
@param now Current network adjusted time
|
|
||||||
@param proposing Whether we were proposing transactions while generating
|
@param proposing Whether we were proposing transactions while generating
|
||||||
this ledger. If we are not proposing, a validation
|
this ledger. If we are not proposing, a validation
|
||||||
can still be sent to inform peers that we know we
|
can still be sent to inform peers that we know we
|
||||||
@@ -351,16 +358,13 @@ private:
|
|||||||
around and trying to catch up.
|
around and trying to catch up.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
validate(
|
validate(RCLCxLedger const& ledger, bool proposing);
|
||||||
RCLCxLedger const & ledger,
|
|
||||||
NetClock::time_point now,
|
|
||||||
bool proposing);
|
|
||||||
|
|
||||||
//!-------------------------------------------------------------------------
|
//!-------------------------------------------------------------------------
|
||||||
Application& app_;
|
Application& app_;
|
||||||
std::unique_ptr <FeeVote> feeVote_;
|
std::unique_ptr<FeeVote> feeVote_;
|
||||||
LedgerMaster & ledgerMaster_;
|
LedgerMaster& ledgerMaster_;
|
||||||
LocalTxs & localTxs_;
|
LocalTxs& localTxs_;
|
||||||
InboundTransactions& inboundTransactions_;
|
InboundTransactions& inboundTransactions_;
|
||||||
beast::Journal j_;
|
beast::Journal j_;
|
||||||
|
|
||||||
@@ -373,11 +377,13 @@ private:
|
|||||||
// only used for our own validations.
|
// only used for our own validations.
|
||||||
NetClock::time_point lastValidationTime_;
|
NetClock::time_point lastValidationTime_;
|
||||||
|
|
||||||
using PeerPositions = hash_map <NodeID, std::deque<RCLCxPeerPos::pointer>>;
|
using PeerPositions = hash_map<NodeID, std::deque<RCLCxPeerPos::pointer>>;
|
||||||
PeerPositions peerPositions_;
|
PeerPositions peerPositions_;
|
||||||
std::mutex peerPositionsLock_;
|
std::mutex peerPositionsLock_;
|
||||||
};
|
|
||||||
|
|
||||||
|
bool validating_ = false;
|
||||||
|
bool simulating_ = false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
#define RIPPLE_APP_CONSENSUS_RCLCXLEDGER_H_INCLUDED
|
#define RIPPLE_APP_CONSENSUS_RCLCXLEDGER_H_INCLUDED
|
||||||
|
|
||||||
#include <ripple/app/ledger/Ledger.h>
|
#include <ripple/app/ledger/Ledger.h>
|
||||||
#include <ripple/ledger/ReadView.h>
|
|
||||||
#include <ripple/app/ledger/LedgerToJson.h>
|
#include <ripple/app/ledger/LedgerToJson.h>
|
||||||
|
#include <ripple/ledger/ReadView.h>
|
||||||
#include <ripple/protocol/RippleLedgerHash.h>
|
#include <ripple/protocol/RippleLedgerHash.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -50,24 +50,26 @@ public:
|
|||||||
|
|
||||||
@param l The ledger to wrap.
|
@param l The ledger to wrap.
|
||||||
*/
|
*/
|
||||||
RCLCxLedger(std::shared_ptr<Ledger const> const & l) : ledger_{ l } {}
|
RCLCxLedger(std::shared_ptr<Ledger const> const& l) : ledger_{l}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
//! Sequence number of the ledger.
|
//! Sequence number of the ledger.
|
||||||
auto const &
|
auto const&
|
||||||
seq() const
|
seq() const
|
||||||
{
|
{
|
||||||
return ledger_->info().seq;
|
return ledger_->info().seq;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Unique identifier (hash) of this ledger.
|
//! Unique identifier (hash) of this ledger.
|
||||||
auto const &
|
auto const&
|
||||||
id() const
|
id() const
|
||||||
{
|
{
|
||||||
return ledger_->info().hash;
|
return ledger_->info().hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Unique identifier (hash) of this ledger's parent.
|
//! Unique identifier (hash) of this ledger's parent.
|
||||||
auto const &
|
auto const&
|
||||||
parentID() const
|
parentID() const
|
||||||
{
|
{
|
||||||
return ledger_->info().parentHash;
|
return ledger_->info().parentHash;
|
||||||
@@ -114,8 +116,6 @@ public:
|
|||||||
a new ledger from a readView?
|
a new ledger from a readView?
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<Ledger const> ledger_;
|
std::shared_ptr<Ledger const> ledger_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -19,28 +19,28 @@
|
|||||||
|
|
||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
#include <ripple/app/consensus/RCLCxPeerPos.h>
|
#include <ripple/app/consensus/RCLCxPeerPos.h>
|
||||||
#include <ripple/protocol/digest.h>
|
|
||||||
#include <ripple/core/Config.h>
|
#include <ripple/core/Config.h>
|
||||||
#include <ripple/protocol/JsonFields.h>
|
|
||||||
#include <ripple/protocol/HashPrefix.h>
|
#include <ripple/protocol/HashPrefix.h>
|
||||||
|
#include <ripple/protocol/JsonFields.h>
|
||||||
#include <ripple/protocol/Serializer.h>
|
#include <ripple/protocol/Serializer.h>
|
||||||
|
#include <ripple/protocol/digest.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
// Used to construct received proposals
|
// Used to construct received proposals
|
||||||
RCLCxPeerPos::RCLCxPeerPos (
|
RCLCxPeerPos::RCLCxPeerPos(
|
||||||
PublicKey const& publicKey,
|
PublicKey const& publicKey,
|
||||||
Slice const& signature,
|
Slice const& signature,
|
||||||
uint256 const& suppression,
|
uint256 const& suppression,
|
||||||
Proposal && proposal)
|
Proposal&& proposal)
|
||||||
: proposal_{ std::move(proposal)}
|
: proposal_{std::move(proposal)}
|
||||||
, mSuppression {suppression}
|
, mSuppression{suppression}
|
||||||
, publicKey_{publicKey}
|
, publicKey_{publicKey}
|
||||||
, signature_{signature}
|
, signature_{signature}
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
uint256 RCLCxPeerPos::getSigningHash () const
|
uint256
|
||||||
|
RCLCxPeerPos::getSigningHash() const
|
||||||
{
|
{
|
||||||
return sha512Half(
|
return sha512Half(
|
||||||
HashPrefix::proposal,
|
HashPrefix::proposal,
|
||||||
@@ -50,28 +50,25 @@ uint256 RCLCxPeerPos::getSigningHash () const
|
|||||||
proposal().position());
|
proposal().position());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RCLCxPeerPos::checkSign () const
|
bool
|
||||||
|
RCLCxPeerPos::checkSign() const
|
||||||
{
|
{
|
||||||
return verifyDigest (
|
return verifyDigest(publicKey_, getSigningHash(), signature_, false);
|
||||||
publicKey_,
|
|
||||||
getSigningHash(),
|
|
||||||
signature_,
|
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value RCLCxPeerPos::getJson () const
|
Json::Value
|
||||||
|
RCLCxPeerPos::getJson() const
|
||||||
{
|
{
|
||||||
auto ret = proposal().getJson();
|
auto ret = proposal().getJson();
|
||||||
|
|
||||||
if (publicKey_.size())
|
if (publicKey_.size())
|
||||||
ret[jss::peer_id] = toBase58 (
|
ret[jss::peer_id] = toBase58(TokenType::TOKEN_NODE_PUBLIC, publicKey_);
|
||||||
TokenType::TOKEN_NODE_PUBLIC,
|
|
||||||
publicKey_);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 proposalUniqueId (
|
uint256
|
||||||
|
proposalUniqueId(
|
||||||
uint256 const& proposeHash,
|
uint256 const& proposeHash,
|
||||||
uint256 const& previousLedger,
|
uint256 const& previousLedger,
|
||||||
std::uint32_t proposeSeq,
|
std::uint32_t proposeSeq,
|
||||||
@@ -79,15 +76,15 @@ uint256 proposalUniqueId (
|
|||||||
Slice const& publicKey,
|
Slice const& publicKey,
|
||||||
Slice const& signature)
|
Slice const& signature)
|
||||||
{
|
{
|
||||||
Serializer s (512);
|
Serializer s(512);
|
||||||
s.add256 (proposeHash);
|
s.add256(proposeHash);
|
||||||
s.add256 (previousLedger);
|
s.add256(previousLedger);
|
||||||
s.add32 (proposeSeq);
|
s.add32(proposeSeq);
|
||||||
s.add32 (closeTime.time_since_epoch().count());
|
s.add32(closeTime.time_since_epoch().count());
|
||||||
s.addVL (publicKey);
|
s.addVL(publicKey);
|
||||||
s.addVL (signature);
|
s.addVL(signature);
|
||||||
|
|
||||||
return s.getSHA512Half ();
|
return s.getSHA512Half();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|||||||
@@ -22,12 +22,12 @@
|
|||||||
|
|
||||||
#include <ripple/basics/CountedObject.h>
|
#include <ripple/basics/CountedObject.h>
|
||||||
#include <ripple/basics/base_uint.h>
|
#include <ripple/basics/base_uint.h>
|
||||||
|
#include <ripple/beast/hash/hash_append.h>
|
||||||
|
#include <ripple/consensus/ConsensusProposal.h>
|
||||||
#include <ripple/json/json_value.h>
|
#include <ripple/json/json_value.h>
|
||||||
#include <ripple/protocol/HashPrefix.h>
|
#include <ripple/protocol/HashPrefix.h>
|
||||||
#include <ripple/protocol/PublicKey.h>
|
#include <ripple/protocol/PublicKey.h>
|
||||||
#include <ripple/protocol/SecretKey.h>
|
#include <ripple/protocol/SecretKey.h>
|
||||||
#include <ripple/beast/hash/hash_append.h>
|
|
||||||
#include <ripple/consensus/ConsensusProposal.h>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -38,18 +38,20 @@ namespace ripple {
|
|||||||
|
|
||||||
Carries a ConsensusProposal signed by a peer.
|
Carries a ConsensusProposal signed by a peer.
|
||||||
*/
|
*/
|
||||||
class RCLCxPeerPos
|
class RCLCxPeerPos : public CountedObject<RCLCxPeerPos>
|
||||||
: public CountedObject <RCLCxPeerPos>
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static char const* getCountedObjectName () { return "RCLCxPeerPos"; }
|
static char const*
|
||||||
|
getCountedObjectName()
|
||||||
|
{
|
||||||
|
return "RCLCxPeerPos";
|
||||||
|
}
|
||||||
using pointer = std::shared_ptr<RCLCxPeerPos>;
|
using pointer = std::shared_ptr<RCLCxPeerPos>;
|
||||||
using ref = const pointer&;
|
using ref = const pointer&;
|
||||||
|
|
||||||
//< The type of the proposed position
|
//< The type of the proposed position
|
||||||
using Proposal = ConsensusProposal<NodeID, uint256, uint256>;
|
using Proposal = ConsensusProposal<NodeID, uint256, uint256>;
|
||||||
|
|
||||||
|
|
||||||
/** Constructor
|
/** Constructor
|
||||||
|
|
||||||
Constructs a signed peer position.
|
Constructs a signed peer position.
|
||||||
@@ -60,57 +62,64 @@ public:
|
|||||||
@param proposal The consensus proposal
|
@param proposal The consensus proposal
|
||||||
*/
|
*/
|
||||||
|
|
||||||
RCLCxPeerPos (
|
RCLCxPeerPos(
|
||||||
PublicKey const& publicKey,
|
PublicKey const& publicKey,
|
||||||
Slice const& signature,
|
Slice const& signature,
|
||||||
uint256 const& suppress,
|
uint256 const& suppress,
|
||||||
Proposal && proposal);
|
Proposal&& proposal);
|
||||||
|
|
||||||
//! Create the signing hash for the proposal
|
//! Create the signing hash for the proposal
|
||||||
uint256 getSigningHash () const;
|
uint256
|
||||||
|
getSigningHash() const;
|
||||||
|
|
||||||
//! Verify the signing hash of the proposal
|
//! Verify the signing hash of the proposal
|
||||||
bool checkSign () const;
|
bool
|
||||||
|
checkSign() const;
|
||||||
|
|
||||||
//! Signature of the proposal (not necessarily verified)
|
//! Signature of the proposal (not necessarily verified)
|
||||||
Slice getSignature () const
|
Slice
|
||||||
|
getSignature() const
|
||||||
{
|
{
|
||||||
return signature_;
|
return signature_;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Public key of peer that sent the proposal
|
//! Public key of peer that sent the proposal
|
||||||
PublicKey const& getPublicKey () const
|
PublicKey const&
|
||||||
|
getPublicKey() const
|
||||||
{
|
{
|
||||||
return publicKey_;
|
return publicKey_;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! ?????
|
//! ?????
|
||||||
uint256 const& getSuppressionID () const
|
uint256 const&
|
||||||
|
getSuppressionID() const
|
||||||
{
|
{
|
||||||
return mSuppression;
|
return mSuppression;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! The consensus proposal
|
//! The consensus proposal
|
||||||
Proposal const & proposal() const
|
Proposal const&
|
||||||
|
proposal() const
|
||||||
{
|
{
|
||||||
return proposal_;
|
return proposal_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @cond Ignore
|
/// @cond Ignore
|
||||||
//! Add a conversion operator to conform to the Consensus interface
|
//! Add a conversion operator to conform to the Consensus interface
|
||||||
operator Proposal const &() const
|
operator Proposal const&() const
|
||||||
{
|
{
|
||||||
return proposal_;
|
return proposal_;
|
||||||
}
|
}
|
||||||
/// @endcond
|
/// @endcond
|
||||||
|
|
||||||
//! JSON representation of proposal
|
//! JSON representation of proposal
|
||||||
Json::Value getJson () const;
|
Json::Value
|
||||||
|
getJson() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <class Hasher>
|
template <class Hasher>
|
||||||
void
|
void
|
||||||
hash_append (Hasher& h) const
|
hash_append(Hasher& h) const
|
||||||
{
|
{
|
||||||
using beast::hash_append;
|
using beast::hash_append;
|
||||||
hash_append(h, HashPrefix::proposal);
|
hash_append(h, HashPrefix::proposal);
|
||||||
@@ -142,14 +151,15 @@ private:
|
|||||||
@param publicKey Signer's public key
|
@param publicKey Signer's public key
|
||||||
@param signature Proposal signature
|
@param signature Proposal signature
|
||||||
*/
|
*/
|
||||||
uint256 proposalUniqueId (
|
uint256
|
||||||
uint256 const& proposeHash,
|
proposalUniqueId(
|
||||||
uint256 const& previousLedger,
|
uint256 const& proposeHash,
|
||||||
std::uint32_t proposeSeq,
|
uint256 const& previousLedger,
|
||||||
NetClock::time_point closeTime,
|
std::uint32_t proposeSeq,
|
||||||
Slice const& publicKey,
|
NetClock::time_point closeTime,
|
||||||
Slice const& signature);
|
Slice const& publicKey,
|
||||||
|
Slice const& signature);
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
#ifndef RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED
|
#ifndef RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED
|
||||||
#define RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED
|
#define RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED
|
||||||
|
|
||||||
|
#include <ripple/app/misc/CanonicalTXSet.h>
|
||||||
#include <ripple/basics/chrono.h>
|
#include <ripple/basics/chrono.h>
|
||||||
#include <ripple/protocol/UintTypes.h>
|
#include <ripple/protocol/UintTypes.h>
|
||||||
#include <ripple/shamap/SHAMap.h>
|
#include <ripple/shamap/SHAMap.h>
|
||||||
#include <ripple/app/misc/CanonicalTXSet.h>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
@@ -42,14 +42,15 @@ public:
|
|||||||
|
|
||||||
@param txn The transaction to wrap
|
@param txn The transaction to wrap
|
||||||
*/
|
*/
|
||||||
RCLCxTx(SHAMapItem const& txn) : tx_{ txn }
|
RCLCxTx(SHAMapItem const& txn) : tx_{txn}
|
||||||
{ }
|
{
|
||||||
|
}
|
||||||
|
|
||||||
//! The unique identifier/hash of the transaction
|
//! The unique identifier/hash of the transaction
|
||||||
ID const&
|
ID const&
|
||||||
id() const
|
id() const
|
||||||
{
|
{
|
||||||
return tx_.key ();
|
return tx_.key();
|
||||||
}
|
}
|
||||||
|
|
||||||
//! The SHAMapItem that represents the transaction.
|
//! The SHAMapItem that represents the transaction.
|
||||||
@@ -74,13 +75,11 @@ public:
|
|||||||
{
|
{
|
||||||
friend class RCLTxSet;
|
friend class RCLTxSet;
|
||||||
//! The SHAMap representing the transactions.
|
//! The SHAMap representing the transactions.
|
||||||
std::shared_ptr <SHAMap> map_;
|
std::shared_ptr<SHAMap> map_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MutableTxSet(RCLTxSet const & src)
|
MutableTxSet(RCLTxSet const& src) : map_{src.map_->snapShot(true)}
|
||||||
: map_{ src.map_->snapShot(true) }
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Insert a new transaction into the set.
|
/** Insert a new transaction into the set.
|
||||||
@@ -92,8 +91,7 @@ public:
|
|||||||
insert(Tx const& t)
|
insert(Tx const& t)
|
||||||
{
|
{
|
||||||
return map_->addItem(
|
return map_->addItem(
|
||||||
SHAMapItem{ t.id(), t.tx_.peekData() },
|
SHAMapItem{t.id(), t.tx_.peekData()}, true, false);
|
||||||
true, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Remove a transaction from the set.
|
/** Remove a transaction from the set.
|
||||||
@@ -112,8 +110,7 @@ public:
|
|||||||
|
|
||||||
@param m SHAMap to wrap
|
@param m SHAMap to wrap
|
||||||
*/
|
*/
|
||||||
RCLTxSet (std::shared_ptr<SHAMap> m)
|
RCLTxSet(std::shared_ptr<SHAMap> m) : map_{std::move(m)}
|
||||||
: map_{ std::move(m) }
|
|
||||||
{
|
{
|
||||||
assert(map_);
|
assert(map_);
|
||||||
}
|
}
|
||||||
@@ -122,10 +119,8 @@ public:
|
|||||||
|
|
||||||
@param m MutableTxSet that will become fixed
|
@param m MutableTxSet that will become fixed
|
||||||
*/
|
*/
|
||||||
RCLTxSet(MutableTxSet const & m)
|
RCLTxSet(MutableTxSet const& m) : map_{m.map_->snapShot(false)}
|
||||||
: map_{m.map_->snapShot(false)}
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Test if a transaction is in the set.
|
/** Test if a transaction is in the set.
|
||||||
@@ -136,7 +131,7 @@ public:
|
|||||||
bool
|
bool
|
||||||
exists(Tx::ID const& entry) const
|
exists(Tx::ID const& entry) const
|
||||||
{
|
{
|
||||||
return map_->hasItem (entry);
|
return map_->hasItem(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Lookup a transaction.
|
/** Lookup a transaction.
|
||||||
@@ -150,10 +145,10 @@ public:
|
|||||||
code use the shared_ptr semantics to know whether the find
|
code use the shared_ptr semantics to know whether the find
|
||||||
was succesfully and properly creates a Tx as needed.
|
was succesfully and properly creates a Tx as needed.
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<const SHAMapItem> const &
|
std::shared_ptr<const SHAMapItem> const&
|
||||||
find(Tx::ID const& entry) const
|
find(Tx::ID const& entry) const
|
||||||
{
|
{
|
||||||
return map_->peekItem (entry);
|
return map_->peekItem(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
//! The unique ID/hash of the transaction set
|
//! The unique ID/hash of the transaction set
|
||||||
@@ -163,7 +158,8 @@ public:
|
|||||||
return map_->getHash().as_uint256();
|
return map_->getHash().as_uint256();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find transactions not in common between this and another transaction set.
|
/** Find transactions not in common between this and another transaction
|
||||||
|
set.
|
||||||
|
|
||||||
@param j The set to compare with
|
@param j The set to compare with
|
||||||
@return Map of transactions in this set and `j` but not both. The key
|
@return Map of transactions in this set and `j` but not both. The key
|
||||||
@@ -171,28 +167,28 @@ public:
|
|||||||
exists in this set.
|
exists in this set.
|
||||||
*/
|
*/
|
||||||
std::map<Tx::ID, bool>
|
std::map<Tx::ID, bool>
|
||||||
compare (RCLTxSet const& j) const
|
compare(RCLTxSet const& j) const
|
||||||
{
|
{
|
||||||
SHAMap::Delta delta;
|
SHAMap::Delta delta;
|
||||||
|
|
||||||
// Bound the work we do in case of a malicious
|
// Bound the work we do in case of a malicious
|
||||||
// map_ from a trusted validator
|
// map_ from a trusted validator
|
||||||
map_->compare (*(j.map_), delta, 65536);
|
map_->compare(*(j.map_), delta, 65536);
|
||||||
|
|
||||||
std::map <uint256, bool> ret;
|
std::map<uint256, bool> ret;
|
||||||
for (auto const& item : delta)
|
for (auto const& item : delta)
|
||||||
{
|
{
|
||||||
assert ( (item.second.first && ! item.second.second) ||
|
assert(
|
||||||
(item.second.second && ! item.second.first) );
|
(item.second.first && !item.second.second) ||
|
||||||
|
(item.second.second && !item.second.first));
|
||||||
|
|
||||||
ret[item.first] = static_cast<bool> (item.second.first);
|
ret[item.first] = static_cast<bool>(item.second.first);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! The SHAMap representing the transactions.
|
//! The SHAMap representing the transactions.
|
||||||
std::shared_ptr <SHAMap> map_;
|
std::shared_ptr<SHAMap> map_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
bool beginConsensus (uint256 const& networkClosed) override;
|
bool beginConsensus (uint256 const& networkClosed) override;
|
||||||
void endConsensus (bool correctLCL) override;
|
void endConsensus () override;
|
||||||
void setStandAlone () override
|
void setStandAlone () override
|
||||||
{
|
{
|
||||||
setMode (omFULL);
|
setMode (omFULL);
|
||||||
@@ -1525,7 +1525,7 @@ bool NetworkOPsImp::beginConsensus (uint256 const& networkClosed)
|
|||||||
|
|
||||||
uint256 NetworkOPsImp::getConsensusLCL ()
|
uint256 NetworkOPsImp::getConsensusLCL ()
|
||||||
{
|
{
|
||||||
return mConsensus->LCL ();
|
return mConsensus->prevLedgerID ();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkOPsImp::processTrustedProposal (
|
void NetworkOPsImp::processTrustedProposal (
|
||||||
@@ -1565,7 +1565,7 @@ NetworkOPsImp::mapComplete (
|
|||||||
RCLTxSet{map});
|
RCLTxSet{map});
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkOPsImp::endConsensus (bool correctLCL)
|
void NetworkOPsImp::endConsensus ()
|
||||||
{
|
{
|
||||||
uint256 deadLedger = m_ledgerMaster.getClosedLedger ()->info().parentHash;
|
uint256 deadLedger = m_ledgerMaster.getClosedLedger ()->info().parentHash;
|
||||||
|
|
||||||
@@ -2164,18 +2164,18 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin)
|
|||||||
info[jss::peers] = Json::UInt (app_.overlay ().size ());
|
info[jss::peers] = Json::UInt (app_.overlay ().size ());
|
||||||
|
|
||||||
Json::Value lastClose = Json::objectValue;
|
Json::Value lastClose = Json::objectValue;
|
||||||
lastClose[jss::proposers] = mConsensus->getLastCloseProposers();
|
lastClose[jss::proposers] = Json::UInt(mConsensus->prevProposers());
|
||||||
|
|
||||||
if (human)
|
if (human)
|
||||||
{
|
{
|
||||||
lastClose[jss::converge_time_s] =
|
lastClose[jss::converge_time_s] =
|
||||||
std::chrono::duration<double>{
|
std::chrono::duration<double>{
|
||||||
mConsensus->getLastConvergeDuration()}.count();
|
mConsensus->prevRoundTime()}.count();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lastClose[jss::converge_time] =
|
lastClose[jss::converge_time] =
|
||||||
Json::Int (mConsensus->getLastConvergeDuration().count());
|
Json::Int (mConsensus->prevRoundTime().count());
|
||||||
}
|
}
|
||||||
|
|
||||||
info[jss::last_close] = lastClose;
|
info[jss::last_close] = lastClose;
|
||||||
@@ -2754,9 +2754,7 @@ std::uint32_t NetworkOPsImp::acceptLedger (
|
|||||||
// FIXME Could we improve on this and remove the need for a specialized
|
// FIXME Could we improve on this and remove the need for a specialized
|
||||||
// API in Consensus?
|
// API in Consensus?
|
||||||
beginConsensus (m_ledgerMaster.getClosedLedger()->info().hash);
|
beginConsensus (m_ledgerMaster.getClosedLedger()->info().hash);
|
||||||
mConsensus->simulate (
|
mConsensus->simulate (app_.timeKeeper().closeTime(), consensusDelay);
|
||||||
app_.timeKeeper().closeTime(),
|
|
||||||
consensusDelay);
|
|
||||||
return m_ledgerMaster.getCurrentLedger ()->info().seq;
|
return m_ledgerMaster.getCurrentLedger ()->info().seq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ public:
|
|||||||
|
|
||||||
// network state machine
|
// network state machine
|
||||||
virtual bool beginConsensus (uint256 const& netLCL) = 0;
|
virtual bool beginConsensus (uint256 const& netLCL) = 0;
|
||||||
virtual void endConsensus (bool correctLCL) = 0;
|
virtual void endConsensus () = 0;
|
||||||
virtual void setStandAlone () = 0;
|
virtual void setStandAlone () = 0;
|
||||||
virtual void setStateTimer () = 0;
|
virtual void setStateTimer () = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/*
|
/*
|
||||||
This file is part of rippled: https://github.com/ripple/rippled
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
Copyright (c) 2012-2016 Ripple Labs Inc.
|
Copyright (c) 2012-2017 Ripple Labs Inc.
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
@@ -19,13 +19,12 @@
|
|||||||
#ifndef RIPPLE_CONSENSUS_ConsensusProposal_H_INCLUDED
|
#ifndef RIPPLE_CONSENSUS_ConsensusProposal_H_INCLUDED
|
||||||
#define RIPPLE_CONSENSUS_ConsensusProposal_H_INCLUDED
|
#define RIPPLE_CONSENSUS_ConsensusProposal_H_INCLUDED
|
||||||
|
|
||||||
#include <cstdint>
|
#include <ripple/basics/chrono.h>
|
||||||
#include <ripple/json/json_value.h>
|
#include <ripple/json/json_value.h>
|
||||||
#include <ripple/protocol/JsonFields.h>
|
#include <ripple/protocol/JsonFields.h>
|
||||||
#include <ripple/basics/chrono.h>
|
#include <cstdint>
|
||||||
|
|
||||||
namespace ripple
|
namespace ripple {
|
||||||
{
|
|
||||||
/** Represents a proposed position taken during a round of consensus.
|
/** Represents a proposed position taken during a round of consensus.
|
||||||
|
|
||||||
During consensus, peers seek agreement on a set of transactions to
|
During consensus, peers seek agreement on a set of transactions to
|
||||||
@@ -49,10 +48,7 @@ namespace ripple
|
|||||||
@tparam Position_t Type used to represent the position taken on transactions
|
@tparam Position_t Type used to represent the position taken on transactions
|
||||||
under consideration during this round of consensus
|
under consideration during this round of consensus
|
||||||
*/
|
*/
|
||||||
template <
|
template <class NodeID_t, class LedgerID_t, class Position_t>
|
||||||
class NodeID_t,
|
|
||||||
class LedgerID_t,
|
|
||||||
class Position_t>
|
|
||||||
class ConsensusProposal
|
class ConsensusProposal
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -64,7 +60,6 @@ public:
|
|||||||
//< Sequence number when a peer wants to bow out and leave consensus
|
//< Sequence number when a peer wants to bow out and leave consensus
|
||||||
static std::uint32_t const seqLeave = 0xffffffff;
|
static std::uint32_t const seqLeave = 0xffffffff;
|
||||||
|
|
||||||
|
|
||||||
/** Constructor
|
/** Constructor
|
||||||
|
|
||||||
@param prevLedger The previous ledger this proposal is building on.
|
@param prevLedger The previous ledger this proposal is building on.
|
||||||
@@ -81,33 +76,32 @@ public:
|
|||||||
NetClock::time_point closeTime,
|
NetClock::time_point closeTime,
|
||||||
NetClock::time_point now,
|
NetClock::time_point now,
|
||||||
NodeID_t const& nodeID)
|
NodeID_t const& nodeID)
|
||||||
: previousLedger_(prevLedger)
|
: previousLedger_(prevLedger)
|
||||||
, position_(position)
|
, position_(position)
|
||||||
, closeTime_(closeTime)
|
, closeTime_(closeTime)
|
||||||
, time_(now)
|
, time_(now)
|
||||||
, proposeSeq_(seq)
|
, proposeSeq_(seq)
|
||||||
, nodeID_(nodeID)
|
, nodeID_(nodeID)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Identifying which peer took this position.
|
//! Identifying which peer took this position.
|
||||||
NodeID_t const&
|
NodeID_t const&
|
||||||
nodeID () const
|
nodeID() const
|
||||||
{
|
{
|
||||||
return nodeID_;
|
return nodeID_;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Get the proposed position.
|
//! Get the proposed position.
|
||||||
Position_t const&
|
Position_t const&
|
||||||
position () const
|
position() const
|
||||||
{
|
{
|
||||||
return position_;
|
return position_;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Get the prior accepted ledger this position is based on.
|
//! Get the prior accepted ledger this position is based on.
|
||||||
LedgerID_t const&
|
LedgerID_t const&
|
||||||
prevLedger () const
|
prevLedger() const
|
||||||
{
|
{
|
||||||
return previousLedger_;
|
return previousLedger_;
|
||||||
}
|
}
|
||||||
@@ -120,21 +114,21 @@ public:
|
|||||||
@return the sequence number
|
@return the sequence number
|
||||||
*/
|
*/
|
||||||
std::uint32_t
|
std::uint32_t
|
||||||
proposeSeq () const
|
proposeSeq() const
|
||||||
{
|
{
|
||||||
return proposeSeq_;
|
return proposeSeq_;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! The current position on the consensus close time.
|
//! The current position on the consensus close time.
|
||||||
NetClock::time_point const &
|
NetClock::time_point const&
|
||||||
closeTime () const
|
closeTime() const
|
||||||
{
|
{
|
||||||
return closeTime_;
|
return closeTime_;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Get when this position was taken.
|
//! Get when this position was taken.
|
||||||
NetClock::time_point const &
|
NetClock::time_point const&
|
||||||
seenTime () const
|
seenTime() const
|
||||||
{
|
{
|
||||||
return time_;
|
return time_;
|
||||||
}
|
}
|
||||||
@@ -143,49 +137,43 @@ public:
|
|||||||
consensus round.
|
consensus round.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
isInitial () const
|
isInitial() const
|
||||||
{
|
{
|
||||||
return proposeSeq_ == seqJoin;
|
return proposeSeq_ == seqJoin;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Get whether this node left the consensus process
|
//! Get whether this node left the consensus process
|
||||||
bool
|
bool
|
||||||
isBowOut () const
|
isBowOut() const
|
||||||
{
|
{
|
||||||
return proposeSeq_ == seqLeave;
|
return proposeSeq_ == seqLeave;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Get whether this position is stale relative to the provided cutoff
|
//! Get whether this position is stale relative to the provided cutoff
|
||||||
bool
|
bool
|
||||||
isStale (NetClock::time_point cutoff) const
|
isStale(NetClock::time_point cutoff) const
|
||||||
{
|
{
|
||||||
return time_ <= cutoff;
|
return time_ <= cutoff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update the position during the consensus process. This will increment
|
/** Update the position during the consensus process. This will increment
|
||||||
the proposal's sequence number.
|
the proposal's sequence number if it has not already bowed out.
|
||||||
|
|
||||||
@param newPosition The new position taken.
|
@param newPosition The new position taken.
|
||||||
@param newCloseTime The new close time.
|
@param newCloseTime The new close time.
|
||||||
@param now the time The new position was taken.
|
@param now the time The new position was taken
|
||||||
|
|
||||||
@return `true` if the position was updated or `false` if this node has
|
|
||||||
already left this consensus round.
|
|
||||||
*/
|
*/
|
||||||
bool
|
void
|
||||||
changePosition(
|
changePosition(
|
||||||
Position_t const& newPosition,
|
Position_t const& newPosition,
|
||||||
NetClock::time_point newCloseTime,
|
NetClock::time_point newCloseTime,
|
||||||
NetClock::time_point now)
|
NetClock::time_point now)
|
||||||
{
|
{
|
||||||
if (proposeSeq_ == seqLeave)
|
position_ = newPosition;
|
||||||
return false;
|
closeTime_ = newCloseTime;
|
||||||
|
time_ = now;
|
||||||
position_ = newPosition;
|
if (proposeSeq_ != seqLeave)
|
||||||
closeTime_ = newCloseTime;
|
++proposeSeq_;
|
||||||
time_ = now;
|
|
||||||
++proposeSeq_;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Leave consensus
|
/** Leave consensus
|
||||||
@@ -197,32 +185,32 @@ public:
|
|||||||
void
|
void
|
||||||
bowOut(NetClock::time_point now)
|
bowOut(NetClock::time_point now)
|
||||||
{
|
{
|
||||||
time_ = now;
|
time_ = now;
|
||||||
proposeSeq_ = seqLeave;
|
proposeSeq_ = seqLeave;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Get JSON representation for debugging
|
//! Get JSON representation for debugging
|
||||||
Json::Value
|
Json::Value
|
||||||
getJson () const
|
getJson() const
|
||||||
{
|
{
|
||||||
using std::to_string;
|
using std::to_string;
|
||||||
|
|
||||||
Json::Value ret = Json::objectValue;
|
Json::Value ret = Json::objectValue;
|
||||||
ret[jss::previous_ledger] = to_string (prevLedger());
|
ret[jss::previous_ledger] = to_string(prevLedger());
|
||||||
|
|
||||||
if (!isBowOut())
|
if (!isBowOut())
|
||||||
{
|
{
|
||||||
ret[jss::transaction_hash] = to_string (position());
|
ret[jss::transaction_hash] = to_string(position());
|
||||||
ret[jss::propose_seq] = proposeSeq();
|
ret[jss::propose_seq] = proposeSeq();
|
||||||
}
|
}
|
||||||
|
|
||||||
ret[jss::close_time] = to_string(closeTime().time_since_epoch().count());
|
ret[jss::close_time] =
|
||||||
|
to_string(closeTime().time_since_epoch().count());
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
//! Unique identifier of prior ledger this proposal is based on
|
//! Unique identifier of prior ledger this proposal is based on
|
||||||
LedgerID_t previousLedger_;
|
LedgerID_t previousLedger_;
|
||||||
|
|
||||||
@@ -240,23 +228,17 @@ private:
|
|||||||
|
|
||||||
//! The identifier of the node taking this position
|
//! The identifier of the node taking this position
|
||||||
NodeID_t nodeID_;
|
NodeID_t nodeID_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class NodeID_t,
|
template <class NodeID_t, class LedgerID_t, class Position_t>
|
||||||
class LedgerID_t,
|
|
||||||
class Position_t>
|
|
||||||
bool
|
bool
|
||||||
operator==(ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const & a,
|
operator==(
|
||||||
ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const & b)
|
ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const& a,
|
||||||
|
ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const& b)
|
||||||
{
|
{
|
||||||
return a.nodeID() == b.nodeID() &&
|
return a.nodeID() == b.nodeID() && a.proposeSeq() == b.proposeSeq() &&
|
||||||
a.proposeSeq() == b.proposeSeq() &&
|
a.prevLedger() == b.prevLedger() && a.position() == b.position() &&
|
||||||
a.prevLedger() == b.prevLedger() &&
|
a.closeTime() == b.closeTime() && a.seenTime() == b.seenTime();
|
||||||
a.position() == b.position() &&
|
|
||||||
a.closeTime() == b.closeTime() &&
|
|
||||||
a.seenTime() == b.seenTime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -17,15 +17,15 @@
|
|||||||
*/
|
*/
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#ifndef RIPPLE_APP_CONSENSUS_DISPUTEDTX_H_INCLUDED
|
#ifndef RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
|
||||||
#define RIPPLE_APP_CONSENSUS_DISPUTEDTX_H_INCLUDED
|
#define RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
|
||||||
|
|
||||||
#include <ripple/protocol/UintTypes.h>
|
#include <ripple/basics/Log.h>
|
||||||
#include <ripple/protocol/Serializer.h>
|
|
||||||
#include <ripple/basics/base_uint.h>
|
#include <ripple/basics/base_uint.h>
|
||||||
#include <ripple/beast/utility/Journal.h>
|
#include <ripple/beast/utility/Journal.h>
|
||||||
#include <ripple/consensus/LedgerTiming.h>
|
#include <ripple/consensus/LedgerTiming.h>
|
||||||
#include <ripple/basics/Log.h>
|
#include <ripple/protocol/Serializer.h>
|
||||||
|
#include <ripple/protocol/UintTypes.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
@@ -47,7 +47,8 @@ namespace ripple {
|
|||||||
template <class Tx_t, class NodeID_t>
|
template <class Tx_t, class NodeID_t>
|
||||||
class DisputedTx
|
class DisputedTx
|
||||||
{
|
{
|
||||||
using TxID_t = typename Tx_t::ID;
|
using TxID_t = typename Tx_t::ID;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** Constructor
|
/** Constructor
|
||||||
|
|
||||||
@@ -55,41 +56,35 @@ public:
|
|||||||
@param ourVote Our vote on whether tx should be included
|
@param ourVote Our vote on whether tx should be included
|
||||||
@param j Journal for debugging
|
@param j Journal for debugging
|
||||||
*/
|
*/
|
||||||
DisputedTx (Tx_t const& tx,
|
DisputedTx(Tx_t const& tx, bool ourVote, beast::Journal j)
|
||||||
bool ourVote,
|
: yays_(0), nays_(0), ourVote_(ourVote), tx_(tx), j_(j)
|
||||||
beast::Journal j)
|
|
||||||
: yays_ (0)
|
|
||||||
, nays_ (0)
|
|
||||||
, ourVote_ (ourVote)
|
|
||||||
, tx_ (tx)
|
|
||||||
, j_ (j)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
//! The unique id/hash of the disputed transaction.
|
//! The unique id/hash of the disputed transaction.
|
||||||
TxID_t
|
TxID_t const&
|
||||||
const& ID () const
|
ID() const
|
||||||
{
|
{
|
||||||
return tx_.id();
|
return tx_.id();
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Our vote on whether the transaction should be included.
|
//! Our vote on whether the transaction should be included.
|
||||||
bool
|
bool
|
||||||
getOurVote () const
|
getOurVote() const
|
||||||
{
|
{
|
||||||
return ourVote_;
|
return ourVote_;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! The disputed transaction.
|
//! The disputed transaction.
|
||||||
Tx_t
|
Tx_t const&
|
||||||
const& tx () const
|
tx() const
|
||||||
{
|
{
|
||||||
return tx_;
|
return tx_;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Change our vote
|
//! Change our vote
|
||||||
void
|
void
|
||||||
setOurVote (bool o)
|
setOurVote(bool o)
|
||||||
{
|
{
|
||||||
ourVote_ = o;
|
ourVote_ = o;
|
||||||
}
|
}
|
||||||
@@ -100,14 +95,14 @@ public:
|
|||||||
@param votesYes Whether peer votes to include the disputed transaction.
|
@param votesYes Whether peer votes to include the disputed transaction.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
setVote (NodeID_t const& peer, bool votesYes);
|
setVote(NodeID_t const& peer, bool votesYes);
|
||||||
|
|
||||||
/** Remove a peer's vote
|
/** Remove a peer's vote
|
||||||
|
|
||||||
@param peer Identifier of peer.
|
@param peer Identifier of peer.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
unVote (NodeID_t const& peer);
|
unVote(NodeID_t const& peer);
|
||||||
|
|
||||||
/** Update our vote given progression of consensus.
|
/** Update our vote given progression of consensus.
|
||||||
|
|
||||||
@@ -120,49 +115,47 @@ public:
|
|||||||
@return Whether our vote changed
|
@return Whether our vote changed
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
updateVote (int percentTime, bool proposing);
|
updateVote(int percentTime, bool proposing);
|
||||||
|
|
||||||
//! JSON representation of dispute, used for debugging
|
//! JSON representation of dispute, used for debugging
|
||||||
Json::Value
|
Json::Value
|
||||||
getJson () const;
|
getJson() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int yays_; //< Number of yes votes
|
int yays_; //< Number of yes votes
|
||||||
int nays_; //< Number of no votes
|
int nays_; //< Number of no votes
|
||||||
bool ourVote_; //< Our vote (true is yes)
|
bool ourVote_; //< Our vote (true is yes)
|
||||||
Tx_t tx_; //< Transaction under dispute
|
Tx_t tx_; //< Transaction under dispute
|
||||||
|
|
||||||
hash_map <NodeID_t, bool> votes_; //< Votes of our peers
|
hash_map<NodeID_t, bool> votes_; //< Votes of our peers
|
||||||
beast::Journal j_; //< Debug journal
|
beast::Journal j_; //< Debug journal
|
||||||
};
|
};
|
||||||
|
|
||||||
// Track a peer's yes/no vote on a particular disputed tx_
|
// Track a peer's yes/no vote on a particular disputed tx_
|
||||||
template <class Tx_t, class NodeID_t>
|
template <class Tx_t, class NodeID_t>
|
||||||
void DisputedTx<Tx_t, NodeID_t>::setVote (NodeID_t const& peer, bool votesYes)
|
void
|
||||||
|
DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
|
||||||
{
|
{
|
||||||
auto res = votes_.insert (std::make_pair (peer, votesYes));
|
auto res = votes_.insert(std::make_pair(peer, votesYes));
|
||||||
|
|
||||||
// new vote
|
// new vote
|
||||||
if (res.second)
|
if (res.second)
|
||||||
{
|
{
|
||||||
if (votesYes)
|
if (votesYes)
|
||||||
{
|
{
|
||||||
JLOG (j_.debug())
|
JLOG(j_.debug()) << "Peer " << peer << " votes YES on " << tx_.id();
|
||||||
<< "Peer " << peer << " votes YES on " << tx_.id();
|
|
||||||
++yays_;
|
++yays_;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JLOG (j_.debug())
|
JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
|
||||||
<< "Peer " << peer << " votes NO on " << tx_.id();
|
|
||||||
++nays_;
|
++nays_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// changes vote to yes
|
// changes vote to yes
|
||||||
else if (votesYes && !res.first->second)
|
else if (votesYes && !res.first->second)
|
||||||
{
|
{
|
||||||
JLOG (j_.debug())
|
JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id();
|
||||||
<< "Peer " << peer << " now votes YES on " << tx_.id();
|
|
||||||
--nays_;
|
--nays_;
|
||||||
++yays_;
|
++yays_;
|
||||||
res.first->second = true;
|
res.first->second = true;
|
||||||
@@ -170,8 +163,7 @@ void DisputedTx<Tx_t, NodeID_t>::setVote (NodeID_t const& peer, bool votesYes)
|
|||||||
// changes vote to no
|
// changes vote to no
|
||||||
else if (!votesYes && res.first->second)
|
else if (!votesYes && res.first->second)
|
||||||
{
|
{
|
||||||
JLOG (j_.debug())
|
JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id();
|
||||||
<< "Peer " << peer << " now votes NO on " << tx_.id();
|
|
||||||
++nays_;
|
++nays_;
|
||||||
--yays_;
|
--yays_;
|
||||||
res.first->second = false;
|
res.first->second = false;
|
||||||
@@ -180,23 +172,25 @@ void DisputedTx<Tx_t, NodeID_t>::setVote (NodeID_t const& peer, bool votesYes)
|
|||||||
|
|
||||||
// Remove a peer's vote on this disputed transasction
|
// Remove a peer's vote on this disputed transasction
|
||||||
template <class Tx_t, class NodeID_t>
|
template <class Tx_t, class NodeID_t>
|
||||||
void DisputedTx<Tx_t, NodeID_t>::unVote (NodeID_t const& peer)
|
void
|
||||||
|
DisputedTx<Tx_t, NodeID_t>::unVote(NodeID_t const& peer)
|
||||||
{
|
{
|
||||||
auto it = votes_.find (peer);
|
auto it = votes_.find(peer);
|
||||||
|
|
||||||
if (it != votes_.end ())
|
if (it != votes_.end())
|
||||||
{
|
{
|
||||||
if (it->second)
|
if (it->second)
|
||||||
--yays_;
|
--yays_;
|
||||||
else
|
else
|
||||||
--nays_;
|
--nays_;
|
||||||
|
|
||||||
votes_.erase (it);
|
votes_.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Tx_t, class NodeID_t>
|
template <class Tx_t, class NodeID_t>
|
||||||
bool DisputedTx<Tx_t, NodeID_t>::updateVote (int percentTime, bool proposing)
|
bool
|
||||||
|
DisputedTx<Tx_t, NodeID_t>::updateVote(int percentTime, bool proposing)
|
||||||
{
|
{
|
||||||
if (ourVote_ && (nays_ == 0))
|
if (ourVote_ && (nays_ == 0))
|
||||||
return false;
|
return false;
|
||||||
@@ -207,7 +201,7 @@ bool DisputedTx<Tx_t, NodeID_t>::updateVote (int percentTime, bool proposing)
|
|||||||
bool newPosition;
|
bool newPosition;
|
||||||
int weight;
|
int weight;
|
||||||
|
|
||||||
if (proposing) // give ourselves full weight
|
if (proposing) // give ourselves full weight
|
||||||
{
|
{
|
||||||
// This is basically the percentage of nodes voting 'yes' (including us)
|
// This is basically the percentage of nodes voting 'yes' (including us)
|
||||||
weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);
|
weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);
|
||||||
@@ -219,7 +213,7 @@ bool DisputedTx<Tx_t, NodeID_t>::updateVote (int percentTime, bool proposing)
|
|||||||
// To prevent avalanche stalls, we increase the needed weight slightly
|
// To prevent avalanche stalls, we increase the needed weight slightly
|
||||||
// over time.
|
// over time.
|
||||||
if (percentTime < AV_MID_CONSENSUS_TIME)
|
if (percentTime < AV_MID_CONSENSUS_TIME)
|
||||||
newPosition = weight > AV_INIT_CONSENSUS_PCT;
|
newPosition = weight > AV_INIT_CONSENSUS_PCT;
|
||||||
else if (percentTime < AV_LATE_CONSENSUS_TIME)
|
else if (percentTime < AV_LATE_CONSENSUS_TIME)
|
||||||
newPosition = weight > AV_MID_CONSENSUS_PCT;
|
newPosition = weight > AV_MID_CONSENSUS_PCT;
|
||||||
else if (percentTime < AV_STUCK_CONSENSUS_TIME)
|
else if (percentTime < AV_STUCK_CONSENSUS_TIME)
|
||||||
@@ -236,43 +230,43 @@ bool DisputedTx<Tx_t, NodeID_t>::updateVote (int percentTime, bool proposing)
|
|||||||
|
|
||||||
if (newPosition == ourVote_)
|
if (newPosition == ourVote_)
|
||||||
{
|
{
|
||||||
JLOG (j_.info())
|
JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO")
|
||||||
<< "No change (" << (ourVote_ ? "YES" : "NO") << ") : weight "
|
<< ") : weight " << weight << ", percent "
|
||||||
<< weight << ", percent " << percentTime;
|
<< percentTime;
|
||||||
JLOG (j_.debug()) << getJson ();
|
JLOG(j_.debug()) << getJson();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ourVote_ = newPosition;
|
ourVote_ = newPosition;
|
||||||
JLOG (j_.debug())
|
JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
|
||||||
<< "We now vote " << (ourVote_ ? "YES" : "NO")
|
<< tx_.id();
|
||||||
<< " on " << tx_.id();
|
JLOG(j_.debug()) << getJson();
|
||||||
JLOG (j_.debug()) << getJson ();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Tx_t, class NodeID_t>
|
template <class Tx_t, class NodeID_t>
|
||||||
Json::Value DisputedTx<Tx_t, NodeID_t>::getJson () const
|
Json::Value
|
||||||
|
DisputedTx<Tx_t, NodeID_t>::getJson() const
|
||||||
{
|
{
|
||||||
using std::to_string;
|
using std::to_string;
|
||||||
|
|
||||||
Json::Value ret (Json::objectValue);
|
Json::Value ret(Json::objectValue);
|
||||||
|
|
||||||
ret["yays"] = yays_;
|
ret["yays"] = yays_;
|
||||||
ret["nays"] = nays_;
|
ret["nays"] = nays_;
|
||||||
ret["our_vote"] = ourVote_;
|
ret["our_vote"] = ourVote_;
|
||||||
|
|
||||||
if (!votes_.empty ())
|
if (!votes_.empty())
|
||||||
{
|
{
|
||||||
Json::Value votesj (Json::objectValue);
|
Json::Value votesj(Json::objectValue);
|
||||||
for (auto& vote : votes_)
|
for (auto& vote : votes_)
|
||||||
votesj[to_string (vote.first)] = vote.second;
|
votesj[to_string(vote.first)] = vote.second;
|
||||||
ret["votes"] = std::move (votesj);
|
ret["votes"] = std::move(votesj);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -18,66 +18,64 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
#include <ripple/consensus/LedgerTiming.h>
|
|
||||||
#include <ripple/basics/Log.h>
|
#include <ripple/basics/Log.h>
|
||||||
|
#include <ripple/consensus/LedgerTiming.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
bool
|
bool
|
||||||
shouldCloseLedger (
|
shouldCloseLedger(
|
||||||
bool anyTransactions,
|
bool anyTransactions,
|
||||||
std::size_t previousProposers,
|
std::size_t prevProposers,
|
||||||
std::size_t proposersClosed,
|
std::size_t proposersClosed,
|
||||||
std::size_t proposersValidated,
|
std::size_t proposersValidated,
|
||||||
std::chrono::milliseconds previousTime,
|
std::chrono::milliseconds prevRoundTime,
|
||||||
std::chrono::milliseconds currentTime, // Time since last ledger's close time
|
std::chrono::milliseconds
|
||||||
std::chrono::milliseconds openTime, // Time waiting to close this ledger
|
timeSincePrevClose, // Time since last ledger's close time
|
||||||
|
std::chrono::milliseconds openTime, // Time waiting to close this ledger
|
||||||
std::chrono::seconds idleInterval,
|
std::chrono::seconds idleInterval,
|
||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
if ((previousTime < -1s) || (previousTime > 10min) ||
|
if ((prevRoundTime < -1s) || (prevRoundTime > 10min) || (timeSincePrevClose > 10min))
|
||||||
(currentTime > 10min))
|
|
||||||
{
|
{
|
||||||
// These are unexpected cases, we just close the ledger
|
// These are unexpected cases, we just close the ledger
|
||||||
JLOG (j.warn()) <<
|
JLOG(j.warn()) << "shouldCloseLedger Trans="
|
||||||
"shouldCloseLedger Trans=" << (anyTransactions ? "yes" : "no") <<
|
<< (anyTransactions ? "yes" : "no")
|
||||||
" Prop: " << previousProposers << "/" << proposersClosed <<
|
<< " Prop: " << prevProposers << "/" << proposersClosed
|
||||||
" Secs: " << currentTime.count() << " (last: " <<
|
<< " Secs: " << timeSincePrevClose.count()
|
||||||
previousTime.count() << ")";
|
<< " (last: " << prevRoundTime.count() << ")";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((proposersClosed + proposersValidated) > (previousProposers / 2))
|
if ((proposersClosed + proposersValidated) > (prevProposers / 2))
|
||||||
{
|
{
|
||||||
// If more than half of the network has closed, we close
|
// If more than half of the network has closed, we close
|
||||||
JLOG (j.trace()) << "Others have closed";
|
JLOG(j.trace()) << "Others have closed";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!anyTransactions)
|
if (!anyTransactions)
|
||||||
{
|
{
|
||||||
// Only close at the end of the idle interval
|
// Only close at the end of the idle interval
|
||||||
return currentTime >= idleInterval; // normal idle
|
return timeSincePrevClose >= idleInterval; // normal idle
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve minimum ledger open time
|
// Preserve minimum ledger open time
|
||||||
if (openTime < LEDGER_MIN_CLOSE)
|
if (openTime < LEDGER_MIN_CLOSE)
|
||||||
{
|
{
|
||||||
JLOG (j.debug()) <<
|
JLOG(j.debug()) << "Must wait minimum time before closing";
|
||||||
"Must wait minimum time before closing";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't let this ledger close more than twice as fast as the previous
|
// Don't let this ledger close more than twice as fast as the previous
|
||||||
// ledger reached consensus so that slower validators can slow down
|
// ledger reached consensus so that slower validators can slow down
|
||||||
// the network
|
// the network
|
||||||
if (openTime < (previousTime / 2))
|
if (openTime < (prevRoundTime / 2))
|
||||||
{
|
{
|
||||||
JLOG (j.debug()) <<
|
JLOG(j.debug()) << "Ledger has not been open long enough";
|
||||||
"Ledger has not been open long enough";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,10 +84,7 @@ shouldCloseLedger (
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
checkConsensusReached (
|
checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self)
|
||||||
std::size_t agreeing,
|
|
||||||
std::size_t total,
|
|
||||||
bool count_self)
|
|
||||||
{
|
{
|
||||||
// If we are alone, we have a consensus
|
// If we are alone, we have a consensus
|
||||||
if (total == 0)
|
if (total == 0)
|
||||||
@@ -107,8 +102,8 @@ checkConsensusReached (
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConsensusState
|
ConsensusState
|
||||||
checkConsensus (
|
checkConsensus(
|
||||||
std::size_t previousProposers,
|
std::size_t prevProposers,
|
||||||
std::size_t currentProposers,
|
std::size_t currentProposers,
|
||||||
std::size_t currentAgree,
|
std::size_t currentAgree,
|
||||||
std::size_t currentFinished,
|
std::size_t currentFinished,
|
||||||
@@ -117,47 +112,45 @@ checkConsensus (
|
|||||||
bool proposing,
|
bool proposing,
|
||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
{
|
{
|
||||||
JLOG (j.trace()) <<
|
JLOG(j.trace()) << "checkConsensus: prop=" << currentProposers << "/"
|
||||||
"checkConsensus: prop=" << currentProposers <<
|
<< prevProposers << " agree=" << currentAgree
|
||||||
"/" << previousProposers <<
|
<< " validated=" << currentFinished
|
||||||
" agree=" << currentAgree << " validated=" << currentFinished <<
|
<< " time=" << currentAgreeTime.count() << "/"
|
||||||
" time=" << currentAgreeTime.count() << "/" << previousAgreeTime.count();
|
<< previousAgreeTime.count();
|
||||||
|
|
||||||
if (currentAgreeTime <= LEDGER_MIN_CONSENSUS)
|
if (currentAgreeTime <= LEDGER_MIN_CONSENSUS)
|
||||||
return ConsensusState::No;
|
return ConsensusState::No;
|
||||||
|
|
||||||
if (currentProposers < (previousProposers * 3 / 4))
|
if (currentProposers < (prevProposers * 3 / 4))
|
||||||
{
|
{
|
||||||
// Less than 3/4 of the last ledger's proposers are present; don't
|
// Less than 3/4 of the last ledger's proposers are present; don't
|
||||||
// rush: we may need more time.
|
// rush: we may need more time.
|
||||||
if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS))
|
if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS))
|
||||||
{
|
{
|
||||||
JLOG (j.trace()) <<
|
JLOG(j.trace()) << "too fast, not enough proposers";
|
||||||
"too fast, not enough proposers";
|
|
||||||
return ConsensusState::No;
|
return ConsensusState::No;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Have we, together with the nodes on our UNL list, reached the threshold
|
// Have we, together with the nodes on our UNL list, reached the threshold
|
||||||
// to declare consensus?
|
// to declare consensus?
|
||||||
if (checkConsensusReached (currentAgree, currentProposers, proposing))
|
if (checkConsensusReached(currentAgree, currentProposers, proposing))
|
||||||
{
|
{
|
||||||
JLOG (j.debug()) << "normal consensus";
|
JLOG(j.debug()) << "normal consensus";
|
||||||
return ConsensusState::Yes;
|
return ConsensusState::Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Have sufficient nodes on our UNL list moved on and reached the threshold
|
// Have sufficient nodes on our UNL list moved on and reached the threshold
|
||||||
// to declare consensus?
|
// to declare consensus?
|
||||||
if (checkConsensusReached (currentFinished, currentProposers, false))
|
if (checkConsensusReached(currentFinished, currentProposers, false))
|
||||||
{
|
{
|
||||||
JLOG (j.warn()) <<
|
JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on";
|
||||||
"We see no consensus, but 80% of nodes have moved on";
|
|
||||||
return ConsensusState::MovedOn;
|
return ConsensusState::MovedOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no consensus yet
|
// no consensus yet
|
||||||
JLOG (j.trace()) << "no consensus";
|
JLOG(j.trace()) << "no consensus";
|
||||||
return ConsensusState::No;
|
return ConsensusState::No;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|||||||
@@ -20,14 +20,13 @@
|
|||||||
#ifndef RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
|
#ifndef RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
|
||||||
#define RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
|
#define RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <ripple/basics/chrono.h>
|
#include <ripple/basics/chrono.h>
|
||||||
#include <ripple/beast/utility/Journal.h>
|
#include <ripple/beast/utility/Journal.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// These are protocol parameters used to control the behavior of the system and
|
// These are protocol parameters used to control the behavior of the system and
|
||||||
// they should not be changed arbitrarily.
|
// they should not be changed arbitrarily.
|
||||||
@@ -42,16 +41,10 @@ using namespace std::chrono_literals;
|
|||||||
@see getNextLedgerTimeResolution
|
@see getNextLedgerTimeResolution
|
||||||
*/
|
*/
|
||||||
std::chrono::seconds constexpr ledgerPossibleTimeResolutions[] =
|
std::chrono::seconds constexpr ledgerPossibleTimeResolutions[] =
|
||||||
{ 10s, 20s, 30s, 60s, 90s, 120s };
|
{10s, 20s, 30s, 60s, 90s, 120s};
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
|
||||||
//! Initial resolution of ledger close time.
|
//! Initial resolution of ledger close time.
|
||||||
auto constexpr ledgerDefaultTimeResolution = ledgerPossibleTimeResolutions[2];
|
auto constexpr ledgerDefaultTimeResolution = ledgerPossibleTimeResolutions[2];
|
||||||
#else
|
|
||||||
// HH Remove this workaround of a VS bug when possible
|
|
||||||
//! Initial resolution of ledger close time.
|
|
||||||
auto constexpr ledgerDefaultTimeResolution = 30s;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//! How often we increase the close time resolution (in numbers of ledgers)
|
//! How often we increase the close time resolution (in numbers of ledgers)
|
||||||
auto constexpr increaseLedgerTimeResolutionEvery = 8;
|
auto constexpr increaseLedgerTimeResolutionEvery = 8;
|
||||||
@@ -161,23 +154,25 @@ getNextLedgerTimeResolution(
|
|||||||
bool previousAgree,
|
bool previousAgree,
|
||||||
std::uint32_t ledgerSeq)
|
std::uint32_t ledgerSeq)
|
||||||
{
|
{
|
||||||
assert (ledgerSeq);
|
assert(ledgerSeq);
|
||||||
|
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
// Find the current resolution:
|
// Find the current resolution:
|
||||||
auto iter = std::find (std::begin (ledgerPossibleTimeResolutions),
|
auto iter = std::find(
|
||||||
std::end (ledgerPossibleTimeResolutions), previousResolution);
|
std::begin(ledgerPossibleTimeResolutions),
|
||||||
assert (iter != std::end (ledgerPossibleTimeResolutions));
|
std::end(ledgerPossibleTimeResolutions),
|
||||||
|
previousResolution);
|
||||||
|
assert(iter != std::end(ledgerPossibleTimeResolutions));
|
||||||
|
|
||||||
// This should never happen, but just as a precaution
|
// This should never happen, but just as a precaution
|
||||||
if (iter == std::end (ledgerPossibleTimeResolutions))
|
if (iter == std::end(ledgerPossibleTimeResolutions))
|
||||||
return previousResolution;
|
return previousResolution;
|
||||||
|
|
||||||
// If we did not previously agree, we try to decrease the resolution to
|
// If we did not previously agree, we try to decrease the resolution to
|
||||||
// improve the chance that we will agree now.
|
// improve the chance that we will agree now.
|
||||||
if (!previousAgree && ledgerSeq % decreaseLedgerTimeResolutionEvery == 0)
|
if (!previousAgree && ledgerSeq % decreaseLedgerTimeResolutionEvery == 0)
|
||||||
{
|
{
|
||||||
if (++iter != std::end (ledgerPossibleTimeResolutions))
|
if (++iter != std::end(ledgerPossibleTimeResolutions))
|
||||||
return *iter;
|
return *iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +180,7 @@ getNextLedgerTimeResolution(
|
|||||||
// if we can continue to agree.
|
// if we can continue to agree.
|
||||||
if (previousAgree && ledgerSeq % increaseLedgerTimeResolutionEvery == 0)
|
if (previousAgree && ledgerSeq % increaseLedgerTimeResolutionEvery == 0)
|
||||||
{
|
{
|
||||||
if (iter-- != std::begin (ledgerPossibleTimeResolutions))
|
if (iter-- != std::begin(ledgerPossibleTimeResolutions))
|
||||||
return *iter;
|
return *iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +207,6 @@ roundCloseTime(
|
|||||||
return closeTime - (closeTime.time_since_epoch() % closeResolution);
|
return closeTime - (closeTime.time_since_epoch() % closeResolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Calculate the effective ledger close time
|
/** Calculate the effective ledger close time
|
||||||
|
|
||||||
After adjusting the ledger close time based on the current resolution, also
|
After adjusting the ledger close time based on the current resolution, also
|
||||||
@@ -223,16 +217,17 @@ roundCloseTime(
|
|||||||
@param priorCloseTime The close time of the prior ledger
|
@param priorCloseTime The close time of the prior ledger
|
||||||
*/
|
*/
|
||||||
template <class time_point>
|
template <class time_point>
|
||||||
time_point effectiveCloseTime(time_point closeTime,
|
time_point
|
||||||
typename time_point::duration const resolution,
|
effCloseTime(
|
||||||
|
time_point closeTime,
|
||||||
|
typename time_point::duration const resolution,
|
||||||
time_point priorCloseTime)
|
time_point priorCloseTime)
|
||||||
{
|
{
|
||||||
if (closeTime == time_point{})
|
if (closeTime == time_point{})
|
||||||
return closeTime;
|
return closeTime;
|
||||||
|
|
||||||
return std::max<time_point>(
|
return std::max<time_point>(
|
||||||
roundCloseTime (closeTime, resolution),
|
roundCloseTime(closeTime, resolution), (priorCloseTime + 1s));
|
||||||
(priorCloseTime + 1s));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Determines whether the current ledger should close at this time.
|
/** Determines whether the current ledger should close at this time.
|
||||||
@@ -241,30 +236,29 @@ time_point effectiveCloseTime(time_point closeTime,
|
|||||||
in progress, or when a transaction is received and no close is in progress.
|
in progress, or when a transaction is received and no close is in progress.
|
||||||
|
|
||||||
@param anyTransactions indicates whether any transactions have been received
|
@param anyTransactions indicates whether any transactions have been received
|
||||||
@param previousProposers proposers in the last closing
|
@param prevProposers proposers in the last closing
|
||||||
@param proposersClosed proposers who have currently closed this ledger
|
@param proposersClosed proposers who have currently closed this ledger
|
||||||
@param proposersValidated proposers who have validated the last closed
|
@param proposersValidated proposers who have validated the last closed
|
||||||
ledger
|
ledger
|
||||||
@param previousTime time for the previous ledger to reach consensus
|
@param prevRoundTime time for the previous ledger to reach consensus
|
||||||
@param currentTime time since the previous ledger's
|
@param timeSincePrevClose time since the previous ledger's (possibly rounded)
|
||||||
(possibly rounded) close time
|
close time
|
||||||
@param openTime time waiting to close this ledger
|
@param openTime duration this ledger has been open
|
||||||
@param idleInterval the network's desired idle interval
|
@param idleInterval the network's desired idle interval
|
||||||
@param j journal for logging
|
@param j journal for logging
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
shouldCloseLedger (
|
shouldCloseLedger(
|
||||||
bool anyTransactions,
|
bool anyTransactions,
|
||||||
std::size_t previousProposers,
|
std::size_t prevProposers,
|
||||||
std::size_t proposersClosed,
|
std::size_t proposersClosed,
|
||||||
std::size_t proposersValidated,
|
std::size_t proposersValidated,
|
||||||
std::chrono::milliseconds previousTime,
|
std::chrono::milliseconds prevRoundTime,
|
||||||
std::chrono::milliseconds currentTime, // Time since last ledger's close time
|
std::chrono::milliseconds timeSincePrevClose,
|
||||||
std::chrono::milliseconds openTime, // Time waiting to close this ledger
|
std::chrono::milliseconds openTime,
|
||||||
std::chrono::seconds idleInterval,
|
std::chrono::seconds idleInterval,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|
||||||
|
|
||||||
/** Determine if a consensus has been reached
|
/** Determine if a consensus has been reached
|
||||||
|
|
||||||
This function determines if a consensus has been reached
|
This function determines if a consensus has been reached
|
||||||
@@ -275,22 +269,18 @@ shouldCloseLedger (
|
|||||||
@return True if a consensus has been reached
|
@return True if a consensus has been reached
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
checkConsensusReached (
|
checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self);
|
||||||
std::size_t agreeing,
|
|
||||||
std::size_t total,
|
|
||||||
bool count_self);
|
|
||||||
|
|
||||||
/** Whether we have or don't have a consensus */
|
/** Whether we have or don't have a consensus */
|
||||||
enum class ConsensusState
|
enum class ConsensusState {
|
||||||
{
|
No, //!< We do not have consensus
|
||||||
No, //!< We do not have consensus
|
MovedOn, //!< The network has consensus without us
|
||||||
MovedOn, //!< The network has consensus without us
|
Yes //!< We have consensus along with the network
|
||||||
Yes //!< We have consensus along with the network
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Determine whether the network reached consensus and whether we joined.
|
/** Determine whether the network reached consensus and whether we joined.
|
||||||
|
|
||||||
@param previousProposers proposers in the last closing (not including us)
|
@param prevProposers proposers in the last closing (not including us)
|
||||||
@param currentProposers proposers in this closing so far (not including us)
|
@param currentProposers proposers in this closing so far (not including us)
|
||||||
@param currentAgree proposers who agree with us
|
@param currentAgree proposers who agree with us
|
||||||
@param currentFinished proposers who have validated a ledger after this one
|
@param currentFinished proposers who have validated a ledger after this one
|
||||||
@@ -302,8 +292,8 @@ enum class ConsensusState
|
|||||||
@param j journal for logging
|
@param j journal for logging
|
||||||
*/
|
*/
|
||||||
ConsensusState
|
ConsensusState
|
||||||
checkConsensus (
|
checkConsensus(
|
||||||
std::size_t previousProposers,
|
std::size_t prevProposers,
|
||||||
std::size_t currentProposers,
|
std::size_t currentProposers,
|
||||||
std::size_t currentAgree,
|
std::size_t currentAgree,
|
||||||
std::size_t currentFinished,
|
std::size_t currentFinished,
|
||||||
@@ -312,6 +302,6 @@ checkConsensus (
|
|||||||
bool proposing,
|
bool proposing,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -17,22 +17,20 @@
|
|||||||
*/
|
*/
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
|
#include <ripple/beast/clock/manual_clock.h>
|
||||||
#include <ripple/beast/unit_test.h>
|
#include <ripple/beast/unit_test.h>
|
||||||
#include <ripple/consensus/Consensus.h>
|
#include <ripple/consensus/Consensus.h>
|
||||||
#include <ripple/consensus/ConsensusProposal.h>
|
#include <ripple/consensus/ConsensusProposal.h>
|
||||||
#include <ripple/beast/clock/manual_clock.h>
|
|
||||||
#include <boost/function_output_iterator.hpp>
|
#include <boost/function_output_iterator.hpp>
|
||||||
#include <test/csf.h>
|
#include <test/csf.h>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
class Consensus_test : public beast::unit_test::suite
|
class Consensus_test : public beast::unit_test::suite
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void
|
void
|
||||||
testStandalone()
|
testStandalone()
|
||||||
{
|
{
|
||||||
@@ -41,21 +39,22 @@ public:
|
|||||||
auto tg = TrustGraph::makeComplete(1);
|
auto tg = TrustGraph::makeComplete(1);
|
||||||
Sim s(tg, topology(tg, fixed{LEDGER_GRANULARITY}));
|
Sim s(tg, topology(tg, fixed{LEDGER_GRANULARITY}));
|
||||||
|
|
||||||
auto & p = s.peers[0];
|
auto& p = s.peers[0];
|
||||||
|
|
||||||
p.targetLedgers = 1;
|
p.targetLedgers = 1;
|
||||||
p.start();
|
p.start();
|
||||||
p.submit(Tx{ 1 });
|
p.submit(Tx{1});
|
||||||
|
|
||||||
s.net.step();
|
s.net.step();
|
||||||
|
|
||||||
// Inspect that the proper ledger was created
|
// Inspect that the proper ledger was created
|
||||||
BEAST_EXPECT(p.LCL().seq == 1);
|
BEAST_EXPECT(p.prevLedgerID().seq == 1);
|
||||||
BEAST_EXPECT(p.LCL() == p.lastClosedLedger.id());
|
BEAST_EXPECT(p.prevLedgerID() == p.lastClosedLedger.id());
|
||||||
BEAST_EXPECT(p.lastClosedLedger.id().txs.size() == 1);
|
BEAST_EXPECT(p.lastClosedLedger.id().txs.size() == 1);
|
||||||
BEAST_EXPECT(p.lastClosedLedger.id().txs.find(Tx{ 1 })
|
BEAST_EXPECT(
|
||||||
!= p.lastClosedLedger.id().txs.end());
|
p.lastClosedLedger.id().txs.find(Tx{1}) !=
|
||||||
BEAST_EXPECT(p.getLastCloseProposers() == 0);
|
p.lastClosedLedger.id().txs.end());
|
||||||
|
BEAST_EXPECT(p.prevProposers() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -65,24 +64,25 @@ public:
|
|||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
auto tg = TrustGraph::makeComplete(5);
|
auto tg = TrustGraph::makeComplete(5);
|
||||||
Sim sim(tg,
|
Sim sim(
|
||||||
|
tg,
|
||||||
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
||||||
|
|
||||||
// everyone submits their own ID as a TX and relay it to peers
|
// everyone submits their own ID as a TX and relay it to peers
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
p.submit(Tx(p.id));
|
p.submit(Tx(p.id));
|
||||||
|
|
||||||
// Verify all peers have the same LCL and it has all the Txs
|
// Verify all peers have the same LCL and it has all the Txs
|
||||||
sim.run(1);
|
sim.run(1);
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
auto const &lgrID = p.LCL();
|
auto const& lgrID = p.prevLedgerID();
|
||||||
BEAST_EXPECT(lgrID.seq == 1);
|
BEAST_EXPECT(lgrID.seq == 1);
|
||||||
BEAST_EXPECT(p.getLastCloseProposers() == sim.peers.size() - 1);
|
BEAST_EXPECT(p.prevProposers() == sim.peers.size() - 1);
|
||||||
for(std::uint32_t i = 0; i < sim.peers.size(); ++i)
|
for (std::uint32_t i = 0; i < sim.peers.size(); ++i)
|
||||||
BEAST_EXPECT(lgrID.txs.find(Tx{ i }) != lgrID.txs.end());
|
BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end());
|
||||||
// Matches peer 0 ledger
|
// Matches peer 0 ledger
|
||||||
BEAST_EXPECT(lgrID.txs == sim.peers[0].LCL().txs);
|
BEAST_EXPECT(lgrID.txs == sim.peers[0].prevLedgerID().txs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,70 +96,69 @@ public:
|
|||||||
// 1. The slow peer is participating in consensus
|
// 1. The slow peer is participating in consensus
|
||||||
// 2. The slow peer is just observing
|
// 2. The slow peer is just observing
|
||||||
|
|
||||||
for(auto isParticipant : {true, false})
|
for (auto isParticipant : {true, false})
|
||||||
{
|
{
|
||||||
auto tg = TrustGraph::makeComplete(5);
|
auto tg = TrustGraph::makeComplete(5);
|
||||||
|
|
||||||
Sim sim(tg, topology(tg,[](PeerID i, PeerID j)
|
Sim sim(tg, topology(tg, [](PeerID i, PeerID j) {
|
||||||
{
|
auto delayFactor = (i == 0 || j == 0) ? 1.1 : 0.2;
|
||||||
auto delayFactor = (i == 0 || j == 0) ? 1.1 : 0.2;
|
return round<milliseconds>(
|
||||||
return round<milliseconds>(delayFactor* LEDGER_GRANULARITY);
|
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
|
||||||
for (auto & p : sim.peers)
|
// peers
|
||||||
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
p.submit(Tx{ p.id });
|
p.submit(Tx{p.id});
|
||||||
}
|
}
|
||||||
|
|
||||||
sim.run(1);
|
sim.run(1);
|
||||||
|
|
||||||
// Verify all peers have same LCL but are missing transaction 0 which
|
// Verify all peers have same LCL but are missing transaction 0
|
||||||
// was not received by all peers before the ledger closed
|
// which was not received by all peers before the ledger closed
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
auto const &lgrID = p.LCL();
|
auto const& lgrID = p.prevLedgerID();
|
||||||
BEAST_EXPECT(lgrID.seq == 1);
|
BEAST_EXPECT(lgrID.seq == 1);
|
||||||
|
|
||||||
|
|
||||||
// If peer 0 is participating
|
// If peer 0 is participating
|
||||||
if(isParticipant)
|
if (isParticipant)
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(p.getLastCloseProposers()
|
BEAST_EXPECT(p.prevProposers() == sim.peers.size() - 1);
|
||||||
== sim.peers.size() - 1);
|
// Peer 0 closes first because it sees a quorum of agreeing
|
||||||
// Peer 0 closes first because it sees a quorum of agreeing positions
|
// positions from all other peers in one hop (1->0, 2->0,
|
||||||
// from all other peers in one hop (1->0, 2->0, ..)
|
// ..) The other peers take an extra timer period before
|
||||||
// The other peers take an extra timer period before they find that
|
// they find that Peer 0 agrees with them ( 1->0->1,
|
||||||
// Peer 0 agrees with them ( 1->0->1, 2->0->2, ...)
|
// 2->0->2, ...)
|
||||||
if(p.id != 0)
|
if (p.id != 0)
|
||||||
BEAST_EXPECT(p.getLastConvergeDuration()
|
BEAST_EXPECT(
|
||||||
> sim.peers[0].getLastConvergeDuration());
|
p.prevRoundTime() > sim.peers[0].prevRoundTime());
|
||||||
}
|
}
|
||||||
else // peer 0 is not participating
|
else // peer 0 is not participating
|
||||||
{
|
{
|
||||||
auto const proposers = p.getLastCloseProposers();
|
auto const proposers = p.prevProposers();
|
||||||
if(p.id == 0)
|
if (p.id == 0)
|
||||||
BEAST_EXPECT(proposers == sim.peers.size() - 1);
|
BEAST_EXPECT(proposers == sim.peers.size() - 1);
|
||||||
else
|
else
|
||||||
BEAST_EXPECT(proposers == sim.peers.size() - 2);
|
BEAST_EXPECT(proposers == sim.peers.size() - 2);
|
||||||
|
|
||||||
// so all peers should have closed together
|
// so all peers should have closed together
|
||||||
BEAST_EXPECT(p.getLastConvergeDuration()
|
BEAST_EXPECT(
|
||||||
== sim.peers[0].getLastConvergeDuration());
|
p.prevRoundTime() == sim.peers[0].prevRoundTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BEAST_EXPECT(lgrID.txs.find(Tx{0}) == lgrID.txs.end());
|
||||||
BEAST_EXPECT(lgrID.txs.find(Tx{ 0 }) == lgrID.txs.end());
|
for (std::uint32_t i = 1; i < sim.peers.size(); ++i)
|
||||||
for(std::uint32_t i = 1; i < sim.peers.size(); ++i)
|
BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end());
|
||||||
BEAST_EXPECT(lgrID.txs.find(Tx{ i }) != lgrID.txs.end());
|
|
||||||
// Matches peer 0 ledger
|
// Matches peer 0 ledger
|
||||||
BEAST_EXPECT(lgrID.txs == sim.peers[0].LCL().txs);
|
BEAST_EXPECT(lgrID.txs == sim.peers[0].prevLedgerID().txs);
|
||||||
}
|
}
|
||||||
BEAST_EXPECT(sim.peers[0].openTxs.find(Tx{ 0 })
|
BEAST_EXPECT(
|
||||||
!= sim.peers[0].openTxs.end());
|
sim.peers[0].openTxs.find(Tx{0}) != sim.peers[0].openTxs.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -185,32 +184,35 @@ public:
|
|||||||
|
|
||||||
// Complicating this matter is that nodes will ignore proposals
|
// Complicating this matter is that nodes will ignore proposals
|
||||||
// with times more than PROPOSE_FRESHNESS =20s in the past. So at
|
// with times more than PROPOSE_FRESHNESS =20s in the past. So at
|
||||||
// the minimum granularity, we have at most 3 types of skews (0s,10s,20s).
|
// the minimum granularity, we have at most 3 types of skews
|
||||||
|
// (0s,10s,20s).
|
||||||
|
|
||||||
// This test therefore has 6 nodes, with 2 nodes having each type of
|
// This test therefore has 6 nodes, with 2 nodes having each type of
|
||||||
// skew. Then no majority (1/3 < 1/2) of nodes will agree on an
|
// skew. Then no majority (1/3 < 1/2) of nodes will agree on an
|
||||||
// actual close time.
|
// actual close time.
|
||||||
|
|
||||||
auto tg = TrustGraph::makeComplete(6);
|
auto tg = TrustGraph::makeComplete(6);
|
||||||
Sim sim(tg,
|
Sim sim(
|
||||||
|
tg,
|
||||||
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
||||||
|
|
||||||
// Run consensus without skew until we have a short close time resolution
|
// Run consensus without skew until we have a short close time
|
||||||
while(sim.peers.front().lastClosedLedger.closeTimeResolution() >=
|
// resolution
|
||||||
PROPOSE_FRESHNESS)
|
while (sim.peers.front().lastClosedLedger.closeTimeResolution() >=
|
||||||
|
PROPOSE_FRESHNESS)
|
||||||
sim.run(1);
|
sim.run(1);
|
||||||
|
|
||||||
// Introduce a shift on the time of half the peers
|
// Introduce a shift on the time of half the peers
|
||||||
sim.peers[0].clockSkew = PROPOSE_FRESHNESS/2;
|
sim.peers[0].clockSkew = PROPOSE_FRESHNESS / 2;
|
||||||
sim.peers[1].clockSkew = PROPOSE_FRESHNESS/2;
|
sim.peers[1].clockSkew = PROPOSE_FRESHNESS / 2;
|
||||||
sim.peers[2].clockSkew = PROPOSE_FRESHNESS;
|
sim.peers[2].clockSkew = PROPOSE_FRESHNESS;
|
||||||
sim.peers[3].clockSkew = PROPOSE_FRESHNESS;
|
sim.peers[3].clockSkew = PROPOSE_FRESHNESS;
|
||||||
|
|
||||||
// Verify all peers have the same LCL and it has all the Txs
|
// Verify all peers have the same LCL and it has all the Txs
|
||||||
sim.run(1);
|
sim.run(1);
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(! p.lastClosedLedger.closeAgree());
|
BEAST_EXPECT(!p.lastClosedLedger.closeAgree());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,9 +226,8 @@ public:
|
|||||||
|
|
||||||
// Vary the time it takes to process validations to exercise detecting
|
// Vary the time it takes to process validations to exercise detecting
|
||||||
// the wrong LCL at different phases of consensus
|
// the wrong LCL at different phases of consensus
|
||||||
for(auto validationDelay : {0s, LEDGER_MIN_CLOSE})
|
for (auto validationDelay : {0s, LEDGER_MIN_CLOSE})
|
||||||
{
|
{
|
||||||
|
|
||||||
// Consider 10 peers:
|
// Consider 10 peers:
|
||||||
// 0 1 2 3 4 5 6 7 8 9
|
// 0 1 2 3 4 5 6 7 8 9
|
||||||
//
|
//
|
||||||
@@ -241,19 +242,21 @@ public:
|
|||||||
// since nodes 2-4 will validate a different ledger.
|
// since nodes 2-4 will validate a different ledger.
|
||||||
|
|
||||||
// Nodes 0-1 will acquire the proper ledger from the network and
|
// Nodes 0-1 will acquire the proper ledger from the network and
|
||||||
// resume consensus and eventually generate the dominant network ledger
|
// resume consensus and eventually generate the dominant network
|
||||||
|
// ledger
|
||||||
|
|
||||||
std::vector<UNL> unls;
|
std::vector<UNL> unls;
|
||||||
unls.push_back({2,3,4,5,6,7,8,9});
|
unls.push_back({2, 3, 4, 5, 6, 7, 8, 9});
|
||||||
unls.push_back({0,1,2,3,4});
|
unls.push_back({0, 1, 2, 3, 4});
|
||||||
std::vector<int> membership(10,0);
|
std::vector<int> membership(10, 0);
|
||||||
membership[0] = 1;
|
membership[0] = 1;
|
||||||
membership[1] = 1;
|
membership[1] = 1;
|
||||||
|
|
||||||
TrustGraph tg{unls, membership};
|
TrustGraph tg{unls, membership};
|
||||||
|
|
||||||
// This topology can fork, which is why we are using it for this test.
|
// This topology can fork, which is why we are using it for this
|
||||||
BEAST_EXPECT(tg.canFork(minimumConsensusPercentage/100.));
|
// test.
|
||||||
|
BEAST_EXPECT(tg.canFork(minimumConsensusPercentage / 100.));
|
||||||
|
|
||||||
auto netDelay = round<milliseconds>(0.2 * LEDGER_GRANULARITY);
|
auto netDelay = round<milliseconds>(0.2 * LEDGER_GRANULARITY);
|
||||||
Sim sim(tg, topology(tg, fixed{netDelay}));
|
Sim sim(tg, topology(tg, fixed{netDelay}));
|
||||||
@@ -261,8 +264,9 @@ public:
|
|||||||
// initial round to set prior state
|
// initial round to set prior state
|
||||||
sim.run(1);
|
sim.run(1);
|
||||||
|
|
||||||
// Nodes in smaller UNL have seen tx 0, nodes in other unl have seen tx 1
|
// Nodes in smaller UNL have seen tx 0, nodes in other unl have seen
|
||||||
for (auto & p : sim.peers)
|
// tx 1
|
||||||
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
p.validationDelay = validationDelay;
|
p.validationDelay = validationDelay;
|
||||||
p.missingLedgerDelay = netDelay;
|
p.missingLedgerDelay = netDelay;
|
||||||
@@ -272,16 +276,29 @@ public:
|
|||||||
p.openTxs.insert(Tx{1});
|
p.openTxs.insert(Tx{1});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run for 2 additional rounds
|
// Run for additional rounds
|
||||||
// - One round to generate different ledgers
|
// With no validation delay, only 2 more rounds are needed.
|
||||||
// - One round to detect different prior ledgers (but still generate
|
// 1. Round to generate different ledgers
|
||||||
// wrong ones) and recover
|
// 2. Round to detect different prior ledgers (but still generate
|
||||||
sim.run(2);
|
// wrong ones) and recover within that round since wrong LCL
|
||||||
|
// is detected before we close
|
||||||
|
//
|
||||||
|
// With a validation delay of LEDGER_MIN_CLOSE, we need 3 more
|
||||||
|
// rounds.
|
||||||
|
// 1. Round to generate different ledgers
|
||||||
|
// 2. Round to detect different prior ledgers (but still generate
|
||||||
|
// wrong ones) but end up declaring consensus on wrong LCL (but
|
||||||
|
// with the right transaction set!). This is because we detect
|
||||||
|
// the wrong LCL after we have closed the ledger, so we declare
|
||||||
|
// consensus based solely on our peer proposals. But we haven't
|
||||||
|
// had time to acquire the right LCL
|
||||||
|
// 3. Round to correct
|
||||||
|
sim.run(3);
|
||||||
|
|
||||||
bc::flat_map<int, bc::flat_set<Ledger::ID>> ledgers;
|
bc::flat_map<int, bc::flat_set<Ledger::ID>> ledgers;
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
for (auto const & l : p.ledgers)
|
for (auto const& l : p.ledgers)
|
||||||
{
|
{
|
||||||
ledgers[l.first.seq].insert(l.first);
|
ledgers[l.first.seq].insert(l.first);
|
||||||
}
|
}
|
||||||
@@ -289,12 +306,19 @@ public:
|
|||||||
|
|
||||||
BEAST_EXPECT(ledgers[0].size() == 1);
|
BEAST_EXPECT(ledgers[0].size() == 1);
|
||||||
BEAST_EXPECT(ledgers[1].size() == 1);
|
BEAST_EXPECT(ledgers[1].size() == 1);
|
||||||
BEAST_EXPECT(ledgers[2].size() == 2);
|
if (validationDelay == 0s)
|
||||||
BEAST_EXPECT(ledgers[3].size() == 1);
|
{
|
||||||
|
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.
|
// Additional test engineered to switch LCL during the establish phase.
|
||||||
// This was added to trigger a scenario that previously crashed, in which
|
// This was added to trigger a scenario that previously crashed, in which
|
||||||
// switchLCL switched from establish to open phase, but still processed
|
// switchLCL switched from establish to open phase, but still processed
|
||||||
@@ -330,7 +354,7 @@ public:
|
|||||||
|
|
||||||
// Check all peers recovered
|
// Check all peers recovered
|
||||||
for (auto &p : sim.peers)
|
for (auto &p : sim.peers)
|
||||||
BEAST_EXPECT(p.LCL() == sim.peers[0].LCL());
|
BEAST_EXPECT(p.prevLedgerID() == sim.peers[0].prevLedgerID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,43 +365,43 @@ public:
|
|||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
int numPeers = 10;
|
int numPeers = 10;
|
||||||
for(int overlap = 0; overlap <= numPeers; ++overlap)
|
for (int overlap = 0; overlap <= numPeers; ++overlap)
|
||||||
{
|
{
|
||||||
auto tg = TrustGraph::makeClique(numPeers, overlap);
|
auto tg = TrustGraph::makeClique(numPeers, overlap);
|
||||||
Sim sim(tg, topology(tg,
|
Sim sim(
|
||||||
fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
tg,
|
||||||
|
topology(
|
||||||
|
tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
||||||
|
|
||||||
// Initial round to set prior state
|
// Initial round to set prior state
|
||||||
sim.run(1);
|
sim.run(1);
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
// Nodes have only seen transactions from their neighbors
|
// Nodes have only seen transactions from their neighbors
|
||||||
p.openTxs.insert(Tx{p.id});
|
p.openTxs.insert(Tx{p.id});
|
||||||
for(auto const link : sim.net.links(&p))
|
for (auto const link : sim.net.links(&p))
|
||||||
p.openTxs.insert(Tx{link.to->id});
|
p.openTxs.insert(Tx{link.to->id});
|
||||||
}
|
}
|
||||||
sim.run(1);
|
sim.run(1);
|
||||||
|
|
||||||
|
|
||||||
// See if the network forked
|
// See if the network forked
|
||||||
bc::flat_set<Ledger::ID> ledgers;
|
bc::flat_set<Ledger::ID> ledgers;
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
ledgers.insert(p.LCL());
|
ledgers.insert(p.prevLedgerID());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fork should not happen for 40% or greater overlap
|
// Fork should not happen for 40% or greater overlap
|
||||||
// Since the overlapped nodes have a UNL that is the union of the
|
// Since the overlapped nodes have a UNL that is the union of the
|
||||||
// two cliques, the maximum sized UNL list is the number of peers
|
// two cliques, the maximum sized UNL list is the number of peers
|
||||||
if(overlap > 0.4 * numPeers)
|
if (overlap > 0.4 * numPeers)
|
||||||
BEAST_EXPECT(ledgers.size() == 1);
|
BEAST_EXPECT(ledgers.size() == 1);
|
||||||
else // Even if we do fork, there shouldn't be more than 3 ledgers
|
else // Even if we do fork, there shouldn't be more than 3 ledgers
|
||||||
// One for cliqueA, one for cliqueB and one for nodes in both
|
// One for cliqueA, one for cliqueB and one for nodes in both
|
||||||
BEAST_EXPECT(ledgers.size() <= 3);
|
BEAST_EXPECT(ledgers.size() <= 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
simClockSkew()
|
simClockSkew()
|
||||||
{
|
{
|
||||||
@@ -396,52 +420,44 @@ public:
|
|||||||
|
|
||||||
// Disabled while continuing to understand testt.
|
// Disabled while continuing to understand testt.
|
||||||
|
|
||||||
|
for (auto stagger : {800ms, 1600ms, 3200ms, 30000ms, 45000ms, 300000ms})
|
||||||
for(auto stagger : {800ms, 1600ms, 3200ms, 30000ms, 45000ms, 300000ms})
|
|
||||||
{
|
{
|
||||||
|
|
||||||
auto tg = TrustGraph::makeComplete(5);
|
auto tg = TrustGraph::makeComplete(5);
|
||||||
Sim sim(tg, topology(tg, [](PeerID i, PeerID)
|
Sim sim(tg, topology(tg, [](PeerID i, PeerID) {
|
||||||
{
|
return 200ms * (i + 1);
|
||||||
return 200ms * (i + 1);
|
}));
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
// all transactions submitted before starting
|
// all transactions submitted before starting
|
||||||
// Initial round to set prior state
|
// Initial round to set prior state
|
||||||
sim.run(1);
|
sim.run(1);
|
||||||
|
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
p.openTxs.insert(Tx{ 0 });
|
p.openTxs.insert(Tx{0});
|
||||||
p.targetLedgers = p.completedLedgers + 1;
|
p.targetLedgers = p.completedLedgers + 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// stagger start of consensus
|
// stagger start of consensus
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
p.start();
|
p.start();
|
||||||
sim.net.step_for(stagger);
|
sim.net.step_for(stagger);
|
||||||
}
|
}
|
||||||
|
|
||||||
// run until all peers have accepted all transactions
|
// run until all peers have accepted all transactions
|
||||||
sim.net.step_while([&]()
|
sim.net.step_while([&]() {
|
||||||
{
|
for (auto& p : sim.peers)
|
||||||
for(auto & p : sim.peers)
|
{
|
||||||
{
|
if (p.prevLedgerID().txs.size() != 1)
|
||||||
if(p.LCL().txs.size() != 1)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
simScaleFree()
|
simScaleFree()
|
||||||
{
|
{
|
||||||
@@ -450,45 +466,46 @@ public:
|
|||||||
// Generate a quasi-random scale free network and simulate consensus
|
// Generate a quasi-random scale free network and simulate consensus
|
||||||
// for a single transaction
|
// for a single transaction
|
||||||
|
|
||||||
int N = 100; // Peers
|
int N = 100; // Peers
|
||||||
|
|
||||||
int numUNLs = 15; // UNL lists
|
int numUNLs = 15; // UNL lists
|
||||||
int minUNLSize = N/4, maxUNLSize = N / 2;
|
int minUNLSize = N / 4, maxUNLSize = N / 2;
|
||||||
|
|
||||||
double transProb = 0.5;
|
double transProb = 0.5;
|
||||||
|
|
||||||
std::mt19937_64 rng;
|
std::mt19937_64 rng;
|
||||||
|
|
||||||
auto tg = TrustGraph::makeRandomRanked(N, numUNLs,
|
auto tg = TrustGraph::makeRandomRanked(
|
||||||
PowerLawDistribution{1,3},
|
N,
|
||||||
std::uniform_int_distribution<>{minUNLSize, maxUNLSize},
|
numUNLs,
|
||||||
rng);
|
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
|
// Initial round to set prior state
|
||||||
sim.run(1);
|
sim.run(1);
|
||||||
|
|
||||||
std::uniform_real_distribution<> u{};
|
std::uniform_real_distribution<> u{};
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
// 50-50 chance to have seen a transaction
|
// 50-50 chance to have seen a transaction
|
||||||
if(u(rng) >= transProb)
|
if (u(rng) >= transProb)
|
||||||
p.openTxs.insert(Tx{0});
|
p.openTxs.insert(Tx{0});
|
||||||
|
|
||||||
}
|
}
|
||||||
sim.run(1);
|
sim.run(1);
|
||||||
|
|
||||||
|
|
||||||
// See if the network forked
|
// See if the network forked
|
||||||
bc::flat_set<Ledger::ID> ledgers;
|
bc::flat_set<Ledger::ID> ledgers;
|
||||||
for (auto & p : sim.peers)
|
for (auto& p : sim.peers)
|
||||||
{
|
{
|
||||||
ledgers.insert(p.LCL());
|
ledgers.insert(p.prevLedgerID());
|
||||||
}
|
}
|
||||||
|
|
||||||
BEAST_EXPECT(ledgers.size() == 1);
|
BEAST_EXPECT(ledgers.size() == 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -507,5 +524,5 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
BEAST_DEFINE_TESTSUITE(Consensus, consensus, ripple);
|
BEAST_DEFINE_TESTSUITE(Consensus, consensus, ripple);
|
||||||
} // test
|
} // test
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|||||||
@@ -30,11 +30,11 @@
|
|||||||
#include <boost/range/adaptor/transformed.hpp>
|
#include <boost/range/adaptor/transformed.hpp>
|
||||||
#include <boost/range/iterator_range.hpp>
|
#include <boost/range/iterator_range.hpp>
|
||||||
#include <boost/tuple/tuple.hpp>
|
#include <boost/tuple/tuple.hpp>
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <memory>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
@@ -98,58 +98,54 @@ class BasicNetwork
|
|||||||
public:
|
public:
|
||||||
using peer_type = Peer;
|
using peer_type = Peer;
|
||||||
|
|
||||||
using clock_type =
|
using clock_type = beast::manual_clock<std::chrono::steady_clock>;
|
||||||
beast::manual_clock<
|
|
||||||
std::chrono::steady_clock>;
|
|
||||||
|
|
||||||
using duration =
|
using duration = typename clock_type::duration;
|
||||||
typename clock_type::duration;
|
|
||||||
|
|
||||||
using time_point =
|
using time_point = typename clock_type::time_point;
|
||||||
typename clock_type::time_point;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct by_to_tag {};
|
struct by_to_tag
|
||||||
struct by_from_tag {};
|
{
|
||||||
struct by_when_tag {};
|
};
|
||||||
|
struct by_from_tag
|
||||||
|
{
|
||||||
|
};
|
||||||
|
struct by_when_tag
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
using by_to_hook =
|
using by_to_hook = boost::intrusive::list_base_hook<
|
||||||
boost::intrusive::list_base_hook<
|
boost::intrusive::link_mode<boost::intrusive::normal_link>,
|
||||||
boost::intrusive::link_mode<
|
boost::intrusive::tag<by_to_tag>>;
|
||||||
boost::intrusive::normal_link>,
|
|
||||||
boost::intrusive::tag<by_to_tag>>;
|
|
||||||
|
|
||||||
using by_from_hook =
|
using by_from_hook = boost::intrusive::list_base_hook<
|
||||||
boost::intrusive::list_base_hook<
|
boost::intrusive::link_mode<boost::intrusive::normal_link>,
|
||||||
boost::intrusive::link_mode<
|
boost::intrusive::tag<by_from_tag>>;
|
||||||
boost::intrusive::normal_link>,
|
|
||||||
boost::intrusive::tag<by_from_tag>>;
|
|
||||||
|
|
||||||
using by_when_hook =
|
using by_when_hook = boost::intrusive::set_base_hook<
|
||||||
boost::intrusive::set_base_hook<
|
boost::intrusive::link_mode<boost::intrusive::normal_link>>;
|
||||||
boost::intrusive::link_mode<
|
|
||||||
boost::intrusive::normal_link>>;
|
|
||||||
|
|
||||||
struct msg
|
struct msg : by_to_hook, by_from_hook, by_when_hook
|
||||||
: by_to_hook, by_from_hook, by_when_hook
|
|
||||||
{
|
{
|
||||||
Peer to;
|
Peer to;
|
||||||
Peer from;
|
Peer from;
|
||||||
time_point when;
|
time_point when;
|
||||||
|
|
||||||
msg (msg const&) = delete;
|
msg(msg const&) = delete;
|
||||||
msg& operator= (msg const&) = delete;
|
msg&
|
||||||
|
operator=(msg const&) = delete;
|
||||||
virtual ~msg() = default;
|
virtual ~msg() = default;
|
||||||
virtual void operator()() const = 0;
|
virtual void
|
||||||
|
operator()() const = 0;
|
||||||
|
|
||||||
msg (Peer const& from_, Peer const& to_,
|
msg(Peer const& from_, Peer const& to_, time_point when_)
|
||||||
time_point when_)
|
|
||||||
: to(to_), from(from_), when(when_)
|
: to(to_), from(from_), when(when_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
operator< (msg const& other) const
|
operator<(msg const& other) const
|
||||||
{
|
{
|
||||||
return when < other.when;
|
return when < other.when;
|
||||||
}
|
}
|
||||||
@@ -162,24 +158,30 @@ private:
|
|||||||
Handler const h_;
|
Handler const h_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
msg_impl (msg_impl const&) = delete;
|
msg_impl(msg_impl const&) = delete;
|
||||||
msg_impl& operator= (msg_impl const&) = delete;
|
msg_impl&
|
||||||
|
operator=(msg_impl const&) = delete;
|
||||||
|
|
||||||
msg_impl (Peer const& from_, Peer const& to_,
|
msg_impl(
|
||||||
time_point when_, Handler&& h)
|
Peer const& from_,
|
||||||
: msg (from_, to_, when_)
|
Peer const& to_,
|
||||||
, h_ (std::move(h))
|
time_point when_,
|
||||||
|
Handler&& h)
|
||||||
|
: msg(from_, to_, when_), h_(std::move(h))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_impl (Peer const& from_, Peer const& to_,
|
msg_impl(
|
||||||
time_point when_, Handler const& h)
|
Peer const& from_,
|
||||||
: msg (from_, to_, when_)
|
Peer const& to_,
|
||||||
, h_ (h)
|
time_point when_,
|
||||||
|
Handler const& h)
|
||||||
|
: msg(from_, to_, when_), h_(h)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()() const override
|
void
|
||||||
|
operator()() const override
|
||||||
{
|
{
|
||||||
h_();
|
h_();
|
||||||
}
|
}
|
||||||
@@ -188,19 +190,19 @@ private:
|
|||||||
class queue_type
|
class queue_type
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
using by_to_list = typename
|
using by_to_list = typename boost::intrusive::make_list<
|
||||||
boost::intrusive::make_list<msg,
|
msg,
|
||||||
boost::intrusive::base_hook<by_to_hook>,
|
boost::intrusive::base_hook<by_to_hook>,
|
||||||
boost::intrusive::constant_time_size<false>>::type;
|
boost::intrusive::constant_time_size<false>>::type;
|
||||||
|
|
||||||
using by_from_list = typename
|
using by_from_list = typename boost::intrusive::make_list<
|
||||||
boost::intrusive::make_list<msg,
|
msg,
|
||||||
boost::intrusive::base_hook<by_from_hook>,
|
boost::intrusive::base_hook<by_from_hook>,
|
||||||
boost::intrusive::constant_time_size<false>>::type;
|
boost::intrusive::constant_time_size<false>>::type;
|
||||||
|
|
||||||
using by_when_set = typename
|
using by_when_set = typename boost::intrusive::make_multiset<
|
||||||
boost::intrusive::make_multiset<msg,
|
msg,
|
||||||
boost::intrusive::constant_time_size<false>>::type;
|
boost::intrusive::constant_time_size<false>>::type;
|
||||||
|
|
||||||
qalloc alloc_;
|
qalloc alloc_;
|
||||||
by_when_set by_when_;
|
by_when_set by_when_;
|
||||||
@@ -208,14 +210,13 @@ private:
|
|||||||
std::unordered_map<Peer, by_from_list> by_from_;
|
std::unordered_map<Peer, by_from_list> by_from_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using iterator =
|
using iterator = typename by_when_set::iterator;
|
||||||
typename by_when_set::iterator;
|
|
||||||
|
|
||||||
queue_type (queue_type const&) = delete;
|
queue_type(queue_type const&) = delete;
|
||||||
queue_type& operator= (queue_type const&) = delete;
|
queue_type&
|
||||||
|
operator=(queue_type const&) = delete;
|
||||||
|
|
||||||
explicit
|
explicit queue_type(qalloc const& alloc);
|
||||||
queue_type (qalloc const& alloc);
|
|
||||||
|
|
||||||
~queue_type();
|
~queue_type();
|
||||||
|
|
||||||
@@ -230,14 +231,13 @@ private:
|
|||||||
|
|
||||||
template <class Handler>
|
template <class Handler>
|
||||||
typename by_when_set::iterator
|
typename by_when_set::iterator
|
||||||
emplace (Peer const& from, Peer const& to,
|
emplace(Peer const& from, Peer const& to, time_point when, Handler&& h);
|
||||||
time_point when, Handler&& h);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
erase (iterator iter);
|
erase(iterator iter);
|
||||||
|
|
||||||
void
|
void
|
||||||
remove (Peer const& from, Peer const& to);
|
remove(Peer const& from, Peer const& to);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct link_type
|
struct link_type
|
||||||
@@ -245,15 +245,13 @@ private:
|
|||||||
bool inbound;
|
bool inbound;
|
||||||
duration delay;
|
duration delay;
|
||||||
|
|
||||||
link_type (bool inbound_, duration delay_)
|
link_type(bool inbound_, duration delay_)
|
||||||
: inbound (inbound_)
|
: inbound(inbound_), delay(delay_)
|
||||||
, delay (delay_)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using links_type =
|
using links_type = boost::container::flat_map<Peer, link_type>;
|
||||||
boost::container::flat_map<Peer, link_type>;
|
|
||||||
|
|
||||||
class link_transform;
|
class link_transform;
|
||||||
|
|
||||||
@@ -265,8 +263,9 @@ private:
|
|||||||
std::unordered_map<Peer, links_type> links_;
|
std::unordered_map<Peer, links_type> links_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BasicNetwork (BasicNetwork const&) = delete;
|
BasicNetwork(BasicNetwork const&) = delete;
|
||||||
BasicNetwork& operator= (BasicNetwork const&) = delete;
|
BasicNetwork&
|
||||||
|
operator=(BasicNetwork const&) = delete;
|
||||||
|
|
||||||
BasicNetwork();
|
BasicNetwork();
|
||||||
|
|
||||||
@@ -308,7 +307,9 @@ public:
|
|||||||
@return `true` if a new connection was established
|
@return `true` if a new connection was established
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
connect (Peer const& from, Peer const& to,
|
connect(
|
||||||
|
Peer const& from,
|
||||||
|
Peer const& to,
|
||||||
duration const& delay = std::chrono::seconds{0});
|
duration const& delay = std::chrono::seconds{0});
|
||||||
|
|
||||||
/** Break a link.
|
/** Break a link.
|
||||||
@@ -324,15 +325,14 @@ public:
|
|||||||
@return `true` if a connection was broken.
|
@return `true` if a connection was broken.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
disconnect (Peer const& peer1, Peer const& peer2);
|
disconnect(Peer const& peer1, Peer const& peer2);
|
||||||
|
|
||||||
/** Return the range of active links.
|
/** Return the range of active links.
|
||||||
|
|
||||||
@return A random access range.
|
@return A random access range.
|
||||||
*/
|
*/
|
||||||
boost::transformed_range<
|
boost::transformed_range<link_transform, links_type>
|
||||||
link_transform, links_type>
|
links(Peer const& from);
|
||||||
links (Peer const& from);
|
|
||||||
|
|
||||||
/** Send a message to a peer.
|
/** Send a message to a peer.
|
||||||
|
|
||||||
@@ -354,8 +354,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template <class Function>
|
template <class Function>
|
||||||
void
|
void
|
||||||
send (Peer const& from, Peer const& to,
|
send(Peer const& from, Peer const& to, Function&& f);
|
||||||
Function&& f);
|
|
||||||
|
|
||||||
// Used to cancel timers
|
// Used to cancel timers
|
||||||
struct cancel_token;
|
struct cancel_token;
|
||||||
@@ -370,8 +369,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template <class Function>
|
template <class Function>
|
||||||
cancel_token
|
cancel_token
|
||||||
timer (time_point const& when,
|
timer(time_point const& when, Function&& f);
|
||||||
Function&& f);
|
|
||||||
|
|
||||||
/** Deliver a timer notification.
|
/** Deliver a timer notification.
|
||||||
|
|
||||||
@@ -383,8 +381,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template <class Function>
|
template <class Function>
|
||||||
cancel_token
|
cancel_token
|
||||||
timer (duration const& delay,
|
timer(duration const& delay, Function&& f);
|
||||||
Function&& f);
|
|
||||||
|
|
||||||
/** Cancel a timer.
|
/** Cancel a timer.
|
||||||
|
|
||||||
@@ -394,7 +391,7 @@ public:
|
|||||||
timer() which has not yet been invoked.
|
timer() which has not yet been invoked.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
cancel (cancel_token const& token);
|
cancel(cancel_token const& token);
|
||||||
|
|
||||||
/** Perform breadth-first search.
|
/** Perform breadth-first search.
|
||||||
|
|
||||||
@@ -407,7 +404,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template <class Function>
|
template <class Function>
|
||||||
void
|
void
|
||||||
bfs (Peer const& start, Function&& f);
|
bfs(Peer const& start, Function&& f);
|
||||||
|
|
||||||
/** Run the network for up to one message.
|
/** Run the network for up to one message.
|
||||||
|
|
||||||
@@ -448,7 +445,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
template <class Function>
|
template <class Function>
|
||||||
bool
|
bool
|
||||||
step_while(Function && func);
|
step_while(Function&& func);
|
||||||
|
|
||||||
/** Run the network until the specified time.
|
/** Run the network until the specified time.
|
||||||
|
|
||||||
@@ -460,7 +457,7 @@ public:
|
|||||||
@return `true` if any messages remain.
|
@return `true` if any messages remain.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
step_until (time_point const& until);
|
step_until(time_point const& until);
|
||||||
|
|
||||||
/** Run the network until time has elapsed.
|
/** Run the network until time has elapsed.
|
||||||
|
|
||||||
@@ -473,24 +470,21 @@ public:
|
|||||||
*/
|
*/
|
||||||
template <class Period, class Rep>
|
template <class Period, class Rep>
|
||||||
bool
|
bool
|
||||||
step_for (std::chrono::duration<
|
step_for(std::chrono::duration<Period, Rep> const& amount);
|
||||||
Period, Rep> const& amount);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
BasicNetwork<Peer>::queue_type::queue_type(
|
BasicNetwork<Peer>::queue_type::queue_type(qalloc const& alloc)
|
||||||
qalloc const& alloc)
|
: alloc_(alloc)
|
||||||
: alloc_ (alloc)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
BasicNetwork<Peer>::queue_type::~queue_type()
|
BasicNetwork<Peer>::queue_type::~queue_type()
|
||||||
{
|
{
|
||||||
for(auto iter = by_when_.begin();
|
for (auto iter = by_when_.begin(); iter != by_when_.end();)
|
||||||
iter != by_when_.end();)
|
|
||||||
{
|
{
|
||||||
auto m = &*iter;
|
auto m = &*iter;
|
||||||
++iter;
|
++iter;
|
||||||
@@ -500,27 +494,22 @@ BasicNetwork<Peer>::queue_type::~queue_type()
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
inline
|
inline bool
|
||||||
bool
|
|
||||||
BasicNetwork<Peer>::queue_type::empty() const
|
BasicNetwork<Peer>::queue_type::empty() const
|
||||||
{
|
{
|
||||||
return by_when_.empty();
|
return by_when_.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
inline
|
inline auto
|
||||||
auto
|
BasicNetwork<Peer>::queue_type::begin() -> iterator
|
||||||
BasicNetwork<Peer>::queue_type::begin() ->
|
|
||||||
iterator
|
|
||||||
{
|
{
|
||||||
return by_when_.begin();
|
return by_when_.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
inline
|
inline auto
|
||||||
auto
|
BasicNetwork<Peer>::queue_type::end() -> iterator
|
||||||
BasicNetwork<Peer>::queue_type::end() ->
|
|
||||||
iterator
|
|
||||||
{
|
{
|
||||||
return by_when_.end();
|
return by_when_.end();
|
||||||
}
|
}
|
||||||
@@ -529,15 +518,14 @@ template <class Peer>
|
|||||||
template <class Handler>
|
template <class Handler>
|
||||||
auto
|
auto
|
||||||
BasicNetwork<Peer>::queue_type::emplace(
|
BasicNetwork<Peer>::queue_type::emplace(
|
||||||
Peer const& from, Peer const& to, time_point when,
|
Peer const& from,
|
||||||
Handler&& h) ->
|
Peer const& to,
|
||||||
typename by_when_set::iterator
|
time_point when,
|
||||||
|
Handler&& h) -> typename by_when_set::iterator
|
||||||
{
|
{
|
||||||
using msg_type = msg_impl<
|
using msg_type = msg_impl<std::decay_t<Handler>>;
|
||||||
std::decay_t<Handler>>;
|
|
||||||
auto const p = alloc_.alloc<msg_type>(1);
|
auto const p = alloc_.alloc<msg_type>(1);
|
||||||
auto& m = *new(p) msg_type(from, to,
|
auto& m = *new (p) msg_type(from, to, when, std::forward<Handler>(h));
|
||||||
when, std::forward<Handler>(h));
|
|
||||||
if (to)
|
if (to)
|
||||||
by_to_[to].push_back(m);
|
by_to_[to].push_back(m);
|
||||||
if (from)
|
if (from)
|
||||||
@@ -547,8 +535,7 @@ BasicNetwork<Peer>::queue_type::emplace(
|
|||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
void
|
void
|
||||||
BasicNetwork<Peer>::queue_type::erase(
|
BasicNetwork<Peer>::queue_type::erase(iterator iter)
|
||||||
iterator iter)
|
|
||||||
{
|
{
|
||||||
auto& m = *iter;
|
auto& m = *iter;
|
||||||
if (iter->to)
|
if (iter->to)
|
||||||
@@ -568,13 +555,11 @@ BasicNetwork<Peer>::queue_type::erase(
|
|||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
void
|
void
|
||||||
BasicNetwork<Peer>::queue_type::remove(
|
BasicNetwork<Peer>::queue_type::remove(Peer const& from, Peer const& to)
|
||||||
Peer const& from, Peer const& to)
|
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto& list = by_to_[to];
|
auto& list = by_to_[to];
|
||||||
for(auto iter = list.begin();
|
for (auto iter = list.begin(); iter != list.end();)
|
||||||
iter != list.end();)
|
|
||||||
{
|
{
|
||||||
auto& m = *iter++;
|
auto& m = *iter++;
|
||||||
if (m.from == from)
|
if (m.from == from)
|
||||||
@@ -583,8 +568,7 @@ BasicNetwork<Peer>::queue_type::remove(
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto& list = by_to_[from];
|
auto& list = by_to_[from];
|
||||||
for(auto iter = list.begin();
|
for (auto iter = list.begin(); iter != list.end();)
|
||||||
iter != list.end();)
|
|
||||||
{
|
{
|
||||||
auto& m = *iter++;
|
auto& m = *iter++;
|
||||||
if (m.from == to)
|
if (m.from == to)
|
||||||
@@ -603,8 +587,7 @@ private:
|
|||||||
Peer from_;
|
Peer from_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using argument_type =
|
using argument_type = typename links_type::value_type;
|
||||||
typename links_type::value_type;
|
|
||||||
|
|
||||||
class result_type
|
class result_type
|
||||||
{
|
{
|
||||||
@@ -612,15 +595,14 @@ public:
|
|||||||
Peer to;
|
Peer to;
|
||||||
bool inbound;
|
bool inbound;
|
||||||
|
|
||||||
result_type (result_type const&) = default;
|
result_type(result_type const&) = default;
|
||||||
|
|
||||||
result_type (BasicNetwork& net,
|
result_type(
|
||||||
Peer const& from, Peer const& to_,
|
BasicNetwork& net,
|
||||||
bool inbound_)
|
Peer const& from,
|
||||||
: to(to_)
|
Peer const& to_,
|
||||||
, inbound(inbound_)
|
bool inbound_)
|
||||||
, net_(net)
|
: to(to_), inbound(inbound_), net_(net), from_(from)
|
||||||
, from_(from)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,18 +623,14 @@ public:
|
|||||||
Peer from_;
|
Peer from_;
|
||||||
};
|
};
|
||||||
|
|
||||||
link_transform (BasicNetwork& net,
|
link_transform(BasicNetwork& net, Peer const& from) : net_(net), from_(from)
|
||||||
Peer const& from)
|
|
||||||
: net_(net)
|
|
||||||
, from_(from)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
result_type const
|
result_type const
|
||||||
operator()(argument_type const& v) const
|
operator()(argument_type const& v) const
|
||||||
{
|
{
|
||||||
return result_type(net_, from_,
|
return result_type(net_, from_, v.first, v.second.inbound);
|
||||||
v.first, v.second.inbound);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -666,14 +644,13 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
cancel_token() = delete;
|
cancel_token() = delete;
|
||||||
cancel_token (cancel_token const&) = default;
|
cancel_token(cancel_token const&) = default;
|
||||||
cancel_token& operator= (cancel_token const&) = default;
|
cancel_token&
|
||||||
|
operator=(cancel_token const&) = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class BasicNetwork;
|
friend class BasicNetwork;
|
||||||
cancel_token(typename
|
cancel_token(typename queue_type::iterator iter) : iter_(iter)
|
||||||
queue_type::iterator iter)
|
|
||||||
: iter_ (iter)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -681,33 +658,27 @@ private:
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
BasicNetwork<Peer>::BasicNetwork()
|
BasicNetwork<Peer>::BasicNetwork() : queue_(alloc_)
|
||||||
: queue_ (alloc_)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
inline
|
inline qalloc const&
|
||||||
qalloc const&
|
|
||||||
BasicNetwork<Peer>::alloc() const
|
BasicNetwork<Peer>::alloc() const
|
||||||
{
|
{
|
||||||
return alloc_;
|
return alloc_;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
inline
|
inline auto
|
||||||
auto
|
BasicNetwork<Peer>::clock() const -> clock_type&
|
||||||
BasicNetwork<Peer>::clock() const ->
|
|
||||||
clock_type&
|
|
||||||
{
|
{
|
||||||
return clock_;
|
return clock_;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
inline
|
inline auto
|
||||||
auto
|
BasicNetwork<Peer>::now() const -> time_point
|
||||||
BasicNetwork<Peer>::now() const ->
|
|
||||||
time_point
|
|
||||||
{
|
{
|
||||||
return clock_.now();
|
return clock_.now();
|
||||||
}
|
}
|
||||||
@@ -715,17 +686,16 @@ BasicNetwork<Peer>::now() const ->
|
|||||||
template <class Peer>
|
template <class Peer>
|
||||||
bool
|
bool
|
||||||
BasicNetwork<Peer>::connect(
|
BasicNetwork<Peer>::connect(
|
||||||
Peer const& from, Peer const& to,
|
Peer const& from,
|
||||||
duration const& delay)
|
Peer const& to,
|
||||||
|
duration const& delay)
|
||||||
{
|
{
|
||||||
if (to == from)
|
if (to == from)
|
||||||
return false;
|
return false;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
if (! links_[from].emplace(to,
|
if (!links_[from].emplace(to, link_type{false, delay}).second)
|
||||||
link_type{ false, delay }).second)
|
|
||||||
return false;
|
return false;
|
||||||
auto const result = links_[to].emplace(
|
auto const result = links_[to].emplace(from, link_type{true, delay});
|
||||||
from, link_type{ true, delay });
|
|
||||||
(void)result;
|
(void)result;
|
||||||
assert(result.second);
|
assert(result.second);
|
||||||
return true;
|
return true;
|
||||||
@@ -733,13 +703,11 @@ BasicNetwork<Peer>::connect(
|
|||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
bool
|
bool
|
||||||
BasicNetwork<Peer>::disconnect(
|
BasicNetwork<Peer>::disconnect(Peer const& peer1, Peer const& peer2)
|
||||||
Peer const& peer1, Peer const& peer2)
|
|
||||||
{
|
{
|
||||||
if (links_[peer1].erase(peer2) == 0)
|
if (links_[peer1].erase(peer2) == 0)
|
||||||
return false;
|
return false;
|
||||||
auto const n =
|
auto const n = links_[peer2].erase(peer1);
|
||||||
links_[peer2].erase(peer1);
|
|
||||||
(void)n;
|
(void)n;
|
||||||
assert(n);
|
assert(n);
|
||||||
queue_.remove(peer1, peer2);
|
queue_.remove(peer1, peer2);
|
||||||
@@ -747,64 +715,45 @@ BasicNetwork<Peer>::disconnect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
inline
|
inline auto
|
||||||
auto
|
BasicNetwork<Peer>::links(Peer const& from)
|
||||||
BasicNetwork<Peer>::links(Peer const& from) ->
|
-> boost::transformed_range<link_transform, links_type>
|
||||||
boost::transformed_range<
|
|
||||||
link_transform, links_type>
|
|
||||||
{
|
{
|
||||||
return boost::adaptors::transform(
|
return boost::adaptors::transform(
|
||||||
links_[from],
|
links_[from], link_transform{*this, from});
|
||||||
link_transform{ *this, from });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
template <class Function>
|
template <class Function>
|
||||||
inline
|
inline void
|
||||||
void
|
BasicNetwork<Peer>::send(Peer const& from, Peer const& to, Function&& f)
|
||||||
BasicNetwork<Peer>::send(
|
|
||||||
Peer const& from, Peer const& to,
|
|
||||||
Function&& f)
|
|
||||||
{
|
{
|
||||||
using namespace std;
|
using namespace std;
|
||||||
auto const iter =
|
auto const iter = links_[from].find(to);
|
||||||
links_[from].find(to);
|
queue_.emplace(
|
||||||
queue_.emplace(from, to,
|
from, to, clock_.now() + iter->second.delay, forward<Function>(f));
|
||||||
clock_.now() + iter->second.delay,
|
|
||||||
forward<Function>(f));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
template <class Function>
|
template <class Function>
|
||||||
inline
|
inline auto
|
||||||
auto
|
BasicNetwork<Peer>::timer(time_point const& when, Function&& f) -> cancel_token
|
||||||
BasicNetwork<Peer>::timer(
|
|
||||||
time_point const& when, Function&& f) ->
|
|
||||||
cancel_token
|
|
||||||
{
|
{
|
||||||
using namespace std;
|
using namespace std;
|
||||||
return queue_.emplace(
|
return queue_.emplace(nullptr, nullptr, when, forward<Function>(f));
|
||||||
nullptr, nullptr, when,
|
|
||||||
forward<Function>(f));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
template <class Function>
|
template <class Function>
|
||||||
inline
|
inline auto
|
||||||
auto
|
BasicNetwork<Peer>::timer(duration const& delay, Function&& f) -> cancel_token
|
||||||
BasicNetwork<Peer>::timer(
|
|
||||||
duration const& delay, Function&& f) ->
|
|
||||||
cancel_token
|
|
||||||
{
|
{
|
||||||
return timer(clock_.now() + delay,
|
return timer(clock_.now() + delay, std::forward<Function>(f));
|
||||||
std::forward<Function>(f));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
inline
|
inline void
|
||||||
void
|
BasicNetwork<Peer>::cancel(cancel_token const& token)
|
||||||
BasicNetwork<Peer>::cancel(
|
|
||||||
cancel_token const& token)
|
|
||||||
{
|
{
|
||||||
queue_.erase(token.iter_);
|
queue_.erase(token.iter_);
|
||||||
}
|
}
|
||||||
@@ -826,10 +775,10 @@ template <class Peer>
|
|||||||
bool
|
bool
|
||||||
BasicNetwork<Peer>::step()
|
BasicNetwork<Peer>::step()
|
||||||
{
|
{
|
||||||
if (! step_one())
|
if (!step_one())
|
||||||
return false;
|
return false;
|
||||||
for(;;)
|
for (;;)
|
||||||
if (! step_one())
|
if (!step_one())
|
||||||
break;
|
break;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -837,7 +786,7 @@ BasicNetwork<Peer>::step()
|
|||||||
template <class Peer>
|
template <class Peer>
|
||||||
template <class Function>
|
template <class Function>
|
||||||
bool
|
bool
|
||||||
BasicNetwork<Peer>::step_while(Function && f)
|
BasicNetwork<Peer>::step_while(Function&& f)
|
||||||
{
|
{
|
||||||
bool ran = false;
|
bool ran = false;
|
||||||
while (f() && step_one())
|
while (f() && step_one())
|
||||||
@@ -847,17 +796,16 @@ BasicNetwork<Peer>::step_while(Function && f)
|
|||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
bool
|
bool
|
||||||
BasicNetwork<Peer>::step_until(
|
BasicNetwork<Peer>::step_until(time_point const& until)
|
||||||
time_point const& until)
|
|
||||||
{
|
{
|
||||||
// VFALCO This routine needs optimizing
|
// VFALCO This routine needs optimizing
|
||||||
if(queue_.empty())
|
if (queue_.empty())
|
||||||
{
|
{
|
||||||
clock_.set(until);
|
clock_.set(until);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto iter = queue_.begin();
|
auto iter = queue_.begin();
|
||||||
if(iter->when > until)
|
if (iter->when > until)
|
||||||
{
|
{
|
||||||
clock_.set(until);
|
clock_.set(until);
|
||||||
return true;
|
return true;
|
||||||
@@ -866,19 +814,15 @@ BasicNetwork<Peer>::step_until(
|
|||||||
{
|
{
|
||||||
step_one();
|
step_one();
|
||||||
iter = queue_.begin();
|
iter = queue_.begin();
|
||||||
}
|
} while (iter != queue_.end() && iter->when <= until);
|
||||||
while(iter != queue_.end() &&
|
|
||||||
iter->when <= until);
|
|
||||||
clock_.set(until);
|
clock_.set(until);
|
||||||
return iter != queue_.end();
|
return iter != queue_.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Peer>
|
template <class Peer>
|
||||||
template <class Period, class Rep>
|
template <class Period, class Rep>
|
||||||
inline
|
inline bool
|
||||||
bool
|
BasicNetwork<Peer>::step_for(std::chrono::duration<Period, Rep> const& amount)
|
||||||
BasicNetwork<Peer>::step_for(
|
|
||||||
std::chrono::duration<Period, Rep> const& amount)
|
|
||||||
{
|
{
|
||||||
return step_until(now() + amount);
|
return step_until(now() + amount);
|
||||||
}
|
}
|
||||||
@@ -886,19 +830,18 @@ BasicNetwork<Peer>::step_for(
|
|||||||
template <class Peer>
|
template <class Peer>
|
||||||
template <class Function>
|
template <class Function>
|
||||||
void
|
void
|
||||||
BasicNetwork<Peer>::bfs(
|
BasicNetwork<Peer>::bfs(Peer const& start, Function&& f)
|
||||||
Peer const& start, Function&& f)
|
|
||||||
{
|
{
|
||||||
std::deque<std::pair<Peer, std::size_t>> q;
|
std::deque<std::pair<Peer, std::size_t>> q;
|
||||||
std::unordered_set<Peer> seen;
|
std::unordered_set<Peer> seen;
|
||||||
q.emplace_back(start, 0);
|
q.emplace_back(start, 0);
|
||||||
seen.insert(start);
|
seen.insert(start);
|
||||||
while(! q.empty())
|
while (!q.empty())
|
||||||
{
|
{
|
||||||
auto v = q.front();
|
auto v = q.front();
|
||||||
q.pop_front();
|
q.pop_front();
|
||||||
f(v.second, v.first);
|
f(v.second, v.first);
|
||||||
for(auto const& link : links_[v.first])
|
for (auto const& link : links_[v.first])
|
||||||
{
|
{
|
||||||
auto const& w = link.first;
|
auto const& w = link.first;
|
||||||
if (seen.count(w) == 0)
|
if (seen.count(w) == 0)
|
||||||
@@ -910,8 +853,8 @@ BasicNetwork<Peer>::bfs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // csf
|
} // csf
|
||||||
} // test
|
} // test
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -18,15 +18,14 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
#include <test/csf/BasicNetwork.h>
|
|
||||||
#include <ripple/beast/unit_test.h>
|
#include <ripple/beast/unit_test.h>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <test/csf/BasicNetwork.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
|
|
||||||
class BasicNetwork_test : public beast::unit_test::suite
|
class BasicNetwork_test : public beast::unit_test::suite
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -35,28 +34,25 @@ public:
|
|||||||
int id;
|
int id;
|
||||||
std::set<int> set;
|
std::set<int> set;
|
||||||
|
|
||||||
Peer (Peer const&) = default;
|
Peer(Peer const&) = default;
|
||||||
Peer (Peer&&) = default;
|
Peer(Peer&&) = default;
|
||||||
|
|
||||||
explicit Peer(int id_)
|
explicit Peer(int id_) : id(id_)
|
||||||
: id(id_)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Net>
|
template <class Net>
|
||||||
void start(Net& net)
|
void
|
||||||
|
start(Net& net)
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
auto t = net.timer(1s,
|
auto t = net.timer(1s, [&] { set.insert(0); });
|
||||||
[&]{ set.insert(0); });
|
|
||||||
if (id == 0)
|
if (id == 0)
|
||||||
{
|
{
|
||||||
for(auto const& link : net.links(this))
|
for (auto const& link : net.links(this))
|
||||||
net.send(this, link.to,
|
net.send(this, link.to, [&, to = link.to ] {
|
||||||
[&, to = link.to]
|
to->receive(net, this, 1);
|
||||||
{
|
});
|
||||||
to->receive(net, this, 1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -65,23 +61,23 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class Net>
|
template <class Net>
|
||||||
void receive(Net& net, Peer* from, int m)
|
void
|
||||||
|
receive(Net& net, Peer* from, int m)
|
||||||
{
|
{
|
||||||
set.insert(m);
|
set.insert(m);
|
||||||
++m;
|
++m;
|
||||||
if (m < 5)
|
if (m < 5)
|
||||||
{
|
{
|
||||||
for(auto const& link : net.links(this))
|
for (auto const& link : net.links(this))
|
||||||
net.send(this, link.to,
|
net.send(this, link.to, [&, mm = m, to = link.to ] {
|
||||||
[&, mm = m, to = link.to]
|
to->receive(net, this, mm);
|
||||||
{
|
});
|
||||||
to->receive(net, this, mm);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void run() override
|
void
|
||||||
|
run() override
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
std::vector<Peer> pv;
|
std::vector<Peer> pv;
|
||||||
@@ -89,45 +85,40 @@ public:
|
|||||||
pv.emplace_back(1);
|
pv.emplace_back(1);
|
||||||
pv.emplace_back(2);
|
pv.emplace_back(2);
|
||||||
csf::BasicNetwork<Peer*> net;
|
csf::BasicNetwork<Peer*> net;
|
||||||
BEAST_EXPECT(! net.connect(&pv[0], &pv[0]));
|
BEAST_EXPECT(!net.connect(&pv[0], &pv[0]));
|
||||||
BEAST_EXPECT(net.connect(&pv[0], &pv[1], 1s));
|
BEAST_EXPECT(net.connect(&pv[0], &pv[1], 1s));
|
||||||
BEAST_EXPECT(net.connect(&pv[1], &pv[2], 1s));
|
BEAST_EXPECT(net.connect(&pv[1], &pv[2], 1s));
|
||||||
BEAST_EXPECT(! net.connect(&pv[0], &pv[1]));
|
BEAST_EXPECT(!net.connect(&pv[0], &pv[1]));
|
||||||
std::size_t diameter = 0;
|
std::size_t diameter = 0;
|
||||||
net.bfs(&pv[0],
|
net.bfs(
|
||||||
[&](auto d, Peer*)
|
&pv[0], [&](auto d, Peer*) { diameter = std::max(d, diameter); });
|
||||||
{ diameter = std::max(d, diameter); });
|
|
||||||
BEAST_EXPECT(diameter == 2);
|
BEAST_EXPECT(diameter == 2);
|
||||||
for(auto& peer : pv)
|
for (auto& peer : pv)
|
||||||
peer.start(net);
|
peer.start(net);
|
||||||
BEAST_EXPECT(net.step_for(0s));
|
BEAST_EXPECT(net.step_for(0s));
|
||||||
BEAST_EXPECT(net.step_for(1s));
|
BEAST_EXPECT(net.step_for(1s));
|
||||||
BEAST_EXPECT(net.step());
|
BEAST_EXPECT(net.step());
|
||||||
BEAST_EXPECT(! net.step());
|
BEAST_EXPECT(!net.step());
|
||||||
BEAST_EXPECT(! net.step_for(1s));
|
BEAST_EXPECT(!net.step_for(1s));
|
||||||
net.send(&pv[0], &pv[1], []{});
|
net.send(&pv[0], &pv[1], [] {});
|
||||||
net.send(&pv[1], &pv[0], []{});
|
net.send(&pv[1], &pv[0], [] {});
|
||||||
BEAST_EXPECT(net.disconnect(&pv[0], &pv[1]));
|
BEAST_EXPECT(net.disconnect(&pv[0], &pv[1]));
|
||||||
BEAST_EXPECT(! net.disconnect(&pv[0], &pv[1]));
|
BEAST_EXPECT(!net.disconnect(&pv[0], &pv[1]));
|
||||||
for(;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
auto const links = net.links(&pv[1]);
|
auto const links = net.links(&pv[1]);
|
||||||
if(links.empty())
|
if (links.empty())
|
||||||
break;
|
break;
|
||||||
BEAST_EXPECT(links[0].disconnect());
|
BEAST_EXPECT(links[0].disconnect());
|
||||||
}
|
}
|
||||||
BEAST_EXPECT(pv[0].set ==
|
BEAST_EXPECT(pv[0].set == std::set<int>({0, 2, 4}));
|
||||||
std::set<int>({0, 2, 4}));
|
BEAST_EXPECT(pv[1].set == std::set<int>({1, 3}));
|
||||||
BEAST_EXPECT(pv[1].set ==
|
BEAST_EXPECT(pv[2].set == std::set<int>({2, 4}));
|
||||||
std::set<int>({1, 3}));
|
net.timer(0s, [] {});
|
||||||
BEAST_EXPECT(pv[2].set ==
|
|
||||||
std::set<int>({2, 4}));
|
|
||||||
net.timer(0s, []{});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BEAST_DEFINE_TESTSUITE(BasicNetwork, test, ripple);
|
BEAST_DEFINE_TESTSUITE(BasicNetwork, test, ripple);
|
||||||
|
|
||||||
} // test
|
} // test
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
|
|||||||
@@ -46,32 +46,32 @@ namespace csf {
|
|||||||
|
|
||||||
class Ledger
|
class Ledger
|
||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
struct ID
|
struct ID
|
||||||
{
|
{
|
||||||
std::uint32_t seq = 0;
|
std::uint32_t seq = 0;
|
||||||
TxSetType txs = TxSetType{};
|
TxSetType txs = TxSetType{};
|
||||||
|
|
||||||
bool operator==(ID const & o) const
|
bool
|
||||||
|
operator==(ID const& o) const
|
||||||
{
|
{
|
||||||
return seq == o.seq && txs == o.txs;
|
return seq == o.seq && txs == o.txs;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(ID const & o) const
|
bool
|
||||||
|
operator!=(ID const& o) const
|
||||||
{
|
{
|
||||||
return !(*this == o);
|
return !(*this == o);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator<(ID const & o) const
|
bool
|
||||||
|
operator<(ID const& o) const
|
||||||
{
|
{
|
||||||
return std::tie(seq, txs) < std::tie(o.seq, o.txs);
|
return std::tie(seq, txs) < std::tie(o.seq, o.txs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto const&
|
||||||
auto const &
|
|
||||||
id() const
|
id() const
|
||||||
{
|
{
|
||||||
return id_;
|
return id_;
|
||||||
@@ -113,7 +113,7 @@ public:
|
|||||||
return parentCloseTime_;
|
return parentCloseTime_;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const &
|
auto const&
|
||||||
parentID() const
|
parentID() const
|
||||||
{
|
{
|
||||||
return parentID_;
|
return parentID_;
|
||||||
@@ -127,30 +127,28 @@ public:
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//! Apply the given transactions to this ledger
|
//! Apply the given transactions to this ledger
|
||||||
Ledger
|
Ledger
|
||||||
close(TxSetType const & txs,
|
close(
|
||||||
|
TxSetType const& txs,
|
||||||
NetClock::duration closeTimeResolution,
|
NetClock::duration closeTimeResolution,
|
||||||
NetClock::time_point const & consensusCloseTime,
|
NetClock::time_point const& consensusCloseTime,
|
||||||
bool closeTimeAgree) const
|
bool closeTimeAgree) const
|
||||||
{
|
{
|
||||||
Ledger res{ *this };
|
Ledger res{*this};
|
||||||
res.id_.txs.insert(txs.begin(), txs.end());
|
res.id_.txs.insert(txs.begin(), txs.end());
|
||||||
res.id_ .seq= seq() + 1;
|
res.id_.seq = seq() + 1;
|
||||||
res.closeTimeResolution_ = closeTimeResolution;
|
res.closeTimeResolution_ = closeTimeResolution;
|
||||||
res.actualCloseTime_ = consensusCloseTime;
|
res.actualCloseTime_ = consensusCloseTime;
|
||||||
res.closeTime_ = effectiveCloseTime(consensusCloseTime,
|
res.closeTime_ = effCloseTime(
|
||||||
closeTimeResolution, parentCloseTime_);
|
consensusCloseTime, closeTimeResolution, parentCloseTime_);
|
||||||
res.closeTimeAgree_ = closeTimeAgree;
|
res.closeTimeAgree_ = closeTimeAgree;
|
||||||
res.parentCloseTime_ = closeTime();
|
res.parentCloseTime_ = closeTime();
|
||||||
res.parentID_ = id();
|
res.parentID_ = id();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
//! Unique identifier of ledger is combination of sequence number and id
|
//! Unique identifier of ledger is combination of sequence number and id
|
||||||
ID id_;
|
ID id_;
|
||||||
|
|
||||||
@@ -171,27 +169,24 @@ private:
|
|||||||
|
|
||||||
//! Close time unadjusted by closeTimeResolution
|
//! Close time unadjusted by closeTimeResolution
|
||||||
NetClock::time_point actualCloseTime_;
|
NetClock::time_point actualCloseTime_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inline
|
inline std::ostream&
|
||||||
std::ostream &
|
operator<<(std::ostream& o, Ledger::ID const& id)
|
||||||
operator<<(std::ostream & o, Ledger::ID const & id)
|
|
||||||
{
|
{
|
||||||
return o << id.seq << "," << id.txs;
|
return o << id.seq << "," << id.txs;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline
|
inline std::string
|
||||||
std::string
|
to_string(Ledger::ID const& id)
|
||||||
to_string(Ledger::ID const & id)
|
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << id;
|
ss << id;
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // csf
|
} // csf
|
||||||
} // test
|
} // test
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -22,15 +22,14 @@
|
|||||||
#include <boost/container/flat_map.hpp>
|
#include <boost/container/flat_map.hpp>
|
||||||
#include <boost/container/flat_set.hpp>
|
#include <boost/container/flat_set.hpp>
|
||||||
|
|
||||||
#include <test/csf/Tx.h>
|
|
||||||
#include <test/csf/Ledger.h>
|
#include <test/csf/Ledger.h>
|
||||||
|
#include <test/csf/Tx.h>
|
||||||
#include <test/csf/UNL.h>
|
#include <test/csf/UNL.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
namespace csf {
|
namespace csf {
|
||||||
|
|
||||||
|
|
||||||
/** Store validations reached by peers */
|
/** Store validations reached by peers */
|
||||||
struct Validation
|
struct Validation
|
||||||
{
|
{
|
||||||
@@ -45,15 +44,17 @@ class Validations
|
|||||||
{
|
{
|
||||||
//< Ledgers seen by peers, saved in order received (which should be order
|
//< Ledgers seen by peers, saved in order received (which should be order
|
||||||
//< created)
|
//< created)
|
||||||
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromLedger;
|
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromLedger;
|
||||||
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromPrevLedger;
|
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromPrevLedger;
|
||||||
bc::flat_map<Ledger::ID, bc::flat_map<Ledger::ID, std::size_t>> childLedgers;
|
bc::flat_map<Ledger::ID, bc::flat_map<Ledger::ID, std::size_t>>
|
||||||
|
childLedgers;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void
|
void
|
||||||
update(Validation const & v)
|
update(Validation const& v)
|
||||||
{
|
{
|
||||||
nodesFromLedger[v.ledger].insert(v.id);
|
nodesFromLedger[v.ledger].insert(v.id);
|
||||||
if(v.ledger.seq > 0)
|
if (v.ledger.seq > 0)
|
||||||
{
|
{
|
||||||
nodesFromPrevLedger[v.prevLedger].insert(v.id);
|
nodesFromPrevLedger[v.prevLedger].insert(v.id);
|
||||||
childLedgers[v.prevLedger][v.ledger]++;
|
childLedgers[v.prevLedger][v.ledger]++;
|
||||||
@@ -62,10 +63,10 @@ public:
|
|||||||
|
|
||||||
//< The number of peers who have validated this ledger
|
//< The number of peers who have validated this ledger
|
||||||
std::size_t
|
std::size_t
|
||||||
proposersValidated(Ledger::ID const & prevLedger) const
|
proposersValidated(Ledger::ID const& prevLedger) const
|
||||||
{
|
{
|
||||||
auto it = nodesFromLedger.find(prevLedger);
|
auto it = nodesFromLedger.find(prevLedger);
|
||||||
if(it != nodesFromLedger.end())
|
if (it != nodesFromLedger.end())
|
||||||
return it->second.size();
|
return it->second.size();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -75,35 +76,33 @@ public:
|
|||||||
as an ancestor.
|
as an ancestor.
|
||||||
*/
|
*/
|
||||||
std::size_t
|
std::size_t
|
||||||
proposersFinished(Ledger::ID const & prevLedger) const
|
proposersFinished(Ledger::ID const& prevLedger) const
|
||||||
{
|
{
|
||||||
auto it = nodesFromPrevLedger.find(prevLedger);
|
auto it = nodesFromPrevLedger.find(prevLedger);
|
||||||
if(it != nodesFromPrevLedger.end())
|
if (it != nodesFromPrevLedger.end())
|
||||||
return it->second.size();
|
return it->second.size();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the ledger starting from prevLedger with the most validations.
|
/** Returns the ledger starting from prevLedger with the most validations.
|
||||||
*/
|
*/
|
||||||
Ledger::ID
|
Ledger::ID
|
||||||
getBestLCL(Ledger::ID const & currLedger,
|
getBestLCL(Ledger::ID const& currLedger, Ledger::ID const& prevLedger) const
|
||||||
Ledger::ID const & prevLedger) const
|
|
||||||
{
|
{
|
||||||
auto it = childLedgers.find(prevLedger);
|
auto it = childLedgers.find(prevLedger);
|
||||||
if (it != childLedgers.end() &&
|
if (it != childLedgers.end() && !it->second.empty())
|
||||||
! it->second.empty())
|
|
||||||
{
|
{
|
||||||
std::size_t bestCount = 0;
|
std::size_t bestCount = 0;
|
||||||
Ledger::ID bestLedger;
|
Ledger::ID bestLedger;
|
||||||
|
|
||||||
for (auto const & b : it->second)
|
for (auto const& b : it->second)
|
||||||
{
|
{
|
||||||
auto currCount = b.second;
|
auto currCount = b.second;
|
||||||
if(currLedger == b.first)
|
if (currLedger == b.first)
|
||||||
currCount++;
|
currCount++;
|
||||||
if(currCount > bestCount)
|
if (currCount > bestCount)
|
||||||
bestLedger = b.first;
|
bestLedger = b.first;
|
||||||
if(currCount == bestCount && currLedger == b.first)
|
if (currCount == bestCount && currLedger == b.first)
|
||||||
bestLedger = b.first;
|
bestLedger = b.first;
|
||||||
}
|
}
|
||||||
return bestLedger;
|
return bestLedger;
|
||||||
@@ -122,11 +121,8 @@ struct Traits
|
|||||||
using Ledger_t = Ledger;
|
using Ledger_t = Ledger;
|
||||||
using NodeID_t = PeerID;
|
using NodeID_t = PeerID;
|
||||||
using TxSet_t = TxSet;
|
using TxSet_t = TxSet;
|
||||||
using MissingTxException_t = MissingTx;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Represents a single node participating in the consensus process.
|
/** Represents a single node participating in the consensus process.
|
||||||
It implements the Callbacks required by Consensus.
|
It implements the Callbacks required by Consensus.
|
||||||
*/
|
*/
|
||||||
@@ -144,7 +140,7 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
Ledger lastClosedLedger;
|
Ledger lastClosedLedger;
|
||||||
|
|
||||||
//! Handle to network for sending messages
|
//! Handle to network for sending messages
|
||||||
BasicNetwork<Peer*> & net;
|
BasicNetwork<Peer*>& net;
|
||||||
|
|
||||||
//! UNL of trusted peers
|
//! UNL of trusted peers
|
||||||
UNL unl;
|
UNL unl;
|
||||||
@@ -173,12 +169,12 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
//! Delay in acquiring missing ledger from the network
|
//! Delay in acquiring missing ledger from the network
|
||||||
std::chrono::milliseconds missingLedgerDelay{0};
|
std::chrono::milliseconds missingLedgerDelay{0};
|
||||||
|
|
||||||
bool validating = true;
|
bool validating_ = true;
|
||||||
bool proposing = true;
|
bool proposing_ = true;
|
||||||
|
|
||||||
//! All peers start from the default constructed ledger
|
//! All peers start from the default constructed ledger
|
||||||
Peer(PeerID i, BasicNetwork<Peer*> & n, UNL const & u)
|
Peer(PeerID i, BasicNetwork<Peer*>& n, UNL const& u)
|
||||||
: Consensus<Peer, Traits>( n.clock(), beast::Journal{})
|
: Consensus<Peer, Traits>(n.clock(), beast::Journal{})
|
||||||
, id{i}
|
, id{i}
|
||||||
, net{n}
|
, net{n}
|
||||||
, unl(u)
|
, unl(u)
|
||||||
@@ -186,19 +182,8 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
ledgers[lastClosedLedger.id()] = lastClosedLedger;
|
ledgers[lastClosedLedger.id()] = lastClosedLedger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ledger const*
|
||||||
// @return whether we are proposing,validating
|
acquireLedger(Ledger::ID const& ledgerHash)
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
auto it = ledgers.find(ledgerHash);
|
auto it = ledgers.find(ledgerHash);
|
||||||
if (it != ledgers.end())
|
if (it != ledgers.end())
|
||||||
@@ -208,16 +193,16 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
|
|
||||||
for (auto const& link : net.links(this))
|
for (auto const& link : net.links(this))
|
||||||
{
|
{
|
||||||
auto const & p = *link.to;
|
auto const& p = *link.to;
|
||||||
auto it = p.ledgers.find(ledgerHash);
|
auto it = p.ledgers.find(ledgerHash);
|
||||||
if (it != p.ledgers.end())
|
if (it != p.ledgers.end())
|
||||||
{
|
{
|
||||||
schedule(missingLedgerDelay,
|
schedule(
|
||||||
[this, ledgerHash, ledger = it->second]()
|
missingLedgerDelay,
|
||||||
{
|
[ this, ledgerHash, ledger = it->second ]() {
|
||||||
ledgers.emplace(ledgerHash, ledger);
|
ledgers.emplace(ledgerHash, ledger);
|
||||||
});
|
});
|
||||||
if(missingLedgerDelay == 0ms)
|
if (missingLedgerDelay == 0ms)
|
||||||
return &ledgers[ledgerHash];
|
return &ledgers[ledgerHash];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -225,23 +210,22 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const &
|
auto const&
|
||||||
proposals(Ledger::ID const & ledgerHash)
|
proposals(Ledger::ID const& ledgerHash)
|
||||||
{
|
{
|
||||||
return peerPositions_[ledgerHash];
|
return peerPositions_[ledgerHash];
|
||||||
}
|
}
|
||||||
|
|
||||||
TxSet const *
|
TxSet const*
|
||||||
acquireTxSet(TxSet::ID const & setId)
|
acquireTxSet(TxSet::ID const& setId)
|
||||||
{
|
{
|
||||||
auto it = txSets.find(setId);
|
auto it = txSets.find(setId);
|
||||||
if(it != txSets.end())
|
if (it != txSets.end())
|
||||||
return &(it->second);
|
return &(it->second);
|
||||||
// TODO Get from network/oracle instead!
|
// TODO Get from network/oracle instead!
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
hasOpenTransactions() const
|
hasOpenTransactions() const
|
||||||
{
|
{
|
||||||
@@ -249,114 +233,68 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::size_t
|
std::size_t
|
||||||
proposersValidated(Ledger::ID const & prevLedger)
|
proposersValidated(Ledger::ID const& prevLedger)
|
||||||
{
|
{
|
||||||
return peerValidations.proposersValidated(prevLedger);
|
return peerValidations.proposersValidated(prevLedger);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t
|
std::size_t
|
||||||
proposersFinished(Ledger::ID const & prevLedger)
|
proposersFinished(Ledger::ID const& prevLedger)
|
||||||
{
|
{
|
||||||
return peerValidations.proposersFinished(prevLedger);
|
return peerValidations.proposersFinished(prevLedger);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
Result
|
||||||
onStartRound(Ledger const &) {}
|
onClose(Ledger const& prevLedger, NetClock::time_point closeTime, Mode mode)
|
||||||
|
|
||||||
void
|
|
||||||
onClose(Ledger const &, bool ) {}
|
|
||||||
|
|
||||||
// don't really offload
|
|
||||||
void
|
|
||||||
dispatchAccept(TxSet const & f)
|
|
||||||
{
|
{
|
||||||
Base::accept(f);
|
TxSet res{openTxs};
|
||||||
|
|
||||||
|
return Result{TxSet{openTxs},
|
||||||
|
Proposal{prevLedger.id(),
|
||||||
|
Proposal::seqJoin,
|
||||||
|
res.id(),
|
||||||
|
closeTime,
|
||||||
|
now(),
|
||||||
|
id}};
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
share(TxSet const &s)
|
onForceAccept(
|
||||||
|
Result const& result,
|
||||||
|
Ledger const& prevLedger,
|
||||||
|
NetClock::duration const& closeResolution,
|
||||||
|
CloseTimes const& rawCloseTimes,
|
||||||
|
Mode const& mode)
|
||||||
{
|
{
|
||||||
relay(s);
|
onAccept(result, prevLedger, closeResolution, rawCloseTimes, mode);
|
||||||
}
|
|
||||||
|
|
||||||
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
|
void
|
||||||
propose(Proposal const & pos)
|
onAccept(
|
||||||
|
Result const& result,
|
||||||
|
Ledger const& prevLedger,
|
||||||
|
NetClock::duration const& closeResolution,
|
||||||
|
CloseTimes const& rawCloseTimes,
|
||||||
|
Mode const& mode)
|
||||||
{
|
{
|
||||||
if(proposing)
|
auto newLedger = prevLedger.close(
|
||||||
relay(pos);
|
result.set.txs_,
|
||||||
}
|
closeResolution,
|
||||||
|
rawCloseTimes.self,
|
||||||
void
|
result.position.closeTime() != NetClock::time_point{});
|
||||||
relay(DisputedTx<Tx, PeerID> const & dispute)
|
|
||||||
{
|
|
||||||
relay(dispute.tx());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair <TxSet, Proposal>
|
|
||||||
makeInitialPosition(
|
|
||||||
Ledger const & prevLedger,
|
|
||||||
bool isProposing,
|
|
||||||
bool isCorrectLCL,
|
|
||||||
NetClock::time_point closeTime,
|
|
||||||
NetClock::time_point now)
|
|
||||||
{
|
|
||||||
TxSet res{ openTxs };
|
|
||||||
|
|
||||||
return { res,
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
auto newLedger = previousLedger_.close(set.txs_, closeResolution_,
|
|
||||||
closeTime, consensusCloseTime != NetClock::time_point{});
|
|
||||||
ledgers[newLedger.id()] = newLedger;
|
ledgers[newLedger.id()] = newLedger;
|
||||||
|
|
||||||
lastClosedLedger = newLedger;
|
lastClosedLedger = newLedger;
|
||||||
|
|
||||||
auto it = std::remove_if(openTxs.begin(), openTxs.end(),
|
auto it =
|
||||||
[&](Tx const & tx)
|
std::remove_if(openTxs.begin(), openTxs.end(), [&](Tx const& tx) {
|
||||||
{
|
return result.set.exists(tx.id());
|
||||||
return set.exists(tx.id());
|
|
||||||
});
|
});
|
||||||
openTxs.erase(it, openTxs.end());
|
openTxs.erase(it, openTxs.end());
|
||||||
|
|
||||||
if(validating)
|
if (validating_)
|
||||||
relay(Validation{id, newLedger.id(), newLedger.parentID()});
|
relay(Validation{id, newLedger.id(), newLedger.parentID()});
|
||||||
return validating_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
endConsensus(bool correct)
|
|
||||||
{
|
|
||||||
// kick off the next round...
|
// kick off the next round...
|
||||||
// in the actual implementation, this passes back through
|
// in the actual implementation, this passes back through
|
||||||
// network ops
|
// network ops
|
||||||
@@ -364,41 +302,58 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
// startRound sets the LCL state, so we need to call it once after
|
// startRound sets the LCL state, so we need to call it once after
|
||||||
// the last requested round completes
|
// the last requested round completes
|
||||||
// TODO: reconsider this and instead just save LCL generated here?
|
// TODO: reconsider this and instead just save LCL generated here?
|
||||||
if(completedLedgers <= targetLedgers)
|
if (completedLedgers <= targetLedgers)
|
||||||
{
|
{
|
||||||
startRound(now(), lastClosedLedger.id(),
|
startRound(
|
||||||
lastClosedLedger);
|
now(), lastClosedLedger.id(), lastClosedLedger, proposing_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ledger::ID
|
||||||
|
getPrevLedger(Ledger::ID const& ledgerID, Ledger const& ledger, Mode mode)
|
||||||
|
{
|
||||||
|
// TODO: Use generic validation code
|
||||||
|
if (mode != Mode::wrongLedger && ledgerID.seq > 0 &&
|
||||||
|
ledger.id().seq > 0)
|
||||||
|
return peerValidations.getBestLCL(ledgerID, ledger.parentID());
|
||||||
|
return ledgerID;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
propose(Proposal const& pos)
|
||||||
|
{
|
||||||
|
if (proposing_)
|
||||||
|
relay(pos);
|
||||||
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// non-callback helpers
|
// non-callback helpers
|
||||||
void
|
void
|
||||||
receive(Proposal const & p)
|
receive(Proposal const& p)
|
||||||
{
|
{
|
||||||
if(unl.find(p.nodeID()) == unl.end())
|
if (unl.find(p.nodeID()) == unl.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// TODO: Be sure this is a new proposal!!!!!
|
// TODO: Be sure this is a new proposal!!!!!
|
||||||
auto & dest = peerPositions_[p.prevLedger()];
|
auto& dest = peerPositions_[p.prevLedger()];
|
||||||
if(std::find(dest.begin(), dest.end(), p) != dest.end())
|
if (std::find(dest.begin(), dest.end(), p) != dest.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dest.push_back(p);
|
dest.push_back(p);
|
||||||
peerProposal(now(), p);
|
peerProposal(now(), p);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
receive(TxSet const & txs)
|
receive(TxSet const& txs)
|
||||||
{
|
{
|
||||||
// save and map complete?
|
// save and map complete?
|
||||||
auto it = txSets.insert(std::make_pair(txs.id(), txs));
|
auto it = txSets.insert(std::make_pair(txs.id(), txs));
|
||||||
if(it.second)
|
if (it.second)
|
||||||
gotTxSet(now(), txs);
|
gotTxSet(now(), txs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
receive(Tx const & tx)
|
receive(Tx const& tx)
|
||||||
{
|
{
|
||||||
if (openTxs.find(tx.id()) == openTxs.end())
|
if (openTxs.find(tx.id()) == openTxs.end())
|
||||||
{
|
{
|
||||||
@@ -409,33 +364,26 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
receive(Validation const & v)
|
receive(Validation const& v)
|
||||||
{
|
{
|
||||||
if(unl.find(v.id) != unl.end())
|
if (unl.find(v.id) != unl.end())
|
||||||
{
|
{
|
||||||
schedule(validationDelay,
|
schedule(validationDelay, [&, v]() { peerValidations.update(v); });
|
||||||
[&, v]()
|
|
||||||
{
|
|
||||||
peerValidations.update(v);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void
|
void
|
||||||
relay(T const & t)
|
relay(T const& t)
|
||||||
{
|
{
|
||||||
for(auto const& link : net.links(this))
|
for (auto const& link : net.links(this))
|
||||||
net.send(this, link.to,
|
net.send(
|
||||||
[msg = t, to = link.to]
|
this, link.to, [ msg = t, to = link.to ] { to->receive(msg); });
|
||||||
{
|
|
||||||
to->receive(msg);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receive and relay locally submitted transaction
|
// Receive and relay locally submitted transaction
|
||||||
void
|
void
|
||||||
submit(Tx const & tx)
|
submit(Tx const& tx)
|
||||||
{
|
{
|
||||||
receive(tx);
|
receive(tx);
|
||||||
relay(tx);
|
relay(tx);
|
||||||
@@ -446,7 +394,7 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
{
|
{
|
||||||
Base::timerEntry(now());
|
Base::timerEntry(now());
|
||||||
// only reschedule if not completed
|
// only reschedule if not completed
|
||||||
if(completedLedgers < targetLedgers)
|
if (completedLedgers < targetLedgers)
|
||||||
net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); });
|
net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); });
|
||||||
}
|
}
|
||||||
void
|
void
|
||||||
@@ -456,10 +404,9 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
// The ID is the one we have seen the most validations for
|
// The ID is the one we have seen the most validations for
|
||||||
// In practice, we might not actually have that ledger itself yet,
|
// In practice, we might not actually have that ledger itself yet,
|
||||||
// so there is no gaurantee that bestLCL == lastClosedLedger.id()
|
// so there is no gaurantee that bestLCL == lastClosedLedger.id()
|
||||||
auto bestLCL = peerValidations.getBestLCL(lastClosedLedger.id(),
|
auto bestLCL = peerValidations.getBestLCL(
|
||||||
lastClosedLedger.parentID());
|
lastClosedLedger.id(), lastClosedLedger.parentID());
|
||||||
startRound(now(), bestLCL,
|
startRound(now(), bestLCL, lastClosedLedger, proposing_);
|
||||||
lastClosedLedger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NetClock::time_point
|
NetClock::time_point
|
||||||
@@ -470,23 +417,24 @@ struct Peer : public Consensus<Peer, Traits>
|
|||||||
// any subtractions of two NetClock::time_point in the consensu
|
// any subtractions of two NetClock::time_point in the consensu
|
||||||
// code are positive. (e.g. PROPOSE_FRESHNESS)
|
// code are positive. (e.g. PROPOSE_FRESHNESS)
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
return NetClock::time_point(duration_cast<NetClock::duration>
|
return NetClock::time_point(duration_cast<NetClock::duration>(
|
||||||
(net.now().time_since_epoch()+ 86400s + clockSkew));
|
net.now().time_since_epoch() + 86400s + clockSkew));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule the provided callback in `when` duration, but if
|
// Schedule the provided callback in `when` duration, but if
|
||||||
// `when` is 0, call immediately
|
// `when` is 0, call immediately
|
||||||
template <class T>
|
template <class T>
|
||||||
void schedule(std::chrono::nanoseconds when, T && what)
|
void
|
||||||
|
schedule(std::chrono::nanoseconds when, T&& what)
|
||||||
{
|
{
|
||||||
if(when == 0ns)
|
if (when == 0ns)
|
||||||
what();
|
what();
|
||||||
else
|
else
|
||||||
net.timer(when, std::forward<T>(what));
|
net.timer(when, std::forward<T>(what));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // csf
|
} // csf
|
||||||
} // test
|
} // test
|
||||||
} // ripple
|
} // ripple
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
#ifndef RIPPLE_TEST_CSF_SIM_H_INCLUDED
|
#ifndef RIPPLE_TEST_CSF_SIM_H_INCLUDED
|
||||||
#define RIPPLE_TEST_CSF_SIM_H_INCLUDED
|
#define RIPPLE_TEST_CSF_SIM_H_INCLUDED
|
||||||
|
|
||||||
#include <test/csf/UNL.h>
|
|
||||||
#include <test/csf/BasicNetwork.h>
|
#include <test/csf/BasicNetwork.h>
|
||||||
|
#include <test/csf/UNL.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
@@ -50,19 +50,19 @@ public:
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
template <class Topology>
|
template <class Topology>
|
||||||
Sim(TrustGraph const & g, Topology const & top)
|
Sim(TrustGraph const& g, Topology const& top)
|
||||||
{
|
{
|
||||||
peers.reserve(g.numPeers());
|
peers.reserve(g.numPeers());
|
||||||
for(int i = 0; i < g.numPeers(); ++i)
|
for (int i = 0; i < g.numPeers(); ++i)
|
||||||
peers.emplace_back(i, net, g.unl(i));
|
peers.emplace_back(i, net, g.unl(i));
|
||||||
|
|
||||||
for(int i = 0; i < peers.size(); ++i)
|
for (int i = 0; i < peers.size(); ++i)
|
||||||
{
|
{
|
||||||
for(int j = 0; j < peers.size(); ++j)
|
for (int j = 0; j < peers.size(); ++j)
|
||||||
{
|
{
|
||||||
if( i != j)
|
if (i != j)
|
||||||
{
|
{
|
||||||
auto d = top(i,j);
|
auto d = top(i, j);
|
||||||
if (d)
|
if (d)
|
||||||
{
|
{
|
||||||
net.connect(&peers[i], &peers[j], *d);
|
net.connect(&peers[i], &peers[j], *d);
|
||||||
@@ -81,10 +81,10 @@ public:
|
|||||||
void
|
void
|
||||||
run(int ledgers)
|
run(int ledgers)
|
||||||
{
|
{
|
||||||
for (auto & p : peers)
|
for (auto& p : peers)
|
||||||
{
|
{
|
||||||
if(p.completedLedgers == 0)
|
if (p.completedLedgers == 0)
|
||||||
p.relay(Validation{p.id, p.LCL(), p.LCL()});
|
p.relay(Validation{p.id, p.prevLedgerID(), p.prevLedgerID()});
|
||||||
p.targetLedgers = p.completedLedgers + ledgers;
|
p.targetLedgers = p.completedLedgers + ledgers;
|
||||||
p.start();
|
p.start();
|
||||||
}
|
}
|
||||||
@@ -93,12 +93,10 @@ public:
|
|||||||
|
|
||||||
std::vector<Peer> peers;
|
std::vector<Peer> peers;
|
||||||
BasicNetwork<Peer*> net;
|
BasicNetwork<Peer*> net;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // csf
|
||||||
} // csf
|
} // test
|
||||||
} // test
|
} // ripple
|
||||||
} // ripple
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -21,9 +21,9 @@
|
|||||||
|
|
||||||
#include <ripple/beast/hash/hash_append.h>
|
#include <ripple/beast/hash/hash_append.h>
|
||||||
#include <boost/container/flat_set.hpp>
|
#include <boost/container/flat_set.hpp>
|
||||||
|
#include <map>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
@@ -35,7 +35,9 @@ class Tx
|
|||||||
public:
|
public:
|
||||||
using ID = std::uint32_t;
|
using ID = std::uint32_t;
|
||||||
|
|
||||||
Tx(ID i) : id_{ i } {}
|
Tx(ID i) : id_{i}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
ID
|
ID
|
||||||
id() const
|
id() const
|
||||||
@@ -44,21 +46,19 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
operator<(Tx const & o) const
|
operator<(Tx const& o) const
|
||||||
{
|
{
|
||||||
return id_ < o.id_;
|
return id_ < o.id_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
operator==(Tx const & o) const
|
operator==(Tx const& o) const
|
||||||
{
|
{
|
||||||
return id_ == o.id_;
|
return id_ == o.id_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ID id_;
|
ID id_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//!-------------------------------------------------------------------------
|
//!-------------------------------------------------------------------------
|
||||||
@@ -74,37 +74,39 @@ public:
|
|||||||
using MutableTxSet = TxSet;
|
using MutableTxSet = TxSet;
|
||||||
|
|
||||||
TxSet() = default;
|
TxSet() = default;
|
||||||
TxSet(TxSetType const & s) : txs_{ s } {}
|
TxSet(TxSetType const& s) : txs_{s}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
insert(Tx const & t)
|
insert(Tx const& t)
|
||||||
{
|
{
|
||||||
return txs_.insert(t).second;
|
return txs_.insert(t).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
erase(Tx::ID const & txId)
|
erase(Tx::ID const& txId)
|
||||||
{
|
{
|
||||||
return txs_.erase(Tx{ txId }) > 0;
|
return txs_.erase(Tx{txId}) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
exists(Tx::ID const txId) const
|
exists(Tx::ID const txId) const
|
||||||
{
|
{
|
||||||
auto it = txs_.find(Tx{ txId });
|
auto it = txs_.find(Tx{txId});
|
||||||
return it != txs_.end();
|
return it != txs_.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
Tx const *
|
Tx const*
|
||||||
find(Tx::ID const& txId) const
|
find(Tx::ID const& txId) const
|
||||||
{
|
{
|
||||||
auto it = txs_.find(Tx{ txId });
|
auto it = txs_.find(Tx{txId});
|
||||||
if (it != txs_.end())
|
if (it != txs_.end())
|
||||||
return &(*it);
|
return &(*it);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const &
|
auto const&
|
||||||
id() const
|
id() const
|
||||||
{
|
{
|
||||||
return txs_;
|
return txs_;
|
||||||
@@ -119,19 +121,14 @@ public:
|
|||||||
{
|
{
|
||||||
std::map<Tx::ID, bool> res;
|
std::map<Tx::ID, bool> res;
|
||||||
|
|
||||||
auto populate_diffs = [&res](auto const & a, auto const & b, bool s)
|
auto populate_diffs = [&res](auto const& a, auto const& b, bool s) {
|
||||||
{
|
auto populator = [&](auto const& tx) { res[tx.id()] = s; };
|
||||||
auto populator = [&](auto const & tx)
|
|
||||||
{
|
|
||||||
res[tx.id()] = s;
|
|
||||||
};
|
|
||||||
std::set_difference(
|
std::set_difference(
|
||||||
a.begin(), a.end(),
|
a.begin(),
|
||||||
b.begin(), b.end(),
|
a.end(),
|
||||||
boost::make_function_output_iterator(
|
b.begin(),
|
||||||
std::ref(populator)
|
b.end(),
|
||||||
)
|
boost::make_function_output_iterator(std::ref(populator)));
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
populate_diffs(txs_, other.txs_, true);
|
populate_diffs(txs_, other.txs_, true);
|
||||||
@@ -143,55 +140,35 @@ public:
|
|||||||
TxSetType txs_;
|
TxSetType txs_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** The RCL consensus process catches missing node SHAMap error
|
|
||||||
in several points. This exception is meant to represent a similar
|
|
||||||
case for the unit test.
|
|
||||||
*/
|
|
||||||
class MissingTx : public std::runtime_error
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MissingTx()
|
|
||||||
: std::runtime_error("MissingTx")
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Helper functions for debug printing
|
// Helper functions for debug printing
|
||||||
|
|
||||||
inline
|
inline std::ostream&
|
||||||
std::ostream&
|
operator<<(std::ostream& o, const Tx& t)
|
||||||
operator<<(std::ostream & o, const Tx & t)
|
|
||||||
{
|
{
|
||||||
return o << t.id();
|
return o << t.id();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
inline
|
inline std::ostream&
|
||||||
std::ostream&
|
operator<<(std::ostream& o, boost::container::flat_set<T> const& ts)
|
||||||
operator<<(std::ostream & o, boost::container::flat_set<T> const & ts)
|
|
||||||
{
|
{
|
||||||
o << "{ ";
|
o << "{ ";
|
||||||
bool do_comma = false;
|
bool do_comma = false;
|
||||||
for (auto const & t : ts)
|
for (auto const& t : ts)
|
||||||
{
|
{
|
||||||
if (do_comma)
|
if (do_comma)
|
||||||
o << ", ";
|
o << ", ";
|
||||||
else
|
else
|
||||||
do_comma = true;
|
do_comma = true;
|
||||||
o << t;
|
o << t;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
o << " }";
|
o << " }";
|
||||||
return o;
|
return o;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline
|
inline std::string
|
||||||
std::string
|
to_string(TxSetType const& txs)
|
||||||
to_string(TxSetType const & txs)
|
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << txs;
|
ss << txs;
|
||||||
@@ -199,23 +176,15 @@ to_string(TxSetType const & txs)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class Hasher>
|
template <class Hasher>
|
||||||
inline
|
inline void
|
||||||
void
|
hash_append(Hasher& h, Tx const& tx)
|
||||||
hash_append(Hasher& h, Tx const & tx)
|
|
||||||
{
|
{
|
||||||
using beast::hash_append;
|
using beast::hash_append;
|
||||||
hash_append(h, tx.id());
|
hash_append(h, tx.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream&
|
} // csf
|
||||||
operator<<(std::ostream & o, MissingTx const &m)
|
} // test
|
||||||
{
|
} // ripple
|
||||||
return o << m.what();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // csf
|
|
||||||
} // test
|
|
||||||
} // ripple
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -22,10 +22,10 @@
|
|||||||
|
|
||||||
#include <boost/container/flat_set.hpp>
|
#include <boost/container/flat_set.hpp>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <vector>
|
|
||||||
#include <random>
|
|
||||||
#include <numeric>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <numeric>
|
||||||
|
#include <random>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
@@ -42,7 +42,7 @@ namespace csf {
|
|||||||
*/
|
*/
|
||||||
template <class T, class G>
|
template <class T, class G>
|
||||||
std::vector<T>
|
std::vector<T>
|
||||||
random_weighted_shuffle(std::vector<T> v, std::vector<double> w, G & g)
|
random_weighted_shuffle(std::vector<T> v, std::vector<double> w, G& g)
|
||||||
{
|
{
|
||||||
using std::swap;
|
using std::swap;
|
||||||
|
|
||||||
@@ -57,7 +57,6 @@ random_weighted_shuffle(std::vector<T> v, std::vector<double> w, G & g)
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Power-law distribution with PDF
|
/** Power-law distribution with PDF
|
||||||
|
|
||||||
P(x) = (x/xmin)^-a
|
P(x) = (x/xmin)^-a
|
||||||
@@ -69,25 +68,22 @@ class PowerLawDistribution
|
|||||||
double xmin_;
|
double xmin_;
|
||||||
double a_;
|
double a_;
|
||||||
double inv_;
|
double inv_;
|
||||||
std::uniform_real_distribution<double> uf_{0,1};
|
std::uniform_real_distribution<double> uf_{0, 1};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PowerLawDistribution(double xmin, double a)
|
PowerLawDistribution(double xmin, double a) : xmin_{xmin}, a_{a}
|
||||||
: xmin_{xmin}, a_{a}
|
{
|
||||||
{
|
inv_ = 1.0 / (1.0 - a_);
|
||||||
inv_ = 1.0/(1.0 - a_);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
template <class Generator>
|
template <class Generator>
|
||||||
inline
|
inline double
|
||||||
double
|
operator()(Generator& g)
|
||||||
operator()(Generator & g)
|
|
||||||
{
|
{
|
||||||
// use inverse transform of CDF to sample
|
// use inverse transform of CDF to sample
|
||||||
// CDF is P(X <= x): 1 - (x/xmin)^(1-a)
|
// CDF is P(X <= x): 1 - (x/xmin)^(1-a)
|
||||||
return xmin_ * std::pow(1 - uf_(g), inv_);
|
return xmin_ * std::pow(1 - uf_(g), inv_);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//< Unique identifier for each node in the network
|
//< Unique identifier for each node in the network
|
||||||
@@ -110,25 +106,23 @@ class TrustGraph
|
|||||||
std::vector<UNL> UNLs_;
|
std::vector<UNL> UNLs_;
|
||||||
|
|
||||||
std::vector<int> assignment_;
|
std::vector<int> assignment_;
|
||||||
public:
|
|
||||||
|
|
||||||
|
public:
|
||||||
//< Constructor
|
//< Constructor
|
||||||
TrustGraph(std::vector<UNL> UNLs, std::vector<int> assignment)
|
TrustGraph(std::vector<UNL> UNLs, std::vector<int> assignment)
|
||||||
: UNLs_{UNLs}
|
: UNLs_{UNLs}, assignment_{assignment}
|
||||||
, assignment_{assignment}
|
{
|
||||||
{}
|
}
|
||||||
|
|
||||||
//< Whether node `i` trusts node `j`
|
//< Whether node `i` trusts node `j`
|
||||||
inline
|
inline bool
|
||||||
bool
|
|
||||||
trusts(PeerID i, PeerID j) const
|
trusts(PeerID i, PeerID j) const
|
||||||
{
|
{
|
||||||
return unl(i).find(j) != unl(i).end();
|
return unl(i).find(j) != unl(i).end();
|
||||||
}
|
}
|
||||||
|
|
||||||
//< Get the UNL for node `i`
|
//< Get the UNL for node `i`
|
||||||
inline
|
inline UNL const&
|
||||||
UNL const &
|
|
||||||
unl(PeerID i) const
|
unl(PeerID i) const
|
||||||
{
|
{
|
||||||
return UNLs_[assignment_[i]];
|
return UNLs_[assignment_[i]];
|
||||||
@@ -138,7 +132,6 @@ public:
|
|||||||
bool
|
bool
|
||||||
canFork(double quorum) const;
|
canFork(double quorum) const;
|
||||||
|
|
||||||
|
|
||||||
auto
|
auto
|
||||||
numPeers() const
|
numPeers() const
|
||||||
{
|
{
|
||||||
@@ -147,7 +140,7 @@ public:
|
|||||||
|
|
||||||
//< Save grapviz dot file reprentation of the trust graph
|
//< Save grapviz dot file reprentation of the trust graph
|
||||||
void
|
void
|
||||||
save_dot(std::string const & fileName);
|
save_dot(std::string const& fileName);
|
||||||
|
|
||||||
/** Generate a random trust graph based on random ranking of peers
|
/** Generate a random trust graph based on random ranking of peers
|
||||||
|
|
||||||
@@ -176,28 +169,23 @@ public:
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
template <class RankPDF, class SizePDF, class Generator>
|
template <class RankPDF, class SizePDF, class Generator>
|
||||||
static
|
static TrustGraph
|
||||||
TrustGraph
|
makeRandomRanked(
|
||||||
makeRandomRanked(int size,
|
int size,
|
||||||
int numUNLs,
|
int numUNLs,
|
||||||
RankPDF rankPDF,
|
RankPDF rankPDF,
|
||||||
SizePDF unlSizePDF,
|
SizePDF unlSizePDF,
|
||||||
Generator & g)
|
Generator& g)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
// 1. Generate ranks
|
// 1. Generate ranks
|
||||||
std::vector<double> weights(size);
|
std::vector<double> weights(size);
|
||||||
std::generate(weights.begin(), weights.end(), [&]()
|
std::generate(
|
||||||
{
|
weights.begin(), weights.end(), [&]() { return rankPDF(g); });
|
||||||
return rankPDF(g);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Generate UNLs based on sampling without replacement according
|
// 2. Generate UNLs based on sampling without replacement according
|
||||||
// to weights
|
// to weights
|
||||||
std::vector<UNL> unls(numUNLs);
|
std::vector<UNL> unls(numUNLs);
|
||||||
std::generate(unls.begin(), unls.end(), [&]()
|
std::generate(unls.begin(), unls.end(), [&]() {
|
||||||
{
|
|
||||||
std::vector<PeerID> ids(size);
|
std::vector<PeerID> ids(size);
|
||||||
std::iota(ids.begin(), ids.end(), 0);
|
std::iota(ids.begin(), ids.end(), 0);
|
||||||
auto res = random_weighted_shuffle(ids, weights, g);
|
auto res = random_weighted_shuffle(ids, weights, g);
|
||||||
@@ -206,12 +194,9 @@ public:
|
|||||||
|
|
||||||
// 3. Assign membership
|
// 3. Assign membership
|
||||||
std::vector<int> assignment(size);
|
std::vector<int> assignment(size);
|
||||||
std::uniform_int_distribution<int> u(0, numUNLs-1);
|
std::uniform_int_distribution<int> u(0, numUNLs - 1);
|
||||||
std::generate(assignment.begin(), assignment.end(),
|
std::generate(
|
||||||
[&]()
|
assignment.begin(), assignment.end(), [&]() { return u(g); });
|
||||||
{
|
|
||||||
return u(g);
|
|
||||||
});
|
|
||||||
|
|
||||||
return TrustGraph(unls, assignment);
|
return TrustGraph(unls, assignment);
|
||||||
}
|
}
|
||||||
@@ -226,8 +211,7 @@ public:
|
|||||||
@param size The number of nodes in the trust graph
|
@param size The number of nodes in the trust graph
|
||||||
@param overlap The number of nodes trusting both cliques
|
@param overlap The number of nodes trusting both cliques
|
||||||
*/
|
*/
|
||||||
static
|
static TrustGraph
|
||||||
TrustGraph
|
|
||||||
makeClique(int size, int overlap);
|
makeClique(int size, int overlap);
|
||||||
|
|
||||||
/** Generate a complete (fully-connect) trust graph
|
/** Generate a complete (fully-connect) trust graph
|
||||||
@@ -237,21 +221,17 @@ public:
|
|||||||
|
|
||||||
@param size The number of nodes in the trust graph
|
@param size The number of nodes in the trust graph
|
||||||
*/
|
*/
|
||||||
static
|
static TrustGraph
|
||||||
TrustGraph
|
|
||||||
makeComplete(int size);
|
makeComplete(int size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//< Make the TrustGraph into a topology with delays given by DelayModel
|
//< Make the TrustGraph into a topology with delays given by DelayModel
|
||||||
template <class DelayModel>
|
template <class DelayModel>
|
||||||
auto
|
auto
|
||||||
topology(TrustGraph const & tg, DelayModel const & d)
|
topology(TrustGraph const& tg, DelayModel const& d)
|
||||||
{
|
{
|
||||||
return [&](PeerID i, PeerID j)
|
return [&](PeerID i, PeerID j) {
|
||||||
{
|
return tg.trusts(i, j) ? boost::make_optional(d(i, j)) : boost::none;
|
||||||
return tg.trusts(i,j) ? boost::make_optional(d(i,j)) : boost::none;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,18 +240,19 @@ class fixed
|
|||||||
std::chrono::nanoseconds d_;
|
std::chrono::nanoseconds d_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
fixed(std::chrono::nanoseconds const & d) : d_{d} {}
|
fixed(std::chrono::nanoseconds const& d) : d_{d}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
inline
|
inline std::chrono::nanoseconds
|
||||||
std::chrono::nanoseconds
|
operator()(PeerID const& i, PeerID const& j) const
|
||||||
operator()(PeerID const & i, PeerID const & j) const
|
|
||||||
{
|
{
|
||||||
return d_;
|
return d_;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // csf
|
} // csf
|
||||||
} // test
|
} // test
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
*/
|
*/
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
#include <test/csf/UNL.h>
|
|
||||||
#include <boost/iterator/counting_iterator.hpp>
|
#include <boost/iterator/counting_iterator.hpp>
|
||||||
#include <fstream>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <test/csf/UNL.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
@@ -38,8 +38,8 @@ TrustGraph::canFork(double quorum) const
|
|||||||
|
|
||||||
for (int i = 0; i < assignment_.size(); ++i)
|
for (int i = 0; i < assignment_.size(); ++i)
|
||||||
{
|
{
|
||||||
auto const & myUNL = UNLs_[assignment_[i]];
|
auto const& myUNL = UNLs_[assignment_[i]];
|
||||||
if(myUNL.find(i) == myUNL.end())
|
if (myUNL.find(i) == myUNL.end())
|
||||||
{
|
{
|
||||||
auto myUNLcopy = myUNL;
|
auto myUNLcopy = myUNL;
|
||||||
myUNLcopy.insert(i);
|
myUNLcopy.insert(i);
|
||||||
@@ -50,21 +50,20 @@ TrustGraph::canFork(double quorum) const
|
|||||||
// Loop over all pairs of uniqueUNLs
|
// Loop over all pairs of uniqueUNLs
|
||||||
for (int i = 0; i < uniqueUNLs.size(); ++i)
|
for (int i = 0; i < uniqueUNLs.size(); ++i)
|
||||||
{
|
{
|
||||||
for (int j = (i+1); j < uniqueUNLs.size(); ++j)
|
for (int j = (i + 1); j < uniqueUNLs.size(); ++j)
|
||||||
{
|
{
|
||||||
auto const & unlA = uniqueUNLs[i];
|
auto const& unlA = uniqueUNLs[i];
|
||||||
auto const & unlB = uniqueUNLs[j];
|
auto const& unlB = uniqueUNLs[j];
|
||||||
|
|
||||||
double rhs = 2.0*(1.-quorum) *
|
double rhs =
|
||||||
std::max(unlA.size(), unlB.size() );
|
2.0 * (1. - quorum) * std::max(unlA.size(), unlB.size());
|
||||||
|
|
||||||
int intersectionSize = std::count_if(unlA.begin(), unlA.end(),
|
int intersectionSize =
|
||||||
[&](PeerID id)
|
std::count_if(unlA.begin(), unlA.end(), [&](PeerID id) {
|
||||||
{
|
|
||||||
return unlB.find(id) != unlB.end();
|
return unlB.find(id) != unlB.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
if(intersectionSize < rhs)
|
if (intersectionSize < rhs)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,56 +79,53 @@ TrustGraph::makeClique(int size, int overlap)
|
|||||||
// Clique A has nodes [0,endA) and Clique B has [startB,numPeers)
|
// Clique A has nodes [0,endA) and Clique B has [startB,numPeers)
|
||||||
// Note: Clique B will have an extra peer when numPeers - overlap
|
// Note: Clique B will have an extra peer when numPeers - overlap
|
||||||
// is odd
|
// is odd
|
||||||
int endA = (size + overlap)/2;
|
int endA = (size + overlap) / 2;
|
||||||
int startB = (size - overlap)/2;
|
int startB = (size - overlap) / 2;
|
||||||
|
|
||||||
std::vector<UNL> unls;
|
std::vector<UNL> unls;
|
||||||
unls.emplace_back(bci(0), bci(endA));
|
unls.emplace_back(bci(0), bci(endA));
|
||||||
unls.emplace_back(bci(startB), bci(size));
|
unls.emplace_back(bci(startB), bci(size));
|
||||||
unls.emplace_back(bci(0), bci(size));
|
unls.emplace_back(bci(0), bci(size));
|
||||||
|
|
||||||
std::vector<int> assignment(size,0);
|
std::vector<int> assignment(size, 0);
|
||||||
|
|
||||||
for (int i = 0; i < size; ++i)
|
for (int i = 0; i < size; ++i)
|
||||||
{
|
{
|
||||||
if(i < startB)
|
if (i < startB)
|
||||||
assignment[i] = 0;
|
assignment[i] = 0;
|
||||||
else if(i > endA)
|
else if (i > endA)
|
||||||
assignment[i] = 1;
|
assignment[i] = 1;
|
||||||
else
|
else
|
||||||
assignment[i] = 2;
|
assignment[i] = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return TrustGraph(unls, assignment);
|
return TrustGraph(unls, assignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrustGraph
|
TrustGraph
|
||||||
TrustGraph::makeComplete(int size)
|
TrustGraph::makeComplete(int size)
|
||||||
{
|
{
|
||||||
UNL all{ boost::counting_iterator<PeerID>( 0 ),
|
UNL all{boost::counting_iterator<PeerID>(0),
|
||||||
boost::counting_iterator<PeerID>( size ) };
|
boost::counting_iterator<PeerID>(size)};
|
||||||
|
|
||||||
return TrustGraph(std::vector<UNL>(1,all),
|
return TrustGraph(std::vector<UNL>(1, all), std::vector<int>(size, 0));
|
||||||
std::vector<int>(size, 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void TrustGraph::save_dot(std::string const & fileName)
|
inline void
|
||||||
|
TrustGraph::save_dot(std::string const& fileName)
|
||||||
{
|
{
|
||||||
std::ofstream out(fileName);
|
std::ofstream out(fileName);
|
||||||
out << "digraph {\n";
|
out << "digraph {\n";
|
||||||
for (int i = 0; i < assignment_.size(); ++i)
|
for (int i = 0; i < assignment_.size(); ++i)
|
||||||
{
|
{
|
||||||
for (auto & j : UNLs_[assignment_[i]])
|
for (auto& j : UNLs_[assignment_[i]])
|
||||||
{
|
{
|
||||||
out << i << " -> " << j << ";\n";
|
out << i << " -> " << j << ";\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
out << "}\n";
|
out << "}\n";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // csf
|
} // csf
|
||||||
} // test
|
} // test
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|||||||