Skip to content

Commit 283bc3e

Browse files
Bronekvvysokikh1
authored andcommitted
Remove directory size limit (#5935)
This change introduces the `fixDirectoryLimit` amendment to remove the directory pages limit. We found that the directory size limit is easier to hit than originally assumed, and there is no good reason to keep this limit, since the object reserve provides the necessary incentive to avoid creating unnecessary objects on the ledger.
1 parent ebc2a9a commit 283bc3e

File tree

8 files changed

+371
-4
lines changed

8 files changed

+371
-4
lines changed

include/xrpl/protocol/Protocol.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ std::size_t constexpr oversizeMetaDataCap = 5200;
5656
/** The maximum number of entries per directory page */
5757
std::size_t constexpr dirNodeMaxEntries = 32;
5858

59-
/** The maximum number of pages allowed in a directory */
59+
/** The maximum number of pages allowed in a directory
60+
61+
Made obsolete by fixDirectoryLimit amendment.
62+
*/
6063
std::uint64_t constexpr dirNodeMaxPages = 262144;
6164

6265
/** The maximum number of items in an NFT page */

include/xrpl/protocol/detail/features.macro

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@
2929

3030
// Add new amendments to the top of this list.
3131
// Keep it sorted in reverse chronological order.
32-
// If you add an amendment here, then do not forget to increment `numFeatures`
33-
// in include/xrpl/protocol/Feature.h.
3432

33+
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
3534
XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo)
3635
XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo)
3736
XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo)

src/test/app/Credentials_test.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,39 @@ struct Credentials_test : public beast::unit_test::suite
568568
jle[jss::result][jss::node]["CredentialType"] ==
569569
strHex(std::string_view(credType)));
570570
}
571+
572+
{
573+
testcase("Credentials fail, directory full");
574+
std::uint32_t const issuerSeq{env.seq(issuer) + 1};
575+
env(ticket::create(issuer, 63));
576+
env.close();
577+
578+
// Everything below can only be tested on open ledger.
579+
auto const res1 = directory::bumpLastPage(
580+
env,
581+
directory::maximumPageIndex(env),
582+
keylet::ownerDir(issuer.id()),
583+
directory::adjustOwnerNode);
584+
BEAST_EXPECT(res1);
585+
586+
auto const jv = credentials::create(issuer, subject, credType);
587+
env(jv, ter(tecDIR_FULL));
588+
// Free one directory entry by using a ticket
589+
env(noop(issuer), ticket::use(issuerSeq + 40));
590+
591+
// Fill subject directory
592+
env(ticket::create(subject, 63));
593+
auto const res2 = directory::bumpLastPage(
594+
env,
595+
directory::maximumPageIndex(env),
596+
keylet::ownerDir(subject.id()),
597+
directory::adjustOwnerNode);
598+
BEAST_EXPECT(res2);
599+
env(jv, ter(tecDIR_FULL));
600+
601+
// End test
602+
env.close();
603+
}
571604
}
572605

573606
{
@@ -1094,6 +1127,7 @@ struct Credentials_test : public beast::unit_test::suite
10941127
testSuccessful(all);
10951128
testCredentialsDelete(all);
10961129
testCreateFailed(all);
1130+
testCreateFailed(all - fixDirectoryLimit);
10971131
testAcceptFailed(all);
10981132
testDeleteFailed(all);
10991133
testFeatureFailed(all - featureCredentials);

src/test/jtx.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <test/jtx/delivermin.h>
4040
#include <test/jtx/deposit.h>
4141
#include <test/jtx/did.h>
42+
#include <test/jtx/directory.h>
4243
#include <test/jtx/domain.h>
4344
#include <test/jtx/escrow.h>
4445
#include <test/jtx/fee.h>

src/test/jtx/directory.h

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//------------------------------------------------------------------------------
2+
/*
3+
This file is part of rippled: https://github.com/ripple/rippled
4+
Copyright (c) 2025 Ripple Labs Inc.
5+
6+
Permission to use, copy, modify, and/or distribute this software for any
7+
purpose with or without fee is hereby granted, provided that the above
8+
copyright notice and this permission notice appear in all copies.
9+
10+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13+
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17+
*/
18+
//==============================================================================
19+
20+
#ifndef RIPPLE_TEST_JTX_DIRECTORY_H_INCLUDED
21+
#define RIPPLE_TEST_JTX_DIRECTORY_H_INCLUDED
22+
23+
#include <test/jtx/Env.h>
24+
25+
#include <xrpl/basics/Expected.h>
26+
#include <xrpl/protocol/Feature.h>
27+
#include <xrpl/protocol/Indexes.h>
28+
29+
#include <cstdint>
30+
#include <limits>
31+
32+
namespace ripple::test::jtx {
33+
34+
/** Directory operations. */
35+
namespace directory {
36+
37+
enum Error {
38+
DirectoryRootNotFound,
39+
DirectoryTooSmall,
40+
DirectoryPageDuplicate,
41+
DirectoryPageNotFound,
42+
InvalidLastPage,
43+
AdjustmentError
44+
};
45+
46+
/// Move the position of the last page in the user's directory on open ledger to
47+
/// newLastPage. Requirements:
48+
/// - directory must have at least two pages (root and one more)
49+
/// - adjust should be used to update owner nodes of the objects affected
50+
/// - newLastPage must be greater than index of the last page in the directory
51+
///
52+
/// Use this to test tecDIR_FULL errors in open ledger.
53+
/// NOTE: effects will be DISCARDED on env.close()
54+
auto
55+
bumpLastPage(
56+
Env& env,
57+
std::uint64_t newLastPage,
58+
Keylet directory,
59+
std::function<bool(ApplyView&, uint256, std::uint64_t)> adjust)
60+
-> Expected<void, Error>;
61+
62+
/// Implementation of adjust for the most common ledger entry, i.e. one where
63+
/// page index is stored in sfOwnerNode (and only there). Pass this function
64+
/// to bumpLastPage if the last page of directory has only objects
65+
/// of this kind (e.g. ticket, DID, offer, deposit preauth, MPToken etc.)
66+
bool
67+
adjustOwnerNode(ApplyView& view, uint256 key, std::uint64_t page);
68+
69+
inline auto
70+
maximumPageIndex(Env const& env) -> std::uint64_t
71+
{
72+
if (env.enabled(fixDirectoryLimit))
73+
return std::numeric_limits<std::uint64_t>::max();
74+
return dirNodeMaxPages - 1;
75+
}
76+
77+
} // namespace directory
78+
79+
} // namespace ripple::test::jtx
80+
81+
#endif

src/test/jtx/impl/directory.cpp

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
//------------------------------------------------------------------------------
2+
/*
3+
This file is part of rippled: https://github.com/ripple/rippled
4+
Copyright (c) 2025 Ripple Labs Inc.
5+
6+
Permission to use, copy, modify, and/or distribute this software for any
7+
purpose with or without fee is hereby granted, provided that the above
8+
copyright notice and this permission notice appear in all copies.
9+
10+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13+
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17+
*/
18+
//==============================================================================
19+
20+
#include <test/jtx/directory.h>
21+
22+
#include <xrpld/ledger/Sandbox.h>
23+
24+
namespace ripple::test::jtx {
25+
26+
/** Directory operations. */
27+
namespace directory {
28+
29+
auto
30+
bumpLastPage(
31+
Env& env,
32+
std::uint64_t newLastPage,
33+
Keylet directory,
34+
std::function<bool(ApplyView&, uint256, std::uint64_t)> adjust)
35+
-> Expected<void, Error>
36+
{
37+
Expected<void, Error> res{};
38+
env.app().openLedger().modify(
39+
[&](OpenView& view, beast::Journal j) -> bool {
40+
Sandbox sb(&view, tapNONE);
41+
42+
// Find the root page
43+
auto sleRoot = sb.peek(directory);
44+
if (!sleRoot)
45+
{
46+
res = Unexpected<Error>(DirectoryRootNotFound);
47+
return false;
48+
}
49+
50+
// Find last page
51+
auto const lastIndex = sleRoot->getFieldU64(sfIndexPrevious);
52+
if (lastIndex == 0)
53+
{
54+
res = Unexpected<Error>(DirectoryTooSmall);
55+
return false;
56+
}
57+
58+
if (sb.exists(keylet::page(directory, newLastPage)))
59+
{
60+
res = Unexpected<Error>(DirectoryPageDuplicate);
61+
return false;
62+
}
63+
64+
if (lastIndex >= newLastPage)
65+
{
66+
res = Unexpected<Error>(InvalidLastPage);
67+
return false;
68+
}
69+
70+
auto slePage = sb.peek(keylet::page(directory, lastIndex));
71+
if (!slePage)
72+
{
73+
res = Unexpected<Error>(DirectoryPageNotFound);
74+
return false;
75+
}
76+
77+
// Copy its data and delete the page
78+
auto indexes = slePage->getFieldV256(sfIndexes);
79+
auto prevIndex = slePage->at(~sfIndexPrevious);
80+
auto owner = slePage->at(~sfOwner);
81+
sb.erase(slePage);
82+
83+
// Create new page to replace slePage
84+
auto sleNew =
85+
std::make_shared<SLE>(keylet::page(directory, newLastPage));
86+
sleNew->setFieldH256(sfRootIndex, directory.key);
87+
sleNew->setFieldV256(sfIndexes, indexes);
88+
if (owner)
89+
sleNew->setAccountID(sfOwner, *owner);
90+
if (prevIndex)
91+
sleNew->setFieldU64(sfIndexPrevious, *prevIndex);
92+
sb.insert(sleNew);
93+
94+
// Adjust root previous and previous node's next
95+
sleRoot->setFieldU64(sfIndexPrevious, newLastPage);
96+
if (prevIndex.value_or(0) == 0)
97+
sleRoot->setFieldU64(sfIndexNext, newLastPage);
98+
else
99+
{
100+
auto slePrev = sb.peek(keylet::page(directory, *prevIndex));
101+
if (!slePrev)
102+
{
103+
res = Unexpected<Error>(DirectoryPageNotFound);
104+
return false;
105+
}
106+
slePrev->setFieldU64(sfIndexNext, newLastPage);
107+
sb.update(slePrev);
108+
}
109+
sb.update(sleRoot);
110+
111+
// Fixup page numbers in the objects referred by indexes
112+
if (adjust)
113+
for (auto const key : indexes)
114+
{
115+
if (!adjust(sb, key, newLastPage))
116+
{
117+
res = Unexpected<Error>(AdjustmentError);
118+
return false;
119+
}
120+
}
121+
122+
sb.apply(view);
123+
return true;
124+
});
125+
126+
return res;
127+
}
128+
129+
bool
130+
adjustOwnerNode(ApplyView& view, uint256 key, std::uint64_t page)
131+
{
132+
auto sle = view.peek({ltANY, key});
133+
if (sle && sle->isFieldPresent(sfOwnerNode))
134+
{
135+
sle->setFieldU64(sfOwnerNode, page);
136+
view.update(sle);
137+
return true;
138+
}
139+
140+
return false;
141+
}
142+
143+
} // namespace directory
144+
145+
} // namespace ripple::test::jtx

0 commit comments

Comments
 (0)