#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { namespace detail { class VotableValue { private: using value_type = XRPAmount; value_type const current_; // The current setting value_type const target_; // The setting we want std::map voteMap_; public: VotableValue(value_type current, value_type target) : current_(current), target_(target) { // Add our vote ++voteMap_[target_]; } void addVote(value_type vote) { ++voteMap_[vote]; } void noVote() { addVote(current_); } value_type current() const { return current_; } std::pair getVotes() const; }; auto VotableValue::getVotes() const -> std::pair { value_type ourVote = current_; int weight = 0; for (auto const& [key, val] : voteMap_) { // Take most voted value between current and target, inclusive if ((key <= std::max(target_, current_)) && (key >= std::min(target_, current_)) && (val > weight)) { ourVote = key; weight = val; } } return {ourVote, ourVote != current_}; } } // namespace detail //------------------------------------------------------------------------------ class FeeVoteImpl : public FeeVote { private: FeeSetup target_; beast::Journal const journal_; public: FeeVoteImpl(FeeSetup const& setup, beast::Journal journal); void doValidation(Fees const& lastFees, Rules const& rules, STValidation& val) override; void doVoting( std::shared_ptr const& lastClosedLedger, std::vector> const& parentValidations, std::shared_ptr const& initialPosition) override; }; //-------------------------------------------------------------------------- FeeVoteImpl::FeeVoteImpl(FeeSetup const& setup, beast::Journal journal) : target_(setup), journal_(journal) { } void FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation& v) { // 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 // not send a value. if (rules.enabled(featureXRPFees)) { auto vote = [&v, this](auto const current, XRPAmount target, char const* name, auto const& sfield) { if (current != target) { JLOG(journal_.info()) << "Voting for " << name << " of " << target; v[sfield] = target; } }; vote(lastFees.base, target_.reference_fee, "base fee", sfBaseFeeDrops); vote(lastFees.reserve, target_.account_reserve, "base reserve", sfReserveBaseDrops); vote( lastFees.increment, target_.owner_reserve, "reserve increment", sfReserveIncrementDrops); } else { auto to32 = [](XRPAmount target) { return target.dropsAs(); }; auto to64 = [](XRPAmount target) { return target.dropsAs(); }; auto vote = [&v, this]( auto const current, XRPAmount target, auto const& convertCallback, char const* name, auto const& sfield) { if (current != target) { JLOG(journal_.info()) << "Voting for " << name << " of " << target; if (auto const f = convertCallback(target)) v[sfield] = *f; } }; vote(lastFees.base, target_.reference_fee, to64, "base fee", sfBaseFee); vote(lastFees.reserve, target_.account_reserve, to32, "base reserve", sfReserveBase); vote( lastFees.increment, target_.owner_reserve, to32, "reserve increment", sfReserveIncrement); } } void FeeVoteImpl::doVoting( std::shared_ptr const& lastClosedLedger, std::vector> const& set, std::shared_ptr const& initialPosition) { // LCL must be flag ledger XRPL_ASSERT( lastClosedLedger && isFlagLedger(lastClosedLedger->seq()), "xrpl::FeeVoteImpl::doVoting : has a flag ledger"); detail::VotableValue baseFeeVote(lastClosedLedger->fees().base, target_.reference_fee); detail::VotableValue baseReserveVote(lastClosedLedger->fees().reserve, target_.account_reserve); detail::VotableValue incReserveVote(lastClosedLedger->fees().increment, target_.owner_reserve); auto const& rules = lastClosedLedger->rules(); if (rules.enabled(featureXRPFees)) { auto doVote = [](std::shared_ptr 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) { if (!val->isTrusted()) continue; doVote(val, baseFeeVote, sfBaseFeeDrops); doVote(val, baseReserveVote, sfReserveBaseDrops); doVote(val, incReserveVote, sfReserveIncrementDrops); } } else { auto doVote = [](std::shared_ptr const& val, detail::VotableValue& value, auto const& valueField) { if (auto const field = val->at(~valueField)) { using XRPType = XRPAmount::value_type; auto const vote = *field; if (vote <= std::numeric_limits::max() && isLegalAmountSigned(XRPAmount{unsafe_cast(vote)})) { value.addVote(XRPAmount{unsafe_cast(vote)}); } else { // Invalid amounts will be treated as if they're // not provided. Don't throw because this value is // provided by an external entity. value.noVote(); } } else { value.noVote(); } }; for (auto const& val : set) { if (!val->isTrusted()) continue; doVote(val, baseFeeVote, sfBaseFee); doVote(val, baseReserveVote, sfReserveBase); doVote(val, incReserveVote, sfReserveIncrement); } } // choose our positions // TODO: Use structured binding once LLVM 16 is the minimum supported // version. See also: https://github.com/llvm/llvm-project/issues/48582 // https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c auto const baseFee = baseFeeVote.getVotes(); auto const baseReserve = baseReserveVote.getVotes(); auto const incReserve = incReserveVote.getVotes(); auto const seq = lastClosedLedger->header().seq + 1; // add transactions to our position if (baseFee.second || baseReserve.second || incReserve.second) { JLOG(journal_.warn()) << "We are voting for a fee change: " << baseFee.first << "/" << baseReserve.first << "/" << incReserve.first; STTx const feeTx(ttFEE, [=, &rules](auto& obj) { obj[sfAccount] = AccountID(); obj[sfLedgerSequence] = seq; if (rules.enabled(featureXRPFees)) { obj[sfBaseFeeDrops] = baseFee.first; obj[sfReserveBaseDrops] = baseReserve.first; obj[sfReserveIncrementDrops] = incReserve.first; } else { // Without the featureXRPFees amendment, these fields are // required. obj[sfBaseFee] = baseFee.first.dropsAs(baseFeeVote.current()); obj[sfReserveBase] = baseReserve.first.dropsAs(baseReserveVote.current()); obj[sfReserveIncrement] = incReserve.first.dropsAs(incReserveVote.current()); obj[sfReferenceFeeUnits] = FEE_UNITS_DEPRECATED; } }); uint256 const txID = feeTx.getTransactionID(); JLOG(journal_.warn()) << "Vote: " << txID; Serializer s; feeTx.add(s); if (!initialPosition->addGiveItem( SHAMapNodeType::tnTRANSACTION_NM, make_shamapitem(txID, s.slice()))) { JLOG(journal_.warn()) << "Ledger already had fee change"; } } } //------------------------------------------------------------------------------ std::unique_ptr make_FeeVote(FeeSetup const& setup, beast::Journal journal) { return std::make_unique(setup, journal); } } // namespace xrpl