mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-27 04:27:30 +00:00
Compare commits
18 Commits
mvadari/te
...
tapanito/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58dcb0ba9c | ||
|
|
0711a7b493 | ||
|
|
dd6f95af6c | ||
|
|
81149c762d | ||
|
|
07c64f07f0 | ||
|
|
db92907a31 | ||
|
|
3097c157b6 | ||
|
|
f68eaeb148 | ||
|
|
38195c6d73 | ||
|
|
fc05d9a02f | ||
|
|
0eca5a7d29 | ||
|
|
78483ccdc9 | ||
|
|
fcb5e9ed6d | ||
|
|
0a8b12cd67 | ||
|
|
00425ee6e9 | ||
|
|
66693fb7a4 | ||
|
|
cc4e2b412e | ||
|
|
51e888785d |
2
.github/actions/setup-conan/action.yml
vendored
2
.github/actions/setup-conan/action.yml
vendored
@@ -9,7 +9,7 @@ inputs:
|
||||
remote_url:
|
||||
description: "The URL of the Conan endpoint to use."
|
||||
required: false
|
||||
default: https://conan.ripplex.io
|
||||
default: https://conan.xrplf.org/repository/conan/
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
4
.github/workflows/on-pr.yml
vendored
4
.github/workflows/on-pr.yml
vendored
@@ -154,8 +154,8 @@ jobs:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && needs.should-run.outputs.go == 'true' && github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'release') }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
|
||||
notify-clio:
|
||||
needs: upload-recipe
|
||||
|
||||
4
.github/workflows/on-tag.yml
vendored
4
.github/workflows/on-tag.yml
vendored
@@ -20,8 +20,8 @@ jobs:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
|
||||
build-test:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
|
||||
4
.github/workflows/on-trigger.yml
vendored
4
.github/workflows/on-trigger.yml
vendored
@@ -98,8 +98,8 @@ jobs:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
|
||||
package:
|
||||
needs: build-test
|
||||
|
||||
20
.github/workflows/reusable-upload-recipe.yml
vendored
20
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
description: "The URL of the Conan endpoint to use."
|
||||
required: false
|
||||
type: string
|
||||
default: https://conan.ripplex.io
|
||||
default: https://conan.xrplf.org/repository/conan/
|
||||
|
||||
secrets:
|
||||
remote_username:
|
||||
@@ -41,6 +41,10 @@ jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
|
||||
CONAN_PASSWORD_XRPLF: ${{ secrets.remote_password }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
@@ -56,15 +60,9 @@ jobs:
|
||||
remote_url: ${{ inputs.remote_url }}
|
||||
|
||||
- name: Log into Conan remote
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
REMOTE_USERNAME: ${{ secrets.remote_username }}
|
||||
REMOTE_PASSWORD: ${{ secrets.remote_password }}
|
||||
run: conan remote login "${REMOTE_NAME}" "${REMOTE_USERNAME}" --password "${REMOTE_PASSWORD}"
|
||||
run: conan remote login "${REMOTE_NAME}" "${CONAN_LOGIN_USERNAME_XRPLF}" --password "${CONAN_PASSWORD_XRPLF}"
|
||||
|
||||
- name: Upload Conan recipe (version)
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
run: |
|
||||
conan export . --version=${{ steps.version.outputs.version }}
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/${{ steps.version.outputs.version }}
|
||||
@@ -73,8 +71,6 @@ jobs:
|
||||
# 'develop' branch, see on-trigger.yml.
|
||||
- name: Upload Conan recipe (develop)
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
run: |
|
||||
conan export . --version=develop
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/develop
|
||||
@@ -83,8 +79,6 @@ jobs:
|
||||
# one of the 'release' branches, see on-pr.yml.
|
||||
- name: Upload Conan recipe (rc)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
run: |
|
||||
conan export . --version=rc
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/rc
|
||||
@@ -93,8 +87,6 @@ jobs:
|
||||
# release, see on-tag.yml.
|
||||
- name: Upload Conan recipe (release)
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
run: |
|
||||
conan export . --version=release
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/release
|
||||
|
||||
6
.github/workflows/upload-conan-deps.yml
vendored
6
.github/workflows/upload-conan-deps.yml
vendored
@@ -34,7 +34,7 @@ on:
|
||||
|
||||
env:
|
||||
CONAN_REMOTE_NAME: xrplf
|
||||
CONAN_REMOTE_URL: https://conan.ripplex.io
|
||||
CONAN_REMOTE_URL: https://conan.xrplf.org/repository/conan/
|
||||
NPROC_SUBTRACT: 2
|
||||
|
||||
concurrency:
|
||||
@@ -108,10 +108,12 @@ jobs:
|
||||
|
||||
- name: Log into Conan remote
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
|
||||
run: conan remote login "${CONAN_REMOTE_NAME}" "${{ secrets.CONAN_REMOTE_USERNAME }}" --password "${{ secrets.CONAN_REMOTE_PASSWORD }}"
|
||||
run: conan remote login "${CONAN_REMOTE_NAME}" "${{ secrets.NEXUS_REMOTE_USERNAME }}" --password "${{ secrets.NEXUS_REMOTE_PASSWORD }}"
|
||||
|
||||
- name: Upload Conan packages
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
|
||||
env:
|
||||
FORCE_OPTION: ${{ github.event.inputs.force_upload == 'true' && '--force' || '' }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
CONAN_PASSWORD_XRPLF: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
run: conan upload "*" --remote="${CONAN_REMOTE_NAME}" --confirm ${FORCE_OPTION}
|
||||
|
||||
2
BUILD.md
2
BUILD.md
@@ -101,7 +101,7 @@ More information on customizing Conan can be found in the [Advanced Conan config
|
||||
Run the following command to add the `xrplf` remote, which hosts some of our dependencies:
|
||||
|
||||
```bash
|
||||
conan remote add --index 0 --force xrplf https://conan.ripplex.io
|
||||
conan remote add --index 0 --force xrplf https://conan.xrplf.org/repository/conan/
|
||||
```
|
||||
|
||||
### Set Up Ccache
|
||||
|
||||
64
conan.lock
64
conan.lock
@@ -1,43 +1,43 @@
|
||||
{
|
||||
"version": "0.5",
|
||||
"requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1778091116.056",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
||||
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1778091117.311",
|
||||
"soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",
|
||||
"secp256k1/0.7.1#481881709eb0bdd0185a12b912bbe8ad%1770910500.329",
|
||||
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1765850186.86",
|
||||
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1774398111.888",
|
||||
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12",
|
||||
"openssl/3.6.2#4789bbf131b77d0515d15e094c8f697f%1778071755.506",
|
||||
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1775040983.408",
|
||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
|
||||
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
|
||||
"libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1778091117.848",
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
|
||||
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447",
|
||||
"soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1782307151.633168",
|
||||
"secp256k1/0.7.1#b1f450b7f78a36fff75bb6934a356f3a%1782338841.3729",
|
||||
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1782392413.075713",
|
||||
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1782392402.431897",
|
||||
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
|
||||
"openssl/3.6.3#1163d4ddc603907084d08a6a0c6e580f%1782307150.583886",
|
||||
"nudb/2.0.9#11149c73f8f2baff9a0198fe25971fc7%1782392402.297166",
|
||||
"lz4/1.10.0#982d9b673900f665a1da109e09c17cab%1782392402.164188",
|
||||
"libiconv/1.17#9923bc6dc6f106646d6967e0039a5ada%1782392792.775744",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1782392402.420732",
|
||||
"libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1782392403.066892",
|
||||
"jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228",
|
||||
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152",
|
||||
"grpc/1.81.0#2fb144aeb47e7f35c6ebb0e5f35bed31%1781620605.685",
|
||||
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772",
|
||||
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1774439234.681",
|
||||
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837",
|
||||
"boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1778091165.282",
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196"
|
||||
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1782392402.791979",
|
||||
"grpc/1.81.1#5217e6ef0544c42b46f4af35d5e7f649%1782307148.845616",
|
||||
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
|
||||
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
|
||||
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1782392402.296732",
|
||||
"boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1782392419.475605",
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1782307147.395833"
|
||||
],
|
||||
"build_requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1778091116.056",
|
||||
"strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1774447376.964",
|
||||
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12",
|
||||
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707",
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
|
||||
"strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1782395692.540639",
|
||||
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
|
||||
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1782395690.33162",
|
||||
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
|
||||
"m4/1.4.19#4523e4347b55cd26ae918bd5770cab9a%1778062762.471",
|
||||
"cmake/4.3.0#b939a42e98f593fb34d3a8c5cc860359%1774439249.183",
|
||||
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1774439233.447",
|
||||
"m4/1.4.19#34c4bbc3eeebe98ca6edf2f52d602e7d%1777282960.259",
|
||||
"cmake/4.3.3#840cf00ea09777e05c2050a50a82c722%1782392418.696091",
|
||||
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1782392402.624226",
|
||||
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
|
||||
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196"
|
||||
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1782307147.395833"
|
||||
],
|
||||
"python_requires": [],
|
||||
"overrides": {
|
||||
@@ -57,7 +57,7 @@
|
||||
"boost/1.91.0"
|
||||
],
|
||||
"lz4/[>=1.9.4 <2]": [
|
||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504"
|
||||
"lz4/1.10.0#982d9b673900f665a1da109e09c17cab"
|
||||
]
|
||||
},
|
||||
"config_requires": []
|
||||
|
||||
@@ -14,7 +14,7 @@ export CONAN_HOME="$TEMP_DIR"
|
||||
# Ensure that the xrplf remote is the first to be consulted, so any recipes we
|
||||
# patched are used. We also add it there to not created huge diff when the
|
||||
# official Conan Center Index is updated.
|
||||
conan remote add --force --index 0 xrplf https://conan.ripplex.io
|
||||
conan remote add --force --index 0 xrplf https://conan.xrplf.org/repository/conan/
|
||||
|
||||
# Delete any existing lockfile.
|
||||
rm -f conan.lock
|
||||
|
||||
@@ -28,10 +28,10 @@ class Xrpl(ConanFile):
|
||||
|
||||
requires = [
|
||||
"ed25519/2015.03",
|
||||
"grpc/1.81.0",
|
||||
"grpc/1.81.1",
|
||||
"libarchive/3.8.7",
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.6.2",
|
||||
"openssl/3.6.3",
|
||||
"secp256k1/0.7.1",
|
||||
"soci/4.0.3",
|
||||
"zlib/1.3.2",
|
||||
|
||||
2
docs/build/advanced_conan.md
vendored
2
docs/build/advanced_conan.md
vendored
@@ -34,7 +34,7 @@ higher index than the default Conan Center remote, so it is consulted first. You
|
||||
can do this by running:
|
||||
|
||||
```bash
|
||||
conan remote add --index 0 --force xrplf https://conan.ripplex.io
|
||||
conan remote add --index 0 --force xrplf https://conan.xrplf.org/repository/conan/
|
||||
```
|
||||
|
||||
Alternatively, you can pull our recipes from the repository and export them locally:
|
||||
|
||||
@@ -375,16 +375,35 @@ public:
|
||||
*/
|
||||
class ValidAmounts
|
||||
{
|
||||
std::vector<std::shared_ptr<SLE const>> afterEntries_;
|
||||
std::vector<SLE::const_pointer> afterEntries_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
visitEntry(bool, SLE::const_ref, SLE::const_ref);
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
|
||||
};
|
||||
|
||||
/*
|
||||
* Verify that when an object with an associated pseudo-account is deleted,
|
||||
* its pseudo-account is also deleted.
|
||||
*
|
||||
* The reverse (pseudo-account deleted → object deleted) is enforced by
|
||||
* AccountRootsDeletedClean via getPseudoAccountFields().
|
||||
*/
|
||||
class ObjectHasPseudoAccount
|
||||
{
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, SLE::const_ref, SLE::const_ref);
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
|
||||
|
||||
private:
|
||||
std::vector<SLE::const_pointer> deletedObjSles_;
|
||||
};
|
||||
// additional invariant checks can be declared above and then added to this
|
||||
// tuple
|
||||
using InvariantChecks = std::tuple<
|
||||
@@ -415,7 +434,8 @@ using InvariantChecks = std::tuple<
|
||||
ValidVault,
|
||||
ValidMPTPayment,
|
||||
ValidAmounts,
|
||||
ValidMPTTransfer>;
|
||||
ValidMPTTransfer,
|
||||
ObjectHasPseudoAccount>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -63,6 +64,23 @@ hasPrivilege(STTx const& tx, Privilege priv)
|
||||
#undef TRANSACTION
|
||||
#pragma pop_macro("TRANSACTION")
|
||||
|
||||
// Returns the human-readable name of a ledger entry's type, falling back to
|
||||
// the numeric type if the format is somehow unknown.
|
||||
static std::string
|
||||
ledgerEntryTypeName(SLE const& sle)
|
||||
{
|
||||
auto const item = LedgerFormats::getInstance().findByType(sle.getType());
|
||||
|
||||
if (item == nullptr)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::ledgerEntryTypeName : ledger entry has no known ledger format");
|
||||
return std::to_string(sle.getType());
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return item->getName();
|
||||
}
|
||||
|
||||
void
|
||||
TransactionFeeCheck::visitEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
@@ -457,16 +475,8 @@ AccountRootsDeletedClean::finalize(
|
||||
if (auto const sle = view.read(keylet))
|
||||
{
|
||||
// Finding the object is bad
|
||||
auto const typeName = [&sle]() {
|
||||
auto item = LedgerFormats::getInstance().findByType(sle->getType());
|
||||
|
||||
if (item != nullptr)
|
||||
return item->getName();
|
||||
return std::to_string(sle->getType());
|
||||
}();
|
||||
|
||||
JLOG(j.fatal()) << "Invariant failed: account deletion left behind a " << typeName
|
||||
<< " object";
|
||||
JLOG(j.fatal()) << "Invariant failed: account deletion left behind a "
|
||||
<< ledgerEntryTypeName(*sle) << " object";
|
||||
// The comment above starting with "assert(enforce)" explains this
|
||||
// assert.
|
||||
XRPL_ASSERT(
|
||||
@@ -1071,4 +1081,69 @@ ValidAmounts::finalize(
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ObjectHasPseudoAccount::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
|
||||
{
|
||||
if (!isDelete)
|
||||
return;
|
||||
|
||||
// Before should never be null when isDelete = true
|
||||
if (!before)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ObjectHasPseudoAccount::visitEntry : deleted ledger entry missing before state");
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
switch (before->getType())
|
||||
{
|
||||
case ltAMM:
|
||||
case ltVAULT:
|
||||
case ltLOAN_BROKER:
|
||||
deletedObjSles_.push_back(before);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
ObjectHasPseudoAccount::finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) const
|
||||
{
|
||||
if (!view.rules().enabled(fixCleanup3_3_0))
|
||||
return true;
|
||||
|
||||
if (deletedObjSles_.empty())
|
||||
return true;
|
||||
|
||||
bool failed = false;
|
||||
for (auto const& sle : deletedObjSles_)
|
||||
{
|
||||
if (!sle->isFieldPresent(sfAccount))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: deleted " << ledgerEntryTypeName(*sle)
|
||||
<< " is missing pseudo-account field";
|
||||
failed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// The pseudo-account must NOT exist on the ledger after the object is deleted.
|
||||
if (view.exists(keylet::account(sle->getAccountID(sfAccount))))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: deleted " << ledgerEntryTypeName(*sle)
|
||||
<< " without deleting its pseudo-account";
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return !failed;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -2823,7 +2823,8 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
});
|
||||
|
||||
doInvariantCheck(
|
||||
{"vault updated by a wrong transaction type"},
|
||||
{"vault updated by a wrong transaction type",
|
||||
"deleted Vault without deleting its pseudo-account"},
|
||||
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
auto const keylet = keylet::vault(a1.id(), ac.view().seq());
|
||||
auto sleVault = ac.view().peek(keylet);
|
||||
@@ -2834,7 +2835,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttPAYMENT, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
[&](Account const& a1, Account const& a2, Env& env) {
|
||||
Vault const vault{env};
|
||||
auto [tx, _] = vault.create({.owner = a1, .asset = xrpIssue()});
|
||||
@@ -2871,6 +2872,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
auto const vaultPage = ac.view().dirInsert(
|
||||
keylet::ownerDir(a1.id()), sleVault->key(), describeOwnerDir(a1.id()));
|
||||
sleVault->setFieldU64(sfOwnerNode, *vaultPage);
|
||||
sleVault->setAccountID(sfAccount, a1.id());
|
||||
ac.view().insert(sleVault);
|
||||
return true;
|
||||
},
|
||||
@@ -2879,7 +2881,8 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
|
||||
doInvariantCheck(
|
||||
{"vault deleted by a wrong transaction type"},
|
||||
{"vault deleted by a wrong transaction type",
|
||||
"deleted Vault without deleting its pseudo-account"},
|
||||
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
auto const keylet = keylet::vault(a1.id(), ac.view().seq());
|
||||
auto sleVault = ac.view().peek(keylet);
|
||||
@@ -2890,7 +2893,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttVAULT_SET, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
[&](Account const& a1, Account const& a2, Env& env) {
|
||||
Vault const vault{env};
|
||||
auto [tx, _] = vault.create({.owner = a1, .asset = xrpIssue()});
|
||||
@@ -2899,7 +2902,8 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
});
|
||||
|
||||
doInvariantCheck(
|
||||
{"vault operation updated more than single vault"},
|
||||
{"vault operation updated more than single vault",
|
||||
"deleted Vault without deleting its pseudo-account"},
|
||||
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
{
|
||||
auto const keylet = keylet::vault(a1.id(), ac.view().seq());
|
||||
@@ -2919,7 +2923,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttVAULT_DELETE, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
[&](Account const& a1, Account const& a2, Env& env) {
|
||||
Vault const vault{env};
|
||||
{
|
||||
@@ -2943,6 +2947,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
auto const vaultPage = ac.view().dirInsert(
|
||||
keylet::ownerDir(a.id()), sleVault->key(), describeOwnerDir(a.id()));
|
||||
sleVault->setFieldU64(sfOwnerNode, *vaultPage);
|
||||
sleVault->setAccountID(sfAccount, a.id());
|
||||
ac.view().insert(sleVault);
|
||||
};
|
||||
insertVault(a1);
|
||||
@@ -2954,7 +2959,8 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
|
||||
doInvariantCheck(
|
||||
{"deleted vault must also delete shares"},
|
||||
{"deleted vault must also delete shares",
|
||||
"deleted Vault without deleting its pseudo-account"},
|
||||
[&](Account const& a1, Account const& a2, ApplyContext& ac) {
|
||||
auto const keylet = keylet::vault(a1.id(), ac.view().seq());
|
||||
auto sleVault = ac.view().peek(keylet);
|
||||
@@ -2965,7 +2971,7 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttVAULT_DELETE, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
[&](Account const& a1, Account const& a2, Env& env) {
|
||||
Vault const vault{env};
|
||||
auto [tx, _] = vault.create({.owner = a1, .asset = xrpIssue()});
|
||||
@@ -4965,6 +4971,125 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testObjectHasPseudoAccount()
|
||||
{
|
||||
testcase << "object has pseudo-account";
|
||||
using namespace jtx;
|
||||
|
||||
auto const amendments = defaultAmendments() | fixCleanup3_3_0;
|
||||
|
||||
// Vault: object deleted without its pseudo-account
|
||||
{
|
||||
Keylet vaultKeylet = keylet::amendments();
|
||||
doInvariantCheck(
|
||||
Env{*this, amendments},
|
||||
{{"deleted Vault without deleting its pseudo-account"}},
|
||||
[&vaultKeylet](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(vaultKeylet);
|
||||
if (!sle)
|
||||
return false;
|
||||
ac.view().erase(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttVAULT_DELETE, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
[&vaultKeylet](Account const& a1, Account const&, Env& env) {
|
||||
Vault const vault{env};
|
||||
auto [tx, keylet] = vault.create({.owner = a1, .asset = xrpIssue()});
|
||||
env(tx);
|
||||
vaultKeylet = keylet;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// AMM: object deleted without its pseudo-account
|
||||
{
|
||||
uint256 ammID{};
|
||||
Account const gw{"gw"};
|
||||
doInvariantCheck(
|
||||
Env{*this, amendments},
|
||||
{{"deleted AMM without deleting its pseudo-account"}},
|
||||
[&ammID](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(keylet::amm(ammID));
|
||||
if (!sle)
|
||||
return false;
|
||||
ac.view().erase(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttAMM_DELETE, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
[&ammID, &gw](Account const&, Account const&, Env& env) {
|
||||
env.fund(XRP(1'000), gw);
|
||||
AMM const amm(env, gw, XRP(100), gw["USD"](100));
|
||||
ammID = amm.ammID();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// LoanBroker: object deleted without its pseudo-account
|
||||
{
|
||||
Keylet loanBrokerKeylet = keylet::amendments();
|
||||
doInvariantCheck(
|
||||
Env{*this, amendments},
|
||||
{{"deleted LoanBroker without deleting its pseudo-account"}},
|
||||
[&loanBrokerKeylet](Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sle = ac.view().peek(loanBrokerKeylet);
|
||||
if (!sle)
|
||||
return false;
|
||||
ac.view().erase(sle);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttLOAN_BROKER_DELETE, [](STObject&) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
[&loanBrokerKeylet, this](Account const& a1, Account const&, Env& env) {
|
||||
PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
|
||||
loanBrokerKeylet = this->createLoanBroker(a1, env, xrpAsset);
|
||||
return BEAST_EXPECT(env.le(loanBrokerKeylet));
|
||||
});
|
||||
}
|
||||
|
||||
// Deleted object missing sfAccount field (defensive check).
|
||||
// Manually construct the view to place a vault SLE without
|
||||
// sfAccount into the base ledger, then erase it.
|
||||
{
|
||||
Env env{*this, amendments};
|
||||
Account const a1{"A1"};
|
||||
Account const a2{"A2"};
|
||||
env.fund(XRP(1000), a1, a2);
|
||||
env.close();
|
||||
|
||||
OpenView ov{*env.current()};
|
||||
|
||||
auto const vaultKeylet = keylet::vault(a1.id(), ov.seq());
|
||||
auto sleVault = std::make_shared<SLE>(vaultKeylet);
|
||||
sleVault->makeFieldAbsent(sfAccount);
|
||||
ov.rawInsert(sleVault);
|
||||
|
||||
STTx const tx{ttVAULT_DELETE, [](STObject&) {}};
|
||||
test::StreamSink sink{beast::Severity::Warning};
|
||||
beast::Journal const jlog{sink};
|
||||
ApplyContext ac{
|
||||
env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
|
||||
CurrentTransactionRulesGuard const rulesGuard(ov.rules());
|
||||
|
||||
auto sle = ac.view().peek(vaultKeylet);
|
||||
if (!BEAST_EXPECT(sle))
|
||||
return;
|
||||
ac.view().erase(sle);
|
||||
|
||||
auto transactor = makeTransactor(ac);
|
||||
if (!BEAST_EXPECT(transactor))
|
||||
return;
|
||||
TER const result = transactor->checkInvariants(tesSUCCESS, XRPAmount{});
|
||||
BEAST_EXPECT(result == tecINVARIANT_FAILED);
|
||||
BEAST_EXPECT(sink.messages().str().contains("is missing pseudo-account field"));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -4998,6 +5123,7 @@ public:
|
||||
testInvariantOverwrite(defaultAmendments() - fixCleanup3_1_3);
|
||||
testVaultComputeCoarsestScale();
|
||||
testAMM();
|
||||
testObjectHasPseudoAccount();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user