mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
Add features to server_definitions (#190)
* add features to `server_definitions` * clang-format * Update RPCCall.cpp * only return features without params * clang-format * include features in hashed value * clang-format * rework features addition to server_defintions to be cached at flag ledgers * fix clang, duplicate hash key --------- Co-authored-by: Richard Holland <richard.holland@starstone.co.nz>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/AmendmentTable.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/reporting/P2pProxy.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
@@ -368,14 +369,13 @@ private:
|
||||
}
|
||||
|
||||
ret[jss::native_currency_code] = systemCurrencyCode();
|
||||
|
||||
// generate hash
|
||||
{
|
||||
const std::string out = Json::FastWriter().write(ret);
|
||||
defsHash =
|
||||
ripple::sha512Half(ripple::Slice{out.data(), out.size()});
|
||||
ret[jss::hash] = to_string(*defsHash);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -385,10 +385,18 @@ private:
|
||||
public:
|
||||
Definitions() : defs(generate()){};
|
||||
|
||||
bool
|
||||
hashMatches(uint256 hash) const
|
||||
uint256 const&
|
||||
getHash() const
|
||||
{
|
||||
return defsHash && *defsHash == hash;
|
||||
if (!defsHash)
|
||||
{
|
||||
// should be unreachable
|
||||
// if this does happen we don't want 0 xor 0 so use a random value
|
||||
// here
|
||||
return uint256(
|
||||
"DF4220E93ADC6F5569063A01B4DC79F8DB9553B6A3222ADE23DEA0");
|
||||
}
|
||||
return *defsHash;
|
||||
}
|
||||
|
||||
Json::Value const&
|
||||
@@ -403,23 +411,62 @@ doServerDefinitions(RPC::JsonContext& context)
|
||||
{
|
||||
auto& params = context.params;
|
||||
|
||||
uint256 hash;
|
||||
uint256 reqHash;
|
||||
if (params.isMember(jss::hash))
|
||||
{
|
||||
if (!params[jss::hash].isString() ||
|
||||
!hash.parseHex(params[jss::hash].asString()))
|
||||
!reqHash.parseHex(params[jss::hash].asString()))
|
||||
return RPC::invalid_field_error(jss::hash);
|
||||
}
|
||||
|
||||
uint32_t curLgrSeq = context.ledgerMaster.getValidatedLedger()->info().seq;
|
||||
|
||||
// static values used for cache
|
||||
static uint32_t lastGenerated = 0; // last ledger seq it was generated
|
||||
static Json::Value lastFeatures{
|
||||
Json::objectValue}; // the actual features JSON last generated
|
||||
static uint256 lastFeatureHash; // the hash of the features JSON last time
|
||||
// it was generated
|
||||
|
||||
// if a flag ledger has passed since it was last generated, regenerate it,
|
||||
// update the cache above
|
||||
if (curLgrSeq > ((lastGenerated >> 8) + 1) << 8 || lastGenerated == 0)
|
||||
{
|
||||
majorityAmendments_t majorities;
|
||||
if (auto const valLedger = context.ledgerMaster.getValidatedLedger())
|
||||
majorities = getMajorityAmendments(*valLedger);
|
||||
auto& table = context.app.getAmendmentTable();
|
||||
auto features = table.getJson();
|
||||
for (auto const& [h, t] : majorities)
|
||||
features[to_string(h)][jss::majority] =
|
||||
t.time_since_epoch().count();
|
||||
|
||||
lastFeatures = features;
|
||||
{
|
||||
const std::string out = Json::FastWriter().write(features);
|
||||
lastFeatureHash =
|
||||
ripple::sha512Half(ripple::Slice{out.data(), out.size()});
|
||||
}
|
||||
}
|
||||
|
||||
static const Definitions defs{};
|
||||
if (defs.hashMatches(hash))
|
||||
|
||||
// the hash is the xor of the two parts
|
||||
uint256 retHash = lastFeatureHash ^ defs.getHash();
|
||||
|
||||
if (reqHash == retHash)
|
||||
{
|
||||
Json::Value jv = Json::objectValue;
|
||||
jv[jss::hash] = to_string(hash);
|
||||
jv[jss::hash] = to_string(retHash);
|
||||
return jv;
|
||||
}
|
||||
|
||||
return defs();
|
||||
// definitions
|
||||
Json::Value ret = defs();
|
||||
ret[jss::hash] = to_string(retHash);
|
||||
ret[jss::features] = lastFeatures;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/misc/AmendmentTable.h>
|
||||
#include <ripple/beast/unit_test.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <test/jtx.h>
|
||||
|
||||
@@ -46,8 +48,10 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
testServerDefinitions()
|
||||
testDefinitions(FeatureBitset features)
|
||||
{
|
||||
testcase("Definitions");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
std::string jsonLE = R"json({
|
||||
@@ -358,10 +362,277 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDefitionsHash(FeatureBitset features)
|
||||
{
|
||||
testcase("Definitions Hash");
|
||||
|
||||
using namespace test::jtx;
|
||||
// test providing the same hash
|
||||
{
|
||||
Env env(*this, features);
|
||||
auto const firstResult = env.rpc("server_definitions");
|
||||
auto const hash = firstResult[jss::result][jss::hash].asString();
|
||||
Json::Value params;
|
||||
params[jss::hash] = hash;
|
||||
auto const result =
|
||||
env.rpc("json", "server_definitions", to_string(params));
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::result][jss::status] == "success");
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::FIELDS));
|
||||
BEAST_EXPECT(
|
||||
!result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
|
||||
BEAST_EXPECT(
|
||||
!result[jss::result].isMember(jss::TRANSACTION_RESULTS));
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_TYPES));
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::TYPES));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
|
||||
}
|
||||
|
||||
// test providing a different hash
|
||||
{
|
||||
Env env(*this, features);
|
||||
std::string const hash =
|
||||
"54296160385A27154BFA70A239DD8E8FD4CC2DB7BA32D970BA3A5B132CF749"
|
||||
"D1";
|
||||
Json::Value params;
|
||||
params[jss::hash] = hash;
|
||||
auto const result =
|
||||
env.rpc("json", "server_definitions", to_string(params));
|
||||
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
|
||||
BEAST_EXPECT(result[jss::result][jss::status] == "success");
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
|
||||
BEAST_EXPECT(
|
||||
result[jss::result].isMember(jss::TRANSACTION_RESULTS));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::TYPES));
|
||||
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testNoParams(FeatureBitset features)
|
||||
{
|
||||
testcase("No Params, None Enabled");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
|
||||
std::map<std::string, VoteBehavior> const& votes =
|
||||
ripple::detail::supportedAmendments();
|
||||
|
||||
auto jrr = env.rpc("server_definitions")[jss::result];
|
||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||
return;
|
||||
for (auto const& feature : jrr[jss::features])
|
||||
{
|
||||
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
||||
return;
|
||||
// default config - so all should be disabled, and
|
||||
// supported. Some may be vetoed.
|
||||
bool expectVeto =
|
||||
(votes.at(feature[jss::name].asString()) ==
|
||||
VoteBehavior::DefaultNo);
|
||||
bool expectObsolete =
|
||||
(votes.at(feature[jss::name].asString()) ==
|
||||
VoteBehavior::Obsolete);
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::enabled) &&
|
||||
!feature[jss::enabled].asBool(),
|
||||
feature[jss::name].asString() + " enabled");
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::vetoed) &&
|
||||
feature[jss::vetoed].isBool() == !expectObsolete &&
|
||||
(!feature[jss::vetoed].isBool() ||
|
||||
feature[jss::vetoed].asBool() == expectVeto) &&
|
||||
(feature[jss::vetoed].isBool() ||
|
||||
feature[jss::vetoed].asString() == "Obsolete"),
|
||||
feature[jss::name].asString() + " vetoed");
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::supported) &&
|
||||
feature[jss::supported].asBool(),
|
||||
feature[jss::name].asString() + " supported");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSomeEnabled(FeatureBitset features)
|
||||
{
|
||||
testcase("No Params, Some Enabled");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{
|
||||
*this, FeatureBitset(featureDepositAuth, featureDepositPreauth)};
|
||||
|
||||
std::map<std::string, VoteBehavior> const& votes =
|
||||
ripple::detail::supportedAmendments();
|
||||
|
||||
auto jrr = env.rpc("server_definitions")[jss::result];
|
||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||
return;
|
||||
for (auto it = jrr[jss::features].begin();
|
||||
it != jrr[jss::features].end();
|
||||
++it)
|
||||
{
|
||||
uint256 id;
|
||||
(void)id.parseHex(it.key().asString().c_str());
|
||||
if (!BEAST_EXPECT((*it).isMember(jss::name)))
|
||||
return;
|
||||
bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
|
||||
bool expectSupported =
|
||||
env.app().getAmendmentTable().isSupported(id);
|
||||
bool expectVeto =
|
||||
(votes.at((*it)[jss::name].asString()) ==
|
||||
VoteBehavior::DefaultNo);
|
||||
bool expectObsolete =
|
||||
(votes.at((*it)[jss::name].asString()) ==
|
||||
VoteBehavior::Obsolete);
|
||||
BEAST_EXPECTS(
|
||||
(*it).isMember(jss::enabled) &&
|
||||
(*it)[jss::enabled].asBool() == expectEnabled,
|
||||
(*it)[jss::name].asString() + " enabled");
|
||||
if (expectEnabled)
|
||||
BEAST_EXPECTS(
|
||||
!(*it).isMember(jss::vetoed),
|
||||
(*it)[jss::name].asString() + " vetoed");
|
||||
else
|
||||
BEAST_EXPECTS(
|
||||
(*it).isMember(jss::vetoed) &&
|
||||
(*it)[jss::vetoed].isBool() == !expectObsolete &&
|
||||
(!(*it)[jss::vetoed].isBool() ||
|
||||
(*it)[jss::vetoed].asBool() == expectVeto) &&
|
||||
((*it)[jss::vetoed].isBool() ||
|
||||
(*it)[jss::vetoed].asString() == "Obsolete"),
|
||||
(*it)[jss::name].asString() + " vetoed");
|
||||
BEAST_EXPECTS(
|
||||
(*it).isMember(jss::supported) &&
|
||||
(*it)[jss::supported].asBool() == expectSupported,
|
||||
(*it)[jss::name].asString() + " supported");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithMajorities(FeatureBitset features)
|
||||
{
|
||||
testcase("With Majorities");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this, envconfig(validator, "")};
|
||||
|
||||
auto jrr = env.rpc("server_definitions")[jss::result];
|
||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||
return;
|
||||
|
||||
// at this point, there are no majorities so no fields related to
|
||||
// amendment voting
|
||||
for (auto const& feature : jrr[jss::features])
|
||||
{
|
||||
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
||||
return;
|
||||
BEAST_EXPECTS(
|
||||
!feature.isMember(jss::majority),
|
||||
feature[jss::name].asString() + " majority");
|
||||
BEAST_EXPECTS(
|
||||
!feature.isMember(jss::count),
|
||||
feature[jss::name].asString() + " count");
|
||||
BEAST_EXPECTS(
|
||||
!feature.isMember(jss::threshold),
|
||||
feature[jss::name].asString() + " threshold");
|
||||
BEAST_EXPECTS(
|
||||
!feature.isMember(jss::validations),
|
||||
feature[jss::name].asString() + " validations");
|
||||
BEAST_EXPECTS(
|
||||
!feature.isMember(jss::vote),
|
||||
feature[jss::name].asString() + " vote");
|
||||
}
|
||||
|
||||
auto majorities = getMajorityAmendments(*env.closed());
|
||||
if (!BEAST_EXPECT(majorities.empty()))
|
||||
return;
|
||||
|
||||
// close ledgers until the amendments show up.
|
||||
for (auto i = 0; i <= 256; ++i)
|
||||
{
|
||||
env.close();
|
||||
majorities = getMajorityAmendments(*env.closed());
|
||||
if (!majorities.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
// There should be at least 5 amendments. Don't do exact comparison
|
||||
// to avoid maintenance as more amendments are added in the future.
|
||||
BEAST_EXPECT(majorities.size() >= 5);
|
||||
std::map<std::string, VoteBehavior> const& votes =
|
||||
ripple::detail::supportedAmendments();
|
||||
|
||||
jrr = env.rpc("server_definitions")[jss::result];
|
||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||
return;
|
||||
for (auto const& feature : jrr[jss::features])
|
||||
{
|
||||
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
||||
return;
|
||||
bool expectVeto =
|
||||
(votes.at(feature[jss::name].asString()) ==
|
||||
VoteBehavior::DefaultNo);
|
||||
bool expectObsolete =
|
||||
(votes.at(feature[jss::name].asString()) ==
|
||||
VoteBehavior::Obsolete);
|
||||
BEAST_EXPECTS(
|
||||
(expectVeto || expectObsolete) ^
|
||||
feature.isMember(jss::majority),
|
||||
feature[jss::name].asString() + " majority");
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::vetoed) &&
|
||||
feature[jss::vetoed].isBool() == !expectObsolete &&
|
||||
(!feature[jss::vetoed].isBool() ||
|
||||
feature[jss::vetoed].asBool() == expectVeto) &&
|
||||
(feature[jss::vetoed].isBool() ||
|
||||
feature[jss::vetoed].asString() == "Obsolete"),
|
||||
feature[jss::name].asString() + " vetoed");
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::count),
|
||||
feature[jss::name].asString() + " count");
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::threshold),
|
||||
feature[jss::name].asString() + " threshold");
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::validations),
|
||||
feature[jss::name].asString() + " validations");
|
||||
BEAST_EXPECT(
|
||||
feature[jss::count] ==
|
||||
((expectVeto || expectObsolete) ? 0 : 1));
|
||||
BEAST_EXPECT(feature[jss::threshold] == 1);
|
||||
BEAST_EXPECT(feature[jss::validations] == 1);
|
||||
BEAST_EXPECTS(
|
||||
expectVeto || expectObsolete || feature[jss::majority] == 2540,
|
||||
"Majority: " + feature[jss::majority].asString());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testServerFeatures(FeatureBitset features)
|
||||
{
|
||||
testNoParams(features);
|
||||
testSomeEnabled(features);
|
||||
testWithMajorities(features);
|
||||
}
|
||||
|
||||
void
|
||||
testServerDefinitions(FeatureBitset features)
|
||||
{
|
||||
testDefinitions(features);
|
||||
testDefitionsHash(features);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testServerDefinitions();
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testServerDefinitions(sa);
|
||||
testServerFeatures(sa);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user