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