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:
Bronek Kozicki
2024-06-11 19:34:02 +01:00
committed by tequ
parent c59bd29b61
commit 8fea47447c
6 changed files with 211 additions and 11 deletions

View File

@@ -228,6 +228,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;
@@ -1257,6 +1258,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.
@@ -1282,7 +1289,8 @@ private:
std::string const& ledgerID,
bool replay,
bool isFilename,
bool isJson);
bool isJson,
std::optional<uint256> trapTxID);
void
setMaxDisallowedLedger();
@@ -1415,7 +1423,8 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
config_->START_LEDGER,
startUp == Config::REPLAY,
startUp == Config::LOAD_FILE,
startUp == Config::LOAD_JSON))
startUp == Config::LOAD_JSON,
config_->TRAP_TX_HASH))
{
JLOG(m_journal.error())
<< "The specified ledger could not be loaded.";
@@ -2265,7 +2274,8 @@ ApplicationImp::loadOldLedger(
std::string const& ledgerID,
bool replay,
bool isFileName,
bool isJson)
bool isJson,
std::optional<uint256> trapTxID)
{
try
{
@@ -2417,6 +2427,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);
@@ -2431,6 +2446,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)

View File

@@ -283,6 +283,9 @@ public:
* than the last ledger it persisted. */
virtual LedgerIndex
getMaxDisallowedLedger() = 0;
virtual const std::optional<uint256>&
trapTxID() const = 0;
};
std::unique_ptr<Application>

View File

@@ -399,6 +399,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>(),
@@ -557,6 +560,7 @@ run(int argc, char** argv)
argc,
argv);
}
// LCOV_EXCL_START
else
{
if (vm.count("unittest-jobs"))
@@ -678,7 +682,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;
}
@@ -692,6 +714,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) ||
@@ -827,6 +856,7 @@ run(int argc, char** argv)
beast::setCurrentThreadName("xahaud: rpc");
return RPCCall::fromCommandLine(
*config, vm["parameters"].as<std::vector<std::string>>(), *logs);
// LCOV_EXCL_STOP
}
} // namespace ripple

View File

@@ -1792,6 +1792,12 @@ Transactor::operator()()
SF_EMITTED)))
return {tecINTERNAL, false};
if (auto const& trap = ctx_.app.trapTxID();
trap && *trap == ctx_.tx.getTransactionID())
{
JLOG(j_.debug()) << "Transaction trapped: " << *trap;
}
auto result = ctx_.preclaimResult;
bool const hooksEnabled = view().rules().enabled(featureHooks);

View File

@@ -177,6 +177,8 @@ public:
std::string START_LEDGER;
std::optional<uint256> TRAP_TX_HASH;
// Network parameters
uint32_t NETWORK_ID = 0;

View File

@@ -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);
auto& sectionNode = cfg->section(ConfigSection::nodeDatabase());
@@ -55,6 +59,7 @@ class LedgerLoad_test : public beast::unit_test::suite
std::string ledgerFile{};
Json::Value ledger{};
Json::Value hashes{};
uint256 trapTxHash{};
};
SetupData
@@ -103,6 +108,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);
@@ -121,7 +136,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];
@@ -141,7 +160,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);
});
@@ -151,7 +175,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);
});
@@ -181,7 +209,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);
});
@@ -198,7 +227,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];
@@ -208,6 +242,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)
{
@@ -217,7 +348,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];
@@ -235,7 +367,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];
@@ -255,6 +388,9 @@ public:
testLoad(sd);
testBadFiles(sd);
testLoadByHash(sd);
testReplay(sd);
testReplayTx(sd);
testReplayTxFail(sd);
testLoadLatest(sd);
testLoadIndex(sd);
}