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 
24 namespace ripple {
25 
27  : myId_(myId), j_(j)
28 {
29 }
30 
31 void
33  std::shared_ptr<Ledger const> const& prevLedger,
34  hash_set<PublicKey> const& unlKeys,
35  RCLValidations& validations,
36  std::shared_ptr<SHAMap> const& initialSet)
37 {
38  // Voting steps:
39  // -- build a reliability score table of validators
40  // -- process the table and find all candidates to disable or to re-enable
41  // -- pick one to disable and one to re-enable if any
42  // -- if found candidates, add ttUNL_MODIFY Tx
43 
44  // Build NodeID set for internal use.
45  // Build NodeID to PublicKey map for lookup before creating ttUNL_MODIFY Tx.
46  hash_set<NodeID> unlNodeIDs;
47  hash_map<NodeID, PublicKey> nidToKeyMap;
48  for (auto const& k : unlKeys)
49  {
50  auto nid = calcNodeID(k);
51  nidToKeyMap.emplace(nid, k);
52  unlNodeIDs.emplace(nid);
53  }
54 
55  // Build a reliability score table of validators
57  buildScoreTable(prevLedger, unlNodeIDs, validations))
58  {
59  // build next negUnl
60  auto negUnlKeys = prevLedger->negativeUNL();
61  auto negUnlToDisable = prevLedger->validatorToDisable();
62  auto negUnlToReEnable = prevLedger->validatorToReEnable();
63  if (negUnlToDisable)
64  negUnlKeys.insert(*negUnlToDisable);
65  if (negUnlToReEnable)
66  negUnlKeys.erase(*negUnlToReEnable);
67 
68  hash_set<NodeID> negUnlNodeIDs;
69  for (auto const& k : negUnlKeys)
70  {
71  auto nid = calcNodeID(k);
72  negUnlNodeIDs.emplace(nid);
73  if (!nidToKeyMap.count(nid))
74  {
75  nidToKeyMap.emplace(nid, k);
76  }
77  }
78 
79  auto const seq = prevLedger->info().seq + 1;
80  purgeNewValidators(seq);
81 
82  // Process the table and find all candidates to disable or to re-enable
83  auto const candidates =
84  findAllCandidates(unlNodeIDs, negUnlNodeIDs, *scoreTable);
85 
86  // Pick one to disable and one to re-enable if any, add ttUNL_MODIFY Tx
87  if (!candidates.toDisableCandidates.empty())
88  {
89  auto n =
90  choose(prevLedger->info().hash, candidates.toDisableCandidates);
91  assert(nidToKeyMap.count(n));
92  addTx(seq, nidToKeyMap[n], ToDisable, initialSet);
93  }
94 
95  if (!candidates.toReEnableCandidates.empty())
96  {
97  auto n = choose(
98  prevLedger->info().hash, candidates.toReEnableCandidates);
99  assert(nidToKeyMap.count(n));
100  addTx(seq, nidToKeyMap[n], ToReEnable, initialSet);
101  }
102  }
103 }
104 
105 void
107  LedgerIndex seq,
108  PublicKey const& vp,
109  NegativeUNLModify modify,
110  std::shared_ptr<SHAMap> const& initialSet)
111 {
112  STTx negUnlTx(ttUNL_MODIFY, [&](auto& obj) {
113  obj.setFieldU8(sfUNLModifyDisabling, modify == ToDisable ? 1 : 0);
114  obj.setFieldU32(sfLedgerSequence, seq);
115  obj.setFieldVL(sfUNLModifyValidator, vp.slice());
116  });
117 
118  uint256 txID = negUnlTx.getTransactionID();
119  Serializer s;
120  negUnlTx.add(s);
121  if (!initialSet->addGiveItem(
122  std::make_shared<SHAMapItem>(txID, s.peekData()), true, false))
123  {
124  JLOG(j_.warn()) << "N-UNL: ledger seq=" << seq
125  << ", add ttUNL_MODIFY tx failed";
126  }
127  else
128  {
129  JLOG(j_.debug()) << "N-UNL: ledger seq=" << seq
130  << ", add a ttUNL_MODIFY Tx with txID: " << txID
131  << ", the validator to "
132  << (modify == ToDisable ? "disable: " : "re-enable: ")
133  << vp;
134  }
135 }
136 
137 NodeID
139  uint256 const& randomPadData,
140  std::vector<NodeID> const& candidates)
141 {
142  assert(!candidates.empty());
143  static_assert(NodeID::bytes <= uint256::bytes);
144  NodeID randomPad = NodeID::fromVoid(randomPadData.data());
145  NodeID txNodeID = candidates[0];
146  for (int j = 1; j < candidates.size(); ++j)
147  {
148  if ((candidates[j] ^ randomPad) < (txNodeID ^ randomPad))
149  {
150  txNodeID = candidates[j];
151  }
152  }
153  return txNodeID;
154 }
155 
158  std::shared_ptr<Ledger const> const& prevLedger,
159  hash_set<NodeID> const& unl,
160  RCLValidations& validations)
161 {
162  // Find agreed validation messages received for
163  // the last FLAG_LEDGER_INTERVAL (i.e. 256) ledgers,
164  // for every validator, and fill the score table.
165 
166  // Ask the validation container to keep enough validation message history
167  // for next time.
168  auto const seq = prevLedger->info().seq + 1;
169  validations.setSeqToKeep(seq - 1);
170 
171  // Find FLAG_LEDGER_INTERVAL (i.e. 256) previous ledger hashes
172  auto const hashIndex = prevLedger->read(keylet::skip());
173  if (!hashIndex || !hashIndex->isFieldPresent(sfHashes))
174  {
175  JLOG(j_.debug()) << "N-UNL: ledger " << seq << " no history.";
176  return {};
177  }
178  auto const ledgerAncestors = hashIndex->getFieldV256(sfHashes).value();
179  auto const numAncestors = ledgerAncestors.size();
180  if (numAncestors < FLAG_LEDGER_INTERVAL)
181  {
182  JLOG(j_.debug()) << "N-UNL: ledger " << seq
183  << " 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 < FLAG_LEDGER_INTERVAL; ++i)
198  {
199  for (auto const& v : validations.getTrustedForLedger(
200  ledgerAncestors[numAncestors - 1 - i]))
201  {
202  if (scoreTable.count(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 < negativeUNLMinLocalValsToVote)
215  {
216  JLOG(j_.debug()) << "N-UNL: ledger " << seq
217  << ". Local node only issued " << myValidationCount
218  << " validations in last " << FLAG_LEDGER_INTERVAL
219  << " ledgers."
220  << " The reliability measurement could be wrong.";
221  return {};
222  }
223  else if (
224  myValidationCount > negativeUNLMinLocalValsToVote &&
225  myValidationCount <= FLAG_LEDGER_INTERVAL)
226  {
227  return scoreTable;
228  }
229  else
230  {
231  // cannot happen because validations.getTrustedForLedger does not
232  // return multiple validations of the same ledger from a validator.
233  JLOG(j_.error()) << "N-UNL: ledger " << seq << ". Local node issued "
234  << myValidationCount << " validations in last "
235  << FLAG_LEDGER_INTERVAL << " ledgers. Too many!";
236  return {};
237  }
238 }
239 
242  hash_set<NodeID> const& unl,
243  hash_set<NodeID> const& negUnl,
244  hash_map<NodeID, std::uint32_t> const& scoreTable)
245 {
246  // Compute if need to find more validators to disable
247  auto const canAdd = [&]() -> bool {
248  auto const maxNegativeListed = static_cast<std::size_t>(
250  std::size_t negativeListed = 0;
251  for (auto const& n : unl)
252  {
253  if (negUnl.count(n))
254  ++negativeListed;
255  }
256  bool const result = negativeListed < maxNegativeListed;
257  JLOG(j_.trace()) << "N-UNL: nodeId " << myId_ << " lowWaterMark "
258  << negativeUNLLowWaterMark << " highWaterMark "
259  << negativeUNLHighWaterMark << " canAdd " << result
260  << " negativeListed " << negativeListed
261  << " maxNegativeListed " << maxNegativeListed;
262  return result;
263  }();
264 
265  Candidates candidates;
266  for (auto const& [nodeId, score] : scoreTable)
267  {
268  JLOG(j_.trace()) << "N-UNL: node " << nodeId << " score " << score;
269 
270  // Find toDisable Candidates: check if
271  // (1) canAdd,
272  // (2) has less than negativeUNLLowWaterMark validations,
273  // (3) is not in negUnl, and
274  // (4) is not a new validator.
275  if (canAdd && score < negativeUNLLowWaterMark &&
276  !negUnl.count(nodeId) && !newValidators_.count(nodeId))
277  {
278  JLOG(j_.trace()) << "N-UNL: toDisable candidate " << nodeId;
279  candidates.toDisableCandidates.push_back(nodeId);
280  }
281 
282  // Find toReEnable Candidates: check if
283  // (1) has more than negativeUNLHighWaterMark validations,
284  // (2) is in negUnl
285  if (score > negativeUNLHighWaterMark && negUnl.count(nodeId))
286  {
287  JLOG(j_.trace()) << "N-UNL: toReEnable candidate " << nodeId;
288  candidates.toReEnableCandidates.push_back(nodeId);
289  }
290  }
291 
292  // If a negative UNL validator is removed from nodes' UNLs, it is no longer
293  // a validator. It should be removed from the negative UNL too.
294  // Note that even if it is still offline and in minority nodes' UNLs, it
295  // will not be re-added to the negative UNL. Because the UNLModify Tx will
296  // not be included in the agreed TxSet of a ledger.
297  //
298  // Find this kind of toReEnable Candidate if did not find any toReEnable
299  // candidate yet: check if
300  // (1) is in negUnl
301  // (2) is not in unl.
302  if (candidates.toReEnableCandidates.empty())
303  {
304  for (auto const& n : negUnl)
305  {
306  if (!unl.count(n))
307  {
308  candidates.toReEnableCandidates.push_back(n);
309  }
310  }
311  }
312  return candidates;
313 }
314 
315 void
317  LedgerIndex seq,
318  hash_set<NodeID> const& nowTrusted)
319 {
320  std::lock_guard lock(mutex_);
321  for (auto const& n : nowTrusted)
322  {
323  if (newValidators_.find(n) == newValidators_.end())
324  {
325  JLOG(j_.trace()) << "N-UNL: add a new validator " << n
326  << " at ledger seq=" << seq;
327  newValidators_[n] = seq;
328  }
329  }
330 }
331 
332 void
334 {
335  std::lock_guard lock(mutex_);
336  auto i = newValidators_.begin();
337  while (i != newValidators_.end())
338  {
339  if (seq - i->second > newValidatorDisableSkip)
340  {
341  i = newValidators_.erase(i);
342  }
343  else
344  {
345  ++i;
346  }
347  }
348 }
349 
350 } // namespace ripple
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:32
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:299
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
std::unordered_set
STL class.
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:133
ripple::FLAG_LEDGER_INTERVAL
constexpr std::uint32_t FLAG_LEDGER_INTERVAL
Definition: Ledger.h:398
std::unordered_map::emplace
T emplace(T... args)
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
ripple::Validations::setSeqToKeep
void setSeqToKeep(Seq const &s)
Set the smallest sequence number of validations to keep from expire.
Definition: Validations.h:697
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::sfUNLModifyValidator
const SF_Blob sfUNLModifyValidator(access, STI_VL, 19, "UNLModifyValidator")
Definition: SField.h:475
ripple::sfUNLModifyDisabling
const SF_U8 sfUNLModifyDisabling(access, STI_UINT8, 17, "UNLModifyDisabling")
Definition: SField.h:343
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:103
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::base_uint< 160, detail::NodeIDTag >::bytes
static constexpr std::size_t bytes
Definition: base_uint.h:86
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:241
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
ripple::sfLedgerSequence
const SF_U32 sfLedgerSequence(access, STI_UINT32, 6, "LedgerSequence")
Definition: SField.h:358
beast::Journal::error
Stream error() const
Definition: Journal.h:333
ripple::STTx
Definition: STTx.h:42
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:157
ripple::NegativeUNLVote::Candidates
UNLModify Tx candidates.
Definition: NegativeUNLVote.h:135
std::ceil
T ceil(T... args)
ripple::Validations::getTrustedForLedger
std::vector< WrappedValidationType > getTrustedForLedger(ID const &ledgerID)
Get trusted full validations for a specific ledger.
Definition: Validations.h:1010
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:106
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_Vec256 sfHashes(access, STI_VECTOR256, 2, "Hashes")
Definition: SField.h:494
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:213
ripple::Serializer::peekData
Blob const & peekData() const
Definition: Serializer.h:166
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 >
ripple::ttUNL_MODIFY
@ ttUNL_MODIFY
Definition: TxFormats.h:61
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:333
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:138
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:26
ripple::NegativeUNLVote::newValidators
void newValidators(LedgerIndex seq, hash_set< NodeID > const &nowTrusted)
Notify NegativeUNLVote that new validators are added.
Definition: NegativeUNLVote.cpp:316