add featureExpandedSignerList

This commit is contained in:
Richard Holland
2022-02-09 17:25:03 +00:00
parent 0f620f4e53
commit 579306f4cd
21 changed files with 491 additions and 209 deletions

View File

@@ -493,6 +493,7 @@ target_sources (rippled PRIVATE
src/ripple/ledger/impl/RawStateTable.cpp
src/ripple/ledger/impl/ReadView.cpp
src/ripple/ledger/impl/View.cpp
src/ripple/ledger/impl/Rules.cpp
#[===============================[
main sources:
subdir: net

View File

@@ -40,6 +40,9 @@ Loop: ripple.basics ripple.rpc
Loop: ripple.core ripple.net
ripple.net > ripple.core
Loop: ripple.ledger ripple.protocol
ripple.ledger > ripple.protocol
Loop: ripple.net ripple.rpc
ripple.rpc > ripple.net

View File

@@ -23,7 +23,6 @@ ripple.ledger > ripple.basics
ripple.ledger > ripple.beast
ripple.ledger > ripple.core
ripple.ledger > ripple.json
ripple.ledger > ripple.protocol
ripple.net > ripple.basics
ripple.net > ripple.beast
ripple.net > ripple.json
@@ -178,6 +177,7 @@ test.protocol > ripple.basics
test.protocol > ripple.beast
test.protocol > ripple.crypto
test.protocol > ripple.json
test.protocol > ripple.ledger
test.protocol > ripple.protocol
test.protocol > test.toplevel
test.resource > ripple.basics

View File

@@ -83,6 +83,7 @@ SetSignerList::preflight(PreflightContext const& ctx)
return ret;
auto const result = determineOperation(ctx.tx, ctx.flags, ctx.j);
if (std::get<0>(result) != tesSUCCESS)
return std::get<0>(result);
@@ -99,7 +100,11 @@ SetSignerList::preflight(PreflightContext const& ctx)
// 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);
std::get<1>(result),
std::get<2>(result),
account,
ctx.j,
ctx.rules);
if (ter != tesSUCCESS)
{
return ter;
@@ -150,7 +155,7 @@ SetSignerList::preCompute()
// is valid until the featureMultiSignReserve amendment passes. Once it
// passes then just 1 OwnerCount is associated with a SignerList.
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:
// o The fact that we're adding/removing a SignerList and
@@ -165,9 +170,10 @@ signerCountBasedOwnerCountDelta(std::size_t entryCount)
// 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 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::maxMultiSigners);
assert(entryCount <= STTx::maxMultiSigners(&rules));
return 2 + static_cast<int>(entryCount);
}
@@ -196,7 +202,8 @@ removeSignersFromLedger(
{
STArray const& actualList = signers->getFieldArray(sfSignerEntries);
removeFromOwnerCount =
signerCountBasedOwnerCountDelta(actualList.size()) * -1;
signerCountBasedOwnerCountDelta(actualList.size(), view.rules()) *
-1;
}
// Remove the node from the account directory.
@@ -239,13 +246,14 @@ SetSignerList::validateQuorumAndSignerEntries(
std::uint32_t quorum,
std::vector<SignerEntries::SignerEntry> const& signers,
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.
{
std::size_t const signerCount = signers.size();
if ((signerCount < STTx::minMultiSigners) ||
(signerCount > STTx::maxMultiSigners))
(signerCount > STTx::maxMultiSigners(&rules)))
{
JLOG(j.trace()) << "Too many or too few signers in signer list.";
return temMALFORMED;
@@ -260,6 +268,9 @@ SetSignerList::validateQuorumAndSignerEntries(
return temBAD_SIGNER;
}
// Is the ExpandedSignerList amendment active?
bool expandedSignerList = rules.enabled(featureExpandedSignerList);
// Make sure no signers reference this account. Also make sure the
// quorum can be reached.
std::uint64_t allSignersWeight(0);
@@ -280,6 +291,14 @@ SetSignerList::validateQuorumAndSignerEntries(
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
// may be phantom accounts (which are permitted).
}
@@ -322,7 +341,8 @@ SetSignerList::replaceSignerList()
std::uint32_t flags{lsfOneOwnerCount};
if (!ctx_.view().rules().enabled(featureMultiSignReserve))
{
addedOwnerCount = signerCountBasedOwnerCountDelta(signers_.size());
addedOwnerCount = signerCountBasedOwnerCountDelta(
signers_.size(), ctx_.view().rules());
flags = 0;
}
@@ -390,6 +410,9 @@ SetSignerList::writeSignersToSLE(
if (flags) // Only set flags if they are non-default (default is zero).
ledgerEntry->setFieldU32(sfFlags, flags);
bool expandedSignerList =
ctx_.view().rules().enabled(featureExpandedSignerList);
// Create the SignerListArray one SignerEntry at a time.
STArray toLedger(signers_.size());
for (auto const& entry : signers_)
@@ -399,6 +422,11 @@ SetSignerList::writeSignersToSLE(
obj.reserve(2);
obj.setAccountID(sfAccount, entry.account);
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.

View File

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

View File

@@ -19,9 +19,11 @@
#include <ripple/app/tx/impl/SignerEntries.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/Rules.h>
#include <ripple/protocol/STArray.h>
#include <ripple/protocol/STObject.h>
#include <cstdint>
#include <optional>
namespace ripple {
@@ -41,7 +43,7 @@ SignerEntries::deserialize(
}
std::vector<SignerEntry> accountVec;
accountVec.reserve(STTx::maxMultiSigners);
accountVec.reserve(STTx::maxMultiSigners());
STArray const& sEntries(obj.getFieldArray(sfSignerEntries));
for (STObject const& sEntry : sEntries)
@@ -57,7 +59,12 @@ SignerEntries::deserialize(
// Extract SignerEntry fields.
AccountID const account = sEntry.getAccountID(sfAccount);
std::uint16_t const weight = sEntry.getFieldU16(sfSignerWeight);
accountVec.emplace_back(account, weight);
std::optional<uint256> tag;
if (sEntry.isFieldPresent(sfWalletLocator))
tag = sEntry.getFieldH256(sfWalletLocator);
accountVec.emplace_back(account, weight, tag);
}
return accountVec;
}

View File

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

View File

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

View File

@@ -26,6 +26,7 @@
#include <ripple/basics/chrono.h>
#include <ripple/beast/hash/uhash.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/ledger/Rules.h>
#include <ripple/ledger/detail/ReadViewFwdRange.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/Protocol.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.
This interface provides read access to state

83
src/ripple/ledger/Rules.h Normal file
View File

@@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
/*
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 <unordered_set>
namespace ripple {
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;
};
} // namespace ripple
#endif

View File

@@ -21,108 +21,6 @@
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)
{
}

View File

@@ -0,0 +1,127 @@
//------------------------------------------------------------------------------
/*
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/ledger/Rules.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)
{
}
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_;
}
bool
Rules::operator!=(Rules const& other) const
{
return !(*this == other);
}
} // namespace ripple

View File

@@ -74,7 +74,7 @@ namespace detail {
// 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
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 46;
static constexpr std::size_t numFeatures = 47;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -334,6 +334,7 @@ extern uint256 const fixSTAmountCanonicalize;
extern uint256 const fixRmSmallIncreasedQOffers;
extern uint256 const featureCheckCashMakesTrustLine;
extern uint256 const featureHooks;
extern uint256 const featureExpandedSignerList;
} // namespace ripple

View File

@@ -21,6 +21,8 @@
#define RIPPLE_PROTOCOL_STTX_H_INCLUDED
#include <ripple/basics/Expected.h>
#include <ripple/ledger/Rules.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/SecretKey.h>
@@ -44,7 +46,16 @@ class STTx final : public STObject, public CountedObject<STTx>
{
public:
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;
}
public:
STTx() = delete;
@@ -133,7 +144,8 @@ public:
*/
enum class RequireFullyCanonicalSig : bool { no, yes };
Expected<void, std::string>
checkSign(RequireFullyCanonicalSig requireCanonicalSig) const;
checkSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules)
const;
// SQL Functions with metadata.
static std::string const&
@@ -155,7 +167,9 @@ private:
checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const;
Expected<void, std::string>
checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const;
checkMultiSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const;
uint256 tid_;
TxType tx_type_;

View File

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

View File

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

View File

@@ -22,6 +22,7 @@
#include <ripple/basics/contract.h>
#include <ripple/basics/safe_cast.h>
#include <ripple/json/to_string.h>
#include <ripple/ledger/ReadView.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/Protocol.h>
@@ -187,7 +188,9 @@ STTx::sign(PublicKey const& publicKey, SecretKey const& secretKey)
}
Expected<void, std::string>
STTx::checkSign(RequireFullyCanonicalSig requireCanonicalSig) const
STTx::checkSign(
RequireFullyCanonicalSig requireCanonicalSig,
Rules const& rules) const
{
try
{
@@ -195,8 +198,9 @@ STTx::checkSign(RequireFullyCanonicalSig requireCanonicalSig) const
// at the SigningPubKey. If it's empty we must be
// multi-signing. Otherwise we're single-signing.
Blob const& signingPubKey = getFieldVL(sfSigningPubKey);
return signingPubKey.empty() ? checkMultiSign(requireCanonicalSig)
: checkSingleSign(requireCanonicalSig);
return signingPubKey.empty()
? checkMultiSign(requireCanonicalSig, rules)
: checkSingleSign(requireCanonicalSig);
}
catch (std::exception const&)
{
@@ -308,7 +312,9 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const
}
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
// attempting multi-signing and we just have a bad SigningPubKey.
@@ -323,7 +329,8 @@ STTx::checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const
STArray const& signers{getFieldArray(sfSigners)};
// 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.");
// We can ease the computational load inside the loop a bit by

View File

@@ -34,6 +34,30 @@ class MultiSign_test : public beast::unit_test::suite
jtx::Account const phase{"phase", KeyType::ed25519};
jtx::Account const shade{"shade", KeyType::secp256k1};
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:
void
@@ -159,22 +183,28 @@ public:
{spook, 1}}),
ter(temBAD_QUORUM));
// Make a signer list that's too big. Should fail.
// Make a signer list that's too big. Should fail. (Even with
// ExpandedSignerList)
Account const spare("spare", KeyType::secp256k1);
env(signers(
alice,
1,
{{bogie, 1},
{demon, 1},
{ghost, 1},
{haunt, 1},
{jinni, 1},
{phase, 1},
{shade, 1},
{spook, 1},
{spare, 1}}),
ter(temMALFORMED));
features[featureExpandedSignerList]
? std::vector<signer>{{bogie, 1}, {demon, 1}, {ghost, 1},
{haunt, 1}, {jinni, 1}, {phase, 1},
{shade, 1}, {spook, 1}, {spare, 1},
{acc10, 1}, {acc11, 1}, {acc12, 1},
{acc13, 1}, {acc14, 1}, {acc15, 1},
{acc16, 1}, {acc17, 1}, {acc18, 1},
{acc19, 1}, {acc20, 1}, {acc21, 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));
env.close();
env.require(owners(alice, 0));
}
@@ -1149,20 +1179,56 @@ public:
"fails local checks: Invalid Signers array size.");
}
{
// Multisign 9 times should fail.
// Multisign 9 (!ExpandedSignerList) | 33 (ExpandedSignerList) times
// should fail.
JTx tx = env.jt(
noop(alice),
fee(2 * baseFee),
msig(
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie,
bogie));
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,
bogie,
bogie,
bogie));
STTx local = *(tx.stx);
auto const info = submitSTTx(local);
BEAST_EXPECT(
@@ -1517,6 +1583,82 @@ public:
BEAST_EXPECT(env.seq(alice) == aliceSeq);
}
void
test_signersWithTags(FeatureBitset features)
{
testcase("Signers With Tags");
if (!features[featureExpandedSignerList])
return;
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
testAll(FeatureBitset features)
{
@@ -1537,6 +1679,7 @@ public:
test_multisigningMultisigner(features);
test_signForHash(features);
test_signersWithTickets(features);
test_signersWithTags(features);
}
void

View File

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

View File

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

View File

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