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