rippled
Loading...
Searching...
No Matches
DisputedTx.h
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#ifndef RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
21#define RIPPLE_APP_CONSENSUS_IMPL_DISPUTEDTX_H_INCLUDED
22
23#include <xrpld/consensus/ConsensusParms.h>
24
25#include <xrpl/basics/Log.h>
26#include <xrpl/beast/utility/Journal.h>
27#include <xrpl/json/json_writer.h>
28
29#include <boost/container/flat_map.hpp>
30
31namespace ripple {
32
47template <class Tx_t, class NodeID_t>
49{
50 using TxID_t = typename Tx_t::ID;
51 using Map_t = boost::container::flat_map<NodeID_t, bool>;
52
53public:
62 Tx_t const& tx,
63 bool ourVote,
64 std::size_t numPeers,
66 : yays_(0), nays_(0), ourVote_(ourVote), tx_(tx), j_(j)
67 {
68 votes_.reserve(numPeers);
69 }
70
72 TxID_t const&
73 ID() const
74 {
75 return tx_.id();
76 }
77
79 bool
80 getOurVote() const
81 {
82 return ourVote_;
83 }
84
87 bool
88 stalled(ConsensusParms const& p, bool proposing, int peersUnchanged) const
89 {
90 // at() can throw, but the map is built by hand to ensure all valid
91 // values are available.
92 auto const& currentCutoff = p.avalancheCutoffs.at(avalancheState_);
93 auto const& nextCutoff = p.avalancheCutoffs.at(currentCutoff.next);
94
95 // We're have not reached the final avalanche state, or been there long
96 // enough, so there's room for change. Check the times in case the state
97 // machine is altered to allow states to loop.
98 if (nextCutoff.consensusTime > currentCutoff.consensusTime ||
100 return false;
101
102 // We've haven't had this vote for minimum rounds yet. Things could
103 // change.
105 return false;
106
107 // If we or any peers have changed a vote in several rounds, then
108 // things could still change. But if _either_ has not changed in that
109 // long, we're unlikely to change our vote any time soon. (This prevents
110 // a malicious peer from flip-flopping a vote to prevent consensus.)
111 if (peersUnchanged < p.avSTALLED_ROUNDS &&
113 return false;
114
115 // Does this transaction have more than 80% agreement
116
117 // Compute the percentage of nodes voting 'yes' (possibly including us)
118 int const support = (yays_ + (proposing && ourVote_ ? 1 : 0)) * 100;
119 int total = nays_ + yays_ + (proposing ? 1 : 0);
120 if (!total)
121 // There are no votes, so we know nothing
122 return false;
123 int const weight = support / total;
124 // Returns true if the tx has more than minCONSENSUS_PCT (80) percent
125 // agreement. Either voting for _or_ voting against the tx.
126 return weight > p.minCONSENSUS_PCT ||
127 weight < (100 - p.minCONSENSUS_PCT);
128 }
129
131 Tx_t const&
132 tx() const
133 {
134 return tx_;
135 }
136
138 void
140 {
141 ourVote_ = o;
142 }
143
152 [[nodiscard]] bool
153 setVote(NodeID_t const& peer, bool votesYes);
154
159 void
160 unVote(NodeID_t const& peer);
161
173 bool
174 updateVote(int percentTime, bool proposing, ConsensusParms const& p);
175
178 getJson() const;
179
180private:
181 int yays_; //< Number of yes votes
182 int nays_; //< Number of no votes
183 bool ourVote_; //< Our vote (true is yes)
184 Tx_t tx_; //< Transaction under dispute
185 Map_t votes_; //< Map from NodeID to vote
193};
194
195// Track a peer's yes/no vote on a particular disputed tx_
196template <class Tx_t, class NodeID_t>
197bool
198DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
199{
200 auto const [it, inserted] = votes_.insert(std::make_pair(peer, votesYes));
201
202 // new vote
203 if (inserted)
204 {
205 if (votesYes)
206 {
207 JLOG(j_.debug()) << "Peer " << peer << " votes YES on " << tx_.id();
208 ++yays_;
209 }
210 else
211 {
212 JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
213 ++nays_;
214 }
215 return true;
216 }
217 // changes vote to yes
218 else if (votesYes && !it->second)
219 {
220 JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id();
221 --nays_;
222 ++yays_;
223 it->second = true;
224 return true;
225 }
226 // changes vote to no
227 else if (!votesYes && it->second)
228 {
229 JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id();
230 ++nays_;
231 --yays_;
232 it->second = false;
233 return true;
234 }
235 return false;
236}
237
238// Remove a peer's vote on this disputed transaction
239template <class Tx_t, class NodeID_t>
240void
242{
243 auto it = votes_.find(peer);
244
245 if (it != votes_.end())
246 {
247 if (it->second)
248 --yays_;
249 else
250 --nays_;
251
252 votes_.erase(it);
253 }
254}
255
256template <class Tx_t, class NodeID_t>
257bool
259 int percentTime,
260 bool proposing,
261 ConsensusParms const& p)
262{
263 if (ourVote_ && (nays_ == 0))
264 return false;
265
266 if (!ourVote_ && (yays_ == 0))
267 return false;
268
269 bool newPosition;
270 int weight;
271
272 // When proposing, to prevent avalanche stalls, we increase the needed
273 // weight slightly over time. We also need to ensure that the consensus has
274 // made a minimum number of attempts at each "state" before moving
275 // to the next.
276 // Proposing or not, we need to keep track of which state we've reached so
277 // we can determine if the vote has stalled.
278 auto const [requiredPct, newState] = getNeededWeight(
279 p, avalancheState_, percentTime, ++avalancheCounter_, p.avMIN_ROUNDS);
280 if (newState)
281 {
282 avalancheState_ = *newState;
283 avalancheCounter_ = 0;
284 }
285
286 if (proposing) // give ourselves full weight
287 {
288 // This is basically the percentage of nodes voting 'yes' (including us)
289 weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);
290
291 newPosition = weight > requiredPct;
292 }
293 else
294 {
295 // don't let us outweigh a proposing node, just recognize consensus
296 weight = -1;
297 newPosition = yays_ > nays_;
298 }
299
300 if (newPosition == ourVote_)
301 {
302 ++currentVoteCounter_;
303 JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO") << ") on "
304 << tx_.id() << " : weight " << weight << ", percent "
305 << percentTime
306 << ", round(s) with this vote: " << currentVoteCounter_;
307 JLOG(j_.debug()) << Json::Compact{getJson()};
308 return false;
309 }
310
311 currentVoteCounter_ = 0;
312 ourVote_ = newPosition;
313 JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
314 << tx_.id();
315 JLOG(j_.debug()) << Json::Compact{getJson()};
316 return true;
317}
318
319template <class Tx_t, class NodeID_t>
322{
323 using std::to_string;
324
326
327 ret["yays"] = yays_;
328 ret["nays"] = nays_;
329 ret["our_vote"] = ourVote_;
330
331 if (!votes_.empty())
332 {
334 for (auto const& [nodeId, vote] : votes_)
335 votesj[to_string(nodeId)] = vote;
336 ret["votes"] = std::move(votesj);
337 }
338
339 return ret;
340}
341
342} // namespace ripple
343
344#endif
Decorator for streaming out compact json.
Definition: json_writer.h:318
Represents a JSON value.
Definition: json_value.h:149
A generic endpoint for log messages.
Definition: Journal.h:60
Stream debug() const
Definition: Journal.h:328
Stream info() const
Definition: Journal.h:334
A transaction discovered to be in dispute during consensus.
Definition: DisputedTx.h:49
std::size_t avalancheCounter_
How long we have been in the current acceptance phase.
Definition: DisputedTx.h:191
std::size_t currentVoteCounter_
The number of rounds we've gone without changing our vote.
Definition: DisputedTx.h:187
Json::Value getJson() const
JSON representation of dispute, used for debugging.
Definition: DisputedTx.h:321
beast::Journal const j_
Definition: DisputedTx.h:192
bool updateVote(int percentTime, bool proposing, ConsensusParms const &p)
Update our vote given progression of consensus.
Definition: DisputedTx.h:258
boost::container::flat_map< NodeID_t, bool > Map_t
Definition: DisputedTx.h:51
bool setVote(NodeID_t const &peer, bool votesYes)
Change a peer's vote.
Definition: DisputedTx.h:198
ConsensusParms::AvalancheState avalancheState_
Which minimum acceptance percentage phase we are currently in.
Definition: DisputedTx.h:189
Tx_t const & tx() const
The disputed transaction.
Definition: DisputedTx.h:132
typename Tx_t::ID TxID_t
Definition: DisputedTx.h:50
DisputedTx(Tx_t const &tx, bool ourVote, std::size_t numPeers, beast::Journal j)
Constructor.
Definition: DisputedTx.h:61
bool getOurVote() const
Our vote on whether the transaction should be included.
Definition: DisputedTx.h:80
void setOurVote(bool o)
Change our vote.
Definition: DisputedTx.h:139
TxID_t const & ID() const
The unique id/hash of the disputed transaction.
Definition: DisputedTx.h:73
void unVote(NodeID_t const &peer)
Remove a peer's vote.
Definition: DisputedTx.h:241
bool stalled(ConsensusParms const &p, bool proposing, int peersUnchanged) const
Are we and our peers "stalled" where we probably won't change our vote?
Definition: DisputedTx.h:88
T make_pair(T... args)
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:45
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
std::pair< std::size_t, std::optional< ConsensusParms::AvalancheState > > getNeededWeight(ConsensusParms const &p, ConsensusParms::AvalancheState currentState, int percentTime, std::size_t currentRounds, std::size_t minimumRounds)
@ proposing
We are normal participant in consensus and propose our position.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
Consensus algorithm parameters.
std::size_t const avSTALLED_ROUNDS
Number of rounds before a stuck vote is considered unlikely to change because voting stalled.
std::size_t const avMIN_ROUNDS
Number of rounds before certain actions can happen.
std::size_t const minCONSENSUS_PCT
The percentage threshold above which we can declare consensus.
std::map< AvalancheState, AvalancheCutoff > const avalancheCutoffs
Map the consensus requirement avalanche state to the amount of time that must pass before moving to t...
T to_string(T... args)