From f13668371e9b638cb2ffc274340048e66e6d5447 Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Tue, 3 Nov 2015 17:31:18 -0800 Subject: [PATCH] Amendment RPC enhancements: * RPC command to veto/unveto * Store votes * Add vote information to JSON * Add ledger majority information to JSON * Config section for vetos --- Builds/VisualStudio2015/RippleD.vcxproj | 12 +- .../VisualStudio2015/RippleD.vcxproj.filters | 9 +- .../app/ledger/impl/LedgerConsensusImp.cpp | 27 +- src/ripple/app/main/Amendments.cpp | 48 ++ src/ripple/app/main/Application.cpp | 32 +- src/ripple/app/misc/AmendmentTable.h | 188 +---- src/ripple/app/misc/AmendmentTableImpl.cpp | 558 -------------- src/ripple/app/misc/Validations.cpp | 1 - src/ripple/app/misc/impl/AmendmentTable.cpp | 585 +++++++++++++++ src/ripple/app/tests/AmendmentTable.test.cpp | 686 ++++++++---------- src/ripple/core/ConfigSections.h | 1 + src/ripple/ledger/View.h | 3 +- src/ripple/ledger/impl/View.cpp | 39 +- src/ripple/net/impl/RPCCall.cpp | 19 +- src/ripple/protocol/JsonFields.h | 2 + src/ripple/rpc/handlers/Feature1.cpp | 55 +- src/ripple/unity/app_main.cpp | 1 + src/ripple/unity/app_misc.cpp | 2 +- 18 files changed, 1120 insertions(+), 1148 deletions(-) create mode 100644 src/ripple/app/main/Amendments.cpp delete mode 100644 src/ripple/app/misc/AmendmentTableImpl.cpp create mode 100644 src/ripple/app/misc/impl/AmendmentTable.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index f83e0351ad..764dc947d4 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -1258,6 +1258,10 @@ + + True + True + True True @@ -1308,10 +1312,6 @@ - - True - True - True True @@ -1336,6 +1336,10 @@ + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index a93564270b..09f6028f03 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -1857,6 +1857,9 @@ ripple\app\ledger + + ripple\app\main + ripple\app\main @@ -1908,9 +1911,6 @@ ripple\app\misc - - ripple\app\misc - ripple\app\misc @@ -1935,6 +1935,9 @@ ripple\app\misc\impl + + ripple\app\misc\impl + ripple\app\misc\impl diff --git a/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp b/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp index 1dd4f68b8a..a4202eb7b2 100644 --- a/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp +++ b/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp @@ -1401,11 +1401,28 @@ void LedgerConsensusImp::takeInitialPosition ( && ((mPreviousLedger->info().seq % 256) == 0)) { // previous ledger was flag ledger, add pseudo-transactions - ValidationSet parentSet = app_.getValidations().getValidations ( - mPreviousLedger->info().parentHash); - m_feeVote.doVoting (mPreviousLedger, parentSet, initialSet); - app_.getAmendmentTable ().doVoting ( - mPreviousLedger, parentSet, initialSet); + auto const validations = + app_.getValidations().getValidations ( + mPreviousLedger->info().parentHash); + + auto const count = std::count_if ( + validations.begin(), validations.end(), + [](auto const& v) + { + return v.second->isTrusted(); + }); + + if (count >= ledgerMaster_.getMinValidations()) + { + m_feeVote.doVoting ( + mPreviousLedger, + validations, + initialSet); + app_.getAmendmentTable ().doVoting ( + mPreviousLedger, + validations, + initialSet); + } } // Set should be immutable snapshot diff --git a/src/ripple/app/main/Amendments.cpp b/src/ripple/app/main/Amendments.cpp new file mode 100644 index 0000000000..daf6de6b87 --- /dev/null +++ b/src/ripple/app/main/Amendments.cpp @@ -0,0 +1,48 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +namespace ripple { + +namespace detail { + +/** Amendments that this server supports and enables by default */ +std::vector +preEnabledAmendments () +{ + return + { + }; +} + +/** Amendments that this server supports, but doesn't enable by default */ +std::vector +supportedAmendments () +{ + return + { + }; +} + +} + +} + diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index aad3416824..387be6fad5 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -210,6 +210,15 @@ public: } }; + +/** Amendments that this server supports and enables by default */ +std::vector +preEnabledAmendments (); + +/** Amendments that this server supports, but doesn't enable by default */ +std::vector +supportedAmendments (); + } // detail //------------------------------------------------------------------------------ @@ -453,10 +462,6 @@ public: , serverHandler_ (make_ServerHandler (*this, *m_networkOPs, get_io_service (), *m_jobQueue, *m_networkOPs, *m_resourceManager, *m_collectorManager)) - , m_amendmentTable (make_AmendmentTable - (weeks(2), MAJORITY_FRACTION, - logs_->journal("AmendmentTable"))) - , mFeeTrack (std::make_unique(logs_->journal("LoadManager"))) , mHashRouter (std::make_unique( @@ -962,8 +967,23 @@ void ApplicationImp::setup() if (!config_->RUN_STANDALONE) updateTables (); - m_amendmentTable->addInitial ( - config_->section (SECTION_AMENDMENTS)); + // Configure the amendments the server supports + { + Section supportedAmendments ("Supported Amendments"); + supportedAmendments.append (detail::supportedAmendments ()); + + Section enabledAmendments = config_->section (SECTION_AMENDMENTS); + enabledAmendments.append (detail::preEnabledAmendments ()); + + m_amendmentTable = make_AmendmentTable ( + weeks(2), + MAJORITY_FRACTION, + supportedAmendments, + enabledAmendments, + config_->section (SECTION_VETO_AMENDMENTS), + logs_->journal("Amendments")); + } + Pathfinder::initPathTable(); m_ledgerMaster->setMinValidations ( diff --git a/src/ripple/app/misc/AmendmentTable.h b/src/ripple/app/misc/AmendmentTable.h index c83f5277f7..c3ba969fdf 100644 --- a/src/ripple/app/misc/AmendmentTable.h +++ b/src/ripple/app/misc/AmendmentTable.h @@ -22,135 +22,11 @@ #include #include +#include #include namespace ripple { -/** The status of all amendments requested in a given window. */ -class AmendmentSet -{ -public: - NetClock::time_point mCloseTime; - int mTrustedValidations; // number of trusted validations - hash_map mVotes; // yes votes by amendment - - AmendmentSet (NetClock::time_point ct) : mCloseTime (ct), mTrustedValidations (0) - { - ; - } - - void addVoter () - { - ++mTrustedValidations; - } - - void addVote (uint256 const& amendment) - { - ++mVotes[amendment]; - } - - int count (uint256 const& amendment) - { - auto const& it = mVotes.find (amendment); - return (it == mVotes.end()) ? 0 : it->second; - } -}; - -/** 256-bit Id and human friendly name of an amendment. -*/ -class AmendmentName final -{ -private: - uint256 mId; - // Keep the hex string around for error reporting - std::string mHexString; - std::string mFriendlyName; - bool mValid{false}; - -public: - AmendmentName () = default; - AmendmentName (AmendmentName const& rhs) = default; - // AmendmentName (AmendmentName&& rhs) = default; // MSVS not supported - AmendmentName (uint256 const& id, std::string friendlyName) - : mId (id), mFriendlyName (std::move (friendlyName)), mValid (true) - { - } - AmendmentName (std::string id, std::string friendlyName) - : mHexString (std::move (id)), mFriendlyName (std::move (friendlyName)) - { - mValid = mId.SetHex (mHexString); - } - bool valid () const - { - return mValid; - } - uint256 const& id () const - { - return mId; - } - std::string const& hexString () const - { - return mHexString; - } - std::string const& friendlyName () const - { - return mFriendlyName; - } -}; - -/** Current state of an amendment. - Tells if a amendment is supported, enabled or vetoed. A vetoed amendment - means the node will never announce its support. -*/ -class AmendmentState -{ -public: - bool mVetoed{false}; // We don't want this amendment enabled - bool mEnabled{false}; - bool mSupported{false}; - bool mDefault{false}; // Include in genesis ledger - - std::string mFriendlyName; - - AmendmentState () = default; - - void setVeto () - { - mVetoed = true; - } - void setDefault () - { - mDefault = true; - } - bool isDefault () - { - return mDefault; - } - bool isSupported () - { - return mSupported; - } - bool isVetoed () - { - return mVetoed; - } - bool isEnabled () - { - return mEnabled; - } - std::string const& getFiendlyName () - { - return mFriendlyName; - } - void setFriendlyName (std::string const& n) - { - mFriendlyName = n; - } -}; - -class Section; - - /** The amendment table stores the list of enabled and potential amendments. Individuals amendments are voted on by validators during the consensus process. @@ -158,30 +34,9 @@ class Section; class AmendmentTable { public: - /** Create a new AmendmentTable. + virtual ~AmendmentTable() = default; - @param majorityTime the number of seconds an amendment must hold a majority - before we're willing to vote yes on it. - @param majorityFraction ratio, out of 256, of servers that must say - they want an amendment before we consider it to - have a majority. - @param journal - */ - - virtual ~AmendmentTable() { } - - /** - @param section the config section of initial amendments - */ - virtual void addInitial (Section const& section) = 0; - - /** Add an amendment to the AmendmentTable - - @throw will throw if the name parameter is not valid - */ - virtual void addKnown (AmendmentName const& name) = 0; - - virtual uint256 get (std::string const& name) = 0; + virtual uint256 find (std::string const& name) = 0; virtual bool veto (uint256 const& amendment) = 0; virtual bool unVeto (uint256 const& amendment) = 0; @@ -192,15 +47,6 @@ public: virtual bool isEnabled (uint256 const& amendment) = 0; virtual bool isSupported (uint256 const& amendment) = 0; - /** Enable only the specified amendments. - Other amendments in the table will be set to disabled. - */ - virtual void setEnabled (const std::vector& amendments) = 0; - /** Support only the specified amendments. - Other amendments in the table will be set to unsupported. - */ - virtual void setSupported (const std::vector& amendments) = 0; - virtual Json::Value getJson (int) = 0; /** Returns a Json::objectValue. */ @@ -221,22 +67,23 @@ public: needValidatedLedger (LedgerIndex seq) = 0; virtual void - doValidatedLedger (LedgerIndex ledgerSeq, enabledAmendments_t enabled) = 0; + doValidatedLedger ( + LedgerIndex ledgerSeq, + std::set const& enabled) = 0; // Called by the consensus code when we need to // inject pseudo-transactions virtual std::map doVoting ( NetClock::time_point closeTime, - enabledAmendments_t const& enabledAmendments, + std::set const& enabledAmendments, majorityAmendments_t const& majorityAmendments, ValidationSet const& valSet) = 0; // Called by the consensus code when we need to // add feature entries to a validation virtual std::vector - doValidation (enabledAmendments_t const&) = 0; - + doValidation (std::set const& enabled) = 0; // The two function below adapt the API callers expect to the // internal amendment table API. This allows the amendment @@ -255,7 +102,6 @@ public: STVector256 (sfAmendments, ourAmendments)); } - void doVoting ( std::shared_ptr const& lastClosedLedger, @@ -269,6 +115,7 @@ public: getMajorityAmendments(*lastClosedLedger), parentValidations); +#if RIPPLE_PROPOSE_AMENDMENTS // Inject appropriate pseudo-transactions for (auto const& it : actions) { @@ -286,14 +133,14 @@ public: Serializer s; amendTx.add (s); - #if ! RIPPLE_PROPOSE_AMENDMENTS - return; - #endif - - uint256 txID = amendTx.getTransactionID(); - auto tItem = std::make_shared (txID, s.peekData()); - initialPosition->addGiveItem (tItem, true, false); + initialPosition->addGiveItem ( + std::make_shared ( + amendTx.getTransactionID(), + s.peekData()), + true, + false); } +#endif } }; @@ -301,6 +148,9 @@ public: std::unique_ptr make_AmendmentTable ( std::chrono::seconds majorityTime, int majorityFraction, + Section const& supported, + Section const& enabled, + Section const& vetoed, beast::Journal journal); } // ripple diff --git a/src/ripple/app/misc/AmendmentTableImpl.cpp b/src/ripple/app/misc/AmendmentTableImpl.cpp deleted file mode 100644 index 72f079d2c3..0000000000 --- a/src/ripple/app/misc/AmendmentTableImpl.cpp +++ /dev/null @@ -1,558 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { -/** Track the list of "amendments" - - An "amendment" is an option that can affect transaction processing rules. - Amendments are proposed and then adopted or rejected by the network. An - Amendment is uniquely identified by its AmendmentID, a 256-bit key. -*/ - -class AmendmentTableImpl final : public AmendmentTable -{ -protected: - using amendmentMap_t = hash_map; - using amendmentList_t = hash_set; - - std::mutex mLock; - - amendmentMap_t m_amendmentMap; - std::uint32_t m_lastUpdateSeq; - - std::chrono::seconds m_majorityTime; // Seconds an amendment must hold a majority - int mMajorityFraction; // 256 = 100% - beast::Journal m_journal; - - AmendmentState& getCreate (uint256 const& amendment); - AmendmentState* getExisting (uint256 const& amendment); - void setJson (Json::Value& v, const AmendmentState&); - -public: - AmendmentTableImpl ( - std::chrono::seconds majorityTime, - int majorityFraction, - beast::Journal journal) - : m_lastUpdateSeq (0) - , m_majorityTime (majorityTime) - , mMajorityFraction (majorityFraction) - , m_journal (journal) - { - } - - void addInitial (Section const& section) override; - - void addKnown (AmendmentName const& name) override; - - uint256 get (std::string const& name) override; - - bool veto (uint256 const& amendment) override; - bool unVeto (uint256 const& amendment) override; - - bool enable (uint256 const& amendment) override; - bool disable (uint256 const& amendment) override; - - bool isEnabled (uint256 const& amendment) override; - bool isSupported (uint256 const& amendment) override; - - void setEnabled (const std::vector& amendments) override; - void setSupported (const std::vector& amendments) override; - - Json::Value getJson (int) override; - Json::Value getJson (uint256 const&) override; - - bool needValidatedLedger (LedgerIndex seq) override; - - void doValidatedLedger (LedgerIndex seq, - enabledAmendments_t enabled) override; - - std::vector - doValidation (enabledAmendments_t const& enabledAmendments) - override; - - std::map doVoting (NetClock::time_point closeTime, - enabledAmendments_t const& enabledAmendments, - majorityAmendments_t const& majorityAmendments, - ValidationSet const& validations) override; - - amendmentList_t getVetoed(); - amendmentList_t getEnabled(); - - // Amendments we support, do not veto, and are not enabled - amendmentList_t getDesired (enabledAmendments_t const&); -}; - -namespace detail -{ -/** preEnabledAmendments is a static collection of amendments that are are - enabled at build time. - - Add amendments to this collection at build time to enable them on this - server. -*/ - -std::vector const preEnabledAmendments; -} - -void -AmendmentTableImpl::addInitial (Section const& section) -{ - for (auto const& a : detail::preEnabledAmendments) - { - if (!a.valid ()) - { - std::string const errorMsg = - (boost::format ( - "preEnabledAmendments contains an invalid hash (expected " - "a hex number). Value was: %1%") % - a.hexString ()).str (); - Throw (errorMsg); - } - } - - std::vector toAdd (detail::preEnabledAmendments); - - { - // add the amendments from the config file - int const numExpectedToks = 2; - for (auto const& line : section.lines ()) - { - boost::tokenizer<> tokenizer (line); - std::vector tokens (tokenizer.begin (), - tokenizer.end ()); - if (tokens.size () != numExpectedToks) - { - std::string const errorMsg = - (boost::format ( - "The %1% section in the config file expects %2% " - "items. Found %3%. Line was: %4%") % - SECTION_AMENDMENTS % numExpectedToks % tokens.size () % - line).str (); - Throw (errorMsg); - } - - toAdd.emplace_back (std::move (tokens[0]), std::move (tokens[1])); - if (!toAdd.back ().valid ()) - { - std::string const errorMsg = - (boost::format ( - "%1% is not a valid hash. Expected a hex " - "number. In config setcion: %2%. Line was: " - "%3%") % - toAdd.back ().hexString () % SECTION_AMENDMENTS % - line).str (); - Throw (errorMsg); - } - } - } - - for (auto const& a : toAdd) - { - addKnown (a); - enable (a.id ()); - } -} - -AmendmentState& -AmendmentTableImpl::getCreate (uint256 const& amendmentHash) -{ - // call with the mutex held - auto iter (m_amendmentMap.find (amendmentHash)); - - if (iter == m_amendmentMap.end()) - { - AmendmentState& amendment = m_amendmentMap[amendmentHash]; - return amendment; - } - - return iter->second; -} - -AmendmentState* -AmendmentTableImpl::getExisting (uint256 const& amendmentHash) -{ - // call with the mutex held - auto iter (m_amendmentMap.find (amendmentHash)); - - if (iter == m_amendmentMap.end()) - return nullptr; - - return & (iter->second); -} - -uint256 -AmendmentTableImpl::get (std::string const& name) -{ - std::lock_guard sl (mLock); - - for (auto const& e : m_amendmentMap) - { - if (name == e.second.mFriendlyName) - return e.first; - } - - return uint256 (); -} - -void -AmendmentTableImpl::addKnown (AmendmentName const& name) -{ - if (!name.valid ()) - { - std::string const errorMsg = - (boost::format ( - "addKnown was given an invalid hash (expected a hex number). " - "Value was: %1%") % - name.hexString ()).str (); - Throw (errorMsg); - } - - std::lock_guard sl (mLock); - AmendmentState& amendment = getCreate (name.id ()); - - if (!name.friendlyName ().empty ()) - amendment.setFriendlyName (name.friendlyName ()); - - amendment.mVetoed = false; - amendment.mSupported = true; -} - -bool -AmendmentTableImpl::veto (uint256 const& amendment) -{ - std::lock_guard sl (mLock); - AmendmentState& s = getCreate (amendment); - - if (s.mVetoed) - return false; - - s.mVetoed = true; - return true; -} - -bool -AmendmentTableImpl::unVeto (uint256 const& amendment) -{ - std::lock_guard sl (mLock); - AmendmentState* s = getExisting (amendment); - - if (!s || !s->mVetoed) - return false; - - s->mVetoed = false; - return true; -} - -bool -AmendmentTableImpl::enable (uint256 const& amendment) -{ - std::lock_guard sl (mLock); - AmendmentState& s = getCreate (amendment); - - if (s.mEnabled) - return false; - - s.mEnabled = true; - return true; -} - -bool -AmendmentTableImpl::disable (uint256 const& amendment) -{ - std::lock_guard sl (mLock); - AmendmentState* s = getExisting (amendment); - - if (!s || !s->mEnabled) - return false; - - s->mEnabled = false; - return true; -} - -bool -AmendmentTableImpl::isEnabled (uint256 const& amendment) -{ - std::lock_guard sl (mLock); - AmendmentState* s = getExisting (amendment); - return s && s->mEnabled; -} - -bool -AmendmentTableImpl::isSupported (uint256 const& amendment) -{ - std::lock_guard sl (mLock); - AmendmentState* s = getExisting (amendment); - return s && s->mSupported; -} - -AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getVetoed () -{ - amendmentList_t ret; - std::lock_guard sl (mLock); - for (auto const& e : m_amendmentMap) - { - if (e.second.mVetoed) - ret.insert (e.first); - } - return ret; -} - -AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getEnabled () -{ - amendmentList_t ret; - std::lock_guard sl (mLock); - for (auto const& e : m_amendmentMap) - { - if (e.second.mEnabled) - ret.insert (e.first); - } - return ret; -} - -AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getDesired (enabledAmendments_t const& enabled) -{ - amendmentList_t ret; - std::lock_guard sl (mLock); - - for (auto const& e : m_amendmentMap) - { - if (e.second.mSupported && ! e.second.mVetoed && - (enabled.count (e.first) == 0)) - ret.insert (e.first); - } - - return ret; -} - -void -AmendmentTableImpl::setEnabled (const std::vector& amendments) -{ - std::lock_guard sl (mLock); - for (auto& e : m_amendmentMap) - { - e.second.mEnabled = false; - } - for (auto const& e : amendments) - { - m_amendmentMap[e].mEnabled = true; - } -} - -void -AmendmentTableImpl::setSupported (const std::vector& amendments) -{ - std::lock_guard sl (mLock); - for (auto &e : m_amendmentMap) - { - e.second.mSupported = false; - } - for (auto const& e : amendments) - { - m_amendmentMap[e].mSupported = true; - } -} - -std::vector -AmendmentTableImpl::doValidation ( - enabledAmendments_t const& enabledAmendments) -{ - auto lAmendments = getDesired (enabledAmendments); - - if (lAmendments.empty()) - return {}; - - std::vector amendments (lAmendments.begin(), lAmendments.end()); - std::sort (amendments.begin (), amendments.end ()); - - return amendments; -} - -std::map -AmendmentTableImpl::doVoting ( - NetClock::time_point closeTime, - enabledAmendments_t const& enabledAmendments, - majorityAmendments_t const& majorityAmendments, - ValidationSet const& valSet) -{ - // LCL must be flag ledger - //assert((lastClosedLedger->info().seq % 256) == 0); - - AmendmentSet amendmentSet (closeTime); - - // process validations for ledger before flag ledger - for (auto const& entry : valSet) - { - auto const& val = *entry.second; - - if (val.isTrusted ()) - { - amendmentSet.addVoter (); - if (val.isFieldPresent (sfAmendments)) - { - for (auto const& amendment : val.getFieldV256 (sfAmendments)) - { - amendmentSet.addVote (amendment); - } - } - } - } - int threshold = - (amendmentSet.mTrustedValidations * mMajorityFraction + 255) / 256; - - if (m_journal.trace) - m_journal.trace << - amendmentSet.mTrustedValidations << " trusted validations, threshold is " - << threshold; - - // Map of amendments to the action to be taken - // for each one. The action is the value of the - // flags in the pseudo-transaction - std::map actions; - - { - std::lock_guard sl (mLock); - - // process all amendments we know of - for (auto const& entry : m_amendmentMap) - { - bool const hasValMajority = amendmentSet.count (entry.first) >= threshold; - - NetClock::time_point majorityTime = {}; - auto const it = majorityAmendments.find (entry.first); - if (it != majorityAmendments.end ()) - majorityTime = it->second; - - // FIXME: Add logging here - if (enabledAmendments.count (entry.first) != 0) - { - // Already enabled, nothing to do - } - else if (hasValMajority && (majorityTime == NetClock::time_point{}) - && (! entry.second.mVetoed)) - { - // Ledger says no majority, validators say yes - actions[entry.first] = tfGotMajority; - } - else if (! hasValMajority && (majorityTime != NetClock::time_point{})) - { - // Ledger says majority, validators say no - actions[entry.first] = tfLostMajority; - } - else if ((majorityTime != NetClock::time_point{}) && - ((majorityTime + m_majorityTime) <= closeTime) && - ! entry.second.mVetoed) - { - // Ledger says majority held - actions[entry.first] = 0; - } - } - } - - return actions; -} - -bool -AmendmentTableImpl::needValidatedLedger (LedgerIndex ledgerSeq) -{ - std::lock_guard sl (mLock); - - // Is there a ledger in which an amendment could have been enabled - // between these two ledger sequences? - - return ((ledgerSeq - 1) / 256) != ((m_lastUpdateSeq - 1) / 256); -} - -void -AmendmentTableImpl::doValidatedLedger (LedgerIndex ledgerSeq, - enabledAmendments_t enabled) -{ - std::lock_guard sl (mLock); - - for (auto& e : m_amendmentMap) - e.second.mEnabled = (enabled.count (e.first) != 0); -} - -Json::Value -AmendmentTableImpl::getJson (int) -{ - Json::Value ret(Json::objectValue); - { - std::lock_guard sl(mLock); - for (auto const& e : m_amendmentMap) - { - setJson (ret[to_string (e.first)] = Json::objectValue, e.second); - } - } - return ret; -} - -void -AmendmentTableImpl::setJson (Json::Value& v, const AmendmentState& fs) -{ - if (!fs.mFriendlyName.empty()) - v[jss::name] = fs.mFriendlyName; - - v[jss::supported] = fs.mSupported; - v[jss::vetoed] = fs.mVetoed; - v[jss::enabled] = fs.mEnabled; -} - -Json::Value -AmendmentTableImpl::getJson (uint256 const& amendmentID) -{ - Json::Value ret = Json::objectValue; - Json::Value& jAmendment = (ret[to_string (amendmentID)] = Json::objectValue); - - { - std::lock_guard sl(mLock); - - AmendmentState& amendmentState = getCreate (amendmentID); - setJson (jAmendment, amendmentState); - } - - return ret; -} - -std::unique_ptr make_AmendmentTable ( - std::chrono::seconds majorityTime, - int majorityFraction, - beast::Journal journal) -{ - return std::make_unique( - majorityTime, majorityFraction, journal); -} - -} // ripple diff --git a/src/ripple/app/misc/Validations.cpp b/src/ripple/app/misc/Validations.cpp index 63d40ad0f5..73b70a007e 100644 --- a/src/ripple/app/misc/Validations.cpp +++ b/src/ripple/app/misc/Validations.cpp @@ -243,7 +243,6 @@ private: JLOG (j_.trace) << "VC: " << ledger << "f:" << full << " p:" << partial; } - int getTrustedValidationCount (uint256 const& ledger) override { int trusted = 0; diff --git a/src/ripple/app/misc/impl/AmendmentTable.cpp b/src/ripple/app/misc/impl/AmendmentTable.cpp new file mode 100644 index 0000000000..7fb2dd32a0 --- /dev/null +++ b/src/ripple/app/misc/impl/AmendmentTable.cpp @@ -0,0 +1,585 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +static +std::vector> +parseSection (Section const& section) +{ + static boost::regex const re1 ( + "^" // start of line + "(?:\\s*)" // whitespace (optional) + "([abcdefABCDEF0-9]{64})" // + "(?:\\s+)" // whitespace + "(\\S+)" // + , boost::regex_constants::optimize + ); + + std::vector> names; + + for (auto const& line : section.lines ()) + { + boost::smatch match; + + if (!boost::regex_match (line, match, re1)) + Throw ( + "Invalid entry '" + line + + "' in [" + section.name () + "]"); + + uint256 id; + + if (!id.SetHexExact (match[1])) + Throw ( + "Invalid amendment ID '" + match[1] + + "' in [" + section.name () + "]"); + + names.push_back (std::make_pair (id, match[2])); + } + + return names; +} + +/** Current state of an amendment. + Tells if a amendment is supported, enabled or vetoed. A vetoed amendment + means the node will never announce its support. +*/ +struct AmendmentState +{ + /** If an amendment is vetoed, a server will not support it */ + bool vetoed = false; + + /** Indicates that the amendment has been enabled. + This is a one-way switch: once an amendment is enabled + it can never be disabled, but it can be superseded by + a subsequent amendment. + */ + bool enabled = false; + + /** Indicates an amendment that this server has code support for. */ + bool supported = false; + + /** The name of this amendment, possibly empty. */ + std::string name; + + AmendmentState () = default; +}; + +/** The status of all amendments requested in a given window. */ +struct AmendmentSet +{ +private: + // How many yes votes each amendment received + hash_map votes_; + +public: + // number of trusted validations + int mTrustedValidations = 0; + + // number of votes needed + int mThreshold = 0; + + AmendmentSet () = default; + + void tally (std::set const& amendments) + { + ++mTrustedValidations; + + for (auto const& amendment : amendments) + ++votes_[amendment]; + } + + int votes (uint256 const& amendment) const + { + auto const& it = votes_.find (amendment); + + if (it == votes_.end()) + return 0; + + return it->second; + } +}; + +//------------------------------------------------------------------------------ + +/** Track the list of "amendments" + + An "amendment" is an option that can affect transaction processing rules. + Amendments are proposed and then adopted or rejected by the network. An + Amendment is uniquely identified by its AmendmentID, a 256-bit key. +*/ +class AmendmentTableImpl final + : public AmendmentTable +{ +protected: + std::mutex mutex_; + + hash_map amendmentMap_; + std::uint32_t lastUpdateSeq_; + + // Time that an amendment must hold a majority for + std::chrono::seconds const majorityTime_; + + // The amount of support that an amendment must receive + // 0 = 0% and 256 = 100% + int const majorityFraction_; + + // The results of the last voting round - may be empty if + // we haven't participated in one yet. + std::unique_ptr lastVote_; + + beast::Journal j_; + + // Finds or creates state + AmendmentState* add (uint256 const& amendment); + + // Finds existing state + AmendmentState* get (uint256 const& amendment); + + void setJson (Json::Value& v, uint256 const& amendment, const AmendmentState&); + +public: + AmendmentTableImpl ( + std::chrono::seconds majorityTime, + int majorityFraction, + Section const& supported, + Section const& enabled, + Section const& vetoed, + beast::Journal journal); + + uint256 find (std::string const& name) override; + + bool veto (uint256 const& amendment) override; + bool unVeto (uint256 const& amendment) override; + + bool enable (uint256 const& amendment) override; + bool disable (uint256 const& amendment) override; + + bool isEnabled (uint256 const& amendment) override; + bool isSupported (uint256 const& amendment) override; + + Json::Value getJson (int) override; + Json::Value getJson (uint256 const&) override; + + bool needValidatedLedger (LedgerIndex seq) override; + + void doValidatedLedger ( + LedgerIndex seq, + std::set const& enabled) override; + + std::vector + doValidation (std::set const& enabledAmendments) override; + + std::map + doVoting ( + NetClock::time_point closeTime, + std::set const& enabledAmendments, + majorityAmendments_t const& majorityAmendments, + ValidationSet const& validations) override; +}; + +//------------------------------------------------------------------------------ + +AmendmentTableImpl::AmendmentTableImpl ( + std::chrono::seconds majorityTime, + int majorityFraction, + Section const& supported, + Section const& enabled, + Section const& vetoed, + beast::Journal journal) + : lastUpdateSeq_ (0) + , majorityTime_ (majorityTime) + , majorityFraction_ (majorityFraction) + , j_ (journal) +{ + assert (majorityFraction_ != 0); + + std::lock_guard sl (mutex_); + + for (auto const& a : parseSection(supported)) + { + auto s = add (a.first); + + if (!a.second.empty ()) + s->name = a.second; + + s->supported = true; + } + + for (auto const& a : parseSection (enabled)) + { + auto s = add (a.first); + + if (!a.second.empty ()) + s->name = a.second; + s->supported = true; + s->enabled = true; + } + + for (auto const& a : parseSection (vetoed)) + { + // Unknown amendments are effectively vetoed already + auto s = get (a.first); + + if (s) + { + if (!a.second.empty ()) + s->name = a.second; + s->vetoed = true; + } + } +} + +AmendmentState* +AmendmentTableImpl::add (uint256 const& amendmentHash) +{ + // call with the mutex held + return &amendmentMap_[amendmentHash]; +} + +AmendmentState* +AmendmentTableImpl::get (uint256 const& amendmentHash) +{ + // call with the mutex held + auto ret = amendmentMap_.find (amendmentHash); + + if (ret == amendmentMap_.end()) + return nullptr; + + return &ret->second; +} + +uint256 +AmendmentTableImpl::find (std::string const& name) +{ + std::lock_guard sl (mutex_); + + for (auto const& e : amendmentMap_) + { + if (name == e.second.name) + return e.first; + } + + return {}; +} + +bool +AmendmentTableImpl::veto (uint256 const& amendment) +{ + std::lock_guard sl (mutex_); + auto s = add (amendment); + + if (s->vetoed) + return false; + s->vetoed = true; + return true; +} + +bool +AmendmentTableImpl::unVeto (uint256 const& amendment) +{ + std::lock_guard sl (mutex_); + auto s = get (amendment); + + if (!s || !s->vetoed) + return false; + s->vetoed = false; + return true; +} + +bool +AmendmentTableImpl::enable (uint256 const& amendment) +{ + std::lock_guard sl (mutex_); + auto s = add (amendment); + + if (s->enabled) + return false; + + s->enabled = true; + return true; +} + +bool +AmendmentTableImpl::disable (uint256 const& amendment) +{ + std::lock_guard sl (mutex_); + auto s = get (amendment); + + if (!s || !s->enabled) + return false; + + s->enabled = false; + return true; +} + +bool +AmendmentTableImpl::isEnabled (uint256 const& amendment) +{ + std::lock_guard sl (mutex_); + auto s = get (amendment); + return s && s->enabled; +} + +bool +AmendmentTableImpl::isSupported (uint256 const& amendment) +{ + std::lock_guard sl (mutex_); + auto s = get (amendment); + return s && s->supported; +} + +std::vector +AmendmentTableImpl::doValidation ( + std::set const& enabled) +{ + // Get the list of amendments we support and do not + // veto, but that are not already enabled + std::vector amendments; + amendments.reserve (amendmentMap_.size()); + + { + std::lock_guard sl (mutex_); + for (auto const& e : amendmentMap_) + { + if (e.second.supported && ! e.second.vetoed && + (enabled.count (e.first) == 0)) + { + amendments.push_back (e.first); + } + } + } + + if (!amendments.empty()) + std::sort (amendments.begin (), amendments.end ()); + + return amendments; +} + +std::map +AmendmentTableImpl::doVoting ( + NetClock::time_point closeTime, + std::set const& enabledAmendments, + majorityAmendments_t const& majorityAmendments, + ValidationSet const& valSet) +{ + JLOG (j_.trace) << + "voting at " << closeTime.time_since_epoch().count() << + ": " << enabledAmendments.size() << + ", " << majorityAmendments.size() << + ", " << valSet.size(); + + auto vote = std::make_unique (); + + // process validations for ledger before flag ledger + for (auto const& entry : valSet) + { + if (entry.second->isTrusted ()) + { + std::set ballot; + + if (entry.second->isFieldPresent (sfAmendments)) + { + auto const choices = + entry.second->getFieldV256 (sfAmendments); + ballot.insert (choices.begin (), choices.end ()); + } + + vote->tally (ballot); + } + } + + vote->mThreshold = std::max(1, + (vote->mTrustedValidations * majorityFraction_) / 256); + + JLOG (j_.debug) << + "Received " << vote->mTrustedValidations << + " trusted validations, threshold is: " << vote->mThreshold; + + // Map of amendments to the action to be taken for each one. The action is + // the value of the flags in the pseudo-transaction + std::map actions; + + { + std::lock_guard sl (mutex_); + + // process all amendments we know of + for (auto const& entry : amendmentMap_) + { + NetClock::time_point majorityTime = {}; + + bool const hasValMajority = + (vote->votes (entry.first) >= vote->mThreshold); + + { + auto const it = majorityAmendments.find (entry.first); + if (it != majorityAmendments.end ()) + majorityTime = it->second; + } + + if (enabledAmendments.count (entry.first) != 0) + { + JLOG (j_.debug) << + entry.first << ": amendment already enabled"; + } + else if (hasValMajority && + (majorityTime == NetClock::time_point{}) && + ! entry.second.vetoed) + { + // Ledger says no majority, validators say yes + JLOG (j_.debug) << + entry.first << ": amendment got majority"; + actions[entry.first] = tfGotMajority; + } + else if (! hasValMajority && + (majorityTime != NetClock::time_point{})) + { + // Ledger says majority, validators say no + JLOG (j_.debug) << + entry.first << ": amendment lost majority"; + actions[entry.first] = tfLostMajority; + } + else if ((majorityTime != NetClock::time_point{}) && + ((majorityTime + majorityTime_) <= closeTime) && + ! entry.second.vetoed) + { + // Ledger says majority held + JLOG (j_.debug) << + entry.first << ": amendment majority held"; + actions[entry.first] = 0; + } + } + + // Stash for reporting + lastVote_ = std::move(vote); + } + + return actions; +} + +bool +AmendmentTableImpl::needValidatedLedger (LedgerIndex ledgerSeq) +{ + std::lock_guard sl (mutex_); + + // Is there a ledger in which an amendment could have been enabled + // between these two ledger sequences? + + return ((ledgerSeq - 1) / 256) != ((lastUpdateSeq_ - 1) / 256); +} + +void +AmendmentTableImpl::doValidatedLedger ( + LedgerIndex ledgerSeq, + std::set const& enabled) +{ + std::lock_guard sl (mutex_); + + for (auto& e : amendmentMap_) + e.second.enabled = (enabled.count (e.first) != 0); +} + +void +AmendmentTableImpl::setJson (Json::Value& v, const uint256& id, const AmendmentState& fs) +{ + if (!fs.name.empty()) + v[jss::name] = fs.name; + + v[jss::supported] = fs.supported; + v[jss::vetoed] = fs.vetoed; + v[jss::enabled] = fs.enabled; + + if (!fs.enabled && lastVote_) + { + auto const votesTotal = lastVote_->mTrustedValidations; + auto const votesNeeded = lastVote_->mThreshold; + auto const votesFor = lastVote_->votes (id); + + v[jss::count] = votesFor; + v[jss::validations] = votesTotal; + + if (votesNeeded) + { + v[jss::vote] = votesFor * 256 / votesNeeded; + v[jss::threshold] = votesNeeded; + } + } +} + +Json::Value +AmendmentTableImpl::getJson (int) +{ + Json::Value ret(Json::objectValue); + { + std::lock_guard sl(mutex_); + for (auto const& e : amendmentMap_) + { + setJson (ret[to_string (e.first)] = Json::objectValue, + e.first, e.second); + } + } + return ret; +} + +Json::Value +AmendmentTableImpl::getJson (uint256 const& amendmentID) +{ + Json::Value ret = Json::objectValue; + Json::Value& jAmendment = (ret[to_string (amendmentID)] = Json::objectValue); + + { + std::lock_guard sl(mutex_); + auto a = add (amendmentID); + setJson (jAmendment, amendmentID, *a); + } + + return ret; +} + +std::unique_ptr make_AmendmentTable ( + std::chrono::seconds majorityTime, + int majorityFraction, + Section const& supported, + Section const& enabled, + Section const& vetoed, + beast::Journal journal) +{ + return std::make_unique ( + majorityTime, + majorityFraction, + supported, + enabled, + vetoed, + journal); +} + +} // ripple diff --git a/src/ripple/app/tests/AmendmentTable.test.cpp b/src/ripple/app/tests/AmendmentTable.test.cpp index 6f032425d5..e531e83e6f 100644 --- a/src/ripple/app/tests/AmendmentTable.test.cpp +++ b/src/ripple/app/tests/AmendmentTable.test.cpp @@ -22,11 +22,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include @@ -35,349 +35,299 @@ namespace ripple class AmendmentTable_test final : public beast::unit_test::suite { -public: - using StringPairVec = std::vector>; - private: - enum class TablePopulationAlgo - { - addInitial, - addKnown - }; - // 204/256 about 80% (we round down because the implementation rounds up) static int const majorityFraction{204}; - static void populateTable (AmendmentTable& table, - std::vector const& configLines) + static + uint256 + amendmentId (std::string in) { - Section section (SECTION_AMENDMENTS); - section.append (configLines); - table.addInitial (section); + sha256_hasher h; + using beast::hash_append; + hash_append(h, in); + auto const d = static_cast(h); + uint256 result; + std::memcpy(result.data(), d.data(), d.size()); + return result; } - static std::vector getAmendmentNames ( - StringPairVec const& amendmentPairs) + static + std::vector + createSet (int group, int count) { - std::vector amendmentNames; - amendmentNames.reserve (amendmentPairs.size ()); - for (auto const& i : amendmentPairs) - { - amendmentNames.emplace_back (i.first, i.second); - } - return amendmentNames; + std::vector amendments; + for (int i = 0; i < count; i++) + amendments.push_back ( + "Amendment" + std::to_string ((1000000 * group) + i)); + return amendments; } - std::vector populateTable ( - AmendmentTable& table, - StringPairVec const& amendmentPairs, - TablePopulationAlgo populationAlgo = TablePopulationAlgo::addKnown) + static + Section + makeSection (std::vector const& amendments) { - std::vector const amendmentNames ( - getAmendmentNames (amendmentPairs)); - switch (populationAlgo) - { - case TablePopulationAlgo::addKnown: - for (auto const& i : amendmentNames) - { - table.addKnown (i); - } - break; - case TablePopulationAlgo::addInitial: - { - std::vector configLines; - configLines.reserve (amendmentPairs.size ()); - for (auto const& i : amendmentPairs) - { - configLines.emplace_back (i.first + " " + i.second); - } - populateTable (table, configLines); - } - break; - default: - fail ("Error in test case logic"); - } - - return amendmentNames; + Section section ("Test"); + for (auto const& a : amendments) + section.append (to_string(amendmentId (a)) + " " + a); + return section; } - static std::unique_ptr< AmendmentTable > - makeTable (int w) + static + Section + makeSection (uint256 const& amendment) + { + Section section ("Test"); + section.append (to_string (amendment) + " " + to_string(amendment)); + return section; + } + + std::vector const m_set1; + std::vector const m_set2; + std::vector const m_set3; + std::vector const m_set4; + + Section const emptySection; + +public: + AmendmentTable_test () + : m_set1 (createSet (1, 12)) + , m_set2 (createSet (2, 12)) + , m_set3 (createSet (3, 12)) + , m_set4 (createSet (4, 12)) + { + } + + std::unique_ptr + makeTable( + int w, + Section const supported, + Section const enabled, + Section const vetoed) { return make_AmendmentTable ( weeks (w), majorityFraction, + supported, + enabled, + vetoed, beast::Journal{}); + } + + std::unique_ptr + makeTable (int w) + { + return makeTable ( + w, + makeSection (m_set1), + makeSection (m_set2), + makeSection (m_set3)); }; - // Create the amendments by string pairs instead of AmendmentNames - // as this helps test the AmendmentNames class - StringPairVec const m_knownAmendmentPairs; - StringPairVec const m_unknownAmendmentPairs; - -public: - AmendmentTable_test () - : m_knownAmendmentPairs ( - {{"a49f90e7cddbcadfed8fc89ec4d02011", "Known1"}, - {"ca956ccabf25151a16d773171c485423", "Known2"}, - {"60dcd528f057711c5d26b57be28e23df", "Known3"}, - {"da956ccabf25151a16d773171c485423", "Known4"}, - {"70dcd528f057711c5d26b57be28e23df", "Known5"}, - {"70dcd528f057711c5d26b57be28e23d0", "Known6"}}) - , m_unknownAmendmentPairs ( - {{"a9f90e7cddbcadfed8fc89ec4d02011c", "Unknown1"}, - {"c956ccabf25151a16d773171c485423b", "Unknown2"}, - {"6dcd528f057711c5d26b57be28e23dfa", "Unknown3"}}) + void testConstruct () { + testcase ("Construction"); + + auto table = makeTable(1); + + for (auto const& a : m_set1) + { + expect (table->isSupported (amendmentId (a))); + expect (!table->isEnabled (amendmentId (a))); + } + + for (auto const& a : m_set2) + { + expect (table->isSupported (amendmentId (a))); + expect (table->isEnabled (amendmentId (a))); + } + + for (auto const& a : m_set3) + { + expect (!table->isSupported (amendmentId (a))); + expect (!table->isEnabled (amendmentId (a))); + } } void testGet () { - testcase ("get"); - auto table (makeTable (2)); - std::vector const amendmentNames ( - populateTable (*table, m_knownAmendmentPairs)); - std::vector const unknownAmendmentNames ( - getAmendmentNames (m_unknownAmendmentPairs)); - for (auto const& i : amendmentNames) - { - expect (table->get (i.friendlyName ()) == i.id ()); - } + testcase ("Name to ID mapping"); - for (auto const& i : unknownAmendmentNames) - { - expect (table->get (i.friendlyName ()) == uint256 ()); - } + auto table = makeTable (1); + + for (auto const& a : m_set1) + expect (table->find (a) == amendmentId (a)); + for (auto const& a : m_set2) + expect (table->find (a) == amendmentId (a)); + + for (auto const& a : m_set3) + expect (!table->find (a)); + for (auto const& a : m_set4) + expect (!table->find (a)); } - void testAddInitialAddKnown () + void testBadConfig () { - testcase ("addInitialAddKnown"); + auto const section = makeSection (m_set1); + auto const id = to_string (amendmentId (m_set2[0])); - for (auto tablePopulationAlgo : - {TablePopulationAlgo::addInitial, TablePopulationAlgo::addKnown}) - { + testcase ("Bad Config"); + + { // Two arguments are required - we pass one + Section test = section; + test.append (id); + + try { - // test that the amendments we add are enabled and amendments we - // didn't add are not enabled - - auto table (makeTable (2)); - std::vector const amendmentNames (populateTable ( - *table, m_knownAmendmentPairs, tablePopulationAlgo)); - std::vector const unknownAmendmentNames ( - getAmendmentNames (m_unknownAmendmentPairs)); - - for (auto const& i : amendmentNames) - { - expect (table->isSupported (i.id ())); - if (tablePopulationAlgo == TablePopulationAlgo::addInitial) - expect (table->isEnabled (i.id ())); - } - - for (auto const& i : unknownAmendmentNames) - { - expect (!table->isSupported (i.id ())); - expect (!table->isEnabled (i.id ())); - } + if (makeTable (2, test, emptySection, emptySection)) + fail ("Accepted only amendment ID"); } - + catch (...) { - // check that we throw an exception on bad hex pairs - StringPairVec const badHexPairs ( - {{"a9f90e7cddbcadfedm8fc89ec4d02011c", "BadHex1"}, - {"c956ccabf25151a16d77T3171c485423b", "BadHex2"}, - {"6dcd528f057711c5d2Z6b57be28e23dfa", "BadHex3"}}); + pass(); + } + } - // make sure each element throws - for (auto const& i : badHexPairs) - { - StringPairVec v ({i}); - auto table (makeTable (2)); - try - { - populateTable (*table, v, tablePopulationAlgo); - // line above should throw - fail ("didn't throw"); - } - catch (std::exception const&) - { - pass (); - } - try - { - populateTable ( - *table, badHexPairs, tablePopulationAlgo); - // line above should throw - fail ("didn't throw"); - } - catch (std::exception const&) - { - pass (); - } - } + { // Two arguments are required - we pass three + Section test = section; + test.append (id + " Test Name"); + + try + { + if (makeTable (2, test, emptySection, emptySection)) + fail ("Accepted extra arguments"); + } + catch (...) + { + pass(); } } { - // check that we thow on bad num tokens - std::vector const badNumTokensConfigLines ( - {"19f6d", - "19fd6 bad friendly name" - "9876 one two"}); + auto sid = id; + sid.resize (sid.length() - 1); - // make sure each element throws - for (auto const& i : badNumTokensConfigLines) + Section test = section; + test.append (sid + " Name"); + + try { - std::vector v ({i}); - auto table (makeTable (2)); - try - { - populateTable (*table, v); - // line above should throw - fail ("didn't throw"); - } - catch (std::exception const&) - { - pass (); - } - try - { - populateTable (*table, badNumTokensConfigLines); - // line above should throw - fail ("didn't throw"); - } - catch (std::exception const&) - { - pass (); - } + if (makeTable (2, test, emptySection, emptySection)) + fail ("Accepted short amendment ID"); + } + catch (...) + { + pass(); + } + } + + { + auto sid = id; + sid.resize (sid.length() + 1, '0'); + + Section test = section; + test.append (sid + " Name"); + + try + { + if (makeTable (2, test, emptySection, emptySection)) + fail ("Accepted long amendment ID"); + } + catch (...) + { + pass(); + } + } + + { + auto sid = id; + sid.resize (sid.length() - 1); + sid.push_back ('Q'); + + Section test = section; + test.append (sid + " Name"); + + try + { + if (makeTable (2, test, emptySection, emptySection)) + fail ("Accepted non-hex amendment ID"); + } + catch (...) + { + pass(); } } } - void testEnable () + std::map + getState ( + AmendmentTable *table, + std::set const& exclude) { - testcase ("enable"); - auto table (makeTable (2)); - std::vector const amendmentNames ( - populateTable (*table, m_knownAmendmentPairs)); - { - // enable/disable tests - for (auto const& i : amendmentNames) - { - auto id (i.id ()); - table->enable (id); - expect (table->isEnabled (id)); - table->disable (id); - expect (!table->isEnabled (id)); - table->enable (id); - expect (table->isEnabled (id)); - } + std::map state; - std::vector toEnable; - for (auto const& i : amendmentNames) + auto track = [&state,table](std::vector const& v) + { + for (auto const& a : v) { - auto id (i.id ()); - toEnable.emplace_back (id); - table->disable (id); - expect (!table->isEnabled (id)); + auto const id = amendmentId(a); + state[id] = table->isEnabled (id); } - table->setEnabled (toEnable); - for (auto const& i : toEnable) - { - expect (table->isEnabled (i)); - } - } + }; + + track (m_set1); + track (m_set2); + track (m_set3); + track (m_set4); + + for (auto const& a : exclude) + state.erase(a); + + return state; } - using ATSetter = - void (AmendmentTable::*)(const std::vector& amendments); - using ATGetter = bool (AmendmentTable::*)(uint256 const& amendment); - void testVectorSetUnset (ATSetter setter, ATGetter getter) + void testEnableDisable () { - auto table (makeTable (2)); - // make pointer to ref syntax a little nicer - auto& tableRef (*table); - std::vector const amendmentNames ( - populateTable (tableRef, m_knownAmendmentPairs)); + testcase ("enable & disable"); - // they should all be set - for (auto const& i : amendmentNames) - { - expect ((tableRef.*getter)(i.id ())); // i.e. "isSupported" - } + auto const testAmendment = amendmentId("TestAmendment"); + auto table = makeTable (2); - { - // only set every other amendment - std::vector toSet; - toSet.reserve (amendmentNames.size ()); - for (int i = 0; i < amendmentNames.size (); ++i) - { - if (i % 2) - { - toSet.emplace_back (amendmentNames[i].id ()); - } - } - (tableRef.*setter)(toSet); - for (int i = 0; i < amendmentNames.size (); ++i) - { - bool const shouldBeSet = i % 2; - expect (shouldBeSet == - (tableRef.*getter)( - amendmentNames[i].id ())); // i.e. "isSupported" - } - } - } - void testSupported () - { - testcase ("supported"); - testVectorSetUnset (&AmendmentTable::setSupported, - &AmendmentTable::isSupported); - } - void testEnabled () - { - testcase ("enabled"); - testVectorSetUnset (&AmendmentTable::setEnabled, - &AmendmentTable::isEnabled); - } - void testSupportedEnabled () - { - // Check that supported/enabled aren't the same thing - testcase ("supportedEnabled"); - auto table (makeTable (2)); + // Subset of amendments to enable + std::set enabled; + enabled.insert (testAmendment); + enabled.insert (amendmentId(m_set1[0])); + enabled.insert (amendmentId(m_set2[0])); + enabled.insert (amendmentId(m_set3[0])); + enabled.insert (amendmentId(m_set4[0])); - std::vector const amendmentNames ( - populateTable (*table, m_knownAmendmentPairs)); + // Get the state before, excluding the items we'll change: + auto const pre_state = getState (table.get(), enabled); - { - // support every even amendment - // enable every odd amendment - std::vector toSupport; - toSupport.reserve (amendmentNames.size ()); - std::vector toEnable; - toEnable.reserve (amendmentNames.size ()); - for (int i = 0; i < amendmentNames.size (); ++i) - { - if (i % 2) - { - toSupport.emplace_back (amendmentNames[i].id ()); - } - else - { - toEnable.emplace_back (amendmentNames[i].id ()); - } - } - table->setEnabled (toEnable); - table->setSupported (toSupport); - for (int i = 0; i < amendmentNames.size (); ++i) - { - bool const shouldBeSupported = i % 2; - bool const shouldBeEnabled = !(i % 2); - expect (shouldBeEnabled == - (table->isEnabled (amendmentNames[i].id ()))); - expect (shouldBeSupported == - (table->isSupported (amendmentNames[i].id ()))); - } - } + // Enable the subset and verify + for (auto const& a : enabled) + table->enable (a); + + for (auto const& a : enabled) + expect (table->isEnabled (a)); + + // Disable the subset and verify + for (auto const& a : enabled) + table->disable (a); + + for (auto const& a : enabled) + expect (!table->isEnabled (a)); + + // Get the state after, excluding the items we changed: + auto const post_state = getState (table.get(), enabled); + + // Ensure the states are identical + auto ret = std::mismatch( + pre_state.begin(), pre_state.end(), + post_state.begin(), post_state.end()); + + expect (ret.first == pre_state.end()); + expect (ret.second == post_state.end()); } std::vector makeValidators (int num) @@ -398,14 +348,14 @@ public: } // Execute a pretend consensus round for a flag ledger - void doRound - ( AmendmentTable& table - , weeks week - , std::vector const& validators - , std::vector > const& votes - , std::vector & ourVotes - , enabledAmendments_t& enabled - , majorityAmendments_t& majority) + void doRound( + AmendmentTable& table, + weeks week, + std::vector const& validators, + std::vector > const& votes, + std::vector & ourVotes, + std::set & enabled, + majorityAmendments_t& majority) { // Do a round at the specified time // Returns the amendments we voted for @@ -427,9 +377,8 @@ public: int i = 0; for (auto const& val : validators) { - STValidation::pointer v = - std::make_shared - (uint256(), roundTime, val, true); + auto v = std::make_shared ( + uint256(), roundTime, val, true); ++i; STVector256 field (sfAmendments); @@ -451,7 +400,8 @@ public: ourVotes = table.doValidation (enabled); - auto actions = table.doVoting (roundTime, enabled, majority, validations); + auto actions = table.doVoting ( + roundTime, enabled, majority, validations); for (auto const& action : actions) { // This code assumes other validators do as we do @@ -462,47 +412,47 @@ public: case 0: // amendment goes from majority to enabled if (enabled.find (hash) != enabled.end ()) - Throw ("enabling already enabled"); + throw std::runtime_error ("enabling already enabled"); if (majority.find (hash) == majority.end ()) - Throw ("enabling without majority"); + throw std::runtime_error ("enabling without majority"); enabled.insert (hash); majority.erase (hash); break; case tfGotMajority: if (majority.find (hash) != majority.end ()) - Throw ("got majority while having majority"); + throw std::runtime_error ("got majority while having majority"); majority[hash] = roundTime; break; case tfLostMajority: if (majority.find (hash) == majority.end ()) - Throw ("lost majority without majority"); + throw std::runtime_error ("lost majority without majority"); majority.erase (hash); break; default: - assert (false); - Throw ("unknown action"); + throw std::runtime_error ("unknown action"); } } } // No vote on unknown amendment - void testNoUnknown () + void testNoOnUnknown () { - testcase ("voteNoUnknown"); - - auto table (makeTable (2)); + testcase ("Vote NO on unknown"); + auto const testAmendment = amendmentId("TestAmendment"); auto const validators = makeValidators (10); - uint256 testAmendment; - testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa"); + auto table = makeTable (2, + emptySection, + emptySection, + emptySection); std::vector > votes; std::vector ourVotes; - enabledAmendments_t enabled; + std::set enabled; majorityAmendments_t majority; doRound (*table, weeks{1}, @@ -541,21 +491,22 @@ public: } // No vote on vetoed amendment - void testNoVetoed () + void testNoOnVetoed () { - testcase ("voteNoVetoed"); + testcase ("Vote NO on vetoed"); - auto table (makeTable (2)); + auto const testAmendment = amendmentId ("vetoedAmendment"); + + auto table = makeTable (2, + emptySection, + emptySection, + makeSection (testAmendment)); auto const validators = makeValidators (10); - uint256 testAmendment; - testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa"); - table->veto(testAmendment); - std::vector > votes; std::vector ourVotes; - enabledAmendments_t enabled; + std::set enabled; majorityAmendments_t majority; doRound (*table, weeks{1}, @@ -596,15 +547,16 @@ public: { testcase ("voteEnable"); - auto table (makeTable (2)); - auto const amendmentNames ( - populateTable (*table, m_knownAmendmentPairs)); + auto table = makeTable ( + 2, + makeSection (m_set1), + emptySection, + emptySection); auto const validators = makeValidators (10); - std::vector > votes; std::vector ourVotes; - enabledAmendments_t enabled; + std::set enabled; majorityAmendments_t majority; // Week 1: We should vote for all known amendments not enabled @@ -614,14 +566,15 @@ public: ourVotes, enabled, majority); - expect (ourVotes.size() == amendmentNames.size(), "Did not vote"); + expect (ourVotes.size() == m_set1.size(), "Did not vote"); expect (enabled.empty(), "Enabled amendment for no reason"); - for (auto const& i : amendmentNames) - expect(majority.find(i.id()) == majority.end(), "majority detected for no reaosn"); + for (auto const& i : m_set1) + expect(majority.find(amendmentId (i)) == majority.end(), + "majority detected for no reason"); // Now, everyone votes for this feature - for (auto const& i : amendmentNames) - votes.emplace_back (i.id(), 256); + for (auto const& i : m_set1) + votes.emplace_back (amendmentId(i), 256); // Week 2: We should recognize a majority doRound (*table, weeks{2}, @@ -630,10 +583,12 @@ public: ourVotes, enabled, majority); - expect (ourVotes.size() == amendmentNames.size(), "Did not vote"); + expect (ourVotes.size() == m_set1.size(), "Did not vote"); expect (enabled.empty(), "Enabled amendment for no reason"); - for (auto const& i : amendmentNames) - expect (majority[i.id()] == weekTime(weeks{2}), "majority not detected"); + + for (auto const& i : m_set1) + expect (majority[amendmentId (i)] == weekTime(weeks{2}), + "majority not detected"); // Week 5: We should enable the amendment doRound (*table, weeks{5}, @@ -642,7 +597,7 @@ public: ourVotes, enabled, majority); - expect (enabled.size() == amendmentNames.size(), "Did not enable"); + expect (enabled.size() == m_set1.size(), "Did not enable"); // Week 6: We should remove it from our votes and from having a majority doRound (*table, weeks{6}, @@ -651,25 +606,28 @@ public: ourVotes, enabled, majority); - expect (enabled.size() == amendmentNames.size(), "Disabled"); + expect (enabled.size() == m_set1.size(), "Disabled"); expect (ourVotes.empty(), "Voted after enabling"); - for (auto const& i : amendmentNames) - expect(majority.find(i.id()) == majority.end(), "majority not removed"); + for (auto const& i : m_set1) + expect(majority.find(amendmentId (i)) == majority.end(), + "majority not removed"); } // Detect majority at 80%, enable later void testDetectMajority () { testcase ("detectMajority"); - auto table (makeTable (2)); - uint256 testAmendment; - testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa"); - table->addKnown({testAmendment, "testAmendment"}); + auto const testAmendment = amendmentId ("detectMajority"); + auto table = makeTable ( + 2, + makeSection (testAmendment), + emptySection, + emptySection); auto const validators = makeValidators (16); - enabledAmendments_t enabled; + std::set enabled; majorityAmendments_t majority; for (int i = 0; i <= 17; ++i) @@ -683,25 +641,22 @@ public: doRound (*table, weeks{i}, validators, votes, ourVotes, enabled, majority); - if (i < 14) + if (i < 13) { - // rounds 0-13 // We are voting yes, not enabled, no majority expect (!ourVotes.empty(), "We aren't voting"); expect (enabled.empty(), "Enabled too early"); expect (majority.empty(), "Majority too early"); } - else if (i < 16) + else if (i < 15) { - // rounds 14 and 15 // We have a majority, not enabled, keep voting expect (!ourVotes.empty(), "We stopped voting"); expect (!majority.empty(), "Failed to detect majority"); expect (enabled.empty(), "Enabled too early"); } - else if (i == 16) // round 16 + else if (i == 15) { - // round 16 // enable, keep voting, remove from majority expect (!ourVotes.empty(), "We stopped voting"); expect (majority.empty(), "Failed to remove from majority"); @@ -709,7 +664,6 @@ public: } else { - // round 17 // Done, we should be enabled and not voting expect (ourVotes.empty(), "We did not stop voting"); expect (majority.empty(), "Failed to revove from majority"); @@ -723,15 +677,16 @@ public: { testcase ("lostMajority"); - auto table (makeTable (8)); - - uint256 testAmendment; - testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa"); - table->addKnown({testAmendment, "testAmendment"}); - + auto const testAmendment = amendmentId ("lostMajority"); auto const validators = makeValidators (16); - enabledAmendments_t enabled; + auto table = makeTable ( + 8, + makeSection (testAmendment), + emptySection, + emptySection); + + std::set enabled; majorityAmendments_t majority; { @@ -759,9 +714,8 @@ public: doRound (*table, weeks{i + 1}, validators, votes, ourVotes, enabled, majority); - if (i < 6) + if (i < 8) { - // rounds 1 to 5 // We are voting yes, not enabled, majority expect (!ourVotes.empty(), "We aren't voting"); expect (enabled.empty(), "Enabled for no reason"); @@ -769,7 +723,6 @@ public: } else { - // rounds 6 to 15 // No majority, not enabled, keep voting expect (!ourVotes.empty(), "We stopped voting"); expect (majority.empty(), "Failed to detect loss of majority"); @@ -780,13 +733,12 @@ public: void run () { + testConstruct(); testGet (); - testAddInitialAddKnown (); - testEnable (); - testSupported (); - testSupportedEnabled (); - testNoUnknown (); - testNoVetoed (); + testBadConfig (); + testEnableDisable (); + testNoOnUnknown (); + testNoOnVetoed (); testVoteEnable (); testDetectMajority (); testLostMajority (); diff --git a/src/ripple/core/ConfigSections.h b/src/ripple/core/ConfigSections.h index b6b53107d4..0207972da4 100644 --- a/src/ripple/core/ConfigSections.h +++ b/src/ripple/core/ConfigSections.h @@ -64,6 +64,7 @@ struct ConfigSection #define SECTION_VALIDATION_SEED "validation_seed" #define SECTION_WEBSOCKET_PING_FREQ "websocket_ping_frequency" #define SECTION_VALIDATORS "validators" +#define SECTION_VETO_AMENDMENTS "veto_amendments" } // ripple diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index e7502f8c8b..0a15c38138 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -130,8 +130,7 @@ cdirNext (ReadView const& view, beast::Journal j); // Return the list of enabled amendments -using enabledAmendments_t = std::set ; -enabledAmendments_t +std::set getEnabledAmendments (ReadView const& view); // Return a map of amendments that have achieved majority diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index ac61387536..c244585ae5 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -469,16 +469,19 @@ cdirNext (ReadView const& view, return true; } -enabledAmendments_t +std::set getEnabledAmendments (ReadView const& view) { - enabledAmendments_t amendments; - auto const sleAmendments = view.read(keylet::amendments()); + std::set amendments; - if (sleAmendments) + if (auto const sle = view.read(keylet::amendments())) { - for (auto const &a : sleAmendments->getFieldV256 (sfAmendments)) - amendments.insert (a); + if (!sle->isFieldPresent (sfAmendments)) + LogicError ("No amendments field is present"); + + auto const& v = sle->getFieldV256 (sfAmendments); + + amendments.insert (v.begin(), v.end()); } return amendments; @@ -487,20 +490,24 @@ getEnabledAmendments (ReadView const& view) majorityAmendments_t getMajorityAmendments (ReadView const& view) { - using tp = NetClock::time_point; - using d = tp::duration; - majorityAmendments_t majorities; - auto const sleAmendments = view.read(keylet::amendments()); + majorityAmendments_t ret; - if (sleAmendments && sleAmendments->isFieldPresent (sfMajorities)) + if (auto const sle = view.read(keylet::amendments())) { - auto const& majArray = sleAmendments->getFieldArray (sfMajorities); - for (auto const& m : majArray) - majorities[m.getFieldH256 (sfAmendment)] = - tp(d(m.getFieldU32(sfCloseTime))); + if (sle->isFieldPresent (sfMajorities)) + { + using tp = NetClock::time_point; + using d = tp::duration; + + auto const majorities = sle->getFieldArray (sfMajorities); + + for (auto const& m : majorities) + ret[m.getFieldH256 (sfAmendment)] = + tp(d(m.getFieldU32(sfCloseTime))); + } } - return majorities; + return ret; } boost::optional diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index 26e38c425e..2153490052 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -406,16 +407,28 @@ private: return rpcError (rpcNO_EVENTS); } - // feature [] [true|false] + // feature [] [accept|reject] Json::Value parseFeature (Json::Value const& jvParams) { Json::Value jvRequest (Json::objectValue); if (jvParams.size () > 0) - jvRequest[jss::feature] = jvParams[0u].asString (); + jvRequest[jss::feature] = jvParams[0u].asString (); if (jvParams.size () > 1) - jvRequest[jss::vote] = beast::lexicalCastThrow (jvParams[1u].asString ()); + { + auto const action = jvParams[1u].asString (); + + // This may look reversed, but it's intentional: jss::vetoed + // determines whether an amendment is vetoed - so "reject" means + // that jss::vetoed is true. + if (beast::ci_equal(action, "reject")) + jvRequest[jss::vetoed] = Json::Value (true); + else if (beast::ci_equal(action, "accept")) + jvRequest[jss::vetoed] = Json::Value (false); + else + return rpcError (rpcINVALID_PARAMS); + } return jvRequest; } diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h index d090de833e..337586b6ec 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -236,6 +236,7 @@ JSS ( load_factor_net ); // out: NetworkOPs JSS ( load_fee ); // out: LoadFeeTrackImp JSS ( local ); // out: resource/Logic.h JSS ( local_txs ); // out: GetCounts +JSS ( majority ); // out: RPC feature JSS ( marker ); // in/out: AccountTx, AccountOffers, // AccountLines, AccountObjects, // LedgerData @@ -412,6 +413,7 @@ JSS ( validation_key ); // out: ValidationCreate, ValidationSeed JSS ( validation_public_key ); // out: ValidationCreate, ValidationSeed JSS ( validation_quorum ); // out: NetworkOPs JSS ( validation_seed ); // out: ValidationCreate, ValidationSeed +JSS ( validations ); // out: AmendmentTableImpl JSS ( value ); // out: STAmount JSS ( version ); // out: RPCVersion JSS ( vetoed ); // out: AmendmentTableImpl diff --git a/src/ripple/rpc/handlers/Feature1.cpp b/src/ripple/rpc/handlers/Feature1.cpp index 3c16f0496c..b1f45b8329 100644 --- a/src/ripple/rpc/handlers/Feature1.cpp +++ b/src/ripple/rpc/handlers/Feature1.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -28,32 +29,60 @@ namespace ripple { + +// { +// feature : +// vetoed : true/false +// } Json::Value doFeature (RPC::Context& context) { + + // Get majority amendment status + majorityAmendments_t majorities; + + if (auto const valLedger = context.ledgerMaster.getValidatedLedger()) + majorities = getMajorityAmendments (*valLedger); + + auto& table = context.app.getAmendmentTable (); + if (!context.params.isMember (jss::feature)) { + auto features = table.getJson(0); + + for (auto const& m : majorities) + { + features[to_string(m.first)][jss::majority] = + m.second.time_since_epoch().count(); + } + Json::Value jvReply = Json::objectValue; - jvReply[jss::features] = context.app.getAmendmentTable ().getJson(0); + jvReply[jss::features] = features; return jvReply; } - uint256 uFeature - = context.app.getAmendmentTable ().get( - context.params[jss::feature].asString()); + auto feature = table.find ( + context.params[jss::feature].asString()); - if (uFeature.isZero ()) + if (!feature && + !feature.SetHexExact (context.params[jss::feature].asString ())) + return rpcError (rpcBAD_FEATURE); + + if (context.params.isMember (jss::vetoed)) { - uFeature.SetHex (context.params[jss::feature].asString ()); - - if (uFeature.isZero ()) - return rpcError (rpcBAD_FEATURE); + if (context.params[jss::vetoed].asBool ()) + context.app.getAmendmentTable().veto (feature); + else + context.app.getAmendmentTable().unVeto(feature); } - if (!context.params.isMember (jss::vote)) - return context.app.getAmendmentTable ().getJson(uFeature); + Json::Value jvReply = table.getJson(feature); - // WRITEME - return rpcError (rpcNOT_SUPPORTED); + auto m = majorities.find (feature); + if (m != majorities.end()) + jvReply [jss::majority] = + m->second.time_since_epoch().count(); + + return jvReply; } diff --git a/src/ripple/unity/app_main.cpp b/src/ripple/unity/app_main.cpp index ebc5c5fc24..be3c746d32 100644 --- a/src/ripple/unity/app_main.cpp +++ b/src/ripple/unity/app_main.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include diff --git a/src/ripple/unity/app_misc.cpp b/src/ripple/unity/app_misc.cpp index 75745f9f61..421636bbfe 100644 --- a/src/ripple/unity/app_misc.cpp +++ b/src/ripple/unity/app_misc.cpp @@ -19,7 +19,6 @@ #include -#include #include #include #include @@ -28,6 +27,7 @@ #include #include +#include #include #include #include