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]]
153 bool
154 setVote(NodeID_t const& peer, bool votesYes);
155
160 void
161 unVote(NodeID_t const& peer);
162
174 bool
175 updateVote(int percentTime, bool proposing, ConsensusParms const& p);
176
179 getJson() const;
180
181private:
182 int yays_; //< Number of yes votes
183 int nays_; //< Number of no votes
184 bool ourVote_; //< Our vote (true is yes)
185 Tx_t tx_; //< Transaction under dispute
186 Map_t votes_; //< Map from NodeID to vote
194};
195
196// Track a peer's yes/no vote on a particular disputed tx_
197template <class Tx_t, class NodeID_t>
198bool
199DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
200{
201 auto const [it, inserted] = votes_.insert(std::make_pair(peer, votesYes));
202
203 // new vote
204 if (inserted)
205 {
206 if (votesYes)
207 {
208 JLOG(j_.debug()) << "Peer " << peer << " votes YES on " << tx_.id();
209 ++yays_;
210 }
211 else
212 {
213 JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
214 ++nays_;
215 }
216 return true;
217 }
218 // changes vote to yes
219 else if (votesYes && !it->second)
220 {
221 JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id();
222 --nays_;
223 ++yays_;
224 it->second = true;
225 return true;
226 }
227 // changes vote to no
228 else if (!votesYes && it->second)
229 {
230 JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id();
231 ++nays_;
232 --yays_;
233 it->second = false;
234 return true;
235 }
236 return false;
237}
238
239// Remove a peer's vote on this disputed transaction
240template <class Tx_t, class NodeID_t>
241void
243{
244 auto it = votes_.find(peer);
245
246 if (it != votes_.end())
247 {
248 if (it->second)
249 --yays_;
250 else
251 --nays_;
252
253 votes_.erase(it);
254 }
255}
256
257template <class Tx_t, class NodeID_t>
258bool
260 int percentTime,
261 bool proposing,
262 ConsensusParms const& p)
263{
264 if (ourVote_ && (nays_ == 0))
265 return false;
266
267 if (!ourVote_ && (yays_ == 0))
268 return false;
269
270 bool newPosition;
271 int weight;
272
273 // When proposing, to prevent avalanche stalls, we increase the needed
274 // weight slightly over time. We also need to ensure that the consensus has
275 // made a minimum number of attempts at each "state" before moving
276 // to the next.
277 // Proposing or not, we need to keep track of which state we've reached so
278 // we can determine if the vote has stalled.
279 auto const [requiredPct, newState] = getNeededWeight(
280 p, avalancheState_, percentTime, ++avalancheCounter_, p.avMIN_ROUNDS);
281 if (newState)
282 {
283 avalancheState_ = *newState;
284 avalancheCounter_ = 0;
285 }
286
287 if (proposing) // give ourselves full weight
288 {
289 // This is basically the percentage of nodes voting 'yes' (including us)
290 weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);
291
292 newPosition = weight > requiredPct;
293 }
294 else
295 {
296 // don't let us outweigh a proposing node, just recognize consensus
297 weight = -1;
298 newPosition = yays_ > nays_;
299 }
300
301 if (newPosition == ourVote_)
302 {
303 ++currentVoteCounter_;
304 JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO") << ") on "
305 << tx_.id() << " : weight " << weight << ", percent "
306 << percentTime
307 << ", round(s) with this vote: " << currentVoteCounter_;
308 JLOG(j_.debug()) << Json::Compact{getJson()};
309 return false;
310 }
311
312 currentVoteCounter_ = 0;
313 ourVote_ = newPosition;
314 JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
315 << tx_.id();
316 JLOG(j_.debug()) << Json::Compact{getJson()};
317 return true;
318}
319
320template <class Tx_t, class NodeID_t>
323{
324 using std::to_string;
325
327
328 ret["yays"] = yays_;
329 ret["nays"] = nays_;
330 ret["our_vote"] = ourVote_;
331
332 if (!votes_.empty())
333 {
335 for (auto const& [nodeId, vote] : votes_)
336 votesj[to_string(nodeId)] = vote;
337 ret["votes"] = std::move(votesj);
338 }
339
340 return ret;
341}
342
343} // namespace ripple
344
345#endif
Decorator for streaming out compact json.
Definition: json_writer.h:318
Represents a JSON value.
Definition: json_value.h:148
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:192
std::size_t currentVoteCounter_
The number of rounds we've gone without changing our vote.
Definition: DisputedTx.h:188
Json::Value getJson() const
JSON representation of dispute, used for debugging.
Definition: DisputedTx.h:322
beast::Journal const j_
Definition: DisputedTx.h:193
bool updateVote(int percentTime, bool proposing, ConsensusParms const &p)
Update our vote given progression of consensus.
Definition: DisputedTx.h:259
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:199
ConsensusParms::AvalancheState avalancheState_
Which minimum acceptance percentage phase we are currently in.
Definition: DisputedTx.h:190
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:242
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:44
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
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)