From efc2159441e2244c40d65e9c8afee1b25f667adc Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Fri, 30 Jan 2015 12:57:57 -0800 Subject: [PATCH] Implement new amendment majority semantics : This implements the tracking of when an amendment achieved a majority in the ledger, ensuring that there's always network-wide agreement on which amendments have achieved a majority and how long they've held it. * New fields * Change transactor changes * AmendmentTable API and implementation changes * Update amendment enabled status on validated ledgers * Reinstate support for ledger sequence in fee transactions --- .../app/ledger/impl/LedgerConsensusImp.cpp | 6 +- src/ripple/app/ledger/impl/LedgerMaster.cpp | 2 + src/ripple/app/main/DBInit.cpp | 6 - src/ripple/app/misc/AmendmentTable.h | 110 +++- src/ripple/app/misc/AmendmentTableImpl.cpp | 466 +++++------------ src/ripple/app/misc/FeeVote.h | 3 +- src/ripple/app/misc/FeeVoteImpl.cpp | 6 +- src/ripple/app/misc/README.md | 16 + .../app/misc/tests/AmendmentTable.test.cpp | 478 ++++++++++++++++-- src/ripple/app/tx/impl/Change.cpp | 67 ++- src/ripple/ledger/ReadView.h | 3 - src/ripple/ledger/View.h | 10 + src/ripple/ledger/impl/View.cpp | 32 ++ src/ripple/protocol/SField.h | 2 + src/ripple/protocol/STVector256.h | 4 + src/ripple/protocol/TxFlags.h | 4 + src/ripple/protocol/impl/LedgerFormats.cpp | 7 +- src/ripple/protocol/impl/SField.cpp | 4 + 18 files changed, 814 insertions(+), 412 deletions(-) diff --git a/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp b/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp index 700df9c919..c19a5a81ef 100644 --- a/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp +++ b/src/ripple/app/ledger/impl/LedgerConsensusImp.cpp @@ -1390,8 +1390,10 @@ void LedgerConsensusImp::takeInitialPosition (Ledger& initialLedger) // previous ledger was flag ledger std::shared_ptr preSet = initialLedger.txMap().snapShot (true); - m_feeVote.doVoting (mPreviousLedger, preSet); - getApp().getAmendmentTable ().doVoting (mPreviousLedger, preSet); + ValidationSet parentSet = getApp().getValidations().getValidations ( + mPreviousLedger->getParentHash ()); + m_feeVote.doVoting (mPreviousLedger, parentSet, preSet); + getApp().getAmendmentTable ().doVoting (mPreviousLedger, parentSet, preSet); initialSet = preSet->snapShot (false); } else diff --git a/src/ripple/app/ledger/impl/LedgerMaster.cpp b/src/ripple/app/ledger/impl/LedgerMaster.cpp index 57a2425bc3..5170200c9f 100644 --- a/src/ripple/app/ledger/impl/LedgerMaster.cpp +++ b/src/ripple/app/ledger/impl/LedgerMaster.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -233,6 +234,7 @@ public: getApp().getOPs().updateLocalTx (l); getApp().getSHAMapStore().onLedgerClosed (getValidatedLedger()); mLedgerHistory.validatedLedger (l); + getApp().getAmendmentTable().doValidatedLedger (l); #if RIPPLE_HOOK_VALIDATORS getApp().getValidators().onLedgerClosed (l->getLedgerSeq(), diff --git a/src/ripple/app/main/DBInit.cpp b/src/ripple/app/main/DBInit.cpp index 1e07ed5f6d..96e9824e9b 100644 --- a/src/ripple/app/main/DBInit.cpp +++ b/src/ripple/app/main/DBInit.cpp @@ -254,12 +254,6 @@ const char* WalletDBInit[] = PRIMARY KEY (Validator,Entry) \ );", - "CREATE TABLE IF NOT EXISTS Features ( \ - Hash CHARACTER(64) PRIMARY KEY, \ - FirstMajority BIGINT UNSIGNED, \ - LastMajority BIGINT UNSIGNED \ - );", - "END TRANSACTION;" }; diff --git a/src/ripple/app/misc/AmendmentTable.h b/src/ripple/app/misc/AmendmentTable.h index ed0f99732e..8ab2b99d25 100644 --- a/src/ripple/app/misc/AmendmentTable.h +++ b/src/ripple/app/misc/AmendmentTable.h @@ -38,14 +38,22 @@ public: { ; } + 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. @@ -102,11 +110,6 @@ public: bool mSupported{false}; bool mDefault{false}; // Include in genesis ledger - Clock::time_point - m_firstMajority{0}; // First time we saw a majority (close time) - Clock::time_point - m_lastMajority{0}; // Most recent time we saw a majority (close time) - std::string mFriendlyName; AmendmentState () = default; @@ -147,6 +150,7 @@ public: class Section; + /** The amendment table stores the list of enabled and potential amendments. Individuals amendments are voted on by validators during the consensus process. @@ -197,27 +201,103 @@ public: */ virtual void setSupported (const std::vector& amendments) = 0; - /** Update the walletDB with the majority times. - */ - virtual void reportValidations (const AmendmentSet&) = 0; - virtual Json::Value getJson (int) = 0; /** Returns a Json::objectValue. */ virtual Json::Value getJson (uint256 const& ) = 0; + /** Called when a new fully-validated ledger is accepted. */ + void doValidatedLedger (std::shared_ptr const& lastValidatedLedger) + { + if (needValidatedLedger (lastValidatedLedger->seq ())) + doValidatedLedger (lastValidatedLedger->seq (), + getEnabledAmendments (*lastValidatedLedger)); + } + + /** Called to determine whether the amendment logic needs to process + a new validated ledger. (If it could have changed things.) + */ + virtual bool + needValidatedLedger (LedgerIndex seq) = 0; + virtual void - doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation) = 0; - virtual void - doVoting (Ledger::ref lastClosedLedger, - std::shared_ptr const& initialPosition) = 0; + doValidatedLedger (LedgerIndex ledgerSeq, enabledAmendments_t enabled) = 0; + + // Called by the consensus code when we need to + // inject pseudo-transactions + virtual std::map + doVoting ( + std::uint32_t closeTime, + enabledAmendments_t 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; + + + // The two function below adapt the API callers expect to the + // internal amendment table API. This allows the amendment + // table implementation to be independent of the ledger + // implementation. These APIs will merge when the view code + // supports a full ledger API + + void + doValidation (std::shared_ptr const& lastClosedLedger, + STObject& baseValidation) + { + auto ourAmendments = + doValidation (getEnabledAmendments(*lastClosedLedger)); + if (! ourAmendments.empty()) + baseValidation.setFieldV256 (sfAmendments, + STVector256 (sfAmendments, ourAmendments)); + } + + + void + doVoting ( + std::shared_ptr const& lastClosedLedger, + ValidationSet const& parentValidations, + std::shared_ptr const& initialPosition) + { + // Ask implementation what to do + auto actions = doVoting ( + lastClosedLedger->parentCloseTime(), + getEnabledAmendments(*lastClosedLedger), + getMajorityAmendments(*lastClosedLedger), + parentValidations); + + // Inject appropriate pseudo-transactions + for (auto const& it : actions) + { + STTx trans (ttAMENDMENT); + trans.setAccountID (sfAccount, AccountID()); + trans.setFieldH256 (sfAmendment, it.first); + trans.setFieldU32 (sfLedgerSequence, lastClosedLedger->seq() + 1); + + if (it.second != 0) + trans.setFieldU32 (sfFlags, it.second); + + uint256 txID = trans.getTransactionID(); + + Serializer s; + trans.add (s); + +#if RIPPLE_PROPOSE_AMENDMENTS + auto tItem = std::make_shared (txID, s.peekData()); + initialPosition->addGiveItem (tItem, true, false); +#endif + } + } + }; std::unique_ptr make_AmendmentTable ( std::chrono::seconds majorityTime, int majorityFraction, - beast::Journal journal, - bool useMockFacade = false); + beast::Journal journal); } // ripple diff --git a/src/ripple/app/misc/AmendmentTableImpl.cpp b/src/ripple/app/misc/AmendmentTableImpl.cpp index eca3752cc1..9a3c3c01c8 100644 --- a/src/ripple/app/misc/AmendmentTableImpl.cpp +++ b/src/ripple/app/misc/AmendmentTableImpl.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +36,7 @@ namespace ripple { Amendments are proposed and then adopted or rejected by the network. An Amendment is uniquely identified by its AmendmentID, a 256-bit key. */ -template + class AmendmentTableImpl final : public AmendmentTable { protected: @@ -47,16 +48,14 @@ protected: LockType 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% - Clock::time_point m_firstReport; // close time of first majority report - Clock::time_point m_lastReport; // close time of most recent majority report beast::Journal m_journal; - AppApiFacade m_appApiFacade; AmendmentState& getCreate (uint256 const& amendment); AmendmentState* getExisting (uint256 const& amendment); - bool shouldEnable (std::uint32_t closeTime, const AmendmentState& fs); void setJson (Json::Value& v, const AmendmentState&); public: @@ -64,10 +63,9 @@ public: std::chrono::seconds majorityTime, int majorityFraction, beast::Journal journal) - : m_majorityTime (majorityTime) + : m_lastUpdateSeq (0) + , m_majorityTime (majorityTime) , mMajorityFraction (majorityFraction) - , m_firstReport (0) - , m_lastReport (0) , m_journal (journal) { } @@ -90,19 +88,28 @@ public: void setEnabled (const std::vector& amendments) override; void setSupported (const std::vector& amendments) override; - void reportValidations (const AmendmentSet&) override; - Json::Value getJson (int) override; Json::Value getJson (uint256 const&) override; - void doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation) override; - void doVoting (Ledger::ref lastClosedLedger, - std::shared_ptr const& initialPosition) 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 (std::uint32_t closeTime, + enabledAmendments_t const& enabledAmendments, + majorityAmendments_t const& majorityAmendments, + ValidationSet const& validations) override; amendmentList_t getVetoed(); amendmentList_t getEnabled(); - amendmentList_t getToEnable(Clock::time_point closeTime); // gets amendments we would vote to enable - amendmentList_t getDesired(); // amendments we support, do not veto, are not enabled + + // Amendments we support, do not veto, and are not enabled + amendmentList_t getDesired (enabledAmendments_t const&); }; namespace detail @@ -117,9 +124,8 @@ namespace detail std::vector const preEnabledAmendments; } -template void -AmendmentTableImpl::addInitial (Section const& section) +AmendmentTableImpl::addInitial (Section const& section) { for (auto const& a : detail::preEnabledAmendments) { @@ -177,9 +183,8 @@ AmendmentTableImpl::addInitial (Section const& section) } } -template AmendmentState& -AmendmentTableImpl::getCreate (uint256 const& amendmentHash) +AmendmentTableImpl::getCreate (uint256 const& amendmentHash) { // call with the mutex held auto iter (m_amendmentMap.find (amendmentHash)); @@ -187,16 +192,14 @@ AmendmentTableImpl::getCreate (uint256 const& amendmentHash) if (iter == m_amendmentMap.end()) { AmendmentState& amendment = m_amendmentMap[amendmentHash]; - m_appApiFacade.setMajorityTimesFromDBToState (amendment, amendmentHash); return amendment; } return iter->second; } -template AmendmentState* -AmendmentTableImpl::getExisting (uint256 const& amendmentHash) +AmendmentTableImpl::getExisting (uint256 const& amendmentHash) { // call with the mutex held auto iter (m_amendmentMap.find (amendmentHash)); @@ -207,9 +210,8 @@ AmendmentTableImpl::getExisting (uint256 const& amendmentHash) return & (iter->second); } -template uint256 -AmendmentTableImpl::get (std::string const& name) +AmendmentTableImpl::get (std::string const& name) { ScopedLockType sl (mLock); @@ -222,9 +224,8 @@ AmendmentTableImpl::get (std::string const& name) return uint256 (); } -template void -AmendmentTableImpl::addKnown (AmendmentName const& name) +AmendmentTableImpl::addKnown (AmendmentName const& name) { if (!name.valid ()) { @@ -246,9 +247,8 @@ AmendmentTableImpl::addKnown (AmendmentName const& name) amendment.mSupported = true; } -template bool -AmendmentTableImpl::veto (uint256 const& amendment) +AmendmentTableImpl::veto (uint256 const& amendment) { ScopedLockType sl (mLock); AmendmentState& s = getCreate (amendment); @@ -260,9 +260,8 @@ AmendmentTableImpl::veto (uint256 const& amendment) return true; } -template bool -AmendmentTableImpl::unVeto (uint256 const& amendment) +AmendmentTableImpl::unVeto (uint256 const& amendment) { ScopedLockType sl (mLock); AmendmentState* s = getExisting (amendment); @@ -274,9 +273,8 @@ AmendmentTableImpl::unVeto (uint256 const& amendment) return true; } -template bool -AmendmentTableImpl::enable (uint256 const& amendment) +AmendmentTableImpl::enable (uint256 const& amendment) { ScopedLockType sl (mLock); AmendmentState& s = getCreate (amendment); @@ -288,9 +286,8 @@ AmendmentTableImpl::enable (uint256 const& amendment) return true; } -template bool -AmendmentTableImpl::disable (uint256 const& amendment) +AmendmentTableImpl::disable (uint256 const& amendment) { ScopedLockType sl (mLock); AmendmentState* s = getExisting (amendment); @@ -302,27 +299,24 @@ AmendmentTableImpl::disable (uint256 const& amendment) return true; } -template bool -AmendmentTableImpl::isEnabled (uint256 const& amendment) +AmendmentTableImpl::isEnabled (uint256 const& amendment) { ScopedLockType sl (mLock); AmendmentState* s = getExisting (amendment); return s && s->mEnabled; } -template bool -AmendmentTableImpl::isSupported (uint256 const& amendment) +AmendmentTableImpl::isSupported (uint256 const& amendment) { ScopedLockType sl (mLock); AmendmentState* s = getExisting (amendment); return s && s->mSupported; } -template -typename AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getVetoed () +AmendmentTableImpl::amendmentList_t +AmendmentTableImpl::getVetoed () { amendmentList_t ret; ScopedLockType sl (mLock); @@ -334,9 +328,8 @@ AmendmentTableImpl::getVetoed () return ret; } -template -typename AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getEnabled () +AmendmentTableImpl::amendmentList_t +AmendmentTableImpl::getEnabled () { amendmentList_t ret; ScopedLockType sl (mLock); @@ -348,128 +341,24 @@ AmendmentTableImpl::getEnabled () return ret; } -template -bool -AmendmentTableImpl::shouldEnable (std::uint32_t closeTime, - const AmendmentState& fs) -{ - if (fs.mVetoed || fs.mEnabled || !fs.mSupported || (fs.m_lastMajority != m_lastReport)) - return false; - - if (fs.m_firstMajority == m_firstReport) - { - // had a majority when we first started the server, relaxed check - // WRITEME - } - - // didn't have a majority when we first started the server, normal check - return (fs.m_lastMajority - fs.m_firstMajority) > m_majorityTime.count(); -} - -template -typename AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getToEnable (Clock::time_point closeTime) -{ - amendmentList_t ret; - ScopedLockType sl (mLock); - - if (m_lastReport != 0) - { - for (auto const& e : m_amendmentMap) - { - if (shouldEnable (closeTime, e.second)) - ret.insert (e.first); - } - } - - return ret; -} - -template -typename AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getDesired () +AmendmentTableImpl::amendmentList_t +AmendmentTableImpl::getDesired (enabledAmendments_t const& enabled) { amendmentList_t ret; ScopedLockType sl (mLock); for (auto const& e : m_amendmentMap) { - if (e.second.mSupported && !e.second.mEnabled && !e.second.mVetoed) + if (e.second.mSupported && ! e.second.mVetoed && + (enabled.count (e.first) == 0)) ret.insert (e.first); } return ret; } -template void -AmendmentTableImpl::reportValidations (const AmendmentSet& set) -{ - if (set.mTrustedValidations == 0) - return; - - int threshold = (set.mTrustedValidations * mMajorityFraction) / 256; - - using u256_int_pair = std::map::value_type; - - ScopedLockType sl (mLock); - - if (m_firstReport == 0) - m_firstReport = set.mCloseTime; - - std::vector changedAmendments; - changedAmendments.reserve (set.mVotes.size()); - - for (auto const& e : set.mVotes) - { - AmendmentState& state = m_amendmentMap[e.first]; - if (m_journal.debug) m_journal.debug << - "Amendment " << to_string (e.first) << - " has " << e.second << - " votes, needs " << threshold; - - if (e.second >= threshold) - { - // we have a majority - state.m_lastMajority = set.mCloseTime; - - if (state.m_firstMajority == 0) - { - if (m_journal.warning) m_journal.warning << - "Amendment " << to_string (e.first) << - " attains a majority vote"; - - state.m_firstMajority = set.mCloseTime; - changedAmendments.push_back(e.first); - } - } - else // we have no majority - { - if (state.m_firstMajority != 0) - { - if (m_journal.warning) m_journal.warning << - "Amendment " << to_string (e.first) << - " loses majority vote"; - - state.m_firstMajority = 0; - state.m_lastMajority = 0; - changedAmendments.push_back(e.first); - } - } - } - m_lastReport = set.mCloseTime; - - if (!changedAmendments.empty()) - { - m_appApiFacade.setMajorityTimesFromStateToDB (changedAmendments, - m_amendmentMap); - changedAmendments.clear (); - } -} - -template -void -AmendmentTableImpl::setEnabled (const std::vector& amendments) +AmendmentTableImpl::setEnabled (const std::vector& amendments) { ScopedLockType sl (mLock); for (auto& e : m_amendmentMap) @@ -482,9 +371,8 @@ AmendmentTableImpl::setEnabled (const std::vector& amendm } } -template void -AmendmentTableImpl::setSupported (const std::vector& amendments) +AmendmentTableImpl::setSupported (const std::vector& amendments) { ScopedLockType sl (mLock); for (auto &e : m_amendmentMap) @@ -497,40 +385,34 @@ AmendmentTableImpl::setSupported (const std::vector& amen } } -template -void -AmendmentTableImpl::doValidation (Ledger::ref lastClosedLedger, - STObject& baseValidation) +std::vector +AmendmentTableImpl::doValidation ( + enabledAmendments_t const& enabledAmendments) { - auto lAmendments = getDesired(); + auto lAmendments = getDesired (enabledAmendments); if (lAmendments.empty()) - return; - - STVector256 amendments (sfAmendments); - - for (auto const& id : lAmendments) - amendments.push_back (id); + return {}; + std::vector amendments (lAmendments.begin(), lAmendments.end()); std::sort (amendments.begin (), amendments.end ()); - baseValidation.setFieldV256 (sfAmendments, amendments); + return amendments; } -template -void -AmendmentTableImpl::doVoting (Ledger::ref lastClosedLedger, - std::shared_ptr const& initialPosition) +std::map +AmendmentTableImpl::doVoting ( + std::uint32_t closeTime, + enabledAmendments_t const& enabledAmendments, + majorityAmendments_t const& majorityAmendments, + ValidationSet const& valSet) { - // LCL must be flag ledger - assert((lastClosedLedger->getLedgerSeq () % 256) == 0); + //assert((lastClosedLedger->getLedgerSeq () % 256) == 0); - AmendmentSet amendmentSet (lastClosedLedger->getParentCloseTimeNC ()); + AmendmentSet amendmentSet (closeTime); - // get validations for ledger before flag ledger - ValidationSet valSet = m_appApiFacade.getValidations ( - lastClosedLedger->getParentHash ()); + // process validations for ledger before flag ledger for (auto const& entry : valSet) { auto const& val = *entry.second; @@ -547,40 +429,83 @@ AmendmentTableImpl::doVoting (Ledger::ref lastClosedLedger, } } } - reportValidations (amendmentSet); + 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; - amendmentList_t lAmendments = getToEnable (lastClosedLedger->getCloseTimeNC ()); - for (auto const& uAmendment : lAmendments) { - if (m_journal.warning) m_journal.warning << - "Voting for amendment: " << uAmendment; + ScopedLockType sl (mLock); - // Create the transaction to enable the amendment - STTx trans (ttAMENDMENT); - trans.setAccountID (sfAccount, AccountID ()); - trans.setFieldH256 (sfAmendment, uAmendment); - uint256 txID = trans.getTransactionID (); - - if (m_journal.warning) m_journal.warning << - "Vote ID: " << txID; - - // Inject the transaction into our initial proposal - Serializer s; - trans.add (s); -#if RIPPLE_PROPOSE_AMENDMENTS - auto tItem = std::make_shared (txID, s.peekData ()); - if (!initialPosition->addGiveItem (tItem, true, false)) + // process all amendments we know of + for (auto const& entry : m_amendmentMap) { - if (m_journal.warning) m_journal.warning << - "Ledger already had amendment transaction"; + bool const hasValMajority = amendmentSet.count (entry.first) >= threshold; + + std::uint32_t majorityTime = 0; + 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 == 0) && (! entry.second.mVetoed)) + { + // Ledger says no majority, validators say yes + actions[entry.first] = tfGotMajority; + } + else if (! hasValMajority && (majorityTime != 0)) + { + // Ledger says majority, validators say no + actions[entry.first] = tfLostMajority; + } + else if ((majorityTime != 0) && + ((majorityTime + m_majorityTime.count()) <= closeTime) && + ! entry.second.mVetoed) + { + // Ledger says majority held + actions[entry.first] = 0; + } } -#endif } + + return actions; +} + +bool +AmendmentTableImpl::needValidatedLedger (LedgerIndex ledgerSeq) +{ + ScopedLockType 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) +{ + ScopedLockType sl (mLock); + + for (auto& e : m_amendmentMap) + e.second.mEnabled = (enabled.count (e.first) != 0); } -template Json::Value -AmendmentTableImpl::getJson (int) +AmendmentTableImpl::getJson (int) { Json::Value ret(Json::objectValue); { @@ -593,56 +518,19 @@ AmendmentTableImpl::getJson (int) return ret; } -template void -AmendmentTableImpl::setJson (Json::Value& v, const AmendmentState& fs) +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; - - if (fs.mEnabled) - v[jss::enabled] = true; - else - { - v[jss::enabled] = false; - - if (m_lastReport != 0) - { - if (fs.m_lastMajority == 0) - { - v["majority"] = false; - } - else - { - if (fs.m_firstMajority != 0) - { - if (fs.m_firstMajority == m_firstReport) - v["majority_start"] = "start"; - else - v["majority_start"] = fs.m_firstMajority; - } - - if (fs.m_lastMajority != 0) - { - if (fs.m_lastMajority == m_lastReport) - v["majority_until"] = "now"; - else - v["majority_until"] = fs.m_lastMajority; - } - } - } - } - - if (fs.mVetoed) - v["veto"] = true; + v[jss::enabled] = fs.mEnabled; } -template Json::Value -AmendmentTableImpl::getJson (uint256 const& amendmentID) +AmendmentTableImpl::getJson (uint256 const& amendmentID) { Json::Value ret = Json::objectValue; Json::Value& jAmendment = (ret[to_string (amendmentID)] = Json::objectValue); @@ -657,101 +545,13 @@ AmendmentTableImpl::getJson (uint256 const& amendmentID) return ret; } -namespace detail -{ -class AppApiFacadeImpl final -{ -public: - void setMajorityTimesFromDBToState (AmendmentState& toUpdate, - uint256 const& amendmentHash) const; - void setMajorityTimesFromStateToDB ( - std::vector const& changedAmendments, - hash_map& amendmentMap) const; - ValidationSet getValidations (uint256 const& hash) const; -}; - -class AppApiFacadeMock final -{ -public: - void setMajorityTimesFromDBToState (AmendmentState& toUpdate, - uint256 const& amendmentHash) const {}; - void setMajorityTimesFromStateToDB ( - std::vector const& changedAmendments, - hash_map& amendmentMap) const {}; - ValidationSet getValidations (uint256 const& hash) const - { - return ValidationSet (); - }; -}; - -void AppApiFacadeImpl::setMajorityTimesFromDBToState ( - AmendmentState& toUpdate, - uint256 const& amendmentHash) const -{ - std::string query = - "SELECT FirstMajority,LastMajority FROM Features WHERE hash='"; - query.append (to_string (amendmentHash)); - query.append ("';"); - - auto db (getApp ().getWalletDB ().checkoutDb ()); - - boost::optional fm, lm; - soci::statement st = (db->prepare << query, - soci::into(fm), - soci::into(lm)); - st.execute (); - while (st.fetch ()) - { - toUpdate.m_firstMajority = fm.value_or (0); - toUpdate.m_lastMajority = lm.value_or (0); - } -} - -void AppApiFacadeImpl::setMajorityTimesFromStateToDB ( - std::vector const& changedAmendments, - hash_map& amendmentMap) const -{ - if (changedAmendments.empty ()) - return; - - auto db (getApp ().getWalletDB ().checkoutDb ()); - - soci::transaction tr(*db); - - for (auto const& hash : changedAmendments) - { - AmendmentState const& fState = amendmentMap[hash]; - *db << boost::str (boost::format ("UPDATE Features SET FirstMajority " - "= %d WHERE Hash = '%s';") % - fState.m_firstMajority % to_string (hash)); - *db << boost::str (boost::format ("UPDATE Features SET LastMajority " - "= %d WHERE Hash = '%s';") % - fState.m_lastMajority % to_string (hash)); - } - - tr.commit (); -} - -ValidationSet AppApiFacadeImpl::getValidations (uint256 const& hash) const -{ - return getApp ().getValidations ().getValidations (hash); -} -} // detail - std::unique_ptr make_AmendmentTable ( std::chrono::seconds majorityTime, int majorityFraction, - beast::Journal journal, - bool useMockFacade) + beast::Journal journal) { - if (useMockFacade) - { - return std::make_unique>( - majorityTime, majorityFraction, std::move (journal)); - } - - return std::make_unique>( - majorityTime, majorityFraction, std::move (journal)); + return std::make_unique( + majorityTime, majorityFraction, journal); } } // ripple diff --git a/src/ripple/app/misc/FeeVote.h b/src/ripple/app/misc/FeeVote.h index 5eac1842d6..b215c88749 100644 --- a/src/ripple/app/misc/FeeVote.h +++ b/src/ripple/app/misc/FeeVote.h @@ -21,6 +21,7 @@ #define RIPPLE_APP_MISC_FEEVOTE_H_INCLUDED #include +#include #include #include @@ -66,7 +67,7 @@ public: */ virtual void - doVoting (Ledger::ref lastClosedLedger, + doVoting (Ledger::ref lastClosedLedger, ValidationSet const& parentValidations, std::shared_ptr const& initialPosition) = 0; }; diff --git a/src/ripple/app/misc/FeeVoteImpl.cpp b/src/ripple/app/misc/FeeVoteImpl.cpp index e08db46311..07a35660b0 100644 --- a/src/ripple/app/misc/FeeVoteImpl.cpp +++ b/src/ripple/app/misc/FeeVoteImpl.cpp @@ -102,6 +102,7 @@ public: void doVoting (Ledger::ref lastClosedLedger, + ValidationSet const& parentValidations, std::shared_ptr const& initialPosition) override; }; @@ -145,6 +146,7 @@ FeeVoteImpl::doValidation (Ledger::ref lastClosedLedger, void FeeVoteImpl::doVoting (Ledger::ref lastClosedLedger, + ValidationSet const& set, std::shared_ptr const& initialPosition) { // LCL must be flag ledger @@ -159,10 +161,6 @@ FeeVoteImpl::doVoting (Ledger::ref lastClosedLedger, detail::VotableInteger incReserveVote ( lastClosedLedger->getReserveInc (), target_.owner_reserve); - // get validations for ledger before flag - ValidationSet const set = - getApp().getValidations ().getValidations ( - lastClosedLedger->getParentHash ()); for (auto const& e : set) { STValidation const& val = *e.second; diff --git a/src/ripple/app/misc/README.md b/src/ripple/app/misc/README.md index ede1bf956c..d664e4e6a2 100644 --- a/src/ripple/app/misc/README.md +++ b/src/ripple/app/misc/README.md @@ -90,6 +90,22 @@ support for those Amendments. Just a few nodes vetoing an Amendment will normall keep it from being accepted. Nodes could also vote yes on an Amendments even before it obtains a super-majority. This might make sense for a critical bug fix. +Validators that support an amendment that is not yet enabled announce their +support in their validations. If 80% support is achieved, they will introduce +a pseudo-transaction to track the amendment's majority status in the ledger. + +If an amendment whose majority status is reported in a ledger loses that +majority status, validators will introduce pseudo-transactions to remove +the majority status from the ledger. + +If an amendment holds majority status for two weeks, validators will +introduce a pseudo-transaction to enable the amendment. + +All amednements are assumed to be critical and irreversible. Thus there +is no mechanism to disable or revoke an amendment, nor is there a way +for a server to operate while an amendment it does not understand is +enabled. + --- # SHAMapStore: Online Delete diff --git a/src/ripple/app/misc/tests/AmendmentTable.test.cpp b/src/ripple/app/misc/tests/AmendmentTable.test.cpp index 4d44d4ba49..cc6a2647b5 100644 --- a/src/ripple/app/misc/tests/AmendmentTable.test.cpp +++ b/src/ripple/app/misc/tests/AmendmentTable.test.cpp @@ -22,11 +22,14 @@ #include #include #include +#include #include +#include #include namespace ripple { + class AmendmentTable_test final : public beast::unit_test::suite { public: @@ -39,7 +42,7 @@ private: addKnown }; - // 204/256 about 80% + // 204/256 about 80% (we round down because the implementation rounds up) static int const majorityFraction{204}; static void populateTable (AmendmentTable& table, @@ -95,51 +98,50 @@ private: return amendmentNames; } - static std::unique_ptr makeTable () + static std::unique_ptr< AmendmentTable > + makeTable (int w) { - beast::Journal journal; return make_AmendmentTable ( - weeks (2), + weeks (w), majorityFraction, - journal, - /*useMock*/ true); + deprecatedLogs().journal("TestAmendmentTable")); }; // Create the amendments by string pairs instead of AmendmentNames // as this helps test the AmendmentNames class - StringPairVec const m_validAmendmentPairs; - StringPairVec const m_notAddedAmendmentPairs; + StringPairVec const m_knownAmendmentPairs; + StringPairVec const m_unknownAmendmentPairs; public: AmendmentTable_test () - : m_validAmendmentPairs ( - {{"a49f90e7cddbcadfed8fc89ec4d02011", "Added1"}, - {"ca956ccabf25151a16d773171c485423", "Added2"}, - {"60dcd528f057711c5d26b57be28e23df", "Added3"}, - {"da956ccabf25151a16d773171c485423", "Added4"}, - {"70dcd528f057711c5d26b57be28e23df", "Added5"}, - {"70dcd528f057711c5d26b57be28e23d0", "Added6"}}) - , m_notAddedAmendmentPairs ( - {{"a9f90e7cddbcadfed8fc89ec4d02011c", "NotAdded1"}, - {"c956ccabf25151a16d773171c485423b", "NotAdded2"}, - {"6dcd528f057711c5d26b57be28e23dfa", "NotAdded3"}}) + : m_knownAmendmentPairs ( + {{"a49f90e7cddbcadfed8fc89ec4d02011", "Known1"}, + {"ca956ccabf25151a16d773171c485423", "Known2"}, + {"60dcd528f057711c5d26b57be28e23df", "Known3"}, + {"da956ccabf25151a16d773171c485423", "Known4"}, + {"70dcd528f057711c5d26b57be28e23df", "Known5"}, + {"70dcd528f057711c5d26b57be28e23d0", "Known6"}}) + , m_unknownAmendmentPairs ( + {{"a9f90e7cddbcadfed8fc89ec4d02011c", "Unknown1"}, + {"c956ccabf25151a16d773171c485423b", "Unknown2"}, + {"6dcd528f057711c5d26b57be28e23dfa", "Unknown3"}}) { } void testGet () { testcase ("get"); - auto table (makeTable ()); + auto table (makeTable (2)); std::vector const amendmentNames ( - populateTable (*table, m_validAmendmentPairs)); - std::vector const notAddedAmendmentNames ( - getAmendmentNames (m_notAddedAmendmentPairs)); + populateTable (*table, m_knownAmendmentPairs)); + std::vector const unknownAmendmentNames ( + getAmendmentNames (m_unknownAmendmentPairs)); for (auto const& i : amendmentNames) { expect (table->get (i.friendlyName ()) == i.id ()); } - for (auto const& i : notAddedAmendmentNames) + for (auto const& i : unknownAmendmentNames) { expect (table->get (i.friendlyName ()) == uint256 ()); } @@ -156,11 +158,11 @@ public: // test that the amendments we add are enabled and amendments we // didn't add are not enabled - auto table (makeTable ()); + auto table (makeTable (2)); std::vector const amendmentNames (populateTable ( - *table, m_validAmendmentPairs, tablePopulationAlgo)); - std::vector const notAddedAmendmentNames ( - getAmendmentNames (m_notAddedAmendmentPairs)); + *table, m_knownAmendmentPairs, tablePopulationAlgo)); + std::vector const unknownAmendmentNames ( + getAmendmentNames (m_unknownAmendmentPairs)); for (auto const& i : amendmentNames) { @@ -169,7 +171,7 @@ public: expect (table->isEnabled (i.id ())); } - for (auto const& i : notAddedAmendmentNames) + for (auto const& i : unknownAmendmentNames) { expect (!table->isSupported (i.id ())); expect (!table->isEnabled (i.id ())); @@ -187,7 +189,7 @@ public: for (auto const& i : badHexPairs) { StringPairVec v ({i}); - auto table (makeTable ()); + auto table (makeTable (2)); try { populateTable (*table, v, tablePopulationAlgo); @@ -224,7 +226,7 @@ public: for (auto const& i : badNumTokensConfigLines) { std::vector v ({i}); - auto table (makeTable ()); + auto table (makeTable (2)); try { populateTable (*table, v); @@ -252,9 +254,9 @@ public: void testEnable () { testcase ("enable"); - auto table (makeTable ()); + auto table (makeTable (2)); std::vector const amendmentNames ( - populateTable (*table, m_validAmendmentPairs)); + populateTable (*table, m_knownAmendmentPairs)); { // enable/disable tests for (auto const& i : amendmentNames) @@ -289,11 +291,11 @@ public: using ATGetter = bool (AmendmentTable::*)(uint256 const& amendment); void testVectorSetUnset (ATSetter setter, ATGetter getter) { - auto table (makeTable ()); + auto table (makeTable (2)); // make pointer to ref syntax a little nicer auto& tableRef (*table); std::vector const amendmentNames ( - populateTable (tableRef, m_validAmendmentPairs)); + populateTable (tableRef, m_knownAmendmentPairs)); // they should all be set for (auto const& i : amendmentNames) @@ -338,10 +340,10 @@ public: { // Check that supported/enabled aren't the same thing testcase ("supportedEnabled"); - auto table (makeTable ()); + auto table (makeTable (2)); std::vector const amendmentNames ( - populateTable (*table, m_validAmendmentPairs)); + populateTable (*table, m_knownAmendmentPairs)); { // support every even amendment @@ -375,8 +377,401 @@ public: } } - // TBD: veto/reportValidations/getJson/doValidation/doVoting - // Threading test? + std::vector makeValidators (int num) + { + std::vector ret; + ret.reserve (num); + for (int i = 0; i < num; ++i) + ret.push_back (RippleAddress::createNodePublic ( + RippleAddress::createSeedRandom ())); + return ret; + } + + static std::uint32_t weekTime (int w) + { + return w * (7*24*60*60); + } + + // Execute a pretend consensus round for a flag ledger + void doRound + ( AmendmentTable& table + , int week + , std::vector const& validators + , std::vector > const& votes + , std::vector & ourVotes + , enabledAmendments_t& enabled + , majorityAmendments_t& majority) + { + // Do a round at the specified time + // Returns the amendments we voted for + + // Parameters: + // table: Our table of known and vetoed amendments + // validators: The addreses of validators we trust + // votes: Amendments and the number of validators who vote for them + // ourVotes: The amendments we vote for in our validation + // enabled: In/out enabled amendments + // majority: In/our majority amendments (and when they got a majority) + + std::uint32_t const roundTime = weekTime (week); + + // Build validations + ValidationSet validations; + validations.reserve (validators.size ()); + + int i = 0; + for (auto const& val : validators) + { + STValidation::pointer v = + std::make_shared + (uint256(), roundTime, val, true); + + ++i; + STVector256 field (sfAmendments); + + for (auto const& amendment : votes) + { + if ((256 * i) < (validators.size() * amendment.second)) + { + // We vote yes on this amendment + field.push_back (amendment.first); + } + } + if (!field.empty ()) + v->setFieldV256 (sfAmendments, field); + + v->setTrusted(); + validations [val.getNodeID()] = v; + } + + ourVotes = table.doValidation (enabled); + + auto actions = table.doVoting (roundTime, enabled, majority, validations); + for (auto const& action : actions) + { + // This code assumes other validators do as we do + + auto const& hash = action.first; + switch (action.second) + { + case 0: + // amendment goes from majority to enabled + if (enabled.find (hash) != enabled.end ()) + throw std::runtime_error ("enabling already enabled"); + if (majority.find (hash) == majority.end ()) + throw std::runtime_error ("enabling without majority"); + enabled.insert (hash); + majority.erase (hash); + break; + + case tfGotMajority: + if (majority.find (hash) != majority.end ()) + throw std::runtime_error ("got majority while having majority"); + majority[hash] = roundTime; + break; + + case tfLostMajority: + if (majority.find (hash) == majority.end ()) + throw std::runtime_error ("lost majority without majority"); + majority.erase (hash); + break; + + default: + assert (false); + throw std::runtime_error ("unknown action"); + } + } + } + + // No vote on unknown amendment + void testNoUnknown () + { + testcase ("voteNoUnknown"); + + auto table (makeTable (2)); + + auto const validators = makeValidators (10); + + uint256 testAmendment; + testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa"); + + std::vector > votes; + std::vector ourVotes; + enabledAmendments_t enabled; + majorityAmendments_t majority; + + doRound (*table, 1, + validators, + votes, + ourVotes, + enabled, + majority); + expect (ourVotes.empty(), "Voted with nothing to vote on"); + expect (enabled.empty(), "Enabled amendment for no reason"); + expect (majority.empty(), "Majority found for no reason"); + + votes.emplace_back (testAmendment, 256); + + doRound (*table, 2, + validators, + votes, + ourVotes, + enabled, + majority); + expect (ourVotes.empty(), "Voted on unknown because others did"); + expect (enabled.empty(), "Enabled amendment for no reason"); + + majority[testAmendment] = weekTime(1); + + // Note that the simulation code assumes others behave as we do, + // so the amendment won't get enabled + doRound (*table, 5, + validators, + votes, + ourVotes, + enabled, + majority); + expect (ourVotes.empty(), "Voted on unknown because it had majority"); + expect (enabled.empty(), "Pseudo-transaction from nowhere"); + } + + // No vote on vetoed amendment + void testNoVetoed () + { + testcase ("voteNoVetoed"); + + auto table (makeTable (2)); + + auto const validators = makeValidators (10); + + uint256 testAmendment; + testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa"); + table->veto(testAmendment); + + std::vector > votes; + std::vector ourVotes; + enabledAmendments_t enabled; + majorityAmendments_t majority; + + doRound (*table, 1, + validators, + votes, + ourVotes, + enabled, + majority); + expect (ourVotes.empty(), "Voted with nothing to vote on"); + expect (enabled.empty(), "Enabled amendment for no reason"); + expect (majority.empty(), "Majority found for no reason"); + + votes.emplace_back (testAmendment, 256); + + doRound (*table, 2, + validators, + votes, + ourVotes, + enabled, + majority); + expect (ourVotes.empty(), "Voted on vetoed amendment because others did"); + expect (enabled.empty(), "Enabled amendment for no reason"); + + majority[testAmendment] = weekTime(1); + + doRound (*table, 5, + validators, + votes, + ourVotes, + enabled, + majority); + expect (ourVotes.empty(), "Voted on vetoed because it had majority"); + expect (enabled.empty(), "Enabled amendment for no reason"); + } + + // Vote on and enable known, not-enabled amendment + void testVoteEnable () + { + testcase ("voteEnable"); + + auto table (makeTable (2)); + auto const amendmentNames ( + populateTable (*table, m_knownAmendmentPairs)); + + auto const validators = makeValidators (10); + + std::vector > votes; + std::vector ourVotes; + enabledAmendments_t enabled; + majorityAmendments_t majority; + + // Week 1: We should vote for all known amendments not enabled + doRound (*table, 1, + validators, + votes, + ourVotes, + enabled, + majority); + expect (ourVotes.size() == amendmentNames.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"); + + // Now, everyone votes for this feature + for (auto const& i : amendmentNames) + votes.emplace_back (i.id(), 256); + + // Week 2: We should recognize a majority + doRound (*table, 2, + validators, + votes, + ourVotes, + enabled, + majority); + expect (ourVotes.size() == amendmentNames.size(), "Did not vote"); + expect (enabled.empty(), "Enabled amendment for no reason"); + for (auto const& i : amendmentNames) + expect (majority[i.id()] == weekTime(2), "majority not detected"); + + // Week 5: We should enable the amendment + doRound (*table, 5, + validators, + votes, + ourVotes, + enabled, + majority); + expect (enabled.size() == amendmentNames.size(), "Did not enable"); + + // Week 6: We should remove it from our votes and from having a majority + doRound (*table, 6, + validators, + votes, + ourVotes, + enabled, + majority); + expect (enabled.size() == amendmentNames.size(), "Disabled"); + expect (ourVotes.empty(), "Voted after enabling"); + for (auto const& i : amendmentNames) + expect(majority.find(i.id()) == 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 validators = makeValidators (16); + + enabledAmendments_t enabled; + majorityAmendments_t majority; + + for (int i = 0; i <= 17; ++i) + { + std::vector > votes; + std::vector ourVotes; + + if ((i > 0) && (i < 17)) + votes.emplace_back (testAmendment, i * 16); + + doRound (*table, i, + validators, votes, ourVotes, enabled, majority); + + if (i < 14) + { + // 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) + { + // 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 + { + // round 16 + // enable, keep voting, remove from majority + expect (!ourVotes.empty(), "We stopped voting"); + expect (majority.empty(), "Failed to remove from majority"); + expect (!enabled.empty(), "Did not enable"); + } + 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"); + expect (!enabled.empty(), "Did not enable"); + } + } + } + + // Detect loss of majority + void testLostMajority () + { + testcase ("lostMajority"); + + auto table (makeTable (8)); + + uint256 testAmendment; + testAmendment.SetHex("6dcd528f057711c5d26b57be28e23dfa"); + table->addKnown({testAmendment, "testAmendment"}); + + auto const validators = makeValidators (16); + + enabledAmendments_t enabled; + majorityAmendments_t majority; + + { + // establish majority + std::vector > votes; + std::vector ourVotes; + + votes.emplace_back (testAmendment, 250); + + doRound (*table, 1, + validators, votes, ourVotes, enabled, majority); + + expect (enabled.empty(), "Enabled for no reason"); + expect (!majority.empty(), "Failed to detect majority"); + } + + for (int i = 1; i < 16; ++i) + { + std::vector > votes; + std::vector ourVotes; + + // Gradually reduce support + votes.emplace_back (testAmendment, 256 - i * 8); + + doRound (*table, i + 1, + validators, votes, ourVotes, enabled, majority); + + if (i < 6) + { + // 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"); + expect (!majority.empty(), "Lost majority too early"); + } + 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"); + expect (enabled.empty(), "Enabled errneously"); + } + } + } void run () { @@ -385,6 +780,11 @@ public: testEnable (); testSupported (); testSupportedEnabled (); + testNoUnknown (); + testNoVetoed (); + testVoteEnable (); + testDetectMajority (); + testLostMajority (); } }; diff --git a/src/ripple/app/tx/impl/Change.cpp b/src/ripple/app/tx/impl/Change.cpp index bdf20b2af9..69ad701f09 100644 --- a/src/ripple/app/tx/impl/Change.cpp +++ b/src/ripple/app/tx/impl/Change.cpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace ripple { @@ -124,15 +125,67 @@ Change::applyAmendment() amendment) != amendments.end ()) return tefALREADY; - amendments.push_back (amendment); - amendmentObject->setFieldV256 (sfAmendments, amendments); + auto flags = mTxn.getFlags (); + + const bool gotMajority = (flags & tfGotMajority) != 0; + const bool lostMajority = (flags & tfLostMajority) != 0; + + if (gotMajority && lostMajority) + return temINVALID_FLAG; + + STArray newMajorities (sfMajorities); + + bool found = false; + if (amendmentObject->isFieldPresent (sfMajorities)) + { + const STArray &oldMajorities = amendmentObject->getFieldArray (sfMajorities); + for (auto const& majority : oldMajorities) + { + if (majority.getFieldH256 (sfAmendment) == amendment) + { + if (gotMajority) + return tefALREADY; + found = true; + } + else + { + // pass through + newMajorities.push_back (majority); + } + } + } + + if (! found && lostMajority) + return tefALREADY; + + if (gotMajority) + { + // This amendment now has a majority + newMajorities.push_back (STObject (sfMajority)); + auto& entry = newMajorities.back (); + entry.emplace_back (STHash256 (sfAmendment, amendment)); + entry.emplace_back (STUInt32 (sfCloseTime, + view().parentCloseTime())); + } + else if (!lostMajority) + { + // No flags, enable amendment + amendments.push_back (amendment); + amendmentObject->setFieldV256 (sfAmendments, amendments); + + getApp().getAmendmentTable ().enable (amendment); + + if (!getApp().getAmendmentTable ().isSupported (amendment)) + getApp().getOPs ().setAmendmentBlocked (); + } + + if (newMajorities.empty ()) + amendmentObject->makeFieldAbsent (sfMajorities); + else + amendmentObject->setFieldArray (sfMajorities, newMajorities); + view().update (amendmentObject); - getApp().getAmendmentTable ().enable (amendment); - - if (!getApp().getAmendmentTable ().isSupported (amendment)) - getApp().getOPs ().setAmendmentBlocked (); - return tesSUCCESS; } diff --git a/src/ripple/ledger/ReadView.h b/src/ripple/ledger/ReadView.h index 37af96f763..7d00fe1702 100644 --- a/src/ripple/ledger/ReadView.h +++ b/src/ripple/ledger/ReadView.h @@ -285,9 +285,6 @@ public: digest (key_type const& key) const = 0; }; -// For now -using BasicView = ReadView; - } // ripple #include diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index e784adc059..80c9676ce7 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -131,6 +131,16 @@ cdirNext (ReadView const& view, unsigned int& uDirEntry, // <-> next entry uint256& uEntryIndex); // <-- The entry, if available. Otherwise, zero. +// Return the list of enabled amendments +using enabledAmendments_t = std::set ; +enabledAmendments_t +getEnabledAmendments (ReadView const& view); + +// Return a map of amendments that have achieved majority +using majorityAmendments_t = std::map ; +majorityAmendments_t +getMajorityAmendments (ReadView const& view); + //------------------------------------------------------------------------------ // // Modifiers diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 2bda381a87..70b3648852 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace ripple { @@ -388,6 +389,37 @@ cdirNext (ReadView const& view, return true; } +enabledAmendments_t +getEnabledAmendments (ReadView const& view) +{ + enabledAmendments_t amendments; + auto const sleAmendments = view.read(keylet::amendments()); + + if (sleAmendments) + { + for (auto const &a : sleAmendments->getFieldV256 (sfAmendments)) + amendments.insert (a); + } + + return amendments; +} + +majorityAmendments_t +getMajorityAmendments (ReadView const& view) +{ + majorityAmendments_t majorities; + auto const sleAmendments = view.read(keylet::amendments()); + + if (sleAmendments && sleAmendments->isFieldPresent (sfMajorities)) + { + auto const& majArray = sleAmendments->getFieldArray (sfMajorities); + for (auto const& m : majArray) + majorities[m.getFieldH256 (sfAmendment)] = m.getFieldU32 (sfCloseTime); + } + + return majorities; +} + //------------------------------------------------------------------------------ // // Modifiers diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index b4acabf413..ec1877acb0 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -436,6 +436,7 @@ extern SField const sfMemo; extern SField const sfSignerEntry; extern SField const sfSigningAccount; extern SField const sfSigningFor; +extern SField const sfMajority; // array of objects // ARRAY/1 is reserved for end of array @@ -447,6 +448,7 @@ extern SField const sfNecessary; extern SField const sfSufficient; extern SField const sfAffectedNodes; extern SField const sfMemos; +extern SField const sfMajorities; //------------------------------------------------------------------------------ diff --git a/src/ripple/protocol/STVector256.h b/src/ripple/protocol/STVector256.h index c1c66e61db..351b05a157 100644 --- a/src/ripple/protocol/STVector256.h +++ b/src/ripple/protocol/STVector256.h @@ -41,6 +41,10 @@ public: : mValue (vector) { } + STVector256 (SField const& n, std::vector const& vector) + : STBase (n), mValue (vector) + { } + STVector256 (SerialIter& sit, SField const& name); STBase* diff --git a/src/ripple/protocol/TxFlags.h b/src/ripple/protocol/TxFlags.h index 3791c47dd9..bd67b15aca 100644 --- a/src/ripple/protocol/TxFlags.h +++ b/src/ripple/protocol/TxFlags.h @@ -91,6 +91,10 @@ const std::uint32_t tfClearFreeze = 0x00200000; const std::uint32_t tfTrustSetMask = ~ (tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze | tfClearFreeze); +// EnableAmendment flags: +const std::uint32_t tfGotMajority = 0x00010000; +const std::uint32_t tfLostMajority = 0x00020000; + } // ripple #endif diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index c01d093cc3..225ae9fefe 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -87,11 +87,14 @@ LedgerFormats::LedgerFormats () << SOElement (sfHashes, SOE_REQUIRED) ; - add ("EnabledAmendments", ltAMENDMENTS) - << SOElement (sfAmendments, SOE_REQUIRED) + add ("Amendments", ltAMENDMENTS) + << SOElement (sfLedgerSequence, SOE_OPTIONAL) + << SOElement (sfAmendments, SOE_OPTIONAL) // Enabled + << SOElement (sfMajorities, SOE_OPTIONAL) ; add ("FeeSettings", ltFEE_SETTINGS) + << SOElement (sfLedgerSequence, SOE_OPTIONAL) << SOElement (sfBaseFee, SOE_REQUIRED) << SOElement (sfReferenceFeeUnits, SOE_REQUIRED) << SOElement (sfReserveBase, SOE_REQUIRED) diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index a8fbf97039..46e04f76e8 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -226,6 +226,7 @@ SField const sfSignerEntry = make::one(&sfSignerEntry, STI_OBJEC // inner object (uncommon) SField const sfSigningAccount = make::one(&sfSigningAccount, STI_OBJECT, 16, "SigningAccount"); SField const sfSigningFor = make::one(&sfSigningFor, STI_OBJECT, 17, "SigningFor"); +SField const sfMajority = make::one(&sfMajority, STI_OBJECT, 18, "Majority"); // array of objects // ARRAY/1 is reserved for end of array @@ -238,6 +239,9 @@ SField const sfSufficient = make::one(&sfSufficient, STI_ARRAY, 7, "Su SField const sfAffectedNodes = make::one(&sfAffectedNodes, STI_ARRAY, 8, "AffectedNodes"); SField const sfMemos = make::one(&sfMemos, STI_ARRAY, 9, "Memos"); +// array of objects (uncommon) +SField const sfMajorities = make::one(&sfMajorities, STI_ARRAY, 16, "Majorities"); + SField::SField (SerializedTypeID tid, int fv, const char* fn, int meta, IsSigning signing) : fieldCode (field_code (tid, fv))