Files
rippled/src/libxrpl/ledger/ApplyView.cpp
Jingchen dc8b37a524 refactor: Modularise ledger (#5493)
This change moves the ledger code to libxrpl.
2025-09-18 11:12:24 -04:00

363 lines
10 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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 <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/protocol/Protocol.h>
namespace ripple {
std::optional<std::uint64_t>
ApplyView::dirAdd(
bool preserveOrder,
Keylet const& directory,
uint256 const& key,
std::function<void(std::shared_ptr<SLE> const&)> const& describe)
{
auto root = peek(directory);
if (!root)
{
// No root, make it.
root = std::make_shared<SLE>(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;
}
// Check whether we're out of pages.
if (++page >= dirNodeMaxPages)
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<SLE>(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)
{
UNREACHABLE("ripple::ApplyView::emptyDirDelete : invalid node type");
return false;
}
// 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<void(uint256 const&)> const& callback)
{
std::optional<std::uint64_t> 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