Improve Consensus interface and documentation (RIPD-1340):
- Add Consensus::Result, which represents the result of the establish state and includes the consensus transaction set, final proposed position and disputes. - Add Consensus::Mode to track how we are participating in consensus and ensures the onAccept callback can distinguish when we entered the round with consensus versus when we recovered from a wrong ledger during a round. - Rename Consensus::Phase to Consensus::State and eliminate the processing phase. Instead, accept is a terminal phase which notifies RCLConsensus via onAccept callbacks. Even if clients dispatch accepting to another thread, all future calls except to startRound will not change the state of consensus. - Move validate_ status from Consensus to RCLConsensus, since generic implementation does not directly reference whether a node is validating or not. - Eliminate gotTxSetInternal and handle externally received TxSets distinct from locally generated positions. - Change ConsensusProposal::changePosition to always update the internal close time and position even if we have bowed out. This enforces the invariant that our proposal's position always matches our transaction set.
@@ -353,6 +353,7 @@ if (WIN32 OR is_xcode)
|
||||
docs/
|
||||
Jamfile.v2
|
||||
boostbook.dtd
|
||||
consensus.qbk
|
||||
index.xml
|
||||
main.qbk
|
||||
quickref.xml
|
||||
|
||||
@@ -44,6 +44,15 @@ install callouts
|
||||
|
||||
explicit callout ;
|
||||
|
||||
install consensus_images
|
||||
:
|
||||
[ glob images/consensus/*.png ]
|
||||
:
|
||||
<location>$(out)/html/images/consensus
|
||||
;
|
||||
|
||||
explicit consensus_images ;
|
||||
|
||||
xml doc
|
||||
:
|
||||
main.qbk
|
||||
@@ -60,7 +69,7 @@ boostbook boostdoc
|
||||
<xsl:param>boost.root=$(broot)
|
||||
<xsl:param>chunk.first.sections=1 # Chunk the first top-level section?
|
||||
<xsl:param>chunk.section.depth=8 # Depth to which sections should be chunked
|
||||
<xsl:param>generate.section.toc.level=1 # Control depth of TOC generation in sections
|
||||
<xsl:param>generate.section.toc.level=2 # Control depth of TOC generation in sections
|
||||
<xsl:param>toc.max.depth=2 # How many levels should be created for each TOC?
|
||||
<xsl:param>toc.section.depth=2 # How deep should recursive sections appear in the TOC?
|
||||
<xsl:param>generate.toc="chapter toc section toc"
|
||||
@@ -68,4 +77,5 @@ boostbook boostdoc
|
||||
<location>temp
|
||||
<dependency>stylesheets
|
||||
<dependency>images
|
||||
<dependency>consensus_images
|
||||
;
|
||||
|
||||
663
docs/consensus.qbk
Normal file
@@ -0,0 +1,663 @@
|
||||
[section Consensus and Validation]
|
||||
|
||||
[*This section is a work in progress!!]
|
||||
|
||||
Consensus is the task of reaching agreement within a distributed system in the
|
||||
presence of faulty or even malicious participants. This document outlines the
|
||||
[@https://ripple.com/files/ripple/consensus/whitepaper.pdf Ripple Consensus
|
||||
Algorithm] as implemented in [@https://github.com/ripple/rippled rippled], but
|
||||
focuses on its utility as a generic consensus algorithm independent of the
|
||||
detailed mechanics of the Ripple Consensus Ledger. Most notably, the algorithm
|
||||
does not require fully synchronous communication between all nodes in the
|
||||
network, or even a fixed network topology, but instead achieves consensus via
|
||||
collectively trusted subnetworks.
|
||||
|
||||
[heading Distributed Agreement]
|
||||
|
||||
A challenge for distributed systems is reaching agreement on changes in shared
|
||||
state. For the Ripple network, the shared state is the current ledger--account
|
||||
information, account balances, order books and other financial data. We will
|
||||
refer to shared distributed state as a /ledger/ throughout the remainder of this
|
||||
document.
|
||||
|
||||
[$images/consensus/ledger_chain.png [width 50%] [height 50%] ]
|
||||
|
||||
As shown above, new ledgers are made by applying a set of transactions to the
|
||||
prior ledger. For the Ripple network, transactions include payments,
|
||||
modification of account settings, updates to offers and more.
|
||||
|
||||
In a centralized system, generating the next ledger is trivial since there is a
|
||||
single unique arbiter of which transactions to include and how to apply them to
|
||||
a ledger. For decentralized systems, participants must resolve disagreements on
|
||||
the set of transactions to include, the order to apply those transactions, and
|
||||
even the resulting ledger after applying the transactions. This is even more
|
||||
difficult when some participants are faulty or malicious.
|
||||
|
||||
The Ripple network is a decentralized and _trust-full_ network. Anyone is free
|
||||
to join and participants are free to choose a subset of peers that are
|
||||
collectively trusted to not collude in an attempt to defraud the participant.
|
||||
Leveraging this network of trust, the Ripple algorithm has two main components.
|
||||
|
||||
* /Consensus/ in which network participants agree on the transactions to apply
|
||||
to a prior ledger, based on the positions of their chosen peers.
|
||||
* /Validation/ in which network participants agree on what ledger was
|
||||
generated, based on the ledgers generated by chosen peers.
|
||||
|
||||
These phases are continually repeated to process transactions submitted to the
|
||||
network, generating successive ledgers and giving rise to the blockchain ledger
|
||||
history depicted below. In this diagram, time is flowing to the right, but
|
||||
links between ledgers point backward to the parent. Also note the alternate
|
||||
Ledger 2 that was generated by some participants, but which failed validation
|
||||
and was abandoned.
|
||||
|
||||
[$images/consensus/block_chain.png]
|
||||
|
||||
The remainder of this section describes the Consensus and Validation algorithms
|
||||
in more detail and is meant as a companion guide to understanding the generic
|
||||
implementation in =rippled=. The document *does not* discuss correctness,
|
||||
fault-tolerance or liveness properties of the algorithms or the full details of
|
||||
how they integrate within =rippled= to support the Ripple Consensus Ledger.
|
||||
|
||||
[section Consensus Overview]
|
||||
|
||||
[heading Definitions]
|
||||
|
||||
* The /ledger/ is the shared distributed state. Each ledger has a unique ID to
|
||||
distinguish it from all other ledgers. During consensus, the /previous/,
|
||||
/prior/ or /last-closed/ ledger is the most recent ledger seen by consensus
|
||||
and is the basis upon which it will build the next ledger.
|
||||
* A /transaction/ is an instruction for an atomic change in the ledger state. A
|
||||
unique ID distinguishes a transaction from other transactions.
|
||||
* A /transaction set/ is a set of transactions under consideration by consensus.
|
||||
The goal of consensus is to reach agreement on this set. The generic
|
||||
consensus algorithm does not rely on an ordering of transactions within the
|
||||
set, nor does it specify how to apply a transaction set to a ledger to
|
||||
generate a new ledger. A unique ID distinguishes a set of transactions from
|
||||
all other sets of transactions.
|
||||
* A /node/ is one of the distributed actors running the consensus algorithm. It
|
||||
has a unique ID to distinguish it from all other nodes.
|
||||
* A /peer/ of a node is another node that it has chosen to follow and which it
|
||||
believes will not collude with other chosen peers. The choice of peers is not
|
||||
symmetric, since participants can decide on their chosen sets independently.
|
||||
* A /position/ is the current belief of the next ledger's transaction set and
|
||||
close time. Position can refer to the node's own position or the position of a
|
||||
peer.
|
||||
* A /proposal/ is one of a sequence of positions a node shares during consensus.
|
||||
An initial proposal contains the starting position taken by a node before it
|
||||
considers any peer positions. If a node subsequently updates its position in
|
||||
response to its peers, it will issue an updated proposal. A proposal is
|
||||
uniquely identified by the ID of the proposing node, the ID of the position
|
||||
taken, the ID of the prior ledger the proposal is for, and the sequence number
|
||||
of the proposal.
|
||||
* A /dispute/ is a transaction that is either not part of a node's position or
|
||||
not in a peer's position. During consensus, the node will add or remove
|
||||
disputed transactions from its position based on that transaction's support
|
||||
amongst its peers.
|
||||
|
||||
Note that most types have an ID as a lightweight identifier of instances of that
|
||||
type. Consensus often operates on the IDs directly since the underlying type is
|
||||
potentially expensive to share over the network. For example, proposal's only
|
||||
contain the ID of the position of a peer. Since many peers likely have the same
|
||||
position, this reduces the need to send the full transaction set multiple times.
|
||||
Instead, a node can request the transaction set from the network if necessary.
|
||||
|
||||
[heading Overview ]
|
||||
[$images/consensus/consensus_overview.png [width 50%] [height 50%] ]
|
||||
|
||||
The diagram above is an overview of the consensus process from the perspective
|
||||
of a single participant. Recall that during a single consensus round, a node is
|
||||
trying to agree with its peers on which transactions to apply to its prior
|
||||
ledger when generating the next ledger. It also attempts to agree on the
|
||||
[link effective_close_time network time when the ledger closed]. There are
|
||||
3 main phases to a consensus round:
|
||||
|
||||
* A call to =startRound= places the node in the =Open= phase. In this phase,
|
||||
the node is waiting for transactions to include in its open ledger.
|
||||
* At some point, the node will =Close= the open ledger and transition to the
|
||||
=Establish= phase. In this phase, the node shares/receives peer proposals on
|
||||
which transactions should be accepted in the closed ledger.
|
||||
* At some point, the node determines it has reached consensus with its peers on
|
||||
which transactions to include. It transitions to the =Accept= phase. In this
|
||||
phase, the node works on applying the transactions to the prior ledger to
|
||||
generate a new closed ledger. Once the new ledger is completed, the node shares
|
||||
the validated ledger hash with the network and makes a call to =startRound= to
|
||||
start the cycle again for the next ledger.
|
||||
|
||||
Throughout, a heartbeat timer calls =timerEntry= at a regular frequency to drive
|
||||
the process forward. Although the =startRound= call occurs at arbitrary times
|
||||
based on when the initial round began and the time it takes to apply
|
||||
transactions, the transitions from =Open= to =Establish= and =Establish= to
|
||||
=Accept= only occur during calls to =timerEntry=. Similarly, transactions can
|
||||
arrive at arbitrary times, independent of the heartbeat timer. Transactions
|
||||
received after the =Open= to =Close= transition and not part of peer proposals
|
||||
won't be considered until the next consensus round. They are represented above
|
||||
by the light green triangles.
|
||||
|
||||
Peer proposals are issued by a node during a =timerEntry= call, but since peers
|
||||
do not synchronize =timerEntry= calls, they are received by other peers at
|
||||
arbitrary times. Peer proposals are only considered if received prior to the
|
||||
=Establish= to =Accept= transition, and only if the peer is working on the same
|
||||
prior ledger. Peer proposals received after consensus is reached will not be
|
||||
meaningful and are represented above by the circle with the X in it. Only
|
||||
proposals from chosen peers are considered.
|
||||
|
||||
[#effective_close_time]
|
||||
[heading Effective Close Time]
|
||||
|
||||
In addition to agreeing on a transaction set, each consensus round tries to
|
||||
agree on the time the ledger closed. Each node calculates its own close time
|
||||
when it closes the open ledger. This exact close time is rounded to the nearest
|
||||
multiple of the current /effective close time resolution/. It is this
|
||||
/effective close time/ that nodes seek to agree on. This allows servers to
|
||||
derive a common time for a ledger without the need for perfectly synchronized
|
||||
clocks. As depicted below, the 3 pink arrows represent exact close times from 3
|
||||
consensus nodes that round to the same effective close time given the current
|
||||
resolution. The purple arrow represents a peer whose estimate rounds to a
|
||||
different effective close time given the current resolution.
|
||||
|
||||
[$images/consensus/EffCloseTime.png]
|
||||
|
||||
The effective close time is part of the node's position and is shared with peers
|
||||
in its proposals. Just like the position on the consensus transaction set, a
|
||||
node will update its close time position in response to its peers' effective
|
||||
close time positions. Peers can agree to disagree on the close time, in which
|
||||
case the effective close time is taken as 1 second past the prior close.
|
||||
|
||||
The close time resolution is itself dynamic, decreasing (coarser) resolution in
|
||||
subsequent consensus rounds if nodes are unable to reach consensus on an
|
||||
effective close time and increasing (finer) resolution if nodes consistently
|
||||
reach close time consensus.
|
||||
|
||||
[heading Modes]
|
||||
|
||||
Internally, a node operates under one of the following consensus modes. Either
|
||||
of the first two modes may be chosen when a consensus round starts.
|
||||
|
||||
* /Proposing/ indicates the node is a full-fledged consensus participant. It
|
||||
takes on positions and sends proposals to its peers.
|
||||
* /Observing/ indicates the node is a passive consensus participant. It
|
||||
maintains a position internally, but does not propose that position to its
|
||||
peers. Instead, it receives peer proposals and updates its position
|
||||
to track the majority of its peers. This may be preferred if the node is only
|
||||
being used to track the state of the network or during a start-up phase while
|
||||
it is still synchronizing with the network.
|
||||
|
||||
The other two modes are set internally during the consensus round when the node
|
||||
believes it is no longer working on the dominant ledger chain based on peer
|
||||
validations. It checks this on every call to =timerEntry=.
|
||||
|
||||
* /Wrong Ledger/ indicates the node is not working on the correct prior ledger
|
||||
and does not have it available. It requests that ledger from the network, but
|
||||
continues to work towards consensus this round while waiting. If it had been
|
||||
/proposing/, it will send a special "bowout" proposal to its peers to indicate
|
||||
its change in mode for the rest of this round. For the duration of the round,
|
||||
it defers to peer positions for determining the consensus outcome as if it
|
||||
were just /observing/.
|
||||
* /Switch Ledger/ indicates that the node has acquired the correct prior ledger
|
||||
from the network. Although it now has the correct prior ledger, the fact that
|
||||
it had the wrong one at some point during this round means it is likely behind
|
||||
and should defer to peer positions for determining the consensus outcome.
|
||||
|
||||
[$images/consensus/consensus_modes.png]
|
||||
|
||||
Once either wrong ledger or switch ledger are reached, the node cannot
|
||||
return to proposing or observing until the next consensus round. However,
|
||||
the node could change its view of the correct prior ledger, so going from
|
||||
switch ledger to wrong ledger and back again is possible.
|
||||
|
||||
The distinction between the wrong and switched ledger modes arises because a
|
||||
ledger's unique identifier may be known by a node before the ledger itself. This
|
||||
reflects that fact that the data corresponding to a ledger may be large and take
|
||||
time to share over the network, whereas the smaller ID could be shared in a peer
|
||||
validation much more quickly. Distinguishing the two states allows the node to
|
||||
decide how best to generate the next ledger once it declares consensus.
|
||||
|
||||
[heading Phases]
|
||||
|
||||
As depicted in the overview diagram, consensus is best viewed as a progression
|
||||
through 3 phases. There are 4 public methods of the generic consensus algorithm
|
||||
that determine this progression
|
||||
|
||||
* =startRound= begins a consensus round.
|
||||
* =timerEntry= is called at a regular frequency (=LEDGER_MIN_CLOSE=) and is the
|
||||
only call to consensus that can change the phase from =Open= to =Establish=
|
||||
or =Accept=.
|
||||
* =peerProposal= is called whenever a peer proposal is received and is what
|
||||
allows a node to update its position in a subsequent =timerEntry= call.
|
||||
* =gotTxSet= is called when a transaction set is received from the network. This
|
||||
is typically in response to a prior request from the node to acquire the
|
||||
transaction set corresponding to a disagreeing peer's position.
|
||||
|
||||
The following subsections describe each consensus phase in more detail and what
|
||||
actions are taken in response to these calls.
|
||||
|
||||
[h6 Open]
|
||||
|
||||
The =Open= phase is a quiescent period to allow transactions to build up in the
|
||||
node's open ledger. The duration is a trade-off between latency and throughput.
|
||||
A shorter window reduces the latency to generating the next ledger, but also
|
||||
reduces transaction throughput due to fewer transactions accepted into the
|
||||
ledger.
|
||||
|
||||
A call to =startRound= would forcibly begin the next consensus round, skipping
|
||||
completion of the current round. This is not expected during normal operation.
|
||||
Calls to =peerProposal= or =gotTxSet= simply store the proposal or transaction
|
||||
set for use in the coming =Establish= phase.
|
||||
|
||||
A call to =timerEntry= first checks that the node is working on the correct
|
||||
prior ledger. If not, it will update the mode and request the correct ledger.
|
||||
Otherwise, the node checks whether to switch to the =Establish= phase and close
|
||||
the ledger.
|
||||
|
||||
['Ledger Close]
|
||||
|
||||
Under normal circumstances, the open ledger period ends when one of the following
|
||||
is true
|
||||
|
||||
* if there are transactions in the open ledger and more than =LEDGER_MIN_CLOSE=
|
||||
have elapsed. This is the typical behavior.
|
||||
* if there are no open transactions and a suitably longer idle interval has
|
||||
elapsed. This increases the opportunity to get some transaction into
|
||||
the next ledger and avoids doing useless work closing an empty ledger.
|
||||
* if more than half the number of prior round peers have already closed or finished
|
||||
this round. This indicates the node is falling behind and needs to catch up.
|
||||
|
||||
|
||||
When closing the ledger, the node takes its initial position based on the
|
||||
transactions in the open ledger and uses the current time as
|
||||
its initial close time estimate. If in the proposing mode, the node shares its
|
||||
initial position with peers. Now that the node has taken a position, it will
|
||||
consider any peer positions for this round that arrived earlier. The node
|
||||
generates disputed transactions for each transaction not in common with a peer's
|
||||
position. The node also records the vote of each peer for each disputed
|
||||
transaction.
|
||||
|
||||
In the example below, we suppose our node has closed with transactions 1,2 and 3. It creates disputes
|
||||
for transactions 2,3 and 4, since at least one peer position differs on each.
|
||||
|
||||
[#disputes_image]
|
||||
[$images/consensus/disputes.png [width 20%] [height 20%]]
|
||||
|
||||
|
||||
[h6 Establish]
|
||||
|
||||
The establish phase is the active period of consensus in which the node
|
||||
exchanges proposals with peers in an attempt to reach agreement on the consensus
|
||||
transactions and effective close time.
|
||||
|
||||
A call to =startRound= would forcibly begin the next consensus round, skipping
|
||||
completion of the current round. This is not expected during normal operation.
|
||||
Calls to =peerProposal= or =gotTxSet= that reflect new positions will generate
|
||||
disputed transactions for any new disagreements and will update the peer's vote
|
||||
for all disputed transactions.
|
||||
|
||||
A call to =timerEntry= first checks that the node is working from the correct
|
||||
prior ledger. If not, the node will update the mode and request the correct
|
||||
ledger. Otherwise, the node updates the node's position and considers whether
|
||||
to switch to the =Accepted= phase and declare consensus reached. However, at
|
||||
least =LEDGER_MIN_CONSENSUS= time must have elapsed before doing either. This
|
||||
allows peers an opportunity to take an initial position and share it.
|
||||
|
||||
['Update Position]
|
||||
|
||||
In order to achieve consensus, the node is looking for a transaction set that is
|
||||
supported by a super-majority of peers. The node works towards this set by
|
||||
adding or removing disputed transactions from its position based on an
|
||||
increasing threshold for inclusion.
|
||||
|
||||
[$images/consensus/threshold.png [width 50%] [height 50%]]
|
||||
|
||||
By starting with a lower threshold, a node initially allows a wide set of
|
||||
transactions into its position. If the establish round continues and the node is
|
||||
"stuck", a higher threshold can focus on accepting transactions with the most
|
||||
support. The constants that define the thresholds and durations at which the
|
||||
thresholds change are given by `AV_XXX_CONSENSUS_PCT` and
|
||||
`AV_XXX_CONSENSUS_TIME` respectively, where =XXX= is =INIT=,=MID=,=LATE= and
|
||||
=STUCK=. The effective close time position is updated using the same
|
||||
thresholds.
|
||||
|
||||
Given the [link disputes_image example disputes above] and an initial threshold
|
||||
of 50%, our node would retain its position since transaction 1 was not in
|
||||
dispute and transactions 2 and 3 have 75% support. Since its position did not
|
||||
change, it would not need to send a new proposal to peers. Peer C would not
|
||||
change either. Peer A would add transaction 3 to its position and Peer B would
|
||||
remove transaction 4 from its position; both would then send an updated
|
||||
position.
|
||||
|
||||
Conversely, if the diagram reflected a later call to =timerEntry= that occurs in
|
||||
the stuck region with a threshold of say 95%, our node would remove transactions
|
||||
2 and 3 from its candidate set and send an updated position. Likewise, all the
|
||||
other peers would end up with only transaction 1 in their position.
|
||||
|
||||
Lastly, if our node were not in the proposing mode, it would not include its own
|
||||
vote and just take the majority (>50%) position of its peers. In this example,
|
||||
our node would maintain its position of transactions 1, 2 and 3.
|
||||
|
||||
['Checking Consensus]
|
||||
|
||||
After updating its position, the node checks for supermajority agreement with
|
||||
its peers on its current position. This agreement is of the exact transaction
|
||||
set, not just the support of individual transactions. That is, if our position
|
||||
is a subset of a peer's position, that counts as a disagreement. Also recall
|
||||
that effective close time agreement allows a supermajority of participants
|
||||
agreeing to disagree.
|
||||
|
||||
Consensus is declared when the following 3 clauses are true:
|
||||
|
||||
* `LEDGER_MIN_CONSENSUS` time has elapsed in the establish phase
|
||||
* At least 75% of the prior round proposers have proposed OR this establish
|
||||
phase is `LEDGER_MIN_CONSENSUS` longer than the last round's establish phase
|
||||
* =minimumConsensusPercentage= of ourself and our peers share the same position
|
||||
|
||||
The middle condition ensures slower peers have a chance to share positions, but
|
||||
prevents waiting too long on peers that have disconnected. Additionally, a node
|
||||
can declare that consensus has moved on if =minimumConsensusPercentage= peers
|
||||
have sent validations and moved on to the next ledger. This outcome indicates
|
||||
the node has fallen behind its peers and needs to catch up.
|
||||
|
||||
If a node is not proposing, it does not include its own position when
|
||||
calculating the percent of agreeing participants but otherwise follows the above
|
||||
logic.
|
||||
|
||||
['Accepting Consensus]
|
||||
|
||||
Once consensus is reached (or moved on), the node switches to the =Accept= phase
|
||||
and signals to the implementing code that the round is complete. That code is
|
||||
responsible for using the consensus transaction set to generate the next ledger
|
||||
and calling =startRound= to begin the next round. The implementation has total
|
||||
freedom on ordering transactions, deciding what to do if consensus moved on,
|
||||
determining whether to retry or abandon local transactions that did not make the
|
||||
consensus set and updating any internal state based on the consensus progress.
|
||||
|
||||
|
||||
[h6 Accept]
|
||||
|
||||
The =Accept= phase is the terminal phase of the consensus algorithm. Calls to
|
||||
=timerEntry=, =peerProposal= and =gotTxSet= will not change the internal
|
||||
consensus state while in the accept phase. The expectation is that the
|
||||
application specific code is working to generate the new ledger based on the
|
||||
consensus outcome. Once complete, that code should make a call to =startRound=
|
||||
to kick off the next consensus round. The =startRound= call includes the new
|
||||
prior ledger, prior ledger ID and whether the round should begin in the
|
||||
proposing or observing mode. After setting some initial state, the phase
|
||||
transitions to =Open=. The node will also check if the provided prior ledger
|
||||
and ID are correct, updating the mode and requesting the proper ledger from the
|
||||
network if necessary.
|
||||
|
||||
[endsect] [/Consensus Overview]
|
||||
|
||||
[section Consensus Type Requirements]
|
||||
|
||||
The consensus type requirements are given below as minimal implementation stubs.
|
||||
Actual implementations would augment these stubs with members appropriate for
|
||||
managing the details of transactions and ledgers within the larger application
|
||||
framework.
|
||||
|
||||
[heading Transaction]
|
||||
The transaction type =Tx= encapsulates a single transaction under consideration
|
||||
by consensus.
|
||||
|
||||
```
|
||||
struct Tx
|
||||
{
|
||||
using ID = ...;
|
||||
ID const & id() const;
|
||||
|
||||
//... implementation specific
|
||||
};
|
||||
```
|
||||
|
||||
[heading Transaction Set]
|
||||
The transaction set type =TxSet= represents a set of [^Tx]s that are collectively
|
||||
under consideration by consensus. A =TxSet= can be compared against other [^TxSet]s
|
||||
(typically from peers) and can be modified to add or remove transactions via
|
||||
the mutable subtype.
|
||||
|
||||
```
|
||||
struct TxSet
|
||||
{
|
||||
using Tx = Tx;
|
||||
using ID = ...;
|
||||
|
||||
ID const & id() const;
|
||||
|
||||
bool exists(Tx::ID const &) const;
|
||||
Tx const * find(Tx::ID const &) const ;
|
||||
|
||||
// Return set of transactions that are not common with another set
|
||||
// Bool in map is true if in our set, false if in other
|
||||
std::map<Tx::ID, bool> compare(TxSet const & other) const;
|
||||
|
||||
// A mutable view that allows changing transactions in the set
|
||||
struct MutableTxSet
|
||||
{
|
||||
MutableTxSet(TxSet const &);
|
||||
bool insert(Tx const &);
|
||||
bool erase(Tx::ID const &);
|
||||
};
|
||||
|
||||
// Construct from a mutable view.
|
||||
TxSet(MutableTxSet const &);
|
||||
|
||||
// Alternatively, if the TxSet is itself mutable
|
||||
// just alias MutableTxSet = TxSet
|
||||
|
||||
//... implementation specific
|
||||
};
|
||||
```
|
||||
|
||||
[heading Ledger] The =Ledger= type represents the state shared amongst the
|
||||
distributed participants. Notice that the details of how the next ledger is
|
||||
generated from the prior ledger and the consensus accepted transaction set is
|
||||
not part of the interface. Within the generic code, this type is primarily used
|
||||
to know that peers are working on the same tip of the ledger chain and to
|
||||
provide some basic timing data for consensus.
|
||||
|
||||
```
|
||||
struct Ledger
|
||||
{
|
||||
using ID = ...;
|
||||
|
||||
ID const & id() const;
|
||||
|
||||
// Sequence number that is 1 more than the parent ledger's seq()
|
||||
std::size_t seq() const;
|
||||
|
||||
// Whether the ledger's close time was a non-trivial consensus result
|
||||
bool closeAgree() const;
|
||||
|
||||
// The close time resolution used in determing the close time
|
||||
NetClock::duration closeTimeResolution() const;
|
||||
|
||||
// The (effective) close time, based on the closeTimeResolution
|
||||
NetClock::time_point closeTime() const;
|
||||
|
||||
// The parent ledger's close time
|
||||
NetClock::time_point parentCloseTime() const;
|
||||
|
||||
Json::Value getJson() const;
|
||||
|
||||
//... implementation specific
|
||||
};
|
||||
```
|
||||
[heading Generic Consensus Interface]
|
||||
|
||||
Following the
|
||||
[@https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern CRTP]
|
||||
idiom, generic =Consensus= relies on a deriving class implementing a set of
|
||||
helpers and callbacks that encapsulate implementation specific details of the
|
||||
algorithm. Below are excerpts of the generic consensus implementation and of
|
||||
helper types that will interact with the concrete implementing class.
|
||||
|
||||
```
|
||||
|
||||
// Represents our proposed position or a peer's proposed position
|
||||
template <class NodeID_t, class LedgerID_t, class Position_t> class ConsensusProposal;
|
||||
|
||||
// Represents a transction under dispute this round
|
||||
template <class Tx_t, class NodeID_t> class DisputedTx;
|
||||
|
||||
template <class Derived, class Traits> class Consensus
|
||||
{
|
||||
protected:
|
||||
enum class Mode { proposing, observing, wrongLedger, switchedLedger};
|
||||
|
||||
// Measure duration of phases of consensus
|
||||
class Stopwatch
|
||||
{
|
||||
public:
|
||||
std::chrono::milliseconds read() const;
|
||||
// details omitted ...
|
||||
};
|
||||
|
||||
// Initial ledger close times, not rounded by closeTimeResolution
|
||||
// Used to gauge degree of synchronization between a node and its peers
|
||||
struct CloseTimes
|
||||
{
|
||||
std::map<NetClock::time_point, int> peers;
|
||||
NetClock::time_point self;
|
||||
};
|
||||
|
||||
// Encapsulates the result of consensus.
|
||||
struct Result
|
||||
{
|
||||
//! The set of transactions consensus agrees go in the ledger
|
||||
TxSet_t set;
|
||||
|
||||
//! Our proposed position on transactions/close time
|
||||
Proposal_t position;
|
||||
|
||||
//! Transactions which are under dispute with our peers
|
||||
using Dispute_t = DisputedTx<Tx_t, NodeID_t>;
|
||||
hash_map<typename Tx_t::ID, Dispute_t> disputes;
|
||||
|
||||
// Set of TxSet ids we have already compared/created disputes
|
||||
hash_set<typename TxSet_t::ID> compares;
|
||||
|
||||
// Measures the duration of the establish phase for this consensus round
|
||||
Stopwatch roundTime;
|
||||
|
||||
// Indicates state in which consensus ended. Once in the accept phase
|
||||
// will be either Yes or MovedOn
|
||||
ConsensusState state = ConsensusState::No;
|
||||
};
|
||||
|
||||
public:
|
||||
// Kick-off the next round of consensus.
|
||||
void startRound(
|
||||
NetClock::time_point const& now,
|
||||
typename Ledger_t::ID const& prevLedgerID,
|
||||
Ledger_t const& prevLedger,
|
||||
bool proposing);
|
||||
|
||||
// Call periodically to drive consensus forward.
|
||||
void timerEntry(NetClock::time_point const& now);
|
||||
|
||||
// A peer has proposed a new position, adjust our tracking. Return true if the proposal
|
||||
// was used.
|
||||
bool peerProposal(NetClock::time_point const& now, Proposal_t const& newProposal);
|
||||
|
||||
// Process a transaction set acquired from the network
|
||||
void gotTxSet(NetClock::time_point const& now, TxSet_t const& txSet);
|
||||
|
||||
// ... details
|
||||
};
|
||||
```
|
||||
[heading Adapting Generic Consensus]
|
||||
|
||||
The stub below shows the set of callback/helper functions required in the implementing class.
|
||||
|
||||
```
|
||||
struct Traits
|
||||
{
|
||||
using Ledger_t = Ledger;
|
||||
using TxSet_t = TxSet;
|
||||
using NodeID_t = ...; // Integer-like std::uint32_t to uniquely identify a node
|
||||
|
||||
};
|
||||
|
||||
class ConsensusImp : public Consensus<ConsensusImp, Traits>
|
||||
{
|
||||
// Attempt to acquire a specific ledger from the network.
|
||||
boost::optional<Ledger> acquireLedger(Ledger::ID const & ledgerID);
|
||||
|
||||
// Acquire the transaction set associated with a proposed position.
|
||||
boost::optional<TxSet> acquireTxSet(TxSet::ID const & setID);
|
||||
|
||||
// Get peers' proposed positions. Returns an iterable
|
||||
// with value_type convertable to ConsensusPosition<...>
|
||||
auto const & proposals(Ledger::ID const & ledgerID);
|
||||
|
||||
// Whether any transactions are in the open ledger
|
||||
bool hasOpenTransactions() const;
|
||||
|
||||
// Number of proposers that have validated the given ledger
|
||||
std::size_t proposersValidated(Ledger::ID const & prevLedger) const;
|
||||
|
||||
// Number of proposers that have validated a ledger descended from the
|
||||
// given ledger
|
||||
std::size_t proposersFinished(Ledger::ID const & prevLedger) const;
|
||||
|
||||
// Return the ID of the last closed (and validated) ledger that the
|
||||
// application thinks consensus should use as the prior ledger.
|
||||
Ledger::ID getPrevLedger(Ledger::ID const & prevLedgerID,
|
||||
Ledger const & prevLedger,
|
||||
Mode mode);
|
||||
|
||||
// Called when ledger closes. Implementation should generate an initial Result
|
||||
// with position based on the current open ledger's transactions.
|
||||
Result onClose(Ledger const &, Ledger const & prev, Mode mode);
|
||||
|
||||
// Called when ledger is accepted by consensus
|
||||
void onAccept(Result const & result,
|
||||
RCLCxLedger const & prevLedger,
|
||||
NetClock::duration closeResolution,
|
||||
CloseTimes const & rawCloseTimes,
|
||||
Mode const & mode);
|
||||
|
||||
// Propose the position to peers.
|
||||
void propose(ConsensusProposal<...> const & pos);
|
||||
|
||||
// Relay a received peer proposal on to other peer's.
|
||||
void relay(ConsensusProposal<...> const & pos);
|
||||
|
||||
// Relay a disputed transaction to peers
|
||||
void relay(TxSet::Tx const & tx);
|
||||
|
||||
// Realy given transaction set with peers
|
||||
void relay(TxSet const &s);
|
||||
|
||||
//... implementation specific
|
||||
};
|
||||
```
|
||||
|
||||
The implementing class hides many details of the peer communication
|
||||
model from the generic code.
|
||||
|
||||
* The =relay= member functions are responsible for sharing the given type with a
|
||||
node's peers, but are agnostic to the mechanism. Ideally, messages are delivered
|
||||
faster than =LEDGER_GRANULARITY=.
|
||||
* The generic code does not specify how transactions are submitted by clients,
|
||||
propagated through the network or stored in the open ledger. Indeed, the open
|
||||
ledger is only conceptual from the perspective of the generic code---the
|
||||
initial position and transaction set are opaquely generated in a
|
||||
`Consensus::Result` instance returned from the =onClose= callback.
|
||||
* The calls to =acquireLedger= and =acquireTxSet= only have non-trivial return
|
||||
if the ledger or transaction set of interest is available. The implementing
|
||||
class is free to block while acquiring, or return the empty option while
|
||||
servicing the request asynchronously. Due to legacy reasons, the two calls
|
||||
are not symmetric. =acquireTxSet= requires the host application to call
|
||||
=gotTxSet= when an asynchronous =acquire= completes. Conversely,
|
||||
=acquireLedger= will be called again later by the consensus code if it still
|
||||
desires the ledger with the hope that the asynchronous acquisition is
|
||||
complete.
|
||||
|
||||
[endsect] [/Consensus Type Requirements]
|
||||
|
||||
[section Validation]
|
||||
|
||||
Coming Soon!
|
||||
|
||||
[endsect] [/Validation]
|
||||
|
||||
[endsect] [/Consensus and Validation]
|
||||
BIN
docs/images/consensus/EffCloseTime.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
docs/images/consensus/block_chain.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
docs/images/consensus/consensus_modes.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/images/consensus/consensus_overview.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/images/consensus/disputes.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/images/consensus/ledger_chain.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/images/consensus/threshold.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
@@ -1,5 +1,5 @@
|
||||
[/
|
||||
Copyright (c) Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
Copyright (c) Copyright (c) 2012-2017 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
[library rippled
|
||||
[quickbook 1.6]
|
||||
[copyright 2012 - 2016 Ripple Labs Inc.]
|
||||
[copyright 2012 - 2017 Ripple Labs Inc.]
|
||||
[purpose C++ Library]
|
||||
[license
|
||||
Distributed under the ISC License
|
||||
@@ -30,6 +30,8 @@
|
||||
[template indexterm1[term1] '''<indexterm><primary>'''[term1]'''</primary></indexterm>''']
|
||||
[template indexterm2[term1 term2] '''<indexterm><primary>'''[term1]'''</primary><secondary>'''[term2]'''</secondary></indexterm>''']
|
||||
|
||||
[include consensus.qbk]
|
||||
|
||||
[section:ref Reference]
|
||||
[include temp/reference.qbk]
|
||||
[endsect]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
@@ -21,19 +21,19 @@
|
||||
#define RIPPLE_APP_CONSENSUS_RCLCONSENSUS_H_INCLUDED
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/consensus/RCLCxLedger.h>
|
||||
#include <ripple/app/consensus/RCLCxPeerPos.h>
|
||||
#include <ripple/app/consensus/RCLCxTx.h>
|
||||
#include <ripple/app/misc/FeeVote.h>
|
||||
#include <ripple/basics/CountedObject.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <ripple/consensus/Consensus.h>
|
||||
#include <ripple/core/JobQueue.h>
|
||||
#include <ripple/overlay/Message.h>
|
||||
#include <ripple/protocol/RippleLedgerHash.h>
|
||||
#include <ripple/protocol/STValidation.h>
|
||||
#include <ripple/shamap/SHAMap.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <ripple/app/misc/FeeVote.h>
|
||||
#include <ripple/protocol/RippleLedgerHash.h>
|
||||
#include <ripple/app/consensus/RCLCxLedger.h>
|
||||
#include <ripple/app/consensus/RCLCxTx.h>
|
||||
#include <ripple/app/consensus/RCLCxPeerPos.h>
|
||||
#include <ripple/core/JobQueue.h>
|
||||
#include <ripple/consensus/Consensus.h>
|
||||
#include <ripple/basics/CountedObject.h>
|
||||
#include <ripple/overlay/Message.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -50,37 +50,40 @@ struct RCLCxTraits
|
||||
using NodeID_t = NodeID;
|
||||
//! TxSet type presented to Consensus
|
||||
using TxSet_t = RCLTxSet;
|
||||
//! MissingTxException type neede by Consensus
|
||||
using MissingTxException_t = SHAMapMissingNode;
|
||||
};
|
||||
|
||||
|
||||
/** Adapts the generic Consensus algorithm for use by RCL.
|
||||
|
||||
@note The enabled_shared_from_this base allows the application to properly
|
||||
create a shared instance of RCLConsensus for use in the accept logic..
|
||||
*/
|
||||
class RCLConsensus : public Consensus<RCLConsensus, RCLCxTraits>
|
||||
, public std::enable_shared_from_this <RCLConsensus>
|
||||
, public CountedObject <RCLConsensus>
|
||||
class RCLConsensus final : public Consensus<RCLConsensus, RCLCxTraits>,
|
||||
public std::enable_shared_from_this<RCLConsensus>,
|
||||
public CountedObject<RCLConsensus>
|
||||
{
|
||||
using Base = Consensus<RCLConsensus, RCLCxTraits>;
|
||||
using Base::accept;
|
||||
public:
|
||||
|
||||
public:
|
||||
//! Constructor
|
||||
RCLConsensus(
|
||||
Application& app,
|
||||
std::unique_ptr<FeeVote> && feeVote,
|
||||
std::unique_ptr<FeeVote>&& feeVote,
|
||||
LedgerMaster& ledgerMaster,
|
||||
LocalTxs& localTxs,
|
||||
InboundTransactions& inboundTransactions,
|
||||
typename Base::clock_type const & clock,
|
||||
typename Base::clock_type const& clock,
|
||||
beast::Journal journal);
|
||||
RCLConsensus(RCLConsensus const&) = delete;
|
||||
RCLConsensus& operator=(RCLConsensus const&) = delete;
|
||||
|
||||
static char const* getCountedObjectName() { return "Consensus"; }
|
||||
RCLConsensus(RCLConsensus const&) = delete;
|
||||
|
||||
RCLConsensus&
|
||||
operator=(RCLConsensus const&) = delete;
|
||||
|
||||
static char const*
|
||||
getCountedObjectName()
|
||||
{
|
||||
return "Consensus";
|
||||
}
|
||||
|
||||
/** Save the given consensus proposed by a peer with nodeID for later
|
||||
use in consensus.
|
||||
@@ -89,15 +92,59 @@ public:
|
||||
@param nodeID ID of peer
|
||||
*/
|
||||
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 */
|
||||
PublicKey const&
|
||||
getValidationPublicKey () const;
|
||||
getValidationPublicKey() const;
|
||||
|
||||
/** Set validation private and public key pair. */
|
||||
void
|
||||
setValidationKeys (SecretKey const& valSecret, PublicKey const& valPublic);
|
||||
setValidationKeys(SecretKey const& valSecret, PublicKey const& valPublic);
|
||||
|
||||
private:
|
||||
friend class Consensus<RCLConsensus, RCLCxTraits>;
|
||||
@@ -105,17 +152,6 @@ private:
|
||||
//-------------------------------------------------------------------------
|
||||
// Consensus type requirements.
|
||||
|
||||
/** Notification that a new consensus round has begun.
|
||||
|
||||
@param ledger The ledger we are building consensus on
|
||||
*/
|
||||
void
|
||||
onStartRound(RCLCxLedger const & ledger);
|
||||
|
||||
//! @return Whether consensus should be (proposing, validating)
|
||||
std::pair <bool, bool>
|
||||
getMode ();
|
||||
|
||||
/** Attempt to acquire a specific ledger.
|
||||
|
||||
If not available, asynchronously acquires from the network.
|
||||
@@ -124,44 +160,44 @@ private:
|
||||
@return Optional ledger, will be seated if we locally had the ledger
|
||||
*/
|
||||
boost::optional<RCLCxLedger>
|
||||
acquireLedger(LedgerHash const & ledger);
|
||||
acquireLedger(LedgerHash const& ledger);
|
||||
|
||||
/** Get peers' proposed positions.
|
||||
@param prevLedger The base ledger which proposals are based on
|
||||
@return The set of proposals
|
||||
*/
|
||||
std::vector<RCLCxPeerPos>
|
||||
proposals (LedgerHash const& prevLedger);
|
||||
proposals(LedgerHash const& prevLedger);
|
||||
|
||||
/** Relay the given proposal to all peers
|
||||
|
||||
@param peerPos The peer position to relay.
|
||||
*/
|
||||
void
|
||||
relay(RCLCxPeerPos const & peerPos);
|
||||
relay(RCLCxPeerPos const& peerPos);
|
||||
|
||||
/** Relay disputed transacction to peers.
|
||||
|
||||
Only relay if the provided transaction hasn't been shared recently.
|
||||
|
||||
@param dispute The disputed transaction to relay.
|
||||
@param tx The disputed transaction to relay.
|
||||
*/
|
||||
void
|
||||
relay(DisputedTx <RCLCxTx, NodeID> const & dispute);
|
||||
relay(RCLCxTx const& tx);
|
||||
|
||||
/** Acquire the transaction set associated with a proposal.
|
||||
/** Acquire the transaction set associated with a proposal.
|
||||
|
||||
If the transaction set is not available locally, will attempt acquire it
|
||||
from the network.
|
||||
If the transaction set is not available locally, will attempt acquire it
|
||||
from the network.
|
||||
|
||||
@param setId The transaction set ID associated with the proposal
|
||||
@return Optional set of transactions, seated if available.
|
||||
*/
|
||||
@param setId The transaction set ID associated with the proposal
|
||||
@return Optional set of transactions, seated if available.
|
||||
*/
|
||||
boost::optional<RCLTxSet>
|
||||
acquireTxSet(RCLTxSet::ID const & setId);
|
||||
acquireTxSet(RCLTxSet::ID const& setId);
|
||||
|
||||
/** Whether the open ledger has any transactions
|
||||
*/
|
||||
*/
|
||||
bool
|
||||
hasOpenTransactions() const;
|
||||
|
||||
@@ -171,133 +207,93 @@ private:
|
||||
@return the number of proposers that validated a ledger
|
||||
*/
|
||||
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.
|
||||
@return The number of validating peers that have validated a ledger
|
||||
succeeding the one provided.
|
||||
*/
|
||||
std::size_t
|
||||
proposersFinished(LedgerHash const & h) const;
|
||||
proposersFinished(LedgerHash const& h) const;
|
||||
|
||||
/** Propose the given position to my peers.
|
||||
|
||||
@param proposal Our proposed position
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
void
|
||||
share (RCLTxSet const& set);
|
||||
relay(RCLTxSet const& set);
|
||||
|
||||
/** Get the last closed ledger (LCL) seen on the network
|
||||
/** Get the ID of the previous ledger/last closed ledger(LCL) on the network
|
||||
|
||||
@param currentLedger Current ledger used in consensus
|
||||
@param priorLedger Prior ledger used in consensus
|
||||
@param believedCorrect Whether consensus believes currentLedger is LCL
|
||||
@param ledgerID ID of previous ledger used by consensus
|
||||
@param ledger Previous ledger consensus has available
|
||||
@param mode Current consensus mode
|
||||
@return The id of the last closed network
|
||||
|
||||
@return The hash of the last closed network
|
||||
@note ledgerID may not match ledger.id() if we haven't acquired
|
||||
the ledger matching ledgerID from the network
|
||||
*/
|
||||
uint256
|
||||
getLCL (
|
||||
uint256 const& currentLedger,
|
||||
uint256 const& priorLedger,
|
||||
bool believedCorrect);
|
||||
getPrevLedger(
|
||||
uint256 ledgerID,
|
||||
RCLCxLedger const& ledger,
|
||||
Mode mode);
|
||||
|
||||
|
||||
/** Notification that the ledger has closed.
|
||||
/** Close the open ledger and return initial consensus position.
|
||||
|
||||
@param ledger the ledger we are changing to
|
||||
@param haveCorrectLCL whether we believe this is the correct LCL
|
||||
@param closeTime When consensus closed the ledger
|
||||
@param mode Current consensus mode
|
||||
@return Tentative consensus result
|
||||
*/
|
||||
void
|
||||
onClose(RCLCxLedger const & ledger, bool haveCorrectLCL);
|
||||
Result
|
||||
onClose(
|
||||
RCLCxLedger const& ledger,
|
||||
NetClock::time_point const& closeTime,
|
||||
Mode mode);
|
||||
|
||||
/** Create our initial position of transactions to accept in this round
|
||||
of consensus.
|
||||
|
||||
@param prevLedger The ledger the transactions apply to
|
||||
@param isProposing Whether we are currently proposing
|
||||
@param isCorrectLCL Whether we have the correct LCL
|
||||
@param closeTime When we believe the ledger closed
|
||||
@param now The current network adjusted time
|
||||
|
||||
@return Pair of (i) transactions we believe are in the ledger
|
||||
(ii) the corresponding proposal of those transactions
|
||||
to send to peers
|
||||
*/
|
||||
std::pair <RCLTxSet, typename RCLCxPeerPos::Proposal>
|
||||
makeInitialPosition (
|
||||
RCLCxLedger const & prevLedger,
|
||||
bool isProposing,
|
||||
bool isCorrectLCL,
|
||||
NetClock::time_point closeTime,
|
||||
NetClock::time_point now);
|
||||
|
||||
|
||||
/** Dispatch a call to Consensus::accept
|
||||
/** Process the accepted ledger.
|
||||
|
||||
Accepting a ledger may be expensive, so this function can dispatch
|
||||
that call to another thread if desired and must call the accept
|
||||
method of the generic consensus algorithm.
|
||||
that call to another thread if desired.
|
||||
|
||||
@param txSet The transactions to accept.
|
||||
@param result The result of consensus
|
||||
@param prevLedger The closed ledger consensus worked from
|
||||
@param closeResolution The resolution used in agreeing on an effective
|
||||
closeTiem
|
||||
@param rawCloseTimes The unrounded closetimes of ourself and our peers
|
||||
@param mode Our participating mode at the time consensus was declared
|
||||
*/
|
||||
void
|
||||
dispatchAccept(RCLTxSet const & txSet);
|
||||
onAccept(
|
||||
Result const& result,
|
||||
RCLCxLedger const& prevLedger,
|
||||
NetClock::duration const & closeResolution,
|
||||
CloseTimes const& rawCloseTimes,
|
||||
Mode const& mode);
|
||||
|
||||
/** Process the accepted ledger that was a result of simulation/force
|
||||
accept.
|
||||
|
||||
/** Accept a new ledger based on the given transactions.
|
||||
|
||||
TODO: Too many arguments, need to group related types.
|
||||
|
||||
@param set The set of accepted transactions
|
||||
@param consensusCloseTime Consensus agreed upon close time
|
||||
@param proposing_ Whether we are proposing
|
||||
@param validating_ Whether we are validating
|
||||
@param haveCorrectLCL_ Whether we had the correct last closed ledger
|
||||
@param consensusFail_ Whether consensus failed
|
||||
@param prevLedgerHash_ The hash/id of the previous ledger
|
||||
@param previousLedger_ The previous ledger
|
||||
@param closeResolution_ The close time resolution used this round
|
||||
@param now Current network adjsuted time
|
||||
@param roundTime_ Duration of this consensus round
|
||||
@param disputes_ Disputed trarnsactions from this round
|
||||
@param closeTimes_ Histogram of peers close times
|
||||
@param closeTime Our close time
|
||||
@return Whether we should continue validating
|
||||
*/
|
||||
bool
|
||||
accept(
|
||||
RCLTxSet const& set,
|
||||
NetClock::time_point consensusCloseTime,
|
||||
bool proposing_,
|
||||
bool validating_,
|
||||
bool haveCorrectLCL_,
|
||||
bool consensusFail_,
|
||||
LedgerHash const &prevLedgerHash_,
|
||||
RCLCxLedger const & previousLedger_,
|
||||
NetClock::duration closeResolution_,
|
||||
NetClock::time_point const & now,
|
||||
std::chrono::milliseconds const & roundTime_,
|
||||
hash_map<RCLCxTx::ID, DisputedTx <RCLCxTx, NodeID>> const & disputes_,
|
||||
std::map <NetClock::time_point, int> closeTimes_,
|
||||
NetClock::time_point const & closeTime
|
||||
);
|
||||
|
||||
/** Signal the end of consensus to the application, which will start the
|
||||
next round.
|
||||
|
||||
@param correctLCL Whether we believe we have the correct LCL
|
||||
@ref onAccept
|
||||
*/
|
||||
void
|
||||
endConsensus(bool correctLCL);
|
||||
onForceAccept(
|
||||
Result const& result,
|
||||
RCLCxLedger const& prevLedger,
|
||||
NetClock::duration const &closeResolution,
|
||||
CloseTimes const& rawCloseTimes,
|
||||
Mode const& mode);
|
||||
|
||||
//!-------------------------------------------------------------------------
|
||||
// Additional members (not directly required by Consensus interface)
|
||||
@@ -308,42 +304,53 @@ private:
|
||||
@param haveCorrectLCL Whether we believ we have the correct LCL.
|
||||
*/
|
||||
void
|
||||
notify(protocol::NodeEvent ne, RCLCxLedger const & ledger, bool haveCorrectLCL);
|
||||
notify(
|
||||
protocol::NodeEvent ne,
|
||||
RCLCxLedger const& ledger,
|
||||
bool haveCorrectLCL);
|
||||
|
||||
/** 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
|
||||
the last closed ledger. Since consensus just agrees on which
|
||||
transactions to apply, but not whether they make it into the closed
|
||||
ledger, this function also populates retriableTxs with those that can
|
||||
be retried in the next round.
|
||||
@ref onAccept
|
||||
*/
|
||||
void
|
||||
doAccept(
|
||||
Result const& result,
|
||||
RCLCxLedger const& prevLedger,
|
||||
NetClock::duration closeResolution,
|
||||
CloseTimes const& rawCloseTimes,
|
||||
Mode const& mode);
|
||||
|
||||
@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 now Current network adjusted time
|
||||
@param roundTime Duration of this consensus rorund
|
||||
@param retriableTxs Populate with transactions to retry in next round
|
||||
@return The newly built ledger
|
||||
*/
|
||||
/** Build the new last closed ledger.
|
||||
|
||||
Accept the given the provided set of consensus transactions and build
|
||||
the last closed ledger. Since consensus just agrees on which
|
||||
transactions to apply, but not whether they make it into the closed
|
||||
ledger, this function also populates retriableTxs with those that can
|
||||
be retried in the next round.
|
||||
|
||||
@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
|
||||
buildLCL(
|
||||
RCLCxLedger const & previousLedger,
|
||||
RCLTxSet const & set,
|
||||
RCLCxLedger const& previousLedger,
|
||||
RCLTxSet const& set,
|
||||
NetClock::time_point closeTime,
|
||||
bool closeTimeCorrect,
|
||||
NetClock::duration closeResolution,
|
||||
NetClock::time_point now,
|
||||
std::chrono::milliseconds roundTime,
|
||||
CanonicalTXSet & retriableTxs
|
||||
);
|
||||
CanonicalTXSet& retriableTxs);
|
||||
|
||||
/** Validate the given ledger and share with peers as necessary
|
||||
|
||||
@param ledger The ledger to validate
|
||||
@param now Current network adjusted time
|
||||
@param proposing Whether we were proposing transactions while generating
|
||||
this ledger. If we are not proposing, a validation
|
||||
can still be sent to inform peers that we know we
|
||||
@@ -351,16 +358,13 @@ private:
|
||||
around and trying to catch up.
|
||||
*/
|
||||
void
|
||||
validate(
|
||||
RCLCxLedger const & ledger,
|
||||
NetClock::time_point now,
|
||||
bool proposing);
|
||||
validate(RCLCxLedger const& ledger, bool proposing);
|
||||
|
||||
//!-------------------------------------------------------------------------
|
||||
Application& app_;
|
||||
std::unique_ptr <FeeVote> feeVote_;
|
||||
LedgerMaster & ledgerMaster_;
|
||||
LocalTxs & localTxs_;
|
||||
std::unique_ptr<FeeVote> feeVote_;
|
||||
LedgerMaster& ledgerMaster_;
|
||||
LocalTxs& localTxs_;
|
||||
InboundTransactions& inboundTransactions_;
|
||||
beast::Journal j_;
|
||||
|
||||
@@ -373,11 +377,13 @@ private:
|
||||
// only used for our own validations.
|
||||
NetClock::time_point lastValidationTime_;
|
||||
|
||||
using PeerPositions = hash_map <NodeID, std::deque<RCLCxPeerPos::pointer>>;
|
||||
using PeerPositions = hash_map<NodeID, std::deque<RCLCxPeerPos::pointer>>;
|
||||
PeerPositions peerPositions_;
|
||||
std::mutex peerPositionsLock_;
|
||||
};
|
||||
|
||||
bool validating_ = false;
|
||||
bool simulating_ = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
#define RIPPLE_APP_CONSENSUS_RCLCXLEDGER_H_INCLUDED
|
||||
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <ripple/app/ledger/LedgerToJson.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <ripple/protocol/RippleLedgerHash.h>
|
||||
#include <memory>
|
||||
|
||||
@@ -50,24 +50,26 @@ public:
|
||||
|
||||
@param l The ledger to wrap.
|
||||
*/
|
||||
RCLCxLedger(std::shared_ptr<Ledger const> const & l) : ledger_{ l } {}
|
||||
RCLCxLedger(std::shared_ptr<Ledger const> const& l) : ledger_{l}
|
||||
{
|
||||
}
|
||||
|
||||
//! Sequence number of the ledger.
|
||||
auto const &
|
||||
auto const&
|
||||
seq() const
|
||||
{
|
||||
return ledger_->info().seq;
|
||||
}
|
||||
|
||||
//! Unique identifier (hash) of this ledger.
|
||||
auto const &
|
||||
auto const&
|
||||
id() const
|
||||
{
|
||||
return ledger_->info().hash;
|
||||
}
|
||||
|
||||
//! Unique identifier (hash) of this ledger's parent.
|
||||
auto const &
|
||||
auto const&
|
||||
parentID() const
|
||||
{
|
||||
return ledger_->info().parentHash;
|
||||
@@ -114,8 +116,6 @@ public:
|
||||
a new ledger from a readView?
|
||||
*/
|
||||
std::shared_ptr<Ledger const> ledger_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -19,28 +19,28 @@
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/consensus/RCLCxPeerPos.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/HashPrefix.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/Serializer.h>
|
||||
#include <ripple/protocol/digest.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// Used to construct received proposals
|
||||
RCLCxPeerPos::RCLCxPeerPos (
|
||||
PublicKey const& publicKey,
|
||||
Slice const& signature,
|
||||
uint256 const& suppression,
|
||||
Proposal && proposal)
|
||||
: proposal_{ std::move(proposal)}
|
||||
, mSuppression {suppression}
|
||||
RCLCxPeerPos::RCLCxPeerPos(
|
||||
PublicKey const& publicKey,
|
||||
Slice const& signature,
|
||||
uint256 const& suppression,
|
||||
Proposal&& proposal)
|
||||
: proposal_{std::move(proposal)}
|
||||
, mSuppression{suppression}
|
||||
, publicKey_{publicKey}
|
||||
, signature_{signature}
|
||||
{
|
||||
|
||||
}
|
||||
uint256 RCLCxPeerPos::getSigningHash () const
|
||||
uint256
|
||||
RCLCxPeerPos::getSigningHash() const
|
||||
{
|
||||
return sha512Half(
|
||||
HashPrefix::proposal,
|
||||
@@ -50,28 +50,25 @@ uint256 RCLCxPeerPos::getSigningHash () const
|
||||
proposal().position());
|
||||
}
|
||||
|
||||
bool RCLCxPeerPos::checkSign () const
|
||||
bool
|
||||
RCLCxPeerPos::checkSign() const
|
||||
{
|
||||
return verifyDigest (
|
||||
publicKey_,
|
||||
getSigningHash(),
|
||||
signature_,
|
||||
false);
|
||||
return verifyDigest(publicKey_, getSigningHash(), signature_, false);
|
||||
}
|
||||
|
||||
Json::Value RCLCxPeerPos::getJson () const
|
||||
Json::Value
|
||||
RCLCxPeerPos::getJson() const
|
||||
{
|
||||
auto ret = proposal().getJson();
|
||||
|
||||
if (publicKey_.size())
|
||||
ret[jss::peer_id] = toBase58 (
|
||||
TokenType::TOKEN_NODE_PUBLIC,
|
||||
publicKey_);
|
||||
ret[jss::peer_id] = toBase58(TokenType::TOKEN_NODE_PUBLIC, publicKey_);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint256 proposalUniqueId (
|
||||
uint256
|
||||
proposalUniqueId(
|
||||
uint256 const& proposeHash,
|
||||
uint256 const& previousLedger,
|
||||
std::uint32_t proposeSeq,
|
||||
@@ -79,15 +76,15 @@ uint256 proposalUniqueId (
|
||||
Slice const& publicKey,
|
||||
Slice const& signature)
|
||||
{
|
||||
Serializer s (512);
|
||||
s.add256 (proposeHash);
|
||||
s.add256 (previousLedger);
|
||||
s.add32 (proposeSeq);
|
||||
s.add32 (closeTime.time_since_epoch().count());
|
||||
s.addVL (publicKey);
|
||||
s.addVL (signature);
|
||||
Serializer s(512);
|
||||
s.add256(proposeHash);
|
||||
s.add256(previousLedger);
|
||||
s.add32(proposeSeq);
|
||||
s.add32(closeTime.time_since_epoch().count());
|
||||
s.addVL(publicKey);
|
||||
s.addVL(signature);
|
||||
|
||||
return s.getSHA512Half ();
|
||||
return s.getSHA512Half();
|
||||
}
|
||||
|
||||
} // ripple
|
||||
} // ripple
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
#include <ripple/basics/CountedObject.h>
|
||||
#include <ripple/basics/base_uint.h>
|
||||
#include <ripple/beast/hash/hash_append.h>
|
||||
#include <ripple/consensus/ConsensusProposal.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/protocol/HashPrefix.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/SecretKey.h>
|
||||
#include <ripple/beast/hash/hash_append.h>
|
||||
#include <ripple/consensus/ConsensusProposal.h>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -38,18 +38,20 @@ namespace ripple {
|
||||
|
||||
Carries a ConsensusProposal signed by a peer.
|
||||
*/
|
||||
class RCLCxPeerPos
|
||||
: public CountedObject <RCLCxPeerPos>
|
||||
class RCLCxPeerPos : public CountedObject<RCLCxPeerPos>
|
||||
{
|
||||
public:
|
||||
static char const* getCountedObjectName () { return "RCLCxPeerPos"; }
|
||||
static char const*
|
||||
getCountedObjectName()
|
||||
{
|
||||
return "RCLCxPeerPos";
|
||||
}
|
||||
using pointer = std::shared_ptr<RCLCxPeerPos>;
|
||||
using ref = const pointer&;
|
||||
|
||||
//< The type of the proposed position
|
||||
using Proposal = ConsensusProposal<NodeID, uint256, uint256>;
|
||||
|
||||
|
||||
/** Constructor
|
||||
|
||||
Constructs a signed peer position.
|
||||
@@ -60,57 +62,64 @@ public:
|
||||
@param proposal The consensus proposal
|
||||
*/
|
||||
|
||||
RCLCxPeerPos (
|
||||
RCLCxPeerPos(
|
||||
PublicKey const& publicKey,
|
||||
Slice const& signature,
|
||||
uint256 const& suppress,
|
||||
Proposal && proposal);
|
||||
Proposal&& proposal);
|
||||
|
||||
//! Create the signing hash for the proposal
|
||||
uint256 getSigningHash () const;
|
||||
uint256
|
||||
getSigningHash() const;
|
||||
|
||||
//! Verify the signing hash of the proposal
|
||||
bool checkSign () const;
|
||||
bool
|
||||
checkSign() const;
|
||||
|
||||
//! Signature of the proposal (not necessarily verified)
|
||||
Slice getSignature () const
|
||||
Slice
|
||||
getSignature() const
|
||||
{
|
||||
return signature_;
|
||||
}
|
||||
|
||||
//! Public key of peer that sent the proposal
|
||||
PublicKey const& getPublicKey () const
|
||||
PublicKey const&
|
||||
getPublicKey() const
|
||||
{
|
||||
return publicKey_;
|
||||
}
|
||||
|
||||
//! ?????
|
||||
uint256 const& getSuppressionID () const
|
||||
uint256 const&
|
||||
getSuppressionID() const
|
||||
{
|
||||
return mSuppression;
|
||||
}
|
||||
|
||||
//! The consensus proposal
|
||||
Proposal const & proposal() const
|
||||
Proposal const&
|
||||
proposal() const
|
||||
{
|
||||
return proposal_;
|
||||
}
|
||||
|
||||
/// @cond Ignore
|
||||
//! Add a conversion operator to conform to the Consensus interface
|
||||
operator Proposal const &() const
|
||||
operator Proposal const&() const
|
||||
{
|
||||
return proposal_;
|
||||
}
|
||||
/// @endcond
|
||||
|
||||
//! JSON representation of proposal
|
||||
Json::Value getJson () const;
|
||||
Json::Value
|
||||
getJson() const;
|
||||
|
||||
private:
|
||||
template <class Hasher>
|
||||
void
|
||||
hash_append (Hasher& h) const
|
||||
hash_append(Hasher& h) const
|
||||
{
|
||||
using beast::hash_append;
|
||||
hash_append(h, HashPrefix::proposal);
|
||||
@@ -142,14 +151,15 @@ private:
|
||||
@param publicKey Signer's public key
|
||||
@param signature Proposal signature
|
||||
*/
|
||||
uint256 proposalUniqueId (
|
||||
uint256 const& proposeHash,
|
||||
uint256 const& previousLedger,
|
||||
std::uint32_t proposeSeq,
|
||||
NetClock::time_point closeTime,
|
||||
Slice const& publicKey,
|
||||
Slice const& signature);
|
||||
uint256
|
||||
proposalUniqueId(
|
||||
uint256 const& proposeHash,
|
||||
uint256 const& previousLedger,
|
||||
std::uint32_t proposeSeq,
|
||||
NetClock::time_point closeTime,
|
||||
Slice const& publicKey,
|
||||
Slice const& signature);
|
||||
|
||||
} // ripple
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
#ifndef RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED
|
||||
#define RIPPLE_APP_CONSENSUS_RCLCXTX_H_INCLUDED
|
||||
|
||||
#include <ripple/app/misc/CanonicalTXSet.h>
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <ripple/protocol/UintTypes.h>
|
||||
#include <ripple/shamap/SHAMap.h>
|
||||
#include <ripple/app/misc/CanonicalTXSet.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -42,14 +42,15 @@ public:
|
||||
|
||||
@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
|
||||
ID const&
|
||||
id() const
|
||||
{
|
||||
return tx_.key ();
|
||||
return tx_.key();
|
||||
}
|
||||
|
||||
//! The SHAMapItem that represents the transaction.
|
||||
@@ -74,13 +75,11 @@ public:
|
||||
{
|
||||
friend class RCLTxSet;
|
||||
//! The SHAMap representing the transactions.
|
||||
std::shared_ptr <SHAMap> map_;
|
||||
std::shared_ptr<SHAMap> map_;
|
||||
|
||||
public:
|
||||
MutableTxSet(RCLTxSet const & src)
|
||||
: map_{ src.map_->snapShot(true) }
|
||||
MutableTxSet(RCLTxSet const& src) : map_{src.map_->snapShot(true)}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/** Insert a new transaction into the set.
|
||||
@@ -92,8 +91,7 @@ public:
|
||||
insert(Tx const& t)
|
||||
{
|
||||
return map_->addItem(
|
||||
SHAMapItem{ t.id(), t.tx_.peekData() },
|
||||
true, false);
|
||||
SHAMapItem{t.id(), t.tx_.peekData()}, true, false);
|
||||
}
|
||||
|
||||
/** Remove a transaction from the set.
|
||||
@@ -112,8 +110,7 @@ public:
|
||||
|
||||
@param m SHAMap to wrap
|
||||
*/
|
||||
RCLTxSet (std::shared_ptr<SHAMap> m)
|
||||
: map_{ std::move(m) }
|
||||
RCLTxSet(std::shared_ptr<SHAMap> m) : map_{std::move(m)}
|
||||
{
|
||||
assert(map_);
|
||||
}
|
||||
@@ -122,10 +119,8 @@ public:
|
||||
|
||||
@param m MutableTxSet that will become fixed
|
||||
*/
|
||||
RCLTxSet(MutableTxSet const & m)
|
||||
: map_{m.map_->snapShot(false)}
|
||||
RCLTxSet(MutableTxSet const& m) : map_{m.map_->snapShot(false)}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/** Test if a transaction is in the set.
|
||||
@@ -136,7 +131,7 @@ public:
|
||||
bool
|
||||
exists(Tx::ID const& entry) const
|
||||
{
|
||||
return map_->hasItem (entry);
|
||||
return map_->hasItem(entry);
|
||||
}
|
||||
|
||||
/** Lookup a transaction.
|
||||
@@ -150,10 +145,10 @@ public:
|
||||
code use the shared_ptr semantics to know whether the find
|
||||
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
|
||||
{
|
||||
return map_->peekItem (entry);
|
||||
return map_->peekItem(entry);
|
||||
}
|
||||
|
||||
//! The unique ID/hash of the transaction set
|
||||
@@ -163,7 +158,8 @@ public:
|
||||
return map_->getHash().as_uint256();
|
||||
}
|
||||
|
||||
/** Find transactions not in common between this and another transaction set.
|
||||
/** Find transactions not in common between this and another transaction
|
||||
set.
|
||||
|
||||
@param j The set to compare with
|
||||
@return Map of transactions in this set and `j` but not both. The key
|
||||
@@ -171,28 +167,28 @@ public:
|
||||
exists in this set.
|
||||
*/
|
||||
std::map<Tx::ID, bool>
|
||||
compare (RCLTxSet const& j) const
|
||||
compare(RCLTxSet const& j) const
|
||||
{
|
||||
SHAMap::Delta delta;
|
||||
|
||||
// Bound the work we do in case of a malicious
|
||||
// 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)
|
||||
{
|
||||
assert ( (item.second.first && ! item.second.second) ||
|
||||
(item.second.second && ! item.second.first) );
|
||||
assert(
|
||||
(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;
|
||||
}
|
||||
|
||||
//! The SHAMap representing the transactions.
|
||||
std::shared_ptr <SHAMap> map_;
|
||||
std::shared_ptr<SHAMap> map_;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -322,7 +322,7 @@ private:
|
||||
|
||||
public:
|
||||
bool beginConsensus (uint256 const& networkClosed) override;
|
||||
void endConsensus (bool correctLCL) override;
|
||||
void endConsensus () override;
|
||||
void setStandAlone () override
|
||||
{
|
||||
setMode (omFULL);
|
||||
@@ -1525,7 +1525,7 @@ bool NetworkOPsImp::beginConsensus (uint256 const& networkClosed)
|
||||
|
||||
uint256 NetworkOPsImp::getConsensusLCL ()
|
||||
{
|
||||
return mConsensus->LCL ();
|
||||
return mConsensus->prevLedgerID ();
|
||||
}
|
||||
|
||||
void NetworkOPsImp::processTrustedProposal (
|
||||
@@ -1565,7 +1565,7 @@ NetworkOPsImp::mapComplete (
|
||||
RCLTxSet{map});
|
||||
}
|
||||
|
||||
void NetworkOPsImp::endConsensus (bool correctLCL)
|
||||
void NetworkOPsImp::endConsensus ()
|
||||
{
|
||||
uint256 deadLedger = m_ledgerMaster.getClosedLedger ()->info().parentHash;
|
||||
|
||||
@@ -2164,18 +2164,18 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin)
|
||||
info[jss::peers] = Json::UInt (app_.overlay ().size ());
|
||||
|
||||
Json::Value lastClose = Json::objectValue;
|
||||
lastClose[jss::proposers] = mConsensus->getLastCloseProposers();
|
||||
lastClose[jss::proposers] = Json::UInt(mConsensus->prevProposers());
|
||||
|
||||
if (human)
|
||||
{
|
||||
lastClose[jss::converge_time_s] =
|
||||
std::chrono::duration<double>{
|
||||
mConsensus->getLastConvergeDuration()}.count();
|
||||
mConsensus->prevRoundTime()}.count();
|
||||
}
|
||||
else
|
||||
{
|
||||
lastClose[jss::converge_time] =
|
||||
Json::Int (mConsensus->getLastConvergeDuration().count());
|
||||
Json::Int (mConsensus->prevRoundTime().count());
|
||||
}
|
||||
|
||||
info[jss::last_close] = lastClose;
|
||||
@@ -2754,9 +2754,7 @@ std::uint32_t NetworkOPsImp::acceptLedger (
|
||||
// FIXME Could we improve on this and remove the need for a specialized
|
||||
// API in Consensus?
|
||||
beginConsensus (m_ledgerMaster.getClosedLedger()->info().hash);
|
||||
mConsensus->simulate (
|
||||
app_.timeKeeper().closeTime(),
|
||||
consensusDelay);
|
||||
mConsensus->simulate (app_.timeKeeper().closeTime(), consensusDelay);
|
||||
return m_ledgerMaster.getCurrentLedger ()->info().seq;
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ public:
|
||||
|
||||
// network state machine
|
||||
virtual bool beginConsensus (uint256 const& netLCL) = 0;
|
||||
virtual void endConsensus (bool correctLCL) = 0;
|
||||
virtual void endConsensus () = 0;
|
||||
virtual void setStandAlone () = 0;
|
||||
virtual void setStateTimer () = 0;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2016 Ripple Labs Inc.
|
||||
Copyright (c) 2012-2017 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -19,13 +19,12 @@
|
||||
#ifndef RIPPLE_CONSENSUS_ConsensusProposal_H_INCLUDED
|
||||
#define RIPPLE_CONSENSUS_ConsensusProposal_H_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple
|
||||
{
|
||||
namespace ripple {
|
||||
/** Represents a proposed position taken during a round of consensus.
|
||||
|
||||
During consensus, peers seek agreement on a set of transactions to
|
||||
@@ -49,10 +48,7 @@ namespace ripple
|
||||
@tparam Position_t Type used to represent the position taken on transactions
|
||||
under consideration during this round of consensus
|
||||
*/
|
||||
template <
|
||||
class NodeID_t,
|
||||
class LedgerID_t,
|
||||
class Position_t>
|
||||
template <class NodeID_t, class LedgerID_t, class Position_t>
|
||||
class ConsensusProposal
|
||||
{
|
||||
public:
|
||||
@@ -64,7 +60,6 @@ public:
|
||||
//< Sequence number when a peer wants to bow out and leave consensus
|
||||
static std::uint32_t const seqLeave = 0xffffffff;
|
||||
|
||||
|
||||
/** Constructor
|
||||
|
||||
@param prevLedger The previous ledger this proposal is building on.
|
||||
@@ -81,33 +76,32 @@ public:
|
||||
NetClock::time_point closeTime,
|
||||
NetClock::time_point now,
|
||||
NodeID_t const& nodeID)
|
||||
: previousLedger_(prevLedger)
|
||||
, position_(position)
|
||||
, closeTime_(closeTime)
|
||||
, time_(now)
|
||||
, proposeSeq_(seq)
|
||||
, nodeID_(nodeID)
|
||||
: previousLedger_(prevLedger)
|
||||
, position_(position)
|
||||
, closeTime_(closeTime)
|
||||
, time_(now)
|
||||
, proposeSeq_(seq)
|
||||
, nodeID_(nodeID)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//! Identifying which peer took this position.
|
||||
NodeID_t const&
|
||||
nodeID () const
|
||||
nodeID() const
|
||||
{
|
||||
return nodeID_;
|
||||
}
|
||||
|
||||
//! Get the proposed position.
|
||||
Position_t const&
|
||||
position () const
|
||||
position() const
|
||||
{
|
||||
return position_;
|
||||
}
|
||||
|
||||
//! Get the prior accepted ledger this position is based on.
|
||||
LedgerID_t const&
|
||||
prevLedger () const
|
||||
prevLedger() const
|
||||
{
|
||||
return previousLedger_;
|
||||
}
|
||||
@@ -120,21 +114,21 @@ public:
|
||||
@return the sequence number
|
||||
*/
|
||||
std::uint32_t
|
||||
proposeSeq () const
|
||||
proposeSeq() const
|
||||
{
|
||||
return proposeSeq_;
|
||||
}
|
||||
|
||||
//! The current position on the consensus close time.
|
||||
NetClock::time_point const &
|
||||
closeTime () const
|
||||
NetClock::time_point const&
|
||||
closeTime() const
|
||||
{
|
||||
return closeTime_;
|
||||
}
|
||||
|
||||
//! Get when this position was taken.
|
||||
NetClock::time_point const &
|
||||
seenTime () const
|
||||
NetClock::time_point const&
|
||||
seenTime() const
|
||||
{
|
||||
return time_;
|
||||
}
|
||||
@@ -143,49 +137,43 @@ public:
|
||||
consensus round.
|
||||
*/
|
||||
bool
|
||||
isInitial () const
|
||||
isInitial() const
|
||||
{
|
||||
return proposeSeq_ == seqJoin;
|
||||
}
|
||||
|
||||
//! Get whether this node left the consensus process
|
||||
bool
|
||||
isBowOut () const
|
||||
isBowOut() const
|
||||
{
|
||||
return proposeSeq_ == seqLeave;
|
||||
}
|
||||
|
||||
//! Get whether this position is stale relative to the provided cutoff
|
||||
bool
|
||||
isStale (NetClock::time_point cutoff) const
|
||||
isStale(NetClock::time_point cutoff) const
|
||||
{
|
||||
return time_ <= cutoff;
|
||||
}
|
||||
|
||||
/** Update the position during the consensus process. This will increment
|
||||
the proposal's sequence number.
|
||||
the proposal's sequence number if it has not already bowed out.
|
||||
|
||||
@param newPosition The new position taken.
|
||||
@param newCloseTime The new close time.
|
||||
@param now the time The new position was taken.
|
||||
|
||||
@return `true` if the position was updated or `false` if this node has
|
||||
already left this consensus round.
|
||||
@param now the time The new position was taken
|
||||
*/
|
||||
bool
|
||||
void
|
||||
changePosition(
|
||||
Position_t const& newPosition,
|
||||
NetClock::time_point newCloseTime,
|
||||
NetClock::time_point now)
|
||||
{
|
||||
if (proposeSeq_ == seqLeave)
|
||||
return false;
|
||||
|
||||
position_ = newPosition;
|
||||
closeTime_ = newCloseTime;
|
||||
time_ = now;
|
||||
++proposeSeq_;
|
||||
return true;
|
||||
position_ = newPosition;
|
||||
closeTime_ = newCloseTime;
|
||||
time_ = now;
|
||||
if (proposeSeq_ != seqLeave)
|
||||
++proposeSeq_;
|
||||
}
|
||||
|
||||
/** Leave consensus
|
||||
@@ -197,32 +185,32 @@ public:
|
||||
void
|
||||
bowOut(NetClock::time_point now)
|
||||
{
|
||||
time_ = now;
|
||||
proposeSeq_ = seqLeave;
|
||||
time_ = now;
|
||||
proposeSeq_ = seqLeave;
|
||||
}
|
||||
|
||||
//! Get JSON representation for debugging
|
||||
Json::Value
|
||||
getJson () const
|
||||
getJson() const
|
||||
{
|
||||
using std::to_string;
|
||||
|
||||
Json::Value ret = Json::objectValue;
|
||||
ret[jss::previous_ledger] = to_string (prevLedger());
|
||||
ret[jss::previous_ledger] = to_string(prevLedger());
|
||||
|
||||
if (!isBowOut())
|
||||
{
|
||||
ret[jss::transaction_hash] = to_string (position());
|
||||
ret[jss::transaction_hash] = to_string(position());
|
||||
ret[jss::propose_seq] = proposeSeq();
|
||||
}
|
||||
|
||||
ret[jss::close_time] = to_string(closeTime().time_since_epoch().count());
|
||||
ret[jss::close_time] =
|
||||
to_string(closeTime().time_since_epoch().count());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
//! Unique identifier of prior ledger this proposal is based on
|
||||
LedgerID_t previousLedger_;
|
||||
|
||||
@@ -240,23 +228,17 @@ private:
|
||||
|
||||
//! The identifier of the node taking this position
|
||||
NodeID_t nodeID_;
|
||||
|
||||
};
|
||||
|
||||
template <class NodeID_t,
|
||||
class LedgerID_t,
|
||||
class Position_t>
|
||||
template <class NodeID_t, class LedgerID_t, class Position_t>
|
||||
bool
|
||||
operator==(ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const & a,
|
||||
ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const & b)
|
||||
operator==(
|
||||
ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const& a,
|
||||
ConsensusProposal<NodeID_t, LedgerID_t, Position_t> const& b)
|
||||
{
|
||||
return a.nodeID() == b.nodeID() &&
|
||||
a.proposeSeq() == b.proposeSeq() &&
|
||||
a.prevLedger() == b.prevLedger() &&
|
||||
a.position() == b.position() &&
|
||||
a.closeTime() == b.closeTime() &&
|
||||
a.seenTime() == b.seenTime();
|
||||
return a.nodeID() == b.nodeID() && a.proposeSeq() == b.proposeSeq() &&
|
||||
a.prevLedger() == b.prevLedger() && a.position() == b.position() &&
|
||||
a.closeTime() == b.closeTime() && a.seenTime() == b.seenTime();
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_APP_CONSENSUS_DISPUTEDTX_H_INCLUDED
|
||||
#define RIPPLE_APP_CONSENSUS_DISPUTEDTX_H_INCLUDED
|
||||
#ifndef RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
|
||||
#define RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
|
||||
|
||||
#include <ripple/protocol/UintTypes.h>
|
||||
#include <ripple/protocol/Serializer.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/base_uint.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <ripple/consensus/LedgerTiming.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/protocol/Serializer.h>
|
||||
#include <ripple/protocol/UintTypes.h>
|
||||
#include <memory>
|
||||
|
||||
namespace ripple {
|
||||
@@ -47,7 +47,8 @@ namespace ripple {
|
||||
template <class Tx_t, class NodeID_t>
|
||||
class DisputedTx
|
||||
{
|
||||
using TxID_t = typename Tx_t::ID;
|
||||
using TxID_t = typename Tx_t::ID;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
|
||||
@@ -55,41 +56,35 @@ public:
|
||||
@param ourVote Our vote on whether tx should be included
|
||||
@param j Journal for debugging
|
||||
*/
|
||||
DisputedTx (Tx_t const& tx,
|
||||
bool ourVote,
|
||||
beast::Journal j)
|
||||
: yays_ (0)
|
||||
, nays_ (0)
|
||||
, ourVote_ (ourVote)
|
||||
, tx_ (tx)
|
||||
, j_ (j)
|
||||
DisputedTx(Tx_t const& tx, bool ourVote, beast::Journal j)
|
||||
: yays_(0), nays_(0), ourVote_(ourVote), tx_(tx), j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
//! The unique id/hash of the disputed transaction.
|
||||
TxID_t
|
||||
const& ID () const
|
||||
TxID_t const&
|
||||
ID() const
|
||||
{
|
||||
return tx_.id();
|
||||
}
|
||||
|
||||
//! Our vote on whether the transaction should be included.
|
||||
bool
|
||||
getOurVote () const
|
||||
getOurVote() const
|
||||
{
|
||||
return ourVote_;
|
||||
}
|
||||
|
||||
//! The disputed transaction.
|
||||
Tx_t
|
||||
const& tx () const
|
||||
Tx_t const&
|
||||
tx() const
|
||||
{
|
||||
return tx_;
|
||||
}
|
||||
|
||||
//! Change our vote
|
||||
void
|
||||
setOurVote (bool o)
|
||||
setOurVote(bool o)
|
||||
{
|
||||
ourVote_ = o;
|
||||
}
|
||||
@@ -100,14 +95,14 @@ public:
|
||||
@param votesYes Whether peer votes to include the disputed transaction.
|
||||
*/
|
||||
void
|
||||
setVote (NodeID_t const& peer, bool votesYes);
|
||||
setVote(NodeID_t const& peer, bool votesYes);
|
||||
|
||||
/** Remove a peer's vote
|
||||
|
||||
@param peer Identifier of peer.
|
||||
*/
|
||||
void
|
||||
unVote (NodeID_t const& peer);
|
||||
unVote(NodeID_t const& peer);
|
||||
|
||||
/** Update our vote given progression of consensus.
|
||||
|
||||
@@ -120,49 +115,47 @@ public:
|
||||
@return Whether our vote changed
|
||||
*/
|
||||
bool
|
||||
updateVote (int percentTime, bool proposing);
|
||||
updateVote(int percentTime, bool proposing);
|
||||
|
||||
//! JSON representation of dispute, used for debugging
|
||||
Json::Value
|
||||
getJson () const;
|
||||
getJson() const;
|
||||
|
||||
private:
|
||||
int yays_; //< Number of yes votes
|
||||
int nays_; //< Number of no votes
|
||||
int yays_; //< Number of yes votes
|
||||
int nays_; //< Number of no votes
|
||||
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
|
||||
beast::Journal j_; //< Debug journal
|
||||
hash_map<NodeID_t, bool> votes_; //< Votes of our peers
|
||||
beast::Journal j_; //< Debug journal
|
||||
};
|
||||
|
||||
// Track a peer's yes/no vote on a particular disputed tx_
|
||||
template <class Tx_t, class NodeID_t>
|
||||
void DisputedTx<Tx_t, NodeID_t>::setVote (NodeID_t const& peer, bool votesYes)
|
||||
void
|
||||
DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
|
||||
{
|
||||
auto res = votes_.insert (std::make_pair (peer, votesYes));
|
||||
auto res = votes_.insert(std::make_pair(peer, votesYes));
|
||||
|
||||
// new vote
|
||||
if (res.second)
|
||||
{
|
||||
if (votesYes)
|
||||
{
|
||||
JLOG (j_.debug())
|
||||
<< "Peer " << peer << " votes YES on " << tx_.id();
|
||||
JLOG(j_.debug()) << "Peer " << peer << " votes YES on " << tx_.id();
|
||||
++yays_;
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG (j_.debug())
|
||||
<< "Peer " << peer << " votes NO on " << tx_.id();
|
||||
JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
|
||||
++nays_;
|
||||
}
|
||||
}
|
||||
// changes vote to yes
|
||||
else if (votesYes && !res.first->second)
|
||||
{
|
||||
JLOG (j_.debug())
|
||||
<< "Peer " << peer << " now votes YES on " << tx_.id();
|
||||
JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id();
|
||||
--nays_;
|
||||
++yays_;
|
||||
res.first->second = true;
|
||||
@@ -170,8 +163,7 @@ void DisputedTx<Tx_t, NodeID_t>::setVote (NodeID_t const& peer, bool votesYes)
|
||||
// changes vote to no
|
||||
else if (!votesYes && res.first->second)
|
||||
{
|
||||
JLOG (j_.debug())
|
||||
<< "Peer " << peer << " now votes NO on " << tx_.id();
|
||||
JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id();
|
||||
++nays_;
|
||||
--yays_;
|
||||
res.first->second = false;
|
||||
@@ -180,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
|
||||
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)
|
||||
--yays_;
|
||||
else
|
||||
--nays_;
|
||||
|
||||
votes_.erase (it);
|
||||
votes_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Tx_t, class NodeID_t>
|
||||
bool DisputedTx<Tx_t, NodeID_t>::updateVote (int percentTime, bool proposing)
|
||||
bool
|
||||
DisputedTx<Tx_t, NodeID_t>::updateVote(int percentTime, bool proposing)
|
||||
{
|
||||
if (ourVote_ && (nays_ == 0))
|
||||
return false;
|
||||
@@ -207,7 +201,7 @@ bool DisputedTx<Tx_t, NodeID_t>::updateVote (int percentTime, bool proposing)
|
||||
bool newPosition;
|
||||
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)
|
||||
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
|
||||
// over 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)
|
||||
newPosition = weight > AV_MID_CONSENSUS_PCT;
|
||||
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_)
|
||||
{
|
||||
JLOG (j_.info())
|
||||
<< "No change (" << (ourVote_ ? "YES" : "NO") << ") : weight "
|
||||
<< weight << ", percent " << percentTime;
|
||||
JLOG (j_.debug()) << getJson ();
|
||||
JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO")
|
||||
<< ") : weight " << weight << ", percent "
|
||||
<< percentTime;
|
||||
JLOG(j_.debug()) << getJson();
|
||||
return false;
|
||||
}
|
||||
|
||||
ourVote_ = newPosition;
|
||||
JLOG (j_.debug())
|
||||
<< "We now vote " << (ourVote_ ? "YES" : "NO")
|
||||
<< " on " << tx_.id();
|
||||
JLOG (j_.debug()) << getJson ();
|
||||
JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
|
||||
<< tx_.id();
|
||||
JLOG(j_.debug()) << getJson();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Tx_t, class NodeID_t>
|
||||
Json::Value DisputedTx<Tx_t, NodeID_t>::getJson () const
|
||||
Json::Value
|
||||
DisputedTx<Tx_t, NodeID_t>::getJson() const
|
||||
{
|
||||
using std::to_string;
|
||||
|
||||
Json::Value ret (Json::objectValue);
|
||||
Json::Value ret(Json::objectValue);
|
||||
|
||||
ret["yays"] = yays_;
|
||||
ret["nays"] = nays_;
|
||||
ret["our_vote"] = ourVote_;
|
||||
|
||||
if (!votes_.empty ())
|
||||
if (!votes_.empty())
|
||||
{
|
||||
Json::Value votesj (Json::objectValue);
|
||||
Json::Value votesj(Json::objectValue);
|
||||
for (auto& vote : votes_)
|
||||
votesj[to_string (vote.first)] = vote.second;
|
||||
ret["votes"] = std::move (votesj);
|
||||
votesj[to_string(vote.first)] = vote.second;
|
||||
ret["votes"] = std::move(votesj);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // ripple
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,66 +18,64 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/consensus/LedgerTiming.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/consensus/LedgerTiming.h>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
bool
|
||||
shouldCloseLedger (
|
||||
shouldCloseLedger(
|
||||
bool anyTransactions,
|
||||
std::size_t previousProposers,
|
||||
std::size_t prevProposers,
|
||||
std::size_t proposersClosed,
|
||||
std::size_t proposersValidated,
|
||||
std::chrono::milliseconds previousTime,
|
||||
std::chrono::milliseconds currentTime, // Time since last ledger's close time
|
||||
std::chrono::milliseconds openTime, // Time waiting to close this ledger
|
||||
std::chrono::milliseconds prevRoundTime,
|
||||
std::chrono::milliseconds
|
||||
timeSincePrevClose, // Time since last ledger's close time
|
||||
std::chrono::milliseconds openTime, // Time waiting to close this ledger
|
||||
std::chrono::seconds idleInterval,
|
||||
beast::Journal j)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
if ((previousTime < -1s) || (previousTime > 10min) ||
|
||||
(currentTime > 10min))
|
||||
if ((prevRoundTime < -1s) || (prevRoundTime > 10min) || (timeSincePrevClose > 10min))
|
||||
{
|
||||
// These are unexpected cases, we just close the ledger
|
||||
JLOG (j.warn()) <<
|
||||
"shouldCloseLedger Trans=" << (anyTransactions ? "yes" : "no") <<
|
||||
" Prop: " << previousProposers << "/" << proposersClosed <<
|
||||
" Secs: " << currentTime.count() << " (last: " <<
|
||||
previousTime.count() << ")";
|
||||
JLOG(j.warn()) << "shouldCloseLedger Trans="
|
||||
<< (anyTransactions ? "yes" : "no")
|
||||
<< " Prop: " << prevProposers << "/" << proposersClosed
|
||||
<< " Secs: " << timeSincePrevClose.count()
|
||||
<< " (last: " << prevRoundTime.count() << ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((proposersClosed + proposersValidated) > (previousProposers / 2))
|
||||
if ((proposersClosed + proposersValidated) > (prevProposers / 2))
|
||||
{
|
||||
// If more than half of the network has closed, we close
|
||||
JLOG (j.trace()) << "Others have closed";
|
||||
JLOG(j.trace()) << "Others have closed";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!anyTransactions)
|
||||
{
|
||||
// Only close at the end of the idle interval
|
||||
return currentTime >= idleInterval; // normal idle
|
||||
return timeSincePrevClose >= idleInterval; // normal idle
|
||||
}
|
||||
|
||||
// Preserve minimum ledger open time
|
||||
if (openTime < LEDGER_MIN_CLOSE)
|
||||
{
|
||||
JLOG (j.debug()) <<
|
||||
"Must wait minimum time before closing";
|
||||
JLOG(j.debug()) << "Must wait minimum time before closing";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't let this ledger close more than twice as fast as the previous
|
||||
// ledger reached consensus so that slower validators can slow down
|
||||
// the network
|
||||
if (openTime < (previousTime / 2))
|
||||
if (openTime < (prevRoundTime / 2))
|
||||
{
|
||||
JLOG (j.debug()) <<
|
||||
"Ledger has not been open long enough";
|
||||
JLOG(j.debug()) << "Ledger has not been open long enough";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -86,10 +84,7 @@ shouldCloseLedger (
|
||||
}
|
||||
|
||||
bool
|
||||
checkConsensusReached (
|
||||
std::size_t agreeing,
|
||||
std::size_t total,
|
||||
bool count_self)
|
||||
checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self)
|
||||
{
|
||||
// If we are alone, we have a consensus
|
||||
if (total == 0)
|
||||
@@ -107,8 +102,8 @@ checkConsensusReached (
|
||||
}
|
||||
|
||||
ConsensusState
|
||||
checkConsensus (
|
||||
std::size_t previousProposers,
|
||||
checkConsensus(
|
||||
std::size_t prevProposers,
|
||||
std::size_t currentProposers,
|
||||
std::size_t currentAgree,
|
||||
std::size_t currentFinished,
|
||||
@@ -117,47 +112,45 @@ checkConsensus (
|
||||
bool proposing,
|
||||
beast::Journal j)
|
||||
{
|
||||
JLOG (j.trace()) <<
|
||||
"checkConsensus: prop=" << currentProposers <<
|
||||
"/" << previousProposers <<
|
||||
" agree=" << currentAgree << " validated=" << currentFinished <<
|
||||
" time=" << currentAgreeTime.count() << "/" << previousAgreeTime.count();
|
||||
JLOG(j.trace()) << "checkConsensus: prop=" << currentProposers << "/"
|
||||
<< prevProposers << " agree=" << currentAgree
|
||||
<< " validated=" << currentFinished
|
||||
<< " time=" << currentAgreeTime.count() << "/"
|
||||
<< previousAgreeTime.count();
|
||||
|
||||
if (currentAgreeTime <= LEDGER_MIN_CONSENSUS)
|
||||
return ConsensusState::No;
|
||||
|
||||
if (currentProposers < (previousProposers * 3 / 4))
|
||||
if (currentProposers < (prevProposers * 3 / 4))
|
||||
{
|
||||
// Less than 3/4 of the last ledger's proposers are present; don't
|
||||
// rush: we may need more time.
|
||||
if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS))
|
||||
{
|
||||
JLOG (j.trace()) <<
|
||||
"too fast, not enough proposers";
|
||||
JLOG(j.trace()) << "too fast, not enough proposers";
|
||||
return ConsensusState::No;
|
||||
}
|
||||
}
|
||||
|
||||
// Have we, together with the nodes on our UNL list, reached the threshold
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Have sufficient nodes on our UNL list moved on and reached the threshold
|
||||
// to declare consensus?
|
||||
if (checkConsensusReached (currentFinished, currentProposers, false))
|
||||
if (checkConsensusReached(currentFinished, currentProposers, false))
|
||||
{
|
||||
JLOG (j.warn()) <<
|
||||
"We see no consensus, but 80% of nodes have moved on";
|
||||
JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on";
|
||||
return ConsensusState::MovedOn;
|
||||
}
|
||||
|
||||
// no consensus yet
|
||||
JLOG (j.trace()) << "no consensus";
|
||||
JLOG(j.trace()) << "no consensus";
|
||||
return ConsensusState::No;
|
||||
}
|
||||
|
||||
} // ripple
|
||||
} // ripple
|
||||
|
||||
@@ -20,14 +20,13 @@
|
||||
#ifndef RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
|
||||
#define RIPPLE_APP_LEDGER_LEDGERTIMING_H_INCLUDED
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <ripple/basics/chrono.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// These are protocol parameters used to control the behavior of the system and
|
||||
// they should not be changed arbitrarily.
|
||||
@@ -42,16 +41,10 @@ using namespace std::chrono_literals;
|
||||
@see getNextLedgerTimeResolution
|
||||
*/
|
||||
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.
|
||||
auto constexpr ledgerDefaultTimeResolution = ledgerPossibleTimeResolutions[2];
|
||||
#else
|
||||
// HH Remove this workaround of a VS bug when possible
|
||||
//! Initial resolution of ledger close time.
|
||||
auto constexpr ledgerDefaultTimeResolution = 30s;
|
||||
#endif
|
||||
|
||||
//! How often we increase the close time resolution (in numbers of ledgers)
|
||||
auto constexpr increaseLedgerTimeResolutionEvery = 8;
|
||||
@@ -161,23 +154,25 @@ getNextLedgerTimeResolution(
|
||||
bool previousAgree,
|
||||
std::uint32_t ledgerSeq)
|
||||
{
|
||||
assert (ledgerSeq);
|
||||
assert(ledgerSeq);
|
||||
|
||||
using namespace std::chrono;
|
||||
// Find the current resolution:
|
||||
auto iter = std::find (std::begin (ledgerPossibleTimeResolutions),
|
||||
std::end (ledgerPossibleTimeResolutions), previousResolution);
|
||||
assert (iter != std::end (ledgerPossibleTimeResolutions));
|
||||
auto iter = std::find(
|
||||
std::begin(ledgerPossibleTimeResolutions),
|
||||
std::end(ledgerPossibleTimeResolutions),
|
||||
previousResolution);
|
||||
assert(iter != std::end(ledgerPossibleTimeResolutions));
|
||||
|
||||
// This should never happen, but just as a precaution
|
||||
if (iter == std::end (ledgerPossibleTimeResolutions))
|
||||
if (iter == std::end(ledgerPossibleTimeResolutions))
|
||||
return previousResolution;
|
||||
|
||||
// If we did not previously agree, we try to decrease the resolution to
|
||||
// improve the chance that we will agree now.
|
||||
if (!previousAgree && ledgerSeq % decreaseLedgerTimeResolutionEvery == 0)
|
||||
{
|
||||
if (++iter != std::end (ledgerPossibleTimeResolutions))
|
||||
if (++iter != std::end(ledgerPossibleTimeResolutions))
|
||||
return *iter;
|
||||
}
|
||||
|
||||
@@ -185,7 +180,7 @@ getNextLedgerTimeResolution(
|
||||
// if we can continue to agree.
|
||||
if (previousAgree && ledgerSeq % increaseLedgerTimeResolutionEvery == 0)
|
||||
{
|
||||
if (iter-- != std::begin (ledgerPossibleTimeResolutions))
|
||||
if (iter-- != std::begin(ledgerPossibleTimeResolutions))
|
||||
return *iter;
|
||||
}
|
||||
|
||||
@@ -212,7 +207,6 @@ roundCloseTime(
|
||||
return closeTime - (closeTime.time_since_epoch() % closeResolution);
|
||||
}
|
||||
|
||||
|
||||
/** Calculate the effective ledger close time
|
||||
|
||||
After adjusting the ledger close time based on the current resolution, also
|
||||
@@ -223,16 +217,17 @@ roundCloseTime(
|
||||
@param priorCloseTime The close time of the prior ledger
|
||||
*/
|
||||
template <class time_point>
|
||||
time_point effectiveCloseTime(time_point closeTime,
|
||||
typename time_point::duration const resolution,
|
||||
time_point
|
||||
effCloseTime(
|
||||
time_point closeTime,
|
||||
typename time_point::duration const resolution,
|
||||
time_point priorCloseTime)
|
||||
{
|
||||
if (closeTime == time_point{})
|
||||
return closeTime;
|
||||
|
||||
return std::max<time_point>(
|
||||
roundCloseTime (closeTime, resolution),
|
||||
(priorCloseTime + 1s));
|
||||
roundCloseTime(closeTime, resolution), (priorCloseTime + 1s));
|
||||
}
|
||||
|
||||
/** Determines whether the current ledger should close at this time.
|
||||
@@ -241,30 +236,29 @@ time_point effectiveCloseTime(time_point closeTime,
|
||||
in progress, or when a transaction is received and no close is in progress.
|
||||
|
||||
@param anyTransactions indicates whether any transactions have been received
|
||||
@param previousProposers proposers in the last closing
|
||||
@param prevProposers proposers in the last closing
|
||||
@param proposersClosed proposers who have currently closed this ledger
|
||||
@param proposersValidated proposers who have validated the last closed
|
||||
ledger
|
||||
@param previousTime time for the previous ledger to reach consensus
|
||||
@param currentTime time since the previous ledger's
|
||||
(possibly rounded) close time
|
||||
@param openTime time waiting to close this ledger
|
||||
@param prevRoundTime time for the previous ledger to reach consensus
|
||||
@param timeSincePrevClose time since the previous ledger's (possibly rounded)
|
||||
close time
|
||||
@param openTime duration this ledger has been open
|
||||
@param idleInterval the network's desired idle interval
|
||||
@param j journal for logging
|
||||
*/
|
||||
bool
|
||||
shouldCloseLedger (
|
||||
shouldCloseLedger(
|
||||
bool anyTransactions,
|
||||
std::size_t previousProposers,
|
||||
std::size_t prevProposers,
|
||||
std::size_t proposersClosed,
|
||||
std::size_t proposersValidated,
|
||||
std::chrono::milliseconds previousTime,
|
||||
std::chrono::milliseconds currentTime, // Time since last ledger's close time
|
||||
std::chrono::milliseconds openTime, // Time waiting to close this ledger
|
||||
std::chrono::milliseconds prevRoundTime,
|
||||
std::chrono::milliseconds timeSincePrevClose,
|
||||
std::chrono::milliseconds openTime,
|
||||
std::chrono::seconds idleInterval,
|
||||
beast::Journal j);
|
||||
|
||||
|
||||
/** Determine if a consensus has been reached
|
||||
|
||||
This function determines if a consensus has been reached
|
||||
@@ -275,22 +269,18 @@ shouldCloseLedger (
|
||||
@return True if a consensus has been reached
|
||||
*/
|
||||
bool
|
||||
checkConsensusReached (
|
||||
std::size_t agreeing,
|
||||
std::size_t total,
|
||||
bool count_self);
|
||||
checkConsensusReached(std::size_t agreeing, std::size_t total, bool count_self);
|
||||
|
||||
/** Whether we have or don't have a consensus */
|
||||
enum class ConsensusState
|
||||
{
|
||||
No, //!< We do not have consensus
|
||||
MovedOn, //!< The network has consensus without us
|
||||
Yes //!< We have consensus along with the network
|
||||
enum class ConsensusState {
|
||||
No, //!< We do not have consensus
|
||||
MovedOn, //!< The network has consensus without us
|
||||
Yes //!< We have consensus along with the network
|
||||
};
|
||||
|
||||
/** Determine whether the network reached consensus and whether we joined.
|
||||
|
||||
@param previousProposers proposers in the last closing (not including us)
|
||||
@param prevProposers proposers in the last closing (not including us)
|
||||
@param currentProposers proposers in this closing so far (not including us)
|
||||
@param currentAgree proposers who agree with us
|
||||
@param currentFinished proposers who have validated a ledger after this one
|
||||
@@ -302,8 +292,8 @@ enum class ConsensusState
|
||||
@param j journal for logging
|
||||
*/
|
||||
ConsensusState
|
||||
checkConsensus (
|
||||
std::size_t previousProposers,
|
||||
checkConsensus(
|
||||
std::size_t prevProposers,
|
||||
std::size_t currentProposers,
|
||||
std::size_t currentAgree,
|
||||
std::size_t currentFinished,
|
||||
@@ -312,6 +302,6 @@ checkConsensus (
|
||||
bool proposing,
|
||||
beast::Journal j);
|
||||
|
||||
} // ripple
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,22 +17,20 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/beast/clock/manual_clock.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <ripple/consensus/Consensus.h>
|
||||
#include <ripple/consensus/ConsensusProposal.h>
|
||||
#include <ripple/beast/clock/manual_clock.h>
|
||||
#include <boost/function_output_iterator.hpp>
|
||||
#include <test/csf.h>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class Consensus_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
|
||||
void
|
||||
testStandalone()
|
||||
{
|
||||
@@ -41,21 +39,22 @@ public:
|
||||
auto tg = TrustGraph::makeComplete(1);
|
||||
Sim s(tg, topology(tg, fixed{LEDGER_GRANULARITY}));
|
||||
|
||||
auto & p = s.peers[0];
|
||||
auto& p = s.peers[0];
|
||||
|
||||
p.targetLedgers = 1;
|
||||
p.start();
|
||||
p.submit(Tx{ 1 });
|
||||
p.submit(Tx{1});
|
||||
|
||||
s.net.step();
|
||||
|
||||
// Inspect that the proper ledger was created
|
||||
BEAST_EXPECT(p.LCL().seq == 1);
|
||||
BEAST_EXPECT(p.LCL() == p.lastClosedLedger.id());
|
||||
BEAST_EXPECT(p.prevLedgerID().seq == 1);
|
||||
BEAST_EXPECT(p.prevLedgerID() == p.lastClosedLedger.id());
|
||||
BEAST_EXPECT(p.lastClosedLedger.id().txs.size() == 1);
|
||||
BEAST_EXPECT(p.lastClosedLedger.id().txs.find(Tx{ 1 })
|
||||
!= p.lastClosedLedger.id().txs.end());
|
||||
BEAST_EXPECT(p.getLastCloseProposers() == 0);
|
||||
BEAST_EXPECT(
|
||||
p.lastClosedLedger.id().txs.find(Tx{1}) !=
|
||||
p.lastClosedLedger.id().txs.end());
|
||||
BEAST_EXPECT(p.prevProposers() == 0);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -65,24 +64,25 @@ public:
|
||||
using namespace std::chrono;
|
||||
|
||||
auto tg = TrustGraph::makeComplete(5);
|
||||
Sim sim(tg,
|
||||
Sim sim(
|
||||
tg,
|
||||
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
||||
|
||||
// everyone submits their own ID as a TX and relay it to peers
|
||||
for (auto & p : sim.peers)
|
||||
for (auto& p : sim.peers)
|
||||
p.submit(Tx(p.id));
|
||||
|
||||
// Verify all peers have the same LCL and it has all the Txs
|
||||
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(p.getLastCloseProposers() == sim.peers.size() - 1);
|
||||
for(std::uint32_t i = 0; i < sim.peers.size(); ++i)
|
||||
BEAST_EXPECT(lgrID.txs.find(Tx{ i }) != lgrID.txs.end());
|
||||
BEAST_EXPECT(p.prevProposers() == sim.peers.size() - 1);
|
||||
for (std::uint32_t i = 0; i < sim.peers.size(); ++i)
|
||||
BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end());
|
||||
// Matches peer 0 ledger
|
||||
BEAST_EXPECT(lgrID.txs == sim.peers[0].LCL().txs);
|
||||
BEAST_EXPECT(lgrID.txs == sim.peers[0].prevLedgerID().txs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,70 +96,69 @@ public:
|
||||
// 1. The slow peer is participating in consensus
|
||||
// 2. The slow peer is just observing
|
||||
|
||||
for(auto isParticipant : {true, false})
|
||||
for (auto isParticipant : {true, false})
|
||||
{
|
||||
auto tg = TrustGraph::makeComplete(5);
|
||||
|
||||
Sim sim(tg, topology(tg,[](PeerID i, PeerID j)
|
||||
{
|
||||
auto delayFactor = (i == 0 || j == 0) ? 1.1 : 0.2;
|
||||
return round<milliseconds>(delayFactor* LEDGER_GRANULARITY);
|
||||
}));
|
||||
Sim sim(tg, topology(tg, [](PeerID i, PeerID j) {
|
||||
auto delayFactor = (i == 0 || j == 0) ? 1.1 : 0.2;
|
||||
return round<milliseconds>(
|
||||
delayFactor * LEDGER_GRANULARITY);
|
||||
}));
|
||||
|
||||
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
|
||||
for (auto & p : sim.peers)
|
||||
// All peers submit their own ID as a transaction and relay it to
|
||||
// peers
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
p.submit(Tx{ p.id });
|
||||
p.submit(Tx{p.id});
|
||||
}
|
||||
|
||||
sim.run(1);
|
||||
|
||||
// Verify all peers have same LCL but are missing transaction 0 which
|
||||
// was not received by all peers before the ledger closed
|
||||
for (auto & p : sim.peers)
|
||||
// Verify all peers have same LCL but are missing transaction 0
|
||||
// which was not received by all peers before the ledger closed
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
auto const &lgrID = p.LCL();
|
||||
auto const& lgrID = p.prevLedgerID();
|
||||
BEAST_EXPECT(lgrID.seq == 1);
|
||||
|
||||
|
||||
// If peer 0 is participating
|
||||
if(isParticipant)
|
||||
if (isParticipant)
|
||||
{
|
||||
BEAST_EXPECT(p.getLastCloseProposers()
|
||||
== sim.peers.size() - 1);
|
||||
// Peer 0 closes first because it sees a quorum of agreeing positions
|
||||
// from all other peers in one hop (1->0, 2->0, ..)
|
||||
// The other peers take an extra timer period before they find that
|
||||
// Peer 0 agrees with them ( 1->0->1, 2->0->2, ...)
|
||||
if(p.id != 0)
|
||||
BEAST_EXPECT(p.getLastConvergeDuration()
|
||||
> sim.peers[0].getLastConvergeDuration());
|
||||
BEAST_EXPECT(p.prevProposers() == sim.peers.size() - 1);
|
||||
// Peer 0 closes first because it sees a quorum of agreeing
|
||||
// positions from all other peers in one hop (1->0, 2->0,
|
||||
// ..) The other peers take an extra timer period before
|
||||
// they find that Peer 0 agrees with them ( 1->0->1,
|
||||
// 2->0->2, ...)
|
||||
if (p.id != 0)
|
||||
BEAST_EXPECT(
|
||||
p.prevRoundTime() > sim.peers[0].prevRoundTime());
|
||||
}
|
||||
else // peer 0 is not participating
|
||||
else // peer 0 is not participating
|
||||
{
|
||||
auto const proposers = p.getLastCloseProposers();
|
||||
if(p.id == 0)
|
||||
auto const proposers = p.prevProposers();
|
||||
if (p.id == 0)
|
||||
BEAST_EXPECT(proposers == sim.peers.size() - 1);
|
||||
else
|
||||
BEAST_EXPECT(proposers == sim.peers.size() - 2);
|
||||
|
||||
// so all peers should have closed together
|
||||
BEAST_EXPECT(p.getLastConvergeDuration()
|
||||
== sim.peers[0].getLastConvergeDuration());
|
||||
BEAST_EXPECT(
|
||||
p.prevRoundTime() == sim.peers[0].prevRoundTime());
|
||||
}
|
||||
|
||||
|
||||
BEAST_EXPECT(lgrID.txs.find(Tx{ 0 }) == lgrID.txs.end());
|
||||
for(std::uint32_t i = 1; i < sim.peers.size(); ++i)
|
||||
BEAST_EXPECT(lgrID.txs.find(Tx{ i }) != lgrID.txs.end());
|
||||
BEAST_EXPECT(lgrID.txs.find(Tx{0}) == lgrID.txs.end());
|
||||
for (std::uint32_t i = 1; i < sim.peers.size(); ++i)
|
||||
BEAST_EXPECT(lgrID.txs.find(Tx{i}) != lgrID.txs.end());
|
||||
// Matches peer 0 ledger
|
||||
BEAST_EXPECT(lgrID.txs == sim.peers[0].LCL().txs);
|
||||
BEAST_EXPECT(lgrID.txs == sim.peers[0].prevLedgerID().txs);
|
||||
}
|
||||
BEAST_EXPECT(sim.peers[0].openTxs.find(Tx{ 0 })
|
||||
!= sim.peers[0].openTxs.end());
|
||||
}
|
||||
BEAST_EXPECT(
|
||||
sim.peers[0].openTxs.find(Tx{0}) != sim.peers[0].openTxs.end());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -185,32 +184,35 @@ public:
|
||||
|
||||
// Complicating this matter is that nodes will ignore proposals
|
||||
// with times more than PROPOSE_FRESHNESS =20s in the past. So at
|
||||
// the minimum granularity, we have at most 3 types of skews (0s,10s,20s).
|
||||
// the minimum granularity, we have at most 3 types of skews
|
||||
// (0s,10s,20s).
|
||||
|
||||
// This test therefore has 6 nodes, with 2 nodes having each type of
|
||||
// skew. Then no majority (1/3 < 1/2) of nodes will agree on an
|
||||
// actual close time.
|
||||
|
||||
auto tg = TrustGraph::makeComplete(6);
|
||||
Sim sim(tg,
|
||||
Sim sim(
|
||||
tg,
|
||||
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
||||
|
||||
// Run consensus without skew until we have a short close time resolution
|
||||
while(sim.peers.front().lastClosedLedger.closeTimeResolution() >=
|
||||
PROPOSE_FRESHNESS)
|
||||
// Run consensus without skew until we have a short close time
|
||||
// resolution
|
||||
while (sim.peers.front().lastClosedLedger.closeTimeResolution() >=
|
||||
PROPOSE_FRESHNESS)
|
||||
sim.run(1);
|
||||
|
||||
// Introduce a shift on the time of half the peers
|
||||
sim.peers[0].clockSkew = PROPOSE_FRESHNESS/2;
|
||||
sim.peers[1].clockSkew = PROPOSE_FRESHNESS/2;
|
||||
sim.peers[0].clockSkew = PROPOSE_FRESHNESS / 2;
|
||||
sim.peers[1].clockSkew = PROPOSE_FRESHNESS / 2;
|
||||
sim.peers[2].clockSkew = PROPOSE_FRESHNESS;
|
||||
sim.peers[3].clockSkew = PROPOSE_FRESHNESS;
|
||||
|
||||
// Verify all peers have the same LCL and it has all the Txs
|
||||
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
|
||||
// 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:
|
||||
// 0 1 2 3 4 5 6 7 8 9
|
||||
//
|
||||
@@ -241,19 +242,21 @@ public:
|
||||
// since nodes 2-4 will validate a different ledger.
|
||||
|
||||
// Nodes 0-1 will acquire the proper ledger from the network and
|
||||
// resume consensus and eventually generate the dominant network ledger
|
||||
// resume consensus and eventually generate the dominant network
|
||||
// ledger
|
||||
|
||||
std::vector<UNL> unls;
|
||||
unls.push_back({2,3,4,5,6,7,8,9});
|
||||
unls.push_back({0,1,2,3,4});
|
||||
std::vector<int> membership(10,0);
|
||||
unls.push_back({2, 3, 4, 5, 6, 7, 8, 9});
|
||||
unls.push_back({0, 1, 2, 3, 4});
|
||||
std::vector<int> membership(10, 0);
|
||||
membership[0] = 1;
|
||||
membership[1] = 1;
|
||||
|
||||
TrustGraph tg{unls, membership};
|
||||
|
||||
// This topology can fork, which is why we are using it for this test.
|
||||
BEAST_EXPECT(tg.canFork(minimumConsensusPercentage/100.));
|
||||
// This topology can fork, which is why we are using it for this
|
||||
// test.
|
||||
BEAST_EXPECT(tg.canFork(minimumConsensusPercentage / 100.));
|
||||
|
||||
auto netDelay = round<milliseconds>(0.2 * LEDGER_GRANULARITY);
|
||||
Sim sim(tg, topology(tg, fixed{netDelay}));
|
||||
@@ -261,8 +264,9 @@ public:
|
||||
// initial round to set prior state
|
||||
sim.run(1);
|
||||
|
||||
// Nodes in smaller UNL have seen tx 0, nodes in other unl have seen tx 1
|
||||
for (auto & p : sim.peers)
|
||||
// Nodes in smaller UNL have seen tx 0, nodes in other unl have seen
|
||||
// tx 1
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
p.validationDelay = validationDelay;
|
||||
p.missingLedgerDelay = netDelay;
|
||||
@@ -272,16 +276,29 @@ public:
|
||||
p.openTxs.insert(Tx{1});
|
||||
}
|
||||
|
||||
// Run for 2 additional rounds
|
||||
// - One round to generate different ledgers
|
||||
// - One round to detect different prior ledgers (but still generate
|
||||
// wrong ones) and recover
|
||||
sim.run(2);
|
||||
// Run for additional rounds
|
||||
// With no validation delay, only 2 more rounds are needed.
|
||||
// 1. Round to generate different ledgers
|
||||
// 2. Round to detect different prior ledgers (but still generate
|
||||
// wrong ones) and recover within that round since wrong LCL
|
||||
// is detected before we close
|
||||
//
|
||||
// With a validation delay of LEDGER_MIN_CLOSE, we need 3 more
|
||||
// rounds.
|
||||
// 1. Round to generate different ledgers
|
||||
// 2. Round to detect different prior ledgers (but still generate
|
||||
// wrong ones) but end up declaring consensus on wrong LCL (but
|
||||
// with the right transaction set!). This is because we detect
|
||||
// the wrong LCL after we have closed the ledger, so we declare
|
||||
// consensus based solely on our peer proposals. But we haven't
|
||||
// had time to acquire the right LCL
|
||||
// 3. Round to correct
|
||||
sim.run(3);
|
||||
|
||||
bc::flat_map<int, bc::flat_set<Ledger::ID>> ledgers;
|
||||
for (auto & p : sim.peers)
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
for (auto const & l : p.ledgers)
|
||||
for (auto const& l : p.ledgers)
|
||||
{
|
||||
ledgers[l.first.seq].insert(l.first);
|
||||
}
|
||||
@@ -289,12 +306,19 @@ public:
|
||||
|
||||
BEAST_EXPECT(ledgers[0].size() == 1);
|
||||
BEAST_EXPECT(ledgers[1].size() == 1);
|
||||
BEAST_EXPECT(ledgers[2].size() == 2);
|
||||
BEAST_EXPECT(ledgers[3].size() == 1);
|
||||
|
||||
|
||||
if (validationDelay == 0s)
|
||||
{
|
||||
BEAST_EXPECT(ledgers[2].size() == 2);
|
||||
BEAST_EXPECT(ledgers[3].size() == 1);
|
||||
BEAST_EXPECT(ledgers[4].size() == 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(ledgers[2].size() == 2);
|
||||
BEAST_EXPECT(ledgers[3].size() == 2);
|
||||
BEAST_EXPECT(ledgers[4].size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Additional test engineered to switch LCL during the establish phase.
|
||||
// This was added to trigger a scenario that previously crashed, in which
|
||||
// switchLCL switched from establish to open phase, but still processed
|
||||
@@ -330,7 +354,7 @@ public:
|
||||
|
||||
// Check all peers recovered
|
||||
for (auto &p : sim.peers)
|
||||
BEAST_EXPECT(p.LCL() == sim.peers[0].LCL());
|
||||
BEAST_EXPECT(p.prevLedgerID() == sim.peers[0].prevLedgerID());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,43 +365,43 @@ public:
|
||||
using namespace std::chrono;
|
||||
|
||||
int numPeers = 10;
|
||||
for(int overlap = 0; overlap <= numPeers; ++overlap)
|
||||
for (int overlap = 0; overlap <= numPeers; ++overlap)
|
||||
{
|
||||
auto tg = TrustGraph::makeClique(numPeers, overlap);
|
||||
Sim sim(tg, topology(tg,
|
||||
fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
||||
Sim sim(
|
||||
tg,
|
||||
topology(
|
||||
tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)}));
|
||||
|
||||
// Initial round to set prior state
|
||||
sim.run(1);
|
||||
for (auto & p : sim.peers)
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
// Nodes have only seen transactions from their neighbors
|
||||
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});
|
||||
}
|
||||
sim.run(1);
|
||||
|
||||
|
||||
// See if the network forked
|
||||
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
|
||||
// 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
|
||||
if(overlap > 0.4 * numPeers)
|
||||
if (overlap > 0.4 * numPeers)
|
||||
BEAST_EXPECT(ledgers.size() == 1);
|
||||
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
|
||||
BEAST_EXPECT(ledgers.size() <= 3);
|
||||
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
|
||||
BEAST_EXPECT(ledgers.size() <= 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
simClockSkew()
|
||||
{
|
||||
@@ -396,52 +420,44 @@ public:
|
||||
|
||||
// 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);
|
||||
Sim sim(tg, topology(tg, [](PeerID i, PeerID)
|
||||
{
|
||||
return 200ms * (i + 1);
|
||||
}));
|
||||
|
||||
Sim sim(tg, topology(tg, [](PeerID i, PeerID) {
|
||||
return 200ms * (i + 1);
|
||||
}));
|
||||
|
||||
// all transactions submitted before starting
|
||||
// Initial round to set prior state
|
||||
sim.run(1);
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
// stagger start of consensus
|
||||
for (auto & p : sim.peers)
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
p.start();
|
||||
sim.net.step_for(stagger);
|
||||
}
|
||||
|
||||
// run until all peers have accepted all transactions
|
||||
sim.net.step_while([&]()
|
||||
{
|
||||
for(auto & p : sim.peers)
|
||||
{
|
||||
if(p.LCL().txs.size() != 1)
|
||||
sim.net.step_while([&]() {
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
if (p.prevLedgerID().txs.size() != 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
simScaleFree()
|
||||
{
|
||||
@@ -450,45 +466,46 @@ public:
|
||||
// Generate a quasi-random scale free network and simulate consensus
|
||||
// for a single transaction
|
||||
|
||||
int N = 100; // Peers
|
||||
int N = 100; // Peers
|
||||
|
||||
int numUNLs = 15; // UNL lists
|
||||
int minUNLSize = N/4, maxUNLSize = N / 2;
|
||||
int minUNLSize = N / 4, maxUNLSize = N / 2;
|
||||
|
||||
double transProb = 0.5;
|
||||
|
||||
std::mt19937_64 rng;
|
||||
|
||||
auto tg = TrustGraph::makeRandomRanked(N, numUNLs,
|
||||
PowerLawDistribution{1,3},
|
||||
std::uniform_int_distribution<>{minUNLSize, maxUNLSize},
|
||||
rng);
|
||||
auto tg = TrustGraph::makeRandomRanked(
|
||||
N,
|
||||
numUNLs,
|
||||
PowerLawDistribution{1, 3},
|
||||
std::uniform_int_distribution<>{minUNLSize, maxUNLSize},
|
||||
rng);
|
||||
|
||||
Sim sim{tg, topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)})};
|
||||
Sim sim{
|
||||
tg,
|
||||
topology(tg, fixed{round<milliseconds>(0.2 * LEDGER_GRANULARITY)})};
|
||||
|
||||
// Initial round to set prior state
|
||||
sim.run(1);
|
||||
|
||||
std::uniform_real_distribution<> u{};
|
||||
for (auto & p : sim.peers)
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
// 50-50 chance to have seen a transaction
|
||||
if(u(rng) >= transProb)
|
||||
if (u(rng) >= transProb)
|
||||
p.openTxs.insert(Tx{0});
|
||||
|
||||
}
|
||||
sim.run(1);
|
||||
|
||||
|
||||
// See if the network forked
|
||||
bc::flat_set<Ledger::ID> ledgers;
|
||||
for (auto & p : sim.peers)
|
||||
for (auto& p : sim.peers)
|
||||
{
|
||||
ledgers.insert(p.LCL());
|
||||
ledgers.insert(p.prevLedgerID());
|
||||
}
|
||||
|
||||
BEAST_EXPECT(ledgers.size() == 1);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
@@ -507,5 +524,5 @@ public:
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Consensus, consensus, ripple);
|
||||
} // test
|
||||
} // ripple
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -98,58 +98,54 @@ class BasicNetwork
|
||||
public:
|
||||
using peer_type = Peer;
|
||||
|
||||
using clock_type =
|
||||
beast::manual_clock<
|
||||
std::chrono::steady_clock>;
|
||||
using clock_type = beast::manual_clock<std::chrono::steady_clock>;
|
||||
|
||||
using duration =
|
||||
typename clock_type::duration;
|
||||
using duration = typename clock_type::duration;
|
||||
|
||||
using time_point =
|
||||
typename clock_type::time_point;
|
||||
using time_point = typename clock_type::time_point;
|
||||
|
||||
private:
|
||||
struct by_to_tag {};
|
||||
struct by_from_tag {};
|
||||
struct by_when_tag {};
|
||||
struct by_to_tag
|
||||
{
|
||||
};
|
||||
struct by_from_tag
|
||||
{
|
||||
};
|
||||
struct by_when_tag
|
||||
{
|
||||
};
|
||||
|
||||
using by_to_hook =
|
||||
boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<
|
||||
boost::intrusive::normal_link>,
|
||||
boost::intrusive::tag<by_to_tag>>;
|
||||
using by_to_hook = boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<boost::intrusive::normal_link>,
|
||||
boost::intrusive::tag<by_to_tag>>;
|
||||
|
||||
using by_from_hook =
|
||||
boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<
|
||||
boost::intrusive::normal_link>,
|
||||
boost::intrusive::tag<by_from_tag>>;
|
||||
using by_from_hook = boost::intrusive::list_base_hook<
|
||||
boost::intrusive::link_mode<boost::intrusive::normal_link>,
|
||||
boost::intrusive::tag<by_from_tag>>;
|
||||
|
||||
using by_when_hook =
|
||||
boost::intrusive::set_base_hook<
|
||||
boost::intrusive::link_mode<
|
||||
boost::intrusive::normal_link>>;
|
||||
using by_when_hook = boost::intrusive::set_base_hook<
|
||||
boost::intrusive::link_mode<boost::intrusive::normal_link>>;
|
||||
|
||||
struct msg
|
||||
: by_to_hook, by_from_hook, by_when_hook
|
||||
struct msg : by_to_hook, by_from_hook, by_when_hook
|
||||
{
|
||||
Peer to;
|
||||
Peer from;
|
||||
time_point when;
|
||||
|
||||
msg (msg const&) = delete;
|
||||
msg& operator= (msg const&) = delete;
|
||||
msg(msg const&) = delete;
|
||||
msg&
|
||||
operator=(msg const&) = delete;
|
||||
virtual ~msg() = default;
|
||||
virtual void operator()() const = 0;
|
||||
virtual void
|
||||
operator()() const = 0;
|
||||
|
||||
msg (Peer const& from_, Peer const& to_,
|
||||
time_point when_)
|
||||
msg(Peer const& from_, Peer const& to_, time_point when_)
|
||||
: to(to_), from(from_), when(when_)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
operator< (msg const& other) const
|
||||
operator<(msg const& other) const
|
||||
{
|
||||
return when < other.when;
|
||||
}
|
||||
@@ -162,24 +158,30 @@ private:
|
||||
Handler const h_;
|
||||
|
||||
public:
|
||||
msg_impl (msg_impl const&) = delete;
|
||||
msg_impl& operator= (msg_impl const&) = delete;
|
||||
msg_impl(msg_impl const&) = delete;
|
||||
msg_impl&
|
||||
operator=(msg_impl const&) = delete;
|
||||
|
||||
msg_impl (Peer const& from_, Peer const& to_,
|
||||
time_point when_, Handler&& h)
|
||||
: msg (from_, to_, when_)
|
||||
, h_ (std::move(h))
|
||||
msg_impl(
|
||||
Peer const& from_,
|
||||
Peer const& to_,
|
||||
time_point when_,
|
||||
Handler&& h)
|
||||
: msg(from_, to_, when_), h_(std::move(h))
|
||||
{
|
||||
}
|
||||
|
||||
msg_impl (Peer const& from_, Peer const& to_,
|
||||
time_point when_, Handler const& h)
|
||||
: msg (from_, to_, when_)
|
||||
, h_ (h)
|
||||
msg_impl(
|
||||
Peer const& from_,
|
||||
Peer const& to_,
|
||||
time_point when_,
|
||||
Handler const& h)
|
||||
: msg(from_, to_, when_), h_(h)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()() const override
|
||||
void
|
||||
operator()() const override
|
||||
{
|
||||
h_();
|
||||
}
|
||||
@@ -188,19 +190,19 @@ private:
|
||||
class queue_type
|
||||
{
|
||||
private:
|
||||
using by_to_list = typename
|
||||
boost::intrusive::make_list<msg,
|
||||
boost::intrusive::base_hook<by_to_hook>,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
using by_to_list = typename boost::intrusive::make_list<
|
||||
msg,
|
||||
boost::intrusive::base_hook<by_to_hook>,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
|
||||
using by_from_list = typename
|
||||
boost::intrusive::make_list<msg,
|
||||
boost::intrusive::base_hook<by_from_hook>,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
using by_from_list = typename boost::intrusive::make_list<
|
||||
msg,
|
||||
boost::intrusive::base_hook<by_from_hook>,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
|
||||
using by_when_set = typename
|
||||
boost::intrusive::make_multiset<msg,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
using by_when_set = typename boost::intrusive::make_multiset<
|
||||
msg,
|
||||
boost::intrusive::constant_time_size<false>>::type;
|
||||
|
||||
qalloc alloc_;
|
||||
by_when_set by_when_;
|
||||
@@ -208,14 +210,13 @@ private:
|
||||
std::unordered_map<Peer, by_from_list> by_from_;
|
||||
|
||||
public:
|
||||
using iterator =
|
||||
typename by_when_set::iterator;
|
||||
using iterator = typename by_when_set::iterator;
|
||||
|
||||
queue_type (queue_type const&) = delete;
|
||||
queue_type& operator= (queue_type const&) = delete;
|
||||
queue_type(queue_type const&) = delete;
|
||||
queue_type&
|
||||
operator=(queue_type const&) = delete;
|
||||
|
||||
explicit
|
||||
queue_type (qalloc const& alloc);
|
||||
explicit queue_type(qalloc const& alloc);
|
||||
|
||||
~queue_type();
|
||||
|
||||
@@ -230,14 +231,13 @@ private:
|
||||
|
||||
template <class Handler>
|
||||
typename by_when_set::iterator
|
||||
emplace (Peer const& from, Peer const& to,
|
||||
time_point when, Handler&& h);
|
||||
emplace(Peer const& from, Peer const& to, time_point when, Handler&& h);
|
||||
|
||||
void
|
||||
erase (iterator iter);
|
||||
erase(iterator iter);
|
||||
|
||||
void
|
||||
remove (Peer const& from, Peer const& to);
|
||||
remove(Peer const& from, Peer const& to);
|
||||
};
|
||||
|
||||
struct link_type
|
||||
@@ -245,15 +245,13 @@ private:
|
||||
bool inbound;
|
||||
duration delay;
|
||||
|
||||
link_type (bool inbound_, duration delay_)
|
||||
: inbound (inbound_)
|
||||
, delay (delay_)
|
||||
link_type(bool inbound_, duration delay_)
|
||||
: inbound(inbound_), delay(delay_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
using links_type =
|
||||
boost::container::flat_map<Peer, link_type>;
|
||||
using links_type = boost::container::flat_map<Peer, link_type>;
|
||||
|
||||
class link_transform;
|
||||
|
||||
@@ -265,8 +263,9 @@ private:
|
||||
std::unordered_map<Peer, links_type> links_;
|
||||
|
||||
public:
|
||||
BasicNetwork (BasicNetwork const&) = delete;
|
||||
BasicNetwork& operator= (BasicNetwork const&) = delete;
|
||||
BasicNetwork(BasicNetwork const&) = delete;
|
||||
BasicNetwork&
|
||||
operator=(BasicNetwork const&) = delete;
|
||||
|
||||
BasicNetwork();
|
||||
|
||||
@@ -308,7 +307,9 @@ public:
|
||||
@return `true` if a new connection was established
|
||||
*/
|
||||
bool
|
||||
connect (Peer const& from, Peer const& to,
|
||||
connect(
|
||||
Peer const& from,
|
||||
Peer const& to,
|
||||
duration const& delay = std::chrono::seconds{0});
|
||||
|
||||
/** Break a link.
|
||||
@@ -324,15 +325,14 @@ public:
|
||||
@return `true` if a connection was broken.
|
||||
*/
|
||||
bool
|
||||
disconnect (Peer const& peer1, Peer const& peer2);
|
||||
disconnect(Peer const& peer1, Peer const& peer2);
|
||||
|
||||
/** Return the range of active links.
|
||||
|
||||
@return A random access range.
|
||||
*/
|
||||
boost::transformed_range<
|
||||
link_transform, links_type>
|
||||
links (Peer const& from);
|
||||
boost::transformed_range<link_transform, links_type>
|
||||
links(Peer const& from);
|
||||
|
||||
/** Send a message to a peer.
|
||||
|
||||
@@ -354,8 +354,7 @@ public:
|
||||
*/
|
||||
template <class Function>
|
||||
void
|
||||
send (Peer const& from, Peer const& to,
|
||||
Function&& f);
|
||||
send(Peer const& from, Peer const& to, Function&& f);
|
||||
|
||||
// Used to cancel timers
|
||||
struct cancel_token;
|
||||
@@ -370,8 +369,7 @@ public:
|
||||
*/
|
||||
template <class Function>
|
||||
cancel_token
|
||||
timer (time_point const& when,
|
||||
Function&& f);
|
||||
timer(time_point const& when, Function&& f);
|
||||
|
||||
/** Deliver a timer notification.
|
||||
|
||||
@@ -383,8 +381,7 @@ public:
|
||||
*/
|
||||
template <class Function>
|
||||
cancel_token
|
||||
timer (duration const& delay,
|
||||
Function&& f);
|
||||
timer(duration const& delay, Function&& f);
|
||||
|
||||
/** Cancel a timer.
|
||||
|
||||
@@ -394,7 +391,7 @@ public:
|
||||
timer() which has not yet been invoked.
|
||||
*/
|
||||
void
|
||||
cancel (cancel_token const& token);
|
||||
cancel(cancel_token const& token);
|
||||
|
||||
/** Perform breadth-first search.
|
||||
|
||||
@@ -407,7 +404,7 @@ public:
|
||||
*/
|
||||
template <class Function>
|
||||
void
|
||||
bfs (Peer const& start, Function&& f);
|
||||
bfs(Peer const& start, Function&& f);
|
||||
|
||||
/** Run the network for up to one message.
|
||||
|
||||
@@ -448,7 +445,7 @@ public:
|
||||
*/
|
||||
template <class Function>
|
||||
bool
|
||||
step_while(Function && func);
|
||||
step_while(Function&& func);
|
||||
|
||||
/** Run the network until the specified time.
|
||||
|
||||
@@ -460,7 +457,7 @@ public:
|
||||
@return `true` if any messages remain.
|
||||
*/
|
||||
bool
|
||||
step_until (time_point const& until);
|
||||
step_until(time_point const& until);
|
||||
|
||||
/** Run the network until time has elapsed.
|
||||
|
||||
@@ -473,24 +470,21 @@ public:
|
||||
*/
|
||||
template <class Period, class Rep>
|
||||
bool
|
||||
step_for (std::chrono::duration<
|
||||
Period, Rep> const& amount);
|
||||
step_for(std::chrono::duration<Period, Rep> const& amount);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Peer>
|
||||
BasicNetwork<Peer>::queue_type::queue_type(
|
||||
qalloc const& alloc)
|
||||
: alloc_ (alloc)
|
||||
BasicNetwork<Peer>::queue_type::queue_type(qalloc const& alloc)
|
||||
: alloc_(alloc)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
BasicNetwork<Peer>::queue_type::~queue_type()
|
||||
{
|
||||
for(auto iter = by_when_.begin();
|
||||
iter != by_when_.end();)
|
||||
for (auto iter = by_when_.begin(); iter != by_when_.end();)
|
||||
{
|
||||
auto m = &*iter;
|
||||
++iter;
|
||||
@@ -500,27 +494,22 @@ BasicNetwork<Peer>::queue_type::~queue_type()
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
bool
|
||||
inline bool
|
||||
BasicNetwork<Peer>::queue_type::empty() const
|
||||
{
|
||||
return by_when_.empty();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::queue_type::begin() ->
|
||||
iterator
|
||||
inline auto
|
||||
BasicNetwork<Peer>::queue_type::begin() -> iterator
|
||||
{
|
||||
return by_when_.begin();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::queue_type::end() ->
|
||||
iterator
|
||||
inline auto
|
||||
BasicNetwork<Peer>::queue_type::end() -> iterator
|
||||
{
|
||||
return by_when_.end();
|
||||
}
|
||||
@@ -529,15 +518,14 @@ template <class Peer>
|
||||
template <class Handler>
|
||||
auto
|
||||
BasicNetwork<Peer>::queue_type::emplace(
|
||||
Peer const& from, Peer const& to, time_point when,
|
||||
Handler&& h) ->
|
||||
typename by_when_set::iterator
|
||||
Peer const& from,
|
||||
Peer const& to,
|
||||
time_point when,
|
||||
Handler&& h) -> typename by_when_set::iterator
|
||||
{
|
||||
using msg_type = msg_impl<
|
||||
std::decay_t<Handler>>;
|
||||
using msg_type = msg_impl<std::decay_t<Handler>>;
|
||||
auto const p = alloc_.alloc<msg_type>(1);
|
||||
auto& m = *new(p) msg_type(from, to,
|
||||
when, std::forward<Handler>(h));
|
||||
auto& m = *new (p) msg_type(from, to, when, std::forward<Handler>(h));
|
||||
if (to)
|
||||
by_to_[to].push_back(m);
|
||||
if (from)
|
||||
@@ -547,8 +535,7 @@ BasicNetwork<Peer>::queue_type::emplace(
|
||||
|
||||
template <class Peer>
|
||||
void
|
||||
BasicNetwork<Peer>::queue_type::erase(
|
||||
iterator iter)
|
||||
BasicNetwork<Peer>::queue_type::erase(iterator iter)
|
||||
{
|
||||
auto& m = *iter;
|
||||
if (iter->to)
|
||||
@@ -568,13 +555,11 @@ BasicNetwork<Peer>::queue_type::erase(
|
||||
|
||||
template <class Peer>
|
||||
void
|
||||
BasicNetwork<Peer>::queue_type::remove(
|
||||
Peer const& from, Peer const& to)
|
||||
BasicNetwork<Peer>::queue_type::remove(Peer const& from, Peer const& to)
|
||||
{
|
||||
{
|
||||
auto& list = by_to_[to];
|
||||
for(auto iter = list.begin();
|
||||
iter != list.end();)
|
||||
for (auto iter = list.begin(); iter != list.end();)
|
||||
{
|
||||
auto& m = *iter++;
|
||||
if (m.from == from)
|
||||
@@ -583,8 +568,7 @@ BasicNetwork<Peer>::queue_type::remove(
|
||||
}
|
||||
{
|
||||
auto& list = by_to_[from];
|
||||
for(auto iter = list.begin();
|
||||
iter != list.end();)
|
||||
for (auto iter = list.begin(); iter != list.end();)
|
||||
{
|
||||
auto& m = *iter++;
|
||||
if (m.from == to)
|
||||
@@ -603,8 +587,7 @@ private:
|
||||
Peer from_;
|
||||
|
||||
public:
|
||||
using argument_type =
|
||||
typename links_type::value_type;
|
||||
using argument_type = typename links_type::value_type;
|
||||
|
||||
class result_type
|
||||
{
|
||||
@@ -612,15 +595,14 @@ public:
|
||||
Peer to;
|
||||
bool inbound;
|
||||
|
||||
result_type (result_type const&) = default;
|
||||
result_type(result_type const&) = default;
|
||||
|
||||
result_type (BasicNetwork& net,
|
||||
Peer const& from, Peer const& to_,
|
||||
bool inbound_)
|
||||
: to(to_)
|
||||
, inbound(inbound_)
|
||||
, net_(net)
|
||||
, from_(from)
|
||||
result_type(
|
||||
BasicNetwork& net,
|
||||
Peer const& from,
|
||||
Peer const& to_,
|
||||
bool inbound_)
|
||||
: to(to_), inbound(inbound_), net_(net), from_(from)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -641,18 +623,14 @@ public:
|
||||
Peer from_;
|
||||
};
|
||||
|
||||
link_transform (BasicNetwork& net,
|
||||
Peer const& from)
|
||||
: net_(net)
|
||||
, from_(from)
|
||||
link_transform(BasicNetwork& net, Peer const& from) : net_(net), from_(from)
|
||||
{
|
||||
}
|
||||
|
||||
result_type const
|
||||
operator()(argument_type const& v) const
|
||||
{
|
||||
return result_type(net_, from_,
|
||||
v.first, v.second.inbound);
|
||||
return result_type(net_, from_, v.first, v.second.inbound);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -666,14 +644,13 @@ private:
|
||||
|
||||
public:
|
||||
cancel_token() = delete;
|
||||
cancel_token (cancel_token const&) = default;
|
||||
cancel_token& operator= (cancel_token const&) = default;
|
||||
cancel_token(cancel_token const&) = default;
|
||||
cancel_token&
|
||||
operator=(cancel_token const&) = default;
|
||||
|
||||
private:
|
||||
friend class BasicNetwork;
|
||||
cancel_token(typename
|
||||
queue_type::iterator iter)
|
||||
: iter_ (iter)
|
||||
cancel_token(typename queue_type::iterator iter) : iter_(iter)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -681,33 +658,27 @@ private:
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class Peer>
|
||||
BasicNetwork<Peer>::BasicNetwork()
|
||||
: queue_ (alloc_)
|
||||
BasicNetwork<Peer>::BasicNetwork() : queue_(alloc_)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
qalloc const&
|
||||
inline qalloc const&
|
||||
BasicNetwork<Peer>::alloc() const
|
||||
{
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::clock() const ->
|
||||
clock_type&
|
||||
inline auto
|
||||
BasicNetwork<Peer>::clock() const -> clock_type&
|
||||
{
|
||||
return clock_;
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::now() const ->
|
||||
time_point
|
||||
inline auto
|
||||
BasicNetwork<Peer>::now() const -> time_point
|
||||
{
|
||||
return clock_.now();
|
||||
}
|
||||
@@ -715,17 +686,16 @@ BasicNetwork<Peer>::now() const ->
|
||||
template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::connect(
|
||||
Peer const& from, Peer const& to,
|
||||
duration const& delay)
|
||||
Peer const& from,
|
||||
Peer const& to,
|
||||
duration const& delay)
|
||||
{
|
||||
if (to == from)
|
||||
return false;
|
||||
using namespace std;
|
||||
if (! links_[from].emplace(to,
|
||||
link_type{ false, delay }).second)
|
||||
if (!links_[from].emplace(to, link_type{false, delay}).second)
|
||||
return false;
|
||||
auto const result = links_[to].emplace(
|
||||
from, link_type{ true, delay });
|
||||
auto const result = links_[to].emplace(from, link_type{true, delay});
|
||||
(void)result;
|
||||
assert(result.second);
|
||||
return true;
|
||||
@@ -733,13 +703,11 @@ BasicNetwork<Peer>::connect(
|
||||
|
||||
template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::disconnect(
|
||||
Peer const& peer1, Peer const& peer2)
|
||||
BasicNetwork<Peer>::disconnect(Peer const& peer1, Peer const& peer2)
|
||||
{
|
||||
if (links_[peer1].erase(peer2) == 0)
|
||||
return false;
|
||||
auto const n =
|
||||
links_[peer2].erase(peer1);
|
||||
auto const n = links_[peer2].erase(peer1);
|
||||
(void)n;
|
||||
assert(n);
|
||||
queue_.remove(peer1, peer2);
|
||||
@@ -747,64 +715,45 @@ BasicNetwork<Peer>::disconnect(
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::links(Peer const& from) ->
|
||||
boost::transformed_range<
|
||||
link_transform, links_type>
|
||||
inline auto
|
||||
BasicNetwork<Peer>::links(Peer const& from)
|
||||
-> boost::transformed_range<link_transform, links_type>
|
||||
{
|
||||
return boost::adaptors::transform(
|
||||
links_[from],
|
||||
link_transform{ *this, from });
|
||||
links_[from], link_transform{*this, from});
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
inline
|
||||
void
|
||||
BasicNetwork<Peer>::send(
|
||||
Peer const& from, Peer const& to,
|
||||
Function&& f)
|
||||
inline void
|
||||
BasicNetwork<Peer>::send(Peer const& from, Peer const& to, Function&& f)
|
||||
{
|
||||
using namespace std;
|
||||
auto const iter =
|
||||
links_[from].find(to);
|
||||
queue_.emplace(from, to,
|
||||
clock_.now() + iter->second.delay,
|
||||
forward<Function>(f));
|
||||
auto const iter = links_[from].find(to);
|
||||
queue_.emplace(
|
||||
from, to, clock_.now() + iter->second.delay, forward<Function>(f));
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::timer(
|
||||
time_point const& when, Function&& f) ->
|
||||
cancel_token
|
||||
inline auto
|
||||
BasicNetwork<Peer>::timer(time_point const& when, Function&& f) -> cancel_token
|
||||
{
|
||||
using namespace std;
|
||||
return queue_.emplace(
|
||||
nullptr, nullptr, when,
|
||||
forward<Function>(f));
|
||||
return queue_.emplace(nullptr, nullptr, when, forward<Function>(f));
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
inline
|
||||
auto
|
||||
BasicNetwork<Peer>::timer(
|
||||
duration const& delay, Function&& f) ->
|
||||
cancel_token
|
||||
inline auto
|
||||
BasicNetwork<Peer>::timer(duration const& delay, Function&& f) -> cancel_token
|
||||
{
|
||||
return timer(clock_.now() + delay,
|
||||
std::forward<Function>(f));
|
||||
return timer(clock_.now() + delay, std::forward<Function>(f));
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
inline
|
||||
void
|
||||
BasicNetwork<Peer>::cancel(
|
||||
cancel_token const& token)
|
||||
inline void
|
||||
BasicNetwork<Peer>::cancel(cancel_token const& token)
|
||||
{
|
||||
queue_.erase(token.iter_);
|
||||
}
|
||||
@@ -826,10 +775,10 @@ template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::step()
|
||||
{
|
||||
if (! step_one())
|
||||
if (!step_one())
|
||||
return false;
|
||||
for(;;)
|
||||
if (! step_one())
|
||||
for (;;)
|
||||
if (!step_one())
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
@@ -837,7 +786,7 @@ BasicNetwork<Peer>::step()
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
bool
|
||||
BasicNetwork<Peer>::step_while(Function && f)
|
||||
BasicNetwork<Peer>::step_while(Function&& f)
|
||||
{
|
||||
bool ran = false;
|
||||
while (f() && step_one())
|
||||
@@ -847,17 +796,16 @@ BasicNetwork<Peer>::step_while(Function && f)
|
||||
|
||||
template <class Peer>
|
||||
bool
|
||||
BasicNetwork<Peer>::step_until(
|
||||
time_point const& until)
|
||||
BasicNetwork<Peer>::step_until(time_point const& until)
|
||||
{
|
||||
// VFALCO This routine needs optimizing
|
||||
if(queue_.empty())
|
||||
if (queue_.empty())
|
||||
{
|
||||
clock_.set(until);
|
||||
return false;
|
||||
}
|
||||
auto iter = queue_.begin();
|
||||
if(iter->when > until)
|
||||
if (iter->when > until)
|
||||
{
|
||||
clock_.set(until);
|
||||
return true;
|
||||
@@ -866,19 +814,15 @@ BasicNetwork<Peer>::step_until(
|
||||
{
|
||||
step_one();
|
||||
iter = queue_.begin();
|
||||
}
|
||||
while(iter != queue_.end() &&
|
||||
iter->when <= until);
|
||||
} while (iter != queue_.end() && iter->when <= until);
|
||||
clock_.set(until);
|
||||
return iter != queue_.end();
|
||||
}
|
||||
|
||||
template <class Peer>
|
||||
template <class Period, class Rep>
|
||||
inline
|
||||
bool
|
||||
BasicNetwork<Peer>::step_for(
|
||||
std::chrono::duration<Period, Rep> const& amount)
|
||||
inline bool
|
||||
BasicNetwork<Peer>::step_for(std::chrono::duration<Period, Rep> const& amount)
|
||||
{
|
||||
return step_until(now() + amount);
|
||||
}
|
||||
@@ -886,19 +830,18 @@ BasicNetwork<Peer>::step_for(
|
||||
template <class Peer>
|
||||
template <class Function>
|
||||
void
|
||||
BasicNetwork<Peer>::bfs(
|
||||
Peer const& start, Function&& f)
|
||||
BasicNetwork<Peer>::bfs(Peer const& start, Function&& f)
|
||||
{
|
||||
std::deque<std::pair<Peer, std::size_t>> q;
|
||||
std::unordered_set<Peer> seen;
|
||||
q.emplace_back(start, 0);
|
||||
seen.insert(start);
|
||||
while(! q.empty())
|
||||
while (!q.empty())
|
||||
{
|
||||
auto v = q.front();
|
||||
q.pop_front();
|
||||
f(v.second, v.first);
|
||||
for(auto const& link : links_[v.first])
|
||||
for (auto const& link : links_[v.first])
|
||||
{
|
||||
auto const& w = link.first;
|
||||
if (seen.count(w) == 0)
|
||||
@@ -910,8 +853,8 @@ BasicNetwork<Peer>::bfs(
|
||||
}
|
||||
}
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,15 +18,14 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <test/csf/BasicNetwork.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <set>
|
||||
#include <test/csf/BasicNetwork.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
|
||||
class BasicNetwork_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
@@ -35,28 +34,25 @@ public:
|
||||
int id;
|
||||
std::set<int> set;
|
||||
|
||||
Peer (Peer const&) = default;
|
||||
Peer (Peer&&) = default;
|
||||
Peer(Peer const&) = default;
|
||||
Peer(Peer&&) = default;
|
||||
|
||||
explicit Peer(int id_)
|
||||
: id(id_)
|
||||
explicit Peer(int id_) : id(id_)
|
||||
{
|
||||
}
|
||||
|
||||
template <class Net>
|
||||
void start(Net& net)
|
||||
void
|
||||
start(Net& net)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
auto t = net.timer(1s,
|
||||
[&]{ set.insert(0); });
|
||||
auto t = net.timer(1s, [&] { set.insert(0); });
|
||||
if (id == 0)
|
||||
{
|
||||
for(auto const& link : net.links(this))
|
||||
net.send(this, link.to,
|
||||
[&, to = link.to]
|
||||
{
|
||||
to->receive(net, this, 1);
|
||||
});
|
||||
for (auto const& link : net.links(this))
|
||||
net.send(this, link.to, [&, to = link.to ] {
|
||||
to->receive(net, this, 1);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -65,23 +61,23 @@ public:
|
||||
}
|
||||
|
||||
template <class Net>
|
||||
void receive(Net& net, Peer* from, int m)
|
||||
void
|
||||
receive(Net& net, Peer* from, int m)
|
||||
{
|
||||
set.insert(m);
|
||||
++m;
|
||||
if (m < 5)
|
||||
{
|
||||
for(auto const& link : net.links(this))
|
||||
net.send(this, link.to,
|
||||
[&, mm = m, to = link.to]
|
||||
{
|
||||
to->receive(net, this, mm);
|
||||
});
|
||||
for (auto const& link : net.links(this))
|
||||
net.send(this, link.to, [&, mm = m, to = link.to ] {
|
||||
to->receive(net, this, mm);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void run() override
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
std::vector<Peer> pv;
|
||||
@@ -89,45 +85,40 @@ public:
|
||||
pv.emplace_back(1);
|
||||
pv.emplace_back(2);
|
||||
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[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;
|
||||
net.bfs(&pv[0],
|
||||
[&](auto d, Peer*)
|
||||
{ diameter = std::max(d, diameter); });
|
||||
net.bfs(
|
||||
&pv[0], [&](auto d, Peer*) { diameter = std::max(d, diameter); });
|
||||
BEAST_EXPECT(diameter == 2);
|
||||
for(auto& peer : pv)
|
||||
for (auto& peer : pv)
|
||||
peer.start(net);
|
||||
BEAST_EXPECT(net.step_for(0s));
|
||||
BEAST_EXPECT(net.step_for(1s));
|
||||
BEAST_EXPECT(net.step());
|
||||
BEAST_EXPECT(! net.step());
|
||||
BEAST_EXPECT(! net.step_for(1s));
|
||||
net.send(&pv[0], &pv[1], []{});
|
||||
net.send(&pv[1], &pv[0], []{});
|
||||
BEAST_EXPECT(!net.step());
|
||||
BEAST_EXPECT(!net.step_for(1s));
|
||||
net.send(&pv[0], &pv[1], [] {});
|
||||
net.send(&pv[1], &pv[0], [] {});
|
||||
BEAST_EXPECT(net.disconnect(&pv[0], &pv[1]));
|
||||
BEAST_EXPECT(! net.disconnect(&pv[0], &pv[1]));
|
||||
for(;;)
|
||||
BEAST_EXPECT(!net.disconnect(&pv[0], &pv[1]));
|
||||
for (;;)
|
||||
{
|
||||
auto const links = net.links(&pv[1]);
|
||||
if(links.empty())
|
||||
if (links.empty())
|
||||
break;
|
||||
BEAST_EXPECT(links[0].disconnect());
|
||||
}
|
||||
BEAST_EXPECT(pv[0].set ==
|
||||
std::set<int>({0, 2, 4}));
|
||||
BEAST_EXPECT(pv[1].set ==
|
||||
std::set<int>({1, 3}));
|
||||
BEAST_EXPECT(pv[2].set ==
|
||||
std::set<int>({2, 4}));
|
||||
net.timer(0s, []{});
|
||||
BEAST_EXPECT(pv[0].set == std::set<int>({0, 2, 4}));
|
||||
BEAST_EXPECT(pv[1].set == std::set<int>({1, 3}));
|
||||
BEAST_EXPECT(pv[2].set == std::set<int>({2, 4}));
|
||||
net.timer(0s, [] {});
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(BasicNetwork, test, ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
@@ -46,32 +46,32 @@ namespace csf {
|
||||
|
||||
class Ledger
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
struct ID
|
||||
{
|
||||
std::uint32_t seq = 0;
|
||||
TxSetType txs = TxSetType{};
|
||||
|
||||
bool operator==(ID const & o) const
|
||||
bool
|
||||
operator==(ID const& o) const
|
||||
{
|
||||
return seq == o.seq && txs == o.txs;
|
||||
}
|
||||
|
||||
bool operator!=(ID const & o) const
|
||||
bool
|
||||
operator!=(ID const& o) const
|
||||
{
|
||||
return !(*this == o);
|
||||
}
|
||||
|
||||
bool operator<(ID const & o) const
|
||||
bool
|
||||
operator<(ID const& o) const
|
||||
{
|
||||
return std::tie(seq, txs) < std::tie(o.seq, o.txs);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
auto const &
|
||||
auto const&
|
||||
id() const
|
||||
{
|
||||
return id_;
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
return parentCloseTime_;
|
||||
}
|
||||
|
||||
auto const &
|
||||
auto const&
|
||||
parentID() const
|
||||
{
|
||||
return parentID_;
|
||||
@@ -127,30 +127,28 @@ public:
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
//! Apply the given transactions to this ledger
|
||||
Ledger
|
||||
close(TxSetType const & txs,
|
||||
close(
|
||||
TxSetType const& txs,
|
||||
NetClock::duration closeTimeResolution,
|
||||
NetClock::time_point const & consensusCloseTime,
|
||||
NetClock::time_point const& consensusCloseTime,
|
||||
bool closeTimeAgree) const
|
||||
{
|
||||
Ledger res{ *this };
|
||||
Ledger res{*this};
|
||||
res.id_.txs.insert(txs.begin(), txs.end());
|
||||
res.id_ .seq= seq() + 1;
|
||||
res.id_.seq = seq() + 1;
|
||||
res.closeTimeResolution_ = closeTimeResolution;
|
||||
res.actualCloseTime_ = consensusCloseTime;
|
||||
res.closeTime_ = effectiveCloseTime(consensusCloseTime,
|
||||
closeTimeResolution, parentCloseTime_);
|
||||
res.closeTime_ = effCloseTime(
|
||||
consensusCloseTime, closeTimeResolution, parentCloseTime_);
|
||||
res.closeTimeAgree_ = closeTimeAgree;
|
||||
res.parentCloseTime_ = closeTime();
|
||||
res.parentID_ = id();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
//! Unique identifier of ledger is combination of sequence number and id
|
||||
ID id_;
|
||||
|
||||
@@ -171,27 +169,24 @@ private:
|
||||
|
||||
//! Close time unadjusted by closeTimeResolution
|
||||
NetClock::time_point actualCloseTime_;
|
||||
|
||||
};
|
||||
|
||||
inline
|
||||
std::ostream &
|
||||
operator<<(std::ostream & o, Ledger::ID const & id)
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& o, Ledger::ID const& id)
|
||||
{
|
||||
return o << id.seq << "," << id.txs;
|
||||
}
|
||||
|
||||
inline
|
||||
std::string
|
||||
to_string(Ledger::ID const & id)
|
||||
inline std::string
|
||||
to_string(Ledger::ID const& id)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << id;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -22,15 +22,14 @@
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
|
||||
#include <test/csf/Tx.h>
|
||||
#include <test/csf/Ledger.h>
|
||||
#include <test/csf/Tx.h>
|
||||
#include <test/csf/UNL.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace csf {
|
||||
|
||||
|
||||
/** Store validations reached by peers */
|
||||
struct Validation
|
||||
{
|
||||
@@ -45,15 +44,17 @@ class Validations
|
||||
{
|
||||
//< Ledgers seen by peers, saved in order received (which should be order
|
||||
//< created)
|
||||
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromLedger;
|
||||
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromPrevLedger;
|
||||
bc::flat_map<Ledger::ID, bc::flat_map<Ledger::ID, std::size_t>> childLedgers;
|
||||
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromLedger;
|
||||
bc::flat_map<Ledger::ID, bc::flat_set<PeerID>> nodesFromPrevLedger;
|
||||
bc::flat_map<Ledger::ID, bc::flat_map<Ledger::ID, std::size_t>>
|
||||
childLedgers;
|
||||
|
||||
public:
|
||||
void
|
||||
update(Validation const & v)
|
||||
update(Validation const& v)
|
||||
{
|
||||
nodesFromLedger[v.ledger].insert(v.id);
|
||||
if(v.ledger.seq > 0)
|
||||
if (v.ledger.seq > 0)
|
||||
{
|
||||
nodesFromPrevLedger[v.prevLedger].insert(v.id);
|
||||
childLedgers[v.prevLedger][v.ledger]++;
|
||||
@@ -62,10 +63,10 @@ public:
|
||||
|
||||
//< The number of peers who have validated this ledger
|
||||
std::size_t
|
||||
proposersValidated(Ledger::ID const & prevLedger) const
|
||||
proposersValidated(Ledger::ID const& prevLedger) const
|
||||
{
|
||||
auto it = nodesFromLedger.find(prevLedger);
|
||||
if(it != nodesFromLedger.end())
|
||||
if (it != nodesFromLedger.end())
|
||||
return it->second.size();
|
||||
return 0;
|
||||
}
|
||||
@@ -75,35 +76,33 @@ public:
|
||||
as an ancestor.
|
||||
*/
|
||||
std::size_t
|
||||
proposersFinished(Ledger::ID const & prevLedger) const
|
||||
proposersFinished(Ledger::ID const& prevLedger) const
|
||||
{
|
||||
auto it = nodesFromPrevLedger.find(prevLedger);
|
||||
if(it != nodesFromPrevLedger.end())
|
||||
if (it != nodesFromPrevLedger.end())
|
||||
return it->second.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Returns the ledger starting from prevLedger with the most validations.
|
||||
*/
|
||||
*/
|
||||
Ledger::ID
|
||||
getBestLCL(Ledger::ID const & currLedger,
|
||||
Ledger::ID const & prevLedger) const
|
||||
getBestLCL(Ledger::ID const& currLedger, Ledger::ID const& prevLedger) const
|
||||
{
|
||||
auto it = childLedgers.find(prevLedger);
|
||||
if (it != childLedgers.end() &&
|
||||
! it->second.empty())
|
||||
if (it != childLedgers.end() && !it->second.empty())
|
||||
{
|
||||
std::size_t bestCount = 0;
|
||||
Ledger::ID bestLedger;
|
||||
|
||||
for (auto const & b : it->second)
|
||||
for (auto const& b : it->second)
|
||||
{
|
||||
auto currCount = b.second;
|
||||
if(currLedger == b.first)
|
||||
if (currLedger == b.first)
|
||||
currCount++;
|
||||
if(currCount > bestCount)
|
||||
if (currCount > bestCount)
|
||||
bestLedger = b.first;
|
||||
if(currCount == bestCount && currLedger == b.first)
|
||||
if (currCount == bestCount && currLedger == b.first)
|
||||
bestLedger = b.first;
|
||||
}
|
||||
return bestLedger;
|
||||
@@ -122,11 +121,8 @@ struct Traits
|
||||
using Ledger_t = Ledger;
|
||||
using NodeID_t = PeerID;
|
||||
using TxSet_t = TxSet;
|
||||
using MissingTxException_t = MissingTx;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** Represents a single node participating in the consensus process.
|
||||
It implements the Callbacks required by Consensus.
|
||||
*/
|
||||
@@ -144,7 +140,7 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
Ledger lastClosedLedger;
|
||||
|
||||
//! Handle to network for sending messages
|
||||
BasicNetwork<Peer*> & net;
|
||||
BasicNetwork<Peer*>& net;
|
||||
|
||||
//! UNL of trusted peers
|
||||
UNL unl;
|
||||
@@ -173,12 +169,12 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
//! Delay in acquiring missing ledger from the network
|
||||
std::chrono::milliseconds missingLedgerDelay{0};
|
||||
|
||||
bool validating = true;
|
||||
bool proposing = true;
|
||||
bool validating_ = true;
|
||||
bool proposing_ = true;
|
||||
|
||||
//! All peers start from the default constructed ledger
|
||||
Peer(PeerID i, BasicNetwork<Peer*> & n, UNL const & u)
|
||||
: Consensus<Peer, Traits>( n.clock(), beast::Journal{})
|
||||
Peer(PeerID i, BasicNetwork<Peer*>& n, UNL const& u)
|
||||
: Consensus<Peer, Traits>(n.clock(), beast::Journal{})
|
||||
, id{i}
|
||||
, net{n}
|
||||
, unl(u)
|
||||
@@ -186,19 +182,8 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
ledgers[lastClosedLedger.id()] = lastClosedLedger;
|
||||
}
|
||||
|
||||
|
||||
// @return whether we are proposing,validating
|
||||
// TODO: Bit akward that this is in callbacks, would be nice to extract
|
||||
std::pair<bool, bool>
|
||||
getMode()
|
||||
{
|
||||
// in RCL this hits NetworkOps to decide whether we are proposing
|
||||
// validating
|
||||
return{ proposing, validating };
|
||||
}
|
||||
|
||||
Ledger const *
|
||||
acquireLedger(Ledger::ID const & ledgerHash)
|
||||
Ledger const*
|
||||
acquireLedger(Ledger::ID const& ledgerHash)
|
||||
{
|
||||
auto it = ledgers.find(ledgerHash);
|
||||
if (it != ledgers.end())
|
||||
@@ -208,16 +193,16 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
|
||||
for (auto const& link : net.links(this))
|
||||
{
|
||||
auto const & p = *link.to;
|
||||
auto const& p = *link.to;
|
||||
auto it = p.ledgers.find(ledgerHash);
|
||||
if (it != p.ledgers.end())
|
||||
{
|
||||
schedule(missingLedgerDelay,
|
||||
[this, ledgerHash, ledger = it->second]()
|
||||
{
|
||||
schedule(
|
||||
missingLedgerDelay,
|
||||
[ this, ledgerHash, ledger = it->second ]() {
|
||||
ledgers.emplace(ledgerHash, ledger);
|
||||
});
|
||||
if(missingLedgerDelay == 0ms)
|
||||
if (missingLedgerDelay == 0ms)
|
||||
return &ledgers[ledgerHash];
|
||||
break;
|
||||
}
|
||||
@@ -225,23 +210,22 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto const &
|
||||
proposals(Ledger::ID const & ledgerHash)
|
||||
auto const&
|
||||
proposals(Ledger::ID const& ledgerHash)
|
||||
{
|
||||
return peerPositions_[ledgerHash];
|
||||
}
|
||||
|
||||
TxSet const *
|
||||
acquireTxSet(TxSet::ID const & setId)
|
||||
TxSet const*
|
||||
acquireTxSet(TxSet::ID const& setId)
|
||||
{
|
||||
auto it = txSets.find(setId);
|
||||
if(it != txSets.end())
|
||||
if (it != txSets.end())
|
||||
return &(it->second);
|
||||
// TODO Get from network/oracle instead!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
hasOpenTransactions() const
|
||||
{
|
||||
@@ -249,114 +233,68 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
}
|
||||
|
||||
std::size_t
|
||||
proposersValidated(Ledger::ID const & prevLedger)
|
||||
proposersValidated(Ledger::ID const& prevLedger)
|
||||
{
|
||||
return peerValidations.proposersValidated(prevLedger);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
proposersFinished(Ledger::ID const & prevLedger)
|
||||
proposersFinished(Ledger::ID const& prevLedger)
|
||||
{
|
||||
return peerValidations.proposersFinished(prevLedger);
|
||||
}
|
||||
|
||||
void
|
||||
onStartRound(Ledger const &) {}
|
||||
|
||||
void
|
||||
onClose(Ledger const &, bool ) {}
|
||||
|
||||
// don't really offload
|
||||
void
|
||||
dispatchAccept(TxSet const & f)
|
||||
Result
|
||||
onClose(Ledger const& prevLedger, NetClock::time_point closeTime, Mode mode)
|
||||
{
|
||||
Base::accept(f);
|
||||
TxSet res{openTxs};
|
||||
|
||||
return Result{TxSet{openTxs},
|
||||
Proposal{prevLedger.id(),
|
||||
Proposal::seqJoin,
|
||||
res.id(),
|
||||
closeTime,
|
||||
now(),
|
||||
id}};
|
||||
}
|
||||
|
||||
void
|
||||
share(TxSet const &s)
|
||||
onForceAccept(
|
||||
Result const& result,
|
||||
Ledger const& prevLedger,
|
||||
NetClock::duration const& closeResolution,
|
||||
CloseTimes const& rawCloseTimes,
|
||||
Mode const& mode)
|
||||
{
|
||||
relay(s);
|
||||
}
|
||||
|
||||
Ledger::ID
|
||||
getLCL(Ledger::ID const & currLedger,
|
||||
Ledger::ID const & priorLedger,
|
||||
bool haveCorrectLCL)
|
||||
{
|
||||
// TODO: Use generic validation code
|
||||
if(currLedger.seq > 0 && priorLedger.seq > 0)
|
||||
return peerValidations.getBestLCL(currLedger, priorLedger);
|
||||
return currLedger;
|
||||
onAccept(result, prevLedger, closeResolution, rawCloseTimes, mode);
|
||||
}
|
||||
|
||||
void
|
||||
propose(Proposal const & pos)
|
||||
onAccept(
|
||||
Result const& result,
|
||||
Ledger const& prevLedger,
|
||||
NetClock::duration const& closeResolution,
|
||||
CloseTimes const& rawCloseTimes,
|
||||
Mode const& mode)
|
||||
{
|
||||
if(proposing)
|
||||
relay(pos);
|
||||
}
|
||||
|
||||
void
|
||||
relay(DisputedTx<Tx, PeerID> const & dispute)
|
||||
{
|
||||
relay(dispute.tx());
|
||||
}
|
||||
|
||||
std::pair <TxSet, Proposal>
|
||||
makeInitialPosition(
|
||||
Ledger const & prevLedger,
|
||||
bool isProposing,
|
||||
bool isCorrectLCL,
|
||||
NetClock::time_point closeTime,
|
||||
NetClock::time_point now)
|
||||
{
|
||||
TxSet res{ openTxs };
|
||||
|
||||
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{});
|
||||
auto newLedger = prevLedger.close(
|
||||
result.set.txs_,
|
||||
closeResolution,
|
||||
rawCloseTimes.self,
|
||||
result.position.closeTime() != NetClock::time_point{});
|
||||
ledgers[newLedger.id()] = newLedger;
|
||||
|
||||
lastClosedLedger = newLedger;
|
||||
|
||||
auto it = std::remove_if(openTxs.begin(), openTxs.end(),
|
||||
[&](Tx const & tx)
|
||||
{
|
||||
return set.exists(tx.id());
|
||||
auto it =
|
||||
std::remove_if(openTxs.begin(), openTxs.end(), [&](Tx const& tx) {
|
||||
return result.set.exists(tx.id());
|
||||
});
|
||||
openTxs.erase(it, openTxs.end());
|
||||
|
||||
if(validating)
|
||||
if (validating_)
|
||||
relay(Validation{id, newLedger.id(), newLedger.parentID()});
|
||||
return validating_;
|
||||
}
|
||||
|
||||
void
|
||||
endConsensus(bool correct)
|
||||
{
|
||||
// kick off the next round...
|
||||
// in the actual implementation, this passes back through
|
||||
// network ops
|
||||
@@ -364,41 +302,58 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
// startRound sets the LCL state, so we need to call it once after
|
||||
// the last requested round completes
|
||||
// TODO: reconsider this and instead just save LCL generated here?
|
||||
if(completedLedgers <= targetLedgers)
|
||||
if (completedLedgers <= targetLedgers)
|
||||
{
|
||||
startRound(now(), lastClosedLedger.id(),
|
||||
lastClosedLedger);
|
||||
startRound(
|
||||
now(), lastClosedLedger.id(), lastClosedLedger, proposing_);
|
||||
}
|
||||
}
|
||||
|
||||
Ledger::ID
|
||||
getPrevLedger(Ledger::ID const& ledgerID, Ledger const& ledger, Mode mode)
|
||||
{
|
||||
// TODO: Use generic validation code
|
||||
if (mode != Mode::wrongLedger && ledgerID.seq > 0 &&
|
||||
ledger.id().seq > 0)
|
||||
return peerValidations.getBestLCL(ledgerID, ledger.parentID());
|
||||
return ledgerID;
|
||||
}
|
||||
|
||||
void
|
||||
propose(Proposal const& pos)
|
||||
{
|
||||
if (proposing_)
|
||||
relay(pos);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// non-callback helpers
|
||||
void
|
||||
receive(Proposal const & p)
|
||||
receive(Proposal const& p)
|
||||
{
|
||||
if(unl.find(p.nodeID()) == unl.end())
|
||||
if (unl.find(p.nodeID()) == unl.end())
|
||||
return;
|
||||
|
||||
// TODO: Be sure this is a new proposal!!!!!
|
||||
auto & dest = peerPositions_[p.prevLedger()];
|
||||
if(std::find(dest.begin(), dest.end(), p) != dest.end())
|
||||
auto& dest = peerPositions_[p.prevLedger()];
|
||||
if (std::find(dest.begin(), dest.end(), p) != dest.end())
|
||||
return;
|
||||
|
||||
dest.push_back(p);
|
||||
peerProposal(now(), p);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
receive(TxSet const & txs)
|
||||
receive(TxSet const& txs)
|
||||
{
|
||||
// save and map complete?
|
||||
auto it = txSets.insert(std::make_pair(txs.id(), txs));
|
||||
if(it.second)
|
||||
if (it.second)
|
||||
gotTxSet(now(), txs);
|
||||
}
|
||||
|
||||
void
|
||||
receive(Tx const & tx)
|
||||
receive(Tx const& tx)
|
||||
{
|
||||
if (openTxs.find(tx.id()) == openTxs.end())
|
||||
{
|
||||
@@ -409,33 +364,26 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
}
|
||||
|
||||
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,
|
||||
[&, v]()
|
||||
{
|
||||
peerValidations.update(v);
|
||||
});
|
||||
schedule(validationDelay, [&, v]() { peerValidations.update(v); });
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
relay(T const & t)
|
||||
relay(T const& t)
|
||||
{
|
||||
for(auto const& link : net.links(this))
|
||||
net.send(this, link.to,
|
||||
[msg = t, to = link.to]
|
||||
{
|
||||
to->receive(msg);
|
||||
});
|
||||
for (auto const& link : net.links(this))
|
||||
net.send(
|
||||
this, link.to, [ msg = t, to = link.to ] { to->receive(msg); });
|
||||
}
|
||||
|
||||
// Receive and relay locally submitted transaction
|
||||
void
|
||||
submit(Tx const & tx)
|
||||
submit(Tx const& tx)
|
||||
{
|
||||
receive(tx);
|
||||
relay(tx);
|
||||
@@ -446,7 +394,7 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
{
|
||||
Base::timerEntry(now());
|
||||
// only reschedule if not completed
|
||||
if(completedLedgers < targetLedgers)
|
||||
if (completedLedgers < targetLedgers)
|
||||
net.timer(LEDGER_GRANULARITY, [&]() { timerEntry(); });
|
||||
}
|
||||
void
|
||||
@@ -456,10 +404,9 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
// The ID is the one we have seen the most validations for
|
||||
// In practice, we might not actually have that ledger itself yet,
|
||||
// so there is no gaurantee that bestLCL == lastClosedLedger.id()
|
||||
auto bestLCL = peerValidations.getBestLCL(lastClosedLedger.id(),
|
||||
lastClosedLedger.parentID());
|
||||
startRound(now(), bestLCL,
|
||||
lastClosedLedger);
|
||||
auto bestLCL = peerValidations.getBestLCL(
|
||||
lastClosedLedger.id(), lastClosedLedger.parentID());
|
||||
startRound(now(), bestLCL, lastClosedLedger, proposing_);
|
||||
}
|
||||
|
||||
NetClock::time_point
|
||||
@@ -470,23 +417,24 @@ struct Peer : public Consensus<Peer, Traits>
|
||||
// any subtractions of two NetClock::time_point in the consensu
|
||||
// code are positive. (e.g. PROPOSE_FRESHNESS)
|
||||
using namespace std::chrono;
|
||||
return NetClock::time_point(duration_cast<NetClock::duration>
|
||||
(net.now().time_since_epoch()+ 86400s + clockSkew));
|
||||
return NetClock::time_point(duration_cast<NetClock::duration>(
|
||||
net.now().time_since_epoch() + 86400s + clockSkew));
|
||||
}
|
||||
|
||||
// Schedule the provided callback in `when` duration, but if
|
||||
// `when` is 0, call immediately
|
||||
template <class T>
|
||||
void schedule(std::chrono::nanoseconds when, T && what)
|
||||
void
|
||||
schedule(std::chrono::nanoseconds when, T&& what)
|
||||
{
|
||||
if(when == 0ns)
|
||||
if (when == 0ns)
|
||||
what();
|
||||
else
|
||||
net.timer(when, std::forward<T>(what));
|
||||
}
|
||||
};
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
#endif
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
#ifndef RIPPLE_TEST_CSF_SIM_H_INCLUDED
|
||||
#define RIPPLE_TEST_CSF_SIM_H_INCLUDED
|
||||
|
||||
#include <test/csf/UNL.h>
|
||||
#include <test/csf/BasicNetwork.h>
|
||||
#include <test/csf/UNL.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -50,19 +50,19 @@ public:
|
||||
|
||||
*/
|
||||
template <class Topology>
|
||||
Sim(TrustGraph const & g, Topology const & top)
|
||||
Sim(TrustGraph const& g, Topology const& top)
|
||||
{
|
||||
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));
|
||||
|
||||
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)
|
||||
{
|
||||
net.connect(&peers[i], &peers[j], *d);
|
||||
@@ -81,10 +81,10 @@ public:
|
||||
void
|
||||
run(int ledgers)
|
||||
{
|
||||
for (auto & p : peers)
|
||||
for (auto& p : peers)
|
||||
{
|
||||
if(p.completedLedgers == 0)
|
||||
p.relay(Validation{p.id, p.LCL(), p.LCL()});
|
||||
if (p.completedLedgers == 0)
|
||||
p.relay(Validation{p.id, p.prevLedgerID(), p.prevLedgerID()});
|
||||
p.targetLedgers = p.completedLedgers + ledgers;
|
||||
p.start();
|
||||
}
|
||||
@@ -93,12 +93,10 @@ public:
|
||||
|
||||
std::vector<Peer> peers;
|
||||
BasicNetwork<Peer*> net;
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
#include <ripple/beast/hash/hash_append.h>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -35,7 +35,9 @@ class Tx
|
||||
public:
|
||||
using ID = std::uint32_t;
|
||||
|
||||
Tx(ID i) : id_{ i } {}
|
||||
Tx(ID i) : id_{i}
|
||||
{
|
||||
}
|
||||
|
||||
ID
|
||||
id() const
|
||||
@@ -44,21 +46,19 @@ public:
|
||||
}
|
||||
|
||||
bool
|
||||
operator<(Tx const & o) const
|
||||
operator<(Tx const& o) const
|
||||
{
|
||||
return id_ < o.id_;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(Tx const & o) const
|
||||
operator==(Tx const& o) const
|
||||
{
|
||||
return id_ == o.id_;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
ID id_;
|
||||
|
||||
};
|
||||
|
||||
//!-------------------------------------------------------------------------
|
||||
@@ -74,37 +74,39 @@ public:
|
||||
using MutableTxSet = TxSet;
|
||||
|
||||
TxSet() = default;
|
||||
TxSet(TxSetType const & s) : txs_{ s } {}
|
||||
TxSet(TxSetType const& s) : txs_{s}
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
insert(Tx const & t)
|
||||
insert(Tx const& t)
|
||||
{
|
||||
return txs_.insert(t).second;
|
||||
}
|
||||
|
||||
bool
|
||||
erase(Tx::ID const & txId)
|
||||
erase(Tx::ID const& txId)
|
||||
{
|
||||
return txs_.erase(Tx{ txId }) > 0;
|
||||
return txs_.erase(Tx{txId}) > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
exists(Tx::ID const txId) const
|
||||
{
|
||||
auto it = txs_.find(Tx{ txId });
|
||||
auto it = txs_.find(Tx{txId});
|
||||
return it != txs_.end();
|
||||
}
|
||||
|
||||
Tx const *
|
||||
Tx const*
|
||||
find(Tx::ID const& txId) const
|
||||
{
|
||||
auto it = txs_.find(Tx{ txId });
|
||||
auto it = txs_.find(Tx{txId});
|
||||
if (it != txs_.end())
|
||||
return &(*it);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto const &
|
||||
auto const&
|
||||
id() const
|
||||
{
|
||||
return txs_;
|
||||
@@ -119,19 +121,14 @@ public:
|
||||
{
|
||||
std::map<Tx::ID, bool> res;
|
||||
|
||||
auto populate_diffs = [&res](auto const & a, auto const & b, bool s)
|
||||
{
|
||||
auto populator = [&](auto const & tx)
|
||||
{
|
||||
res[tx.id()] = s;
|
||||
};
|
||||
auto populate_diffs = [&res](auto const& a, auto const& b, bool s) {
|
||||
auto populator = [&](auto const& tx) { res[tx.id()] = s; };
|
||||
std::set_difference(
|
||||
a.begin(), a.end(),
|
||||
b.begin(), b.end(),
|
||||
boost::make_function_output_iterator(
|
||||
std::ref(populator)
|
||||
)
|
||||
);
|
||||
a.begin(),
|
||||
a.end(),
|
||||
b.begin(),
|
||||
b.end(),
|
||||
boost::make_function_output_iterator(std::ref(populator)));
|
||||
};
|
||||
|
||||
populate_diffs(txs_, other.txs_, true);
|
||||
@@ -143,55 +140,35 @@ public:
|
||||
TxSetType txs_;
|
||||
};
|
||||
|
||||
|
||||
/** The RCL consensus process catches missing node SHAMap error
|
||||
in several points. This exception is meant to represent a similar
|
||||
case for the unit test.
|
||||
*/
|
||||
class MissingTx : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
MissingTx()
|
||||
: std::runtime_error("MissingTx")
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helper functions for debug printing
|
||||
|
||||
inline
|
||||
std::ostream&
|
||||
operator<<(std::ostream & o, const Tx & t)
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& o, const Tx& t)
|
||||
{
|
||||
return o << t.id();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline
|
||||
std::ostream&
|
||||
operator<<(std::ostream & o, boost::container::flat_set<T> const & ts)
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& o, boost::container::flat_set<T> const& ts)
|
||||
{
|
||||
o << "{ ";
|
||||
bool do_comma = false;
|
||||
for (auto const & t : ts)
|
||||
for (auto const& t : ts)
|
||||
{
|
||||
if (do_comma)
|
||||
o << ", ";
|
||||
else
|
||||
do_comma = true;
|
||||
o << t;
|
||||
|
||||
|
||||
}
|
||||
o << " }";
|
||||
return o;
|
||||
|
||||
}
|
||||
|
||||
inline
|
||||
std::string
|
||||
to_string(TxSetType const & txs)
|
||||
inline std::string
|
||||
to_string(TxSetType const& txs)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << txs;
|
||||
@@ -199,23 +176,15 @@ to_string(TxSetType const & txs)
|
||||
}
|
||||
|
||||
template <class Hasher>
|
||||
inline
|
||||
void
|
||||
hash_append(Hasher& h, Tx const & tx)
|
||||
inline void
|
||||
hash_append(Hasher& h, Tx const& tx)
|
||||
{
|
||||
using beast::hash_append;
|
||||
hash_append(h, tx.id());
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream & o, MissingTx const &m)
|
||||
{
|
||||
return o << m.what();
|
||||
}
|
||||
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <numeric>
|
||||
#include <chrono>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -42,7 +42,7 @@ namespace csf {
|
||||
*/
|
||||
template <class T, class G>
|
||||
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;
|
||||
|
||||
@@ -57,7 +57,6 @@ random_weighted_shuffle(std::vector<T> v, std::vector<double> w, G & g)
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
/** Power-law distribution with PDF
|
||||
|
||||
P(x) = (x/xmin)^-a
|
||||
@@ -69,25 +68,22 @@ class PowerLawDistribution
|
||||
double xmin_;
|
||||
double a_;
|
||||
double inv_;
|
||||
std::uniform_real_distribution<double> uf_{0,1};
|
||||
std::uniform_real_distribution<double> uf_{0, 1};
|
||||
|
||||
public:
|
||||
PowerLawDistribution(double xmin, double a)
|
||||
: xmin_{xmin}, a_{a}
|
||||
{
|
||||
inv_ = 1.0/(1.0 - a_);
|
||||
}
|
||||
PowerLawDistribution(double xmin, double a) : xmin_{xmin}, a_{a}
|
||||
{
|
||||
inv_ = 1.0 / (1.0 - a_);
|
||||
}
|
||||
|
||||
template <class Generator>
|
||||
inline
|
||||
double
|
||||
operator()(Generator & g)
|
||||
inline double
|
||||
operator()(Generator& g)
|
||||
{
|
||||
// use inverse transform of CDF to sample
|
||||
// CDF is P(X <= x): 1 - (x/xmin)^(1-a)
|
||||
return xmin_ * std::pow(1 - uf_(g), inv_);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//< Unique identifier for each node in the network
|
||||
@@ -110,25 +106,23 @@ class TrustGraph
|
||||
std::vector<UNL> UNLs_;
|
||||
|
||||
std::vector<int> assignment_;
|
||||
public:
|
||||
|
||||
public:
|
||||
//< Constructor
|
||||
TrustGraph(std::vector<UNL> UNLs, std::vector<int> assignment)
|
||||
: UNLs_{UNLs}
|
||||
, assignment_{assignment}
|
||||
{}
|
||||
: UNLs_{UNLs}, assignment_{assignment}
|
||||
{
|
||||
}
|
||||
|
||||
//< Whether node `i` trusts node `j`
|
||||
inline
|
||||
bool
|
||||
inline bool
|
||||
trusts(PeerID i, PeerID j) const
|
||||
{
|
||||
return unl(i).find(j) != unl(i).end();
|
||||
}
|
||||
|
||||
//< Get the UNL for node `i`
|
||||
inline
|
||||
UNL const &
|
||||
inline UNL const&
|
||||
unl(PeerID i) const
|
||||
{
|
||||
return UNLs_[assignment_[i]];
|
||||
@@ -138,7 +132,6 @@ public:
|
||||
bool
|
||||
canFork(double quorum) const;
|
||||
|
||||
|
||||
auto
|
||||
numPeers() const
|
||||
{
|
||||
@@ -147,7 +140,7 @@ public:
|
||||
|
||||
//< Save grapviz dot file reprentation of the trust graph
|
||||
void
|
||||
save_dot(std::string const & fileName);
|
||||
save_dot(std::string const& fileName);
|
||||
|
||||
/** Generate a random trust graph based on random ranking of peers
|
||||
|
||||
@@ -176,28 +169,23 @@ public:
|
||||
|
||||
*/
|
||||
template <class RankPDF, class SizePDF, class Generator>
|
||||
static
|
||||
TrustGraph
|
||||
makeRandomRanked(int size,
|
||||
int numUNLs,
|
||||
RankPDF rankPDF,
|
||||
SizePDF unlSizePDF,
|
||||
Generator & g)
|
||||
static TrustGraph
|
||||
makeRandomRanked(
|
||||
int size,
|
||||
int numUNLs,
|
||||
RankPDF rankPDF,
|
||||
SizePDF unlSizePDF,
|
||||
Generator& g)
|
||||
{
|
||||
|
||||
|
||||
// 1. Generate ranks
|
||||
std::vector<double> weights(size);
|
||||
std::generate(weights.begin(), weights.end(), [&]()
|
||||
{
|
||||
return rankPDF(g);
|
||||
});
|
||||
std::generate(
|
||||
weights.begin(), weights.end(), [&]() { return rankPDF(g); });
|
||||
|
||||
// 2. Generate UNLs based on sampling without replacement according
|
||||
// to weights
|
||||
std::vector<UNL> unls(numUNLs);
|
||||
std::generate(unls.begin(), unls.end(), [&]()
|
||||
{
|
||||
std::generate(unls.begin(), unls.end(), [&]() {
|
||||
std::vector<PeerID> ids(size);
|
||||
std::iota(ids.begin(), ids.end(), 0);
|
||||
auto res = random_weighted_shuffle(ids, weights, g);
|
||||
@@ -206,12 +194,9 @@ public:
|
||||
|
||||
// 3. Assign membership
|
||||
std::vector<int> assignment(size);
|
||||
std::uniform_int_distribution<int> u(0, numUNLs-1);
|
||||
std::generate(assignment.begin(), assignment.end(),
|
||||
[&]()
|
||||
{
|
||||
return u(g);
|
||||
});
|
||||
std::uniform_int_distribution<int> u(0, numUNLs - 1);
|
||||
std::generate(
|
||||
assignment.begin(), assignment.end(), [&]() { return u(g); });
|
||||
|
||||
return TrustGraph(unls, assignment);
|
||||
}
|
||||
@@ -226,8 +211,7 @@ public:
|
||||
@param size The number of nodes in the trust graph
|
||||
@param overlap The number of nodes trusting both cliques
|
||||
*/
|
||||
static
|
||||
TrustGraph
|
||||
static TrustGraph
|
||||
makeClique(int size, int overlap);
|
||||
|
||||
/** Generate a complete (fully-connect) trust graph
|
||||
@@ -237,21 +221,17 @@ public:
|
||||
|
||||
@param size The number of nodes in the trust graph
|
||||
*/
|
||||
static
|
||||
TrustGraph
|
||||
static TrustGraph
|
||||
makeComplete(int size);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//< Make the TrustGraph into a topology with delays given by DelayModel
|
||||
template <class DelayModel>
|
||||
auto
|
||||
topology(TrustGraph const & tg, DelayModel const & d)
|
||||
topology(TrustGraph const& tg, DelayModel const& d)
|
||||
{
|
||||
return [&](PeerID i, PeerID j)
|
||||
{
|
||||
return tg.trusts(i,j) ? boost::make_optional(d(i,j)) : boost::none;
|
||||
return [&](PeerID i, PeerID j) {
|
||||
return tg.trusts(i, j) ? boost::make_optional(d(i, j)) : boost::none;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -260,18 +240,19 @@ class fixed
|
||||
std::chrono::nanoseconds d_;
|
||||
|
||||
public:
|
||||
fixed(std::chrono::nanoseconds const & d) : d_{d} {}
|
||||
fixed(std::chrono::nanoseconds const& d) : d_{d}
|
||||
{
|
||||
}
|
||||
|
||||
inline
|
||||
std::chrono::nanoseconds
|
||||
operator()(PeerID const & i, PeerID const & j) const
|
||||
inline std::chrono::nanoseconds
|
||||
operator()(PeerID const& i, PeerID const& j) const
|
||||
{
|
||||
return d_;
|
||||
}
|
||||
};
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
#include <test/csf/UNL.h>
|
||||
#include <boost/iterator/counting_iterator.hpp>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <test/csf/UNL.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -38,8 +38,8 @@ TrustGraph::canFork(double quorum) const
|
||||
|
||||
for (int i = 0; i < assignment_.size(); ++i)
|
||||
{
|
||||
auto const & myUNL = UNLs_[assignment_[i]];
|
||||
if(myUNL.find(i) == myUNL.end())
|
||||
auto const& myUNL = UNLs_[assignment_[i]];
|
||||
if (myUNL.find(i) == myUNL.end())
|
||||
{
|
||||
auto myUNLcopy = myUNL;
|
||||
myUNLcopy.insert(i);
|
||||
@@ -50,21 +50,20 @@ TrustGraph::canFork(double quorum) const
|
||||
// Loop over all pairs of uniqueUNLs
|
||||
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 & unlB = uniqueUNLs[j];
|
||||
auto const& unlA = uniqueUNLs[i];
|
||||
auto const& unlB = uniqueUNLs[j];
|
||||
|
||||
double rhs = 2.0*(1.-quorum) *
|
||||
std::max(unlA.size(), unlB.size() );
|
||||
double rhs =
|
||||
2.0 * (1. - quorum) * std::max(unlA.size(), unlB.size());
|
||||
|
||||
int intersectionSize = std::count_if(unlA.begin(), unlA.end(),
|
||||
[&](PeerID id)
|
||||
{
|
||||
int intersectionSize =
|
||||
std::count_if(unlA.begin(), unlA.end(), [&](PeerID id) {
|
||||
return unlB.find(id) != unlB.end();
|
||||
});
|
||||
|
||||
if(intersectionSize < rhs)
|
||||
if (intersectionSize < rhs)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -80,56 +79,53 @@ TrustGraph::makeClique(int size, int overlap)
|
||||
// Clique A has nodes [0,endA) and Clique B has [startB,numPeers)
|
||||
// Note: Clique B will have an extra peer when numPeers - overlap
|
||||
// is odd
|
||||
int endA = (size + overlap)/2;
|
||||
int startB = (size - overlap)/2;
|
||||
int endA = (size + overlap) / 2;
|
||||
int startB = (size - overlap) / 2;
|
||||
|
||||
std::vector<UNL> unls;
|
||||
unls.emplace_back(bci(0), bci(endA));
|
||||
unls.emplace_back(bci(startB), 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)
|
||||
{
|
||||
if(i < startB)
|
||||
if (i < startB)
|
||||
assignment[i] = 0;
|
||||
else if(i > endA)
|
||||
else if (i > endA)
|
||||
assignment[i] = 1;
|
||||
else
|
||||
assignment[i] = 2;
|
||||
}
|
||||
|
||||
|
||||
return TrustGraph(unls, assignment);
|
||||
}
|
||||
|
||||
TrustGraph
|
||||
TrustGraph::makeComplete(int size)
|
||||
{
|
||||
UNL all{ boost::counting_iterator<PeerID>( 0 ),
|
||||
boost::counting_iterator<PeerID>( size ) };
|
||||
UNL all{boost::counting_iterator<PeerID>(0),
|
||||
boost::counting_iterator<PeerID>(size)};
|
||||
|
||||
return TrustGraph(std::vector<UNL>(1,all),
|
||||
std::vector<int>(size, 0));
|
||||
return TrustGraph(std::vector<UNL>(1, all), std::vector<int>(size, 0));
|
||||
}
|
||||
|
||||
inline void TrustGraph::save_dot(std::string const & fileName)
|
||||
inline void
|
||||
TrustGraph::save_dot(std::string const& fileName)
|
||||
{
|
||||
std::ofstream out(fileName);
|
||||
out << "digraph {\n";
|
||||
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 << "}\n";
|
||||
|
||||
}
|
||||
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
} // csf
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||