mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-16 12:28:34 +00:00
Compare commits
1 Commits
develop
...
dangell7/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48ca26c2a4 |
4
.github/scripts/strategy-matrix/generate.py
vendored
4
.github/scripts/strategy-matrix/generate.py
vendored
@@ -81,6 +81,8 @@ class LinuxConfig:
|
||||
suffix: str = ""
|
||||
extra_cmake_args: str = ""
|
||||
package: PackageConfig | None = None # set to also package this config
|
||||
# Flip every amendment to Supported::Yes before building (perf/test only).
|
||||
force_supported: bool = False
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.package, dict):
|
||||
@@ -168,6 +170,7 @@ class MatrixEntry:
|
||||
image: str = "" # container image; empty for macOS/Windows (runs natively)
|
||||
compiler: str = "" # compiler name ("gcc" or "clang"); empty for macOS/Windows
|
||||
toolchain: str = "" # "nix" for the flake's CI environment; see PlatformConfig
|
||||
force_supported: bool = False # flip amendments to Supported::Yes before build
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
@@ -233,6 +236,7 @@ def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]:
|
||||
architecture=arch_info,
|
||||
sanitizers=sanitizer,
|
||||
compiler=compiler,
|
||||
force_supported=cfg.force_supported,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
18
.github/scripts/strategy-matrix/linux.json
vendored
18
.github/scripts/strategy-matrix/linux.json
vendored
@@ -17,7 +17,6 @@
|
||||
"minimal": true,
|
||||
"benchmark": true
|
||||
},
|
||||
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
@@ -30,7 +29,6 @@
|
||||
"arch": ["arm64"],
|
||||
"minimal": false
|
||||
},
|
||||
|
||||
{
|
||||
"compiler": ["gcc", "clang"],
|
||||
"build_type": ["Debug", "Release"],
|
||||
@@ -38,7 +36,6 @@
|
||||
"minimal": false,
|
||||
"sanitizers": ["address", "undefinedbehavior"]
|
||||
},
|
||||
|
||||
{
|
||||
"compiler": ["clang"],
|
||||
"build_type": ["Debug"],
|
||||
@@ -62,9 +59,21 @@
|
||||
"minimal": false,
|
||||
"suffix": "unity",
|
||||
"extra_cmake_args": "-Dunity=ON"
|
||||
},
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": false,
|
||||
"suffix": "supported",
|
||||
"force_supported": true,
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "deb",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-49cdc10"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"debian": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
@@ -78,7 +87,6 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"rhel": [
|
||||
{
|
||||
"compiler": ["gcc"],
|
||||
|
||||
103
.github/workflows/build-supported-image.yml
vendored
Normal file
103
.github/workflows/build-supported-image.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
# Package the "all amendments Supported::Yes" build into a runtime Docker image
|
||||
# and push it to GHCR, as a drop-in for the rippleci/xrpld image xrpl.js uses
|
||||
# for standalone testing -- except every amendment is built Supported::Yes.
|
||||
#
|
||||
# This does NOT build or package anything: the Trigger workflow already builds
|
||||
# the supported binary and the supported .deb (the force_supported build config
|
||||
# and the matching supported package config in linux.json). This workflow waits
|
||||
# for a successful Trigger run on develop, downloads that run's supported .deb
|
||||
# artifact, installs it into a slim base (docker/supported.Dockerfile, which
|
||||
# replicates rippleci's layout), and pushes ghcr.io/xrplf/xrpld/supported.
|
||||
#
|
||||
# Perf/test artifact only -- never run it on a production validator.
|
||||
name: Build supported Docker image
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Trigger"]
|
||||
types: [completed]
|
||||
branches: [develop]
|
||||
# Manual runs: point at a specific completed Trigger run via its run id.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
trigger_run_id:
|
||||
description: "Run id of the Trigger workflow whose supported .deb to package."
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ghcr.io/xrplf/xrpld/supported
|
||||
# The supported .deb artifact uploaded by reusable-package.yml:
|
||||
# <artifact_name>-pkg, where artifact_name carries the -supported suffix.
|
||||
DEB_ARTIFACT: xrpld-ubuntu-gcc-release-amd64-supported-pkg
|
||||
SOURCE_RUN_ID: ${{ github.event.workflow_run.id || github.event.inputs.trigger_run_id }}
|
||||
jobs:
|
||||
image:
|
||||
# Only for successful Trigger runs (workflow_run), and only on the canonical
|
||||
# repo where GITHUB_TOKEN can push to ghcr.io/xrplf/*.
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && (github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Download the supported .deb from the Trigger run
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ env.DEB_ARTIFACT }}
|
||||
path: dl
|
||||
run-id: ${{ env.SOURCE_RUN_ID }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Assemble build context
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p docker-context
|
||||
deb="$(find dl -name '*.deb' | head -n1)"
|
||||
[ -n "${deb}" ] || {
|
||||
echo "::error::no supported .deb found in run ${SOURCE_RUN_ID}"
|
||||
exit 1
|
||||
}
|
||||
mv "${deb}" docker-context/xrpld.deb
|
||||
echo "Packaging $(basename "${deb}") into ${IMAGE_NAME}"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-,format=short
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||
with:
|
||||
context: docker-context
|
||||
file: docker/supported.Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
19
.github/workflows/reusable-build-test-config.yml
vendored
19
.github/workflows/reusable-build-test-config.yml
vendored
@@ -74,6 +74,11 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
force_supported:
|
||||
description: "Flip every amendment to Supported::Yes before building. For perf/test builds only; never for release artifacts."
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
@@ -128,6 +133,20 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Force all amendments to Supported::Yes
|
||||
if: ${{ inputs.force_supported && runner.os == 'Linux' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
MACRO="include/xrpl/protocol/detail/features.macro"
|
||||
echo "Flipping Supported::No -> Supported::Yes in ${MACRO}:"
|
||||
grep -n 'Supported::No,' "${MACRO}" || echo " (none found)"
|
||||
sed -i 's/Supported::No,/Supported::Yes,/g' "${MACRO}"
|
||||
if grep -q 'Supported::No,' "${MACRO}"; then
|
||||
echo "::error::Supported::No entries remain after sed"
|
||||
exit 1
|
||||
fi
|
||||
git diff -- "${MACRO}" || true
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
|
||||
1
.github/workflows/reusable-build-test.yml
vendored
1
.github/workflows/reusable-build-test.yml
vendored
@@ -52,5 +52,6 @@ jobs:
|
||||
sanitizers: ${{ matrix.sanitizers }}
|
||||
compiler: ${{ matrix.compiler || '' }}
|
||||
toolchain: ${{ matrix.toolchain || '' }}
|
||||
force_supported: ${{ matrix.force_supported || false }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -41,7 +41,6 @@ Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta
|
||||
- `gateway_balances`: The `account` and `ident` fields now return an `invalidParams` error if the value is not a string, instead of an `internal` error. [#7655](https://github.com/XRPLF/rippled/pull/7655)
|
||||
- `account_lines`: The `peer` field now returns an error if the value is not a string. [#7728](https://github.com/XRPLF/rippled/pull/7728)
|
||||
- `ledger`: `delivered_amount` is now included in the metadata of successful `AccountDelete` transactions when transactions are expanded (`expand`, or admin-only `full`). Previously it was only added for `Payment` and `CheckCash`, which made `ledger` inconsistent with `tx` and `account_tx`. [#5706](https://github.com/XRPLF/rippled/pull/5706)
|
||||
- `noripple_check`: The `transactions` field is no longer included in error responses; it is still returned (possibly as an empty array) whenever `transactions` is `true` and the request succeeds. A malformed `account` is now rejected before the ledger is looked up, so that error response no longer carries the `ledger_hash`, `ledger_index`, and `validated` fields ([#6303](https://github.com/XRPLF/rippled/pull/6303)).
|
||||
|
||||
## XRP Ledger server version 3.3.0
|
||||
|
||||
|
||||
25
docker/supported.Dockerfile
Normal file
25
docker/supported.Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
# Runtime image for the perf/test xrpld build with all amendments Supported::Yes.
|
||||
# Installs the .deb into ubuntu:jammy (matching rippleci/xrpld): gives
|
||||
# /usr/bin/xrpld, /etc/xrpld/xrpld.cfg, and the xrpld user.
|
||||
# NOT for production validators.
|
||||
ARG BASE_IMAGE=ubuntu:jammy
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
# Build context must contain the supported package as xrpld.deb.
|
||||
COPY xrpld.deb /tmp/xrpld.deb
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends ca-certificates jq /tmp/xrpld.deb; \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/xrpld.deb; \
|
||||
id -u xrpld >/dev/null 2>&1 || \
|
||||
useradd --system --home-dir /var/lib/xrpld --shell /sbin/nologin --user-group xrpld; \
|
||||
mkdir -p /var/log/xrpld /var/lib/xrpld; \
|
||||
chown -R xrpld:xrpld /var/log/xrpld /var/lib/xrpld; \
|
||||
# Symlink for consumers that exec /opt/xrpld/bin/xrpld.
|
||||
mkdir -p /opt/xrpld/bin; \
|
||||
ln -sf /usr/bin/xrpld /opt/xrpld/bin/xrpld
|
||||
|
||||
EXPOSE 2459/tcp 5005/tcp 6006/tcp
|
||||
USER xrpld
|
||||
ENTRYPOINT ["/usr/bin/xrpld"]
|
||||
@@ -26,8 +26,7 @@ struct Config
|
||||
/**
|
||||
* The largest number of public peer slots to allow.
|
||||
* This includes both inbound and outbound, but does not include
|
||||
* fixed peers. A configuration built by `makeConfig` always holds
|
||||
* `maxPeers == inPeers + outPeers`.
|
||||
* fixed peers.
|
||||
*/
|
||||
std::size_t maxPeers{tuning::kDefaultMaxPeers};
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STInteger.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
@@ -302,63 +301,6 @@ verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 co
|
||||
NotTEC
|
||||
checkEncryptedAmountFormat(STObject const& object);
|
||||
|
||||
/**
|
||||
* @brief Checks whether a holder's issuer mirror is encrypted under the
|
||||
* issuance's currently registered issuer key.
|
||||
*
|
||||
* Verifies that the holder's issuer mirror epoch matches the active issuer key
|
||||
* epoch on the issuance. An absent mirror epoch defaults to epoch 0. A holder without an issuer
|
||||
* mirror is considered stale, as there is no key anchor for future re-encryptions.
|
||||
*
|
||||
* @param issuance The MPTokenIssuance ledger object.
|
||||
* @param mptoken The holder's MPToken ledger object.
|
||||
* @return true if the MPToken's issuer mirror is current. false if stale.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isIssuerMirrorCurrent(SLE const& issuance, SLE const& mptoken);
|
||||
|
||||
/**
|
||||
* @brief Checks whether a holder's auditor mirror is encrypted under the
|
||||
* issuance's currently registered auditor key.
|
||||
*
|
||||
* Verifies that the holder's auditor mirror epoch matches the active auditor key
|
||||
* epoch on the issuance. An absent mirror epoch defaults to epoch 0. An issuance
|
||||
* without an auditor key requires no auditor mirror and is considered current.
|
||||
*
|
||||
* @param issuance The MPTokenIssuance ledger object.
|
||||
* @param mptoken The holder's MPToken ledger object.
|
||||
* @return true if the auditor mirror is current or not required.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isAuditorMirrorCurrent(SLE const& issuance, SLE const& mptoken);
|
||||
|
||||
/**
|
||||
* @brief Checks whether each mirror a holder is required to have is encrypted
|
||||
* under the issuance's currently registered ElGamal keys.
|
||||
*
|
||||
* Verifies that both the issuer mirror and the auditor mirror (if required)
|
||||
* are current. This serves as a combined check, ensuring all necessary
|
||||
* holder mirror epochs match the active key epochs on the issuance.
|
||||
*
|
||||
* @param issuance The MPTokenIssuance ledger object.
|
||||
* @param mptoken The holder's MPToken ledger object.
|
||||
* @return true if the required mirrors are current.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
areMirrorsCurrent(SLE const& issuance, SLE const& mptoken);
|
||||
|
||||
/**
|
||||
* @brief Set the holder's MPToken mirror epochs to match the issuance's current key epochs.
|
||||
*
|
||||
* Call this after writing mirror ciphertexts under the issuance's currently
|
||||
* registered keys, so that the mirrors read as current afterwards.
|
||||
*
|
||||
* @param issuance The MPTokenIssuance ledger object.
|
||||
* @param mptoken The holder's MPToken ledger entry to update.
|
||||
*/
|
||||
void
|
||||
setMirrorEpochs(SLE const& issuance, SLE& mptoken);
|
||||
|
||||
/**
|
||||
* @brief Verifies revealed amount encryptions for all recipients.
|
||||
*
|
||||
|
||||
@@ -436,8 +436,6 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
{sfConfidentialBalanceVersion, SoeDefault},
|
||||
{sfIssuerEncryptedBalance, SoeOptional},
|
||||
{sfAuditorEncryptedBalance, SoeOptional},
|
||||
{sfIssuerKeyMirrorEpoch, SoeOptional},
|
||||
{sfAuditorKeyMirrorEpoch, SoeOptional},
|
||||
{sfHolderEncryptionKey, SoeOptional},
|
||||
}))
|
||||
|
||||
|
||||
@@ -278,7 +278,6 @@ JSS(frozen_balances); // out: GatewayBalances
|
||||
JSS(full); // in: LedgerClearer, handlers/Ledger
|
||||
JSS(full_reply); // out: PathFind
|
||||
JSS(fullbelow_size); // out: GetCounts
|
||||
JSS(gateway); // in: noripple_check
|
||||
JSS(git); // out: server_info
|
||||
JSS(good); // out: RPCVersion
|
||||
JSS(hash); // out: NetworkOPs, InboundLedger, LedgerToJson, STTx; field
|
||||
@@ -482,7 +481,6 @@ JSS(ports); // out: NetworkOPs
|
||||
JSS(previous); // out: Reservations
|
||||
JSS(previous_ledger); // out: LedgerPropose
|
||||
JSS(price); // out: amm_info, AuctionSlot
|
||||
JSS(problems); // out: noripple_check
|
||||
JSS(proof); // in: BookOffers
|
||||
JSS(propose_seq); // out: LedgerPropose
|
||||
JSS(proposers); // out: NetworkOPs, LedgerConsensus
|
||||
@@ -662,7 +660,6 @@ JSS(url); // in/out: Subscribe, Unsubscribe
|
||||
JSS(url_password); // in: Subscribe
|
||||
JSS(url_username); // in: Subscribe
|
||||
JSS(urlgravatar); //
|
||||
JSS(user); // in: noripple_check
|
||||
JSS(username); // in: Subscribe
|
||||
JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx*, Tx
|
||||
JSS(validator_list_expires); // out: NetworkOps, ValidatorList
|
||||
|
||||
@@ -268,54 +268,6 @@ public:
|
||||
return this->sle_->isFieldPresent(sfAuditorEncryptedBalance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfIssuerKeyMirrorEpoch (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getIssuerKeyMirrorEpoch() const
|
||||
{
|
||||
if (hasIssuerKeyMirrorEpoch())
|
||||
return this->sle_->at(sfIssuerKeyMirrorEpoch);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfIssuerKeyMirrorEpoch is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasIssuerKeyMirrorEpoch() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfIssuerKeyMirrorEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAuditorKeyMirrorEpoch (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getAuditorKeyMirrorEpoch() const
|
||||
{
|
||||
if (hasAuditorKeyMirrorEpoch())
|
||||
return this->sle_->at(sfAuditorKeyMirrorEpoch);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfAuditorKeyMirrorEpoch is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasAuditorKeyMirrorEpoch() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfAuditorKeyMirrorEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfHolderEncryptionKey (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -519,28 +471,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfIssuerKeyMirrorEpoch (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenBuilder&
|
||||
setIssuerKeyMirrorEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfIssuerKeyMirrorEpoch] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAuditorKeyMirrorEpoch (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenBuilder&
|
||||
setAuditorKeyMirrorEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAuditorKeyMirrorEpoch] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfHolderEncryptionKey (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -107,12 +107,8 @@ Config::makeConfig(
|
||||
else
|
||||
{
|
||||
config.outPeers = *limits.outPeers;
|
||||
|
||||
// Inbound slots only exist if we accept incoming connections, and
|
||||
// `maxPeers` is the total across both directions. The legacy branch
|
||||
// above upholds the same two invariants.
|
||||
config.inPeers = config.wantIncoming ? *limits.inPeers : 0;
|
||||
config.maxPeers = config.inPeers + config.outPeers;
|
||||
config.inPeers = *limits.inPeers;
|
||||
config.maxPeers = 0;
|
||||
}
|
||||
|
||||
// This will cause servers configured as validators to request that
|
||||
|
||||
@@ -6,11 +6,9 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STBlob.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
@@ -399,59 +397,6 @@ checkEncryptedAmountFormat(STObject const& object)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
isIssuerMirrorCurrent(SLE const& issuance, SLE const& mptoken)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuance.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::isIssuerMirrorCurrent : issuance MPTokenIssuance object");
|
||||
XRPL_ASSERT(
|
||||
mptoken.getType() == ltMPTOKEN, "xrpl::isIssuerMirrorCurrent : mptoken MPToken object");
|
||||
|
||||
return mptoken.isFieldPresent(sfIssuerEncryptedBalance) &&
|
||||
mptoken[~sfIssuerKeyMirrorEpoch].value_or(0) == issuance[~sfIssuerKeyEpoch].value_or(0);
|
||||
}
|
||||
|
||||
bool
|
||||
isAuditorMirrorCurrent(SLE const& issuance, SLE const& mptoken)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuance.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::isAuditorMirrorCurrent : issuance MPTokenIssuance object");
|
||||
XRPL_ASSERT(
|
||||
mptoken.getType() == ltMPTOKEN, "xrpl::isAuditorMirrorCurrent : mptoken MPToken object");
|
||||
|
||||
if (!issuance.isFieldPresent(sfAuditorEncryptionKey))
|
||||
return true;
|
||||
|
||||
return mptoken.isFieldPresent(sfAuditorEncryptedBalance) &&
|
||||
mptoken[~sfAuditorKeyMirrorEpoch].value_or(0) == issuance[~sfAuditorKeyEpoch].value_or(0);
|
||||
}
|
||||
|
||||
bool
|
||||
areMirrorsCurrent(SLE const& issuance, SLE const& mptoken)
|
||||
{
|
||||
return isIssuerMirrorCurrent(issuance, mptoken) && isAuditorMirrorCurrent(issuance, mptoken);
|
||||
}
|
||||
|
||||
void
|
||||
setMirrorEpochs(SLE const& issuance, SLE& mptoken)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuance.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::setMirrorEpochs : issuance MPTokenIssuance object");
|
||||
XRPL_ASSERT(mptoken.getType() == ltMPTOKEN, "xrpl::setMirrorEpochs : mptoken MPToken object");
|
||||
|
||||
if (auto const epoch = issuance[~sfIssuerKeyEpoch].value_or(0); epoch != 0)
|
||||
mptoken[sfIssuerKeyMirrorEpoch] = epoch;
|
||||
|
||||
if (mptoken.isFieldPresent(sfAuditorEncryptedBalance))
|
||||
{
|
||||
if (auto const epoch = issuance[~sfAuditorKeyEpoch].value_or(0); epoch != 0)
|
||||
mptoken[sfAuditorKeyMirrorEpoch] = epoch;
|
||||
}
|
||||
}
|
||||
|
||||
TER
|
||||
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -210,12 +209,6 @@ ConfidentialMPTClawback::doApply()
|
||||
(*sleHolderMPToken)[sfAuditorEncryptedBalance] = std::move(*encZeroForAuditor);
|
||||
}
|
||||
|
||||
// Allow clawback on stale mirrors since the issuer can still generate the
|
||||
// proof using the corresponding stale private key. The mirrors are updated
|
||||
// to the current epoch during execution.
|
||||
if (view().rules().enabled(featureConfidentialMPTKeyRotation))
|
||||
setMirrorEpochs(*sleIssuance, *sleHolderMPToken);
|
||||
|
||||
// Decrease Global Confidential Outstanding Amount
|
||||
auto const oldCOA = (*sleIssuance)[sfConfidentialOutstandingAmount];
|
||||
if (clawAmount > oldCOA)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
@@ -112,17 +111,6 @@ ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
|
||||
if (!sleMptoken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// An already-initialized holder has their new ciphertexts homomorphically
|
||||
// added to their existing mirrors, so those mirrors must be encrypted under
|
||||
// the currently registered keys. A first-time convert creates the mirrors
|
||||
// under those keys instead, and has nothing to be stale.
|
||||
if (ctx.view.rules().enabled(featureConfidentialMPTKeyRotation) &&
|
||||
sleMptoken->isFieldPresent(sfIssuerEncryptedBalance) &&
|
||||
!areMirrorsCurrent(*sleIssuance, *sleMptoken))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
auto const mptIssue = MPTIssue{issuanceID};
|
||||
|
||||
// Explicit freeze and auth checks are required because accountHolds
|
||||
@@ -343,10 +331,6 @@ ConfidentialMPTConvert::doApply()
|
||||
if (auditorEc)
|
||||
(*sleMptoken)[sfAuditorEncryptedBalance] = *auditorEc;
|
||||
|
||||
// Initialize key epochs when registering the keys.
|
||||
if (view().rules().enabled(featureConfidentialMPTKeyRotation))
|
||||
setMirrorEpochs(*sleIssuance, *sleMptoken);
|
||||
|
||||
// Spending balance starts at zero. Must use canonical zero encryption
|
||||
// (deterministic ciphertext) so the ledger state is reproducible.
|
||||
auto zeroBalance = encryptCanonicalZeroAmount(
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -198,14 +197,6 @@ ConfidentialMPTConvertBack::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Converting back homomorphically subtracts from the holder's mirrors, so
|
||||
// those mirrors must be current.
|
||||
if (ctx.view.rules().enabled(featureConfidentialMPTKeyRotation) &&
|
||||
!areMirrorsCurrent(*sleIssuance, *sleMptoken))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Sanity check: holder's MPToken must have auditor balance field if auditing
|
||||
// is enabled
|
||||
if (requiresAuditor && !sleMptoken->isFieldPresent(sfAuditorEncryptedBalance))
|
||||
|
||||
@@ -247,15 +247,6 @@ ConfidentialMPTSend::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// A send homomorphically updates the mirrors of both parties, so both must
|
||||
// be current.
|
||||
if (ctx.view.rules().enabled(featureConfidentialMPTKeyRotation) &&
|
||||
(!areMirrorsCurrent(*sleIssuance, *sleSenderMPToken) ||
|
||||
!areMirrorsCurrent(*sleIssuance, *sleDestinationMPToken)))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Sanity check: Both MPTokens' auditor fields must be present if auditing
|
||||
// is enabled
|
||||
if (requiresAuditor &&
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -605,661 +604,6 @@ class ConfidentialMPTKeyRotation_test : public ConfidentialTransferTestBase
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(kMaxKeyEpoch, kMaxKeyEpoch));
|
||||
}
|
||||
|
||||
void
|
||||
testConfidentialMPTConvertEpoch(FeatureBitset features)
|
||||
{
|
||||
testcase("ConfidentialMPTConvert mirror epoch");
|
||||
using namespace test::jtx;
|
||||
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const carol("carol");
|
||||
Account const auditor("auditor");
|
||||
|
||||
// A first-time convert with no rotation leaves both mirror
|
||||
// epochs absent.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}, .auditor = auditor});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob}, {auditor});
|
||||
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(std::nullopt, std::nullopt));
|
||||
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
|
||||
// Both mirrors are current, so converting again is allowed and
|
||||
// leaves the epochs untouched.
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 20,
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
}
|
||||
|
||||
// Every remaining case needs key rotation to be enabled.
|
||||
if (!features[featureConfidentialMPTKeyRotation])
|
||||
return;
|
||||
|
||||
// A first-time convert stamps the mirrors with whatever epochs the
|
||||
// issuance currently sits at. Only issuer key rotated in this case.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}, .auditor = auditor});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob, carol}, {auditor});
|
||||
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
// Ten rotations, issuance's issuer epoch is 10.
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
}
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(10u, std::nullopt));
|
||||
BEAST_EXPECT(mptAlice.checkEncryptionKeys(alice, auditor));
|
||||
|
||||
// carol converts for the first time, and her mirrors are stamped with the current
|
||||
// issuer epoch of 10.
|
||||
mptAlice.convert({
|
||||
.account = carol,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(carol),
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, 10u, std::nullopt));
|
||||
}
|
||||
|
||||
// A first-time convert stamps the mirrors with whatever epochs the
|
||||
// issuance currently sits at. Both keys rotated in this case.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}, .auditor = auditor});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob, carol}, {auditor});
|
||||
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
// 100 rotations of both keys, so both epochs are 100.
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
}
|
||||
|
||||
// 5 more rotations of the auditor key alone, so the auditor epoch is 105 now.
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
mptAlice.set({.account = alice, .auditorPubKey = mptAlice.getPubKey(auditor)});
|
||||
}
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(100u, 105u));
|
||||
BEAST_EXPECT(mptAlice.checkEncryptionKeys(alice, auditor));
|
||||
|
||||
// carol converts for the first time, and each of her mirrors is stamped with the epoch
|
||||
// of the key it was encrypted under.
|
||||
mptAlice.convert({
|
||||
.account = carol,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(carol),
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, 100u, 105u));
|
||||
}
|
||||
|
||||
// An issuer key rotation leaves an existing holder's issuer mirror
|
||||
// behind, converting will be blocked until the holder's mirror is updated to the new epoch.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}, .auditor = auditor});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob, carol}, {auditor});
|
||||
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
// carol initializes before any rotation, so her mirrors carry no epoch
|
||||
// at all, the state every holder is in before the amendment.
|
||||
mptAlice.convert({
|
||||
.account = carol,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(carol),
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, std::nullopt, std::nullopt));
|
||||
|
||||
// Rotate the issuer key to epoch 1.
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(1u, std::nullopt));
|
||||
|
||||
// bob converts for the first time which is allowed when registering the key.
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, 1u, std::nullopt));
|
||||
|
||||
// carol's absent epoch reads as 0 which is stale.
|
||||
mptAlice.convert({
|
||||
.account = carol,
|
||||
.amt = 20,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, std::nullopt, std::nullopt));
|
||||
|
||||
// Rotate the issuer key to epoch 2, leaving bob's issuer mirror stale.
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(2u, std::nullopt));
|
||||
|
||||
// This is not the first time convert, and bob's issuer mirror is behind the current
|
||||
// epoch, so the convert is rejected.
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 20,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
// The rejected convert leaves bob's mirrors exactly as they were.
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, 1u, std::nullopt));
|
||||
|
||||
// carol still cannot convert.
|
||||
mptAlice.convert({
|
||||
.account = carol,
|
||||
.amt = 20,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, std::nullopt, std::nullopt));
|
||||
}
|
||||
|
||||
// The auditor mirror is checked the same way, so rotating only the
|
||||
// auditor key blocks the convert on its own, with the issuer epoch
|
||||
// untouched.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}, .auditor = auditor});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob, carol}, {auditor});
|
||||
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
// bob initializes his confidential balance at epoch 0, so both of his
|
||||
// mirrors are current.
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
|
||||
// Rotate the auditor key only, leaving bob's auditor mirror behind
|
||||
// while his issuer mirror stays current.
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
mptAlice.set({.account = alice, .auditorPubKey = mptAlice.getPubKey(auditor)});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(std::nullopt, 1u));
|
||||
BEAST_EXPECT(mptAlice.checkEncryptionKeys(alice, auditor));
|
||||
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 20,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
|
||||
// Carol converts for the first time, and her auditor mirror is stamped with the current
|
||||
// auditor epoch of 1.
|
||||
mptAlice.convert({
|
||||
.account = carol,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(carol),
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, std::nullopt, 1u));
|
||||
}
|
||||
|
||||
// A late auditor key registration bumps no epoch.
|
||||
// Although both epochs are still zero, the convert is blocked
|
||||
// because auditor mirror is missing.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}, .auditor = auditor});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob}, {auditor});
|
||||
|
||||
// Register the issuer key only.
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
});
|
||||
|
||||
// The issuance has no auditor yet, so no auditor mirror is created.
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.fillAuditorEncryptedAmt = false,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
|
||||
// Register the auditor key later, which bumps no epoch.
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(std::nullopt, std::nullopt));
|
||||
|
||||
// bob's auditor mirror is still missing, so the convert is rejected.
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 20,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testConfidentialMPTSendEpoch(FeatureBitset features)
|
||||
{
|
||||
testcase("ConfidentialMPTSend mirror epoch");
|
||||
using namespace test::jtx;
|
||||
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const carol("carol");
|
||||
Account const auditor("auditor");
|
||||
|
||||
// Two holders that both initialized after a rotation are current, so a
|
||||
// send between them succeeds and leaves both mirrors untouched.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob, carol});
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
// Rotate the issuer key to epoch 1 before anyone holds a confidential
|
||||
// balance.
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(1u, std::nullopt));
|
||||
|
||||
for (auto const& holder : {bob, carol})
|
||||
{
|
||||
mptAlice.convert({
|
||||
.account = holder,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(holder),
|
||||
});
|
||||
mptAlice.mergeInbox({.account = holder});
|
||||
}
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, 1u, std::nullopt));
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, 1u, std::nullopt));
|
||||
|
||||
mptAlice.send({.account = bob, .dest = carol, .amt = 10});
|
||||
|
||||
// The epochs are unchanged after send.
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, 1u, std::nullopt));
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, 1u, std::nullopt));
|
||||
}
|
||||
|
||||
// Either the sender or the destination being stale will be rejected.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob, carol});
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
// carol initializes at epoch 0.
|
||||
mptAlice.convert({
|
||||
.account = carol,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(carol),
|
||||
});
|
||||
mptAlice.mergeInbox({.account = carol});
|
||||
|
||||
// Rotate the issuer key to epoch 1, leaving carol behind.
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
// bob initializes after the rotation, so his mirrors are current.
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
mptAlice.mergeInbox({.account = bob});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, std::nullopt, std::nullopt));
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, 1u, std::nullopt));
|
||||
|
||||
// This is rejected because the sender is the stale even though the destination is
|
||||
// current.
|
||||
mptAlice.send({
|
||||
.account = carol,
|
||||
.dest = bob,
|
||||
.amt = 10,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
// This is rejected because the destination is the stale even though the sender is
|
||||
// current.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 10,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
// The rejected sends leave both mirrors as they were.
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, std::nullopt, std::nullopt));
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, 1u, std::nullopt));
|
||||
}
|
||||
|
||||
// Auditor mirror is stale, the send will be rejected.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}, .auditor = auditor});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob, carol}, {auditor});
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
for (auto const& holder : {bob, carol})
|
||||
{
|
||||
mptAlice.convert({
|
||||
.account = holder,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(holder),
|
||||
});
|
||||
mptAlice.mergeInbox({.account = holder});
|
||||
}
|
||||
|
||||
// Rotate the auditor key only
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
mptAlice.set({.account = alice, .auditorPubKey = mptAlice.getPubKey(auditor)});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(std::nullopt, 1u));
|
||||
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 10,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
mptAlice.send({
|
||||
.account = carol,
|
||||
.dest = bob,
|
||||
.amt = 10,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(carol, std::nullopt, std::nullopt));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testConfidentialMPTConvertBackEpoch(FeatureBitset features)
|
||||
{
|
||||
testcase("ConfidentialMPTConvertBack mirror epoch");
|
||||
using namespace test::jtx;
|
||||
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const auditor("auditor");
|
||||
|
||||
// A holder who initialized after a rotation is current, so converting
|
||||
// back is allowed and leaves the epoch it was stamped with alone.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob});
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
// Rotate the issuer key to epoch 1 before bob holds a confidential
|
||||
// balance.
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(1u, std::nullopt));
|
||||
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
mptAlice.mergeInbox({.account = bob});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, 1u, std::nullopt));
|
||||
|
||||
mptAlice.convertBack({.account = bob, .amt = 20});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, 1u, std::nullopt));
|
||||
}
|
||||
|
||||
// Converting back with stale mirrors is rejected.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob});
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
// bob initializes at epoch 0.
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
mptAlice.mergeInbox({.account = bob});
|
||||
|
||||
// Converting back is allowed while his mirrors are still current.
|
||||
mptAlice.convertBack({.account = bob, .amt = 20});
|
||||
|
||||
// Rotate the issuer key to epoch 1, leaving bob behind.
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
.amt = 10,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
// The rejected convert back leaves bob's mirrors as they were.
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
}
|
||||
|
||||
// Converting back with a stale auditor mirror is rejected, even if the issuer mirror is
|
||||
// current.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}, .auditor = auditor});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob}, {auditor});
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
mptAlice.mergeInbox({.account = bob});
|
||||
|
||||
// Rotate the auditor key only, leaving bob behind on that mirror alone.
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
mptAlice.set({.account = alice, .auditorPubKey = mptAlice.getPubKey(auditor)});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(std::nullopt, 1u));
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
.amt = 10,
|
||||
.err = tecNO_PERMISSION,
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testConfidentialMPTClawbackEpoch(FeatureBitset features)
|
||||
{
|
||||
testcase("ConfidentialMPTClawback mirror epoch");
|
||||
using namespace test::jtx;
|
||||
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const auditor("auditor");
|
||||
|
||||
std::uint32_t const clawbackFlags =
|
||||
tfMPTCanTransfer | tfMPTCanHoldConfidentialBalance | tfMPTCanClawback;
|
||||
|
||||
// Clawback is not blocked on
|
||||
// a stale auditor mirror.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}, .auditor = auditor});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob}, {auditor}, clawbackFlags);
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mptAlice.getPubKey(alice),
|
||||
.auditorPubKey = mptAlice.getPubKey(auditor),
|
||||
});
|
||||
|
||||
// bob initializes both mirrors at epoch 0.
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
|
||||
// Rotate the auditor key twice, leaving bob's auditor mirror behind.
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
mptAlice.generateKeyPair(auditor);
|
||||
mptAlice.set({.account = alice, .auditorPubKey = mptAlice.getPubKey(auditor)});
|
||||
}
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(std::nullopt, 2u));
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
|
||||
mptAlice.confidentialClaw({.account = alice, .holder = bob, .amt = 50});
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, 2u));
|
||||
}
|
||||
|
||||
// A holder who initialized after a rotation is clawed back successfully, and
|
||||
// the issuer mirror is updated to the current epoch.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob}, {}, clawbackFlags);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
// Rotate the issuer key five times, issuance's issuer epoch is 5.
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
}
|
||||
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, 5u, std::nullopt));
|
||||
|
||||
mptAlice.confidentialClaw({.account = alice, .holder = bob, .amt = 50});
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, 5u, std::nullopt));
|
||||
}
|
||||
|
||||
// Clawback is not blocked on
|
||||
// a stale issuer mirror. For now the proof cannot verify: it is checked
|
||||
// against the key registered on the issuance, while the mirror is still
|
||||
// encrypted under the key it was written with, and that older key is
|
||||
// nowhere on the ledger yet. This will be added in a separate PR.
|
||||
{
|
||||
Env env{*this, features};
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||
setupConfidentialIssuance(mptAlice, alice, {bob}, {}, clawbackFlags);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
// bob initializes at epoch 0.
|
||||
mptAlice.convert({
|
||||
.account = bob,
|
||||
.amt = 50,
|
||||
.holderPubKey = mptAlice.getPubKey(bob),
|
||||
});
|
||||
|
||||
// Rotate the issuer key to epoch 1, leaving bob behind.
|
||||
mptAlice.generateKeyPair(alice);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(alice)});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkKeyEpochs(1u, std::nullopt));
|
||||
|
||||
mptAlice.confidentialClaw({
|
||||
.account = alice,
|
||||
.holder = bob,
|
||||
.amt = 50,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
|
||||
BEAST_EXPECT(mptAlice.checkMirrorEpochs(bob, std::nullopt, std::nullopt));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
testMPTokenIssuanceSetWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -1282,12 +626,6 @@ public:
|
||||
|
||||
testMPTokenIssuanceSetWithFeats(all);
|
||||
testMPTokenIssuanceSetWithFeats(all - featureConfidentialMPTKeyRotation);
|
||||
|
||||
testConfidentialMPTConvertEpoch(all);
|
||||
testConfidentialMPTConvertEpoch(all - featureConfidentialMPTKeyRotation);
|
||||
testConfidentialMPTSendEpoch(all);
|
||||
testConfidentialMPTConvertBackEpoch(all);
|
||||
testConfidentialMPTClawbackEpoch(all);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2191,7 +2191,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.account = bob,
|
||||
.dest = bob,
|
||||
.amt = 10,
|
||||
.proof = getTrivialSendProofHex(),
|
||||
.err = temMALFORMED,
|
||||
});
|
||||
|
||||
@@ -2898,6 +2897,22 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
auto& mptAlice = confEnv.mpt;
|
||||
|
||||
{
|
||||
// Bob has 60, tries to send 70. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 70,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
|
||||
// Bob has 60, tries to send 61. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 61,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
|
||||
// Bob has 60, sends 60. Remainder is exactly 0. Valid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
@@ -2918,12 +2933,12 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
});
|
||||
|
||||
// Bob has 100, tries to send 2^64-1. Invalid remaining balance.
|
||||
{
|
||||
ConfidentialSendSetup const setup(
|
||||
mptAlice, bob, carol, alice, std::numeric_limits<std::uint64_t>::max());
|
||||
auto const forged = getForgedSendProof(mptAlice, env, bob, carol, setup);
|
||||
mptAlice.send(setup.sendArgs(bob, carol, forged, tecBAD_PROOF));
|
||||
}
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = std::numeric_limits<std::uint64_t>::max(),
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
|
||||
// Bob sends 1, remaining 99.
|
||||
mptAlice.send({
|
||||
@@ -2932,6 +2947,14 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.amt = 1,
|
||||
.err = tesSUCCESS,
|
||||
});
|
||||
|
||||
// Bob sends 100, but only has 99. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = 100,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
}
|
||||
|
||||
// send when spending balance is 0 (key registered, inbox merged, but nothing converted)
|
||||
@@ -2948,13 +2971,18 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
|
||||
// Trying to send any amount with 0 spending balance must fail:
|
||||
// the range proof for < 0 is invalid.
|
||||
ConfidentialSendSetup const setup(mptAlice2, bob2, carol2, alice2, 1);
|
||||
auto const forged = getForgedSendProof(mptAlice2, env2, bob2, carol2, setup);
|
||||
mptAlice2.send(setup.sendArgs(bob2, carol2, forged, tecBAD_PROOF));
|
||||
mptAlice2.send({
|
||||
.account = bob2,
|
||||
.dest = carol2,
|
||||
.amt = 1,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
|
||||
BEAST_EXPECT(
|
||||
mptAlice2.getDecryptedBalance(bob2, MPTTester::holderEncryptedSpending) == 0);
|
||||
}
|
||||
|
||||
// todo: test m exceeding range, require using scala and refactor
|
||||
}
|
||||
|
||||
/* The equality proof library and range proof library do not
|
||||
@@ -3434,7 +3462,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const convertBackContextHash =
|
||||
getConvertBackContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
convertBackAmt,
|
||||
convertBackContextHash,
|
||||
@@ -3444,8 +3472,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
{
|
||||
json::Value jv;
|
||||
@@ -3457,7 +3483,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
jv[sfIssuerEncryptedAmount.jsonName] = strHex(convertBackIssuerCiphertext);
|
||||
jv[sfBlindingFactor.jsonName] = strHex(convertBackBlindingFactor);
|
||||
jv[sfBalanceCommitment.jsonName] = strHex(pedersenCommitment);
|
||||
jv[sfZKProof.jsonName] = strHex(requireOptionalRef(proof, "Missing proof"));
|
||||
jv[sfZKProof.jsonName] = strHex(proof);
|
||||
|
||||
env(jv, Ter(tesSUCCESS));
|
||||
}
|
||||
@@ -5257,7 +5283,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
Buffer const badPedersenCommitment =
|
||||
mptAlice.getPedersenCommitment(1, pcBlindingFactor);
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5267,8 +5293,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5289,7 +5313,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5299,8 +5323,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = generateBlindingFactor(), // wrong blinding factor
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5315,26 +5337,22 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
}
|
||||
|
||||
// Test 3: Proof generated with wrong balance value.
|
||||
// The sigma proof claims balance=20 but the pedersen commitment and
|
||||
// encrypted spending balance were built for the actual balance (40).
|
||||
// we cannot call mpt_get_convert_back_proof because it has client-side
|
||||
// verification.
|
||||
// The proof claims balance=1 but the encrypted spending balance contains
|
||||
// the actual balance. Verification fails because the values don't match.
|
||||
{
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
uint64_t constexpr claimedBalance = 20; // wrong: real balance is 40
|
||||
|
||||
auto const proof = getForgedConvertBackProof(
|
||||
mptAlice,
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
claimedBalance,
|
||||
spendingBalance,
|
||||
amt,
|
||||
pedersenCommitment,
|
||||
encryptedSpendingBalance,
|
||||
pcBlindingFactor,
|
||||
contextHash);
|
||||
contextHash,
|
||||
{
|
||||
.pedersenCommitment = pedersenCommitment,
|
||||
.amt = 1, // wrong balance
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5357,7 +5375,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
Buffer const badPedersenCommitment =
|
||||
mptAlice.getPedersenCommitment(1, pcBlindingFactor);
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5367,8 +5385,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5389,7 +5405,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
{
|
||||
uint256 const badContextHash{1};
|
||||
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
badContextHash, // wrong context hash
|
||||
@@ -5399,8 +5415,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5420,7 +5434,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5430,8 +5444,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5907,26 +5919,22 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// linkage, and that the remaining balance is non-negative.
|
||||
|
||||
// Test 1: Proof generated with wrong balance value.
|
||||
// The sigma proof claims balance=20 but the pedersen commitment and
|
||||
// encrypted spending balance were built for the actual balance (40).
|
||||
// we cannot call mpt_get_convert_back_proof because it has client-side
|
||||
// verification.
|
||||
// The sigma proof claims balance=1 but the spending balance contains the
|
||||
// actual balance. The compact proof's balance-linkage check fails.
|
||||
{
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
uint64_t constexpr claimedBalance = 20; // wrong: real balance is 40
|
||||
|
||||
auto const proof = getForgedConvertBackProof(
|
||||
mptAlice,
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
claimedBalance,
|
||||
spendingBalance,
|
||||
amt,
|
||||
pedersenCommitment,
|
||||
encryptedSpendingBalance,
|
||||
pcBlindingFactor,
|
||||
contextHash);
|
||||
contextHash,
|
||||
{
|
||||
.pedersenCommitment = pedersenCommitment,
|
||||
.amt = 1, // wrong balance (actual balance is ~40)
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5948,7 +5956,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5958,8 +5966,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = generateBlindingFactor(), // wrong blinding factor
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5979,7 +5985,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// makes the proof invalid for this transaction, preventing replay attacks.
|
||||
{
|
||||
uint256 const badContextHash{1};
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
badContextHash, // wrong context hash
|
||||
@@ -5989,8 +5995,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6010,7 +6014,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -6020,8 +6024,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6071,7 +6073,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
Buffer const bobCiphertext = mptAlice.encryptAmount(bob, amt, blindingFactor);
|
||||
auto const version = mptAlice.getMPTokenVersion(bob);
|
||||
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
makeContextHash(env, mptAlice, alice, bob, carol, version),
|
||||
@@ -6082,8 +6084,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
encryptedSpendingBalance, "Missing encrypted spending balance"),
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6173,7 +6173,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHashA =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), currentSeq, version);
|
||||
|
||||
auto const proofA = mptAlice.getConvertBackProof(
|
||||
Buffer const proofA = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amtA,
|
||||
contextHashA,
|
||||
@@ -6183,8 +6183,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proofA.has_value()))
|
||||
return;
|
||||
|
||||
// Construct Transaction B with Amount m2 = 20 and attach Proof pi
|
||||
uint64_t const amtB = 20;
|
||||
@@ -6256,7 +6254,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const oldContextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), currentSeq, versionV);
|
||||
|
||||
auto const oldProof = mptAlice.getConvertBackProof(
|
||||
Buffer const oldProof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
oldContextHash,
|
||||
@@ -6266,8 +6264,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalanceV,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(oldProof.has_value()))
|
||||
return;
|
||||
|
||||
// Submit and verify failure
|
||||
mptAlice.convertBack({
|
||||
@@ -6330,7 +6326,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), currentVersion);
|
||||
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -6340,8 +6336,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = spendingBalEnc,
|
||||
.blindingFactor = pcBf,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
// Submit transaction with Divergent Ciphertexts
|
||||
// Holder Ciphertext encrypts 11. Issuer Ciphertext encrypts 10.
|
||||
@@ -6475,7 +6469,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), currentVersion);
|
||||
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
1,
|
||||
contextHash,
|
||||
@@ -6485,8 +6479,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = underflowedCt,
|
||||
.blindingFactor = pcBf,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -7749,7 +7741,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const convertBackCtxHash =
|
||||
getConvertBackContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
auto const convertBackProof = mptAlice.getConvertBackProof(
|
||||
Buffer const convertBackProof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
sendAmount,
|
||||
convertBackCtxHash,
|
||||
@@ -7759,18 +7751,14 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpending,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(convertBackProof.has_value()))
|
||||
return;
|
||||
|
||||
// Resize the convertBack proof to match the expected send proof
|
||||
// size so it passes preflight's size check and reaches the actual
|
||||
// ZK verification in doApply.
|
||||
auto const expectedSendSize = kEcSendProofLength;
|
||||
Buffer resizedProof(expectedSendSize);
|
||||
Buffer const& convertBackProofRef =
|
||||
requireOptionalRef(convertBackProof, "Missing proof");
|
||||
auto const copyLen = std::min(convertBackProofRef.size(), expectedSendSize);
|
||||
std::memcpy(resizedProof.data(), convertBackProofRef.data(), copyLen);
|
||||
auto const copyLen = std::min(convertBackProof.size(), expectedSendSize);
|
||||
std::memcpy(resizedProof.data(), convertBackProof.data(), copyLen);
|
||||
// Zero-pad the rest (if convertBack proof is shorter)
|
||||
if (copyLen < expectedSendSize)
|
||||
std::memset(resizedProof.data() + copyLen, 0, expectedSendSize - copyLen);
|
||||
|
||||
@@ -124,87 +124,6 @@ protected:
|
||||
return proof;
|
||||
}
|
||||
|
||||
// Forges a ConvertBack proof (compact sigma + single bulletproof) whose
|
||||
// sigma component claims claimedBalance (which may be wrong) while binding
|
||||
// to the real pedersen commitment and encrypted spending balance
|
||||
// ciphertext already on the ledger. The bulletproof component is built
|
||||
// from realBalance so it stays honest.
|
||||
// mpt_get_convert_back_proof does not allow to build a proof whose amount
|
||||
// exceeds the holder's claimed balance.
|
||||
static Buffer
|
||||
getForgedConvertBackProof(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Account const& holder,
|
||||
uint64_t claimedBalance,
|
||||
uint64_t realBalance,
|
||||
uint64_t amt,
|
||||
Buffer const& pedersenCommitment,
|
||||
Buffer const& encryptedSpendingBalance,
|
||||
Buffer const& pcBlindingFactor,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (pedersenCommitment.size() != kCompressedEcPointLength)
|
||||
Throw<std::runtime_error>("getForgedConvertBackProof: bad pedersenCommitment length");
|
||||
if (encryptedSpendingBalance.size() != kEcGamalEncryptedTotalLength)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"getForgedConvertBackProof: bad encryptedSpendingBalance length");
|
||||
}
|
||||
if (amt > realBalance)
|
||||
Throw<std::runtime_error>("getForgedConvertBackProof: amt exceeds realBalance");
|
||||
|
||||
auto* const ctx = mpt_secp256k1_context();
|
||||
auto const holderPubKey = requireOptional(mpt.getPubKey(holder), "Missing holder pubkey");
|
||||
auto const holderPrivKey =
|
||||
requireOptional(mpt.getPrivKey(holder), "Missing holder privkey");
|
||||
|
||||
secp256k1_pubkey pkHolder;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pkHolder, holderPubKey.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse holder's public key");
|
||||
|
||||
secp256k1_pubkey pcB;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcB, pedersenCommitment.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse pedersen commitment");
|
||||
|
||||
secp256k1_pubkey b1, b2;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &b1, encryptedSpendingBalance.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&b2,
|
||||
encryptedSpendingBalance.data() + kCompressedEcPointLength,
|
||||
kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse balance ciphertext");
|
||||
|
||||
Buffer sigmaProof(SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE);
|
||||
if (secp256k1_compact_convertback_prove(
|
||||
ctx,
|
||||
sigmaProof.data(),
|
||||
claimedBalance,
|
||||
holderPrivKey.data(),
|
||||
pcBlindingFactor.data(),
|
||||
&pkHolder,
|
||||
&b1,
|
||||
&b2,
|
||||
&pcB,
|
||||
contextHash.data()) != 1)
|
||||
Throw<std::runtime_error>("Failed to generate convertback sigma proof");
|
||||
|
||||
auto const forgedBulletproof =
|
||||
getForgedSingleBulletproof(realBalance - amt, pcBlindingFactor, contextHash);
|
||||
|
||||
Buffer proof(kEcConvertBackProofLength);
|
||||
std::memcpy(proof.data(), sigmaProof.data(), SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE);
|
||||
std::memcpy(
|
||||
proof.data() + SECP256K1_COMPACT_CONVERTBACK_PROOF_SIZE,
|
||||
forgedBulletproof.data(),
|
||||
kEcSingleBulletproofLength);
|
||||
|
||||
return proof;
|
||||
}
|
||||
|
||||
// Get a bad ciphertext with valid structure but cryptographic invalid for
|
||||
// testing purposes. For preflight test purposes.
|
||||
static Buffer const&
|
||||
@@ -428,111 +347,6 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
// Forges a ConfidentialMPTSend proof (compact sigma + double bulletproof)
|
||||
// for setup.sendAmount against setup's real balance commitment/ciphertext.
|
||||
// mpt_get_confidential_send_proof does not allow to build a proof whose amount
|
||||
// exceeds the sender's claimed balance.
|
||||
static Buffer
|
||||
getForgedSendProof(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Account const& sender,
|
||||
test::jtx::Account const& dest,
|
||||
ConfidentialSendSetup const& setup)
|
||||
{
|
||||
auto* const ctx = mpt_secp256k1_context();
|
||||
|
||||
secp256k1_pubkey c1;
|
||||
std::vector<secp256k1_pubkey> c2Vec(setup.recipients.size());
|
||||
std::vector<secp256k1_pubkey> pkVec(setup.recipients.size());
|
||||
for (std::size_t i = 0; i < setup.recipients.size(); ++i)
|
||||
{
|
||||
auto const& r = setup.recipients[i];
|
||||
if (i == 0 &&
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &c1, r.encryptedAmount.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse C1");
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&c2Vec[i],
|
||||
r.encryptedAmount.data() + kCompressedEcPointLength,
|
||||
kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse C2");
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pkVec[i], r.publicKey.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse recipient pubkey");
|
||||
}
|
||||
|
||||
secp256k1_pubkey pkSender, pcAmount, pcBalance, b1, b2;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pkSender, setup.senderPubKey.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcAmount, setup.amountCommitment.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcBalance, setup.balanceCommitment.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx, &b1, setup.prevEncryptedSpending.data(), kCompressedEcPointLength) != 1 ||
|
||||
secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&b2,
|
||||
setup.prevEncryptedSpending.data() + kCompressedEcPointLength,
|
||||
kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse commitments/ciphertext");
|
||||
|
||||
Buffer const senderPrivKey =
|
||||
requireOptional(mpt.getPrivKey(sender), "Missing sender privkey");
|
||||
auto const ctxHash = getSendContextHash(
|
||||
sender.id(), mpt.issuanceID(), env.seq(sender), dest.id(), setup.version);
|
||||
|
||||
Buffer sigmaProof(SECP256K1_COMPACT_STANDARD_PROOF_SIZE);
|
||||
if (secp256k1_compact_standard_prove(
|
||||
ctx,
|
||||
sigmaProof.data(),
|
||||
setup.sendAmount,
|
||||
setup.prevSpending,
|
||||
setup.blindingFactor.data(),
|
||||
senderPrivKey.data(),
|
||||
setup.balanceBlindingFactor.data(),
|
||||
setup.recipients.size(),
|
||||
&c1,
|
||||
c2Vec.data(),
|
||||
pkVec.data(),
|
||||
&pcAmount,
|
||||
&pkSender,
|
||||
&pcBalance,
|
||||
&b1,
|
||||
&b2,
|
||||
ctxHash.data()) != 1)
|
||||
Throw<std::runtime_error>("Failed to generate sigma proof");
|
||||
|
||||
// Wraps (mod 2^64) for overdrafts, unlike the ledger's own homomorphic
|
||||
// commitment subtraction (mod the curve order) — that mismatch is
|
||||
// exactly what makes the forged proof fail verification.
|
||||
// Computed without a wrapping `uint64` subtract: Clang UBSan treats
|
||||
// unsigned overflow as fatal (see incrementConfidentialVersion).
|
||||
std::uint64_t const remaining = setup.sendAmount <= setup.prevSpending
|
||||
? setup.prevSpending - setup.sendAmount
|
||||
: ~setup.sendAmount + setup.prevSpending + 1;
|
||||
|
||||
Buffer negAmountBf(kEcBlindingFactorLength);
|
||||
Buffer remainingBf(kEcBlindingFactorLength);
|
||||
secp256k1_mpt_scalar_negate(negAmountBf.data(), setup.amountBlindingFactor.data());
|
||||
secp256k1_mpt_scalar_add(
|
||||
remainingBf.data(), setup.balanceBlindingFactor.data(), negAmountBf.data());
|
||||
|
||||
auto const forgedBulletproof = getForgedBulletproof(
|
||||
{setup.sendAmount, remaining}, {setup.amountBlindingFactor, remainingBf}, ctxHash);
|
||||
|
||||
Buffer combinedProof(kEcSendProofLength);
|
||||
std::memcpy(combinedProof.data(), sigmaProof.data(), SECP256K1_COMPACT_STANDARD_PROOF_SIZE);
|
||||
std::memcpy(
|
||||
combinedProof.data() + SECP256K1_COMPACT_STANDARD_PROOF_SIZE,
|
||||
forgedBulletproof.data(),
|
||||
kEcDoubleBulletproofLength);
|
||||
|
||||
return combinedProof;
|
||||
}
|
||||
|
||||
// Helper that wraps the boilerplate setup: Env + MPT creation, funding, key
|
||||
// generation, and seeding each holder with a confidential balance.
|
||||
// The caller supplies the issuer and any number of holders.
|
||||
@@ -604,18 +418,6 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
// Create an issuance that can hold confidential balances, with the listed
|
||||
// holders funded and authorized, and a key pair generated for the issuer,
|
||||
// every holder, and every extra key owner. The keys are
|
||||
// generated but not registered.
|
||||
static void
|
||||
setupConfidentialIssuance(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Account const& issuer,
|
||||
std::vector<test::jtx::Account> const& holders,
|
||||
std::vector<test::jtx::Account> const& keyOwners = {},
|
||||
std::uint32_t flags = tfMPTCanTransfer | tfMPTCanHoldConfidentialBalance);
|
||||
|
||||
// Set up an MPT environment suitable for batch testing.
|
||||
// alice is issuer; bob has 'bobAmt' in confidential spending; carol has
|
||||
// 'carolAmt' in confidential spending; dave is initialised with pubkey but
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#include <test/jtx/ConfidentialTransfer.h>
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
ConfidentialTransferTestBase::setupConfidentialIssuance(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Account const& issuer,
|
||||
std::vector<test::jtx::Account> const& holders,
|
||||
std::vector<test::jtx::Account> const& keyOwners,
|
||||
std::uint32_t flags)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
mpt.create({
|
||||
.ownerCount = 1,
|
||||
.flags = flags,
|
||||
});
|
||||
|
||||
for (auto const& holder : holders)
|
||||
{
|
||||
mpt.authorize({.account = holder});
|
||||
mpt.pay(issuer, holder, 100);
|
||||
mpt.generateKeyPair(holder);
|
||||
}
|
||||
|
||||
mpt.generateKeyPair(issuer);
|
||||
for (auto const& keyOwner : keyOwners)
|
||||
mpt.generateKeyPair(keyOwner);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,6 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
@@ -452,10 +451,8 @@ class MPTTester
|
||||
std::optional<Account> const auditor_;
|
||||
std::optional<MPTID> id_;
|
||||
bool close_;
|
||||
// Keys generated for each account. Buffer vector's index is the key epoch: index 0 is
|
||||
// the initial pair and each rotation appends.
|
||||
std::unordered_map<AccountID, std::vector<Buffer>> pubKeys_;
|
||||
std::unordered_map<AccountID, std::vector<Buffer>> privKeys_;
|
||||
std::unordered_map<AccountID, Buffer> pubKeys_;
|
||||
std::unordered_map<AccountID, Buffer> privKeys_;
|
||||
|
||||
public:
|
||||
enum class EncryptedBalanceType {
|
||||
@@ -622,15 +619,6 @@ public:
|
||||
std::optional<std::uint32_t> issuerKeyEpoch,
|
||||
std::optional<std::uint32_t> auditorKeyEpoch) const;
|
||||
|
||||
// Checks both mirror epochs on a holder's MPToken. Pass std::nullopt for an
|
||||
// epoch that is expected to be absent, which means the mirror was written
|
||||
// under the issuance's epoch 0 key.
|
||||
[[nodiscard]] bool
|
||||
checkMirrorEpochs(
|
||||
Account const& holder,
|
||||
std::optional<std::uint32_t> issuerKeyMirrorEpoch,
|
||||
std::optional<std::uint32_t> auditorKeyMirrorEpoch) const;
|
||||
|
||||
// Checks that the issuance carries the encryption keys of the given
|
||||
// accounts. Pass std::nullopt for a key that is expected to be absent,
|
||||
// which means the key is never registered.
|
||||
@@ -690,31 +678,20 @@ public:
|
||||
|
||||
operator Asset() const;
|
||||
|
||||
// Generates the account's next key pair and returns the key epoch it landed
|
||||
// at, leaving the earlier ones retrievable.
|
||||
std::uint32_t
|
||||
void
|
||||
generateKeyPair(Account const& account);
|
||||
|
||||
// Returns the account's public key at the given key epoch, or its latest key when
|
||||
// no epoch is given.
|
||||
[[nodiscard]] std::optional<Buffer>
|
||||
getPubKey(Account const& account, std::optional<std::uint32_t> epoch = std::nullopt) const;
|
||||
getPubKey(Account const& account) const;
|
||||
|
||||
// Returns the account's private key at the given key epoch, or its latest key when
|
||||
// no epoch is given.
|
||||
[[nodiscard]] std::optional<Buffer>
|
||||
getPrivKey(Account const& account, std::optional<std::uint32_t> epoch = std::nullopt) const;
|
||||
getPrivKey(Account const& account) const;
|
||||
|
||||
[[nodiscard]] Buffer
|
||||
encryptAmount(Account const& account, uint64_t const amt, Buffer const& blindingFactor) const;
|
||||
|
||||
// Decrypts with the account's key at the given key epoch, or its latest key
|
||||
// when no epoch is given.
|
||||
[[nodiscard]] std::optional<uint64_t>
|
||||
decryptAmount(
|
||||
Account const& account,
|
||||
Buffer const& amt,
|
||||
std::optional<std::uint32_t> epoch = std::nullopt) const;
|
||||
decryptAmount(Account const& account, Buffer const& amt) const;
|
||||
|
||||
[[nodiscard]] std::optional<uint64_t>
|
||||
getDecryptedBalance(Account const& account, EncryptedBalanceType balanceType) const;
|
||||
@@ -742,7 +719,7 @@ public:
|
||||
PedersenProofParams const& amountParams,
|
||||
PedersenProofParams const& balanceParams) const;
|
||||
|
||||
[[nodiscard]] std::optional<Buffer>
|
||||
[[nodiscard]] Buffer
|
||||
getConvertBackProof(
|
||||
Account const& holder,
|
||||
std::uint64_t const amount,
|
||||
@@ -768,10 +745,6 @@ private:
|
||||
std::function<bool(SLEP const& sle)> const& cb,
|
||||
std::optional<Account> const& holder = std::nullopt) const;
|
||||
|
||||
// Reads one of the holder's mirror key epochs off their MPToken.
|
||||
[[nodiscard]] std::optional<std::uint32_t>
|
||||
getMirrorEpoch(Account const& holder, SF_UINT32 const& field) const;
|
||||
|
||||
template <typename A>
|
||||
TER
|
||||
submit(A const& arg, json::Value jv)
|
||||
@@ -841,28 +814,15 @@ private:
|
||||
[[nodiscard]] std::uint32_t
|
||||
getFlags(std::optional<Account> const& holder) const;
|
||||
|
||||
/**
|
||||
* @brief Sets sfMPTokenIssuanceID on jv, falling back to id_ if arg's id is
|
||||
* not set.
|
||||
*
|
||||
* @param jv The JSON object to set the field on.
|
||||
* @param id The explicit issuance ID override from the caller, if any.
|
||||
*/
|
||||
void
|
||||
setIssuanceIdField(json::Value& jv, std::optional<MPTID> const& id) const;
|
||||
|
||||
[[nodiscard]] std::uint32_t
|
||||
ticketOrSeq(
|
||||
std::optional<std::uint32_t> const& ticketSeq,
|
||||
std::optional<Account> const& account) const;
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
fillConversionCiphertexts(
|
||||
T const& arg,
|
||||
json::Value& jv,
|
||||
Account const& account,
|
||||
std::uint64_t const amount) const;
|
||||
Buffer& holderCiphertext,
|
||||
Buffer& issuerCiphertext,
|
||||
std::optional<Buffer>& auditorCiphertext,
|
||||
Buffer& blindingFactor) const;
|
||||
};
|
||||
|
||||
} // namespace xrpl::test::jtx
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -97,29 +96,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Membership: isMember agrees with member");
|
||||
|
||||
// Number of network nodes that also belong to the cluster.
|
||||
std::size_t const overlapCount = 16;
|
||||
|
||||
// Total size of the cluster once padded with non-network nodes.
|
||||
std::size_t const clusterSize = 32;
|
||||
|
||||
std::vector<PublicKey> cluster(network.begin(), network.begin() + overlapCount);
|
||||
|
||||
while (cluster.size() != clusterSize)
|
||||
cluster.push_back(randomNode());
|
||||
|
||||
auto c = create(cluster);
|
||||
|
||||
for (auto const& n : cluster)
|
||||
BEAST_EXPECT(c->isMember(n));
|
||||
|
||||
for (auto const& n : network)
|
||||
BEAST_EXPECT(c->isMember(n) == static_cast<bool>(c->member(n)));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Membership: Non-empty cluster and all present");
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/Overlay.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/peerfinder/detail/Tuning.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
/**
|
||||
* Tests for `Overlay::limit()`, the configured peer allowance reported once
|
||||
* `OverlayImpl::start()` applies the computed `peer_finder::Config`.
|
||||
*
|
||||
* `ApplicationImp::fdRequired()` runs before `OverlayImpl::start()` does, so it
|
||||
* always sees the peer finder manager's default-constructed configuration and
|
||||
* never this value; `Overlay::limit()` instead surfaces through the PeerFinder
|
||||
* property stream and other post-startup callers.
|
||||
*
|
||||
* `jtx::Env` runs standalone, and `ServerHandler` strips the `peer` protocol
|
||||
* from every configured port under `config.standalone()`, so the peer port
|
||||
* declared here is never bound and incoming connections are disabled
|
||||
* throughout; every limit in this suite is an outbound-only allowance. The
|
||||
* inbound cases live alongside `peer_finder::Config::makeConfig`, which takes
|
||||
* the port as a parameter.
|
||||
*/
|
||||
class OverlayLimit_test : public beast::unit_test::Suite
|
||||
{
|
||||
void
|
||||
testLegacyPeersMax()
|
||||
{
|
||||
testcase("Legacy peers_max is reported");
|
||||
|
||||
auto config = jtx::envconfig();
|
||||
config->peersMax = 40;
|
||||
|
||||
Env env(*this, std::move(config));
|
||||
BEAST_EXPECT(env.app().getOverlay().limit() == 40);
|
||||
}
|
||||
|
||||
void
|
||||
testPerDirectionPeerLimits()
|
||||
{
|
||||
testcase("Per-direction peer limits are reported");
|
||||
|
||||
// With incoming connections disabled the 50 inbound slots are dropped
|
||||
// and only the outbound allowance remains, so neither zero (the value
|
||||
// `maxPeers` used to hold in this branch of makeConfig) nor 70 (the
|
||||
// unconditional sum of both directions) is correct.
|
||||
auto config = jtx::envconfig();
|
||||
config->peersInMax = 50;
|
||||
config->peersOutMax = 20;
|
||||
|
||||
Env env(*this, std::move(config));
|
||||
BEAST_EXPECT(env.app().getOverlay().limit() == 20);
|
||||
}
|
||||
|
||||
void
|
||||
testDefaultConfig()
|
||||
{
|
||||
testcase("A default configuration reports the default limit");
|
||||
|
||||
Env env(*this);
|
||||
BEAST_EXPECT(env.app().getOverlay().limit() == peer_finder::tuning::kDefaultMaxPeers);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testLegacyPeersMax();
|
||||
testPerDirectionPeerLimits();
|
||||
testDefaultConfig();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(OverlayLimit, overlay, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -126,16 +126,9 @@ class NoRippleCheck_test : public beast::unit_test::Suite
|
||||
params[jss::account] = toBase58(TokenType::NodePrivate, alice.sk());
|
||||
params[jss::role] = "user";
|
||||
params[jss::ledger] = "current";
|
||||
params[jss::transactions] = true;
|
||||
auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(result[jss::error] == "actMalformed");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Account malformed.");
|
||||
// The changelog promises malformed-account responses carry
|
||||
// neither `transactions` nor any ledger metadata.
|
||||
BEAST_EXPECT(!result.isMember(jss::transactions));
|
||||
BEAST_EXPECT(!result.isMember(jss::ledger_hash));
|
||||
BEAST_EXPECT(!result.isMember(jss::ledger_index));
|
||||
BEAST_EXPECT(!result.isMember(jss::validated));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -201,7 +194,6 @@ class NoRippleCheck_test : public beast::unit_test::Suite
|
||||
if (!BEAST_EXPECT(pa.isArray()))
|
||||
return;
|
||||
|
||||
BEAST_EXPECT(!result.isMember(jss::transactions));
|
||||
if (problems)
|
||||
{
|
||||
if (!BEAST_EXPECT(pa.size() == 2))
|
||||
@@ -227,12 +219,12 @@ class NoRippleCheck_test : public beast::unit_test::Suite
|
||||
// time.
|
||||
params[jss::transactions] = true;
|
||||
result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
|
||||
if (!BEAST_EXPECT(result[jss::transactions].isArray()))
|
||||
return;
|
||||
|
||||
auto const txs = result[jss::transactions];
|
||||
if (problems)
|
||||
{
|
||||
if (!BEAST_EXPECT(result[jss::transactions].isArray()))
|
||||
return;
|
||||
if (!BEAST_EXPECT(txs.size() == (user ? 1 : 2)))
|
||||
return;
|
||||
|
||||
|
||||
@@ -1214,9 +1214,6 @@ TEST(PeerFinderConfig, applies_legacy_and_explicit_peer_limits)
|
||||
.expectedOut = 10,
|
||||
.expectedIn = 0,
|
||||
.expectedIpLimit = 1},
|
||||
// A port of zero disables incoming connections, so the configured
|
||||
// inbound limit is dropped and the per-IP inbound limit collapses to
|
||||
// one, exactly as in the legacy private case above.
|
||||
{.name = "new in 100/out 10, private",
|
||||
.maxPeers = {},
|
||||
.maxIn = 100,
|
||||
@@ -1224,7 +1221,7 @@ TEST(PeerFinderConfig, applies_legacy_and_explicit_peer_limits)
|
||||
.port = 0,
|
||||
.expectedOut = 10,
|
||||
.expectedIn = 0,
|
||||
.expectedIpLimit = 1}};
|
||||
.expectedIpLimit = 6}};
|
||||
|
||||
for (auto const& testCase : cases)
|
||||
{
|
||||
@@ -1242,13 +1239,6 @@ TEST(PeerFinderConfig, applies_legacy_and_explicit_peer_limits)
|
||||
EXPECT_EQ(counts.inMax(), testCase.expectedIn);
|
||||
EXPECT_EQ(config.ipLimit, testCase.expectedIpLimit);
|
||||
|
||||
// The configuration itself carries the same per-direction allowances
|
||||
// that the slot counts derive, and `maxPeers` is their total. Callers
|
||||
// such as `Overlay::limit` read `maxPeers` directly.
|
||||
EXPECT_EQ(config.outPeers, testCase.expectedOut);
|
||||
EXPECT_EQ(config.inPeers, testCase.expectedIn);
|
||||
EXPECT_EQ(config.maxPeers, config.inPeers + config.outPeers);
|
||||
|
||||
NiceMock<MockStore> store;
|
||||
allowEmptyStore(store);
|
||||
NiceMock<MockChecker> checker;
|
||||
|
||||
@@ -32,8 +32,6 @@ TEST(MPTokenTests, BuilderSettersRoundTrip)
|
||||
auto const confidentialBalanceVersionValue = canonical_UINT32();
|
||||
auto const issuerEncryptedBalanceValue = canonical_VL();
|
||||
auto const auditorEncryptedBalanceValue = canonical_VL();
|
||||
auto const issuerKeyMirrorEpochValue = canonical_UINT32();
|
||||
auto const auditorKeyMirrorEpochValue = canonical_UINT32();
|
||||
auto const holderEncryptionKeyValue = canonical_VL();
|
||||
|
||||
MPTokenBuilder builder{
|
||||
@@ -51,8 +49,6 @@ TEST(MPTokenTests, BuilderSettersRoundTrip)
|
||||
builder.setConfidentialBalanceVersion(confidentialBalanceVersionValue);
|
||||
builder.setIssuerEncryptedBalance(issuerEncryptedBalanceValue);
|
||||
builder.setAuditorEncryptedBalance(auditorEncryptedBalanceValue);
|
||||
builder.setIssuerKeyMirrorEpoch(issuerKeyMirrorEpochValue);
|
||||
builder.setAuditorKeyMirrorEpoch(auditorKeyMirrorEpochValue);
|
||||
builder.setHolderEncryptionKey(holderEncryptionKeyValue);
|
||||
|
||||
builder.setLedgerIndex(index);
|
||||
@@ -150,22 +146,6 @@ TEST(MPTokenTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(entry.hasAuditorEncryptedBalance());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = issuerKeyMirrorEpochValue;
|
||||
auto const actualOpt = entry.getIssuerKeyMirrorEpoch();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfIssuerKeyMirrorEpoch");
|
||||
EXPECT_TRUE(entry.hasIssuerKeyMirrorEpoch());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = auditorKeyMirrorEpochValue;
|
||||
auto const actualOpt = entry.getAuditorKeyMirrorEpoch();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfAuditorKeyMirrorEpoch");
|
||||
EXPECT_TRUE(entry.hasAuditorKeyMirrorEpoch());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = holderEncryptionKeyValue;
|
||||
auto const actualOpt = entry.getHolderEncryptionKey();
|
||||
@@ -199,8 +179,6 @@ TEST(MPTokenTests, BuilderFromSleRoundTrip)
|
||||
auto const confidentialBalanceVersionValue = canonical_UINT32();
|
||||
auto const issuerEncryptedBalanceValue = canonical_VL();
|
||||
auto const auditorEncryptedBalanceValue = canonical_VL();
|
||||
auto const issuerKeyMirrorEpochValue = canonical_UINT32();
|
||||
auto const auditorKeyMirrorEpochValue = canonical_UINT32();
|
||||
auto const holderEncryptionKeyValue = canonical_VL();
|
||||
|
||||
auto sle = std::make_shared<SLE>(MPToken::entryType, index);
|
||||
@@ -217,8 +195,6 @@ TEST(MPTokenTests, BuilderFromSleRoundTrip)
|
||||
sle->at(sfConfidentialBalanceVersion) = confidentialBalanceVersionValue;
|
||||
sle->at(sfIssuerEncryptedBalance) = issuerEncryptedBalanceValue;
|
||||
sle->at(sfAuditorEncryptedBalance) = auditorEncryptedBalanceValue;
|
||||
sle->at(sfIssuerKeyMirrorEpoch) = issuerKeyMirrorEpochValue;
|
||||
sle->at(sfAuditorKeyMirrorEpoch) = auditorKeyMirrorEpochValue;
|
||||
sle->at(sfHolderEncryptionKey) = holderEncryptionKeyValue;
|
||||
|
||||
MPTokenBuilder builderFromSle{sle};
|
||||
@@ -371,32 +347,6 @@ TEST(MPTokenTests, BuilderFromSleRoundTrip)
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfAuditorEncryptedBalance");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = issuerKeyMirrorEpochValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getIssuerKeyMirrorEpoch();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getIssuerKeyMirrorEpoch();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfIssuerKeyMirrorEpoch");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfIssuerKeyMirrorEpoch");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = auditorKeyMirrorEpochValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getAuditorKeyMirrorEpoch();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getAuditorKeyMirrorEpoch();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfAuditorKeyMirrorEpoch");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfAuditorKeyMirrorEpoch");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = holderEncryptionKeyValue;
|
||||
|
||||
@@ -486,10 +436,6 @@ TEST(MPTokenTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(entry.getIssuerEncryptedBalance().has_value());
|
||||
EXPECT_FALSE(entry.hasAuditorEncryptedBalance());
|
||||
EXPECT_FALSE(entry.getAuditorEncryptedBalance().has_value());
|
||||
EXPECT_FALSE(entry.hasIssuerKeyMirrorEpoch());
|
||||
EXPECT_FALSE(entry.getIssuerKeyMirrorEpoch().has_value());
|
||||
EXPECT_FALSE(entry.hasAuditorKeyMirrorEpoch());
|
||||
EXPECT_FALSE(entry.getAuditorKeyMirrorEpoch().has_value());
|
||||
EXPECT_FALSE(entry.hasHolderEncryptionKey());
|
||||
EXPECT_FALSE(entry.getHolderEncryptionKey().has_value());
|
||||
}
|
||||
|
||||
@@ -62,19 +62,6 @@ public:
|
||||
std::optional<std::string>
|
||||
member(PublicKey const& node) const;
|
||||
|
||||
/**
|
||||
* Determines whether a node belongs in the cluster.
|
||||
*
|
||||
* Prefer this to `member` when the comment is not wanted: `member`
|
||||
* copies the node's name out from under the lock, and most callers
|
||||
* only test the result for engagement.
|
||||
*
|
||||
* @param node The node's public identity.
|
||||
* @return Whether the node is a cluster member.
|
||||
*/
|
||||
bool
|
||||
isMember(PublicKey const& node) const;
|
||||
|
||||
/**
|
||||
* The number of nodes in the cluster list.
|
||||
*/
|
||||
|
||||
@@ -38,14 +38,6 @@ Cluster::member(PublicKey const& identity) const
|
||||
return iter->name();
|
||||
}
|
||||
|
||||
bool
|
||||
Cluster::isMember(PublicKey const& identity) const
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
|
||||
return nodes_.contains(identity);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
Cluster::size() const
|
||||
{
|
||||
|
||||
@@ -306,7 +306,7 @@ OverlayImpl::onHandoff(
|
||||
{
|
||||
// The node gets a reserved slot if it is in our cluster
|
||||
// or if it has a reservation.
|
||||
bool const reserved = app_.getCluster().isMember(publicKey) ||
|
||||
bool const reserved = static_cast<bool>(app_.getCluster().member(publicKey)) ||
|
||||
app_.getPeerReservations().contains(publicKey);
|
||||
auto const result = peerFinder_->activate(slot, publicKey, reserved);
|
||||
if (result != peer_finder::Result::Success)
|
||||
|
||||
@@ -413,7 +413,7 @@ PeerImp::crawl() const
|
||||
bool
|
||||
PeerImp::cluster() const
|
||||
{
|
||||
return app_.getCluster().isMember(publicKey_);
|
||||
return static_cast<bool>(app_.getCluster().member(publicKey_));
|
||||
}
|
||||
|
||||
std::string
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -34,13 +32,12 @@ fillTransaction(
|
||||
std::uint32_t& sequence,
|
||||
ReadView const& ledger)
|
||||
{
|
||||
txArray[jss::Sequence] = json::UInt(sequence++);
|
||||
txArray[jss::Account] = toBase58(accountID);
|
||||
txArray["Sequence"] = json::UInt(sequence++);
|
||||
txArray["Account"] = toBase58(accountID);
|
||||
auto& fees = ledger.fees();
|
||||
// Convert the reference transaction cost in fee units to drops
|
||||
// scaled to represent the current fee load.
|
||||
txArray[jss::Fee] =
|
||||
scaleFeeLoad(fees.base, context.app.getFeeTrack(), fees, false).jsonClipped();
|
||||
txArray["Fee"] = scaleFeeLoad(fees.base, context.app.getFeeTrack(), fees, false).jsonClipped();
|
||||
}
|
||||
|
||||
// {
|
||||
@@ -56,33 +53,24 @@ doNoRippleCheck(rpc::JsonContext& context)
|
||||
{
|
||||
auto const& params(context.params);
|
||||
if (!params.isMember(jss::account))
|
||||
return rpc::missingFieldError(jss::account);
|
||||
return rpc::missingFieldError("account");
|
||||
|
||||
if (!params.isMember("role"))
|
||||
return rpc::missingFieldError("role");
|
||||
|
||||
if (!params[jss::account].isString())
|
||||
return rpc::invalidFieldError(jss::account);
|
||||
|
||||
auto id = parseBase58<AccountID>(params[jss::account].asString());
|
||||
if (!id)
|
||||
{
|
||||
return rpcError(RpcActMalformed);
|
||||
}
|
||||
auto const accountID{id.value()};
|
||||
|
||||
if (!params.isMember(jss::role))
|
||||
return rpc::missingFieldError(jss::role);
|
||||
|
||||
bool roleGateway = false;
|
||||
{
|
||||
if (!params[jss::role].isString())
|
||||
return rpc::expectedFieldError(jss::role, "string");
|
||||
std::string const role = params[jss::role].asString();
|
||||
if (role == jss::gateway)
|
||||
std::string const role = params["role"].asString();
|
||||
if (role == "gateway")
|
||||
{
|
||||
roleGateway = true;
|
||||
}
|
||||
else if (role != jss::user)
|
||||
else if (role != "user")
|
||||
{
|
||||
return rpc::invalidFieldError(jss::role);
|
||||
return rpc::invalidFieldError("role");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,49 +78,61 @@ doNoRippleCheck(rpc::JsonContext& context)
|
||||
if (auto err = readLimitField(limit, rpc::tuning::kNoRippleCheck, context))
|
||||
return *err;
|
||||
|
||||
// API v1 silently accepts any string as `transactions`; v2+ enforces bool.
|
||||
bool transactions = false;
|
||||
if (params.isMember(jss::transactions))
|
||||
transactions = params["transactions"].asBool();
|
||||
|
||||
// The document[https://xrpl.org/noripple_check.html#noripple_check] states
|
||||
// that transactions params is a boolean value, however, assigning any
|
||||
// string value works. Do not allow this. This check is for api Version 2
|
||||
// onwards only
|
||||
if (context.apiVersion > 1u && params.isMember(jss::transactions) &&
|
||||
!params[jss::transactions].isBool())
|
||||
{
|
||||
return rpc::invalidFieldError(jss::transactions);
|
||||
}
|
||||
|
||||
bool transactions = false;
|
||||
if (params.isMember(jss::transactions))
|
||||
transactions = params[jss::transactions].asBool();
|
||||
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
auto result = rpc::lookupLedger(ledger, context);
|
||||
if (!ledger)
|
||||
return result;
|
||||
|
||||
json::Value dummy; // NOLINT(misc-const-correctness)
|
||||
json::Value& jvTransactions =
|
||||
transactions ? (result[jss::transactions] = json::ValueType::Array) : dummy;
|
||||
|
||||
auto id = parseBase58<AccountID>(params[jss::account].asString());
|
||||
if (!id)
|
||||
{
|
||||
rpc::injectError(RpcActMalformed, result);
|
||||
return result;
|
||||
}
|
||||
auto const accountID{id.value()};
|
||||
auto const sle = ledger->read(keylet::account(accountID));
|
||||
if (!sle)
|
||||
return rpcError(RpcActNotFound);
|
||||
|
||||
std::uint32_t seq = sle->getFieldU32(sfSequence);
|
||||
|
||||
json::Value& problems = (result[jss::problems] = json::ValueType::Array);
|
||||
json::Value& problems = (result["problems"] = json::ValueType::Array);
|
||||
|
||||
bool const defaultRipple = sle->isFlag(lsfDefaultRipple);
|
||||
bool const bDefaultRipple = sle->isFlag(lsfDefaultRipple);
|
||||
|
||||
json::Value jvTransactions = json::ValueType::Array;
|
||||
|
||||
if (defaultRipple && !roleGateway)
|
||||
if (bDefaultRipple && !roleGateway)
|
||||
{
|
||||
problems.append(
|
||||
"You appear to have set your default ripple flag even though you "
|
||||
"are not a gateway. This is not recommended unless you are "
|
||||
"experimenting");
|
||||
}
|
||||
else if (roleGateway && !defaultRipple)
|
||||
else if (roleGateway && !bDefaultRipple)
|
||||
{
|
||||
problems.append("You should immediately set your default ripple flag");
|
||||
if (transactions)
|
||||
{
|
||||
json::Value& tx = jvTransactions.append(json::ValueType::Object);
|
||||
tx[jss::TransactionType] = jss::AccountSet;
|
||||
tx[jss::SetFlag] = 8;
|
||||
tx["TransactionType"] = jss::AccountSet;
|
||||
tx["SetFlag"] = 8;
|
||||
fillTransaction(context, tx, accountID, seq, *ledger);
|
||||
}
|
||||
}
|
||||
@@ -140,18 +140,18 @@ doNoRippleCheck(rpc::JsonContext& context)
|
||||
forEachItemAfter(*ledger, accountID, uint256(), 0, limit, [&](SLE::const_ref ownedItem) {
|
||||
if (ownedItem->getType() == ltRIPPLE_STATE)
|
||||
{
|
||||
bool const low = accountID == ownedItem->getFieldAmount(sfLowLimit).getIssuer();
|
||||
bool const bLow = accountID == ownedItem->getFieldAmount(sfLowLimit).getIssuer();
|
||||
|
||||
bool const noRipple = ownedItem->isFlag(low ? lsfLowNoRipple : lsfHighNoRipple);
|
||||
bool const bNoRipple = ownedItem->isFlag(bLow ? lsfLowNoRipple : lsfHighNoRipple);
|
||||
|
||||
std::string problem;
|
||||
bool needFix = false;
|
||||
if (noRipple && roleGateway)
|
||||
if (bNoRipple && roleGateway)
|
||||
{
|
||||
problem = "You should clear the no ripple flag on your ";
|
||||
needFix = true;
|
||||
}
|
||||
else if (!roleGateway && !noRipple)
|
||||
else if (!roleGateway && !bNoRipple)
|
||||
{
|
||||
problem = "You should probably set the no ripple flag on your ";
|
||||
needFix = true;
|
||||
@@ -159,25 +159,22 @@ doNoRippleCheck(rpc::JsonContext& context)
|
||||
if (needFix)
|
||||
{
|
||||
AccountID const peer =
|
||||
ownedItem->getFieldAmount(low ? sfHighLimit : sfLowLimit).getIssuer();
|
||||
ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit).getIssuer();
|
||||
STAmount const peerLimit =
|
||||
ownedItem->getFieldAmount(low ? sfHighLimit : sfLowLimit);
|
||||
ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit);
|
||||
problem += to_string(peerLimit.get<Issue>().currency);
|
||||
problem += " line to ";
|
||||
problem += to_string(peerLimit.getIssuer());
|
||||
problems.append(problem);
|
||||
|
||||
STAmount limitAmount(ownedItem->getFieldAmount(low ? sfLowLimit : sfHighLimit));
|
||||
STAmount limitAmount(ownedItem->getFieldAmount(bLow ? sfLowLimit : sfHighLimit));
|
||||
limitAmount.get<Issue>().account = peer;
|
||||
|
||||
if (transactions)
|
||||
{
|
||||
json::Value& tx = jvTransactions.append(json::ValueType::Object);
|
||||
tx[jss::TransactionType] = jss::TrustSet;
|
||||
tx[jss::LimitAmount] = limitAmount.getJson(JsonOptions::Values::None);
|
||||
tx[jss::Flags] = noRipple ? tfClearNoRipple : tfSetNoRipple;
|
||||
fillTransaction(context, tx, accountID, seq, *ledger);
|
||||
}
|
||||
json::Value& tx = jvTransactions.append(json::ValueType::Object);
|
||||
tx["TransactionType"] = jss::TrustSet;
|
||||
tx["LimitAmount"] = limitAmount.getJson(JsonOptions::Values::None);
|
||||
tx["Flags"] = bNoRipple ? tfClearNoRipple : tfSetNoRipple;
|
||||
fillTransaction(context, tx, accountID, seq, *ledger);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -185,8 +182,6 @@ doNoRippleCheck(rpc::JsonContext& context)
|
||||
return false;
|
||||
});
|
||||
|
||||
if (transactions)
|
||||
result[jss::transactions] = std::move(jvTransactions);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user