xrpld
Loading...
Searching...
No Matches
ApplyView.cpp
1#include <xrpl/ledger/ApplyView.h>
2
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/basics/contract.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/protocol/Feature.h>
7#include <xrpl/protocol/Indexes.h>
8#include <xrpl/protocol/Keylet.h>
9#include <xrpl/protocol/LedgerFormats.h>
10#include <xrpl/protocol/Protocol.h>
11#include <xrpl/protocol/SField.h>
12#include <xrpl/protocol/STLedgerEntry.h>
13#include <xrpl/protocol/STVector256.h>
14
15#include <algorithm>
16#include <cstdint>
17#include <functional>
18#include <limits>
19#include <memory>
20#include <optional>
21#include <stdexcept>
22#include <tuple>
23#include <type_traits>
24
25namespace xrpl {
26
27namespace directory {
28
29std::uint64_t
31 ApplyView& view,
32 Keylet const& directory,
33 uint256 const& key,
34 std::function<void(SLE::ref)> const& describe)
35{
36 auto newRoot = std::make_shared<SLE>(directory);
37 newRoot->setFieldH256(sfRootIndex, directory.key);
38 describe(newRoot);
39
41 v.pushBack(key);
42 newRoot->setFieldV256(sfIndexes, v);
43
44 view.insert(newRoot);
45 return std::uint64_t{0};
46}
47
48auto
50{
51 std::uint64_t const page = start->getFieldU64(sfIndexPrevious);
52
53 auto node = start;
54
55 if (page != 0u)
56 {
57 node = view.peek(keylet::page(directory, page));
58 if (!node)
59 {
61 "Directory chain: root back-pointer broken."); // LCOV_EXCL_LINE
62 }
63 }
64
65 auto indexes = node->getFieldV256(sfIndexes);
66 return std::make_tuple(page, node, indexes);
67}
68
71 ApplyView& view,
72 SLE::ref node,
73 std::uint64_t page,
74 bool preserveOrder,
75 STVector256& indexes,
76 uint256 const& key)
77{
78 if (preserveOrder)
79 {
80 if (std::ranges::find(indexes, key) != indexes.end())
81 Throw<std::logic_error>("dirInsert: double insertion"); // LCOV_EXCL_LINE
82
83 indexes.pushBack(key);
84 }
85 else
86 {
87 // We can't be sure if this page is already sorted because it may be a
88 // legacy page we haven't yet touched. Take the time to sort it.
89 std::ranges::sort(indexes);
90
91 auto pos = std::ranges::lower_bound(indexes, key);
92
93 if (pos != indexes.end() && key == *pos)
94 Throw<std::logic_error>("dirInsert: double insertion"); // LCOV_EXCL_LINE
95
96 indexes.insert(pos, key);
97 }
98
99 node->setFieldV256(sfIndexes, indexes);
100 view.update(node);
101 return page;
102}
103
106 ApplyView& view,
107 std::uint64_t page,
108 SLE::pointer node,
109 std::uint64_t nextPage,
110 SLE::ref next,
111 uint256 const& key,
112 Keylet const& directory,
113 std::function<void(SLE::ref)> const& describe)
114{
115 // We rely on modulo arithmetic of unsigned integers (guaranteed in
116 // [basic.fundamental] paragraph 2) to detect page representation overflow.
117 // For signed integers this would be UB, hence static_assert here.
118 static_assert(std::is_unsigned_v<decltype(page)>);
119 // Defensive check against breaking changes in compiler.
120 static_assert([]<typename T>(std::type_identity<T>) constexpr -> T {
122 return ++tmp;
123 }(std::type_identity<decltype(page)>{}) == 0);
124 ++page;
125 // Check whether we're out of pages.
126 if (page == 0)
127 return std::nullopt;
128 if (!view.rules().enabled(fixDirectoryLimit) && page >= kDirNodeMaxPages) // Old pages limit
129 return std::nullopt;
130
131 // We are about to create a new node; we'll link it to
132 // the chain first:
133 node->setFieldU64(sfIndexNext, page);
134 view.update(node);
135
136 next->setFieldU64(sfIndexPrevious, page);
137 view.update(next);
138
139 // Insert the new key:
140 STVector256 indexes;
141 indexes.pushBack(key);
142
144 node->setFieldH256(sfRootIndex, directory.key);
145 node->setFieldV256(sfIndexes, indexes);
146
147 // Save some space by not specifying the value 0 since it's the default.
148 if (page != 1)
149 node->setFieldU64(sfIndexPrevious, page - 1);
150 XRPL_ASSERT_PARTS(!nextPage, "xrpl::directory::insertPage", "nextPage has default value");
151 /* Reserved for future use when directory pages may be inserted in
152 * between two other pages instead of only at the end of the chain.
153 if (nextPage)
154 node->setFieldU64(sfIndexNext, nextPage);
155 */
156 describe(node);
157 view.insert(node);
158
159 return page;
160}
161
162} // namespace directory
163
166 bool preserveOrder,
167 Keylet const& directory,
168 uint256 const& key,
169 std::function<void(SLE::ref)> const& describe)
170{
171 auto root = peek(directory);
172
173 if (!root)
174 {
175 // No root, make it.
176 return directory::createRoot(*this, directory, key, describe);
177 }
178
179 auto [page, node, indexes] = directory::findPreviousPage(*this, directory, root);
180
181 // If there's space, we use it:
182 if (indexes.size() < kDirNodeMaxEntries)
183 {
184 return directory::insertKey(*this, node, page, preserveOrder, indexes, key);
185 }
186
187 return directory::insertPage(*this, page, node, 0, root, key, directory, describe);
188}
189
190bool
192{
193 auto node = peek(directory);
194
195 if (!node)
196 return false;
197
198 // Verify that the passed directory node is the directory root.
199 if (directory.type != ltDIR_NODE || node->getFieldH256(sfRootIndex) != directory.key)
200 {
201 // LCOV_EXCL_START
202 UNREACHABLE("xrpl::ApplyView::emptyDirDelete : invalid node type");
203 return false;
204 // LCOV_EXCL_STOP
205 }
206
207 // The directory still contains entries and so it cannot be removed
208 if (!node->getFieldV256(sfIndexes).empty())
209 return false;
210
211 static constexpr std::uint64_t kRootPage = 0;
212 auto prevPage = node->getFieldU64(sfIndexPrevious);
213 auto nextPage = node->getFieldU64(sfIndexNext);
214
215 if (nextPage == kRootPage && prevPage != kRootPage)
216 Throw<std::logic_error>("Directory chain: fwd link broken"); // LCOV_EXCL_LINE
217
218 if (prevPage == kRootPage && nextPage != kRootPage)
219 Throw<std::logic_error>("Directory chain: rev link broken"); // LCOV_EXCL_LINE
220
221 // Older versions of the code would, in some cases, allow the last
222 // page to be empty. Remove such pages:
223 if (nextPage == prevPage && nextPage != kRootPage)
224 {
225 auto last = peek(keylet::page(directory, nextPage));
226
227 if (!last)
228 Throw<std::logic_error>("Directory chain: fwd link broken."); // LCOV_EXCL_LINE
229
230 if (!last->getFieldV256(sfIndexes).empty())
231 return false;
232
233 // Update the first page's linked list and
234 // mark it as updated.
235 node->setFieldU64(sfIndexNext, kRootPage);
236 node->setFieldU64(sfIndexPrevious, kRootPage);
237 update(node);
238
239 // And erase the empty last page:
240 erase(last);
241
242 // Make sure our local values reflect the
243 // updated information:
244 nextPage = kRootPage;
245 prevPage = kRootPage;
246 }
247
248 // If there are no other pages, erase the root:
249 if (nextPage == kRootPage && prevPage == kRootPage)
250 erase(node);
251
252 return true;
253}
254
255bool
256ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& key, bool keepRoot)
257{
258 auto node = peek(keylet::page(directory, page));
259
260 if (!node)
261 return false;
262
263 static constexpr std::uint64_t kRootPage = 0;
264
265 {
266 auto entries = node->getFieldV256(sfIndexes);
267
268 auto it = std::ranges::find(entries, key);
269
270 if (entries.end() == it)
271 return false;
272
273 // We always preserve the relative order when we remove.
274 entries.erase(it);
275
276 node->setFieldV256(sfIndexes, entries);
277 update(node);
278
279 if (!entries.empty())
280 return true;
281 }
282
283 // The current page is now empty; check if it can be deleted, and, if so,
284 // whether the entire directory can now be removed.
285 auto prevPage = node->getFieldU64(sfIndexPrevious);
286 auto nextPage = node->getFieldU64(sfIndexNext);
287
288 // The first page is the directory's root node and is
289 // treated specially: it can never be deleted even if
290 // it is empty, unless we plan on removing the entire
291 // directory.
292 if (page == kRootPage)
293 {
294 if (nextPage == page && prevPage != page)
295 Throw<std::logic_error>("Directory chain: fwd link broken"); // LCOV_EXCL_LINE
296
297 if (prevPage == page && nextPage != page)
298 Throw<std::logic_error>("Directory chain: rev link broken"); // LCOV_EXCL_LINE
299
300 // Older versions of the code would, in some cases, allow the last page
301 // to be empty. Remove such pages if we stumble on them:
302 if (nextPage == prevPage && nextPage != page)
303 {
304 auto last = peek(keylet::page(directory, nextPage));
305 if (!last)
306 Throw<std::logic_error>("Directory chain: fwd link broken."); // LCOV_EXCL_LINE
307
308 if (last->getFieldV256(sfIndexes).empty())
309 {
310 // Update the first page's linked list and mark it as updated.
311 node->setFieldU64(sfIndexNext, page);
312 node->setFieldU64(sfIndexPrevious, page);
313 update(node);
314
315 // And erase the empty last page:
316 erase(last);
317
318 // Make sure our local values reflect the updated information:
319 nextPage = page;
320 prevPage = page;
321 }
322 }
323
324 if (keepRoot)
325 return true;
326
327 // If there's no other pages, erase the root:
328 if (nextPage == page && prevPage == page)
329 erase(node);
330
331 return true;
332 }
333
334 // This can never happen for nodes other than the root:
335 if (nextPage == page)
336 Throw<std::logic_error>("Directory chain: fwd link broken"); // LCOV_EXCL_LINE
337
338 if (prevPage == page)
339 Throw<std::logic_error>("Directory chain: rev link broken"); // LCOV_EXCL_LINE
340
341 // This node isn't the root, so it can either be in the middle of the list,
342 // or at the end. Unlink it first and then check if that leaves the list
343 // with only a root:
344 auto prev = peek(keylet::page(directory, prevPage));
345 if (!prev)
346 Throw<std::logic_error>("Directory chain: fwd link broken."); // LCOV_EXCL_LINE
347 // Fix previous to point to its new next.
348 prev->setFieldU64(sfIndexNext, nextPage);
349 update(prev);
350
351 auto next = peek(keylet::page(directory, nextPage));
352 if (!next)
353 Throw<std::logic_error>("Directory chain: rev link broken."); // LCOV_EXCL_LINE
354 // Fix next to point to its new previous.
355 next->setFieldU64(sfIndexPrevious, prevPage);
356 update(next);
357
358 // The page is no longer linked. Delete it.
359 erase(node);
360
361 // Check whether the next page is the last page and, if
362 // so, whether it's empty. If it is, delete it.
363 if (nextPage != kRootPage && next->getFieldU64(sfIndexNext) == kRootPage &&
364 next->getFieldV256(sfIndexes).empty())
365 {
366 // Since next doesn't point to the root, it can't be pointing to prev.
367 erase(next);
368
369 // The previous page is now the last page:
370 prev->setFieldU64(sfIndexNext, kRootPage);
371 update(prev);
372
373 // And the root points to the last page:
374 auto root = peek(keylet::page(directory, kRootPage));
375 if (!root)
376 Throw<std::logic_error>("Directory chain: root link broken."); // LCOV_EXCL_LINE
377
378 root->setFieldU64(sfIndexPrevious, prevPage);
379 update(root);
380
381 nextPage = kRootPage;
382 }
383
384 // If we're not keeping the root, then check to see if
385 // it's left empty. If so, delete it as well.
386 if (!keepRoot && nextPage == kRootPage && prevPage == kRootPage)
387 {
388 if (prev->getFieldV256(sfIndexes).empty())
389 erase(prev);
390 }
391
392 return true;
393}
394
395bool
396ApplyView::dirDelete(Keylet const& directory, std::function<void(uint256 const&)> const& callback)
397{
399
400 do
401 {
402 auto const page = peek(keylet::page(directory, pi.value_or(0)));
403
404 if (!page)
405 return false;
406
407 for (auto const& item : page->getFieldV256(sfIndexes))
408 callback(item);
409
410 pi = (*page)[~sfIndexNext];
411
412 erase(page);
413 } while (pi);
414
415 return true;
416}
417
418} // namespace xrpl
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:118
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void insert(SLE::ref sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirAdd(bool preserveOrder, Keylet const &directory, uint256 const &key, std::function< void(SLE::ref)> const &describe)
Add an entry to a directory using the specified insert strategy.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
virtual void erase(SLE::ref sle)=0
Remove a peeked SLE.
bool dirDelete(Keylet const &directory, std::function< void(uint256 const &)> const &)
Remove the specified directory, invoking the callback for every node.
bool emptyDirDelete(Keylet const &directory)
Remove the specified directory, if it is empty.
virtual void update(SLE::ref sle)=0
Indicate changes to a peeked SLE.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry > pointer
std::vector< uint256 >::iterator insert(std::vector< uint256 >::const_iterator pos, uint256 const &value)
void pushBack(uint256 const &v)
std::vector< uint256 >::iterator end()
T find(T... args)
T is_unsigned_v
T lower_bound(T... args)
T make_shared(T... args)
T make_tuple(T... args)
T max(T... args)
auto findPreviousPage(ApplyView &view, Keylet const &directory, SLE::ref start)
Definition ApplyView.cpp:49
std::uint64_t insertKey(ApplyView &view, SLE::ref node, std::uint64_t page, bool preserveOrder, STVector256 &indexes, uint256 const &key)
Definition ApplyView.cpp:70
std::optional< std::uint64_t > insertPage(ApplyView &view, std::uint64_t page, SLE::pointer node, std::uint64_t nextPage, SLE::ref next, uint256 const &key, Keylet const &directory, std::function< void(SLE::ref)> const &describe)
std::uint64_t createRoot(ApplyView &view, Keylet const &directory, uint256 const &key, std::function< void(SLE::ref)> const &describe)
Helper functions for managing low-level directory operations.
Definition ApplyView.cpp:30
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:363
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr std::size_t kDirNodeMaxEntries
The maximum number of entries per directory page.
Definition Protocol.h:41
Number root(Number f, unsigned d)
Definition Number.cpp:1201
constexpr std::uint64_t kDirNodeMaxPages
The maximum number of pages allowed in a directory.
Definition Protocol.h:47
BaseUInt< 256 > uint256
Definition base_uint.h:562
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
T sort(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:19
T value_or(T... args)