rippled
Loading...
Searching...
No Matches
NegativeUNLVote.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2020 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#include <xrpld/app/consensus/RCLValidations.h>
21#include <xrpld/app/ledger/Ledger.h>
22#include <xrpld/app/misc/NegativeUNLVote.h>
23#include <xrpld/shamap/SHAMapItem.h>
24
25namespace ripple {
26
28 : myId_(myId), j_(j)
29{
30}
31
32void
34 std::shared_ptr<Ledger const> const& prevLedger,
35 hash_set<PublicKey> const& unlKeys,
36 RCLValidations& validations,
37 std::shared_ptr<SHAMap> const& initialSet)
38{
39 // Voting steps:
40 // -- build a reliability score table of validators
41 // -- process the table and find all candidates to disable or to re-enable
42 // -- pick one to disable and one to re-enable if any
43 // -- if found candidates, add ttUNL_MODIFY Tx
44
45 // Build NodeID set for internal use.
46 // Build NodeID to PublicKey map for lookup before creating ttUNL_MODIFY Tx.
47 hash_set<NodeID> unlNodeIDs;
49 for (auto const& k : unlKeys)
50 {
51 auto nid = calcNodeID(k);
52 nidToKeyMap.emplace(nid, k);
53 unlNodeIDs.emplace(nid);
54 }
55
56 // Build a reliability score table of validators
58 buildScoreTable(prevLedger, unlNodeIDs, validations))
59 {
60 // build next negUnl
61 auto negUnlKeys = prevLedger->negativeUNL();
62 auto negUnlToDisable = prevLedger->validatorToDisable();
63 auto negUnlToReEnable = prevLedger->validatorToReEnable();
64 if (negUnlToDisable)
65 negUnlKeys.insert(*negUnlToDisable);
66 if (negUnlToReEnable)
67 negUnlKeys.erase(*negUnlToReEnable);
68
69 hash_set<NodeID> negUnlNodeIDs;
70 for (auto const& k : negUnlKeys)
71 {
72 auto nid = calcNodeID(k);
73 negUnlNodeIDs.emplace(nid);
74 if (!nidToKeyMap.count(nid))
75 {
76 nidToKeyMap.emplace(nid, k);
77 }
78 }
79
80 auto const seq = prevLedger->info().seq + 1;
82
83 // Process the table and find all candidates to disable or to re-enable
84 auto const candidates =
85 findAllCandidates(unlNodeIDs, negUnlNodeIDs, *scoreTable);
86
87 // Pick one to disable and one to re-enable if any, add ttUNL_MODIFY Tx
88 if (!candidates.toDisableCandidates.empty())
89 {
90 auto n =
91 choose(prevLedger->info().hash, candidates.toDisableCandidates);
92 XRPL_ASSERT(
93 nidToKeyMap.contains(n),
94 "ripple::NegativeUNLVote::doVoting : found node to disable");
95 addTx(seq, nidToKeyMap.at(n), ToDisable, initialSet);
96 }
97
98 if (!candidates.toReEnableCandidates.empty())
99 {
100 auto n = choose(
101 prevLedger->info().hash, candidates.toReEnableCandidates);
102 XRPL_ASSERT(
103 nidToKeyMap.contains(n),
104 "ripple::NegativeUNLVote::doVoting : found node to enable");
105 addTx(seq, nidToKeyMap.at(n), ToReEnable, initialSet);
106 }
107 }
108}
109
110void
113 PublicKey const& vp,
114 NegativeUNLModify modify,
115 std::shared_ptr<SHAMap> const& initialSet)
116{
117 STTx negUnlTx(ttUNL_MODIFY, [&](auto& obj) {
118 obj.setFieldU8(sfUNLModifyDisabling, modify == ToDisable ? 1 : 0);
119 obj.setFieldU32(sfLedgerSequence, seq);
120 obj.setFieldVL(sfUNLModifyValidator, vp.slice());
121 });
122
123 Serializer s;
124 negUnlTx.add(s);
125 if (!initialSet->addGiveItem(
127 make_shamapitem(negUnlTx.getTransactionID(), s.slice())))
128 {
129 JLOG(j_.warn()) << "N-UNL: ledger seq=" << seq
130 << ", add ttUNL_MODIFY tx failed";
131 }
132 else
133 {
134 JLOG(j_.debug()) << "N-UNL: ledger seq=" << seq
135 << ", add a ttUNL_MODIFY Tx with txID: "
136 << negUnlTx.getTransactionID() << ", the validator to "
137 << (modify == ToDisable ? "disable: " : "re-enable: ")
138 << vp;
139 }
140}
141
142NodeID
144 uint256 const& randomPadData,
145 std::vector<NodeID> const& candidates)
146{
147 XRPL_ASSERT(
148 !candidates.empty(),
149 "ripple::NegativeUNLVote::choose : non-empty input");
150 static_assert(NodeID::bytes <= uint256::bytes);
151 NodeID randomPad = NodeID::fromVoid(randomPadData.data());
152 NodeID txNodeID = candidates[0];
153 for (int j = 1; j < candidates.size(); ++j)
154 {
155 if ((candidates[j] ^ randomPad) < (txNodeID ^ randomPad))
156 {
157 txNodeID = candidates[j];
158 }
159 }
160 return txNodeID;
161}
162
165 std::shared_ptr<Ledger const> const& prevLedger,
166 hash_set<NodeID> const& unl,
167 RCLValidations& validations)
168{
169 // Find agreed validation messages received for
170 // the last FLAG_LEDGER_INTERVAL (i.e. 256) ledgers,
171 // for every validator, and fill the score table.
172
173 // Ask the validation container to keep enough validation message history
174 // for next time.
175 auto const seq = prevLedger->info().seq + 1;
176 validations.setSeqToKeep(seq - 1, seq + FLAG_LEDGER_INTERVAL);
177
178 // Find FLAG_LEDGER_INTERVAL (i.e. 256) previous ledger hashes
179 auto const hashIndex = prevLedger->read(keylet::skip());
180 if (!hashIndex || !hashIndex->isFieldPresent(sfHashes))
181 {
182 JLOG(j_.debug()) << "N-UNL: ledger " << seq << " no history.";
183 return {};
184 }
185 auto const ledgerAncestors = hashIndex->getFieldV256(sfHashes).value();
186 auto const numAncestors = ledgerAncestors.size();
187 if (numAncestors < FLAG_LEDGER_INTERVAL)
188 {
189 JLOG(j_.debug()) << "N-UNL: ledger " << seq
190 << " not enough history. Can trace back only "
191 << numAncestors << " ledgers.";
192 return {};
193 }
194
195 // have enough ledger ancestors, build the score table
197 for (auto const& k : unl)
198 {
199 scoreTable[k] = 0;
200 }
201
202 // Query the validation container for every ledger hash and fill
203 // the score table.
204 for (int i = 0; i < FLAG_LEDGER_INTERVAL; ++i)
205 {
206 for (auto const& v : validations.getTrustedForLedger(
207 ledgerAncestors[numAncestors - 1 - i], seq - 2 - i))
208 {
209 if (scoreTable.count(v->getNodeID()))
210 ++scoreTable[v->getNodeID()];
211 }
212 }
213
214 // Return false if the validation message history or local node's
215 // participation in the history is not good.
216 auto const myValidationCount = [&]() -> std::uint32_t {
217 if (auto const it = scoreTable.find(myId_); it != scoreTable.end())
218 return it->second;
219 return 0;
220 }();
221 if (myValidationCount < negativeUNLMinLocalValsToVote)
222 {
223 JLOG(j_.debug()) << "N-UNL: ledger " << seq
224 << ". Local node only issued " << myValidationCount
225 << " validations in last " << FLAG_LEDGER_INTERVAL
226 << " ledgers."
227 << " The reliability measurement could be wrong.";
228 return {};
229 }
230 else if (
231 myValidationCount > negativeUNLMinLocalValsToVote &&
232 myValidationCount <= FLAG_LEDGER_INTERVAL)
233 {
234 return scoreTable;
235 }
236 else
237 {
238 // cannot happen because validations.getTrustedForLedger does not
239 // return multiple validations of the same ledger from a validator.
240 JLOG(j_.error()) << "N-UNL: ledger " << seq << ". Local node issued "
241 << myValidationCount << " validations in last "
242 << FLAG_LEDGER_INTERVAL << " ledgers. Too many!";
243 return {};
244 }
245}
246
249 hash_set<NodeID> const& unl,
250 hash_set<NodeID> const& negUnl,
251 hash_map<NodeID, std::uint32_t> const& scoreTable)
252{
253 // Compute if need to find more validators to disable
254 auto const canAdd = [&]() -> bool {
255 auto const maxNegativeListed = static_cast<std::size_t>(
257 std::size_t negativeListed = 0;
258 for (auto const& n : unl)
259 {
260 if (negUnl.count(n))
261 ++negativeListed;
262 }
263 bool const result = negativeListed < maxNegativeListed;
264 JLOG(j_.trace()) << "N-UNL: nodeId " << myId_ << " lowWaterMark "
265 << negativeUNLLowWaterMark << " highWaterMark "
266 << negativeUNLHighWaterMark << " canAdd " << result
267 << " negativeListed " << negativeListed
268 << " maxNegativeListed " << maxNegativeListed;
269 return result;
270 }();
271
272 Candidates candidates;
273 for (auto const& [nodeId, score] : scoreTable)
274 {
275 JLOG(j_.trace()) << "N-UNL: node " << nodeId << " score " << score;
276
277 // Find toDisable Candidates: check if
278 // (1) canAdd,
279 // (2) has less than negativeUNLLowWaterMark validations,
280 // (3) is not in negUnl, and
281 // (4) is not a new validator.
282 if (canAdd && score < negativeUNLLowWaterMark &&
283 !negUnl.count(nodeId) && !newValidators_.count(nodeId))
284 {
285 JLOG(j_.trace()) << "N-UNL: toDisable candidate " << nodeId;
286 candidates.toDisableCandidates.push_back(nodeId);
287 }
288
289 // Find toReEnable Candidates: check if
290 // (1) has more than negativeUNLHighWaterMark validations,
291 // (2) is in negUnl
292 if (score > negativeUNLHighWaterMark && negUnl.count(nodeId))
293 {
294 JLOG(j_.trace()) << "N-UNL: toReEnable candidate " << nodeId;
295 candidates.toReEnableCandidates.push_back(nodeId);
296 }
297 }
298
299 // If a negative UNL validator is removed from nodes' UNLs, it is no longer
300 // a validator. It should be removed from the negative UNL too.
301 // Note that even if it is still offline and in minority nodes' UNLs, it
302 // will not be re-added to the negative UNL. Because the UNLModify Tx will
303 // not be included in the agreed TxSet of a ledger.
304 //
305 // Find this kind of toReEnable Candidate if did not find any toReEnable
306 // candidate yet: check if
307 // (1) is in negUnl
308 // (2) is not in unl.
309 if (candidates.toReEnableCandidates.empty())
310 {
311 for (auto const& n : negUnl)
312 {
313 if (!unl.count(n))
314 {
315 candidates.toReEnableCandidates.push_back(n);
316 }
317 }
318 }
319 return candidates;
320}
321
322void
325 hash_set<NodeID> const& nowTrusted)
326{
328 for (auto const& n : nowTrusted)
329 {
330 if (newValidators_.find(n) == newValidators_.end())
331 {
332 JLOG(j_.trace()) << "N-UNL: add a new validator " << n
333 << " at ledger seq=" << seq;
334 newValidators_[n] = seq;
335 }
336 }
337}
338
339void
341{
343 auto i = newValidators_.begin();
344 while (i != newValidators_.end())
345 {
346 if (seq - i->second > newValidatorDisableSkip)
347 {
348 i = newValidators_.erase(i);
349 }
350 else
351 {
352 ++i;
353 }
354 }
355}
356
357} // namespace ripple
T at(T... args)
T ceil(T... args)
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 trace() const
Severity stream access functions.
Definition: Journal.h:322
Stream warn() const
Definition: Journal.h:340
static constexpr size_t negativeUNLHighWaterMark
An unreliable validator must have more than negativeUNLHighWaterMark validations in the last flag led...
NodeID choose(uint256 const &randomPadData, std::vector< NodeID > const &candidates)
Pick one candidate from a vector of candidates.
std::optional< hash_map< NodeID, std::uint32_t > > buildScoreTable(std::shared_ptr< Ledger const > const &prevLedger, hash_set< NodeID > const &unl, RCLValidations &validations)
Build a reliability measurement score table of validators' validation messages in the last flag ledge...
NegativeUNLModify
A flag indicating whether a UNLModify Tx is to disable or to re-enable a validator.
void purgeNewValidators(LedgerIndex seq)
Purge validators that are not new anymore.
Candidates const findAllCandidates(hash_set< NodeID > const &unl, hash_set< NodeID > const &negUnl, hash_map< NodeID, std::uint32_t > const &scoreTable)
Process the score table and find all disabling and re-enabling candidates.
static constexpr size_t newValidatorDisableSkip
We don't want to disable new validators immediately after adding them.
static constexpr size_t negativeUNLLowWaterMark
A validator is considered unreliable if its validations is less than negativeUNLLowWaterMark in the l...
void doVoting(std::shared_ptr< Ledger const > const &prevLedger, hash_set< PublicKey > const &unlKeys, RCLValidations &validations, std::shared_ptr< SHAMap > const &initialSet)
Cast our local vote on the NegativeUNL candidates.
hash_map< NodeID, LedgerIndex > newValidators_
void newValidators(LedgerIndex seq, hash_set< NodeID > const &nowTrusted)
Notify NegativeUNLVote that new validators are added.
static constexpr float negativeUNLMaxListed
We only want to put 25% of the UNL on the NegativeUNL.
void addTx(LedgerIndex seq, PublicKey const &vp, NegativeUNLModify modify, std::shared_ptr< SHAMap > const &initialSet)
Add a ttUNL_MODIFY Tx to the transaction set.
NegativeUNLVote(NodeID const &myId, beast::Journal j)
Constructor.
static constexpr size_t negativeUNLMinLocalValsToVote
The minimum number of validations of the local node for it to participate in the voting.
A public key.
Definition: PublicKey.h:62
Slice slice() const noexcept
Definition: PublicKey.h:123
std::vector< WrappedValidationType > getTrustedForLedger(ID const &ledgerID, Seq const &seq)
Get trusted full validations for a specific ledger.
Definition: Validations.h:1059
void setSeqToKeep(Seq const &low, Seq const &high)
Set the range [low, high) of validations to keep from expire.
Definition: Validations.h:716
static std::size_t constexpr bytes
Definition: base_uint.h:108
static base_uint fromVoid(void const *data)
Definition: base_uint.h:319
pointer data()
Definition: base_uint.h:125
T contains(T... args)
T count(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T find(T... args)
Keylet const & skip() noexcept
The index of the "short" skip list.
Definition: Indexes.cpp:189
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
NodeID calcNodeID(PublicKey const &)
Calculate the 160-bit node ID from a node public key.
Definition: PublicKey.cpp:319
boost::intrusive_ptr< SHAMapItem > make_shamapitem(uint256 const &tag, Slice data)
Definition: SHAMapItem.h:161
std::uint32_t constexpr FLAG_LEDGER_INTERVAL
Definition: Ledger.h:426
T size(T... args)
std::vector< NodeID > toReEnableCandidates
std::vector< NodeID > toDisableCandidates
Set the sequence number on a JTx.
Definition: seq.h:34