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 const bool 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())
229 << " Built:" << " Result: " << builtMetaData->getResult()
230 << " Index: " << builtMetaData->getIndex();
231 JLOG(j.debug())
232 << " Valid:" << " 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())
240 << " Built:" << " Result: " << builtMetaData->getResult();
241 JLOG(j.debug())
242 << " Valid:" << " 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())
249 << " Built:" << " Index: " << builtMetaData->getIndex();
250 JLOG(j.debug())
251 << " Valid:" << " 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:" << " Result: " << builtMetaData->getResult()
271 << " Nodes:\n"
272 << builtNodes.getJson(JsonOptions::none);
273 JLOG(j.debug())
274 << " Valid:" << " Result: " << validMetaData->getResult()
275 << " 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:" << " Index: " << builtMetaData->getIndex()
284 << " Nodes:\n"
285 << builtNodes.getJson(JsonOptions::none);
286 JLOG(j.debug())
287 << " Valid:" << " Index: " << validMetaData->getIndex()
288 << " 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:" << " Nodes:\n"
296 << builtNodes.getJson(JsonOptions::none);
297 JLOG(j.debug()) << " Valid:" << " Nodes:\n"
298 << validNodes.getJson(JsonOptions::none);
299 }
300 }
301
302 return;
303 }
304
305 if (validMetaData)
306 {
307 JLOG(j.error()) << "MISMATCH on TX " << tx
308 << ": Metadata Difference. Valid=\n"
309 << validMetaData->getJson(JsonOptions::none);
310 }
311
312 if (builtMetaData)
313 {
314 JLOG(j.error()) << "MISMATCH on TX " << tx
315 << ": Metadata Difference. Built=\n"
316 << builtMetaData->getJson(JsonOptions::none);
317 }
318}
319
320//------------------------------------------------------------------------------
321
322// Return list of leaves sorted by key
324leaves(SHAMap const& sm)
325{
327 for (auto const& item : sm)
328 v.push_back(&item);
329 std::sort(
330 v.begin(), v.end(), [](SHAMapItem const* lhs, SHAMapItem const* rhs) {
331 return lhs->key() < rhs->key();
332 });
333 return v;
334}
335
336void
338 LedgerHash const& built,
339 LedgerHash const& valid,
340 std::optional<uint256> const& builtConsensusHash,
341 std::optional<uint256> const& validatedConsensusHash,
342 Json::Value const& consensus)
343{
344 XRPL_ASSERT(
345 built != valid,
346 "ripple::LedgerHistory::handleMismatch : unequal hashes");
348
349 auto builtLedger = getLedgerByHash(built);
350 auto validLedger = getLedgerByHash(valid);
351
352 if (!builtLedger || !validLedger)
353 {
354 JLOG(j_.error()) << "MISMATCH cannot be analyzed:" << " builtLedger: "
355 << to_string(built) << " -> " << builtLedger
356 << " validLedger: " << to_string(valid) << " -> "
357 << validLedger;
358 return;
359 }
360
361 XRPL_ASSERT(
362 builtLedger->info().seq == validLedger->info().seq,
363 "ripple::LedgerHistory::handleMismatch : sequence match");
364
365 if (auto stream = j_.debug())
366 {
367 stream << "Built: " << getJson({*builtLedger, {}});
368 stream << "Valid: " << getJson({*validLedger, {}});
369 stream << "Consensus: " << consensus;
370 }
371
372 // Determine the mismatch reason, distinguishing Byzantine
373 // failure from transaction processing difference
374
375 // Disagreement over prior ledger indicates sync issue
376 if (builtLedger->info().parentHash != validLedger->info().parentHash)
377 {
378 JLOG(j_.error()) << "MISMATCH on prior ledger";
379 return;
380 }
381
382 // Disagreement over close time indicates Byzantine failure
383 if (builtLedger->info().closeTime != validLedger->info().closeTime)
384 {
385 JLOG(j_.error()) << "MISMATCH on close time";
386 return;
387 }
388
389 if (builtConsensusHash && validatedConsensusHash)
390 {
391 if (builtConsensusHash != validatedConsensusHash)
392 JLOG(j_.error())
393 << "MISMATCH on consensus transaction set "
394 << " built: " << to_string(*builtConsensusHash)
395 << " validated: " << to_string(*validatedConsensusHash);
396 else
397 JLOG(j_.error()) << "MISMATCH with same consensus transaction set: "
398 << to_string(*builtConsensusHash);
399 }
400
401 // Find differences between built and valid ledgers
402 auto const builtTx = leaves(builtLedger->txMap());
403 auto const validTx = leaves(validLedger->txMap());
404
405 if (builtTx == validTx)
406 JLOG(j_.error()) << "MISMATCH with same " << builtTx.size()
407 << " transactions";
408 else
409 JLOG(j_.error()) << "MISMATCH with " << builtTx.size() << " built and "
410 << validTx.size() << " valid transactions.";
411
412 JLOG(j_.error()) << "built\n" << getJson({*builtLedger, {}});
413 JLOG(j_.error()) << "valid\n" << getJson({*validLedger, {}});
414
415 // Log all differences between built and valid ledgers
416 auto b = builtTx.begin();
417 auto v = validTx.begin();
418 while (b != builtTx.end() && v != validTx.end())
419 {
420 if ((*b)->key() < (*v)->key())
421 {
422 log_one(*builtLedger, (*b)->key(), "valid", j_);
423 ++b;
424 }
425 else if ((*b)->key() > (*v)->key())
426 {
427 log_one(*validLedger, (*v)->key(), "built", j_);
428 ++v;
429 }
430 else
431 {
432 if ((*b)->slice() != (*v)->slice())
433 {
434 // Same transaction with different metadata
436 *builtLedger, *validLedger, (*b)->key(), j_);
437 }
438 ++b;
439 ++v;
440 }
441 }
442 for (; b != builtTx.end(); ++b)
443 log_one(*builtLedger, (*b)->key(), "valid", j_);
444 for (; v != validTx.end(); ++v)
445 log_one(*validLedger, (*v)->key(), "built", j_);
446}
447
448void
450 std::shared_ptr<Ledger const> const& ledger,
451 uint256 const& consensusHash,
452 Json::Value consensus)
453{
454 LedgerIndex index = ledger->info().seq;
455 LedgerHash hash = ledger->info().hash;
456 XRPL_ASSERT(
457 !hash.isZero(), "ripple::LedgerHistory::builtLedger : nonzero hash");
458
460
461 auto entry = std::make_shared<cv_entry>();
463
464 if (entry->validated && !entry->built)
465 {
466 if (entry->validated.value() != hash)
467 {
468 JLOG(j_.error()) << "MISMATCH: seq=" << index
469 << " validated:" << entry->validated.value()
470 << " then:" << hash;
472 hash,
473 entry->validated.value(),
474 consensusHash,
475 entry->validatedConsensusHash,
476 consensus);
477 }
478 else
479 {
480 // We validated a ledger and then built it locally
481 JLOG(j_.debug()) << "MATCH: seq=" << index << " late";
482 }
483 }
484
485 entry->built.emplace(hash);
486 entry->builtConsensusHash.emplace(consensusHash);
487 entry->consensus.emplace(std::move(consensus));
488}
489
490void
492 std::shared_ptr<Ledger const> const& ledger,
493 std::optional<uint256> const& consensusHash)
494{
495 LedgerIndex index = ledger->info().seq;
496 LedgerHash hash = ledger->info().hash;
497 XRPL_ASSERT(
498 !hash.isZero(),
499 "ripple::LedgerHistory::validatedLedger : nonzero hash");
500
502
503 auto entry = std::make_shared<cv_entry>();
505
506 if (entry->built && !entry->validated)
507 {
508 if (entry->built.value() != hash)
509 {
510 JLOG(j_.error())
511 << "MISMATCH: seq=" << index
512 << " built:" << entry->built.value() << " then:" << hash;
514 entry->built.value(),
515 hash,
516 entry->builtConsensusHash,
517 consensusHash,
518 entry->consensus.value());
519 }
520 else
521 {
522 // We built a ledger locally and then validated it
523 JLOG(j_.debug()) << "MATCH: seq=" << index;
524 }
525 }
526
527 entry->validated.emplace(hash);
528 entry->validatedConsensusHash = consensusHash;
529}
530
533bool
534LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash)
535{
537 auto it = mLedgersByIndex.find(ledgerIndex);
538
539 if ((it != mLedgersByIndex.end()) && (it->second != ledgerHash))
540 {
541 it->second = ledgerHash;
542 return false;
543 }
544 return true;
545}
546
547void
549{
551 {
552 auto const ledger = getLedgerByHash(it);
553 if (!ledger || ledger->info().seq < seq)
554 m_ledgers_by_hash.del(it, false);
555 }
556}
557
558} // namespace ripple
T begin(T... args)
Represents a JSON value.
Definition: json_value.h:148
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:98
bool del(const key_type &key, bool valid)
bool canonicalize_replace_cache(const key_type &key, SharedPointerType const &data)
mutex_type & peekMutex()
std::vector< key_type > getKeys() const
SharedPointerType fetch(const key_type &key)
bool canonicalize_replace_client(const key_type &key, SharedPointerType &data)
bool isZero() const
Definition: base_uint.h:540
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:44
std::shared_ptr< Ledger > loadByIndex(std::uint32_t ledgerIndex, Application &app, bool acquire)
Definition: Ledger.cpp:1113
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:1126
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.
Definition: contract.cpp:37
STL namespace.
T push_back(T... args)
T sort(T... args)
T unlock(T... args)