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 // VFALCO TODO replace macros
30 
31 #ifndef CACHED_LEDGER_NUM
32 #define CACHED_LEDGER_NUM 96
33 #endif
34 
36 
37 // FIXME: Need to clean up ledgers by index at some point
38 
40  beast::insight::Collector::ptr const& collector,
41  Application& app)
42  : app_(app)
43  , collector_(collector)
44  , mismatch_counter_(collector->make_counter("ledger.history", "mismatch"))
45  , m_ledgers_by_hash(
46  "LedgerCache",
47  CACHED_LEDGER_NUM,
49  stopwatch(),
50  app_.journal("TaggedCache"))
51  , m_consensus_validated(
52  "ConsensusValidated",
53  64,
54  std::chrono::minutes{5},
55  stopwatch(),
56  app_.journal("TaggedCache"))
57  , j_(app.journal("LedgerHistory"))
58 {
59 }
60 
61 bool
63 {
64  if (!ledger->isImmutable())
65  LogicError("mutable Ledger in insert");
66 
67  assert(ledger->stateMap().getHash().isNonZero());
68 
70 
71  const bool alreadyHad = m_ledgers_by_hash.canonicalize_replace_cache(
72  ledger->info().hash, ledger);
73  if (validated)
74  mLedgersByIndex[ledger->info().seq] = ledger->info().hash;
75 
76  return alreadyHad;
77 }
78 
81 {
83  auto it = mLedgersByIndex.find(index);
84 
85  if (it != mLedgersByIndex.end())
86  return it->second;
87 
88  return uint256();
89 }
90 
93 {
94  {
96  auto it = mLedgersByIndex.find(index);
97 
98  if (it != mLedgersByIndex.end())
99  {
100  uint256 hash = it->second;
101  sl.unlock();
102  return getLedgerByHash(hash);
103  }
104  }
105 
107 
108  if (!ret)
109  return ret;
110 
111  assert(ret->info().seq == index);
112 
113  {
114  // Add this ledger to the local tracking by index
116 
117  assert(ret->isImmutable());
118  m_ledgers_by_hash.canonicalize_replace_client(ret->info().hash, ret);
119  mLedgersByIndex[ret->info().seq] = ret->info().hash;
120  return (ret->info().seq == index) ? ret : nullptr;
121  }
122 }
123 
126 {
127  auto ret = m_ledgers_by_hash.fetch(hash);
128 
129  if (ret)
130  {
131  assert(ret->isImmutable());
132  assert(ret->info().hash == hash);
133  return ret;
134  }
135 
136  ret = loadByHash(hash, app_);
137 
138  if (!ret)
139  return ret;
140 
141  assert(ret->isImmutable());
142  assert(ret->info().hash == hash);
143  m_ledgers_by_hash.canonicalize_replace_client(ret->info().hash, ret);
144  assert(ret->info().hash == hash);
145 
146  return ret;
147 }
148 
149 static void
151  ReadView const& ledger,
152  uint256 const& tx,
153  char const* msg,
154  beast::Journal& j)
155 {
156  auto metaData = ledger.txRead(tx).second;
157 
158  if (metaData != nullptr)
159  {
160  JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg
161  << " is missing this transaction:\n"
162  << metaData->getJson(JsonOptions::none);
163  }
164  else
165  {
166  JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg
167  << " is missing this transaction.";
168  }
169 }
170 
171 static void
173  ReadView const& builtLedger,
174  ReadView const& validLedger,
175  uint256 const& tx,
176  beast::Journal j)
177 {
178  auto getMeta = [](ReadView const& ledger,
179  uint256 const& txID) -> std::shared_ptr<TxMeta> {
180  auto meta = ledger.txRead(txID).second;
181  if (!meta)
182  return {};
183  return std::make_shared<TxMeta>(txID, ledger.seq(), *meta);
184  };
185 
186  auto validMetaData = getMeta(validLedger, tx);
187  auto builtMetaData = getMeta(builtLedger, tx);
188  assert(validMetaData != nullptr || builtMetaData != nullptr);
189 
190  if (validMetaData != nullptr && builtMetaData != nullptr)
191  {
192  auto const& validNodes = validMetaData->getNodes();
193  auto const& builtNodes = builtMetaData->getNodes();
194 
195  bool const result_diff =
196  validMetaData->getResultTER() != builtMetaData->getResultTER();
197 
198  bool const index_diff =
199  validMetaData->getIndex() != builtMetaData->getIndex();
200 
201  bool const nodes_diff = validNodes != builtNodes;
202 
203  if (!result_diff && !index_diff && !nodes_diff)
204  {
205  JLOG(j.error()) << "MISMATCH on TX " << tx
206  << ": No apparent mismatches detected!";
207  return;
208  }
209 
210  if (!nodes_diff)
211  {
212  if (result_diff && index_diff)
213  {
214  JLOG(j.debug()) << "MISMATCH on TX " << tx
215  << ": Different result and index!";
216  JLOG(j.debug()) << " Built:"
217  << " Result: " << builtMetaData->getResult()
218  << " Index: " << builtMetaData->getIndex();
219  JLOG(j.debug()) << " Valid:"
220  << " Result: " << validMetaData->getResult()
221  << " Index: " << validMetaData->getIndex();
222  }
223  else if (result_diff)
224  {
225  JLOG(j.debug())
226  << "MISMATCH on TX " << tx << ": Different result!";
227  JLOG(j.debug()) << " Built:"
228  << " Result: " << builtMetaData->getResult();
229  JLOG(j.debug()) << " Valid:"
230  << " Result: " << validMetaData->getResult();
231  }
232  else if (index_diff)
233  {
234  JLOG(j.debug())
235  << "MISMATCH on TX " << tx << ": Different index!";
236  JLOG(j.debug()) << " Built:"
237  << " Index: " << builtMetaData->getIndex();
238  JLOG(j.debug()) << " Valid:"
239  << " Index: " << validMetaData->getIndex();
240  }
241  }
242  else
243  {
244  if (result_diff && index_diff)
245  {
246  JLOG(j.debug()) << "MISMATCH on TX " << tx
247  << ": Different result, index and nodes!";
248  JLOG(j.debug()) << " Built:\n"
249  << builtMetaData->getJson(JsonOptions::none);
250  JLOG(j.debug()) << " Valid:\n"
251  << validMetaData->getJson(JsonOptions::none);
252  }
253  else if (result_diff)
254  {
255  JLOG(j.debug()) << "MISMATCH on TX " << tx
256  << ": Different result and nodes!";
257  JLOG(j.debug())
258  << " Built:"
259  << " Result: " << builtMetaData->getResult() << " Nodes:\n"
260  << builtNodes.getJson(JsonOptions::none);
261  JLOG(j.debug())
262  << " Valid:"
263  << " Result: " << validMetaData->getResult() << " Nodes:\n"
264  << validNodes.getJson(JsonOptions::none);
265  }
266  else if (index_diff)
267  {
268  JLOG(j.debug()) << "MISMATCH on TX " << tx
269  << ": Different index and nodes!";
270  JLOG(j.debug())
271  << " Built:"
272  << " Index: " << builtMetaData->getIndex() << " Nodes:\n"
273  << builtNodes.getJson(JsonOptions::none);
274  JLOG(j.debug())
275  << " Valid:"
276  << " Index: " << validMetaData->getIndex() << " Nodes:\n"
277  << validNodes.getJson(JsonOptions::none);
278  }
279  else // nodes_diff
280  {
281  JLOG(j.debug())
282  << "MISMATCH on TX " << tx << ": Different nodes!";
283  JLOG(j.debug()) << " Built:"
284  << " Nodes:\n"
285  << builtNodes.getJson(JsonOptions::none);
286  JLOG(j.debug()) << " Valid:"
287  << " Nodes:\n"
288  << validNodes.getJson(JsonOptions::none);
289  }
290  }
291  }
292  else if (validMetaData != nullptr)
293  {
294  JLOG(j.error()) << "MISMATCH on TX " << tx
295  << ": Metadata Difference (built has none)\n"
296  << validMetaData->getJson(JsonOptions::none);
297  }
298  else // builtMetaData != nullptr
299  {
300  JLOG(j.error()) << "MISMATCH on TX " << tx
301  << ": Metadata Difference (valid has none)\n"
302  << builtMetaData->getJson(JsonOptions::none);
303  }
304 }
305 
306 //------------------------------------------------------------------------------
307 
308 // Return list of leaves sorted by key
310 leaves(SHAMap const& sm)
311 {
313  for (auto const& item : sm)
314  v.push_back(&item);
315  std::sort(
316  v.begin(), v.end(), [](SHAMapItem const* lhs, SHAMapItem const* rhs) {
317  return lhs->key() < rhs->key();
318  });
319  return v;
320 }
321 
322 void
324  LedgerHash const& built,
325  LedgerHash const& valid,
326  std::optional<uint256> const& builtConsensusHash,
327  std::optional<uint256> const& validatedConsensusHash,
328  Json::Value const& consensus)
329 {
330  assert(built != valid);
332 
333  auto builtLedger = getLedgerByHash(built);
334  auto validLedger = getLedgerByHash(valid);
335 
336  if (!builtLedger || !validLedger)
337  {
338  JLOG(j_.error()) << "MISMATCH cannot be analyzed:"
339  << " builtLedger: " << to_string(built) << " -> "
340  << builtLedger << " validLedger: " << to_string(valid)
341  << " -> " << validLedger;
342  return;
343  }
344 
345  assert(builtLedger->info().seq == validLedger->info().seq);
346 
347  if (auto stream = j_.debug())
348  {
349  stream << "Built: " << getJson({*builtLedger, {}});
350  stream << "Valid: " << getJson({*validLedger, {}});
351  stream << "Consensus: " << consensus;
352  }
353 
354  // Determine the mismatch reason, distinguishing Byzantine
355  // failure from transaction processing difference
356 
357  // Disagreement over prior ledger indicates sync issue
358  if (builtLedger->info().parentHash != validLedger->info().parentHash)
359  {
360  JLOG(j_.error()) << "MISMATCH on prior ledger";
361  return;
362  }
363 
364  // Disagreement over close time indicates Byzantine failure
365  if (builtLedger->info().closeTime != validLedger->info().closeTime)
366  {
367  JLOG(j_.error()) << "MISMATCH on close time";
368  return;
369  }
370 
371  if (builtConsensusHash && validatedConsensusHash)
372  {
373  if (builtConsensusHash != validatedConsensusHash)
374  JLOG(j_.error())
375  << "MISMATCH on consensus transaction set "
376  << " built: " << to_string(*builtConsensusHash)
377  << " validated: " << to_string(*validatedConsensusHash);
378  else
379  JLOG(j_.error()) << "MISMATCH with same consensus transaction set: "
380  << to_string(*builtConsensusHash);
381  }
382 
383  // Find differences between built and valid ledgers
384  auto const builtTx = leaves(builtLedger->txMap());
385  auto const validTx = leaves(validLedger->txMap());
386 
387  if (builtTx == validTx)
388  JLOG(j_.error()) << "MISMATCH with same " << builtTx.size()
389  << " transactions";
390  else
391  JLOG(j_.error()) << "MISMATCH with " << builtTx.size() << " built and "
392  << validTx.size() << " valid transactions.";
393 
394  JLOG(j_.error()) << "built\n" << getJson({*builtLedger, {}});
395  JLOG(j_.error()) << "valid\n" << getJson({*validLedger, {}});
396 
397  // Log all differences between built and valid ledgers
398  auto b = builtTx.begin();
399  auto v = validTx.begin();
400  while (b != builtTx.end() && v != validTx.end())
401  {
402  if ((*b)->key() < (*v)->key())
403  {
404  log_one(*builtLedger, (*b)->key(), "valid", j_);
405  ++b;
406  }
407  else if ((*b)->key() > (*v)->key())
408  {
409  log_one(*validLedger, (*v)->key(), "built", j_);
410  ++v;
411  }
412  else
413  {
414  if ((*b)->slice() != (*v)->slice())
415  {
416  // Same transaction with different metadata
418  *builtLedger, *validLedger, (*b)->key(), j_);
419  }
420  ++b;
421  ++v;
422  }
423  }
424  for (; b != builtTx.end(); ++b)
425  log_one(*builtLedger, (*b)->key(), "valid", j_);
426  for (; v != validTx.end(); ++v)
427  log_one(*validLedger, (*v)->key(), "built", j_);
428 }
429 
430 void
432  std::shared_ptr<Ledger const> const& ledger,
433  uint256 const& consensusHash,
434  Json::Value consensus)
435 {
436  LedgerIndex index = ledger->info().seq;
437  LedgerHash hash = ledger->info().hash;
438  assert(!hash.isZero());
439 
441 
442  auto entry = std::make_shared<cv_entry>();
444 
445  if (entry->validated && !entry->built)
446  {
447  if (entry->validated.value() != hash)
448  {
449  JLOG(j_.error()) << "MISMATCH: seq=" << index
450  << " validated:" << entry->validated.value()
451  << " then:" << hash;
453  hash,
454  entry->validated.value(),
455  consensusHash,
456  entry->validatedConsensusHash,
457  consensus);
458  }
459  else
460  {
461  // We validated a ledger and then built it locally
462  JLOG(j_.debug()) << "MATCH: seq=" << index << " late";
463  }
464  }
465 
466  entry->built.emplace(hash);
467  entry->builtConsensusHash.emplace(consensusHash);
468  entry->consensus.emplace(std::move(consensus));
469 }
470 
471 void
473  std::shared_ptr<Ledger const> const& ledger,
474  std::optional<uint256> const& consensusHash)
475 {
476  LedgerIndex index = ledger->info().seq;
477  LedgerHash hash = ledger->info().hash;
478  assert(!hash.isZero());
479 
481 
482  auto entry = std::make_shared<cv_entry>();
484 
485  if (entry->built && !entry->validated)
486  {
487  if (entry->built.value() != hash)
488  {
489  JLOG(j_.error())
490  << "MISMATCH: seq=" << index
491  << " built:" << entry->built.value() << " then:" << hash;
493  entry->built.value(),
494  hash,
495  entry->builtConsensusHash,
496  consensusHash,
497  entry->consensus.value());
498  }
499  else
500  {
501  // We built a ledger locally and then validated it
502  JLOG(j_.debug()) << "MATCH: seq=" << index;
503  }
504  }
505 
506  entry->validated.emplace(hash);
507  entry->validatedConsensusHash = consensusHash;
508 }
509 
512 bool
513 LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash)
514 {
516  auto it = mLedgersByIndex.find(ledgerIndex);
517 
518  if ((it != mLedgersByIndex.end()) && (it->second != ledgerHash))
519  {
520  it->second = ledgerHash;
521  return false;
522  }
523  return true;
524 }
525 
526 void
528 {
531 }
532 
533 void
535 {
537  {
538  auto const ledger = getLedgerByHash(it);
539  if (!ledger || ledger->info().seq < seq)
540  m_ledgers_by_hash.del(it, false);
541  }
542 }
543 
544 } // namespace ripple
ripple::TaggedCache::getKeys
std::vector< key_type > getKeys() const
Definition: TaggedCache.h:455
ripple::Application
Definition: Application.h:115
ripple::TaggedCache::del
bool del(const key_type &key, bool valid)
Definition: TaggedCache.h:266
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:323
ripple::loadByIndex
std::shared_ptr< Ledger > loadByIndex(std::uint32_t ledgerIndex, Application &app, bool acquire)
Definition: Ledger.cpp:1061
ripple::CachedLedgerAge
constexpr std::chrono::seconds CachedLedgerAge
Definition: LedgerHistory.cpp:35
ripple::leaves
static std::vector< SHAMapItem const * > leaves(SHAMap const &sm)
Definition: LedgerHistory.cpp:310
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:172
ripple::LedgerHistory::fixIndex
bool fixIndex(LedgerIndex ledgerIndex, LedgerHash const &ledgerHash)
Repair a hash to index mapping.
Definition: LedgerHistory.cpp:513
std::vector
STL class.
ripple::LedgerHistory::mLedgersByIndex
std::map< LedgerIndex, LedgerHash > mLedgersByIndex
Definition: LedgerHistory.h:159
ripple::LedgerHistory::j_
beast::Journal j_
Definition: LedgerHistory.h:161
std::chrono::seconds
ripple::LedgerHistory::tune
void tune(int size, std::chrono::seconds age)
Set the history cache's parameters.
Definition: LedgerHistory.cpp:527
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:80
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:379
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:431
std::unique_lock::unlock
T unlock(T... args)
ripple::uint256
base_uint< 256 > uint256
Definition: base_uint.h:529
std::vector::push_back
T push_back(T... args)
ripple::LedgerHistory::m_consensus_validated
ConsensusValidated m_consensus_validated
Definition: LedgerHistory.h:156
ripple::base_uint< 256 >
ripple::TaggedCache::setTargetSize
void setTargetSize(int s)
Definition: TaggedCache.h:105
ripple::loadByHash
std::shared_ptr< Ledger > loadByHash(uint256 const &ledgerHash, Application &app, bool acquire)
Definition: Ledger.cpp:1074
ripple::LedgerHistory::insert
bool insert(std::shared_ptr< Ledger const > ledger, bool validated)
Track a ledger.
Definition: LedgerHistory.cpp:62
ripple::base_uint::isZero
bool isZero() const
Definition: base_uint.h:511
ripple::TaggedCache::setTargetAge
void setTargetAge(clock_type::duration s)
Definition: TaggedCache.h:132
ripple::LedgerHistory::app_
Application & app_
Definition: LedgerHistory.h:132
ripple::SHAMapItem
Definition: SHAMapItem.h:31
ripple::LedgerHistory::getLedgerByHash
std::shared_ptr< Ledger const > getLedgerByHash(LedgerHash const &ledgerHash)
Retrieve a ledger given its hash.
Definition: LedgerHistory.cpp:125
ripple::JsonOptions::none
@ none
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:472
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:92
ripple::log_one
static void log_one(ReadView const &ledger, uint256 const &tx, char const *msg, beast::Journal &j)
Definition: LedgerHistory.cpp:150
ripple::getJson
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
Definition: LedgerToJson.cpp:291
std::optional::value
T value(T... args)
ripple::ReadView
A view into a ledger.
Definition: ReadView.h:192
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:260
ripple::TaggedCache::fetch
std::shared_ptr< T > fetch(const key_type &key)
Definition: TaggedCache.h:393
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:534
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:38
ripple::TaggedCache::canonicalize_replace_client
bool canonicalize_replace_client(const key_type &key, std::shared_ptr< T > &data)
Definition: TaggedCache.h:387
std::vector::end
T end(T... args)
ripple::TaggedCache::peekMutex
mutex_type & peekMutex()
Definition: TaggedCache.h:449
ripple::LedgerHistory::mismatch_counter_
beast::insight::Counter mismatch_counter_
Definition: LedgerHistory.h:134
ripple::LedgerHistory::LedgerHistory
LedgerHistory(beast::insight::Collector::ptr const &collector, Application &app)
Definition: LedgerHistory.cpp:39
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:138
Json::Value
Represents a JSON value.
Definition: json_value.h:145