mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Add new command line option to make replaying transactions easier: (#5027)
* Add trap_tx_hash command line option This new option can be used only if replay is also enabled. It takes a transaction hash from the ledger loaded for replay, and will cause a specific line to be hit in Transactor.cpp, right before the selected transaction is applied.
This commit is contained in:
@@ -225,6 +225,7 @@ public:
|
||||
std::unique_ptr<RelationalDatabase> mRelationalDatabase;
|
||||
std::unique_ptr<DatabaseCon> mWalletDB;
|
||||
std::unique_ptr<Overlay> overlay_;
|
||||
std::optional<uint256> trapTxID_;
|
||||
|
||||
boost::asio::signal_set m_signals;
|
||||
|
||||
@@ -1254,6 +1255,12 @@ public:
|
||||
return maxDisallowedLedger_;
|
||||
}
|
||||
|
||||
virtual const std::optional<uint256>&
|
||||
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 @@ private:
|
||||
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<uint256> trapTxID);
|
||||
|
||||
void
|
||||
setMaxDisallowedLedger();
|
||||
@@ -1404,7 +1415,8 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
|
||||
if (!loadOldLedger(
|
||||
config_->START_LEDGER,
|
||||
startUp == Config::REPLAY,
|
||||
startUp == Config::LOAD_FILE))
|
||||
startUp == Config::LOAD_FILE,
|
||||
config_->TRAP_TX_HASH))
|
||||
{
|
||||
JLOG(m_journal.error())
|
||||
<< "The specified ledger could not be loaded.";
|
||||
@@ -2086,7 +2098,8 @@ bool
|
||||
ApplicationImp::loadOldLedger(
|
||||
std::string const& ledgerID,
|
||||
bool replay,
|
||||
bool isFileName)
|
||||
bool isFileName,
|
||||
std::optional<uint256> trapTxID)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2233,6 +2246,11 @@ ApplicationImp::loadOldLedger(
|
||||
{
|
||||
(void)_;
|
||||
auto txID = tx->getTransactionID();
|
||||
if (trapTxID == txID)
|
||||
{
|
||||
trapTxID_ = txID;
|
||||
JLOG(m_journal.debug()) << "Trap transaction set: " << txID;
|
||||
}
|
||||
|
||||
auto s = std::make_shared<Serializer>();
|
||||
tx->add(*s);
|
||||
@@ -2247,6 +2265,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)
|
||||
|
||||
@@ -284,6 +284,9 @@ public:
|
||||
* than the last ledger it persisted. */
|
||||
virtual LedgerIndex
|
||||
getMaxDisallowedLedger() = 0;
|
||||
|
||||
virtual const std::optional<uint256>&
|
||||
trapTxID() const = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<Application>
|
||||
|
||||
@@ -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<std::string>(),
|
||||
"Trap a specific transaction during replay.")(
|
||||
"start", "Start from a fresh Ledger.")(
|
||||
"startReporting",
|
||||
po::value<std::string>(),
|
||||
@@ -558,6 +561,7 @@ run(int argc, char** argv)
|
||||
argc,
|
||||
argv);
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
else
|
||||
{
|
||||
if (vm.count("unittest-jobs"))
|
||||
@@ -679,7 +683,25 @@ run(int argc, char** argv)
|
||||
{
|
||||
config->START_LEDGER = vm["ledger"].as<std::string>();
|
||||
if (vm.count("replay"))
|
||||
{
|
||||
config->START_UP = Config::REPLAY;
|
||||
if (vm.count("trap_tx_hash"))
|
||||
{
|
||||
uint256 tmp = {};
|
||||
auto hash = vm["trap_tx_hash"].as<std::string>();
|
||||
if (tmp.parseHex(hash))
|
||||
{
|
||||
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::LOAD;
|
||||
}
|
||||
@@ -693,6 +715,13 @@ 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) ||
|
||||
@@ -828,6 +857,7 @@ run(int argc, char** argv)
|
||||
beast::setCurrentThreadName("rippled: rpc");
|
||||
return RPCCall::fromCommandLine(
|
||||
*config, vm["parameters"].as<std::vector<std::string>>(), *logs);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -854,6 +854,12 @@ Transactor::operator()()
|
||||
}
|
||||
#endif
|
||||
|
||||
if (auto const& trap = ctx_.app.trapTxID();
|
||||
trap && *trap == ctx_.tx.getTransactionID())
|
||||
{
|
||||
JLOG(j_.debug()) << "Transaction trapped: " << *trap;
|
||||
}
|
||||
|
||||
auto result = ctx_.preclaimResult;
|
||||
if (result == tesSUCCESS)
|
||||
result = apply();
|
||||
|
||||
@@ -162,6 +162,8 @@ public:
|
||||
|
||||
std::string START_LEDGER;
|
||||
|
||||
std::optional<uint256> TRAP_TX_HASH;
|
||||
|
||||
// Network parameters
|
||||
uint32_t NETWORK_ID = 0;
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <ripple/beast/unit_test/suite.h>
|
||||
#include <ripple/beast/utility/Journal.h>
|
||||
#include <ripple/beast/utility/temp_dir.h>
|
||||
#include <ripple/protocol/SField.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
@@ -36,10 +38,12 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
std::unique_ptr<Config> cfg,
|
||||
std::string const& dbPath,
|
||||
std::string const& ledger,
|
||||
Config::StartUpType type)
|
||||
Config::StartUpType type,
|
||||
std::optional<uint256> trapTxHash)
|
||||
{
|
||||
cfg->START_LEDGER = ledger;
|
||||
cfg->START_UP = type;
|
||||
cfg->TRAP_TX_HASH = trapTxHash;
|
||||
assert(!dbPath.empty());
|
||||
cfg->legacy("database_path", dbPath);
|
||||
return cfg;
|
||||
@@ -52,6 +56,7 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
std::string ledgerFile{};
|
||||
Json::Value ledger{};
|
||||
Json::Value hashes{};
|
||||
uint256 trapTxHash{};
|
||||
};
|
||||
|
||||
SetupData
|
||||
@@ -94,6 +99,16 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
}();
|
||||
|
||||
BEAST_EXPECT(retval.hashes.size() == 41);
|
||||
retval.trapTxHash = [&]() {
|
||||
auto const txs = env.rpc(
|
||||
"ledger",
|
||||
std::to_string(41),
|
||||
"tx")[jss::result][jss::ledger][jss::transactions];
|
||||
BEAST_EXPECT(txs.isArray() && txs.size() > 0);
|
||||
uint256 tmp;
|
||||
BEAST_EXPECT(tmp.parseHex(txs[0u][jss::hash].asString()));
|
||||
return tmp;
|
||||
}();
|
||||
|
||||
// write this ledger data to a file.
|
||||
std::ofstream o(retval.ledgerFile, std::ios::out | std::ios::trunc);
|
||||
@@ -112,7 +127,11 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
Env env(
|
||||
*this,
|
||||
envconfig(
|
||||
ledgerConfig, sd.dbPath, sd.ledgerFile, Config::LOAD_FILE),
|
||||
ledgerConfig,
|
||||
sd.dbPath,
|
||||
sd.ledgerFile,
|
||||
Config::LOAD_FILE,
|
||||
std::nullopt),
|
||||
nullptr,
|
||||
beast::severities::kDisabled);
|
||||
auto jrb = env.rpc("ledger", "current", "full")[jss::result];
|
||||
@@ -132,7 +151,12 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
except([&] {
|
||||
Env env(
|
||||
*this,
|
||||
envconfig(ledgerConfig, sd.dbPath, "", Config::LOAD_FILE),
|
||||
envconfig(
|
||||
ledgerConfig,
|
||||
sd.dbPath,
|
||||
"",
|
||||
Config::LOAD_FILE,
|
||||
std::nullopt),
|
||||
nullptr,
|
||||
beast::severities::kDisabled);
|
||||
});
|
||||
@@ -142,7 +166,11 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
Env env(
|
||||
*this,
|
||||
envconfig(
|
||||
ledgerConfig, sd.dbPath, "badfile.json", Config::LOAD_FILE),
|
||||
ledgerConfig,
|
||||
sd.dbPath,
|
||||
"badfile.json",
|
||||
Config::LOAD_FILE,
|
||||
std::nullopt),
|
||||
nullptr,
|
||||
beast::severities::kDisabled);
|
||||
});
|
||||
@@ -172,7 +200,8 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
ledgerConfig,
|
||||
sd.dbPath,
|
||||
ledgerFileCorrupt.string(),
|
||||
Config::LOAD_FILE),
|
||||
Config::LOAD_FILE,
|
||||
std::nullopt),
|
||||
nullptr,
|
||||
beast::severities::kDisabled);
|
||||
});
|
||||
@@ -189,7 +218,12 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
boost::erase_all(ledgerHash, "\"");
|
||||
Env env(
|
||||
*this,
|
||||
envconfig(ledgerConfig, sd.dbPath, ledgerHash, Config::LOAD),
|
||||
envconfig(
|
||||
ledgerConfig,
|
||||
sd.dbPath,
|
||||
ledgerHash,
|
||||
Config::LOAD,
|
||||
std::nullopt),
|
||||
nullptr,
|
||||
beast::severities::kDisabled);
|
||||
auto jrb = env.rpc("ledger", "current", "full")[jss::result];
|
||||
@@ -199,6 +233,103 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
sd.ledger[jss::ledger][jss::accountState].size());
|
||||
}
|
||||
|
||||
void
|
||||
testReplay(SetupData const& sd)
|
||||
{
|
||||
testcase("Load and replay by hash");
|
||||
using namespace test::jtx;
|
||||
|
||||
// create a new env with the ledger hash specified for startup
|
||||
auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]);
|
||||
boost::erase_all(ledgerHash, "\"");
|
||||
Env env(
|
||||
*this,
|
||||
envconfig(
|
||||
ledgerConfig,
|
||||
sd.dbPath,
|
||||
ledgerHash,
|
||||
Config::REPLAY,
|
||||
std::nullopt),
|
||||
nullptr,
|
||||
beast::severities::kDisabled);
|
||||
auto const jrb = env.rpc("ledger", "current", "full")[jss::result];
|
||||
BEAST_EXPECT(jrb[jss::ledger][jss::accountState].size() == 97);
|
||||
// in replace mode do not automatically accept the ledger being replayed
|
||||
|
||||
env.close();
|
||||
auto const closed = env.rpc("ledger", "current", "full")[jss::result];
|
||||
BEAST_EXPECT(closed[jss::ledger][jss::accountState].size() == 98);
|
||||
BEAST_EXPECT(
|
||||
closed[jss::ledger][jss::accountState].size() <=
|
||||
sd.ledger[jss::ledger][jss::accountState].size());
|
||||
}
|
||||
|
||||
void
|
||||
testReplayTx(SetupData const& sd)
|
||||
{
|
||||
testcase("Load and replay transaction by hash");
|
||||
using namespace test::jtx;
|
||||
|
||||
// create a new env with the ledger hash specified for startup
|
||||
auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]);
|
||||
boost::erase_all(ledgerHash, "\"");
|
||||
Env env(
|
||||
*this,
|
||||
envconfig(
|
||||
ledgerConfig,
|
||||
sd.dbPath,
|
||||
ledgerHash,
|
||||
Config::REPLAY,
|
||||
sd.trapTxHash),
|
||||
nullptr,
|
||||
beast::severities::kDisabled);
|
||||
auto const jrb = env.rpc("ledger", "current", "full")[jss::result];
|
||||
BEAST_EXPECT(jrb[jss::ledger][jss::accountState].size() == 97);
|
||||
// in replace mode do not automatically accept the ledger being replayed
|
||||
|
||||
env.close();
|
||||
auto const closed = env.rpc("ledger", "current", "full")[jss::result];
|
||||
BEAST_EXPECT(closed[jss::ledger][jss::accountState].size() == 98);
|
||||
BEAST_EXPECT(
|
||||
closed[jss::ledger][jss::accountState].size() <=
|
||||
sd.ledger[jss::ledger][jss::accountState].size());
|
||||
}
|
||||
|
||||
void
|
||||
testReplayTxFail(SetupData const& sd)
|
||||
{
|
||||
testcase("Load and replay transaction by hash failure");
|
||||
using namespace test::jtx;
|
||||
|
||||
// create a new env with the ledger hash specified for startup
|
||||
auto ledgerHash = to_string(sd.hashes[sd.hashes.size() - 1]);
|
||||
boost::erase_all(ledgerHash, "\"");
|
||||
try
|
||||
{
|
||||
// will throw an exception, because we cannot load a ledger for
|
||||
// replay when trapTxHash is set to an invalid transaction
|
||||
Env env(
|
||||
*this,
|
||||
envconfig(
|
||||
ledgerConfig,
|
||||
sd.dbPath,
|
||||
ledgerHash,
|
||||
Config::REPLAY,
|
||||
~sd.trapTxHash),
|
||||
nullptr,
|
||||
beast::severities::kDisabled);
|
||||
BEAST_EXPECT(false);
|
||||
}
|
||||
catch (std::runtime_error const&)
|
||||
{
|
||||
BEAST_EXPECT(true);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
BEAST_EXPECT(false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLoadLatest(SetupData const& sd)
|
||||
{
|
||||
@@ -208,7 +339,8 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
// create a new env with the ledger "latest" specified for startup
|
||||
Env env(
|
||||
*this,
|
||||
envconfig(ledgerConfig, sd.dbPath, "latest", Config::LOAD),
|
||||
envconfig(
|
||||
ledgerConfig, sd.dbPath, "latest", Config::LOAD, std::nullopt),
|
||||
nullptr,
|
||||
beast::severities::kDisabled);
|
||||
auto jrb = env.rpc("ledger", "current", "full")[jss::result];
|
||||
@@ -226,7 +358,8 @@ class LedgerLoad_test : public beast::unit_test::suite
|
||||
// create a new env with specific ledger index at startup
|
||||
Env env(
|
||||
*this,
|
||||
envconfig(ledgerConfig, sd.dbPath, "43", Config::LOAD),
|
||||
envconfig(
|
||||
ledgerConfig, sd.dbPath, "43", Config::LOAD, std::nullopt),
|
||||
nullptr,
|
||||
beast::severities::kDisabled);
|
||||
auto jrb = env.rpc("ledger", "current", "full")[jss::result];
|
||||
@@ -246,6 +379,9 @@ public:
|
||||
testLoad(sd);
|
||||
testBadFiles(sd);
|
||||
testLoadByHash(sd);
|
||||
testReplay(sd);
|
||||
testReplayTx(sd);
|
||||
testReplayTxFail(sd);
|
||||
testLoadLatest(sd);
|
||||
testLoadIndex(sd);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user