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