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 ripple {
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->info().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 =
73 choose(prevLedger->info().hash, candidates.toDisableCandidates);
74 XRPL_ASSERT(
75 nidToKeyMap.contains(n),
76 "ripple::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->info().hash, candidates.toReEnableCandidates);
84 XRPL_ASSERT(
85 nidToKeyMap.contains(n),
86 "ripple::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(),
131 "ripple::NegativeUNLVote::choose : non-empty input");
132 static_assert(NodeID::bytes <= uint256::bytes);
133 NodeID randomPad = NodeID::fromVoid(randomPadData.data());
134 NodeID txNodeID = candidates[0];
135 for (int j = 1; j < candidates.size(); ++j)
136 {
137 if ((candidates[j] ^ randomPad) < (txNodeID ^ randomPad))
138 {
139 txNodeID = candidates[j];
140 }
141 }
142 return txNodeID;
143}
144
147 std::shared_ptr<Ledger const> const& prevLedger,
148 hash_set<NodeID> const& unl,
149 RCLValidations& validations)
150{
151 // Find agreed validation messages received for
152 // the last FLAG_LEDGER_INTERVAL (i.e. 256) ledgers,
153 // for every validator, and fill the score table.
154
155 // Ask the validation container to keep enough validation message history
156 // for next time.
157 auto const seq = prevLedger->info().seq + 1;
158 validations.setSeqToKeep(seq - 1, seq + FLAG_LEDGER_INTERVAL);
159
160 // Find FLAG_LEDGER_INTERVAL (i.e. 256) previous ledger hashes
161 auto const hashIndex = prevLedger->read(keylet::skip());
162 if (!hashIndex || !hashIndex->isFieldPresent(sfHashes))
163 {
164 JLOG(j_.debug()) << "N-UNL: ledger " << seq << " no history.";
165 return {};
166 }
167 auto const ledgerAncestors = hashIndex->getFieldV256(sfHashes).value();
168 auto const numAncestors = ledgerAncestors.size();
169 if (numAncestors < FLAG_LEDGER_INTERVAL)
170 {
171 JLOG(j_.debug()) << "N-UNL: ledger " << seq
172 << " not enough history. Can trace back only "
173 << numAncestors << " ledgers.";
174 return {};
175 }
176
177 // have enough ledger ancestors, build the score table
179 for (auto const& k : unl)
180 {
181 scoreTable[k] = 0;
182 }
183
184 // Query the validation container for every ledger hash and fill
185 // the score table.
186 for (int i = 0; i < FLAG_LEDGER_INTERVAL; ++i)
187 {
188 for (auto const& v : validations.getTrustedForLedger(
189 ledgerAncestors[numAncestors - 1 - i], seq - 2 - i))
190 {
191 if (scoreTable.count(v->getNodeID()))
192 ++scoreTable[v->getNodeID()];
193 }
194 }
195
196 // Return false if the validation message history or local node's
197 // participation in the history is not good.
198 auto const myValidationCount = [&]() -> std::uint32_t {
199 if (auto const it = scoreTable.find(myId_); it != scoreTable.end())
200 return it->second;
201 return 0;
202 }();
203 if (myValidationCount < negativeUNLMinLocalValsToVote)
204 {
205 JLOG(j_.debug()) << "N-UNL: ledger " << seq
206 << ". Local node only issued " << myValidationCount
207 << " validations in last " << FLAG_LEDGER_INTERVAL
208 << " ledgers."
209 << " The reliability measurement could be wrong.";
210 return {};
211 }
212 else if (
213 myValidationCount > negativeUNLMinLocalValsToVote &&
214 myValidationCount <= FLAG_LEDGER_INTERVAL)
215 {
216 return scoreTable;
217 }
218 else
219 {
220 // cannot happen because validations.getTrustedForLedger does not
221 // return multiple validations of the same ledger from a validator.
222 JLOG(j_.error()) << "N-UNL: ledger " << seq << ". Local node issued "
223 << myValidationCount << " validations in last "
224 << FLAG_LEDGER_INTERVAL << " ledgers. Too many!";
225 return {};
226 }
227}
228
231 hash_set<NodeID> const& unl,
232 hash_set<NodeID> const& negUnl,
233 hash_map<NodeID, std::uint32_t> const& scoreTable)
234{
235 // Compute if need to find more validators to disable
236 auto const canAdd = [&]() -> bool {
237 auto const maxNegativeListed = static_cast<std::size_t>(
239 std::size_t negativeListed = 0;
240 for (auto const& n : unl)
241 {
242 if (negUnl.count(n))
243 ++negativeListed;
244 }
245 bool const result = negativeListed < maxNegativeListed;
246 JLOG(j_.trace()) << "N-UNL: nodeId " << myId_ << " lowWaterMark "
247 << negativeUNLLowWaterMark << " highWaterMark "
248 << negativeUNLHighWaterMark << " canAdd " << result
249 << " negativeListed " << negativeListed
250 << " maxNegativeListed " << maxNegativeListed;
251 return result;
252 }();
253
254 Candidates candidates;
255 for (auto const& [nodeId, score] : scoreTable)
256 {
257 JLOG(j_.trace()) << "N-UNL: node " << nodeId << " score " << score;
258
259 // Find toDisable Candidates: check if
260 // (1) canAdd,
261 // (2) has less than negativeUNLLowWaterMark validations,
262 // (3) is not in negUnl, and
263 // (4) is not a new validator.
264 if (canAdd && score < negativeUNLLowWaterMark &&
265 !negUnl.count(nodeId) && !newValidators_.count(nodeId))
266 {
267 JLOG(j_.trace()) << "N-UNL: toDisable candidate " << nodeId;
268 candidates.toDisableCandidates.push_back(nodeId);
269 }
270
271 // Find toReEnable Candidates: check if
272 // (1) has more than negativeUNLHighWaterMark validations,
273 // (2) is in negUnl
274 if (score > negativeUNLHighWaterMark && negUnl.count(nodeId))
275 {
276 JLOG(j_.trace()) << "N-UNL: toReEnable candidate " << nodeId;
277 candidates.toReEnableCandidates.push_back(nodeId);
278 }
279 }
280
281 // If a negative UNL validator is removed from nodes' UNLs, it is no longer
282 // a validator. It should be removed from the negative UNL too.
283 // Note that even if it is still offline and in minority nodes' UNLs, it
284 // will not be re-added to the negative UNL. Because the UNLModify Tx will
285 // not be included in the agreed TxSet of a ledger.
286 //
287 // Find this kind of toReEnable Candidate if did not find any toReEnable
288 // candidate yet: check if
289 // (1) is in negUnl
290 // (2) is not in unl.
291 if (candidates.toReEnableCandidates.empty())
292 {
293 for (auto const& n : negUnl)
294 {
295 if (!unl.count(n))
296 {
297 candidates.toReEnableCandidates.push_back(n);
298 }
299 }
300 }
301 return candidates;
302}
303
304void
306 LedgerIndex seq,
307 hash_set<NodeID> const& nowTrusted)
308{
310 for (auto const& n : nowTrusted)
311 {
312 if (newValidators_.find(n) == newValidators_.end())
313 {
314 JLOG(j_.trace()) << "N-UNL: add a new validator " << n
315 << " at ledger seq=" << seq;
316 newValidators_[n] = seq;
317 }
318 }
319}
320
321void
323{
325 auto i = newValidators_.begin();
326 while (i != newValidators_.end())
327 {
328 if (seq - i->second > newValidatorDisableSkip)
329 {
330 i = newValidators_.erase(i);
331 }
332 else
333 {
334 ++i;
335 }
336 }
337}
338
339} // namespace ripple
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...
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: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 std::size_t constexpr bytes
Definition base_uint.h:89
static base_uint fromVoid(void const *data)
Definition base_uint.h:300
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:177
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
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:486
NodeID calcNodeID(PublicKey const &)
Calculate the 160-bit node ID from a node public key.
boost::intrusive_ptr< SHAMapItem > make_shamapitem(uint256 const &tag, Slice data)
Definition SHAMapItem.h:142
std::uint32_t constexpr FLAG_LEDGER_INTERVAL
Definition Ledger.h:407
T size(T... args)
std::vector< NodeID > toReEnableCandidates
std::vector< NodeID > toDisableCandidates