Implement MultiSignReserve amendment [RIPD-1647]:

Reduces the account reserve for a multisigning SignerList from
(conditionally) 3 to 10 OwnerCounts to (unconditionally) 1
OwnerCount.  Includes a transition process.
This commit is contained in:
Scott Schurr
2018-07-13 12:34:26 -07:00
parent 3ce4dda5cb
commit 6572fc8e95
7 changed files with 390 additions and 208 deletions

View File

@@ -218,11 +218,19 @@ SetSignerList::replaceSignerList ()
auto const sle = view().peek(accountKeylet);
// Compute new reserve. Verify the account has funds to meet the reserve.
auto const oldOwnerCount = (*sle)[sfOwnerCount];
std::uint32_t const addedOwnerCount = ownerCountDelta (signers_.size ());
std::uint32_t const oldOwnerCount {(*sle)[sfOwnerCount]};
auto const newReserve =
view().fees().accountReserve(oldOwnerCount + addedOwnerCount);
// The required reserve changes based on featureMultiSignReserve...
int addedOwnerCount {1};
std::uint32_t flags {lsfOneOwnerCount};
if (! ctx_.view().rules().enabled(featureMultiSignReserve))
{
addedOwnerCount = signerCountBasedOwnerCountDelta (signers_.size ());
flags = 0;
}
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
@@ -233,15 +241,15 @@ SetSignerList::replaceSignerList ()
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
auto signerList = std::make_shared<SLE>(signerListKeylet);
view().insert (signerList);
writeSignersToSLE (signerList);
writeSignersToSLE (signerList, flags);
auto viewJ = ctx_.app.journal ("View");
// Add the signer list to the account's directory.
auto page = dirAdd(ctx_.view (), ownerDirKeylet,
auto const page = dirAdd (ctx_.view (), ownerDirKeylet,
signerListKeylet.key, false, describeOwnerDir (account_), viewJ);
JLOG(j_.trace()) << "Create signer list for account " <<
toBase58(account_) << ": " << (page ? "success" : "failure");
toBase58 (account_) << ": " << (page ? "success" : "failure");
if (!page)
return tecDIR_FULL;
@@ -283,8 +291,16 @@ SetSignerList::removeSignersFromLedger (Keylet const& accountKeylet,
if (!signers)
return tesSUCCESS;
STArray const& actualList = signers->getFieldArray (sfSignerEntries);
int const removeFromOwnerCount = ownerCountDelta (actualList.size()) * -1;
// 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()) * -1;
}
// Remove the node from the account directory.
auto const hint = (*signers)[sfOwnerNode];
@@ -305,13 +321,14 @@ SetSignerList::removeSignersFromLedger (Keylet const& accountKeylet,
}
void
SetSignerList::writeSignersToSLE (SLE::pointer const& ledgerEntry) const
SetSignerList::writeSignersToSLE (
SLE::pointer const& ledgerEntry, std::uint32_t flags) const
{
// Assign the quorum.
// Assign the quorum, default SignerListID, and flags.
ledgerEntry->setFieldU32 (sfSignerQuorum, quorum_);
// For now, assign the default SignerListID.
ledgerEntry->setFieldU32 (sfSignerListID, defaultSignerListID_);
if (flags) // 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 ());
@@ -330,8 +347,12 @@ SetSignerList::writeSignersToSLE (SLE::pointer const& ledgerEntry) const
// The return type is signed so it is compatible with the 3rd argument
// of adjustOwnerCount() (which must be signed).
//
// NOTE: This way of computing the OwnerCount associated with a SignerList
// is valid until the featureMultiSignReserve amendment passes. Once it
// passes then just 1 OwnerCount is associated with a SignerList.
int
SetSignerList::ownerCountDelta (std::size_t entryCount)
SetSignerList::signerCountBasedOwnerCountDelta (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

View File

@@ -87,9 +87,14 @@ private:
TER removeSignersFromLedger (Keylet const& accountKeylet,
Keylet const& ownerDirKeylet, Keylet const& signerListKeylet);
void writeSignersToSLE (SLE::pointer const& ledgerEntry) const;
void writeSignersToSLE (
SLE::pointer const& ledgerEntry, std::uint32_t flags) const;
static int ownerCountDelta (std::size_t entryCount);
// Way of computing owner count prior to featureMultiSignReserve.
// This needs to stay in the code base until no signerLists remain
// in the ledger that were created prior to acceptance of
// featureMultiSignReserve... Effectively forever.
static int signerCountBasedOwnerCountDelta (std::size_t entryCount);
};
} // ripple

View File

@@ -81,7 +81,8 @@ class FeatureCollections
"fix1623",
"DepositPreauth",
"fix1515",
"fix1578"
"fix1578",
"MultiSignReserve"
};
std::vector<uint256> features;
@@ -369,6 +370,7 @@ extern uint256 const fix1623;
extern uint256 const featureDepositPreauth;
extern uint256 const fix1515;
extern uint256 const fix1578;
extern uint256 const featureMultiSignReserve;
} // ripple

View File

@@ -152,6 +152,9 @@ enum LedgerSpecificFlags
lsfHighNoRipple = 0x00200000,
lsfLowFreeze = 0x00400000, // True, low side has set freeze flag
lsfHighFreeze = 0x00800000, // True, high side has set freeze flag
// ltSIGNER_LIST
lsfOneOwnerCount = 0x00010000, // True, uses only one OwnerCount
};
//------------------------------------------------------------------------------

View File

@@ -114,7 +114,8 @@ detail::supportedAmendments ()
{ "3CBC5C4E630A1B82380295CDA84B32B49DD066602E74E39B85EF64137FA65194 DepositPreauth" },
// Use liquidity from strands that consume max offers, but mark as dry
{ "5D08145F0A4983F23AFFFF514E83FAD355C5ABFBB6CAB76FB5BC8519FF5F33BE fix1515" },
{ "FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288 fix1578" }
{ "FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288 fix1578" },
{ "586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF1FC07EFE41D MultiSignReserve" }
};
return supported;
}
@@ -170,5 +171,6 @@ uint256 const fix1623 = *getRegisteredFeature("fix1623");
uint256 const featureDepositPreauth = *getRegisteredFeature("DepositPreauth");
uint256 const fix1515 = *getRegisteredFeature("fix1515");
uint256 const fix1578 = *getRegisteredFeature("fix1578");
uint256 const featureMultiSignReserve = *getRegisteredFeature("MultiSignReserve");
} // ripple

View File

@@ -956,9 +956,16 @@ class Check_test : public beast::unit_test::suite
BEAST_EXPECT (ownerCount (env, alice) == 1);
BEAST_EXPECT (ownerCount (env, bob ) == 1);
}
// Use a regular key and also multisign to cash a check.
// featureMultiSign changes the reserve on a SignerList, so
// check both before and after.
FeatureBitset const allSupported {supported_amendments()};
for (auto const features :
{allSupported - featureMultiSignReserve,
allSupported | featureMultiSignReserve})
{
// Use a regular key and also multisign to cash a check.
Env env {*this};
Env env {*this, features};
auto const closeTime =
fix1449Time() + 100 * env.closed()->info().closeTimeResolution;
env.close (closeTime);
@@ -989,7 +996,11 @@ class Check_test : public beast::unit_test::suite
Account const demon {"demon", KeyType::ed25519};
env (signers (bob, 2, {{bogie, 1}, {demon, 1}}), sig (bobby));
env.close();
BEAST_EXPECT (ownerCount (env, bob) == 5); // signerList -> 4 owners
// If featureMultiSignReserve is enabled then bob's signer list
// has an owner count of 1, otherwise it's 4.
int const signersCount {features[featureMultiSignReserve] ? 1 : 4};
BEAST_EXPECT (ownerCount (env, bob) == signersCount + 1);
// bob uses his regular key to cash a check.
env (check::cash (bob, chkId1, (USD(1))), sig (bobby));
@@ -999,7 +1010,7 @@ class Check_test : public beast::unit_test::suite
BEAST_EXPECT (checksOnAccount (env, alice).size() == 1);
BEAST_EXPECT (checksOnAccount (env, bob ).size() == 1);
BEAST_EXPECT (ownerCount (env, alice) == 2);
BEAST_EXPECT (ownerCount (env, bob ) == 5);
BEAST_EXPECT (ownerCount (env, bob ) == signersCount + 1);
// bob uses multisigning to cash a check.
std::uint64_t const baseFeeDrops {env.current()->fees().base};
@@ -1011,7 +1022,7 @@ class Check_test : public beast::unit_test::suite
BEAST_EXPECT (checksOnAccount (env, alice).size() == 0);
BEAST_EXPECT (checksOnAccount (env, bob ).size() == 0);
BEAST_EXPECT (ownerCount (env, alice) == 1);
BEAST_EXPECT (ownerCount (env, bob ) == 5);
BEAST_EXPECT (ownerCount (env, bob ) == signersCount + 1);
}
}
@@ -1596,147 +1607,160 @@ class Check_test : public beast::unit_test::suite
Account const zoe {"zoe"};
IOU const USD {gw["USD"]};
Env env {*this};
auto const closeTime =
fix1449Time() + 100 * env.closed()->info().closeTimeResolution;
env.close (closeTime);
// featureMultiSign changes the reserve on a SignerList, so
// check both before and after.
FeatureBitset const allSupported {supported_amendments()};
for (auto const features :
{allSupported - featureMultiSignReserve,
allSupported | featureMultiSignReserve})
{
Env env {*this, features};
env.fund (XRP(1000), gw, alice, bob, zoe);
auto const closeTime =
fix1449Time() + 100 * env.closed()->info().closeTimeResolution;
env.close (closeTime);
// alice creates her checks ahead of time.
// Three ordinary checks with no expiration.
uint256 const chkId1 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)));
env.close();
env.fund (XRP(1000), gw, alice, bob, zoe);
uint256 const chkId2 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)));
env.close();
// alice creates her checks ahead of time.
// Three ordinary checks with no expiration.
uint256 const chkId1 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)));
env.close();
uint256 const chkId3 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)));
env.close();
uint256 const chkId2 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)));
env.close();
// Three checks that expire in 10 minutes.
using namespace std::chrono_literals;
uint256 const chkIdNotExp1 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s));
env.close();
uint256 const chkId3 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)));
env.close();
uint256 const chkIdNotExp2 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)), expiration (env.now()+600s));
env.close();
// Three checks that expire in 10 minutes.
using namespace std::chrono_literals;
uint256 const chkIdNotExp1 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s));
env.close();
uint256 const chkIdNotExp3 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s));
env.close();
uint256 const chkIdNotExp2 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)), expiration (env.now()+600s));
env.close();
// Three checks that expire in one second.
uint256 const chkIdExp1 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)), expiration (env.now()+1s));
env.close();
uint256 const chkIdNotExp3 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s));
env.close();
uint256 const chkIdExp2 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)), expiration (env.now()+1s));
env.close();
// Three checks that expire in one second.
uint256 const chkIdExp1 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)), expiration (env.now()+1s));
env.close();
uint256 const chkIdExp3 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)), expiration (env.now()+1s));
env.close();
uint256 const chkIdExp2 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)), expiration (env.now()+1s));
env.close();
// Two checks to cancel using a regular key and using multisigning.
uint256 const chkIdReg {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)));
env.close();
uint256 const chkIdExp3 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)), expiration (env.now()+1s));
env.close();
uint256 const chkIdMSig {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 11);
BEAST_EXPECT (ownerCount (env, alice) == 11);
// Two checks to cancel using a regular key and using multisigning.
uint256 const chkIdReg {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)));
env.close();
// Creator, destination, and an outsider cancel the checks.
env (check::cancel (alice, chkId1));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 10);
BEAST_EXPECT (ownerCount (env, alice) == 10);
uint256 const chkIdMSig {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 11);
BEAST_EXPECT (ownerCount (env, alice) == 11);
env (check::cancel (bob, chkId2));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 9);
BEAST_EXPECT (ownerCount (env, alice) == 9);
// Creator, destination, and an outsider cancel the checks.
env (check::cancel (alice, chkId1));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 10);
BEAST_EXPECT (ownerCount (env, alice) == 10);
env (check::cancel (zoe, chkId3), ter (tecNO_PERMISSION));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 9);
BEAST_EXPECT (ownerCount (env, alice) == 9);
env (check::cancel (bob, chkId2));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 9);
BEAST_EXPECT (ownerCount (env, alice) == 9);
// Creator, destination, and an outsider cancel unexpired checks.
env (check::cancel (alice, chkIdNotExp1));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 8);
BEAST_EXPECT (ownerCount (env, alice) == 8);
env (check::cancel (zoe, chkId3), ter (tecNO_PERMISSION));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 9);
BEAST_EXPECT (ownerCount (env, alice) == 9);
env (check::cancel (bob, chkIdNotExp2));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 7);
BEAST_EXPECT (ownerCount (env, alice) == 7);
// Creator, destination, and an outsider cancel unexpired checks.
env (check::cancel (alice, chkIdNotExp1));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 8);
BEAST_EXPECT (ownerCount (env, alice) == 8);
env (check::cancel (zoe, chkIdNotExp3), ter (tecNO_PERMISSION));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 7);
BEAST_EXPECT (ownerCount (env, alice) == 7);
env (check::cancel (bob, chkIdNotExp2));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 7);
BEAST_EXPECT (ownerCount (env, alice) == 7);
// Creator, destination, and an outsider cancel expired checks.
env (check::cancel (alice, chkIdExp1));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 6);
BEAST_EXPECT (ownerCount (env, alice) == 6);
env (check::cancel (zoe, chkIdNotExp3), ter (tecNO_PERMISSION));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 7);
BEAST_EXPECT (ownerCount (env, alice) == 7);
env (check::cancel (bob, chkIdExp2));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 5);
BEAST_EXPECT (ownerCount (env, alice) == 5);
// Creator, destination, and an outsider cancel expired checks.
env (check::cancel (alice, chkIdExp1));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 6);
BEAST_EXPECT (ownerCount (env, alice) == 6);
env (check::cancel (zoe, chkIdExp3));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 4);
BEAST_EXPECT (ownerCount (env, alice) == 4);
env (check::cancel (bob, chkIdExp2));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 5);
BEAST_EXPECT (ownerCount (env, alice) == 5);
// Use a regular key and also multisign to cancel checks.
Account const alie {"alie", KeyType::ed25519};
env (regkey (alice, alie));
env.close();
env (check::cancel (zoe, chkIdExp3));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 4);
BEAST_EXPECT (ownerCount (env, alice) == 4);
Account const bogie {"bogie", KeyType::secp256k1};
Account const demon {"demon", KeyType::ed25519};
env (signers (alice, 2, {{bogie, 1}, {demon, 1}}), sig (alie));
env.close();
// Use a regular key and also multisign to cancel checks.
Account const alie {"alie", KeyType::ed25519};
env (regkey (alice, alie));
env.close();
// alice uses her regular key to cancel a check.
env (check::cancel (alice, chkIdReg), sig (alie));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 3);
BEAST_EXPECT (ownerCount (env, alice) == 7);
Account const bogie {"bogie", KeyType::secp256k1};
Account const demon {"demon", KeyType::ed25519};
env (signers (alice, 2, {{bogie, 1}, {demon, 1}}), sig (alie));
env.close();
// alice uses multisigning to cancel a check.
std::uint64_t const baseFeeDrops {env.current()->fees().base};
env (check::cancel (alice, chkIdMSig),
msig (bogie, demon), fee (3 * baseFeeDrops));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 2);
BEAST_EXPECT (ownerCount (env, alice) == 6);
// If featureMultiSignReserve is enabled then alices's signer list
// has an owner count of 1, otherwise it's 4.
int const signersCount {features[featureMultiSignReserve] ? 1 : 4};
// Creator and destination cancel the remaining unexpired checks.
env (check::cancel (alice, chkId3), sig (alice));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 1);
BEAST_EXPECT (ownerCount (env, alice) == 5);
// alice uses her regular key to cancel a check.
env (check::cancel (alice, chkIdReg), sig (alie));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 3);
BEAST_EXPECT (ownerCount (env, alice) == signersCount + 3);
env (check::cancel (bob, chkIdNotExp3));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 0);
BEAST_EXPECT (ownerCount (env, alice) == 4);
// alice uses multisigning to cancel a check.
std::uint64_t const baseFeeDrops {env.current()->fees().base};
env (check::cancel (alice, chkIdMSig),
msig (bogie, demon), fee (3 * baseFeeDrops));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 2);
BEAST_EXPECT (ownerCount (env, alice) == signersCount + 2);
// Creator and destination cancel the remaining unexpired checks.
env (check::cancel (alice, chkId3), sig (alice));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 1);
BEAST_EXPECT (ownerCount (env, alice) == signersCount + 1);
env (check::cancel (bob, chkIdNotExp3));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 0);
BEAST_EXPECT (ownerCount (env, alice) == signersCount + 0);
}
}
void testCancelInvalid()

View File

@@ -36,15 +36,23 @@ class MultiSign_test : public beast::unit_test::suite
jtx::Account const spook {"spook", KeyType::ed25519};
public:
void test_noReserve()
void test_noReserve (FeatureBitset features)
{
testcase ("No Reserve");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::secp256k1};
// The reserve required for a signer list changes with the passage
// of featureMultiSignReserve. Make the required adjustments.
bool const reserve1 {features[featureMultiSignReserve]};
// Pay alice enough to meet the initial reserve, but not enough to
// meet the reserve for a SignerListSet.
env.fund(XRP(200), alice);
auto const fee = env.current()->fees().base;
auto const smallSignersReserve = reserve1 ? XRP(250) : XRP(350);
env.fund(smallSignersReserve - drops (1), alice);
env.close();
env.require (owners (alice, 0));
@@ -56,27 +64,32 @@ public:
env.require (owners (alice, 0));
// Fund alice enough to set the signer list, then attach signers.
env(pay(env.master, alice, XRP(151)));
env(pay(env.master, alice, fee + drops (1)));
env.close();
env(smallSigners);
env.close();
env.require (owners (alice, 3));
env.require (owners (alice, reserve1 ? 1 : 3));
}
{
// Pay alice enough to almost make the reserve for the biggest
// possible list.
auto const addReserveBigSigners = reserve1 ? XRP(0) : XRP(350);
env(pay(env.master, alice, addReserveBigSigners + fee - drops(1)));
// Replace with the biggest possible signer list. Should fail.
Json::Value bigSigners = signers(alice, 1, {
{ bogie, 1 }, { demon, 1 }, { ghost, 1 }, { haunt, 1 },
{ jinni, 1 }, { phase, 1 }, { shade, 1 }, { spook, 1 }});
env(bigSigners, ter(tecINSUFFICIENT_RESERVE));
env.close();
env.require (owners (alice, 3));
env.require (owners (alice, reserve1 ? 1 : 3));
// Fund alice and succeed.
env(pay(env.master, alice, XRP(350)));
// Fund alice one more drop (plus the fee) and succeed.
env(pay(env.master, alice, fee + drops(1)));
env.close();
env(bigSigners);
env.close();
env.require (owners (alice, 10));
env.require (owners (alice, reserve1 ? 1 : 10));
}
// Remove alice's signer list and get the owner count back.
env(signers(alice, jtx::none));
@@ -84,10 +97,12 @@ public:
env.require (owners (alice, 0));
}
void test_signerListSet()
void test_signerListSet (FeatureBitset features)
{
testcase ("SignerListSet");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
@@ -129,10 +144,12 @@ public:
env.require (owners (alice, 0));
}
void test_phantomSigners()
void test_phantomSigners (FeatureBitset features)
{
testcase ("Phantom Signers");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
@@ -140,7 +157,7 @@ public:
// Attach phantom signers to alice and use them for a transaction.
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
env.close();
env.require (owners (alice, 4));
env.require (owners (alice, features[featureMultiSignReserve] ? 1 : 4));
// This should work.
auto const baseFee = env.current()->fees().base;
@@ -188,20 +205,22 @@ public:
}
void
test_enablement()
test_enablement (FeatureBitset features)
{
testcase ("Enablement");
using namespace jtx;
Env env(*this, envconfig([](std::unique_ptr<Config> cfg)
{
cfg->loadFromString ("[" SECTION_SIGNING_SUPPORT "]\ntrue");
return cfg;
}), FeatureBitset{});
}), features - featureMultiSign);
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
// NOTE: These six tests will fail when multisign is default enabled.
// NOTE: These six tests will fail if multisign is enabled.
env(signers(alice, 1, {{bogie, 1}}), ter(temDISABLED));
env.close();
env.require (owners (alice, 0));
@@ -235,10 +254,12 @@ public:
}
}
void test_fee ()
void test_fee (FeatureBitset features)
{
testcase ("Fee");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
@@ -247,7 +268,7 @@ public:
env(signers(alice, 1, {{bogie, 1}, {demon, 1}, {ghost, 1}, {haunt, 1},
{jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}}));
env.close();
env.require (owners (alice, 10));
env.require (owners(alice, features[featureMultiSignReserve] ? 1 : 10));
// This should work.
auto const baseFee = env.current()->fees().base;
@@ -285,10 +306,12 @@ public:
BEAST_EXPECT(env.seq(alice) == aliceSeq);
}
void test_misorderedSigners()
void test_misorderedSigners (FeatureBitset features)
{
testcase ("Misordered Signers");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
env.close();
@@ -297,7 +320,7 @@ public:
// Make sure the transaction fails if they are not.
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
env.close();
env.require (owners(alice, 4));
env.require (owners (alice, features[featureMultiSignReserve] ? 1 : 4));
msig phantoms {bogie, demon};
std::reverse (phantoms.signers.begin(), phantoms.signers.end());
@@ -307,10 +330,12 @@ public:
BEAST_EXPECT(env.seq(alice) == aliceSeq);
}
void test_masterSigners()
void test_masterSigners (FeatureBitset features)
{
testcase ("Master Signers");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::ed25519};
Account const becky {"becky", KeyType::secp256k1};
Account const cheri {"cheri", KeyType::ed25519};
@@ -330,7 +355,7 @@ public:
//Attach signers to alice
env(signers(alice, 4, {{becky, 3}, {cheri, 4}}), sig (alice));
env.close();
env.require (owners (alice, 4));
env.require (owners (alice, features[featureMultiSignReserve] ? 1 : 4));
// Attempt a multisigned transaction that meets the quorum.
auto const baseFee = env.current()->fees().base;
@@ -359,10 +384,12 @@ public:
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
}
void test_regularSigners()
void test_regularSigners (FeatureBitset features)
{
testcase ("Regular Signers");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const cheri {"cheri", KeyType::secp256k1};
@@ -417,14 +444,16 @@ public:
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
}
void test_regularSignersUsingSubmitMulti()
void test_regularSignersUsingSubmitMulti (FeatureBitset features)
{
testcase ("Regular Signers Using submit_multisigned");
using namespace jtx;
Env env(*this, envconfig([](std::unique_ptr<Config> cfg)
{
cfg->loadFromString ("[" SECTION_SIGNING_SUPPORT "]\ntrue");
return cfg;
}));
}), features);
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const cheri {"cheri", KeyType::secp256k1};
@@ -624,10 +653,12 @@ public:
}
}
void test_heterogeneousSigners()
void test_heterogeneousSigners (FeatureBitset features)
{
testcase ("Heterogenious Signers");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const cheri {"cheri", KeyType::secp256k1};
@@ -656,7 +687,7 @@ public:
env(signers(alice, 1,
{{becky, 1}, {cheri, 1}, {daria, 1}, {jinni, 1}}), sig (alie));
env.close();
env.require (owners (alice, 6));
env.require (owners (alice, features[featureMultiSignReserve] ? 1 : 6));
// Each type of signer should succeed individually.
auto const baseFee = env.current()->fees().base;
@@ -696,7 +727,7 @@ public:
env(signers(alice, 0x3FFFC, {{becky, 0xFFFF},
{cheri, 0xFFFF}, {daria, 0xFFFF}, {jinni, 0xFFFF}}), sig (alie));
env.close();
env.require (owners (alice, 6));
env.require (owners (alice, features[featureMultiSignReserve] ? 1 : 6));
aliceSeq = env.seq (alice);
env(noop(alice), fee(9 * baseFee),
@@ -716,7 +747,7 @@ public:
{daria, 0xFFFF}, {haunt, 0xFFFF}, {jinni, 0xFFFF},
{phase, 0xFFFF}, {shade, 0xFFFF}, {spook, 0xFFFF}}), sig (alie));
env.close();
env.require (owners (alice, 10));
env.require (owners(alice, features[featureMultiSignReserve] ? 1 : 10));
aliceSeq = env.seq (alice);
env(noop(alice), fee(9 * baseFee), msig(becky, msig::Reg{cheri, cher},
@@ -739,10 +770,12 @@ public:
// We want to always leave an account signable. Make sure the that we
// disallow removing the last way a transaction may be signed.
void test_keyDisable()
void test_keyDisable (FeatureBitset features)
{
testcase ("Key Disable");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::ed25519};
env.fund(XRP(1000), alice);
@@ -814,10 +847,12 @@ public:
// Verify that the first regular key can be made for free using the
// master key, but not when multisigning.
void test_regKey()
void test_regKey (FeatureBitset features)
{
testcase ("Regular Key");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::secp256k1};
env.fund(XRP(1000), alice);
@@ -846,10 +881,12 @@ public:
}
// See if every kind of transaction can be successfully multi-signed.
void test_txTypes()
void test_txTypes (FeatureBitset features)
{
testcase ("Transaction Types");
using namespace jtx;
Env env(*this);
Env env {*this, features};
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const zelda {"zelda", KeyType::secp256k1};
@@ -866,7 +903,8 @@ public:
// Attach signers to alice.
env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig (alie));
env.close();
env.require (owners (alice, 4));
int const signerListOwners {features[featureMultiSignReserve] ? 1 : 4};
env.require (owners (alice, signerListOwners + 0));
// Multisign a ttPAYMENT.
auto const baseFee = env.current()->fees().base;
@@ -893,7 +931,7 @@ public:
env(trust("alice", USD(100)),
msig(becky, bogie), fee(3 * baseFee), require (lines("alice", 1)));
env.close();
env.require (owners (alice, 5));
env.require (owners (alice, signerListOwners + 1));
// Multisign a ttOFFER_CREATE transaction.
env(pay(gw, alice, USD(50)));
@@ -905,7 +943,7 @@ public:
env(offer(alice, XRP(50), USD(50)),
msig (becky, bogie), fee(3 * baseFee));
env.close();
env.require(owners(alice, 6));
env.require (owners (alice, signerListOwners + 2));
// Now multisign a ttOFFER_CANCEL canceling the offer we just created.
{
@@ -918,22 +956,24 @@ public:
msig (becky, bogie), fee(3 * baseFee));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
env.require(owners(alice, 5));
env.require(owners(alice, signerListOwners + 1));
}
// Multisign a ttSIGNER_LIST_SET.
env(signers(alice, 3, {{becky, 1}, {bogie, 1}, {demon, 1}}),
msig (becky, bogie), fee(3 * baseFee));
env.close();
env.require (owners (alice, 6));
env.require (owners (alice, features[featureMultiSignReserve] ? 2 : 6));
}
void test_badSignatureText()
void test_badSignatureText (FeatureBitset features)
{
testcase ("Bad Signature Text");
// Verify that the text returned for signature failures is correct.
using namespace jtx;
Env env(*this);
Env env {*this, features};
// lambda that submits an STTx and returns the resulting JSON.
auto submitSTTx = [&env] (STTx const& stx)
@@ -1064,10 +1104,12 @@ public:
}
}
void test_noMultiSigners()
void test_noMultiSigners (FeatureBitset features)
{
testcase ("No Multisigners");
using namespace jtx;
Env env {*this};
Env env {*this, features};
Account const alice {"alice", KeyType::ed25519};
Account const becky {"becky", KeyType::secp256k1};
env.fund(XRP(1000), alice, becky);
@@ -1077,15 +1119,17 @@ public:
env(noop(alice), msig(becky, demon), fee(3 * baseFee), ter(tefNOT_MULTI_SIGNING));
}
void test_multisigningMultisigner()
void test_multisigningMultisigner (FeatureBitset features)
{
testcase ("Multisigning multisigner");
// Set up a signer list where one of the signers has both the
// master disabled and no regular key (because that signer is
// exclusively multisigning). That signer should no longer be
// able to successfully sign the signer list.
using namespace jtx;
Env env (*this);
Env env {*this, features};
Account const alice {"alice", KeyType::ed25519};
Account const becky {"becky", KeyType::secp256k1};
env.fund (XRP(1000), alice, becky);
@@ -1143,8 +1187,10 @@ public:
env.close();
}
void test_signForHash()
void test_signForHash (FeatureBitset features)
{
testcase ("sign_for Hash");
// Make sure that the "hash" field returned by the "sign_for" RPC
// command matches the hash returned when that command is sent
// through "submit_multisigned". Make sure that hash also locates
@@ -1156,7 +1202,7 @@ public:
{
cfg->loadFromString ("[" SECTION_SIGNING_SUPPORT "]\ntrue");
return cfg;
}));
}), features);
env.fund (XRP(1000), alice);
env.close();
@@ -1221,25 +1267,104 @@ public:
[sfTransactionResult.jsonName].asString() == "tesSUCCESS");
}
void test_amendmentTransition ()
{
testcase ("Amendment Transition");
// The OwnerCount associated with a SignerList changes once the
// featureMultiSignReserve amendment goes live. Create a couple
// of signer lists before and after the amendment goes live and
// verify that the OwnerCount is managed properly for all of them.
using namespace jtx;
Account const alice {"alice", KeyType::secp256k1};
Account const becky {"becky", KeyType::ed25519};
Account const cheri {"cheri", KeyType::secp256k1};
Account const daria {"daria", KeyType::ed25519};
Env env {*this, supported_amendments() - featureMultiSignReserve};
env.fund (XRP(1000), alice, becky, cheri, daria);
env.close();
// Give alice and becky signer lists before the amendment goes live.
env (signers (alice, 1, {{bogie, 1}}));
env (signers (becky, 1, {{bogie, 1}, {demon, 1}, {ghost, 1},
{haunt, 1}, {jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}}));
env.close();
env.require (owners (alice, 3));
env.require (owners (becky, 10));
// Enable the amendment.
env.enableFeature (featureMultiSignReserve);
env.close();
// Give cheri and daria signer lists after the amendment goes live.
env (signers (cheri, 1, {{bogie, 1}}));
env (signers (daria, 1, {{bogie, 1}, {demon, 1}, {ghost, 1},
{haunt, 1}, {jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}}));
env.close();
env.require (owners (alice, 3));
env.require (owners (becky, 10));
env.require (owners (cheri, 1));
env.require (owners (daria, 1));
// Delete becky's signer list; her OwnerCount should drop to zero.
// Replace alice's signer list; her OwnerCount should drop to one.
env (signers (becky, jtx::none));
env (signers (alice, 1, {{bogie, 1}, {demon, 1}, {ghost, 1},
{haunt, 1}, {jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}}));
env.close();
env.require (owners (alice, 1));
env.require (owners (becky, 0));
env.require (owners (cheri, 1));
env.require (owners (daria, 1));
// Delete the three remaining signer lists. Everybody's OwnerCount
// should now be zero.
env (signers (alice, jtx::none));
env (signers (cheri, jtx::none));
env (signers (daria, jtx::none));
env.close();
env.require (owners (alice, 0));
env.require (owners (becky, 0));
env.require (owners (cheri, 0));
env.require (owners (daria, 0));
}
void testAll(FeatureBitset features)
{
test_noReserve (features);
test_signerListSet (features);
test_phantomSigners (features);
test_enablement (features);
test_fee (features);
test_misorderedSigners (features);
test_masterSigners (features);
test_regularSigners (features);
test_regularSignersUsingSubmitMulti (features);
test_heterogeneousSigners (features);
test_keyDisable (features);
test_regKey (features);
test_txTypes (features);
test_badSignatureText (features);
test_noMultiSigners (features);
test_multisigningMultisigner (features);
test_signForHash (features);
}
void run() override
{
test_noReserve();
test_signerListSet();
test_phantomSigners();
test_enablement();
test_fee();
test_misorderedSigners();
test_masterSigners();
test_regularSigners();
test_regularSignersUsingSubmitMulti();
test_heterogeneousSigners();
test_keyDisable();
test_regKey();
test_txTypes();
test_badSignatureText();
test_noMultiSigners();
test_multisigningMultisigner();
test_signForHash();
using namespace jtx;
auto const all = supported_amendments();
// The reserve required on a signer list changes based on.
// featureMultiSignReserve. Test both with and without.
testAll (all - featureMultiSignReserve);
testAll (all | featureMultiSignReserve);
test_amendmentTransition();
}
};