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
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.count(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(nidToKeyMap.contains(n), "xrpl::NegativeUNLVote::doVoting : found node to disable");
72 addTx(seq, nidToKeyMap.at(n), ToDisable, initialSet);
73 }
74
75 if (!candidates.toReEnableCandidates.empty())
76 {
77 auto n = choose(prevLedger->header().hash, candidates.toReEnableCandidates);
78 XRPL_ASSERT(nidToKeyMap.contains(n), "xrpl::NegativeUNLVote::doVoting : found node to enable");
79 addTx(seq, nidToKeyMap.at(n), ToReEnable, initialSet);
80 }
81 }
82}
83
84void
86 LedgerIndex seq,
87 PublicKey const& vp,
88 NegativeUNLModify modify,
89 std::shared_ptr<SHAMap> const& initialSet)
90{
91 STTx negUnlTx(ttUNL_MODIFY, [&](auto& obj) {
92 obj.setFieldU8(sfUNLModifyDisabling, modify == ToDisable ? 1 : 0);
93 obj.setFieldU32(sfLedgerSequence, seq);
94 obj.setFieldVL(sfUNLModifyValidator, vp.slice());
95 });
96
97 Serializer s;
98 negUnlTx.add(s);
99 if (!initialSet->addGiveItem(
100 SHAMapNodeType::tnTRANSACTION_NM, make_shamapitem(negUnlTx.getTransactionID(), s.slice())))
101 {
102 JLOG(j_.warn()) << "N-UNL: ledger seq=" << seq << ", add ttUNL_MODIFY tx failed";
103 }
104 else
105 {
106 JLOG(j_.debug()) << "N-UNL: ledger seq=" << seq
107 << ", add a ttUNL_MODIFY Tx with txID: " << negUnlTx.getTransactionID()
108 << ", the validator to " << (modify == ToDisable ? "disable: " : "re-enable: ") << vp;
109 }
110}
111
112NodeID
113NegativeUNLVote::choose(uint256 const& randomPadData, std::vector<NodeID> const& candidates)
114{
115 XRPL_ASSERT(!candidates.empty(), "xrpl::NegativeUNLVote::choose : non-empty input");
116 static_assert(NodeID::bytes <= uint256::bytes);
117 NodeID randomPad = NodeID::fromVoid(randomPadData.data());
118 NodeID txNodeID = candidates[0];
119 for (int j = 1; j < candidates.size(); ++j)
120 {
121 if ((candidates[j] ^ randomPad) < (txNodeID ^ randomPad))
122 {
123 txNodeID = candidates[j];
124 }
125 }
126 return txNodeID;
127}
128
131 std::shared_ptr<Ledger const> const& prevLedger,
132 hash_set<NodeID> const& unl,
133 RCLValidations& validations)
134{
135 // Find agreed validation messages received for
136 // the last FLAG_LEDGER_INTERVAL (i.e. 256) ledgers,
137 // for every validator, and fill the score table.
138
139 // Ask the validation container to keep enough validation message history
140 // for next time.
141 auto const seq = prevLedger->header().seq + 1;
142 validations.setSeqToKeep(seq - 1, seq + FLAG_LEDGER_INTERVAL);
143
144 // Find FLAG_LEDGER_INTERVAL (i.e. 256) previous ledger hashes
145 auto const hashIndex = prevLedger->read(keylet::skip());
146 if (!hashIndex || !hashIndex->isFieldPresent(sfHashes))
147 {
148 JLOG(j_.debug()) << "N-UNL: ledger " << seq << " no history.";
149 return {};
150 }
151 auto const ledgerAncestors = hashIndex->getFieldV256(sfHashes).value();
152 auto const numAncestors = ledgerAncestors.size();
153 if (numAncestors < FLAG_LEDGER_INTERVAL)
154 {
155 JLOG(j_.debug()) << "N-UNL: ledger " << seq << " not enough history. Can trace back only " << numAncestors
156 << " ledgers.";
157 return {};
158 }
159
160 // have enough ledger ancestors, build the score table
162 for (auto const& k : unl)
163 {
164 scoreTable[k] = 0;
165 }
166
167 // Query the validation container for every ledger hash and fill
168 // the score table.
169 for (int i = 0; i < FLAG_LEDGER_INTERVAL; ++i)
170 {
171 for (auto const& v : validations.getTrustedForLedger(ledgerAncestors[numAncestors - 1 - i], seq - 2 - i))
172 {
173 if (scoreTable.count(v->getNodeID()))
174 ++scoreTable[v->getNodeID()];
175 }
176 }
177
178 // Return false if the validation message history or local node's
179 // participation in the history is not good.
180 auto const myValidationCount = [&]() -> std::uint32_t {
181 if (auto const it = scoreTable.find(myId_); it != scoreTable.end())
182 return it->second;
183 return 0;
184 }();
185 if (myValidationCount < negativeUNLMinLocalValsToVote)
186 {
187 JLOG(j_.debug()) << "N-UNL: ledger " << seq << ". Local node only issued " << myValidationCount
188 << " validations in last " << FLAG_LEDGER_INTERVAL << " ledgers."
189 << " The reliability measurement could be wrong.";
190 return {};
191 }
192 else if (myValidationCount > negativeUNLMinLocalValsToVote && myValidationCount <= FLAG_LEDGER_INTERVAL)
193 {
194 return scoreTable;
195 }
196 else
197 {
198 // cannot happen because validations.getTrustedForLedger does not
199 // return multiple validations of the same ledger from a validator.
200 JLOG(j_.error()) << "N-UNL: ledger " << seq << ". Local node issued " << myValidationCount
201 << " validations in last " << FLAG_LEDGER_INTERVAL << " ledgers. Too many!";
202 return {};
203 }
204}
205
208 hash_set<NodeID> const& unl,
209 hash_set<NodeID> const& negUnl,
210 hash_map<NodeID, std::uint32_t> const& scoreTable)
211{
212 // Compute if need to find more validators to disable
213 auto const canAdd = [&]() -> bool {
214 auto const maxNegativeListed = static_cast<std::size_t>(std::ceil(unl.size() * negativeUNLMaxListed));
215 std::size_t negativeListed = 0;
216 for (auto const& n : unl)
217 {
218 if (negUnl.count(n))
219 ++negativeListed;
220 }
221 bool const result = negativeListed < maxNegativeListed;
222 JLOG(j_.trace()) << "N-UNL: nodeId " << myId_ << " lowWaterMark " << negativeUNLLowWaterMark
223 << " highWaterMark " << negativeUNLHighWaterMark << " canAdd " << result << " negativeListed "
224 << negativeListed << " maxNegativeListed " << maxNegativeListed;
225 return result;
226 }();
227
228 Candidates candidates;
229 for (auto const& [nodeId, score] : scoreTable)
230 {
231 JLOG(j_.trace()) << "N-UNL: node " << nodeId << " score " << score;
232
233 // Find toDisable Candidates: check if
234 // (1) canAdd,
235 // (2) has less than negativeUNLLowWaterMark validations,
236 // (3) is not in negUnl, and
237 // (4) is not a new validator.
238 if (canAdd && score < negativeUNLLowWaterMark && !negUnl.count(nodeId) && !newValidators_.count(nodeId))
239 {
240 JLOG(j_.trace()) << "N-UNL: toDisable candidate " << nodeId;
241 candidates.toDisableCandidates.push_back(nodeId);
242 }
243
244 // Find toReEnable Candidates: check if
245 // (1) has more than negativeUNLHighWaterMark validations,
246 // (2) is in negUnl
247 if (score > negativeUNLHighWaterMark && negUnl.count(nodeId))
248 {
249 JLOG(j_.trace()) << "N-UNL: toReEnable candidate " << nodeId;
250 candidates.toReEnableCandidates.push_back(nodeId);
251 }
252 }
253
254 // If a negative UNL validator is removed from nodes' UNLs, it is no longer
255 // a validator. It should be removed from the negative UNL too.
256 // Note that even if it is still offline and in minority nodes' UNLs, it
257 // will not be re-added to the negative UNL. Because the UNLModify Tx will
258 // not be included in the agreed TxSet of a ledger.
259 //
260 // Find this kind of toReEnable Candidate if did not find any toReEnable
261 // candidate yet: check if
262 // (1) is in negUnl
263 // (2) is not in unl.
264 if (candidates.toReEnableCandidates.empty())
265 {
266 for (auto const& n : negUnl)
267 {
268 if (!unl.count(n))
269 {
270 candidates.toReEnableCandidates.push_back(n);
271 }
272 }
273 }
274 return candidates;
275}
276
277void
279{
281 for (auto const& n : nowTrusted)
282 {
283 if (newValidators_.find(n) == newValidators_.end())
284 {
285 JLOG(j_.trace()) << "N-UNL: add a new validator " << n << " at ledger seq=" << seq;
286 newValidators_[n] = seq;
287 }
288 }
289}
290
291void
293{
295 auto i = newValidators_.begin();
296 while (i != newValidators_.end())
297 {
298 if (seq - i->second > newValidatorDisableSkip)
299 {
300 i = newValidators_.erase(i);
301 }
302 else
303 {
304 ++i;
305 }
306 }
307}
308
309} // 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:318
Stream debug() const
Definition Journal.h:300
Stream trace() const
Severity stream access functions.
Definition Journal.h:294
Stream warn() const
Definition Journal.h:312
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: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:291
pointer data()
Definition base_uint.h:101
static std::size_t constexpr bytes
Definition base_uint.h:84
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:172
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:137
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:474
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:394
T size(T... args)
std::vector< NodeID > toDisableCandidates
std::vector< NodeID > toReEnableCandidates