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