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
89 ConsensusParms const& p,
90 bool proposing,
91 int peersUnchanged,
93 std::unique_ptr<std::stringstream> const& clog) const
94 {
95 // at() can throw, but the map is built by hand to ensure all valid
96 // values are available.
97 auto const& currentCutoff = p.avalancheCutoffs.at(avalancheState_);
98 auto const& nextCutoff = p.avalancheCutoffs.at(currentCutoff.next);
99
100 // We're have not reached the final avalanche state, or been there long
101 // enough, so there's room for change. Check the times in case the state
102 // machine is altered to allow states to loop.
103 if (nextCutoff.consensusTime > currentCutoff.consensusTime ||
105 return false;
106
107 // We've haven't had this vote for minimum rounds yet. Things could
108 // change.
110 return false;
111
112 // If we or any peers have changed a vote in several rounds, then
113 // things could still change. But if _either_ has not changed in that
114 // long, we're unlikely to change our vote any time soon. (This prevents
115 // a malicious peer from flip-flopping a vote to prevent consensus.)
116 if (peersUnchanged < p.avSTALLED_ROUNDS &&
118 return false;
119
120 // Does this transaction have more than 80% agreement
121
122 // Compute the percentage of nodes voting 'yes' (possibly including us)
123 int const support = (yays_ + (proposing && ourVote_ ? 1 : 0)) * 100;
124 int total = nays_ + yays_ + (proposing ? 1 : 0);
125 if (!total)
126 // There are no votes, so we know nothing
127 return false;
128 int const weight = support / total;
129 // Returns true if the tx has more than minCONSENSUS_PCT (80) percent
130 // agreement. Either voting for _or_ voting against the tx.
131 bool const stalled =
132 weight > p.minCONSENSUS_PCT || weight < (100 - p.minCONSENSUS_PCT);
133
134 if (stalled)
135 {
136 // stalling is an error condition for even a single
137 // transaction.
139 s << "Transaction " << ID() << " is stalled. We have been voting "
140 << (getOurVote() ? "YES" : "NO") << " for " << currentVoteCounter_
141 << " rounds. Peers have not changed their votes in "
142 << peersUnchanged << " rounds. The transaction has " << weight
143 << "% support. ";
144 JLOG(j_.error()) << s.str();
145 CLOG(clog) << s.str();
146 }
147
148 return stalled;
149 }
150
152 Tx_t const&
153 tx() const
154 {
155 return tx_;
156 }
157
159 void
161 {
162 ourVote_ = o;
163 }
164
173 [[nodiscard]] bool
174 setVote(NodeID_t const& peer, bool votesYes);
175
180 void
181 unVote(NodeID_t const& peer);
182
194 bool
195 updateVote(int percentTime, bool proposing, ConsensusParms const& p);
196
199 getJson() const;
200
201private:
202 int yays_; //< Number of yes votes
203 int nays_; //< Number of no votes
204 bool ourVote_; //< Our vote (true is yes)
205 Tx_t tx_; //< Transaction under dispute
206 Map_t votes_; //< Map from NodeID to vote
214};
215
216// Track a peer's yes/no vote on a particular disputed tx_
217template <class Tx_t, class NodeID_t>
218bool
219DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
220{
221 auto const [it, inserted] = votes_.insert(std::make_pair(peer, votesYes));
222
223 // new vote
224 if (inserted)
225 {
226 if (votesYes)
227 {
228 JLOG(j_.debug()) << "Peer " << peer << " votes YES on " << tx_.id();
229 ++yays_;
230 }
231 else
232 {
233 JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
234 ++nays_;
235 }
236 return true;
237 }
238 // changes vote to yes
239 else if (votesYes && !it->second)
240 {
241 JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id();
242 --nays_;
243 ++yays_;
244 it->second = true;
245 return true;
246 }
247 // changes vote to no
248 else if (!votesYes && it->second)
249 {
250 JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id();
251 ++nays_;
252 --yays_;
253 it->second = false;
254 return true;
255 }
256 return false;
257}
258
259// Remove a peer's vote on this disputed transaction
260template <class Tx_t, class NodeID_t>
261void
263{
264 auto it = votes_.find(peer);
265
266 if (it != votes_.end())
267 {
268 if (it->second)
269 --yays_;
270 else
271 --nays_;
272
273 votes_.erase(it);
274 }
275}
276
277template <class Tx_t, class NodeID_t>
278bool
280 int percentTime,
281 bool proposing,
282 ConsensusParms const& p)
283{
284 if (ourVote_ && (nays_ == 0))
285 return false;
286
287 if (!ourVote_ && (yays_ == 0))
288 return false;
289
290 bool newPosition;
291 int weight;
292
293 // When proposing, to prevent avalanche stalls, we increase the needed
294 // weight slightly over time. We also need to ensure that the consensus has
295 // made a minimum number of attempts at each "state" before moving
296 // to the next.
297 // Proposing or not, we need to keep track of which state we've reached so
298 // we can determine if the vote has stalled.
299 auto const [requiredPct, newState] = getNeededWeight(
300 p, avalancheState_, percentTime, ++avalancheCounter_, p.avMIN_ROUNDS);
301 if (newState)
302 {
303 avalancheState_ = *newState;
304 avalancheCounter_ = 0;
305 }
306
307 if (proposing) // give ourselves full weight
308 {
309 // This is basically the percentage of nodes voting 'yes' (including us)
310 weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);
311
312 newPosition = weight > requiredPct;
313 }
314 else
315 {
316 // don't let us outweigh a proposing node, just recognize consensus
317 weight = -1;
318 newPosition = yays_ > nays_;
319 }
320
321 if (newPosition == ourVote_)
322 {
323 ++currentVoteCounter_;
324 JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO") << ") on "
325 << tx_.id() << " : weight " << weight << ", percent "
326 << percentTime
327 << ", round(s) with this vote: " << currentVoteCounter_;
328 JLOG(j_.debug()) << Json::Compact{getJson()};
329 return false;
330 }
331
332 currentVoteCounter_ = 0;
333 ourVote_ = newPosition;
334 JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
335 << tx_.id();
336 JLOG(j_.debug()) << Json::Compact{getJson()};
337 return true;
338}
339
340template <class Tx_t, class NodeID_t>
343{
344 using std::to_string;
345
347
348 ret["yays"] = yays_;
349 ret["nays"] = nays_;
350 ret["our_vote"] = ourVote_;
351
352 if (!votes_.empty())
353 {
355 for (auto const& [nodeId, vote] : votes_)
356 votesj[to_string(nodeId)] = vote;
357 ret["votes"] = std::move(votesj);
358 }
359
360 return ret;
361}
362
363} // namespace ripple
364
365#endif
Decorator for streaming out compact json.
Represents a JSON value.
Definition json_value.h:149
A generic endpoint for log messages.
Definition Journal.h:60
Stream error() const
Definition Journal.h:346
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:212
std::size_t currentVoteCounter_
The number of rounds we've gone without changing our vote.
Definition DisputedTx.h:208
Json::Value getJson() const
JSON representation of dispute, used for debugging.
Definition DisputedTx.h:342
beast::Journal const j_
Definition DisputedTx.h:213
bool updateVote(int percentTime, bool proposing, ConsensusParms const &p)
Update our vote given progression of consensus.
Definition DisputedTx.h:279
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:219
ConsensusParms::AvalancheState avalancheState_
Which minimum acceptance percentage phase we are currently in.
Definition DisputedTx.h:210
Tx_t const & tx() const
The disputed transaction.
Definition DisputedTx.h:153
typename Tx_t::ID TxID_t
Definition DisputedTx.h:50
bool stalled(ConsensusParms const &p, bool proposing, int peersUnchanged, beast::Journal j, std::unique_ptr< std::stringstream > const &clog) const
Are we and our peers "stalled" where we probably won't change our vote?
Definition DisputedTx.h:88
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:160
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:262
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.
T str(T... args)
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)