rippled
NegativeUNLVote.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2020 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/app/consensus/RCLValidations.h>
21 #include <ripple/app/ledger/Ledger.h>
22 #include <ripple/app/misc/NegativeUNLVote.h>
23 #include <ripple/shamap/SHAMapItem.h>
24 
25 namespace ripple {
26 
28  : myId_(myId), j_(j)
29 {
30 }
31 
32 void
34  std::shared_ptr<Ledger const> const& prevLedger,
35  hash_set<PublicKey> const& unlKeys,
36  RCLValidations& validations,
37  std::shared_ptr<SHAMap> const& initialSet)
38 {
39  // Voting steps:
40  // -- build a reliability score table of validators
41  // -- process the table and find all candidates to disable or to re-enable
42  // -- pick one to disable and one to re-enable if any
43  // -- if found candidates, add ttUNL_MODIFY Tx
44 
45  // Build NodeID set for internal use.
46  // Build NodeID to PublicKey map for lookup before creating ttUNL_MODIFY Tx.
47  hash_set<NodeID> unlNodeIDs;
48  hash_map<NodeID, PublicKey> nidToKeyMap;
49  for (auto const& k : unlKeys)
50  {
51  auto nid = calcNodeID(k);
52  nidToKeyMap.emplace(nid, k);
53  unlNodeIDs.emplace(nid);
54  }
55 
56  // Build a reliability score table of validators
58  buildScoreTable(prevLedger, unlNodeIDs, validations))
59  {
60  // build next negUnl
61  auto negUnlKeys = prevLedger->negativeUNL();
62  auto negUnlToDisable = prevLedger->validatorToDisable();
63  auto negUnlToReEnable = prevLedger->validatorToReEnable();
64  if (negUnlToDisable)
65  negUnlKeys.insert(*negUnlToDisable);
66  if (negUnlToReEnable)
67  negUnlKeys.erase(*negUnlToReEnable);
68 
69  hash_set<NodeID> negUnlNodeIDs;
70  for (auto const& k : negUnlKeys)
71  {
72  auto nid = calcNodeID(k);
73  negUnlNodeIDs.emplace(nid);
74  if (!nidToKeyMap.count(nid))
75  {
76  nidToKeyMap.emplace(nid, k);
77  }
78  }
79 
80  auto const seq = prevLedger->info().seq + 1;
81  purgeNewValidators(seq);
82 
83  // Process the table and find all candidates to disable or to re-enable
84  auto const candidates =
85  findAllCandidates(unlNodeIDs, negUnlNodeIDs, *scoreTable);
86 
87  // Pick one to disable and one to re-enable if any, add ttUNL_MODIFY Tx
88  if (!candidates.toDisableCandidates.empty())
89  {
90  auto n =
91  choose(prevLedger->info().hash, candidates.toDisableCandidates);
92  assert(nidToKeyMap.count(n));
93  addTx(seq, nidToKeyMap[n], ToDisable, initialSet);
94  }
95 
96  if (!candidates.toReEnableCandidates.empty())
97  {
98  auto n = choose(
99  prevLedger->info().hash, candidates.toReEnableCandidates);
100  assert(nidToKeyMap.count(n));
101  addTx(seq, nidToKeyMap[n], ToReEnable, initialSet);
102  }
103  }
104 }
105 
106 void
108  LedgerIndex seq,
109  PublicKey const& vp,
110  NegativeUNLModify modify,
111  std::shared_ptr<SHAMap> const& initialSet)
112 {
113  STTx negUnlTx(ttUNL_MODIFY, [&](auto& obj) {
114  obj.setFieldU8(sfUNLModifyDisabling, modify == ToDisable ? 1 : 0);
115  obj.setFieldU32(sfLedgerSequence, seq);
116  obj.setFieldVL(sfUNLModifyValidator, vp.slice());
117  });
118 
119  Serializer s;
120  negUnlTx.add(s);
121  if (!initialSet->addGiveItem(
123  make_shamapitem(negUnlTx.getTransactionID(), s.slice())))
124  {
125  JLOG(j_.warn()) << "N-UNL: ledger seq=" << seq
126  << ", add ttUNL_MODIFY tx failed";
127  }
128  else
129  {
130  JLOG(j_.debug()) << "N-UNL: ledger seq=" << seq
131  << ", add a ttUNL_MODIFY Tx with txID: "
132  << negUnlTx.getTransactionID() << ", the validator to "
133  << (modify == ToDisable ? "disable: " : "re-enable: ")
134  << vp;
135  }
136 }
137 
138 NodeID
140  uint256 const& randomPadData,
141  std::vector<NodeID> const& candidates)
142 {
143  assert(!candidates.empty());
144  static_assert(NodeID::bytes <= uint256::bytes);
145  NodeID 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->info().seq + 1;
170  validations.setSeqToKeep(seq - 1, seq + FLAG_LEDGER_INTERVAL);
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 < FLAG_LEDGER_INTERVAL)
182  {
183  JLOG(j_.debug()) << "N-UNL: ledger " << seq
184  << " not enough history. Can trace back only "
185  << numAncestors << " ledgers.";
186  return {};
187  }
188 
189  // have enough ledger ancestors, build the score table
191  for (auto const& k : unl)
192  {
193  scoreTable[k] = 0;
194  }
195 
196  // Query the validation container for every ledger hash and fill
197  // the score table.
198  for (int i = 0; i < FLAG_LEDGER_INTERVAL; ++i)
199  {
200  for (auto const& v : validations.getTrustedForLedger(
201  ledgerAncestors[numAncestors - 1 - i], seq - 2 - i))
202  {
203  if (scoreTable.count(v->getNodeID()))
204  ++scoreTable[v->getNodeID()];
205  }
206  }
207 
208  // Return false if the validation message history or local node's
209  // participation in the history is not good.
210  auto const myValidationCount = [&]() -> std::uint32_t {
211  if (auto const it = scoreTable.find(myId_); it != scoreTable.end())
212  return it->second;
213  return 0;
214  }();
215  if (myValidationCount < negativeUNLMinLocalValsToVote)
216  {
217  JLOG(j_.debug()) << "N-UNL: ledger " << seq
218  << ". Local node only issued " << myValidationCount
219  << " validations in last " << FLAG_LEDGER_INTERVAL
220  << " ledgers."
221  << " The reliability measurement could be wrong.";
222  return {};
223  }
224  else if (
225  myValidationCount > negativeUNLMinLocalValsToVote &&
226  myValidationCount <= FLAG_LEDGER_INTERVAL)
227  {
228  return scoreTable;
229  }
230  else
231  {
232  // cannot happen because validations.getTrustedForLedger does not
233  // return multiple validations of the same ledger from a validator.
234  JLOG(j_.error()) << "N-UNL: ledger " << seq << ". Local node issued "
235  << myValidationCount << " validations in last "
236  << FLAG_LEDGER_INTERVAL << " ledgers. Too many!";
237  return {};
238  }
239 }
240 
243  hash_set<NodeID> const& unl,
244  hash_set<NodeID> const& negUnl,
245  hash_map<NodeID, std::uint32_t> const& scoreTable)
246 {
247  // Compute if need to find more validators to disable
248  auto const canAdd = [&]() -> bool {
249  auto const maxNegativeListed = static_cast<std::size_t>(
251  std::size_t negativeListed = 0;
252  for (auto const& n : unl)
253  {
254  if (negUnl.count(n))
255  ++negativeListed;
256  }
257  bool const result = negativeListed < maxNegativeListed;
258  JLOG(j_.trace()) << "N-UNL: nodeId " << myId_ << " lowWaterMark "
259  << negativeUNLLowWaterMark << " highWaterMark "
260  << negativeUNLHighWaterMark << " canAdd " << result
261  << " negativeListed " << negativeListed
262  << " maxNegativeListed " << maxNegativeListed;
263  return result;
264  }();
265 
266  Candidates candidates;
267  for (auto const& [nodeId, score] : scoreTable)
268  {
269  JLOG(j_.trace()) << "N-UNL: node " << nodeId << " score " << score;
270 
271  // Find toDisable Candidates: check if
272  // (1) canAdd,
273  // (2) has less than negativeUNLLowWaterMark validations,
274  // (3) is not in negUnl, and
275  // (4) is not a new validator.
276  if (canAdd && score < negativeUNLLowWaterMark &&
277  !negUnl.count(nodeId) && !newValidators_.count(nodeId))
278  {
279  JLOG(j_.trace()) << "N-UNL: toDisable candidate " << nodeId;
280  candidates.toDisableCandidates.push_back(nodeId);
281  }
282 
283  // Find toReEnable Candidates: check if
284  // (1) has more than negativeUNLHighWaterMark validations,
285  // (2) is in negUnl
286  if (score > negativeUNLHighWaterMark && negUnl.count(nodeId))
287  {
288  JLOG(j_.trace()) << "N-UNL: toReEnable candidate " << nodeId;
289  candidates.toReEnableCandidates.push_back(nodeId);
290  }
291  }
292 
293  // If a negative UNL validator is removed from nodes' UNLs, it is no longer
294  // a validator. It should be removed from the negative UNL too.
295  // Note that even if it is still offline and in minority nodes' UNLs, it
296  // will not be re-added to the negative UNL. Because the UNLModify Tx will
297  // not be included in the agreed TxSet of a ledger.
298  //
299  // Find this kind of toReEnable Candidate if did not find any toReEnable
300  // candidate yet: check if
301  // (1) is in negUnl
302  // (2) is not in unl.
303  if (candidates.toReEnableCandidates.empty())
304  {
305  for (auto const& n : negUnl)
306  {
307  if (!unl.count(n))
308  {
309  candidates.toReEnableCandidates.push_back(n);
310  }
311  }
312  }
313  return candidates;
314 }
315 
316 void
318  LedgerIndex seq,
319  hash_set<NodeID> const& nowTrusted)
320 {
321  std::lock_guard lock(mutex_);
322  for (auto const& n : nowTrusted)
323  {
324  if (newValidators_.find(n) == newValidators_.end())
325  {
326  JLOG(j_.trace()) << "N-UNL: add a new validator " << n
327  << " at ledger seq=" << seq;
328  newValidators_[n] = seq;
329  }
330  }
331 }
332 
333 void
335 {
336  std::lock_guard lock(mutex_);
337  auto i = newValidators_.begin();
338  while (i != newValidators_.end())
339  {
340  if (seq - i->second > newValidatorDisableSkip)
341  {
342  i = newValidators_.erase(i);
343  }
344  else
345  {
346  ++i;
347  }
348  }
349 }
350 
351 } // namespace ripple
ripple::sfUNLModifyValidator
const SF_VL sfUNLModifyValidator
ripple::NegativeUNLVote::doVoting
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.
Definition: NegativeUNLVote.cpp:33
std::shared_ptr
STL class.
ripple::calcNodeID
NodeID calcNodeID(PublicKey const &pk)
Calculate the 160-bit node ID from a node public key.
Definition: PublicKey.cpp:303
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
ripple::sfLedgerSequence
const SF_UINT32 sfLedgerSequence
std::unordered_set
STL class.
ripple::make_shamapitem
boost::intrusive_ptr< SHAMapItem > make_shamapitem(uint256 const &tag, Slice data)
Definition: SHAMapItem.h:160
ripple::NegativeUNLVote::j_
beast::Journal j_
Definition: NegativeUNLVote.h:128
std::vector
STL class.
std::unordered_map::find
T find(T... args)
std::vector::size
T size(T... args)
ripple::keylet::skip
Keylet const & skip() noexcept
The index of the "short" skip list.
Definition: Indexes.cpp:145
ripple::FLAG_LEDGER_INTERVAL
constexpr std::uint32_t FLAG_LEDGER_INTERVAL
Definition: Ledger.h:426
std::unordered_map::emplace
T emplace(T... args)
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
std::lock_guard
STL class.
ripple::NegativeUNLVote::ToReEnable
@ ToReEnable
Definition: NegativeUNLVote.h:85
ripple::PublicKey::slice
Slice slice() const noexcept
Definition: PublicKey.h:123
ripple::NegativeUNLVote::negativeUNLHighWaterMark
static constexpr size_t negativeUNLHighWaterMark
An unreliable validator must have more than negativeUNLHighWaterMark validations in the last flag led...
Definition: NegativeUNLVote.h:61
ripple::base_uint::data
pointer data()
Definition: base_uint.h:122
ripple::SHAMapNodeType::tnTRANSACTION_NM
@ tnTRANSACTION_NM
ripple::NegativeUNLVote::newValidators_
hash_map< NodeID, LedgerIndex > newValidators_
Definition: NegativeUNLVote.h:130
ripple::NegativeUNLVote::Candidates::toDisableCandidates
std::vector< NodeID > toDisableCandidates
Definition: NegativeUNLVote.h:137
ripple::base_uint< 160, detail::NodeIDTag >
ripple::NegativeUNLVote::Candidates::toReEnableCandidates
std::vector< NodeID > toReEnableCandidates
Definition: NegativeUNLVote.h:138
ripple::NegativeUNLVote::ToDisable
@ ToDisable
Definition: NegativeUNLVote.h:84
ripple::ttUNL_MODIFY
@ ttUNL_MODIFY
This system-generated transaction type is used to update the network's negative UNL.
Definition: TxFormats.h:158
ripple::base_uint< 160, detail::NodeIDTag >::bytes
static constexpr std::size_t bytes
Definition: base_uint.h:105
ripple::NegativeUNLVote::findAllCandidates
const 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.
Definition: NegativeUNLVote.cpp:242
ripple::PublicKey
A public key.
Definition: PublicKey.h:59
ripple::NegativeUNLVote::negativeUNLMaxListed
static constexpr float negativeUNLMaxListed
We only want to put 25% of the UNL on the NegativeUNL.
Definition: NegativeUNLVote.h:77
ripple::NegativeUNLVote::negativeUNLMinLocalValsToVote
static constexpr size_t negativeUNLMinLocalValsToVote
The minimum number of validations of the local node for it to participate in the voting.
Definition: NegativeUNLVote.h:67
beast::Journal::error
Stream error() const
Definition: Journal.h:333
ripple::STTx
Definition: STTx.h:45
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint32_t
ripple::NegativeUNLVote::buildScoreTable
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...
Definition: NegativeUNLVote.cpp:158
ripple::NegativeUNLVote::Candidates
UNLModify Tx candidates.
Definition: NegativeUNLVote.h:135
std::ceil
T ceil(T... args)
ripple::NegativeUNLVote::addTx
void addTx(LedgerIndex seq, PublicKey const &vp, NegativeUNLModify modify, std::shared_ptr< SHAMap > const &initialSet)
Add a ttUNL_MODIFY Tx to the transaction set.
Definition: NegativeUNLVote.cpp:107
ripple::sfUNLModifyDisabling
const SF_UINT8 sfUNLModifyDisabling
ripple::NegativeUNLVote::newValidatorDisableSkip
static constexpr size_t newValidatorDisableSkip
We don't want to disable new validators immediately after adding them.
Definition: NegativeUNLVote.h:73
ripple::Serializer
Definition: Serializer.h:39
ripple::sfHashes
const SF_VECTOR256 sfHashes
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::NegativeUNLVote::myId_
const NodeID myId_
Definition: NegativeUNLVote.h:127
ripple::base_uint< 160, detail::NodeIDTag >::fromVoid
static base_uint fromVoid(void const *data)
Definition: base_uint.h:312
ripple::Validations::setSeqToKeep
void setSeqToKeep(Seq const &low, Seq const &high)
Set the range [low, high) of validations to keep from expire.
Definition: Validations.h:714
ripple::NegativeUNLVote::NegativeUNLModify
NegativeUNLModify
A flag indicating whether a UNLModify Tx is to disable or to re-enable a validator.
Definition: NegativeUNLVote.h:83
ripple::NegativeUNLVote::negativeUNLLowWaterMark
static constexpr size_t negativeUNLLowWaterMark
A validator is considered unreliable if its validations is less than negativeUNLLowWaterMark in the l...
Definition: NegativeUNLVote.h:55
std::unordered_map::count
T count(T... args)
std::vector::empty
T empty(T... args)
ripple::Validations< RCLValidationsAdaptor >
std::optional
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
std::size_t
ripple::NegativeUNLVote::purgeNewValidators
void purgeNewValidators(LedgerIndex seq)
Purge validators that are not new anymore.
Definition: NegativeUNLVote.cpp:334
std::unordered_map::end
T end(T... args)
ripple::NegativeUNLVote::choose
NodeID choose(uint256 const &randomPadData, std::vector< NodeID > const &candidates)
Pick one candidate from a vector of candidates.
Definition: NegativeUNLVote.cpp:139
ripple::Validations::getTrustedForLedger
std::vector< WrappedValidationType > getTrustedForLedger(ID const &ledgerID, Seq const &seq)
Get trusted full validations for a specific ledger.
Definition: Validations.h:1056
std::unordered_map
STL class.
ripple::NegativeUNLVote::mutex_
std::mutex mutex_
Definition: NegativeUNLVote.h:129
ripple::NegativeUNLVote::NegativeUNLVote
NegativeUNLVote(NodeID const &myId, beast::Journal j)
Constructor.
Definition: NegativeUNLVote.cpp:27
ripple::NegativeUNLVote::newValidators
void newValidators(LedgerIndex seq, hash_set< NodeID > const &nowTrusted)
Notify NegativeUNLVote that new validators are added.
Definition: NegativeUNLVote.cpp:317