mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
SignerListSet txn and InnerObjectFormats (RIPD-182):
Add support for the SignerListSet transaction as a step toward multi-sign support. As part of the SignerListSet implementation, add InnerObjectFormat templates (similar to TxFormats and LedgerFormats) and enforce them in STObject, STArray, and STParsedJSON.
This commit is contained in:
committed by
Vinnie Falco
parent
92799187ed
commit
64ebd64d2b
363
src/ripple/app/tx/impl/SetSignerList.cpp
Normal file
363
src/ripple/app/tx/impl/SetSignerList.cpp
Normal file
@@ -0,0 +1,363 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2014 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
#include <ripple/app/tx/impl/SignerEntries.h>
|
||||
#include <ripple/protocol/STObject.h>
|
||||
#include <ripple/protocol/STArray.h>
|
||||
#include <ripple/protocol/STTx.h>
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/**
|
||||
See the README.md for an overview of the SetSignerList transaction that
|
||||
this class implements.
|
||||
*/
|
||||
class SetSignerList final
|
||||
: public Transactor
|
||||
{
|
||||
private:
|
||||
// Values determined during preCheck for use later.
|
||||
enum Operation {unknown, set, destroy};
|
||||
Operation do_ {unknown};
|
||||
std::uint32_t quorum_ {0};
|
||||
std::vector<SignerEntries::SignerEntry> signers_;
|
||||
|
||||
public:
|
||||
SetSignerList (
|
||||
STTx const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
: Transactor (
|
||||
txn,
|
||||
params,
|
||||
engine,
|
||||
deprecatedLogs().journal("SetSignerList"))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Applies the transaction if it is well formed and the ledger state permits.
|
||||
*/
|
||||
TER doApply () override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
Check anything that can be checked without the ledger.
|
||||
*/
|
||||
TER preCheck () override;
|
||||
|
||||
private:
|
||||
// signers are not const because method (intentionally) sorts vector.
|
||||
TER validateQuorumAndSignerEntries (
|
||||
std::uint32_t quorum,
|
||||
std::vector<SignerEntries::SignerEntry>& signers) const;
|
||||
|
||||
// Methods called by doApply()
|
||||
TER replaceSignerList (uint256 const& index);
|
||||
TER destroySignerList (uint256 const& index);
|
||||
|
||||
void writeSignersToLedger (SLE::pointer ledgerEntry);
|
||||
|
||||
static std::size_t ownerCountDelta (std::size_t entryCount);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
SetSignerList::doApply ()
|
||||
{
|
||||
assert (mTxnAccount);
|
||||
|
||||
// All operations require our ledger index. Compute that once and pass it
|
||||
// to our handlers.
|
||||
uint256 const index = getSignerListIndex (mTxnAccountID);
|
||||
|
||||
// Perform the operation preCheck() decided on.
|
||||
switch (do_)
|
||||
{
|
||||
case set:
|
||||
return replaceSignerList (index);
|
||||
|
||||
case destroy:
|
||||
return destroySignerList (index);
|
||||
|
||||
default:
|
||||
// Fall through intentionally
|
||||
break;
|
||||
}
|
||||
assert (false); // Should not be possible to get here.
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
TER
|
||||
SetSignerList::preCheck()
|
||||
{
|
||||
// We need the account ID later, so do this check first.
|
||||
preCheckAccount ();
|
||||
|
||||
// Check the quorum. A non-zero quorum means we're creating or replacing
|
||||
// the list. A zero quorum means we're destroying the list.
|
||||
quorum_ = (mTxn.getFieldU32 (sfSignerQuorum));
|
||||
|
||||
bool const hasSignerEntries (mTxn.isFieldPresent (sfSignerEntries));
|
||||
if (quorum_ && hasSignerEntries)
|
||||
{
|
||||
SignerEntries::Decoded signers (
|
||||
SignerEntries::deserialize (mTxn, m_journal, "transaction"));
|
||||
|
||||
if (signers.ter != tesSUCCESS)
|
||||
return signers.ter;
|
||||
|
||||
// Validate our settings.
|
||||
if (TER const ter =
|
||||
validateQuorumAndSignerEntries (quorum_, signers.vec))
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
|
||||
// Save deserialized and validated list for later.
|
||||
signers_ = std::move (signers.vec);
|
||||
do_ = set;
|
||||
}
|
||||
else if ((quorum_ == 0) && !hasSignerEntries)
|
||||
{
|
||||
do_ = destroy;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Neither a set nor a destroy. Malformed.
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
"Malformed transaction: Invalid signer set list format.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return preCheckSigningKey ();
|
||||
}
|
||||
|
||||
TER
|
||||
SetSignerList::validateQuorumAndSignerEntries (
|
||||
std::uint32_t quorum,
|
||||
std::vector<SignerEntries::SignerEntry>& signers) const
|
||||
{
|
||||
// Reject if there are too many or too few entries in the list.
|
||||
{
|
||||
std::size_t const signerCount = signers.size ();
|
||||
if ((signerCount < SignerEntries::minEntries)
|
||||
|| (signerCount > SignerEntries::maxEntries))
|
||||
{
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
"Too many or too few signers in signer list.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure there are no duplicate signers.
|
||||
std::sort (signers.begin (), signers.end ());
|
||||
if (std::adjacent_find (
|
||||
signers.begin (), signers.end ()) != signers.end ())
|
||||
{
|
||||
if (m_journal.trace) m_journal.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)
|
||||
{
|
||||
allSignersWeight += signer.weight;
|
||||
|
||||
if (signer.account == mTxnAccountID)
|
||||
{
|
||||
if (m_journal.trace) m_journal.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))
|
||||
{
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
"Quorum is unreachable";
|
||||
return temBAD_QUORUM;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SetSignerList::replaceSignerList (uint256 const& index)
|
||||
{
|
||||
// This may be either a create or a replace. Preemptively destroy any
|
||||
// old signer list. May reduce the reserve, so this is done before
|
||||
// checking the reserve.
|
||||
if (TER const ter = destroySignerList (index))
|
||||
return ter;
|
||||
|
||||
// Compute new reserve. Verify the account has funds to meet the reserve.
|
||||
std::size_t const oldOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount);
|
||||
std::size_t const addedOwnerCount = ownerCountDelta (signers_.size ());
|
||||
|
||||
std::uint64_t const newReserve =
|
||||
mEngine->getLedger ()->getReserve (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 CreateTicket.
|
||||
if (mPriorBalance < newReserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
|
||||
SLE::pointer signerList (
|
||||
mEngine->view().entryCreate (ltSIGNER_LIST, index));
|
||||
writeSignersToLedger (signerList);
|
||||
|
||||
// Lambda for call to dirAdd.
|
||||
auto describer = [&] (SLE::ref sle, bool dummy)
|
||||
{
|
||||
Ledger::ownerDirDescriber (sle, dummy, mTxnAccountID);
|
||||
};
|
||||
|
||||
// Add the signer list to the account's directory.
|
||||
std::uint64_t hint;
|
||||
TER result = mEngine->view ().dirAdd (
|
||||
hint, getOwnerDirIndex (mTxnAccountID), index, describer);
|
||||
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
"Create signer list for account " <<
|
||||
mTxnAccountID << ": " << transHuman (result);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
|
||||
signerList->setFieldU64 (sfOwnerNode, hint);
|
||||
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
mEngine->view ().increaseOwnerCount (mTxnAccount, addedOwnerCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TER
|
||||
SetSignerList::destroySignerList (uint256 const& index)
|
||||
{
|
||||
// See if there's an ltSIGNER_LIST for this account.
|
||||
SLE::pointer signerList =
|
||||
mEngine->view ().entryCache (ltSIGNER_LIST, index);
|
||||
|
||||
// If the signer list doesn't exist we've already succeeded in deleting it.
|
||||
if (!signerList)
|
||||
return tesSUCCESS;
|
||||
|
||||
// We have to examine the current SignerList so we know how much to
|
||||
// reduce the OwnerCount.
|
||||
std::size_t removeFromOwnerCount = 0;
|
||||
uint256 const signerListIndex = getSignerListIndex (mTxnAccountID);
|
||||
SLE::pointer accountSignersList =
|
||||
mEngine->view ().entryCache (ltSIGNER_LIST, signerListIndex);
|
||||
if (accountSignersList)
|
||||
{
|
||||
STArray const& actualList =
|
||||
accountSignersList->getFieldArray (sfSignerEntries);
|
||||
removeFromOwnerCount = ownerCountDelta (actualList.size ());
|
||||
}
|
||||
|
||||
// Remove the node from the account directory.
|
||||
std::uint64_t const hint (signerList->getFieldU64 (sfOwnerNode));
|
||||
|
||||
TER const result = mEngine->view ().dirDelete (false, hint,
|
||||
getOwnerDirIndex (mTxnAccountID), index, false, (hint == 0));
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
mEngine->view ().decreaseOwnerCount (mTxnAccount, removeFromOwnerCount);
|
||||
|
||||
mEngine->view ().entryDelete (signerList);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
SetSignerList::writeSignersToLedger (SLE::pointer ledgerEntry)
|
||||
{
|
||||
// Assign the quorum.
|
||||
ledgerEntry->setFieldU32 (sfSignerQuorum, quorum_);
|
||||
|
||||
// Create the SignerListArray one SignerEntry at a time.
|
||||
STArray toLedger (signers_.size ());
|
||||
for (auto const& entry : signers_)
|
||||
{
|
||||
toLedger.emplace_back(sfSignerEntry);
|
||||
STObject& obj = toLedger.back();
|
||||
obj.reserve (2);
|
||||
obj.setFieldAccount (sfAccount, entry.account);
|
||||
obj.setFieldU16 (sfSignerWeight, entry.weight);
|
||||
}
|
||||
|
||||
// Assign the SignerEntries.
|
||||
ledgerEntry->setFieldArray (sfSignerEntries, toLedger);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
SetSignerList::ownerCountDelta (std::size_t entryCount)
|
||||
{
|
||||
// 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 wiki (https://wiki.ripple.com/Multisign#Fees_2) currently says
|
||||
// (December 2014) the reserve should be
|
||||
// Reserve * (N + 1) / 2
|
||||
// That's not making sense to me right now, since I'm working in
|
||||
// integral OwnerCount units. If, say, N is 4 I don't know how to return
|
||||
// 4.5 units as an integer.
|
||||
//
|
||||
// So, just to get started, I'm saying that:
|
||||
// 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 2 entries costs 4 OwnerCount
|
||||
// units. A SignerList with 8 entries would cost 10 OwnerCount units.
|
||||
//
|
||||
// It's worth noting that once this reserve policy has gotten into the
|
||||
// wild it will be very difficult to change. So think hard about what
|
||||
// we want for the long term.
|
||||
return 2 + entryCount;
|
||||
}
|
||||
|
||||
TER
|
||||
transact_SetSignerList (
|
||||
STTx const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
{
|
||||
return SetSignerList (txn, params, engine).apply ();
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
Reference in New Issue
Block a user