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