mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 03:26:01 +00:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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;"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
else if (hasValMajority && (majorityTime == 0) && (! entry.second.mVetoed))
|
||||||
|
{
|
||||||
|
// Ledger says no majority, validators say yes
|
||||||
|
actions[entry.first] = tfGotMajority;
|
||||||
|
}
|
||||||
|
else if (! hasValMajority && (majorityTime != 0))
|
||||||
|
{
|
||||||
|
// Ledger says majority, validators say no
|
||||||
|
actions[entry.first] = tfLostMajority;
|
||||||
|
}
|
||||||
|
else if ((majorityTime != 0) &&
|
||||||
|
((majorityTime + m_majorityTime.count()) <= closeTime) &&
|
||||||
|
! entry.second.mVetoed)
|
||||||
|
{
|
||||||
|
// Ledger says majority held
|
||||||
|
actions[entry.first] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
AmendmentTableImpl::needValidatedLedger (LedgerIndex ledgerSeq)
|
||||||
|
{
|
||||||
|
ScopedLockType sl (mLock);
|
||||||
|
|
||||||
|
// Is there a ledger in which an amendment could have been enabled
|
||||||
|
// between these two ledger sequences?
|
||||||
|
|
||||||
|
return ((ledgerSeq - 1) / 256) != ((m_lastUpdateSeq - 1) / 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
AmendmentTableImpl::doValidatedLedger (LedgerIndex ledgerSeq,
|
||||||
|
enabledAmendments_t enabled)
|
||||||
|
{
|
||||||
|
ScopedLockType sl (mLock);
|
||||||
|
|
||||||
|
for (auto& e : m_amendmentMap)
|
||||||
|
e.second.mEnabled = (enabled.count (e.first) != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<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
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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,15 +125,67 @@ Change::applyAmendment()
|
|||||||
amendment) != amendments.end ())
|
amendment) != amendments.end ())
|
||||||
return tefALREADY;
|
return tefALREADY;
|
||||||
|
|
||||||
amendments.push_back (amendment);
|
auto flags = mTxn.getFlags ();
|
||||||
amendmentObject->setFieldV256 (sfAmendments, amendments);
|
|
||||||
|
const bool gotMajority = (flags & tfGotMajority) != 0;
|
||||||
|
const bool lostMajority = (flags & tfLostMajority) != 0;
|
||||||
|
|
||||||
|
if (gotMajority && lostMajority)
|
||||||
|
return temINVALID_FLAG;
|
||||||
|
|
||||||
|
STArray newMajorities (sfMajorities);
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
if (amendmentObject->isFieldPresent (sfMajorities))
|
||||||
|
{
|
||||||
|
const STArray &oldMajorities = amendmentObject->getFieldArray (sfMajorities);
|
||||||
|
for (auto const& majority : oldMajorities)
|
||||||
|
{
|
||||||
|
if (majority.getFieldH256 (sfAmendment) == amendment)
|
||||||
|
{
|
||||||
|
if (gotMajority)
|
||||||
|
return tefALREADY;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// pass through
|
||||||
|
newMajorities.push_back (majority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! found && lostMajority)
|
||||||
|
return tefALREADY;
|
||||||
|
|
||||||
|
if (gotMajority)
|
||||||
|
{
|
||||||
|
// This amendment now has a majority
|
||||||
|
newMajorities.push_back (STObject (sfMajority));
|
||||||
|
auto& entry = newMajorities.back ();
|
||||||
|
entry.emplace_back (STHash256 (sfAmendment, amendment));
|
||||||
|
entry.emplace_back (STUInt32 (sfCloseTime,
|
||||||
|
view().parentCloseTime()));
|
||||||
|
}
|
||||||
|
else if (!lostMajority)
|
||||||
|
{
|
||||||
|
// No flags, enable amendment
|
||||||
|
amendments.push_back (amendment);
|
||||||
|
amendmentObject->setFieldV256 (sfAmendments, amendments);
|
||||||
|
|
||||||
|
getApp().getAmendmentTable ().enable (amendment);
|
||||||
|
|
||||||
|
if (!getApp().getAmendmentTable ().isSupported (amendment))
|
||||||
|
getApp().getOPs ().setAmendmentBlocked ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newMajorities.empty ())
|
||||||
|
amendmentObject->makeFieldAbsent (sfMajorities);
|
||||||
|
else
|
||||||
|
amendmentObject->setFieldArray (sfMajorities, newMajorities);
|
||||||
|
|
||||||
view().update (amendmentObject);
|
view().update (amendmentObject);
|
||||||
|
|
||||||
getApp().getAmendmentTable ().enable (amendment);
|
|
||||||
|
|
||||||
if (!getApp().getAmendmentTable ().isSupported (amendment))
|
|
||||||
getApp().getOPs ().setAmendmentBlocked ();
|
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -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*
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user