mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-16 20:38:35 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
551a19b10d | ||
|
|
b0a940a383 | ||
|
|
e302e4eeed | ||
|
|
1a4a40ebb8 | ||
|
|
7f55dd390c | ||
|
|
9403736199 | ||
|
|
028783661d | ||
|
|
21890d9daf | ||
|
|
060957ed39 | ||
|
|
1381483c7a | ||
|
|
3e4bdf2782 | ||
|
|
e3c8996e44 | ||
|
|
d5bfe94f15 |
2
.github/scripts/strategy-matrix/linux.json
vendored
2
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-473fe44",
|
||||
"image_tag": "sha-060957e",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
|
||||
6
.github/workflows/build-nix-images.yml
vendored
6
.github/workflows/build-nix-images.yml
vendored
@@ -5,15 +5,13 @@ on:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "rust-toolchain.toml"
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "!nix/check-tools/*.txt"
|
||||
- "bin/check-tools.sh"
|
||||
- "!nix/check-tools/**"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
pull_request:
|
||||
@@ -25,7 +23,7 @@ on:
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "!nix/check-tools/*.txt"
|
||||
- "!nix/check-tools/**"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
|
||||
2
.github/workflows/cargo-audit.yml
vendored
2
.github/workflows/cargo-audit.yml
vendored
@@ -34,7 +34,7 @@ permissions:
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to open an issue on scheduled failures.
|
||||
|
||||
4
.github/workflows/publish-docs.yml
vendored
4
.github/workflows/publish-docs.yml
vendored
@@ -41,7 +41,7 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -91,4 +91,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deploy
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
|
||||
|
||||
2
.github/workflows/reusable-clang-tidy.yml
vendored
2
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: [determine-files]
|
||||
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-473fe44"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-060957e"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
17
.github/workflows/reusable-package.yml
vendored
17
.github/workflows/reusable-package.yml
vendored
@@ -209,6 +209,23 @@ jobs:
|
||||
}
|
||||
echo "package=${package}" >>"${GITHUB_OUTPUT}"
|
||||
|
||||
# Debian 11 went end-of-life on 2026-08-31
|
||||
# (https://www.debian.org/News/2026/20260831) and its packages are
|
||||
# already partly gone from deb.debian.org, so switch to the
|
||||
# snapshot.debian.org entries the image ships commented out in its
|
||||
# sources.list: they are pinned to the snapshot the image was built
|
||||
# from, so they serve every version it needs and never go away.
|
||||
# Snapshots keep their original, long-passed Valid-Until, hence the
|
||||
# disabled check; the retries absorb snapshot.debian.org's throttling.
|
||||
- name: Switch Debian 11 to snapshot.debian.org
|
||||
if: ${{ matrix.image == 'debian:11' }}
|
||||
run: |
|
||||
sed -i 's|^deb |# deb |; s|^# deb http://snapshot|deb http://snapshot|' /etc/apt/sources.list
|
||||
printf '%s\n' \
|
||||
'Acquire::Check-Valid-Until "false";' \
|
||||
'Acquire::Retries "3";' \
|
||||
>/etc/apt/apt.conf.d/99snapshot
|
||||
|
||||
- name: Install the DEB
|
||||
if: ${{ matrix.package_type == 'deb' }}
|
||||
env:
|
||||
|
||||
14
.github/workflows/reusable-rust.yml
vendored
14
.github/workflows/reusable-rust.yml
vendored
@@ -1,8 +1,9 @@
|
||||
# Clippy, coverage and documentation for the Rust crates in crates/. Each runs
|
||||
# as an independent job on a GitHub-hosted runner, but inside the same container
|
||||
# image used to build the crates in the C++/Corrosion path, so the toolchain
|
||||
# (and therefore the lints, coverage instrumentation and the cargo cache) matches
|
||||
# what production builds use.
|
||||
# (and therefore the lints and the cargo cache) matches what production builds
|
||||
# use. Coverage is the exception: it needs the nightly rustc that honours
|
||||
# #[coverage(off)], which the image carries alongside the pinned stable.
|
||||
#
|
||||
# Rust unit tests are deliberately NOT run here. They run as part of the C++
|
||||
# build (reusable-build-test-config.yml), which already compiles the crates on a
|
||||
@@ -27,7 +28,7 @@ permissions:
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -40,11 +41,14 @@ jobs:
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use the nightly Rust toolchain
|
||||
run: rust-nightly path >>"${GITHUB_PATH}"
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
|
||||
@@ -66,7 +70,7 @@ jobs:
|
||||
|
||||
doc:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
2
.github/workflows/reusable-upload-recipe.yml
vendored
2
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -40,7 +40,7 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
|
||||
|
||||
@@ -70,6 +70,11 @@ repos:
|
||||
language: system
|
||||
types: [rust]
|
||||
pass_filenames: false # rustfmt formats the whole workspace
|
||||
- id: check-coverage-attrs
|
||||
name: check Rust coverage attributes
|
||||
entry: ./bin/pre-commit/check_rust_coverage_attrs.py
|
||||
language: python
|
||||
files: ^crates/.*\.rs$
|
||||
|
||||
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
|
||||
rev: e98930bdc210d3387007f9252d8c1694ea7e410f # frozen: 0.27.7
|
||||
|
||||
@@ -41,6 +41,7 @@ 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
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
check cargo-nextest cargo nextest --version
|
||||
check clippy-driver
|
||||
check rust-analyzer
|
||||
check rust-nightly rust-nightly run rustc --version
|
||||
check rustc
|
||||
check rustfmt
|
||||
fi
|
||||
|
||||
149
bin/pre-commit/check_rust_coverage_attrs.py
Executable file
149
bin/pre-commit/check_rust_coverage_attrs.py
Executable file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check that Rust unit tests stay out of the coverage report.
|
||||
|
||||
cargo-llvm-cov instruments the test code along with everything else, so a test
|
||||
module that is not excluded counts its own body as covered and inflates the
|
||||
reported number. Excluding it takes two attributes:
|
||||
|
||||
* every `#[cfg(test)]` module carries
|
||||
`#[cfg_attr(coverage_nightly, coverage(off))]`;
|
||||
* every crate root (lib.rs, main.rs) carries
|
||||
`#![cfg_attr(coverage_nightly, feature(coverage_attribute))]`, which the
|
||||
attribute above needs in order to compile.
|
||||
|
||||
Both are inert outside the coverage job: cargo-llvm-cov defines
|
||||
`coverage_nightly` only when it runs on a nightly toolchain.
|
||||
|
||||
The crate-root gate is checked even in a crate that has no tests yet, because
|
||||
that is what lets the first test module added later carry the attribute without
|
||||
a build failure. Missing it is a hard error, so it cannot go unnoticed; a
|
||||
missing `coverage(off)` fails open, which is why this check exists.
|
||||
|
||||
Matching is on exact attribute text, which works because `cargo fmt` runs over
|
||||
the whole workspace in the hook ahead of this one: rustfmt puts every attribute
|
||||
on its own line and normalizes what is inside it, turning `#[cfg( test )]`
|
||||
and `#[cfg(test,)]` alike into `#[cfg(test)]`. So there is nothing here that
|
||||
parses Rust. The price is that a cfg this file does not spell out literally --
|
||||
`all(test, ...)`, `any(test, ...)`, `not(test)` -- is reported rather than
|
||||
classified, on the grounds that guessing at coverage semantics is how a check
|
||||
like this ends up quietly wrong.
|
||||
|
||||
Usage: ./bin/pre-commit/check_rust_coverage_attrs.py <file1> <file2> ...
|
||||
|
||||
Exit status is non-zero if any violation is found.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
CRATE_ROOTS = {"lib.rs", "main.rs"}
|
||||
|
||||
FEATURE_ATTR = "#![cfg_attr(coverage_nightly, feature(coverage_attribute))]"
|
||||
COVERAGE_OFF_ATTR = "#[cfg_attr(coverage_nightly, coverage(off))]"
|
||||
CFG_TEST_ATTR = "#[cfg(test)]"
|
||||
|
||||
# Any other cfg that mentions `test`. String literals are blanked before this
|
||||
# runs, so `feature = "test"` does not read as the `test` cfg.
|
||||
RE_CFG_MENTIONS_TEST = re.compile(r"^#\[cfg\(.*\btest\b.*\)\]$")
|
||||
RE_STRING = re.compile(r'"(?:[^"\\]|\\.)*"')
|
||||
RE_MOD = re.compile(r"^(?:pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Finding:
|
||||
line: int
|
||||
label: str
|
||||
message: str
|
||||
|
||||
|
||||
def _check_module(attrs: list[str], line: int, name: str) -> list[Finding]:
|
||||
"""Findings for one module, given the attributes attached to it."""
|
||||
if COVERAGE_OFF_ATTR in attrs:
|
||||
return [] # excluded from coverage; which cfg gates it does not matter
|
||||
if CFG_TEST_ATTR in attrs:
|
||||
return [
|
||||
Finding(
|
||||
line,
|
||||
"missing-coverage-off",
|
||||
f"`mod {name}` is #[cfg(test)] but not excluded from coverage; "
|
||||
f"add {COVERAGE_OFF_ATTR}",
|
||||
)
|
||||
]
|
||||
unclassified = [
|
||||
attr for attr in attrs if RE_CFG_MENTIONS_TEST.match(RE_STRING.sub('""', attr))
|
||||
]
|
||||
if unclassified:
|
||||
return [
|
||||
Finding(
|
||||
line,
|
||||
"unclassified-cfg",
|
||||
f"`mod {name}` is gated on {unclassified[0]}, which this check "
|
||||
f"cannot tell apart from a module that ships in the library; "
|
||||
f"add {COVERAGE_OFF_ATTR} if it is test-only, or teach this "
|
||||
f"check the cfg if it is not",
|
||||
)
|
||||
]
|
||||
return []
|
||||
|
||||
|
||||
def _check_test_modules(lines: list[str]) -> list[Finding]:
|
||||
"""Findings for every test module that is not excluded from coverage."""
|
||||
findings: list[Finding] = []
|
||||
attrs: list[str] = []
|
||||
attrs_line = 0
|
||||
for number, raw in enumerate(lines, start=1):
|
||||
stripped = raw.strip()
|
||||
# Blank lines and comments are allowed between an attribute and its item.
|
||||
if not stripped or stripped.startswith("//"):
|
||||
continue
|
||||
if stripped.startswith("#["):
|
||||
if not attrs:
|
||||
attrs_line = number
|
||||
attrs.append(stripped)
|
||||
continue
|
||||
module = RE_MOD.match(stripped)
|
||||
if module is not None and attrs:
|
||||
findings += _check_module(attrs, attrs_line, module.group(1))
|
||||
attrs = []
|
||||
return findings
|
||||
|
||||
|
||||
def _check_crate_root(name: str, lines: list[str]) -> list[Finding]:
|
||||
"""A finding if a crate root is missing the coverage_attribute feature gate."""
|
||||
if name not in CRATE_ROOTS:
|
||||
return []
|
||||
if any(line.strip() == FEATURE_ATTR for line in lines):
|
||||
return []
|
||||
return [
|
||||
Finding(
|
||||
1,
|
||||
"missing-feature-gate",
|
||||
f"crate root is missing {FEATURE_ATTR}",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def check_source(name: str, text: str) -> list[Finding]:
|
||||
"""Findings for one file's contents; `name` is its base name (lib.rs, ...)."""
|
||||
lines = text.splitlines()
|
||||
return _check_crate_root(name, lines) + _check_test_modules(lines)
|
||||
|
||||
|
||||
def check_file(path: Path) -> list[Finding]:
|
||||
return check_source(path.name, path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
total = 0
|
||||
for path in (Path(name) for name in sys.argv[1:]):
|
||||
for finding in check_file(path):
|
||||
total += 1
|
||||
print(f"{path}:{finding.line}: {finding.label}: {finding.message}")
|
||||
return 1 if total else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -8,6 +8,9 @@ cxx = { version = "1.0.198", features = ["c++20"] }
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(coverage)', 'cfg(coverage_nightly)' ] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
overflow-checks = true
|
||||
|
||||
@@ -8,3 +8,6 @@ crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
cxx.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
|
||||
|
||||
#[cxx::bridge(namespace = "rs::hello_world")]
|
||||
mod ffi {
|
||||
extern "Rust" {
|
||||
@@ -8,3 +10,14 @@ mod ffi {
|
||||
pub fn hello_world() -> String {
|
||||
"hello_world".to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg_attr(coverage_nightly, coverage(off))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn hello_world_returns_hello_world() {
|
||||
assert_eq!(hello_world(), "hello_world")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ struct Config
|
||||
/**
|
||||
* The largest number of public peer slots to allow.
|
||||
* This includes both inbound and outbound, but does not include
|
||||
* fixed peers.
|
||||
* fixed peers. A configuration built by `makeConfig` always holds
|
||||
* `maxPeers == inPeers + outPeers`.
|
||||
*/
|
||||
std::size_t maxPeers{tuning::kDefaultMaxPeers};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#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>
|
||||
@@ -301,6 +302,63 @@ 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.
|
||||
*
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -544,6 +545,11 @@ constexpr std::size_t kEcClawbackProofLength = SECP256K1_COMPACT_CLAWBACK_PROOF_
|
||||
*/
|
||||
constexpr std::uint32_t kConfidentialFeeMultiplier = 9;
|
||||
|
||||
/**
|
||||
* Maximum value a confidential MPT key epoch may reach.
|
||||
*/
|
||||
constexpr std::uint32_t kMaxKeyEpoch = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
/**
|
||||
* Compressed EC point prefix for even y-coordinate
|
||||
*/
|
||||
|
||||
@@ -129,8 +129,11 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
|
||||
temBAD_MPT,
|
||||
temBAD_CIPHERTEXT,
|
||||
temINVALID_BYTECODE,
|
||||
temTEMP_DISABLED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -179,6 +182,8 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefNO_DST_PARTIAL,
|
||||
tefBAD_PATH_COUNT,
|
||||
tefNO_BYTECODE,
|
||||
tefBYTECODE_NOT_INCLUDED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -370,6 +375,8 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
tecBAD_PROOF = 199,
|
||||
tecNO_SPONSOR_PERMISSION = 200,
|
||||
tecOUT_OF_GAS = 201,
|
||||
tecBYTECODE_REJECTED = 202,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocolV1_2, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_5_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(ConfidentialMPTKeyRotation, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -309,6 +309,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// Smart Escrow fields
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
|
||||
{sfPreviousTxnID, SoeOptional},
|
||||
{sfPreviousTxnLgrSeq, SoeOptional},
|
||||
}))
|
||||
@@ -339,6 +344,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCancelAfter, SoeOptional},
|
||||
{sfFinishAfter, SoeOptional},
|
||||
{sfBytecode, SoeOptional},
|
||||
{sfData, SoeOptional},
|
||||
{sfSourceTag, SoeOptional},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfOwnerNode, SoeRequired},
|
||||
@@ -408,6 +415,8 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
|
||||
{sfReferenceHolding, SoeOptional},
|
||||
{sfIssuerEncryptionKey, SoeOptional},
|
||||
{sfAuditorEncryptionKey, SoeOptional},
|
||||
{sfIssuerKeyEpoch, SoeOptional},
|
||||
{sfAuditorKeyEpoch, SoeOptional},
|
||||
{sfConfidentialOutstandingAmount, SoeDefault},
|
||||
}))
|
||||
|
||||
@@ -427,6 +436,8 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
{sfConfidentialBalanceVersion, SoeDefault},
|
||||
{sfIssuerEncryptedBalance, SoeOptional},
|
||||
{sfAuditorEncryptedBalance, SoeOptional},
|
||||
{sfIssuerKeyMirrorEpoch, SoeOptional},
|
||||
{sfAuditorKeyMirrorEpoch, SoeOptional},
|
||||
{sfHolderEncryptionKey, SoeOptional},
|
||||
}))
|
||||
|
||||
|
||||
@@ -119,6 +119,15 @@ TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73)
|
||||
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
|
||||
TYPED_SFIELD(sfSubscriptionDate, UINT32, 75)
|
||||
TYPED_SFIELD(sfRedemptionDate, UINT32, 76)
|
||||
TYPED_SFIELD(sfIssuerKeyEpoch, UINT32, 77)
|
||||
TYPED_SFIELD(sfAuditorKeyEpoch, UINT32, 78)
|
||||
TYPED_SFIELD(sfIssuerKeyMirrorEpoch, UINT32, 79)
|
||||
TYPED_SFIELD(sfAuditorKeyMirrorEpoch, UINT32, 80)
|
||||
TYPED_SFIELD(sfGasLimit, UINT32, 81)
|
||||
TYPED_SFIELD(sfBytecodeSizeLimit, UINT32, 82)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 83)
|
||||
TYPED_SFIELD(sfGas, UINT32, 84)
|
||||
TYPED_SFIELD(sfGasUsed, UINT32, 85)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -234,6 +243,7 @@ TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset
|
||||
// 32-bit signed (common)
|
||||
TYPED_SFIELD(sfLoanScale, INT32, 1)
|
||||
TYPED_SFIELD(sfRemainingOwnerCountDelta, INT32, 2)
|
||||
TYPED_SFIELD(sfVMReturnCode, INT32, 3)
|
||||
|
||||
// currency amount (common)
|
||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||
@@ -318,6 +328,7 @@ TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
|
||||
TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
|
||||
TYPED_SFIELD(sfAmountCommitment, VL, 45)
|
||||
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
|
||||
TYPED_SFIELD(sfBytecode, VL, 47)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
|
||||
@@ -66,11 +66,13 @@ TRANSACTION(ttPAYMENT, 0, Payment,
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({.delegable = Delegation::Delegable}), ({
|
||||
{sfDestination, SoeRequired},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfAmount, SoeRequired, SoeMptSupported},
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCancelAfter, SoeOptional},
|
||||
{sfFinishAfter, SoeOptional},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfBytecode, SoeOptional},
|
||||
{sfData, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
@@ -83,6 +85,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({.delegable = Delegation::Delegab
|
||||
{sfFulfillment, SoeOptional},
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCredentialIDs, SoeOptional},
|
||||
{sfGas, SoeOptional},
|
||||
}))
|
||||
|
||||
|
||||
@@ -1161,6 +1164,10 @@ TRANSACTION(ttFEE, 101, SetFee,
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// Smart Escrow fields
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the network's negative UNL
|
||||
|
||||
@@ -278,6 +278,7 @@ 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
|
||||
@@ -481,6 +482,7 @@ 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
|
||||
@@ -660,6 +662,7 @@ 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
|
||||
|
||||
@@ -174,6 +174,54 @@ public:
|
||||
return this->sle_->isFieldPresent(sfFinishAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecode (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getBytecode() const
|
||||
{
|
||||
if (hasBytecode())
|
||||
return this->sle_->at(sfBytecode);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecode is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecode() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfBytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfData (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getData() const
|
||||
{
|
||||
if (hasData())
|
||||
return this->sle_->at(sfData);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfData is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasData() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSourceTag (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -453,6 +501,28 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecode (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowBuilder&
|
||||
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowBuilder&
|
||||
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSourceTag (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -213,6 +213,78 @@ public:
|
||||
return this->sle_->isFieldPresent(sfReserveIncrementDrops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasLimit() const
|
||||
{
|
||||
if (hasGasLimit())
|
||||
return this->sle_->at(sfGasLimit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasLimit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfGasLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getBytecodeSizeLimit() const
|
||||
{
|
||||
if (hasBytecodeSizeLimit())
|
||||
return this->sle_->at(sfBytecodeSizeLimit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecodeSizeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecodeSizeLimit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfBytecodeSizeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasPrice (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasPrice() const
|
||||
{
|
||||
if (hasGasPrice())
|
||||
return this->sle_->at(sfGasPrice);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasPrice is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasPrice() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfGasPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnID (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -375,6 +447,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setGasLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecodeSizeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasPrice (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasPrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -268,6 +268,54 @@ 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.
|
||||
@@ -471,6 +519,28 @@ 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.
|
||||
|
||||
@@ -351,6 +351,54 @@ public:
|
||||
return this->sle_->isFieldPresent(sfAuditorEncryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfIssuerKeyEpoch (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getIssuerKeyEpoch() const
|
||||
{
|
||||
if (hasIssuerKeyEpoch())
|
||||
return this->sle_->at(sfIssuerKeyEpoch);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfIssuerKeyEpoch is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasIssuerKeyEpoch() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfIssuerKeyEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAuditorKeyEpoch (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getAuditorKeyEpoch() const
|
||||
{
|
||||
if (hasAuditorKeyEpoch())
|
||||
return this->sle_->at(sfAuditorKeyEpoch);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfAuditorKeyEpoch is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasAuditorKeyEpoch() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfAuditorKeyEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfConfidentialOutstandingAmount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -600,6 +648,28 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfIssuerKeyEpoch (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceBuilder&
|
||||
setIssuerKeyEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfIssuerKeyEpoch] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAuditorKeyEpoch (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceBuilder&
|
||||
setAuditorKeyEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAuditorKeyEpoch] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfConfidentialOutstandingAmount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -58,6 +58,32 @@ public:
|
||||
return this->tx_->at(sfDestination);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestinationTag (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getDestinationTag() const
|
||||
{
|
||||
if (hasDestinationTag())
|
||||
{
|
||||
return this->tx_->at(sfDestinationTag);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfDestinationTag is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasDestinationTag() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfDestinationTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
@@ -149,29 +175,55 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestinationTag (SoeOptional)
|
||||
* @brief Get sfBytecode (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getDestinationTag() const
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getBytecode() const
|
||||
{
|
||||
if (hasDestinationTag())
|
||||
if (hasBytecode())
|
||||
{
|
||||
return this->tx_->at(sfDestinationTag);
|
||||
return this->tx_->at(sfBytecode);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfDestinationTag is present.
|
||||
* @brief Check if sfBytecode is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasDestinationTag() const
|
||||
hasBytecode() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfDestinationTag);
|
||||
return this->tx_->isFieldPresent(sfBytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfData (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getData() const
|
||||
{
|
||||
if (hasData())
|
||||
{
|
||||
return this->tx_->at(sfData);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfData is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasData() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfData);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -232,6 +284,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestinationTag (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestinationTag] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
@@ -278,13 +341,24 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestinationTag (SoeOptional)
|
||||
* @brief Set sfBytecode (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestinationTag] = value;
|
||||
object_[sfBytecode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,32 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfCredentialIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGas (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGas() const
|
||||
{
|
||||
if (hasGas())
|
||||
{
|
||||
return this->tx_->at(sfGas);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGas is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGas() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGas);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -249,6 +275,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGas (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowFinishBuilder&
|
||||
setGas(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGas] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the EscrowFinish wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -254,6 +254,84 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfReserveIncrementDrops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasLimit() const
|
||||
{
|
||||
if (hasGasLimit())
|
||||
{
|
||||
return this->tx_->at(sfGasLimit);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasLimit() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGasLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getBytecodeSizeLimit() const
|
||||
{
|
||||
if (hasBytecodeSizeLimit())
|
||||
{
|
||||
return this->tx_->at(sfBytecodeSizeLimit);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecodeSizeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecodeSizeLimit() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfBytecodeSizeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasPrice (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasPrice() const
|
||||
{
|
||||
if (hasGasPrice())
|
||||
{
|
||||
return this->tx_->at(sfGasPrice);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasPrice is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasPrice() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGasPrice);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -386,6 +464,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setGasLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecodeSizeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasPrice (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasPrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the SetFee wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -131,6 +131,9 @@ Rust toolchain:
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f4 2026-07-14)
|
||||
/nix/store/j6apc5pmd0giy15da9p650r8zklslmvi-rust-analyzer-preview-1.97.1-aarch64-apple-darwin/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/fqpjz4l0nsnji8b2pz57mnj0akbp6hcl-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/rustc
|
||||
@@ -140,4 +143,4 @@ Rust toolchain:
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 44 checked tools are present and runnable.
|
||||
✅ All 45 checked tools are present and runnable.
|
||||
|
||||
@@ -131,6 +131,9 @@ Rust toolchain:
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f 2026-07-14)
|
||||
/nix/store/lr3m97p3hx1k22a7c44pb0wa7rbayhfi-rust-analyzer-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/j7kf7a5h4xypzp6x1skg4dsdx2k4fwb3-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/40d3mzka7r1ps71l0yv2fs6616nbw85m-rust-minimal-1.97.1/bin/rustc
|
||||
@@ -168,4 +171,4 @@ Mold:
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 52 checked tools are present and runnable.
|
||||
✅ All 53 checked tools are present and runnable.
|
||||
|
||||
@@ -131,6 +131,9 @@ Rust toolchain:
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f 2026-07-14)
|
||||
/nix/store/262830dlw2517lnagfx7i7agqgl4fmsd-rust-analyzer-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/c59pxk1yikdlf129qwyg4fplmxcrha0k-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/a6p27cg6b8szfixfyvkssx6l0c345zw8-rust-minimal-1.97.1/bin/rustc
|
||||
@@ -168,4 +171,4 @@ Mold:
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 52 checked tools are present and runnable.
|
||||
✅ All 53 checked tools are present and runnable.
|
||||
|
||||
@@ -10,6 +10,8 @@ RUN mkdir -p ~/.config/nix && \
|
||||
COPY nix/ci-env.nix /tmp/build/nix/ci-env.nix
|
||||
COPY nix/linux.nix /tmp/build/nix/linux.nix
|
||||
COPY nix/packages.nix /tmp/build/nix/packages.nix
|
||||
COPY nix/rust-nightly.sh /tmp/build/nix/rust-nightly.sh
|
||||
COPY nix/rust.nix /tmp/build/nix/rust.nix
|
||||
COPY nix/utils.nix /tmp/build/nix/utils.nix
|
||||
COPY flake.nix /tmp/build/
|
||||
COPY flake.lock /tmp/build/
|
||||
|
||||
@@ -16,23 +16,7 @@ let
|
||||
exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@"
|
||||
'';
|
||||
|
||||
# rust-overlay's toolchain propagates the *default* stdenv.cc onto the PATH (so
|
||||
# cargo has a linker). That default may be different from the clang we pin here,
|
||||
# so it shadows our clang and the build can silently use a different compiler
|
||||
# version. Drop that cc from every propagation channel instead of pinning a
|
||||
# replacement: the toolchain then carries no compiler and cargo just uses the
|
||||
# active shell's stdenv cc. Must cover all channels — rust-overlay uses both
|
||||
# propagatedBuildInputs and depsHostHostPropagated.
|
||||
rustToolchainBase = pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml;
|
||||
rustToolchain =
|
||||
let
|
||||
defaultCc = pkgs.stdenv.cc; # default compiler from nixpkgs stdenv
|
||||
withoutDefaultCc = builtins.filter (dep: (dep.outPath or "") != defaultCc.outPath);
|
||||
in
|
||||
rustToolchainBase.overrideAttrs (old: {
|
||||
propagatedBuildInputs = withoutDefaultCc (old.propagatedBuildInputs or [ ]);
|
||||
depsHostHostPropagated = withoutDefaultCc (old.depsHostHostPropagated or [ ]);
|
||||
});
|
||||
rust = import ./rust.nix { inherit pkgs; };
|
||||
|
||||
# Nix wraps its toolchain so that binaries are exposed only under unsuffixed
|
||||
# names (gcc, g++, clang-tidy, ...). Several tools probe for a
|
||||
@@ -108,41 +92,38 @@ in
|
||||
mkGcov
|
||||
;
|
||||
|
||||
commonPackages = with pkgs; [
|
||||
clangToolLinks
|
||||
runClangTidyLink
|
||||
ccache
|
||||
clangbuildanalyzer
|
||||
clangTools
|
||||
cmake
|
||||
conan
|
||||
curlMinimal # needed for codecov/codecov-action
|
||||
doxygen
|
||||
file # needed for cpack in Clio
|
||||
gcovr
|
||||
gh
|
||||
git
|
||||
git-cliff
|
||||
git-lfs
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
graphviz
|
||||
less # needed for git diff
|
||||
mold
|
||||
nettools # provides netstat, used to debug failures in CI
|
||||
ninja
|
||||
patchelf
|
||||
perl # needed for openssl
|
||||
pkg-config
|
||||
pre-commit
|
||||
python3
|
||||
runClangTidy
|
||||
vim
|
||||
zip
|
||||
# Rust packages
|
||||
cargo-audit
|
||||
cargo-llvm-cov
|
||||
cargo-nextest
|
||||
rustToolchain
|
||||
];
|
||||
commonPackages =
|
||||
(with pkgs; [
|
||||
clangToolLinks
|
||||
runClangTidyLink
|
||||
ccache
|
||||
clangbuildanalyzer
|
||||
clangTools
|
||||
cmake
|
||||
conan
|
||||
curlMinimal # needed for codecov/codecov-action
|
||||
doxygen
|
||||
file # needed for cpack in Clio
|
||||
gcovr
|
||||
gh
|
||||
git
|
||||
git-cliff
|
||||
git-lfs
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
graphviz
|
||||
less # needed for git diff
|
||||
mold
|
||||
nettools # provides netstat, used to debug failures in CI
|
||||
ninja
|
||||
patchelf
|
||||
perl # needed for openssl
|
||||
pkg-config
|
||||
pre-commit
|
||||
python3
|
||||
runClangTidy
|
||||
vim
|
||||
zip
|
||||
])
|
||||
++ rust.packages;
|
||||
}
|
||||
|
||||
23
nix/rust-nightly.sh
Normal file
23
nix/rust-nightly.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!@runtimeShell@
|
||||
# Reaches the nightly Rust toolchain, which is deliberately kept off PATH.
|
||||
# Packaged by nix/rust.nix, which explains why.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "usage: rust-nightly (path | run <command>...)" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
case "${1-}" in
|
||||
path) printf '%s\n' "@rustNightlyBin@" ;;
|
||||
run)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
usage
|
||||
fi
|
||||
export PATH="@rustNightlyBin@:${PATH}"
|
||||
exec "$@"
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
84
nix/rust.nix
Normal file
84
nix/rust.nix
Normal file
@@ -0,0 +1,84 @@
|
||||
# The Rust half of the tool set shared by the CI environment and the dev shell:
|
||||
# the stable toolchain pinned by rust-toolchain.toml, the nightly the Rust
|
||||
# coverage job needs, and the cargo plugins. Consumed by packages.nix.
|
||||
{ pkgs }:
|
||||
let
|
||||
# rust-overlay's toolchain propagates the *default* stdenv.cc onto the PATH (so
|
||||
# cargo has a linker). That default may be different from the clang we pin
|
||||
# elsewhere, so it shadows our clang and the build can silently use a different
|
||||
# compiler version. Drop that cc from every propagation channel instead of
|
||||
# pinning a replacement: the toolchain then carries no compiler and cargo just
|
||||
# uses the active shell's stdenv cc.
|
||||
#
|
||||
# The channel list is every list mkDerivation propagates to a dependent's
|
||||
# environment (including the two legacy aliases). rust-overlay currently only
|
||||
# uses propagatedBuildInputs and depsHostHostPropagated, but covering all of
|
||||
# them means an upstream switch to another channel cannot quietly put the
|
||||
# compiler back on PATH.
|
||||
dropDefaultCc =
|
||||
toolchain:
|
||||
let
|
||||
defaultCc = pkgs.stdenv.cc; # default compiler from nixpkgs stdenv
|
||||
withoutDefaultCc = builtins.filter (dep: (dep.outPath or "") != defaultCc.outPath);
|
||||
in
|
||||
toolchain.overrideAttrs (
|
||||
old:
|
||||
pkgs.lib.genAttrs [
|
||||
"depsBuildBuildPropagated"
|
||||
"propagatedNativeBuildInputs" # alias of depsBuildHostPropagated
|
||||
"depsBuildTargetPropagated"
|
||||
"depsHostHostPropagated"
|
||||
"propagatedBuildInputs" # alias of depsHostTargetPropagated
|
||||
"depsTargetTargetPropagated"
|
||||
] (channel: withoutDefaultCc (old.${channel} or [ ]))
|
||||
);
|
||||
|
||||
rustToolchain = dropDefaultCc (pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml);
|
||||
|
||||
# cargo-llvm-cov honours the #[coverage(off)] that keeps unit tests out of the
|
||||
# coverage report only under a nightly rustc, and looks for llvm-profdata and
|
||||
# llvm-cov in that same toolchain's sysroot — hence llvm-tools-preview.
|
||||
#
|
||||
# Not every nightly ships every component, so `nightly.latest` breaks on the
|
||||
# days llvm-tools-preview is absent; selectLatestNightlyWith walks back to the
|
||||
# newest one that has it. The result is the newest such nightly *known to the
|
||||
# locked rust-overlay*, which means updating flake.lock moves the compiler that
|
||||
# produces the coverage numbers — and with it the rustc version recorded in
|
||||
# nix/check-tools/*.txt, so those snapshots need regenerating alongside.
|
||||
rustNightly = dropDefaultCc (
|
||||
pkgs.rust-bin.selectLatestNightlyWith (
|
||||
toolchain: toolchain.minimal.override { extensions = [ "llvm-tools-preview" ]; }
|
||||
)
|
||||
);
|
||||
|
||||
# A second toolchain cannot go on PATH: its cargo and rustc would collide with
|
||||
# the pinned stable's in the ci-env buildEnv, which resolves collisions by
|
||||
# picking one silently. Reaching the nightly only through this wrapper keeps it
|
||||
# in the image closure (the Docker build copies the whole closure, not just
|
||||
# what is linked into /bin) while leaving it inactive everywhere that does not
|
||||
# ask for it.
|
||||
#
|
||||
# The script's `path` subcommand exists for scopes wider than one command — a
|
||||
# CI job appending to $GITHUB_PATH, so that the cargo cache action's own
|
||||
# `rustc -vV` probe, which runs in a step of its own, agrees with the toolchain
|
||||
# the build will use.
|
||||
rustNightlyScript = pkgs.replaceVarsWith {
|
||||
name = "rust-nightly";
|
||||
src = ./rust-nightly.sh;
|
||||
dir = "bin";
|
||||
isExecutable = true;
|
||||
replacements = {
|
||||
inherit (pkgs) runtimeShell;
|
||||
rustNightlyBin = "${rustNightly}/bin";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = [
|
||||
pkgs.cargo-audit
|
||||
pkgs.cargo-llvm-cov
|
||||
pkgs.cargo-nextest
|
||||
rustNightlyScript
|
||||
rustToolchain
|
||||
];
|
||||
}
|
||||
@@ -107,8 +107,12 @@ Config::makeConfig(
|
||||
else
|
||||
{
|
||||
config.outPeers = *limits.outPeers;
|
||||
config.inPeers = *limits.inPeers;
|
||||
config.maxPeers = 0;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// This will cause servers configured as validators to request that
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#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>
|
||||
@@ -397,6 +399,59 @@ 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)
|
||||
{
|
||||
|
||||
@@ -59,6 +59,10 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// featureSmartEscrow
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -108,6 +108,8 @@ transResults()
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"),
|
||||
MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."),
|
||||
MAKE_ERROR(tecOUT_OF_GAS, "The WASM code ran out of gas during execution."),
|
||||
MAKE_ERROR(tecBYTECODE_REJECTED, "The custom WASM code that was run rejected your transaction."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
@@ -133,6 +135,8 @@ transResults()
|
||||
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
|
||||
MAKE_ERROR(tefNO_DST_PARTIAL, "Partial payment to create account not allowed."),
|
||||
MAKE_ERROR(tefBAD_PATH_COUNT, "Malformed: Too many paths."),
|
||||
MAKE_ERROR(tefNO_BYTECODE, "There is no WASM code to run, but a WASM-specific field was included."),
|
||||
MAKE_ERROR(tefBYTECODE_NOT_INCLUDED, "WASM code requires a field that was not included."),
|
||||
|
||||
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
|
||||
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
|
||||
@@ -204,6 +208,8 @@ transResults()
|
||||
MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."),
|
||||
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
|
||||
MAKE_ERROR(temBAD_CIPHERTEXT, "Malformed: Invalid ciphertext."),
|
||||
MAKE_ERROR(temINVALID_BYTECODE, "Malformed: Provided byte code is invalid."),
|
||||
MAKE_ERROR(temTEMP_DISABLED, "The transaction requires logic that is currently temporarily disabled."),
|
||||
|
||||
MAKE_ERROR(terRETRY, "Retry transaction."),
|
||||
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
|
||||
|
||||
@@ -88,8 +88,13 @@ EscrowCreate::checkExtraFeatures(PreflightContext const& ctx)
|
||||
// Only require featureMPTokensV1 when the escrow amount is an MPT and
|
||||
// fixCleanup3_2_0 is active; XRP/IOU escrows are unaffected by this gate.
|
||||
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds<MPTIssue>())
|
||||
return ctx.rules.enabled(featureMPTokensV1);
|
||||
return true;
|
||||
{
|
||||
if (!ctx.rules.enabled(featureMPTokensV1))
|
||||
return false;
|
||||
}
|
||||
|
||||
return (!ctx.tx.isFieldPresent(sfBytecode) && !ctx.tx.isFieldPresent(sfData)) ||
|
||||
ctx.rules.enabled(featureSmartEscrow);
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
|
||||
@@ -65,7 +65,13 @@ checkCondition(Slice f, Slice c)
|
||||
bool
|
||||
EscrowFinish::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
|
||||
if (ctx.tx.isFieldPresent(sfCredentialIDs) && !ctx.rules.enabled(featureCredentials))
|
||||
return false;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfGas) && !ctx.rules.enabled(featureSmartEscrow))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
|
||||
@@ -123,6 +123,12 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
|
||||
return temDISABLED;
|
||||
}
|
||||
// The ttFEE transaction format defines these fields as optional,
|
||||
// but they are unconditionally forbidden until FeeVoteImpl is
|
||||
// updated to populate them (SmartEscrow behavioral port).
|
||||
if (ctx.tx.isFieldPresent(sfGasLimit) || ctx.tx.isFieldPresent(sfBytecodeSizeLimit) ||
|
||||
ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temDISABLED;
|
||||
return tesSUCCESS;
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#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>
|
||||
@@ -209,6 +210,12 @@ 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,6 +8,7 @@
|
||||
#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>
|
||||
@@ -111,6 +112,17 @@ 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
|
||||
@@ -331,6 +343,10 @@ 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,6 +7,7 @@
|
||||
#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>
|
||||
@@ -197,6 +198,14 @@ 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,6 +247,15 @@ 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 &&
|
||||
|
||||
@@ -119,7 +119,15 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
||||
if (hasHolder && (hasIssuerElGamalKey || hasAuditorElGamalKey))
|
||||
return temMALFORMED;
|
||||
|
||||
if (hasAuditorElGamalKey && !hasIssuerElGamalKey)
|
||||
// Pre-ConfidentialMPTKeyRotation amendment, the auditor key could not be
|
||||
// registered independently of the issuer key. The issuer could either:
|
||||
// - Register only the issuer key (in which case an auditor key could not be added later), or
|
||||
// - Register both the issuer and auditor keys simultaneously.
|
||||
//
|
||||
// Post-ConfidentialMPTKeyRotation amendment, the auditor key can be
|
||||
// registered after the issuer key has already been registered.
|
||||
if (hasAuditorElGamalKey && !hasIssuerElGamalKey &&
|
||||
!ctx.rules.enabled(featureConfidentialMPTKeyRotation))
|
||||
return temMALFORMED;
|
||||
|
||||
if (hasIssuerElGamalKey && !isValidCompressedECPoint(ctx.tx[sfIssuerEncryptionKey]))
|
||||
@@ -219,18 +227,57 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// cannot update issuer public key
|
||||
if (ctx.tx.isFieldPresent(sfIssuerEncryptionKey) &&
|
||||
sleMptIssuance->isFieldPresent(sfIssuerEncryptionKey))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
// Updating an existing encryption key requires the
|
||||
// ConfidentialMPTKeyRotation amendment.
|
||||
bool const canRotateKey = ctx.view.rules().enabled(featureConfidentialMPTKeyRotation);
|
||||
|
||||
// cannot update auditor public key
|
||||
if (ctx.tx.isFieldPresent(sfAuditorEncryptionKey) &&
|
||||
sleMptIssuance->isFieldPresent(sfAuditorEncryptionKey))
|
||||
bool const txHasIssuerKey = ctx.tx.isFieldPresent(sfIssuerEncryptionKey);
|
||||
bool const txHasAuditorKey = ctx.tx.isFieldPresent(sfAuditorEncryptionKey);
|
||||
bool const sleHasIssuerKey = sleMptIssuance->isFieldPresent(sfIssuerEncryptionKey);
|
||||
bool const sleHasAuditorKey = sleMptIssuance->isFieldPresent(sfAuditorEncryptionKey);
|
||||
|
||||
if (canRotateKey)
|
||||
{
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
// Post-ConfidentialMPTKeyRotation amendment, the encryption keys can be updated.
|
||||
// A first-time auditor key registration requires an issuer key,
|
||||
// either already on the issuance or set by the same transaction.
|
||||
bool const registersAuditorKey = txHasAuditorKey && !sleHasAuditorKey;
|
||||
bool const issuerKeyExists = sleHasIssuerKey || txHasIssuerKey;
|
||||
if (registersAuditorKey && !issuerKeyExists)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Rotating a key to its current value is not permitted: a key epoch
|
||||
// increment must always correspond to an actual key change.
|
||||
if (txHasIssuerKey && sleHasIssuerKey &&
|
||||
ctx.tx[sfIssuerEncryptionKey] == (*sleMptIssuance)[sfIssuerEncryptionKey])
|
||||
return tecDUPLICATE;
|
||||
|
||||
if (txHasAuditorKey && sleHasAuditorKey &&
|
||||
ctx.tx[sfAuditorEncryptionKey] == (*sleMptIssuance)[sfAuditorEncryptionKey])
|
||||
return tecDUPLICATE;
|
||||
|
||||
// Key epochs must never wrap. Epoch 0 serves as the sentinel for "never
|
||||
// rotated." Holders' mirror epochs are checked against it for equality,
|
||||
// so a wrap would cause stale mirror ciphertexts to appear valid instead
|
||||
// of failing loudly.
|
||||
if (txHasIssuerKey && sleHasIssuerKey &&
|
||||
(*sleMptIssuance)[~sfIssuerKeyEpoch].value_or(0) == kMaxKeyEpoch)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (txHasAuditorKey && sleHasAuditorKey &&
|
||||
(*sleMptIssuance)[~sfAuditorKeyEpoch].value_or(0) == kMaxKeyEpoch)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pre-ConfidentialMPTKeyRotation amendment, the encryption keys can not be updated.
|
||||
// cannot update issuer public key
|
||||
if (txHasIssuerKey && sleHasIssuerKey)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// cannot update auditor public key
|
||||
if (txHasAuditorKey && sleHasAuditorKey)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
auto const enablesConfidentialBalance =
|
||||
@@ -241,25 +288,30 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
// Encryption keys can only be set if confidential amounts are already
|
||||
// enabled on the issuance OR if the transaction is enabling it
|
||||
if (ctx.tx.isFieldPresent(sfIssuerEncryptionKey) &&
|
||||
!sleMptIssuance->isFlag(lsfMPTCanHoldConfidentialBalance) && !enablesConfidentialBalance)
|
||||
if (txHasIssuerKey && !sleMptIssuance->isFlag(lsfMPTCanHoldConfidentialBalance) &&
|
||||
!enablesConfidentialBalance)
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfAuditorEncryptionKey) &&
|
||||
!sleMptIssuance->isFlag(lsfMPTCanHoldConfidentialBalance) && !enablesConfidentialBalance)
|
||||
if (txHasAuditorKey && !sleMptIssuance->isFlag(lsfMPTCanHoldConfidentialBalance) &&
|
||||
!enablesConfidentialBalance)
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// cannot upload key if there's circulating supply of COA
|
||||
if ((ctx.tx.isFieldPresent(sfIssuerEncryptionKey) ||
|
||||
ctx.tx.isFieldPresent(sfAuditorEncryptionKey) || enablesConfidentialBalance) &&
|
||||
(*sleMptIssuance)[~sfConfidentialOutstandingAmount].value_or(0) > 0)
|
||||
{
|
||||
bool const hasConfidentialOA =
|
||||
(*sleMptIssuance)[~sfConfidentialOutstandingAmount].value_or(0) > 0;
|
||||
|
||||
// Pre-ConfidentialMPTKeyRotation amendment, keys cannot be uploaded while
|
||||
// COA > 0. Post-amendment they can be uploaded even if COA > 0.
|
||||
if (!canRotateKey && (txHasIssuerKey || txHasAuditorKey) && hasConfidentialOA)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Enabling confidential balances when COA > 0 is not permitted, regardless of
|
||||
// ConfidentialMPTKeyRotation.
|
||||
if (enablesConfidentialBalance && hasConfidentialOA)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -377,25 +429,69 @@ MPTokenIssuanceSet::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const pubKey = ctx_.tx[~sfIssuerEncryptionKey])
|
||||
{
|
||||
// This is enforced in preflight.
|
||||
// Sets an encryption key on the issuance. Overwriting an existing key
|
||||
// (a rotation) increments the corresponding key epoch; a first-time
|
||||
// registration leaves the epoch absent (epoch 0), matching issuances
|
||||
// whose keys were registered before the ConfidentialMPTKeyRotation
|
||||
// amendment.
|
||||
bool const canRotateKey = view().rules().enabled(featureConfidentialMPTKeyRotation);
|
||||
auto const setEncryptionKey = [&](SF_VL const& keyField, SF_UINT32 const& epochField) -> TER {
|
||||
auto const pubKey = ctx_.tx[~keyField];
|
||||
if (!pubKey)
|
||||
return tesSUCCESS;
|
||||
|
||||
// This is enforced in preflight, which rejects a transaction carrying
|
||||
// both sfHolder and an encryption key.
|
||||
XRPL_ASSERT(
|
||||
sle->getType() == ltMPTOKEN_ISSUANCE,
|
||||
"MPTokenIssuanceSet::doApply : modifying MPTokenIssuance");
|
||||
|
||||
sle->setFieldVL(sfIssuerEncryptionKey, *pubKey);
|
||||
}
|
||||
// Add sanity check under the amendment ConfidentialMPTKeyRotation.
|
||||
// Pre-confidentialMPTKeyRotation did not return tecINTERNAL so
|
||||
// this should be under the amendment guard.
|
||||
if (canRotateKey && sle->getType() != ltMPTOKEN_ISSUANCE)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const pubKey = ctx_.tx[~sfAuditorEncryptionKey])
|
||||
{
|
||||
// This is enforced in preflight.
|
||||
XRPL_ASSERT(
|
||||
sle->getType() == ltMPTOKEN_ISSUANCE,
|
||||
"MPTokenIssuanceSet::doApply : modifying MPTokenIssuance");
|
||||
// NOTE: presence must be checked before the key is overwritten below.
|
||||
bool const isRotation = sle->isFieldPresent(keyField);
|
||||
sle->setFieldVL(keyField, *pubKey);
|
||||
|
||||
sle->setFieldVL(sfAuditorEncryptionKey, *pubKey);
|
||||
}
|
||||
if (isRotation)
|
||||
{
|
||||
// Preclaim rejects overwriting an existing key unless the amendment is
|
||||
// enabled.
|
||||
if (!canRotateKey)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::MPTokenIssuanceSet::doApply : rotation without amendment");
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const epoch = (*sle)[~epochField].valueOr(0);
|
||||
|
||||
// Preclaim rejects a rotation that would wrap the epoch. So this should never happen.
|
||||
if (epoch >= kMaxKeyEpoch)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::MPTokenIssuanceSet::doApply : key epoch overflow");
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
(*sle)[epochField] = epoch + 1;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
};
|
||||
|
||||
if (auto const ter = setEncryptionKey(sfIssuerEncryptionKey, sfIssuerKeyEpoch);
|
||||
!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const ter = setEncryptionKey(sfAuditorEncryptionKey, sfAuditorKeyEpoch);
|
||||
!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
|
||||
view().update(sle);
|
||||
|
||||
|
||||
1296
src/test/app/ConfidentialMPTKeyRotation_test.cpp
Normal file
1296
src/test/app/ConfidentialMPTKeyRotation_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -736,12 +736,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.err = temMALFORMED,
|
||||
});
|
||||
|
||||
// Cannot set auditor key without issuer key
|
||||
mptAlice.set({
|
||||
.account = alice,
|
||||
.auditorPubKey = mptAlice.getPubKey(alice),
|
||||
.err = temMALFORMED,
|
||||
});
|
||||
// Note: "auditor key without issuer key" (temMALFORMED before
|
||||
// ConfidentialMPTKeyRotation) is covered in ConfidentialMPTKeyRotation_test
|
||||
|
||||
// Cannot set Holder and issuer Keys in the same transaction
|
||||
mptAlice.set({
|
||||
@@ -787,9 +783,9 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
});
|
||||
}
|
||||
|
||||
// Cannot update issuer public key once set
|
||||
// Cannot update issuer public key once set (pre-ConfidentialMPTKeyRotation behavior)
|
||||
{
|
||||
Env env{*this, features};
|
||||
Env env{*this, features - featureConfidentialMPTKeyRotation};
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||
@@ -819,8 +815,9 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// Cannot update issuer and auditor public keys once set
|
||||
// Note: trying to set only auditor key fails in preflight (temMALFORMED)
|
||||
// so we must provide both keys, which fails on issuer key check first
|
||||
// (pre-ConfidentialMPTKeyRotation behavior)
|
||||
{
|
||||
Env env{*this, features};
|
||||
Env env{*this, features - featureConfidentialMPTKeyRotation};
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const auditor("auditor");
|
||||
@@ -900,8 +897,9 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
}
|
||||
|
||||
// Set issuer key first, then auditor key in a separate tx
|
||||
// (pre-ConfidentialMPTKeyRotation behavior)
|
||||
{
|
||||
Env env{*this, features};
|
||||
Env env{*this, features - featureConfidentialMPTKeyRotation};
|
||||
Account const alice("alice");
|
||||
Account const auditor("auditor");
|
||||
MPTTester mptAlice(env, alice, {.holders = {}, .auditor = auditor});
|
||||
@@ -2193,6 +2191,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.account = bob,
|
||||
.dest = bob,
|
||||
.amt = 10,
|
||||
.proof = getTrivialSendProofHex(),
|
||||
.err = temMALFORMED,
|
||||
});
|
||||
|
||||
@@ -2899,22 +2898,6 @@ 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,
|
||||
@@ -2935,12 +2918,12 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
});
|
||||
|
||||
// Bob has 100, tries to send 2^64-1. Invalid remaining balance.
|
||||
mptAlice.send({
|
||||
.account = bob,
|
||||
.dest = carol,
|
||||
.amt = std::numeric_limits<std::uint64_t>::max(),
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
// Bob sends 1, remaining 99.
|
||||
mptAlice.send({
|
||||
@@ -2949,14 +2932,6 @@ 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)
|
||||
@@ -2973,18 +2948,13 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
|
||||
// Trying to send any amount with 0 spending balance must fail:
|
||||
// the range proof for < 0 is invalid.
|
||||
mptAlice2.send({
|
||||
.account = bob2,
|
||||
.dest = carol2,
|
||||
.amt = 1,
|
||||
.err = tecBAD_PROOF,
|
||||
});
|
||||
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));
|
||||
|
||||
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
|
||||
@@ -3464,7 +3434,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const convertBackContextHash =
|
||||
getConvertBackContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
convertBackAmt,
|
||||
convertBackContextHash,
|
||||
@@ -3474,6 +3444,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
{
|
||||
json::Value jv;
|
||||
@@ -3485,7 +3457,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(proof);
|
||||
jv[sfZKProof.jsonName] = strHex(requireOptionalRef(proof, "Missing proof"));
|
||||
|
||||
env(jv, Ter(tesSUCCESS));
|
||||
}
|
||||
@@ -5285,7 +5257,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
Buffer const badPedersenCommitment =
|
||||
mptAlice.getPedersenCommitment(1, pcBlindingFactor);
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5295,6 +5267,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5315,7 +5289,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5325,6 +5299,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = generateBlindingFactor(), // wrong blinding factor
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5339,22 +5315,26 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
}
|
||||
|
||||
// Test 3: Proof generated with wrong balance value.
|
||||
// The proof claims balance=1 but the encrypted spending balance contains
|
||||
// the actual balance. Verification fails because the values don't match.
|
||||
// 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.
|
||||
{
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
uint64_t constexpr claimedBalance = 20; // wrong: real balance is 40
|
||||
|
||||
auto const proof = getForgedConvertBackProof(
|
||||
mptAlice,
|
||||
bob,
|
||||
claimedBalance,
|
||||
spendingBalance,
|
||||
amt,
|
||||
contextHash,
|
||||
{
|
||||
.pedersenCommitment = pedersenCommitment,
|
||||
.amt = 1, // wrong balance
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
pedersenCommitment,
|
||||
encryptedSpendingBalance,
|
||||
pcBlindingFactor,
|
||||
contextHash);
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5377,7 +5357,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
Buffer const badPedersenCommitment =
|
||||
mptAlice.getPedersenCommitment(1, pcBlindingFactor);
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5387,6 +5367,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5407,7 +5389,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
{
|
||||
uint256 const badContextHash{1};
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
badContextHash, // wrong context hash
|
||||
@@ -5417,6 +5399,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5436,7 +5420,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5446,6 +5430,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5921,22 +5907,26 @@ 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=1 but the spending balance contains the
|
||||
// actual balance. The compact proof's balance-linkage check fails.
|
||||
// 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.
|
||||
{
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
uint64_t constexpr claimedBalance = 20; // wrong: real balance is 40
|
||||
|
||||
auto const proof = getForgedConvertBackProof(
|
||||
mptAlice,
|
||||
bob,
|
||||
claimedBalance,
|
||||
spendingBalance,
|
||||
amt,
|
||||
contextHash,
|
||||
{
|
||||
.pedersenCommitment = pedersenCommitment,
|
||||
.amt = 1, // wrong balance (actual balance is ~40)
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
pedersenCommitment,
|
||||
encryptedSpendingBalance,
|
||||
pcBlindingFactor,
|
||||
contextHash);
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5958,7 +5948,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -5968,6 +5958,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = generateBlindingFactor(), // wrong blinding factor
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -5987,7 +5979,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
// makes the proof invalid for this transaction, preventing replay attacks.
|
||||
{
|
||||
uint256 const badContextHash{1};
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
badContextHash, // wrong context hash
|
||||
@@ -5997,6 +5989,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6016,7 +6010,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -6026,6 +6020,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalance,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6075,7 +6071,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
Buffer const bobCiphertext = mptAlice.encryptAmount(bob, amt, blindingFactor);
|
||||
auto const version = mptAlice.getMPTokenVersion(bob);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
makeContextHash(env, mptAlice, alice, bob, carol, version),
|
||||
@@ -6086,6 +6082,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
encryptedSpendingBalance, "Missing encrypted spending balance"),
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -6175,7 +6173,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHashA =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), currentSeq, version);
|
||||
|
||||
Buffer const proofA = mptAlice.getConvertBackProof(
|
||||
auto const proofA = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amtA,
|
||||
contextHashA,
|
||||
@@ -6185,6 +6183,8 @@ 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 +6256,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const oldContextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), currentSeq, versionV);
|
||||
|
||||
Buffer const oldProof = mptAlice.getConvertBackProof(
|
||||
auto const oldProof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
oldContextHash,
|
||||
@@ -6266,6 +6266,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = encryptedSpendingBalanceV,
|
||||
.blindingFactor = pcBlindingFactor,
|
||||
});
|
||||
if (!BEAST_EXPECT(oldProof.has_value()))
|
||||
return;
|
||||
|
||||
// Submit and verify failure
|
||||
mptAlice.convertBack({
|
||||
@@ -6328,7 +6330,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), currentVersion);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
amt,
|
||||
contextHash,
|
||||
@@ -6338,6 +6340,8 @@ 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.
|
||||
@@ -6471,7 +6475,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const contextHash =
|
||||
getConvertBackContextHash(bob, mptAlice.issuanceID(), env.seq(bob), currentVersion);
|
||||
|
||||
Buffer const proof = mptAlice.getConvertBackProof(
|
||||
auto const proof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
1,
|
||||
contextHash,
|
||||
@@ -6481,6 +6485,8 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.encryptedAmt = underflowedCt,
|
||||
.blindingFactor = pcBf,
|
||||
});
|
||||
if (!BEAST_EXPECT(proof.has_value()))
|
||||
return;
|
||||
|
||||
mptAlice.convertBack({
|
||||
.account = bob,
|
||||
@@ -7743,7 +7749,7 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
uint256 const convertBackCtxHash =
|
||||
getConvertBackContextHash(bob.id(), mptAlice.issuanceID(), env.seq(bob), version);
|
||||
|
||||
Buffer const convertBackProof = mptAlice.getConvertBackProof(
|
||||
auto const convertBackProof = mptAlice.getConvertBackProof(
|
||||
bob,
|
||||
sendAmount,
|
||||
convertBackCtxHash,
|
||||
@@ -7753,14 +7759,18 @@ 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);
|
||||
auto const copyLen = std::min(convertBackProof.size(), expectedSendSize);
|
||||
std::memcpy(resizedProof.data(), convertBackProof.data(), copyLen);
|
||||
Buffer const& convertBackProofRef =
|
||||
requireOptionalRef(convertBackProof, "Missing proof");
|
||||
auto const copyLen = std::min(convertBackProofRef.size(), expectedSendSize);
|
||||
std::memcpy(resizedProof.data(), convertBackProofRef.data(), copyLen);
|
||||
// Zero-pad the rest (if convertBack proof is shorter)
|
||||
if (copyLen < expectedSendSize)
|
||||
std::memset(resizedProof.data() + copyLen, 0, expectedSendSize - copyLen);
|
||||
|
||||
@@ -124,6 +124,87 @@ 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&
|
||||
@@ -347,6 +428,111 @@ 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.
|
||||
@@ -418,6 +604,18 @@ 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
|
||||
|
||||
37
src/test/jtx/impl/ConfidentialTransfer.cpp
Normal file
37
src/test/jtx/impl/ConfidentialTransfer.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#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,6 +20,7 @@
|
||||
#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>
|
||||
@@ -451,8 +452,10 @@ class MPTTester
|
||||
std::optional<Account> const auditor_;
|
||||
std::optional<MPTID> id_;
|
||||
bool close_;
|
||||
std::unordered_map<AccountID, Buffer> pubKeys_;
|
||||
std::unordered_map<AccountID, Buffer> privKeys_;
|
||||
// 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_;
|
||||
|
||||
public:
|
||||
enum class EncryptedBalanceType {
|
||||
@@ -612,6 +615,30 @@ public:
|
||||
[[nodiscard]] bool
|
||||
checkImmutableFlags(std::uint32_t expectedFlags) const;
|
||||
|
||||
// Checks both key epochs on the issuance. Pass std::nullopt for an epoch
|
||||
// that is expected to be absent, which means the key is never rotated.
|
||||
[[nodiscard]] bool
|
||||
checkKeyEpochs(
|
||||
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.
|
||||
[[nodiscard]] bool
|
||||
checkEncryptionKeys(
|
||||
std::optional<Account> const& issuerKeyOwner,
|
||||
std::optional<Account> const& auditorKeyOwner) const;
|
||||
|
||||
[[nodiscard]] Account const&
|
||||
issuer() const
|
||||
{
|
||||
@@ -663,20 +690,31 @@ public:
|
||||
|
||||
operator Asset() const;
|
||||
|
||||
void
|
||||
// Generates the account's next key pair and returns the key epoch it landed
|
||||
// at, leaving the earlier ones retrievable.
|
||||
std::uint32_t
|
||||
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) const;
|
||||
getPubKey(Account const& account, std::optional<std::uint32_t> epoch = std::nullopt) 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) const;
|
||||
getPrivKey(Account const& account, std::optional<std::uint32_t> epoch = std::nullopt) 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) const;
|
||||
decryptAmount(
|
||||
Account const& account,
|
||||
Buffer const& amt,
|
||||
std::optional<std::uint32_t> epoch = std::nullopt) const;
|
||||
|
||||
[[nodiscard]] std::optional<uint64_t>
|
||||
getDecryptedBalance(Account const& account, EncryptedBalanceType balanceType) const;
|
||||
@@ -704,7 +742,7 @@ public:
|
||||
PedersenProofParams const& amountParams,
|
||||
PedersenProofParams const& balanceParams) const;
|
||||
|
||||
[[nodiscard]] Buffer
|
||||
[[nodiscard]] std::optional<Buffer>
|
||||
getConvertBackProof(
|
||||
Account const& holder,
|
||||
std::uint64_t const amount,
|
||||
@@ -730,6 +768,10 @@ 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)
|
||||
@@ -799,15 +841,28 @@ 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,
|
||||
Buffer& holderCiphertext,
|
||||
Buffer& issuerCiphertext,
|
||||
std::optional<Buffer>& auditorCiphertext,
|
||||
Buffer& blindingFactor) const;
|
||||
Account const& account,
|
||||
std::uint64_t const amount) const;
|
||||
};
|
||||
|
||||
} // namespace xrpl::test::jtx
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -96,6 +97,29 @@ 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");
|
||||
|
||||
|
||||
83
src/test/overlay/overlay_limit_test.cpp
Normal file
83
src/test/overlay/overlay_limit_test.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#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,9 +126,16 @@ 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));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -194,6 +201,7 @@ 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))
|
||||
@@ -219,12 +227,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,6 +1214,9 @@ 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,
|
||||
@@ -1221,7 +1224,7 @@ TEST(PeerFinderConfig, applies_legacy_and_explicit_peer_limits)
|
||||
.port = 0,
|
||||
.expectedOut = 10,
|
||||
.expectedIn = 0,
|
||||
.expectedIpLimit = 6}};
|
||||
.expectedIpLimit = 1}};
|
||||
|
||||
for (auto const& testCase : cases)
|
||||
{
|
||||
@@ -1239,6 +1242,13 @@ 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;
|
||||
|
||||
@@ -27,6 +27,8 @@ TEST(EscrowTests, BuilderSettersRoundTrip)
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const cancelAfterValue = canonical_UINT32();
|
||||
auto const finishAfterValue = canonical_UINT32();
|
||||
auto const bytecodeValue = canonical_VL();
|
||||
auto const dataValue = canonical_VL();
|
||||
auto const sourceTagValue = canonical_UINT32();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
@@ -49,6 +51,8 @@ TEST(EscrowTests, BuilderSettersRoundTrip)
|
||||
builder.setCondition(conditionValue);
|
||||
builder.setCancelAfter(cancelAfterValue);
|
||||
builder.setFinishAfter(finishAfterValue);
|
||||
builder.setBytecode(bytecodeValue);
|
||||
builder.setData(dataValue);
|
||||
builder.setSourceTag(sourceTagValue);
|
||||
builder.setDestinationTag(destinationTagValue);
|
||||
builder.setDestinationNode(destinationNodeValue);
|
||||
@@ -132,6 +136,22 @@ TEST(EscrowTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(entry.hasFinishAfter());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = bytecodeValue;
|
||||
auto const actualOpt = entry.getBytecode();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfBytecode");
|
||||
EXPECT_TRUE(entry.hasBytecode());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = dataValue;
|
||||
auto const actualOpt = entry.getData();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfData");
|
||||
EXPECT_TRUE(entry.hasData());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sourceTagValue;
|
||||
auto const actualOpt = entry.getSourceTag();
|
||||
@@ -192,6 +212,8 @@ TEST(EscrowTests, BuilderFromSleRoundTrip)
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const cancelAfterValue = canonical_UINT32();
|
||||
auto const finishAfterValue = canonical_UINT32();
|
||||
auto const bytecodeValue = canonical_VL();
|
||||
auto const dataValue = canonical_VL();
|
||||
auto const sourceTagValue = canonical_UINT32();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const ownerNodeValue = canonical_UINT64();
|
||||
@@ -210,6 +232,8 @@ TEST(EscrowTests, BuilderFromSleRoundTrip)
|
||||
sle->at(sfCondition) = conditionValue;
|
||||
sle->at(sfCancelAfter) = cancelAfterValue;
|
||||
sle->at(sfFinishAfter) = finishAfterValue;
|
||||
sle->at(sfBytecode) = bytecodeValue;
|
||||
sle->at(sfData) = dataValue;
|
||||
sle->at(sfSourceTag) = sourceTagValue;
|
||||
sle->at(sfDestinationTag) = destinationTagValue;
|
||||
sle->at(sfOwnerNode) = ownerNodeValue;
|
||||
@@ -340,6 +364,32 @@ TEST(EscrowTests, BuilderFromSleRoundTrip)
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfFinishAfter");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = bytecodeValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getBytecode();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getBytecode();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfBytecode");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfBytecode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = dataValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getData();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getData();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfData");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfData");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = sourceTagValue;
|
||||
|
||||
@@ -477,6 +527,10 @@ TEST(EscrowTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(entry.getCancelAfter().has_value());
|
||||
EXPECT_FALSE(entry.hasFinishAfter());
|
||||
EXPECT_FALSE(entry.getFinishAfter().has_value());
|
||||
EXPECT_FALSE(entry.hasBytecode());
|
||||
EXPECT_FALSE(entry.getBytecode().has_value());
|
||||
EXPECT_FALSE(entry.hasData());
|
||||
EXPECT_FALSE(entry.getData().has_value());
|
||||
EXPECT_FALSE(entry.hasSourceTag());
|
||||
EXPECT_FALSE(entry.getSourceTag().has_value());
|
||||
EXPECT_FALSE(entry.hasDestinationTag());
|
||||
|
||||
@@ -27,6 +27,9 @@ TEST(FeeSettingsTests, BuilderSettersRoundTrip)
|
||||
auto const baseFeeDropsValue = canonical_AMOUNT();
|
||||
auto const reserveBaseDropsValue = canonical_AMOUNT();
|
||||
auto const reserveIncrementDropsValue = canonical_AMOUNT();
|
||||
auto const gasLimitValue = canonical_UINT32();
|
||||
auto const bytecodeSizeLimitValue = canonical_UINT32();
|
||||
auto const gasPriceValue = canonical_UINT32();
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
|
||||
@@ -40,6 +43,9 @@ TEST(FeeSettingsTests, BuilderSettersRoundTrip)
|
||||
builder.setBaseFeeDrops(baseFeeDropsValue);
|
||||
builder.setReserveBaseDrops(reserveBaseDropsValue);
|
||||
builder.setReserveIncrementDrops(reserveIncrementDropsValue);
|
||||
builder.setGasLimit(gasLimitValue);
|
||||
builder.setBytecodeSizeLimit(bytecodeSizeLimitValue);
|
||||
builder.setGasPrice(gasPriceValue);
|
||||
builder.setPreviousTxnID(previousTxnIDValue);
|
||||
builder.setPreviousTxnLgrSeq(previousTxnLgrSeqValue);
|
||||
|
||||
@@ -108,6 +114,30 @@ TEST(FeeSettingsTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(entry.hasReserveIncrementDrops());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasLimitValue;
|
||||
auto const actualOpt = entry.getGasLimit();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfGasLimit");
|
||||
EXPECT_TRUE(entry.hasGasLimit());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = bytecodeSizeLimitValue;
|
||||
auto const actualOpt = entry.getBytecodeSizeLimit();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfBytecodeSizeLimit");
|
||||
EXPECT_TRUE(entry.hasBytecodeSizeLimit());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasPriceValue;
|
||||
auto const actualOpt = entry.getGasPrice();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfGasPrice");
|
||||
EXPECT_TRUE(entry.hasGasPrice());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnIDValue;
|
||||
auto const actualOpt = entry.getPreviousTxnID();
|
||||
@@ -144,6 +174,9 @@ TEST(FeeSettingsTests, BuilderFromSleRoundTrip)
|
||||
auto const baseFeeDropsValue = canonical_AMOUNT();
|
||||
auto const reserveBaseDropsValue = canonical_AMOUNT();
|
||||
auto const reserveIncrementDropsValue = canonical_AMOUNT();
|
||||
auto const gasLimitValue = canonical_UINT32();
|
||||
auto const bytecodeSizeLimitValue = canonical_UINT32();
|
||||
auto const gasPriceValue = canonical_UINT32();
|
||||
auto const previousTxnIDValue = canonical_UINT256();
|
||||
auto const previousTxnLgrSeqValue = canonical_UINT32();
|
||||
|
||||
@@ -156,6 +189,9 @@ TEST(FeeSettingsTests, BuilderFromSleRoundTrip)
|
||||
sle->at(sfBaseFeeDrops) = baseFeeDropsValue;
|
||||
sle->at(sfReserveBaseDrops) = reserveBaseDropsValue;
|
||||
sle->at(sfReserveIncrementDrops) = reserveIncrementDropsValue;
|
||||
sle->at(sfGasLimit) = gasLimitValue;
|
||||
sle->at(sfBytecodeSizeLimit) = bytecodeSizeLimitValue;
|
||||
sle->at(sfGasPrice) = gasPriceValue;
|
||||
sle->at(sfPreviousTxnID) = previousTxnIDValue;
|
||||
sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue;
|
||||
|
||||
@@ -259,6 +295,45 @@ TEST(FeeSettingsTests, BuilderFromSleRoundTrip)
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfReserveIncrementDrops");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasLimitValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getGasLimit();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getGasLimit();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfGasLimit");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfGasLimit");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = bytecodeSizeLimitValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getBytecodeSizeLimit();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getBytecodeSizeLimit();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfBytecodeSizeLimit");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfBytecodeSizeLimit");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasPriceValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getGasPrice();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getGasPrice();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfGasPrice");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfGasPrice");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = previousTxnIDValue;
|
||||
|
||||
@@ -351,6 +426,12 @@ TEST(FeeSettingsTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(entry.getReserveBaseDrops().has_value());
|
||||
EXPECT_FALSE(entry.hasReserveIncrementDrops());
|
||||
EXPECT_FALSE(entry.getReserveIncrementDrops().has_value());
|
||||
EXPECT_FALSE(entry.hasGasLimit());
|
||||
EXPECT_FALSE(entry.getGasLimit().has_value());
|
||||
EXPECT_FALSE(entry.hasBytecodeSizeLimit());
|
||||
EXPECT_FALSE(entry.getBytecodeSizeLimit().has_value());
|
||||
EXPECT_FALSE(entry.hasGasPrice());
|
||||
EXPECT_FALSE(entry.getGasPrice().has_value());
|
||||
EXPECT_FALSE(entry.hasPreviousTxnID());
|
||||
EXPECT_FALSE(entry.getPreviousTxnID().has_value());
|
||||
EXPECT_FALSE(entry.hasPreviousTxnLgrSeq());
|
||||
|
||||
@@ -36,6 +36,8 @@ TEST(MPTokenIssuanceTests, BuilderSettersRoundTrip)
|
||||
auto const referenceHoldingValue = canonical_UINT256();
|
||||
auto const issuerEncryptionKeyValue = canonical_VL();
|
||||
auto const auditorEncryptionKeyValue = canonical_VL();
|
||||
auto const issuerKeyEpochValue = canonical_UINT32();
|
||||
auto const auditorKeyEpochValue = canonical_UINT32();
|
||||
auto const confidentialOutstandingAmountValue = canonical_UINT64();
|
||||
|
||||
MPTokenIssuanceBuilder builder{
|
||||
@@ -57,6 +59,8 @@ TEST(MPTokenIssuanceTests, BuilderSettersRoundTrip)
|
||||
builder.setReferenceHolding(referenceHoldingValue);
|
||||
builder.setIssuerEncryptionKey(issuerEncryptionKeyValue);
|
||||
builder.setAuditorEncryptionKey(auditorEncryptionKeyValue);
|
||||
builder.setIssuerKeyEpoch(issuerKeyEpochValue);
|
||||
builder.setAuditorKeyEpoch(auditorKeyEpochValue);
|
||||
builder.setConfidentialOutstandingAmount(confidentialOutstandingAmountValue);
|
||||
|
||||
builder.setLedgerIndex(index);
|
||||
@@ -184,6 +188,22 @@ TEST(MPTokenIssuanceTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(entry.hasAuditorEncryptionKey());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = issuerKeyEpochValue;
|
||||
auto const actualOpt = entry.getIssuerKeyEpoch();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfIssuerKeyEpoch");
|
||||
EXPECT_TRUE(entry.hasIssuerKeyEpoch());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = auditorKeyEpochValue;
|
||||
auto const actualOpt = entry.getAuditorKeyEpoch();
|
||||
ASSERT_TRUE(actualOpt.has_value());
|
||||
expectEqualField(expected, *actualOpt, "sfAuditorKeyEpoch");
|
||||
EXPECT_TRUE(entry.hasAuditorKeyEpoch());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = confidentialOutstandingAmountValue;
|
||||
auto const actualOpt = entry.getConfidentialOutstandingAmount();
|
||||
@@ -221,6 +241,8 @@ TEST(MPTokenIssuanceTests, BuilderFromSleRoundTrip)
|
||||
auto const referenceHoldingValue = canonical_UINT256();
|
||||
auto const issuerEncryptionKeyValue = canonical_VL();
|
||||
auto const auditorEncryptionKeyValue = canonical_VL();
|
||||
auto const issuerKeyEpochValue = canonical_UINT32();
|
||||
auto const auditorKeyEpochValue = canonical_UINT32();
|
||||
auto const confidentialOutstandingAmountValue = canonical_UINT64();
|
||||
|
||||
auto sle = std::make_shared<SLE>(MPTokenIssuance::entryType, index);
|
||||
@@ -241,6 +263,8 @@ TEST(MPTokenIssuanceTests, BuilderFromSleRoundTrip)
|
||||
sle->at(sfReferenceHolding) = referenceHoldingValue;
|
||||
sle->at(sfIssuerEncryptionKey) = issuerEncryptionKeyValue;
|
||||
sle->at(sfAuditorEncryptionKey) = auditorEncryptionKeyValue;
|
||||
sle->at(sfIssuerKeyEpoch) = issuerKeyEpochValue;
|
||||
sle->at(sfAuditorKeyEpoch) = auditorKeyEpochValue;
|
||||
sle->at(sfConfidentialOutstandingAmount) = confidentialOutstandingAmountValue;
|
||||
|
||||
MPTokenIssuanceBuilder builderFromSle{sle};
|
||||
@@ -442,6 +466,32 @@ TEST(MPTokenIssuanceTests, BuilderFromSleRoundTrip)
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfAuditorEncryptionKey");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = issuerKeyEpochValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getIssuerKeyEpoch();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getIssuerKeyEpoch();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfIssuerKeyEpoch");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfIssuerKeyEpoch");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = auditorKeyEpochValue;
|
||||
|
||||
auto const fromSleOpt = entryFromSle.getAuditorKeyEpoch();
|
||||
auto const fromBuilderOpt = entryFromBuilder.getAuditorKeyEpoch();
|
||||
|
||||
ASSERT_TRUE(fromSleOpt.has_value());
|
||||
ASSERT_TRUE(fromBuilderOpt.has_value());
|
||||
|
||||
expectEqualField(expected, *fromSleOpt, "sfAuditorKeyEpoch");
|
||||
expectEqualField(expected, *fromBuilderOpt, "sfAuditorKeyEpoch");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = confidentialOutstandingAmountValue;
|
||||
|
||||
@@ -539,6 +589,10 @@ TEST(MPTokenIssuanceTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(entry.getIssuerEncryptionKey().has_value());
|
||||
EXPECT_FALSE(entry.hasAuditorEncryptionKey());
|
||||
EXPECT_FALSE(entry.getAuditorEncryptionKey().has_value());
|
||||
EXPECT_FALSE(entry.hasIssuerKeyEpoch());
|
||||
EXPECT_FALSE(entry.getIssuerKeyEpoch().has_value());
|
||||
EXPECT_FALSE(entry.hasAuditorKeyEpoch());
|
||||
EXPECT_FALSE(entry.getAuditorKeyEpoch().has_value());
|
||||
EXPECT_FALSE(entry.hasConfidentialOutstandingAmount());
|
||||
EXPECT_FALSE(entry.getConfidentialOutstandingAmount().has_value());
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ 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{
|
||||
@@ -49,6 +51,8 @@ TEST(MPTokenTests, BuilderSettersRoundTrip)
|
||||
builder.setConfidentialBalanceVersion(confidentialBalanceVersionValue);
|
||||
builder.setIssuerEncryptedBalance(issuerEncryptedBalanceValue);
|
||||
builder.setAuditorEncryptedBalance(auditorEncryptedBalanceValue);
|
||||
builder.setIssuerKeyMirrorEpoch(issuerKeyMirrorEpochValue);
|
||||
builder.setAuditorKeyMirrorEpoch(auditorKeyMirrorEpochValue);
|
||||
builder.setHolderEncryptionKey(holderEncryptionKeyValue);
|
||||
|
||||
builder.setLedgerIndex(index);
|
||||
@@ -146,6 +150,22 @@ 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();
|
||||
@@ -179,6 +199,8 @@ 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);
|
||||
@@ -195,6 +217,8 @@ 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};
|
||||
@@ -347,6 +371,32 @@ 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;
|
||||
|
||||
@@ -436,6 +486,10 @@ 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());
|
||||
}
|
||||
|
||||
@@ -30,11 +30,13 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip)
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const destinationValue = canonical_ACCOUNT();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const amountValue = canonical_AMOUNT();
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const cancelAfterValue = canonical_UINT32();
|
||||
auto const finishAfterValue = canonical_UINT32();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const bytecodeValue = canonical_VL();
|
||||
auto const dataValue = canonical_VL();
|
||||
|
||||
EscrowCreateBuilder builder{
|
||||
accountValue,
|
||||
@@ -45,10 +47,12 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip)
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setDestinationTag(destinationTagValue);
|
||||
builder.setCondition(conditionValue);
|
||||
builder.setCancelAfter(cancelAfterValue);
|
||||
builder.setFinishAfter(finishAfterValue);
|
||||
builder.setDestinationTag(destinationTagValue);
|
||||
builder.setBytecode(bytecodeValue);
|
||||
builder.setData(dataValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
@@ -78,6 +82,14 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip)
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = destinationTagValue;
|
||||
auto const actualOpt = tx.getDestinationTag();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfDestinationTag");
|
||||
EXPECT_TRUE(tx.hasDestinationTag());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = conditionValue;
|
||||
auto const actualOpt = tx.getCondition();
|
||||
@@ -103,11 +115,19 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip)
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = destinationTagValue;
|
||||
auto const actualOpt = tx.getDestinationTag();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfDestinationTag");
|
||||
EXPECT_TRUE(tx.hasDestinationTag());
|
||||
auto const& expected = bytecodeValue;
|
||||
auto const actualOpt = tx.getBytecode();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBytecode should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfBytecode");
|
||||
EXPECT_TRUE(tx.hasBytecode());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = dataValue;
|
||||
auto const actualOpt = tx.getData();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfData should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfData");
|
||||
EXPECT_TRUE(tx.hasData());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -127,11 +147,13 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip)
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const destinationValue = canonical_ACCOUNT();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const amountValue = canonical_AMOUNT();
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const cancelAfterValue = canonical_UINT32();
|
||||
auto const finishAfterValue = canonical_UINT32();
|
||||
auto const destinationTagValue = canonical_UINT32();
|
||||
auto const bytecodeValue = canonical_VL();
|
||||
auto const dataValue = canonical_VL();
|
||||
|
||||
// Build an initial transaction
|
||||
EscrowCreateBuilder initialBuilder{
|
||||
@@ -142,10 +164,12 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip)
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setDestinationTag(destinationTagValue);
|
||||
initialBuilder.setCondition(conditionValue);
|
||||
initialBuilder.setCancelAfter(cancelAfterValue);
|
||||
initialBuilder.setFinishAfter(finishAfterValue);
|
||||
initialBuilder.setDestinationTag(destinationTagValue);
|
||||
initialBuilder.setBytecode(bytecodeValue);
|
||||
initialBuilder.setData(dataValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
@@ -176,6 +200,13 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip)
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = destinationTagValue;
|
||||
auto const actualOpt = rebuiltTx.getDestinationTag();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfDestinationTag");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = conditionValue;
|
||||
auto const actualOpt = rebuiltTx.getCondition();
|
||||
@@ -198,10 +229,17 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip)
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = destinationTagValue;
|
||||
auto const actualOpt = rebuiltTx.getDestinationTag();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfDestinationTag");
|
||||
auto const& expected = bytecodeValue;
|
||||
auto const actualOpt = rebuiltTx.getBytecode();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBytecode should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfBytecode");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = dataValue;
|
||||
auto const actualOpt = rebuiltTx.getData();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfData should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfData");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -263,14 +301,18 @@ TEST(TransactionsEscrowCreateTests, OptionalFieldsReturnNullopt)
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasDestinationTag());
|
||||
EXPECT_FALSE(tx.getDestinationTag().has_value());
|
||||
EXPECT_FALSE(tx.hasCondition());
|
||||
EXPECT_FALSE(tx.getCondition().has_value());
|
||||
EXPECT_FALSE(tx.hasCancelAfter());
|
||||
EXPECT_FALSE(tx.getCancelAfter().has_value());
|
||||
EXPECT_FALSE(tx.hasFinishAfter());
|
||||
EXPECT_FALSE(tx.getFinishAfter().has_value());
|
||||
EXPECT_FALSE(tx.hasDestinationTag());
|
||||
EXPECT_FALSE(tx.getDestinationTag().has_value());
|
||||
EXPECT_FALSE(tx.hasBytecode());
|
||||
EXPECT_FALSE(tx.getBytecode().has_value());
|
||||
EXPECT_FALSE(tx.hasData());
|
||||
EXPECT_FALSE(tx.getData().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip)
|
||||
auto const fulfillmentValue = canonical_VL();
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const credentialIDsValue = canonical_VECTOR256();
|
||||
auto const gasValue = canonical_UINT32();
|
||||
|
||||
EscrowFinishBuilder builder{
|
||||
accountValue,
|
||||
@@ -47,6 +48,7 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip)
|
||||
builder.setFulfillment(fulfillmentValue);
|
||||
builder.setCondition(conditionValue);
|
||||
builder.setCredentialIDs(credentialIDsValue);
|
||||
builder.setGas(gasValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
@@ -100,6 +102,14 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(tx.hasCredentialIDs());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasValue;
|
||||
auto const actualOpt = tx.getGas();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGas should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfGas");
|
||||
EXPECT_TRUE(tx.hasGas());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
@@ -121,6 +131,7 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip)
|
||||
auto const fulfillmentValue = canonical_VL();
|
||||
auto const conditionValue = canonical_VL();
|
||||
auto const credentialIDsValue = canonical_VECTOR256();
|
||||
auto const gasValue = canonical_UINT32();
|
||||
|
||||
// Build an initial transaction
|
||||
EscrowFinishBuilder initialBuilder{
|
||||
@@ -134,6 +145,7 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip)
|
||||
initialBuilder.setFulfillment(fulfillmentValue);
|
||||
initialBuilder.setCondition(conditionValue);
|
||||
initialBuilder.setCredentialIDs(credentialIDsValue);
|
||||
initialBuilder.setGas(gasValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
@@ -185,6 +197,13 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip)
|
||||
expectEqualField(expected, *actualOpt, "sfCredentialIDs");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasValue;
|
||||
auto const actualOpt = rebuiltTx.getGas();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGas should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfGas");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
@@ -250,6 +269,8 @@ TEST(TransactionsEscrowFinishTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(tx.getCondition().has_value());
|
||||
EXPECT_FALSE(tx.hasCredentialIDs());
|
||||
EXPECT_FALSE(tx.getCredentialIDs().has_value());
|
||||
EXPECT_FALSE(tx.hasGas());
|
||||
EXPECT_FALSE(tx.getGas().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ TEST(TransactionsSetFeeTests, BuilderSettersRoundTrip)
|
||||
auto const baseFeeDropsValue = canonical_AMOUNT();
|
||||
auto const reserveBaseDropsValue = canonical_AMOUNT();
|
||||
auto const reserveIncrementDropsValue = canonical_AMOUNT();
|
||||
auto const gasLimitValue = canonical_UINT32();
|
||||
auto const bytecodeSizeLimitValue = canonical_UINT32();
|
||||
auto const gasPriceValue = canonical_UINT32();
|
||||
|
||||
SetFeeBuilder builder{
|
||||
accountValue,
|
||||
@@ -53,6 +56,9 @@ TEST(TransactionsSetFeeTests, BuilderSettersRoundTrip)
|
||||
builder.setBaseFeeDrops(baseFeeDropsValue);
|
||||
builder.setReserveBaseDrops(reserveBaseDropsValue);
|
||||
builder.setReserveIncrementDrops(reserveIncrementDropsValue);
|
||||
builder.setGasLimit(gasLimitValue);
|
||||
builder.setBytecodeSizeLimit(bytecodeSizeLimitValue);
|
||||
builder.setGasPrice(gasPriceValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
@@ -134,6 +140,30 @@ TEST(TransactionsSetFeeTests, BuilderSettersRoundTrip)
|
||||
EXPECT_TRUE(tx.hasReserveIncrementDrops());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasLimitValue;
|
||||
auto const actualOpt = tx.getGasLimit();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGasLimit should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfGasLimit");
|
||||
EXPECT_TRUE(tx.hasGasLimit());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = bytecodeSizeLimitValue;
|
||||
auto const actualOpt = tx.getBytecodeSizeLimit();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBytecodeSizeLimit should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfBytecodeSizeLimit");
|
||||
EXPECT_TRUE(tx.hasBytecodeSizeLimit());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasPriceValue;
|
||||
auto const actualOpt = tx.getGasPrice();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGasPrice should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfGasPrice");
|
||||
EXPECT_TRUE(tx.hasGasPrice());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
@@ -158,6 +188,9 @@ TEST(TransactionsSetFeeTests, BuilderFromStTxRoundTrip)
|
||||
auto const baseFeeDropsValue = canonical_AMOUNT();
|
||||
auto const reserveBaseDropsValue = canonical_AMOUNT();
|
||||
auto const reserveIncrementDropsValue = canonical_AMOUNT();
|
||||
auto const gasLimitValue = canonical_UINT32();
|
||||
auto const bytecodeSizeLimitValue = canonical_UINT32();
|
||||
auto const gasPriceValue = canonical_UINT32();
|
||||
|
||||
// Build an initial transaction
|
||||
SetFeeBuilder initialBuilder{
|
||||
@@ -174,6 +207,9 @@ TEST(TransactionsSetFeeTests, BuilderFromStTxRoundTrip)
|
||||
initialBuilder.setBaseFeeDrops(baseFeeDropsValue);
|
||||
initialBuilder.setReserveBaseDrops(reserveBaseDropsValue);
|
||||
initialBuilder.setReserveIncrementDrops(reserveIncrementDropsValue);
|
||||
initialBuilder.setGasLimit(gasLimitValue);
|
||||
initialBuilder.setBytecodeSizeLimit(bytecodeSizeLimitValue);
|
||||
initialBuilder.setGasPrice(gasPriceValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
@@ -248,6 +284,27 @@ TEST(TransactionsSetFeeTests, BuilderFromStTxRoundTrip)
|
||||
expectEqualField(expected, *actualOpt, "sfReserveIncrementDrops");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasLimitValue;
|
||||
auto const actualOpt = rebuiltTx.getGasLimit();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGasLimit should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfGasLimit");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = bytecodeSizeLimitValue;
|
||||
auto const actualOpt = rebuiltTx.getBytecodeSizeLimit();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfBytecodeSizeLimit should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfBytecodeSizeLimit");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = gasPriceValue;
|
||||
auto const actualOpt = rebuiltTx.getGasPrice();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGasPrice should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfGasPrice");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
@@ -319,6 +376,12 @@ TEST(TransactionsSetFeeTests, OptionalFieldsReturnNullopt)
|
||||
EXPECT_FALSE(tx.getReserveBaseDrops().has_value());
|
||||
EXPECT_FALSE(tx.hasReserveIncrementDrops());
|
||||
EXPECT_FALSE(tx.getReserveIncrementDrops().has_value());
|
||||
EXPECT_FALSE(tx.hasGasLimit());
|
||||
EXPECT_FALSE(tx.getGasLimit().has_value());
|
||||
EXPECT_FALSE(tx.hasBytecodeSizeLimit());
|
||||
EXPECT_FALSE(tx.getBytecodeSizeLimit().has_value());
|
||||
EXPECT_FALSE(tx.hasGasPrice());
|
||||
EXPECT_FALSE(tx.getGasPrice().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,6 +62,19 @@ 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,6 +38,14 @@ 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 = static_cast<bool>(app_.getCluster().member(publicKey)) ||
|
||||
bool const reserved = app_.getCluster().isMember(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 static_cast<bool>(app_.getCluster().member(publicKey_));
|
||||
return app_.getCluster().isMember(publicKey_);
|
||||
}
|
||||
|
||||
std::string
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -32,12 +34,13 @@ fillTransaction(
|
||||
std::uint32_t& sequence,
|
||||
ReadView const& ledger)
|
||||
{
|
||||
txArray["Sequence"] = json::UInt(sequence++);
|
||||
txArray["Account"] = toBase58(accountID);
|
||||
txArray[jss::Sequence] = json::UInt(sequence++);
|
||||
txArray[jss::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["Fee"] = scaleFeeLoad(fees.base, context.app.getFeeTrack(), fees, false).jsonClipped();
|
||||
txArray[jss::Fee] =
|
||||
scaleFeeLoad(fees.base, context.app.getFeeTrack(), fees, false).jsonClipped();
|
||||
}
|
||||
|
||||
// {
|
||||
@@ -53,24 +56,33 @@ doNoRippleCheck(rpc::JsonContext& context)
|
||||
{
|
||||
auto const& params(context.params);
|
||||
if (!params.isMember(jss::account))
|
||||
return rpc::missingFieldError("account");
|
||||
|
||||
if (!params.isMember("role"))
|
||||
return rpc::missingFieldError("role");
|
||||
return rpc::missingFieldError(jss::account);
|
||||
|
||||
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;
|
||||
{
|
||||
std::string const role = params["role"].asString();
|
||||
if (role == "gateway")
|
||||
if (!params[jss::role].isString())
|
||||
return rpc::expectedFieldError(jss::role, "string");
|
||||
std::string const role = params[jss::role].asString();
|
||||
if (role == jss::gateway)
|
||||
{
|
||||
roleGateway = true;
|
||||
}
|
||||
else if (role != "user")
|
||||
else if (role != jss::user)
|
||||
{
|
||||
return rpc::invalidFieldError("role");
|
||||
return rpc::invalidFieldError(jss::role);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,61 +90,49 @@ doNoRippleCheck(rpc::JsonContext& context)
|
||||
if (auto err = readLimitField(limit, rpc::tuning::kNoRippleCheck, context))
|
||||
return *err;
|
||||
|
||||
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
|
||||
// API v1 silently accepts any string as `transactions`; v2+ enforces bool.
|
||||
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["problems"] = json::ValueType::Array);
|
||||
json::Value& problems = (result[jss::problems] = json::ValueType::Array);
|
||||
|
||||
bool const bDefaultRipple = sle->isFlag(lsfDefaultRipple);
|
||||
bool const defaultRipple = sle->isFlag(lsfDefaultRipple);
|
||||
|
||||
if (bDefaultRipple && !roleGateway)
|
||||
json::Value jvTransactions = json::ValueType::Array;
|
||||
|
||||
if (defaultRipple && !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 && !bDefaultRipple)
|
||||
else if (roleGateway && !defaultRipple)
|
||||
{
|
||||
problems.append("You should immediately set your default ripple flag");
|
||||
if (transactions)
|
||||
{
|
||||
json::Value& tx = jvTransactions.append(json::ValueType::Object);
|
||||
tx["TransactionType"] = jss::AccountSet;
|
||||
tx["SetFlag"] = 8;
|
||||
tx[jss::TransactionType] = jss::AccountSet;
|
||||
tx[jss::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 bLow = accountID == ownedItem->getFieldAmount(sfLowLimit).getIssuer();
|
||||
bool const low = accountID == ownedItem->getFieldAmount(sfLowLimit).getIssuer();
|
||||
|
||||
bool const bNoRipple = ownedItem->isFlag(bLow ? lsfLowNoRipple : lsfHighNoRipple);
|
||||
bool const noRipple = ownedItem->isFlag(low ? lsfLowNoRipple : lsfHighNoRipple);
|
||||
|
||||
std::string problem;
|
||||
bool needFix = false;
|
||||
if (bNoRipple && roleGateway)
|
||||
if (noRipple && roleGateway)
|
||||
{
|
||||
problem = "You should clear the no ripple flag on your ";
|
||||
needFix = true;
|
||||
}
|
||||
else if (!roleGateway && !bNoRipple)
|
||||
else if (!roleGateway && !noRipple)
|
||||
{
|
||||
problem = "You should probably set the no ripple flag on your ";
|
||||
needFix = true;
|
||||
@@ -159,22 +159,25 @@ doNoRippleCheck(rpc::JsonContext& context)
|
||||
if (needFix)
|
||||
{
|
||||
AccountID const peer =
|
||||
ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit).getIssuer();
|
||||
ownedItem->getFieldAmount(low ? sfHighLimit : sfLowLimit).getIssuer();
|
||||
STAmount const peerLimit =
|
||||
ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit);
|
||||
ownedItem->getFieldAmount(low ? sfHighLimit : sfLowLimit);
|
||||
problem += to_string(peerLimit.get<Issue>().currency);
|
||||
problem += " line to ";
|
||||
problem += to_string(peerLimit.getIssuer());
|
||||
problems.append(problem);
|
||||
|
||||
STAmount limitAmount(ownedItem->getFieldAmount(bLow ? sfLowLimit : sfHighLimit));
|
||||
STAmount limitAmount(ownedItem->getFieldAmount(low ? sfLowLimit : sfHighLimit));
|
||||
limitAmount.get<Issue>().account = peer;
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -182,6 +185,8 @@ doNoRippleCheck(rpc::JsonContext& context)
|
||||
return false;
|
||||
});
|
||||
|
||||
if (transactions)
|
||||
result[jss::transactions] = std::move(jvTransactions);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user