Introduce the ExpandedSignerList amendment:

The amendment increases the maximum sign of an account's signer
list from 8 to 32.

Like all new features, the associated amendment is configured with
a default vote of "no" and server operators will have to vote for
it explicitly if they believe it is useful.
This commit is contained in:
Richard Holland
2022-04-11 10:21:56 +00:00
committed by manojsdoshi
parent 04bd5878f1
commit 01c37fed69
24 changed files with 506 additions and 215 deletions

View File

@@ -82,6 +82,7 @@ target_sources (xrpl_core PRIVATE
src/ripple/protocol/impl/PublicKey.cpp src/ripple/protocol/impl/PublicKey.cpp
src/ripple/protocol/impl/Quality.cpp src/ripple/protocol/impl/Quality.cpp
src/ripple/protocol/impl/Rate2.cpp src/ripple/protocol/impl/Rate2.cpp
src/ripple/protocol/impl/Rules.cpp
src/ripple/protocol/impl/SField.cpp src/ripple/protocol/impl/SField.cpp
src/ripple/protocol/impl/SOTemplate.cpp src/ripple/protocol/impl/SOTemplate.cpp
src/ripple/protocol/impl/STAccount.cpp src/ripple/protocol/impl/STAccount.cpp
@@ -209,6 +210,7 @@ install (
src/ripple/protocol/PublicKey.h src/ripple/protocol/PublicKey.h
src/ripple/protocol/Quality.h src/ripple/protocol/Quality.h
src/ripple/protocol/Rate.h src/ripple/protocol/Rate.h
src/ripple/protocol/Rules.h
src/ripple/protocol/SField.h src/ripple/protocol/SField.h
src/ripple/protocol/SOTemplate.h src/ripple/protocol/SOTemplate.h
src/ripple/protocol/STAccount.h src/ripple/protocol/STAccount.h

View File

@@ -632,7 +632,7 @@ RCLConsensus::Adaptor::doAccept(
auto const lastVal = ledgerMaster_.getValidatedLedger(); auto const lastVal = ledgerMaster_.getValidatedLedger();
std::optional<Rules> rules; std::optional<Rules> rules;
if (lastVal) if (lastVal)
rules.emplace(*lastVal, app_.config().features); rules = makeRulesGivenLedger(*lastVal, app_.config().features);
else else
rules.emplace(app_.config().features); rules.emplace(app_.config().features);
app_.openLedger().accept( app_.openLedger().accept(

View File

@@ -626,7 +626,7 @@ Ledger::setup(Config const& config)
try try
{ {
rules_ = Rules(*this, config.features); rules_ = makeRulesGivenLedger(*this, config.features);
} }
catch (SHAMapMissingNode const&) catch (SHAMapMissingNode const&)
{ {

View File

@@ -1749,7 +1749,7 @@ NetworkOPsImp::switchLastClosedLedger(
auto const lastVal = app_.getLedgerMaster().getValidatedLedger(); auto const lastVal = app_.getLedgerMaster().getValidatedLedger();
std::optional<Rules> rules; std::optional<Rules> rules;
if (lastVal) if (lastVal)
rules.emplace(*lastVal, app_.config().features); rules = makeRulesGivenLedger(*lastVal, app_.config().features);
else else
rules.emplace(app_.config().features); rules.emplace(app_.config().features);
app_.openLedger().accept( app_.openLedger().accept(

View File

@@ -82,6 +82,7 @@ SetSignerList::preflight(PreflightContext const& ctx)
return ret; return ret;
auto const result = determineOperation(ctx.tx, ctx.flags, ctx.j); auto const result = determineOperation(ctx.tx, ctx.flags, ctx.j);
if (std::get<0>(result) != tesSUCCESS) if (std::get<0>(result) != tesSUCCESS)
return std::get<0>(result); return std::get<0>(result);
@@ -98,7 +99,11 @@ SetSignerList::preflight(PreflightContext const& ctx)
// Validate our settings. // Validate our settings.
auto const account = ctx.tx.getAccountID(sfAccount); auto const account = ctx.tx.getAccountID(sfAccount);
NotTEC const ter = validateQuorumAndSignerEntries( NotTEC const ter = validateQuorumAndSignerEntries(
std::get<1>(result), std::get<2>(result), account, ctx.j); std::get<1>(result),
std::get<2>(result),
account,
ctx.j,
ctx.rules);
if (ter != tesSUCCESS) if (ter != tesSUCCESS)
{ {
return ter; return ter;
@@ -149,7 +154,7 @@ SetSignerList::preCompute()
// is valid until the featureMultiSignReserve amendment passes. Once it // is valid until the featureMultiSignReserve amendment passes. Once it
// passes then just 1 OwnerCount is associated with a SignerList. // passes then just 1 OwnerCount is associated with a SignerList.
static int static int
signerCountBasedOwnerCountDelta(std::size_t entryCount) signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules)
{ {
// We always compute the full change in OwnerCount, taking into account: // We always compute the full change in OwnerCount, taking into account:
// o The fact that we're adding/removing a SignerList and // o The fact that we're adding/removing a SignerList and
@@ -164,9 +169,10 @@ signerCountBasedOwnerCountDelta(std::size_t entryCount)
// units. A SignerList with 8 entries would cost 10 OwnerCount units. // units. A SignerList with 8 entries would cost 10 OwnerCount units.
// //
// The static_cast should always be safe since entryCount should always // The static_cast should always be safe since entryCount should always
// be in the range from 1 to 8. We've got a lot of room to grow. // be in the range from 1 to 8 (or 32 if ExpandedSignerList is enabled).
// We've got a lot of room to grow.
assert(entryCount >= STTx::minMultiSigners); assert(entryCount >= STTx::minMultiSigners);
assert(entryCount <= STTx::maxMultiSigners); assert(entryCount <= STTx::maxMultiSigners(&rules));
return 2 + static_cast<int>(entryCount); return 2 + static_cast<int>(entryCount);
} }
@@ -195,7 +201,8 @@ removeSignersFromLedger(
{ {
STArray const& actualList = signers->getFieldArray(sfSignerEntries); STArray const& actualList = signers->getFieldArray(sfSignerEntries);
removeFromOwnerCount = removeFromOwnerCount =
signerCountBasedOwnerCountDelta(actualList.size()) * -1; signerCountBasedOwnerCountDelta(actualList.size(), view.rules()) *
-1;
} }
// Remove the node from the account directory. // Remove the node from the account directory.
@@ -238,13 +245,14 @@ SetSignerList::validateQuorumAndSignerEntries(
std::uint32_t quorum, std::uint32_t quorum,
std::vector<SignerEntries::SignerEntry> const& signers, std::vector<SignerEntries::SignerEntry> const& signers,
AccountID const& account, AccountID const& account,
beast::Journal j) beast::Journal j,
Rules const& rules)
{ {
// Reject if there are too many or too few entries in the list. // Reject if there are too many or too few entries in the list.
{ {
std::size_t const signerCount = signers.size(); std::size_t const signerCount = signers.size();
if ((signerCount < STTx::minMultiSigners) || if ((signerCount < STTx::minMultiSigners) ||
(signerCount > STTx::maxMultiSigners)) (signerCount > STTx::maxMultiSigners(&rules)))
{ {
JLOG(j.trace()) << "Too many or too few signers in signer list."; JLOG(j.trace()) << "Too many or too few signers in signer list.";
return temMALFORMED; return temMALFORMED;
@@ -259,6 +267,9 @@ SetSignerList::validateQuorumAndSignerEntries(
return temBAD_SIGNER; return temBAD_SIGNER;
} }
// Is the ExpandedSignerList amendment active?
bool const expandedSignerList = rules.enabled(featureExpandedSignerList);
// Make sure no signers reference this account. Also make sure the // Make sure no signers reference this account. Also make sure the
// quorum can be reached. // quorum can be reached.
std::uint64_t allSignersWeight(0); std::uint64_t allSignersWeight(0);
@@ -279,6 +290,14 @@ SetSignerList::validateQuorumAndSignerEntries(
return temBAD_SIGNER; return temBAD_SIGNER;
} }
if (signer.tag && !expandedSignerList)
{
JLOG(j.trace()) << "Malformed transaction: sfWalletLocator "
"specified in SignerEntry "
<< "but featureExpandedSignerList is not enabled.";
return temMALFORMED;
}
// Don't verify that the signer accounts exist. Non-existent accounts // Don't verify that the signer accounts exist. Non-existent accounts
// may be phantom accounts (which are permitted). // may be phantom accounts (which are permitted).
} }
@@ -321,7 +340,8 @@ SetSignerList::replaceSignerList()
std::uint32_t flags{lsfOneOwnerCount}; std::uint32_t flags{lsfOneOwnerCount};
if (!ctx_.view().rules().enabled(featureMultiSignReserve)) if (!ctx_.view().rules().enabled(featureMultiSignReserve))
{ {
addedOwnerCount = signerCountBasedOwnerCountDelta(signers_.size()); addedOwnerCount = signerCountBasedOwnerCountDelta(
signers_.size(), ctx_.view().rules());
flags = 0; flags = 0;
} }
@@ -389,6 +409,9 @@ SetSignerList::writeSignersToSLE(
if (flags) // Only set flags if they are non-default (default is zero). if (flags) // Only set flags if they are non-default (default is zero).
ledgerEntry->setFieldU32(sfFlags, flags); ledgerEntry->setFieldU32(sfFlags, flags);
bool const expandedSignerList =
ctx_.view().rules().enabled(featureExpandedSignerList);
// Create the SignerListArray one SignerEntry at a time. // Create the SignerListArray one SignerEntry at a time.
STArray toLedger(signers_.size()); STArray toLedger(signers_.size());
for (auto const& entry : signers_) for (auto const& entry : signers_)
@@ -398,6 +421,11 @@ SetSignerList::writeSignersToSLE(
obj.reserve(2); obj.reserve(2);
obj.setAccountID(sfAccount, entry.account); obj.setAccountID(sfAccount, entry.account);
obj.setFieldU16(sfSignerWeight, entry.weight); obj.setFieldU16(sfSignerWeight, entry.weight);
// This is a defensive check to make absolutely sure we will never write
// a tag into the ledger while featureExpandedSignerList is not enabled
if (expandedSignerList && entry.tag)
obj.setFieldH256(sfWalletLocator, *(entry.tag));
} }
// Assign the SignerEntries. // Assign the SignerEntries.

View File

@@ -25,6 +25,7 @@
#include <ripple/app/tx/impl/Transactor.h> #include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/Rules.h>
#include <ripple/protocol/STArray.h> #include <ripple/protocol/STArray.h>
#include <ripple/protocol/STObject.h> #include <ripple/protocol/STObject.h>
#include <ripple/protocol/STTx.h> #include <ripple/protocol/STTx.h>
@@ -83,7 +84,8 @@ private:
std::uint32_t quorum, std::uint32_t quorum,
std::vector<SignerEntries::SignerEntry> const& signers, std::vector<SignerEntries::SignerEntry> const& signers,
AccountID const& account, AccountID const& account,
beast::Journal j); beast::Journal j,
Rules const&);
TER TER
replaceSignerList(); replaceSignerList();

View File

@@ -22,6 +22,7 @@
#include <ripple/protocol/STArray.h> #include <ripple/protocol/STArray.h>
#include <ripple/protocol/STObject.h> #include <ripple/protocol/STObject.h>
#include <cstdint> #include <cstdint>
#include <optional>
namespace ripple { namespace ripple {
@@ -41,7 +42,7 @@ SignerEntries::deserialize(
} }
std::vector<SignerEntry> accountVec; std::vector<SignerEntry> accountVec;
accountVec.reserve(STTx::maxMultiSigners); accountVec.reserve(STTx::maxMultiSigners());
STArray const& sEntries(obj.getFieldArray(sfSignerEntries)); STArray const& sEntries(obj.getFieldArray(sfSignerEntries));
for (STObject const& sEntry : sEntries) for (STObject const& sEntry : sEntries)
@@ -57,7 +58,9 @@ SignerEntries::deserialize(
// Extract SignerEntry fields. // Extract SignerEntry fields.
AccountID const account = sEntry.getAccountID(sfAccount); AccountID const account = sEntry.getAccountID(sfAccount);
std::uint16_t const weight = sEntry.getFieldU16(sfSignerWeight); std::uint16_t const weight = sEntry.getFieldU16(sfSignerWeight);
accountVec.emplace_back(account, weight); std::optional<uint256> const tag = sEntry.at(~sfWalletLocator);
accountVec.emplace_back(account, weight, tag);
} }
return accountVec; return accountVec;
} }

View File

@@ -23,9 +23,11 @@
#include <ripple/app/tx/impl/Transactor.h> // NotTEC #include <ripple/app/tx/impl/Transactor.h> // NotTEC
#include <ripple/basics/Expected.h> // #include <ripple/basics/Expected.h> //
#include <ripple/beast/utility/Journal.h> // beast::Journal #include <ripple/beast/utility/Journal.h> // beast::Journal
#include <ripple/protocol/Rules.h> // Rules
#include <ripple/protocol/STTx.h> // STTx::maxMultiSigners #include <ripple/protocol/STTx.h> // STTx::maxMultiSigners
#include <ripple/protocol/TER.h> // temMALFORMED #include <ripple/protocol/TER.h> // temMALFORMED
#include <ripple/protocol/UintTypes.h> // AccountID #include <ripple/protocol/UintTypes.h> // AccountID
#include <optional>
namespace ripple { namespace ripple {
@@ -42,9 +44,13 @@ public:
{ {
AccountID account; AccountID account;
std::uint16_t weight; std::uint16_t weight;
std::optional<uint256> tag;
SignerEntry(AccountID const& inAccount, std::uint16_t inWeight) SignerEntry(
: account(inAccount), weight(inWeight) AccountID const& inAccount,
std::uint16_t inWeight,
std::optional<uint256> inTag)
: account(inAccount), weight(inWeight), tag(inTag)
{ {
} }

View File

@@ -54,7 +54,7 @@ checkValidity(
? STTx::RequireFullyCanonicalSig::yes ? STTx::RequireFullyCanonicalSig::yes
: STTx::RequireFullyCanonicalSig::no; : STTx::RequireFullyCanonicalSig::no;
auto const sigVerify = tx.checkSign(requireCanonicalSig); auto const sigVerify = tx.checkSign(requireCanonicalSig, rules);
if (!sigVerify) if (!sigVerify)
{ {
router.setFlags(id, SF_SIGBAD); router.setFlags(id, SF_SIGBAD);

View File

@@ -29,6 +29,7 @@
#include <ripple/ledger/detail/ReadViewFwdRange.h> #include <ripple/ledger/detail/ReadViewFwdRange.h>
#include <ripple/protocol/Indexes.h> #include <ripple/protocol/Indexes.h>
#include <ripple/protocol/Protocol.h> #include <ripple/protocol/Protocol.h>
#include <ripple/protocol/Rules.h>
#include <ripple/protocol/STAmount.h> #include <ripple/protocol/STAmount.h>
#include <ripple/protocol/STLedgerEntry.h> #include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/STTx.h> #include <ripple/protocol/STTx.h>
@@ -125,64 +126,6 @@ struct LedgerInfo
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
class DigestAwareReadView;
/** Rules controlling protocol behavior. */
class Rules
{
private:
class Impl;
std::shared_ptr<Impl const> impl_;
public:
Rules(Rules const&) = default;
Rules&
operator=(Rules const&) = default;
Rules() = delete;
/** Construct an empty rule set.
These are the rules reflected by
the genesis ledger.
*/
explicit Rules(std::unordered_set<uint256, beast::uhash<>> const& presets);
/** Construct rules from a ledger.
The ledger contents are analyzed for rules
and amendments and extracted to the object.
*/
explicit Rules(
DigestAwareReadView const& ledger,
std::unordered_set<uint256, beast::uhash<>> const& presets);
/** Returns `true` if a feature is enabled. */
bool
enabled(uint256 const& id) const;
/** Returns `true` if these rules don't match the ledger. */
bool
changed(DigestAwareReadView const& ledger) const;
/** Returns `true` if two rule sets are identical.
@note This is for diagnostics. To determine if new
rules should be constructed, call changed() first instead.
*/
bool
operator==(Rules const&) const;
bool
operator!=(Rules const& other) const
{
return !(*this == other);
}
};
//------------------------------------------------------------------------------
/** A view into a ledger. /** A view into a ledger.
This interface provides read access to state This interface provides read access to state
@@ -423,6 +366,11 @@ getCloseAgree(LedgerInfo const& info)
void void
addRaw(LedgerInfo const&, Serializer&, bool includeHash = false); addRaw(LedgerInfo const&, Serializer&, bool includeHash = false);
Rules
makeRulesGivenLedger(
DigestAwareReadView const& ledger,
std::unordered_set<uint256, beast::uhash<>> const& presets);
} // namespace ripple } // namespace ripple
#include <ripple/ledger/detail/ReadViewFwdRange.ipp> #include <ripple/ledger/detail/ReadViewFwdRange.ipp>

View File

@@ -21,108 +21,6 @@
namespace ripple { namespace ripple {
class Rules::Impl
{
private:
std::unordered_set<uint256, hardened_hash<>> set_;
std::optional<uint256> digest_;
std::unordered_set<uint256, beast::uhash<>> const& presets_;
public:
explicit Impl(std::unordered_set<uint256, beast::uhash<>> const& presets)
: presets_(presets)
{
}
explicit Impl(
DigestAwareReadView const& ledger,
std::unordered_set<uint256, beast::uhash<>> const& presets)
: presets_(presets)
{
auto const k = keylet::amendments();
digest_ = ledger.digest(k.key);
if (!digest_)
return;
auto const sle = ledger.read(k);
if (!sle)
{
// LogicError() ?
return;
}
for (auto const& item : sle->getFieldV256(sfAmendments))
set_.insert(item);
}
bool
enabled(uint256 const& feature) const
{
if (presets_.count(feature) > 0)
return true;
return set_.count(feature) > 0;
}
bool
changed(DigestAwareReadView const& ledger) const
{
auto const digest = ledger.digest(keylet::amendments().key);
if (!digest && !digest_)
return false;
if (!digest || !digest_)
return true;
return *digest != *digest_;
}
bool
operator==(Impl const& other) const
{
if (!digest_ && !other.digest_)
return true;
if (!digest_ || !other.digest_)
return false;
return *digest_ == *other.digest_;
}
};
//------------------------------------------------------------------------------
Rules::Rules(
DigestAwareReadView const& ledger,
std::unordered_set<uint256, beast::uhash<>> const& presets)
: impl_(std::make_shared<Impl>(ledger, presets))
{
}
Rules::Rules(std::unordered_set<uint256, beast::uhash<>> const& presets)
: impl_(std::make_shared<Impl>(presets))
{
}
bool
Rules::enabled(uint256 const& id) const
{
assert(impl_);
return impl_->enabled(id);
}
bool
Rules::changed(DigestAwareReadView const& ledger) const
{
assert(impl_);
return impl_->changed(ledger);
}
bool
Rules::operator==(Rules const& other) const
{
assert(impl_ && other.impl_);
if (impl_.get() == other.impl_.get())
return true;
return *impl_ == *other.impl_;
}
//------------------------------------------------------------------------------
ReadView::sles_type::sles_type(ReadView const& view) : ReadViewFwdRange(view) ReadView::sles_type::sles_type(ReadView const& view) : ReadViewFwdRange(view)
{ {
} }
@@ -167,4 +65,20 @@ ReadView::txs_type::end() const -> iterator
return iterator(view_, view_->txsEnd()); return iterator(view_, view_->txsEnd());
} }
Rules
makeRulesGivenLedger(
DigestAwareReadView const& ledger,
std::unordered_set<uint256, beast::uhash<>> const& presets)
{
Keylet const k = keylet::amendments();
std::optional digest = ledger.digest(k.key);
if (digest)
{
auto const sle = ledger.read(k);
if (sle)
return Rules(presets, digest, sle->getFieldV256(sfAmendments));
}
return Rules(presets);
}
} // namespace ripple } // namespace ripple

View File

@@ -373,6 +373,12 @@ message RootIndex
bytes value = 1; bytes value = 1;
} }
message WalletLocator
{
// 32 bytes
bytes value = 1;
}
// *** Messages wrapping variable length byte arrays *** // *** Messages wrapping variable length byte arrays ***
@@ -586,6 +592,8 @@ message SignerEntry
Account account = 1; Account account = 1;
SignerWeight signer_weight = 2; SignerWeight signer_weight = 2;
WalletLocator wallet_locator = 3;
} }
// Next field: 3 // Next field: 3

View File

@@ -334,6 +334,7 @@ extern uint256 const fixSTAmountCanonicalize;
extern uint256 const fixRmSmallIncreasedQOffers; extern uint256 const fixRmSmallIncreasedQOffers;
extern uint256 const featureCheckCashMakesTrustLine; extern uint256 const featureCheckCashMakesTrustLine;
extern uint256 const featureNonFungibleTokensV1; extern uint256 const featureNonFungibleTokensV1;
extern uint256 const featureExpandedSignerList;
} // namespace ripple } // namespace ripple

View File

@@ -0,0 +1,86 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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.
*/
//==============================================================================
#ifndef RIPPLE_LEDGER_RULES_H_INCLUDED
#define RIPPLE_LEDGER_RULES_H_INCLUDED
#include <ripple/basics/base_uint.h>
#include <ripple/beast/hash/uhash.h>
#include <ripple/protocol/STVector256.h>
#include <unordered_set>
namespace ripple {
class DigestAwareReadView;
/** Rules controlling protocol behavior. */
class Rules
{
private:
class Impl;
// Carrying impl by shared_ptr makes Rules comparatively cheap to pass
// by value.
std::shared_ptr<Impl const> impl_;
public:
Rules(Rules const&) = default;
Rules&
operator=(Rules const&) = default;
Rules() = delete;
/** Construct an empty rule set.
These are the rules reflected by
the genesis ledger.
*/
explicit Rules(std::unordered_set<uint256, beast::uhash<>> const& presets);
private:
// Allow a friend function to construct Rules.
friend Rules
makeRulesGivenLedger(
DigestAwareReadView const& ledger,
std::unordered_set<uint256, beast::uhash<>> const& presets);
Rules(
std::unordered_set<uint256, beast::uhash<>> const& presets,
std::optional<uint256> const& digest,
STVector256 const& amendments);
public:
/** Returns `true` if a feature is enabled. */
bool
enabled(uint256 const& feature) const;
/** Returns `true` if two rule sets are identical.
@note This is for diagnostics.
*/
bool
operator==(Rules const&) const;
bool
operator!=(Rules const& other) const;
};
} // namespace ripple
#endif

View File

@@ -21,7 +21,9 @@
#define RIPPLE_PROTOCOL_STTX_H_INCLUDED #define RIPPLE_PROTOCOL_STTX_H_INCLUDED
#include <ripple/basics/Expected.h> #include <ripple/basics/Expected.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/PublicKey.h> #include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/Rules.h>
#include <ripple/protocol/STObject.h> #include <ripple/protocol/STObject.h>
#include <ripple/protocol/SecretKey.h> #include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/SeqProxy.h> #include <ripple/protocol/SeqProxy.h>
@@ -47,7 +49,16 @@ class STTx final : public STObject, public CountedObject<STTx>
public: public:
static std::size_t const minMultiSigners = 1; static std::size_t const minMultiSigners = 1;
static std::size_t const maxMultiSigners = 8;
// if rules are not supplied then the largest possible value is returned
static std::size_t
maxMultiSigners(Rules const* rules = 0)
{
if (rules && !rules->enabled(featureExpandedSignerList))
return 8;
return 32;
}
STTx() = delete; STTx() = delete;
STTx(STTx const& other) = default; STTx(STTx const& other) = default;
@@ -108,7 +119,8 @@ public:
*/ */
enum class RequireFullyCanonicalSig : bool { no, yes }; enum class RequireFullyCanonicalSig : bool { no, yes };
Expected<void, std::string> Expected<void, std::string>
checkSign(RequireFullyCanonicalSig requireCanonicalSig) const; checkSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules)
const;
// SQL Functions with metadata. // SQL Functions with metadata.
static std::string const& static std::string const&
@@ -130,7 +142,9 @@ private:
checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const; checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const;
Expected<void, std::string> Expected<void, std::string>
checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const; checkMultiSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const;
STBase* STBase*
copy(std::size_t n, void* buf) const override; copy(std::size_t n, void* buf) const override;

View File

@@ -438,6 +438,7 @@ REGISTER_FIX (fixSTAmountCanonicalize, Supported::yes, DefaultVote::yes
REGISTER_FIX (fixRmSmallIncreasedQOffers, Supported::yes, DefaultVote::yes); REGISTER_FIX (fixRmSmallIncreasedQOffers, Supported::yes, DefaultVote::yes);
REGISTER_FEATURE(CheckCashMakesTrustLine, Supported::yes, DefaultVote::no); REGISTER_FEATURE(CheckCashMakesTrustLine, Supported::yes, DefaultVote::no);
REGISTER_FEATURE(NonFungibleTokensV1, Supported::yes, DefaultVote::no); REGISTER_FEATURE(NonFungibleTokensV1, Supported::yes, DefaultVote::no);
REGISTER_FEATURE(ExpandedSignerList, Supported::yes, DefaultVote::no);
// The following amendments have been active for at least two years. Their // The following amendments have been active for at least two years. Their
// pre-amendment code has been removed and the identifiers are deprecated. // pre-amendment code has been removed and the identifiers are deprecated.

View File

@@ -28,6 +28,7 @@ InnerObjectFormats::InnerObjectFormats()
{ {
{sfAccount, soeREQUIRED}, {sfAccount, soeREQUIRED},
{sfSignerWeight, soeREQUIRED}, {sfSignerWeight, soeREQUIRED},
{sfWalletLocator, soeOPTIONAL},
}); });
add(sfSigner.jsonName.c_str(), add(sfSigner.jsonName.c_str(),

View File

@@ -0,0 +1,103 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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 <ripple/protocol/Rules.h>
#include <ripple/protocol/Indexes.h>
namespace ripple {
class Rules::Impl
{
private:
std::unordered_set<uint256, hardened_hash<>> set_;
std::optional<uint256> digest_;
std::unordered_set<uint256, beast::uhash<>> const& presets_;
public:
explicit Impl(std::unordered_set<uint256, beast::uhash<>> const& presets)
: presets_(presets)
{
}
Impl(
std::unordered_set<uint256, beast::uhash<>> const& presets,
std::optional<uint256> const& digest,
STVector256 const& amendments)
: presets_(presets)
{
digest_ = digest;
set_.reserve(amendments.size());
set_.insert(amendments.begin(), amendments.end());
}
bool
enabled(uint256 const& feature) const
{
if (presets_.count(feature) > 0)
return true;
return set_.count(feature) > 0;
}
bool
operator==(Impl const& other) const
{
if (!digest_ && !other.digest_)
return true;
if (!digest_ || !other.digest_)
return false;
return *digest_ == *other.digest_;
}
};
Rules::Rules(std::unordered_set<uint256, beast::uhash<>> const& presets)
: impl_(std::make_shared<Impl>(presets))
{
}
Rules::Rules(
std::unordered_set<uint256, beast::uhash<>> const& presets,
std::optional<uint256> const& digest,
STVector256 const& amendments)
: impl_(std::make_shared<Impl>(presets, digest, amendments))
{
}
bool
Rules::enabled(uint256 const& feature) const
{
assert(impl_);
return impl_->enabled(feature);
}
bool
Rules::operator==(Rules const& other) const
{
assert(impl_ && other.impl_);
if (impl_.get() == other.impl_.get())
return true;
return *impl_ == *other.impl_;
}
bool
Rules::operator!=(Rules const& other) const
{
return !(*this == other);
}
} // namespace ripple

View File

@@ -206,7 +206,9 @@ STTx::sign(PublicKey const& publicKey, SecretKey const& secretKey)
} }
Expected<void, std::string> Expected<void, std::string>
STTx::checkSign(RequireFullyCanonicalSig requireCanonicalSig) const STTx::checkSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const
{ {
try try
{ {
@@ -214,7 +216,8 @@ STTx::checkSign(RequireFullyCanonicalSig requireCanonicalSig) const
// at the SigningPubKey. If it's empty we must be // at the SigningPubKey. If it's empty we must be
// multi-signing. Otherwise we're single-signing. // multi-signing. Otherwise we're single-signing.
Blob const& signingPubKey = getFieldVL(sfSigningPubKey); Blob const& signingPubKey = getFieldVL(sfSigningPubKey);
return signingPubKey.empty() ? checkMultiSign(requireCanonicalSig) return signingPubKey.empty()
? checkMultiSign(requireCanonicalSig, rules)
: checkSingleSign(requireCanonicalSig); : checkSingleSign(requireCanonicalSig);
} }
catch (std::exception const&) catch (std::exception const&)
@@ -327,7 +330,9 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const
} }
Expected<void, std::string> Expected<void, std::string>
STTx::checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const STTx::checkMultiSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const
{ {
// Make sure the MultiSigners are present. Otherwise they are not // Make sure the MultiSigners are present. Otherwise they are not
// attempting multi-signing and we just have a bad SigningPubKey. // attempting multi-signing and we just have a bad SigningPubKey.
@@ -342,7 +347,8 @@ STTx::checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const
STArray const& signers{getFieldArray(sfSigners)}; STArray const& signers{getFieldArray(sfSigners)};
// There are well known bounds that the number of signers must be within. // There are well known bounds that the number of signers must be within.
if (signers.size() < minMultiSigners || signers.size() > maxMultiSigners) if (signers.size() < minMultiSigners ||
signers.size() > maxMultiSigners(&rules))
return Unexpected("Invalid Signers array size."); return Unexpected("Invalid Signers array size.");
// We can ease the computational load inside the loop a bit by // We can ease the computational load inside the loop a bit by

View File

@@ -746,6 +746,14 @@ populateSignerListID(T& to, STObject const& from)
[&to]() { return to.mutable_signer_list_id(); }, from, sfSignerListID); [&to]() { return to.mutable_signer_list_id(); }, from, sfSignerListID);
} }
template <class T>
void
populateWalletLocator(T& to, STObject const& from)
{
populateProtoPrimitive(
[&to]() { return to.mutable_wallet_locator(); }, from, sfWalletLocator);
}
template <class T> template <class T>
void void
populateTicketSequence(T& to, STObject const& from) populateTicketSequence(T& to, STObject const& from)
@@ -1012,6 +1020,7 @@ populateSignerEntries(T& to, STObject const& from)
[](auto& innerObj, auto& innerProto) { [](auto& innerObj, auto& innerProto) {
populateAccount(innerProto, innerObj); populateAccount(innerProto, innerObj);
populateSignerWeight(innerProto, innerObj); populateSignerWeight(innerProto, innerObj);
populateWalletLocator(innerProto, innerObj);
}, },
from, from,
sfSignerEntries, sfSignerEntries,

View File

@@ -34,6 +34,30 @@ class MultiSign_test : public beast::unit_test::suite
jtx::Account const phase{"phase", KeyType::ed25519}; jtx::Account const phase{"phase", KeyType::ed25519};
jtx::Account const shade{"shade", KeyType::secp256k1}; jtx::Account const shade{"shade", KeyType::secp256k1};
jtx::Account const spook{"spook", KeyType::ed25519}; jtx::Account const spook{"spook", KeyType::ed25519};
jtx::Account const acc10{"acc10", KeyType::ed25519};
jtx::Account const acc11{"acc11", KeyType::ed25519};
jtx::Account const acc12{"acc12", KeyType::ed25519};
jtx::Account const acc13{"acc13", KeyType::ed25519};
jtx::Account const acc14{"acc14", KeyType::ed25519};
jtx::Account const acc15{"acc15", KeyType::ed25519};
jtx::Account const acc16{"acc16", KeyType::ed25519};
jtx::Account const acc17{"acc17", KeyType::ed25519};
jtx::Account const acc18{"acc18", KeyType::ed25519};
jtx::Account const acc19{"acc19", KeyType::ed25519};
jtx::Account const acc20{"acc20", KeyType::ed25519};
jtx::Account const acc21{"acc21", KeyType::ed25519};
jtx::Account const acc22{"acc22", KeyType::ed25519};
jtx::Account const acc23{"acc23", KeyType::ed25519};
jtx::Account const acc24{"acc24", KeyType::ed25519};
jtx::Account const acc25{"acc25", KeyType::ed25519};
jtx::Account const acc26{"acc26", KeyType::ed25519};
jtx::Account const acc27{"acc27", KeyType::ed25519};
jtx::Account const acc28{"acc28", KeyType::ed25519};
jtx::Account const acc29{"acc29", KeyType::ed25519};
jtx::Account const acc30{"acc30", KeyType::ed25519};
jtx::Account const acc31{"acc31", KeyType::ed25519};
jtx::Account const acc32{"acc32", KeyType::ed25519};
jtx::Account const acc33{"acc33", KeyType::ed25519};
public: public:
void void
@@ -159,22 +183,30 @@ public:
{spook, 1}}), {spook, 1}}),
ter(temBAD_QUORUM)); ter(temBAD_QUORUM));
// Make a signer list that's too big. Should fail. // clang-format off
// Make a signer list that's too big. Should fail. (Even with
// ExpandedSignerList)
Account const spare("spare", KeyType::secp256k1); Account const spare("spare", KeyType::secp256k1);
env(signers( env(signers(
alice, alice,
1, 1,
{{bogie, 1}, features[featureExpandedSignerList]
{demon, 1}, ? std::vector<signer>{{bogie, 1}, {demon, 1}, {ghost, 1},
{ghost, 1}, {haunt, 1}, {jinni, 1}, {phase, 1},
{haunt, 1}, {shade, 1}, {spook, 1}, {spare, 1},
{jinni, 1}, {acc10, 1}, {acc11, 1}, {acc12, 1},
{phase, 1}, {acc13, 1}, {acc14, 1}, {acc15, 1},
{shade, 1}, {acc16, 1}, {acc17, 1}, {acc18, 1},
{spook, 1}, {acc19, 1}, {acc20, 1}, {acc21, 1},
{spare, 1}}), {acc22, 1}, {acc23, 1}, {acc24, 1},
{acc25, 1}, {acc26, 1}, {acc27, 1},
{acc28, 1}, {acc29, 1}, {acc30, 1},
{acc31, 1}, {acc32, 1}, {acc33, 1}}
: std::vector<signer>{{bogie, 1}, {demon, 1}, {ghost, 1},
{haunt, 1}, {jinni, 1}, {phase, 1},
{shade, 1}, {spook, 1}, {spare, 1}}),
ter(temMALFORMED)); ter(temMALFORMED));
// clang-format on
env.close(); env.close();
env.require(owners(alice, 0)); env.require(owners(alice, 0));
} }
@@ -1149,11 +1181,47 @@ public:
"fails local checks: Invalid Signers array size."); "fails local checks: Invalid Signers array size.");
} }
{ {
// Multisign 9 times should fail. // Multisign 9 (!ExpandedSignerList) | 33 (ExpandedSignerList) times
// should fail.
JTx tx = env.jt( JTx tx = env.jt(
noop(alice), noop(alice),
fee(2 * baseFee), fee(2 * baseFee),
msig(
features[featureExpandedSignerList] ? msig(
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie)
: msig(
bogie, bogie,
bogie, bogie,
bogie, bogie,
@@ -1517,6 +1585,82 @@ public:
BEAST_EXPECT(env.seq(alice) == aliceSeq); BEAST_EXPECT(env.seq(alice) == aliceSeq);
} }
void
test_signersWithTags(FeatureBitset features)
{
if (!features[featureExpandedSignerList])
return;
testcase("Signers With Tags");
using namespace jtx;
Env env{*this, features};
Account const alice{"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
uint8_t tag1[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
uint8_t tag2[] =
"hello world some ascii 32b long"; // including 1 byte for NUL
uint256 bogie_tag = ripple::base_uint<256>::fromVoid(tag1);
uint256 demon_tag = ripple::base_uint<256>::fromVoid(tag2);
// Attach phantom signers to alice and use them for a transaction.
env(signers(alice, 1, {{bogie, 1, bogie_tag}, {demon, 1, demon_tag}}));
env.close();
env.require(owners(alice, features[featureMultiSignReserve] ? 1 : 4));
// This should work.
auto const baseFee = env.current()->fees().base;
std::uint32_t aliceSeq = env.seq(alice);
env(noop(alice), msig(bogie, demon), fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Either signer alone should work.
aliceSeq = env.seq(alice);
env(noop(alice), msig(bogie), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
aliceSeq = env.seq(alice);
env(noop(alice), msig(demon), fee(2 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
// Duplicate signers should fail.
aliceSeq = env.seq(alice);
env(noop(alice), msig(demon, demon), fee(3 * baseFee), ter(temINVALID));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
// A non-signer should fail.
aliceSeq = env.seq(alice);
env(noop(alice),
msig(bogie, spook),
fee(3 * baseFee),
ter(tefBAD_SIGNATURE));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
// Don't meet the quorum. Should fail.
env(signers(alice, 2, {{bogie, 1}, {demon, 1}}));
aliceSeq = env.seq(alice);
env(noop(alice), msig(bogie), fee(2 * baseFee), ter(tefBAD_QUORUM));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
// Meet the quorum. Should succeed.
aliceSeq = env.seq(alice);
env(noop(alice), msig(bogie, demon), fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
}
void void
testAll(FeatureBitset features) testAll(FeatureBitset features)
{ {
@@ -1537,6 +1681,7 @@ public:
test_multisigningMultisigner(features); test_multisigningMultisigner(features);
test_signForHash(features); test_signForHash(features);
test_signersWithTickets(features); test_signersWithTickets(features);
test_signersWithTags(features);
} }
void void
@@ -1545,10 +1690,13 @@ public:
using namespace jtx; using namespace jtx;
auto const all = supported_amendments(); auto const all = supported_amendments();
// The reserve required on a signer list changes based on. // The reserve required on a signer list changes based on
// featureMultiSignReserve. Test both with and without. // featureMultiSignReserve. Limits on the number of signers
testAll(all - featureMultiSignReserve); // changes based on featureExpandedSignerList. Test both with and
testAll(all | featureMultiSignReserve); // without.
testAll(all - featureMultiSignReserve - featureExpandedSignerList);
testAll(all - featureExpandedSignerList);
testAll(all);
test_amendmentTransition(); test_amendmentTransition();
} }
}; };

View File

@@ -22,6 +22,8 @@
#include <ripple/protocol/Sign.h> #include <ripple/protocol/Sign.h>
#include <ripple/protocol/UintTypes.h> #include <ripple/protocol/UintTypes.h>
#include <ripple/protocol/jss.h> #include <ripple/protocol/jss.h>
#include <optional>
#include <sstream>
#include <test/jtx/multisign.h> #include <test/jtx/multisign.h>
#include <test/jtx/utility.h> #include <test/jtx/utility.h>
@@ -46,6 +48,8 @@ signers(
auto& je = ja[i][sfSignerEntry.getJsonName()]; auto& je = ja[i][sfSignerEntry.getJsonName()];
je[jss::Account] = e.account.human(); je[jss::Account] = e.account.human();
je[sfSignerWeight.getJsonName()] = e.weight; je[sfSignerWeight.getJsonName()] = e.weight;
if (e.tag)
je[sfWalletLocator.getJsonName()] = to_string(*e.tag);
} }
return jv; return jv;
} }

View File

@@ -21,6 +21,7 @@
#define RIPPLE_TEST_JTX_MULTISIGN_H_INCLUDED #define RIPPLE_TEST_JTX_MULTISIGN_H_INCLUDED
#include <cstdint> #include <cstdint>
#include <optional>
#include <test/jtx/Account.h> #include <test/jtx/Account.h>
#include <test/jtx/amount.h> #include <test/jtx/amount.h>
#include <test/jtx/owners.h> #include <test/jtx/owners.h>
@@ -35,9 +36,13 @@ struct signer
{ {
std::uint32_t weight; std::uint32_t weight;
Account account; Account account;
std::optional<uint256> tag;
signer(Account account_, std::uint32_t weight_ = 1) signer(
: weight(weight_), account(std::move(account_)) Account account_,
std::uint32_t weight_ = 1,
std::optional<uint256> tag_ = std::nullopt)
: weight(weight_), account(std::move(account_)), tag(std::move(tag_))
{ {
} }
}; };

View File

@@ -20,6 +20,7 @@
#include <ripple/basics/Slice.h> #include <ripple/basics/Slice.h>
#include <ripple/beast/unit_test.h> #include <ripple/beast/unit_test.h>
#include <ripple/json/to_string.h> #include <ripple/json/to_string.h>
#include <ripple/protocol/Rules.h>
#include <ripple/protocol/STAmount.h> #include <ripple/protocol/STAmount.h>
#include <ripple/protocol/STParsedJSON.h> #include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/STTx.h> #include <ripple/protocol/STTx.h>
@@ -27,7 +28,6 @@
#include <ripple/protocol/TxFormats.h> #include <ripple/protocol/TxFormats.h>
#include <ripple/protocol/UintTypes.h> #include <ripple/protocol/UintTypes.h>
#include <ripple/protocol/messages.h> #include <ripple/protocol/messages.h>
#include <memory> #include <memory>
#include <regex> #include <regex>
@@ -1591,8 +1591,10 @@ public:
}); });
j.sign(keypair.first, keypair.second); j.sign(keypair.first, keypair.second);
Rules defaultRules{{}};
unexpected( unexpected(
!j.checkSign(STTx::RequireFullyCanonicalSig::yes), !j.checkSign(STTx::RequireFullyCanonicalSig::yes, defaultRules),
"Transaction fails signature test"); "Transaction fails signature test");
Serializer rawTxn; Serializer rawTxn;