mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 01:06:48 +00:00
428 lines
14 KiB
C++
428 lines
14 KiB
C++
#include <xrpl/tx/transactors/account/SignerListSet.h>
|
|
|
|
#include <xrpl/basics/Log.h>
|
|
#include <xrpl/beast/utility/Journal.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/core/ServiceRegistry.h>
|
|
#include <xrpl/ledger/ApplyView.h>
|
|
#include <xrpl/ledger/ReadView.h>
|
|
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
|
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
|
#include <xrpl/protocol/AccountID.h>
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/Keylet.h>
|
|
#include <xrpl/protocol/LedgerFormats.h>
|
|
#include <xrpl/protocol/SField.h>
|
|
#include <xrpl/protocol/STArray.h>
|
|
#include <xrpl/protocol/STLedgerEntry.h>
|
|
#include <xrpl/protocol/STObject.h>
|
|
#include <xrpl/protocol/STTx.h>
|
|
#include <xrpl/protocol/TER.h>
|
|
#include <xrpl/protocol/TxFlags.h>
|
|
#include <xrpl/protocol/XRPAmount.h>
|
|
#include <xrpl/tx/SignerEntries.h>
|
|
#include <xrpl/tx/Transactor.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace xrpl {
|
|
|
|
// We're prepared for there to be multiple signer lists in the future,
|
|
// but we don't need them yet. So for the time being we're manually
|
|
// setting the sfSignerListID to zero in all cases.
|
|
static std::uint32_t const DEFAULT_SIGNER_LIST_ID = 0;
|
|
|
|
std::tuple<NotTEC, std::uint32_t, std::vector<SignerEntries::SignerEntry>, SignerListSet::Operation>
|
|
SignerListSet::determineOperation(STTx const& tx, ApplyFlags flags, beast::Journal j)
|
|
{
|
|
// Check the quorum. A non-zero quorum means we're creating or replacing
|
|
// the list. A zero quorum means we're destroying the list.
|
|
auto const quorum = tx[sfSignerQuorum];
|
|
std::vector<SignerEntries::SignerEntry> sign;
|
|
Operation op = Operation::unknown;
|
|
|
|
bool const hasSignerEntries(tx.isFieldPresent(sfSignerEntries));
|
|
if ((quorum != 0u) && hasSignerEntries)
|
|
{
|
|
auto signers = SignerEntries::deserialize(tx, j, "transaction");
|
|
|
|
if (!signers)
|
|
return std::make_tuple(signers.error(), quorum, sign, op);
|
|
|
|
std::sort(signers->begin(), signers->end());
|
|
|
|
// Save deserialized list for later.
|
|
sign = std::move(*signers);
|
|
op = Operation::set;
|
|
}
|
|
else if ((quorum == 0) && !hasSignerEntries)
|
|
{
|
|
op = Operation::destroy;
|
|
}
|
|
|
|
return std::make_tuple(tesSUCCESS, quorum, sign, op);
|
|
}
|
|
|
|
std::uint32_t
|
|
SignerListSet::getFlagsMask(PreflightContext const& ctx)
|
|
{
|
|
// 0 means "Allow any flags"
|
|
return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
|
|
}
|
|
|
|
NotTEC
|
|
SignerListSet::preflight(PreflightContext const& ctx)
|
|
{
|
|
auto const result = determineOperation(ctx.tx, ctx.flags, ctx.j);
|
|
|
|
if (!isTesSuccess(std::get<0>(result)))
|
|
return std::get<0>(result);
|
|
|
|
if (std::get<3>(result) == Operation::unknown)
|
|
{
|
|
// Neither a set nor a destroy. Malformed.
|
|
JLOG(ctx.j.trace()) << "Malformed transaction: Invalid signer set list format.";
|
|
return temMALFORMED;
|
|
}
|
|
|
|
if (std::get<3>(result) == Operation::set)
|
|
{
|
|
// Validate our settings.
|
|
auto const account = ctx.tx.getAccountID(sfAccount);
|
|
NotTEC const ter = validateQuorumAndSignerEntries(
|
|
std::get<1>(result), std::get<2>(result), account, ctx.j, ctx.rules);
|
|
if (!isTesSuccess(ter))
|
|
{
|
|
return ter;
|
|
}
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
SignerListSet::doApply()
|
|
{
|
|
// Perform the operation preCompute() decided on.
|
|
switch (do_)
|
|
{
|
|
case Operation::set:
|
|
return replaceSignerList();
|
|
|
|
case Operation::destroy:
|
|
return destroySignerList();
|
|
|
|
default:
|
|
break;
|
|
}
|
|
// LCOV_EXCL_START
|
|
UNREACHABLE("xrpl::SignerListSet::doApply : invalid operation");
|
|
return temMALFORMED;
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
|
|
void
|
|
SignerListSet::preCompute()
|
|
{
|
|
// Get the quorum and operation info.
|
|
auto result = determineOperation(ctx_.tx, view().flags(), j_);
|
|
XRPL_ASSERT(
|
|
isTesSuccess(std::get<0>(result)),
|
|
"xrpl::SignerListSet::preCompute : result is tesSUCCESS");
|
|
XRPL_ASSERT(
|
|
std::get<3>(result) != Operation::unknown,
|
|
"xrpl::SignerListSet::preCompute : result is known operation");
|
|
|
|
quorum_ = std::get<1>(result);
|
|
signers_ = std::get<2>(result);
|
|
do_ = std::get<3>(result);
|
|
|
|
Transactor::preCompute();
|
|
}
|
|
|
|
// The return type is signed so it is compatible with the 3rd argument
|
|
// of adjustOwnerCount() (which must be signed).
|
|
static int
|
|
signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules)
|
|
{
|
|
// We always compute the full change in OwnerCount, taking into account:
|
|
// o The fact that we're adding/removing a SignerList and
|
|
// o Accounting for the number of entries in the list.
|
|
// We can get away with that because lists are not adjusted incrementally;
|
|
// we add or remove an entire list.
|
|
//
|
|
// The rule is:
|
|
// o Simply having a SignerList costs 2 OwnerCount units.
|
|
// o And each signer in the list costs 1 more OwnerCount unit.
|
|
// So, at a minimum, adding a SignerList with 1 entry costs 3 OwnerCount
|
|
// units. A SignerList with 8 entries would cost 10 OwnerCount units.
|
|
//
|
|
// The static_cast should always be safe since entryCount should always
|
|
// be in the range from 1 to 32.
|
|
// We've got a lot of room to grow.
|
|
XRPL_ASSERT(
|
|
entryCount >= STTx::minMultiSigners,
|
|
"xrpl::signerCountBasedOwnerCountDelta : minimum signers");
|
|
XRPL_ASSERT(
|
|
entryCount <= STTx::maxMultiSigners,
|
|
"xrpl::signerCountBasedOwnerCountDelta : maximum signers");
|
|
return 2 + static_cast<int>(entryCount);
|
|
}
|
|
|
|
static TER
|
|
removeSignersFromLedger(
|
|
ServiceRegistry& registry,
|
|
ApplyView& view,
|
|
Keylet const& accountKeylet,
|
|
Keylet const& ownerDirKeylet,
|
|
Keylet const& signerListKeylet,
|
|
beast::Journal j)
|
|
{
|
|
// We have to examine the current SignerList so we know how much to
|
|
// reduce the OwnerCount.
|
|
SLE::pointer const signers = view.peek(signerListKeylet);
|
|
|
|
// If the signer list doesn't exist we've already succeeded in deleting it.
|
|
if (!signers)
|
|
return tesSUCCESS;
|
|
|
|
// There are two different ways that the OwnerCount could be managed.
|
|
// If the lsfOneOwnerCount bit is set then remove just one owner count.
|
|
// Otherwise use the pre-MultiSignReserve amendment calculation.
|
|
int removeFromOwnerCount = -1;
|
|
if ((signers->getFlags() & lsfOneOwnerCount) == 0)
|
|
{
|
|
STArray const& actualList = signers->getFieldArray(sfSignerEntries);
|
|
removeFromOwnerCount =
|
|
signerCountBasedOwnerCountDelta(actualList.size(), view.rules()) * -1;
|
|
}
|
|
|
|
// Remove the node from the account directory.
|
|
auto const hint = (*signers)[sfOwnerNode];
|
|
|
|
if (!view.dirRemove(ownerDirKeylet, hint, signerListKeylet.key, false))
|
|
{
|
|
// LCOV_EXCL_START
|
|
JLOG(j.fatal()) << "Unable to delete SignerList from owner.";
|
|
return tefBAD_LEDGER;
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
|
|
adjustOwnerCount(
|
|
view, view.peek(accountKeylet), removeFromOwnerCount, registry.getJournal("View"));
|
|
|
|
view.erase(signers);
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
SignerListSet::removeFromLedger(
|
|
ServiceRegistry& registry,
|
|
ApplyView& view,
|
|
AccountID const& account,
|
|
beast::Journal j)
|
|
{
|
|
auto const accountKeylet = keylet::account(account);
|
|
auto const ownerDirKeylet = keylet::ownerDir(account);
|
|
auto const signerListKeylet = keylet::signerList(account);
|
|
|
|
return removeSignersFromLedger(
|
|
registry, view, accountKeylet, ownerDirKeylet, signerListKeylet, j);
|
|
}
|
|
|
|
NotTEC
|
|
SignerListSet::validateQuorumAndSignerEntries(
|
|
std::uint32_t quorum,
|
|
std::vector<SignerEntries::SignerEntry> const& signers,
|
|
AccountID const& account,
|
|
beast::Journal j,
|
|
Rules const& rules)
|
|
{
|
|
// Reject if there are too many or too few entries in the list.
|
|
{
|
|
std::size_t const signerCount = signers.size();
|
|
if (signerCount < STTx::minMultiSigners || signerCount > STTx::maxMultiSigners)
|
|
{
|
|
JLOG(j.trace()) << "Too many or too few signers in signer list.";
|
|
return temMALFORMED;
|
|
}
|
|
}
|
|
|
|
// Make sure there are no duplicate signers.
|
|
XRPL_ASSERT(
|
|
std::ranges::is_sorted(signers),
|
|
"xrpl::SignerListSet::validateQuorumAndSignerEntries : sorted "
|
|
"signers");
|
|
if (std::ranges::adjacent_find(signers) != signers.end())
|
|
{
|
|
JLOG(j.trace()) << "Duplicate signers in signer list";
|
|
return temBAD_SIGNER;
|
|
}
|
|
|
|
// Make sure no signers reference this account. Also make sure the
|
|
// quorum can be reached.
|
|
std::uint64_t allSignersWeight(0);
|
|
for (auto const& signer : signers)
|
|
{
|
|
std::uint32_t const weight = signer.weight;
|
|
if (weight <= 0)
|
|
{
|
|
JLOG(j.trace()) << "Every signer must have a positive weight.";
|
|
return temBAD_WEIGHT;
|
|
}
|
|
|
|
allSignersWeight += signer.weight;
|
|
|
|
if (signer.account == account)
|
|
{
|
|
JLOG(j.trace()) << "A signer may not self reference account.";
|
|
return temBAD_SIGNER;
|
|
}
|
|
// Don't verify that the signer accounts exist. Non-existent accounts
|
|
// may be phantom accounts (which are permitted).
|
|
}
|
|
if ((quorum <= 0) || (allSignersWeight < quorum))
|
|
{
|
|
JLOG(j.trace()) << "Quorum is unreachable";
|
|
return temBAD_QUORUM;
|
|
}
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
SignerListSet::replaceSignerList()
|
|
{
|
|
auto const accountKeylet = keylet::account(account_);
|
|
auto const ownerDirKeylet = keylet::ownerDir(account_);
|
|
auto const signerListKeylet = keylet::signerList(account_);
|
|
|
|
// This may be either a create or a replace. Preemptively remove any
|
|
// old signer list. May reduce the reserve, so this is done before
|
|
// checking the reserve.
|
|
if (TER const ter = removeSignersFromLedger(
|
|
ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_))
|
|
return ter;
|
|
|
|
auto const sle = view().peek(accountKeylet);
|
|
if (!sle)
|
|
return tefINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
// Compute new reserve. Verify the account has funds to meet the reserve.
|
|
std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]};
|
|
|
|
constexpr int addedOwnerCount = 1;
|
|
std::uint32_t const flags{lsfOneOwnerCount};
|
|
|
|
XRPAmount const newReserve{view().fees().accountReserve(oldOwnerCount + addedOwnerCount)};
|
|
|
|
// We check the reserve against the starting balance because we want to
|
|
// allow dipping into the reserve to pay fees. This behavior is consistent
|
|
// with TicketCreate.
|
|
if (preFeeBalance_ < newReserve)
|
|
return tecINSUFFICIENT_RESERVE;
|
|
|
|
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
|
|
auto signerList = std::make_shared<SLE>(signerListKeylet);
|
|
view().insert(signerList);
|
|
writeSignersToSLE(signerList, flags);
|
|
|
|
auto viewJ = ctx_.registry.get().getJournal("View");
|
|
// Add the signer list to the account's directory.
|
|
auto const page =
|
|
ctx_.view().dirInsert(ownerDirKeylet, signerListKeylet, describeOwnerDir(account_));
|
|
|
|
JLOG(j_.trace()) << "Create signer list for account " << toBase58(account_) << ": "
|
|
<< (page ? "success" : "failure");
|
|
|
|
if (!page)
|
|
return tecDIR_FULL; // LCOV_EXCL_LINE
|
|
|
|
signerList->setFieldU64(sfOwnerNode, *page);
|
|
|
|
// If we succeeded, the new entry counts against the
|
|
// creator's reserve.
|
|
adjustOwnerCount(view(), sle, addedOwnerCount, viewJ);
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
SignerListSet::destroySignerList()
|
|
{
|
|
auto const accountKeylet = keylet::account(account_);
|
|
// Destroying the signer list is only allowed if either the master key
|
|
// is enabled or there is a regular key.
|
|
SLE::pointer const ledgerEntry = view().peek(accountKeylet);
|
|
if (!ledgerEntry)
|
|
return tefINTERNAL; // LCOV_EXCL_LINE
|
|
|
|
if ((ledgerEntry->isFlag(lsfDisableMaster)) && (!ledgerEntry->isFieldPresent(sfRegularKey)))
|
|
return tecNO_ALTERNATIVE_KEY;
|
|
|
|
auto const ownerDirKeylet = keylet::ownerDir(account_);
|
|
auto const signerListKeylet = keylet::signerList(account_);
|
|
return removeSignersFromLedger(
|
|
ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_);
|
|
}
|
|
|
|
void
|
|
SignerListSet::writeSignersToSLE(SLE::pointer const& ledgerEntry, std::uint32_t flags) const
|
|
{
|
|
// Assign the quorum, default SignerListID, and flags.
|
|
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
|
{
|
|
ledgerEntry->setAccountID(sfOwner, account_);
|
|
}
|
|
ledgerEntry->setFieldU32(sfSignerQuorum, quorum_);
|
|
ledgerEntry->setFieldU32(sfSignerListID, DEFAULT_SIGNER_LIST_ID);
|
|
if (flags != 0u) // Only set flags if they are non-default (default is zero).
|
|
ledgerEntry->setFieldU32(sfFlags, flags);
|
|
|
|
// Create the SignerListArray one SignerEntry at a time.
|
|
STArray toLedger(signers_.size());
|
|
for (auto const& entry : signers_)
|
|
{
|
|
toLedger.push_back(STObject::makeInnerObject(sfSignerEntry));
|
|
STObject& obj = toLedger.back();
|
|
obj.reserve(2);
|
|
obj[sfAccount] = entry.account;
|
|
obj[sfSignerWeight] = entry.weight;
|
|
|
|
// This is a defensive check to make absolutely sure we will never write
|
|
// a tag into the ledger.
|
|
if (entry.tag)
|
|
obj.setFieldH256(sfWalletLocator, *(entry.tag));
|
|
}
|
|
|
|
// Assign the SignerEntries.
|
|
ledgerEntry->setFieldArray(sfSignerEntries, toLedger);
|
|
}
|
|
|
|
void
|
|
SignerListSet::visitInvariantEntry(
|
|
bool,
|
|
std::shared_ptr<SLE const> const&,
|
|
std::shared_ptr<SLE const> const&)
|
|
{
|
|
}
|
|
|
|
bool
|
|
SignerListSet::finalizeInvariants(
|
|
STTx const&,
|
|
TER,
|
|
XRPAmount,
|
|
ReadView const&,
|
|
beast::Journal const&)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
} // namespace xrpl
|