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
66 bool const alreadyHad = m_ledgers_by_hash.canonicalize_replace_cache(
67 ledger->info().hash, ledger);
68 if (validated)
69 mLedgersByIndex[ledger->info().seq] = ledger->info().hash;
70
71 return alreadyHad;
72}
73
76{
77 if (auto it = mLedgersByIndex.find(index); it != mLedgersByIndex.end())
78 return it->second;
79 return {};
80}
81
84{
85 {
86 auto it = mLedgersByIndex.find(index);
87
88 if (it != mLedgersByIndex.end())
89 {
90 uint256 hash = it->second;
91 return getLedgerByHash(hash);
92 }
93 }
94
96
97 if (!ret)
98 return ret;
99
100 XRPL_ASSERT(
101 ret->info().seq == index,
102 "ripple::LedgerHistory::getLedgerBySeq : result sequence match");
103
104 {
105 // Add this ledger to the local tracking by index
106
107 XRPL_ASSERT(
108 ret->isImmutable(),
109 "ripple::LedgerHistory::getLedgerBySeq : immutable result ledger");
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 XRPL_ASSERT(
124 ret->isImmutable(),
125 "ripple::LedgerHistory::getLedgerByHash : immutable fetched "
126 "ledger");
127 XRPL_ASSERT(
128 ret->info().hash == hash,
129 "ripple::LedgerHistory::getLedgerByHash : fetched ledger hash "
130 "match");
131 return ret;
132 }
133
134 ret = loadByHash(hash, app_);
135
136 if (!ret)
137 return ret;
138
139 XRPL_ASSERT(
140 ret->isImmutable(),
141 "ripple::LedgerHistory::getLedgerByHash : immutable loaded ledger");
142 XRPL_ASSERT(
143 ret->info().hash == hash,
144 "ripple::LedgerHistory::getLedgerByHash : loaded ledger hash match");
145 m_ledgers_by_hash.canonicalize_replace_client(ret->info().hash, ret);
146 XRPL_ASSERT(
147 ret->info().hash == hash,
148 "ripple::LedgerHistory::getLedgerByHash : result hash match");
149
150 return ret;
151}
152
153static void
155 ReadView const& ledger,
156 uint256 const& tx,
157 char const* msg,
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::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,
194 "ripple::log_metadata_difference : some metadata present");
195
196 if (validMetaData && builtMetaData)
197 {
198 auto const& validNodes = validMetaData->getNodes();
199 auto const& builtNodes = builtMetaData->getNodes();
200
201 bool const result_diff =
202 validMetaData->getResultTER() != builtMetaData->getResultTER();
203
204 bool const index_diff =
205 validMetaData->getIndex() != builtMetaData->getIndex();
206
207 bool const nodes_diff = validNodes != builtNodes;
208
209 if (!result_diff && !index_diff && !nodes_diff)
210 {
211 JLOG(j.error()) << "MISMATCH on TX " << tx
212 << ": No apparent mismatches detected!";
213 return;
214 }
215
216 if (!nodes_diff)
217 {
218 if (result_diff && index_diff)
219 {
220 JLOG(j.debug()) << "MISMATCH on TX " << tx
221 << ": Different result and index!";
222 JLOG(j.debug()) << " Built:"
223 << " Result: " << builtMetaData->getResult()
224 << " Index: " << builtMetaData->getIndex();
225 JLOG(j.debug()) << " Valid:"
226 << " Result: " << validMetaData->getResult()
227 << " Index: " << validMetaData->getIndex();
228 }
229 else if (result_diff)
230 {
231 JLOG(j.debug())
232 << "MISMATCH on TX " << tx << ": Different result!";
233 JLOG(j.debug()) << " Built:"
234 << " Result: " << builtMetaData->getResult();
235 JLOG(j.debug()) << " Valid:"
236 << " Result: " << validMetaData->getResult();
237 }
238 else if (index_diff)
239 {
240 JLOG(j.debug())
241 << "MISMATCH on TX " << tx << ": Different index!";
242 JLOG(j.debug()) << " Built:"
243 << " Index: " << builtMetaData->getIndex();
244 JLOG(j.debug()) << " Valid:"
245 << " Index: " << validMetaData->getIndex();
246 }
247 }
248 else
249 {
250 if (result_diff && index_diff)
251 {
252 JLOG(j.debug()) << "MISMATCH on TX " << tx
253 << ": Different result, index and nodes!";
254 JLOG(j.debug()) << " Built:\n"
255 << builtMetaData->getJson(JsonOptions::none);
256 JLOG(j.debug()) << " Valid:\n"
257 << validMetaData->getJson(JsonOptions::none);
258 }
259 else if (result_diff)
260 {
261 JLOG(j.debug()) << "MISMATCH on TX " << tx
262 << ": Different result and nodes!";
263 JLOG(j.debug())
264 << " Built:"
265 << " Result: " << builtMetaData->getResult() << " Nodes:\n"
266 << builtNodes.getJson(JsonOptions::none);
267 JLOG(j.debug())
268 << " Valid:"
269 << " Result: " << validMetaData->getResult() << " Nodes:\n"
270 << validNodes.getJson(JsonOptions::none);
271 }
272 else if (index_diff)
273 {
274 JLOG(j.debug()) << "MISMATCH on TX " << tx
275 << ": Different index and nodes!";
276 JLOG(j.debug())
277 << " Built:"
278 << " Index: " << builtMetaData->getIndex() << " Nodes:\n"
279 << builtNodes.getJson(JsonOptions::none);
280 JLOG(j.debug())
281 << " Valid:"
282 << " Index: " << validMetaData->getIndex() << " Nodes:\n"
283 << validNodes.getJson(JsonOptions::none);
284 }
285 else // nodes_diff
286 {
287 JLOG(j.debug())
288 << "MISMATCH on TX " << tx << ": Different nodes!";
289 JLOG(j.debug()) << " Built:"
290 << " Nodes:\n"
291 << builtNodes.getJson(JsonOptions::none);
292 JLOG(j.debug()) << " Valid:"
293 << " Nodes:\n"
294 << validNodes.getJson(JsonOptions::none);
295 }
296 }
297
298 return;
299 }
300
301 if (validMetaData)
302 {
303 JLOG(j.error()) << "MISMATCH on TX " << tx
304 << ": Metadata Difference. Valid=\n"
305 << validMetaData->getJson(JsonOptions::none);
306 }
307
308 if (builtMetaData)
309 {
310 JLOG(j.error()) << "MISMATCH on TX " << tx
311 << ": Metadata Difference. Built=\n"
312 << builtMetaData->getJson(JsonOptions::none);
313 }
314}
315
316//------------------------------------------------------------------------------
317
318// Return list of leaves sorted by key
320leaves(SHAMap const& sm)
321{
323 for (auto const& item : sm)
324 v.push_back(&item);
325 std::sort(
326 v.begin(), v.end(), [](SHAMapItem const* lhs, SHAMapItem const* rhs) {
327 return lhs->key() < rhs->key();
328 });
329 return v;
330}
331
332void
334 LedgerHash const& built,
335 LedgerHash const& valid,
336 std::optional<uint256> const& builtConsensusHash,
337 std::optional<uint256> const& validatedConsensusHash,
338 Json::Value const& consensus)
339{
340 XRPL_ASSERT(
341 built != valid,
342 "ripple::LedgerHistory::handleMismatch : unequal hashes");
344
345 auto builtLedger = getLedgerByHash(built);
346 auto validLedger = getLedgerByHash(valid);
347
348 if (!builtLedger || !validLedger)
349 {
350 JLOG(j_.error()) << "MISMATCH cannot be analyzed:"
351 << " builtLedger: " << to_string(built) << " -> "
352 << builtLedger << " validLedger: " << to_string(valid)
353 << " -> " << validLedger;
354 return;
355 }
356
357 XRPL_ASSERT(
358 builtLedger->info().seq == validLedger->info().seq,
359 "ripple::LedgerHistory::handleMismatch : sequence match");
360
361 if (auto stream = j_.debug())
362 {
363 stream << "Built: " << getJson({*builtLedger, {}});
364 stream << "Valid: " << getJson({*validLedger, {}});
365 stream << "Consensus: " << consensus;
366 }
367
368 // Determine the mismatch reason, distinguishing Byzantine
369 // failure from transaction processing difference
370
371 // Disagreement over prior ledger indicates sync issue
372 if (builtLedger->info().parentHash != validLedger->info().parentHash)
373 {
374 JLOG(j_.error()) << "MISMATCH on prior ledger";
375 return;
376 }
377
378 // Disagreement over close time indicates Byzantine failure
379 if (builtLedger->info().closeTime != validLedger->info().closeTime)
380 {
381 JLOG(j_.error()) << "MISMATCH on close time";
382 return;
383 }
384
385 if (builtConsensusHash && validatedConsensusHash)
386 {
387 if (builtConsensusHash != validatedConsensusHash)
388 JLOG(j_.error())
389 << "MISMATCH on consensus transaction set "
390 << " built: " << to_string(*builtConsensusHash)
391 << " validated: " << to_string(*validatedConsensusHash);
392 else
393 JLOG(j_.error()) << "MISMATCH with same consensus transaction set: "
394 << to_string(*builtConsensusHash);
395 }
396
397 // Find differences between built and valid ledgers
398 auto const builtTx = leaves(builtLedger->txMap());
399 auto const validTx = leaves(validLedger->txMap());
400
401 if (builtTx == validTx)
402 JLOG(j_.error()) << "MISMATCH with same " << builtTx.size()
403 << " transactions";
404 else
405 JLOG(j_.error()) << "MISMATCH with " << builtTx.size() << " built and "
406 << validTx.size() << " valid transactions.";
407
408 JLOG(j_.error()) << "built\n" << getJson({*builtLedger, {}});
409 JLOG(j_.error()) << "valid\n" << getJson({*validLedger, {}});
410
411 // Log all differences between built and valid ledgers
412 auto b = builtTx.begin();
413 auto v = validTx.begin();
414 while (b != builtTx.end() && v != validTx.end())
415 {
416 if ((*b)->key() < (*v)->key())
417 {
418 log_one(*builtLedger, (*b)->key(), "valid", j_);
419 ++b;
420 }
421 else if ((*b)->key() > (*v)->key())
422 {
423 log_one(*validLedger, (*v)->key(), "built", j_);
424 ++v;
425 }
426 else
427 {
428 if ((*b)->slice() != (*v)->slice())
429 {
430 // Same transaction with different metadata
432 *builtLedger, *validLedger, (*b)->key(), j_);
433 }
434 ++b;
435 ++v;
436 }
437 }
438 for (; b != builtTx.end(); ++b)
439 log_one(*builtLedger, (*b)->key(), "valid", j_);
440 for (; v != validTx.end(); ++v)
441 log_one(*validLedger, (*v)->key(), "built", j_);
442}
443
444void
446 std::shared_ptr<Ledger const> const& ledger,
447 uint256 const& consensusHash,
448 Json::Value consensus)
449{
450 LedgerIndex index = ledger->info().seq;
451 LedgerHash hash = ledger->info().hash;
452 XRPL_ASSERT(
453 !hash.isZero(), "ripple::LedgerHistory::builtLedger : nonzero hash");
454
455 auto entry = std::make_shared<cv_entry>();
457
458 if (entry->validated && !entry->built)
459 {
460 if (entry->validated.value() != hash)
461 {
462 JLOG(j_.error()) << "MISMATCH: seq=" << index
463 << " validated:" << entry->validated.value()
464 << " then:" << hash;
466 hash,
467 entry->validated.value(),
468 consensusHash,
469 entry->validatedConsensusHash,
470 consensus);
471 }
472 else
473 {
474 // We validated a ledger and then built it locally
475 JLOG(j_.debug()) << "MATCH: seq=" << index << " late";
476 }
477 }
478
479 entry->built.emplace(hash);
480 entry->builtConsensusHash.emplace(consensusHash);
481 entry->consensus.emplace(std::move(consensus));
482}
483
484void
486 std::shared_ptr<Ledger const> const& ledger,
487 std::optional<uint256> const& consensusHash)
488{
489 LedgerIndex index = ledger->info().seq;
490 LedgerHash hash = ledger->info().hash;
491 XRPL_ASSERT(
492 !hash.isZero(),
493 "ripple::LedgerHistory::validatedLedger : nonzero hash");
494
495 auto entry = std::make_shared<cv_entry>();
497
498 if (entry->built && !entry->validated)
499 {
500 if (entry->built.value() != hash)
501 {
502 JLOG(j_.error())
503 << "MISMATCH: seq=" << index
504 << " built:" << entry->built.value() << " then:" << hash;
506 entry->built.value(),
507 hash,
508 entry->builtConsensusHash,
509 consensusHash,
510 entry->consensus.value());
511 }
512 else
513 {
514 // We built a ledger locally and then validated it
515 JLOG(j_.debug()) << "MATCH: seq=" << index;
516 }
517 }
518
519 entry->validated.emplace(hash);
520 entry->validatedConsensusHash = consensusHash;
521}
522
525bool
526LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash)
527{
528 auto ledger = m_ledgers_by_hash.fetch(ledgerHash);
529 auto it = mLedgersByIndex.find(ledgerIndex);
530 if (ledger && (it != mLedgersByIndex.end()) && (it->second != ledgerHash))
531 {
532 it->second = ledgerHash;
533 return false;
534 }
535 return true;
536}
537
538void
540{
542 {
543 auto const ledger = getLedgerByHash(it);
544 if (!ledger || ledger->info().seq < seq)
545 m_ledgers_by_hash.del(it, false);
546 }
547}
548
549} // 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:52
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition: ReadView.h:119
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:99
bool canonicalize_replace_client(key_type const &key, SharedPointerType &data)
bool del(key_type const &key, bool valid)
SharedPointerType fetch(key_type const &key)
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)
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:1114
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:1127
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)