XRPFees: Fee setting and handling improvements (#4247)

* Introduces amendment `XRPFees`
* Convert fee voting and protocol messages to use XRPAmounts
* Includes Validations, Change transactions, the "Fees" ledger object,
  and subscription messages

* Improve handling of 0 drop reference fee with TxQ. For use with networks that do not want to require fees
* Note that fee escalation logic is still in place, which may cause the
  open ledger fee to rise if the network is busy. 0 drop transactions
  will still queue, and fee escalation can be effectively disabled by
  modifying the configuration on all nodes

* Change default network reserves to match Mainnet

* Name the new SFields *Drops (not *XRP)
* Reserve SField IDs for Hooks

* Clarify comments explaining the ttFEE transaction field validation
This commit is contained in:
Ed Hennis
2023-02-02 16:20:35 -08:00
committed by GitHub
parent 0ce15e0e35
commit e4b17d1cf2
46 changed files with 691 additions and 337 deletions

View File

@@ -839,7 +839,8 @@ RCLConsensus::Adaptor::validate(
if (ledger.ledger_->isVotingLedger()) if (ledger.ledger_->isVotingLedger())
{ {
// Fees: // Fees:
feeVote_->doValidation(ledger.ledger_->fees(), v); feeVote_->doValidation(
ledger.ledger_->fees(), ledger.ledger_->rules(), v);
// Amendments // Amendments
// FIXME: pass `v` and have the function insert the array // FIXME: pass `v` and have the function insert the array

View File

@@ -591,29 +591,9 @@ Ledger::setup(Config const& config)
{ {
bool ret = true; bool ret = true;
fees_.base = config.FEE_DEFAULT;
fees_.units = config.TRANSACTION_FEE_BASE;
fees_.reserve = config.FEE_ACCOUNT_RESERVE;
fees_.increment = config.FEE_OWNER_RESERVE;
try try
{ {
if (auto const sle = read(keylet::fees())) rules_ = makeRulesGivenLedger(*this, config.features);
{
// VFALCO NOTE Why getFieldIndex and not isFieldPresent?
if (sle->getFieldIndex(sfBaseFee) != -1)
fees_.base = sle->getFieldU64(sfBaseFee);
if (sle->getFieldIndex(sfReferenceFeeUnits) != -1)
fees_.units = sle->getFieldU32(sfReferenceFeeUnits);
if (sle->getFieldIndex(sfReserveBase) != -1)
fees_.reserve = sle->getFieldU32(sfReserveBase);
if (sle->getFieldIndex(sfReserveIncrement) != -1)
fees_.increment = sle->getFieldU32(sfReserveIncrement);
}
} }
catch (SHAMapMissingNode const&) catch (SHAMapMissingNode const&)
{ {
@@ -624,9 +604,56 @@ Ledger::setup(Config const& config)
Rethrow(); Rethrow();
} }
fees_.base = config.FEE_DEFAULT;
fees_.reserve = config.FEE_ACCOUNT_RESERVE;
fees_.increment = config.FEE_OWNER_RESERVE;
try try
{ {
rules_ = makeRulesGivenLedger(*this, config.features); if (auto const sle = read(keylet::fees()))
{
bool oldFees = false;
bool newFees = false;
{
auto const baseFee = sle->at(~sfBaseFee);
auto const reserveBase = sle->at(~sfReserveBase);
auto const reserveIncrement = sle->at(~sfReserveIncrement);
if (baseFee)
fees_.base = *baseFee;
if (reserveBase)
fees_.reserve = *reserveBase;
if (reserveIncrement)
fees_.increment = *reserveIncrement;
oldFees = baseFee || reserveBase || reserveIncrement;
}
{
auto const baseFeeXRP = sle->at(~sfBaseFeeDrops);
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
auto const reserveIncrementXRP =
sle->at(~sfReserveIncrementDrops);
auto assign = [&ret](
XRPAmount& dest,
std::optional<STAmount> const& src) {
if (src)
{
if (src->native())
dest = src->xrp();
else
ret = false;
}
};
assign(fees_.base, baseFeeXRP);
assign(fees_.reserve, reserveBaseXRP);
assign(fees_.increment, reserveIncrementXRP);
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
}
if (oldFees && newFees)
// Should be all of one or the other, but not both
ret = false;
if (!rules_.enabled(featureXRPFees) && newFees)
// Can't populate the new fees before the amendment is enabled
ret = false;
}
} }
catch (SHAMapMissingNode const&) catch (SHAMapMissingNode const&)
{ {

View File

@@ -42,9 +42,6 @@ public:
/** The cost of a reference transaction in drops. */ /** The cost of a reference transaction in drops. */
XRPAmount reference_fee{10}; XRPAmount reference_fee{10};
/** The cost of a reference transaction in fee units. */
static constexpr FeeUnit32 reference_fee_units{10};
/** The account reserve requirement in drops. */ /** The account reserve requirement in drops. */
XRPAmount account_reserve{10 * DROPS_PER_XRP}; XRPAmount account_reserve{10 * DROPS_PER_XRP};
@@ -60,7 +57,10 @@ public:
@param baseValidation @param baseValidation
*/ */
virtual void virtual void
doValidation(Fees const& lastFees, STValidation& val) = 0; doValidation(
Fees const& lastFees,
Rules const& rules,
STValidation& val) = 0;
/** Cast our local vote on the fee. /** Cast our local vote on the fee.

View File

@@ -33,51 +33,57 @@ class VotableValue
{ {
private: private:
using value_type = XRPAmount; using value_type = XRPAmount;
value_type const mCurrent; // The current setting value_type const current_; // The current setting
value_type const mTarget; // The setting we want value_type const target_; // The setting we want
std::map<value_type, int> mVoteMap; std::map<value_type, int> voteMap_;
public: public:
VotableValue(value_type current, value_type target) VotableValue(value_type current, value_type target)
: mCurrent(current), mTarget(target) : current_(current), target_(target)
{ {
// Add our vote // Add our vote
++mVoteMap[mTarget]; ++voteMap_[target_];
} }
void void
addVote(value_type vote) addVote(value_type vote)
{ {
++mVoteMap[vote]; ++voteMap_[vote];
} }
void void
noVote() noVote()
{ {
addVote(mCurrent); addVote(current_);
} }
value_type value_type
current() const
{
return current_;
}
std::pair<value_type, bool>
getVotes() const; getVotes() const;
}; };
auto auto
VotableValue::getVotes() const -> value_type VotableValue::getVotes() const -> std::pair<value_type, bool>
{ {
value_type ourVote = mCurrent; value_type ourVote = current_;
int weight = 0; int weight = 0;
for (auto const& [key, val] : mVoteMap) for (auto const& [key, val] : voteMap_)
{ {
// Take most voted value between current and target, inclusive // Take most voted value between current and target, inclusive
if ((key <= std::max(mTarget, mCurrent)) && if ((key <= std::max(target_, current_)) &&
(key >= std::min(mTarget, mCurrent)) && (val > weight)) (key >= std::min(target_, current_)) && (val > weight))
{ {
ourVote = key; ourVote = key;
weight = val; weight = val;
} }
} }
return ourVote; return {ourVote, ourVote != current_};
} }
} // namespace detail } // namespace detail
@@ -94,7 +100,8 @@ public:
FeeVoteImpl(Setup const& setup, beast::Journal journal); FeeVoteImpl(Setup const& setup, beast::Journal journal);
void void
doValidation(Fees const& lastFees, STValidation& val) override; doValidation(Fees const& lastFees, Rules const& rules, STValidation& val)
override;
void void
doVoting( doVoting(
@@ -111,36 +118,78 @@ FeeVoteImpl::FeeVoteImpl(Setup const& setup, beast::Journal journal)
} }
void void
FeeVoteImpl::doValidation(Fees const& lastFees, STValidation& v) FeeVoteImpl::doValidation(
Fees const& lastFees,
Rules const& rules,
STValidation& v)
{ {
// Values should always be in a valid range (because the voting process // Values should always be in a valid range (because the voting process
// will ignore out-of-range values) but if we detect such a case, we do // will ignore out-of-range values) but if we detect such a case, we do
// not send a value. // not send a value.
if (lastFees.base != target_.reference_fee) if (rules.enabled(featureXRPFees))
{
auto vote = [&v, this](
auto const current,
XRPAmount target,
const char* name,
auto const& sfield) {
if (current != target)
{ {
JLOG(journal_.info()) JLOG(journal_.info())
<< "Voting for base fee of " << target_.reference_fee; << "Voting for " << name << " of " << target;
if (auto const f = target_.reference_fee.dropsAs<std::uint64_t>()) v[sfield] = target;
v.setFieldU64(sfBaseFee, *f);
} }
};
if (lastFees.accountReserve(0) != target_.account_reserve) vote(lastFees.base, target_.reference_fee, "base fee", sfBaseFeeDrops);
{ vote(
JLOG(journal_.info()) lastFees.accountReserve(0),
<< "Voting for base reserve of " << target_.account_reserve; target_.account_reserve,
"base reserve",
if (auto const f = target_.account_reserve.dropsAs<std::uint32_t>()) sfReserveBaseDrops);
v.setFieldU32(sfReserveBase, *f); vote(
lastFees.increment,
target_.owner_reserve,
"reserve increment",
sfReserveIncrementDrops);
} }
else
if (lastFees.increment != target_.owner_reserve) {
auto to32 = [](XRPAmount target) {
return target.dropsAs<std::uint32_t>();
};
auto to64 = [](XRPAmount target) {
return target.dropsAs<std::uint64_t>();
};
auto vote = [&v, this](
auto const current,
XRPAmount target,
auto const& convertCallback,
const char* name,
auto const& sfield) {
if (current != target)
{ {
JLOG(journal_.info()) JLOG(journal_.info())
<< "Voting for reserve increment of " << target_.owner_reserve; << "Voting for " << name << " of " << target;
if (auto const f = target_.owner_reserve.dropsAs<std::uint32_t>()) if (auto const f = convertCallback(target))
v.setFieldU32(sfReserveIncrement, *f); v[sfield] = *f;
}
};
vote(lastFees.base, target_.reference_fee, to64, "base fee", sfBaseFee);
vote(
lastFees.accountReserve(0),
target_.account_reserve,
to32,
"base reserve",
sfReserveBase);
vote(
lastFees.increment,
target_.owner_reserve,
to32,
"reserve increment",
sfReserveIncrement);
} }
} }
@@ -151,7 +200,7 @@ FeeVoteImpl::doVoting(
std::shared_ptr<SHAMap> const& initialPosition) std::shared_ptr<SHAMap> const& initialPosition)
{ {
// LCL must be flag ledger // LCL must be flag ledger
assert(isFlagLedger(lastClosedLedger->seq())); assert(lastClosedLedger && isFlagLedger(lastClosedLedger->seq()));
detail::VotableValue baseFeeVote( detail::VotableValue baseFeeVote(
lastClosedLedger->fees().base, target_.reference_fee); lastClosedLedger->fees().base, target_.reference_fee);
@@ -162,79 +211,110 @@ FeeVoteImpl::doVoting(
detail::VotableValue incReserveVote( detail::VotableValue incReserveVote(
lastClosedLedger->fees().increment, target_.owner_reserve); lastClosedLedger->fees().increment, target_.owner_reserve);
auto const& rules = lastClosedLedger->rules();
if (rules.enabled(featureXRPFees))
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue& value,
SF_AMOUNT const& xrpField) {
if (auto const field = ~val->at(~xrpField);
field && field->native())
{
auto const vote = field->xrp();
if (isLegalAmountSigned(vote))
value.addVote(vote);
else
value.noVote();
}
else
{
value.noVote();
}
};
for (auto const& val : set) for (auto const& val : set)
{ {
if (val->isTrusted()) if (!val->isTrusted())
continue;
doVote(val, baseFeeVote, sfBaseFeeDrops);
doVote(val, baseReserveVote, sfReserveBaseDrops);
doVote(val, incReserveVote, sfReserveIncrementDrops);
}
}
else
{ {
if (val->isFieldPresent(sfBaseFee)) auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue& value,
auto const& valueField) {
if (auto const field = val->at(~valueField))
{ {
using xrptype = XRPAmount::value_type; using xrptype = XRPAmount::value_type;
auto const vote = val->getFieldU64(sfBaseFee); auto const vote = *field;
if (vote <= std::numeric_limits<xrptype>::max() && if (vote <= std::numeric_limits<xrptype>::max() &&
isLegalAmount(XRPAmount{unsafe_cast<xrptype>(vote)})) isLegalAmountSigned(XRPAmount{unsafe_cast<xrptype>(vote)}))
baseFeeVote.addVote( value.addVote(
XRPAmount{unsafe_cast<XRPAmount::value_type>(vote)}); XRPAmount{unsafe_cast<XRPAmount::value_type>(vote)});
else else
// Invalid amounts will be treated as if they're // Invalid amounts will be treated as if they're
// not provided. Don't throw because this value is // not provided. Don't throw because this value is
// provided by an external entity. // provided by an external entity.
baseFeeVote.noVote(); value.noVote();
} }
else else
{ {
baseFeeVote.noVote(); value.noVote();
} }
};
if (val->isFieldPresent(sfReserveBase)) for (auto const& val : set)
{ {
baseReserveVote.addVote( if (!val->isTrusted())
XRPAmount{val->getFieldU32(sfReserveBase)}); continue;
} doVote(val, baseFeeVote, sfBaseFee);
else doVote(val, baseReserveVote, sfReserveBase);
{ doVote(val, incReserveVote, sfReserveIncrement);
baseReserveVote.noVote();
}
if (val->isFieldPresent(sfReserveIncrement))
{
incReserveVote.addVote(
XRPAmount{val->getFieldU32(sfReserveIncrement)});
}
else
{
incReserveVote.noVote();
}
} }
} }
// choose our positions // choose our positions
// If any of the values are invalid, send the current values. // TODO: Use structured binding once LLVM issue
auto const baseFee = baseFeeVote.getVotes().dropsAs<std::uint64_t>( // https://github.com/llvm/llvm-project/issues/48582
lastClosedLedger->fees().base); // is fixed.
auto const baseReserve = baseReserveVote.getVotes().dropsAs<std::uint32_t>( auto const baseFee = baseFeeVote.getVotes();
lastClosedLedger->fees().accountReserve(0)); auto const baseReserve = baseReserveVote.getVotes();
auto const incReserve = incReserveVote.getVotes().dropsAs<std::uint32_t>( auto const incReserve = incReserveVote.getVotes();
lastClosedLedger->fees().increment);
constexpr FeeUnit32 feeUnits = Setup::reference_fee_units;
auto const seq = lastClosedLedger->info().seq + 1; auto const seq = lastClosedLedger->info().seq + 1;
// add transactions to our position // add transactions to our position
if ((baseFee != lastClosedLedger->fees().base) || if (baseFee.second || baseReserve.second || incReserve.second)
(baseReserve != lastClosedLedger->fees().accountReserve(0)) ||
(incReserve != lastClosedLedger->fees().increment))
{ {
JLOG(journal_.warn()) << "We are voting for a fee change: " << baseFee JLOG(journal_.warn())
<< "/" << baseReserve << "/" << incReserve; << "We are voting for a fee change: " << baseFee.first << "/"
<< baseReserve.first << "/" << incReserve.first;
STTx feeTx( STTx feeTx(ttFEE, [=, &rules](auto& obj) {
ttFEE,
[seq, baseFee, baseReserve, incReserve, feeUnits](auto& obj) {
obj[sfAccount] = AccountID(); obj[sfAccount] = AccountID();
obj[sfLedgerSequence] = seq; obj[sfLedgerSequence] = seq;
obj[sfBaseFee] = baseFee; if (rules.enabled(featureXRPFees))
obj[sfReserveBase] = baseReserve; {
obj[sfReserveIncrement] = incReserve; obj[sfBaseFeeDrops] = baseFee.first;
obj[sfReferenceFeeUnits] = feeUnits.fee(); obj[sfReserveBaseDrops] = baseReserve.first;
obj[sfReserveIncrementDrops] = incReserve.first;
}
else
{
// Without the featureXRPFees amendment, these fields are
// required.
obj[sfBaseFee] =
baseFee.first.dropsAs<std::uint64_t>(baseFeeVote.current());
obj[sfReserveBase] = baseReserve.first.dropsAs<std::uint32_t>(
baseReserveVote.current());
obj[sfReserveIncrement] =
incReserve.first.dropsAs<std::uint32_t>(
incReserveVote.current());
obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED;
}
}); });
uint256 txID = feeTx.getTransactionID(); uint256 txID = feeTx.getTransactionID();

View File

@@ -161,7 +161,7 @@ private:
// Scale using load as well as base rate // Scale using load as well as base rate
XRPAmount XRPAmount
scaleFeeLoad( scaleFeeLoad(
FeeUnit64 fee, XRPAmount fee,
LoadFeeTrack const& feeTrack, LoadFeeTrack const& feeTrack,
Fees const& fees, Fees const& fees,
bool bUnlimited); bool bUnlimited);

View File

@@ -2171,15 +2171,30 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
if (auto const loadFee = (*val)[~sfLoadFee]) if (auto const loadFee = (*val)[~sfLoadFee])
jvObj[jss::load_fee] = *loadFee; jvObj[jss::load_fee] = *loadFee;
if (auto const baseFee = (*val)[~sfBaseFee]) if (auto const baseFee = val->at(~sfBaseFee))
jvObj[jss::base_fee] = static_cast<double>(*baseFee); jvObj[jss::base_fee] = static_cast<double>(*baseFee);
if (auto const reserveBase = (*val)[~sfReserveBase]) if (auto const reserveBase = val->at(~sfReserveBase))
jvObj[jss::reserve_base] = *reserveBase; jvObj[jss::reserve_base] = *reserveBase;
if (auto const reserveInc = (*val)[~sfReserveIncrement]) if (auto const reserveInc = val->at(~sfReserveIncrement))
jvObj[jss::reserve_inc] = *reserveInc; jvObj[jss::reserve_inc] = *reserveInc;
// (The ~ operator converts the Proxy to a std::optional, which
// simplifies later operations)
if (auto const baseFeeXRP = ~val->at(~sfBaseFeeDrops);
baseFeeXRP && baseFeeXRP->native())
jvObj[jss::base_fee_drops] = baseFeeXRP->xrp().jsonClipped();
if (auto const reserveBaseXRP = ~val->at(~sfReserveBaseDrops);
reserveBaseXRP && reserveBaseXRP->native())
jvObj[jss::reserve_base_drops] =
reserveBaseXRP->xrp().jsonClipped();
if (auto const reserveIncXRP = ~val->at(~sfReserveIncrementDrops);
reserveIncXRP && reserveIncXRP->native())
jvObj[jss::reserve_inc_drops] = reserveIncXRP->xrp().jsonClipped();
for (auto i = mStreamMaps[sValidations].begin(); for (auto i = mStreamMaps[sValidations].begin();
i != mStreamMaps[sValidations].end();) i != mStreamMaps[sValidations].end();)
{ {
@@ -2883,7 +2898,8 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
jvObj[jss::ledger_time] = Json::Value::UInt( jvObj[jss::ledger_time] = Json::Value::UInt(
lpAccepted->info().closeTime.time_since_epoch().count()); lpAccepted->info().closeTime.time_since_epoch().count());
jvObj[jss::fee_ref] = lpAccepted->fees().units.jsonClipped(); if (!lpAccepted->rules().enabled(featureXRPFees))
jvObj[jss::fee_ref] = Config::FEE_UNITS_DEPRECATED;
jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped(); jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped();
jvObj[jss::reserve_base] = jvObj[jss::reserve_base] =
lpAccepted->fees().accountReserve(0).jsonClipped(); lpAccepted->fees().accountReserve(0).jsonClipped();
@@ -3889,7 +3905,8 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
jvResult[jss::ledger_hash] = to_string(lpClosed->info().hash); jvResult[jss::ledger_hash] = to_string(lpClosed->info().hash);
jvResult[jss::ledger_time] = Json::Value::UInt( jvResult[jss::ledger_time] = Json::Value::UInt(
lpClosed->info().closeTime.time_since_epoch().count()); lpClosed->info().closeTime.time_since_epoch().count());
jvResult[jss::fee_ref] = lpClosed->fees().units.jsonClipped(); if (!lpClosed->rules().enabled(featureXRPFees))
jvResult[jss::fee_ref] = Config::FEE_UNITS_DEPRECATED;
jvResult[jss::fee_base] = lpClosed->fees().base.jsonClipped(); jvResult[jss::fee_base] = lpClosed->fees().base.jsonClipped();
jvResult[jss::reserve_base] = jvResult[jss::reserve_base] =
lpClosed->fees().accountReserve(0).jsonClipped(); lpClosed->fees().accountReserve(0).jsonClipped();

View File

@@ -860,7 +860,7 @@ setup_TxQ(Config const&);
template <class T> template <class T>
XRPAmount XRPAmount
toDrops(FeeLevel<T> const& level, XRPAmount const& baseFee) toDrops(FeeLevel<T> const& level, XRPAmount baseFee)
{ {
if (auto const drops = mulDiv(level, baseFee, TxQ::baseLevel); drops.first) if (auto const drops = mulDiv(level, baseFee, TxQ::baseLevel); drops.first)
return drops.second; return drops.second;

View File

@@ -87,30 +87,13 @@ LoadFeeTrack::lowerLocalFee()
// Scale using load as well as base rate // Scale using load as well as base rate
XRPAmount XRPAmount
scaleFeeLoad( scaleFeeLoad(
FeeUnit64 fee, XRPAmount fee,
LoadFeeTrack const& feeTrack, LoadFeeTrack const& feeTrack,
Fees const& fees, Fees const& fees,
bool bUnlimited) bool bUnlimited)
{ {
if (fee == 0) if (fee == 0)
return XRPAmount{0}; return fee;
// Normally, types with different units wouldn't be mathematically
// compatible. This function is an exception.
auto lowestTerms = [](auto& a, auto& b) {
auto value = [](auto val) {
if constexpr (std::is_arithmetic_v<decltype(val)>)
return val;
else
return val.value();
};
if (auto const g = std::gcd(value(a), value(b)))
{
a = value(a) / g;
b = value(b) / g;
}
};
// Collect the fee rates // Collect the fee rates
auto [feeFactor, uRemFee] = feeTrack.getScalingFactors(); auto [feeFactor, uRemFee] = feeTrack.getScalingFactors();
@@ -120,45 +103,12 @@ scaleFeeLoad(
if (bUnlimited && (feeFactor > uRemFee) && (feeFactor < (4 * uRemFee))) if (bUnlimited && (feeFactor > uRemFee) && (feeFactor < (4 * uRemFee)))
feeFactor = uRemFee; feeFactor = uRemFee;
XRPAmount baseFee{fees.base};
// Compute: // Compute:
// fee = fee * baseFee * feeFactor / (fees.units * lftNormalFee); // fee = fee * feeFactor / (lftNormalFee);
// without overflow, and as accurately as possible // without overflow, and as accurately as possible
// The denominator of the fraction we're trying to compute. auto const result = mulDiv(
// fees.units and lftNormalFee are both 32 bit, fee, feeFactor, safe_cast<std::uint64_t>(feeTrack.getLoadBase()));
// so the multiplication can't overflow.
auto den = FeeUnit64{fees.units} *
safe_cast<std::uint64_t>(feeTrack.getLoadBase());
// Reduce fee * baseFee * feeFactor / (fees.units * lftNormalFee)
// to lowest terms.
lowestTerms(fee, den);
lowestTerms(baseFee, den);
lowestTerms(feeFactor, den);
// fee and baseFee are 64 bit, feeFactor is 32 bit
// Order fee and baseFee largest first
// Normally, these types wouldn't be comparable or swappable.
// This function is an exception.
if (fee.value() < baseFee.value())
{
auto tmp = fee.value();
fee = baseFee.value();
baseFee = tmp;
}
// double check
assert(fee.value() >= baseFee.value());
// If baseFee * feeFactor overflows, the final result will overflow
XRPAmount const baseFeeOverflow{
std::numeric_limits<XRPAmount::value_type>::max() / feeFactor};
if (baseFee > baseFeeOverflow)
{
Throw<std::overflow_error>("scaleFeeLoad");
}
baseFee *= feeFactor;
auto const result = mulDiv(fee, baseFee, den);
if (!result.first) if (!result.first)
Throw<std::overflow_error>("scaleFeeLoad"); Throw<std::overflow_error>("scaleFeeLoad");
return result.second; return result.second;

View File

@@ -38,14 +38,18 @@ static FeeLevel64
getFeeLevelPaid(ReadView const& view, STTx const& tx) getFeeLevelPaid(ReadView const& view, STTx const& tx)
{ {
auto const [baseFee, effectiveFeePaid] = [&view, &tx]() { auto const [baseFee, effectiveFeePaid] = [&view, &tx]() {
XRPAmount baseFee = view.fees().toDrops(calculateBaseFee(view, tx)); XRPAmount baseFee = calculateBaseFee(view, tx);
XRPAmount feePaid = tx[sfFee].xrp(); XRPAmount feePaid = tx[sfFee].xrp();
// If baseFee is 0 then the cost of a basic transaction is free. // If baseFee is 0 then the cost of a basic transaction is free, but we
XRPAmount const ref = baseFee.signum() > 0 // need the effective fee level to be non-zero.
? XRPAmount{0} XRPAmount const mod = [&view, &tx, baseFee]() {
: calculateDefaultBaseFee(view, tx); if (baseFee.signum() > 0)
return std::pair{baseFee + ref, feePaid + ref}; return XRPAmount{0};
auto def = calculateDefaultBaseFee(view, tx);
return def.signum() == 0 ? XRPAmount{1} : def;
}();
return std::pair{baseFee + mod, feePaid + mod};
}(); }();
assert(baseFee.signum() > 0); assert(baseFee.signum() > 0);
@@ -1072,19 +1076,27 @@ TxQ::apply(
LastLedgerSeq and MaybeTx::retriesRemaining. LastLedgerSeq and MaybeTx::retriesRemaining.
*/ */
auto const balance = (*sleAccount)[sfBalance].xrp(); auto const balance = (*sleAccount)[sfBalance].xrp();
/* Get the minimum possible reserve. If fees exceed /* Get the minimum possible account reserve. If it
is at least 10 * the base fee, and fees exceed
this amount, the transaction can't be queued. this amount, the transaction can't be queued.
Considering that typical fees are several orders
Currently typical fees are several orders
of magnitude smaller than any current or expected of magnitude smaller than any current or expected
future reserve, this calculation is simpler than future reserve. This calculation is simpler than
trying to figure out the potential changes to trying to figure out the potential changes to
the ownerCount that may occur to the account the ownerCount that may occur to the account
as a result of these transactions, and removes as a result of these transactions, and removes
any need to account for other transactions that any need to account for other transactions that
may affect the owner count while these are queued. may affect the owner count while these are queued.
However, in case the account reserve is on a
comparable scale to the base fee, ignore the
reserve. Only check the account balance.
*/ */
auto const reserve = view.fees().accountReserve(0); auto const reserve = view.fees().accountReserve(0);
if (totalFee >= balance || totalFee >= reserve) auto const base = view.fees().base;
if (totalFee >= balance ||
(reserve > 10 * base && totalFee >= reserve))
{ {
// Drop the current transaction // Drop the current transaction
JLOG(j_.trace()) << "Ignoring transaction " << transactionID JLOG(j_.trace()) << "Ignoring transaction " << transactionID
@@ -1104,7 +1116,10 @@ TxQ::apply(
// inserted in the middle from fouling up later transactions. // inserted in the middle from fouling up later transactions.
auto const potentialTotalSpend = totalFee + auto const potentialTotalSpend = totalFee +
std::min(balance - std::min(balance, reserve), potentialSpend); std::min(balance - std::min(balance, reserve), potentialSpend);
assert(potentialTotalSpend > XRPAmount{0}); assert(
potentialTotalSpend > XRPAmount{0} ||
(potentialTotalSpend == XRPAmount{0} &&
multiTxn->applyView.fees().base == 0));
sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend); sleBump->setFieldAmount(sfBalance, balance - potentialTotalSpend);
// The transaction's sequence/ticket will be valid when the other // The transaction's sequence/ticket will be valid when the other
// transactions in the queue have been processed. If the tx has a // transactions in the queue have been processed. If the tx has a
@@ -1758,7 +1773,7 @@ TxQ::getTxRequiredFeeAndSeq(
std::lock_guard lock(mutex_); std::lock_guard lock(mutex_);
auto const snapshot = feeMetrics_.getSnapshot(); auto const snapshot = feeMetrics_.getSnapshot();
auto const baseFee = view.fees().toDrops(calculateBaseFee(view, *tx)); auto const baseFee = calculateBaseFee(view, *tx);
auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view); auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view);
auto const sle = view.read(keylet::account(account)); auto const sle = view.read(keylet::account(account));
@@ -1834,15 +1849,26 @@ TxQ::doRPC(Application& app) const
levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel); levels[jss::open_ledger_level] = to_string(metrics.openLedgerFeeLevel);
auto const baseFee = view->fees().base; auto const baseFee = view->fees().base;
// If the base fee is 0 drops, but escalation has kicked in, treat the
// base fee as if it is 1 drop, which makes the rest of the math
// work.
auto const effectiveBaseFee = [&baseFee, &metrics]() {
if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
return XRPAmount{1};
return baseFee;
}();
auto& drops = ret[jss::drops] = Json::Value(); auto& drops = ret[jss::drops] = Json::Value();
drops[jss::base_fee] = drops[jss::base_fee] = to_string(baseFee);
to_string(toDrops(metrics.referenceFeeLevel, baseFee));
drops[jss::minimum_fee] =
to_string(toDrops(metrics.minProcessingFeeLevel, baseFee));
drops[jss::median_fee] = to_string(toDrops(metrics.medFeeLevel, baseFee)); drops[jss::median_fee] = to_string(toDrops(metrics.medFeeLevel, baseFee));
drops[jss::open_ledger_fee] = to_string( drops[jss::minimum_fee] = to_string(toDrops(
toDrops(metrics.openLedgerFeeLevel - FeeLevel64{1}, baseFee) + 1); metrics.minProcessingFeeLevel,
metrics.txCount >= metrics.txQMaxSize ? effectiveBaseFee : baseFee));
auto openFee = toDrops(metrics.openLedgerFeeLevel, effectiveBaseFee);
if (effectiveBaseFee &&
toFeeLevel(openFee, effectiveBaseFee) < metrics.openLedgerFeeLevel)
openFee += 1;
drops[jss::open_ledger_fee] = to_string(openFee);
return ret; return ret;
} }

View File

@@ -300,7 +300,7 @@ preclaim(
@return The base fee. @return The base fee.
*/ */
FeeUnit64 XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx); calculateBaseFee(ReadView const& view, STTx const& tx);
/** Return the minimum fee that an "ordinary" transaction would pay. /** Return the minimum fee that an "ordinary" transaction would pay.

View File

@@ -33,7 +33,7 @@ ApplyContext::ApplyContext(
OpenView& base, OpenView& base,
STTx const& tx_, STTx const& tx_,
TER preclaimResult_, TER preclaimResult_,
FeeUnit64 baseFee_, XRPAmount baseFee_,
ApplyFlags flags, ApplyFlags flags,
beast::Journal journal_) beast::Journal journal_)
: app(app_) : app(app_)

View File

@@ -40,14 +40,14 @@ public:
OpenView& base, OpenView& base,
STTx const& tx, STTx const& tx,
TER preclaimResult, TER preclaimResult,
FeeUnit64 baseFee, XRPAmount baseFee,
ApplyFlags flags, ApplyFlags flags,
beast::Journal = beast::Journal{beast::Journal::getNullSink()}); beast::Journal = beast::Journal{beast::Journal::getNullSink()});
Application& app; Application& app;
STTx const& tx; STTx const& tx;
TER const preclaimResult; TER const preclaimResult;
FeeUnit64 const baseFee; XRPAmount const baseFee;
beast::Journal const journal; beast::Journal const journal;
ApplyView& ApplyView&

View File

@@ -90,8 +90,46 @@ Change::preclaim(PreclaimContext const& ctx)
switch (ctx.tx.getTxnType()) switch (ctx.tx.getTxnType())
{ {
case ttAMENDMENT:
case ttFEE: case ttFEE:
if (ctx.view.rules().enabled(featureXRPFees))
{
// The ttFEE transaction format defines these fields as
// optional, but once the XRPFees feature is enabled, they are
// required.
if (!ctx.tx.isFieldPresent(sfBaseFeeDrops) ||
!ctx.tx.isFieldPresent(sfReserveBaseDrops) ||
!ctx.tx.isFieldPresent(sfReserveIncrementDrops))
return temMALFORMED;
// The ttFEE transaction format defines these fields as
// optional, but once the XRPFees feature is enabled, they are
// forbidden.
if (ctx.tx.isFieldPresent(sfBaseFee) ||
ctx.tx.isFieldPresent(sfReferenceFeeUnits) ||
ctx.tx.isFieldPresent(sfReserveBase) ||
ctx.tx.isFieldPresent(sfReserveIncrement))
return temMALFORMED;
}
else
{
// The ttFEE transaction format formerly defined these fields
// as required. When the XRPFees feature was implemented, they
// were changed to be optional. Until the feature has been
// enabled, they are required.
if (!ctx.tx.isFieldPresent(sfBaseFee) ||
!ctx.tx.isFieldPresent(sfReferenceFeeUnits) ||
!ctx.tx.isFieldPresent(sfReserveBase) ||
!ctx.tx.isFieldPresent(sfReserveIncrement))
return temMALFORMED;
// The ttFEE transaction format defines these fields as
// optional, but without the XRPFees feature, they are
// forbidden.
if (ctx.tx.isFieldPresent(sfBaseFeeDrops) ||
ctx.tx.isFieldPresent(sfReserveBaseDrops) ||
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
return temDISABLED;
}
return tesSUCCESS;
case ttAMENDMENT:
case ttUNL_MODIFY: case ttUNL_MODIFY:
return tesSUCCESS; return tesSUCCESS;
default: default:
@@ -315,13 +353,27 @@ Change::applyFee()
feeObject = std::make_shared<SLE>(k); feeObject = std::make_shared<SLE>(k);
view().insert(feeObject); view().insert(feeObject);
} }
auto set = [](SLE::pointer& feeObject, STTx const& tx, auto const& field) {
feeObject->setFieldU64(sfBaseFee, ctx_.tx.getFieldU64(sfBaseFee)); feeObject->at(field) = tx[field];
feeObject->setFieldU32( };
sfReferenceFeeUnits, ctx_.tx.getFieldU32(sfReferenceFeeUnits)); if (view().rules().enabled(featureXRPFees))
feeObject->setFieldU32(sfReserveBase, ctx_.tx.getFieldU32(sfReserveBase)); {
feeObject->setFieldU32( set(feeObject, ctx_.tx, sfBaseFeeDrops);
sfReserveIncrement, ctx_.tx.getFieldU32(sfReserveIncrement)); set(feeObject, ctx_.tx, sfReserveBaseDrops);
set(feeObject, ctx_.tx, sfReserveIncrementDrops);
// Ensure the old fields are removed
feeObject->makeFieldAbsent(sfBaseFee);
feeObject->makeFieldAbsent(sfReferenceFeeUnits);
feeObject->makeFieldAbsent(sfReserveBase);
feeObject->makeFieldAbsent(sfReserveIncrement);
}
else
{
set(feeObject, ctx_.tx, sfBaseFee);
set(feeObject, ctx_.tx, sfReferenceFeeUnits);
set(feeObject, ctx_.tx, sfReserveBase);
set(feeObject, ctx_.tx, sfReserveIncrement);
}
view().update(feeObject); view().update(feeObject);

View File

@@ -46,10 +46,10 @@ public:
void void
preCompute() override; preCompute() override;
static FeeUnit64 static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx) calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
return FeeUnit64{0}; return XRPAmount{0};
} }
static TER static TER

View File

@@ -52,20 +52,11 @@ DeleteAccount::preflight(PreflightContext const& ctx)
return preflight2(ctx); return preflight2(ctx);
} }
FeeUnit64 XRPAmount
DeleteAccount::calculateBaseFee(ReadView const& view, STTx const& tx) DeleteAccount::calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
// The fee required for AccountDelete is one owner reserve. But the // The fee required for AccountDelete is one owner reserve.
// owner reserve is stored in drops. We need to convert it to fee units. return view.fees().increment;
Fees const& fees{view.fees()};
std::pair<bool, FeeUnit64> const mulDivResult{
mulDiv(fees.increment, safe_cast<FeeUnit64>(fees.units), fees.base)};
if (mulDivResult.first)
return mulDivResult.second;
// If mulDiv returns false then overflow happened. Punt by using the
// standard calculation.
return Transactor::calculateBaseFee(view, tx);
} }
namespace { namespace {

View File

@@ -38,7 +38,7 @@ public:
static NotTEC static NotTEC
preflight(PreflightContext const& ctx); preflight(PreflightContext const& ctx);
static FeeUnit64 static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx); calculateBaseFee(ReadView const& view, STTx const& tx);
static TER static TER

View File

@@ -338,15 +338,14 @@ EscrowFinish::preflight(PreflightContext const& ctx)
return tesSUCCESS; return tesSUCCESS;
} }
FeeUnit64 XRPAmount
EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx) EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
FeeUnit64 extraFee{0}; XRPAmount extraFee{0};
if (auto const fb = tx[~sfFulfillment]) if (auto const fb = tx[~sfFulfillment])
{ {
extraFee += extraFee += view.fees().base * (32 + (fb->size() / 16));
safe_cast<FeeUnit64>(view.fees().units) * (32 + (fb->size() / 16));
} }
return Transactor::calculateBaseFee(view, tx) + extraFee; return Transactor::calculateBaseFee(view, tx) + extraFee;

View File

@@ -57,7 +57,7 @@ public:
static NotTEC static NotTEC
preflight(PreflightContext const& ctx); preflight(PreflightContext const& ctx);
static FeeUnit64 static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx); calculateBaseFee(ReadView const& view, STTx const& tx);
TER TER

View File

@@ -24,7 +24,7 @@
namespace ripple { namespace ripple {
FeeUnit64 XRPAmount
SetRegularKey::calculateBaseFee(ReadView const& view, STTx const& tx) SetRegularKey::calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
auto const id = tx.getAccountID(sfAccount); auto const id = tx.getAccountID(sfAccount);
@@ -39,7 +39,7 @@ SetRegularKey::calculateBaseFee(ReadView const& view, STTx const& tx)
if (sle && (!(sle->getFlags() & lsfPasswordSpent))) if (sle && (!(sle->getFlags() & lsfPasswordSpent)))
{ {
// flag is armed and they signed with the right account // flag is armed and they signed with the right account
return FeeUnit64{0}; return XRPAmount{0};
} }
} }
} }

View File

@@ -39,7 +39,7 @@ public:
static NotTEC static NotTEC
preflight(PreflightContext const& ctx); preflight(PreflightContext const& ctx);
static FeeUnit64 static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx); calculateBaseFee(ReadView const& view, STTx const& tx);
TER TER

View File

@@ -137,7 +137,7 @@ Transactor::Transactor(ApplyContext& ctx)
{ {
} }
FeeUnit64 XRPAmount
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
// Returns the fee in fee units. // Returns the fee in fee units.
@@ -145,7 +145,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
// The computation has two parts: // The computation has two parts:
// * The base fee, which is the same for most transactions. // * The base fee, which is the same for most transactions.
// * The additional cost of each multisignature on the transaction. // * The additional cost of each multisignature on the transaction.
FeeUnit64 const baseFee = safe_cast<FeeUnit64>(view.fees().units); XRPAmount const baseFee = view.fees().base;
// Each signer adds one more baseFee to the minimum required fee // Each signer adds one more baseFee to the minimum required fee
// for the transaction. // for the transaction.
@@ -158,7 +158,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
XRPAmount XRPAmount
Transactor::minimumFee( Transactor::minimumFee(
Application& app, Application& app,
FeeUnit64 baseFee, XRPAmount baseFee,
Fees const& fees, Fees const& fees,
ApplyFlags flags) ApplyFlags flags)
{ {
@@ -166,7 +166,7 @@ Transactor::minimumFee(
} }
TER TER
Transactor::checkFee(PreclaimContext const& ctx, FeeUnit64 baseFee) Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
{ {
if (!ctx.tx[sfFee].native()) if (!ctx.tx[sfFee].native())
return temBAD_FEE; return temBAD_FEE;

View File

@@ -132,13 +132,13 @@ public:
checkPriorTxAndLastLedger(PreclaimContext const& ctx); checkPriorTxAndLastLedger(PreclaimContext const& ctx);
static TER static TER
checkFee(PreclaimContext const& ctx, FeeUnit64 baseFee); checkFee(PreclaimContext const& ctx, XRPAmount baseFee);
static NotTEC static NotTEC
checkSign(PreclaimContext const& ctx); checkSign(PreclaimContext const& ctx);
// Returns the fee in fee units, not scaled for load. // Returns the fee in fee units, not scaled for load.
static FeeUnit64 static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx); calculateBaseFee(ReadView const& view, STTx const& tx);
static TER static TER
@@ -182,7 +182,7 @@ protected:
static XRPAmount static XRPAmount
minimumFee( minimumFee(
Application& app, Application& app,
FeeUnit64 baseFee, XRPAmount baseFee,
Fees const& fees, Fees const& fees,
ApplyFlags flags); ApplyFlags flags);

View File

@@ -254,7 +254,7 @@ invoke_preclaim(PreclaimContext const& ctx)
} }
} }
static FeeUnit64 static XRPAmount
invoke_calculateBaseFee(ReadView const& view, STTx const& tx) invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
switch (tx.getTxnType()) switch (tx.getTxnType())
@@ -313,7 +313,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
return NFTokenAcceptOffer::calculateBaseFee(view, tx); return NFTokenAcceptOffer::calculateBaseFee(view, tx);
default: default:
assert(false); assert(false);
return FeeUnit64{0}; return XRPAmount{0};
} }
} }
@@ -535,7 +535,7 @@ preclaim(
} }
} }
FeeUnit64 XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx) calculateBaseFee(ReadView const& view, STTx const& tx)
{ {
return invoke_calculateBaseFee(view, tx); return invoke_calculateBaseFee(view, tx);
@@ -544,7 +544,7 @@ calculateBaseFee(ReadView const& view, STTx const& tx)
XRPAmount XRPAmount
calculateDefaultBaseFee(ReadView const& view, STTx const& tx) calculateDefaultBaseFee(ReadView const& view, STTx const& tx)
{ {
return view.fees().toDrops(Transactor::calculateBaseFee(view, tx)); return Transactor::calculateBaseFee(view, tx);
} }
std::pair<TER, bool> std::pair<TER, bool>

View File

@@ -454,11 +454,6 @@ mulDivU(Source1 value, Dest mul, Source2 div)
} // namespace feeunit } // namespace feeunit
template <class T>
using FeeUnit = feeunit::TaggedFee<feeunit::feeunitTag, T>;
using FeeUnit32 = FeeUnit<std::uint32_t>;
using FeeUnit64 = FeeUnit<std::uint64_t>;
template <class T> template <class T>
using FeeLevel = feeunit::TaggedFee<feeunit::feelevelTag, T>; using FeeLevel = feeunit::TaggedFee<feeunit::feelevelTag, T>;
using FeeLevel64 = FeeLevel<std::uint64_t>; using FeeLevel64 = FeeLevel<std::uint64_t>;

View File

@@ -139,8 +139,9 @@ public:
// Network parameters // Network parameters
// The number of fee units a reference transaction costs // DEPRECATED - Fee units for a reference transction.
static constexpr FeeUnit32 TRANSACTION_FEE_BASE{10}; // Only provided for backwards compatibility in a couple of places
static constexpr std::uint32_t FEE_UNITS_DEPRECATED = 10;
// Note: The following parameters do not relate to the UNL or trust at all // Note: The following parameters do not relate to the UNL or trust at all
// Minimum number of nodes to consider the network present // Minimum number of nodes to consider the network present

View File

@@ -49,7 +49,6 @@ namespace ripple {
struct Fees struct Fees
{ {
XRPAmount base{0}; // Reference tx cost (drops) XRPAmount base{0}; // Reference tx cost (drops)
FeeUnit32 units{0}; // Reference fee units
XRPAmount reserve{0}; // Reserve base (drops) XRPAmount reserve{0}; // Reserve base (drops)
XRPAmount increment{0}; // Reserve increment (drops) XRPAmount increment{0}; // Reserve increment (drops)
@@ -68,15 +67,6 @@ struct Fees
{ {
return reserve + ownerCount * increment; return reserve + ownerCount * increment;
} }
XRPAmount
toDrops(FeeUnit64 const& fee) const
{
if (auto const resultPair = mulDiv(base, fee, units); resultPair.first)
return resultPair.second;
return XRPAmount(STAmount::cMaxNativeN);
}
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@@ -74,7 +74,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how // Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this. // the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 54; static constexpr std::size_t numFeatures = 55;
/** Amendments that this server supports and the default voting behavior. /** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated Whether they are enabled depends on the Rules defined in the validated
@@ -341,6 +341,7 @@ extern uint256 const fixTrustLinesToSelf;
extern uint256 const fixRemoveNFTokenAutoTrustLine; extern uint256 const fixRemoveNFTokenAutoTrustLine;
extern uint256 const featureImmediateOfferKilled; extern uint256 const featureImmediateOfferKilled;
extern uint256 const featureDisallowIncoming; extern uint256 const featureDisallowIncoming;
extern uint256 const featureXRPFees;
} // namespace ripple } // namespace ripple

View File

@@ -483,6 +483,11 @@ extern SF_AMOUNT const sfRippleEscrow;
extern SF_AMOUNT const sfDeliveredAmount; extern SF_AMOUNT const sfDeliveredAmount;
extern SF_AMOUNT const sfNFTokenBrokerFee; extern SF_AMOUNT const sfNFTokenBrokerFee;
// currency amount (fees)
extern SF_AMOUNT const sfBaseFeeDrops;
extern SF_AMOUNT const sfReserveBaseDrops;
extern SF_AMOUNT const sfReserveIncrementDrops;
// variable length (common) // variable length (common)
extern SF_VL const sfPublicKey; extern SF_VL const sfPublicKey;
extern SF_VL const sfMessageKey; extern SF_VL const sfMessageKey;

View File

@@ -49,6 +49,14 @@ isLegalAmount(XRPAmount const& amount)
return amount <= INITIAL_XRP; return amount <= INITIAL_XRP;
} }
/** Returns true if the absolute value of the amount does not exceed the initial
* XRP in existence. */
inline bool
isLegalAmountSigned(XRPAmount const& amount)
{
return amount >= -INITIAL_XRP && amount <= INITIAL_XRP;
}
/* The currency code for the native currency. */ /* The currency code for the native currency. */
static inline std::string const& static inline std::string const&
systemCurrencyCode() systemCurrencyCode()

View File

@@ -451,6 +451,7 @@ REGISTER_FIX (fixTrustLinesToSelf, Supported::yes, DefaultVote::no)
REGISTER_FIX (fixRemoveNFTokenAutoTrustLine, Supported::yes, DefaultVote::yes); REGISTER_FIX (fixRemoveNFTokenAutoTrustLine, Supported::yes, DefaultVote::yes);
REGISTER_FEATURE(ImmediateOfferKilled, Supported::yes, DefaultVote::no); REGISTER_FEATURE(ImmediateOfferKilled, Supported::yes, DefaultVote::no);
REGISTER_FEATURE(DisallowIncoming, Supported::yes, DefaultVote::no); REGISTER_FEATURE(DisallowIncoming, Supported::yes, DefaultVote::no);
REGISTER_FEATURE(XRPFees, Supported::yes, DefaultVote::no);
// The following amendments have been active for at least two years. Their // The following amendments have been active for at least two years. Their
// pre-amendment code has been removed and the identifiers are deprecated. // pre-amendment code has been removed and the identifiers are deprecated.

View File

@@ -146,10 +146,15 @@ LedgerFormats::LedgerFormats()
add(jss::FeeSettings, add(jss::FeeSettings,
ltFEE_SETTINGS, ltFEE_SETTINGS,
{ {
{sfBaseFee, soeREQUIRED}, // Old version uses raw numbers
{sfReferenceFeeUnits, soeREQUIRED}, {sfBaseFee, soeOPTIONAL},
{sfReserveBase, soeREQUIRED}, {sfReferenceFeeUnits, soeOPTIONAL},
{sfReserveIncrement, soeREQUIRED}, {sfReserveBase, soeOPTIONAL},
{sfReserveIncrement, soeOPTIONAL},
// New version uses Amounts
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
}, },
commonFields); commonFields);

View File

@@ -234,6 +234,13 @@ CONSTRUCT_TYPED_SFIELD(sfRippleEscrow, "RippleEscrow", AMOUNT,
CONSTRUCT_TYPED_SFIELD(sfDeliveredAmount, "DeliveredAmount", AMOUNT, 18); CONSTRUCT_TYPED_SFIELD(sfDeliveredAmount, "DeliveredAmount", AMOUNT, 18);
CONSTRUCT_TYPED_SFIELD(sfNFTokenBrokerFee, "NFTokenBrokerFee", AMOUNT, 19); CONSTRUCT_TYPED_SFIELD(sfNFTokenBrokerFee, "NFTokenBrokerFee", AMOUNT, 19);
// Reserve 20 & 21 for Hooks
// currency amount (fees)
CONSTRUCT_TYPED_SFIELD(sfBaseFeeDrops, "BaseFeeDrops", AMOUNT, 22);
CONSTRUCT_TYPED_SFIELD(sfReserveBaseDrops, "ReserveBaseDrops", AMOUNT, 23);
CONSTRUCT_TYPED_SFIELD(sfReserveIncrementDrops, "ReserveIncrementDrops", AMOUNT, 24);
// variable length (common) // variable length (common)
CONSTRUCT_TYPED_SFIELD(sfPublicKey, "PublicKey", VL, 1); CONSTRUCT_TYPED_SFIELD(sfPublicKey, "PublicKey", VL, 1);
CONSTRUCT_TYPED_SFIELD(sfMessageKey, "MessageKey", VL, 2); CONSTRUCT_TYPED_SFIELD(sfMessageKey, "MessageKey", VL, 2);

View File

@@ -58,9 +58,14 @@ STValidation::validationFormat()
{sfSigningPubKey, soeREQUIRED}, {sfSigningPubKey, soeREQUIRED},
{sfSignature, soeREQUIRED}, {sfSignature, soeREQUIRED},
{sfConsensusHash, soeOPTIONAL}, {sfConsensusHash, soeOPTIONAL},
// featureHardenedValidations
{sfCookie, soeDEFAULT}, {sfCookie, soeDEFAULT},
{sfValidatedHash, soeOPTIONAL}, {sfValidatedHash, soeOPTIONAL},
{sfServerVersion, soeOPTIONAL}, {sfServerVersion, soeOPTIONAL},
// featureXRPFees
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
}; };
// clang-format on // clang-format on

View File

@@ -155,10 +155,15 @@ TxFormats::TxFormats()
ttFEE, ttFEE,
{ {
{sfLedgerSequence, soeOPTIONAL}, {sfLedgerSequence, soeOPTIONAL},
{sfBaseFee, soeREQUIRED}, // Old version uses raw numbers
{sfReferenceFeeUnits, soeREQUIRED}, {sfBaseFee, soeOPTIONAL},
{sfReserveBase, soeREQUIRED}, {sfReferenceFeeUnits, soeOPTIONAL},
{sfReserveIncrement, soeREQUIRED}, {sfReserveBase, soeOPTIONAL},
{sfReserveIncrement, soeOPTIONAL},
// New version uses Amounts
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
}, },
commonFields); commonFields);

View File

@@ -151,6 +151,7 @@ JSS(balance); // out: AccountLines
JSS(balances); // out: GatewayBalances JSS(balances); // out: GatewayBalances
JSS(base); // out: LogLevel JSS(base); // out: LogLevel
JSS(base_fee); // out: NetworkOPs JSS(base_fee); // out: NetworkOPs
JSS(base_fee_drops); // out: NetworkOPs
JSS(base_fee_xrp); // out: NetworkOPs JSS(base_fee_xrp); // out: NetworkOPs
JSS(bids); // out: Subscribe JSS(bids); // out: Subscribe
JSS(binary); // in: AccountTX, LedgerEntry, JSS(binary); // in: AccountTX, LedgerEntry,
@@ -495,8 +496,10 @@ JSS(request); // RPC
JSS(requested); // out: Manifest JSS(requested); // out: Manifest
JSS(reservations); // out: Reservations JSS(reservations); // out: Reservations
JSS(reserve_base); // out: NetworkOPs JSS(reserve_base); // out: NetworkOPs
JSS(reserve_base_drops); // out: NetworkOPs
JSS(reserve_base_xrp); // out: NetworkOPs JSS(reserve_base_xrp); // out: NetworkOPs
JSS(reserve_inc); // out: NetworkOPs JSS(reserve_inc); // out: NetworkOPs
JSS(reserve_inc_drops); // out: NetworkOPs
JSS(reserve_inc_xrp); // out: NetworkOPs JSS(reserve_inc_xrp); // out: NetworkOPs
JSS(response); // websocket JSS(response); // websocket
JSS(result); // RPC JSS(result); // RPC

View File

@@ -45,7 +45,7 @@ fillTransaction(
// Convert the reference transaction cost in fee units to drops // Convert the reference transaction cost in fee units to drops
// scaled to represent the current fee load. // scaled to represent the current fee load.
txArray["Fee"] = txArray["Fee"] =
scaleFeeLoad(fees.units, context.app.getFeeTrack(), fees, false) scaleFeeLoad(fees.base, context.app.getFeeTrack(), fees, false)
.jsonClipped(); .jsonClipped();
} }

View File

@@ -720,8 +720,7 @@ checkFee(
} }
} }
// Default fee in fee units. XRPAmount const feeDefault = config.FEE_DEFAULT;
FeeUnit32 const feeDefault = config.TRANSACTION_FEE_BASE;
auto ledger = app.openLedger().current(); auto ledger = app.openLedger().current();
// Administrative and identified endpoints are exempt from local fees. // Administrative and identified endpoints are exempt from local fees.
@@ -738,11 +737,7 @@ checkFee(
auto const limit = [&]() { auto const limit = [&]() {
// Scale fee units to drops: // Scale fee units to drops:
auto const drops = auto const result = mulDiv(feeDefault, mult, div);
mulDiv(feeDefault, ledger->fees().base, ledger->fees().units);
if (!drops.first)
Throw<std::overflow_error>("mulDiv");
auto const result = mulDiv(drops.second, mult, div);
if (!result.first) if (!result.first)
Throw<std::overflow_error>("mulDiv"); Throw<std::overflow_error>("mulDiv");
return result.second; return result.second;

View File

@@ -515,7 +515,10 @@ public:
// All it takes is a large enough XRP payment to resurrect // All it takes is a large enough XRP payment to resurrect
// becky's account. Try too small a payment. // becky's account. Try too small a payment.
env(pay(alice, becky, XRP(9)), ter(tecNO_DST_INSUF_XRP)); env(pay(alice,
becky,
drops(env.current()->fees().accountReserve(0)) - XRP(1)),
ter(tecNO_DST_INSUF_XRP));
env.close(); env.close();
// Actually resurrect becky's account. // Actually resurrect becky's account.

View File

@@ -29,13 +29,14 @@ class FeeVote_test : public beast::unit_test::suite
void void
testSetup() testSetup()
{ {
FeeVote::Setup const defaultSetup;
{ {
// defaults // defaults
Section config; Section config;
auto setup = setup_FeeVote(config); auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == 10); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == 10 * DROPS_PER_XRP); BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == 2 * DROPS_PER_XRP); BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
} }
{ {
Section config; Section config;
@@ -56,9 +57,9 @@ class FeeVote_test : public beast::unit_test::suite
"owner_reserve = foo"}); "owner_reserve = foo"});
// Illegal values are ignored, and the defaults left unchanged // Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config); auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == 10); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == 10 * DROPS_PER_XRP); BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == 2 * DROPS_PER_XRP); BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
} }
{ {
Section config; Section config;
@@ -68,7 +69,7 @@ class FeeVote_test : public beast::unit_test::suite
"owner_reserve = -1234"}); "owner_reserve = -1234"});
// Illegal values are ignored, and the defaults left unchanged // Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config); auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == 10); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT( BEAST_EXPECT(
setup.account_reserve == static_cast<std::uint32_t>(-1234567)); setup.account_reserve == static_cast<std::uint32_t>(-1234567));
BEAST_EXPECT( BEAST_EXPECT(
@@ -86,9 +87,9 @@ class FeeVote_test : public beast::unit_test::suite
"owner_reserve = " + big64}); "owner_reserve = " + big64});
// Illegal values are ignored, and the defaults left unchanged // Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config); auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == 10); BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == 10 * DROPS_PER_XRP); BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == 2 * DROPS_PER_XRP); BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
} }
} }

View File

@@ -36,55 +36,52 @@ public:
Fees const fees = [&]() { Fees const fees = [&]() {
Fees f; Fees f;
f.base = d.FEE_DEFAULT; f.base = d.FEE_DEFAULT;
f.units = d.TRANSACTION_FEE_BASE;
f.reserve = 200 * DROPS_PER_XRP; f.reserve = 200 * DROPS_PER_XRP;
f.increment = 50 * DROPS_PER_XRP; f.increment = 50 * DROPS_PER_XRP;
return f; return f;
}(); }();
BEAST_EXPECT( BEAST_EXPECT(
scaleFeeLoad(FeeUnit64{0}, l, fees, false) == XRPAmount{0}); scaleFeeLoad(XRPAmount{0}, l, fees, false) == XRPAmount{0});
BEAST_EXPECT( BEAST_EXPECT(
scaleFeeLoad(FeeUnit64{10000}, l, fees, false) == scaleFeeLoad(XRPAmount{10000}, l, fees, false) ==
XRPAmount{10000}); XRPAmount{10000});
BEAST_EXPECT( BEAST_EXPECT(
scaleFeeLoad(FeeUnit64{1}, l, fees, false) == XRPAmount{1}); scaleFeeLoad(XRPAmount{1}, l, fees, false) == XRPAmount{1});
} }
{ {
Fees const fees = [&]() { Fees const fees = [&]() {
Fees f; Fees f;
f.base = d.FEE_DEFAULT * 10; f.base = d.FEE_DEFAULT * 10;
f.units = d.TRANSACTION_FEE_BASE;
f.reserve = 200 * DROPS_PER_XRP; f.reserve = 200 * DROPS_PER_XRP;
f.increment = 50 * DROPS_PER_XRP; f.increment = 50 * DROPS_PER_XRP;
return f; return f;
}(); }();
BEAST_EXPECT( BEAST_EXPECT(
scaleFeeLoad(FeeUnit64{0}, l, fees, false) == XRPAmount{0}); scaleFeeLoad(XRPAmount{0}, l, fees, false) == XRPAmount{0});
BEAST_EXPECT( BEAST_EXPECT(
scaleFeeLoad(FeeUnit64{10000}, l, fees, false) == scaleFeeLoad(XRPAmount{10000}, l, fees, false) ==
XRPAmount{100000}); XRPAmount{10000});
BEAST_EXPECT( BEAST_EXPECT(
scaleFeeLoad(FeeUnit64{1}, l, fees, false) == XRPAmount{10}); scaleFeeLoad(XRPAmount{1}, l, fees, false) == XRPAmount{1});
} }
{ {
Fees const fees = [&]() { Fees const fees = [&]() {
Fees f; Fees f;
f.base = d.FEE_DEFAULT; f.base = d.FEE_DEFAULT;
f.units = d.TRANSACTION_FEE_BASE * 10;
f.reserve = 200 * DROPS_PER_XRP; f.reserve = 200 * DROPS_PER_XRP;
f.increment = 50 * DROPS_PER_XRP; f.increment = 50 * DROPS_PER_XRP;
return f; return f;
}(); }();
BEAST_EXPECT( BEAST_EXPECT(
scaleFeeLoad(FeeUnit64{0}, l, fees, false) == XRPAmount{0}); scaleFeeLoad(XRPAmount{0}, l, fees, false) == XRPAmount{0});
BEAST_EXPECT( BEAST_EXPECT(
scaleFeeLoad(FeeUnit64{10000}, l, fees, false) == scaleFeeLoad(XRPAmount{10000}, l, fees, false) ==
XRPAmount{1000}); XRPAmount{10000});
BEAST_EXPECT( BEAST_EXPECT(
scaleFeeLoad(FeeUnit64{1}, l, fees, false) == XRPAmount{0}); scaleFeeLoad(XRPAmount{1}, l, fees, false) == XRPAmount{1});
} }
} }
}; };

View File

@@ -16,6 +16,7 @@
//============================================================================== //==============================================================================
#include <ripple/app/tx/apply.h> #include <ripple/app/tx/apply.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/STAccount.h> #include <ripple/protocol/STAccount.h>
#include <string> #include <string>
#include <test/jtx.h> #include <test/jtx.h>
@@ -27,17 +28,26 @@ namespace test {
struct PseudoTx_test : public beast::unit_test::suite struct PseudoTx_test : public beast::unit_test::suite
{ {
std::vector<STTx> std::vector<STTx>
getPseudoTxs(std::uint32_t seq) getPseudoTxs(Rules const& rules, std::uint32_t seq)
{ {
std::vector<STTx> res; std::vector<STTx> res;
res.emplace_back(STTx(ttFEE, [&](auto& obj) { res.emplace_back(STTx(ttFEE, [&](auto& obj) {
obj[sfAccount] = AccountID(); obj[sfAccount] = AccountID();
obj[sfLedgerSequence] = seq; obj[sfLedgerSequence] = seq;
if (rules.enabled(featureXRPFees))
{
obj[sfBaseFeeDrops] = XRPAmount{0};
obj[sfReserveBaseDrops] = XRPAmount{0};
obj[sfReserveIncrementDrops] = XRPAmount{0};
}
else
{
obj[sfBaseFee] = 0; obj[sfBaseFee] = 0;
obj[sfReserveBase] = 0; obj[sfReserveBase] = 0;
obj[sfReserveIncrement] = 0; obj[sfReserveIncrement] = 0;
obj[sfReferenceFeeUnits] = 0; obj[sfReferenceFeeUnits] = 0;
}
})); }));
res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) { res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) {
@@ -66,12 +76,13 @@ struct PseudoTx_test : public beast::unit_test::suite
} }
void void
testPrevented() testPrevented(FeatureBitset features)
{ {
using namespace jtx; using namespace jtx;
Env env(*this); Env env(*this, features);
for (auto const& stx : getPseudoTxs(env.closed()->seq() + 1)) for (auto const& stx :
getPseudoTxs(env.closed()->rules(), env.closed()->seq() + 1))
{ {
std::string reason; std::string reason;
BEAST_EXPECT(isPseudoTx(stx)); BEAST_EXPECT(isPseudoTx(stx));
@@ -101,7 +112,12 @@ struct PseudoTx_test : public beast::unit_test::suite
void void
run() override run() override
{ {
testPrevented(); using namespace test::jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const xrpFees{featureXRPFees};
testPrevented(all - featureXRPFees);
testPrevented(all);
testAllowed(); testAllowed();
} }
}; };

View File

@@ -144,9 +144,15 @@ class TxQ1_test : public beast::unit_test::suite
auto const& view = *env.current(); auto const& view = *env.current();
auto metrics = env.app().getTxQ().getMetrics(view); auto metrics = env.app().getTxQ().getMetrics(view);
auto const base = [&view]() {
auto base = view.fees().base;
if (!base)
base += 1;
return base;
}();
// Don't care about the overflow flag // Don't care about the overflow flag
return fee(toDrops(metrics.openLedgerFeeLevel, view.fees().base) + 1); return fee(toDrops(metrics.openLedgerFeeLevel, base) + 1);
} }
static std::unique_ptr<Config> static std::unique_ptr<Config>
@@ -189,7 +195,6 @@ class TxQ1_test : public beast::unit_test::suite
std::size_t expectedPerLedger, std::size_t expectedPerLedger,
std::size_t ledgersInQueue, std::size_t ledgersInQueue,
std::uint32_t base, std::uint32_t base,
std::uint32_t units,
std::uint32_t reserve, std::uint32_t reserve,
std::uint32_t increment) std::uint32_t increment)
{ {
@@ -219,7 +224,6 @@ class TxQ1_test : public beast::unit_test::suite
checkMetrics(__LINE__, env, 0, flagMaxQueue, 0, expectedPerLedger, 256); checkMetrics(__LINE__, env, 0, flagMaxQueue, 0, expectedPerLedger, 256);
auto const fees = env.current()->fees(); auto const fees = env.current()->fees();
BEAST_EXPECT(fees.base == XRPAmount{base}); BEAST_EXPECT(fees.base == XRPAmount{base});
BEAST_EXPECT(fees.units == FeeUnit64{units});
BEAST_EXPECT(fees.reserve == XRPAmount{reserve}); BEAST_EXPECT(fees.reserve == XRPAmount{reserve});
BEAST_EXPECT(fees.increment == XRPAmount{increment}); BEAST_EXPECT(fees.increment == XRPAmount{increment});
@@ -1095,7 +1099,7 @@ public:
checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3, 256); checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3, 256);
// ledgers in queue is 2 because of makeConfig // ledgers in queue is 2 because of makeConfig
auto const initQueueMax = initFee(env, 3, 2, 10, 10, 200, 50); auto const initQueueMax = initFee(env, 3, 2, 10, 200, 50);
// Create several accounts while the fee is cheap so they all apply. // Create several accounts while the fee is cheap so they all apply.
env.fund(drops(2000), noripple(alice)); env.fund(drops(2000), noripple(alice));
@@ -1742,7 +1746,7 @@ public:
auto queued = ter(terQUEUED); auto queued = ter(terQUEUED);
// ledgers in queue is 2 because of makeConfig // ledgers in queue is 2 because of makeConfig
auto const initQueueMax = initFee(env, 3, 2, 10, 10, 200, 50); auto const initQueueMax = initFee(env, 3, 2, 10, 200, 50);
BEAST_EXPECT(env.current()->fees().base == 10); BEAST_EXPECT(env.current()->fees().base == 10);
@@ -2137,7 +2141,7 @@ public:
// queued before the open ledger fee approached the reserve, // queued before the open ledger fee approached the reserve,
// which would unnecessarily slow down this test. // which would unnecessarily slow down this test.
// ledgers in queue is 2 because of makeConfig // ledgers in queue is 2 because of makeConfig
auto const initQueueMax = initFee(env, 3, 2, 10, 10, 200, 50); auto const initQueueMax = initFee(env, 3, 2, 10, 200, 50);
auto limit = 3; auto limit = 3;
@@ -4785,6 +4789,144 @@ public:
} }
} }
void
testZeroReferenceFee()
{
testcase("Zero reference fee");
using namespace jtx;
Account const alice("alice");
auto const queued = ter(terQUEUED);
Env env(
*this,
makeConfig(
{{"minimum_txn_in_ledger_standalone", "3"}},
{{"reference_fee", "0"},
{"account_reserve", "0"},
{"owner_reserve", "0"}}));
BEAST_EXPECT(env.current()->fees().base == 10);
checkMetrics(__LINE__, env, 0, std::nullopt, 0, 3, 256);
// ledgers in queue is 2 because of makeConfig
auto const initQueueMax = initFee(env, 3, 2, 0, 0, 0);
BEAST_EXPECT(env.current()->fees().base == 0);
{
auto const fee = env.rpc("fee");
if (BEAST_EXPECT(fee.isMember(jss::result)) &&
BEAST_EXPECT(!RPC::contains_error(fee[jss::result])))
{
auto const& result = fee[jss::result];
BEAST_EXPECT(result.isMember(jss::levels));
auto const& levels = result[jss::levels];
BEAST_EXPECT(
levels.isMember(jss::median_level) &&
levels[jss::median_level] == "128000");
BEAST_EXPECT(
levels.isMember(jss::minimum_level) &&
levels[jss::minimum_level] == "256");
BEAST_EXPECT(
levels.isMember(jss::open_ledger_level) &&
levels[jss::open_ledger_level] == "256");
BEAST_EXPECT(
levels.isMember(jss::reference_level) &&
levels[jss::reference_level] == "256");
auto const& drops = result[jss::drops];
BEAST_EXPECT(
drops.isMember(jss::base_fee) &&
drops[jss::base_fee] == "0");
BEAST_EXPECT(
drops.isMember(jss::median_fee) &&
drops[jss::base_fee] == "0");
BEAST_EXPECT(
drops.isMember(jss::minimum_fee) &&
drops[jss::base_fee] == "0");
BEAST_EXPECT(
drops.isMember(jss::open_ledger_fee) &&
drops[jss::base_fee] == "0");
}
}
checkMetrics(__LINE__, env, 0, initQueueMax, 0, 3, 256);
// The noripple is to reduce the number of transactions required to
// fund the accounts. There is no rippling in this test.
env.fund(XRP(100000), noripple(alice));
checkMetrics(__LINE__, env, 0, initQueueMax, 1, 3, 256);
env.close();
checkMetrics(__LINE__, env, 0, 6, 0, 3, 256);
fillQueue(env, alice);
checkMetrics(__LINE__, env, 0, 6, 4, 3, 256);
env(noop(alice), openLedgerFee(env));
checkMetrics(__LINE__, env, 0, 6, 5, 3, 256);
auto aliceSeq = env.seq(alice);
env(noop(alice), queued);
checkMetrics(__LINE__, env, 1, 6, 5, 3, 256);
env(noop(alice), seq(aliceSeq + 1), fee(10), queued);
checkMetrics(__LINE__, env, 2, 6, 5, 3, 256);
{
auto const fee = env.rpc("fee");
if (BEAST_EXPECT(fee.isMember(jss::result)) &&
BEAST_EXPECT(!RPC::contains_error(fee[jss::result])))
{
auto const& result = fee[jss::result];
BEAST_EXPECT(result.isMember(jss::levels));
auto const& levels = result[jss::levels];
BEAST_EXPECT(
levels.isMember(jss::median_level) &&
levels[jss::median_level] == "128000");
BEAST_EXPECT(
levels.isMember(jss::minimum_level) &&
levels[jss::minimum_level] == "256");
BEAST_EXPECT(
levels.isMember(jss::open_ledger_level) &&
levels[jss::open_ledger_level] == "355555");
BEAST_EXPECT(
levels.isMember(jss::reference_level) &&
levels[jss::reference_level] == "256");
auto const& drops = result[jss::drops];
BEAST_EXPECT(
drops.isMember(jss::base_fee) &&
drops[jss::base_fee] == "0");
BEAST_EXPECT(
drops.isMember(jss::median_fee) &&
drops[jss::median_fee] == "0");
BEAST_EXPECT(
drops.isMember(jss::minimum_fee) &&
drops[jss::minimum_fee] == "0");
BEAST_EXPECT(
drops.isMember(jss::open_ledger_fee) &&
drops[jss::open_ledger_fee] == "1389");
}
}
env.close();
checkMetrics(__LINE__, env, 0, 10, 2, 5, 256);
}
void void
run() override run() override
{ {
@@ -4825,6 +4967,7 @@ public:
testReexecutePreflight(); testReexecutePreflight();
testQueueFullDropPenalty(); testQueueFullDropPenalty();
testCancelQueuedOffers(); testCancelQueuedOffers();
testZeroReferenceFee();
} }
}; };

View File

@@ -30,6 +30,8 @@ private:
void void
testTypes() testTypes()
{ {
using FeeLevel32 = FeeLevel<std::uint32_t>;
{ {
XRPAmount x{100}; XRPAmount x{100};
BEAST_EXPECT(x.drops() == 100); BEAST_EXPECT(x.drops() == 100);
@@ -45,8 +47,8 @@ private:
BEAST_EXPECT( BEAST_EXPECT(
(std::is_same_v<decltype(z)::unit_type, feeunit::dropTag>)); (std::is_same_v<decltype(z)::unit_type, feeunit::dropTag>));
FeeUnit32 f{10}; FeeLevel32 f{10};
FeeUnit32 baseFee{100}; FeeLevel32 baseFee{100};
auto drops = mulDiv(baseFee, x, f).second; auto drops = mulDiv(baseFee, x, f).second;
@@ -65,8 +67,8 @@ private:
BEAST_EXPECT( BEAST_EXPECT(
(std::is_same_v<decltype(y)::unit_type, feeunit::dropTag>)); (std::is_same_v<decltype(y)::unit_type, feeunit::dropTag>));
FeeUnit64 f{10}; FeeLevel64 f{10};
FeeUnit64 baseFee{100}; FeeLevel64 baseFee{100};
auto drops = mulDiv(baseFee, x, f).second; auto drops = mulDiv(baseFee, x, f).second;
@@ -102,22 +104,24 @@ private:
testJson() testJson()
{ {
// Json value functionality // Json value functionality
using FeeLevel32 = FeeLevel<std::uint32_t>;
{ {
FeeUnit32 x{std::numeric_limits<std::uint32_t>::max()}; FeeLevel32 x{std::numeric_limits<std::uint32_t>::max()};
auto y = x.jsonClipped(); auto y = x.jsonClipped();
BEAST_EXPECT(y.type() == Json::uintValue); BEAST_EXPECT(y.type() == Json::uintValue);
BEAST_EXPECT(y == Json::Value{x.fee()}); BEAST_EXPECT(y == Json::Value{x.fee()});
} }
{ {
FeeUnit32 x{std::numeric_limits<std::uint32_t>::min()}; FeeLevel32 x{std::numeric_limits<std::uint32_t>::min()};
auto y = x.jsonClipped(); auto y = x.jsonClipped();
BEAST_EXPECT(y.type() == Json::uintValue); BEAST_EXPECT(y.type() == Json::uintValue);
BEAST_EXPECT(y == Json::Value{x.fee()}); BEAST_EXPECT(y == Json::Value{x.fee()});
} }
{ {
FeeUnit64 x{std::numeric_limits<std::uint64_t>::max()}; FeeLevel64 x{std::numeric_limits<std::uint64_t>::max()};
auto y = x.jsonClipped(); auto y = x.jsonClipped();
BEAST_EXPECT(y.type() == Json::uintValue); BEAST_EXPECT(y.type() == Json::uintValue);
BEAST_EXPECT( BEAST_EXPECT(
@@ -125,7 +129,7 @@ private:
} }
{ {
FeeUnit64 x{std::numeric_limits<std::uint64_t>::min()}; FeeLevel64 x{std::numeric_limits<std::uint64_t>::min()};
auto y = x.jsonClipped(); auto y = x.jsonClipped();
BEAST_EXPECT(y.type() == Json::uintValue); BEAST_EXPECT(y.type() == Json::uintValue);
BEAST_EXPECT(y == Json::Value{0}); BEAST_EXPECT(y == Json::Value{0});
@@ -167,15 +171,17 @@ private:
{ {
// Explicitly test every defined function for the TaggedFee class // Explicitly test every defined function for the TaggedFee class
// since some of them are templated, but not used anywhere else. // since some of them are templated, but not used anywhere else.
using FeeLevel32 = FeeLevel<std::uint32_t>;
{ {
auto make = [&](auto x) -> FeeUnit64 { return x; }; auto make = [&](auto x) -> FeeLevel64 { return x; };
auto explicitmake = [&](auto x) -> FeeUnit64 { auto explicitmake = [&](auto x) -> FeeLevel64 {
return FeeUnit64{x}; return FeeLevel64{x};
}; };
FeeUnit64 defaulted; FeeLevel64 defaulted;
(void)defaulted; (void)defaulted;
FeeUnit64 test{0}; FeeLevel64 test{0};
BEAST_EXPECT(test.fee() == 0); BEAST_EXPECT(test.fee() == 0);
test = explicitmake(beast::zero); test = explicitmake(beast::zero);
@@ -187,13 +193,13 @@ private:
test = explicitmake(100u); test = explicitmake(100u);
BEAST_EXPECT(test.fee() == 100); BEAST_EXPECT(test.fee() == 100);
FeeUnit64 const targetSame{200u}; FeeLevel64 const targetSame{200u};
FeeUnit32 const targetOther{300u}; FeeLevel32 const targetOther{300u};
test = make(targetSame); test = make(targetSame);
BEAST_EXPECT(test.fee() == 200); BEAST_EXPECT(test.fee() == 200);
BEAST_EXPECT(test == targetSame); BEAST_EXPECT(test == targetSame);
BEAST_EXPECT(test < FeeUnit64{1000}); BEAST_EXPECT(test < FeeLevel64{1000});
BEAST_EXPECT(test > FeeUnit64{100}); BEAST_EXPECT(test > FeeLevel64{100});
test = make(targetOther); test = make(targetOther);
BEAST_EXPECT(test.fee() == 300); BEAST_EXPECT(test.fee() == 300);
BEAST_EXPECT(test == targetOther); BEAST_EXPECT(test == targetOther);

View File

@@ -64,7 +64,7 @@ class Invariants_test : public beast::unit_test::suite
ov, ov,
tx, tx,
tesSUCCESS, tesSUCCESS,
safe_cast<FeeUnit64>(env.current()->fees().units), env.current()->fees().base,
tapNONE, tapNONE,
jlog}; jlog};

View File

@@ -547,7 +547,10 @@ class AccountTx_test : public beast::unit_test::suite
// All it takes is a large enough XRP payment to resurrect // All it takes is a large enough XRP payment to resurrect
// becky's account. Try too small a payment. // becky's account. Try too small a payment.
env(pay(alice, becky, XRP(9)), ter(tecNO_DST_INSUF_XRP)); env(pay(alice,
becky,
drops(env.current()->fees().accountReserve(0)) - XRP(1)),
ter(tecNO_DST_INSUF_XRP));
env.close(); env.close();
// Actually resurrect becky's account. // Actually resurrect becky's account.

View File

@@ -20,6 +20,7 @@
#include <ripple/app/misc/NetworkOPs.h> #include <ripple/app/misc/NetworkOPs.h>
#include <ripple/beast/unit_test.h> #include <ripple/beast/unit_test.h>
#include <ripple/core/ConfigSections.h> #include <ripple/core/ConfigSections.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/jss.h> #include <ripple/protocol/jss.h>
#include <test/jtx.h> #include <test/jtx.h>
#include <test/jtx/WSClient.h> #include <test/jtx/WSClient.h>
@@ -326,11 +327,11 @@ public:
} }
void void
testValidations() testValidations(FeatureBitset features)
{ {
using namespace jtx; using namespace jtx;
Env env{*this, envconfig(validator, "")}; Env env{*this, envconfig(validator, ""), features};
auto& cfg = env.app().config(); auto& cfg = env.app().config();
if (!BEAST_EXPECT(cfg.section(SECTION_VALIDATION_SEED).empty())) if (!BEAST_EXPECT(cfg.section(SECTION_VALIDATION_SEED).empty()))
return; return;
@@ -410,10 +411,25 @@ public:
if (jv.isMember(jss::server_version) != isFlagLedger) if (jv.isMember(jss::server_version) != isFlagLedger)
return false; return false;
if (jv.isMember(jss::reserve_base) != isFlagLedger) bool xrpFees = env.closed()->rules().enabled(featureXRPFees);
if ((!xrpFees &&
jv.isMember(jss::reserve_base) != isFlagLedger) ||
(xrpFees && jv.isMember(jss::reserve_base)))
return false; return false;
if (jv.isMember(jss::reserve_inc) != isFlagLedger) if ((!xrpFees &&
jv.isMember(jss::reserve_inc) != isFlagLedger) ||
(xrpFees && jv.isMember(jss::reserve_inc)))
return false;
if ((xrpFees &&
jv.isMember(jss::reserve_base_drops) != isFlagLedger) ||
(!xrpFees && jv.isMember(jss::reserve_base_drops)))
return false;
if ((xrpFees &&
jv.isMember(jss::reserve_inc_drops) != isFlagLedger) ||
(!xrpFees && jv.isMember(jss::reserve_inc_drops)))
return false; return false;
return true; return true;
@@ -1140,11 +1156,16 @@ public:
void void
run() override run() override
{ {
using namespace test::jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const xrpFees{featureXRPFees};
testServer(); testServer();
testLedger(); testLedger();
testTransactions(); testTransactions();
testManifests(); testManifests();
testValidations(); testValidations(all - xrpFees);
testValidations(all);
testSubErrors(true); testSubErrors(true);
testSubErrors(false); testSubErrors(false);
testSubByUrl(); testSubByUrl();