#include #include #include #include #include #include #include #include #include #include namespace xrpl { namespace test { class LedgerHistory_test : public beast::unit_test::suite { public: /** Generate a new ledger by hand, applying a specific close time offset and optionally inserting a transaction. If prev is nullptr, then the genesis ledger is made and no offset or transaction is applied. */ static std::shared_ptr makeLedger( std::shared_ptr const& prev, jtx::Env& env, LedgerHistory& lh, NetClock::duration closeOffset, std::shared_ptr stx = {}) { if (!prev) { assert(!stx); return std::make_shared( create_genesis, env.app().config(), std::vector{}, env.app().getNodeFamily()); } auto res = std::make_shared( *prev, prev->header().closeTime + closeOffset); if (stx) { OpenView accum(&*res); applyTransaction( env.app(), accum, *stx, false, tapNONE, env.journal); accum.apply(*res); } res->updateSkipList(); { res->stateMap().flushDirty(hotACCOUNT_NODE); res->txMap().flushDirty(hotTRANSACTION_NODE); } res->unshare(); // Accept ledger res->setAccepted( res->header().closeTime, res->header().closeTimeResolution, true /* close time correct*/); lh.insert(res, false); return res; } void testHandleMismatch() { testcase("LedgerHistory mismatch"); using namespace jtx; using namespace std::chrono; // No mismatch { bool found = false; Env env{ *this, envconfig(), std::make_unique("MISMATCH ", &found)}; LedgerHistory lh{beast::insight::NullCollector::New(), env.app()}; auto const genesis = makeLedger({}, env, lh, 0s); uint256 const dummyTxHash{1}; lh.builtLedger(genesis, dummyTxHash, {}); lh.validatedLedger(genesis, dummyTxHash); BEAST_EXPECT(!found); } // Close time mismatch { bool found = false; Env env{ *this, envconfig(), std::make_unique( "MISMATCH on close time", &found)}; LedgerHistory lh{beast::insight::NullCollector::New(), env.app()}; auto const genesis = makeLedger({}, env, lh, 0s); auto const ledgerA = makeLedger(genesis, env, lh, 4s); auto const ledgerB = makeLedger(genesis, env, lh, 40s); uint256 const dummyTxHash{1}; lh.builtLedger(ledgerA, dummyTxHash, {}); lh.validatedLedger(ledgerB, dummyTxHash); BEAST_EXPECT(found); } // Prior ledger mismatch { bool found = false; Env env{ *this, envconfig(), std::make_unique( "MISMATCH on prior ledger", &found)}; LedgerHistory lh{beast::insight::NullCollector::New(), env.app()}; auto const genesis = makeLedger({}, env, lh, 0s); auto const ledgerA = makeLedger(genesis, env, lh, 4s); auto const ledgerB = makeLedger(genesis, env, lh, 40s); auto const ledgerAC = makeLedger(ledgerA, env, lh, 4s); auto const ledgerBD = makeLedger(ledgerB, env, lh, 4s); uint256 const dummyTxHash{1}; lh.builtLedger(ledgerAC, dummyTxHash, {}); lh.validatedLedger(ledgerBD, dummyTxHash); BEAST_EXPECT(found); } // Simulate a bug in which consensus may agree on transactions, but // somehow generate different ledgers for (bool const txBug : {true, false}) { std::string const msg = txBug ? "MISMATCH with same consensus transaction set" : "MISMATCH on consensus transaction set"; bool found = false; Env env{ *this, envconfig(), std::make_unique(msg, &found)}; LedgerHistory lh{beast::insight::NullCollector::New(), env.app()}; Account alice{"A1"}; Account bob{"A2"}; env.fund(XRP(1000), alice, bob); env.close(); auto const ledgerBase = env.app().getLedgerMaster().getClosedLedger(); JTx txAlice = env.jt(noop(alice)); auto const ledgerA = makeLedger(ledgerBase, env, lh, 4s, txAlice.stx); JTx txBob = env.jt(noop(bob)); auto const ledgerB = makeLedger(ledgerBase, env, lh, 4s, txBob.stx); lh.builtLedger(ledgerA, txAlice.stx->getTransactionID(), {}); // Simulate the bug by claiming ledgerB had the same consensus hash // as ledgerA, but somehow generated different ledgers lh.validatedLedger( ledgerB, txBug ? txAlice.stx->getTransactionID() : txBob.stx->getTransactionID()); BEAST_EXPECT(found); } } void run() override { testHandleMismatch(); } }; BEAST_DEFINE_TESTSUITE(LedgerHistory, app, xrpl); } // namespace test } // namespace xrpl