mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-26 22:15:52 +00:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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&)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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_)
|
||||||
|
|||||||
@@ -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&
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user