Merge branch 'develop' into sponsor

This commit is contained in:
tequ
2026-01-07 12:35:47 +09:00
committed by GitHub
32 changed files with 1055 additions and 361 deletions

View File

@@ -5,6 +5,7 @@
#include <xrpld/core/ConfigSections.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/server/Port.h>
#include <boost/filesystem.hpp>
@@ -18,7 +19,7 @@ namespace detail {
std::string
configContents(std::string const& dbPath, std::string const& validatorsFile)
{
static boost::format configContentsTemplate(R"rippleConfig(
static boost::format configContentsTemplate(R"xrpldConfig(
[server]
port_rpc
port_peer
@@ -51,14 +52,14 @@ protocol = wss
[node_size]
medium
# This is primary persistent datastore for rippled. This includes transaction
# This is primary persistent datastore for xrpld. This includes transaction
# metadata, account states, and ledger headers. Helpful information can be
# found on https://xrpl.org/capacity-planning.html#node-db-type
# delete old ledgers while maintaining at least 2000. Do not require an
# external administrative command to initiate deletion.
[node_db]
type=memory
path=/Users/dummy/ripple/config/db/rocksdb
path=/Users/dummy/xrpld/config/db/rocksdb
open_files=2000
filter_bits=12
cache_mb=256
@@ -72,7 +73,7 @@ file_size_mult=2
# This needs to be an absolute directory reference, not a relative one.
# Modify this value as required.
[debug_logfile]
/Users/dummy/ripple/config/log/debug.log
/Users/dummy/xrpld/config/log/debug.log
[sntp_servers]
time.windows.com
@@ -97,7 +98,7 @@ r.ripple.com 51235
[sqdb]
backend=sqlite
)rippleConfig");
)xrpldConfig");
std::string dbPathSection =
dbPath.empty() ? "" : "[database_path]\n" + dbPath;
@@ -107,9 +108,9 @@ backend=sqlite
}
/**
Write a rippled config file and remove when done.
Write a xrpld config file and remove when done.
*/
class RippledCfgGuard : public xrpl::detail::FileDirGuard
class FileCfgGuard : public xrpl::detail::FileDirGuard
{
private:
path dataDir_;
@@ -119,17 +120,18 @@ private:
Config config_;
public:
RippledCfgGuard(
FileCfgGuard(
beast::unit_test::suite& test,
path subDir,
path const& dbPath,
path const& configFile,
path const& validatorsFile,
bool useCounter = true,
std::string confContents = "")
: FileDirGuard(
test,
std::move(subDir),
path(Config::configFileName),
configFile,
confContents.empty()
? configContents(dbPath.string(), validatorsFile.string())
: confContents,
@@ -171,7 +173,7 @@ public:
return fileExists();
}
~RippledCfgGuard()
~FileCfgGuard()
{
try
{
@@ -182,7 +184,7 @@ public:
catch (std::exception& e)
{
// if we throw here, just let it die.
test_.log << "Error in ~RippledCfgGuard: " << e.what() << std::endl;
test_.log << "Error in ~FileCfgGuard: " << e.what() << std::endl;
};
}
};
@@ -190,7 +192,7 @@ public:
std::string
valFileContents()
{
std::string configContents(R"rippleConfig(
std::string configContents(R"xrpldConfig(
[validators]
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
@@ -204,8 +206,8 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
nHUPDdcdb2Y5DZAJne4c2iabFuAP3F34xZUgYQT2NH7qfkdapgnz
[validator_list_sites]
recommendedripplevalidators.com
moreripplevalidators.net
recommendedxrplvalidators.com
morexrplvalidators.net
[validator_list_keys]
03E74EE14CB525AFBB9F1B7D86CD58ECC4B91452294B42AB4E78F260BD905C091D
@@ -213,7 +215,7 @@ moreripplevalidators.net
[validator_list_threshold]
2
)rippleConfig");
)xrpldConfig");
return configContents;
}
@@ -270,7 +272,7 @@ public:
Config c;
std::string toLoad(R"rippleConfig(
std::string toLoad(R"xrpldConfig(
[server]
port_rpc
port_peer
@@ -278,7 +280,7 @@ port_wss_admin
[ssl_verify]
0
)rippleConfig");
)xrpldConfig");
c.loadFromString(toLoad);
@@ -291,6 +293,126 @@ port_wss_admin
BEAST_EXPECT(c.legacy("not_in_file") == "new_value");
}
void
testConfigFile()
{
testcase("config_file");
using namespace boost::filesystem;
auto const cwd = current_path();
// Test both config file names.
char const* configFiles[] = {
Config::configFileName, Config::configLegacyName};
// Config file in current directory.
for (auto const& configFile : configFiles)
{
// Use a temporary directory for testing.
beast::temp_dir td;
current_path(td.path());
path const f = td.file(configFile);
std::ofstream o(f.string());
o << detail::configContents("", "");
o.close();
// Load the config file from the current directory and verify it.
Config c;
c.setup("", true, false, true);
BEAST_EXPECT(c.section(SECTION_DEBUG_LOGFILE).values().size() == 1);
BEAST_EXPECT(
c.section(SECTION_DEBUG_LOGFILE).values()[0] ==
"/Users/dummy/xrpld/config/log/debug.log");
}
// Config file in HOME or XDG_CONFIG_HOME directory.
#if BOOST_OS_LINUX || BOOST_OS_MACOS
for (auto const& configFile : configFiles)
{
// Point the current working directory to a temporary directory, so
// we don't pick up an actual config file from the repository root.
beast::temp_dir td;
current_path(td.path());
// The XDG config directory is set: the config file must be in a
// subdirectory named after the system.
{
beast::temp_dir tc;
// Set the HOME and XDG_CONFIG_HOME environment variables. The
// HOME variable is not used when XDG_CONFIG_HOME is set, but
// must be set.
char const* h = getenv("HOME");
setenv("HOME", tc.path().c_str(), 1);
char const* x = getenv("XDG_CONFIG_HOME");
setenv("XDG_CONFIG_HOME", tc.path().c_str(), 1);
// Create the config file in '${XDG_CONFIG_HOME}/[systemName]'.
path p = tc.file(systemName());
create_directory(p);
p = tc.file(systemName() + "/" + configFile);
std::ofstream o(p.string());
o << detail::configContents("", "");
o.close();
// Load the config file from the config directory and verify it.
Config c;
c.setup("", true, false, true);
BEAST_EXPECT(
c.section(SECTION_DEBUG_LOGFILE).values().size() == 1);
BEAST_EXPECT(
c.section(SECTION_DEBUG_LOGFILE).values()[0] ==
"/Users/dummy/xrpld/config/log/debug.log");
// Restore the environment variables.
h ? setenv("HOME", h, 1) : unsetenv("HOME");
x ? setenv("XDG_CONFIG_HOME", x, 1)
: unsetenv("XDG_CONFIG_HOME");
}
// The XDG config directory is not set: the config file must be in a
// subdirectory named .config followed by the system name.
{
beast::temp_dir tc;
// Set only the HOME environment variable.
char const* h = getenv("HOME");
setenv("HOME", tc.path().c_str(), 1);
char const* x = getenv("XDG_CONFIG_HOME");
unsetenv("XDG_CONFIG_HOME");
// Create the config file in '${HOME}/.config/[systemName]'.
std::string s = ".config";
path p = tc.file(s);
create_directory(p);
s += "/" + systemName();
p = tc.file(s);
create_directory(p);
p = tc.file(s + "/" + configFile);
std::ofstream o(p.string());
o << detail::configContents("", "");
o.close();
// Load the config file from the config directory and verify it.
Config c;
c.setup("", true, false, true);
BEAST_EXPECT(
c.section(SECTION_DEBUG_LOGFILE).values().size() == 1);
BEAST_EXPECT(
c.section(SECTION_DEBUG_LOGFILE).values()[0] ==
"/Users/dummy/xrpld/config/log/debug.log");
// Restore the environment variables.
h ? setenv("HOME", h, 1) : unsetenv("HOME");
if (x)
setenv("XDG_CONFIG_HOME", x, 1);
}
}
#endif
// Restore the current working directory.
current_path(cwd);
}
void
testDbPath()
{
testcase("database_path");
@@ -326,11 +448,16 @@ port_wss_admin
{
// read from file absolute path
auto const cwd = current_path();
xrpl::detail::DirGuard const g0(*this, "test_db");
detail::DirGuard const g0(*this, "test_db");
path const dataDirRel("test_data_dir");
path const dataDirAbs(cwd / g0.subdir() / dataDirRel);
detail::RippledCfgGuard const g(
*this, g0.subdir(), dataDirAbs, "", false);
detail::FileCfgGuard const g(
*this,
g0.subdir(),
dataDirAbs,
Config::configFileName,
"",
false);
auto const& c(g.config());
BEAST_EXPECT(g.dataDirExists());
BEAST_EXPECT(g.configFileExists());
@@ -339,7 +466,8 @@ port_wss_admin
{
// read from file relative path
std::string const dbPath("my_db");
detail::RippledCfgGuard const g(*this, "test_db", dbPath, "");
detail::FileCfgGuard const g(
*this, "test_db", dbPath, Config::configFileName, "");
auto const& c(g.config());
std::string const nativeDbPath = absolute(path(dbPath)).string();
BEAST_EXPECT(g.dataDirExists());
@@ -348,7 +476,8 @@ port_wss_admin
}
{
// read from file no path
detail::RippledCfgGuard const g(*this, "test_db", "", "");
detail::FileCfgGuard const g(
*this, "test_db", "", Config::configFileName, "");
auto const& c(g.config());
std::string const nativeDbPath =
absolute(g.subdir() / path(Config::databaseDirName)).string();
@@ -378,13 +507,13 @@ port_wss_admin
{
Config c;
static boost::format configTemplate(R"rippleConfig(
static boost::format configTemplate(R"xrpldConfig(
[validation_seed]
%1%
[validator_token]
%2%
)rippleConfig");
)xrpldConfig");
std::string error;
auto const expectedError =
"Cannot have both [validation_seed] "
@@ -410,10 +539,10 @@ port_wss_admin
Config c;
try
{
c.loadFromString(R"rippleConfig(
c.loadFromString(R"xrpldConfig(
[network_id]
main
)rippleConfig");
)xrpldConfig");
}
catch (std::runtime_error& e)
{
@@ -425,8 +554,8 @@ main
try
{
c.loadFromString(R"rippleConfig(
)rippleConfig");
c.loadFromString(R"xrpldConfig(
)xrpldConfig");
}
catch (std::runtime_error& e)
{
@@ -438,10 +567,10 @@ main
try
{
c.loadFromString(R"rippleConfig(
c.loadFromString(R"xrpldConfig(
[network_id]
255
)rippleConfig");
)xrpldConfig");
}
catch (std::runtime_error& e)
{
@@ -453,10 +582,10 @@ main
try
{
c.loadFromString(R"rippleConfig(
c.loadFromString(R"xrpldConfig(
[network_id]
10000
)rippleConfig");
)xrpldConfig");
}
catch (std::runtime_error& e)
{
@@ -516,7 +645,7 @@ main
{
// load validators from config into single section
Config c;
std::string toLoad(R"rippleConfig(
std::string toLoad(R"xrpldConfig(
[validators]
n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7
n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj
@@ -525,7 +654,7 @@ n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C
[validator_keys]
nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5
nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
)rippleConfig");
)xrpldConfig");
c.loadFromString(toLoad);
BEAST_EXPECT(c.legacy("validators_file").empty());
BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 5);
@@ -534,9 +663,9 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8
{
// load validator list sites and keys from config
Config c;
std::string toLoad(R"rippleConfig(
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
ripplevalidators.com
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
@@ -544,13 +673,13 @@ trustthesevalidators.gov
[validator_list_threshold]
1
)rippleConfig");
)xrpldConfig");
c.loadFromString(toLoad);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] ==
"ripplevalidators.com");
"xrplvalidators.com");
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] ==
"trustthesevalidators.gov");
@@ -570,9 +699,9 @@ trustthesevalidators.gov
{
// load validator list sites and keys from config
Config c;
std::string toLoad(R"rippleConfig(
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
ripplevalidators.com
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
@@ -580,13 +709,13 @@ trustthesevalidators.gov
[validator_list_threshold]
0
)rippleConfig");
)xrpldConfig");
c.loadFromString(toLoad);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2);
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] ==
"ripplevalidators.com");
"xrplvalidators.com");
BEAST_EXPECT(
c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] ==
"trustthesevalidators.gov");
@@ -607,9 +736,9 @@ trustthesevalidators.gov
// load should throw if [validator_list_threshold] is greater than
// the number of [validator_list_keys]
Config c;
std::string toLoad(R"rippleConfig(
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
ripplevalidators.com
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
@@ -617,7 +746,7 @@ trustthesevalidators.gov
[validator_list_threshold]
2
)rippleConfig");
)xrpldConfig");
std::string error;
auto const expectedError =
"Value in config section [validator_list_threshold] exceeds "
@@ -636,9 +765,9 @@ trustthesevalidators.gov
{
// load should throw if [validator_list_threshold] is malformed
Config c;
std::string toLoad(R"rippleConfig(
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
ripplevalidators.com
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
@@ -646,7 +775,7 @@ trustthesevalidators.gov
[validator_list_threshold]
value = 2
)rippleConfig");
)xrpldConfig");
std::string error;
auto const expectedError =
"Config section [validator_list_threshold] should contain "
@@ -665,9 +794,9 @@ value = 2
{
// load should throw if [validator_list_threshold] is negative
Config c;
std::string toLoad(R"rippleConfig(
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
ripplevalidators.com
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
@@ -675,7 +804,7 @@ trustthesevalidators.gov
[validator_list_threshold]
-1
)rippleConfig");
)xrpldConfig");
bool error = false;
try
{
@@ -692,11 +821,11 @@ trustthesevalidators.gov
// load should throw if [validator_list_sites] is configured but
// [validator_list_keys] is not
Config c;
std::string toLoad(R"rippleConfig(
std::string toLoad(R"xrpldConfig(
[validator_list_sites]
ripplevalidators.com
xrplvalidators.com
trustthesevalidators.gov
)rippleConfig");
)xrpldConfig");
std::string error;
auto const expectedError =
"[validator_list_keys] config section is missing";
@@ -736,8 +865,13 @@ trustthesevalidators.gov
std::string const valFileName = "validators.txt";
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", valFileName);
detail::RippledCfgGuard const rcg(
*this, vtg.subdir(), "", valFileName, false);
detail::FileCfgGuard const rcg(
*this,
vtg.subdir(),
"",
Config::configFileName,
valFileName,
false);
BEAST_EXPECT(vtg.validatorsFileExists());
BEAST_EXPECT(rcg.configFileExists());
auto const& c(rcg.config());
@@ -758,8 +892,13 @@ trustthesevalidators.gov
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.txt");
auto const valFilePath = ".." / vtg.subdir() / "validators.txt";
detail::RippledCfgGuard const rcg(
*this, vtg.subdir(), "", valFilePath, false);
detail::FileCfgGuard const rcg(
*this,
vtg.subdir(),
"",
Config::configFileName,
valFilePath,
false);
BEAST_EXPECT(vtg.validatorsFileExists());
BEAST_EXPECT(rcg.configFileExists());
auto const& c(rcg.config());
@@ -778,8 +917,8 @@ trustthesevalidators.gov
// load from validators file in default location
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.txt");
detail::RippledCfgGuard const rcg(
*this, vtg.subdir(), "", "", false);
detail::FileCfgGuard const rcg(
*this, vtg.subdir(), "", Config::configFileName, "", false);
BEAST_EXPECT(vtg.validatorsFileExists());
BEAST_EXPECT(rcg.configFileExists());
auto const& c(rcg.config());
@@ -803,8 +942,13 @@ trustthesevalidators.gov
detail::ValidatorsTxtGuard const vtgDefault(
*this, vtg.subdir(), "validators.txt", false);
BEAST_EXPECT(vtgDefault.validatorsFileExists());
detail::RippledCfgGuard const rcg(
*this, vtg.subdir(), "", vtg.validatorsFile(), false);
detail::FileCfgGuard const rcg(
*this,
vtg.subdir(),
"",
Config::configFileName,
vtg.validatorsFile(),
false);
BEAST_EXPECT(rcg.configFileExists());
auto const& c(rcg.config());
BEAST_EXPECT(c.legacy("validators_file") == vtg.validatorsFile());
@@ -821,7 +965,7 @@ trustthesevalidators.gov
{
// load validators from both config and validators file
boost::format cc(R"rippleConfig(
boost::format cc(R"xrpldConfig(
[validators_file]
%1%
@@ -837,12 +981,12 @@ nHB1X37qrniVugfQcuBTAjswphC1drx7QjFFojJPZwKHHnt8kU7v
nHUkAWDR4cB8AgPg7VXMX6et8xRTQb2KJfgv1aBEXozwrawRKgMB
[validator_list_sites]
ripplevalidators.com
xrplvalidators.com
trustthesevalidators.gov
[validator_list_keys]
021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566
)rippleConfig");
)xrpldConfig");
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.cfg");
BEAST_EXPECT(vtg.validatorsFileExists());
@@ -861,14 +1005,14 @@ trustthesevalidators.gov
}
{
// load should throw if [validator_list_threshold] is present both
// in rippled cfg and validators file
boost::format cc(R"rippleConfig(
// in xrpld.cfg and validators file
boost::format cc(R"xrpldConfig(
[validators_file]
%1%
[validator_list_threshold]
1
)rippleConfig");
)xrpldConfig");
std::string error;
detail::ValidatorsTxtGuard const vtg(
*this, "test_cfg", "validators.cfg");
@@ -890,7 +1034,7 @@ trustthesevalidators.gov
}
{
// load should throw if [validators], [validator_keys] and
// [validator_list_keys] are missing from rippled cfg and
// [validator_list_keys] are missing from xrpld.cfg and
// validators file
Config c;
boost::format cc("[validators_file]\n%1%\n");
@@ -920,9 +1064,13 @@ trustthesevalidators.gov
void
testSetup(bool explicitPath)
{
detail::RippledCfgGuard const cfg(
*this, "testSetup", explicitPath ? "test_db" : "", "");
/* RippledCfgGuard has a Config object that gets loaded on
detail::FileCfgGuard const cfg(
*this,
"testSetup",
explicitPath ? "test_db" : "",
Config::configFileName,
"");
/* FileCfgGuard has a Config object that gets loaded on
construction, but Config::setup is not reentrant, so we
need a fresh config for every test case, so ignore it.
*/
@@ -1039,7 +1187,8 @@ trustthesevalidators.gov
void
testPort()
{
detail::RippledCfgGuard const cfg(*this, "testPort", "", "");
detail::FileCfgGuard const cfg(
*this, "testPort", "", Config::configFileName, "");
auto const& conf = cfg.config();
if (!BEAST_EXPECT(conf.exists("port_rpc")))
return;
@@ -1065,8 +1214,14 @@ trustthesevalidators.gov
try
{
detail::RippledCfgGuard const cfg(
*this, "testPort", "", "", true, contents);
detail::FileCfgGuard const cfg(
*this,
"testPort",
"",
Config::configFileName,
"",
true,
contents);
BEAST_EXPECT(false);
}
catch (std::exception const& ex)
@@ -1377,9 +1532,9 @@ r.ripple.com:51235
for (auto& [unit, sec, val, shouldPass] : units)
{
Config c;
std::string toLoad(R"rippleConfig(
std::string toLoad(R"xrpldConfig(
[amendment_majority_time]
)rippleConfig");
)xrpldConfig");
toLoad += std::to_string(val) + space + unit;
space = space == "" ? " " : "";
@@ -1480,6 +1635,7 @@ r.ripple.com:51235
run() override
{
testLegacy();
testConfigFile();
testDbPath();
testValidatorKeys();
testValidatorsFile();

View File

@@ -30,6 +30,7 @@ enum class FieldType {
CurrencyField,
HashField,
HashOrObjectField,
IssueField,
ObjectField,
StringField,
TwoAccountArrayField,
@@ -40,6 +41,8 @@ enum class FieldType {
std::vector<std::pair<Json::StaticString, FieldType>> mappings{
{jss::account, FieldType::AccountField},
{jss::accounts, FieldType::TwoAccountArrayField},
{jss::asset, FieldType::IssueField},
{jss::asset2, FieldType::IssueField},
{jss::authorize, FieldType::AccountField},
{jss::authorized, FieldType::AccountField},
{jss::credential_type, FieldType::BlobField},
@@ -74,24 +77,26 @@ getTypeName(FieldType typeID)
{
switch (typeID)
{
case FieldType::UInt32Field:
return "number";
case FieldType::UInt64Field:
return "number";
case FieldType::HashField:
return "hex string";
case FieldType::AccountField:
return "AccountID";
case FieldType::ArrayField:
return "array";
case FieldType::BlobField:
return "hex string";
case FieldType::CurrencyField:
return "Currency";
case FieldType::ArrayField:
return "array";
case FieldType::HashField:
return "hex string";
case FieldType::HashOrObjectField:
return "hex string or object";
case FieldType::IssueField:
return "Issue";
case FieldType::TwoAccountArrayField:
return "length-2 array of Accounts";
case FieldType::UInt32Field:
return "number";
case FieldType::UInt64Field:
return "number";
default:
Throw<std::runtime_error>(
"unknown type " + std::to_string(static_cast<uint8_t>(typeID)));
@@ -192,34 +197,37 @@ class LedgerEntry_test : public beast::unit_test::suite
return values;
};
static auto const& badUInt32Values = remove({2, 3});
static auto const& badUInt64Values = remove({2, 3});
static auto const& badHashValues = remove({2, 3, 7, 8, 16});
static auto const& badAccountValues = remove({12});
static auto const& badArrayValues = remove({17, 20});
static auto const& badBlobValues = remove({3, 7, 8, 16});
static auto const& badCurrencyValues = remove({14});
static auto const& badArrayValues = remove({17, 20});
static auto const& badHashValues = remove({2, 3, 7, 8, 16});
static auto const& badIndexValues = remove({12, 16, 18, 19});
static auto const& badUInt32Values = remove({2, 3});
static auto const& badUInt64Values = remove({2, 3});
static auto const& badIssueValues = remove({});
switch (fieldType)
{
case FieldType::UInt32Field:
return badUInt32Values;
case FieldType::UInt64Field:
return badUInt64Values;
case FieldType::HashField:
return badHashValues;
case FieldType::AccountField:
return badAccountValues;
case FieldType::ArrayField:
case FieldType::TwoAccountArrayField:
return badArrayValues;
case FieldType::BlobField:
return badBlobValues;
case FieldType::CurrencyField:
return badCurrencyValues;
case FieldType::ArrayField:
case FieldType::TwoAccountArrayField:
return badArrayValues;
case FieldType::HashField:
return badHashValues;
case FieldType::HashOrObjectField:
return badIndexValues;
case FieldType::IssueField:
return badIssueValues;
case FieldType::UInt32Field:
return badUInt32Values;
case FieldType::UInt64Field:
return badUInt64Values;
default:
Throw<std::runtime_error>(
"unknown type " +
@@ -236,30 +244,37 @@ class LedgerEntry_test : public beast::unit_test::suite
arr[1u] = "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5";
return arr;
}();
static Json::Value const issueObject = []() {
Json::Value arr(Json::objectValue);
arr[jss::currency] = "XRP";
return arr;
}();
auto const typeID = getFieldType(fieldName);
switch (typeID)
{
case FieldType::UInt32Field:
return 1;
case FieldType::UInt64Field:
return 1;
case FieldType::HashField:
return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
"B01403D6D";
case FieldType::AccountField:
return "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5";
case FieldType::ArrayField:
return Json::arrayValue;
case FieldType::BlobField:
return "ABCDEF";
case FieldType::CurrencyField:
return "USD";
case FieldType::ArrayField:
return Json::arrayValue;
case FieldType::HashField:
return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
"B01403D6D";
case FieldType::IssueField:
return issueObject;
case FieldType::HashOrObjectField:
return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
"B01403D6D";
case FieldType::TwoAccountArrayField:
return twoAccountArray;
case FieldType::UInt32Field:
return 1;
case FieldType::UInt64Field:
return 1;
default:
Throw<std::runtime_error>(
"unknown type " +
@@ -444,7 +459,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryInvalid()
testInvalid()
{
testcase("Invalid requests");
using namespace test::jtx;
@@ -526,7 +541,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryAccountRoot()
testAccountRoot()
{
testcase("AccountRoot");
using namespace test::jtx;
@@ -632,7 +647,147 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryCheck()
testAmendments()
{
testcase("Amendments");
using namespace test::jtx;
Env env{*this};
// positive test
{
Keylet const keylet = keylet::amendments();
// easier to hack an object into the ledger than generate it
// legitimately
{
auto const amendments = [&](OpenView& view,
beast::Journal) -> bool {
auto const sle = std::make_shared<SLE>(keylet);
// Create Amendments vector (enabled amendments)
std::vector<uint256> enabledAmendments;
enabledAmendments.push_back(
uint256::fromVoid("42426C4D4F1009EE67080A9B7965B44656D7"
"714D104A72F9B4369F97ABF044EE"));
enabledAmendments.push_back(
uint256::fromVoid("4C97EBA926031A7CF7D7B36FDE3ED66DDA54"
"21192D63DE53FFB46E43B9DC8373"));
enabledAmendments.push_back(
uint256::fromVoid("03BDC0099C4E14163ADA272C1B6F6FABB448"
"CC3E51F522F978041E4B57D9158C"));
enabledAmendments.push_back(
uint256::fromVoid("35291ADD2D79EB6991343BDA0912269C817D"
"0F094B02226C1C14AD2858962ED4"));
sle->setFieldV256(
sfAmendments, STVector256(enabledAmendments));
// Create Majorities array
STArray majorities;
auto majority1 = STObject::makeInnerObject(sfMajority);
majority1.setFieldH256(
sfAmendment,
uint256::fromVoid("7BB62DC13EC72B775091E9C71BF8CF97E122"
"647693B50C5E87A80DFD6FCFAC50"));
majority1.setFieldU32(sfCloseTime, 779561310);
majorities.push_back(std::move(majority1));
auto majority2 = STObject::makeInnerObject(sfMajority);
majority2.setFieldH256(
sfAmendment,
uint256::fromVoid("755C971C29971C9F20C6F080F2ED96F87884"
"E40AD19554A5EBECDCEC8A1F77FE"));
majority2.setFieldU32(sfCloseTime, 779561310);
majorities.push_back(std::move(majority2));
sle->setFieldArray(sfMajorities, majorities);
view.rawInsert(sle);
return true;
};
env.app().openLedger().modify(amendments);
}
Json::Value jvParams;
jvParams[jss::amendments] = to_string(keylet.key);
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(
jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Amendments);
}
// negative tests
runLedgerEntryTest(env, jss::amendments);
}
void
testAMM()
{
testcase("AMM");
using namespace test::jtx;
Env env{*this};
// positive test
Account const alice{"alice"};
env.fund(XRP(10000), alice);
env.close();
AMM amm(env, alice, XRP(10), alice["USD"](1000));
env.close();
{
Json::Value jvParams;
jvParams[jss::amm] = to_string(amm.ammID());
auto const result =
env.rpc("json", "ledger_entry", to_string(jvParams));
BEAST_EXPECT(
result.isObject() && result.isMember(jss::result) &&
!result[jss::result].isMember(jss::error) &&
result[jss::result].isMember(jss::node) &&
result[jss::result][jss::node].isMember(
sfLedgerEntryType.jsonName) &&
result[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
jss::AMM);
}
{
Json::Value jvParams;
Json::Value ammParams(Json::objectValue);
{
Json::Value obj(Json::objectValue);
obj[jss::currency] = "XRP";
ammParams[jss::asset] = obj;
}
{
Json::Value obj(Json::objectValue);
obj[jss::currency] = "USD";
obj[jss::issuer] = alice.human();
ammParams[jss::asset2] = obj;
}
jvParams[jss::amm] = ammParams;
auto const result =
env.rpc("json", "ledger_entry", to_string(jvParams));
BEAST_EXPECT(
result.isObject() && result.isMember(jss::result) &&
!result[jss::result].isMember(jss::error) &&
result[jss::result].isMember(jss::node) &&
result[jss::result][jss::node].isMember(
sfLedgerEntryType.jsonName) &&
result[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
jss::AMM);
}
// negative tests
runLedgerEntryTest(
env,
jss::amm,
{
{jss::asset, "malformedRequest"},
{jss::asset2, "malformedRequest"},
});
}
void
testCheck()
{
testcase("Check");
using namespace test::jtx;
@@ -684,7 +839,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryCredentials()
testCredentials()
{
testcase("Credentials");
@@ -752,7 +907,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryDelegate()
testDelegate()
{
testcase("Delegate");
@@ -807,7 +962,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryDepositPreauth()
testDepositPreauth()
{
testcase("Deposit Preauth");
@@ -868,7 +1023,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryDepositPreauthCred()
testDepositPreauthCred()
{
testcase("Deposit Preauth with credentials");
@@ -1149,7 +1304,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryDirectory()
testDirectory()
{
testcase("Directory");
using namespace test::jtx;
@@ -1303,7 +1458,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryEscrow()
testEscrow()
{
testcase("Escrow");
using namespace test::jtx;
@@ -1365,7 +1520,177 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryOffer()
testFeeSettings()
{
testcase("Fee Settings");
using namespace test::jtx;
Env env{*this};
// positive test
{
Keylet const keylet = keylet::fees();
Json::Value jvParams;
jvParams[jss::fee] = to_string(keylet.key);
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(
jrr[jss::node][sfLedgerEntryType.jsonName] == jss::FeeSettings);
}
// negative tests
runLedgerEntryTest(env, jss::fee);
}
void
testLedgerHashes()
{
testcase("Ledger Hashes");
using namespace test::jtx;
Env env{*this};
// positive test
{
Keylet const keylet = keylet::skip();
Json::Value jvParams;
jvParams[jss::hashes] = to_string(keylet.key);
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(
jrr[jss::node][sfLedgerEntryType.jsonName] ==
jss::LedgerHashes);
}
// negative tests
runLedgerEntryTest(env, jss::hashes);
}
void
testNFTokenOffer()
{
testcase("NFT Offer");
using namespace test::jtx;
Env env{*this};
// positive test
Account const issuer{"issuer"};
Account const buyer{"buyer"};
env.fund(XRP(1000), issuer, buyer);
uint256 const nftokenID0 =
token::getNextID(env, issuer, 0, tfTransferable);
env(token::mint(issuer, 0), txflags(tfTransferable));
env.close();
uint256 const offerID = keylet::nftoffer(issuer, env.seq(issuer)).key;
env(token::createOffer(issuer, nftokenID0, drops(1)),
token::destination(buyer),
txflags(tfSellNFToken));
{
Json::Value jvParams;
jvParams[jss::nft_offer] = to_string(offerID);
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(
jrr[jss::node][sfLedgerEntryType.jsonName] ==
jss::NFTokenOffer);
BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == issuer.human());
BEAST_EXPECT(
jrr[jss::node][sfNFTokenID.jsonName] == to_string(nftokenID0));
BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "1");
}
// negative tests
runLedgerEntryTest(env, jss::nft_offer);
}
void
testNFTokenPage()
{
testcase("NFT Page");
using namespace test::jtx;
Env env{*this};
// positive test
Account const issuer{"issuer"};
env.fund(XRP(1000), issuer);
env(token::mint(issuer, 0), txflags(tfTransferable));
env.close();
auto const nftpage = keylet::nftpage_max(issuer);
BEAST_EXPECT(env.le(nftpage) != nullptr);
{
Json::Value jvParams;
jvParams[jss::nft_page] = to_string(nftpage.key);
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(
jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
}
// negative tests
runLedgerEntryTest(env, jss::nft_page);
}
void
testNegativeUNL()
{
testcase("Negative UNL");
using namespace test::jtx;
Env env{*this};
// positive test
{
Keylet const keylet = keylet::negativeUNL();
// easier to hack an object into the ledger than generate it
// legitimately
{
auto const nUNL = [&](OpenView& view, beast::Journal) -> bool {
auto const sle = std::make_shared<SLE>(keylet);
// Create DisabledValidators array
STArray disabledValidators;
auto disabledValidator =
STObject::makeInnerObject(sfDisabledValidator);
auto pubKeyBlob = strUnHex(
"ED58F6770DB5DD77E59D28CB650EC3816E2FC95021BB56E720C9A1"
"2DA79C58A3AB");
disabledValidator.setFieldVL(sfPublicKey, *pubKeyBlob);
disabledValidator.setFieldU32(
sfFirstLedgerSequence, 91371264);
disabledValidators.push_back(std::move(disabledValidator));
sle->setFieldArray(
sfDisabledValidators, disabledValidators);
sle->setFieldH256(
sfPreviousTxnID,
uint256::fromVoid("8D47FFE664BE6C335108DF689537625855A6"
"A95160CC6D351341B9"
"2624D9C5E3"));
sle->setFieldU32(sfPreviousTxnLgrSeq, 91442944);
view.rawInsert(sle);
return true;
};
env.app().openLedger().modify(nUNL);
}
Json::Value jvParams;
jvParams[jss::nunl] = to_string(keylet.key);
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(
jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NegativeUNL);
}
// negative tests
runLedgerEntryTest(env, jss::nunl);
}
void
testOffer()
{
testcase("Offer");
using namespace test::jtx;
@@ -1413,7 +1738,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryPayChan()
testPayChan()
{
testcase("Pay Chan");
using namespace test::jtx;
@@ -1475,7 +1800,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryRippleState()
testRippleState()
{
testcase("RippleState");
using namespace test::jtx;
@@ -1626,7 +1951,16 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryTicket()
testSignerList()
{
testcase("Signer List");
using namespace test::jtx;
Env env{*this};
runLedgerEntryTest(env, jss::signer_list);
}
void
testTicket()
{
testcase("Ticket");
using namespace test::jtx;
@@ -1711,7 +2045,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryDID()
testDID()
{
testcase("DID");
using namespace test::jtx;
@@ -1848,7 +2182,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryMPT()
testMPT()
{
testcase("MPT");
using namespace test::jtx;
@@ -1931,7 +2265,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryPermissionedDomain()
testPermissionedDomain()
{
testcase("PermissionedDomain");
@@ -2010,7 +2344,7 @@ class LedgerEntry_test : public beast::unit_test::suite
}
void
testLedgerEntryCLI()
testCLI()
{
testcase("command-line");
using namespace test::jtx;
@@ -2040,25 +2374,33 @@ public:
void
run() override
{
testLedgerEntryInvalid();
testLedgerEntryAccountRoot();
testLedgerEntryCheck();
testLedgerEntryCredentials();
testLedgerEntryDelegate();
testLedgerEntryDepositPreauth();
testLedgerEntryDepositPreauthCred();
testLedgerEntryDirectory();
testLedgerEntryEscrow();
testLedgerEntryOffer();
testLedgerEntryPayChan();
testLedgerEntryRippleState();
testLedgerEntryTicket();
testLedgerEntryDID();
testInvalid();
testAccountRoot();
testAmendments();
testAMM();
testCheck();
testCredentials();
testDelegate();
testDepositPreauth();
testDepositPreauthCred();
testDirectory();
testEscrow();
testFeeSettings();
testLedgerHashes();
testNFTokenOffer();
testNFTokenPage();
testNegativeUNL();
testOffer();
testPayChan();
testRippleState();
testSignerList();
testTicket();
testDID();
testInvalidOracleLedgerEntry();
testOracleLedgerEntry();
testLedgerEntryMPT();
testLedgerEntryPermissionedDomain();
testLedgerEntryCLI();
testMPT();
testPermissionedDomain();
testCLI();
}
};
@@ -2086,7 +2428,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite,
}
void
testLedgerEntryBridge()
testBridge()
{
testcase("ledger_entry: bridge");
using namespace test::jtx;
@@ -2177,7 +2519,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite,
}
void
testLedgerEntryClaimID()
testClaimID()
{
testcase("ledger_entry: xchain_claim_id");
using namespace test::jtx;
@@ -2235,7 +2577,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite,
}
void
testLedgerEntryCreateAccountClaimID()
testCreateAccountClaimID()
{
testcase("ledger_entry: xchain_create_account_claim_id");
using namespace test::jtx;
@@ -2362,9 +2704,9 @@ public:
void
run() override
{
testLedgerEntryBridge();
testLedgerEntryClaimID();
testLedgerEntryCreateAccountClaimID();
testBridge();
testClaimID();
testCreateAccountClaimID();
}
};