rippled
LedgerHistory.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 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/ledger/LedgerHistory.h>
21 #include <ripple/app/ledger/LedgerToJson.h>
22 #include <ripple/basics/Log.h>
23 #include <ripple/basics/chrono.h>
24 #include <ripple/basics/contract.h>
25 #include <ripple/json/to_string.h>
26 
27 namespace ripple {
28 
29 // FIXME: Need to clean up ledgers by index at some point
30 
32  beast::insight::Collector::ptr const& collector,
33  Application& app)
34  : app_(app)
35  , collector_(collector)
36  , mismatch_counter_(collector->make_counter("ledger.history", "mismatch"))
37  , m_ledgers_by_hash(
38  "LedgerCache",
39  app_.config().getValueFor(SizedItem::ledgerSize),
40  std::chrono::seconds{app_.config().getValueFor(SizedItem::ledgerAge)},
41  stopwatch(),
42  app_.journal("TaggedCache"))
43  , m_consensus_validated(
44  "ConsensusValidated",
45  64,
47  stopwatch(),
48  app_.journal("TaggedCache"))
49  , j_(app.journal("LedgerHistory"))
50 {
51 }
52 
53 bool
55  std::shared_ptr<Ledger const> const& ledger,
56  bool validated)
57 {
58  if (!ledger->isImmutable())
59  LogicError("mutable Ledger in insert");
60 
61  assert(ledger->stateMap().getHash().isNonZero());
62 
64 
65  const bool alreadyHad = m_ledgers_by_hash.canonicalize_replace_cache(
66  ledger->info().hash, ledger);
67  if (validated)
68  mLedgersByIndex[ledger->info().seq] = ledger->info().hash;
69 
70  return alreadyHad;
71 }
72 
75 {
77  if (auto it = mLedgersByIndex.find(index); it != mLedgersByIndex.end())
78  return it->second;
79  return {};
80 }
81 
84 {
85  {
87  auto it = mLedgersByIndex.find(index);
88 
89  if (it != mLedgersByIndex.end())
90  {
91  uint256 hash = it->second;
92  sl.unlock();
93  return getLedgerByHash(hash);
94  }
95  }
96 
98 
99  if (!ret)
100  return ret;
101 
102  assert(ret->info().seq == index);
103 
104  {
105  // Add this ledger to the local tracking by index
107 
108  assert(ret->isImmutable());
109  m_ledgers_by_hash.canonicalize_replace_client(ret->info().hash, ret);
110  mLedgersByIndex[ret->info().seq] = ret->info().hash;
111  return (ret->info().seq == index) ? ret : nullptr;
112  }
113 }
114 
117 {
118  auto ret = m_ledgers_by_hash.fetch(hash);
119 
120  if (ret)
121  {
122  assert(ret->isImmutable());
123  assert(ret->info().hash == hash);
124  return ret;
125  }
126 
127  ret = loadByHash(hash, app_);
128 
129  if (!ret)
130  return ret;
131 
132  assert(ret->isImmutable());
133  assert(ret->info().hash == hash);
134  m_ledgers_by_hash.canonicalize_replace_client(ret->info().hash, ret);
135  assert(ret->info().hash == hash);
136 
137  return ret;
138 }
139 
140 static void
142  ReadView const& ledger,
143  uint256 const& tx,
144  char const* msg,
145  beast::Journal& j)
146 {
147  auto metaData = ledger.txRead(tx).second;
148 
149  if (metaData != nullptr)
150  {
151  JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg
152  << " is missing this transaction:\n"
153  << metaData->getJson(JsonOptions::none);
154  }
155  else
156  {
157  JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg
158  << " is missing this transaction.";
159  }
160 }
161 
162 static void
164  ReadView const& builtLedger,
165  ReadView const& validLedger,
166  uint256 const& tx,
167  beast::Journal j)
168 {
169  auto getMeta = [](ReadView const& ledger, uint256 const& txID) {
171  if (auto meta = ledger.txRead(txID).second)
172  ret.emplace(txID, ledger.seq(), *meta);
173  return ret;
174  };
175 
176  auto validMetaData = getMeta(validLedger, tx);
177  auto builtMetaData = getMeta(builtLedger, tx);
178 
179  assert(validMetaData || builtMetaData);
180 
181  if (validMetaData && builtMetaData)
182  {
183  auto const& validNodes = validMetaData->getNodes();
184  auto const& builtNodes = builtMetaData->getNodes();
185 
186  bool const result_diff =
187  validMetaData->getResultTER() != builtMetaData->getResultTER();
188 
189  bool const index_diff =
190  validMetaData->getIndex() != builtMetaData->getIndex();
191 
192  bool const nodes_diff = validNodes != builtNodes;
193 
194  if (!result_diff && !index_diff && !nodes_diff)
195  {
196  JLOG(j.error()) << "MISMATCH on TX " << tx
197  << ": No apparent mismatches detected!";
198  return;
199  }
200 
201  if (!nodes_diff)
202  {
203  if (result_diff && index_diff)
204  {
205  JLOG(j.debug()) << "MISMATCH on TX " << tx
206  << ": Different result and index!";
207  JLOG(j.debug()) << " Built:"
208  << " Result: " << builtMetaData->getResult()
209  << " Index: " << builtMetaData->getIndex();
210  JLOG(j.debug()) << " Valid:"
211  << " Result: " << validMetaData->getResult()
212  << " Index: " << validMetaData->getIndex();
213  }
214  else if (result_diff)
215  {
216  JLOG(j.debug())
217  << "MISMATCH on TX " << tx << ": Different result!";
218  JLOG(j.debug()) << " Built:"
219  << " Result: " << builtMetaData->getResult();
220  JLOG(j.debug()) << " Valid:"
221  << " Result: " << validMetaData->getResult();
222  }
223  else if (index_diff)
224  {
225  JLOG(j.debug())
226  << "MISMATCH on TX " << tx << ": Different index!";
227  JLOG(j.debug()) << " Built:"
228  << " Index: " << builtMetaData->getIndex();
229  JLOG(j.debug()) << " Valid:"
230  << " Index: " << validMetaData->getIndex();
231  }
232  }
233  else
234  {
235  if (result_diff && index_diff)
236  {
237  JLOG(j.debug()) << "MISMATCH on TX " << tx
238  << ": Different result, index and nodes!";
239  JLOG(j.debug()) << " Built:\n"
240  << builtMetaData->getJson(JsonOptions::none);
241  JLOG(j.debug()) << " Valid:\n"
242  << validMetaData->getJson(JsonOptions::none);
243  }
244  else if (result_diff)
245  {
246  JLOG(j.debug()) << "MISMATCH on TX " << tx
247  << ": Different result and nodes!";
248  JLOG(j.debug())
249  << " Built:"
250  << " Result: " << builtMetaData->getResult() << " Nodes:\n"
251  << builtNodes.getJson(JsonOptions::none);
252  JLOG(j.debug())
253  << " Valid:"
254  << " Result: " << validMetaData->getResult() << " Nodes:\n"
255  << validNodes.getJson(JsonOptions::none);
256  }
257  else if (index_diff)
258  {
259  JLOG(j.debug()) << "MISMATCH on TX " << tx
260  << ": Different index and nodes!";
261  JLOG(j.debug())
262  << " Built:"
263  << " Index: " << builtMetaData->getIndex() << " Nodes:\n"
264  << builtNodes.getJson(JsonOptions::none);
265  JLOG(j.debug())
266  << " Valid:"
267  << " Index: " << validMetaData->getIndex() << " Nodes:\n"
268  << validNodes.getJson(JsonOptions::none);
269  }
270  else // nodes_diff
271  {
272  JLOG(j.debug())
273  << "MISMATCH on TX " << tx << ": Different nodes!";
274  JLOG(j.debug()) << " Built:"
275  << " Nodes:\n"
276  << builtNodes.getJson(JsonOptions::none);
277  JLOG(j.debug()) << " Valid:"
278  << " Nodes:\n"
279  << validNodes.getJson(JsonOptions::none);
280  }
281  }
282 
283  return;
284  }
285 
286  if (validMetaData)
287  {
288  JLOG(j.error()) << "MISMATCH on TX " << tx
289  << ": Metadata Difference. Valid=\n"
290  << validMetaData->getJson(JsonOptions::none);
291  }
292 
293  if (builtMetaData)
294  {
295  JLOG(j.error()) << "MISMATCH on TX " << tx
296  << ": Metadata Difference. Built=\n"
297  << builtMetaData->getJson(JsonOptions::none);
298  }
299 }
300 
301 //------------------------------------------------------------------------------
302 
303 // Return list of leaves sorted by key
305 leaves(SHAMap const& sm)
306 {
308  for (auto const& item : sm)
309  v.push_back(&item);
310  std::sort(
311  v.begin(), v.end(), [](SHAMapItem const* lhs, SHAMapItem const* rhs) {
312  return lhs->key() < rhs->key();
313  });
314  return v;
315 }
316 
317 void
319  LedgerHash const& built,
320  LedgerHash const& valid,
321  std::optional<uint256> const& builtConsensusHash,
322  std::optional<uint256> const& validatedConsensusHash,
323  Json::Value const& consensus)
324 {
325  assert(built != valid);
327 
328  auto builtLedger = getLedgerByHash(built);
329  auto validLedger = getLedgerByHash(valid);
330 
331  if (!builtLedger || !validLedger)
332  {
333  JLOG(j_.error()) << "MISMATCH cannot be analyzed:"
334  << " builtLedger: " << to_string(built) << " -> "
335  << builtLedger << " validLedger: " << to_string(valid)
336  << " -> " << validLedger;
337  return;
338  }
339 
340  assert(builtLedger->info().seq == validLedger->info().seq);
341 
342  if (auto stream = j_.debug())
343  {
344  stream << "Built: " << getJson({*builtLedger, {}});
345  stream << "Valid: " << getJson({*validLedger, {}});
346  stream << "Consensus: " << consensus;
347  }
348 
349  // Determine the mismatch reason, distinguishing Byzantine
350  // failure from transaction processing difference
351 
352  // Disagreement over prior ledger indicates sync issue
353  if (builtLedger->info().parentHash != validLedger->info().parentHash)
354  {
355  JLOG(j_.error()) << "MISMATCH on prior ledger";
356  return;
357  }
358 
359  // Disagreement over close time indicates Byzantine failure
360  if (builtLedger->info().closeTime != validLedger->info().closeTime)
361  {
362  JLOG(j_.error()) << "MISMATCH on close time";
363  return;
364  }
365 
366  if (builtConsensusHash && validatedConsensusHash)
367  {
368  if (builtConsensusHash != validatedConsensusHash)
369  JLOG(j_.error())
370  << "MISMATCH on consensus transaction set "
371  << " built: " << to_string(*builtConsensusHash)
372  << " validated: " << to_string(*validatedConsensusHash);
373  else
374  JLOG(j_.error()) << "MISMATCH with same consensus transaction set: "
375  << to_string(*builtConsensusHash);
376  }
377 
378  // Find differences between built and valid ledgers
379  auto const builtTx = leaves(builtLedger->txMap());
380  auto const validTx = leaves(validLedger->txMap());
381 
382  if (builtTx == validTx)
383  JLOG(j_.error()) << "MISMATCH with same " << builtTx.size()
384  << " transactions";
385  else
386  JLOG(j_.error()) << "MISMATCH with " << builtTx.size() << " built and "
387  << validTx.size() << " valid transactions.";
388 
389  JLOG(j_.error()) << "built\n" << getJson({*builtLedger, {}});
390  JLOG(j_.error()) << "valid\n" << getJson({*validLedger, {}});
391 
392  // Log all differences between built and valid ledgers
393  auto b = builtTx.begin();
394  auto v = validTx.begin();
395  while (b != builtTx.end() && v != validTx.end())
396  {
397  if ((*b)->key() < (*v)->key())
398  {
399  log_one(*builtLedger, (*b)->key(), "valid", j_);
400  ++b;
401  }
402  else if ((*b)->key() > (*v)->key())
403  {
404  log_one(*validLedger, (*v)->key(), "built", j_);
405  ++v;
406  }
407  else
408  {
409  if ((*b)->slice() != (*v)->slice())
410  {
411  // Same transaction with different metadata
413  *builtLedger, *validLedger, (*b)->key(), j_);
414  }
415  ++b;
416  ++v;
417  }
418  }
419  for (; b != builtTx.end(); ++b)
420  log_one(*builtLedger, (*b)->key(), "valid", j_);
421  for (; v != validTx.end(); ++v)
422  log_one(*validLedger, (*v)->key(), "built", j_);
423 }
424 
425 void
427  std::shared_ptr<Ledger const> const& ledger,
428  uint256 const& consensusHash,
429  Json::Value consensus)
430 {
431  LedgerIndex index = ledger->info().seq;
432  LedgerHash hash = ledger->info().hash;
433  assert(!hash.isZero());
434 
436 
437  auto entry = std::make_shared<cv_entry>();
439 
440  if (entry->validated && !entry->built)
441  {
442  if (entry->validated.value() != hash)
443  {
444  JLOG(j_.error()) << "MISMATCH: seq=" << index
445  << " validated:" << entry->validated.value()
446  << " then:" << hash;
448  hash,
449  entry->validated.value(),
450  consensusHash,
451  entry->validatedConsensusHash,
452  consensus);
453  }
454  else
455  {
456  // We validated a ledger and then built it locally
457  JLOG(j_.debug()) << "MATCH: seq=" << index << " late";
458  }
459  }
460 
461  entry->built.emplace(hash);
462  entry->builtConsensusHash.emplace(consensusHash);
463  entry->consensus.emplace(std::move(consensus));
464 }
465 
466 void
468  std::shared_ptr<Ledger const> const& ledger,
469  std::optional<uint256> const& consensusHash)
470 {
471  LedgerIndex index = ledger->info().seq;
472  LedgerHash hash = ledger->info().hash;
473  assert(!hash.isZero());
474 
476 
477  auto entry = std::make_shared<cv_entry>();
479 
480  if (entry->built && !entry->validated)
481  {
482  if (entry->built.value() != hash)
483  {
484  JLOG(j_.error())
485  << "MISMATCH: seq=" << index
486  << " built:" << entry->built.value() << " then:" << hash;
488  entry->built.value(),
489  hash,
490  entry->builtConsensusHash,
491  consensusHash,
492  entry->consensus.value());
493  }
494  else
495  {
496  // We built a ledger locally and then validated it
497  JLOG(j_.debug()) << "MATCH: seq=" << index;
498  }
499  }
500 
501  entry->validated.emplace(hash);
502  entry->validatedConsensusHash = consensusHash;
503 }
504 
507 bool
508 LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash)
509 {
511  auto it = mLedgersByIndex.find(ledgerIndex);
512 
513  if ((it != mLedgersByIndex.end()) && (it->second != ledgerHash))
514  {
515  it->second = ledgerHash;
516  return false;
517  }
518  return true;
519 }
520 
521 void
523 {
525  {
526  auto const ledger = getLedgerByHash(it);
527  if (!ledger || ledger->info().seq < seq)
528  m_ledgers_by_hash.del(it, false);
529  }
530 }
531 
532 } // namespace ripple
ripple::TaggedCache::getKeys
std::vector< key_type > getKeys() const
Definition: TaggedCache.h:458
ripple::Application
Definition: Application.h:115
ripple::TaggedCache::del
bool del(const key_type &key, bool valid)
Definition: TaggedCache.h:269
std::shared_ptr< Collector >
ripple::LedgerHistory::handleMismatch
void handleMismatch(LedgerHash const &built, LedgerHash const &valid, std::optional< uint256 > const &builtConsensusHash, std::optional< uint256 > const &validatedConsensusHash, Json::Value const &consensus)
Log details in the case where we build one ledger but validate a different one.
Definition: LedgerHistory.cpp:318
ripple::loadByIndex
std::shared_ptr< Ledger > loadByIndex(std::uint32_t ledgerIndex, Application &app, bool acquire)
Definition: Ledger.cpp:1123
ripple::SizedItem
SizedItem
Definition: Config.h:48
ripple::leaves
static std::vector< SHAMapItem const * > leaves(SHAMap const &sm)
Definition: LedgerHistory.cpp:305
std::pair::second
T second
ripple::log_metadata_difference
static void log_metadata_difference(ReadView const &builtLedger, ReadView const &validLedger, uint256 const &tx, beast::Journal j)
Definition: LedgerHistory.cpp:163
ripple::LedgerHistory::fixIndex
bool fixIndex(LedgerIndex ledgerIndex, LedgerHash const &ledgerHash)
Repair a hash to index mapping.
Definition: LedgerHistory.cpp:508
std::vector
STL class.
ripple::LedgerHistory::mLedgersByIndex
std::map< LedgerIndex, LedgerHash > mLedgersByIndex
Definition: LedgerHistory.h:152
ripple::LedgerHistory::j_
beast::Journal j_
Definition: LedgerHistory.h:154
std::chrono::minutes
std::optional::emplace
T emplace(T... args)
ripple::stopwatch
Stopwatch & stopwatch()
Returns an instance of a wall clock.
Definition: chrono.h:88
ripple::LedgerHistory::getLedgerHash
LedgerHash getLedgerHash(LedgerIndex ledgerIndex)
Get a ledger's hash given its sequence number.
Definition: LedgerHistory.cpp:74
ripple::LedgerHistory::insert
bool insert(std::shared_ptr< Ledger const > const &ledger, bool validated)
Track a ledger.
Definition: LedgerHistory.cpp:54
std::sort
T sort(T... args)
ripple::TaggedCache::canonicalize_replace_cache
bool canonicalize_replace_cache(const key_type &key, std::shared_ptr< T > const &data)
Definition: TaggedCache.h:378
ripple::LedgerHistory::builtLedger
void builtLedger(std::shared_ptr< Ledger const > const &, uint256 const &consensusHash, Json::Value)
Report that we have locally built a particular ledger.
Definition: LedgerHistory.cpp:426
std::unique_lock::unlock
T unlock(T... args)
std::vector::push_back
T push_back(T... args)
ripple::LedgerHistory::m_consensus_validated
ConsensusValidated m_consensus_validated
Definition: LedgerHistory.h:149
ripple::base_uint< 256 >
ripple::loadByHash
std::shared_ptr< Ledger > loadByHash(uint256 const &ledgerHash, Application &app, bool acquire)
Definition: Ledger.cpp:1136
ripple::Config::getValueFor
int getValueFor(SizedItem item, std::optional< std::size_t > node=std::nullopt) const
Retrieve the default value for the item at the specified node size.
Definition: Config.cpp:1022
ripple::base_uint::isZero
bool isZero() const
Definition: base_uint.h:532
ripple::LedgerHistory::app_
Application & app_
Definition: LedgerHistory.h:125
ripple::SHAMapItem
Definition: SHAMapItem.h:34
ripple::LedgerHistory::getLedgerByHash
std::shared_ptr< Ledger const > getLedgerByHash(LedgerHash const &ledgerHash)
Retrieve a ledger given its hash.
Definition: LedgerHistory.cpp:116
ripple::JsonOptions::none
@ none
ripple::Application::config
virtual Config & config()=0
std::unique_lock
STL class.
ripple::ReadView::txRead
virtual tx_type txRead(key_type const &key) const =0
Read a transaction from the tx map.
ripple::SHAMap
A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree.
Definition: SHAMap.h:95
ripple::LedgerHistory::validatedLedger
void validatedLedger(std::shared_ptr< Ledger const > const &, std::optional< uint256 > const &consensusHash)
Report that we have validated a particular ledger.
Definition: LedgerHistory.cpp:467
beast::Journal::error
Stream error() const
Definition: Journal.h:333
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint32_t
ripple::LedgerHistory::getLedgerBySeq
std::shared_ptr< Ledger const > getLedgerBySeq(LedgerIndex ledgerIndex)
Get a ledger given its sequence number.
Definition: LedgerHistory.cpp:83
ripple::log_one
static void log_one(ReadView const &ledger, uint256 const &tx, char const *msg, beast::Journal &j)
Definition: LedgerHistory.cpp:141
ripple::getJson
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
Definition: LedgerToJson.cpp:296
std::optional::value
T value(T... args)
ripple::ReadView
A view into a ledger.
Definition: ReadView.h:125
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::ReadView::seq
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition: ReadView.h:193
ripple::TaggedCache::fetch
std::shared_ptr< T > fetch(const key_type &key)
Definition: TaggedCache.h:396
std::vector::begin
T begin(T... args)
std
STL namespace.
ripple::LogicError
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:48
ripple::LedgerHistory::clearLedgerCachePrior
void clearLedgerCachePrior(LedgerIndex seq)
Definition: LedgerHistory.cpp:522
ripple::SizedItem::ledgerSize
@ ledgerSize
std::optional
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::TaggedCache::canonicalize_replace_client
bool canonicalize_replace_client(const key_type &key, std::shared_ptr< T > &data)
Definition: TaggedCache.h:389
std::vector::end
T end(T... args)
ripple::TaggedCache::peekMutex
mutex_type & peekMutex()
Definition: TaggedCache.h:452
ripple::LedgerHistory::mismatch_counter_
beast::insight::Counter mismatch_counter_
Definition: LedgerHistory.h:127
ripple::LedgerHistory::LedgerHistory
LedgerHistory(beast::insight::Collector::ptr const &collector, Application &app)
Definition: LedgerHistory.cpp:31
Json::Value::begin
const_iterator begin() const
Definition: json_value.cpp:1046
ripple::LedgerHistory::m_ledgers_by_hash
LedgersByHash m_ledgers_by_hash
Definition: LedgerHistory.h:131
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::SizedItem::ledgerAge
@ ledgerAge