xrpld
Loading...
Searching...
No Matches
LedgerMaster_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/amount.h>
4#include <test/jtx/envconfig.h>
5#include <test/jtx/noop.h>
6
7#include <xrpld/app/ledger/LedgerMaster.h>
8#include <xrpld/app/misc/SHAMapStore.h>
9#include <xrpld/core/Config.h>
10
11#include <xrpl/basics/ToString.h>
12#include <xrpl/basics/base_uint.h>
13#include <xrpl/beast/unit_test/suite.h>
14#include <xrpl/core/JobQueue.h>
15#include <xrpl/protocol/Feature.h>
16#include <xrpl/protocol/Protocol.h>
17#include <xrpl/protocol/SField.h>
18#include <xrpl/protocol/STObject.h>
19#include <xrpl/protocol/STTx.h>
20
21#include <chrono>
22#include <cstdint>
23#include <memory>
24#include <optional>
25#include <ostream>
26#include <sstream>
27#include <vector>
28
29namespace xrpl::test {
30
32{
34 makeNetworkConfig(uint32_t networkID)
35 {
36 using namespace jtx;
37 return envconfig([&](std::unique_ptr<Config> cfg) {
38 cfg->networkId = networkID;
39 // This test relies on ledger hash so must lock it to fee 10.
40 cfg->fees.referenceFee = 10;
41 return cfg;
42 });
43 }
44
45 void
47 {
48 testcase("tx_id_from_index");
49
50 using namespace test::jtx;
51 using namespace std::literals;
52
53 test::jtx::Env env{*this, makeNetworkConfig(11111)};
54
55 auto const alice = Account("alice");
56 env.fund(XRP(1000), alice);
57 env.close();
58
59 // build ledgers
62 auto const startLegSeq = env.current()->header().seq;
63 for (int i = 0; i < 2; ++i)
64 {
65 env(noop(alice));
66 txns.emplace_back(env.tx());
67 env.close();
68 metas.emplace_back(env.closed()->txRead(env.tx()->getTransactionID()).second);
69 }
70 // add last (empty) ledger
71 env.close();
72 auto const endLegSeq = env.closed()->header().seq;
73
74 // test invalid range
75 {
76 std::uint32_t const ledgerSeq = -1;
77 std::uint32_t const txnIndex = 0;
78 auto result = env.app().getLedgerMaster().txnIdFromIndex(ledgerSeq, txnIndex);
79 BEAST_EXPECT(!result);
80 }
81 // test not in ledger
82 {
83 uint32_t const txnIndex = metas[0]->getFieldU32(sfTransactionIndex);
84 auto result = env.app().getLedgerMaster().txnIdFromIndex(0, txnIndex);
85 BEAST_EXPECT(!result);
86 }
87 // test empty ledger
88 {
89 auto result = env.app().getLedgerMaster().txnIdFromIndex(endLegSeq, 0);
90 BEAST_EXPECT(!result);
91 }
92 // ended without result
93 {
94 uint32_t const txnIndex = metas[0]->getFieldU32(sfTransactionIndex);
95 auto result = env.app().getLedgerMaster().txnIdFromIndex(endLegSeq + 1, txnIndex);
96 BEAST_EXPECT(!result);
97 }
98 // success (first tx)
99 {
100 uint32_t const txnIndex = metas[0]->getFieldU32(sfTransactionIndex);
101 auto result = env.app().getLedgerMaster().txnIdFromIndex(startLegSeq, txnIndex);
102 BEAST_EXPECT(
103 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
104 *result ==
105 UInt256(
106 "277F4FD89C20B92457FEF05FF63F6405563AD0563C73D967A29727"
107 "72679ADC65"));
108 }
109 // success (second tx)
110 {
111 uint32_t const txnIndex = metas[1]->getFieldU32(sfTransactionIndex);
112 auto result = env.app().getLedgerMaster().txnIdFromIndex(startLegSeq + 1, txnIndex);
113 BEAST_EXPECT(
114 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
115 *result ==
116 UInt256(
117 "293DF7335EBBAF4420D52E70ABF470EB4C5792CAEA2F91F76193C2"
118 "819F538FDE"));
119 }
120 }
121
122 // Wait until the SHAMapStore has finished processing the ledger that the
123 // preceding env.close() produced.
124 //
125 // env.close() returns as soon as the ledger_accept RPC returns, but the
126 // validated ledger path -- LedgerMaster::setValidLedger() ->
127 // SHAMapStore::onLedgerClosed() -- runs on a job queue thread. Without
128 // draining the job queue first, the store may not have been handed the
129 // ledger at all, in which case rendezvous() observes working_ == false and
130 // returns immediately, before any work has been done.
131 [[nodiscard]] static bool
133 {
134 // Drain the job queue first, so that onLedgerClosed() has run and
135 // working_ is set. Then use the store's timeout overload, so a store
136 // that never finishes fails this test instead of blocking on it.
137 //
138 // Only the second wait is bounded: JobQueue::rendezvous() has no
139 // timeout overload, so a job that never completes hangs here. That is
140 // pre-existing -- ~AppBundle waits on it the same way for every jtx
141 // test -- but it does mean this helper is not hang-proof end to end.
142 env.app().getJobQueue().rendezvous();
144 }
145
146 // Bring the SHAMapStore to the point where it has been handed a validated
147 // ledger and initialized lastRotated, and report how many extra ledgers had
148 // to be closed to get it there (normally none). Returns std::nullopt if
149 // syncStore() itself failed.
150 //
151 // syncStore() alone does not guarantee that, because
152 // SHAMapStoreImp::run()'s loop does not use the notification and the
153 // working_ flag safely:
154 //
155 // * onLedgerClosed() notifies cond_ whether or not run()'s thread is
156 // parked on it, and run() waits on cond_ without a predicate, so a
157 // notification that lands while the thread is still starting up --
158 // before it first reaches that wait -- is lost.
159 // * run() clears working_ at the top of its loop without checking
160 // whether newLedger_ is still set, so rendezvous() can report the
161 // store idle with a validated ledger queued.
162 //
163 // Either way the store ends up parked with work pending, and only another
164 // notification gets it moving again. In a standalone test nothing else
165 // closes ledgers, so that has to come from here: this closes a ledger
166 // rather than polling getLastRotated(), because polling would just time
167 // out. onLedgerClosed() keeps only the most recent ledger in newLedger_,
168 // so the ledger the store picks up -- and therefore lastRotated -- is a
169 // timing detail, which is why the caller derives its expectations from the
170 // value it observes instead of assuming one.
171 //
172 // run() is deliberately left as it is. In production the only effect is
173 // latency: the trigger is validatedSeq >= lastRotated + deleteInterval, so
174 // a lost notification delays rotation to the next validated ledger and
175 // nothing is skipped or accumulated -- starting at 513 instead of 512 does
176 // not matter. Two consequences do follow from leaving it in place, and both
177 // hold today: nothing in production decides anything from working_ or
178 // rendezvous() (rendezvous() has no production callers at all), and a node
179 // whose ledgers only advance on demand -- standalone, driven by
180 // ledger_accept -- can sit on a queued ledger until something closes the
181 // next one, which is exactly the situation this helper is working around.
182 //
183 // So this helper is permanent rather than a stopgap. Working around the
184 // race must not make it invisible, so every extra close is logged. That
185 // keeps how often it is actually hit observable in the unit test output --
186 // which is the only signal left once this testcase stops flaking on it.
187 [[nodiscard]] std::optional<int>
188 initializeStore(jtx::Env& env, int const maxExtraCloses = 3)
189 {
190 auto& store = env.app().getSHAMapStore();
191
192 for (int extraCloses = 0;; ++extraCloses)
193 {
194 if (!syncStore(env))
195 return std::nullopt;
196 if (store.getLastRotated() != 0 || extraCloses == maxExtraCloses)
197 {
198 if (extraCloses != 0)
199 {
200 log << "initializeStore: the store needed " << extraCloses
201 << " extra ledger close(s) to pick up a validated ledger. "
202 "SHAMapStoreImp::run() dropped the notification for the "
203 "first one; see the comment on initializeStore()."
204 << std::endl;
205 }
206 return extraCloses;
207 }
208 env.close();
209 }
210 }
211
212 void
214 {
215 // Note that this test is intentionally very similar to
216 // SHAMapStore_test::testLedgerGaps, but has a different
217 // focus.
218
219 testcase("Complete Ledger operations");
220
221 using namespace test::jtx;
222
223 auto const deleteInterval = 8;
224
225 Env env{*this, envconfig(onlineDelete, deleteInterval)};
226
227 auto const alice = Account("alice");
228 env.fund(XRP(1000), alice);
229 env.close();
230
231 auto& lm = env.app().getLedgerMaster();
232 LedgerIndex minSeq = 2;
233 auto& store = env.app().getSHAMapStore();
234 // Which of the existing complete ledgers the store initializes
235 // lastRotated from is a timing detail; all this test needs is that it is
236 // one of them. Everything below derives from the observed value rather
237 // than assuming a particular one.
238 //
239 // The range check and the initializeStore() one both end the testcase
240 // rather than merely reporting, because lastRotated is the only value
241 // from the store that enters minSeq. A lastRotated of 0 -- the value
242 // getLastRotated() reports until the store has been handed a
243 // validated ledger -- makes minSeq 0 below, and the minSeq - 1 and
244 // minSeq - 2 ranges then underflow to first > last, which aborts a
245 // Debug build inside missingFromCompleteLedgerRange().
246 auto const extraCloses = initializeStore(env);
247 if (!BEAST_EXPECT(extraCloses.has_value()))
248 return;
249 LedgerIndex maxSeq = env.closed()->header().seq;
250 LedgerIndex lastRotated = store.getLastRotated();
251 if (!BEAST_EXPECTS(lastRotated >= minSeq && lastRotated <= maxSeq, to_string(lastRotated)))
252 return;
253 // The BEAST_EXPECT above already returned if this is nullopt, but that
254 // is invisible to clang-tidy's optional model.
255 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
256 BEAST_EXPECTS(maxSeq == 3 + *extraCloses, to_string(maxSeq));
257 std::stringstream initialRange;
258 initialRange << minSeq << "-" << maxSeq;
259 BEAST_EXPECTS(lm.getCompleteLedgers() == initialRange.str(), lm.getCompleteLedgers());
260 BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
261 // The inner range is empty unless initializeStore() had to close extra
262 // ledgers, and missingFromCompleteLedgerRange() treats first > last as a
263 // precondition violation that aborts a Debug build via UNREACHABLE, so
264 // only check it when it is well formed.
265 if (minSeq + 1 <= maxSeq - 1)
266 {
267 BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
268 }
269 BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
270 BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
271 BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
272
273 // Close enough ledgers to rotate a few times
274 for (int i = 0; i < 24; ++i)
275 {
276 for (int t = 0; t < 3; ++t)
277 {
278 env(noop(alice));
279 }
280 env.close();
281 BEAST_EXPECT(syncStore(env));
282
283 ++maxSeq;
284
285 if (maxSeq == lastRotated + deleteInterval)
286 {
287 minSeq = lastRotated;
288 lastRotated = maxSeq;
289 }
290 BEAST_EXPECTS(
291 env.closed()->header().seq == maxSeq, to_string(env.closed()->header().seq));
292 BEAST_EXPECTS(store.getLastRotated() == lastRotated, to_string(store.getLastRotated()));
293 std::stringstream expectedRange;
294 expectedRange << minSeq << "-" << maxSeq;
295 BEAST_EXPECTS(lm.getCompleteLedgers() == expectedRange.str(), lm.getCompleteLedgers());
296 BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq, maxSeq) == 0);
297 // missingFromCompleteLedgerRange() treats first > last as a
298 // precondition violation and aborts a Debug build via UNREACHABLE.
299 // The range can only collapse if this test's model of minSeq /
300 // maxSeq has desynced from the store, so report that as a failure
301 // instead of taking down the whole unit test job.
302 if (minSeq + 1 <= maxSeq - 1)
303 {
304 BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 1, maxSeq - 1) == 0);
305 }
306 else
307 {
308 BEAST_EXPECTS(false, to_string(minSeq) + "-" + to_string(maxSeq));
309 }
310 BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 1, maxSeq + 1) == 2);
311 BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq - 2, maxSeq - 2) == 2);
312 BEAST_EXPECT(lm.missingFromCompleteLedgerRange(minSeq + 2, maxSeq + 2) == 2);
313 }
314 }
315
316public:
317 void
318 run() override
319 {
320 using namespace test::jtx;
322 testWithFeats(all);
323 }
324
325 void
327 {
328 testTxnIdFromIndex(features);
329 testCompleteLedgerRange(features);
330 }
331};
332
334
335} // namespace xrpl::test
A testsuite class.
Definition suite.h:52
LogOs< char > log
Logging output stream.
Definition suite.h:150
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
void rendezvous()
Block until no jobs running.
Definition JobQueue.cpp:243
Tracks the current ledger and any ledgers in the process of closing.
std::optional< UInt256 > txnIdFromIndex(uint32_t ledgerSeq, uint32_t txnIndex)
Iff a txn exists at the specified ledger and offset then return its txnid.
virtual bool rendezvous(std::optional< std::chrono::milliseconds > const &timeout={}) const =0
virtual JobQueue & getJobQueue()=0
virtual SHAMapStore & getSHAMapStore()=0
virtual LedgerMaster & getLedgerMaster()=0
void testWithFeats(FeatureBitset features)
std::optional< int > initializeStore(jtx::Env &env, int const maxExtraCloses=3)
void testTxnIdFromIndex(FeatureBitset features)
void run() override
Runs the suite.
static bool syncStore(jtx::Env &env)
static std::unique_ptr< Config > makeNetworkConfig(uint32_t networkID)
void testCompleteLedgerRange(FeatureBitset features)
A transaction testing environment.
Definition Env.h:161
Application & app()
Definition Env.h:300
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:127
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:323
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition Env.cpp:560
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
T emplace_back(T... args)
T endl(T... args)
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
json::Value noop(Account const &account)
The null transaction.
Definition noop.h:14
FeatureBitset testableAmendments()
Definition Env.h:92
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:38
std::unique_ptr< Config > onlineDelete(std::unique_ptr< Config > cfg, std::uint32_t deleteInterval=8)
adjust config to enable online_delete
Definition envconfig.cpp:66
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:382
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
BaseUInt< 256 > UInt256
Definition base_uint.h:580
T str(T... args)