From b6d9f1d4b29946e9fa7be8e9a3b35b2b7fdc5366 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 16 Sep 2014 15:09:09 -0700 Subject: [PATCH] Add fee voting configuration and docs (RIPD-564) --- doc/rippled-example.cfg | 63 ++++- src/ripple/app/misc/FeeVote.h | 55 ++-- src/ripple/app/misc/FeeVoteImpl.cpp | 408 +++++++++++++++------------- src/ripple/app/misc/NetworkOPs.cpp | 5 +- src/ripple/app/misc/README.md | 61 +++++ 5 files changed, 375 insertions(+), 217 deletions(-) diff --git a/doc/rippled-example.cfg b/doc/rippled-example.cfg index 3f1e92936..44e43dc38 100644 --- a/doc/rippled-example.cfg +++ b/doc/rippled-example.cfg @@ -22,6 +22,8 @@ # # 8. Diagnostics # +# 9. Voting +# #------------------------------------------------------------------------------- # # Purpose @@ -770,6 +772,57 @@ # prefix=my_validator # #------------------------------------------------------------------------------- +# +# 9. Voting +# +#---------- +# +# The vote settings configure settings for the entire Ripple network. +# While a single instance of rippled cannot unilaterally enforce network-wide +# settings, these choices become part of the instance's vote during the +# consensus process for each voting ledger. +# +# [voting] +# +# A set of key/value pair parameters used during voting ledgers. +# +# reference_fee = +# +# The cost of the reference transaction fee, specified in drops. +# The reference transaction is the simplest form of transaction. +# It represents an XRP payment between two parties. +# +# If this parameter is unspecified, rippled will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# reference_fee = 10 # 10 drops +# +# account_reserve = +# +# The account reserve requirement specified in drops. The portion of an +# account's XRP balance that is at or below the reserve may only be +# spent on transaction fees, and not transferred out of the account. +# +# If this parameter is unspecified, rippled will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# account_reserve = 20000000 # 20 XRP +# +# owner_reserve = +# +# The owner reserve is the amount of XRP reserved in the account for +# each ledger item owned by the account. Ledger items an account may +# own include trust lines, open orders, and tickets. +# +# If this parameter is unspecified, rippled will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# owner_reserve = 5000000 # 5 XRP +# +#------------------------------------------------------------------------------- # Allow other peers to connect to this server. # @@ -844,11 +897,11 @@ r.ripple.com 51235 # https://ripple.com/ripple.txt # [validators] -n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1 -n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2 -n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3 -n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4 -n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5 +n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1 +n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2 +n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3 +n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4 +n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5 # Ditto. [validation_quorum] diff --git a/src/ripple/app/misc/FeeVote.h b/src/ripple/app/misc/FeeVote.h index 7cbd60d62..88221f685 100644 --- a/src/ripple/app/misc/FeeVote.h +++ b/src/ripple/app/misc/FeeVote.h @@ -17,8 +17,11 @@ */ //============================================================================== -#ifndef RIPPLE_IFEEVOTE_H -#define RIPPLE_IFEEVOTE_H +#ifndef RIPPLE_APP_FEEVOTE_H_INCLUDED +#define RIPPLE_APP_FEEVOTE_H_INCLUDED + +#include +#include namespace ripple { @@ -26,36 +29,56 @@ namespace ripple { class FeeVote { public: - /** Create a new fee vote manager. - - @param targetBaseFee - @param targetReserveBase - @param targetReserveIncrement - @param journal + /** Fee schedule to vote for. + During voting ledgers, the FeeVote logic will try to move towards + these values when injecting fee-setting transactions. + A default-constructed Setup contains recommended values. */ + struct Setup + { + /** The cost of a reference transaction in drops. */ + std::uint64_t reference_fee = 10; - virtual ~FeeVote () { } + /** The account reserve requirement in drops. */ + std::uint64_t account_reserve = 20 * SYSTEM_CURRENCY_PARTS; + + /** The per-owned item reserve requirement in drops. */ + std::uint64_t owner_reserve = 5 * SYSTEM_CURRENCY_PARTS; + }; + + virtual ~FeeVote () = default; /** Add local fee preference to validation. @param lastClosedLedger @param baseValidation */ - virtual void doValidation (Ledger::ref lastClosedLedger, - STObject& baseValidation) = 0; + virtual + void + doValidation (Ledger::ref lastClosedLedger, + STObject& baseValidation) = 0; /** Cast our local vote on the fee. @param lastClosedLedger @param initialPosition */ - virtual void doVoting (Ledger::ref lastClosedLedger, - SHAMap::ref initialPosition) = 0; + virtual + void + doVoting (Ledger::ref lastClosedLedger, + SHAMap::ref initialPosition) = 0; }; -std::unique_ptr -make_FeeVote (std::uint64_t targetBaseFee, std::uint32_t targetReserveBase, - std::uint32_t targetReserveIncrement, beast::Journal journal); +/** Build FeeVote::Setup from a config section. */ +FeeVote::Setup +setup_FeeVote (Section const& section); + +/** Create an instance of the FeeVote logic. + @param setup The fee schedule to vote for. + @param journal Where to log. +*/ +std::unique_ptr +make_FeeVote (FeeVote::Setup const& setup, beast::Journal journal); } // ripple diff --git a/src/ripple/app/misc/FeeVoteImpl.cpp b/src/ripple/app/misc/FeeVoteImpl.cpp index 918f43620..00202cdfe 100644 --- a/src/ripple/app/misc/FeeVoteImpl.cpp +++ b/src/ripple/app/misc/FeeVoteImpl.cpp @@ -19,215 +19,235 @@ namespace ripple { -class FeaturesImpl; +namespace detail { + +template +class VotableInteger +{ +private: + typedef std::map map_type; + Integer mCurrent; // The current setting + Integer mTarget; // The setting we want + map_type mVoteMap; + +public: + VotableInteger (Integer current, Integer target) + : mCurrent (current) + , mTarget (target) + { + // Add our vote + ++mVoteMap[mTarget]; + } + + void + addVote(Integer vote) + { + ++mVoteMap[vote]; + } + + void + noVote() + { + addVote (mCurrent); + } + + Integer + getVotes() const; +}; + +template +Integer +VotableInteger ::getVotes() const +{ + Integer ourVote = mCurrent; + int weight = 0; + for (auto const& e : mVoteMap) + { + // Take most voted value between current and target, inclusive + if ((e.first <= std::max (mTarget, mCurrent)) && + (e.first >= std::min (mTarget, mCurrent)) && + (e.second > weight)) + { + ourVote = e.first; + weight = e.second; + } + } + + return ourVote; +} + +} + +//------------------------------------------------------------------------------ class FeeVoteImpl : public FeeVote { private: - - template - class VotableInteger - { - public: - VotableInteger (Integer current, Integer target) - : mCurrent (current) - , mTarget (target) - { - // Add our vote - ++mVoteMap[mTarget]; - } - - bool - mayVote () const - { - // If we love the current setting, we will not vote - return mCurrent != mTarget; - } - - void - addVote (Integer vote) - { - ++mVoteMap[vote]; - } - - void - noVote () - { - addVote (mCurrent); - } - - Integer - getVotes () - { - Integer ourVote = mCurrent; - int weight = 0; - - typedef typename std::map::value_type mapVType; - for (auto const& e : mVoteMap) - { - // Take most voted value between current and target, inclusive - if ((e.first <= std::max (mTarget, mCurrent)) && - (e.first >= std::min (mTarget, mCurrent)) && - (e.second > weight)) - { - ourVote = e.first; - weight = e.second; - } - } - - return ourVote; - } - - private: - Integer mCurrent; // The current setting - Integer mTarget; // The setting we want - std::map mVoteMap; - }; + Setup target_; + beast::Journal journal_; public: - FeeVoteImpl (std::uint64_t targetBaseFee, std::uint32_t targetReserveBase, - std::uint32_t targetReserveIncrement, beast::Journal journal) - : mTargetBaseFee (targetBaseFee) - , mTargetReserveBase (targetReserveBase) - , mTargetReserveIncrement (targetReserveIncrement) - , m_journal (journal) - { - } - - //-------------------------------------------------------------------------- + FeeVoteImpl (Setup const& setup, beast::Journal journal); void - doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation) override - { - if (lastClosedLedger->getBaseFee () != mTargetBaseFee) - { - if (m_journal.info) m_journal.info << - "Voting for base fee of " << mTargetBaseFee; - - baseValidation.setFieldU64 (sfBaseFee, mTargetBaseFee); - } - - if (lastClosedLedger->getReserve (0) != mTargetReserveBase) - { - if (m_journal.info) m_journal.info << - "Voting for base resrve of " << mTargetReserveBase; - - baseValidation.setFieldU32(sfReserveBase, mTargetReserveBase); - } - - if (lastClosedLedger->getReserveInc () != mTargetReserveIncrement) - { - if (m_journal.info) m_journal.info << - "Voting for reserve increment of " << mTargetReserveIncrement; - - baseValidation.setFieldU32 (sfReserveIncrement, mTargetReserveIncrement); - } - } - - //-------------------------------------------------------------------------- + doValidation (Ledger::ref lastClosedLedger, + STObject& baseValidation) override; void - doVoting (Ledger::ref lastClosedLedger, SHAMap::ref initialPosition) override - { - // LCL must be flag ledger - assert ((lastClosedLedger->getLedgerSeq () % 256) == 0); - - VotableInteger baseFeeVote (lastClosedLedger->getBaseFee (), mTargetBaseFee); - VotableInteger baseReserveVote (lastClosedLedger->getReserve (0), mTargetReserveBase); - VotableInteger incReserveVote (lastClosedLedger->getReserveInc (), mTargetReserveIncrement); - - // get validations for ledger before flag - ValidationSet set = getApp().getValidations ().getValidations (lastClosedLedger->getParentHash ()); - for (auto const& e : set) - { - SerializedValidation const& val = *e.second; - - if (val.isTrusted ()) - { - if (val.isFieldPresent (sfBaseFee)) - { - baseFeeVote.addVote (val.getFieldU64 (sfBaseFee)); - } - else - { - baseFeeVote.noVote (); - } - - if (val.isFieldPresent (sfReserveBase)) - { - baseReserveVote.addVote (val.getFieldU32 (sfReserveBase)); - } - else - { - baseReserveVote.noVote (); - } - - if (val.isFieldPresent (sfReserveIncrement)) - { - incReserveVote.addVote (val.getFieldU32 (sfReserveIncrement)); - } - else - { - incReserveVote.noVote (); - } - } - } - - // choose our positions - std::uint64_t baseFee = baseFeeVote.getVotes (); - std::uint32_t baseReserve = baseReserveVote.getVotes (); - std::uint32_t incReserve = incReserveVote.getVotes (); - - // add transactions to our position - if ((baseFee != lastClosedLedger->getBaseFee ()) || - (baseReserve != lastClosedLedger->getReserve (0)) || - (incReserve != lastClosedLedger->getReserveInc ())) - { - if (m_journal.warning) m_journal.warning << - "We are voting for a fee change: " << baseFee << - "/" << baseReserve << - "/" << incReserve; - - SerializedTransaction trans (ttFEE); - trans.setFieldAccount (sfAccount, Account ()); - trans.setFieldU64 (sfBaseFee, baseFee); - trans.setFieldU32 (sfReferenceFeeUnits, 10); - trans.setFieldU32 (sfReserveBase, baseReserve); - trans.setFieldU32 (sfReserveIncrement, incReserve); - - uint256 txID = trans.getTransactionID (); - - if (m_journal.warning) - m_journal.warning << "Vote: " << txID; - - Serializer s; - trans.add (s, true); - - SHAMapItem::pointer tItem = std::make_shared (txID, s.peekData ()); - - if (!initialPosition->addGiveItem (tItem, true, false)) - { - if (m_journal.warning) m_journal.warning << - "Ledger already had fee change"; - } - } - } - -private: - std::uint64_t mTargetBaseFee; - std::uint32_t mTargetReserveBase; - std::uint32_t mTargetReserveIncrement; - beast::Journal m_journal; + doVoting (Ledger::ref lastClosedLedger, + SHAMap::ref initialPosition) override; }; +//-------------------------------------------------------------------------- + +FeeVoteImpl::FeeVoteImpl (Setup const& setup, beast::Journal journal) + : target_ (setup) + , journal_ (journal) +{ +} + +void +FeeVoteImpl::doValidation (Ledger::ref lastClosedLedger, + STObject& baseValidation) +{ + if (lastClosedLedger->getBaseFee () != target_.reference_fee) + { + if (journal_.info) journal_.info << + "Voting for base fee of " << target_.reference_fee; + + baseValidation.setFieldU64 (sfBaseFee, target_.reference_fee); + } + + if (lastClosedLedger->getReserve (0) != target_.account_reserve) + { + if (journal_.info) journal_.info << + "Voting for base resrve of " << target_.account_reserve; + + baseValidation.setFieldU32(sfReserveBase, target_.account_reserve); + } + + if (lastClosedLedger->getReserveInc () != target_.owner_reserve) + { + if (journal_.info) journal_.info << + "Voting for reserve increment of " << target_.owner_reserve; + + baseValidation.setFieldU32 (sfReserveIncrement, + target_.owner_reserve); + } +} + +void +FeeVoteImpl::doVoting (Ledger::ref lastClosedLedger, + SHAMap::ref initialPosition) +{ + // LCL must be flag ledger + assert ((lastClosedLedger->getLedgerSeq () % 256) == 0); + + detail::VotableInteger baseFeeVote ( + lastClosedLedger->getBaseFee (), target_.reference_fee); + + detail::VotableInteger baseReserveVote ( + lastClosedLedger->getReserve (0), target_.account_reserve); + + detail::VotableInteger incReserveVote ( + lastClosedLedger->getReserveInc (), target_.owner_reserve); + + // get validations for ledger before flag + ValidationSet const set = + getApp().getValidations ().getValidations ( + lastClosedLedger->getParentHash ()); + for (auto const& e : set) + { + SerializedValidation const& val = *e.second; + + if (val.isTrusted ()) + { + if (val.isFieldPresent (sfBaseFee)) + { + baseFeeVote.addVote (val.getFieldU64 (sfBaseFee)); + } + else + { + baseFeeVote.noVote (); + } + + if (val.isFieldPresent (sfReserveBase)) + { + baseReserveVote.addVote (val.getFieldU32 (sfReserveBase)); + } + else + { + baseReserveVote.noVote (); + } + + if (val.isFieldPresent (sfReserveIncrement)) + { + incReserveVote.addVote (val.getFieldU32 (sfReserveIncrement)); + } + else + { + incReserveVote.noVote (); + } + } + } + + // choose our positions + std::uint64_t const baseFee = baseFeeVote.getVotes (); + std::uint32_t const baseReserve = baseReserveVote.getVotes (); + std::uint32_t const incReserve = incReserveVote.getVotes (); + + // add transactions to our position + if ((baseFee != lastClosedLedger->getBaseFee ()) || + (baseReserve != lastClosedLedger->getReserve (0)) || + (incReserve != lastClosedLedger->getReserveInc ())) + { + if (journal_.warning) journal_.warning << + "We are voting for a fee change: " << baseFee << + "/" << baseReserve << + "/" << incReserve; + + SerializedTransaction trans (ttFEE); + trans.setFieldAccount (sfAccount, Account ()); + trans.setFieldU64 (sfBaseFee, baseFee); + trans.setFieldU32 (sfReferenceFeeUnits, 10); + trans.setFieldU32 (sfReserveBase, baseReserve); + trans.setFieldU32 (sfReserveIncrement, incReserve); + + uint256 txID = trans.getTransactionID (); + + if (journal_.warning) + journal_.warning << "Vote: " << txID; + + Serializer s; + trans.add (s, true); + + SHAMapItem::pointer tItem = std::make_shared ( + txID, s.peekData ()); + + if (!initialPosition->addGiveItem (tItem, true, false)) + { + if (journal_.warning) journal_.warning << + "Ledger already had fee change"; + } + } +} + //------------------------------------------------------------------------------ -std::unique_ptr -make_FeeVote (std::uint64_t targetBaseFee, std::uint32_t targetReserveBase, - std::uint32_t targetReserveIncrement, beast::Journal journal) +FeeVote::Setup +setup_FeeVote (Section const& section) { - return std::make_unique (targetBaseFee, targetReserveBase, - targetReserveIncrement, journal); + FeeVote::Setup setup; + set (setup.reference_fee, "reference_fee", section); + set (setup.account_reserve, "account_reserve", section); + set (setup.owner_reserve, "owner_reserve", section); + return setup; +} + +std::unique_ptr +make_FeeVote (FeeVote::Setup const& setup, beast::Journal journal) +{ + return std::make_unique (setup, journal); } } // ripple diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 4a25edef7..216475915 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -56,8 +57,8 @@ public: , m_clock (clock) , m_journal (journal) , m_localTX (LocalTxs::New ()) - , m_feeVote (make_FeeVote(10, 20 * SYSTEM_CURRENCY_PARTS, - 5 * SYSTEM_CURRENCY_PARTS, deprecatedLogs().journal("FeeVote"))) + , m_feeVote (make_FeeVote (setup_FeeVote (getConfig().section ("voting")), + deprecatedLogs().journal("FeeVote"))) , mMode (omDISCONNECTED) , mNeedNetworkLedger (false) , mProposing (false) diff --git a/src/ripple/app/misc/README.md b/src/ripple/app/misc/README.md index 3bc809d09..da23c07c8 100644 --- a/src/ripple/app/misc/README.md +++ b/src/ripple/app/misc/README.md @@ -1,3 +1,64 @@ +# Fee Voting + +The Ripple payment protocol enforces a fee schedule expressed in units of the +native currency, XRP. Fees for transactions are paid directly from the account +owner. There are also reserve requirements for each item that occupies storage +in the ledger. The reserve fee schedule contains both a per-account reserve, +and a per-owned-item reserve. The items an account may own include active +offers, trust lines, and tickets. + +Validators may vote to increase fees if they feel that the network is charging +too little. They may also vote to decrease fees if the fees are too costly +relative to the value the network provides. One common case where a validator +may want to change fees is when the value of the native currency XRP fluctuates +relative to other currencies. + +The fee voting mechanism takes place every 256 ledgers ("voting ledgers"). In +a voting ledger, each validator takes a position on what they think the fees +should be. The consensus process converges on the majority position, and in +subsequent ledgers a new fee schedule is enacted. + +## Consensus + +The Ripple consensus algorithm allows distributed participants to arrive at +the same answer for yes/no questions. The canonical case for consensus is +whether or not a particular transaction is included in the ledger. Fees +present a more difficult challenge, since the decision on the new fee is not +a yes or no question. + +To convert validators' positions on fees into a yes or no question that can +be converged in the consensus process, the following algorithm is used: + +- In the ledger before a voting ledger, validators send proposals which also + include the values they think the network should use for the new fee schedule. + +- In the voting ledger, validators examine the proposals from other validators + and choose a new fee schedule which moves the fees in a direction closer to + the validator's ideal fee schedule and is also likely to be accepted. A fee + amount is likely to be accepted if a majority of validators agree on the + number. + +- Each validator injects a "pseudo transaction" into their proposed ledger + which sets the fees to the chosen schedule. + +- The consensus process is applied to these fee-setting transactions as normal. + Each transaction is either included in the ledger or not. In most cases, one + fee setting transaction will make it in while the others are rejected. In + some rare cases more than one fee setting transaction will make it in. The + last one to be applied will take effect. This is harmless since a majority + of validators still agreed on it. + +- After the voting ledger has been validated, future pseudo transactions + before the next voting ledger are rejected as fee setting transactions may + only appear in voting ledgers. + +## Configuration + +A validating instance of rippled uses information in the configuration file +to determine how it wants to vote on the fee schedule. It is the responsibility +of the administrator to set these values. + +--- # Amendment