rippled
Loading...
Searching...
No Matches
LedgerHistory.cpp
1#include <xrpld/app/ledger/LedgerHistory.h>
2#include <xrpld/app/ledger/LedgerToJson.h>
3
4#include <xrpl/basics/Log.h>
5#include <xrpl/basics/chrono.h>
6#include <xrpl/basics/contract.h>
7#include <xrpl/json/to_string.h>
8
9namespace xrpl {
10
11// FIXME: Need to clean up ledgers by index at some point
12
14 beast::insight::Collector::ptr const& collector,
15 Application& app)
16 : app_(app)
17 , collector_(collector)
18 , mismatch_counter_(collector->make_counter("ledger.history", "mismatch"))
19 , m_ledgers_by_hash(
20 "LedgerCache",
21 app_.config().getValueFor(SizedItem::ledgerSize),
22 std::chrono::seconds{app_.config().getValueFor(SizedItem::ledgerAge)},
23 stopwatch(),
24 app_.journal("TaggedCache"))
25 , m_consensus_validated(
26 "ConsensusValidated",
27 64,
28 std::chrono::minutes{5},
29 stopwatch(),
30 app_.journal("TaggedCache"))
31 , j_(app.journal("LedgerHistory"))
32{
33}
34
35bool
38 bool validated)
39{
40 if (!ledger->isImmutable())
41 LogicError("mutable Ledger in insert");
42
43 XRPL_ASSERT(
44 ledger->stateMap().getHash().isNonZero(),
45 "xrpl::LedgerHistory::insert : nonzero hash");
46
48
49 bool const alreadyHad = m_ledgers_by_hash.canonicalize_replace_cache(
50 ledger->header().hash, ledger);
51 if (validated)
52 mLedgersByIndex[ledger->header().seq] = ledger->header().hash;
53
54 return alreadyHad;
55}
56
59{
61 if (auto it = mLedgersByIndex.find(index); it != mLedgersByIndex.end())
62 return it->second;
63 return {};
64}
65
68{
69 {
71 auto it = mLedgersByIndex.find(index);
72
73 if (it != mLedgersByIndex.end())
74 {
75 uint256 hash = it->second;
76 sl.unlock();
77 return getLedgerByHash(hash);
78 }
79 }
80
82
83 if (!ret)
84 return ret;
85
86 XRPL_ASSERT(
87 ret->header().seq == index,
88 "xrpl::LedgerHistory::getLedgerBySeq : result sequence match");
89
90 {
91 // Add this ledger to the local tracking by index
93
94 XRPL_ASSERT(
95 ret->isImmutable(),
96 "xrpl::LedgerHistory::getLedgerBySeq : immutable result ledger");
97 m_ledgers_by_hash.canonicalize_replace_client(ret->header().hash, ret);
98 mLedgersByIndex[ret->header().seq] = ret->header().hash;
99 return (ret->header().seq == index) ? ret : nullptr;
100 }
101}
102
105{
106 auto ret = m_ledgers_by_hash.fetch(hash);
107
108 if (ret)
109 {
110 XRPL_ASSERT(
111 ret->isImmutable(),
112 "xrpl::LedgerHistory::getLedgerByHash : immutable fetched "
113 "ledger");
114 XRPL_ASSERT(
115 ret->header().hash == hash,
116 "xrpl::LedgerHistory::getLedgerByHash : fetched ledger hash "
117 "match");
118 return ret;
119 }
120
121 ret = loadByHash(hash, app_);
122
123 if (!ret)
124 return ret;
125
126 XRPL_ASSERT(
127 ret->isImmutable(),
128 "xrpl::LedgerHistory::getLedgerByHash : immutable loaded ledger");
129 XRPL_ASSERT(
130 ret->header().hash == hash,
131 "xrpl::LedgerHistory::getLedgerByHash : loaded ledger hash match");
132 m_ledgers_by_hash.canonicalize_replace_client(ret->header().hash, ret);
133 XRPL_ASSERT(
134 ret->header().hash == hash,
135 "xrpl::LedgerHistory::getLedgerByHash : result hash match");
136
137 return ret;
138}
139
140static void
142 ReadView const& ledger,
143 uint256 const& tx,
144 char const* msg,
146{
147 auto metaData = ledger.txRead(tx).second;
148
149 if (metaData != nullptr)
150 {
151 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg
152 << " is missing this transaction:\n"
153 << metaData->getJson(JsonOptions::none);
154 }
155 else
156 {
157 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg
158 << " is missing this transaction.";
159 }
160}
161
162static void
164 ReadView const& builtLedger,
165 ReadView const& validLedger,
166 uint256 const& tx,
168{
169 auto getMeta = [](ReadView const& ledger, uint256 const& txID) {
171 if (auto meta = ledger.txRead(txID).second)
172 ret.emplace(txID, ledger.seq(), *meta);
173 return ret;
174 };
175
176 auto validMetaData = getMeta(validLedger, tx);
177 auto builtMetaData = getMeta(builtLedger, tx);
178
179 XRPL_ASSERT(
180 validMetaData || builtMetaData,
181 "xrpl::log_metadata_difference : some metadata present");
182
183 if (validMetaData && builtMetaData)
184 {
185 auto const& validNodes = validMetaData->getNodes();
186 auto const& builtNodes = builtMetaData->getNodes();
187
188 bool const result_diff =
189 validMetaData->getResultTER() != builtMetaData->getResultTER();
190
191 bool const index_diff =
192 validMetaData->getIndex() != builtMetaData->getIndex();
193
194 bool const nodes_diff = validNodes != builtNodes;
195
196 if (!result_diff && !index_diff && !nodes_diff)
197 {
198 JLOG(j.error()) << "MISMATCH on TX " << tx
199 << ": No apparent mismatches detected!";
200 return;
201 }
202
203 if (!nodes_diff)
204 {
205 if (result_diff && index_diff)
206 {
207 JLOG(j.debug()) << "MISMATCH on TX " << tx
208 << ": Different result and index!";
209 JLOG(j.debug()) << " Built:"
210 << " Result: " << builtMetaData->getResult()
211 << " Index: " << builtMetaData->getIndex();
212 JLOG(j.debug()) << " Valid:"
213 << " Result: " << validMetaData->getResult()
214 << " Index: " << validMetaData->getIndex();
215 }
216 else if (result_diff)
217 {
218 JLOG(j.debug())
219 << "MISMATCH on TX " << tx << ": Different result!";
220 JLOG(j.debug()) << " Built:"
221 << " Result: " << builtMetaData->getResult();
222 JLOG(j.debug()) << " Valid:"
223 << " Result: " << validMetaData->getResult();
224 }
225 else if (index_diff)
226 {
227 JLOG(j.debug())
228 << "MISMATCH on TX " << tx << ": Different index!";
229 JLOG(j.debug()) << " Built:"
230 << " Index: " << builtMetaData->getIndex();
231 JLOG(j.debug()) << " Valid:"
232 << " Index: " << validMetaData->getIndex();
233 }
234 }
235 else
236 {
237 if (result_diff && index_diff)
238 {
239 JLOG(j.debug()) << "MISMATCH on TX " << tx
240 << ": Different result, index and nodes!";
241 JLOG(j.debug()) << " Built:\n"
242 << builtMetaData->getJson(JsonOptions::none);
243 JLOG(j.debug()) << " Valid:\n"
244 << validMetaData->getJson(JsonOptions::none);
245 }
246 else if (result_diff)
247 {
248 JLOG(j.debug()) << "MISMATCH on TX " << tx
249 << ": Different result and nodes!";
250 JLOG(j.debug())
251 << " Built:"
252 << " Result: " << builtMetaData->getResult() << " Nodes:\n"
253 << builtNodes.getJson(JsonOptions::none);
254 JLOG(j.debug())
255 << " Valid:"
256 << " Result: " << validMetaData->getResult() << " Nodes:\n"
257 << validNodes.getJson(JsonOptions::none);
258 }
259 else if (index_diff)
260 {
261 JLOG(j.debug()) << "MISMATCH on TX " << tx
262 << ": Different index and nodes!";
263 JLOG(j.debug())
264 << " Built:"
265 << " Index: " << builtMetaData->getIndex() << " Nodes:\n"
266 << builtNodes.getJson(JsonOptions::none);
267 JLOG(j.debug())
268 << " Valid:"
269 << " Index: " << validMetaData->getIndex() << " Nodes:\n"
270 << validNodes.getJson(JsonOptions::none);
271 }
272 else // nodes_diff
273 {
274 JLOG(j.debug())
275 << "MISMATCH on TX " << tx << ": Different nodes!";
276 JLOG(j.debug()) << " Built:"
277 << " Nodes:\n"
278 << builtNodes.getJson(JsonOptions::none);
279 JLOG(j.debug()) << " Valid:"
280 << " Nodes:\n"
281 << validNodes.getJson(JsonOptions::none);
282 }
283 }
284
285 return;
286 }
287
288 if (validMetaData)
289 {
290 JLOG(j.error()) << "MISMATCH on TX " << tx
291 << ": Metadata Difference. Valid=\n"
292 << validMetaData->getJson(JsonOptions::none);
293 }
294
295 if (builtMetaData)
296 {
297 JLOG(j.error()) << "MISMATCH on TX " << tx
298 << ": Metadata Difference. Built=\n"
299 << builtMetaData->getJson(JsonOptions::none);
300 }
301}
302
303//------------------------------------------------------------------------------
304
305// Return list of leaves sorted by key
307leaves(SHAMap const& sm)
308{
310 for (auto const& item : sm)
311 v.push_back(&item);
312 std::sort(
313 v.begin(), v.end(), [](SHAMapItem const* lhs, SHAMapItem const* rhs) {
314 return lhs->key() < rhs->key();
315 });
316 return v;
317}
318
319void
321 LedgerHash const& built,
322 LedgerHash const& valid,
323 std::optional<uint256> const& builtConsensusHash,
324 std::optional<uint256> const& validatedConsensusHash,
325 Json::Value const& consensus)
326{
327 XRPL_ASSERT(
328 built != valid, "xrpl::LedgerHistory::handleMismatch : unequal hashes");
330
331 auto builtLedger = getLedgerByHash(built);
332 auto validLedger = getLedgerByHash(valid);
333
334 if (!builtLedger || !validLedger)
335 {
336 JLOG(j_.error()) << "MISMATCH cannot be analyzed:"
337 << " builtLedger: " << to_string(built) << " -> "
338 << builtLedger << " validLedger: " << to_string(valid)
339 << " -> " << validLedger;
340 return;
341 }
342
343 XRPL_ASSERT(
344 builtLedger->header().seq == validLedger->header().seq,
345 "xrpl::LedgerHistory::handleMismatch : sequence match");
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->header().parentHash != validLedger->header().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->header().closeTime != validLedger->header().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
430void
432 std::shared_ptr<Ledger const> const& ledger,
433 uint256 const& consensusHash,
434 Json::Value consensus)
435{
436 LedgerIndex index = ledger->header().seq;
437 LedgerHash hash = ledger->header().hash;
438 XRPL_ASSERT(
439 !hash.isZero(), "xrpl::LedgerHistory::builtLedger : nonzero hash");
440
442
443 auto entry = std::make_shared<cv_entry>();
445
446 if (entry->validated && !entry->built)
447 {
448 if (entry->validated.value() != hash)
449 {
450 JLOG(j_.error()) << "MISMATCH: seq=" << index
451 << " validated:" << entry->validated.value()
452 << " then:" << hash;
454 hash,
455 entry->validated.value(),
456 consensusHash,
457 entry->validatedConsensusHash,
458 consensus);
459 }
460 else
461 {
462 // We validated a ledger and then built it locally
463 JLOG(j_.debug()) << "MATCH: seq=" << index << " late";
464 }
465 }
466
467 entry->built.emplace(hash);
468 entry->builtConsensusHash.emplace(consensusHash);
469 entry->consensus.emplace(std::move(consensus));
470}
471
472void
474 std::shared_ptr<Ledger const> const& ledger,
475 std::optional<uint256> const& consensusHash)
476{
477 LedgerIndex index = ledger->header().seq;
478 LedgerHash hash = ledger->header().hash;
479 XRPL_ASSERT(
480 !hash.isZero(), "xrpl::LedgerHistory::validatedLedger : nonzero hash");
481
483
484 auto entry = std::make_shared<cv_entry>();
486
487 if (entry->built && !entry->validated)
488 {
489 if (entry->built.value() != hash)
490 {
491 JLOG(j_.error())
492 << "MISMATCH: seq=" << index
493 << " built:" << entry->built.value() << " then:" << hash;
495 entry->built.value(),
496 hash,
497 entry->builtConsensusHash,
498 consensusHash,
499 entry->consensus.value());
500 }
501 else
502 {
503 // We built a ledger locally and then validated it
504 JLOG(j_.debug()) << "MATCH: seq=" << index;
505 }
506 }
507
508 entry->validated.emplace(hash);
509 entry->validatedConsensusHash = consensusHash;
510}
511
514bool
515LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash)
516{
518 auto it = mLedgersByIndex.find(ledgerIndex);
519
520 if ((it != mLedgersByIndex.end()) && (it->second != ledgerHash))
521 {
522 it->second = ledgerHash;
523 return false;
524 }
525 return true;
526}
527
528void
530{
532 {
533 auto const ledger = getLedgerByHash(it);
534 if (!ledger || ledger->header().seq < seq)
535 m_ledgers_by_hash.del(it, false);
536 }
537}
538
539} // namespace xrpl
T begin(T... args)
Represents a JSON value.
Definition json_value.h:131
const_iterator begin() const
A generic endpoint for log messages.
Definition Journal.h:41
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
std::map< LedgerIndex, LedgerHash > mLedgersByIndex
void validatedLedger(std::shared_ptr< Ledger const > const &, std::optional< uint256 > const &consensusHash)
Report that we have validated a particular ledger.
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.
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.
ConsensusValidated m_consensus_validated
LedgerHistory(beast::insight::Collector::ptr const &collector, Application &app)
LedgersByHash m_ledgers_by_hash
void builtLedger(std::shared_ptr< Ledger const > const &, uint256 const &consensusHash, Json::Value)
Report that we have locally built a particular ledger.
beast::insight::Counter mismatch_counter_
std::shared_ptr< Ledger const > getLedgerBySeq(LedgerIndex ledgerIndex)
Get a ledger given its sequence number.
beast::Journal j_
void clearLedgerCachePrior(LedgerIndex seq)
A view into a ledger.
Definition ReadView.h:32
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:99
A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree.
Definition SHAMap.h:78
std::vector< key_type > getKeys() const
bool del(key_type const &key, bool valid)
bool canonicalize_replace_cache(key_type const &key, SharedPointerType const &data)
bool canonicalize_replace_client(key_type const &key, SharedPointerType &data)
SharedPointerType fetch(key_type const &key)
mutex_type & peekMutex()
bool isZero() const
Definition base_uint.h:521
T emplace(T... args)
T end(T... args)
T is_same_v
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:6
std::shared_ptr< Ledger > loadByHash(uint256 const &ledgerHash, Application &app, bool acquire)
Definition Ledger.cpp:1115
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
Stopwatch & stopwatch()
Returns an instance of a wall clock.
Definition chrono.h:100
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
static void log_metadata_difference(ReadView const &builtLedger, ReadView const &validLedger, uint256 const &tx, beast::Journal j)
SizedItem
Definition Config.h:25
static void log_one(ReadView const &ledger, uint256 const &tx, char const *msg, beast::Journal &j)
std::shared_ptr< Ledger > loadByIndex(std::uint32_t ledgerIndex, Application &app, bool acquire)
Definition Ledger.cpp:1102
static std::vector< SHAMapItem const * > leaves(SHAMap const &sm)
T push_back(T... args)
T sort(T... args)
T unlock(T... args)