rippled
Loading...
Searching...
No Matches
LedgerHistory.cpp
1#include <xrpld/app/ledger/LedgerHistory.h>
2#include <xrpld/app/ledger/LedgerPersistence.h>
3#include <xrpld/app/ledger/LedgerToJson.h>
4#include <xrpld/app/main/Application.h>
5
6#include <xrpl/basics/chrono.h>
7#include <xrpl/basics/contract.h>
8#include <xrpl/json/to_string.h>
9
10namespace xrpl {
11
12// FIXME: Need to clean up ledgers by index at some point
13
15 : app_(app)
16 , collector_(collector)
17 , mismatch_counter_(collector->make_counter("ledger.history", "mismatch"))
18 , m_ledgers_by_hash(
19 "LedgerCache",
20 app_.config().getValueFor(SizedItem::ledgerSize),
21 std::chrono::seconds{app_.config().getValueFor(SizedItem::ledgerAge)},
22 stopwatch(),
23 app_.getJournal("TaggedCache"))
24 , m_consensus_validated(
25 "ConsensusValidated",
26 64,
27 std::chrono::minutes{5},
28 stopwatch(),
29 app_.getJournal("TaggedCache"))
30 , j_(app.getJournal("LedgerHistory"))
31{
32}
33
34bool
36{
37 if (!ledger->isImmutable())
38 LogicError("mutable Ledger in insert");
39
40 XRPL_ASSERT(
41 ledger->stateMap().getHash().isNonZero(), "xrpl::LedgerHistory::insert : nonzero hash");
42
44
45 bool const alreadyHad =
46 m_ledgers_by_hash.canonicalize_replace_cache(ledger->header().hash, ledger);
47 if (validated)
48 mLedgersByIndex[ledger->header().seq] = ledger->header().hash;
49
50 return alreadyHad;
51}
52
55{
57 if (auto it = mLedgersByIndex.find(index); it != mLedgersByIndex.end())
58 return it->second;
59 return {};
60}
61
64{
65 {
67 auto it = mLedgersByIndex.find(index);
68
69 if (it != mLedgersByIndex.end())
70 {
71 uint256 const hash = it->second;
72 sl.unlock();
73 return getLedgerByHash(hash);
74 }
75 }
76
77 Rules const rules{app_.config().features};
78 Fees const fees = app_.config().FEES.toFees();
79 std::shared_ptr<Ledger const> ret = loadByIndex(index, rules, fees, app_);
80
81 if (!ret)
82 return ret;
83
84 XRPL_ASSERT(
85 ret->header().seq == index, "xrpl::LedgerHistory::getLedgerBySeq : result sequence match");
86
87 {
88 // Add this ledger to the local tracking by index
90
91 XRPL_ASSERT(
92 ret->isImmutable(), "xrpl::LedgerHistory::getLedgerBySeq : immutable result ledger");
93 m_ledgers_by_hash.canonicalize_replace_client(ret->header().hash, ret);
94 mLedgersByIndex[ret->header().seq] = ret->header().hash;
95 return (ret->header().seq == index) ? ret : nullptr;
96 }
97}
98
101{
102 auto ret = m_ledgers_by_hash.fetch(hash);
103
104 if (ret)
105 {
106 XRPL_ASSERT(
107 ret->isImmutable(),
108 "xrpl::LedgerHistory::getLedgerByHash : immutable fetched "
109 "ledger");
110 XRPL_ASSERT(
111 ret->header().hash == hash,
112 "xrpl::LedgerHistory::getLedgerByHash : fetched ledger hash "
113 "match");
114 return ret;
115 }
116
117 Rules const rules{app_.config().features};
118 Fees const fees = app_.config().FEES.toFees();
119 ret = loadByHash(hash, rules, fees, app_);
120
121 if (!ret)
122 return ret;
123
124 XRPL_ASSERT(
125 ret->isImmutable(), "xrpl::LedgerHistory::getLedgerByHash : immutable loaded ledger");
126 XRPL_ASSERT(
127 ret->header().hash == hash,
128 "xrpl::LedgerHistory::getLedgerByHash : loaded ledger hash match");
129 m_ledgers_by_hash.canonicalize_replace_client(ret->header().hash, ret);
130 XRPL_ASSERT(
131 ret->header().hash == hash, "xrpl::LedgerHistory::getLedgerByHash : result hash match");
132
133 return ret;
134}
135
136static void
137log_one(ReadView const& ledger, uint256 const& tx, char const* msg, beast::Journal& j)
138{
139 auto metaData = ledger.txRead(tx).second;
140
141 if (metaData != nullptr)
142 {
143 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg
144 << " is missing this transaction:\n"
145 << metaData->getJson(JsonOptions::none);
146 }
147 else
148 {
149 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg
150 << " is missing this transaction.";
151 }
152}
153
154static void
156 ReadView const& builtLedger,
157 ReadView const& validLedger,
158 uint256 const& tx,
160{
161 auto getMeta = [](ReadView const& ledger, uint256 const& txID) {
163 if (auto meta = ledger.txRead(txID).second)
164 ret.emplace(txID, ledger.seq(), *meta);
165 return ret;
166 };
167
168 auto validMetaData = getMeta(validLedger, tx);
169 auto builtMetaData = getMeta(builtLedger, tx);
170
171 XRPL_ASSERT(
172 validMetaData || builtMetaData, "xrpl::log_metadata_difference : some metadata present");
173
174 if (validMetaData && builtMetaData)
175 {
176 auto const& validNodes = validMetaData->getNodes();
177 auto const& builtNodes = builtMetaData->getNodes();
178
179 bool const result_diff = validMetaData->getResultTER() != builtMetaData->getResultTER();
180
181 bool const index_diff = validMetaData->getIndex() != builtMetaData->getIndex();
182
183 bool const nodes_diff = validNodes != builtNodes;
184
185 if (!result_diff && !index_diff && !nodes_diff)
186 {
187 JLOG(j.error()) << "MISMATCH on TX " << tx << ": No apparent mismatches detected!";
188 return;
189 }
190
191 if (!nodes_diff)
192 {
193 if (result_diff && index_diff)
194 {
195 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result and index!";
196 JLOG(j.debug()) << " Built:"
197 << " Result: " << builtMetaData->getResult()
198 << " Index: " << builtMetaData->getIndex();
199 JLOG(j.debug()) << " Valid:"
200 << " Result: " << validMetaData->getResult()
201 << " Index: " << validMetaData->getIndex();
202 }
203 else if (result_diff)
204 {
205 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result!";
206 JLOG(j.debug()) << " Built:"
207 << " Result: " << builtMetaData->getResult();
208 JLOG(j.debug()) << " Valid:"
209 << " Result: " << validMetaData->getResult();
210 }
211 else if (index_diff)
212 {
213 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different index!";
214 JLOG(j.debug()) << " Built:"
215 << " Index: " << builtMetaData->getIndex();
216 JLOG(j.debug()) << " Valid:"
217 << " Index: " << validMetaData->getIndex();
218 }
219 }
220 else
221 {
222 if (result_diff && index_diff)
223 {
224 JLOG(j.debug()) << "MISMATCH on TX " << tx
225 << ": Different result, index and nodes!";
226 JLOG(j.debug()) << " Built:\n" << builtMetaData->getJson(JsonOptions::none);
227 JLOG(j.debug()) << " Valid:\n" << validMetaData->getJson(JsonOptions::none);
228 }
229 else if (result_diff)
230 {
231 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result and nodes!";
232 JLOG(j.debug()) << " Built:"
233 << " Result: " << builtMetaData->getResult() << " Nodes:\n"
234 << builtNodes.getJson(JsonOptions::none);
235 JLOG(j.debug()) << " Valid:"
236 << " Result: " << validMetaData->getResult() << " Nodes:\n"
237 << validNodes.getJson(JsonOptions::none);
238 }
239 else if (index_diff)
240 {
241 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different index and nodes!";
242 JLOG(j.debug()) << " Built:"
243 << " Index: " << builtMetaData->getIndex() << " Nodes:\n"
244 << builtNodes.getJson(JsonOptions::none);
245 JLOG(j.debug()) << " Valid:"
246 << " Index: " << validMetaData->getIndex() << " Nodes:\n"
247 << validNodes.getJson(JsonOptions::none);
248 }
249 else // nodes_diff
250 {
251 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different nodes!";
252 JLOG(j.debug()) << " Built:"
253 << " Nodes:\n"
254 << builtNodes.getJson(JsonOptions::none);
255 JLOG(j.debug()) << " Valid:"
256 << " Nodes:\n"
257 << validNodes.getJson(JsonOptions::none);
258 }
259 }
260
261 return;
262 }
263
264 if (validMetaData)
265 {
266 JLOG(j.error()) << "MISMATCH on TX " << tx << ": Metadata Difference. Valid=\n"
267 << validMetaData->getJson(JsonOptions::none);
268 }
269
270 if (builtMetaData)
271 {
272 JLOG(j.error()) << "MISMATCH on TX " << tx << ": Metadata Difference. Built=\n"
273 << builtMetaData->getJson(JsonOptions::none);
274 }
275}
276
277//------------------------------------------------------------------------------
278
279// Return list of leaves sorted by key
281leaves(SHAMap const& sm)
282{
284 for (auto const& item : sm)
285 v.push_back(&item);
286 std::sort(v.begin(), v.end(), [](SHAMapItem const* lhs, SHAMapItem const* rhs) {
287 return lhs->key() < rhs->key();
288 });
289 return v;
290}
291
292void
294 LedgerHash const& built,
295 LedgerHash const& valid,
296 std::optional<uint256> const& builtConsensusHash,
297 std::optional<uint256> const& validatedConsensusHash,
298 Json::Value const& consensus)
299{
300 XRPL_ASSERT(built != valid, "xrpl::LedgerHistory::handleMismatch : unequal hashes");
302
303 auto builtLedger = getLedgerByHash(built);
304 auto validLedger = getLedgerByHash(valid);
305
306 if (!builtLedger || !validLedger)
307 {
308 JLOG(j_.error()) << "MISMATCH cannot be analyzed:"
309 << " builtLedger: " << to_string(built) << " -> " << builtLedger
310 << " validLedger: " << to_string(valid) << " -> " << validLedger;
311 return;
312 }
313
314 XRPL_ASSERT(
315 builtLedger->header().seq == validLedger->header().seq,
316 "xrpl::LedgerHistory::handleMismatch : sequence match");
317
318 if (auto stream = j_.debug())
319 {
320 stream << "Built: " << getJson({*builtLedger, {}});
321 stream << "Valid: " << getJson({*validLedger, {}});
322 stream << "Consensus: " << consensus;
323 }
324
325 // Determine the mismatch reason, distinguishing Byzantine
326 // failure from transaction processing difference
327
328 // Disagreement over prior ledger indicates sync issue
329 if (builtLedger->header().parentHash != validLedger->header().parentHash)
330 {
331 JLOG(j_.error()) << "MISMATCH on prior ledger";
332 return;
333 }
334
335 // Disagreement over close time indicates Byzantine failure
336 if (builtLedger->header().closeTime != validLedger->header().closeTime)
337 {
338 JLOG(j_.error()) << "MISMATCH on close time";
339 return;
340 }
341
342 if (builtConsensusHash && validatedConsensusHash)
343 {
344 if (builtConsensusHash != validatedConsensusHash)
345 {
346 JLOG(j_.error()) << "MISMATCH on consensus transaction set "
347 << " built: " << to_string(*builtConsensusHash)
348 << " validated: " << to_string(*validatedConsensusHash);
349 }
350 else
351 JLOG(j_.error()) << "MISMATCH with same consensus transaction set: "
352 << to_string(*builtConsensusHash);
353 }
354
355 // Find differences between built and valid ledgers
356 auto const builtTx = leaves(builtLedger->txMap());
357 auto const validTx = leaves(validLedger->txMap());
358
359 if (builtTx == validTx)
360 {
361 JLOG(j_.error()) << "MISMATCH with same " << builtTx.size() << " transactions";
362 }
363 else
364 JLOG(j_.error()) << "MISMATCH with " << builtTx.size() << " built and " << validTx.size()
365 << " valid transactions.";
366
367 JLOG(j_.error()) << "built\n" << getJson({*builtLedger, {}});
368 JLOG(j_.error()) << "valid\n" << getJson({*validLedger, {}});
369
370 // Log all differences between built and valid ledgers
371 auto b = builtTx.begin();
372 auto v = validTx.begin();
373 while (b != builtTx.end() && v != validTx.end())
374 {
375 if ((*b)->key() < (*v)->key())
376 {
377 log_one(*builtLedger, (*b)->key(), "valid", j_);
378 ++b;
379 }
380 else if ((*b)->key() > (*v)->key())
381 {
382 log_one(*validLedger, (*v)->key(), "built", j_);
383 ++v;
384 }
385 else
386 {
387 if ((*b)->slice() != (*v)->slice())
388 {
389 // Same transaction with different metadata
390 log_metadata_difference(*builtLedger, *validLedger, (*b)->key(), j_);
391 }
392 ++b;
393 ++v;
394 }
395 }
396 for (; b != builtTx.end(); ++b)
397 log_one(*builtLedger, (*b)->key(), "valid", j_);
398 for (; v != validTx.end(); ++v)
399 log_one(*validLedger, (*v)->key(), "built", j_);
400}
401
402void
404 std::shared_ptr<Ledger const> const& ledger,
405 uint256 const& consensusHash,
406 Json::Value consensus)
407{
408 LedgerIndex const index = ledger->header().seq;
409 LedgerHash const hash = ledger->header().hash;
410 XRPL_ASSERT(!hash.isZero(), "xrpl::LedgerHistory::builtLedger : nonzero hash");
411
413
414 auto entry = std::make_shared<cv_entry>();
416
417 if (entry->validated && !entry->built)
418 {
419 if (entry->validated.value() != hash)
420 {
421 JLOG(j_.error()) << "MISMATCH: seq=" << index
422 << " validated:" << entry->validated.value() << " then:" << hash;
424 hash,
425 entry->validated.value(),
426 consensusHash,
427 entry->validatedConsensusHash,
428 consensus);
429 }
430 else
431 {
432 // We validated a ledger and then built it locally
433 JLOG(j_.debug()) << "MATCH: seq=" << index << " late";
434 }
435 }
436
437 entry->built.emplace(hash);
438 entry->builtConsensusHash.emplace(consensusHash);
439 entry->consensus.emplace(std::move(consensus));
440}
441
442void
444 std::shared_ptr<Ledger const> const& ledger,
445 std::optional<uint256> const& consensusHash)
446{
447 LedgerIndex const index = ledger->header().seq;
448 LedgerHash const hash = ledger->header().hash;
449 XRPL_ASSERT(!hash.isZero(), "xrpl::LedgerHistory::validatedLedger : nonzero hash");
450
452
453 auto entry = std::make_shared<cv_entry>();
455
456 if (entry->built && !entry->validated)
457 {
458 if (entry->built.value() != hash)
459 {
460 JLOG(j_.error()) << "MISMATCH: seq=" << index << " built:" << entry->built.value()
461 << " then:" << hash;
463 entry->built.value(),
464 hash,
465 entry->builtConsensusHash,
466 consensusHash,
467 entry->consensus.value());
468 }
469 else
470 {
471 // We built a ledger locally and then validated it
472 JLOG(j_.debug()) << "MATCH: seq=" << index;
473 }
474 }
475
476 entry->validated.emplace(hash);
477 entry->validatedConsensusHash = consensusHash;
478}
479
482bool
483LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash)
484{
486 auto it = mLedgersByIndex.find(ledgerIndex);
487
488 if ((it != mLedgersByIndex.end()) && (it->second != ledgerHash))
489 {
490 it->second = ledgerHash;
491 return false;
492 }
493 return true;
494}
495
496void
498{
499 for (LedgerHash const it : m_ledgers_by_hash.getKeys())
500 {
501 auto const ledger = getLedgerByHash(it);
502 if (!ledger || ledger->header().seq < seq)
503 m_ledgers_by_hash.del(it, false);
504 }
505}
506
507} // namespace xrpl
T begin(T... args)
Represents a JSON value.
Definition json_value.h:130
const_iterator begin() const
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
virtual Config & config()=0
std::unordered_set< uint256, beast::uhash<> > features
Definition Config.h:261
FeeSetup FEES
Definition Config.h:189
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: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:18
A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree.
Definition SHAMap.h:77
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:513
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:5
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:94
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
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.
static void log_metadata_difference(ReadView const &builtLedger, ReadView const &validLedger, uint256 const &tx, beast::Journal j)
SizedItem
Definition Config.h:27
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, Rules const &rules, Fees const &fees, ServiceRegistry &registry, bool acquire)
Load a ledger by its hash.
T push_back(T... args)
T sort(T... args)
Fees toFees() const
Convert to a Fees object for use with Ledger construction.
Definition Config.h:64
Reflects the fee settings for a particular ledger.
T unlock(T... args)