#include #include #include #include #include #include namespace ripple { std::optional ApplyView::dirAdd( bool preserveOrder, Keylet const& directory, uint256 const& key, std::function const&)> const& describe) { auto root = peek(directory); if (!root) { // No root, make it. root = std::make_shared(directory); root->setFieldH256(sfRootIndex, directory.key); describe(root); STVector256 v; v.push_back(key); root->setFieldV256(sfIndexes, v); insert(root); return std::uint64_t{0}; } std::uint64_t page = root->getFieldU64(sfIndexPrevious); auto node = root; if (page) { node = peek(keylet::page(directory, page)); if (!node) LogicError("Directory chain: root back-pointer broken."); } auto indexes = node->getFieldV256(sfIndexes); // If there's space, we use it: if (indexes.size() < dirNodeMaxEntries) { if (preserveOrder) { if (std::find(indexes.begin(), indexes.end(), key) != indexes.end()) LogicError("dirInsert: double insertion"); indexes.push_back(key); } else { // We can't be sure if this page is already sorted because // it may be a legacy page we haven't yet touched. Take // the time to sort it. std::sort(indexes.begin(), indexes.end()); auto pos = std::lower_bound(indexes.begin(), indexes.end(), key); if (pos != indexes.end() && key == *pos) LogicError("dirInsert: double insertion"); indexes.insert(pos, key); } node->setFieldV256(sfIndexes, indexes); update(node); return page; } // We rely on modulo arithmetic of unsigned integers (guaranteed in // [basic.fundamental] paragraph 2) to detect page representation overflow. // For signed integers this would be UB, hence static_assert here. static_assert(std::is_unsigned_v); // Defensive check against breaking changes in compiler. static_assert([](std::type_identity) constexpr -> T { T tmp = std::numeric_limits::max(); return ++tmp; }(std::type_identity{}) == 0); ++page; // Check whether we're out of pages. if (page == 0) return std::nullopt; if (!rules().enabled(fixDirectoryLimit) && page >= dirNodeMaxPages) // Old pages limit return std::nullopt; // We are about to create a new node; we'll link it to // the chain first: node->setFieldU64(sfIndexNext, page); update(node); root->setFieldU64(sfIndexPrevious, page); update(root); // Insert the new key: indexes.clear(); indexes.push_back(key); node = std::make_shared(keylet::page(directory, page)); node->setFieldH256(sfRootIndex, directory.key); node->setFieldV256(sfIndexes, indexes); // Save some space by not specifying the value 0 since // it's the default. if (page != 1) node->setFieldU64(sfIndexPrevious, page - 1); describe(node); insert(node); return page; } bool ApplyView::emptyDirDelete(Keylet const& directory) { auto node = peek(directory); if (!node) return false; // Verify that the passed directory node is the directory root. if (directory.type != ltDIR_NODE || node->getFieldH256(sfRootIndex) != directory.key) { // LCOV_EXCL_START UNREACHABLE("ripple::ApplyView::emptyDirDelete : invalid node type"); return false; // LCOV_EXCL_STOP } // The directory still contains entries and so it cannot be removed if (!node->getFieldV256(sfIndexes).empty()) return false; std::uint64_t constexpr rootPage = 0; auto prevPage = node->getFieldU64(sfIndexPrevious); auto nextPage = node->getFieldU64(sfIndexNext); if (nextPage == rootPage && prevPage != rootPage) LogicError("Directory chain: fwd link broken"); if (prevPage == rootPage && nextPage != rootPage) LogicError("Directory chain: rev link broken"); // Older versions of the code would, in some cases, allow the last // page to be empty. Remove such pages: if (nextPage == prevPage && nextPage != rootPage) { auto last = peek(keylet::page(directory, nextPage)); if (!last) LogicError("Directory chain: fwd link broken."); if (!last->getFieldV256(sfIndexes).empty()) return false; // Update the first page's linked list and // mark it as updated. node->setFieldU64(sfIndexNext, rootPage); node->setFieldU64(sfIndexPrevious, rootPage); update(node); // And erase the empty last page: erase(last); // Make sure our local values reflect the // updated information: nextPage = rootPage; prevPage = rootPage; } // If there are no other pages, erase the root: if (nextPage == rootPage && prevPage == rootPage) erase(node); return true; } bool ApplyView::dirRemove( Keylet const& directory, std::uint64_t page, uint256 const& key, bool keepRoot) { auto node = peek(keylet::page(directory, page)); if (!node) return false; std::uint64_t constexpr rootPage = 0; { auto entries = node->getFieldV256(sfIndexes); auto it = std::find(entries.begin(), entries.end(), key); if (entries.end() == it) return false; // We always preserve the relative order when we remove. entries.erase(it); node->setFieldV256(sfIndexes, entries); update(node); if (!entries.empty()) return true; } // The current page is now empty; check if it can be // deleted, and, if so, whether the entire directory // can now be removed. auto prevPage = node->getFieldU64(sfIndexPrevious); auto nextPage = node->getFieldU64(sfIndexNext); // The first page is the directory's root node and is // treated specially: it can never be deleted even if // it is empty, unless we plan on removing the entire // directory. if (page == rootPage) { if (nextPage == page && prevPage != page) LogicError("Directory chain: fwd link broken"); if (prevPage == page && nextPage != page) LogicError("Directory chain: rev link broken"); // Older versions of the code would, in some cases, // allow the last page to be empty. Remove such // pages if we stumble on them: if (nextPage == prevPage && nextPage != page) { auto last = peek(keylet::page(directory, nextPage)); if (!last) LogicError("Directory chain: fwd link broken."); if (last->getFieldV256(sfIndexes).empty()) { // Update the first page's linked list and // mark it as updated. node->setFieldU64(sfIndexNext, page); node->setFieldU64(sfIndexPrevious, page); update(node); // And erase the empty last page: erase(last); // Make sure our local values reflect the // updated information: nextPage = page; prevPage = page; } } if (keepRoot) return true; // If there's no other pages, erase the root: if (nextPage == page && prevPage == page) erase(node); return true; } // This can never happen for nodes other than the root: if (nextPage == page) LogicError("Directory chain: fwd link broken"); if (prevPage == page) LogicError("Directory chain: rev link broken"); // This node isn't the root, so it can either be in the // middle of the list, or at the end. Unlink it first // and then check if that leaves the list with only a // root: auto prev = peek(keylet::page(directory, prevPage)); if (!prev) LogicError("Directory chain: fwd link broken."); // Fix previous to point to its new next. prev->setFieldU64(sfIndexNext, nextPage); update(prev); auto next = peek(keylet::page(directory, nextPage)); if (!next) LogicError("Directory chain: rev link broken."); // Fix next to point to its new previous. next->setFieldU64(sfIndexPrevious, prevPage); update(next); // The page is no longer linked. Delete it. erase(node); // Check whether the next page is the last page and, if // so, whether it's empty. If it is, delete it. if (nextPage != rootPage && next->getFieldU64(sfIndexNext) == rootPage && next->getFieldV256(sfIndexes).empty()) { // Since next doesn't point to the root, it // can't be pointing to prev. erase(next); // The previous page is now the last page: prev->setFieldU64(sfIndexNext, rootPage); update(prev); // And the root points to the last page: auto root = peek(keylet::page(directory, rootPage)); if (!root) LogicError("Directory chain: root link broken."); root->setFieldU64(sfIndexPrevious, prevPage); update(root); nextPage = rootPage; } // If we're not keeping the root, then check to see if // it's left empty. If so, delete it as well. if (!keepRoot && nextPage == rootPage && prevPage == rootPage) { if (prev->getFieldV256(sfIndexes).empty()) erase(prev); } return true; } bool ApplyView::dirDelete( Keylet const& directory, std::function const& callback) { std::optional pi; do { auto const page = peek(keylet::page(directory, pi.value_or(0))); if (!page) return false; for (auto const& item : page->getFieldV256(sfIndexes)) callback(item); pi = (*page)[~sfIndexNext]; erase(page); } while (pi); return true; } } // namespace ripple