mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 11:05:54 +00:00
Compare commits
4 Commits
ripple/sma
...
2.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df24ee0774 | ||
|
|
c6aa80b0ef | ||
|
|
283bc3ea39 | ||
|
|
ebc2a9a625 |
91
.github/workflows/libxrpl.yml
vendored
91
.github/workflows/libxrpl.yml
vendored
@@ -1,91 +0,0 @@
|
|||||||
name: Check libXRPL compatibility with Clio
|
|
||||||
env:
|
|
||||||
CONAN_REMOTE_URL: https://conan.ripplex.io
|
|
||||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
|
||||||
CONAN_PASSWORD_XRPLF: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "src/libxrpl/protocol/BuildInfo.cpp"
|
|
||||||
- ".github/workflows/libxrpl.yml"
|
|
||||||
types: [opened, reopened, synchronize, ready_for_review]
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
if: ${{ github.event_name == 'push' || github.event.pull_request.draft != true || contains(github.event.pull_request.labels.*.name, 'DraftRunCI') }}
|
|
||||||
name: Publish libXRPL
|
|
||||||
outputs:
|
|
||||||
outcome: ${{ steps.upload.outputs.outcome }}
|
|
||||||
version: ${{ steps.version.outputs.version }}
|
|
||||||
channel: ${{ steps.channel.outputs.channel }}
|
|
||||||
runs-on: [self-hosted, heavy]
|
|
||||||
container: ghcr.io/xrplf/rippled-build-ubuntu:aaf5e3e
|
|
||||||
steps:
|
|
||||||
- name: Wait for essential checks to succeed
|
|
||||||
uses: lewagon/wait-on-check-action@v1.3.4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
||||||
running-workflow-name: wait-for-check-regexp
|
|
||||||
check-regexp: "(dependencies|test).*linux.*" # Ignore windows and mac tests but make sure linux passes
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
wait-interval: 10
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Generate channel
|
|
||||||
id: channel
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo channel="clio/pr_${{ github.event.pull_request.number }}" | tee ${GITHUB_OUTPUT}
|
|
||||||
- name: Export new package
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
conan export . ${{ steps.channel.outputs.channel }}
|
|
||||||
- name: Add Conan remote
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "Adding Conan remote 'xrplf' at ${{ env.CONAN_REMOTE_URL }}."
|
|
||||||
conan remote add xrplf ${{ env.CONAN_REMOTE_URL }} --insert 0 --force
|
|
||||||
echo "Listing Conan remotes."
|
|
||||||
conan remote list
|
|
||||||
- name: Parse new version
|
|
||||||
id: version
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo version="$(cat src/libxrpl/protocol/BuildInfo.cpp | grep "versionString =" \
|
|
||||||
| awk -F '"' '{print $2}')" | tee ${GITHUB_OUTPUT}
|
|
||||||
- name: Try to authenticate to Conan remote
|
|
||||||
id: remote
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
# `conan user` implicitly uses the environment variables CONAN_LOGIN_USERNAME_<REMOTE> and CONAN_PASSWORD_<REMOTE>.
|
|
||||||
# https://docs.conan.io/1/reference/commands/misc/user.html#using-environment-variables
|
|
||||||
# https://docs.conan.io/1/reference/env_vars.html#conan-login-username-conan-login-username-remote-name
|
|
||||||
# https://docs.conan.io/1/reference/env_vars.html#conan-password-conan-password-remote-name
|
|
||||||
echo outcome=$(conan user --remote xrplf --password >&2 \
|
|
||||||
&& echo success || echo failure) | tee ${GITHUB_OUTPUT}
|
|
||||||
- name: Upload new package
|
|
||||||
id: upload
|
|
||||||
if: (steps.remote.outputs.outcome == 'success')
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "conan upload version ${{ steps.version.outputs.version }} on channel ${{ steps.channel.outputs.channel }}"
|
|
||||||
echo outcome=$(conan upload xrpl/${{ steps.version.outputs.version }}@${{ steps.channel.outputs.channel }} --remote ripple --confirm >&2 \
|
|
||||||
&& echo success || echo failure) | tee ${GITHUB_OUTPUT}
|
|
||||||
notify_clio:
|
|
||||||
name: Notify Clio
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: publish
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.CLIO_NOTIFY_TOKEN }}
|
|
||||||
steps:
|
|
||||||
- name: Notify Clio about new version
|
|
||||||
if: (needs.publish.outputs.outcome == 'success')
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" \
|
|
||||||
/repos/xrplf/clio/dispatches -f "event_type=check_libxrpl" \
|
|
||||||
-F "client_payload[version]=${{ needs.publish.outputs.version }}@${{ needs.publish.outputs.channel }}" \
|
|
||||||
-F "client_payload[pr]=${{ github.event.pull_request.number }}"
|
|
||||||
45
.github/workflows/macos.yml
vendored
45
.github/workflows/macos.yml
vendored
@@ -50,30 +50,35 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
- name: install Conan
|
- name: Delete old build tools installed using Homebrew
|
||||||
run: |
|
run: |
|
||||||
brew install conan
|
brew uninstall --force \
|
||||||
- name: install Ninja
|
cmake \
|
||||||
if: matrix.generator == 'Ninja'
|
conan
|
||||||
run: brew install ninja
|
|
||||||
- name: install python
|
- name: Install build tools using Homebrew
|
||||||
run: |
|
run: |
|
||||||
if which python > /dev/null 2>&1; then
|
brew install --quiet \
|
||||||
echo "Python executable exists"
|
ca-certificates \
|
||||||
else
|
ninja \
|
||||||
brew install python@3.13
|
python@3.14
|
||||||
ln -s /opt/homebrew/bin/python3 /opt/homebrew/bin/python
|
|
||||||
fi
|
- name: Remove old fmt using Homebrew
|
||||||
- name: install cmake
|
|
||||||
run: |
|
run: |
|
||||||
if which cmake > /dev/null 2>&1; then
|
brew unlink fmt
|
||||||
echo "cmake executable exists"
|
brew cleanup
|
||||||
else
|
brew link fmt
|
||||||
brew install cmake
|
|
||||||
fi
|
- name: List software installed using Homebrew
|
||||||
- name: install nproc
|
run: brew list --version
|
||||||
|
|
||||||
|
- name: Install build tools using pip
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
brew install coreutils
|
pip3 install --break-system-packages --upgrade pip
|
||||||
|
pip3 install --break-system-packages \
|
||||||
|
cmake==4.1.2 \
|
||||||
|
conan==2.22.1
|
||||||
- name: check environment
|
- name: check environment
|
||||||
run: |
|
run: |
|
||||||
env | sort
|
env | sort
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ std::size_t constexpr oversizeMetaDataCap = 5200;
|
|||||||
/** The maximum number of entries per directory page */
|
/** The maximum number of entries per directory page */
|
||||||
std::size_t constexpr dirNodeMaxEntries = 32;
|
std::size_t constexpr dirNodeMaxEntries = 32;
|
||||||
|
|
||||||
/** The maximum number of pages allowed in a directory */
|
/** The maximum number of pages allowed in a directory
|
||||||
|
|
||||||
|
Made obsolete by fixDirectoryLimit amendment.
|
||||||
|
*/
|
||||||
std::uint64_t constexpr dirNodeMaxPages = 262144;
|
std::uint64_t constexpr dirNodeMaxPages = 262144;
|
||||||
|
|
||||||
/** The maximum number of items in an NFT page */
|
/** The maximum number of items in an NFT page */
|
||||||
|
|||||||
@@ -29,9 +29,8 @@
|
|||||||
|
|
||||||
// Add new amendments to the top of this list.
|
// Add new amendments to the top of this list.
|
||||||
// Keep it sorted in reverse chronological order.
|
// Keep it sorted in reverse chronological order.
|
||||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
|
||||||
// in include/xrpl/protocol/Feature.h.
|
|
||||||
|
|
||||||
|
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo)
|
XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo)
|
XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo)
|
XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace BuildInfo {
|
|||||||
// and follow the format described at http://semver.org/
|
// and follow the format described at http://semver.org/
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// clang-format off
|
// clang-format off
|
||||||
char const* const versionString = "2.6.1"
|
char const* const versionString = "2.6.2"
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
#if defined(DEBUG) || defined(SANITIZER)
|
#if defined(DEBUG) || defined(SANITIZER)
|
||||||
|
|||||||
@@ -568,6 +568,39 @@ struct Credentials_test : public beast::unit_test::suite
|
|||||||
jle[jss::result][jss::node]["CredentialType"] ==
|
jle[jss::result][jss::node]["CredentialType"] ==
|
||||||
strHex(std::string_view(credType)));
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1094,6 +1127,7 @@ struct Credentials_test : public beast::unit_test::suite
|
|||||||
testSuccessful(all);
|
testSuccessful(all);
|
||||||
testCredentialsDelete(all);
|
testCredentialsDelete(all);
|
||||||
testCreateFailed(all);
|
testCreateFailed(all);
|
||||||
|
testCreateFailed(all - fixDirectoryLimit);
|
||||||
testAcceptFailed(all);
|
testAcceptFailed(all);
|
||||||
testDeleteFailed(all);
|
testDeleteFailed(all);
|
||||||
testFeatureFailed(all - featureCredentials);
|
testFeatureFailed(all - featureCredentials);
|
||||||
|
|||||||
80
src/test/app/NetworkOPs_test.cpp
Normal file
80
src/test/app/NetworkOPs_test.cpp
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2020 Dev Null Productions
|
||||||
|
|
||||||
|
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.h>
|
||||||
|
#include <test/jtx/CaptureLogs.h>
|
||||||
|
#include <test/jtx/Env.h>
|
||||||
|
|
||||||
|
#include <xrpld/app/misc/HashRouter.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
class NetworkOPs_test : public beast::unit_test::suite
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void
|
||||||
|
run() override
|
||||||
|
{
|
||||||
|
testAllBadHeldTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testAllBadHeldTransactions()
|
||||||
|
{
|
||||||
|
// All trasactions are already marked as SF_BAD, and we should be able
|
||||||
|
// to handle the case properly without an assertion failure
|
||||||
|
testcase("No valid transactions in batch");
|
||||||
|
|
||||||
|
std::string logs;
|
||||||
|
|
||||||
|
{
|
||||||
|
using namespace jtx;
|
||||||
|
auto const alice = Account{"alice"};
|
||||||
|
Env env{
|
||||||
|
*this,
|
||||||
|
envconfig(),
|
||||||
|
std::make_unique<CaptureLogs>(&logs),
|
||||||
|
beast::severities::kAll};
|
||||||
|
env.memoize(env.master);
|
||||||
|
env.memoize(alice);
|
||||||
|
|
||||||
|
auto const jtx = env.jt(ticket::create(alice, 1), seq(1), fee(10));
|
||||||
|
|
||||||
|
auto transacionId = jtx.stx->getTransactionID();
|
||||||
|
env.app().getHashRouter().setFlags(
|
||||||
|
transacionId, HashRouterFlags::HELD);
|
||||||
|
|
||||||
|
env(jtx, json(jss::Sequence, 1), ter(terNO_ACCOUNT));
|
||||||
|
|
||||||
|
env.app().getHashRouter().setFlags(
|
||||||
|
transacionId, HashRouterFlags::BAD);
|
||||||
|
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
BEAST_EXPECT(
|
||||||
|
logs.find("No transaction to process!") != std::string::npos);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BEAST_DEFINE_TESTSUITE(NetworkOPs, app, ripple);
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace ripple
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
#include <test/jtx/delivermin.h>
|
#include <test/jtx/delivermin.h>
|
||||||
#include <test/jtx/deposit.h>
|
#include <test/jtx/deposit.h>
|
||||||
#include <test/jtx/did.h>
|
#include <test/jtx/did.h>
|
||||||
|
#include <test/jtx/directory.h>
|
||||||
#include <test/jtx/domain.h>
|
#include <test/jtx/domain.h>
|
||||||
#include <test/jtx/escrow.h>
|
#include <test/jtx/escrow.h>
|
||||||
#include <test/jtx/fee.h>
|
#include <test/jtx/fee.h>
|
||||||
|
|||||||
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
|
||||||
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 <xrpld/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
|
||||||
@@ -23,9 +23,11 @@
|
|||||||
#include <xrpl/basics/random.h>
|
#include <xrpl/basics/random.h>
|
||||||
#include <xrpl/protocol/Feature.h>
|
#include <xrpl/protocol/Feature.h>
|
||||||
#include <xrpl/protocol/Protocol.h>
|
#include <xrpl/protocol/Protocol.h>
|
||||||
|
#include <xrpl/protocol/TER.h>
|
||||||
#include <xrpl/protocol/jss.h>
|
#include <xrpl/protocol/jss.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
@@ -490,6 +492,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
|
void
|
||||||
run() override
|
run() override
|
||||||
{
|
{
|
||||||
@@ -498,6 +585,7 @@ struct Directory_test : public beast::unit_test::suite
|
|||||||
testRipd1353();
|
testRipd1353();
|
||||||
testEmptyChain();
|
testEmptyChain();
|
||||||
testPreviousTxnID();
|
testPreviousTxnID();
|
||||||
|
testDirectoryFull();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1448,6 +1448,11 @@ NetworkOPsImp::processTransactionSet(CanonicalTXSet const& set)
|
|||||||
for (auto& t : transactions)
|
for (auto& t : transactions)
|
||||||
mTransactions.push_back(std::move(t));
|
mTransactions.push_back(std::move(t));
|
||||||
}
|
}
|
||||||
|
if (mTransactions.empty())
|
||||||
|
{
|
||||||
|
JLOG(m_journal.debug()) << "No transaction to process!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
doTransactionSyncBatch(lock, [&](std::unique_lock<std::mutex> const&) {
|
doTransactionSyncBatch(lock, [&](std::unique_lock<std::mutex> const&) {
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
#include <xrpl/beast/utility/instrumentation.h>
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
#include <xrpl/protocol/Protocol.h>
|
#include <xrpl/protocol/Protocol.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
std::optional<std::uint64_t>
|
std::optional<std::uint64_t>
|
||||||
@@ -92,8 +95,21 @@ ApplyView::dirAdd(
|
|||||||
return page;
|
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<decltype(page)>);
|
||||||
|
// Defensive check against breaking changes in compiler.
|
||||||
|
static_assert([]<typename T>(std::type_identity<T>) constexpr -> T {
|
||||||
|
T tmp = std::numeric_limits<T>::max();
|
||||||
|
return ++tmp;
|
||||||
|
}(std::type_identity<decltype(page)>{}) == 0);
|
||||||
|
++page;
|
||||||
// Check whether we're out of pages.
|
// Check whether we're out of pages.
|
||||||
if (++page >= dirNodeMaxPages)
|
if (page == 0)
|
||||||
|
return std::nullopt;
|
||||||
|
if (!rules().enabled(fixDirectoryLimit) &&
|
||||||
|
page >= dirNodeMaxPages) // Old pages limit
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
// We are about to create a new node; we'll link it to
|
// We are about to create a new node; we'll link it to
|
||||||
|
|||||||
@@ -1286,8 +1286,7 @@ PeerImp::handleTransaction(
|
|||||||
|
|
||||||
// Charge strongly for attempting to relay a txn with tfInnerBatchTxn
|
// Charge strongly for attempting to relay a txn with tfInnerBatchTxn
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
if (stx->isFlag(tfInnerBatchTxn) &&
|
if (stx->isFlag(tfInnerBatchTxn))
|
||||||
getCurrentTransactionRules()->enabled(featureBatch))
|
|
||||||
{
|
{
|
||||||
JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing "
|
JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing "
|
||||||
"tfInnerBatchTxn (handleTransaction).";
|
"tfInnerBatchTxn (handleTransaction).";
|
||||||
@@ -2851,8 +2850,7 @@ PeerImp::checkTransaction(
|
|||||||
{
|
{
|
||||||
// charge strongly for relaying batch txns
|
// charge strongly for relaying batch txns
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
if (stx->isFlag(tfInnerBatchTxn) &&
|
if (stx->isFlag(tfInnerBatchTxn))
|
||||||
getCurrentTransactionRules()->enabled(featureBatch))
|
|
||||||
{
|
{
|
||||||
JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing "
|
JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing "
|
||||||
"tfInnerBatchTxn (checkSignature).";
|
"tfInnerBatchTxn (checkSignature).";
|
||||||
@@ -2866,6 +2864,9 @@ PeerImp::checkTransaction(
|
|||||||
(stx->getFieldU32(sfLastLedgerSequence) <
|
(stx->getFieldU32(sfLastLedgerSequence) <
|
||||||
app_.getLedgerMaster().getValidLedgerIndex()))
|
app_.getLedgerMaster().getValidLedgerIndex()))
|
||||||
{
|
{
|
||||||
|
JLOG(p_journal_.info())
|
||||||
|
<< "Marking transaction " << stx->getTransactionID()
|
||||||
|
<< "as BAD because it's expired";
|
||||||
app_.getHashRouter().setFlags(
|
app_.getHashRouter().setFlags(
|
||||||
stx->getTransactionID(), HashRouterFlags::BAD);
|
stx->getTransactionID(), HashRouterFlags::BAD);
|
||||||
charge(Resource::feeUselessData, "expired tx");
|
charge(Resource::feeUselessData, "expired tx");
|
||||||
@@ -2922,7 +2923,7 @@ PeerImp::checkTransaction(
|
|||||||
{
|
{
|
||||||
if (!validReason.empty())
|
if (!validReason.empty())
|
||||||
{
|
{
|
||||||
JLOG(p_journal_.trace())
|
JLOG(p_journal_.debug())
|
||||||
<< "Exception checking transaction: " << validReason;
|
<< "Exception checking transaction: " << validReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2949,7 +2950,7 @@ PeerImp::checkTransaction(
|
|||||||
{
|
{
|
||||||
if (!reason.empty())
|
if (!reason.empty())
|
||||||
{
|
{
|
||||||
JLOG(p_journal_.trace())
|
JLOG(p_journal_.debug())
|
||||||
<< "Exception checking transaction: " << reason;
|
<< "Exception checking transaction: " << reason;
|
||||||
}
|
}
|
||||||
app_.getHashRouter().setFlags(
|
app_.getHashRouter().setFlags(
|
||||||
|
|||||||
Reference in New Issue
Block a user