xrpld
Loading...
Searching...
No Matches
LedgerHistory.cpp
1#include <xrpld/app/ledger/LedgerHistory.h>
2
3#include <xrpld/app/ledger/LedgerPersistence.h>
4#include <xrpld/app/ledger/LedgerToJson.h>
5#include <xrpld/app/main/Application.h>
6#include <xrpld/core/Config.h>
7
8#include <xrpl/basics/Log.h>
9#include <xrpl/basics/base_uint.h>
10#include <xrpl/basics/chrono.h>
11#include <xrpl/basics/contract.h>
12#include <xrpl/beast/insight/Collector.h>
13#include <xrpl/beast/utility/Journal.h>
14#include <xrpl/beast/utility/instrumentation.h>
15#include <xrpl/json/json_value.h>
16#include <xrpl/json/to_string.h> // IWYU pragma: keep
17#include <xrpl/ledger/ReadView.h>
18#include <xrpl/protocol/Protocol.h>
19#include <xrpl/protocol/RippleLedgerHash.h>
20#include <xrpl/protocol/Rules.h>
21#include <xrpl/protocol/TxMeta.h>
22#include <xrpl/shamap/SHAMap.h>
23#include <xrpl/shamap/SHAMapItem.h>
24
25#include <algorithm>
26#include <memory>
27#include <mutex>
28#include <optional>
29#include <utility>
30#include <vector>
31
32namespace xrpl {
33
34// FIXME: Need to clean up ledgers by index at some point
35
37 : app_(app)
38 , collector_(collector)
39 , mismatchCounter_(collector->makeCounter("ledger.history", "mismatch"))
41 "LedgerCache",
42 app_.config().getValueFor(SizedItem::LedgerSize),
43 std::chrono::seconds{app_.config().getValueFor(SizedItem::LedgerAge)},
44 stopwatch(),
45 app_.getJournal("TaggedCache"))
47 "ConsensusValidated",
48 64,
49 std::chrono::minutes{5},
50 stopwatch(),
51 app_.getJournal("TaggedCache"))
52 , j_(app.getJournal("LedgerHistory"))
53{
54}
55
56bool
58{
59 if (!ledger->isImmutable())
60 logicError("mutable Ledger in insert");
61
62 XRPL_ASSERT(
63 ledger->stateMap().getHash().isNonZero(), "xrpl::LedgerHistory::insert : nonzero hash");
64
65 std::unique_lock const sl(ledgersByHash_.peekMutex());
66
67 bool const alreadyHad = ledgersByHash_.canonicalizeReplaceCache(ledger->header().hash, ledger);
68 if (validated)
69 ledgersByIndex_[ledger->header().seq] = ledger->header().hash;
70
71 return alreadyHad;
72}
73
76{
77 std::unique_lock const sl(ledgersByHash_.peekMutex());
78 if (auto it = ledgersByIndex_.find(index); it != ledgersByIndex_.end())
79 return it->second;
80 return {};
81}
82
85{
86 {
87 std::unique_lock sl(ledgersByHash_.peekMutex());
88 auto it = ledgersByIndex_.find(index);
89
90 if (it != ledgersByIndex_.end())
91 {
92 uint256 const hash = it->second;
93 sl.unlock();
94 return getLedgerByHash(hash);
95 }
96 }
97
98 Rules const rules{app_.config().features};
99 Fees const fees = app_.config().fees.toFees();
100 std::shared_ptr<Ledger const> ret = loadByIndex(index, rules, fees, app_);
101
102 if (!ret)
103 return ret;
104
105 XRPL_ASSERT(
106 ret->header().seq == index, "xrpl::LedgerHistory::getLedgerBySeq : result sequence match");
107
108 {
109 // Add this ledger to the local tracking by index
110 std::unique_lock const sl(ledgersByHash_.peekMutex());
111
112 XRPL_ASSERT(
113 ret->isImmutable(), "xrpl::LedgerHistory::getLedgerBySeq : immutable result ledger");
114 ledgersByHash_.canonicalizeReplaceClient(ret->header().hash, ret);
115 ledgersByIndex_[ret->header().seq] = ret->header().hash;
116 return (ret->header().seq == index) ? ret : nullptr;
117 }
118}
119
122{
123 auto ret = ledgersByHash_.fetch(hash);
124
125 if (ret)
126 {
127 XRPL_ASSERT(
128 ret->isImmutable(),
129 "xrpl::LedgerHistory::getLedgerByHash : immutable fetched "
130 "ledger");
131 XRPL_ASSERT(
132 ret->header().hash == hash,
133 "xrpl::LedgerHistory::getLedgerByHash : fetched ledger hash "
134 "match");
135 return ret;
136 }
137
138 Rules const rules{app_.config().features};
139 Fees const fees = app_.config().fees.toFees();
140 ret = loadByHash(hash, rules, fees, app_);
141
142 if (!ret)
143 return ret;
144
145 XRPL_ASSERT(
146 ret->isImmutable(), "xrpl::LedgerHistory::getLedgerByHash : immutable loaded ledger");
147 XRPL_ASSERT(
148 ret->header().hash == hash,
149 "xrpl::LedgerHistory::getLedgerByHash : loaded ledger hash match");
150 ledgersByHash_.canonicalizeReplaceClient(ret->header().hash, ret);
151 XRPL_ASSERT(
152 ret->header().hash == hash, "xrpl::LedgerHistory::getLedgerByHash : result hash match");
153
154 return ret;
155}
156
157static void
158logOne(ReadView const& ledger, uint256 const& tx, char const* msg, beast::Journal& j)
159{
160 auto metaData = ledger.txRead(tx).second;
161
162 if (metaData != nullptr)
163 {
164 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg
165 << " is missing this transaction:\n"
166 << metaData->getJson(JsonOptions::Values::None);
167 }
168 else
169 {
170 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg
171 << " is missing this transaction.";
172 }
173}
174
175static void
177 ReadView const& builtLedger,
178 ReadView const& validLedger,
179 uint256 const& tx,
181{
182 auto getMeta = [](ReadView const& ledger, uint256 const& txID) {
184 if (auto meta = ledger.txRead(txID).second)
185 ret.emplace(txID, ledger.seq(), *meta);
186 return ret;
187 };
188
189 auto validMetaData = getMeta(validLedger, tx);
190 auto builtMetaData = getMeta(builtLedger, tx);
191
192 XRPL_ASSERT(
193 validMetaData || builtMetaData, "xrpl::log_metadata_difference : some metadata present");
194
195 if (validMetaData && builtMetaData)
196 {
197 auto const& validNodes = validMetaData->getNodes();
198 auto const& builtNodes = builtMetaData->getNodes();
199
200 bool const resultDiff = validMetaData->getResultTER() != builtMetaData->getResultTER();
201
202 bool const indexDiff = validMetaData->getIndex() != builtMetaData->getIndex();
203
204 bool const nodesDiff = validNodes != builtNodes;
205
206 if (!resultDiff && !indexDiff && !nodesDiff)
207 {
208 JLOG(j.error()) << "MISMATCH on TX " << tx << ": No apparent mismatches detected!";
209 return;
210 }
211
212 if (!nodesDiff)
213 {
214 if (resultDiff && indexDiff)
215 {
216 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result and index!";
217 JLOG(j.debug()) << " Built:"
218 << " Result: " << builtMetaData->getResult()
219 << " Index: " << builtMetaData->getIndex();
220 JLOG(j.debug()) << " Valid:"
221 << " Result: " << validMetaData->getResult()
222 << " Index: " << validMetaData->getIndex();
223 }
224 else if (resultDiff)
225 {
226 JLOG(j.debug()) << "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 (indexDiff)
233 {
234 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different index!";
235 JLOG(j.debug()) << " Built:"
236 << " Index: " << builtMetaData->getIndex();
237 JLOG(j.debug()) << " Valid:"
238 << " Index: " << validMetaData->getIndex();
239 }
240 }
241 else
242 {
243 if (resultDiff && indexDiff)
244 {
245 JLOG(j.debug()) << "MISMATCH on TX " << tx
246 << ": Different result, index and nodes!";
247 JLOG(j.debug()) << " Built:\n" << builtMetaData->getJson(JsonOptions::Values::None);
248 JLOG(j.debug()) << " Valid:\n" << validMetaData->getJson(JsonOptions::Values::None);
249 }
250 else if (resultDiff)
251 {
252 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result and nodes!";
253 JLOG(j.debug()) << " Built:"
254 << " Result: " << builtMetaData->getResult() << " Nodes:\n"
255 << builtNodes.getJson(JsonOptions::Values::None);
256 JLOG(j.debug()) << " Valid:"
257 << " Result: " << validMetaData->getResult() << " Nodes:\n"
258 << validNodes.getJson(JsonOptions::Values::None);
259 }
260 else if (indexDiff)
261 {
262 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different index and nodes!";
263 JLOG(j.debug()) << " Built:"
264 << " Index: " << builtMetaData->getIndex() << " Nodes:\n"
265 << builtNodes.getJson(JsonOptions::Values::None);
266 JLOG(j.debug()) << " Valid:"
267 << " Index: " << validMetaData->getIndex() << " Nodes:\n"
268 << validNodes.getJson(JsonOptions::Values::None);
269 }
270 else // nodes_diff
271 {
272 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different nodes!";
273 JLOG(j.debug()) << " Built:"
274 << " Nodes:\n"
275 << builtNodes.getJson(JsonOptions::Values::None);
276 JLOG(j.debug()) << " Valid:"
277 << " Nodes:\n"
278 << validNodes.getJson(JsonOptions::Values::None);
279 }
280 }
281
282 return;
283 }
284
285 if (validMetaData)
286 {
287 JLOG(j.error()) << "MISMATCH on TX " << tx << ": Metadata Difference. Valid=\n"
288 << validMetaData->getJson(JsonOptions::Values::None);
289 }
290
291 if (builtMetaData)
292 {
293 JLOG(j.error()) << "MISMATCH on TX " << tx << ": Metadata Difference. Built=\n"
294 << builtMetaData->getJson(JsonOptions::Values::None);
295 }
296}
297
298//------------------------------------------------------------------------------
299
300// Return list of leaves sorted by key
302leaves(SHAMap const& sm)
303{
305 for (auto const& item : sm)
306 v.push_back(&item);
308 v, [](SHAMapItem const* lhs, SHAMapItem const* rhs) { return lhs->key() < rhs->key(); });
309 return v;
310}
311
312void
314 LedgerHash const& built,
315 LedgerHash const& valid,
316 std::optional<uint256> const& builtConsensusHash,
317 std::optional<uint256> const& validatedConsensusHash,
318 json::Value const& consensus)
319{
320 XRPL_ASSERT(built != valid, "xrpl::LedgerHistory::handleMismatch : unequal hashes");
322
323 auto builtLedger = getLedgerByHash(built);
324 auto validLedger = getLedgerByHash(valid);
325
326 if (!builtLedger || !validLedger)
327 {
328 JLOG(j_.error()) << "MISMATCH cannot be analyzed:"
329 << " builtLedger: " << to_string(built) << " -> " << builtLedger
330 << " validLedger: " << to_string(valid) << " -> " << validLedger;
331 return;
332 }
333
334 XRPL_ASSERT(
335 builtLedger->header().seq == validLedger->header().seq,
336 "xrpl::LedgerHistory::handleMismatch : sequence match");
337
338 if (auto stream = j_.debug())
339 {
340 stream << "Built: " << getJson({*builtLedger, {}});
341 stream << "Valid: " << getJson({*validLedger, {}});
342 stream << "Consensus: " << consensus;
343 }
344
345 // Determine the mismatch reason, distinguishing Byzantine
346 // failure from transaction processing difference
347
348 // Disagreement over prior ledger indicates sync issue
349 if (builtLedger->header().parentHash != validLedger->header().parentHash)
350 {
351 JLOG(j_.error()) << "MISMATCH on prior ledger";
352 return;
353 }
354
355 // Disagreement over close time indicates Byzantine failure
356 if (builtLedger->header().closeTime != validLedger->header().closeTime)
357 {
358 JLOG(j_.error()) << "MISMATCH on close time";
359 return;
360 }
361
362 if (builtConsensusHash && validatedConsensusHash)
363 {
364 if (builtConsensusHash != validatedConsensusHash)
365 {
366 JLOG(j_.error()) << "MISMATCH on consensus transaction set "
367 << " built: " << to_string(*builtConsensusHash)
368 << " validated: " << to_string(*validatedConsensusHash);
369 }
370 else
371 {
372 JLOG(j_.error()) << "MISMATCH with same consensus transaction set: "
373 << to_string(*builtConsensusHash);
374 }
375 }
376
377 // Find differences between built and valid ledgers
378 auto const builtTx = leaves(builtLedger->txMap());
379 auto const validTx = leaves(validLedger->txMap());
380
381 if (builtTx == validTx)
382 {
383 JLOG(j_.error()) << "MISMATCH with same " << builtTx.size() << " transactions";
384 }
385 else
386 {
387 JLOG(j_.error()) << "MISMATCH with " << builtTx.size() << " built and " << validTx.size()
388 << " valid transactions.";
389 }
390
391 JLOG(j_.error()) << "built\n" << getJson({*builtLedger, {}});
392 JLOG(j_.error()) << "valid\n" << getJson({*validLedger, {}});
393
394 // Log all differences between built and valid ledgers
395 auto b = builtTx.begin();
396 auto v = validTx.begin();
397 while (b != builtTx.end() && v != validTx.end())
398 {
399 if ((*b)->key() < (*v)->key())
400 {
401 logOne(*builtLedger, (*b)->key(), "valid", j_);
402 ++b;
403 }
404 else if ((*b)->key() > (*v)->key())
405 {
406 logOne(*validLedger, (*v)->key(), "built", j_);
407 ++v;
408 }
409 else
410 {
411 if ((*b)->slice() != (*v)->slice())
412 {
413 // Same transaction with different metadata
414 logMetadataDifference(*builtLedger, *validLedger, (*b)->key(), j_);
415 }
416 ++b;
417 ++v;
418 }
419 }
420 for (; b != builtTx.end(); ++b)
421 logOne(*builtLedger, (*b)->key(), "valid", j_);
422 for (; v != validTx.end(); ++v)
423 logOne(*validLedger, (*v)->key(), "built", j_);
424}
425
426void
428 std::shared_ptr<Ledger const> const& ledger,
429 uint256 const& consensusHash,
430 json::Value consensus)
431{
432 LedgerIndex const index = ledger->header().seq;
433 LedgerHash const hash = ledger->header().hash;
434 XRPL_ASSERT(!hash.isZero(), "xrpl::LedgerHistory::builtLedger : nonzero hash");
435
436 std::unique_lock const sl(consensusValidated_.peekMutex());
437
438 auto entry = std::make_shared<CvEntry>();
439 consensusValidated_.canonicalizeReplaceClient(index, entry);
440
441 if (entry->validated && !entry->built)
442 {
443 if (entry->validated.value() != hash)
444 {
445 JLOG(j_.error()) << "MISMATCH: seq=" << index
446 << " validated:" << entry->validated.value() << " 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
466void
468 std::shared_ptr<Ledger const> const& ledger,
469 std::optional<uint256> const& consensusHash)
470{
471 LedgerIndex const index = ledger->header().seq;
472 LedgerHash const hash = ledger->header().hash;
473 XRPL_ASSERT(!hash.isZero(), "xrpl::LedgerHistory::validatedLedger : nonzero hash");
474
475 std::unique_lock const sl(consensusValidated_.peekMutex());
476
477 auto entry = std::make_shared<CvEntry>();
478 consensusValidated_.canonicalizeReplaceClient(index, entry);
479
480 if (entry->built && !entry->validated)
481 {
482 if (entry->built.value() != hash)
483 {
484 JLOG(j_.error()) << "MISMATCH: seq=" << index << " built:" << entry->built.value()
485 << " then:" << hash;
487 entry->built.value(),
488 hash,
489 entry->builtConsensusHash,
490 consensusHash,
491 entry->consensus.value()); // NOLINT(bugprone-unchecked-optional-access) consensus
492 // always emplaced with built
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
507bool
508LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash)
509{
510 std::unique_lock const sl(ledgersByHash_.peekMutex());
511 auto it = ledgersByIndex_.find(ledgerIndex);
512
513 if ((it != ledgersByIndex_.end()) && (it->second != ledgerHash))
514 {
515 it->second = ledgerHash;
516 return false;
517 }
518 return true;
519}
520
521void
523{
524 for (LedgerHash const it : ledgersByHash_.getKeys())
525 {
526 auto const ledger = getLedgerByHash(it);
527 if (!ledger || ledger->header().seq < seq)
528 ledgersByHash_.del(it, false);
529 }
530}
531
532} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream error() const
Definition Journal.h:315
Stream debug() const
Definition Journal.h:297
std::shared_ptr< Collector > ptr
Definition Collector.h:26
Represents a JSON value.
Definition json_value.h:130
const_iterator begin() const
bool isZero() const
Definition base_uint.h:544
void validatedLedger(std::shared_ptr< Ledger const > const &, std::optional< uint256 > const &consensusHash)
Report that we have validated a particular ledger.
ConsensusValidated consensusValidated_
beast::insight::Collector::ptr collector_
bool insert(std::shared_ptr< Ledger const > const &ledger, bool validated)
Track a ledger.
LedgerHash getLedgerHash(LedgerIndex ledgerIndex)
Get a ledger's hash given its sequence number.
bool fixIndex(LedgerIndex ledgerIndex, LedgerHash const &ledgerHash)
Repair a hash to index mapping.
std::shared_ptr< Ledger const > getLedgerByHash(LedgerHash const &ledgerHash)
Retrieve a ledger given its hash.
void builtLedger(std::shared_ptr< Ledger const > const &, uint256 const &consensusHash, json::Value)
Report that we have locally built a particular ledger.
LedgerHistory(beast::insight::Collector::ptr const &collector, Application &app)
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.
beast::insight::Counter mismatchCounter_
std::shared_ptr< Ledger const > getLedgerBySeq(LedgerIndex ledgerIndex)
Get a ledger given its sequence number.
beast::Journal j_
LedgersByHash ledgersByHash_
void clearLedgerCachePrior(LedgerIndex seq)
std::map< LedgerIndex, LedgerHash > ledgersByIndex_
A view into a ledger.
Definition ReadView.h:31
virtual tx_type txRead(key_type const &key) const =0
Read a transaction from the tx map.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:97
Rules controlling protocol behavior.
Definition Rules.h:33
uint256 const & key() const
Definition SHAMapItem.h:66
A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree.
Definition SHAMap.h:77
T emplace(T... args)
T make_shared(T... args)
STL namespace.
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:5
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:259
Stopwatch & stopwatch()
Returns an instance of a wall clock.
Definition chrono.h:94
static void logMetadataDifference(ReadView const &builtLedger, ReadView const &validLedger, uint256 const &tx, beast::Journal j)
std::shared_ptr< Ledger > loadByIndex(std::uint32_t ledgerIndex, Rules const &rules, Fees const &fees, ServiceRegistry &registry, bool acquire)
Load a ledger by its sequence number.
SizedItem
Definition Config.h:27
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
void logicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
uint256 LedgerHash
static std::vector< SHAMapItem const * > leaves(SHAMap const &sm)
static void logOne(ReadView const &ledger, uint256 const &tx, char const *msg, beast::Journal &j)
json::Value getJson(LedgerFill const &fill)
Return a new json::Value representing the ledger with given options.
std::shared_ptr< Ledger > loadByHash(uint256 const &ledgerHash, Rules const &rules, Fees const &fees, ServiceRegistry &registry, bool acquire)
Load a ledger by its hash.
BaseUInt< 256 > uint256
Definition base_uint.h:562
T push_back(T... args)
T sort(T... args)
Reflects the fee settings for a particular ledger.
T unlock(T... args)