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
This commit is contained in:
JoelKatz
2015-01-30 12:57:57 -08:00
committed by Vinnie Falco
parent 3078c6da12
commit efc2159441
18 changed files with 814 additions and 412 deletions

View File

@@ -1390,8 +1390,10 @@ void LedgerConsensusImp::takeInitialPosition (Ledger& initialLedger)
// previous ledger was flag ledger // previous ledger was flag ledger
std::shared_ptr<SHAMap> preSet std::shared_ptr<SHAMap> preSet
= initialLedger.txMap().snapShot (true); = initialLedger.txMap().snapShot (true);
m_feeVote.doVoting (mPreviousLedger, preSet); ValidationSet parentSet = getApp().getValidations().getValidations (
getApp().getAmendmentTable ().doVoting (mPreviousLedger, preSet); mPreviousLedger->getParentHash ());
m_feeVote.doVoting (mPreviousLedger, parentSet, preSet);
getApp().getAmendmentTable ().doVoting (mPreviousLedger, parentSet, preSet);
initialSet = preSet->snapShot (false); initialSet = preSet->snapShot (false);
} }
else else

View File

@@ -27,6 +27,7 @@
#include <ripple/app/ledger/impl/LedgerCleaner.h> #include <ripple/app/ledger/impl/LedgerCleaner.h>
#include <ripple/app/tx/apply.h> #include <ripple/app/tx/apply.h>
#include <ripple/app/main/Application.h> #include <ripple/app/main/Application.h>
#include <ripple/app/misc/AmendmentTable.h>
#include <ripple/app/misc/IHashRouter.h> #include <ripple/app/misc/IHashRouter.h>
#include <ripple/app/misc/NetworkOPs.h> #include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/CanonicalTXSet.h> #include <ripple/app/misc/CanonicalTXSet.h>
@@ -233,6 +234,7 @@ public:
getApp().getOPs().updateLocalTx (l); getApp().getOPs().updateLocalTx (l);
getApp().getSHAMapStore().onLedgerClosed (getValidatedLedger()); getApp().getSHAMapStore().onLedgerClosed (getValidatedLedger());
mLedgerHistory.validatedLedger (l); mLedgerHistory.validatedLedger (l);
getApp().getAmendmentTable().doValidatedLedger (l);
#if RIPPLE_HOOK_VALIDATORS #if RIPPLE_HOOK_VALIDATORS
getApp().getValidators().onLedgerClosed (l->getLedgerSeq(), getApp().getValidators().onLedgerClosed (l->getLedgerSeq(),

View File

@@ -254,12 +254,6 @@ const char* WalletDBInit[] =
PRIMARY KEY (Validator,Entry) \ PRIMARY KEY (Validator,Entry) \
);", );",
"CREATE TABLE IF NOT EXISTS Features ( \
Hash CHARACTER(64) PRIMARY KEY, \
FirstMajority BIGINT UNSIGNED, \
LastMajority BIGINT UNSIGNED \
);",
"END TRANSACTION;" "END TRANSACTION;"
}; };

View File

@@ -38,14 +38,22 @@ public:
{ {
; ;
} }
void addVoter () void addVoter ()
{ {
++mTrustedValidations; ++mTrustedValidations;
} }
void addVote (uint256 const& amendment) void addVote (uint256 const& amendment)
{ {
++mVotes[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. /** 256-bit Id and human friendly name of an amendment.
@@ -102,11 +110,6 @@ public:
bool mSupported{false}; bool mSupported{false};
bool mDefault{false}; // Include in genesis ledger 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; std::string mFriendlyName;
AmendmentState () = default; AmendmentState () = default;
@@ -147,6 +150,7 @@ public:
class Section; class Section;
/** The amendment table stores the list of enabled and potential amendments. /** The amendment table stores the list of enabled and potential amendments.
Individuals amendments are voted on by validators during the consensus Individuals amendments are voted on by validators during the consensus
process. process.
@@ -197,27 +201,103 @@ public:
*/ */
virtual void setSupported (const std::vector<uint256>& amendments) = 0; virtual void setSupported (const std::vector<uint256>& amendments) = 0;
/** Update the walletDB with the majority times.
*/
virtual void reportValidations (const AmendmentSet&) = 0;
virtual Json::Value getJson (int) = 0; virtual Json::Value getJson (int) = 0;
/** Returns a Json::objectValue. */ /** Returns a Json::objectValue. */
virtual Json::Value getJson (uint256 const& ) = 0; virtual Json::Value getJson (uint256 const& ) = 0;
/** Called when a new fully-validated ledger is accepted. */
void doValidatedLedger (std::shared_ptr<ReadView const> 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 virtual void
doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation) = 0; doValidatedLedger (LedgerIndex ledgerSeq, enabledAmendments_t enabled) = 0;
virtual void
doVoting (Ledger::ref lastClosedLedger, // Called by the consensus code when we need to
std::shared_ptr<SHAMap> const& initialPosition) = 0; // inject pseudo-transactions
virtual std::map <uint256, std::uint32_t>
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 <uint256>
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 <ReadView const> const& lastClosedLedger,
STObject& baseValidation)
{
auto ourAmendments =
doValidation (getEnabledAmendments(*lastClosedLedger));
if (! ourAmendments.empty())
baseValidation.setFieldV256 (sfAmendments,
STVector256 (sfAmendments, ourAmendments));
}
void
doVoting (
std::shared_ptr <ReadView const> const& lastClosedLedger,
ValidationSet const& parentValidations,
std::shared_ptr<SHAMap> 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 <SHAMapItem> (txID, s.peekData());
initialPosition->addGiveItem (tItem, true, false);
#endif
}
}
}; };
std::unique_ptr<AmendmentTable> make_AmendmentTable ( std::unique_ptr<AmendmentTable> make_AmendmentTable (
std::chrono::seconds majorityTime, std::chrono::seconds majorityTime,
int majorityFraction, int majorityFraction,
beast::Journal journal, beast::Journal journal);
bool useMockFacade = false);
} // ripple } // ripple

View File

@@ -24,6 +24,7 @@
#include <ripple/core/DatabaseCon.h> #include <ripple/core/DatabaseCon.h>
#include <ripple/core/ConfigSections.h> #include <ripple/core/ConfigSections.h>
#include <ripple/protocol/JsonFields.h> #include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/TxFlags.h>
#include <boost/format.hpp> #include <boost/format.hpp>
#include <boost/tokenizer.hpp> #include <boost/tokenizer.hpp>
#include <algorithm> #include <algorithm>
@@ -35,7 +36,7 @@ namespace ripple {
Amendments are proposed and then adopted or rejected by the network. An Amendments are proposed and then adopted or rejected by the network. An
Amendment is uniquely identified by its AmendmentID, a 256-bit key. Amendment is uniquely identified by its AmendmentID, a 256-bit key.
*/ */
template<class AppApiFacade>
class AmendmentTableImpl final : public AmendmentTable class AmendmentTableImpl final : public AmendmentTable
{ {
protected: protected:
@@ -47,16 +48,14 @@ protected:
LockType mLock; LockType mLock;
amendmentMap_t m_amendmentMap; amendmentMap_t m_amendmentMap;
std::uint32_t m_lastUpdateSeq;
std::chrono::seconds m_majorityTime; // Seconds an amendment must hold a majority std::chrono::seconds m_majorityTime; // Seconds an amendment must hold a majority
int mMajorityFraction; // 256 = 100% 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; beast::Journal m_journal;
AppApiFacade m_appApiFacade;
AmendmentState& getCreate (uint256 const& amendment); AmendmentState& getCreate (uint256 const& amendment);
AmendmentState* getExisting (uint256 const& amendment); AmendmentState* getExisting (uint256 const& amendment);
bool shouldEnable (std::uint32_t closeTime, const AmendmentState& fs);
void setJson (Json::Value& v, const AmendmentState&); void setJson (Json::Value& v, const AmendmentState&);
public: public:
@@ -64,10 +63,9 @@ public:
std::chrono::seconds majorityTime, std::chrono::seconds majorityTime,
int majorityFraction, int majorityFraction,
beast::Journal journal) beast::Journal journal)
: m_majorityTime (majorityTime) : m_lastUpdateSeq (0)
, m_majorityTime (majorityTime)
, mMajorityFraction (majorityFraction) , mMajorityFraction (majorityFraction)
, m_firstReport (0)
, m_lastReport (0)
, m_journal (journal) , m_journal (journal)
{ {
} }
@@ -90,19 +88,28 @@ public:
void setEnabled (const std::vector<uint256>& amendments) override; void setEnabled (const std::vector<uint256>& amendments) override;
void setSupported (const std::vector<uint256>& amendments) override; void setSupported (const std::vector<uint256>& amendments) override;
void reportValidations (const AmendmentSet&) override;
Json::Value getJson (int) override; Json::Value getJson (int) override;
Json::Value getJson (uint256 const&) override; Json::Value getJson (uint256 const&) override;
void doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation) override; bool needValidatedLedger (LedgerIndex seq) override;
void doVoting (Ledger::ref lastClosedLedger,
std::shared_ptr<SHAMap> const& initialPosition) override; void doValidatedLedger (LedgerIndex seq,
enabledAmendments_t enabled) override;
std::vector <uint256>
doValidation (enabledAmendments_t const& enabledAmendments)
override;
std::map <uint256, std::uint32_t> doVoting (std::uint32_t closeTime,
enabledAmendments_t const& enabledAmendments,
majorityAmendments_t const& majorityAmendments,
ValidationSet const& validations) override;
amendmentList_t getVetoed(); amendmentList_t getVetoed();
amendmentList_t getEnabled(); 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 namespace detail
@@ -117,9 +124,8 @@ namespace detail
std::vector<AmendmentName> const preEnabledAmendments; std::vector<AmendmentName> const preEnabledAmendments;
} }
template<class AppApiFacade>
void void
AmendmentTableImpl<AppApiFacade>::addInitial (Section const& section) AmendmentTableImpl::addInitial (Section const& section)
{ {
for (auto const& a : detail::preEnabledAmendments) for (auto const& a : detail::preEnabledAmendments)
{ {
@@ -177,9 +183,8 @@ AmendmentTableImpl<AppApiFacade>::addInitial (Section const& section)
} }
} }
template<class AppApiFacade>
AmendmentState& AmendmentState&
AmendmentTableImpl<AppApiFacade>::getCreate (uint256 const& amendmentHash) AmendmentTableImpl::getCreate (uint256 const& amendmentHash)
{ {
// call with the mutex held // call with the mutex held
auto iter (m_amendmentMap.find (amendmentHash)); auto iter (m_amendmentMap.find (amendmentHash));
@@ -187,16 +192,14 @@ AmendmentTableImpl<AppApiFacade>::getCreate (uint256 const& amendmentHash)
if (iter == m_amendmentMap.end()) if (iter == m_amendmentMap.end())
{ {
AmendmentState& amendment = m_amendmentMap[amendmentHash]; AmendmentState& amendment = m_amendmentMap[amendmentHash];
m_appApiFacade.setMajorityTimesFromDBToState (amendment, amendmentHash);
return amendment; return amendment;
} }
return iter->second; return iter->second;
} }
template<class AppApiFacade>
AmendmentState* AmendmentState*
AmendmentTableImpl<AppApiFacade>::getExisting (uint256 const& amendmentHash) AmendmentTableImpl::getExisting (uint256 const& amendmentHash)
{ {
// call with the mutex held // call with the mutex held
auto iter (m_amendmentMap.find (amendmentHash)); auto iter (m_amendmentMap.find (amendmentHash));
@@ -207,9 +210,8 @@ AmendmentTableImpl<AppApiFacade>::getExisting (uint256 const& amendmentHash)
return & (iter->second); return & (iter->second);
} }
template<class AppApiFacade>
uint256 uint256
AmendmentTableImpl<AppApiFacade>::get (std::string const& name) AmendmentTableImpl::get (std::string const& name)
{ {
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
@@ -222,9 +224,8 @@ AmendmentTableImpl<AppApiFacade>::get (std::string const& name)
return uint256 (); return uint256 ();
} }
template<class AppApiFacade>
void void
AmendmentTableImpl<AppApiFacade>::addKnown (AmendmentName const& name) AmendmentTableImpl::addKnown (AmendmentName const& name)
{ {
if (!name.valid ()) if (!name.valid ())
{ {
@@ -246,9 +247,8 @@ AmendmentTableImpl<AppApiFacade>::addKnown (AmendmentName const& name)
amendment.mSupported = true; amendment.mSupported = true;
} }
template<class AppApiFacade>
bool bool
AmendmentTableImpl<AppApiFacade>::veto (uint256 const& amendment) AmendmentTableImpl::veto (uint256 const& amendment)
{ {
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
AmendmentState& s = getCreate (amendment); AmendmentState& s = getCreate (amendment);
@@ -260,9 +260,8 @@ AmendmentTableImpl<AppApiFacade>::veto (uint256 const& amendment)
return true; return true;
} }
template<class AppApiFacade>
bool bool
AmendmentTableImpl<AppApiFacade>::unVeto (uint256 const& amendment) AmendmentTableImpl::unVeto (uint256 const& amendment)
{ {
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
AmendmentState* s = getExisting (amendment); AmendmentState* s = getExisting (amendment);
@@ -274,9 +273,8 @@ AmendmentTableImpl<AppApiFacade>::unVeto (uint256 const& amendment)
return true; return true;
} }
template<class AppApiFacade>
bool bool
AmendmentTableImpl<AppApiFacade>::enable (uint256 const& amendment) AmendmentTableImpl::enable (uint256 const& amendment)
{ {
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
AmendmentState& s = getCreate (amendment); AmendmentState& s = getCreate (amendment);
@@ -288,9 +286,8 @@ AmendmentTableImpl<AppApiFacade>::enable (uint256 const& amendment)
return true; return true;
} }
template<class AppApiFacade>
bool bool
AmendmentTableImpl<AppApiFacade>::disable (uint256 const& amendment) AmendmentTableImpl::disable (uint256 const& amendment)
{ {
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
AmendmentState* s = getExisting (amendment); AmendmentState* s = getExisting (amendment);
@@ -302,27 +299,24 @@ AmendmentTableImpl<AppApiFacade>::disable (uint256 const& amendment)
return true; return true;
} }
template<class AppApiFacade>
bool bool
AmendmentTableImpl<AppApiFacade>::isEnabled (uint256 const& amendment) AmendmentTableImpl::isEnabled (uint256 const& amendment)
{ {
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
AmendmentState* s = getExisting (amendment); AmendmentState* s = getExisting (amendment);
return s && s->mEnabled; return s && s->mEnabled;
} }
template<class AppApiFacade>
bool bool
AmendmentTableImpl<AppApiFacade>::isSupported (uint256 const& amendment) AmendmentTableImpl::isSupported (uint256 const& amendment)
{ {
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
AmendmentState* s = getExisting (amendment); AmendmentState* s = getExisting (amendment);
return s && s->mSupported; return s && s->mSupported;
} }
template<class AppApiFacade> AmendmentTableImpl::amendmentList_t
typename AmendmentTableImpl<AppApiFacade>::amendmentList_t AmendmentTableImpl::getVetoed ()
AmendmentTableImpl<AppApiFacade>::getVetoed ()
{ {
amendmentList_t ret; amendmentList_t ret;
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
@@ -334,9 +328,8 @@ AmendmentTableImpl<AppApiFacade>::getVetoed ()
return ret; return ret;
} }
template<class AppApiFacade> AmendmentTableImpl::amendmentList_t
typename AmendmentTableImpl<AppApiFacade>::amendmentList_t AmendmentTableImpl::getEnabled ()
AmendmentTableImpl<AppApiFacade>::getEnabled ()
{ {
amendmentList_t ret; amendmentList_t ret;
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
@@ -348,128 +341,24 @@ AmendmentTableImpl<AppApiFacade>::getEnabled ()
return ret; return ret;
} }
template<class AppApiFacade> AmendmentTableImpl::amendmentList_t
bool AmendmentTableImpl::getDesired (enabledAmendments_t const& enabled)
AmendmentTableImpl<AppApiFacade>::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<class AppApiFacade>
typename AmendmentTableImpl<AppApiFacade>::amendmentList_t
AmendmentTableImpl<AppApiFacade>::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<class AppApiFacade>
typename AmendmentTableImpl<AppApiFacade>::amendmentList_t
AmendmentTableImpl<AppApiFacade>::getDesired ()
{ {
amendmentList_t ret; amendmentList_t ret;
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
for (auto const& e : m_amendmentMap) 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); ret.insert (e.first);
} }
return ret; return ret;
} }
template<class AppApiFacade>
void void
AmendmentTableImpl<AppApiFacade>::reportValidations (const AmendmentSet& set) AmendmentTableImpl::setEnabled (const std::vector<uint256>& amendments)
{
if (set.mTrustedValidations == 0)
return;
int threshold = (set.mTrustedValidations * mMajorityFraction) / 256;
using u256_int_pair = std::map<uint256, int>::value_type;
ScopedLockType sl (mLock);
if (m_firstReport == 0)
m_firstReport = set.mCloseTime;
std::vector<uint256> 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<class AppApiFacade>
void
AmendmentTableImpl<AppApiFacade>::setEnabled (const std::vector<uint256>& amendments)
{ {
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
for (auto& e : m_amendmentMap) for (auto& e : m_amendmentMap)
@@ -482,9 +371,8 @@ AmendmentTableImpl<AppApiFacade>::setEnabled (const std::vector<uint256>& amendm
} }
} }
template<class AppApiFacade>
void void
AmendmentTableImpl<AppApiFacade>::setSupported (const std::vector<uint256>& amendments) AmendmentTableImpl::setSupported (const std::vector<uint256>& amendments)
{ {
ScopedLockType sl (mLock); ScopedLockType sl (mLock);
for (auto &e : m_amendmentMap) for (auto &e : m_amendmentMap)
@@ -497,40 +385,34 @@ AmendmentTableImpl<AppApiFacade>::setSupported (const std::vector<uint256>& amen
} }
} }
template<class AppApiFacade> std::vector <uint256>
void AmendmentTableImpl::doValidation (
AmendmentTableImpl<AppApiFacade>::doValidation (Ledger::ref lastClosedLedger, enabledAmendments_t const& enabledAmendments)
STObject& baseValidation)
{ {
auto lAmendments = getDesired(); auto lAmendments = getDesired (enabledAmendments);
if (lAmendments.empty()) if (lAmendments.empty())
return; return {};
STVector256 amendments (sfAmendments);
for (auto const& id : lAmendments)
amendments.push_back (id);
std::vector <uint256> amendments (lAmendments.begin(), lAmendments.end());
std::sort (amendments.begin (), amendments.end ()); std::sort (amendments.begin (), amendments.end ());
baseValidation.setFieldV256 (sfAmendments, amendments); return amendments;
} }
template<class AppApiFacade> std::map <uint256, std::uint32_t>
void AmendmentTableImpl::doVoting (
AmendmentTableImpl<AppApiFacade>::doVoting (Ledger::ref lastClosedLedger, std::uint32_t closeTime,
std::shared_ptr<SHAMap> const& initialPosition) enabledAmendments_t const& enabledAmendments,
majorityAmendments_t const& majorityAmendments,
ValidationSet const& valSet)
{ {
// LCL must be flag ledger // 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 // process validations for ledger before flag ledger
ValidationSet valSet = m_appApiFacade.getValidations (
lastClosedLedger->getParentHash ());
for (auto const& entry : valSet) for (auto const& entry : valSet)
{ {
auto const& val = *entry.second; auto const& val = *entry.second;
@@ -547,40 +429,83 @@ AmendmentTableImpl<AppApiFacade>::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 <uint256, std::uint32_t> actions;
amendmentList_t lAmendments = getToEnable (lastClosedLedger->getCloseTimeNC ());
for (auto const& uAmendment : lAmendments)
{ {
if (m_journal.warning) m_journal.warning << ScopedLockType sl (mLock);
"Voting for amendment: " << uAmendment;
// Create the transaction to enable the amendment // process all amendments we know of
STTx trans (ttAMENDMENT); for (auto const& entry : m_amendmentMap)
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<SHAMapItem> (txID, s.peekData ());
if (!initialPosition->addGiveItem (tItem, true, false))
{ {
if (m_journal.warning) m_journal.warning << bool const hasValMajority = amendmentSet.count (entry.first) >= threshold;
"Ledger already had amendment transaction";
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
} }
#endif 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;
}
}
}
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<class AppApiFacade>
Json::Value Json::Value
AmendmentTableImpl<AppApiFacade>::getJson (int) AmendmentTableImpl::getJson (int)
{ {
Json::Value ret(Json::objectValue); Json::Value ret(Json::objectValue);
{ {
@@ -593,56 +518,19 @@ AmendmentTableImpl<AppApiFacade>::getJson (int)
return ret; return ret;
} }
template<class AppApiFacade>
void void
AmendmentTableImpl<AppApiFacade>::setJson (Json::Value& v, const AmendmentState& fs) AmendmentTableImpl::setJson (Json::Value& v, const AmendmentState& fs)
{ {
if (!fs.mFriendlyName.empty()) if (!fs.mFriendlyName.empty())
v[jss::name] = fs.mFriendlyName; v[jss::name] = fs.mFriendlyName;
v[jss::supported] = fs.mSupported; v[jss::supported] = fs.mSupported;
v[jss::vetoed] = fs.mVetoed; v[jss::vetoed] = fs.mVetoed;
v[jss::enabled] = fs.mEnabled;
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;
} }
template<class AppApiFacade>
Json::Value Json::Value
AmendmentTableImpl<AppApiFacade>::getJson (uint256 const& amendmentID) AmendmentTableImpl::getJson (uint256 const& amendmentID)
{ {
Json::Value ret = Json::objectValue; Json::Value ret = Json::objectValue;
Json::Value& jAmendment = (ret[to_string (amendmentID)] = Json::objectValue); Json::Value& jAmendment = (ret[to_string (amendmentID)] = Json::objectValue);
@@ -657,101 +545,13 @@ AmendmentTableImpl<AppApiFacade>::getJson (uint256 const& amendmentID)
return ret; return ret;
} }
namespace detail
{
class AppApiFacadeImpl final
{
public:
void setMajorityTimesFromDBToState (AmendmentState& toUpdate,
uint256 const& amendmentHash) const;
void setMajorityTimesFromStateToDB (
std::vector<uint256> const& changedAmendments,
hash_map<uint256, AmendmentState>& amendmentMap) const;
ValidationSet getValidations (uint256 const& hash) const;
};
class AppApiFacadeMock final
{
public:
void setMajorityTimesFromDBToState (AmendmentState& toUpdate,
uint256 const& amendmentHash) const {};
void setMajorityTimesFromStateToDB (
std::vector<uint256> const& changedAmendments,
hash_map<uint256, AmendmentState>& 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<std::uint64_t> 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<uint256> const& changedAmendments,
hash_map<uint256, AmendmentState>& 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<AmendmentTable> make_AmendmentTable ( std::unique_ptr<AmendmentTable> make_AmendmentTable (
std::chrono::seconds majorityTime, std::chrono::seconds majorityTime,
int majorityFraction, int majorityFraction,
beast::Journal journal, beast::Journal journal)
bool useMockFacade)
{ {
if (useMockFacade) return std::make_unique<AmendmentTableImpl>(
{ majorityTime, majorityFraction, journal);
return std::make_unique<AmendmentTableImpl<detail::AppApiFacadeMock>>(
majorityTime, majorityFraction, std::move (journal));
}
return std::make_unique<AmendmentTableImpl<detail::AppApiFacadeImpl>>(
majorityTime, majorityFraction, std::move (journal));
} }
} // ripple } // ripple

View File

@@ -21,6 +21,7 @@
#define RIPPLE_APP_MISC_FEEVOTE_H_INCLUDED #define RIPPLE_APP_MISC_FEEVOTE_H_INCLUDED
#include <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/app/misc/Validations.h>
#include <ripple/basics/BasicConfig.h> #include <ripple/basics/BasicConfig.h>
#include <ripple/protocol/SystemParameters.h> #include <ripple/protocol/SystemParameters.h>
@@ -66,7 +67,7 @@ public:
*/ */
virtual virtual
void void
doVoting (Ledger::ref lastClosedLedger, doVoting (Ledger::ref lastClosedLedger, ValidationSet const& parentValidations,
std::shared_ptr<SHAMap> const& initialPosition) = 0; std::shared_ptr<SHAMap> const& initialPosition) = 0;
}; };

View File

@@ -102,6 +102,7 @@ public:
void void
doVoting (Ledger::ref lastClosedLedger, doVoting (Ledger::ref lastClosedLedger,
ValidationSet const& parentValidations,
std::shared_ptr<SHAMap> const& initialPosition) override; std::shared_ptr<SHAMap> const& initialPosition) override;
}; };
@@ -145,6 +146,7 @@ FeeVoteImpl::doValidation (Ledger::ref lastClosedLedger,
void void
FeeVoteImpl::doVoting (Ledger::ref lastClosedLedger, FeeVoteImpl::doVoting (Ledger::ref lastClosedLedger,
ValidationSet const& set,
std::shared_ptr<SHAMap> const& initialPosition) std::shared_ptr<SHAMap> const& initialPosition)
{ {
// LCL must be flag ledger // LCL must be flag ledger
@@ -159,10 +161,6 @@ FeeVoteImpl::doVoting (Ledger::ref lastClosedLedger,
detail::VotableInteger<std::uint32_t> incReserveVote ( detail::VotableInteger<std::uint32_t> incReserveVote (
lastClosedLedger->getReserveInc (), target_.owner_reserve); lastClosedLedger->getReserveInc (), target_.owner_reserve);
// get validations for ledger before flag
ValidationSet const set =
getApp().getValidations ().getValidations (
lastClosedLedger->getParentHash ());
for (auto const& e : set) for (auto const& e : set)
{ {
STValidation const& val = *e.second; STValidation const& val = *e.second;

View File

@@ -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 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. 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 # SHAMapStore: Online Delete

View File

@@ -22,11 +22,14 @@
#include <ripple/app/misc/AmendmentTable.h> #include <ripple/app/misc/AmendmentTable.h>
#include <ripple/basics/BasicConfig.h> #include <ripple/basics/BasicConfig.h>
#include <ripple/basics/chrono.h> #include <ripple/basics/chrono.h>
#include <ripple/basics/Log.h>
#include <ripple/core/ConfigSections.h> #include <ripple/core/ConfigSections.h>
#include <ripple/protocol/TxFlags.h>
#include <beast/unit_test/suite.h> #include <beast/unit_test/suite.h>
namespace ripple namespace ripple
{ {
class AmendmentTable_test final : public beast::unit_test::suite class AmendmentTable_test final : public beast::unit_test::suite
{ {
public: public:
@@ -39,7 +42,7 @@ private:
addKnown addKnown
}; };
// 204/256 about 80% // 204/256 about 80% (we round down because the implementation rounds up)
static int const majorityFraction{204}; static int const majorityFraction{204};
static void populateTable (AmendmentTable& table, static void populateTable (AmendmentTable& table,
@@ -95,51 +98,50 @@ private:
return amendmentNames; return amendmentNames;
} }
static std::unique_ptr<AmendmentTable> makeTable () static std::unique_ptr< AmendmentTable >
makeTable (int w)
{ {
beast::Journal journal;
return make_AmendmentTable ( return make_AmendmentTable (
weeks (2), weeks (w),
majorityFraction, majorityFraction,
journal, deprecatedLogs().journal("TestAmendmentTable"));
/*useMock*/ true);
}; };
// Create the amendments by string pairs instead of AmendmentNames // Create the amendments by string pairs instead of AmendmentNames
// as this helps test the AmendmentNames class // as this helps test the AmendmentNames class
StringPairVec const m_validAmendmentPairs; StringPairVec const m_knownAmendmentPairs;
StringPairVec const m_notAddedAmendmentPairs; StringPairVec const m_unknownAmendmentPairs;
public: public:
AmendmentTable_test () AmendmentTable_test ()
: m_validAmendmentPairs ( : m_knownAmendmentPairs (
{{"a49f90e7cddbcadfed8fc89ec4d02011", "Added1"}, {{"a49f90e7cddbcadfed8fc89ec4d02011", "Known1"},
{"ca956ccabf25151a16d773171c485423", "Added2"}, {"ca956ccabf25151a16d773171c485423", "Known2"},
{"60dcd528f057711c5d26b57be28e23df", "Added3"}, {"60dcd528f057711c5d26b57be28e23df", "Known3"},
{"da956ccabf25151a16d773171c485423", "Added4"}, {"da956ccabf25151a16d773171c485423", "Known4"},
{"70dcd528f057711c5d26b57be28e23df", "Added5"}, {"70dcd528f057711c5d26b57be28e23df", "Known5"},
{"70dcd528f057711c5d26b57be28e23d0", "Added6"}}) {"70dcd528f057711c5d26b57be28e23d0", "Known6"}})
, m_notAddedAmendmentPairs ( , m_unknownAmendmentPairs (
{{"a9f90e7cddbcadfed8fc89ec4d02011c", "NotAdded1"}, {{"a9f90e7cddbcadfed8fc89ec4d02011c", "Unknown1"},
{"c956ccabf25151a16d773171c485423b", "NotAdded2"}, {"c956ccabf25151a16d773171c485423b", "Unknown2"},
{"6dcd528f057711c5d26b57be28e23dfa", "NotAdded3"}}) {"6dcd528f057711c5d26b57be28e23dfa", "Unknown3"}})
{ {
} }
void testGet () void testGet ()
{ {
testcase ("get"); testcase ("get");
auto table (makeTable ()); auto table (makeTable (2));
std::vector<AmendmentName> const amendmentNames ( std::vector<AmendmentName> const amendmentNames (
populateTable (*table, m_validAmendmentPairs)); populateTable (*table, m_knownAmendmentPairs));
std::vector<AmendmentName> const notAddedAmendmentNames ( std::vector<AmendmentName> const unknownAmendmentNames (
getAmendmentNames (m_notAddedAmendmentPairs)); getAmendmentNames (m_unknownAmendmentPairs));
for (auto const& i : amendmentNames) for (auto const& i : amendmentNames)
{ {
expect (table->get (i.friendlyName ()) == i.id ()); expect (table->get (i.friendlyName ()) == i.id ());
} }
for (auto const& i : notAddedAmendmentNames) for (auto const& i : unknownAmendmentNames)
{ {
expect (table->get (i.friendlyName ()) == uint256 ()); expect (table->get (i.friendlyName ()) == uint256 ());
} }
@@ -156,11 +158,11 @@ public:
// test that the amendments we add are enabled and amendments we // test that the amendments we add are enabled and amendments we
// didn't add are not enabled // didn't add are not enabled
auto table (makeTable ()); auto table (makeTable (2));
std::vector<AmendmentName> const amendmentNames (populateTable ( std::vector<AmendmentName> const amendmentNames (populateTable (
*table, m_validAmendmentPairs, tablePopulationAlgo)); *table, m_knownAmendmentPairs, tablePopulationAlgo));
std::vector<AmendmentName> const notAddedAmendmentNames ( std::vector<AmendmentName> const unknownAmendmentNames (
getAmendmentNames (m_notAddedAmendmentPairs)); getAmendmentNames (m_unknownAmendmentPairs));
for (auto const& i : amendmentNames) for (auto const& i : amendmentNames)
{ {
@@ -169,7 +171,7 @@ public:
expect (table->isEnabled (i.id ())); expect (table->isEnabled (i.id ()));
} }
for (auto const& i : notAddedAmendmentNames) for (auto const& i : unknownAmendmentNames)
{ {
expect (!table->isSupported (i.id ())); expect (!table->isSupported (i.id ()));
expect (!table->isEnabled (i.id ())); expect (!table->isEnabled (i.id ()));
@@ -187,7 +189,7 @@ public:
for (auto const& i : badHexPairs) for (auto const& i : badHexPairs)
{ {
StringPairVec v ({i}); StringPairVec v ({i});
auto table (makeTable ()); auto table (makeTable (2));
try try
{ {
populateTable (*table, v, tablePopulationAlgo); populateTable (*table, v, tablePopulationAlgo);
@@ -224,7 +226,7 @@ public:
for (auto const& i : badNumTokensConfigLines) for (auto const& i : badNumTokensConfigLines)
{ {
std::vector<std::string> v ({i}); std::vector<std::string> v ({i});
auto table (makeTable ()); auto table (makeTable (2));
try try
{ {
populateTable (*table, v); populateTable (*table, v);
@@ -252,9 +254,9 @@ public:
void testEnable () void testEnable ()
{ {
testcase ("enable"); testcase ("enable");
auto table (makeTable ()); auto table (makeTable (2));
std::vector<AmendmentName> const amendmentNames ( std::vector<AmendmentName> const amendmentNames (
populateTable (*table, m_validAmendmentPairs)); populateTable (*table, m_knownAmendmentPairs));
{ {
// enable/disable tests // enable/disable tests
for (auto const& i : amendmentNames) for (auto const& i : amendmentNames)
@@ -289,11 +291,11 @@ public:
using ATGetter = bool (AmendmentTable::*)(uint256 const& amendment); using ATGetter = bool (AmendmentTable::*)(uint256 const& amendment);
void testVectorSetUnset (ATSetter setter, ATGetter getter) void testVectorSetUnset (ATSetter setter, ATGetter getter)
{ {
auto table (makeTable ()); auto table (makeTable (2));
// make pointer to ref syntax a little nicer // make pointer to ref syntax a little nicer
auto& tableRef (*table); auto& tableRef (*table);
std::vector<AmendmentName> const amendmentNames ( std::vector<AmendmentName> const amendmentNames (
populateTable (tableRef, m_validAmendmentPairs)); populateTable (tableRef, m_knownAmendmentPairs));
// they should all be set // they should all be set
for (auto const& i : amendmentNames) for (auto const& i : amendmentNames)
@@ -338,10 +340,10 @@ public:
{ {
// Check that supported/enabled aren't the same thing // Check that supported/enabled aren't the same thing
testcase ("supportedEnabled"); testcase ("supportedEnabled");
auto table (makeTable ()); auto table (makeTable (2));
std::vector<AmendmentName> const amendmentNames ( std::vector<AmendmentName> const amendmentNames (
populateTable (*table, m_validAmendmentPairs)); populateTable (*table, m_knownAmendmentPairs));
{ {
// support every even amendment // support every even amendment
@@ -375,8 +377,401 @@ public:
} }
} }
// TBD: veto/reportValidations/getJson/doValidation/doVoting std::vector <RippleAddress> makeValidators (int num)
// Threading test? {
std::vector <RippleAddress> 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 <RippleAddress> const& validators
, std::vector <std::pair <uint256, int> > const& votes
, std::vector <uint256>& 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 <STValidation>
(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 <std::pair <uint256, int>> votes;
std::vector <uint256> 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 <std::pair <uint256, int>> votes;
std::vector <uint256> 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 <std::pair <uint256, int>> votes;
std::vector <uint256> 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 <std::pair <uint256, int>> votes;
std::vector <uint256> 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 <std::pair <uint256, int>> votes;
std::vector <uint256> 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 <std::pair <uint256, int>> votes;
std::vector <uint256> 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 () void run ()
{ {
@@ -385,6 +780,11 @@ public:
testEnable (); testEnable ();
testSupported (); testSupported ();
testSupportedEnabled (); testSupportedEnabled ();
testNoUnknown ();
testNoVetoed ();
testVoteEnable ();
testDetectMajority ();
testLostMajority ();
} }
}; };

View File

@@ -24,6 +24,7 @@
#include <ripple/app/misc/NetworkOPs.h> #include <ripple/app/misc/NetworkOPs.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
namespace ripple { namespace ripple {
@@ -124,14 +125,66 @@ Change::applyAmendment()
amendment) != amendments.end ()) amendment) != amendments.end ())
return tefALREADY; return tefALREADY;
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); amendments.push_back (amendment);
amendmentObject->setFieldV256 (sfAmendments, amendments); amendmentObject->setFieldV256 (sfAmendments, amendments);
view().update (amendmentObject);
getApp().getAmendmentTable ().enable (amendment); getApp().getAmendmentTable ().enable (amendment);
if (!getApp().getAmendmentTable ().isSupported (amendment)) if (!getApp().getAmendmentTable ().isSupported (amendment))
getApp().getOPs ().setAmendmentBlocked (); getApp().getOPs ().setAmendmentBlocked ();
}
if (newMajorities.empty ())
amendmentObject->makeFieldAbsent (sfMajorities);
else
amendmentObject->setFieldArray (sfMajorities, newMajorities);
view().update (amendmentObject);
return tesSUCCESS; return tesSUCCESS;
} }

View File

@@ -285,9 +285,6 @@ public:
digest (key_type const& key) const = 0; digest (key_type const& key) const = 0;
}; };
// For now
using BasicView = ReadView;
} // ripple } // ripple
#include <ripple/ledger/detail/ReadViewFwdRange.ipp> #include <ripple/ledger/detail/ReadViewFwdRange.ipp>

View File

@@ -131,6 +131,16 @@ cdirNext (ReadView const& view,
unsigned int& uDirEntry, // <-> next entry unsigned int& uDirEntry, // <-> next entry
uint256& uEntryIndex); // <-- The entry, if available. Otherwise, zero. uint256& uEntryIndex); // <-- The entry, if available. Otherwise, zero.
// Return the list of enabled amendments
using enabledAmendments_t = std::set <uint256>;
enabledAmendments_t
getEnabledAmendments (ReadView const& view);
// Return a map of amendments that have achieved majority
using majorityAmendments_t = std::map <uint256, std::uint32_t>;
majorityAmendments_t
getMajorityAmendments (ReadView const& view);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// //
// Modifiers // Modifiers

View File

@@ -23,6 +23,7 @@
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/basics/StringUtilities.h> #include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/Quality.h> #include <ripple/protocol/Quality.h>
#include <ripple/protocol/STArray.h>
namespace ripple { namespace ripple {
@@ -388,6 +389,37 @@ cdirNext (ReadView const& view,
return true; 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 // Modifiers

View File

@@ -436,6 +436,7 @@ extern SField const sfMemo;
extern SField const sfSignerEntry; extern SField const sfSignerEntry;
extern SField const sfSigningAccount; extern SField const sfSigningAccount;
extern SField const sfSigningFor; extern SField const sfSigningFor;
extern SField const sfMajority;
// array of objects // array of objects
// ARRAY/1 is reserved for end of array // ARRAY/1 is reserved for end of array
@@ -447,6 +448,7 @@ extern SField const sfNecessary;
extern SField const sfSufficient; extern SField const sfSufficient;
extern SField const sfAffectedNodes; extern SField const sfAffectedNodes;
extern SField const sfMemos; extern SField const sfMemos;
extern SField const sfMajorities;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@@ -41,6 +41,10 @@ public:
: mValue (vector) : mValue (vector)
{ } { }
STVector256 (SField const& n, std::vector<uint256> const& vector)
: STBase (n), mValue (vector)
{ }
STVector256 (SerialIter& sit, SField const& name); STVector256 (SerialIter& sit, SField const& name);
STBase* STBase*

View File

@@ -91,6 +91,10 @@ const std::uint32_t tfClearFreeze = 0x00200000;
const std::uint32_t tfTrustSetMask = ~ (tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple const std::uint32_t tfTrustSetMask = ~ (tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple
| tfSetFreeze | tfClearFreeze); | tfSetFreeze | tfClearFreeze);
// EnableAmendment flags:
const std::uint32_t tfGotMajority = 0x00010000;
const std::uint32_t tfLostMajority = 0x00020000;
} // ripple } // ripple
#endif #endif

View File

@@ -87,11 +87,14 @@ LedgerFormats::LedgerFormats ()
<< SOElement (sfHashes, SOE_REQUIRED) << SOElement (sfHashes, SOE_REQUIRED)
; ;
add ("EnabledAmendments", ltAMENDMENTS) add ("Amendments", ltAMENDMENTS)
<< SOElement (sfAmendments, SOE_REQUIRED) << SOElement (sfLedgerSequence, SOE_OPTIONAL)
<< SOElement (sfAmendments, SOE_OPTIONAL) // Enabled
<< SOElement (sfMajorities, SOE_OPTIONAL)
; ;
add ("FeeSettings", ltFEE_SETTINGS) add ("FeeSettings", ltFEE_SETTINGS)
<< SOElement (sfLedgerSequence, SOE_OPTIONAL)
<< SOElement (sfBaseFee, SOE_REQUIRED) << SOElement (sfBaseFee, SOE_REQUIRED)
<< SOElement (sfReferenceFeeUnits, SOE_REQUIRED) << SOElement (sfReferenceFeeUnits, SOE_REQUIRED)
<< SOElement (sfReserveBase, SOE_REQUIRED) << SOElement (sfReserveBase, SOE_REQUIRED)

View File

@@ -226,6 +226,7 @@ SField const sfSignerEntry = make::one(&sfSignerEntry, STI_OBJEC
// inner object (uncommon) // inner object (uncommon)
SField const sfSigningAccount = make::one(&sfSigningAccount, STI_OBJECT, 16, "SigningAccount"); SField const sfSigningAccount = make::one(&sfSigningAccount, STI_OBJECT, 16, "SigningAccount");
SField const sfSigningFor = make::one(&sfSigningFor, STI_OBJECT, 17, "SigningFor"); SField const sfSigningFor = make::one(&sfSigningFor, STI_OBJECT, 17, "SigningFor");
SField const sfMajority = make::one(&sfMajority, STI_OBJECT, 18, "Majority");
// array of objects // array of objects
// ARRAY/1 is reserved for end of array // 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 sfAffectedNodes = make::one(&sfAffectedNodes, STI_ARRAY, 8, "AffectedNodes");
SField const sfMemos = make::one(&sfMemos, STI_ARRAY, 9, "Memos"); 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, SField::SField (SerializedTypeID tid, int fv, const char* fn,
int meta, IsSigning signing) int meta, IsSigning signing)
: fieldCode (field_code (tid, fv)) : fieldCode (field_code (tid, fv))