Merge branch 'develop' into ximinez/online-delete-gaps

This commit is contained in:
Ed Hennis
2025-10-31 12:51:24 -04:00
committed by GitHub
43 changed files with 1031 additions and 314 deletions

View File

@@ -1867,49 +1867,35 @@ class Check_test : public beast::unit_test::suite
}
void
testFix1623Enable(FeatureBitset features)
testDeliveredAmountForCheckCashTxn(FeatureBitset features)
{
testcase("Fix1623 enable");
testcase("DeliveredAmount For CheckCash Txn");
using namespace test::jtx;
Account const alice{"alice"};
Account const bob{"bob"};
auto testEnable = [this](
FeatureBitset const& features, bool hasFields) {
// Unless fix1623 is enabled a "tx" RPC command should return
// neither "DeliveredAmount" nor "delivered_amount" on a CheckCash
// transaction.
Account const alice{"alice"};
Account const bob{"bob"};
Env env{*this, features};
Env env{*this, features};
env.fund(XRP(1000), alice, bob);
env.close();
env.fund(XRP(1000), alice, bob);
env.close();
uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
env(check::create(alice, bob, XRP(200)));
env.close();
uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
env(check::create(alice, bob, XRP(200)));
env.close();
env(check::cash(bob, chkId, check::DeliverMin(XRP(100))));
env(check::cash(bob, chkId, check::DeliverMin(XRP(100))));
// Get the hash for the most recent transaction.
std::string const txHash{
env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
// Get the hash for the most recent transaction.
std::string const txHash{
env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
env.close();
Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
// DeliveredAmount and delivered_amount are either present or
// not present in the metadata returned by "tx" based on fix1623.
env.close();
Json::Value const meta =
env.rpc("tx", txHash)[jss::result][jss::meta];
BEAST_EXPECT(
meta.isMember(sfDeliveredAmount.jsonName) == hasFields);
BEAST_EXPECT(meta.isMember(jss::delivered_amount) == hasFields);
};
// Run both the disabled and enabled cases.
testEnable(features - fix1623, false);
testEnable(features, true);
// DeliveredAmount and delivered_amount are present.
BEAST_EXPECT(meta.isMember(sfDeliveredAmount.jsonName));
BEAST_EXPECT(meta.isMember(jss::delivered_amount));
}
void
@@ -2711,7 +2697,7 @@ class Check_test : public beast::unit_test::suite
testCashInvalid(features);
testCancelValid(features);
testCancelInvalid(features);
testFix1623Enable(features);
testDeliveredAmountForCheckCashTxn(features);
testWithTickets(features);
}

View File

@@ -558,6 +558,39 @@ struct Credentials_test : public beast::unit_test::suite
jle[jss::result][jss::node]["CredentialType"] ==
strHex(std::string_view(credType)));
}
{
testcase("Credentials fail, directory full");
std::uint32_t const issuerSeq{env.seq(issuer) + 1};
env(ticket::create(issuer, 63));
env.close();
// Everything below can only be tested on open ledger.
auto const res1 = directory::bumpLastPage(
env,
directory::maximumPageIndex(env),
keylet::ownerDir(issuer.id()),
directory::adjustOwnerNode);
BEAST_EXPECT(res1);
auto const jv = credentials::create(issuer, subject, credType);
env(jv, ter(tecDIR_FULL));
// Free one directory entry by using a ticket
env(noop(issuer), ticket::use(issuerSeq + 40));
// Fill subject directory
env(ticket::create(subject, 63));
auto const res2 = directory::bumpLastPage(
env,
directory::maximumPageIndex(env),
keylet::ownerDir(subject.id()),
directory::adjustOwnerNode);
BEAST_EXPECT(res2);
env(jv, ter(tecDIR_FULL));
// End test
env.close();
}
}
{
@@ -1084,6 +1117,7 @@ struct Credentials_test : public beast::unit_test::suite
testSuccessful(all);
testCredentialsDelete(all);
testCreateFailed(all);
testCreateFailed(all - fixDirectoryLimit);
testAcceptFailed(all);
testDeleteFailed(all);
testFeatureFailed(all - featureCredentials);

View File

@@ -5294,14 +5294,12 @@ public:
{
using namespace jtx;
static FeatureBitset const all{testable_amendments()};
static FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval};
static FeatureBitset const immediateOfferKilled{
featureImmediateOfferKilled};
FeatureBitset const fillOrKill{fixFillOrKill};
FeatureBitset const permDEX{featurePermissionedDEX};
static std::array<FeatureBitset, 6> const feats{
all - takerDryOffer - immediateOfferKilled - permDEX,
static std::array<FeatureBitset, 5> const feats{
all - immediateOfferKilled - permDEX,
all - immediateOfferKilled - fillOrKill - permDEX,
all - fillOrKill - permDEX,
@@ -5323,7 +5321,7 @@ public:
}
};
class OfferWTakerDryOffer_test : public OfferBaseUtil_test
class OfferWOSmallQOffers_test : public OfferBaseUtil_test
{
void
run() override
@@ -5332,7 +5330,7 @@ class OfferWTakerDryOffer_test : public OfferBaseUtil_test
}
};
class OfferWOSmallQOffers_test : public OfferBaseUtil_test
class OfferWOFillOrKill_test : public OfferBaseUtil_test
{
void
run() override
@@ -5341,7 +5339,7 @@ class OfferWOSmallQOffers_test : public OfferBaseUtil_test
}
};
class OfferWOFillOrKill_test : public OfferBaseUtil_test
class OfferWOPermDEX_test : public OfferBaseUtil_test
{
void
run() override
@@ -5350,21 +5348,12 @@ class OfferWOFillOrKill_test : public OfferBaseUtil_test
}
};
class OfferWOPermDEX_test : public OfferBaseUtil_test
{
void
run() override
{
OfferBaseUtil_test::run(4);
}
};
class OfferAllFeatures_test : public OfferBaseUtil_test
{
void
run() override
{
OfferBaseUtil_test::run(5, true);
OfferBaseUtil_test::run(4, true);
}
};
@@ -5376,7 +5365,6 @@ class Offer_manual_test : public OfferBaseUtil_test
using namespace jtx;
FeatureBitset const all{testable_amendments()};
FeatureBitset const immediateOfferKilled{featureImmediateOfferKilled};
FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval};
FeatureBitset const fillOrKill{fixFillOrKill};
FeatureBitset const permDEX{featurePermissionedDEX};
@@ -5385,13 +5373,10 @@ class Offer_manual_test : public OfferBaseUtil_test
testAll(all - fillOrKill - permDEX);
testAll(all - permDEX);
testAll(all);
testAll(all - takerDryOffer - permDEX);
}
};
BEAST_DEFINE_TESTSUITE_PRIO(OfferBaseUtil, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(OfferWTakerDryOffer, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(OfferWOSmallQOffers, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(OfferWOFillOrKill, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(OfferWOPermDEX, app, ripple, 2);

View File

@@ -39,6 +39,7 @@
#include <test/jtx/delivermin.h>
#include <test/jtx/deposit.h>
#include <test/jtx/did.h>
#include <test/jtx/directory.h>
#include <test/jtx/domain.h>
#include <test/jtx/escrow.h>
#include <test/jtx/fee.h>

View File

@@ -54,7 +54,11 @@ struct JTx
bool fill_sig = true;
bool fill_netid = true;
std::shared_ptr<STTx const> stx;
std::function<void(Env&, JTx&)> signer;
// Functions that sign the transaction from the Account
std::vector<std::function<void(Env&, JTx&)>> mainSigners;
// Functions that sign something else after the mainSigners, such as
// sfCounterpartySignature
std::vector<std::function<void(Env&, JTx&)>> postSigners;
JTx() = default;
JTx(JTx const&) = default;

View File

@@ -618,7 +618,7 @@ create(
} // namespace check
static constexpr FeeLevel64 baseFeeLevel{256};
static constexpr FeeLevel64 baseFeeLevel{TxQ::baseLevel};
static constexpr FeeLevel64 minEscalationFeeLevel = baseFeeLevel * 500;
template <class Suite>

View File

@@ -213,14 +213,16 @@ public:
template <std::integral T>
PrettyAmount
operator()(T v) const
operator()(T v, Number::rounding_mode rounding = Number::getround()) const
{
return operator()(Number(v));
return operator()(Number(v), rounding);
}
PrettyAmount
operator()(Number v) const
operator()(Number v, Number::rounding_mode rounding = Number::getround())
const
{
NumberRoundModeGuard mg(rounding);
STAmount amount{asset_, v * scale_};
return {amount, ""};
}

81
src/test/jtx/directory.h Normal file
View File

@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 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_TEST_JTX_DIRECTORY_H_INCLUDED
#define RIPPLE_TEST_JTX_DIRECTORY_H_INCLUDED
#include <test/jtx/Env.h>
#include <xrpl/basics/Expected.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <cstdint>
#include <limits>
namespace ripple::test::jtx {
/** Directory operations. */
namespace directory {
enum Error {
DirectoryRootNotFound,
DirectoryTooSmall,
DirectoryPageDuplicate,
DirectoryPageNotFound,
InvalidLastPage,
AdjustmentError
};
/// Move the position of the last page in the user's directory on open ledger to
/// newLastPage. Requirements:
/// - directory must have at least two pages (root and one more)
/// - adjust should be used to update owner nodes of the objects affected
/// - newLastPage must be greater than index of the last page in the directory
///
/// Use this to test tecDIR_FULL errors in open ledger.
/// NOTE: effects will be DISCARDED on env.close()
auto
bumpLastPage(
Env& env,
std::uint64_t newLastPage,
Keylet directory,
std::function<bool(ApplyView&, uint256, std::uint64_t)> adjust)
-> Expected<void, Error>;
/// Implementation of adjust for the most common ledger entry, i.e. one where
/// page index is stored in sfOwnerNode (and only there). Pass this function
/// to bumpLastPage if the last page of directory has only objects
/// of this kind (e.g. ticket, DID, offer, deposit preauth, MPToken etc.)
bool
adjustOwnerNode(ApplyView& view, uint256 key, std::uint64_t page);
inline auto
maximumPageIndex(Env const& env) -> std::uint64_t
{
if (env.enabled(fixDirectoryLimit))
return std::numeric_limits<std::uint64_t>::max();
return dirNodeMaxPages - 1;
}
} // namespace directory
} // namespace ripple::test::jtx
#endif

View File

@@ -34,6 +34,7 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/scope.h>
#include <xrpl/json/to_string.h>
#include <xrpl/net/HTTPClient.h>
#include <xrpl/protocol/ErrorCodes.h>
@@ -531,8 +532,22 @@ void
Env::autofill_sig(JTx& jt)
{
auto& jv = jt.jv;
if (jt.signer)
return jt.signer(*this, jt);
scope_success success([&]() {
// Call all the post-signers after the main signers or autofill are done
for (auto const& signer : jt.postSigners)
signer(*this, jt);
});
// Call all the main signers
if (!jt.mainSigners.empty())
{
for (auto const& signer : jt.mainSigners)
signer(*this, jt);
return;
}
// If the sig is still needed, get it here.
if (!jt.fill_sig)
return;
auto const account = jv.isMember(sfDelegate.jsonName)

View File

@@ -0,0 +1,145 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 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 <test/jtx/directory.h>
#include <xrpl/ledger/Sandbox.h>
namespace ripple::test::jtx {
/** Directory operations. */
namespace directory {
auto
bumpLastPage(
Env& env,
std::uint64_t newLastPage,
Keylet directory,
std::function<bool(ApplyView&, uint256, std::uint64_t)> adjust)
-> Expected<void, Error>
{
Expected<void, Error> res{};
env.app().openLedger().modify(
[&](OpenView& view, beast::Journal j) -> bool {
Sandbox sb(&view, tapNONE);
// Find the root page
auto sleRoot = sb.peek(directory);
if (!sleRoot)
{
res = Unexpected<Error>(DirectoryRootNotFound);
return false;
}
// Find last page
auto const lastIndex = sleRoot->getFieldU64(sfIndexPrevious);
if (lastIndex == 0)
{
res = Unexpected<Error>(DirectoryTooSmall);
return false;
}
if (sb.exists(keylet::page(directory, newLastPage)))
{
res = Unexpected<Error>(DirectoryPageDuplicate);
return false;
}
if (lastIndex >= newLastPage)
{
res = Unexpected<Error>(InvalidLastPage);
return false;
}
auto slePage = sb.peek(keylet::page(directory, lastIndex));
if (!slePage)
{
res = Unexpected<Error>(DirectoryPageNotFound);
return false;
}
// Copy its data and delete the page
auto indexes = slePage->getFieldV256(sfIndexes);
auto prevIndex = slePage->at(~sfIndexPrevious);
auto owner = slePage->at(~sfOwner);
sb.erase(slePage);
// Create new page to replace slePage
auto sleNew =
std::make_shared<SLE>(keylet::page(directory, newLastPage));
sleNew->setFieldH256(sfRootIndex, directory.key);
sleNew->setFieldV256(sfIndexes, indexes);
if (owner)
sleNew->setAccountID(sfOwner, *owner);
if (prevIndex)
sleNew->setFieldU64(sfIndexPrevious, *prevIndex);
sb.insert(sleNew);
// Adjust root previous and previous node's next
sleRoot->setFieldU64(sfIndexPrevious, newLastPage);
if (prevIndex.value_or(0) == 0)
sleRoot->setFieldU64(sfIndexNext, newLastPage);
else
{
auto slePrev = sb.peek(keylet::page(directory, *prevIndex));
if (!slePrev)
{
res = Unexpected<Error>(DirectoryPageNotFound);
return false;
}
slePrev->setFieldU64(sfIndexNext, newLastPage);
sb.update(slePrev);
}
sb.update(sleRoot);
// Fixup page numbers in the objects referred by indexes
if (adjust)
for (auto const key : indexes)
{
if (!adjust(sb, key, newLastPage))
{
res = Unexpected<Error>(AdjustmentError);
return false;
}
}
sb.apply(view);
return true;
});
return res;
}
bool
adjustOwnerNode(ApplyView& view, uint256 key, std::uint64_t page)
{
auto sle = view.peek({ltANY, key});
if (sle && sle->isFieldPresent(sfOwnerNode))
{
sle->setFieldU64(sfOwnerNode, page);
view.update(sle);
return true;
}
return false;
}
} // namespace directory
} // namespace ripple::test::jtx

View File

@@ -507,7 +507,7 @@ MPTTester::getFlags(std::optional<Account> const& holder) const
}
MPT
MPTTester::operator[](std::string const& name)
MPTTester::operator[](std::string const& name) const
{
return MPT(name, issuanceID());
}

View File

@@ -69,8 +69,15 @@ void
msig::operator()(Env& env, JTx& jt) const
{
auto const mySigners = signers;
jt.signer = [mySigners, &env](Env&, JTx& jtx) {
jtx[sfSigningPubKey.getJsonName()] = "";
auto callback = [subField = subField, mySigners, &env](Env&, JTx& jtx) {
// Where to put the signature. Supports sfCounterPartySignature.
auto& sigObject = subField ? jtx[*subField] : jtx.jv;
// The signing pub key is only required at the top level.
if (!subField)
sigObject[sfSigningPubKey] = "";
else if (sigObject.isNull())
sigObject = Json::Value(Json::objectValue);
std::optional<STObject> st;
try
{
@@ -81,7 +88,7 @@ msig::operator()(Env& env, JTx& jt) const
env.test.log << pretty(jtx.jv) << std::endl;
Rethrow();
}
auto& js = jtx[sfSigners.getJsonName()];
auto& js = sigObject[sfSigners];
for (std::size_t i = 0; i < mySigners.size(); ++i)
{
auto const& e = mySigners[i];
@@ -96,6 +103,10 @@ msig::operator()(Env& env, JTx& jt) const
strHex(Slice{sig.data(), sig.size()});
}
};
if (!subField)
jt.mainSigners.emplace_back(callback);
else
jt.postSigners.emplace_back(callback);
}
} // namespace jtx

View File

@@ -29,12 +29,22 @@ sig::operator()(Env&, JTx& jt) const
{
if (!manual_)
return;
jt.fill_sig = false;
if (!subField_)
jt.fill_sig = false;
if (account_)
{
// VFALCO Inefficient pre-C++14
auto const account = *account_;
jt.signer = [account](Env&, JTx& jtx) { jtx::sign(jtx.jv, account); };
auto callback = [subField = subField_, account](Env&, JTx& jtx) {
// Where to put the signature. Supports sfCounterPartySignature.
auto& sigObject = subField ? jtx[*subField] : jtx.jv;
jtx::sign(jtx.jv, account, sigObject);
};
if (!subField_)
jt.mainSigners.emplace_back(callback);
else
jt.postSigners.emplace_back(callback);
}
}

View File

@@ -44,14 +44,20 @@ parse(Json::Value const& jv)
}
void
sign(Json::Value& jv, Account const& account)
sign(Json::Value& jv, Account const& account, Json::Value& sigObject)
{
jv[jss::SigningPubKey] = strHex(account.pk().slice());
sigObject[jss::SigningPubKey] = strHex(account.pk().slice());
Serializer ss;
ss.add32(HashPrefix::txSign);
parse(jv).addWithoutSigningFields(ss);
auto const sig = ripple::sign(account.pk(), account.sk(), ss.slice());
jv[jss::TxnSignature] = strHex(Slice{sig.data(), sig.size()});
sigObject[jss::TxnSignature] = strHex(Slice{sig.data(), sig.size()});
}
void
sign(Json::Value& jv, Account const& account)
{
sign(jv, account, jv);
}
void

View File

@@ -235,7 +235,7 @@ public:
getBalance(Account const& account) const;
MPT
operator[](std::string const& name);
operator[](std::string const& name) const;
private:
using SLEP = std::shared_ptr<SLE const>;

View File

@@ -67,18 +67,63 @@ class msig
{
public:
std::vector<Reg> signers;
/** Alternative transaction object field in which to place the signer list.
*
* subField is only supported if an account_ is provided as well.
*/
SField const* const subField = nullptr;
/// Used solely as a convenience placeholder for ctors that do _not_ specify
/// a subfield.
static constexpr SField* const topLevel = nullptr;
msig(std::vector<Reg> signers_) : signers(std::move(signers_))
msig(SField const* subField_, std::vector<Reg> signers_)
: signers(std::move(signers_)), subField(subField_)
{
sortSigners(signers);
}
msig(SField const& subField_, std::vector<Reg> signers_)
: msig{&subField_, signers_}
{
}
msig(std::vector<Reg> signers_) : msig(topLevel, signers_)
{
}
template <class AccountType, class... Accounts>
requires std::convertible_to<AccountType, Reg>
explicit msig(AccountType&& a0, Accounts&&... aN)
: signers{std::forward<AccountType>(a0), std::forward<Accounts>(aN)...}
explicit msig(SField const* subField_, AccountType&& a0, Accounts&&... aN)
: msig{
subField_,
std::vector<Reg>{
std::forward<AccountType>(a0),
std::forward<Accounts>(aN)...}}
{
}
template <class AccountType, class... Accounts>
requires std::convertible_to<AccountType, Reg>
explicit msig(SField const& subField_, AccountType&& a0, Accounts&&... aN)
: msig{
&subField_,
std::vector<Reg>{
std::forward<AccountType>(a0),
std::forward<Accounts>(aN)...}}
{
}
template <class AccountType, class... Accounts>
requires(
std::convertible_to<AccountType, Reg> &&
!std::is_same_v<AccountType, SField*>)
explicit msig(AccountType&& a0, Accounts&&... aN)
: msig{
topLevel,
std::vector<Reg>{
std::forward<AccountType>(a0),
std::forward<Accounts>(aN)...}}
{
sortSigners(signers);
}
void

View File

@@ -35,7 +35,20 @@ class sig
{
private:
bool manual_ = true;
/** Alternative transaction object field in which to place the signature.
*
* subField is only supported if an account_ is provided as well.
*/
SField const* const subField_ = nullptr;
/** Account that will generate the signature.
*
* If not provided, no signature will be added by this helper. See also
* Env::autofill_sig.
*/
std::optional<Account> account_;
/// Used solely as a convenience placeholder for ctors that do _not_ specify
/// a subfield.
static constexpr SField* const topLevel = nullptr;
public:
explicit sig(autofill_t) : manual_(false)
@@ -46,7 +59,17 @@ public:
{
}
explicit sig(Account const& account) : account_(account)
explicit sig(SField const* subField, Account const& account)
: subField_(subField), account_(account)
{
}
explicit sig(SField const& subField, Account const& account)
: sig(&subField, account)
{
}
explicit sig(Account const& account) : sig(topLevel, account)
{
}

View File

@@ -51,6 +51,12 @@ struct parse_error : std::logic_error
STObject
parse(Json::Value const& jv);
/** Sign automatically into a specific Json field of the jv object.
@note This only works on accounts with multi-signing off.
*/
void
sign(Json::Value& jv, Account const& account, Json::Value& sigObject);
/** Sign automatically.
@note This only works on accounts with multi-signing off.
*/

View File

@@ -22,9 +22,11 @@
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <limits>
namespace ripple {
namespace test {
@@ -489,6 +491,91 @@ struct Directory_test : public beast::unit_test::suite
}
}
void
testDirectoryFull()
{
using namespace test::jtx;
Account alice("alice");
auto const testCase = [&, this](FeatureBitset features, auto setup) {
using namespace test::jtx;
Env env(*this, features);
env.fund(XRP(20000), alice);
env.close();
auto const [lastPage, full] = setup(env);
// Populate root page and last page
for (int i = 0; i < 63; ++i)
env(credentials::create(alice, alice, std::to_string(i)));
env.close();
// NOTE, everything below can only be tested on open ledger because
// there is no transaction type to express what bumpLastPage does.
// Bump position of last page from 1 to highest possible
auto const res = directory::bumpLastPage(
env,
lastPage,
keylet::ownerDir(alice.id()),
[lastPage, this](
ApplyView& view, uint256 key, std::uint64_t page) {
auto sle = view.peek({ltCREDENTIAL, key});
if (!BEAST_EXPECT(sle))
return false;
BEAST_EXPECT(page == lastPage);
sle->setFieldU64(sfIssuerNode, page);
// sfSubjectNode is not set in self-issued credentials
view.update(sle);
return true;
});
BEAST_EXPECT(res);
// Create one more credential
env(credentials::create(alice, alice, std::to_string(63)));
// Not enough space for another object if full
auto const expected = full ? ter{tecDIR_FULL} : ter{tesSUCCESS};
env(credentials::create(alice, alice, "foo"), expected);
// Destroy all objects in directory
for (int i = 0; i < 64; ++i)
env(credentials::deleteCred(
alice, alice, alice, std::to_string(i)));
if (!full)
env(credentials::deleteCred(alice, alice, alice, "foo"));
// Verify directory is empty.
auto const sle = env.le(keylet::ownerDir(alice.id()));
BEAST_EXPECT(sle == nullptr);
// Test completed
env.close();
};
testCase(
testable_amendments() - fixDirectoryLimit,
[this](Env&) -> std::tuple<std::uint64_t, bool> {
testcase("directory full without fixDirectoryLimit");
return {dirNodeMaxPages - 1, true};
});
testCase(
testable_amendments(), //
[this](Env&) -> std::tuple<std::uint64_t, bool> {
testcase("directory not full with fixDirectoryLimit");
return {dirNodeMaxPages - 1, false};
});
testCase(
testable_amendments(), //
[this](Env&) -> std::tuple<std::uint64_t, bool> {
testcase("directory full with fixDirectoryLimit");
return {std::numeric_limits<std::uint64_t>::max(), true};
});
}
void
run() override
{
@@ -497,6 +584,7 @@ struct Directory_test : public beast::unit_test::suite
testRipd1353();
testEmptyChain();
testPreviousTxnID();
testDirectoryFull();
}
};

View File

@@ -144,8 +144,8 @@ class Feature_test : public beast::unit_test::suite
BEAST_EXPECT(featureToName(featureFlow) == "Flow");
BEAST_EXPECT(featureToName(featureNegativeUNL) == "NegativeUNL");
BEAST_EXPECT(
featureToName(fixTakerDryOfferRemoval) ==
"fixTakerDryOfferRemoval");
featureToName(fixIncludeKeyletFields) == "fixIncludeKeyletFields");
BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow");
}
void

View File

@@ -4643,10 +4643,34 @@ static RPCCallTestData const rpcCallTestArray[] = {
}
]
})"},
{"sign: too many arguments.",
{"sign: offline flag with signature_target.",
__LINE__,
{"sign", "my_secret", R"({"json_argument":true})", "offline", "extra"},
RPCCallTestData::no_exception,
R"({
"method" : "sign",
"params" : [
{
"api_version" : %API_VER%,
"offline" : true,
"secret" : "my_secret",
"signature_target" : "extra",
"tx_json" :
{
"json_argument" : true
}
}
]
})"},
{"sign: too many arguments.",
__LINE__,
{"sign",
"my_secret",
R"({"json_argument":true})",
"offline",
"CounterpartySignature",
"extra"},
RPCCallTestData::no_exception,
R"({
"method" : "sign",
"params" : [
@@ -4675,20 +4699,24 @@ static RPCCallTestData const rpcCallTestArray[] = {
}
]
})"},
{"sign: invalid final argument.",
{"sign: misspelled offline flag interpreted as signature_target.",
__LINE__,
{"sign", "my_secret", R"({"json_argument":true})", "offlin"},
RPCCallTestData::no_exception,
R"({
"method" : "sign",
"params" : [
{
"error" : "invalidParams",
"error_code" : 31,
"error_message" : "Invalid parameters."
}
]
})"},
"method" : "sign",
"params" : [
{
"api_version" : %API_VER%,
"secret" : "my_secret",
"signature_target" : "offlin",
"tx_json" :
{
"json_argument" : true
}
}
]
})"},
// sign_for
// --------------------------------------------------------------------
@@ -4880,10 +4908,34 @@ static RPCCallTestData const rpcCallTestArray[] = {
}
]
})"},
{"submit: too many arguments.",
{"submit: offline flag with signature_target.",
__LINE__,
{"submit", "my_secret", R"({"json_argument":true})", "offline", "extra"},
RPCCallTestData::no_exception,
R"({
"method" : "submit",
"params" : [
{
"api_version" : %API_VER%,
"offline" : true,
"secret" : "my_secret",
"signature_target" : "extra",
"tx_json" :
{
"json_argument" : true
}
}
]
})"},
{"submit: too many arguments.",
__LINE__,
{"submit",
"my_secret",
R"({"json_argument":true})",
"offline",
"CounterpartySignature",
"extra"},
RPCCallTestData::no_exception,
R"({
"method" : "submit",
"params" : [
@@ -4912,19 +4964,23 @@ static RPCCallTestData const rpcCallTestArray[] = {
}
]
})"},
{"submit: last argument not \"offline\".",
{"submit: misspelled offline flag interpreted as signature_target.",
__LINE__,
{"submit", "my_secret", R"({"json_argument":true})", "offlne"},
RPCCallTestData::no_exception,
R"({
"method" : "submit",
"params" : [
{
"error" : "invalidParams",
"error_code" : 31,
"error_message" : "Invalid parameters."
}
]
"method" : "submit",
"params" : [
{
"api_version" : %API_VER%,
"secret" : "my_secret",
"signature_target" : "offlne",
"tx_json" :
{
"json_argument" : true
}
}
]
})"},
// submit_multisigned