mirror of
				https://github.com/Xahau/xahaud.git
				synced 2025-11-04 10:45:50 +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