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 : app_(app)
15 , collector_(collector)
16 , mismatch_counter_(collector->make_counter("ledger.history", "mismatch"))
17 , m_ledgers_by_hash(
18 "LedgerCache",
19 app_.config().getValueFor(SizedItem::ledgerSize),
20 std::chrono::seconds{app_.config().getValueFor(SizedItem::ledgerAge)},
21 stopwatch(),
22 app_.journal("TaggedCache"))
23 , m_consensus_validated("ConsensusValidated", 64, std::chrono::minutes{5}, stopwatch(), app_.journal("TaggedCache"))
24 , j_(app.journal("LedgerHistory"))
25{
26}
27
28bool
30{
31 if (!ledger->isImmutable())
32 LogicError("mutable Ledger in insert");
33
34 XRPL_ASSERT(ledger->stateMap().getHash().isNonZero(), "xrpl::LedgerHistory::insert : nonzero hash");
35
37
38 bool const alreadyHad = m_ledgers_by_hash.canonicalize_replace_cache(ledger->header().hash, ledger);
39 if (validated)
40 mLedgersByIndex[ledger->header().seq] = ledger->header().hash;
41
42 return alreadyHad;
43}
44
47{
49 if (auto it = mLedgersByIndex.find(index); it != mLedgersByIndex.end())
50 return it->second;
51 return {};
52}
53
56{
57 {
59 auto it = mLedgersByIndex.find(index);
60
61 if (it != mLedgersByIndex.end())
62 {
63 uint256 hash = it->second;
64 sl.unlock();
65 return getLedgerByHash(hash);
66 }
67 }
68
70
71 if (!ret)
72 return ret;
73
74 XRPL_ASSERT(ret->header().seq == index, "xrpl::LedgerHistory::getLedgerBySeq : result sequence match");
75
76 {
77 // Add this ledger to the local tracking by index
79
80 XRPL_ASSERT(ret->isImmutable(), "xrpl::LedgerHistory::getLedgerBySeq : immutable result ledger");
81 m_ledgers_by_hash.canonicalize_replace_client(ret->header().hash, ret);
82 mLedgersByIndex[ret->header().seq] = ret->header().hash;
83 return (ret->header().seq == index) ? ret : nullptr;
84 }
85}
86
89{
90 auto ret = m_ledgers_by_hash.fetch(hash);
91
92 if (ret)
93 {
94 XRPL_ASSERT(
95 ret->isImmutable(),
96 "xrpl::LedgerHistory::getLedgerByHash : immutable fetched "
97 "ledger");
98 XRPL_ASSERT(
99 ret->header().hash == hash,
100 "xrpl::LedgerHistory::getLedgerByHash : fetched ledger hash "
101 "match");
102 return ret;
103 }
104
105 ret = loadByHash(hash, app_);
106
107 if (!ret)
108 return ret;
109
110 XRPL_ASSERT(ret->isImmutable(), "xrpl::LedgerHistory::getLedgerByHash : immutable loaded ledger");
111 XRPL_ASSERT(ret->header().hash == hash, "xrpl::LedgerHistory::getLedgerByHash : loaded ledger hash match");
112 m_ledgers_by_hash.canonicalize_replace_client(ret->header().hash, ret);
113 XRPL_ASSERT(ret->header().hash == hash, "xrpl::LedgerHistory::getLedgerByHash : result hash match");
114
115 return ret;
116}
117
118static void
119log_one(ReadView const& ledger, uint256 const& tx, char const* msg, beast::Journal& j)
120{
121 auto metaData = ledger.txRead(tx).second;
122
123 if (metaData != nullptr)
124 {
125 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg << " is missing this transaction:\n"
126 << metaData->getJson(JsonOptions::none);
127 }
128 else
129 {
130 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": " << msg << " is missing this transaction.";
131 }
132}
133
134static void
135log_metadata_difference(ReadView const& builtLedger, ReadView const& validLedger, uint256 const& tx, beast::Journal j)
136{
137 auto getMeta = [](ReadView const& ledger, uint256 const& txID) {
139 if (auto meta = ledger.txRead(txID).second)
140 ret.emplace(txID, ledger.seq(), *meta);
141 return ret;
142 };
143
144 auto validMetaData = getMeta(validLedger, tx);
145 auto builtMetaData = getMeta(builtLedger, tx);
146
147 XRPL_ASSERT(validMetaData || builtMetaData, "xrpl::log_metadata_difference : some metadata present");
148
149 if (validMetaData && builtMetaData)
150 {
151 auto const& validNodes = validMetaData->getNodes();
152 auto const& builtNodes = builtMetaData->getNodes();
153
154 bool const result_diff = validMetaData->getResultTER() != builtMetaData->getResultTER();
155
156 bool const index_diff = validMetaData->getIndex() != builtMetaData->getIndex();
157
158 bool const nodes_diff = validNodes != builtNodes;
159
160 if (!result_diff && !index_diff && !nodes_diff)
161 {
162 JLOG(j.error()) << "MISMATCH on TX " << tx << ": No apparent mismatches detected!";
163 return;
164 }
165
166 if (!nodes_diff)
167 {
168 if (result_diff && index_diff)
169 {
170 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result and index!";
171 JLOG(j.debug()) << " Built:"
172 << " Result: " << builtMetaData->getResult() << " Index: " << builtMetaData->getIndex();
173 JLOG(j.debug()) << " Valid:"
174 << " Result: " << validMetaData->getResult() << " Index: " << validMetaData->getIndex();
175 }
176 else if (result_diff)
177 {
178 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result!";
179 JLOG(j.debug()) << " Built:"
180 << " Result: " << builtMetaData->getResult();
181 JLOG(j.debug()) << " Valid:"
182 << " Result: " << validMetaData->getResult();
183 }
184 else if (index_diff)
185 {
186 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different index!";
187 JLOG(j.debug()) << " Built:"
188 << " Index: " << builtMetaData->getIndex();
189 JLOG(j.debug()) << " Valid:"
190 << " Index: " << validMetaData->getIndex();
191 }
192 }
193 else
194 {
195 if (result_diff && index_diff)
196 {
197 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result, index and nodes!";
198 JLOG(j.debug()) << " Built:\n" << builtMetaData->getJson(JsonOptions::none);
199 JLOG(j.debug()) << " Valid:\n" << validMetaData->getJson(JsonOptions::none);
200 }
201 else if (result_diff)
202 {
203 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different result and nodes!";
204 JLOG(j.debug()) << " Built:"
205 << " Result: " << builtMetaData->getResult() << " Nodes:\n"
206 << builtNodes.getJson(JsonOptions::none);
207 JLOG(j.debug()) << " Valid:"
208 << " Result: " << validMetaData->getResult() << " Nodes:\n"
209 << validNodes.getJson(JsonOptions::none);
210 }
211 else if (index_diff)
212 {
213 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different index and nodes!";
214 JLOG(j.debug()) << " Built:"
215 << " Index: " << builtMetaData->getIndex() << " Nodes:\n"
216 << builtNodes.getJson(JsonOptions::none);
217 JLOG(j.debug()) << " Valid:"
218 << " Index: " << validMetaData->getIndex() << " Nodes:\n"
219 << validNodes.getJson(JsonOptions::none);
220 }
221 else // nodes_diff
222 {
223 JLOG(j.debug()) << "MISMATCH on TX " << tx << ": Different nodes!";
224 JLOG(j.debug()) << " Built:"
225 << " Nodes:\n"
226 << builtNodes.getJson(JsonOptions::none);
227 JLOG(j.debug()) << " Valid:"
228 << " Nodes:\n"
229 << validNodes.getJson(JsonOptions::none);
230 }
231 }
232
233 return;
234 }
235
236 if (validMetaData)
237 {
238 JLOG(j.error()) << "MISMATCH on TX " << tx << ": Metadata Difference. Valid=\n"
239 << validMetaData->getJson(JsonOptions::none);
240 }
241
242 if (builtMetaData)
243 {
244 JLOG(j.error()) << "MISMATCH on TX " << tx << ": Metadata Difference. Built=\n"
245 << builtMetaData->getJson(JsonOptions::none);
246 }
247}
248
249//------------------------------------------------------------------------------
250
251// Return list of leaves sorted by key
253leaves(SHAMap const& sm)
254{
256 for (auto const& item : sm)
257 v.push_back(&item);
258 std::sort(v.begin(), v.end(), [](SHAMapItem const* lhs, SHAMapItem const* rhs) { return lhs->key() < rhs->key(); });
259 return v;
260}
261
262void
264 LedgerHash const& built,
265 LedgerHash const& valid,
266 std::optional<uint256> const& builtConsensusHash,
267 std::optional<uint256> const& validatedConsensusHash,
268 Json::Value const& consensus)
269{
270 XRPL_ASSERT(built != valid, "xrpl::LedgerHistory::handleMismatch : unequal hashes");
272
273 auto builtLedger = getLedgerByHash(built);
274 auto validLedger = getLedgerByHash(valid);
275
276 if (!builtLedger || !validLedger)
277 {
278 JLOG(j_.error()) << "MISMATCH cannot be analyzed:"
279 << " builtLedger: " << to_string(built) << " -> " << builtLedger
280 << " validLedger: " << to_string(valid) << " -> " << validLedger;
281 return;
282 }
283
284 XRPL_ASSERT(
285 builtLedger->header().seq == validLedger->header().seq, "xrpl::LedgerHistory::handleMismatch : sequence match");
286
287 if (auto stream = j_.debug())
288 {
289 stream << "Built: " << getJson({*builtLedger, {}});
290 stream << "Valid: " << getJson({*validLedger, {}});
291 stream << "Consensus: " << consensus;
292 }
293
294 // Determine the mismatch reason, distinguishing Byzantine
295 // failure from transaction processing difference
296
297 // Disagreement over prior ledger indicates sync issue
298 if (builtLedger->header().parentHash != validLedger->header().parentHash)
299 {
300 JLOG(j_.error()) << "MISMATCH on prior ledger";
301 return;
302 }
303
304 // Disagreement over close time indicates Byzantine failure
305 if (builtLedger->header().closeTime != validLedger->header().closeTime)
306 {
307 JLOG(j_.error()) << "MISMATCH on close time";
308 return;
309 }
310
311 if (builtConsensusHash && validatedConsensusHash)
312 {
313 if (builtConsensusHash != validatedConsensusHash)
314 JLOG(j_.error()) << "MISMATCH on consensus transaction set "
315 << " built: " << to_string(*builtConsensusHash)
316 << " validated: " << to_string(*validatedConsensusHash);
317 else
318 JLOG(j_.error()) << "MISMATCH with same consensus transaction set: " << to_string(*builtConsensusHash);
319 }
320
321 // Find differences between built and valid ledgers
322 auto const builtTx = leaves(builtLedger->txMap());
323 auto const validTx = leaves(validLedger->txMap());
324
325 if (builtTx == validTx)
326 JLOG(j_.error()) << "MISMATCH with same " << builtTx.size() << " transactions";
327 else
328 JLOG(j_.error()) << "MISMATCH with " << builtTx.size() << " built and " << validTx.size()
329 << " valid transactions.";
330
331 JLOG(j_.error()) << "built\n" << getJson({*builtLedger, {}});
332 JLOG(j_.error()) << "valid\n" << getJson({*validLedger, {}});
333
334 // Log all differences between built and valid ledgers
335 auto b = builtTx.begin();
336 auto v = validTx.begin();
337 while (b != builtTx.end() && v != validTx.end())
338 {
339 if ((*b)->key() < (*v)->key())
340 {
341 log_one(*builtLedger, (*b)->key(), "valid", j_);
342 ++b;
343 }
344 else if ((*b)->key() > (*v)->key())
345 {
346 log_one(*validLedger, (*v)->key(), "built", j_);
347 ++v;
348 }
349 else
350 {
351 if ((*b)->slice() != (*v)->slice())
352 {
353 // Same transaction with different metadata
354 log_metadata_difference(*builtLedger, *validLedger, (*b)->key(), j_);
355 }
356 ++b;
357 ++v;
358 }
359 }
360 for (; b != builtTx.end(); ++b)
361 log_one(*builtLedger, (*b)->key(), "valid", j_);
362 for (; v != validTx.end(); ++v)
363 log_one(*validLedger, (*v)->key(), "built", j_);
364}
365
366void
368 std::shared_ptr<Ledger const> const& ledger,
369 uint256 const& consensusHash,
370 Json::Value consensus)
371{
372 LedgerIndex index = ledger->header().seq;
373 LedgerHash hash = ledger->header().hash;
374 XRPL_ASSERT(!hash.isZero(), "xrpl::LedgerHistory::builtLedger : nonzero hash");
375
377
378 auto entry = std::make_shared<cv_entry>();
380
381 if (entry->validated && !entry->built)
382 {
383 if (entry->validated.value() != hash)
384 {
385 JLOG(j_.error()) << "MISMATCH: seq=" << index << " validated:" << entry->validated.value()
386 << " then:" << hash;
387 handleMismatch(hash, entry->validated.value(), consensusHash, entry->validatedConsensusHash, consensus);
388 }
389 else
390 {
391 // We validated a ledger and then built it locally
392 JLOG(j_.debug()) << "MATCH: seq=" << index << " late";
393 }
394 }
395
396 entry->built.emplace(hash);
397 entry->builtConsensusHash.emplace(consensusHash);
398 entry->consensus.emplace(std::move(consensus));
399}
400
401void
403{
404 LedgerIndex index = ledger->header().seq;
405 LedgerHash hash = ledger->header().hash;
406 XRPL_ASSERT(!hash.isZero(), "xrpl::LedgerHistory::validatedLedger : nonzero hash");
407
409
410 auto entry = std::make_shared<cv_entry>();
412
413 if (entry->built && !entry->validated)
414 {
415 if (entry->built.value() != hash)
416 {
417 JLOG(j_.error()) << "MISMATCH: seq=" << index << " built:" << entry->built.value() << " then:" << hash;
419 entry->built.value(), hash, entry->builtConsensusHash, consensusHash, entry->consensus.value());
420 }
421 else
422 {
423 // We built a ledger locally and then validated it
424 JLOG(j_.debug()) << "MATCH: seq=" << index;
425 }
426 }
427
428 entry->validated.emplace(hash);
429 entry->validatedConsensusHash = consensusHash;
430}
431
434bool
435LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash)
436{
438 auto it = mLedgersByIndex.find(ledgerIndex);
439
440 if ((it != mLedgersByIndex.end()) && (it->second != ledgerHash))
441 {
442 it->second = ledgerHash;
443 return false;
444 }
445 return true;
446}
447
448void
450{
452 {
453 auto const ledger = getLedgerByHash(it);
454 if (!ledger || ledger->header().seq < seq)
455 m_ledgers_by_hash.del(it, false);
456 }
457}
458
459} // 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:319
Stream debug() const
Definition Journal.h:301
Application & app_
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:98
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:509
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:1038
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:598
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:1026
static std::vector< SHAMapItem const * > leaves(SHAMap const &sm)
T push_back(T... args)
T sort(T... args)
T unlock(T... args)