From 44450bf6448c56ca3f218ba726a9b4e33d422e51 Mon Sep 17 00:00:00 2001 From: Scott Determan Date: Fri, 9 Jan 2015 09:49:18 -0500 Subject: [PATCH] Enable Amendments from config file or static data (RIPD-746): * The rippled.cfg file has a new section called "amendments" * Each line in this section contains two white-space separated items ** The first item is the ID of the amendment (a 256-bit hash) ** The second item is the friendly name * Replaces config section name macros with variables * Make addKnown arguments safer * Added lock to addKnown --- Builds/VisualStudio2013/RippleD.vcxproj | 3 + .../VisualStudio2013/RippleD.vcxproj.filters | 6 + src/ripple/app/main/Application.cpp | 9 +- src/ripple/app/misc/AmendmentTable.h | 97 ++++- src/ripple/app/misc/AmendmentTableImpl.cpp | 395 +++++++++++++----- .../app/misc/tests/AmendmentTable.test.cpp | 391 +++++++++++++++++ src/ripple/core/ConfigSections.h | 1 + src/ripple/unity/app6.cpp | 1 + 8 files changed, 765 insertions(+), 138 deletions(-) mode change 100644 => 100755 Builds/VisualStudio2013/RippleD.vcxproj mode change 100644 => 100755 Builds/VisualStudio2013/RippleD.vcxproj.filters create mode 100644 src/ripple/app/misc/tests/AmendmentTable.test.cpp diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj old mode 100644 new mode 100755 index 98fab1e69c..3861c4a1ec --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -1990,6 +1990,9 @@ + + True + True diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters old mode 100644 new mode 100755 index d3f19eddbe..e1d31811a2 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -325,6 +325,9 @@ {5A1509B2-871B-A7AC-1E60-544D3F398741} + + {815DC1A2-E2EF-E6E3-D979-19AD1476A28B} + {0FCD3973-E9A6-7172-C8A3-C3401E1A03DD} @@ -2964,6 +2967,9 @@ ripple\app\misc + + ripple\app\misc\tests + ripple\app\misc diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 1449fee87c..e44ec8dd75 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -317,8 +318,9 @@ public: , m_validators (Validators::make_Manager(*this, get_io_service(), getConfig ().getModuleDatabasePath (), m_logs.journal("UVL"))) - , m_amendmentTable (make_AmendmentTable (weeks(2), MAJORITY_FRACTION, - m_logs.journal("AmendmentTable"))) + , m_amendmentTable (make_AmendmentTable + (weeks(2), MAJORITY_FRACTION, + m_logs.journal("AmendmentTable"))) , mFeeTrack (LoadFeeTrack::New (m_logs.journal("LoadManager"))) @@ -650,7 +652,8 @@ public: if (!getConfig ().RUN_STANDALONE) updateTables (); - m_amendmentTable->addInitial(); + m_amendmentTable->addInitial ( + getConfig ().section (SECTION_AMENDMENTS)); initializePathfinding (); m_ledgerMaster->setMinValidations (getConfig ().VALIDATION_QUORUM); diff --git a/src/ripple/app/misc/AmendmentTable.h b/src/ripple/app/misc/AmendmentTable.h index 1a6e809070..3fd3da6d7f 100644 --- a/src/ripple/app/misc/AmendmentTable.h +++ b/src/ripple/app/misc/AmendmentTable.h @@ -21,6 +21,7 @@ #define RIPPLE_AMENDMENT_TABLE_H #include +#include namespace ripple { @@ -46,6 +47,48 @@ public: } }; +/** 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. @@ -53,22 +96,19 @@ public: class AmendmentState { public: - bool mVetoed; // We don't want this amendment enabled - bool mEnabled; - bool mSupported; - bool mDefault; // Include in genesis ledger + bool mVetoed{false}; // We don't want this amendment enabled + bool mEnabled{false}; + bool mSupported{false}; + bool mDefault{false}; // Include in genesis ledger - core::Clock::time_point m_firstMajority; // First time we saw a majority (close time) - core::Clock::time_point m_lastMajority; // Most recent time we saw a majority (close time) + core::Clock::time_point + m_firstMajority{0}; // First time we saw a majority (close time) + core::Clock::time_point + m_lastMajority{0}; // Most recent time we saw a majority (close time) std::string mFriendlyName; - AmendmentState () - : mVetoed (false), mEnabled (false), mSupported (false), mDefault (false), - m_firstMajority (0), m_lastMajority (0) - { - ; - } + AmendmentState () = default; void setVeto () { @@ -104,6 +144,8 @@ 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. @@ -123,10 +165,17 @@ public: virtual ~AmendmentTable() { } - virtual void addInitial () = 0; + /** + @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 AmendmentState* addKnown (const char* amendmentID, - const char* friendlyName, bool veto) = 0; virtual uint256 get (std::string const& name) = 0; virtual bool veto (uint256 const& amendment) = 0; @@ -138,9 +187,17 @@ 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; + /** Update the walletDB with the majority times. + */ virtual void reportValidations (const AmendmentSet&) = 0; virtual Json::Value getJson (int) = 0; @@ -154,10 +211,12 @@ public: doVoting (Ledger::ref lastClosedLedger, SHAMap::ref initialPosition) = 0; }; -std::unique_ptr -make_AmendmentTable (std::chrono::seconds majorityTime, int majorityFraction, - beast::Journal journal); +std::unique_ptr make_AmendmentTable ( + std::chrono::seconds majorityTime, + int majorityFraction, + beast::Journal journal, + bool useMockFacade = false); -} // ripple +} // ripple #endif diff --git a/src/ripple/app/misc/AmendmentTableImpl.cpp b/src/ripple/app/misc/AmendmentTableImpl.cpp index 75da35b182..8125cc1e97 100644 --- a/src/ripple/app/misc/AmendmentTableImpl.cpp +++ b/src/ripple/app/misc/AmendmentTableImpl.cpp @@ -19,24 +19,24 @@ #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 that is identified by a 256-bit amendment identifier - and adopted, or rejected, by the network. + 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 : public AmendmentTable +template +class AmendmentTableImpl final : public AmendmentTable { protected: typedef hash_map amendmentMap_t; - typedef std::pair amendmentIt_t; typedef hash_set amendmentList_t; typedef RippleMutex LockType; @@ -49,14 +49,18 @@ protected: core::Clock::time_point m_firstReport; // close time of first majority report core::Clock::time_point m_lastReport; // close time of most recent majority report beast::Journal m_journal; + AppApiFacade m_appApiFacade; - AmendmentState* getCreate (uint256 const& amendment, bool create); + 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: - AmendmentTableImpl (std::chrono::seconds majorityTime, int majorityFraction, - beast::Journal journal) + AmendmentTableImpl ( + std::chrono::seconds majorityTime, + int majorityFraction, + beast::Journal journal) : m_majorityTime (majorityTime) , mMajorityFraction (majorityFraction) , m_firstReport (0) @@ -65,10 +69,10 @@ public: { } - void addInitial () override; + void addInitial (Section const& section) override; + + void addKnown (AmendmentName const& name) override; - AmendmentState* addKnown (const char* amendmentID, const char* friendlyName, - bool veto) override; uint256 get (std::string const& name) override; bool veto (uint256 const& amendment) override; @@ -97,51 +101,114 @@ public: amendmentList_t getDesired(); // amendments we support, do not veto, are not enabled }; -void -AmendmentTableImpl::addInitial () +namespace detail { - // For each amendment this version supports, construct the AmendmentState object by calling - // addKnown. Set any vetoes or defaults. A pointer to the AmendmentState can be stashed +/** 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; } -AmendmentState* -AmendmentTableImpl::getCreate (uint256 const& amendmentHash, bool create) +template +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 std::runtime_error (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 std::runtime_error (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 std::runtime_error (errorMsg); + } + } + } + + for (auto const& a : toAdd) + { + addKnown (a); + enable (a.id ()); + } +} + +template +AmendmentState& +AmendmentTableImpl::getCreate (uint256 const& amendmentHash) { // call with the mutex held auto iter (m_amendmentMap.find (amendmentHash)); if (iter == m_amendmentMap.end()) { - if (!create) - return nullptr; - - AmendmentState* amendment = & (m_amendmentMap[amendmentHash]); - - { - std::string query = "SELECT FirstMajority,LastMajority FROM Features WHERE hash='"; - query.append (to_string (amendmentHash)); - query.append ("';"); - - auto sl (getApp().getWalletDB ().lock ()); - auto db = getApp().getWalletDB ().getDB (); - - if (db->executeSQL (query) && db->startIterRows ()) - { - amendment->m_firstMajority = db->getBigInt("FirstMajority"); - amendment->m_lastMajority = db->getBigInt("LastMajority"); - db->endIterRows (); - } - } - + AmendmentState& amendment = m_amendmentMap[amendmentHash]; + m_appApiFacade.setMajorityTimesFromDBToState (amendment, amendmentHash); return amendment; } + return iter->second; +} + +template +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); } +template uint256 -AmendmentTableImpl::get (std::string const& name) +AmendmentTableImpl::get (std::string const& name) { + ScopedLockType sl (mLock); + for (auto const& e : m_amendmentMap) { if (name == e.second.mFriendlyName) @@ -151,48 +218,50 @@ AmendmentTableImpl::get (std::string const& name) return uint256 (); } -AmendmentState* -AmendmentTableImpl::addKnown (const char* amendmentID, const char* friendlyName, - bool veto) +template +void +AmendmentTableImpl::addKnown (AmendmentName const& name) { - uint256 hash; - hash.SetHex (amendmentID); - - if (hash.isZero ()) + if (!name.valid ()) { - assert (false); - return nullptr; + std::string const errorMsg = + (boost::format ( + "addKnown was given an invalid hash (expected a hex number). " + "Value was: %1%") % + name.hexString ()).str (); + throw std::runtime_error (errorMsg); } - AmendmentState* f = getCreate (hash, true); + ScopedLockType sl (mLock); + AmendmentState& amendment = getCreate (name.id ()); - if (friendlyName != nullptr) - f->setFriendlyName (friendlyName); + if (!name.friendlyName ().empty ()) + amendment.setFriendlyName (name.friendlyName ()); - f->mVetoed = veto; - f->mSupported = true; - - return f; + amendment.mVetoed = false; + amendment.mSupported = true; } +template bool -AmendmentTableImpl::veto (uint256 const& amendment) +AmendmentTableImpl::veto (uint256 const& amendment) { ScopedLockType sl (mLock); - AmendmentState* s = getCreate (amendment, true); + AmendmentState& s = getCreate (amendment); - if (s->mVetoed) + if (s.mVetoed) return false; - s->mVetoed = true; + s.mVetoed = true; return true; } +template bool -AmendmentTableImpl::unVeto (uint256 const& amendment) +AmendmentTableImpl::unVeto (uint256 const& amendment) { ScopedLockType sl (mLock); - AmendmentState* s = getCreate (amendment, false); + AmendmentState* s = getExisting (amendment); if (!s || !s->mVetoed) return false; @@ -201,24 +270,26 @@ 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, true); + AmendmentState& s = getCreate (amendment); - if (s->mEnabled) + if (s.mEnabled) return false; - s->mEnabled = true; + s.mEnabled = true; return true; } +template bool -AmendmentTableImpl::disable (uint256 const& amendment) +AmendmentTableImpl::disable (uint256 const& amendment) { ScopedLockType sl (mLock); - AmendmentState* s = getCreate (amendment, false); + AmendmentState* s = getExisting (amendment); if (!s || !s->mEnabled) return false; @@ -227,24 +298,27 @@ AmendmentTableImpl::disable (uint256 const& amendment) return true; } +template bool -AmendmentTableImpl::isEnabled (uint256 const& amendment) +AmendmentTableImpl::isEnabled (uint256 const& amendment) { ScopedLockType sl (mLock); - AmendmentState* s = getCreate (amendment, false); + 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 = getCreate (amendment, false); + AmendmentState* s = getExisting (amendment); return s && s->mSupported; } -AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getVetoed () +template +typename AmendmentTableImpl::amendmentList_t +AmendmentTableImpl::getVetoed () { amendmentList_t ret; ScopedLockType sl (mLock); @@ -256,8 +330,9 @@ AmendmentTableImpl::getVetoed () return ret; } -AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getEnabled () +template +typename AmendmentTableImpl::amendmentList_t +AmendmentTableImpl::getEnabled () { amendmentList_t ret; ScopedLockType sl (mLock); @@ -269,8 +344,9 @@ AmendmentTableImpl::getEnabled () return ret; } +template bool -AmendmentTableImpl::shouldEnable (std::uint32_t closeTime, +AmendmentTableImpl::shouldEnable (std::uint32_t closeTime, const AmendmentState& fs) { if (fs.mVetoed || fs.mEnabled || !fs.mSupported || (fs.m_lastMajority != m_lastReport)) @@ -286,8 +362,9 @@ AmendmentTableImpl::shouldEnable (std::uint32_t closeTime, return (fs.m_lastMajority - fs.m_firstMajority) > m_majorityTime.count(); } -AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getToEnable (core::Clock::time_point closeTime) +template +typename AmendmentTableImpl::amendmentList_t +AmendmentTableImpl::getToEnable (core::Clock::time_point closeTime) { amendmentList_t ret; ScopedLockType sl (mLock); @@ -304,8 +381,9 @@ AmendmentTableImpl::getToEnable (core::Clock::time_point closeTime) return ret; } -AmendmentTableImpl::amendmentList_t -AmendmentTableImpl::getDesired () +template +typename AmendmentTableImpl::amendmentList_t +AmendmentTableImpl::getDesired () { amendmentList_t ret; ScopedLockType sl (mLock); @@ -319,8 +397,9 @@ AmendmentTableImpl::getDesired () return ret; } +template void -AmendmentTableImpl::reportValidations (const AmendmentSet& set) +AmendmentTableImpl::reportValidations (const AmendmentSet& set) { if (set.mTrustedValidations == 0) return; @@ -335,7 +414,7 @@ AmendmentTableImpl::reportValidations (const AmendmentSet& set) m_firstReport = set.mCloseTime; std::vector changedAmendments; - changedAmendments.resize(set.mVotes.size()); + changedAmendments.reserve (set.mVotes.size()); for (auto const& e : set.mVotes) { @@ -378,27 +457,15 @@ AmendmentTableImpl::reportValidations (const AmendmentSet& set) if (!changedAmendments.empty()) { - auto sl (getApp().getWalletDB ().lock ()); - auto db = getApp().getWalletDB ().getDB (); - - db->executeSQL ("BEGIN TRANSACTION;"); - for (auto const& hash : changedAmendments) - { - AmendmentState& fState = m_amendmentMap[hash]; - db->executeSQL (boost::str (boost::format ( - "UPDATE Features SET FirstMajority = %d WHERE Hash = '%s';") % - fState.m_firstMajority % to_string (hash))); - db->executeSQL (boost::str (boost::format ( - "UPDATE Features SET LastMajority = %d WHERE Hash = '%s';") % - fState.m_lastMajority % to_string(hash))); - } - db->executeSQL ("END TRANSACTION;"); - changedAmendments.clear(); + 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) @@ -411,8 +478,9 @@ AmendmentTableImpl::setEnabled (const std::vector& amendments) } } +template void -AmendmentTableImpl::setSupported (const std::vector& amendments) +AmendmentTableImpl::setSupported (const std::vector& amendments) { ScopedLockType sl (mLock); for (auto &e : m_amendmentMap) @@ -425,8 +493,9 @@ AmendmentTableImpl::setSupported (const std::vector& amendments) } } +template void -AmendmentTableImpl::doValidation (Ledger::ref lastClosedLedger, +AmendmentTableImpl::doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation) { amendmentList_t lAmendments = getDesired(); @@ -441,8 +510,9 @@ AmendmentTableImpl::doValidation (Ledger::ref lastClosedLedger, baseValidation.setFieldV256 (sfAmendments, vAmendments); } +template void -AmendmentTableImpl::doVoting (Ledger::ref lastClosedLedger, +AmendmentTableImpl::doVoting (Ledger::ref lastClosedLedger, SHAMap::ref initialPosition) { @@ -452,7 +522,8 @@ AmendmentTableImpl::doVoting (Ledger::ref lastClosedLedger, AmendmentSet amendmentSet (lastClosedLedger->getParentCloseTimeNC ()); // get validations for ledger before flag ledger - ValidationSet valSet = getApp().getValidations ().getValidations (lastClosedLedger->getParentHash ()); + ValidationSet valSet = m_appApiFacade.getValidations ( + lastClosedLedger->getParentHash ()); for (auto const& entry : valSet) { auto const& val = *entry.second; @@ -500,8 +571,9 @@ AmendmentTableImpl::doVoting (Ledger::ref lastClosedLedger, } } +template Json::Value -AmendmentTableImpl::getJson (int) +AmendmentTableImpl::getJson (int) { Json::Value ret(Json::objectValue); { @@ -514,8 +586,9 @@ 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["name"] = fs.mFriendlyName; @@ -560,8 +633,9 @@ AmendmentTableImpl::setJson (Json::Value& v, const AmendmentState& fs) v["veto"] = true; } +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); @@ -569,19 +643,108 @@ AmendmentTableImpl::getJson (uint256 const& amendmentID) { ScopedLockType sl(mLock); - AmendmentState *amendmentState = getCreate (amendmentID, true); - setJson (jAmendment, *amendmentState); + AmendmentState& amendmentState = getCreate (amendmentID); + setJson (jAmendment, amendmentState); } return ret; } -std::unique_ptr -make_AmendmentTable (std::chrono::seconds majorityTime, int majorityFraction, - beast::Journal journal) +namespace detail { - return std::make_unique (majorityTime, majorityFraction, - journal); +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& walletDB (getApp ().getWalletDB ()); + auto sl (walletDB.lock ()); + auto db (walletDB.getDB ()); + + if (db->executeSQL (query) && db->startIterRows ()) + { + toUpdate.m_firstMajority = db->getBigInt ("FirstMajority"); + toUpdate.m_lastMajority = db->getBigInt ("LastMajority"); + db->endIterRows (); + } } -} // ripple +void AppApiFacadeImpl::setMajorityTimesFromStateToDB ( + std::vector const& changedAmendments, + hash_map& amendmentMap) const +{ + if (changedAmendments.empty ()) + return; + + auto& walletDB (getApp ().getWalletDB ()); + auto sl (walletDB.lock ()); + auto db (walletDB.getDB ()); + + db->executeSQL ("BEGIN TRANSACTION;"); + for (auto const& hash : changedAmendments) + { + AmendmentState const& fState = amendmentMap[hash]; + db->executeSQL (boost::str (boost::format ( + "UPDATE Features SET FirstMajority " + "= %d WHERE Hash = '%s';") % + fState.m_firstMajority % to_string (hash))); + db->executeSQL (boost::str (boost::format ( + "UPDATE Features SET LastMajority " + "= %d WHERE Hash = '%s';") % + fState.m_lastMajority % to_string (hash))); + } + db->executeSQL ("END TRANSACTION;"); +} + +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) +{ + if (useMockFacade) + { + return std::make_unique>( + majorityTime, majorityFraction, std::move (journal)); + } + + return std::make_unique>( + majorityTime, majorityFraction, std::move (journal)); +} + +} // ripple diff --git a/src/ripple/app/misc/tests/AmendmentTable.test.cpp b/src/ripple/app/misc/tests/AmendmentTable.test.cpp new file mode 100644 index 0000000000..7e9e0ac8ac --- /dev/null +++ b/src/ripple/app/misc/tests/AmendmentTable.test.cpp @@ -0,0 +1,391 @@ +//------------------------------------------------------------------------------ +/* + 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 + +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% + static int const majorityFraction{204}; + + static void populateTable (AmendmentTable& table, + std::vector const& configLines) + { + Section section (SECTION_AMENDMENTS); + section.append (configLines); + table.addInitial (section); + } + + static std::vector getAmendmentNames ( + StringPairVec const& amendmentPairs) + { + std::vector amendmentNames; + amendmentNames.reserve (amendmentPairs.size ()); + for (auto const& i : amendmentPairs) + { + amendmentNames.emplace_back (i.first, i.second); + } + return amendmentNames; + } + + std::vector populateTable ( + AmendmentTable& table, + StringPairVec const& amendmentPairs, + TablePopulationAlgo populationAlgo = TablePopulationAlgo::addKnown) + { + 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; + } + + static std::unique_ptr makeTable () + { + beast::Journal journal; + return make_AmendmentTable ( + weeks (2), + majorityFraction, + journal, + /*useMock*/ true); + }; + + // Create the amendments by string pairs instead of AmendmentNames + // as this helps test the AmendmentNames class + StringPairVec const m_validAmendmentPairs; + StringPairVec const m_notAddedAmendmentPairs; + +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"}}) + { + } + + void testGet () + { + testcase ("get"); + auto table (makeTable ()); + std::vector const amendmentNames ( + populateTable (*table, m_validAmendmentPairs)); + std::vector const notAddedAmendmentNames ( + getAmendmentNames (m_notAddedAmendmentPairs)); + for (auto const& i : amendmentNames) + { + expect (table->get (i.friendlyName ()) == i.id ()); + } + + for (auto const& i : notAddedAmendmentNames) + { + expect (table->get (i.friendlyName ()) == uint256 ()); + } + } + + void testAddInitialAddKnown () + { + testcase ("addInitialAddKnown"); + + for (auto tablePopulationAlgo : + {TablePopulationAlgo::addInitial, TablePopulationAlgo::addKnown}) + { + { + // test that the amendments we add are enabled and amendments we + // didn't add are not enabled + + auto table (makeTable ()); + std::vector const amendmentNames (populateTable ( + *table, m_validAmendmentPairs, tablePopulationAlgo)); + std::vector const notAddedAmendmentNames ( + getAmendmentNames (m_notAddedAmendmentPairs)); + + for (auto const& i : amendmentNames) + { + expect (table->isSupported (i.id ())); + if (tablePopulationAlgo == TablePopulationAlgo::addInitial) + expect (table->isEnabled (i.id ())); + } + + for (auto const& i : notAddedAmendmentNames) + { + expect (!table->isSupported (i.id ())); + expect (!table->isEnabled (i.id ())); + } + } + + { + // check that we throw an exception on bad hex pairs + StringPairVec const badHexPairs ( + {{"a9f90e7cddbcadfedm8fc89ec4d02011c", "BadHex1"}, + {"c956ccabf25151a16d77T3171c485423b", "BadHex2"}, + {"6dcd528f057711c5d2Z6b57be28e23dfa", "BadHex3"}}); + + // make sure each element throws + for (auto const& i : badHexPairs) + { + StringPairVec v ({i}); + auto table (makeTable ()); + try + { + populateTable (*table, v, tablePopulationAlgo); + // line above should throw + fail ("didn't throw"); + } + catch (...) + { + pass (); + } + try + { + populateTable ( + *table, badHexPairs, tablePopulationAlgo); + // line above should throw + fail ("didn't throw"); + } + catch (...) + { + pass (); + } + } + } + } + + { + // check that we thow on bad num tokens + std::vector const badNumTokensConfigLines ( + {"19f6d", + "19fd6 bad friendly name" + "9876 one two"}); + + // make sure each element throws + for (auto const& i : badNumTokensConfigLines) + { + std::vector v ({i}); + auto table (makeTable ()); + try + { + populateTable (*table, v); + // line above should throw + fail ("didn't throw"); + } + catch (...) + { + pass (); + } + try + { + populateTable (*table, badNumTokensConfigLines); + // line above should throw + fail ("didn't throw"); + } + catch (...) + { + pass (); + } + } + } + } + + void testEnable () + { + testcase ("enable"); + auto table (makeTable ()); + std::vector const amendmentNames ( + populateTable (*table, m_validAmendmentPairs)); + { + // 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::vector toEnable; + for (auto const& i : amendmentNames) + { + auto id (i.id ()); + toEnable.emplace_back (id); + table->disable (id); + expect (!table->isEnabled (id)); + } + table->setEnabled (toEnable); + for (auto const& i : toEnable) + { + expect (table->isEnabled (i)); + } + } + } + + using ATSetter = + void (AmendmentTable::*)(const std::vector& amendments); + using ATGetter = bool (AmendmentTable::*)(uint256 const& amendment); + void testVectorSetUnset (ATSetter setter, ATGetter getter) + { + auto table (makeTable ()); + // make pointer to ref syntax a little nicer + auto& tableRef (*table); + std::vector const amendmentNames ( + populateTable (tableRef, m_validAmendmentPairs)); + + // they should all be set + for (auto const& i : amendmentNames) + { + expect ((tableRef.*getter)(i.id ())); // i.e. "isSupported" + } + + { + // 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 ()); + + std::vector const amendmentNames ( + populateTable (*table, m_validAmendmentPairs)); + + { + // 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 ()))); + } + } + } + + // TBD: veto/reportValidations/getJson/doValidation/doVoting + // Threading test? + + void run () + { + testGet (); + testAddInitialAddKnown (); + testEnable (); + testSupported (); + testSupportedEnabled (); + } +}; + +BEAST_DEFINE_TESTSUITE (AmendmentTable, app, ripple); + +} // ripple diff --git a/src/ripple/core/ConfigSections.h b/src/ripple/core/ConfigSections.h index 8ebb357e4d..f4bb70fea9 100644 --- a/src/ripple/core/ConfigSections.h +++ b/src/ripple/core/ConfigSections.h @@ -32,6 +32,7 @@ struct ConfigSection // VFALCO TODO Rename and replace these macros with variables. #define SECTION_ACCOUNT_PROBE_MAX "account_probe_max" +#define SECTION_AMENDMENTS "amendments" #define SECTION_CLUSTER_NODES "cluster_nodes" #define SECTION_DATABASE_PATH "database_path" #define SECTION_DEBUG_LOGFILE "debug_logfile" diff --git a/src/ripple/unity/app6.cpp b/src/ripple/unity/app6.cpp index 3ba3083820..151e87a55b 100644 --- a/src/ripple/unity/app6.cpp +++ b/src/ripple/unity/app6.cpp @@ -30,3 +30,4 @@ #include #include #include +#include