diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 55300a390c9..6ea12873b1c 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -225,6 +225,7 @@ class ApplicationImp : public Application, public BasicApp std::unique_ptr mRelationalDatabase; std::unique_ptr mWalletDB; std::unique_ptr overlay_; + std::optional trapTxID_; boost::asio::signal_set m_signals; @@ -1254,6 +1255,12 @@ class ApplicationImp : public Application, public BasicApp return maxDisallowedLedger_; } + virtual std::optional + trapTxID() const override + { + return trapTxID_; + } + private: // For a newly-started validator, this is the greatest persisted ledger // and new validations must be greater than this. @@ -1272,7 +1279,11 @@ class ApplicationImp : public Application, public BasicApp loadLedgerFromFile(std::string const& ledgerID); bool - loadOldLedger(std::string const& ledgerID, bool replay, bool isFilename); + loadOldLedger( + std::string const& ledgerID, + bool replay, + bool isFilename, + std::optional trapTxID); void setMaxDisallowedLedger(); @@ -1396,15 +1407,20 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) startGenesisLedger(); } else if ( - startUp == Config::LOAD || startUp == Config::LOAD_FILE || - startUp == Config::REPLAY) + startUp == Config::LOAD || // + startUp == Config::LOAD_FILE || startUp == Config::REPLAY || + startUp == Config::REPLAY_TRAP) { JLOG(m_journal.info()) << "Loading specified Ledger"; if (!loadOldLedger( config_->START_LEDGER, - startUp == Config::REPLAY, - startUp == Config::LOAD_FILE)) + (startUp == Config::REPLAY || + startUp == Config::REPLAY_TRAP), + startUp == Config::LOAD_FILE, + (startUp == Config::REPLAY_TRAP + ? std::optional(config_->TRAP_TX_HASH) + : std::nullopt))) { JLOG(m_journal.error()) << "The specified ledger could not be loaded."; @@ -2086,7 +2102,8 @@ bool ApplicationImp::loadOldLedger( std::string const& ledgerID, bool replay, - bool isFileName) + bool isFileName, + std::optional trapTxID) { try { @@ -2233,6 +2250,11 @@ ApplicationImp::loadOldLedger( { (void)_; auto txID = tx->getTransactionID(); + if (trapTxID && *trapTxID == txID) + { + trapTxID_ = txID; + JLOG(m_journal.debug()) << "Trap transaction set: " << txID; + } auto s = std::make_shared(); tx->add(*s); @@ -2247,6 +2269,14 @@ ApplicationImp::loadOldLedger( } m_ledgerMaster->takeReplay(std::move(replayData)); + + if (trapTxID && !trapTxID_) + { + JLOG(m_journal.fatal()) + << "Ledger " << replayLedger->info().seq + << " does not contain the transaction hash " << *trapTxID; + return false; + } } } catch (SHAMapMissingNode const& mn) diff --git a/src/ripple/app/main/Application.h b/src/ripple/app/main/Application.h index 3fa8d13e870..b24ee11298e 100644 --- a/src/ripple/app/main/Application.h +++ b/src/ripple/app/main/Application.h @@ -284,6 +284,9 @@ class Application : public beast::PropertyStream::Source * than the last ledger it persisted. */ virtual LedgerIndex getMaxDisallowedLedger() = 0; + + virtual std::optional + trapTxID() const = 0; }; std::unique_ptr diff --git a/src/ripple/app/main/Main.cpp b/src/ripple/app/main/Main.cpp index 710e4e9674f..6e291bfaeba 100644 --- a/src/ripple/app/main/Main.cpp +++ b/src/ripple/app/main/Main.cpp @@ -400,6 +400,9 @@ run(int argc, char** argv) "net", "Get the initial ledger from the network.")( "nodetoshard", "Import node store into shards")( "replay", "Replay a ledger close.")( + "trap_tx_hash", + po::value(), + "Trap a specific transaction during replay.")( "start", "Start from a fresh Ledger.")( "startReporting", po::value(), @@ -679,7 +682,27 @@ run(int argc, char** argv) { config->START_LEDGER = vm["ledger"].as(); if (vm.count("replay")) - config->START_UP = Config::REPLAY; + { + if (vm.count("trap_tx_hash")) + { + uint256 tmp = {}; + auto hash = vm["trap_tx_hash"].as(); + if (tmp.parseHex(hash)) + { + config->START_UP = Config::REPLAY_TRAP; + config->TRAP_TX_HASH = tmp; + } + else + { + std::cerr << "Trap parameter was ill-formed, expected " + "valid transaction hash but received: " + << hash << std::endl; + return -1; + } + } + else + config->START_UP = Config::REPLAY; + } else config->START_UP = Config::LOAD; } @@ -693,10 +716,18 @@ run(int argc, char** argv) config->START_UP = Config::LOAD; } + if (vm.count("trap_tx_hash") && vm.count("replay") == 0) + { + std::cerr << "Cannot use trap option without replay option" + << std::endl; + return -1; + } + if (vm.count("net") && !config->FAST_LOAD) { if ((config->START_UP == Config::LOAD) || - (config->START_UP == Config::REPLAY)) + (config->START_UP == Config::REPLAY) || + (config->START_UP == Config::REPLAY_TRAP)) { std::cerr << "Net and load/replay options are incompatible" << std::endl; diff --git a/src/ripple/app/rdb/backend/detail/impl/Node.cpp b/src/ripple/app/rdb/backend/detail/impl/Node.cpp index 0905d6121ae..cebdc88376a 100644 --- a/src/ripple/app/rdb/backend/detail/impl/Node.cpp +++ b/src/ripple/app/rdb/backend/detail/impl/Node.cpp @@ -85,9 +85,11 @@ makeLedgerDBs( boost::format("PRAGMA cache_size=-%d;") % kilobytes(config.getValueFor(SizedItem::txnDBCache))); - if (!setup.standAlone || setup.startUp == Config::LOAD || + if (!setup.standAlone || // + setup.startUp == Config::LOAD || setup.startUp == Config::LOAD_FILE || - setup.startUp == Config::REPLAY) + setup.startUp == Config::REPLAY || + setup.startUp == Config::REPLAY_TRAP) { // Check if AccountTransactions has primary key std::string cid, name, type; diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index 7dcf3f15ab7..846c571c058 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -854,6 +854,12 @@ Transactor::operator()() } #endif + if (auto trap = ctx_.app.trapTxID(); + trap && ctx_.tx.getTransactionID() == *trap) + { + JLOG(j_.debug()) << "Transaction trapped: " << *trap; + } + auto result = ctx_.preclaimResult; if (result == tesSUCCESS) result = apply(); diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h index cf41678a16c..5d58e8f3b8b 100644 --- a/src/ripple/core/Config.h +++ b/src/ripple/core/Config.h @@ -155,13 +155,23 @@ class Config : public BasicConfig // Entries from [ips_fixed] config stanza std::vector IPS_FIXED; - enum StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK }; + enum StartUpType { + FRESH, + NORMAL, + LOAD, + LOAD_FILE, + REPLAY, + NETWORK, + REPLAY_TRAP + }; StartUpType START_UP = NORMAL; bool START_VALID = false; std::string START_LEDGER; + uint256 TRAP_TX_HASH = {}; + // Network parameters uint32_t NETWORK_ID = 0; diff --git a/src/ripple/core/DatabaseCon.h b/src/ripple/core/DatabaseCon.h index 59eec3a32e9..8cc69e2f546 100644 --- a/src/ripple/core/DatabaseCon.h +++ b/src/ripple/core/DatabaseCon.h @@ -118,10 +118,12 @@ class DatabaseCon std::array const& initSQL) // Use temporary files or regular DB files? : DatabaseCon( - setup.standAlone && !setup.reporting && + setup.standAlone && // + !setup.reporting && // setup.startUp != Config::LOAD && setup.startUp != Config::LOAD_FILE && - setup.startUp != Config::REPLAY + setup.startUp != Config::REPLAY && + setup.startUp != Config::REPLAY_TRAP ? "" : (setup.dataDir / dbName), setup.commonPragma(),