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); auto const sle = view().peek(accountKeylet);
// Compute new reserve. Verify the account has funds to meet the reserve. // Compute new reserve. Verify the account has funds to meet the reserve.
auto const oldOwnerCount = (*sle)[sfOwnerCount]; std::uint32_t const oldOwnerCount {(*sle)[sfOwnerCount]};
std::uint32_t const addedOwnerCount = ownerCountDelta (signers_.size ());
auto const newReserve = // The required reserve changes based on featureMultiSignReserve...
view().fees().accountReserve(oldOwnerCount + addedOwnerCount); 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 // We check the reserve against the starting balance because we want to
// allow dipping into the reserve to pay fees. This behavior is consistent // 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. // Everything's ducky. Add the ltSIGNER_LIST to the ledger.
auto signerList = std::make_shared<SLE>(signerListKeylet); auto signerList = std::make_shared<SLE>(signerListKeylet);
view().insert (signerList); view().insert (signerList);
writeSignersToSLE (signerList); writeSignersToSLE (signerList, flags);
auto viewJ = ctx_.app.journal ("View"); auto viewJ = ctx_.app.journal ("View");
// Add the signer list to the account's directory. // 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); signerListKeylet.key, false, describeOwnerDir (account_), viewJ);
JLOG(j_.trace()) << "Create signer list for account " << JLOG(j_.trace()) << "Create signer list for account " <<
toBase58(account_) << ": " << (page ? "success" : "failure"); toBase58 (account_) << ": " << (page ? "success" : "failure");
if (!page) if (!page)
return tecDIR_FULL; return tecDIR_FULL;
@@ -283,8 +291,16 @@ SetSignerList::removeSignersFromLedger (Keylet const& accountKeylet,
if (!signers) if (!signers)
return tesSUCCESS; return tesSUCCESS;
STArray const& actualList = signers->getFieldArray (sfSignerEntries); // There are two different ways that the OwnerCount could be managed.
int const removeFromOwnerCount = ownerCountDelta (actualList.size()) * -1; // 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. // Remove the node from the account directory.
auto const hint = (*signers)[sfOwnerNode]; auto const hint = (*signers)[sfOwnerNode];
@@ -305,13 +321,14 @@ SetSignerList::removeSignersFromLedger (Keylet const& accountKeylet,
} }
void 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_); ledgerEntry->setFieldU32 (sfSignerQuorum, quorum_);
// For now, assign the default SignerListID.
ledgerEntry->setFieldU32 (sfSignerListID, defaultSignerListID_); 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. // Create the SignerListArray one SignerEntry at a time.
STArray toLedger (signers_.size ()); 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 // The return type is signed so it is compatible with the 3rd argument
// of adjustOwnerCount() (which must be signed). // 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 int
SetSignerList::ownerCountDelta (std::size_t entryCount) SetSignerList::signerCountBasedOwnerCountDelta (std::size_t entryCount)
{ {
// 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

View File

@@ -87,9 +87,14 @@ private:
TER removeSignersFromLedger (Keylet const& accountKeylet, TER removeSignersFromLedger (Keylet const& accountKeylet,
Keylet const& ownerDirKeylet, Keylet const& signerListKeylet); 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 } // ripple

View File

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

View File

@@ -152,6 +152,9 @@ enum LedgerSpecificFlags
lsfHighNoRipple = 0x00200000, lsfHighNoRipple = 0x00200000,
lsfLowFreeze = 0x00400000, // True, low side has set freeze flag lsfLowFreeze = 0x00400000, // True, low side has set freeze flag
lsfHighFreeze = 0x00800000, // True, high 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" }, { "3CBC5C4E630A1B82380295CDA84B32B49DD066602E74E39B85EF64137FA65194 DepositPreauth" },
// Use liquidity from strands that consume max offers, but mark as dry // Use liquidity from strands that consume max offers, but mark as dry
{ "5D08145F0A4983F23AFFFF514E83FAD355C5ABFBB6CAB76FB5BC8519FF5F33BE fix1515" }, { "5D08145F0A4983F23AFFFF514E83FAD355C5ABFBB6CAB76FB5BC8519FF5F33BE fix1515" },
{ "FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288 fix1578" } { "FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288 fix1578" },
{ "586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF1FC07EFE41D MultiSignReserve" }
}; };
return supported; return supported;
} }
@@ -170,5 +171,6 @@ uint256 const fix1623 = *getRegisteredFeature("fix1623");
uint256 const featureDepositPreauth = *getRegisteredFeature("DepositPreauth"); uint256 const featureDepositPreauth = *getRegisteredFeature("DepositPreauth");
uint256 const fix1515 = *getRegisteredFeature("fix1515"); uint256 const fix1515 = *getRegisteredFeature("fix1515");
uint256 const fix1578 = *getRegisteredFeature("fix1578"); uint256 const fix1578 = *getRegisteredFeature("fix1578");
uint256 const featureMultiSignReserve = *getRegisteredFeature("MultiSignReserve");
} // ripple } // 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, alice) == 1);
BEAST_EXPECT (ownerCount (env, bob ) == 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, features};
Env env {*this};
auto const closeTime = auto const closeTime =
fix1449Time() + 100 * env.closed()->info().closeTimeResolution; fix1449Time() + 100 * env.closed()->info().closeTimeResolution;
env.close (closeTime); env.close (closeTime);
@@ -989,7 +996,11 @@ class Check_test : public beast::unit_test::suite
Account const demon {"demon", KeyType::ed25519}; Account const demon {"demon", KeyType::ed25519};
env (signers (bob, 2, {{bogie, 1}, {demon, 1}}), sig (bobby)); env (signers (bob, 2, {{bogie, 1}, {demon, 1}}), sig (bobby));
env.close(); 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. // bob uses his regular key to cash a check.
env (check::cash (bob, chkId1, (USD(1))), sig (bobby)); 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, alice).size() == 1);
BEAST_EXPECT (checksOnAccount (env, bob ).size() == 1); BEAST_EXPECT (checksOnAccount (env, bob ).size() == 1);
BEAST_EXPECT (ownerCount (env, alice) == 2); 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. // bob uses multisigning to cash a check.
std::uint64_t const baseFeeDrops {env.current()->fees().base}; 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, alice).size() == 0);
BEAST_EXPECT (checksOnAccount (env, bob ).size() == 0); BEAST_EXPECT (checksOnAccount (env, bob ).size() == 0);
BEAST_EXPECT (ownerCount (env, alice) == 1); 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"}; Account const zoe {"zoe"};
IOU const USD {gw["USD"]}; IOU const USD {gw["USD"]};
Env env {*this}; // featureMultiSign changes the reserve on a SignerList, so
auto const closeTime = // check both before and after.
fix1449Time() + 100 * env.closed()->info().closeTimeResolution; FeatureBitset const allSupported {supported_amendments()};
env.close (closeTime); 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. env.fund (XRP(1000), gw, alice, bob, zoe);
// 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 chkId2 {getCheckIndex (alice, env.seq (alice))}; // alice creates her checks ahead of time.
env (check::create (alice, bob, XRP(10))); // Three ordinary checks with no expiration.
env.close(); 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))}; uint256 const chkId2 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10))); env (check::create (alice, bob, XRP(10)));
env.close(); env.close();
// Three checks that expire in 10 minutes. uint256 const chkId3 {getCheckIndex (alice, env.seq (alice))};
using namespace std::chrono_literals; env (check::create (alice, bob, USD(10)));
uint256 const chkIdNotExp1 {getCheckIndex (alice, env.seq (alice))}; env.close();
env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s));
env.close();
uint256 const chkIdNotExp2 {getCheckIndex (alice, env.seq (alice))}; // Three checks that expire in 10 minutes.
env (check::create (alice, bob, USD(10)), expiration (env.now()+600s)); using namespace std::chrono_literals;
env.close(); 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))}; uint256 const chkIdNotExp2 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s)); env (check::create (alice, bob, USD(10)), expiration (env.now()+600s));
env.close(); env.close();
// Three checks that expire in one second. uint256 const chkIdNotExp3 {getCheckIndex (alice, env.seq (alice))};
uint256 const chkIdExp1 {getCheckIndex (alice, env.seq (alice))}; env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s));
env (check::create (alice, bob, USD(10)), expiration (env.now()+1s)); env.close();
env.close();
uint256 const chkIdExp2 {getCheckIndex (alice, env.seq (alice))}; // Three checks that expire in one second.
env (check::create (alice, bob, XRP(10)), expiration (env.now()+1s)); uint256 const chkIdExp1 {getCheckIndex (alice, env.seq (alice))};
env.close(); env (check::create (alice, bob, USD(10)), expiration (env.now()+1s));
env.close();
uint256 const chkIdExp3 {getCheckIndex (alice, env.seq (alice))}; uint256 const chkIdExp2 {getCheckIndex (alice, env.seq (alice))};
env (check::create (alice, bob, USD(10)), expiration (env.now()+1s)); env (check::create (alice, bob, XRP(10)), expiration (env.now()+1s));
env.close(); env.close();
// Two checks to cancel using a regular key and using multisigning. uint256 const chkIdExp3 {getCheckIndex (alice, env.seq (alice))};
uint256 const chkIdReg {getCheckIndex (alice, env.seq (alice))}; env (check::create (alice, bob, USD(10)), expiration (env.now()+1s));
env (check::create (alice, bob, USD(10))); env.close();
env.close();
uint256 const chkIdMSig {getCheckIndex (alice, env.seq (alice))}; // Two checks to cancel using a regular key and using multisigning.
env (check::create (alice, bob, XRP(10))); uint256 const chkIdReg {getCheckIndex (alice, env.seq (alice))};
env.close(); env (check::create (alice, bob, USD(10)));
BEAST_EXPECT (checksOnAccount (env, alice).size() == 11); env.close();
BEAST_EXPECT (ownerCount (env, alice) == 11);
// Creator, destination, and an outsider cancel the checks. uint256 const chkIdMSig {getCheckIndex (alice, env.seq (alice))};
env (check::cancel (alice, chkId1)); env (check::create (alice, bob, XRP(10)));
env.close(); env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 10); BEAST_EXPECT (checksOnAccount (env, alice).size() == 11);
BEAST_EXPECT (ownerCount (env, alice) == 10); BEAST_EXPECT (ownerCount (env, alice) == 11);
env (check::cancel (bob, chkId2)); // Creator, destination, and an outsider cancel the checks.
env.close(); env (check::cancel (alice, chkId1));
BEAST_EXPECT (checksOnAccount (env, alice).size() == 9); env.close();
BEAST_EXPECT (ownerCount (env, alice) == 9); BEAST_EXPECT (checksOnAccount (env, alice).size() == 10);
BEAST_EXPECT (ownerCount (env, alice) == 10);
env (check::cancel (zoe, chkId3), ter (tecNO_PERMISSION)); env (check::cancel (bob, chkId2));
env.close(); env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 9); BEAST_EXPECT (checksOnAccount (env, alice).size() == 9);
BEAST_EXPECT (ownerCount (env, alice) == 9); BEAST_EXPECT (ownerCount (env, alice) == 9);
// Creator, destination, and an outsider cancel unexpired checks. env (check::cancel (zoe, chkId3), ter (tecNO_PERMISSION));
env (check::cancel (alice, chkIdNotExp1)); env.close();
env.close(); BEAST_EXPECT (checksOnAccount (env, alice).size() == 9);
BEAST_EXPECT (checksOnAccount (env, alice).size() == 8); BEAST_EXPECT (ownerCount (env, alice) == 9);
BEAST_EXPECT (ownerCount (env, alice) == 8);
env (check::cancel (bob, chkIdNotExp2)); // Creator, destination, and an outsider cancel unexpired checks.
env.close(); env (check::cancel (alice, chkIdNotExp1));
BEAST_EXPECT (checksOnAccount (env, alice).size() == 7); env.close();
BEAST_EXPECT (ownerCount (env, alice) == 7); BEAST_EXPECT (checksOnAccount (env, alice).size() == 8);
BEAST_EXPECT (ownerCount (env, alice) == 8);
env (check::cancel (zoe, chkIdNotExp3), ter (tecNO_PERMISSION)); env (check::cancel (bob, chkIdNotExp2));
env.close(); env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 7); BEAST_EXPECT (checksOnAccount (env, alice).size() == 7);
BEAST_EXPECT (ownerCount (env, alice) == 7); BEAST_EXPECT (ownerCount (env, alice) == 7);
// Creator, destination, and an outsider cancel expired checks. env (check::cancel (zoe, chkIdNotExp3), ter (tecNO_PERMISSION));
env (check::cancel (alice, chkIdExp1)); env.close();
env.close(); BEAST_EXPECT (checksOnAccount (env, alice).size() == 7);
BEAST_EXPECT (checksOnAccount (env, alice).size() == 6); BEAST_EXPECT (ownerCount (env, alice) == 7);
BEAST_EXPECT (ownerCount (env, alice) == 6);
env (check::cancel (bob, chkIdExp2)); // Creator, destination, and an outsider cancel expired checks.
env.close(); env (check::cancel (alice, chkIdExp1));
BEAST_EXPECT (checksOnAccount (env, alice).size() == 5); env.close();
BEAST_EXPECT (ownerCount (env, alice) == 5); BEAST_EXPECT (checksOnAccount (env, alice).size() == 6);
BEAST_EXPECT (ownerCount (env, alice) == 6);
env (check::cancel (zoe, chkIdExp3)); env (check::cancel (bob, chkIdExp2));
env.close(); env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 4); BEAST_EXPECT (checksOnAccount (env, alice).size() == 5);
BEAST_EXPECT (ownerCount (env, alice) == 4); BEAST_EXPECT (ownerCount (env, alice) == 5);
// Use a regular key and also multisign to cancel checks. env (check::cancel (zoe, chkIdExp3));
Account const alie {"alie", KeyType::ed25519}; env.close();
env (regkey (alice, alie)); BEAST_EXPECT (checksOnAccount (env, alice).size() == 4);
env.close(); BEAST_EXPECT (ownerCount (env, alice) == 4);
Account const bogie {"bogie", KeyType::secp256k1}; // Use a regular key and also multisign to cancel checks.
Account const demon {"demon", KeyType::ed25519}; Account const alie {"alie", KeyType::ed25519};
env (signers (alice, 2, {{bogie, 1}, {demon, 1}}), sig (alie)); env (regkey (alice, alie));
env.close(); env.close();
// alice uses her regular key to cancel a check. Account const bogie {"bogie", KeyType::secp256k1};
env (check::cancel (alice, chkIdReg), sig (alie)); Account const demon {"demon", KeyType::ed25519};
env.close(); env (signers (alice, 2, {{bogie, 1}, {demon, 1}}), sig (alie));
BEAST_EXPECT (checksOnAccount (env, alice).size() == 3); env.close();
BEAST_EXPECT (ownerCount (env, alice) == 7);
// alice uses multisigning to cancel a check. // If featureMultiSignReserve is enabled then alices's signer list
std::uint64_t const baseFeeDrops {env.current()->fees().base}; // has an owner count of 1, otherwise it's 4.
env (check::cancel (alice, chkIdMSig), int const signersCount {features[featureMultiSignReserve] ? 1 : 4};
msig (bogie, demon), fee (3 * baseFeeDrops));
env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 2);
BEAST_EXPECT (ownerCount (env, alice) == 6);
// Creator and destination cancel the remaining unexpired checks. // alice uses her regular key to cancel a check.
env (check::cancel (alice, chkId3), sig (alice)); env (check::cancel (alice, chkIdReg), sig (alie));
env.close(); env.close();
BEAST_EXPECT (checksOnAccount (env, alice).size() == 1); BEAST_EXPECT (checksOnAccount (env, alice).size() == 3);
BEAST_EXPECT (ownerCount (env, alice) == 5); BEAST_EXPECT (ownerCount (env, alice) == signersCount + 3);
env (check::cancel (bob, chkIdNotExp3)); // alice uses multisigning to cancel a check.
env.close(); std::uint64_t const baseFeeDrops {env.current()->fees().base};
BEAST_EXPECT (checksOnAccount (env, alice).size() == 0); env (check::cancel (alice, chkIdMSig),
BEAST_EXPECT (ownerCount (env, alice) == 4); 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() void testCancelInvalid()

View File

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