mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Merge branch 'develop' into ximinez/online-delete-gaps
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
81
src/test/jtx/directory.h
Normal 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
|
||||
@@ -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)
|
||||
|
||||
145
src/test/jtx/impl/directory.cpp
Normal file
145
src/test/jtx/impl/directory.cpp
Normal 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
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user