diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
old mode 100644
new mode 100755
index 98fab1e69..3861c4a1e
--- 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 d3f19eddb..e1d31811a
--- 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 1449fee87..e44ec8dd7 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 1a6e80907..3fd3da6d7 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 75da35b18..8125cc1e9 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 000000000..7e9e0ac8a
--- /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 8ebb357e4..f4bb70fea 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 3ba308382..151e87a55 100644
--- a/src/ripple/unity/app6.cpp
+++ b/src/ripple/unity/app6.cpp
@@ -30,3 +30,4 @@
#include
#include
#include
+#include