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