mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-23 03:57:09 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
930def8040 | ||
|
|
6558ce3d22 | ||
|
|
4b2cd3637d | ||
|
|
7f0190949a | ||
|
|
73d2ed51c4 | ||
|
|
5a76baabb3 | ||
|
|
373952a62c | ||
|
|
739ef8639a | ||
|
|
dc8973053e | ||
|
|
d06a03baa6 | ||
|
|
050dbc628f | ||
|
|
261b5201e0 | ||
|
|
890b2cfa17 | ||
|
|
f03f890511 | ||
|
|
553a36ac6f | ||
|
|
e6d849dd01 | ||
|
|
a1ac0a31de | ||
|
|
1368a1cdf1 | ||
|
|
91724750fb | ||
|
|
8febef6777 | ||
|
|
3bc338211a | ||
|
|
1b05dba80b | ||
|
|
3595ad0928 | ||
|
|
0af23e1466 | ||
|
|
cfcf1d30e2 | ||
|
|
6f8618b2f2 | ||
|
|
361f3469b8 |
No files matched your search
@@ -112,7 +112,6 @@ test.jtx > xrpl.config
|
||||
test.jtx > xrpl.core
|
||||
test.jtx > xrpld.app
|
||||
test.jtx > xrpld.core
|
||||
test.jtx > xrpld.overlay
|
||||
test.jtx > xrpld.rpc
|
||||
test.jtx > xrpl.json
|
||||
test.jtx > xrpl.ledger
|
||||
|
||||
@@ -62,7 +62,7 @@ ${SED_COMMAND} -i 's@ripple/@xrpld/@g' src/test/core/Config_test.cpp
|
||||
${SED_COMMAND} -i 's/Rippled/File/g' src/test/core/Config_test.cpp
|
||||
|
||||
# Restore the old config file name in the code that maintains support for now.
|
||||
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/g' src/xrpld/core/Config.h
|
||||
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/g' src/xrpld/core/detail/Config.cpp
|
||||
|
||||
# Restore an URL.
|
||||
${SED_COMMAND} -i 's/connect-your-xrpld-to-the-xrp-test-net.html/connect-your-rippled-to-the-xrp-test-net.html/g' cfg/xrpld-example.cfg
|
||||
|
||||
@@ -90,7 +90,7 @@ ${SED_COMMAND} -i 's/www.ripple.com/www.xrpl.org/g' src/test/protocol/Seed_test.
|
||||
# Restore specific changes.
|
||||
${SED_COMMAND} -i 's@b5efcc/src/xrpld@b5efcc/src/ripple@' include/xrpl/protocol/README.md
|
||||
${SED_COMMAND} -i 's/dbPrefix_ = "xrpldb"/dbPrefix_ = "rippledb"/' src/xrpld/app/misc/SHAMapStoreImp.h # cspell: disable-line
|
||||
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/' src/xrpld/core/Config.h
|
||||
${SED_COMMAND} -i 's/kConfigLegacyName = "xrpld.cfg"/kConfigLegacyName = "rippled.cfg"/' src/xrpld/core/detail/Config.cpp
|
||||
|
||||
popd
|
||||
echo "Renaming complete."
|
||||
@@ -53,11 +53,6 @@ repos:
|
||||
entry: ./bin/pre-commit/check_doxygen_style.py
|
||||
language: python
|
||||
types_or: [c++, c]
|
||||
- id: fix-gtest-names
|
||||
name: "fix gtest names: CamelCase suite, snake_case test case"
|
||||
entry: ./bin/pre-commit/fix_gtest_names.py
|
||||
language: python
|
||||
types_or: [c++, c]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: f4d7745e17a28aad7eed2f4874ca8d1568c11c4c # frozen: v22.1.8
|
||||
|
||||
@@ -42,6 +42,7 @@ Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta
|
||||
- `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)).
|
||||
- `submit`: Augmented response fields (`accepted`, `applied`, `broadcast`, `queued`, `kept`, `account_sequence_next`, `account_sequence_available`, `open_ledger_cost`, `validated_ledger_index`) are now included in sign-and-submit mode. Previously, these fields were only returned when submitting a binary transaction blob. ([#6304](https://github.com/XRPLF/rippled/pull/6304))
|
||||
|
||||
## XRP Ledger server version 3.3.0
|
||||
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Rewrites gtest names to the required style in this project: the suite name is
|
||||
CamelCase, the test-case name is snake_case.
|
||||
|
||||
TEST(SuiteName, test_case_name)
|
||||
|
||||
The gtest `DISABLED_` prefix is kept verbatim on either name.
|
||||
|
||||
Both conversions fold acronyms the way a reader expects:
|
||||
`SetAndResetAccountTxnID` -> `set_and_reset_account_txn_id`, not
|
||||
`set_and_reset_account_txn_i_d`.
|
||||
|
||||
The first argument of `TEST_F`, `TEST_P`, `TYPED_TEST` and `TYPED_TEST_P` is a
|
||||
fixture class rather than a free identifier, so rewriting it here would leave
|
||||
the class it names behind. Those are reported for a human to rename (clang-tidy
|
||||
checks the class declaration itself, via readability-identifier-naming).
|
||||
|
||||
Usage: ./bin/pre-commit/fix_gtest_names.py <file1> <file2> ...
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
# A test-case definition, `MACRO(SuiteOrFixture, TestName)`, anchored at the
|
||||
# start of a line so that commented-out definitions and project macros that
|
||||
# merely look similar (`TEST_EXPECT(...)`) are left alone. The `\s*` between
|
||||
# arguments allows for a definition clang-format wrapped over several lines.
|
||||
PATTERN = re.compile(
|
||||
r"(?P<head>^[ \t]*(?P<macro>TYPED_TEST_P|TYPED_TEST|TEST_F|TEST_P|TEST)\s*\(\s*)"
|
||||
r"(?P<suite>\w+)(?P<mid>\s*,\s*)(?P<name>\w+)(?P<tail>\s*\))",
|
||||
re.MULTILINE,
|
||||
)
|
||||
|
||||
# The macros whose first argument names a fixture class, not a free identifier.
|
||||
FIXTURE_MACROS = ("TEST_F", "TEST_P", "TYPED_TEST", "TYPED_TEST_P")
|
||||
|
||||
DISABLED = "DISABLED_"
|
||||
|
||||
ACRONYM_BOUNDARY = re.compile(r"([A-Z]+)([A-Z][a-z])")
|
||||
WORD_BOUNDARY = re.compile(r"([a-z\d])([A-Z])")
|
||||
|
||||
|
||||
def _split_disabled(name: str) -> tuple[str, str]:
|
||||
"""Splits off gtest's `DISABLED_` prefix, which is kept verbatim."""
|
||||
if name.startswith(DISABLED):
|
||||
return DISABLED, name[len(DISABLED) :]
|
||||
return "", name
|
||||
|
||||
|
||||
def snake_case(name: str) -> str:
|
||||
"""Returns the name in snake_case, leaving acronyms whole.
|
||||
|
||||
`SetAndResetAccountTxnID` -> `set_and_reset_account_txn_id`,
|
||||
`parseStatRSSkB` -> `parse_stat_rs_sk_b`.
|
||||
"""
|
||||
prefix, core = _split_disabled(name)
|
||||
core = ACRONYM_BOUNDARY.sub(r"\1_\2", core)
|
||||
return prefix + WORD_BOUNDARY.sub(r"\1_\2", core).lower()
|
||||
|
||||
|
||||
def camel_case(name: str) -> str:
|
||||
"""Returns the name in CamelCase, capitalizing each underscored word.
|
||||
|
||||
Only the letters that have to change are touched, so acronyms survive: a
|
||||
conversion that went via snake_case would turn `SHAMapTest` into
|
||||
`ShaMapTest`, whereas here it is already CamelCase and stays put.
|
||||
`json_value` -> `JsonValue`, `parseStatRSSkB` -> `ParseStatRSSkB`.
|
||||
"""
|
||||
prefix, core = _split_disabled(name)
|
||||
return prefix + "".join(w[:1].upper() + w[1:] for w in core.split("_") if w)
|
||||
|
||||
|
||||
def _corrected(match: re.Match) -> tuple[str, str]:
|
||||
"""Returns the suite and test-case names this definition should end up with."""
|
||||
suite = match["suite"]
|
||||
return (
|
||||
suite if match["macro"] in FIXTURE_MACROS else camel_case(suite),
|
||||
snake_case(match["name"]),
|
||||
)
|
||||
|
||||
|
||||
def fix_source(text: str) -> tuple[str, list[str]]:
|
||||
"""Returns the corrected text and one `line: message` report per bad name."""
|
||||
# gtest joins the suite and test names into one class name, so two test
|
||||
# cases whose joined names agree cannot coexist: `TEST(a, b_c)` and
|
||||
# `TEST(a_b, c)` both define `a_b_c_Test`. A rename that would introduce
|
||||
# such a clash is reported for a human instead of applied.
|
||||
joined = Counter("_".join(_corrected(m)) for m in PATTERN.finditer(text))
|
||||
reports = []
|
||||
|
||||
def rewrite(match: re.Match) -> str:
|
||||
suite, name = match["suite"], match["name"]
|
||||
new_suite, new_name = _corrected(match)
|
||||
line = text.count("\n", 0, match.start()) + 1
|
||||
|
||||
if match["macro"] in FIXTURE_MACROS and camel_case(suite) != suite:
|
||||
reports.append(
|
||||
f"{line}: fixture '{suite}' is not CamelCase: rename the class "
|
||||
f"to '{camel_case(suite)}' by hand"
|
||||
)
|
||||
if (new_suite, new_name) == (suite, name):
|
||||
return match[0]
|
||||
if joined[f"{new_suite}_{new_name}"] > 1:
|
||||
reports.append(
|
||||
f"{line}: cannot rename '{suite}, {name}' to '{new_suite}, "
|
||||
f"{new_name}': another test case already generates that name"
|
||||
)
|
||||
return match[0]
|
||||
if new_suite != suite:
|
||||
reports.append(f"{line}: renamed suite '{suite}' to '{new_suite}'")
|
||||
if new_name != name:
|
||||
reports.append(f"{line}: renamed test case '{name}' to '{new_name}'")
|
||||
return match["head"] + new_suite + match["mid"] + new_name + match["tail"]
|
||||
|
||||
return PATTERN.sub(rewrite, text), reports
|
||||
|
||||
|
||||
def fix_names(path: Path) -> bool:
|
||||
"""Corrects one file's gtest names, reporting each on stdout."""
|
||||
original = path.read_text(encoding="utf-8")
|
||||
fixed, reports = fix_source(original)
|
||||
for report in reports:
|
||||
print(f"{path}:{report}")
|
||||
if fixed != original:
|
||||
path.write_text(fixed, encoding="utf-8")
|
||||
return not reports
|
||||
|
||||
|
||||
def main() -> int:
|
||||
files = [Path(f) for f in sys.argv[1:]]
|
||||
success = True
|
||||
|
||||
for path in files:
|
||||
success &= fix_names(path)
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,259 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for fix_gtest_names.py.
|
||||
|
||||
Run directly (no test framework needed):
|
||||
./bin/pre-commit/test_fix_gtest_names.py
|
||||
or under pytest:
|
||||
pytest bin/pre-commit/test_fix_gtest_names.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from fix_gtest_names import camel_case, fix_source, snake_case
|
||||
|
||||
|
||||
def dedent(text: str) -> str:
|
||||
"""Removes a fixture's common indentation and its leading newline.
|
||||
|
||||
Lets fixtures be written as indented triple-quoted here-docs while keeping
|
||||
honest 1-based line numbers.
|
||||
"""
|
||||
return textwrap.dedent(text).lstrip("\n")
|
||||
|
||||
|
||||
def fixed(text: str) -> str:
|
||||
return fix_source(dedent(text))[0]
|
||||
|
||||
|
||||
def reports(text: str) -> list[str]:
|
||||
return fix_source(dedent(text))[1]
|
||||
|
||||
|
||||
# --- conversion --------------------------------------------------------------
|
||||
|
||||
|
||||
def test_snake_case_conversion() -> None:
|
||||
assert snake_case("BadInputs") == "bad_inputs"
|
||||
assert snake_case("mulDiv") == "mul_div"
|
||||
assert snake_case("already_snake") == "already_snake"
|
||||
assert snake_case("base64") == "base64"
|
||||
|
||||
|
||||
def test_snake_case_keeps_acronyms_whole() -> None:
|
||||
assert snake_case("SetAndResetAccountTxnID") == "set_and_reset_account_txn_id"
|
||||
assert snake_case("XRPToIOU") == "xrp_to_iou"
|
||||
assert snake_case("STAmountMath") == "st_amount_math"
|
||||
|
||||
|
||||
def test_camel_case_conversion() -> None:
|
||||
assert camel_case("json_value") == "JsonValue"
|
||||
assert camel_case("mulDiv") == "MulDiv"
|
||||
assert camel_case("scope") == "Scope"
|
||||
assert camel_case("base64") == "Base64"
|
||||
|
||||
|
||||
def test_camel_case_leaves_acronyms_alone() -> None:
|
||||
# A snake_case round-trip would give `ShaMapTest` / `ParseStatmRsSkB` here.
|
||||
assert camel_case("SHAMapTest") == "SHAMapTest"
|
||||
assert camel_case("parseStatmRSSkB") == "ParseStatmRSSkB"
|
||||
assert camel_case("XRPAmount") == "XRPAmount"
|
||||
assert camel_case("CSPRNG") == "CSPRNG"
|
||||
|
||||
|
||||
def test_disabled_prefix_preserved() -> None:
|
||||
assert snake_case("DISABLED_FooBar") == "DISABLED_foo_bar"
|
||||
assert snake_case("DISABLED_foo_bar") == "DISABLED_foo_bar"
|
||||
assert snake_case("DISABLED_") == "DISABLED_"
|
||||
assert camel_case("DISABLED_foo_bar") == "DISABLED_FooBar"
|
||||
assert camel_case("DISABLED_") == "DISABLED_"
|
||||
|
||||
|
||||
# --- what counts as a test definition ---------------------------------------
|
||||
|
||||
|
||||
def test_all_macros_recognized() -> None:
|
||||
code = """
|
||||
TEST(Suite, oneName)
|
||||
TEST_F(Fixture, twoName)
|
||||
TEST_P(Fixture, threeName)
|
||||
TYPED_TEST(Fixture, fourName)
|
||||
TYPED_TEST_P(Fixture, fiveName)
|
||||
"""
|
||||
assert fixed(code) == dedent("""
|
||||
TEST(Suite, one_name)
|
||||
TEST_F(Fixture, two_name)
|
||||
TEST_P(Fixture, three_name)
|
||||
TYPED_TEST(Fixture, four_name)
|
||||
TYPED_TEST_P(Fixture, five_name)
|
||||
""")
|
||||
|
||||
|
||||
def test_conforming_definitions_untouched() -> None:
|
||||
code = """
|
||||
TEST(AccountSet, bad_inputs)
|
||||
TEST_F(MutexMakeTest, default_constructor)
|
||||
TEST(SHAMap, DISABLED_slow_path)
|
||||
"""
|
||||
assert reports(code) == []
|
||||
assert fixed(code) == dedent(code)
|
||||
|
||||
|
||||
def test_lookalikes_ignored() -> None:
|
||||
code = """
|
||||
// TEST(Suite, notATest)
|
||||
TEST_EXPECT(someCall())
|
||||
TEST_EXPECTS(amount == value, amount.getText())
|
||||
INSTANTIATE_TEST_SUITE_P(Prefix, Fixture, testValues());
|
||||
auto x = TEST(Suite, notATest);
|
||||
TYPED_TEST_SUITE(Fixture, MyTypes);
|
||||
"""
|
||||
assert reports(code) == []
|
||||
assert fixed(code) == dedent(code)
|
||||
|
||||
|
||||
def test_indented_and_wrapped_definitions() -> None:
|
||||
code = """
|
||||
namespace ripple {
|
||||
TEST(Suite, indentedName)
|
||||
}
|
||||
TEST_F(
|
||||
SomeVeryLongFixtureName,
|
||||
wrappedName)
|
||||
"""
|
||||
assert fixed(code) == dedent("""
|
||||
namespace ripple {
|
||||
TEST(Suite, indented_name)
|
||||
}
|
||||
TEST_F(
|
||||
SomeVeryLongFixtureName,
|
||||
wrapped_name)
|
||||
""")
|
||||
|
||||
|
||||
# --- rewriting --------------------------------------------------------------
|
||||
|
||||
|
||||
def test_only_the_two_names_are_rewritten() -> None:
|
||||
code = """
|
||||
TEST(mulDiv, mulDiv)
|
||||
{
|
||||
auto const mulDiv = 1; // mulDiv stays
|
||||
}
|
||||
"""
|
||||
assert fixed(code) == dedent("""
|
||||
TEST(MulDiv, mul_div)
|
||||
{
|
||||
auto const mulDiv = 1; // mulDiv stays
|
||||
}
|
||||
""")
|
||||
|
||||
|
||||
def test_suite_name_camel_cased() -> None:
|
||||
code = """
|
||||
TEST(json_value, limits)
|
||||
TEST(scope, ScopeExit)
|
||||
"""
|
||||
assert reports(code) == [
|
||||
"1: renamed suite 'json_value' to 'JsonValue'",
|
||||
"2: renamed suite 'scope' to 'Scope'",
|
||||
"2: renamed test case 'ScopeExit' to 'scope_exit'",
|
||||
]
|
||||
assert fixed(code) == dedent("""
|
||||
TEST(JsonValue, limits)
|
||||
TEST(Scope, scope_exit)
|
||||
""")
|
||||
|
||||
|
||||
def test_fixture_reported_but_not_renamed() -> None:
|
||||
# The first argument names a class, so only a human (or clang-tidy) can
|
||||
# rename it; the test-case name is still fixed.
|
||||
code = """
|
||||
TEST_F(my_fixture, someTest)
|
||||
"""
|
||||
assert reports(code) == [
|
||||
"1: fixture 'my_fixture' is not CamelCase: rename the class to "
|
||||
"'MyFixture' by hand",
|
||||
"1: renamed test case 'someTest' to 'some_test'",
|
||||
]
|
||||
assert fixed(code) == dedent("""
|
||||
TEST_F(my_fixture, some_test)
|
||||
""")
|
||||
|
||||
|
||||
def test_reports_carry_line_numbers() -> None:
|
||||
code = """
|
||||
#include <foo.h>
|
||||
|
||||
TEST(Suite, firstName)
|
||||
|
||||
TEST(Suite, secondName)
|
||||
"""
|
||||
assert reports(code) == [
|
||||
"3: renamed test case 'firstName' to 'first_name'",
|
||||
"5: renamed test case 'secondName' to 'second_name'",
|
||||
]
|
||||
|
||||
|
||||
# --- collisions -------------------------------------------------------------
|
||||
|
||||
|
||||
def test_collision_reported_and_not_applied() -> None:
|
||||
# Both would define `Suite_mul_div_Test`.
|
||||
code = """
|
||||
TEST(Suite, mulDiv)
|
||||
TEST(Suite, mul_div)
|
||||
"""
|
||||
assert reports(code) == [
|
||||
"1: cannot rename 'Suite, mulDiv' to 'Suite, mul_div': another test "
|
||||
"case already generates that name"
|
||||
]
|
||||
assert fixed(code) == dedent(code)
|
||||
|
||||
|
||||
def test_collision_between_converging_suites() -> None:
|
||||
# Both suites camel-case to `SuiteA`, so both would define
|
||||
# `SuiteA_one_test_Test`.
|
||||
code = """
|
||||
TEST(SuiteA, oneTest)
|
||||
TEST(Suite_a, one_test)
|
||||
"""
|
||||
assert [r.split(":")[1].strip() for r in reports(code)] == [
|
||||
"cannot rename 'SuiteA, oneTest' to 'SuiteA, one_test'",
|
||||
"cannot rename 'Suite_a, one_test' to 'SuiteA, one_test'",
|
||||
]
|
||||
assert fixed(code) == dedent(code)
|
||||
|
||||
|
||||
def test_same_name_in_different_suites_is_not_a_collision() -> None:
|
||||
code = """
|
||||
TEST(SuiteOne, mulDiv)
|
||||
TEST(SuiteTwo, mulDiv)
|
||||
"""
|
||||
assert fixed(code) == dedent("""
|
||||
TEST(SuiteOne, mul_div)
|
||||
TEST(SuiteTwo, mul_div)
|
||||
""")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
tests = sorted(
|
||||
(name, fn)
|
||||
for name, fn in globals().items()
|
||||
if name.startswith("test_") and callable(fn)
|
||||
)
|
||||
failed = 0
|
||||
for name, fn in tests:
|
||||
try:
|
||||
fn()
|
||||
print(f"PASS {name}")
|
||||
except AssertionError as exc:
|
||||
failed += 1
|
||||
print(f"FAIL {name}: {exc!r}")
|
||||
print(f"\n{len(tests) - failed}/{len(tests)} passed")
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -347,30 +347,6 @@ isAuditorMirrorCurrent(SLE const& issuance, SLE const& mptoken);
|
||||
[[nodiscard]] bool
|
||||
areMirrorsCurrent(SLE const& issuance, SLE const& mptoken);
|
||||
|
||||
/**
|
||||
* @brief Set the holder's issuer mirror epoch to match the issuance's current issuer key epoch.
|
||||
*
|
||||
* Call this after writing the issuer mirror ciphertext under the issuance's
|
||||
* currently registered issuer key, so that the mirror reads as current afterwards.
|
||||
*
|
||||
* @param issuance The MPTokenIssuance ledger object.
|
||||
* @param mptoken The holder's MPToken ledger entry to update.
|
||||
*/
|
||||
void
|
||||
setIssuerMirrorEpoch(SLE const& issuance, SLE& mptoken);
|
||||
|
||||
/**
|
||||
* @brief Set the holder's auditor mirror epoch to match the issuance's current auditor key epoch.
|
||||
*
|
||||
* Call this after writing the auditor mirror ciphertext under the issuance's
|
||||
* currently registered auditor key. Does nothing when the holder has no auditor mirror.
|
||||
*
|
||||
* @param issuance The MPTokenIssuance ledger object.
|
||||
* @param mptoken The holder's MPToken ledger entry to update.
|
||||
*/
|
||||
void
|
||||
setAuditorMirrorEpoch(SLE const& issuance, SLE& mptoken);
|
||||
|
||||
/**
|
||||
* @brief Set the holder's MPToken mirror epochs to match the issuance's current key epochs.
|
||||
*
|
||||
|
||||
@@ -540,11 +540,6 @@ constexpr std::size_t kEcConvertBackProofLength =
|
||||
*/
|
||||
constexpr std::size_t kEcClawbackProofLength = SECP256K1_COMPACT_CLAWBACK_PROOF_SIZE;
|
||||
|
||||
/**
|
||||
* Length of compact equality proof.
|
||||
*/
|
||||
constexpr std::size_t kEcEqualityProofLength = 128;
|
||||
|
||||
/**
|
||||
* Extra base fee multiplier charged to confidential MPT transactions.
|
||||
*/
|
||||
|
||||
@@ -1134,19 +1134,6 @@ TRANSACTION(ttSPONSORSHIP_SET, 91, SponsorshipSet,
|
||||
{sfRemainingOwnerCountDelta, SoeOptional},
|
||||
}))
|
||||
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/token/ConfidentialMPTMirrorUpdate.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, 92, ConfidentialMPTMirrorUpdate,
|
||||
({.delegable = Delegation::Delegable, .amendment = featureConfidentialMPTKeyRotation}),
|
||||
({
|
||||
{sfMPTokenIssuanceID, SoeRequired},
|
||||
{sfHolder, SoeOptional},
|
||||
{sfIssuerEncryptedAmount, SoeOptional},
|
||||
{sfAuditorEncryptedAmount, SoeOptional},
|
||||
{sfZKProof, SoeRequired},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||
|
||||
For details, see: https://xrpl.org/amendments.html
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
// This file is auto-generated. Do not edit.
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBase.h>
|
||||
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
class ConfidentialMPTMirrorUpdateBuilder;
|
||||
|
||||
/**
|
||||
* @brief Transaction: ConfidentialMPTMirrorUpdate
|
||||
*
|
||||
* Type: ttCONFIDENTIAL_MPT_MIRROR_UPDATE (92)
|
||||
* Delegable: Delegation::Delegable
|
||||
* Amendment: featureConfidentialMPTKeyRotation
|
||||
* Privileges: Privilege::NoPriv
|
||||
*
|
||||
* Immutable wrapper around STTx providing type-safe field access.
|
||||
* Use ConfidentialMPTMirrorUpdateBuilder to construct new transactions.
|
||||
*/
|
||||
class ConfidentialMPTMirrorUpdate : public TransactionBase
|
||||
{
|
||||
public:
|
||||
static constexpr xrpl::TxType txType = ttCONFIDENTIAL_MPT_MIRROR_UPDATE;
|
||||
|
||||
/**
|
||||
* @brief Construct a ConfidentialMPTMirrorUpdate transaction wrapper from an existing STTx object.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
explicit ConfidentialMPTMirrorUpdate(std::shared_ptr<STTx const> tx)
|
||||
: TransactionBase(std::move(tx))
|
||||
{
|
||||
// Verify transaction type
|
||||
if (tx_->getTxnType() != txType)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMirrorUpdate");
|
||||
}
|
||||
}
|
||||
|
||||
// Transaction-specific field getters
|
||||
|
||||
/**
|
||||
* @brief Get sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_UINT192::type::value_type
|
||||
getMPTokenIssuanceID() const
|
||||
{
|
||||
return this->tx_->at(sfMPTokenIssuanceID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfHolder (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
|
||||
getHolder() const
|
||||
{
|
||||
if (hasHolder())
|
||||
{
|
||||
return this->tx_->at(sfHolder);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfHolder is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasHolder() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfIssuerEncryptedAmount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getIssuerEncryptedAmount() const
|
||||
{
|
||||
if (hasIssuerEncryptedAmount())
|
||||
{
|
||||
return this->tx_->at(sfIssuerEncryptedAmount);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfIssuerEncryptedAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasIssuerEncryptedAmount() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfIssuerEncryptedAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAuditorEncryptedAmount (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getAuditorEncryptedAmount() const
|
||||
{
|
||||
if (hasAuditorEncryptedAmount())
|
||||
{
|
||||
return this->tx_->at(sfAuditorEncryptedAmount);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfAuditorEncryptedAmount is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasAuditorEncryptedAmount() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfAuditorEncryptedAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfZKProof (SoeRequired)
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
SF_VL::type::value_type
|
||||
getZKProof() const
|
||||
{
|
||||
return this->tx_->at(sfZKProof);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Builder for ConfidentialMPTMirrorUpdate transactions.
|
||||
*
|
||||
* Provides a fluent interface for constructing transactions with method chaining.
|
||||
* Uses STObject internally for flexible transaction construction.
|
||||
* Inherits common field setters from TransactionBuilderBase.
|
||||
*/
|
||||
class ConfidentialMPTMirrorUpdateBuilder : public TransactionBuilderBase<ConfidentialMPTMirrorUpdateBuilder>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new ConfidentialMPTMirrorUpdateBuilder with required fields.
|
||||
* @param account The account initiating the transaction.
|
||||
* @param mPTokenIssuanceID The sfMPTokenIssuanceID field value.
|
||||
* @param zKProof The sfZKProof field value.
|
||||
* @param sequence Optional sequence number for the transaction.
|
||||
* @param fee Optional fee for the transaction.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder(SF_ACCOUNT::type::value_type account,
|
||||
std::decay_t<typename SF_UINT192::type::value_type> const& mPTokenIssuanceID, std::decay_t<typename SF_VL::type::value_type> const& zKProof, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
|
||||
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
|
||||
)
|
||||
: TransactionBuilderBase<ConfidentialMPTMirrorUpdateBuilder>(ttCONFIDENTIAL_MPT_MIRROR_UPDATE, account, sequence, fee)
|
||||
{
|
||||
setMPTokenIssuanceID(mPTokenIssuanceID);
|
||||
setZKProof(zKProof);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a ConfidentialMPTMirrorUpdateBuilder from an existing STTx object.
|
||||
* @param tx The existing transaction to copy from.
|
||||
* @throws std::runtime_error if the transaction type doesn't match.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder(std::shared_ptr<STTx const> tx)
|
||||
{
|
||||
if (tx->getTxnType() != ttCONFIDENTIAL_MPT_MIRROR_UPDATE)
|
||||
{
|
||||
throw std::runtime_error("Invalid transaction type for ConfidentialMPTMirrorUpdateBuilder");
|
||||
}
|
||||
object_ = *tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transaction-specific field setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Set sfMPTokenIssuanceID (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder&
|
||||
setMPTokenIssuanceID(std::decay_t<typename SF_UINT192::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMPTokenIssuanceID] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfHolder (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder&
|
||||
setHolder(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
|
||||
{
|
||||
object_[sfHolder] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfIssuerEncryptedAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder&
|
||||
setIssuerEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfIssuerEncryptedAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAuditorEncryptedAmount (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder&
|
||||
setAuditorEncryptedAmount(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAuditorEncryptedAmount] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfZKProof (SoeRequired)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdateBuilder&
|
||||
setZKProof(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfZKProof] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the ConfidentialMPTMirrorUpdate wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @return The constructed transaction wrapper.
|
||||
*/
|
||||
ConfidentialMPTMirrorUpdate
|
||||
build(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
sign(publicKey, secretKey);
|
||||
return ConfidentialMPTMirrorUpdate{std::make_shared<STTx>(std::move(object_))};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl::transactions
|
||||
@@ -592,20 +592,6 @@ private:
|
||||
SHAMapLeafNode*
|
||||
belowHelper(NodePathStack& stack, BelowDirection direction) const;
|
||||
|
||||
/**
|
||||
* Returns the nearest item strictly past `id`, in the given direction.
|
||||
*
|
||||
* Walks back up the path to `id`. At each inner node the branches beyond the one `id` takes
|
||||
* hold the candidates, so the first non-empty one is the closest and the extreme leaf below
|
||||
* it is the answer.
|
||||
*
|
||||
* @param id The key to search from, which need not be in the map.
|
||||
* @param direction First to search upwards from `id`, Last to search downwards.
|
||||
* @return An iterator to the item found, or end() if no item lies on that side of `id`.
|
||||
*/
|
||||
ConstIterator
|
||||
boundHelper(uint256 const& id, BelowDirection direction) const;
|
||||
|
||||
// Simple descent
|
||||
// Get a child of the specified node
|
||||
SHAMapTreeNode*
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Updates the encrypted mirror balances of a Confidential MPToken.
|
||||
*
|
||||
* @details
|
||||
* This transaction updates a single holder's mirrored confidential balances
|
||||
* (`sfIssuerEncryptedBalance` and/or `sfAuditorEncryptedBalance`) with the latest
|
||||
* ElGamal public keys defined on the `MPTokenIssuance`.
|
||||
*
|
||||
* It supports both issuer and holder self-migration modes, each mode supports multiple flows:
|
||||
* - Issuer mode: Submitted by the issuer.
|
||||
* 1. Issuer Key Rotation Migration: Re-encrypts the
|
||||
* holder's `sfIssuerEncryptedBalance` under the issuer's new ElGamal public key.
|
||||
*
|
||||
* 2. Auditor Key Rotation Migration: Re-encrypts the
|
||||
* holder's `sfAuditorEncryptedBalance` under the auditor's new ElGamal public key.
|
||||
*
|
||||
* 3. Simultaneous Rotation Migration: Updates both the issuer
|
||||
* and auditor encrypted balances in a single transaction to optimize network throughput.
|
||||
*
|
||||
* 4. Auditor Late-Registration Migration: When the issuer ElGamal
|
||||
* public key is already registered on the `MPTokenIssuance` object, the issuer can
|
||||
* register an auditor key at a later time through `MPTokenIssuanceSet`. Then the issuer uses this
|
||||
* flow to set the holder's initial `sfAuditorEncryptedBalance` on `MPToken` object.
|
||||
*
|
||||
* - Holder self-migration mode: Submitted by the holder. The holder decrypts their own
|
||||
* `sfConfidentialBalanceSpending` with holder's private key to recover the balance and
|
||||
* re-encrypts it under the relevant new ElGamal public key(s). This mode is always
|
||||
* available to the holder and is not conditioned on the issuer being unable to migrate
|
||||
* them: the ledger cannot verify whether an issuer has really lost its private key. That
|
||||
* loss is only the expected motivation, since an issuer that still holds its key can
|
||||
* migrate holders itself in issuer mode.
|
||||
* @note All holder migration flows strictly require the holder's
|
||||
* `sfConfidentialBalanceInbox` to be canonically zero; the holder must run
|
||||
* `ConfidentialMPTMergeInbox` first so the spending balance reflects the
|
||||
* full balance.
|
||||
*
|
||||
* 5. Holder Issuer-Mirror Migration: Re-encrypts the holder's
|
||||
* `sfIssuerEncryptedBalance` under the issuer's new ElGamal public key.
|
||||
*
|
||||
* 6. Holder Auditor-Mirror Migration: Re-encrypts the holder's
|
||||
* `sfAuditorEncryptedBalance` under the auditor's new ElGamal public key, or
|
||||
* sets it for the first time when the auditor key was late-registered. This is the
|
||||
* holder-driven counterpart to flows 2 and 4, for when the issuer does not migrate
|
||||
* the holder itself.
|
||||
*
|
||||
* 7. Simultaneous Holder Self-Migration: Updates both the issuer and auditor
|
||||
* encrypted balances in a single transaction (both keys have rotated).
|
||||
*/
|
||||
class ConfidentialMPTMirrorUpdate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
explicit ConfidentialMPTMirrorUpdate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
finalizeInvariants(
|
||||
STTx const& tx,
|
||||
TER result,
|
||||
XRPAmount fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -434,42 +434,22 @@ areMirrorsCurrent(SLE const& issuance, SLE const& mptoken)
|
||||
return isIssuerMirrorCurrent(issuance, mptoken) && isAuditorMirrorCurrent(issuance, mptoken);
|
||||
}
|
||||
|
||||
void
|
||||
setIssuerMirrorEpoch(SLE const& issuance, SLE& mptoken)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuance.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::setIssuerMirrorEpoch : issuance MPTokenIssuance object");
|
||||
XRPL_ASSERT(
|
||||
mptoken.getType() == ltMPTOKEN, "xrpl::setIssuerMirrorEpoch : mptoken MPToken object");
|
||||
|
||||
// Unlike the auditor mirror, the issuer mirror is not optional: every
|
||||
// confidential MPToken carries one, so there is no existence check here.
|
||||
if (auto const epoch = issuance[~sfIssuerKeyEpoch].value_or(0); epoch != 0)
|
||||
mptoken[sfIssuerKeyMirrorEpoch] = epoch;
|
||||
}
|
||||
|
||||
void
|
||||
setAuditorMirrorEpoch(SLE const& issuance, SLE& mptoken)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuance.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::setAuditorMirrorEpoch : issuance MPTokenIssuance object");
|
||||
XRPL_ASSERT(
|
||||
mptoken.getType() == ltMPTOKEN, "xrpl::setAuditorMirrorEpoch : mptoken MPToken object");
|
||||
|
||||
if (!mptoken.isFieldPresent(sfAuditorEncryptedBalance))
|
||||
return;
|
||||
|
||||
if (auto const epoch = issuance[~sfAuditorKeyEpoch].value_or(0); epoch != 0)
|
||||
mptoken[sfAuditorKeyMirrorEpoch] = epoch;
|
||||
}
|
||||
|
||||
void
|
||||
setMirrorEpochs(SLE const& issuance, SLE& mptoken)
|
||||
{
|
||||
setIssuerMirrorEpoch(issuance, mptoken);
|
||||
setAuditorMirrorEpoch(issuance, 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
|
||||
|
||||
@@ -575,10 +575,8 @@ SHAMap::peekItem(uint256 const& id, SHAMapHash& hash) const
|
||||
}
|
||||
|
||||
SHAMap::ConstIterator
|
||||
SHAMap::boundHelper(uint256 const& id, BelowDirection direction) const
|
||||
SHAMap::upperBound(uint256 const& id) const
|
||||
{
|
||||
auto const searchingForward = direction == BelowDirection::First;
|
||||
|
||||
NodePathStack stack;
|
||||
walkTowardsKey(id, &stack);
|
||||
while (!stack.empty())
|
||||
@@ -586,45 +584,63 @@ SHAMap::boundHelper(uint256 const& id, BelowDirection direction) const
|
||||
auto const [node, nodeID] = stack.top();
|
||||
if (node->isLeaf())
|
||||
{
|
||||
auto const& item = safeDowncast<SHAMapLeafNode const&>(*node).peekItem();
|
||||
if (searchingForward ? (item->key() > id) : (item->key() < id))
|
||||
return ConstIterator(this, item.get(), std::move(stack));
|
||||
auto leaf = safeDowncast<SHAMapLeafNode*>(node.get());
|
||||
if (leaf->peekItem()->key() > id)
|
||||
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& inner = safeDowncast<SHAMapInnerNode&>(*node);
|
||||
auto const taken = selectBranch(nodeID, id);
|
||||
auto const remaining = searchingForward ? (kBranchFactor - 1u - taken) : taken;
|
||||
|
||||
for (auto scanned = 0u; scanned < remaining; ++scanned)
|
||||
for (auto branch = selectBranch(nodeID, id) + 1; branch < kBranchFactor; ++branch)
|
||||
{
|
||||
auto const branch =
|
||||
searchingForward ? (taken + 1u + scanned) : (taken - 1u - scanned);
|
||||
if (inner.isEmptyBranch(branch))
|
||||
continue;
|
||||
|
||||
stack.pushChild(descendThrow(inner, branch), branch);
|
||||
auto const leaf = belowHelper(stack, direction);
|
||||
if (leaf == nullptr)
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
|
||||
if (!inner.isEmptyBranch(branch))
|
||||
{
|
||||
stack.pushChild(descendThrow(inner, branch), branch);
|
||||
auto leaf = belowHelper(stack, BelowDirection::First);
|
||||
if (leaf == nullptr)
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
return end();
|
||||
}
|
||||
|
||||
SHAMap::ConstIterator
|
||||
SHAMap::upperBound(uint256 const& id) const
|
||||
{
|
||||
return boundHelper(id, BelowDirection::First);
|
||||
}
|
||||
|
||||
SHAMap::ConstIterator
|
||||
SHAMap::lowerBound(uint256 const& id) const
|
||||
{
|
||||
return boundHelper(id, BelowDirection::Last);
|
||||
NodePathStack stack;
|
||||
walkTowardsKey(id, &stack);
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto const [node, nodeID] = stack.top();
|
||||
if (node->isLeaf())
|
||||
{
|
||||
auto leaf = safeDowncast<SHAMapLeafNode*>(node.get());
|
||||
if (leaf->peekItem()->key() < id)
|
||||
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& inner = safeDowncast<SHAMapInnerNode&>(*node);
|
||||
for (auto branch = selectBranch(nodeID, id); branch > 0u;)
|
||||
{
|
||||
--branch;
|
||||
if (!inner.isEmptyBranch(branch))
|
||||
{
|
||||
stack.pushChild(descendThrow(inner, branch), branch);
|
||||
auto leaf = belowHelper(stack, BelowDirection::Last);
|
||||
if (leaf == nullptr)
|
||||
Throw<SHAMapMissingNode>(type_, id);
|
||||
return ConstIterator(this, leaf->peekItem().get(), std::move(stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
// TODO: what to return here?
|
||||
return end();
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -40,7 +40,6 @@ constexpr auto kConfidentialMptTxTypes = std::to_array<TxType>({
|
||||
ttCONFIDENTIAL_MPT_CONVERT_BACK,
|
||||
ttCONFIDENTIAL_MPT_MERGE_INBOX,
|
||||
ttCONFIDENTIAL_MPT_CLAWBACK,
|
||||
ttCONFIDENTIAL_MPT_MIRROR_UPDATE,
|
||||
});
|
||||
|
||||
// Clamp to the cap (== INT64_MAX) before the signed conversion. Invariant
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTMirrorUpdate.h>
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#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/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
ConfidentialMPTMirrorUpdate::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
// Key rotation makes sense only when featureConfidentialTransfer is enabled.
|
||||
return ctx.rules.enabled(featureConfidentialTransfer);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
ConfidentialMPTMirrorUpdate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const issuer = MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer();
|
||||
auto const holder = ctx.tx[~sfHolder];
|
||||
bool const hasHolder = holder.has_value();
|
||||
|
||||
// The rotation mode is determined by the presence of the
|
||||
// Holder field: Holder present is issuer mode, Holder absent is
|
||||
// holder self-migration.
|
||||
if (hasHolder)
|
||||
{
|
||||
// Issuer mode: account must be the issuer
|
||||
if (account != issuer)
|
||||
return temMALFORMED;
|
||||
|
||||
if (account == *holder)
|
||||
return temMALFORMED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Holder self-migration: the submitter is the holder, account must not be the issuer.
|
||||
if (account == issuer)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
// At least one ciphertext will be updated.
|
||||
bool const hasIssuerAmount = ctx.tx.isFieldPresent(sfIssuerEncryptedAmount);
|
||||
bool const hasAuditorAmount = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
if (!hasIssuerAmount && !hasAuditorAmount)
|
||||
return temMALFORMED;
|
||||
|
||||
// Check the length of the encrypted amounts. Length check is cheaper than format check so put
|
||||
// it before the format check.
|
||||
if (hasIssuerAmount && ctx.tx[sfIssuerEncryptedAmount].length() != kEcGamalEncryptedTotalLength)
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (hasAuditorAmount &&
|
||||
ctx.tx[sfAuditorEncryptedAmount].length() != kEcGamalEncryptedTotalLength)
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
// Check proof length.
|
||||
if (ctx.tx[sfZKProof].length() != kEcEqualityProofLength)
|
||||
return temMALFORMED;
|
||||
|
||||
// Check the encrypted amount formats. It is more expensive so put it at the end of preflight.
|
||||
if (hasIssuerAmount && !isValidCiphertext(ctx.tx[sfIssuerEncryptedAmount]))
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (hasAuditorAmount && !isValidCiphertext(ctx.tx[sfAuditorEncryptedAmount]))
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
ConfidentialMPTMirrorUpdate::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
return Transactor::calculateBaseFee(view, tx, kConfidentialFeeMultiplier);
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTMirrorUpdate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// Check if account exists
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
if (!ctx.view.exists(keylet::account(account)))
|
||||
return terNO_ACCOUNT; // LCOV_EXCL_LINE
|
||||
|
||||
// The issuance must exist and have confidential balances enabled with a
|
||||
// registered issuer encryption key; otherwise there is no mirror to update.
|
||||
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// The issuance must have confidential balances enabled with a registered issuer encryption key.
|
||||
if (!sleIssuance->isFlag(lsfMPTCanHoldConfidentialBalance) ||
|
||||
!sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Sanity check: preflight already enforced the issuer holder combination
|
||||
// under different rotation modes.
|
||||
auto const holder = ctx.tx[~sfHolder];
|
||||
bool const hasHolder = holder.has_value();
|
||||
auto const issuer = sleIssuance->getAccountID(sfIssuer);
|
||||
if (hasHolder ? (issuer != account) : (issuer == account))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTMirrorUpdate::preclaim : invalid issuer holder combination");
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// The holder is sfHolder in issuer mode and is sfAccount in holder mode.
|
||||
auto const holderID = hasHolder ? *holder : account;
|
||||
|
||||
// In issuer mode, the holder must exist. In holder mode, the account existence was checked
|
||||
// already.
|
||||
if (hasHolder && !ctx.view.exists(keylet::account(holderID)))
|
||||
return tecNO_TARGET;
|
||||
|
||||
// In either issuer or holder mode, check the existence of the MPToken object.
|
||||
auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, holderID));
|
||||
if (!sleMptoken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// The holder must already hold an issuer confidential balance.
|
||||
if (!sleMptoken->isFieldPresent(sfIssuerEncryptedBalance))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
bool const hasIssuerAmount = ctx.tx.isFieldPresent(sfIssuerEncryptedAmount);
|
||||
bool const hasAuditorAmount = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
|
||||
// Migrating the auditor mirror requires the issuance to have a registered
|
||||
// auditor encryption key.
|
||||
if (hasAuditorAmount && !sleIssuance->isFieldPresent(sfAuditorEncryptionKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// An issuer mirror may only be re-encrypted while it is stale, reject if it is already current.
|
||||
if (hasIssuerAmount && isIssuerMirrorCurrent(*sleIssuance, *sleMptoken))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (hasAuditorAmount)
|
||||
{
|
||||
// An issuer-mode auditor-only migration: the issuer mirror must already be up to date.
|
||||
if (hasHolder && !hasIssuerAmount && !isIssuerMirrorCurrent(*sleIssuance, *sleMptoken))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// An auditor mirror may only be re-encrypted while it is stale, reject if it is already
|
||||
// current. isAuditorMirrorCurrent reports an absent auditor mirror as stale, which is what
|
||||
// allows an auditor-only migration to create one for the first time.
|
||||
if (isAuditorMirrorCurrent(*sleIssuance, *sleMptoken))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Holder self-migration re-encrypts the mirror from the holder's own
|
||||
// spending balance, which reflects the holder's full balance only once the
|
||||
// inbox has been merged into it. Require the inbox to be canonical zero,
|
||||
// i.e. ConfidentialMPTMergeInbox has already been applied.
|
||||
if (!hasHolder)
|
||||
{
|
||||
// Sanity check: a holder that already carries an issuer mirror
|
||||
// necessarily has a holder encryption key and a spending balance
|
||||
if (!sleMptoken->isFieldPresent(sfHolderEncryptionKey) ||
|
||||
!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTMirrorUpdate::preclaim : an issuer mirror implies a holder "
|
||||
"key and spending balance");
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const expectedZeroInbox = encryptCanonicalZeroAmount(
|
||||
(*sleMptoken)[sfHolderEncryptionKey], holderID, mptIssuanceID);
|
||||
if (!expectedZeroInbox)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTMirrorUpdate::preclaim : canonical zero encryption cannot "
|
||||
"fail for an already-valid holder public key");
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
bool const inboxIsCanonicalZero = sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) &&
|
||||
Slice((*sleMptoken)[sfConfidentialBalanceInbox]) == Slice(*expectedZeroInbox);
|
||||
if (!inboxIsCanonicalZero)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTMirrorUpdate::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
|
||||
auto const sleIssuance = view().read(keylet::mptokenIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTMirrorUpdate::doApply : preclaim already validated the "
|
||||
"issuance exists");
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// The holderID is sfHolder in issuer mode and sfAccount in holder mode.
|
||||
auto const holder = ctx_.tx[~sfHolder];
|
||||
auto const holderID = holder.value_or(accountID_);
|
||||
|
||||
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, holderID));
|
||||
if (!sleMptoken)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::ConfidentialMPTMirrorUpdate::doApply : preclaim already validated the "
|
||||
"MPToken exists");
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Re-encrypt the requested mirror(s) and advance the corresponding mirror
|
||||
// epoch to match the issuance key epoch. Each mirror is stamped separately
|
||||
// because this transaction may migrate either one or both.
|
||||
if (ctx_.tx.isFieldPresent(sfIssuerEncryptedAmount))
|
||||
{
|
||||
(*sleMptoken)[sfIssuerEncryptedBalance] = ctx_.tx[sfIssuerEncryptedAmount];
|
||||
setIssuerMirrorEpoch(*sleIssuance, *sleMptoken);
|
||||
}
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfAuditorEncryptedAmount))
|
||||
{
|
||||
(*sleMptoken)[sfAuditorEncryptedBalance] = ctx_.tx[sfAuditorEncryptedAmount];
|
||||
setAuditorMirrorEpoch(*sleIssuance, *sleMptoken);
|
||||
}
|
||||
|
||||
view().update(sleMptoken);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
ConfidentialMPTMirrorUpdate::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
ConfidentialMPTMirrorUpdate::finalizeInvariants(
|
||||
STTx const&,
|
||||
TER,
|
||||
XRPAmount,
|
||||
ReadView const&,
|
||||
beast::Journal const&)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
File diff suppressed because it is too large.
Load diff
@@ -7131,18 +7131,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
mptAlice.confidentialClaw(
|
||||
{.account = alice, .holder = carol, .amt = 15, .fee = expectedFee});
|
||||
});
|
||||
|
||||
// Check fee for the mirror update transaction.
|
||||
Account const newIssuerKey("newIssuerKey");
|
||||
mptAlice.generateKeyPair(newIssuerKey);
|
||||
mptAlice.set({.account = alice, .issuerPubKey = mptAlice.getPubKey(newIssuerKey)});
|
||||
checkFee(alice, [&]() {
|
||||
mptAlice.mirrorUpdate(
|
||||
{.account = alice,
|
||||
.holder = bob,
|
||||
.issuerEncryptedAmount = getTrivialCiphertext(),
|
||||
.fee = expectedFee});
|
||||
});
|
||||
}
|
||||
|
||||
// test insufficient fee for confidential transactions
|
||||
@@ -7174,12 +7162,6 @@ class ConfidentialTransfer_test : public ConfidentialTransferTestBase
|
||||
.amt = 1,
|
||||
.fee = baseFee,
|
||||
.err = telINSUF_FEE_P});
|
||||
mptAlice.mirrorUpdate(
|
||||
{.account = alice,
|
||||
.holder = bob,
|
||||
.issuerEncryptedAmount = getTrivialCiphertext(),
|
||||
.fee = baseFee,
|
||||
.err = telINSUF_FEE_P});
|
||||
}
|
||||
|
||||
// test excessive fee for confidential transactions
|
||||
|
||||
@@ -2838,7 +2838,7 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
// DO NOT modify expectedDelegableCount unless all scenarios, including
|
||||
// edge cases, have been fully tested and verified.
|
||||
// ====================================================================
|
||||
std::size_t const expectedDelegableCount = 57;
|
||||
std::size_t const expectedDelegableCount = 56;
|
||||
|
||||
BEAST_EXPECTS(
|
||||
delegableCount == expectedDelegableCount,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/PeerStub.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/batch.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
@@ -22,6 +21,7 @@
|
||||
#include <xrpld/app/ledger/detail/LedgerReplayMsgHandler.h>
|
||||
#include <xrpld/app/ledger/detail/SkipListAcquire.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/PeerSet.h>
|
||||
#include <xrpld/overlay/detail/Handshake.h>
|
||||
@@ -29,14 +29,19 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/net/IPAddress.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/RippleLedgerHash.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
#include <xrpl/shamap/SHAMapItem.h>
|
||||
|
||||
@@ -263,33 +268,136 @@ enum class PeerFeature {
|
||||
* Simulate a network peer.
|
||||
* Depending on the configured PeerFeature,
|
||||
* it either supports the ProtocolFeature::LedgerReplay or not
|
||||
*
|
||||
* `PeerStub` supplies the rest of the `Peer` interface as no-ops.
|
||||
*/
|
||||
class TestPeer : public PeerStub
|
||||
class TestPeer : public Peer
|
||||
{
|
||||
public:
|
||||
// Arbitrary but fixed: the replay code only compares ids.
|
||||
explicit TestPeer(bool enableLedgerReplay)
|
||||
: PeerStub(1234), ledgerReplayEnabled_(enableLedgerReplay)
|
||||
TestPeer(bool enableLedgerReplay)
|
||||
: ledgerReplayEnabled_(enableLedgerReplay)
|
||||
, nodePublicKey_(derivePublicKey(KeyType::Ed25519, randomSecretKey()))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
send(std::shared_ptr<Message> const& m) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] beast::ip::Endpoint
|
||||
getRemoteAddress() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
void
|
||||
charge(resource::Charge const& fee, std::string const& context = {}) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] id_t
|
||||
id() const override
|
||||
{
|
||||
return 1234;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
cluster() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
isHighLatency() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] int
|
||||
getScore(bool) const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
[[nodiscard]] PublicKey const&
|
||||
getNodePublic() const override
|
||||
{
|
||||
return nodePublicKey_;
|
||||
}
|
||||
json::Value
|
||||
json() override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
supportsFeature(ProtocolFeature f) const override
|
||||
{
|
||||
return f == ProtocolFeature::LedgerReplay && ledgerReplayEnabled_;
|
||||
}
|
||||
|
||||
// The replay code only asks peers that already have the ledger.
|
||||
[[nodiscard]] std::optional<std::size_t>
|
||||
publisherListSequence(PublicKey const&) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
void
|
||||
setPublisherListSequence(PublicKey const&, std::size_t const) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] uint256
|
||||
getClosedLedgerHash() const override
|
||||
{
|
||||
static uint256 const kHash{};
|
||||
return kHash;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
hasLedger(uint256 const&, std::uint32_t) const override
|
||||
hasLedger(uint256 const& hash, std::uint32_t seq) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
void
|
||||
ledgerRange(std::uint32_t& minSeq, std::uint32_t& maxSeq) const override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
hasTxSet(uint256 const& hash) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void
|
||||
cycleStatus() override
|
||||
{
|
||||
}
|
||||
bool
|
||||
hasRange(std::uint32_t uMin, std::uint32_t uMax) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
compressionEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void
|
||||
sendTxQueue() override
|
||||
{
|
||||
}
|
||||
void
|
||||
addTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
void
|
||||
removeTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
txReduceRelayEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::string const&
|
||||
fingerprint() const override
|
||||
{
|
||||
return fingerprint_;
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
std::string fingerprint_;
|
||||
bool ledgerReplayEnabled_;
|
||||
PublicKey nodePublicKey_;
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
};
|
||||
|
||||
enum class PeerSetBehavior {
|
||||
|
||||
+42
-243
@@ -20,13 +20,10 @@
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <streambuf>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <typeinfo>
|
||||
@@ -125,149 +122,6 @@ backend=sqlite
|
||||
return std::format(kConfigContentsTemplate, dbPathSection, valFileSection);
|
||||
}
|
||||
|
||||
// setenv and unsetenv are POSIX, and Windows declares neither, so this guard is
|
||||
// compiled only where the test cases that use it are.
|
||||
#if BOOST_OS_LINUX || BOOST_OS_MACOS
|
||||
|
||||
/**
|
||||
* Set an environment variable and restore its previous value when done.
|
||||
*
|
||||
* The previous value is copied on construction rather than kept as the pointer
|
||||
* `getenv` returned, because POSIX allows a later `setenv` to release that
|
||||
* string. Restoring through the pointer can therefore read freed memory and
|
||||
* restore a wrong value. Restoring in the destructor also means an assertion
|
||||
* that throws no longer leaves the variable overwritten.
|
||||
*/
|
||||
class EnvVarGuard
|
||||
{
|
||||
private:
|
||||
std::string const name_;
|
||||
std::optional<std::string> const saved_;
|
||||
|
||||
/**
|
||||
* Read an environment variable into a value this guard owns.
|
||||
*
|
||||
* @param name Name of the environment variable to read.
|
||||
* @return The variable's value, or std::nullopt when it is not set.
|
||||
*/
|
||||
[[nodiscard]] static std::optional<std::string>
|
||||
read(char const* name)
|
||||
{
|
||||
if (char const* const value = std::getenv(name); value != nullptr)
|
||||
return std::string{value};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give an environment variable a value, or remove it.
|
||||
*
|
||||
* @param name Name of the environment variable to write.
|
||||
* @param value Value to set, or std::nullopt to remove the variable.
|
||||
*/
|
||||
static void
|
||||
apply(char const* name, std::optional<std::string> const& value) noexcept
|
||||
{
|
||||
// unsetenv succeeds on a variable that is not set, so the std::nullopt
|
||||
// case needs no separate check.
|
||||
if (value)
|
||||
{
|
||||
setenv(name, value->c_str(), 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
unsetenv(name);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Save the variable's current value, then write the given one.
|
||||
*
|
||||
* @param name Name of the environment variable to set.
|
||||
* @param value Value to set, or std::nullopt to remove the variable.
|
||||
*/
|
||||
EnvVarGuard(char const* name, std::optional<std::string> const& value)
|
||||
: name_(name), saved_(read(name))
|
||||
{
|
||||
apply(name_.c_str(), value);
|
||||
}
|
||||
|
||||
EnvVarGuard(EnvVarGuard const&) = delete;
|
||||
EnvVarGuard&
|
||||
operator=(EnvVarGuard const&) = delete;
|
||||
|
||||
/**
|
||||
* Restore the value the variable held before construction.
|
||||
*/
|
||||
~EnvVarGuard()
|
||||
{
|
||||
// Nothing here can throw: setenv and unsetenv are C functions, and
|
||||
// neither std::string::c_str nor std::optional::operator-> throws. The
|
||||
// noexcept on apply keeps that true, so unlike ~FileCfgGuard below
|
||||
// this destructor needs no try/catch.
|
||||
apply(name_.c_str(), saved_);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // BOOST_OS_LINUX || BOOST_OS_MACOS
|
||||
|
||||
/**
|
||||
* Send a stream's output to a buffer of our own, and restore it when done.
|
||||
*
|
||||
* Restoring in the destructor means an assertion that throws cannot leave the
|
||||
* redirect in place. A leaked redirect on std::cerr would swallow the output of
|
||||
* every later test case in the run.
|
||||
*/
|
||||
class StreamRedirectGuard
|
||||
{
|
||||
private:
|
||||
// Declaration order matters here. The constructor initializes saved_ from
|
||||
// captured_.rdbuf(), and members initialize in declaration order, so
|
||||
// captured_ must stay declared before saved_. Moving saved_ above it would
|
||||
// call rdbuf() on an unconstructed stream, which no warning reports because
|
||||
// the member initializer list is already in declaration order.
|
||||
std::ostream& stream_;
|
||||
std::ostringstream captured_;
|
||||
std::streambuf* const saved_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Send the stream's output to this guard's own buffer.
|
||||
*
|
||||
* @param stream Stream to redirect for the guard's lifetime.
|
||||
*/
|
||||
explicit StreamRedirectGuard(std::ostream& stream)
|
||||
: stream_(stream), saved_(stream.rdbuf(captured_.rdbuf()))
|
||||
{
|
||||
}
|
||||
|
||||
StreamRedirectGuard(StreamRedirectGuard const&) = delete;
|
||||
StreamRedirectGuard&
|
||||
operator=(StreamRedirectGuard const&) = delete;
|
||||
|
||||
/**
|
||||
* Read what the stream has written so far.
|
||||
*
|
||||
* @return Everything written to the stream since construction.
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
str() const
|
||||
{
|
||||
return captured_.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the stream's output back where it went before.
|
||||
*/
|
||||
~StreamRedirectGuard()
|
||||
{
|
||||
// std::ios::rdbuf(std::streambuf*) does not throw, so this destructor
|
||||
// needs no try/catch.
|
||||
stream_.rdbuf(saved_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Write an xrpld config file and remove when done.
|
||||
*/
|
||||
@@ -302,13 +156,11 @@ public:
|
||||
dataDir_ = subdir() / path(Config::kDatabaseDirName);
|
||||
|
||||
rmDataDir_ = !exists(dataDir_);
|
||||
test_.expect(
|
||||
config_.setup(
|
||||
file_.string(),
|
||||
/* bQuiet */ true,
|
||||
/* bSilent */ false,
|
||||
/* bStandalone */ false),
|
||||
"setup failed for " + file_.string());
|
||||
config_.setup(
|
||||
file_.string(),
|
||||
/* bQuiet */ true,
|
||||
/* bSilent */ false,
|
||||
/* bStandalone */ false);
|
||||
}
|
||||
|
||||
[[nodiscard]] Config const&
|
||||
@@ -475,21 +327,13 @@ port_wss_admin
|
||||
|
||||
// Load the config file from the current directory and verify it.
|
||||
Config c;
|
||||
BEAST_EXPECT(c.setup("", true, false, true));
|
||||
c.setup("", true, false, true);
|
||||
BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1);
|
||||
BEAST_EXPECT(
|
||||
c.section(Sections::kDebugLogfile).values()[0] ==
|
||||
"/Users/dummy/xrpld/config/log/debug.log");
|
||||
}
|
||||
|
||||
// A named config file that cannot be read. Naming one is an explicit
|
||||
// request for that file, so setup() must not fall back to defaults.
|
||||
{
|
||||
TempDir const td;
|
||||
Config c;
|
||||
BEAST_EXPECT(!c.setup(td.file("does_not_exist.cfg"), true, false, true));
|
||||
}
|
||||
|
||||
// Config file in HOME or XDG_CONFIG_HOME directory.
|
||||
#if BOOST_OS_LINUX || BOOST_OS_MACOS
|
||||
for (auto const& configFile : configFiles)
|
||||
@@ -506,10 +350,11 @@ port_wss_admin
|
||||
|
||||
// Set the HOME and XDG_CONFIG_HOME environment variables. The
|
||||
// HOME variable is not used when XDG_CONFIG_HOME is set, but
|
||||
// must be set. Both guards are declared after tc, so both
|
||||
// restore before tc removes the directory they point into.
|
||||
detail::EnvVarGuard const homeGuard("HOME", tc.path());
|
||||
detail::EnvVarGuard const xdgGuard("XDG_CONFIG_HOME", tc.path());
|
||||
// must be set.
|
||||
char const* h = getenv("HOME");
|
||||
setenv("HOME", tc.path().c_str(), 1);
|
||||
char const* x = getenv("XDG_CONFIG_HOME");
|
||||
setenv("XDG_CONFIG_HOME", tc.path().c_str(), 1);
|
||||
|
||||
// Create the config file in '${XDG_CONFIG_HOME}/[systemName]'.
|
||||
path p = tc.file(systemName());
|
||||
@@ -521,11 +366,15 @@ port_wss_admin
|
||||
|
||||
// Load the config file from the config directory and verify it.
|
||||
Config c;
|
||||
BEAST_EXPECT(c.setup("", true, false, true));
|
||||
c.setup("", true, false, true);
|
||||
BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1);
|
||||
BEAST_EXPECT(
|
||||
c.section(Sections::kDebugLogfile).values()[0] ==
|
||||
"/Users/dummy/xrpld/config/log/debug.log");
|
||||
|
||||
// Restore the environment variables.
|
||||
(h != nullptr) ? setenv("HOME", h, 1) : unsetenv("HOME");
|
||||
(x != nullptr) ? setenv("XDG_CONFIG_HOME", x, 1) : unsetenv("XDG_CONFIG_HOME");
|
||||
}
|
||||
|
||||
// The XDG config directory is not set: the config file must be in a
|
||||
@@ -533,11 +382,11 @@ port_wss_admin
|
||||
{
|
||||
TempDir const tc;
|
||||
|
||||
// Set only the HOME environment variable. Both guards are
|
||||
// declared after tc, so both restore before tc removes the
|
||||
// directory HOME points into.
|
||||
detail::EnvVarGuard const homeGuard("HOME", tc.path());
|
||||
detail::EnvVarGuard const xdgGuard("XDG_CONFIG_HOME", std::nullopt);
|
||||
// Set only the HOME environment variable.
|
||||
char const* h = getenv("HOME");
|
||||
setenv("HOME", tc.path().c_str(), 1);
|
||||
char const* x = getenv("XDG_CONFIG_HOME");
|
||||
unsetenv("XDG_CONFIG_HOME");
|
||||
|
||||
// Create the config file in '${HOME}/.config/[systemName]'.
|
||||
std::string s = ".config";
|
||||
@@ -553,66 +402,16 @@ port_wss_admin
|
||||
|
||||
// Load the config file from the config directory and verify it.
|
||||
Config c;
|
||||
BEAST_EXPECT(c.setup("", true, false, true));
|
||||
c.setup("", true, false, true);
|
||||
BEAST_EXPECT(c.section(Sections::kDebugLogfile).values().size() == 1);
|
||||
BEAST_EXPECT(
|
||||
c.section(Sections::kDebugLogfile).values()[0] ==
|
||||
"/Users/dummy/xrpld/config/log/debug.log");
|
||||
}
|
||||
}
|
||||
|
||||
// No config file under either name in any searched directory.
|
||||
{
|
||||
// The system config directory is the last one searched and is not
|
||||
// under the test's control, so only run this when it holds no
|
||||
// config file.
|
||||
path const systemDir = path("/etc") / systemName();
|
||||
if (exists(systemDir / std::string{Config::kConfigFileName}) ||
|
||||
exists(systemDir / std::string{Config::kConfigLegacyName}))
|
||||
{
|
||||
log << "Skipping: " << systemDir.string() << " holds a config file." << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Point the working directory and HOME at empty temporary
|
||||
// directories, so neither holds a config file. The guards are
|
||||
// declared after tc, so they restore before it is removed.
|
||||
TempDir const td;
|
||||
current_path(td.path());
|
||||
TempDir const tc;
|
||||
|
||||
detail::EnvVarGuard const homeGuard("HOME", tc.path());
|
||||
detail::EnvVarGuard const xdgGuard("XDG_CONFIG_HOME", std::nullopt);
|
||||
|
||||
// The three directories setup() is expected to search and
|
||||
// report. Read the working directory back rather than reusing
|
||||
// td.path(), because current_path() can resolve a symlink and
|
||||
// return a different spelling of the same directory.
|
||||
path const searchedCwd = current_path();
|
||||
std::string const searchedXdgDir = tc.path() + "/.config/" + systemName();
|
||||
std::string const searchedSystemDir = systemDir.string();
|
||||
|
||||
// setup() must report the failure rather than fall back to the
|
||||
// last directory it searched. Capture and restore stderr around
|
||||
// the call only, so that an assertion below cannot have its own
|
||||
// output swallowed by the capture.
|
||||
Config c;
|
||||
bool setupResult = true;
|
||||
std::string reported;
|
||||
{
|
||||
detail::StreamRedirectGuard const capture(std::cerr);
|
||||
setupResult = c.setup("", true, false, true);
|
||||
reported = capture.str();
|
||||
}
|
||||
|
||||
BEAST_EXPECT(!setupResult);
|
||||
|
||||
// Assert on the directory paths rather than on the wording of
|
||||
// the header, so that a later change to that sentence does not
|
||||
// break this.
|
||||
BEAST_EXPECT(reported.contains(searchedCwd.string()));
|
||||
BEAST_EXPECT(reported.contains(searchedXdgDir));
|
||||
BEAST_EXPECT(reported.contains(searchedSystemDir));
|
||||
// Restore the environment variables.
|
||||
(h != nullptr) ? setenv("HOME", h, 1) : unsetenv("HOME");
|
||||
if (x != nullptr)
|
||||
setenv("XDG_CONFIG_HOME", x, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1209,11 +1008,11 @@ trust-these-validators.gov
|
||||
*/
|
||||
{
|
||||
Config config;
|
||||
BEAST_EXPECT(config.setup(
|
||||
config.setup(
|
||||
cfg.configFile(),
|
||||
/*bQuiet*/ false,
|
||||
/* bSilent */ false,
|
||||
/* bStandalone */ false));
|
||||
/* bStandalone */ false);
|
||||
BEAST_EXPECT(!config.quiet());
|
||||
BEAST_EXPECT(!config.silent());
|
||||
BEAST_EXPECT(!config.standalone());
|
||||
@@ -1222,11 +1021,11 @@ trust-these-validators.gov
|
||||
}
|
||||
{
|
||||
Config config;
|
||||
BEAST_EXPECT(config.setup(
|
||||
config.setup(
|
||||
cfg.configFile(),
|
||||
/*bQuiet*/ true,
|
||||
/* bSilent */ false,
|
||||
/* bStandalone */ false));
|
||||
/* bStandalone */ false);
|
||||
BEAST_EXPECT(config.quiet());
|
||||
BEAST_EXPECT(!config.silent());
|
||||
BEAST_EXPECT(!config.standalone());
|
||||
@@ -1235,11 +1034,11 @@ trust-these-validators.gov
|
||||
}
|
||||
{
|
||||
Config config;
|
||||
BEAST_EXPECT(config.setup(
|
||||
config.setup(
|
||||
cfg.configFile(),
|
||||
/*bQuiet*/ false,
|
||||
/* bSilent */ true,
|
||||
/* bStandalone */ false));
|
||||
/* bStandalone */ false);
|
||||
BEAST_EXPECT(config.quiet());
|
||||
BEAST_EXPECT(config.silent());
|
||||
BEAST_EXPECT(!config.standalone());
|
||||
@@ -1248,11 +1047,11 @@ trust-these-validators.gov
|
||||
}
|
||||
{
|
||||
Config config;
|
||||
BEAST_EXPECT(config.setup(
|
||||
config.setup(
|
||||
cfg.configFile(),
|
||||
/*bQuiet*/ true,
|
||||
/* bSilent */ true,
|
||||
/* bStandalone */ false));
|
||||
/* bStandalone */ false);
|
||||
BEAST_EXPECT(config.quiet());
|
||||
BEAST_EXPECT(config.silent());
|
||||
BEAST_EXPECT(!config.standalone());
|
||||
@@ -1261,11 +1060,11 @@ trust-these-validators.gov
|
||||
}
|
||||
{
|
||||
Config config;
|
||||
BEAST_EXPECT(config.setup(
|
||||
config.setup(
|
||||
cfg.configFile(),
|
||||
/*bQuiet*/ false,
|
||||
/* bSilent */ false,
|
||||
/* bStandalone */ true));
|
||||
/* bStandalone */ true);
|
||||
BEAST_EXPECT(!config.quiet());
|
||||
BEAST_EXPECT(!config.silent());
|
||||
BEAST_EXPECT(config.standalone());
|
||||
@@ -1274,11 +1073,11 @@ trust-these-validators.gov
|
||||
}
|
||||
{
|
||||
Config config;
|
||||
BEAST_EXPECT(config.setup(
|
||||
config.setup(
|
||||
cfg.configFile(),
|
||||
/*bQuiet*/ true,
|
||||
/* bSilent */ false,
|
||||
/* bStandalone */ true));
|
||||
/* bStandalone */ true);
|
||||
BEAST_EXPECT(config.quiet());
|
||||
BEAST_EXPECT(!config.silent());
|
||||
BEAST_EXPECT(config.standalone());
|
||||
@@ -1287,11 +1086,11 @@ trust-these-validators.gov
|
||||
}
|
||||
{
|
||||
Config config;
|
||||
BEAST_EXPECT(config.setup(
|
||||
config.setup(
|
||||
cfg.configFile(),
|
||||
/*bQuiet*/ false,
|
||||
/* bSilent */ true,
|
||||
/* bStandalone */ true));
|
||||
/* bStandalone */ true);
|
||||
BEAST_EXPECT(config.quiet());
|
||||
BEAST_EXPECT(config.silent());
|
||||
BEAST_EXPECT(config.standalone());
|
||||
@@ -1300,11 +1099,11 @@ trust-these-validators.gov
|
||||
}
|
||||
{
|
||||
Config config;
|
||||
BEAST_EXPECT(config.setup(
|
||||
config.setup(
|
||||
cfg.configFile(),
|
||||
/*bQuiet*/ true,
|
||||
/* bSilent */ true,
|
||||
/* bStandalone */ true));
|
||||
/* bStandalone */ true);
|
||||
BEAST_EXPECT(config.quiet());
|
||||
BEAST_EXPECT(config.silent());
|
||||
BEAST_EXPECT(config.standalone());
|
||||
|
||||
+575
-113
@@ -3,19 +3,28 @@
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/vault.h>
|
||||
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
#include <utility/mpt_utility.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_mpt.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
@@ -45,66 +54,237 @@ protected:
|
||||
return *value;
|
||||
}
|
||||
|
||||
// Creates the MPT issuance on the given Env, authorizes and funds each
|
||||
// holder, generates keys for the issuer, holders and optional auditor,
|
||||
// registers the issuer/auditor keys, and converts part of each holder's
|
||||
// balance to a confidential balance.
|
||||
struct ConfidentialEnv
|
||||
// Offset where the bulletproof begins in a send proof blob.
|
||||
// Proof layout: [compact_sigma | bulletproof]
|
||||
static constexpr size_t kBulletproofOffset = kEcSendProofLength - kEcDoubleBulletproofLength;
|
||||
|
||||
// Generate a forged aggregated bulletproof (double bulletproof) for
|
||||
// the given values and blinding factors. Used to test that splicing
|
||||
// a bulletproof claiming a different remaining balance is rejected.
|
||||
// secp256k1 convention: returns 1 on success, 0 on failure.
|
||||
static Buffer
|
||||
getForgedBulletproof(
|
||||
std::array<uint64_t, 2> const& values,
|
||||
std::array<Buffer, 2> const& blindingFactors,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
// Per-holder configuration: the account, how much MPT to fund it
|
||||
// with, and how much of that to convert to a confidential balance.
|
||||
struct HolderInit
|
||||
auto* const ctx = mpt_secp256k1_context();
|
||||
|
||||
secp256k1_pubkey h;
|
||||
secp256k1_mpt_get_h_generator(ctx, &h);
|
||||
|
||||
Buffer proof(kEcDoubleBulletproofLength);
|
||||
size_t proofLen = kEcDoubleBulletproofLength;
|
||||
|
||||
unsigned char blindings[64];
|
||||
std::memcpy(blindings, blindingFactors[0].data(), 32);
|
||||
std::memcpy(blindings + 32, blindingFactors[1].data(), 32);
|
||||
|
||||
if (secp256k1_bulletproof_prove_agg(
|
||||
ctx,
|
||||
proof.data(),
|
||||
&proofLen,
|
||||
values.data(),
|
||||
blindings,
|
||||
2,
|
||||
&h,
|
||||
contextHash.data()) == 0)
|
||||
Throw<std::runtime_error>("Failed to generate forged bulletproof");
|
||||
|
||||
return proof;
|
||||
}
|
||||
|
||||
// Generate a forged single bulletproof for a single value and blinding factor.
|
||||
// Used to test ConvertBack overdraft prevention via bulletproof verification.
|
||||
static Buffer
|
||||
getForgedSingleBulletproof(
|
||||
uint64_t value,
|
||||
Buffer const& blindingFactor,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
auto* const ctx = mpt_secp256k1_context();
|
||||
|
||||
secp256k1_pubkey h;
|
||||
secp256k1_mpt_get_h_generator(ctx, &h);
|
||||
|
||||
Buffer proof(kEcSingleBulletproofLength);
|
||||
size_t proofLen = kEcSingleBulletproofLength;
|
||||
|
||||
if (secp256k1_bulletproof_prove_agg(
|
||||
ctx,
|
||||
proof.data(),
|
||||
&proofLen,
|
||||
&value,
|
||||
blindingFactor.data(),
|
||||
1, // m = 1 (single bulletproof)
|
||||
&h,
|
||||
contextHash.data()) == 0)
|
||||
Throw<std::runtime_error>("Failed to generate forged single bulletproof");
|
||||
|
||||
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)
|
||||
{
|
||||
test::jtx::Account account;
|
||||
std::uint64_t payAmount = 1000;
|
||||
std::uint64_t convertAmount = 100;
|
||||
};
|
||||
Throw<std::runtime_error>(
|
||||
"getForgedConvertBackProof: bad encryptedSpendingBalance length");
|
||||
}
|
||||
if (amt > realBalance)
|
||||
Throw<std::runtime_error>("getForgedConvertBackProof: amt exceeds realBalance");
|
||||
|
||||
test::jtx::MPTTester mpt;
|
||||
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");
|
||||
|
||||
ConfidentialEnv(
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Account const& issuer,
|
||||
std::vector<HolderInit> const& holders,
|
||||
std::uint32_t flags = tfMPTCanLock | tfMPTCanHoldConfidentialBalance | tfMPTCanTransfer,
|
||||
std::optional<test::jtx::Account> auditor = std::nullopt);
|
||||
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");
|
||||
|
||||
private:
|
||||
static std::vector<test::jtx::Account>
|
||||
extractAccounts(std::vector<HolderInit> const& holders);
|
||||
};
|
||||
secp256k1_pubkey pcB;
|
||||
if (secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcB, pedersenCommitment.data(), kCompressedEcPointLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to parse pedersen commitment");
|
||||
|
||||
// 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);
|
||||
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");
|
||||
|
||||
// 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
|
||||
// zero spending/inbox.
|
||||
static void
|
||||
setupBatchEnv(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Account const& alice,
|
||||
test::jtx::Account const& bob,
|
||||
test::jtx::Account const& carol,
|
||||
test::jtx::Account const& dave,
|
||||
std::uint64_t bobAmt,
|
||||
std::uint64_t carolAmt);
|
||||
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&
|
||||
getBadCiphertext()
|
||||
{
|
||||
static Buffer const kBadCiphertext = []() {
|
||||
Buffer buf(kEcGamalEncryptedTotalLength);
|
||||
std::memset(buf.data(), 0xFF, kEcGamalEncryptedTotalLength);
|
||||
|
||||
buf.data()[0] = kEcCompressedPrefixEvenY;
|
||||
buf.data()[kEcCiphertextComponentLength] = kEcCompressedPrefixEvenY;
|
||||
return buf;
|
||||
}();
|
||||
|
||||
return kBadCiphertext;
|
||||
}
|
||||
|
||||
// Get a trivial buffer that is structurally and mathematically valid, but
|
||||
// contains invalid data that does not match the ledger state. For preclaim
|
||||
// test purposes.
|
||||
static Buffer const&
|
||||
getTrivialCiphertext()
|
||||
{
|
||||
static Buffer const kTrivialCiphertext = []() {
|
||||
Buffer buf(kEcGamalEncryptedTotalLength);
|
||||
std::memset(buf.data(), 0, kEcGamalEncryptedTotalLength);
|
||||
|
||||
buf.data()[0] = kEcCompressedPrefixEvenY;
|
||||
buf.data()[kEcCiphertextComponentLength] = kEcCompressedPrefixEvenY;
|
||||
|
||||
buf.data()[kEcCiphertextComponentLength - 1] = 0x01;
|
||||
buf.data()[kEcGamalEncryptedTotalLength - 1] = 0x01;
|
||||
|
||||
return buf;
|
||||
}();
|
||||
|
||||
return kTrivialCiphertext;
|
||||
}
|
||||
|
||||
// Returns a valid compressed EC point (33 bytes) that can pass preflight
|
||||
// validation but contains invalid data for preclaim test purposes.
|
||||
static Buffer const&
|
||||
getTrivialCommitment()
|
||||
{
|
||||
static Buffer const kTrivialCommitment = []() {
|
||||
Buffer buf(kEcPedersenCommitmentLength);
|
||||
std::memset(buf.data(), 0, kEcPedersenCommitmentLength);
|
||||
|
||||
buf.data()[0] = kEcCompressedPrefixEvenY;
|
||||
// Set last byte to make it a valid x-coordinate on the curve
|
||||
buf.data()[kEcPedersenCommitmentLength - 1] = 0x01;
|
||||
|
||||
return buf;
|
||||
}();
|
||||
|
||||
return kTrivialCommitment;
|
||||
}
|
||||
|
||||
static std::string
|
||||
getTrivialSendProofHex()
|
||||
{
|
||||
Buffer buf(kEcSendProofLength);
|
||||
std::memset(buf.data(), 0, kEcSendProofLength);
|
||||
|
||||
for (std::size_t i = 0; i < kEcSendProofLength; i += kEcCiphertextComponentLength)
|
||||
{
|
||||
buf.data()[i] = kEcCompressedPrefixEvenY;
|
||||
if (i + kEcCiphertextComponentLength - 1 < kEcSendProofLength)
|
||||
buf.data()[i + kEcCiphertextComponentLength - 1] = 0x01;
|
||||
}
|
||||
|
||||
return strHex(buf);
|
||||
}
|
||||
|
||||
// Helper struct to encapsulate common setup for integration tests.
|
||||
struct ConfidentialSendSetup
|
||||
{
|
||||
// Constants
|
||||
uint64_t sendAmount;
|
||||
size_t nRecipients;
|
||||
uint32_t version;
|
||||
|
||||
// Blinding factors
|
||||
@@ -144,7 +324,55 @@ protected:
|
||||
test::jtx::Account const& dest,
|
||||
test::jtx::Account const& issuer,
|
||||
uint64_t amount,
|
||||
std::optional<std::reference_wrapper<test::jtx::Account const>> auditor = std::nullopt);
|
||||
std::optional<std::reference_wrapper<test::jtx::Account const>> auditor = std::nullopt)
|
||||
: sendAmount(amount)
|
||||
, nRecipients(auditor ? 4 : 3)
|
||||
, version(mpt.getMPTokenVersion(sender))
|
||||
, blindingFactor(generateBlindingFactor())
|
||||
, amountBlindingFactor(blindingFactor)
|
||||
, balanceBlindingFactor(generateBlindingFactor())
|
||||
, senderAmt(mpt.encryptAmount(sender, amount, blindingFactor))
|
||||
, destAmt(mpt.encryptAmount(dest, amount, blindingFactor))
|
||||
, issuerAmt(mpt.encryptAmount(issuer, amount, blindingFactor))
|
||||
, auditorAmt(
|
||||
auditor ? std::optional<Buffer>(
|
||||
mpt.encryptAmount(auditor->get(), amount, blindingFactor))
|
||||
: std::nullopt)
|
||||
, amountCommitment(mpt.getPedersenCommitment(amount, amountBlindingFactor))
|
||||
, senderPubKey(requireOptional(mpt.getPubKey(sender), "Missing sender public key"))
|
||||
, destPubKey(requireOptional(mpt.getPubKey(dest), "Missing destination public key"))
|
||||
, issuerPubKey(requireOptional(mpt.getPubKey(issuer), "Missing issuer public key"))
|
||||
, auditorPubKey(auditor ? mpt.getPubKey(auditor->get()) : std::nullopt)
|
||||
, prevSpending(requireOptional(
|
||||
mpt.getDecryptedBalance(sender, test::jtx::MPTTester::holderEncryptedSpending),
|
||||
"Missing sender spending balance"))
|
||||
, prevEncryptedSpending(requireOptional(
|
||||
mpt.getEncryptedBalance(sender, test::jtx::MPTTester::holderEncryptedSpending),
|
||||
"Missing sender encrypted spending balance"))
|
||||
, balanceCommitment(mpt.getPedersenCommitment(prevSpending, balanceBlindingFactor))
|
||||
{
|
||||
recipients.push_back({
|
||||
.publicKey = Slice(senderPubKey),
|
||||
.encryptedAmount = senderAmt,
|
||||
});
|
||||
recipients.push_back({
|
||||
.publicKey = Slice(destPubKey),
|
||||
.encryptedAmount = destAmt,
|
||||
});
|
||||
recipients.push_back({
|
||||
.publicKey = Slice(issuerPubKey),
|
||||
.encryptedAmount = issuerAmt,
|
||||
});
|
||||
if (auditor)
|
||||
{
|
||||
recipients.push_back({
|
||||
.publicKey =
|
||||
Slice(requireOptionalRef(auditorPubKey, "Missing auditor public key")),
|
||||
.encryptedAmount =
|
||||
requireOptionalRef(auditorAmt, "Missing auditor encrypted amount"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Generate proof with current account sequence
|
||||
std::optional<Buffer>
|
||||
@@ -152,78 +380,54 @@ protected:
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Account const& sender,
|
||||
test::jtx::Account const& dest) const;
|
||||
test::jtx::Account const& dest) const
|
||||
{
|
||||
auto const ctxHash = getSendContextHash(
|
||||
sender.id(), mpt.issuanceID(), env.seq(sender), dest.id(), version);
|
||||
|
||||
return mpt.getConfidentialSendProof(
|
||||
sender,
|
||||
sendAmount,
|
||||
recipients,
|
||||
blindingFactor,
|
||||
ctxHash,
|
||||
{
|
||||
.pedersenCommitment = amountCommitment,
|
||||
.amt = sendAmount,
|
||||
.encryptedAmt = senderAmt,
|
||||
.blindingFactor = amountBlindingFactor,
|
||||
},
|
||||
{
|
||||
.pedersenCommitment = balanceCommitment,
|
||||
.amt = prevSpending,
|
||||
.encryptedAmt = prevEncryptedSpending,
|
||||
.blindingFactor = balanceBlindingFactor,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] test::jtx::MPTConfidentialSend
|
||||
sendArgs(
|
||||
test::jtx::Account const& sender,
|
||||
test::jtx::Account const& dest,
|
||||
Buffer const& proof,
|
||||
std::optional<TER> err = std::nullopt) const;
|
||||
std::optional<TER> err = std::nullopt) const
|
||||
{
|
||||
return {
|
||||
.account = sender,
|
||||
.dest = dest,
|
||||
.amt = sendAmount,
|
||||
.proof = strHex(proof),
|
||||
.senderEncryptedAmt = senderAmt,
|
||||
.destEncryptedAmt = destAmt,
|
||||
.issuerEncryptedAmt = issuerAmt,
|
||||
.auditorEncryptedAmt = auditorAmt,
|
||||
.amountCommitment = amountCommitment,
|
||||
.balanceCommitment = balanceCommitment,
|
||||
.err = err,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Get a bad ciphertext with valid structure but cryptographic invalid for
|
||||
// testing purposes. For preflight test purposes.
|
||||
static Buffer const&
|
||||
getBadCiphertext();
|
||||
|
||||
// Get a trivial buffer that is structurally and mathematically valid, but
|
||||
// contains invalid data that does not match the ledger state. For preclaim
|
||||
// test purposes.
|
||||
static Buffer const&
|
||||
getTrivialCiphertext();
|
||||
|
||||
// Returns a valid compressed EC point (33 bytes) that can pass preflight
|
||||
// validation but contains invalid data for preclaim test purposes.
|
||||
static Buffer const&
|
||||
getTrivialCommitment();
|
||||
|
||||
// Returns a hex-encoded send proof of the correct length filled with
|
||||
// placeholder data. It passes the proof length check in preflight but
|
||||
// fails proof verification.
|
||||
static std::string
|
||||
getTrivialSendProofHex();
|
||||
|
||||
// Offset where the bulletproof begins in a send proof blob.
|
||||
// Proof layout: [compact_sigma | bulletproof]
|
||||
static constexpr size_t kBulletproofOffset = kEcSendProofLength - kEcDoubleBulletproofLength;
|
||||
|
||||
// Generate a forged aggregated bulletproof (double bulletproof) for
|
||||
// the given values and blinding factors. Used to test that splicing
|
||||
// a bulletproof claiming a different remaining balance is rejected.
|
||||
static Buffer
|
||||
getForgedBulletproof(
|
||||
std::array<uint64_t, 2> const& values,
|
||||
std::array<Buffer, 2> const& blindingFactors,
|
||||
uint256 const& contextHash);
|
||||
|
||||
// Generate a forged single bulletproof for a single value and blinding factor.
|
||||
// Used to test ConvertBack overdraft prevention via bulletproof verification.
|
||||
static Buffer
|
||||
getForgedSingleBulletproof(
|
||||
uint64_t value,
|
||||
Buffer const& blindingFactor,
|
||||
uint256 const& contextHash);
|
||||
|
||||
// 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 to the encrypted spending balance
|
||||
// already on the ledger. The bulletproof component is built from the real
|
||||
// remaining balance (realBalance - amt) so it stays honest.
|
||||
// mpt_get_convert_back_proof validates its inputs before proving, so it
|
||||
// cannot be used to build such an inconsistent proof.
|
||||
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);
|
||||
|
||||
// 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
|
||||
@@ -234,7 +438,265 @@ protected:
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Account const& sender,
|
||||
test::jtx::Account const& dest,
|
||||
ConfidentialSendSetup const& setup);
|
||||
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.
|
||||
struct ConfidentialEnv
|
||||
{
|
||||
// Per-holder configuration: the account, how much MPT to fund it
|
||||
// with, and how much of that to convert to a confidential balance.
|
||||
struct HolderInit
|
||||
{
|
||||
test::jtx::Account account;
|
||||
std::uint64_t payAmount = 1000;
|
||||
std::uint64_t convertAmount = 100;
|
||||
};
|
||||
|
||||
test::jtx::MPTTester mpt;
|
||||
|
||||
ConfidentialEnv(
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Account const& issuer,
|
||||
std::vector<HolderInit> const& holders,
|
||||
std::uint32_t flags = tfMPTCanLock | tfMPTCanHoldConfidentialBalance | tfMPTCanTransfer,
|
||||
std::optional<test::jtx::Account> auditor = std::nullopt)
|
||||
: mpt{env, issuer, {.holders = extractAccounts(holders), .auditor = auditor}}
|
||||
{
|
||||
mpt.create({.ownerCount = 1, .flags = flags});
|
||||
|
||||
for (auto const& h : holders)
|
||||
{
|
||||
mpt.authorize({.account = h.account});
|
||||
if ((flags & tfMPTRequireAuth) != 0)
|
||||
mpt.authorize({.account = issuer, .holder = h.account});
|
||||
mpt.pay(issuer, h.account, h.payAmount);
|
||||
}
|
||||
|
||||
mpt.generateKeyPair(issuer);
|
||||
for (auto const& h : holders)
|
||||
mpt.generateKeyPair(h.account);
|
||||
if (auditor)
|
||||
mpt.generateKeyPair(requireOptionalRef(auditor, "Missing auditor"));
|
||||
|
||||
mpt.set({
|
||||
.account = issuer,
|
||||
.issuerPubKey = mpt.getPubKey(issuer),
|
||||
.auditorPubKey = auditor
|
||||
? mpt.getPubKey(requireOptionalRef(auditor, "Missing auditor"))
|
||||
: std::optional<Buffer>{},
|
||||
});
|
||||
|
||||
for (auto const& h : holders)
|
||||
{
|
||||
mpt.convert({
|
||||
.account = h.account,
|
||||
.amt = h.convertAmount,
|
||||
.holderPubKey = mpt.getPubKey(h.account),
|
||||
});
|
||||
mpt.mergeInbox({.account = h.account});
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static std::vector<test::jtx::Account>
|
||||
extractAccounts(std::vector<HolderInit> const& holders)
|
||||
{
|
||||
std::vector<test::jtx::Account> accounts;
|
||||
accounts.reserve(holders.size());
|
||||
for (auto const& h : holders)
|
||||
accounts.push_back(h.account);
|
||||
return accounts;
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
// zero spending/inbox.
|
||||
static void
|
||||
setupBatchEnv(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Account const& alice,
|
||||
test::jtx::Account const& bob,
|
||||
test::jtx::Account const& carol,
|
||||
test::jtx::Account const& dave,
|
||||
std::uint64_t bobAmt,
|
||||
std::uint64_t carolAmt)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
mpt.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanHoldConfidentialBalance,
|
||||
});
|
||||
mpt.authorize({.account = bob});
|
||||
mpt.authorize({.account = carol});
|
||||
mpt.authorize({.account = dave});
|
||||
|
||||
if (bobAmt > 0)
|
||||
mpt.pay(alice, bob, bobAmt);
|
||||
if (carolAmt > 0)
|
||||
mpt.pay(alice, carol, carolAmt);
|
||||
|
||||
mpt.generateKeyPair(alice);
|
||||
mpt.generateKeyPair(bob);
|
||||
mpt.generateKeyPair(carol);
|
||||
mpt.generateKeyPair(dave);
|
||||
|
||||
mpt.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mpt.getPubKey(alice),
|
||||
});
|
||||
|
||||
if (bobAmt > 0)
|
||||
{
|
||||
mpt.convert({
|
||||
.account = bob,
|
||||
.amt = bobAmt,
|
||||
.holderPubKey = mpt.getPubKey(bob),
|
||||
});
|
||||
mpt.mergeInbox({.account = bob});
|
||||
}
|
||||
else
|
||||
{
|
||||
mpt.convert({
|
||||
.account = bob,
|
||||
.amt = 0,
|
||||
.holderPubKey = mpt.getPubKey(bob),
|
||||
});
|
||||
}
|
||||
|
||||
if (carolAmt > 0)
|
||||
{
|
||||
mpt.convert({
|
||||
.account = carol,
|
||||
.amt = carolAmt,
|
||||
.holderPubKey = mpt.getPubKey(carol),
|
||||
});
|
||||
mpt.mergeInbox({.account = carol});
|
||||
}
|
||||
else
|
||||
{
|
||||
mpt.convert({
|
||||
.account = carol,
|
||||
.amt = 0,
|
||||
.holderPubKey = mpt.getPubKey(carol),
|
||||
});
|
||||
}
|
||||
|
||||
// dave: register pubkey only (0 spending/inbox)
|
||||
mpt.convert({
|
||||
.account = dave,
|
||||
.amt = 0,
|
||||
.holderPubKey = mpt.getPubKey(dave),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,185 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
/**
|
||||
* A `Peer` whose every method is a no-op returning a default.
|
||||
*
|
||||
* Derive from this and override only the methods a test cares about. Adding a
|
||||
* method to `Peer` then costs one stub here, not one per double.
|
||||
*
|
||||
* The id and the node public key are real, because the code under test routes
|
||||
* and deduplicates on both.
|
||||
*/
|
||||
class PeerStub : public Peer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param id The connection id reported by `id()`.
|
||||
*/
|
||||
explicit PeerStub(id_t id = 0)
|
||||
: id_(id), nodePublicKey_(derivePublicKey(KeyType::Ed25519, randomSecretKey()))
|
||||
{
|
||||
}
|
||||
|
||||
~PeerStub() override = default;
|
||||
|
||||
void
|
||||
send(std::shared_ptr<Message> const&) override
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] beast::ip::Endpoint
|
||||
getRemoteAddress() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
sendTxQueue() override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
addTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
removeTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
charge(resource::Charge const&, std::string const&) override
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] id_t
|
||||
id() const override
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
cluster() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isHighLatency() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] int
|
||||
getScore(bool) const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] PublicKey const&
|
||||
getNodePublic() const override
|
||||
{
|
||||
return nodePublicKey_;
|
||||
}
|
||||
|
||||
json::Value
|
||||
json() override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
supportsFeature(ProtocolFeature) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::size_t>
|
||||
publisherListSequence(PublicKey const&) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
setPublisherListSequence(PublicKey const&, std::size_t const) override
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string const&
|
||||
fingerprint() const override
|
||||
{
|
||||
return fingerprint_;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint256
|
||||
getClosedLedgerHash() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasLedger(uint256 const&, std::uint32_t) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
ledgerRange(std::uint32_t&, std::uint32_t&) const override
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasTxSet(uint256 const&) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
cycleStatus() override
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
hasRange(std::uint32_t, std::uint32_t) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
compressionEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
txReduceRelayEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
id_t const id_;
|
||||
PublicKey const nodePublicKey_;
|
||||
std::string const fingerprint_;
|
||||
};
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1,89 +1,13 @@
|
||||
#include <test/jtx/ConfidentialTransfer.h>
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
#include <utility/mpt_utility.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_mpt.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
ConfidentialTransferTestBase::ConfidentialEnv::ConfidentialEnv(
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Account const& issuer,
|
||||
std::vector<HolderInit> const& holders,
|
||||
std::uint32_t flags,
|
||||
std::optional<test::jtx::Account> auditor)
|
||||
: mpt{env, issuer, {.holders = extractAccounts(holders), .auditor = auditor}}
|
||||
{
|
||||
mpt.create({.ownerCount = 1, .flags = flags});
|
||||
|
||||
for (auto const& h : holders)
|
||||
{
|
||||
mpt.authorize({.account = h.account});
|
||||
if ((flags & tfMPTRequireAuth) != 0)
|
||||
mpt.authorize({.account = issuer, .holder = h.account});
|
||||
mpt.pay(issuer, h.account, h.payAmount);
|
||||
}
|
||||
|
||||
mpt.generateKeyPair(issuer);
|
||||
for (auto const& h : holders)
|
||||
mpt.generateKeyPair(h.account);
|
||||
if (auditor)
|
||||
mpt.generateKeyPair(requireOptionalRef(auditor, "Missing auditor"));
|
||||
|
||||
mpt.set({
|
||||
.account = issuer,
|
||||
.issuerPubKey = mpt.getPubKey(issuer),
|
||||
.auditorPubKey = auditor ? mpt.getPubKey(requireOptionalRef(auditor, "Missing auditor"))
|
||||
: std::optional<Buffer>{},
|
||||
});
|
||||
|
||||
for (auto const& h : holders)
|
||||
{
|
||||
mpt.convert({
|
||||
.account = h.account,
|
||||
.amt = h.convertAmount,
|
||||
.holderPubKey = mpt.getPubKey(h.account),
|
||||
});
|
||||
mpt.mergeInbox({.account = h.account});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<test::jtx::Account>
|
||||
ConfidentialTransferTestBase::ConfidentialEnv::extractAccounts(
|
||||
std::vector<HolderInit> const& holders)
|
||||
{
|
||||
std::vector<test::jtx::Account> accounts;
|
||||
accounts.reserve(holders.size());
|
||||
for (auto const& h : holders)
|
||||
accounts.push_back(h.account);
|
||||
return accounts;
|
||||
}
|
||||
|
||||
void
|
||||
ConfidentialTransferTestBase::setupConfidentialIssuance(
|
||||
test::jtx::MPTTester& mpt,
|
||||
@@ -110,478 +34,4 @@ ConfidentialTransferTestBase::setupConfidentialIssuance(
|
||||
mpt.generateKeyPair(keyOwner);
|
||||
}
|
||||
|
||||
void
|
||||
ConfidentialTransferTestBase::setupBatchEnv(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Account const& alice,
|
||||
test::jtx::Account const& bob,
|
||||
test::jtx::Account const& carol,
|
||||
test::jtx::Account const& dave,
|
||||
std::uint64_t bobAmt,
|
||||
std::uint64_t carolAmt)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
mpt.create({
|
||||
.ownerCount = 1,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanHoldConfidentialBalance,
|
||||
});
|
||||
mpt.authorize({.account = bob});
|
||||
mpt.authorize({.account = carol});
|
||||
mpt.authorize({.account = dave});
|
||||
|
||||
if (bobAmt > 0)
|
||||
mpt.pay(alice, bob, bobAmt);
|
||||
if (carolAmt > 0)
|
||||
mpt.pay(alice, carol, carolAmt);
|
||||
|
||||
mpt.generateKeyPair(alice);
|
||||
mpt.generateKeyPair(bob);
|
||||
mpt.generateKeyPair(carol);
|
||||
mpt.generateKeyPair(dave);
|
||||
|
||||
mpt.set({
|
||||
.account = alice,
|
||||
.issuerPubKey = mpt.getPubKey(alice),
|
||||
});
|
||||
|
||||
if (bobAmt > 0)
|
||||
{
|
||||
mpt.convert({
|
||||
.account = bob,
|
||||
.amt = bobAmt,
|
||||
.holderPubKey = mpt.getPubKey(bob),
|
||||
});
|
||||
mpt.mergeInbox({.account = bob});
|
||||
}
|
||||
else
|
||||
{
|
||||
mpt.convert({
|
||||
.account = bob,
|
||||
.amt = 0,
|
||||
.holderPubKey = mpt.getPubKey(bob),
|
||||
});
|
||||
}
|
||||
|
||||
if (carolAmt > 0)
|
||||
{
|
||||
mpt.convert({
|
||||
.account = carol,
|
||||
.amt = carolAmt,
|
||||
.holderPubKey = mpt.getPubKey(carol),
|
||||
});
|
||||
mpt.mergeInbox({.account = carol});
|
||||
}
|
||||
else
|
||||
{
|
||||
mpt.convert({
|
||||
.account = carol,
|
||||
.amt = 0,
|
||||
.holderPubKey = mpt.getPubKey(carol),
|
||||
});
|
||||
}
|
||||
|
||||
// dave: register pubkey only (0 spending/inbox)
|
||||
mpt.convert({
|
||||
.account = dave,
|
||||
.amt = 0,
|
||||
.holderPubKey = mpt.getPubKey(dave),
|
||||
});
|
||||
}
|
||||
|
||||
ConfidentialTransferTestBase::ConfidentialSendSetup::ConfidentialSendSetup(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Account const& sender,
|
||||
test::jtx::Account const& dest,
|
||||
test::jtx::Account const& issuer,
|
||||
uint64_t amount,
|
||||
std::optional<std::reference_wrapper<test::jtx::Account const>> auditor)
|
||||
: sendAmount(amount)
|
||||
, version(mpt.getMPTokenVersion(sender))
|
||||
, blindingFactor(generateBlindingFactor())
|
||||
, amountBlindingFactor(blindingFactor)
|
||||
, balanceBlindingFactor(generateBlindingFactor())
|
||||
, senderAmt(mpt.encryptAmount(sender, amount, blindingFactor))
|
||||
, destAmt(mpt.encryptAmount(dest, amount, blindingFactor))
|
||||
, issuerAmt(mpt.encryptAmount(issuer, amount, blindingFactor))
|
||||
, auditorAmt(
|
||||
auditor ? std::optional<Buffer>(mpt.encryptAmount(auditor->get(), amount, blindingFactor))
|
||||
: std::nullopt)
|
||||
, amountCommitment(mpt.getPedersenCommitment(amount, amountBlindingFactor))
|
||||
, senderPubKey(requireOptional(mpt.getPubKey(sender), "Missing sender public key"))
|
||||
, destPubKey(requireOptional(mpt.getPubKey(dest), "Missing destination public key"))
|
||||
, issuerPubKey(requireOptional(mpt.getPubKey(issuer), "Missing issuer public key"))
|
||||
, auditorPubKey(auditor ? mpt.getPubKey(auditor->get()) : std::nullopt)
|
||||
, prevSpending(requireOptional(
|
||||
mpt.getDecryptedBalance(sender, test::jtx::MPTTester::holderEncryptedSpending),
|
||||
"Missing sender spending balance"))
|
||||
, prevEncryptedSpending(requireOptional(
|
||||
mpt.getEncryptedBalance(sender, test::jtx::MPTTester::holderEncryptedSpending),
|
||||
"Missing sender encrypted spending balance"))
|
||||
, balanceCommitment(mpt.getPedersenCommitment(prevSpending, balanceBlindingFactor))
|
||||
{
|
||||
recipients.push_back({
|
||||
.publicKey = Slice(senderPubKey),
|
||||
.encryptedAmount = senderAmt,
|
||||
});
|
||||
recipients.push_back({
|
||||
.publicKey = Slice(destPubKey),
|
||||
.encryptedAmount = destAmt,
|
||||
});
|
||||
recipients.push_back({
|
||||
.publicKey = Slice(issuerPubKey),
|
||||
.encryptedAmount = issuerAmt,
|
||||
});
|
||||
if (auditor)
|
||||
{
|
||||
recipients.push_back({
|
||||
.publicKey = Slice(requireOptionalRef(auditorPubKey, "Missing auditor public key")),
|
||||
.encryptedAmount = requireOptionalRef(auditorAmt, "Missing auditor encrypted amount"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Buffer>
|
||||
ConfidentialTransferTestBase::ConfidentialSendSetup::generateProof(
|
||||
test::jtx::MPTTester& mpt,
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Account const& sender,
|
||||
test::jtx::Account const& dest) const
|
||||
{
|
||||
auto const ctxHash =
|
||||
getSendContextHash(sender.id(), mpt.issuanceID(), env.seq(sender), dest.id(), version);
|
||||
|
||||
return mpt.getConfidentialSendProof(
|
||||
sender,
|
||||
sendAmount,
|
||||
recipients,
|
||||
blindingFactor,
|
||||
ctxHash,
|
||||
{
|
||||
.pedersenCommitment = amountCommitment,
|
||||
.amt = sendAmount,
|
||||
.encryptedAmt = senderAmt,
|
||||
.blindingFactor = amountBlindingFactor,
|
||||
},
|
||||
{
|
||||
.pedersenCommitment = balanceCommitment,
|
||||
.amt = prevSpending,
|
||||
.encryptedAmt = prevEncryptedSpending,
|
||||
.blindingFactor = balanceBlindingFactor,
|
||||
});
|
||||
}
|
||||
|
||||
test::jtx::MPTConfidentialSend
|
||||
ConfidentialTransferTestBase::ConfidentialSendSetup::sendArgs(
|
||||
test::jtx::Account const& sender,
|
||||
test::jtx::Account const& dest,
|
||||
Buffer const& proof,
|
||||
std::optional<TER> err) const
|
||||
{
|
||||
return {
|
||||
.account = sender,
|
||||
.dest = dest,
|
||||
.amt = sendAmount,
|
||||
.proof = strHex(proof),
|
||||
.senderEncryptedAmt = senderAmt,
|
||||
.destEncryptedAmt = destAmt,
|
||||
.issuerEncryptedAmt = issuerAmt,
|
||||
.auditorEncryptedAmt = auditorAmt,
|
||||
.amountCommitment = amountCommitment,
|
||||
.balanceCommitment = balanceCommitment,
|
||||
.err = err,
|
||||
};
|
||||
}
|
||||
|
||||
Buffer const&
|
||||
ConfidentialTransferTestBase::getBadCiphertext()
|
||||
{
|
||||
static Buffer const kBadCiphertext = []() {
|
||||
Buffer buf(kEcGamalEncryptedTotalLength);
|
||||
std::memset(buf.data(), 0xFF, kEcGamalEncryptedTotalLength);
|
||||
|
||||
buf.data()[0] = kEcCompressedPrefixEvenY;
|
||||
buf.data()[kEcCiphertextComponentLength] = kEcCompressedPrefixEvenY;
|
||||
return buf;
|
||||
}();
|
||||
|
||||
return kBadCiphertext;
|
||||
}
|
||||
|
||||
Buffer const&
|
||||
ConfidentialTransferTestBase::getTrivialCiphertext()
|
||||
{
|
||||
static Buffer const kTrivialCiphertext = []() {
|
||||
Buffer buf(kEcGamalEncryptedTotalLength);
|
||||
std::memset(buf.data(), 0, kEcGamalEncryptedTotalLength);
|
||||
|
||||
buf.data()[0] = kEcCompressedPrefixEvenY;
|
||||
buf.data()[kEcCiphertextComponentLength] = kEcCompressedPrefixEvenY;
|
||||
|
||||
buf.data()[kEcCiphertextComponentLength - 1] = 0x01;
|
||||
buf.data()[kEcGamalEncryptedTotalLength - 1] = 0x01;
|
||||
|
||||
return buf;
|
||||
}();
|
||||
|
||||
return kTrivialCiphertext;
|
||||
}
|
||||
|
||||
Buffer const&
|
||||
ConfidentialTransferTestBase::getTrivialCommitment()
|
||||
{
|
||||
static Buffer const kTrivialCommitment = []() {
|
||||
Buffer buf(kEcPedersenCommitmentLength);
|
||||
std::memset(buf.data(), 0, kEcPedersenCommitmentLength);
|
||||
|
||||
buf.data()[0] = kEcCompressedPrefixEvenY;
|
||||
// Set last byte to make it a valid x-coordinate on the curve
|
||||
buf.data()[kEcPedersenCommitmentLength - 1] = 0x01;
|
||||
|
||||
return buf;
|
||||
}();
|
||||
|
||||
return kTrivialCommitment;
|
||||
}
|
||||
|
||||
std::string
|
||||
ConfidentialTransferTestBase::getTrivialSendProofHex()
|
||||
{
|
||||
Buffer buf(kEcSendProofLength);
|
||||
std::memset(buf.data(), 0, kEcSendProofLength);
|
||||
|
||||
for (std::size_t i = 0; i < kEcSendProofLength; i += kEcCiphertextComponentLength)
|
||||
{
|
||||
buf.data()[i] = kEcCompressedPrefixEvenY;
|
||||
if (i + kEcCiphertextComponentLength - 1 < kEcSendProofLength)
|
||||
buf.data()[i + kEcCiphertextComponentLength - 1] = 0x01;
|
||||
}
|
||||
|
||||
return strHex(buf);
|
||||
}
|
||||
|
||||
Buffer
|
||||
ConfidentialTransferTestBase::getForgedBulletproof(
|
||||
std::array<uint64_t, 2> const& values,
|
||||
std::array<Buffer, 2> const& blindingFactors,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
auto* const ctx = mpt_secp256k1_context();
|
||||
|
||||
secp256k1_pubkey h;
|
||||
secp256k1_mpt_get_h_generator(ctx, &h);
|
||||
|
||||
Buffer proof(kEcDoubleBulletproofLength);
|
||||
size_t proofLen = kEcDoubleBulletproofLength;
|
||||
|
||||
unsigned char blindings[64];
|
||||
std::memcpy(blindings, blindingFactors[0].data(), 32);
|
||||
std::memcpy(blindings + 32, blindingFactors[1].data(), 32);
|
||||
|
||||
if (secp256k1_bulletproof_prove_agg(
|
||||
ctx, proof.data(), &proofLen, values.data(), blindings, 2, &h, contextHash.data()) == 0)
|
||||
Throw<std::runtime_error>("Failed to generate forged bulletproof");
|
||||
|
||||
return proof;
|
||||
}
|
||||
|
||||
Buffer
|
||||
ConfidentialTransferTestBase::getForgedSingleBulletproof(
|
||||
uint64_t value,
|
||||
Buffer const& blindingFactor,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
auto* const ctx = mpt_secp256k1_context();
|
||||
|
||||
secp256k1_pubkey h;
|
||||
secp256k1_mpt_get_h_generator(ctx, &h);
|
||||
|
||||
Buffer proof(kEcSingleBulletproofLength);
|
||||
size_t proofLen = kEcSingleBulletproofLength;
|
||||
|
||||
if (secp256k1_bulletproof_prove_agg(
|
||||
ctx,
|
||||
proof.data(),
|
||||
&proofLen,
|
||||
&value,
|
||||
blindingFactor.data(),
|
||||
1, // m = 1 (single bulletproof)
|
||||
&h,
|
||||
contextHash.data()) == 0)
|
||||
Throw<std::runtime_error>("Failed to generate forged single bulletproof");
|
||||
|
||||
return proof;
|
||||
}
|
||||
|
||||
Buffer
|
||||
ConfidentialTransferTestBase::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;
|
||||
}
|
||||
|
||||
Buffer
|
||||
ConfidentialTransferTestBase::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;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
+206
-326
@@ -42,7 +42,6 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
@@ -176,41 +175,22 @@ keyAtEpoch(
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
// Renders the check's own construction site (usually in mpt.cpp) so it can be
|
||||
// embedded as the "reason" alongside the test call site reported as file/line.
|
||||
std::string
|
||||
checkSiteReason(std::source_location const& checkLoc)
|
||||
{
|
||||
return std::string("check ") + std::filesystem::path{checkLoc.file_name()}.filename().string() +
|
||||
"(" + std::to_string(checkLoc.line()) + ")";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void
|
||||
MptFlags::operator()(Env& env) const
|
||||
{
|
||||
env.test.expect(
|
||||
tester_.checkFlags(flags_, holder_),
|
||||
checkSiteReason(checkLoc_),
|
||||
testLoc_.file_name(),
|
||||
testLoc_.line());
|
||||
env.test.expect(tester_.checkFlags(flags_, holder_));
|
||||
}
|
||||
|
||||
void
|
||||
MptBalance::operator()(Env& env) const
|
||||
{
|
||||
env.test.expect(
|
||||
amount_ == tester_.getBalance(account_),
|
||||
checkSiteReason(checkLoc_),
|
||||
testLoc_.file_name(),
|
||||
testLoc_.line());
|
||||
env.test.expect(amount_ == tester_.getBalance(account_));
|
||||
}
|
||||
|
||||
void
|
||||
RequireAny::operator()(Env& env) const
|
||||
{
|
||||
env.test.expect(cb_(), checkSiteReason(checkLoc_), testLoc_.file_name(), testLoc_.line());
|
||||
env.test.expect(cb_());
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, Account>
|
||||
@@ -226,7 +206,7 @@ MPTTester::makeHolders(std::vector<Account> const& holders)
|
||||
return accounts;
|
||||
}
|
||||
|
||||
MPTTester::MPTTester(Env& env, Account issuer, MPTInit const& arg, std::source_location const& loc)
|
||||
MPTTester::MPTTester(Env& env, Account issuer, MPTInit const& arg)
|
||||
: env_(env)
|
||||
, issuer_(std::move(issuer))
|
||||
, holders_(makeHolders(arg.holders))
|
||||
@@ -258,7 +238,7 @@ MPTTester::MPTTester(Env& env, Account issuer, MPTInit const& arg, std::source_l
|
||||
env_.require(Owners(*arg.auditor, 0));
|
||||
}
|
||||
if (arg.create)
|
||||
create(*arg.create, loc);
|
||||
create(*arg.create);
|
||||
}
|
||||
|
||||
MPTTester::MPTTester(
|
||||
@@ -293,7 +273,7 @@ makeMPTCreate(MPTInitDef const& arg)
|
||||
.authHolder = arg.authHolder};
|
||||
}
|
||||
|
||||
MPTTester::MPTTester(MPTInitDef const& arg, std::source_location const& loc)
|
||||
MPTTester::MPTTester(MPTInitDef const& arg)
|
||||
: MPTTester{
|
||||
arg.env,
|
||||
arg.issuer,
|
||||
@@ -302,8 +282,7 @@ MPTTester::MPTTester(MPTInitDef const& arg, std::source_location const& loc)
|
||||
.fund = arg.fund,
|
||||
.close = arg.close,
|
||||
.create = makeMPTCreate(arg),
|
||||
},
|
||||
loc}
|
||||
}}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -340,7 +319,7 @@ MPTTester::createJV(MPTCreate const& arg)
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::create(MPTCreate const& arg, std::source_location const& loc)
|
||||
MPTTester::create(MPTCreate const& arg)
|
||||
{
|
||||
if (id_)
|
||||
Throw<std::runtime_error>("MPT can't be reused");
|
||||
@@ -353,30 +332,30 @@ MPTTester::create(MPTCreate const& arg, std::source_location const& loc)
|
||||
.metadata = arg.metadata,
|
||||
.immutableFlags = arg.immutableFlags,
|
||||
.domainID = arg.domainID});
|
||||
if (!isTesSuccess(submit(arg, {jv, loc})))
|
||||
if (!isTesSuccess(submit(arg, jv)))
|
||||
{
|
||||
// Verify issuance doesn't exist
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return env_.le(keylet::mptokenIssuance(*id_)) == nullptr; }, loc));
|
||||
[&]() -> bool { return env_.le(keylet::mptokenIssuance(*id_)) == nullptr; }));
|
||||
|
||||
id_.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
env_.require(MptFlags(*this, arg.flags.value_or(0), std::nullopt, loc));
|
||||
env_.require(MptFlags(*this, arg.flags.value_or(0)));
|
||||
auto authAndPay = [&](auto const& accts, auto const&& getAcct) {
|
||||
for (auto const& it : accts)
|
||||
{
|
||||
authorize({.account = getAcct(it)}, loc);
|
||||
authorize({.account = getAcct(it)});
|
||||
if ((arg.flags.value_or(0) & tfMPTRequireAuth) && arg.authHolder)
|
||||
authorize({.account = issuer_, .holder = getAcct(it)}, loc);
|
||||
authorize({.account = issuer_, .holder = getAcct(it)});
|
||||
if (arg.pay && arg.pay->first.empty())
|
||||
pay(issuer_, getAcct(it), arg.pay->second, std::nullopt, std::nullopt, loc);
|
||||
pay(issuer_, getAcct(it), arg.pay->second);
|
||||
}
|
||||
if (arg.pay)
|
||||
{
|
||||
for (auto const& p : arg.pay->first)
|
||||
pay(issuer_, p, arg.pay->second, std::nullopt, std::nullopt, loc);
|
||||
pay(issuer_, p, arg.pay->second);
|
||||
}
|
||||
};
|
||||
if (arg.authorize)
|
||||
@@ -418,13 +397,13 @@ MPTTester::destroyJV(MPTDestroy const& arg)
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::destroy(MPTDestroy const& arg, std::source_location const& loc)
|
||||
MPTTester::destroy(MPTDestroy const& arg)
|
||||
{
|
||||
if (!arg.id && !id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
json::Value const jv =
|
||||
destroyJV({.issuer = arg.issuer ? arg.issuer : issuer_, .id = arg.id ? arg.id : id_});
|
||||
submit(arg, {jv, loc});
|
||||
submit(arg, jv);
|
||||
}
|
||||
|
||||
Account const&
|
||||
@@ -452,7 +431,7 @@ MPTTester::authorizeJV(MPTAuthorize const& arg)
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::authorize(MPTAuthorize const& arg, std::source_location const& loc)
|
||||
MPTTester::authorize(MPTAuthorize const& arg)
|
||||
{
|
||||
if (!arg.id && !id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
@@ -461,7 +440,7 @@ MPTTester::authorize(MPTAuthorize const& arg, std::source_location const& loc)
|
||||
.holder = arg.holder,
|
||||
.id = arg.id ? arg.id : id_,
|
||||
});
|
||||
if (auto const result = submit(arg, {jv, loc}); isTesSuccess(result))
|
||||
if (auto const result = submit(arg, jv); isTesSuccess(result))
|
||||
{
|
||||
// Issuer authorizes
|
||||
if (!arg.account || *arg.account == issuer_)
|
||||
@@ -470,12 +449,12 @@ MPTTester::authorize(MPTAuthorize const& arg, std::source_location const& loc)
|
||||
// issuer un-authorizes the holder
|
||||
if (arg.flags.value_or(0) == tfMPTUnauthorize)
|
||||
{
|
||||
env_.require(MptFlags(*this, flags, arg.holder, loc));
|
||||
env_.require(MptFlags(*this, flags, arg.holder));
|
||||
// issuer authorizes the holder
|
||||
}
|
||||
else
|
||||
{
|
||||
env_.require(MptFlags(*this, flags | lsfMPTAuthorized, arg.holder, loc));
|
||||
env_.require(MptFlags(*this, flags | lsfMPTAuthorized, arg.holder));
|
||||
}
|
||||
}
|
||||
// Holder authorizes
|
||||
@@ -483,17 +462,13 @@ MPTTester::authorize(MPTAuthorize const& arg, std::source_location const& loc)
|
||||
{
|
||||
auto const flags = getFlags(arg.account);
|
||||
// holder creates a token
|
||||
env_.require(MptFlags(*this, flags, arg.account, loc));
|
||||
env_.require(MptBalance(*this, *arg.account, 0, loc));
|
||||
env_.require(MptFlags(*this, flags, arg.account));
|
||||
env_.require(MptBalance(*this, *arg.account, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verify that the MPToken doesn't exist.
|
||||
forObject(
|
||||
[&](SLEP const& sle) {
|
||||
return env_.test.expect(!sle, "", loc.file_name(), loc.line());
|
||||
},
|
||||
arg.account);
|
||||
forObject([&](SLEP const& sle) { return env_.test.BEAST_EXPECT(!sle); }, arg.account);
|
||||
}
|
||||
}
|
||||
else if (
|
||||
@@ -502,31 +477,27 @@ MPTTester::authorize(MPTAuthorize const& arg, std::source_location const& loc)
|
||||
if (result == tecDUPLICATE)
|
||||
{
|
||||
// Verify that MPToken already exists
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return env_.le(keylet::mptoken(*id_, arg.account->id())) != nullptr;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return env_.le(keylet::mptoken(*id_, arg.account->id())) != nullptr;
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Verify MPToken doesn't exist if holder failed authorizing(unless
|
||||
// it already exists)
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return env_.le(keylet::mptoken(*id_, arg.account->id())) == nullptr;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return env_.le(keylet::mptoken(*id_, arg.account->id())) == nullptr;
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::authorizeHolders(Holders const& holders, std::source_location const& loc)
|
||||
MPTTester::authorizeHolders(Holders const& holders)
|
||||
{
|
||||
for (auto const& holder : holders)
|
||||
{
|
||||
authorize({.account = holder}, loc);
|
||||
authorize({.account = holder});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,7 +545,7 @@ MPTTester::setJV(MPTSet const& arg)
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::set(MPTSet const& arg, std::source_location const& loc)
|
||||
MPTTester::set(MPTSet const& arg)
|
||||
{
|
||||
if (!arg.id && !id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
@@ -589,7 +560,7 @@ MPTTester::set(MPTSet const& arg, std::source_location const& loc)
|
||||
.domainID = arg.domainID,
|
||||
.issuerPubKey = arg.issuerPubKey,
|
||||
.auditorPubKey = arg.auditorPubKey});
|
||||
if (submit(arg, {jv, loc}) == tesSUCCESS && arg.flags.value_or(0) != 0u)
|
||||
if (submit(arg, jv) == tesSUCCESS && arg.flags.value_or(0) != 0u)
|
||||
{
|
||||
auto require = [&](std::optional<Account> const& holder, bool unchanged) {
|
||||
auto flags = getFlags(holder);
|
||||
@@ -615,7 +586,7 @@ MPTTester::set(MPTSet const& arg, std::source_location const& loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
env_.require(MptFlags(*this, flags, holder, loc));
|
||||
env_.require(MptFlags(*this, flags, holder));
|
||||
};
|
||||
if (arg.account)
|
||||
require(std::nullopt, arg.holder.has_value());
|
||||
@@ -624,48 +595,43 @@ MPTTester::set(MPTSet const& arg, std::source_location const& loc)
|
||||
|
||||
if (arg.issuerPubKey)
|
||||
{
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return forObject([&](SLEP const& sle) -> bool {
|
||||
if (sle)
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return forObject([&](SLEP const& sle) -> bool {
|
||||
if (sle)
|
||||
{
|
||||
auto const issuerPubKey = getPubKey(issuer_);
|
||||
if (!issuerPubKey)
|
||||
{
|
||||
auto const issuerPubKey = getPubKey(issuer_);
|
||||
if (!issuerPubKey)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"MPTTester::set: issuer's pubkey is not set");
|
||||
}
|
||||
|
||||
return strHex((*sle)[sfIssuerEncryptionKey]) == strHex(*issuerPubKey);
|
||||
Throw<std::runtime_error>("MPTTester::set: issuer's pubkey is not set");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
},
|
||||
loc));
|
||||
|
||||
return strHex((*sle)[sfIssuerEncryptionKey]) == strHex(*issuerPubKey);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}));
|
||||
}
|
||||
if (arg.auditorPubKey)
|
||||
{
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return forObject([&](SLEP const& sle) -> bool {
|
||||
if (sle)
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return forObject([&](SLEP const& sle) -> bool {
|
||||
if (sle)
|
||||
{
|
||||
if (!auditor_.has_value())
|
||||
Throw<std::runtime_error>("MPTTester::set: auditor is not set");
|
||||
|
||||
auto const auditorPubKey = getPubKey(*auditor_);
|
||||
if (!auditorPubKey)
|
||||
{
|
||||
if (!auditor_.has_value())
|
||||
Throw<std::runtime_error>("MPTTester::set: auditor is not set");
|
||||
|
||||
auto const auditorPubKey = getPubKey(*auditor_);
|
||||
if (!auditorPubKey)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"MPTTester::set: auditor's pubkey is not set");
|
||||
}
|
||||
|
||||
return strHex((*sle)[sfAuditorEncryptionKey]) == strHex(*auditorPubKey);
|
||||
Throw<std::runtime_error>(
|
||||
"MPTTester::set: auditor's pubkey is not set");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
},
|
||||
loc));
|
||||
|
||||
return strHex((*sle)[sfAuditorEncryptionKey]) == strHex(*auditorPubKey);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -828,8 +794,7 @@ MPTTester::pay(
|
||||
Account const& dest,
|
||||
std::int64_t amount,
|
||||
std::optional<TER> err,
|
||||
std::optional<std::vector<std::string>> credentials,
|
||||
std::source_location const& loc)
|
||||
std::optional<std::vector<std::string>> credentials)
|
||||
{
|
||||
if (!id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
@@ -840,15 +805,13 @@ MPTTester::pay(
|
||||
if (credentials)
|
||||
{
|
||||
env_(
|
||||
WithSourceLocation<json::Value>{jtx::pay(src, dest, mpt(amount)), loc},
|
||||
jtx::pay(src, dest, mpt(amount)),
|
||||
Ter(err.value_or(tesSUCCESS)),
|
||||
credentials::Ids(*credentials));
|
||||
}
|
||||
else
|
||||
{
|
||||
env_(
|
||||
WithSourceLocation<json::Value>{jtx::pay(src, dest, mpt(amount)), loc},
|
||||
Ter(err.value_or(tesSUCCESS)));
|
||||
env_(jtx::pay(src, dest, mpt(amount)), Ter(err.value_or(tesSUCCESS)));
|
||||
}
|
||||
|
||||
if (!isTesSuccess(env_.ter()))
|
||||
@@ -857,23 +820,23 @@ MPTTester::pay(
|
||||
env_.close();
|
||||
if (src == issuer_)
|
||||
{
|
||||
env_.require(MptBalance(*this, src, srcAmt + amount, loc));
|
||||
env_.require(MptBalance(*this, dest, destAmt + amount, loc));
|
||||
env_.require(MptBalance(*this, src, srcAmt + amount));
|
||||
env_.require(MptBalance(*this, dest, destAmt + amount));
|
||||
}
|
||||
else if (dest == issuer_)
|
||||
{
|
||||
env_.require(MptBalance(*this, src, srcAmt - amount, loc));
|
||||
env_.require(MptBalance(*this, dest, destAmt - amount, loc));
|
||||
env_.require(MptBalance(*this, src, srcAmt - amount));
|
||||
env_.require(MptBalance(*this, dest, destAmt - amount));
|
||||
}
|
||||
else
|
||||
{
|
||||
STAmount const saAmount = {*id_, amount};
|
||||
auto const actual = multiply(saAmount, transferRate(*env_.current(), *id_)).mpt().value();
|
||||
// Sender pays the transfer fee if any
|
||||
env_.require(MptBalance(*this, src, srcAmt - actual, loc));
|
||||
env_.require(MptBalance(*this, dest, destAmt + amount, loc));
|
||||
env_.require(MptBalance(*this, src, srcAmt - actual));
|
||||
env_.require(MptBalance(*this, dest, destAmt + amount));
|
||||
// Outstanding amount is reduced by the transfer fee if any
|
||||
env_.require(MptBalance(*this, issuer_, outstandingAmt - (actual - amount), loc));
|
||||
env_.require(MptBalance(*this, issuer_, outstandingAmt - (actual - amount)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,23 +845,20 @@ MPTTester::claw(
|
||||
Account const& issuer,
|
||||
Account const& holder,
|
||||
std::int64_t amount,
|
||||
std::optional<TER> err,
|
||||
std::source_location const& loc)
|
||||
std::optional<TER> err)
|
||||
{
|
||||
if (!id_)
|
||||
Throw<std::runtime_error>("MPT has not been created");
|
||||
auto const issuerAmt = getBalance(issuer);
|
||||
auto const holderAmt = getBalance(holder);
|
||||
env_(
|
||||
WithSourceLocation<json::Value>{jtx::claw(issuer, mpt(amount), holder), loc},
|
||||
Ter(err.value_or(tesSUCCESS)));
|
||||
env_(jtx::claw(issuer, mpt(amount), holder), Ter(err.value_or(tesSUCCESS)));
|
||||
if (!isTesSuccess(env_.ter()))
|
||||
amount = 0;
|
||||
if (close_)
|
||||
env_.close();
|
||||
|
||||
env_.require(MptBalance(*this, issuer, issuerAmt - std::min(holderAmt, amount), loc));
|
||||
env_.require(MptBalance(*this, holder, holderAmt - std::min(holderAmt, amount), loc));
|
||||
env_.require(MptBalance(*this, issuer, issuerAmt - std::min(holderAmt, amount)));
|
||||
env_.require(MptBalance(*this, holder, holderAmt - std::min(holderAmt, amount)));
|
||||
}
|
||||
|
||||
PrettyAmount
|
||||
@@ -1263,7 +1223,7 @@ MPTTester::fillConversionCiphertexts(
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::convert(MPTConvert const& arg, std::source_location const& loc)
|
||||
MPTTester::convert(MPTConvert const& arg)
|
||||
{
|
||||
json::Value const jv = convertJV(arg, ticketOrSeq(arg.ticketSeq, arg.account));
|
||||
|
||||
@@ -1295,37 +1255,27 @@ MPTTester::convert(MPTConvert const& arg, std::source_location const& loc)
|
||||
|
||||
auto const prevOutstanding = getIssuanceOutstandingBalance();
|
||||
|
||||
if (submit(arg, {jv, loc}) == tesSUCCESS)
|
||||
if (submit(arg, jv) == tesSUCCESS)
|
||||
{
|
||||
auto const postConfidentialOutstanding = getIssuanceConfidentialBalance();
|
||||
auto const postOutstanding = getIssuanceOutstandingBalance();
|
||||
env_.require(MptBalance(*this, account, holderAmt - amt, loc));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return prevOutstanding && postOutstanding && *prevOutstanding == *postOutstanding;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return prevConfidentialOutstanding + amt == postConfidentialOutstanding;
|
||||
},
|
||||
loc));
|
||||
env_.require(MptBalance(*this, account, holderAmt - amt));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return prevOutstanding && postOutstanding && *prevOutstanding == *postOutstanding;
|
||||
}));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return prevConfidentialOutstanding + amt == postConfidentialOutstanding;
|
||||
}));
|
||||
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return getEncryptedBalance(account, holderEncryptedInbox).has_value();
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return getEncryptedBalance(account, holderEncryptedSpending).has_value();
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return getEncryptedBalance(account, issuerEncryptedBalance).has_value();
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return getEncryptedBalance(account, holderEncryptedInbox).has_value();
|
||||
}));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return getEncryptedBalance(account, holderEncryptedSpending).has_value();
|
||||
}));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return getEncryptedBalance(account, issuerEncryptedBalance).has_value();
|
||||
}));
|
||||
|
||||
auto const postInboxBalance = getDecryptedBalance(account, holderEncryptedInbox);
|
||||
auto const postIssuerBalance = getDecryptedBalance(account, issuerEncryptedBalance);
|
||||
@@ -1341,60 +1291,53 @@ MPTTester::convert(MPTConvert const& arg, std::source_location const& loc)
|
||||
if (!postAuditorBalance)
|
||||
Throw<std::runtime_error>("Failed to get post-convert auditor balance");
|
||||
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return getEncryptedBalance(account, auditorEncryptedBalance).has_value();
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return getEncryptedBalance(account, auditorEncryptedBalance).has_value();
|
||||
}));
|
||||
|
||||
// auditor's encrypted balance is updated correctly
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *prevAuditorBalance + amt == *postAuditorBalance; }, loc));
|
||||
[&]() -> bool { return *prevAuditorBalance + amt == *postAuditorBalance; }));
|
||||
}
|
||||
// spending balance should not change
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *postSpendingBalance == *prevSpendingBalance; }, loc));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *postSpendingBalance == *prevSpendingBalance; }));
|
||||
|
||||
// issuer's encrypted balance is updated correctly
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *prevIssuerBalance + amt == *postIssuerBalance; }, loc));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *prevIssuerBalance + amt == *postIssuerBalance; }));
|
||||
|
||||
// holder's inbox balance is updated correctly
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *prevInboxBalance + amt == *postInboxBalance; }, loc));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *prevInboxBalance + amt == *postInboxBalance; }));
|
||||
|
||||
// sum of holder's inbox and spending balance should equal to issuer's
|
||||
// encrypted balance
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return *postInboxBalance + *postSpendingBalance == *postIssuerBalance;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return *postInboxBalance + *postSpendingBalance == *postIssuerBalance;
|
||||
}));
|
||||
|
||||
if (arg.holderPubKey)
|
||||
{
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return forObject(
|
||||
[&](SLEP const& sle) -> bool {
|
||||
if (sle)
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return forObject(
|
||||
[&](SLEP const& sle) -> bool {
|
||||
if (sle)
|
||||
{
|
||||
auto const holderPubKey = getPubKey(account);
|
||||
if (!holderPubKey)
|
||||
{
|
||||
auto const holderPubKey = getPubKey(account);
|
||||
if (!holderPubKey)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"MPTTester::convert: holder's pubkey is "
|
||||
"not set");
|
||||
}
|
||||
|
||||
return strHex((*sle)[sfHolderEncryptionKey]) ==
|
||||
strHex(*holderPubKey);
|
||||
Throw<std::runtime_error>(
|
||||
"MPTTester::convert: holder's pubkey is "
|
||||
"not set");
|
||||
}
|
||||
return false;
|
||||
},
|
||||
account);
|
||||
},
|
||||
loc));
|
||||
|
||||
return strHex((*sle)[sfHolderEncryptionKey]) == strHex(*holderPubKey);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
account);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1429,7 +1372,7 @@ MPTTester::convertJV(MPTConvert const& arg, std::uint32_t seq)
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::send(MPTConfidentialSend const& arg, std::source_location const& loc)
|
||||
MPTTester::send(MPTConfidentialSend const& arg)
|
||||
{
|
||||
json::Value const jv = sendJV(arg, ticketOrSeq(arg.ticketSeq, arg.account));
|
||||
|
||||
@@ -1476,7 +1419,7 @@ MPTTester::send(MPTConfidentialSend const& arg, std::source_location const& loc)
|
||||
auto const prevCOA = getIssuanceConfidentialBalance();
|
||||
auto const prevOA = getIssuanceOutstandingBalance();
|
||||
|
||||
if (submit(arg, {jv, loc}) == tesSUCCESS)
|
||||
if (submit(arg, jv) == tesSUCCESS)
|
||||
{
|
||||
auto const postCOA = getIssuanceConfidentialBalance();
|
||||
auto const postOA = getIssuanceOutstandingBalance();
|
||||
@@ -1498,48 +1441,39 @@ MPTTester::send(MPTConfidentialSend const& arg, std::source_location const& loc)
|
||||
Throw<std::runtime_error>("Failed to get Post-send balance");
|
||||
|
||||
// Public balances unchanged
|
||||
env_.require(MptBalance(*this, account, senderPubAmt, loc));
|
||||
env_.require(MptBalance(*this, dest, destPubAmt, loc));
|
||||
env_.require(MptBalance(*this, account, senderPubAmt));
|
||||
env_.require(MptBalance(*this, dest, destPubAmt));
|
||||
|
||||
// OA and COA unchanged
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return prevOA && postOA && *prevOA == *postOA; }, loc));
|
||||
env_.require(RequireAny([&]() -> bool { return prevCOA == postCOA; }, loc));
|
||||
env_.require(RequireAny([&]() -> bool { return prevOA && postOA && *prevOA == *postOA; }));
|
||||
env_.require(RequireAny([&]() -> bool { return prevCOA == postCOA; }));
|
||||
|
||||
// Verify sender changes
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return *prevSenderSpending >= amt &&
|
||||
*postSenderSpending == *prevSenderSpending - amt;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool { return postSenderInbox == prevSenderInbox; }, loc));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return *prevSenderIssuer >= amt && *postSenderIssuer == *prevSenderIssuer - amt;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return *prevSenderSpending >= amt && *postSenderSpending == *prevSenderSpending - amt;
|
||||
}));
|
||||
env_.require(RequireAny([&]() -> bool { return postSenderInbox == prevSenderInbox; }));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return *prevSenderIssuer >= amt && *postSenderIssuer == *prevSenderIssuer - amt;
|
||||
}));
|
||||
|
||||
// Verify destination changes
|
||||
env_.require(RequireAny([&]() -> bool { return *postDestInbox == *prevDestInbox + amt; }));
|
||||
env_.require(RequireAny([&]() -> bool { return *postDestSpending == *prevDestSpending; }));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *postDestInbox == *prevDestInbox + amt; }, loc));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *postDestSpending == *prevDestSpending; }, loc));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *postDestIssuer == *prevDestIssuer + amt; }, loc));
|
||||
RequireAny([&]() -> bool { return *postDestIssuer == *prevDestIssuer + amt; }));
|
||||
|
||||
// Cross checks
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *postSenderInbox + *postSenderSpending == *postSenderIssuer; },
|
||||
loc));
|
||||
[&]() -> bool { return *postSenderInbox + *postSenderSpending == *postSenderIssuer; }));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *postDestInbox + *postDestSpending == *postDestIssuer; }, loc));
|
||||
[&]() -> bool { return *postDestInbox + *postDestSpending == *postDestIssuer; }));
|
||||
|
||||
// Version: sender increments by 1; receiver version is unchanged by incoming sends
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return getMPTokenVersion(account) == prevSenderVersion + 1; }, loc));
|
||||
[&]() -> bool { return getMPTokenVersion(account) == prevSenderVersion + 1; }));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return getMPTokenVersion(dest) == prevDestVersion; }, loc));
|
||||
RequireAny([&]() -> bool { return getMPTokenVersion(dest) == prevDestVersion; }));
|
||||
|
||||
if (arg.auditorEncryptedAmt || auditor_)
|
||||
{
|
||||
@@ -1548,24 +1482,19 @@ MPTTester::send(MPTConfidentialSend const& arg, std::source_location const& loc)
|
||||
if (!postSenderAuditor || !postDestAuditor)
|
||||
Throw<std::runtime_error>("Failed to get Post-send balance");
|
||||
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return *postSenderAuditor == *postSenderIssuer &&
|
||||
*postDestAuditor == *postDestIssuer;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return *postSenderAuditor == *postSenderIssuer &&
|
||||
*postDestAuditor == *postDestIssuer;
|
||||
}));
|
||||
|
||||
// verify sender
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return *prevSenderAuditor >= amt &&
|
||||
*postSenderAuditor == *prevSenderAuditor - amt;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return *prevSenderAuditor >= amt && *postSenderAuditor == *prevSenderAuditor - amt;
|
||||
}));
|
||||
|
||||
// verify dest
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *postDestAuditor == *prevDestAuditor + amt; }, loc));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *postDestAuditor == *prevDestAuditor + amt; }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1797,7 +1726,7 @@ computeNextSendChainState(
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::confidentialClaw(MPTConfidentialClawback const& arg, std::source_location const& loc)
|
||||
MPTTester::confidentialClaw(MPTConfidentialClawback const& arg)
|
||||
{
|
||||
json::Value jv;
|
||||
auto const account = arg.account ? *arg.account : issuer_;
|
||||
@@ -1837,39 +1766,34 @@ MPTTester::confidentialClaw(MPTConfidentialClawback const& arg, std::source_loca
|
||||
auto const prevOA = getIssuanceOutstandingBalance();
|
||||
auto const prevVersion = getMPTokenVersion(holder);
|
||||
|
||||
if (submit(arg, {jv, loc}) == tesSUCCESS)
|
||||
if (submit(arg, jv) == tesSUCCESS)
|
||||
{
|
||||
auto const postCOA = getIssuanceConfidentialBalance();
|
||||
auto const postOA = getIssuanceOutstandingBalance();
|
||||
auto const postVersion = getMPTokenVersion(holder);
|
||||
|
||||
// Verify holder's public balance is unchanged
|
||||
env_.require(MptBalance(*this, holder, holderPubAmt, loc));
|
||||
env_.require(MptBalance(*this, holder, holderPubAmt));
|
||||
|
||||
// Verify COA and OA are reduced correctly
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return prevCOA >= amt && postCOA == prevCOA - amt; }, loc));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return prevOA && postOA && *prevOA >= amt && *postOA == *prevOA - amt;
|
||||
},
|
||||
loc));
|
||||
RequireAny([&]() -> bool { return prevCOA >= amt && postCOA == prevCOA - amt; }));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return prevOA && postOA && *prevOA >= amt && *postOA == *prevOA - amt;
|
||||
}));
|
||||
|
||||
// Verify holder's confidential balances are zeroed out
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return getDecryptedBalance(holder, holderEncryptedInbox) == 0; }, loc));
|
||||
[&]() -> bool { return getDecryptedBalance(holder, holderEncryptedInbox) == 0; }));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return getDecryptedBalance(holder, holderEncryptedSpending) == 0; },
|
||||
loc));
|
||||
[&]() -> bool { return getDecryptedBalance(holder, holderEncryptedSpending) == 0; }));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return getDecryptedBalance(holder, issuerEncryptedBalance) == 0; },
|
||||
loc));
|
||||
[&]() -> bool { return getDecryptedBalance(holder, issuerEncryptedBalance) == 0; }));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return getDecryptedBalance(holder, auditorEncryptedBalance) == 0; },
|
||||
loc));
|
||||
[&]() -> bool { return getDecryptedBalance(holder, auditorEncryptedBalance) == 0; }));
|
||||
|
||||
// Verify version is incremented
|
||||
env_.require(RequireAny([&]() -> bool { return postVersion == prevVersion + 1; }, loc));
|
||||
env_.require(RequireAny([&]() -> bool { return postVersion == prevVersion + 1; }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2001,7 +1925,7 @@ MPTTester::mergeInboxJV(MPTMergeInbox const& arg) const
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::mergeInbox(MPTMergeInbox const& arg, std::source_location const& loc)
|
||||
MPTTester::mergeInbox(MPTMergeInbox const& arg)
|
||||
{
|
||||
json::Value const jv = mergeInboxJV(arg);
|
||||
Account const& account = requireValue(arg.account, "account");
|
||||
@@ -2019,7 +1943,7 @@ MPTTester::mergeInbox(MPTMergeInbox const& arg, std::source_location const& loc)
|
||||
if (!prevInboxBalance || !prevSpendingBalance || !prevIssuerBalance)
|
||||
Throw<std::runtime_error>("Failed to get pre-mergeInbox balances");
|
||||
|
||||
if (submit(arg, {jv, loc}) == tesSUCCESS)
|
||||
if (submit(arg, jv) == tesSUCCESS)
|
||||
{
|
||||
auto const postCOA = getIssuanceConfidentialBalance();
|
||||
auto const postOA = getIssuanceOutstandingBalance();
|
||||
@@ -2035,20 +1959,17 @@ MPTTester::mergeInbox(MPTMergeInbox const& arg, std::source_location const& loc)
|
||||
!prevIssuerEncrypted || !postInboxEncrypted || !postIssuerEncrypted)
|
||||
Throw<std::runtime_error>("Failed to get post-mergeInbox balances");
|
||||
|
||||
env_.require(MptBalance(*this, account, holderPubAmt, loc));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return prevOA && postOA && *prevOA == *postOA; }, loc));
|
||||
env_.require(RequireAny([&]() -> bool { return prevCOA == postCOA; }, loc));
|
||||
env_.require(MptBalance(*this, account, holderPubAmt));
|
||||
env_.require(RequireAny([&]() -> bool { return prevOA && postOA && *prevOA == *postOA; }));
|
||||
env_.require(RequireAny([&]() -> bool { return prevCOA == postCOA; }));
|
||||
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return *postSpendingBalance == *prevInboxBalance + *prevSpendingBalance &&
|
||||
*postInboxBalance == 0;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return *postSpendingBalance == *prevInboxBalance + *prevSpendingBalance &&
|
||||
*postInboxBalance == 0;
|
||||
}));
|
||||
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *prevIssuerBalance == *postIssuerBalance; }, loc));
|
||||
RequireAny([&]() -> bool { return *prevIssuerBalance == *postIssuerBalance; }));
|
||||
|
||||
auto const holderPubKey = getPubKey(account);
|
||||
if (!holderPubKey)
|
||||
@@ -2059,23 +1980,18 @@ MPTTester::mergeInbox(MPTMergeInbox const& arg, std::source_location const& loc)
|
||||
if (!expectedInbox)
|
||||
Throw<std::runtime_error>("Failed to get canonical zero encryption");
|
||||
|
||||
env_.require(RequireAny([&]() -> bool { return *postInboxEncrypted == *expectedInbox; }));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *postInboxEncrypted == *expectedInbox; }, loc));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *postIssuerEncrypted == *prevIssuerEncrypted; }, loc));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return postAuditorEncrypted.has_value() == prevAuditorEncrypted.has_value() &&
|
||||
(!postAuditorEncrypted || *postAuditorEncrypted == *prevAuditorEncrypted);
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool { return postVersion == prevVersion + 1; }, loc));
|
||||
RequireAny([&]() -> bool { return *postIssuerEncrypted == *prevIssuerEncrypted; }));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return postAuditorEncrypted.has_value() == prevAuditorEncrypted.has_value() &&
|
||||
(!postAuditorEncrypted || *postAuditorEncrypted == *prevAuditorEncrypted);
|
||||
}));
|
||||
env_.require(RequireAny([&]() -> bool { return postVersion == prevVersion + 1; }));
|
||||
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return *postSpendingBalance + *postInboxBalance == *postIssuerBalance;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return *postSpendingBalance + *postInboxBalance == *postIssuerBalance;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2110,7 +2026,7 @@ MPTTester::getMPTokenVersion(Account const account) const
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::convertBack(MPTConvertBack const& arg, std::source_location const& loc)
|
||||
MPTTester::convertBack(MPTConvertBack const& arg)
|
||||
{
|
||||
json::Value const jv = convertBackJV(arg, ticketOrSeq(arg.ticketSeq, arg.account));
|
||||
|
||||
@@ -2138,22 +2054,18 @@ MPTTester::convertBack(MPTConvertBack const& arg, std::source_location const& lo
|
||||
auto const prevOutstanding = getIssuanceOutstandingBalance();
|
||||
auto const prevVersion = getMPTokenVersion(account);
|
||||
|
||||
if (submit(arg, {jv, loc}) == tesSUCCESS)
|
||||
if (submit(arg, jv) == tesSUCCESS)
|
||||
{
|
||||
auto const postConfidentialOutstanding = getIssuanceConfidentialBalance();
|
||||
auto const postOutstanding = getIssuanceOutstandingBalance();
|
||||
auto const postVersion = getMPTokenVersion(account);
|
||||
env_.require(MptBalance(*this, account, holderAmt + amt, loc));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return prevOutstanding && postOutstanding && *prevOutstanding == *postOutstanding;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return prevConfidentialOutstanding - amt == postConfidentialOutstanding;
|
||||
},
|
||||
loc));
|
||||
env_.require(MptBalance(*this, account, holderAmt + amt));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return prevOutstanding && postOutstanding && *prevOutstanding == *postOutstanding;
|
||||
}));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return prevConfidentialOutstanding - amt == postConfidentialOutstanding;
|
||||
}));
|
||||
|
||||
auto const postInboxBalance = getDecryptedBalance(account, holderEncryptedInbox);
|
||||
auto const postIssuerBalance = getDecryptedBalance(account, issuerEncryptedBalance);
|
||||
@@ -2171,31 +2083,28 @@ MPTTester::convertBack(MPTConvertBack const& arg, std::source_location const& lo
|
||||
|
||||
// auditor's encrypted balance is updated correctly
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *prevAuditorBalance - amt == *postAuditorBalance; }, loc));
|
||||
[&]() -> bool { return *prevAuditorBalance - amt == *postAuditorBalance; }));
|
||||
}
|
||||
|
||||
// inbox balance should not change
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *postInboxBalance == *prevInboxBalance; }, loc));
|
||||
env_.require(RequireAny([&]() -> bool { return *postInboxBalance == *prevInboxBalance; }));
|
||||
|
||||
// issuer's encrypted balance is updated correctly
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *prevIssuerBalance - amt == *postIssuerBalance; }, loc));
|
||||
env_.require(
|
||||
RequireAny([&]() -> bool { return *prevIssuerBalance - amt == *postIssuerBalance; }));
|
||||
|
||||
// holder's spending balance is updated correctly
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool { return *prevSpendingBalance - amt == *postSpendingBalance; }, loc));
|
||||
[&]() -> bool { return *prevSpendingBalance - amt == *postSpendingBalance; }));
|
||||
|
||||
// holder's confidential balance version is updated correctly
|
||||
env_.require(RequireAny([&]() -> bool { return postVersion == prevVersion + 1; }, loc));
|
||||
env_.require(RequireAny([&]() -> bool { return postVersion == prevVersion + 1; }));
|
||||
|
||||
// sum of holder's inbox and spending balance should equal to issuer's
|
||||
// encrypted balance
|
||||
env_.require(RequireAny(
|
||||
[&]() -> bool {
|
||||
return *postInboxBalance + *postSpendingBalance == *postIssuerBalance;
|
||||
},
|
||||
loc));
|
||||
env_.require(RequireAny([&]() -> bool {
|
||||
return *postInboxBalance + *postSpendingBalance == *postIssuerBalance;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2262,33 +2171,4 @@ MPTTester::convertBackJV(MPTConvertBack const& arg, std::uint32_t seq)
|
||||
return jv;
|
||||
}
|
||||
|
||||
void
|
||||
MPTTester::mirrorUpdate(MPTMirrorUpdate const& arg, std::source_location const& loc)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::ConfidentialMPTMirrorUpdate;
|
||||
|
||||
setAccountField(jv, arg.account);
|
||||
setIssuanceIdField(jv, arg.id);
|
||||
|
||||
if (arg.holder)
|
||||
jv[sfHolder] = arg.holder->human();
|
||||
if (arg.issuerEncryptedAmount)
|
||||
jv[sfIssuerEncryptedAmount] = strHex(*arg.issuerEncryptedAmount);
|
||||
if (arg.auditorEncryptedAmount)
|
||||
jv[sfAuditorEncryptedAmount] = strHex(*arg.auditorEncryptedAmount);
|
||||
|
||||
// Placeholder for proof, the logic will be added in the future
|
||||
if (arg.zkProof)
|
||||
{
|
||||
jv[sfZKProof] = strHex(*arg.zkProof);
|
||||
}
|
||||
else
|
||||
{
|
||||
jv[sfZKProof] = strHex(gMakeZeroBuffer(kEcEqualityProofLength));
|
||||
}
|
||||
|
||||
submit(arg, {jv, loc});
|
||||
}
|
||||
|
||||
} // namespace xrpl::test::jtx
|
||||
@@ -78,7 +78,7 @@ fillFee(json::Value& jv, ReadView const& view)
|
||||
auto const txType = jv[jss::TransactionType].asString();
|
||||
if (txType == jss::ConfidentialMPTConvert || txType == jss::ConfidentialMPTConvertBack ||
|
||||
txType == jss::ConfidentialMPTSend || txType == jss::ConfidentialMPTMergeInbox ||
|
||||
txType == jss::ConfidentialMPTClawback || txType == jss::ConfidentialMPTMirrorUpdate)
|
||||
txType == jss::ConfidentialMPTClawback)
|
||||
{
|
||||
jv[jss::Fee] = to_string(base * (kConfidentialFeeMultiplier + 1));
|
||||
}
|
||||
|
||||
+21
-98
@@ -32,7 +32,6 @@
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -76,19 +75,13 @@ private:
|
||||
MPTTester& tester_;
|
||||
std::uint32_t flags_;
|
||||
std::optional<Account> holder_;
|
||||
// Test call site (forwarded from the MPTTester method).
|
||||
std::source_location testLoc_;
|
||||
// The site where this check object was constructed (usually in mpt.cpp).
|
||||
std::source_location checkLoc_;
|
||||
|
||||
public:
|
||||
MptFlags(
|
||||
MPTTester& tester,
|
||||
std::uint32_t flags,
|
||||
std::optional<Account> const& holder = std::nullopt,
|
||||
std::source_location const& testLoc = std::source_location::current(),
|
||||
std::source_location const& checkLoc = std::source_location::current())
|
||||
: tester_(tester), flags_(flags), holder_(holder), testLoc_(testLoc), checkLoc_(checkLoc)
|
||||
std::optional<Account> const& holder = std::nullopt)
|
||||
: tester_(tester), flags_(flags), holder_(holder)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -105,23 +98,10 @@ private:
|
||||
MPTTester const& tester_;
|
||||
Account const& account_;
|
||||
std::int64_t const amount_;
|
||||
// Test call site (forwarded from the MPTTester method).
|
||||
std::source_location testLoc_;
|
||||
// The site where this check object was constructed (usually in mpt.cpp).
|
||||
std::source_location checkLoc_;
|
||||
|
||||
public:
|
||||
MptBalance(
|
||||
MPTTester& tester,
|
||||
Account const& account,
|
||||
std::int64_t amount,
|
||||
std::source_location const& testLoc = std::source_location::current(),
|
||||
std::source_location const& checkLoc = std::source_location::current())
|
||||
: tester_(tester)
|
||||
, account_(account)
|
||||
, amount_(amount)
|
||||
, testLoc_(testLoc)
|
||||
, checkLoc_(checkLoc)
|
||||
MptBalance(MPTTester& tester, Account const& account, std::int64_t amount)
|
||||
: tester_(tester), account_(account), amount_(amount)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -136,17 +116,9 @@ class RequireAny
|
||||
{
|
||||
private:
|
||||
std::function<bool()> cb_;
|
||||
// Test call site (forwarded from the MPTTester method).
|
||||
std::source_location testLoc_;
|
||||
// The site where this check object was constructed (usually in mpt.cpp).
|
||||
std::source_location checkLoc_;
|
||||
|
||||
public:
|
||||
RequireAny(
|
||||
std::function<bool()> const& cb,
|
||||
std::source_location const& testLoc = std::source_location::current(),
|
||||
std::source_location const& checkLoc = std::source_location::current())
|
||||
: cb_(cb), testLoc_(testLoc), checkLoc_(checkLoc)
|
||||
RequireAny(std::function<bool()> const& cb) : cb_(cb)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -390,24 +362,6 @@ struct MPTConfidentialClawback
|
||||
std::optional<TER> err = std::nullopt;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Arguments for building a ConfidentialMPTMirrorUpdate test transaction.
|
||||
*/
|
||||
struct MPTMirrorUpdate
|
||||
{
|
||||
std::optional<Account> account = std::nullopt;
|
||||
std::optional<Account> holder = std::nullopt;
|
||||
std::optional<MPTID> id = std::nullopt;
|
||||
std::optional<Buffer> issuerEncryptedAmount = std::nullopt;
|
||||
std::optional<Buffer> auditorEncryptedAmount = std::nullopt;
|
||||
std::optional<Buffer> zkProof = std::nullopt;
|
||||
std::optional<XRPAmount> fee = std::nullopt;
|
||||
std::optional<std::uint32_t> flags = std::nullopt;
|
||||
std::optional<std::uint32_t> ownerCount = std::nullopt;
|
||||
std::optional<std::uint32_t> holderCount = std::nullopt;
|
||||
std::optional<TER> err = std::nullopt;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Stores the parameters that are exclusively used to generate a
|
||||
* Pedersen linkage proof.
|
||||
@@ -516,14 +470,8 @@ public:
|
||||
static constexpr auto holderEncryptedSpending = EncryptedBalanceType::HolderEncryptedSpending;
|
||||
static constexpr auto auditorEncryptedBalance = EncryptedBalanceType::AuditorEncryptedBalance;
|
||||
|
||||
MPTTester(
|
||||
Env& env,
|
||||
Account issuer,
|
||||
MPTInit const& constr = {},
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
MPTTester(
|
||||
MPTInitDef const& constr,
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
MPTTester(Env& env, Account issuer, MPTInit const& constr = {});
|
||||
MPTTester(MPTInitDef const& constr);
|
||||
MPTTester(
|
||||
Env& env,
|
||||
Account issuer,
|
||||
@@ -533,44 +481,34 @@ public:
|
||||
operator MPT() const;
|
||||
|
||||
void
|
||||
create(
|
||||
MPTCreate const& arg = MPTCreate{},
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
create(MPTCreate const& arg = MPTCreate{});
|
||||
|
||||
static json::Value
|
||||
createJV(MPTCreate const& arg = MPTCreate{});
|
||||
|
||||
void
|
||||
destroy(
|
||||
MPTDestroy const& arg = MPTDestroy{},
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
destroy(MPTDestroy const& arg = MPTDestroy{});
|
||||
|
||||
static json::Value
|
||||
destroyJV(MPTDestroy const& arg = MPTDestroy{});
|
||||
|
||||
void
|
||||
authorize(
|
||||
MPTAuthorize const& arg = MPTAuthorize{},
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
authorize(MPTAuthorize const& arg = MPTAuthorize{});
|
||||
|
||||
static json::Value
|
||||
authorizeJV(MPTAuthorize const& arg = MPTAuthorize{});
|
||||
|
||||
void
|
||||
authorizeHolders(
|
||||
Holders const& holders,
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
authorizeHolders(Holders const& holders);
|
||||
|
||||
void
|
||||
set(MPTSet const& set = {}, std::source_location const& loc = std::source_location::current());
|
||||
set(MPTSet const& set = {});
|
||||
|
||||
static json::Value
|
||||
setJV(MPTSet const& set = {});
|
||||
|
||||
void
|
||||
convert(
|
||||
MPTConvert const& arg = MPTConvert{},
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
convert(MPTConvert const& arg = MPTConvert{});
|
||||
|
||||
/**
|
||||
* @brief Build a confidential convert JV without submitting it.
|
||||
@@ -584,17 +522,13 @@ public:
|
||||
convertJV(MPTConvert const& arg, std::uint32_t seq);
|
||||
|
||||
void
|
||||
mergeInbox(
|
||||
MPTMergeInbox const& arg = MPTMergeInbox{},
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
mergeInbox(MPTMergeInbox const& arg = MPTMergeInbox{});
|
||||
|
||||
[[nodiscard]] json::Value
|
||||
mergeInboxJV(MPTMergeInbox const& arg = MPTMergeInbox{}) const;
|
||||
|
||||
void
|
||||
send(
|
||||
MPTConfidentialSend const& arg = MPTConfidentialSend{},
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
send(MPTConfidentialSend const& arg = MPTConfidentialSend{});
|
||||
|
||||
/**
|
||||
* @brief Build a confidential send JV.
|
||||
@@ -632,9 +566,7 @@ public:
|
||||
chainAfterSend(Account const& sender, std::uint64_t sendAmt, json::Value const& jv) const;
|
||||
|
||||
void
|
||||
convertBack(
|
||||
MPTConvertBack const& arg = MPTConvertBack{},
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
convertBack(MPTConvertBack const& arg = MPTConvertBack{});
|
||||
|
||||
/**
|
||||
* @brief Build a confidential convertBack JV without submitting it.
|
||||
@@ -650,14 +582,7 @@ public:
|
||||
convertBackJV(MPTConvertBack const& arg, std::uint32_t seq);
|
||||
|
||||
void
|
||||
confidentialClaw(
|
||||
MPTConfidentialClawback const& arg = MPTConfidentialClawback{},
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
|
||||
void
|
||||
mirrorUpdate(
|
||||
MPTMirrorUpdate const& arg = MPTMirrorUpdate{},
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
confidentialClaw(MPTConfidentialClawback const& arg = MPTConfidentialClawback{});
|
||||
|
||||
[[nodiscard]] bool
|
||||
checkDomainID(std::optional<uint256> expected) const;
|
||||
@@ -727,16 +652,14 @@ public:
|
||||
Account const& dest,
|
||||
std::int64_t amount,
|
||||
std::optional<TER> err = std::nullopt,
|
||||
std::optional<std::vector<std::string>> credentials = std::nullopt,
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
std::optional<std::vector<std::string>> credentials = std::nullopt);
|
||||
|
||||
void
|
||||
claw(
|
||||
Account const& issuer,
|
||||
Account const& holder,
|
||||
std::int64_t amount,
|
||||
std::optional<TER> err = std::nullopt,
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
std::optional<TER> err = std::nullopt);
|
||||
|
||||
[[nodiscard]] PrettyAmount
|
||||
mpt(std::int64_t amount) const;
|
||||
@@ -851,7 +774,7 @@ private:
|
||||
|
||||
template <typename A>
|
||||
TER
|
||||
submit(A const& arg, WithSourceLocation<json::Value> jv)
|
||||
submit(A const& arg, json::Value jv)
|
||||
{
|
||||
auto const expectedFlags = Txflags(arg.flags.value_or(0));
|
||||
auto const expectedTer = Ter(arg.err.value_or(tesSUCCESS));
|
||||
@@ -859,7 +782,7 @@ private:
|
||||
if constexpr (requires { arg.fee; })
|
||||
{
|
||||
if (arg.fee)
|
||||
jv.value[jss::Fee] = to_string(*arg.fee);
|
||||
jv[jss::Fee] = to_string(*arg.fee);
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t> ticketSeq;
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/peerfinder/Slot.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
/**
|
||||
* A real `PeerImp` that captures the messages it would have sent.
|
||||
*
|
||||
* Only `send` and `run` are overridden, so `onMessage` runs production code.
|
||||
* Derive from this to reach a `protected` `PeerImp` member.
|
||||
*/
|
||||
class CapturePeer : public PeerImp
|
||||
{
|
||||
public:
|
||||
using MiddleType = boost::beast::tcp_stream;
|
||||
using StreamType = boost::beast::ssl_stream<MiddleType>;
|
||||
using SocketType = boost::asio::ip::tcp::socket;
|
||||
|
||||
/**
|
||||
* Takes `PeerImp`'s two rvalue-reference parameters by value instead, so a
|
||||
* derived double can inherit this constructor without a never-moved-from
|
||||
* warning.
|
||||
*
|
||||
* @param app The application owning the peer.
|
||||
* @param id The connection id, unique among the overlay's peers.
|
||||
* @param slot The peer finder slot; must be seated.
|
||||
* @param request The handshake request.
|
||||
* @param publicKey The peer's node public key.
|
||||
* @param protocol The negotiated protocol version.
|
||||
* @param consumer The resource manager endpoint for the peer.
|
||||
* @param streamPtr The connection's ssl stream.
|
||||
* @param overlay The overlay to register with.
|
||||
*/
|
||||
CapturePeer(
|
||||
Application& app,
|
||||
Peer::id_t id,
|
||||
std::shared_ptr<peer_finder::Slot> const& slot,
|
||||
http_request_type request,
|
||||
PublicKey const& publicKey,
|
||||
ProtocolVersion protocol,
|
||||
resource::Consumer consumer,
|
||||
std::unique_ptr<StreamType> streamPtr,
|
||||
OverlayImpl& overlay)
|
||||
: PeerImp(
|
||||
app,
|
||||
id,
|
||||
slot,
|
||||
std::move(request),
|
||||
publicKey,
|
||||
protocol,
|
||||
consumer, // copy-only, so `std::move` would be a copy anyway
|
||||
std::move(streamPtr),
|
||||
overlay)
|
||||
{
|
||||
}
|
||||
|
||||
~CapturePeer() override = default;
|
||||
|
||||
/**
|
||||
* Does nothing, so the peer stays registered. The real `run()` reaches
|
||||
* `PeerImp::doAccept`, which fails on an unconnected socket and detaches.
|
||||
*/
|
||||
void
|
||||
run() override
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures the message instead of writing it, so replies are observable.
|
||||
*/
|
||||
void
|
||||
send(std::shared_ptr<Message> const& m) override
|
||||
{
|
||||
sent_.push_back(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Every message sent to this peer, in order.
|
||||
*/
|
||||
std::vector<std::shared_ptr<Message>> const&
|
||||
sent() const
|
||||
{
|
||||
return sent_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The most recent message sent, or null if there was none.
|
||||
*/
|
||||
std::shared_ptr<Message>
|
||||
lastSent() const
|
||||
{
|
||||
return sent_.empty() ? nullptr : sent_.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the accumulated charge without draining it through `charge()`.
|
||||
*
|
||||
* @return The charge accumulated on the peer so far.
|
||||
*/
|
||||
resource::Charge
|
||||
feeCharge() const
|
||||
{
|
||||
return currentFeeCharge();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Message>> sent_;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// `inline` so the functions below name one entity across translation units.
|
||||
inline constexpr std::uint16_t kCapturePeerPort = 51235;
|
||||
|
||||
/**
|
||||
* Non-template, so all `makeCapturePeer` instantiations share one counter. A
|
||||
* per-instantiation counter would give two peer types the same id, and
|
||||
* `addActive` would silently drop the second from `ids_`.
|
||||
*
|
||||
* @return The next unused connection id.
|
||||
*/
|
||||
inline Peer::id_t
|
||||
nextCapturePeerId()
|
||||
{
|
||||
static Peer::id_t id{0};
|
||||
return ++id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-template for the same reason as `nextCapturePeerId`. Each peer needs its
|
||||
* own address, not just its own port: the peer finder caps inbound connections
|
||||
* per address at `ipLimit`, which is at most 2 unless configured.
|
||||
*
|
||||
* @return The next unused remote endpoint.
|
||||
*/
|
||||
inline beast::ip::Endpoint
|
||||
nextCapturePeerRemote()
|
||||
{
|
||||
// From 172.2.0.1 upward, so ~900k fit before reaching 172.16/12, where the
|
||||
// peer finder would treat them as private rather than as real inbound.
|
||||
static std::uint32_t next{0xAC020001};
|
||||
return beast::ip::Endpoint(boost::asio::ip::address_v4(next++), kCapturePeerPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outlives every peer, whose stream keeps a reference to it. `PeerImp::charge`
|
||||
* posts a handler holding the peer, so a peer can outlive its caller's scope.
|
||||
*
|
||||
* @return The ssl context every test peer's stream is built on.
|
||||
*/
|
||||
inline boost::asio::ssl::context&
|
||||
capturePeerSslContext()
|
||||
{
|
||||
static std::shared_ptr<boost::asio::ssl::context> const kContext{makeSslContext("")};
|
||||
return *kContext;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Build an active `CapturePeer` and register it with the overlay.
|
||||
*
|
||||
* @tparam PeerType The peer class to build; must derive from `CapturePeer` and
|
||||
* inherit its constructor.
|
||||
* @param env The environment owning the overlay.
|
||||
* @param key The peer's node public key, or unseated for a fresh random
|
||||
* one.
|
||||
* @param request The handshake request. `PeerImp` reads its `X-Protocol-Ctl`
|
||||
* header in the constructor to negotiate features.
|
||||
* @return The peer, already registered with the overlay. Throws if the peer
|
||||
* finder refused a slot.
|
||||
*/
|
||||
template <class PeerType = CapturePeer>
|
||||
std::shared_ptr<PeerType>
|
||||
makeCapturePeer(
|
||||
jtx::Env& env,
|
||||
std::optional<PublicKey> key = std::nullopt,
|
||||
http_request_type request = {})
|
||||
{
|
||||
auto& overlay = dynamic_cast<OverlayImpl&>(env.app().getOverlay());
|
||||
auto streamPtr = std::make_unique<CapturePeer::StreamType>(
|
||||
CapturePeer::SocketType(env.app().getIOContext()), detail::capturePeerSslContext());
|
||||
|
||||
beast::ip::Endpoint const local(
|
||||
boost::asio::ip::make_address("172.1.1.1"), detail::kCapturePeerPort);
|
||||
auto const remote = detail::nextCapturePeerRemote();
|
||||
|
||||
auto consumer = overlay.resourceManager().newInboundEndpoint(remote);
|
||||
auto [slot, _] = overlay.peerFinder().newInboundSlot(local, remote);
|
||||
|
||||
// Unseated when the endpoint is already connected or at the per-address
|
||||
// limit. `PeerImp` dereferences the slot, so fail here, not there.
|
||||
if (!slot)
|
||||
{
|
||||
Throw<std::runtime_error>("makeCapturePeer: no slot for " + to_string(remote));
|
||||
}
|
||||
|
||||
if (!key)
|
||||
key = PublicKey(std::get<0>(randomKeyPair(KeyType::Ed25519)));
|
||||
|
||||
auto peer = std::make_shared<PeerType>(
|
||||
env.app(),
|
||||
detail::nextCapturePeerId(),
|
||||
slot,
|
||||
std::move(request),
|
||||
*key,
|
||||
// An unsupported version fails every `supportsFeature` test, so a
|
||||
// version-gated reply would only ever take its legacy branch.
|
||||
newestSupportedProtocolVersion(),
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay);
|
||||
|
||||
overlay.addActive(peer);
|
||||
return peer;
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -0,0 +1,166 @@
|
||||
#include <test/overlay/PeerTest.h>
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/peerfinder/Slot.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
PeerTest::PeerTest(
|
||||
Application& app,
|
||||
std::shared_ptr<peer_finder::Slot> const& slot,
|
||||
http_request_type&& request,
|
||||
PublicKey const& publicKey,
|
||||
ProtocolVersion protocol,
|
||||
resource::Consumer consumer,
|
||||
std::unique_ptr<StreamType>&& streamPtr,
|
||||
OverlayImpl& overlay)
|
||||
: PeerImp{
|
||||
app,
|
||||
id++,
|
||||
slot,
|
||||
std::move(request),
|
||||
publicKey,
|
||||
protocol,
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::run()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::send(std::shared_ptr<Message> const& message)
|
||||
{
|
||||
lastSentMessage_ = message;
|
||||
}
|
||||
|
||||
std::shared_ptr<Message>
|
||||
PeerTest::getLastSentMessage() const
|
||||
{
|
||||
return lastSentMessage_;
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::runProcessGetObjectByHash(std::shared_ptr<protocol::TMGetObjectByHash> const& message)
|
||||
{
|
||||
PeerImp::processGetObjectByHash(message);
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::runProcessLedgerRequest(
|
||||
std::shared_ptr<protocol::TMGetLedger> const& message,
|
||||
std::vector<SHAMapNodeID> nodeIDs)
|
||||
{
|
||||
PeerImp::processLedgerRequest(message, std::move(nodeIDs));
|
||||
}
|
||||
|
||||
resource::Charge
|
||||
PeerTest::getCurrentFeeCharge() const
|
||||
{
|
||||
return PeerImp::currentFeeCharge();
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::resetId()
|
||||
{
|
||||
id = 0;
|
||||
}
|
||||
|
||||
bool
|
||||
PeerTest::compressionEnabled() const
|
||||
{
|
||||
if (compressionEnabled_.has_value())
|
||||
{
|
||||
return *compressionEnabled_;
|
||||
}
|
||||
return PeerImp::compressionEnabled();
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::compressionEnabled(std::optional<bool> enabled)
|
||||
{
|
||||
compressionEnabled_ = enabled;
|
||||
}
|
||||
|
||||
bool
|
||||
PeerTest::txReduceRelayEnabled() const
|
||||
{
|
||||
if (reduceRelayEnabled_.has_value())
|
||||
{
|
||||
return *reduceRelayEnabled_;
|
||||
}
|
||||
return PeerImp::txReduceRelayEnabled();
|
||||
}
|
||||
|
||||
void
|
||||
PeerTest::txReduceRelayEnabled(std::optional<bool> enabled)
|
||||
{
|
||||
reduceRelayEnabled_ = enabled;
|
||||
}
|
||||
|
||||
std::shared_ptr<PeerTest>
|
||||
makePeerTest(jtx::Env& env, PeerTest::SharedContext const& context, ProtocolVersion protocolVersion)
|
||||
{
|
||||
using SocketType = boost::asio::ip::tcp::socket;
|
||||
|
||||
auto& overlay = dynamic_cast<OverlayImpl&>(env.app().getOverlay());
|
||||
boost::beast::http::request<boost::beast::http::dynamic_body> request;
|
||||
auto streamPtr =
|
||||
std::make_unique<PeerTest::StreamType>(SocketType(env.app().getIOContext()), *context);
|
||||
|
||||
beast::ip::Endpoint const local(boost::asio::ip::make_address("172.1.1.1"), 51235);
|
||||
beast::ip::Endpoint const remote(boost::asio::ip::make_address("172.1.1.2"), 51235);
|
||||
|
||||
PublicKey const key{std::get<0>(randomKeyPair(KeyType::Ed25519))};
|
||||
auto consumer = overlay.resourceManager().newInboundEndpoint(remote);
|
||||
auto [slot, _] = overlay.peerFinder().newInboundSlot(local, remote);
|
||||
|
||||
auto peer = std::make_shared<PeerTest>(
|
||||
env.app(),
|
||||
slot,
|
||||
std::move(request),
|
||||
key,
|
||||
protocolVersion,
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay);
|
||||
|
||||
overlay.addActive(peer);
|
||||
return peer;
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/peerfinder/Slot.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
/**
|
||||
* Test peer that captures sent messages for verification.
|
||||
*/
|
||||
class PeerTest : public PeerImp
|
||||
{
|
||||
inline static Peer::id_t id{};
|
||||
std::shared_ptr<Message> lastSentMessage_;
|
||||
std::optional<bool> compressionEnabled_;
|
||||
std::optional<bool> reduceRelayEnabled_;
|
||||
|
||||
public:
|
||||
using MiddleType = boost::beast::tcp_stream;
|
||||
using SharedContext = std::shared_ptr<boost::asio::ssl::context>;
|
||||
using StreamType = boost::beast::ssl_stream<MiddleType>;
|
||||
|
||||
PeerTest(
|
||||
Application& app,
|
||||
std::shared_ptr<peer_finder::Slot> const& slot,
|
||||
http_request_type&& request,
|
||||
PublicKey const& publicKey,
|
||||
ProtocolVersion protocol,
|
||||
resource::Consumer consumer,
|
||||
std::unique_ptr<StreamType>&& streamPtr,
|
||||
OverlayImpl& overlay);
|
||||
|
||||
~PeerTest() override = default;
|
||||
|
||||
void
|
||||
run() override;
|
||||
|
||||
void
|
||||
send(std::shared_ptr<Message> const& m) override;
|
||||
|
||||
std::shared_ptr<Message>
|
||||
getLastSentMessage() const;
|
||||
|
||||
// Synchronous test access to the JobQueue-dispatched processor.
|
||||
// The production path runs this on JtLedgerReq; tests need a
|
||||
// synchronous entry point to inspect the reply via send().
|
||||
// PeerImp::processGetObjectByHash is `protected` so the derived
|
||||
// test subclass can call it directly.
|
||||
void
|
||||
runProcessGetObjectByHash(std::shared_ptr<protocol::TMGetObjectByHash> const& m);
|
||||
|
||||
void
|
||||
runProcessLedgerRequest(
|
||||
std::shared_ptr<protocol::TMGetLedger> const& m,
|
||||
std::vector<SHAMapNodeID> nodeIDs);
|
||||
|
||||
resource::Charge
|
||||
getCurrentFeeCharge() const;
|
||||
|
||||
static void
|
||||
resetId();
|
||||
|
||||
bool
|
||||
compressionEnabled() const override;
|
||||
|
||||
void
|
||||
compressionEnabled(std::optional<bool> enabled);
|
||||
|
||||
bool
|
||||
txReduceRelayEnabled() const override;
|
||||
|
||||
void
|
||||
txReduceRelayEnabled(std::optional<bool> enabled);
|
||||
};
|
||||
|
||||
std::shared_ptr<PeerTest>
|
||||
makePeerTest(
|
||||
jtx::Env& env,
|
||||
PeerTest::SharedContext const& context,
|
||||
ProtocolVersion protocolVersion);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -1,19 +1,30 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/overlay/CapturePeer.h>
|
||||
#include <test/overlay/PeerTest.h>
|
||||
|
||||
#include <xrpld/overlay/Compression.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/resource/Charge.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -22,23 +33,8 @@ using namespace jtx;
|
||||
|
||||
class TMGetLedger_test : public beast::unit_test::Suite
|
||||
{
|
||||
/**
|
||||
* Calls the JtLedgerReq-dispatched processor synchronously, so the reply is
|
||||
* visible through `lastSent()`.
|
||||
*/
|
||||
class GetLedgerPeer : public CapturePeer
|
||||
{
|
||||
public:
|
||||
using CapturePeer::CapturePeer;
|
||||
|
||||
void
|
||||
runProcessLedgerRequest(
|
||||
std::shared_ptr<protocol::TMGetLedger> const& m,
|
||||
std::vector<SHAMapNodeID> nodeIDs)
|
||||
{
|
||||
processLedgerRequest(m, std::move(nodeIDs));
|
||||
}
|
||||
};
|
||||
PeerTest::SharedContext context_{makeSslContext("")};
|
||||
ProtocolVersion protocolVersion_{1, 7};
|
||||
|
||||
// Build a well-formed TMGetLedger node request carrying `numNodeIds` node
|
||||
// IDs.
|
||||
@@ -68,16 +64,17 @@ class TMGetLedger_test : public beast::unit_test::Suite
|
||||
testcase("Node ID Count Accepted");
|
||||
|
||||
Env env{*this};
|
||||
PeerTest::resetId();
|
||||
|
||||
auto peer = makeCapturePeer(env);
|
||||
auto peer = makePeerTest(env, context_, protocolVersion_);
|
||||
peer->onMessage(createRequest(numNodeIds));
|
||||
|
||||
// A request outside the accepted node-ID count is charged kFeeInvalidData; one inside
|
||||
// it is not. The JobQueue handler may run concurrently and update the fee in the
|
||||
// accepted case.
|
||||
BEAST_EXPECT(
|
||||
expectRejected ? (peer->feeCharge() == resource::kFeeInvalidData)
|
||||
: !(peer->feeCharge() == resource::kFeeInvalidData));
|
||||
expectRejected ? (peer->getCurrentFeeCharge() == resource::kFeeInvalidData)
|
||||
: !(peer->getCurrentFeeCharge() == resource::kFeeInvalidData));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -87,8 +84,9 @@ class TMGetLedger_test : public beast::unit_test::Suite
|
||||
|
||||
Env env{*this};
|
||||
env.close();
|
||||
PeerTest::resetId();
|
||||
|
||||
auto peer = makeCapturePeer<GetLedgerPeer>(env);
|
||||
auto peer = makePeerTest(env, context_, protocolVersion_);
|
||||
|
||||
// Ask for the account-state root node of the closed ledger.
|
||||
auto request = createRequest(numNodeIds);
|
||||
@@ -98,7 +96,7 @@ class TMGetLedger_test : public beast::unit_test::Suite
|
||||
|
||||
peer->runProcessLedgerRequest(request, std::vector<SHAMapNodeID>(numNodeIds));
|
||||
|
||||
auto sentMessage = peer->lastSent();
|
||||
auto sentMessage = peer->getLastSentMessage();
|
||||
BEAST_EXPECT(sentMessage != nullptr);
|
||||
if (!sentMessage)
|
||||
{
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/overlay/CapturePeer.h>
|
||||
#include <test/overlay/PeerTest.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/overlay/Compression.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <cstddef>
|
||||
@@ -32,21 +41,8 @@ using namespace jtx;
|
||||
*/
|
||||
class TMGetObjectByHash_test : public beast::unit_test::Suite
|
||||
{
|
||||
/**
|
||||
* Calls the JtLedgerReq-dispatched processor synchronously, so the reply is
|
||||
* visible through `sent()`.
|
||||
*/
|
||||
class GetObjectPeer : public CapturePeer
|
||||
{
|
||||
public:
|
||||
using CapturePeer::CapturePeer;
|
||||
|
||||
void
|
||||
runProcessGetObjectByHash(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
|
||||
{
|
||||
processGetObjectByHash(m);
|
||||
}
|
||||
};
|
||||
PeerTest::SharedContext context_{makeSslContext("")};
|
||||
ProtocolVersion protocolVersion_{1, 7};
|
||||
|
||||
static std::shared_ptr<protocol::TMGetObjectByHash>
|
||||
createRequest(size_t const numObjects, Env& env)
|
||||
@@ -94,13 +90,15 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite
|
||||
testcase("Reply Object Count");
|
||||
|
||||
Env env(*this);
|
||||
auto peer = makeCapturePeer<GetObjectPeer>(env);
|
||||
PeerTest::resetId();
|
||||
|
||||
auto peer = makePeerTest(env, context_, protocolVersion_);
|
||||
|
||||
auto request = createRequest(numObjects, env);
|
||||
peer->runProcessGetObjectByHash(request);
|
||||
|
||||
// Verify that a reply was sent
|
||||
auto sentMessage = peer->lastSent();
|
||||
auto sentMessage = peer->getLastSentMessage();
|
||||
BEAST_EXPECT(sentMessage != nullptr);
|
||||
|
||||
// Parse the reply message
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/overlay/CapturePeer.h>
|
||||
#include <test/overlay/PeerTest.h>
|
||||
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <memory>
|
||||
@@ -15,14 +26,18 @@ using namespace jtx;
|
||||
|
||||
class TMTransaction_test : public beast::unit_test::Suite
|
||||
{
|
||||
PeerTest::SharedContext context_{makeSslContext("")};
|
||||
ProtocolVersion protocolVersion_{1, 7};
|
||||
|
||||
void
|
||||
testFailureDeserializingTransactionIsCharged()
|
||||
{
|
||||
testcase("Undeserializable Transaction Is Charged");
|
||||
|
||||
Env env{*this, envconfig()};
|
||||
PeerTest::resetId();
|
||||
|
||||
auto peer = makeCapturePeer(env);
|
||||
auto peer = makePeerTest(env, context_, protocolVersion_);
|
||||
auto tx = std::make_shared<protocol::TMTransaction>();
|
||||
tx->set_status(protocol::tsNEW);
|
||||
|
||||
@@ -30,7 +45,7 @@ class TMTransaction_test : public beast::unit_test::Suite
|
||||
tx->set_rawtransaction("\x01\x02\x03", 3);
|
||||
|
||||
peer->onMessage(tx);
|
||||
BEAST_EXPECT(peer->feeCharge() == resource::kFeeInvalidData);
|
||||
BEAST_EXPECT(peer->getCurrentFeeCharge() == resource::kFeeInvalidData);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
#include <test/jtx/CheckMessageLogs.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/overlay/CapturePeer.h>
|
||||
#include <test/overlay/PeerTest.h>
|
||||
|
||||
#include <xrpld/overlay/ReduceRelayCommon.h>
|
||||
#include <xrpld/overlay/detail/Handshake.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
#include <xrpld/overlay/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
@@ -23,6 +30,9 @@ using namespace jtx;
|
||||
|
||||
class TMTransactions_test : public beast::unit_test::Suite
|
||||
{
|
||||
PeerTest::SharedContext context_{makeSslContext("")};
|
||||
ProtocolVersion protocolVersion_{1, 7};
|
||||
|
||||
static std::shared_ptr<protocol::TMTransactions>
|
||||
createRequest(std::size_t const numTransactions)
|
||||
{
|
||||
@@ -45,17 +55,13 @@ class TMTransactions_test : public beast::unit_test::Suite
|
||||
*this,
|
||||
envconfig(),
|
||||
std::make_unique<CheckMessageLogs>(kLimitExceededMessage, &foundExpectedLog)};
|
||||
PeerTest::resetId();
|
||||
|
||||
// `PeerImp` decides `txReduceRelayEnabled()` in its constructor, from
|
||||
// the config and the handshake header, so set this first.
|
||||
env.app().config().txReduceRelayEnable = true;
|
||||
http_request_type request;
|
||||
request.insert("X-Protocol-Ctl", makeFeaturesRequestHeader(false, false, true, false));
|
||||
|
||||
auto peer = makeCapturePeer(env, std::nullopt, std::move(request));
|
||||
auto peer = makePeerTest(env, context_, protocolVersion_);
|
||||
peer->txReduceRelayEnabled(true);
|
||||
peer->onMessage(createRequest(numTransactions));
|
||||
|
||||
auto fee = peer->feeCharge();
|
||||
auto fee = peer->getCurrentFeeCharge();
|
||||
if (expectRejected)
|
||||
{
|
||||
BEAST_EXPECT(fee == resource::kFeeMalformedRequest);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/PeerStub.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
@@ -13,8 +12,10 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/net/IPAddress.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
@@ -26,6 +27,7 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
@@ -65,16 +67,16 @@ static constexpr std::uint32_t kMaxMessages = 200000;
|
||||
/**
|
||||
* Simulate two entities - peer directly connected to the server
|
||||
* (via squelch in PeerSim) and PeerImp (via Overlay)
|
||||
*
|
||||
* `PeerStub` supplies the rest of the `Peer` interface as no-ops.
|
||||
*/
|
||||
class PeerPartial : public PeerStub
|
||||
class PeerPartial : public Peer
|
||||
{
|
||||
public:
|
||||
using PeerStub::PeerStub;
|
||||
// Keep the base overload visible; the one below would otherwise hide it.
|
||||
using PeerStub::send;
|
||||
PeerPartial() : nodePublicKey(derivePublicKey(KeyType::Ed25519, randomSecretKey()))
|
||||
{
|
||||
}
|
||||
|
||||
PublicKey nodePublicKey;
|
||||
~PeerPartial() override = default;
|
||||
virtual void
|
||||
onMessage(MessageSPtr const& m, SquelchCB f) = 0;
|
||||
virtual void
|
||||
@@ -84,6 +86,111 @@ public:
|
||||
{
|
||||
onMessage(squelch);
|
||||
}
|
||||
|
||||
// dummy implementation
|
||||
void
|
||||
send(std::shared_ptr<Message> const& m) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] beast::ip::Endpoint
|
||||
getRemoteAddress() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
void
|
||||
charge(resource::Charge const& fee, std::string const& context = {}) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
cluster() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
isHighLatency() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] int
|
||||
getScore(bool) const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
[[nodiscard]] PublicKey const&
|
||||
getNodePublic() const override
|
||||
{
|
||||
return nodePublicKey;
|
||||
}
|
||||
json::Value
|
||||
json() override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
supportsFeature(ProtocolFeature f) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] std::optional<std::size_t>
|
||||
publisherListSequence(PublicKey const&) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
void
|
||||
setPublisherListSequence(PublicKey const&, std::size_t const) override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] uint256
|
||||
getClosedLedgerHash() const override
|
||||
{
|
||||
static uint256 const kHash{};
|
||||
return kHash;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
hasLedger(uint256 const& hash, std::uint32_t seq) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void
|
||||
ledgerRange(std::uint32_t& minSeq, std::uint32_t& maxSeq) const override
|
||||
{
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
hasTxSet(uint256 const& hash) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void
|
||||
cycleStatus() override
|
||||
{
|
||||
}
|
||||
bool
|
||||
hasRange(std::uint32_t uMin, std::uint32_t uMax) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
compressionEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
txReduceRelayEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void
|
||||
sendTxQueue() override
|
||||
{
|
||||
}
|
||||
void
|
||||
addTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
void
|
||||
removeTxQueue(uint256 const&) override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -359,13 +466,24 @@ class PeerSim : public PeerPartial, public std::enable_shared_from_this<PeerSim>
|
||||
{
|
||||
public:
|
||||
using id_t = Peer::id_t;
|
||||
PeerSim(Overlay& overlay, beast::Journal journal)
|
||||
: PeerPartial(sid++), overlay_(overlay), squelch_(journal)
|
||||
PeerSim(Overlay& overlay, beast::Journal journal) : overlay_(overlay), squelch_(journal)
|
||||
{
|
||||
}
|
||||
|
||||
~PeerSim() override = default;
|
||||
|
||||
id_t
|
||||
id() const override
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
fingerprint() const override
|
||||
{
|
||||
return fingerprint_;
|
||||
}
|
||||
|
||||
static void
|
||||
resetId()
|
||||
{
|
||||
@@ -407,6 +525,8 @@ public:
|
||||
|
||||
private:
|
||||
inline static id_t sid = 0;
|
||||
std::string fingerprint_;
|
||||
id_t id_{sid++};
|
||||
Overlay& overlay_;
|
||||
reduce_relay::Squelch<ManualClock> squelch_;
|
||||
};
|
||||
|
||||
@@ -1,24 +1,38 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/noop.h>
|
||||
#include <test/overlay/CapturePeer.h>
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/overlay/Message.h>
|
||||
#include <xrpld/overlay/Peer.h>
|
||||
#include <xrpld/overlay/detail/Handshake.h>
|
||||
#include <xrpld/overlay/detail/OverlayImpl.h>
|
||||
#include <xrpld/overlay/detail/PeerImp.h>
|
||||
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/make_SSLContext.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/peerfinder/Slot.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/beast/core/multi_buffer.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/ssl/ssl_stream.hpp>
|
||||
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
@@ -33,6 +47,13 @@ namespace xrpl::test {
|
||||
|
||||
class tx_reduce_relay_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
using middle_type = boost::beast::tcp_stream;
|
||||
using stream_type = boost::beast::ssl_stream<middle_type>;
|
||||
using shared_context = std::shared_ptr<boost::asio::ssl::context>;
|
||||
|
||||
private:
|
||||
void
|
||||
doTest(std::string const& msg, bool log, std::function<void(bool)> f)
|
||||
{
|
||||
@@ -95,83 +116,107 @@ class tx_reduce_relay_test : public beast::unit_test::Suite
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts queued transaction hashes. Relayed messages are counted through
|
||||
* the inherited `sent()`.
|
||||
*/
|
||||
class TxReducePeer : public CapturePeer
|
||||
class PeerTest : public PeerImp
|
||||
{
|
||||
public:
|
||||
using CapturePeer::CapturePeer;
|
||||
PeerTest(
|
||||
Application& app,
|
||||
std::shared_ptr<peer_finder::Slot> const& slot,
|
||||
http_request_type&& request,
|
||||
PublicKey const& publicKey,
|
||||
ProtocolVersion protocol,
|
||||
resource::Consumer consumer,
|
||||
std::unique_ptr<tx_reduce_relay_test::stream_type>&& streamPtr,
|
||||
OverlayImpl& overlay)
|
||||
: PeerImp(
|
||||
app,
|
||||
sid,
|
||||
slot,
|
||||
std::move(request),
|
||||
publicKey,
|
||||
protocol,
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay)
|
||||
{
|
||||
sid++;
|
||||
}
|
||||
~PeerTest() override = default;
|
||||
|
||||
void
|
||||
addTxQueue(uint256 const&) override
|
||||
run() override
|
||||
{
|
||||
++queued_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of transaction hashes queued for this peer.
|
||||
*/
|
||||
std::size_t
|
||||
queued() const
|
||||
void
|
||||
send(std::shared_ptr<Message> const&) override
|
||||
{
|
||||
return queued_;
|
||||
sendTx++;
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t queued_{0};
|
||||
void
|
||||
addTxQueue(uint256 const& hash) override
|
||||
{
|
||||
queueTx++;
|
||||
}
|
||||
static void
|
||||
init()
|
||||
{
|
||||
queueTx = 0;
|
||||
sendTx = 0;
|
||||
sid = 0;
|
||||
}
|
||||
inline static std::size_t sid = 0;
|
||||
inline static std::uint16_t queueTx = 0;
|
||||
inline static std::uint16_t sendTx = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build one peer and register it with the overlay.
|
||||
*
|
||||
* The first `nDisabled` peers get no `X-Protocol-Ctl` header, which leaves
|
||||
* tx reduce-relay disabled on them. Built first, they sit at the front of
|
||||
* `peers`, where `testRelay`'s skip set expects them.
|
||||
*
|
||||
* @param env The environment owning the overlay.
|
||||
* @param peers Receives the peer; the overlay holds only a weak
|
||||
* pointer, so the caller keeps it alive.
|
||||
* @param nDisabled How many more peers to leave disabled; decremented
|
||||
* per peer built.
|
||||
*/
|
||||
void
|
||||
addPeer(
|
||||
jtx::Env& env,
|
||||
std::vector<std::shared_ptr<TxReducePeer>>& peers,
|
||||
std::uint16_t& nDisabled)
|
||||
std::uint16_t lid_{0};
|
||||
std::uint16_t rid_{1};
|
||||
shared_context context_;
|
||||
ProtocolVersion protocolVersion_;
|
||||
boost::beast::multi_buffer readBuf_;
|
||||
|
||||
public:
|
||||
tx_reduce_relay_test() : context_(makeSslContext("")), protocolVersion_{1, 7}
|
||||
{
|
||||
auto& overlay = dynamic_cast<OverlayImpl&>(env.app().getOverlay());
|
||||
PublicKey const key(std::get<0>(randomKeyPair(KeyType::Ed25519)));
|
||||
|
||||
bool const disabled = nDisabled > 0;
|
||||
if (disabled)
|
||||
--nDisabled;
|
||||
|
||||
http_request_type request;
|
||||
if (!disabled)
|
||||
request.insert("X-Protocol-Ctl", makeFeaturesRequestHeader(false, false, true, false));
|
||||
|
||||
BEAST_EXPECT(overlay.findPeerByPublicKey(key) == std::shared_ptr<PeerImp>{});
|
||||
auto const peer = makeCapturePeer<TxReducePeer>(env, key, std::move(request));
|
||||
BEAST_EXPECT(overlay.findPeerByPublicKey(key) == peer);
|
||||
peers.emplace_back(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relay one transaction to `nPeers` peers and check the split.
|
||||
*
|
||||
* @param test The testcase name.
|
||||
* @param txRREnabled The `tx_enable` config value.
|
||||
* @param nPeers How many peers to attach to the overlay.
|
||||
* @param nDisabled How many of those peers have reduce-relay disabled.
|
||||
* @param minPeers The `tx_min_peers` config value.
|
||||
* @param relayPercentage The `tx_relay_percentage` config value.
|
||||
* @param expectRelay The expected number of peers relayed to.
|
||||
* @param expectQueue The expected number of peers queued for.
|
||||
* @param nSkip How many of the first-built peers to skip.
|
||||
*/
|
||||
private:
|
||||
void
|
||||
addPeer(jtx::Env& env, std::vector<std::shared_ptr<PeerTest>>& peers, std::uint16_t& nDisabled)
|
||||
{
|
||||
auto& overlay = dynamic_cast<OverlayImpl&>(env.app().getOverlay());
|
||||
boost::beast::http::request<boost::beast::http::dynamic_body> request;
|
||||
(nDisabled == 0)
|
||||
? request.insert("X-Protocol-Ctl", makeFeaturesRequestHeader(false, false, true, false))
|
||||
: (void)nDisabled--;
|
||||
auto streamPtr = std::make_unique<stream_type>(
|
||||
socket_type(std::forward<boost::asio::io_context&>(env.app().getIOContext())),
|
||||
*context_);
|
||||
beast::ip::Endpoint const local(
|
||||
boost::asio::ip::make_address("172.1.1." + std::to_string(lid_)));
|
||||
beast::ip::Endpoint const remote(
|
||||
boost::asio::ip::make_address("172.1.1." + std::to_string(rid_)));
|
||||
PublicKey const key(std::get<0>(randomKeyPair(KeyType::Ed25519)));
|
||||
auto consumer = overlay.resourceManager().newInboundEndpoint(remote);
|
||||
auto [slot, _] = overlay.peerFinder().newInboundSlot(local, remote);
|
||||
auto const peer = std::make_shared<PeerTest>(
|
||||
env.app(),
|
||||
slot,
|
||||
std::move(request),
|
||||
key,
|
||||
protocolVersion_,
|
||||
consumer,
|
||||
std::move(streamPtr),
|
||||
overlay);
|
||||
BEAST_EXPECT(overlay.findPeerByPublicKey(key) == std::shared_ptr<PeerImp>{});
|
||||
overlay.addActive(peer);
|
||||
BEAST_EXPECT(overlay.findPeerByPublicKey(key) == peer);
|
||||
peers.emplace_back(peer); // overlay stores week ptr to PeerImp
|
||||
lid_ += 2;
|
||||
rid_ += 2;
|
||||
assert(lid_ <= 254);
|
||||
}
|
||||
|
||||
void
|
||||
testRelay(
|
||||
std::string const& test,
|
||||
@@ -182,30 +227,20 @@ class tx_reduce_relay_test : public beast::unit_test::Suite
|
||||
std::uint16_t relayPercentage,
|
||||
std::uint16_t expectRelay,
|
||||
std::uint16_t expectQueue,
|
||||
std::size_t nSkip = 0)
|
||||
std::set<Peer::id_t> const& toSkip = {})
|
||||
{
|
||||
testcase(test);
|
||||
jtx::Env env(*this);
|
||||
std::vector<std::shared_ptr<TxReducePeer>> peers;
|
||||
// `PeerImp` decides `txReduceRelayEnabled()` in its constructor, from
|
||||
// the config and the handshake header, so set these first.
|
||||
std::vector<std::shared_ptr<PeerTest>> peers;
|
||||
env.app().config().txReduceRelayEnable = txRREnabled;
|
||||
env.app().config().txReduceRelayMinPeers = minPeers;
|
||||
env.app().config().txRelayPercentage = relayPercentage;
|
||||
PeerTest::init();
|
||||
lid_ = 0;
|
||||
rid_ = 0;
|
||||
for (int i = 0; i < nPeers; i++)
|
||||
addPeer(env, peers, nDisabled);
|
||||
|
||||
// An under-filled skip set would also fail the relay counts below, for
|
||||
// a reason that looks unrelated.
|
||||
if (!BEAST_EXPECT(nSkip <= peers.size()))
|
||||
return;
|
||||
|
||||
// Skip the peers built first, so the skip set overlaps the disabled
|
||||
// peers as the expected counts assume.
|
||||
std::set<Peer::id_t> toSkip;
|
||||
for (std::size_t i = 0; i < nSkip; ++i)
|
||||
toSkip.insert(peers[i]->id());
|
||||
|
||||
auto const jtx = env.jt(noop(env.master));
|
||||
if (BEAST_EXPECT(jtx.stx))
|
||||
{
|
||||
@@ -216,15 +251,7 @@ class tx_reduce_relay_test : public beast::unit_test::Suite
|
||||
m.set_deferred(false);
|
||||
m.set_status(protocol::TransactionStatus::tsNEW);
|
||||
env.app().getOverlay().relay(uint256{0}, m, toSkip);
|
||||
|
||||
std::size_t sendTx = 0;
|
||||
std::size_t queueTx = 0;
|
||||
for (auto const& peer : peers)
|
||||
{
|
||||
sendTx += peer->sent().size();
|
||||
queueTx += peer->queued();
|
||||
}
|
||||
BEAST_EXPECT(sendTx == expectRelay && queueTx == expectQueue);
|
||||
BEAST_EXPECT(PeerTest::sendTx == expectRelay && PeerTest::queueTx == expectQueue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,11 +259,12 @@ class tx_reduce_relay_test : public beast::unit_test::Suite
|
||||
run() override
|
||||
{
|
||||
bool const log = false;
|
||||
std::set<Peer::id_t> skip = {0, 1, 2, 3, 4};
|
||||
testConfig(log);
|
||||
// relay to all peers, no hash queue
|
||||
testRelay("feature disabled", false, 10, 0, 10, 25, 10, 0);
|
||||
// relay to nPeers - skip (10-5=5)
|
||||
testRelay("feature disabled & skip", false, 10, 0, 10, 25, 5, 0, 5);
|
||||
testRelay("feature disabled & skip", false, 10, 0, 10, 25, 5, 0, skip);
|
||||
// relay to all peers because min is greater than nPeers
|
||||
testRelay("relay all 1", true, 10, 0, 20, 25, 10, 0);
|
||||
// relay to all peers because min + disabled is greater thant nPeers
|
||||
@@ -247,22 +275,24 @@ class tx_reduce_relay_test : public beast::unit_test::Suite
|
||||
// relay to minPeers + 25% of (nPeers - nPeers) - skip
|
||||
// (20+0.25*(60-20)-5=25), queue the rest, skip counts towards relayed
|
||||
// (60-25-5=30)
|
||||
testRelay("skip", true, 60, 0, 20, 25, 25, 30, 5);
|
||||
testRelay("skip", true, 60, 0, 20, 25, 25, 30, skip);
|
||||
// relay to minPeers + disabled + 25% of (nPeers - minPeers - disabled)
|
||||
// (20+10+0.25*(70-20-10)=40), queue the rest (30)
|
||||
testRelay("disabled", true, 70, 10, 20, 25, 40, 30);
|
||||
// relay to minPeers + disabled-not-in-skip + 25% of (nPeers - minPeers
|
||||
// - disabled) (20+5+0.25*(70-20-10)=35), queue the rest, skip counts
|
||||
// towards relayed (70-35-5=30))
|
||||
testRelay("disabled & skip", true, 70, 10, 20, 25, 35, 30, 5);
|
||||
testRelay("disabled & skip", true, 70, 10, 20, 25, 35, 30, skip);
|
||||
// relay to minPeers + disabled + 25% of (nPeers - minPeers - disabled)
|
||||
// - skip (10+5+0.25*(15-10-5)-10=5), queue the rest, skip counts
|
||||
// towards relayed (15-5-10=0)
|
||||
testRelay("disabled & skip, no queue", true, 15, 5, 10, 25, 5, 0, 10);
|
||||
skip = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
testRelay("disabled & skip, no queue", true, 15, 5, 10, 25, 5, 0, skip);
|
||||
// relay to minPeers + disabled + 25% of (nPeers - minPeers - disabled)
|
||||
// - skip (10+2+0.25*(20-10-2)-14=0), queue the rest, skip counts
|
||||
// towards relayed (20-14=6)
|
||||
testRelay("disabled & skip, no relay", true, 20, 2, 10, 25, 0, 6, 14);
|
||||
skip = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
|
||||
testRelay("disabled & skip, no relay", true, 20, 2, 10, 25, 0, 6, skip);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -13,21 +13,17 @@
|
||||
#include <test/jtx/ter.h>
|
||||
|
||||
#include <xrpld/app/misc/TxQ.h>
|
||||
#include <xrpld/rpc/CTID.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/config/Constants.h>
|
||||
#include <xrpl/core/NetworkIDService.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -811,95 +807,6 @@ class LedgerRPC_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLedgerExpandedTransactionsCTID()
|
||||
{
|
||||
testcase("Expanded Transactions CTID");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
uint32_t const netID = env.app().getNetworkIDService().getNetworkID();
|
||||
|
||||
// API v2 non-binary: CTID present
|
||||
{
|
||||
json::Value jvParams;
|
||||
jvParams[jss::ledger_index] = "validated";
|
||||
jvParams[jss::transactions] = true;
|
||||
jvParams[jss::expand] = true;
|
||||
jvParams[jss::api_version] = 2;
|
||||
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "success");
|
||||
auto const& txns = jrr[jss::ledger][jss::transactions];
|
||||
BEAST_EXPECT(txns.isArray() && txns.size() > 0);
|
||||
for (auto const& txn : txns)
|
||||
{
|
||||
BEAST_EXPECT(txn.isMember(jss::ctid));
|
||||
auto const expectedCtid = rpc::encodeCTID(
|
||||
jrr[jss::ledger][jss::ledger_index].asUInt(),
|
||||
txn[jss::meta][sfTransactionIndex.jsonName].asUInt(),
|
||||
netID);
|
||||
// NOLINTBEGIN(bugprone-unchecked-optional-access)
|
||||
if (BEAST_EXPECT(expectedCtid.has_value()))
|
||||
BEAST_EXPECT(txn[jss::ctid] == expectedCtid.value());
|
||||
// NOLINTEND(bugprone-unchecked-optional-access)
|
||||
}
|
||||
}
|
||||
|
||||
// API v1 non-binary: CTID present
|
||||
{
|
||||
json::Value jvParams;
|
||||
jvParams[jss::ledger_index] = "validated";
|
||||
jvParams[jss::transactions] = true;
|
||||
jvParams[jss::expand] = true;
|
||||
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "success");
|
||||
auto const& txns = jrr[jss::ledger][jss::transactions];
|
||||
BEAST_EXPECT(txns.isArray() && txns.size() > 0);
|
||||
for (auto const& txn : txns)
|
||||
{
|
||||
BEAST_EXPECT(txn.isMember(jss::ctid));
|
||||
}
|
||||
}
|
||||
|
||||
// Binary expanded: CTID present
|
||||
{
|
||||
json::Value jvParams;
|
||||
jvParams[jss::ledger_index] = "validated";
|
||||
jvParams[jss::transactions] = true;
|
||||
jvParams[jss::expand] = true;
|
||||
jvParams[jss::binary] = true;
|
||||
jvParams[jss::api_version] = 2;
|
||||
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "success");
|
||||
auto const& txns = jrr[jss::ledger][jss::transactions];
|
||||
BEAST_EXPECT(txns.isArray() && txns.size() > 0);
|
||||
for (auto const& txn : txns)
|
||||
{
|
||||
BEAST_EXPECT(txn.isMember(jss::ctid));
|
||||
}
|
||||
}
|
||||
|
||||
// Non-expanded: transactions are plain hash strings, no CTID
|
||||
{
|
||||
json::Value jvParams;
|
||||
jvParams[jss::ledger_index] = "validated";
|
||||
jvParams[jss::transactions] = true;
|
||||
jvParams[jss::api_version] = 2;
|
||||
auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::status] == "success");
|
||||
auto const& txns = jrr[jss::ledger][jss::transactions];
|
||||
BEAST_EXPECT(txns.isArray() && txns.size() > 0);
|
||||
for (auto const& txn : txns)
|
||||
{
|
||||
BEAST_EXPECT(txn.isString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -915,7 +822,6 @@ public:
|
||||
testNoQueue();
|
||||
testQueue();
|
||||
testLedgerAccountsOption();
|
||||
testLedgerExpandedTransactionsCTID();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,20 +2,104 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/JTx.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/jtx/pay.h>
|
||||
|
||||
#include <xrpld/core/Config.h>
|
||||
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/config/Constants.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
class Submit_test : public beast::unit_test::Suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testAugmentedFields()
|
||||
{
|
||||
testcase("Augmented fields in sign-and-submit mode");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
// Enable signing support in config
|
||||
Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
static std::string const kSigningSupportCfg =
|
||||
std::string("[") + Sections::kSigningSupport + "]\ntrue";
|
||||
cfg->loadFromString(kSigningSupportCfg);
|
||||
return cfg;
|
||||
})};
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// Test 1: Sign-and-submit mode should return augmented fields
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::tx_json][jss::TransactionType] = jss::Payment;
|
||||
jv[jss::tx_json][jss::Account] = alice.human();
|
||||
jv[jss::tx_json][jss::Destination] = bob.human();
|
||||
jv[jss::tx_json][jss::Amount] = XRP(100).value().getJson();
|
||||
jv[jss::secret] = alice.name();
|
||||
|
||||
auto const result = env.rpc("json", "submit", to_string(jv))[jss::result];
|
||||
|
||||
// These are the augmented fields that should be present
|
||||
BEAST_EXPECT(result.isMember(jss::engine_result));
|
||||
BEAST_EXPECT(result.isMember(jss::engine_result_code));
|
||||
BEAST_EXPECT(result.isMember(jss::engine_result_message));
|
||||
|
||||
// New augmented fields from issue #3125
|
||||
BEAST_EXPECT(result.isMember(jss::accepted));
|
||||
BEAST_EXPECT(result.isMember(jss::applied));
|
||||
BEAST_EXPECT(result.isMember(jss::broadcast));
|
||||
BEAST_EXPECT(result.isMember(jss::queued));
|
||||
BEAST_EXPECT(result.isMember(jss::kept));
|
||||
|
||||
// Current ledger state fields
|
||||
BEAST_EXPECT(result.isMember(jss::account_sequence_next));
|
||||
BEAST_EXPECT(result.isMember(jss::account_sequence_available));
|
||||
BEAST_EXPECT(result.isMember(jss::open_ledger_cost));
|
||||
BEAST_EXPECT(result.isMember(jss::validated_ledger_index));
|
||||
|
||||
// Verify basic transaction fields
|
||||
BEAST_EXPECT(result.isMember(jss::tx_blob));
|
||||
BEAST_EXPECT(result.isMember(jss::tx_json));
|
||||
}
|
||||
|
||||
// Test 2: Binary blob mode should also return augmented fields (regression test)
|
||||
{
|
||||
auto jt = env.jt(pay(alice, bob, XRP(100)));
|
||||
Serializer s;
|
||||
jt.stx->add(s);
|
||||
|
||||
auto const result = env.rpc("submit", strHex(s.slice()))[jss::result];
|
||||
|
||||
// Verify augmented fields are present in binary mode too
|
||||
BEAST_EXPECT(result.isMember(jss::engine_result));
|
||||
BEAST_EXPECT(result.isMember(jss::accepted));
|
||||
BEAST_EXPECT(result.isMember(jss::applied));
|
||||
BEAST_EXPECT(result.isMember(jss::broadcast));
|
||||
BEAST_EXPECT(result.isMember(jss::queued));
|
||||
BEAST_EXPECT(result.isMember(jss::kept));
|
||||
BEAST_EXPECT(result.isMember(jss::account_sequence_next));
|
||||
BEAST_EXPECT(result.isMember(jss::account_sequence_available));
|
||||
BEAST_EXPECT(result.isMember(jss::open_ledger_cost));
|
||||
BEAST_EXPECT(result.isMember(jss::validated_ledger_index));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFailHardValidation()
|
||||
{
|
||||
@@ -89,6 +173,7 @@ public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testAugmentedFields();
|
||||
testFailHardValidation();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ TEST(MallocTrimReport, structure)
|
||||
}
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
TEST(ParseStatmRSSkB, standard_format)
|
||||
TEST(parseStatmRSSkB, standard_format)
|
||||
{
|
||||
using xrpl::detail::parseStatmRSSkB;
|
||||
|
||||
@@ -121,7 +121,7 @@ TEST(ParseStatmRSSkB, standard_format)
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(MallocTrim, without_debug_logging)
|
||||
TEST(mallocTrim, without_debug_logging)
|
||||
{
|
||||
beast::Journal const journal{beast::Journal::getNullSink()};
|
||||
|
||||
@@ -144,7 +144,7 @@ TEST(MallocTrim, without_debug_logging)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(MallocTrim, empty_tag)
|
||||
TEST(mallocTrim, empty_tag)
|
||||
{
|
||||
beast::Journal const journal{beast::Journal::getNullSink()};
|
||||
MallocTrimReport const report = mallocTrim("", journal);
|
||||
@@ -157,7 +157,7 @@ TEST(MallocTrim, empty_tag)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(MallocTrim, with_debug_logging)
|
||||
TEST(mallocTrim, with_debug_logging)
|
||||
{
|
||||
struct DebugSink : public beast::Journal::Sink
|
||||
{
|
||||
@@ -194,7 +194,7 @@ TEST(MallocTrim, with_debug_logging)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(MallocTrim, repeated_calls)
|
||||
TEST(mallocTrim, repeated_calls)
|
||||
{
|
||||
beast::Journal const journal{beast::Journal::getNullSink()};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(RangeSet, prev_missing)
|
||||
TEST(RangeSet, prevMissing)
|
||||
{
|
||||
// Set will include:
|
||||
// [ 0, 5]
|
||||
@@ -36,7 +36,7 @@ TEST(RangeSet, prev_missing)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RangeSet, to_string)
|
||||
TEST(RangeSet, toString)
|
||||
{
|
||||
RangeSet<std::uint32_t> set;
|
||||
EXPECT_EQ(to_string(set), "empty");
|
||||
@@ -54,7 +54,7 @@ TEST(RangeSet, to_string)
|
||||
EXPECT_EQ(to_string(set), "1-2,6");
|
||||
}
|
||||
|
||||
TEST(RangeSet, from_string)
|
||||
TEST(RangeSet, fromString)
|
||||
{
|
||||
RangeSet<std::uint32_t> set;
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ TEST_F(StringUtilitiesTest, to_string)
|
||||
EXPECT_EQ(result, "hello");
|
||||
}
|
||||
|
||||
TEST_F(StringUtilitiesTest, trim_whitespace)
|
||||
TEST_F(StringUtilitiesTest, trimWhitespace)
|
||||
{
|
||||
EXPECT_EQ(trimWhitespace(""), "");
|
||||
EXPECT_EQ(trimWhitespace(" "), "");
|
||||
@@ -303,7 +303,7 @@ TEST_F(StringUtilitiesTest, trim_whitespace)
|
||||
EXPECT_EQ(trimWhitespace(" a b\tc "), "a b\tc");
|
||||
}
|
||||
|
||||
TEST_F(StringUtilitiesTest, to_lower)
|
||||
TEST_F(StringUtilitiesTest, toLower)
|
||||
{
|
||||
EXPECT_EQ(toLower(""), "");
|
||||
EXPECT_EQ(toLower("ABC"), "abc");
|
||||
@@ -318,7 +318,7 @@ TEST_F(StringUtilitiesTest, to_lower)
|
||||
// Both helpers are documented as depending only on their input. Guard that by
|
||||
// checking the bytes just outside ASCII, which a locale-aware isspace/tolower
|
||||
// could classify differently.
|
||||
TEST_F(StringUtilitiesTest, trim_and_lower_ignore_locale)
|
||||
TEST_F(StringUtilitiesTest, trimAndLowerIgnoreLocale)
|
||||
{
|
||||
// 0xA0 is NO-BREAK SPACE in Latin-1 and is whitespace to some locales.
|
||||
std::string const nbsp("\xA0", 1);
|
||||
|
||||
@@ -14,7 +14,7 @@ check(std::string const& in, std::string const& out)
|
||||
EXPECT_EQ(base64Decode(encoded), in);
|
||||
}
|
||||
|
||||
TEST(Base64, base64)
|
||||
TEST(base64, base64)
|
||||
{
|
||||
// cspell: disable
|
||||
check("", "");
|
||||
|
||||
@@ -128,7 +128,7 @@ struct BaseUintTest : public ::testing::Test
|
||||
|
||||
using BaseUintDeathTest = BaseUintTest;
|
||||
|
||||
TEST_F(BaseUintDeathTest, from_raw_size_mismatch)
|
||||
TEST_F(BaseUintDeathTest, fromRaw_size_mismatch)
|
||||
{
|
||||
// ENABLE_VOIDSTAR is a debug build, but does not crash on failed asserts. Rather than twist
|
||||
// these tests into knots to make them work, just skip them.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(Contract, contract)
|
||||
TEST(contract, contract)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(MulDiv, mul_div)
|
||||
TEST(mulDiv, mulDiv)
|
||||
{
|
||||
auto const max = std::numeric_limits<std::uint64_t>::max();
|
||||
std::uint64_t const max32 = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(Scope, scope_exit)
|
||||
TEST(scope, ScopeExit)
|
||||
{
|
||||
// ScopeExit always executes the functor on destruction,
|
||||
// unless release() is called
|
||||
@@ -56,7 +56,7 @@ TEST(Scope, scope_exit)
|
||||
EXPECT_EQ(i, 5);
|
||||
}
|
||||
|
||||
TEST(Scope, scope_fail)
|
||||
TEST(scope, ScopeFail)
|
||||
{
|
||||
// ScopeFail executes the functor on destruction only
|
||||
// if an exception is unwinding, unless release() is called
|
||||
@@ -106,7 +106,7 @@ TEST(Scope, scope_fail)
|
||||
EXPECT_EQ(i, 5);
|
||||
}
|
||||
|
||||
TEST(Scope, scope_success)
|
||||
TEST(scope, ScopeSuccess)
|
||||
{
|
||||
// ScopeSuccess executes the functor on destruction only
|
||||
// if an exception is not unwinding, unless release() is called
|
||||
|
||||
@@ -105,7 +105,7 @@ static_assert(
|
||||
|
||||
using TagInt = TaggedInteger<std::int32_t, Tag1>;
|
||||
|
||||
TEST(TaggedInteger, comparison_operators)
|
||||
TEST(tagged_integer, comparison_operators)
|
||||
{
|
||||
TagInt const zero(0);
|
||||
TagInt const one(1);
|
||||
@@ -131,7 +131,7 @@ TEST(TaggedInteger, comparison_operators)
|
||||
EXPECT_FALSE(one <= zero);
|
||||
}
|
||||
|
||||
TEST(TaggedInteger, increment_decrement_operators)
|
||||
TEST(tagged_integer, increment_decrement_operators)
|
||||
{
|
||||
TagInt const zero(0);
|
||||
TagInt const one(1);
|
||||
@@ -146,7 +146,7 @@ TEST(TaggedInteger, increment_decrement_operators)
|
||||
EXPECT_EQ(a, zero);
|
||||
}
|
||||
|
||||
TEST(TaggedInteger, arithmetic_operators)
|
||||
TEST(tagged_integer, arithmetic_operators)
|
||||
{
|
||||
TagInt const a{-2};
|
||||
EXPECT_EQ(+a, TagInt{-2});
|
||||
@@ -166,7 +166,7 @@ TEST(TaggedInteger, arithmetic_operators)
|
||||
EXPECT_EQ((TagInt{16} >> TagInt{2}), TagInt{4});
|
||||
}
|
||||
|
||||
TEST(TaggedInteger, assignment_operators)
|
||||
TEST(tagged_integer, assignment_operators)
|
||||
{
|
||||
TagInt a{-2};
|
||||
TagInt b{0};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(Csprng, get_values)
|
||||
TEST(csprng, get_values)
|
||||
{
|
||||
auto& engine = cryptoPrng();
|
||||
auto randVal = engine();
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TEST(JsonValue, limits)
|
||||
TEST(json_value, limits)
|
||||
{
|
||||
using namespace json;
|
||||
static_assert(Value::kMinInt == Int(~(UInt(-1) / 2)));
|
||||
@@ -29,7 +29,7 @@ TEST(JsonValue, limits)
|
||||
static_assert(Value::kMaxUInt == UInt(-1));
|
||||
}
|
||||
|
||||
TEST(JsonValue, construct_and_compare_json_static_string)
|
||||
TEST(json_value, construct_and_compare_Json_StaticString)
|
||||
{
|
||||
static constexpr char kSample[]{"Contents of a json::StaticString"};
|
||||
|
||||
@@ -52,7 +52,7 @@ TEST(JsonValue, construct_and_compare_json_static_string)
|
||||
EXPECT_NE(kTest3, str);
|
||||
}
|
||||
|
||||
TEST(JsonValue, different_types)
|
||||
TEST(json_value, different_types)
|
||||
{
|
||||
// Exercise ValueType constructor
|
||||
static constexpr json::StaticString kStaticStr{"staticStr"};
|
||||
@@ -206,7 +206,7 @@ TEST(JsonValue, different_types)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, compare_strings)
|
||||
TEST(json_value, compare_strings)
|
||||
{
|
||||
auto doCompare = [&](json::Value const& lhs,
|
||||
json::Value const& rhs,
|
||||
@@ -560,7 +560,7 @@ TEST(JsonValue, compare_strings)
|
||||
#pragma pop_macro("DO_COMPARE")
|
||||
}
|
||||
|
||||
TEST(JsonValue, bool)
|
||||
TEST(json_value, bool)
|
||||
{
|
||||
EXPECT_FALSE(json::Value());
|
||||
|
||||
@@ -583,7 +583,7 @@ TEST(JsonValue, bool)
|
||||
EXPECT_TRUE(bool(object));
|
||||
}
|
||||
|
||||
TEST(JsonValue, bad_json)
|
||||
TEST(json_value, bad_json)
|
||||
{
|
||||
char const* s(R"({"method":"ledger","params":[{"ledger_index":1e300}]})");
|
||||
|
||||
@@ -607,7 +607,7 @@ parseValue(std::string const& doc)
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(JsonValue, parse_double_valid)
|
||||
TEST(json_value, parse_double_valid)
|
||||
{
|
||||
// 1e300 is large but still representable, so it parses (unlike the out-of-range cases below).
|
||||
for (auto const& [text, expected] :
|
||||
@@ -627,14 +627,14 @@ TEST(JsonValue, parse_double_valid)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, parse_double_out_of_range)
|
||||
TEST(json_value, parse_double_out_of_range)
|
||||
{
|
||||
// Magnitudes with no finite double representation are rejected.
|
||||
for (char const* oor : {"1e400", "-1e400", "0.001e500", "1e-400", "-1e-400", "123e-500"})
|
||||
EXPECT_FALSE(parseValue(oor).has_value()) << oor;
|
||||
}
|
||||
|
||||
TEST(JsonValue, parse_double_malformed)
|
||||
TEST(json_value, parse_double_malformed)
|
||||
{
|
||||
// readNumber() collects any run of digits and '.eE+-' into a single Double
|
||||
// token, so these malformed tokens reach decodeDouble. Each has a valid
|
||||
@@ -644,7 +644,7 @@ TEST(JsonValue, parse_double_malformed)
|
||||
EXPECT_FALSE(parseValue(bad).has_value()) << bad;
|
||||
}
|
||||
|
||||
TEST(JsonValue, edge_cases)
|
||||
TEST(json_value, edge_cases)
|
||||
{
|
||||
std::uint32_t const maxUInt = std::numeric_limits<std::uint32_t>::max();
|
||||
std::int32_t const maxInt = std::numeric_limits<std::int32_t>::max();
|
||||
@@ -791,7 +791,7 @@ TEST(JsonValue, edge_cases)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, copy)
|
||||
TEST(json_value, copy)
|
||||
{
|
||||
json::Value v1{2.5};
|
||||
EXPECT_TRUE(v1.isDouble());
|
||||
@@ -812,7 +812,7 @@ TEST(JsonValue, copy)
|
||||
EXPECT_EQ(v1, v2);
|
||||
}
|
||||
|
||||
TEST(JsonValue, move)
|
||||
TEST(json_value, move)
|
||||
{
|
||||
json::Value v1{2.5};
|
||||
EXPECT_TRUE(v1.isDouble());
|
||||
@@ -831,7 +831,7 @@ TEST(JsonValue, move)
|
||||
EXPECT_NE(v1, v2); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
|
||||
TEST(JsonValue, comparisons)
|
||||
TEST(json_value, comparisons)
|
||||
{
|
||||
json::Value a, b;
|
||||
auto testEquals = [&](std::string const& name) {
|
||||
@@ -886,7 +886,7 @@ TEST(JsonValue, comparisons)
|
||||
testGreaterThan("big");
|
||||
}
|
||||
|
||||
TEST(JsonValue, compact)
|
||||
TEST(json_value, compact)
|
||||
{
|
||||
json::Value j;
|
||||
json::Reader r;
|
||||
@@ -909,7 +909,7 @@ TEST(JsonValue, compact)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, conversions)
|
||||
TEST(json_value, conversions)
|
||||
{
|
||||
// We have json::ValueType::Real but json::Value::asDouble.
|
||||
// TODO: What's the thinking here?
|
||||
@@ -1125,7 +1125,7 @@ TEST(JsonValue, conversions)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, access_members)
|
||||
TEST(json_value, access_members)
|
||||
{
|
||||
json::Value val;
|
||||
EXPECT_EQ(val.type(), json::ValueType::Null);
|
||||
@@ -1218,7 +1218,7 @@ TEST(JsonValue, access_members)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, remove_members)
|
||||
TEST(json_value, remove_members)
|
||||
{
|
||||
json::Value val;
|
||||
EXPECT_EQ(val.removeMember(std::string("member")).type(), json::ValueType::Null);
|
||||
@@ -1245,7 +1245,7 @@ TEST(JsonValue, remove_members)
|
||||
EXPECT_EQ(val.size(), 0);
|
||||
}
|
||||
|
||||
TEST(JsonValue, iterator)
|
||||
TEST(json_value, iterator)
|
||||
{
|
||||
{
|
||||
// Iterating an array.
|
||||
@@ -1331,7 +1331,7 @@ TEST(JsonValue, iterator)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, nest_limits)
|
||||
TEST(json_value, nest_limits)
|
||||
{
|
||||
json::Reader r;
|
||||
{
|
||||
@@ -1377,7 +1377,7 @@ TEST(JsonValue, nest_limits)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, memory_leak)
|
||||
TEST(json_value, memory_leak)
|
||||
{
|
||||
// When run with the address sanitizer, this test confirms there is no
|
||||
// memory leak with the scenarios below.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(AMMEntryTests, constructors)
|
||||
TEST(AMMEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(AccountRootEntryTests, constructors)
|
||||
TEST(AccountRootEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(AmendmentsEntryTests, constructors)
|
||||
TEST(AmendmentsEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(BridgeEntryTests, constructors)
|
||||
TEST(BridgeEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(CheckEntryTests, constructors)
|
||||
TEST(CheckEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(CredentialEntryTests, constructors)
|
||||
TEST(CredentialEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(DIDEntryTests, constructors)
|
||||
TEST(DIDEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(DelegateEntryTests, constructors)
|
||||
TEST(DelegateEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(DepositPreauthEntryTests, constructors)
|
||||
TEST(DepositPreauthEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(DirectoryNodeEntryTests, constructors)
|
||||
TEST(DirectoryNodeEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(EscrowEntryTests, constructors)
|
||||
TEST(EscrowEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(FeeSettingsEntryTests, constructors)
|
||||
TEST(FeeSettingsEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(LedgerHashesEntryTests, constructors)
|
||||
TEST(LedgerHashesEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(LoanBrokerEntryTests, constructors)
|
||||
TEST(LoanBrokerEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(LoanEntryTests, constructors)
|
||||
TEST(LoanEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(MPTokenEntryTests, constructors)
|
||||
TEST(MPTokenEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(MPTokenIssuanceEntryTests, constructors)
|
||||
TEST(MPTokenIssuanceEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(NFTokenOfferEntryTests, constructors)
|
||||
TEST(NFTokenOfferEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(NFTokenPageEntryTests, constructors)
|
||||
TEST(NFTokenPageEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(NegativeUNLEntryTests, constructors)
|
||||
TEST(NegativeUNLEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(OfferEntryTests, constructors)
|
||||
TEST(OfferEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(OracleEntryTests, constructors)
|
||||
TEST(OracleEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(PayChannelEntryTests, constructors)
|
||||
TEST(PayChannelEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(PermissionedDomainEntryTests, constructors)
|
||||
TEST(PermissionedDomainEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(RippleStateEntryTests, constructors)
|
||||
TEST(RippleStateEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SLEBaseTests, read_only)
|
||||
TEST_F(SLEBaseTests, ReadOnly)
|
||||
{
|
||||
AccountRootEntryR const absent(bob_.id(), env_.getClosedLedger());
|
||||
EXPECT_FALSE(absent.exists());
|
||||
@@ -168,7 +168,7 @@ TEST_F(SLEBaseTests, read_only)
|
||||
EXPECT_EQ(&present.readView(), &env_.getClosedLedger());
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, adopt_sle)
|
||||
TEST_F(SLEBaseTests, AdoptSLE)
|
||||
{
|
||||
auto const sle = env_.getClosedLedger().read(keylet::account(alice_.id()));
|
||||
ASSERT_NE(sle, nullptr);
|
||||
@@ -201,7 +201,7 @@ TEST_F(SLEBaseTests, adopt_sle)
|
||||
"writable entries must not be constructible from a bare SLE");
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, writable_accessors)
|
||||
TEST_F(SLEBaseTests, WritableAccessors)
|
||||
{
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
beast::Journal const j{beast::Journal::getNullSink()};
|
||||
@@ -236,7 +236,7 @@ TEST_F(SLEBaseTests, writable_accessors)
|
||||
!HasApplyView<AccountRootEntryR>, "applyView() must not exist on a read-only entry");
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, apply_view_context_ctor)
|
||||
TEST_F(SLEBaseTests, ApplyViewContextCtor)
|
||||
{
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
beast::Journal const j{beast::Journal::getNullSink()};
|
||||
@@ -260,7 +260,7 @@ TEST_F(SLEBaseTests, apply_view_context_ctor)
|
||||
EXPECT_EQ(fromCtx.rawSle(), fromView.rawSle());
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, writable_lifecycle)
|
||||
TEST_F(SLEBaseTests, WritableLifecycle)
|
||||
{
|
||||
// A view we never apply, so nothing here reaches the ledger.
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
@@ -318,7 +318,7 @@ TEST_F(SLEBaseTests, writable_lifecycle)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, conversion)
|
||||
TEST_F(SLEBaseTests, Conversion)
|
||||
{
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
|
||||
@@ -336,7 +336,7 @@ TEST_F(SLEBaseTests, conversion)
|
||||
EXPECT_EQ(generic.type(), ltACCOUNT_ROOT);
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, resolve_entry_peeks)
|
||||
TEST_F(SLEBaseTests, ResolveEntryPeeks)
|
||||
{
|
||||
// getOpenLedger() is an OpenView, which derives from ReadView but not
|
||||
// from ApplyView, so resolveEntry's dynamic_cast fails and this takes
|
||||
@@ -368,7 +368,7 @@ TEST_F(SLEBaseTests, resolve_entry_peeks)
|
||||
EXPECT_EQ(readOnly->getFieldU32(sfSequence), bumped);
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, throws_on_missing_entry)
|
||||
TEST_F(SLEBaseTests, ThrowsOnMissingEntry)
|
||||
{
|
||||
// A generic read-only entry has no static type to fall back on, so
|
||||
// type() must read it off the (absent) SLE and throw.
|
||||
@@ -389,7 +389,7 @@ TEST_F(SLEBaseTests, throws_on_missing_entry)
|
||||
EXPECT_THROW(std::ignore = (*missing).getType(), std::logic_error);
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, throws_on_missing_writable_entry)
|
||||
TEST_F(SLEBaseTests, ThrowsOnMissingWritableEntry)
|
||||
{
|
||||
// A view we never apply, so nothing here reaches the ledger.
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(SignerListEntryTests, constructors)
|
||||
TEST(SignerListEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(SponsorshipEntryTests, constructors)
|
||||
TEST(SponsorshipEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(TicketEntryTests, constructors)
|
||||
TEST(TicketEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(VaultEntryTests, constructors)
|
||||
TEST(VaultEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(XChainOwnedClaimIDEntryTests, constructors)
|
||||
TEST(XChainOwnedClaimIDEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(XChainOwnedCreateAccountClaimIDEntryTests, constructors)
|
||||
TEST(XChainOwnedCreateAccountClaimIDEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ account(std::string_view hex)
|
||||
// getText() builds its string from eight substitutions of the same type, so a
|
||||
// transposed pair would still compile and still type check. Pin the output so
|
||||
// the field/value pairing is actually verified.
|
||||
TEST(STXChainBridge, get_text_pairs_each_field_with_its_value)
|
||||
TEST(STXChainBridge, getTextPairsEachFieldWithItsValue)
|
||||
{
|
||||
auto const lockingDoor = account("0102030405060708090A0B0C0D0E0F1011121314");
|
||||
auto const issuingDoor = account("14131211100F0E0D0C0B0A090807060504030201");
|
||||
@@ -46,7 +46,7 @@ TEST(STXChainBridge, get_text_pairs_each_field_with_its_value)
|
||||
EXPECT_EQ(bridge.getText(), expected);
|
||||
}
|
||||
|
||||
TEST(STXChainBridge, get_text_on_a_default_bridge)
|
||||
TEST(STXChainBridge, getTextOnADefaultBridge)
|
||||
{
|
||||
STXChainBridge const bridge;
|
||||
auto const text = bridge.getText();
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
// Auto-generated unit tests for transaction ConfidentialMPTMirrorUpdate
|
||||
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <protocol_autogen/TestHelpers.h>
|
||||
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Seed.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol_autogen/transactions/ConfidentialMPTMirrorUpdate.h>
|
||||
#include <xrpl/protocol_autogen/transactions/AccountSet.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::transactions {
|
||||
|
||||
// 1 & 4) Set fields via builder setters, build, then read them back via
|
||||
// wrapper getters. After build(), validate() should succeed.
|
||||
TEST(TransactionsConfidentialMPTMirrorUpdateTests, BuilderSettersRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testConfidentialMPTMirrorUpdate"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 1;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const holderValue = canonical_ACCOUNT();
|
||||
auto const issuerEncryptedAmountValue = canonical_VL();
|
||||
auto const auditorEncryptedAmountValue = canonical_VL();
|
||||
auto const zKProofValue = canonical_VL();
|
||||
|
||||
ConfidentialMPTMirrorUpdateBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
zKProofValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setHolder(holderValue);
|
||||
builder.setIssuerEncryptedAmount(issuerEncryptedAmountValue);
|
||||
builder.setAuditorEncryptedAmount(auditorEncryptedAmountValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(tx.validate(reason)) << reason;
|
||||
|
||||
// Verify signing was applied
|
||||
EXPECT_FALSE(tx.getSigningPubKey().empty());
|
||||
EXPECT_TRUE(tx.hasTxnSignature());
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(tx.getAccount(), accountValue);
|
||||
EXPECT_EQ(tx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(tx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = tx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = zKProofValue;
|
||||
auto const actual = tx.getZKProof();
|
||||
expectEqualField(expected, actual, "sfZKProof");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = holderValue;
|
||||
auto const actualOpt = tx.getHolder();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfHolder should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfHolder");
|
||||
EXPECT_TRUE(tx.hasHolder());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = issuerEncryptedAmountValue;
|
||||
auto const actualOpt = tx.getIssuerEncryptedAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfIssuerEncryptedAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfIssuerEncryptedAmount");
|
||||
EXPECT_TRUE(tx.hasIssuerEncryptedAmount());
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = auditorEncryptedAmountValue;
|
||||
auto const actualOpt = tx.getAuditorEncryptedAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfAuditorEncryptedAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfAuditorEncryptedAmount");
|
||||
EXPECT_TRUE(tx.hasAuditorEncryptedAmount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
// and verify all fields match.
|
||||
TEST(TransactionsConfidentialMPTMirrorUpdateTests, BuilderFromStTxRoundTrip)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testConfidentialMPTMirrorUpdateFromTx"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 2;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const holderValue = canonical_ACCOUNT();
|
||||
auto const issuerEncryptedAmountValue = canonical_VL();
|
||||
auto const auditorEncryptedAmountValue = canonical_VL();
|
||||
auto const zKProofValue = canonical_VL();
|
||||
|
||||
// Build an initial transaction
|
||||
ConfidentialMPTMirrorUpdateBuilder initialBuilder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
zKProofValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setHolder(holderValue);
|
||||
initialBuilder.setIssuerEncryptedAmount(issuerEncryptedAmountValue);
|
||||
initialBuilder.setAuditorEncryptedAmount(auditorEncryptedAmountValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
// Create builder from existing STTx
|
||||
ConfidentialMPTMirrorUpdateBuilder builderFromTx{initialTx.getSTTx()};
|
||||
|
||||
auto rebuiltTx = builderFromTx.build(publicKey, secretKey);
|
||||
|
||||
std::string reason;
|
||||
EXPECT_TRUE(rebuiltTx.validate(reason)) << reason;
|
||||
|
||||
// Verify common fields
|
||||
EXPECT_EQ(rebuiltTx.getAccount(), accountValue);
|
||||
EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue);
|
||||
EXPECT_EQ(rebuiltTx.getFee(), feeValue);
|
||||
|
||||
// Verify required fields
|
||||
{
|
||||
auto const& expected = mPTokenIssuanceIDValue;
|
||||
auto const actual = rebuiltTx.getMPTokenIssuanceID();
|
||||
expectEqualField(expected, actual, "sfMPTokenIssuanceID");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = zKProofValue;
|
||||
auto const actual = rebuiltTx.getZKProof();
|
||||
expectEqualField(expected, actual, "sfZKProof");
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = holderValue;
|
||||
auto const actualOpt = rebuiltTx.getHolder();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfHolder should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfHolder");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = issuerEncryptedAmountValue;
|
||||
auto const actualOpt = rebuiltTx.getIssuerEncryptedAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfIssuerEncryptedAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfIssuerEncryptedAmount");
|
||||
}
|
||||
|
||||
{
|
||||
auto const& expected = auditorEncryptedAmountValue;
|
||||
auto const actualOpt = rebuiltTx.getAuditorEncryptedAmount();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfAuditorEncryptedAmount should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfAuditorEncryptedAmount");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsConfidentialMPTMirrorUpdateTests, WrapperThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongType"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(ConfidentialMPTMirrorUpdate{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 4) Verify builder throws when constructed from wrong transaction type.
|
||||
TEST(TransactionsConfidentialMPTMirrorUpdateTests, BuilderThrowsOnWrongTxType)
|
||||
{
|
||||
// Build a valid transaction of a different type
|
||||
auto const [pk, sk] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testWrongTypeBuilder"));
|
||||
auto const account = calcAccountID(pk);
|
||||
|
||||
AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()};
|
||||
auto wrongTx = wrongBuilder.build(pk, sk);
|
||||
|
||||
EXPECT_THROW(ConfidentialMPTMirrorUpdateBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(TransactionsConfidentialMPTMirrorUpdateTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::Secp256k1, generateSeed("testConfidentialMPTMirrorUpdateNullopt"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 3;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific required field values
|
||||
auto const mPTokenIssuanceIDValue = canonical_UINT192();
|
||||
auto const zKProofValue = canonical_VL();
|
||||
|
||||
ConfidentialMPTMirrorUpdateBuilder builder{
|
||||
accountValue,
|
||||
mPTokenIssuanceIDValue,
|
||||
zKProofValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Do NOT set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasHolder());
|
||||
EXPECT_FALSE(tx.getHolder().has_value());
|
||||
EXPECT_FALSE(tx.hasIssuerEncryptedAmount());
|
||||
EXPECT_FALSE(tx.getIssuerEncryptedAmount().has_value());
|
||||
EXPECT_FALSE(tx.hasAuditorEncryptedAmount());
|
||||
EXPECT_FALSE(tx.getAuditorEncryptedAmount().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@ using namespace xrpl;
|
||||
// by subscribing the real cap through a WebSocket, which would exceed the frame
|
||||
// limit and drop the connection before the check runs) lets the boundary be
|
||||
// asserted exactly.
|
||||
TEST(InfoSubSubscriptionCap, boundary)
|
||||
TEST(InfoSubSubscriptionCap, Boundary)
|
||||
{
|
||||
constexpr std::size_t cap = kMaxSubscriptionsPerConnection;
|
||||
|
||||
@@ -30,7 +30,7 @@ TEST(InfoSubSubscriptionCap, boundary)
|
||||
EXPECT_TRUE(exceedsSubscriptionCap(cap - 1, 2));
|
||||
}
|
||||
|
||||
TEST(InfoSubSubscriptionCap, no_overflow)
|
||||
TEST(InfoSubSubscriptionCap, NoOverflow)
|
||||
{
|
||||
constexpr std::size_t cap = kMaxSubscriptionsPerConnection;
|
||||
constexpr std::size_t max = std::numeric_limits<std::size_t>::max();
|
||||
@@ -41,7 +41,7 @@ TEST(InfoSubSubscriptionCap, no_overflow)
|
||||
EXPECT_TRUE(exceedsSubscriptionCap(cap, max));
|
||||
}
|
||||
|
||||
TEST(InfoSubSubscriptionCap, explicit_cap)
|
||||
TEST(InfoSubSubscriptionCap, ExplicitCap)
|
||||
{
|
||||
// A configured override is honored: the boundary tracks the passed cap, not
|
||||
// the built-in default. This is the seam doSubscribe uses to enforce a
|
||||
|
||||
@@ -455,52 +455,6 @@ TEST_F(SHAMapTraversal, bounds_agree_with_iteration_for_absent_keys)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SHAMapTraversal, bounds_on_empty_map_return_end)
|
||||
{
|
||||
tests::TestNodeFamily f{j_};
|
||||
SHAMap map{SHAMapType::FREE, f};
|
||||
map.setUnbacked();
|
||||
|
||||
// The root is a childless inner node, so boundHelper's inner-node branch scans every branch on
|
||||
// the requested side of the one id selects, finds them all empty, and falls through to end()
|
||||
// rather than dereference a child.
|
||||
EXPECT_EQ(map.upperBound(uint256{}), map.end());
|
||||
EXPECT_EQ(map.lowerBound(uint256{}), map.end());
|
||||
|
||||
uint256 probe;
|
||||
std::fill_n(probe.begin(), probe.size(), std::uint8_t{0xff});
|
||||
EXPECT_EQ(map.upperBound(probe), map.end());
|
||||
EXPECT_EQ(map.lowerBound(probe), map.end());
|
||||
}
|
||||
|
||||
TEST_F(SHAMapTraversal, bounds_on_single_item_map_use_the_leaf_below_the_root)
|
||||
{
|
||||
tests::TestNodeFamily f{j_};
|
||||
SHAMap map{SHAMapType::FREE, f};
|
||||
|
||||
auto const key = deepFanOutKeys().front();
|
||||
fillMap(map, {key});
|
||||
|
||||
// fillMap adds items in-process, so root_ stays an inner node with the single leaf below it.
|
||||
// The stack holds both, so boundHelper examines the leaf first.
|
||||
uint256 below = key;
|
||||
--below;
|
||||
uint256 above = key;
|
||||
++above;
|
||||
|
||||
auto const upper = map.upperBound(below);
|
||||
ASSERT_NE(upper, map.end());
|
||||
EXPECT_EQ(upper->key(), key);
|
||||
EXPECT_EQ(map.upperBound(key), map.end());
|
||||
EXPECT_EQ(map.upperBound(above), map.end());
|
||||
|
||||
auto const lower = map.lowerBound(above);
|
||||
ASSERT_NE(lower, map.end());
|
||||
EXPECT_EQ(lower->key(), key);
|
||||
EXPECT_EQ(map.lowerBound(key), map.end());
|
||||
EXPECT_EQ(map.lowerBound(below), map.end());
|
||||
}
|
||||
|
||||
TEST_F(SHAMapTraversal, iteration_survives_deletions)
|
||||
{
|
||||
tests::TestNodeFamily f{j_};
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(AccountSet, null_account_set)
|
||||
TEST(AccountSet, NullAccountSet)
|
||||
{
|
||||
TxTest env;
|
||||
|
||||
@@ -60,7 +60,7 @@ TEST(AccountSet, null_account_set)
|
||||
EXPECT_EQ(accountRoot.getFlags(), 0);
|
||||
}
|
||||
|
||||
TEST(AccountSet, most_flags)
|
||||
TEST(AccountSet, MostFlags)
|
||||
{
|
||||
Account const alice("alice");
|
||||
|
||||
@@ -175,7 +175,7 @@ TEST(AccountSet, most_flags)
|
||||
});
|
||||
}
|
||||
|
||||
TEST(AccountSet, set_and_reset_account_txn_id)
|
||||
TEST(AccountSet, SetAndResetAccountTxnID)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -206,7 +206,7 @@ TEST(AccountSet, set_and_reset_account_txn_id)
|
||||
EXPECT_EQ(nowFlags, origFlags);
|
||||
}
|
||||
|
||||
TEST(AccountSet, set_no_freeze)
|
||||
TEST(AccountSet, SetNoFreeze)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -249,7 +249,7 @@ TEST(AccountSet, set_no_freeze)
|
||||
EXPECT_TRUE(env.getAccountRoot(alice).isFlag(lsfNoFreeze));
|
||||
}
|
||||
|
||||
TEST(AccountSet, domain)
|
||||
TEST(AccountSet, Domain)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -317,7 +317,7 @@ TEST(AccountSet, domain)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AccountSet, message_key)
|
||||
TEST(AccountSet, MessageKey)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -358,7 +358,7 @@ TEST(AccountSet, message_key)
|
||||
telBAD_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
TEST(AccountSet, wallet_id)
|
||||
TEST(AccountSet, WalletID)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -391,7 +391,7 @@ TEST(AccountSet, wallet_id)
|
||||
EXPECT_FALSE(env.getAccountRoot(alice).hasWalletLocator());
|
||||
}
|
||||
|
||||
TEST(AccountSet, email_hash)
|
||||
TEST(AccountSet, EmailHash)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -422,7 +422,7 @@ TEST(AccountSet, email_hash)
|
||||
EXPECT_FALSE(env.getAccountRoot(alice).hasEmailHash());
|
||||
}
|
||||
|
||||
TEST(AccountSet, transfer_rate)
|
||||
TEST(AccountSet, TransferRate)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
@@ -473,7 +473,7 @@ TEST(AccountSet, transfer_rate)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AccountSet, bad_inputs)
|
||||
TEST(AccountSet, BadInputs)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -553,7 +553,7 @@ TEST(AccountSet, bad_inputs)
|
||||
tecNO_ALTERNATIVE_KEY);
|
||||
}
|
||||
|
||||
TEST(AccountSet, require_auth_with_dir)
|
||||
TEST(AccountSet, RequireAuthWithDir)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -601,7 +601,7 @@ TEST(AccountSet, require_auth_with_dir)
|
||||
tesSUCCESS);
|
||||
}
|
||||
|
||||
TEST(AccountSet, ticket)
|
||||
TEST(AccountSet, Ticket)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -660,7 +660,7 @@ TEST(AccountSet, ticket)
|
||||
tefNO_TICKET);
|
||||
}
|
||||
|
||||
TEST(AccountSet, bad_signing_key)
|
||||
TEST(AccountSet, BadSigningKey)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -684,7 +684,7 @@ TEST(AccountSet, bad_signing_key)
|
||||
EXPECT_FALSE(result.applied);
|
||||
}
|
||||
|
||||
TEST(AccountSet, gateway)
|
||||
TEST(AccountSet, Gateway)
|
||||
{
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/misc/DeliverMax.h>
|
||||
#include <xrpld/app/misc/TxQ.h>
|
||||
#include <xrpld/rpc/CTID.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/SyntheticFields.h>
|
||||
|
||||
@@ -12,7 +11,6 @@
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/NetworkIDService.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -29,7 +27,6 @@
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol/serialize.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -182,19 +179,6 @@ fillJsonTx(
|
||||
}
|
||||
}
|
||||
|
||||
// compute outgoing CTID
|
||||
if (stMeta && stMeta->isFieldPresent(sfTransactionIndex))
|
||||
{
|
||||
uint32_t const lgrSeq = fill.ledger.seq();
|
||||
uint32_t const txnIdx = stMeta->getFieldU32(sfTransactionIndex);
|
||||
uint32_t netID = fill.context->app.getNetworkIDService().getNetworkID();
|
||||
if (txn->isFieldPresent(sfNetworkID))
|
||||
netID = txn->getFieldU32(sfNetworkID);
|
||||
|
||||
if (auto ctid = rpc::encodeCTID(lgrSeq, txnIdx, netID))
|
||||
txJson[jss::ctid] = *ctid;
|
||||
}
|
||||
|
||||
if (((fill.options & static_cast<int>(LedgerFill::Options::OwnerFunds)) != 0) &&
|
||||
txn->getTxnType() == ttOFFER_CREATE)
|
||||
{
|
||||
|
||||
@@ -574,23 +574,9 @@ run(int argc, char** argv)
|
||||
|
||||
auto configFile = vm.contains("conf") ? vm["conf"].as<std::string>() : std::string();
|
||||
|
||||
// config file, quiet flag. Setup reports a config file it cannot find or
|
||||
// read by returning false, and invalid config contents by throwing, so both
|
||||
// are handled here rather than left to escape.
|
||||
try
|
||||
{
|
||||
if (!config->setup(
|
||||
configFile, vm.contains("quiet"), vm.contains("silent"), vm.contains("standalone")))
|
||||
{
|
||||
// setup() has already reported the reason it failed.
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
std::cerr << "xrpld: invalid configuration: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
// config file, quiet flag.
|
||||
config->setup(
|
||||
configFile, vm.contains("quiet"), vm.contains("silent"), vm.contains("standalone"));
|
||||
|
||||
if (vm.contains("vacuum"))
|
||||
{
|
||||
|
||||
+12
-43
@@ -17,7 +17,6 @@
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -89,10 +88,10 @@ class Config : public BasicConfig
|
||||
{
|
||||
public:
|
||||
// Settings related to the configuration file location and directories
|
||||
static constexpr std::string_view kConfigFileName = "xrpld.cfg";
|
||||
static constexpr std::string_view kConfigLegacyName = "rippled.cfg";
|
||||
static constexpr std::string_view kDatabaseDirName = "db";
|
||||
static constexpr std::string_view kValidatorsFileName = "validators.txt";
|
||||
static char const* const kConfigFileName;
|
||||
static char const* const kConfigLegacyName;
|
||||
static char const* const kDatabaseDirName;
|
||||
static char const* const kValidatorsFileName;
|
||||
|
||||
/**
|
||||
* Returns the full path and filename of the debug log file.
|
||||
@@ -100,22 +99,17 @@ public:
|
||||
[[nodiscard]] std::filesystem::path
|
||||
getDebugLogFile() const;
|
||||
|
||||
private:
|
||||
std::filesystem::path configFile_;
|
||||
|
||||
public:
|
||||
std::filesystem::path configDir;
|
||||
|
||||
private:
|
||||
std::filesystem::path debugLogfile_;
|
||||
|
||||
/**
|
||||
* Reads and parses the given config file.
|
||||
*
|
||||
* @param configFile Path of the config file to read.
|
||||
* @return True when the file was read, false when it could not be, in
|
||||
* which case the reason has been written to stderr and the
|
||||
* configuration is left unchanged.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
load(std::filesystem::path const& configFile);
|
||||
void
|
||||
load();
|
||||
beast::Journal const j_;
|
||||
|
||||
bool quiet_ = false; // Minimize logging verbosity.
|
||||
@@ -335,34 +329,9 @@ public:
|
||||
public:
|
||||
Config();
|
||||
|
||||
/**
|
||||
* Sets up the configuration, and determines the config and data
|
||||
* directories.
|
||||
*
|
||||
* When no config file is named, searches the current working directory,
|
||||
* the XDG config directory, and the system config directory, in that
|
||||
* order, and takes the first one that holds a config file under either the
|
||||
* current or the legacy name. When none of them does, writes the searched
|
||||
* directories to stderr and fails.
|
||||
*
|
||||
* Failing to read the config file, whether it was named or found, is also
|
||||
* a failure, and writes the reason to stderr.
|
||||
*
|
||||
* Be very careful to make sure these bool params are in the right order.
|
||||
*
|
||||
* @param strConf Path of the config file, or empty to search for it.
|
||||
* @param bQuiet Whether to minimize logging verbosity.
|
||||
* @param bSilent Whether to write no console output after startup.
|
||||
* @param bStandalone Whether to operate in stand-alone mode.
|
||||
* @return True when a config file was named or found, and read. False when
|
||||
* no config file could be found in any searched directory, or when
|
||||
* the file that was named or found could not be read. Both cases
|
||||
* have written their reason to stderr first.
|
||||
* @throws std::runtime_error When the config file contents are invalid, or
|
||||
* when the data directory cannot be created. Invalid contents are
|
||||
* reported this way rather than through the return value.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
/* Be very careful to make sure these bool params
|
||||
are in the right order. */
|
||||
void
|
||||
setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStandalone);
|
||||
|
||||
void
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
@@ -247,6 +246,11 @@ getSingleSection(
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
char const* const Config::kConfigFileName = "xrpld.cfg";
|
||||
char const* const Config::kConfigLegacyName = "rippled.cfg";
|
||||
char const* const Config::kDatabaseDirName = "db";
|
||||
char const* const Config::kValidatorsFileName = "validators.txt";
|
||||
|
||||
[[nodiscard]] static std::string
|
||||
getEnvVar(char const* name)
|
||||
{
|
||||
@@ -298,7 +302,7 @@ Config::setupControl(bool bQuiet, bool bSilent, bool bStandalone)
|
||||
XRPL_ASSERT(nodeSize <= 4, "xrpl::Config::setupControl : node size is set");
|
||||
}
|
||||
|
||||
bool
|
||||
void
|
||||
Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStandalone)
|
||||
{
|
||||
setupControl(bQuiet, bSilent, bStandalone);
|
||||
@@ -310,99 +314,73 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand
|
||||
// directory.
|
||||
std::filesystem::path dataDir;
|
||||
|
||||
// The config file to read. It stays unset when the search for one finds
|
||||
// nothing, which is the only way this function fails.
|
||||
std::optional<std::filesystem::path> configFile;
|
||||
|
||||
if (!strConf.empty())
|
||||
{
|
||||
// --conf=<path> : everything is relative that file.
|
||||
configFile = strConf;
|
||||
configDir = std::filesystem::absolute(*configFile);
|
||||
configFile_ = strConf;
|
||||
configDir = std::filesystem::absolute(configFile_);
|
||||
configDir.remove_filename();
|
||||
dataDir = configDir / kDatabaseDirName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A config directory to search, paired with the data directory that
|
||||
// goes with it.
|
||||
struct Candidate
|
||||
do
|
||||
{
|
||||
std::filesystem::path configDir;
|
||||
std::filesystem::path dataDir;
|
||||
};
|
||||
|
||||
// The candidates to search, in order. There are at most three.
|
||||
std::vector<Candidate> candidates;
|
||||
candidates.reserve(3);
|
||||
|
||||
// The current working directory, with the databases in a subdirectory.
|
||||
auto const cwd = std::filesystem::current_path();
|
||||
candidates.emplace_back(cwd, cwd / kDatabaseDirName);
|
||||
|
||||
// The XDG config and data directories, which only apply when the home
|
||||
// directory is set. See
|
||||
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html.
|
||||
if (auto const strHome = getEnvVar("HOME"); !strHome.empty())
|
||||
{
|
||||
auto strXdgConfigHome = getEnvVar("XDG_CONFIG_HOME");
|
||||
if (strXdgConfigHome.empty())
|
||||
{
|
||||
// $XDG_CONFIG_HOME was not set, use default based on $HOME.
|
||||
strXdgConfigHome = strHome + "/.config";
|
||||
}
|
||||
auto strXdgDataHome = getEnvVar("XDG_DATA_HOME");
|
||||
if (strXdgDataHome.empty())
|
||||
{
|
||||
// $XDG_DATA_HOME was not set, use default based on $HOME.
|
||||
strXdgDataHome = strHome + "/.local/share";
|
||||
}
|
||||
candidates.emplace_back(
|
||||
strXdgConfigHome + "/" + systemName(), strXdgDataHome + "/" + systemName());
|
||||
}
|
||||
|
||||
// The system config directory, as a last resort.
|
||||
candidates.emplace_back("/etc/" + systemName(), "/var/lib/" + systemName());
|
||||
|
||||
// Take the first candidate directory holding a config file under either
|
||||
// name. When none of them does, configFile stays unset, while configDir
|
||||
// and dataDir keep the values of the last candidate.
|
||||
for (auto const& candidate : candidates)
|
||||
{
|
||||
configDir = candidate.configDir;
|
||||
dataDir = candidate.dataDir;
|
||||
|
||||
for (std::string_view const fileName : {kConfigFileName, kConfigLegacyName})
|
||||
{
|
||||
auto candidateFile = configDir / fileName;
|
||||
if (std::filesystem::exists(candidateFile))
|
||||
{
|
||||
configFile = std::move(candidateFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (configFile)
|
||||
// Check if either of the config files exist in the current working
|
||||
// directory, in which case the databases will be stored in a
|
||||
// subdirectory.
|
||||
configDir = std::filesystem::current_path();
|
||||
dataDir = configDir / kDatabaseDirName;
|
||||
configFile_ = configDir / kConfigFileName;
|
||||
if (std::filesystem::exists(configFile_))
|
||||
break;
|
||||
configFile_ = configDir / kConfigLegacyName;
|
||||
if (std::filesystem::exists(configFile_))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!configFile)
|
||||
{
|
||||
// Report every directory searched. Naming only the last one would
|
||||
// suggest it is the only place a config file is expected. Report
|
||||
// this even when quiet, because it is the reason for failing.
|
||||
std::cerr << std::format(
|
||||
"No {} or {} found. Searched:\n", kConfigFileName, kConfigLegacyName);
|
||||
for (auto const& candidate : candidates)
|
||||
std::cerr << std::format(" {}\n", candidate.configDir.string());
|
||||
// Check if the home directory is set, and optionally the XDG config
|
||||
// and/or data directories, as the config may be there. See
|
||||
// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html.
|
||||
auto const strHome = getEnvVar("HOME");
|
||||
if (!strHome.empty())
|
||||
{
|
||||
auto strXdgConfigHome = getEnvVar("XDG_CONFIG_HOME");
|
||||
auto strXdgDataHome = getEnvVar("XDG_DATA_HOME");
|
||||
if (strXdgConfigHome.empty())
|
||||
{
|
||||
// $XDG_CONFIG_HOME was not set, use default based on $HOME.
|
||||
strXdgConfigHome = strHome + "/.config";
|
||||
}
|
||||
if (strXdgDataHome.empty())
|
||||
{
|
||||
// $XDG_DATA_HOME was not set, use default based on $HOME.
|
||||
strXdgDataHome = strHome + "/.local/share";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// Check if either of the config files exist in the XDG config
|
||||
// dir.
|
||||
dataDir = strXdgDataHome + "/" + systemName();
|
||||
configDir = strXdgConfigHome + "/" + systemName();
|
||||
configFile_ = configDir / kConfigFileName;
|
||||
if (std::filesystem::exists(configFile_))
|
||||
break;
|
||||
configFile_ = configDir / kConfigLegacyName;
|
||||
if (std::filesystem::exists(configFile_))
|
||||
break;
|
||||
}
|
||||
|
||||
// As a last resort, check the system config directory.
|
||||
dataDir = "/var/lib/" + systemName();
|
||||
configDir = "/etc/" + systemName();
|
||||
configFile_ = configDir / kConfigFileName;
|
||||
if (std::filesystem::exists(configFile_))
|
||||
break;
|
||||
configFile_ = configDir / kConfigLegacyName;
|
||||
} while (false);
|
||||
}
|
||||
|
||||
// Update default values
|
||||
if (!load(*configFile))
|
||||
return false;
|
||||
load();
|
||||
{
|
||||
// load() may have set a new value for the dataDir
|
||||
std::string const dbPath(legacy(Sections::kDatabasePath));
|
||||
@@ -437,8 +415,6 @@ Config::setup(std::string const& strConf, bool bQuiet, bool bSilent, bool bStand
|
||||
|
||||
Section const& nodeDbSection{section(Sections::kNodeDatabase)};
|
||||
getIfExists(nodeDbSection, Keys::kFastLoad, fastLoad);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0 ports are allowed for unit tests, but still not allowed to be present in
|
||||
@@ -469,34 +445,27 @@ checkZeroPorts(Config const& config)
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Config::load(std::filesystem::path const& configFile)
|
||||
void
|
||||
Config::load()
|
||||
{
|
||||
// Report the path as a plain string. There is no std::formatter for
|
||||
// std::filesystem::path, and streaming one adds quotes of its own, which
|
||||
// would double up with the quotes written here.
|
||||
auto const fileName = configFile.string();
|
||||
|
||||
// NOTE: this writes to cerr because we want cout to be reserved
|
||||
// for the writing of the json response (so that stdout can be part of a
|
||||
// pipeline, for instance)
|
||||
if (!quiet_)
|
||||
std::cerr << std::format("Loading: '{}'\n", fileName);
|
||||
std::cerr << "Loading: " << configFile_ << "\n";
|
||||
|
||||
std::error_code ec;
|
||||
auto const fileContents = getFileContents(ec, configFile);
|
||||
auto const fileContents = getFileContents(ec, configFile_);
|
||||
|
||||
if (ec)
|
||||
{
|
||||
std::cerr << std::format(
|
||||
"Failed to read '{}': {} (error {})\n", fileName, ec.message(), ec.value());
|
||||
return false;
|
||||
std::cerr << "Failed to read '" << configFile_ << "'." << ec.value() << ": " << ec.message()
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
loadFromString(fileContents);
|
||||
checkZeroPorts(*this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -151,12 +151,4 @@ isProtocolSupported(ProtocolVersion const& v)
|
||||
return std::end(kSupportedProtocolList) != std::ranges::find(kSupportedProtocolList, v);
|
||||
}
|
||||
|
||||
ProtocolVersion
|
||||
newestSupportedProtocolVersion()
|
||||
{
|
||||
// Scans rather than reading the sorted list's last entry, so it does not
|
||||
// depend on an invariant kept elsewhere.
|
||||
return *std::ranges::max_element(kSupportedProtocolList);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -68,13 +68,4 @@ supportedProtocolVersions();
|
||||
bool
|
||||
isProtocolSupported(ProtocolVersion const& v);
|
||||
|
||||
/**
|
||||
* The version negotiated with a peer that speaks everything we speak, so also
|
||||
* the one that enables every version-gated feature.
|
||||
*
|
||||
* @return The largest version in the list of supported protocol versions.
|
||||
*/
|
||||
ProtocolVersion
|
||||
newestSupportedProtocolVersion();
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/NetworkIDService.h>
|
||||
@@ -809,6 +810,8 @@ transactionFormatResultImpl(Transaction::pointer tpTrans, unsigned apiVersion)
|
||||
jvResult[jss::engine_result] = sToken;
|
||||
jvResult[jss::engine_result_code] = tpTrans->getResult();
|
||||
jvResult[jss::engine_result_message] = sHuman;
|
||||
|
||||
rpc::populateAugmentedSubmitFields(jvResult, tpTrans);
|
||||
}
|
||||
}
|
||||
catch (std::exception&)
|
||||
@@ -822,6 +825,33 @@ transactionFormatResultImpl(Transaction::pointer tpTrans, unsigned apiVersion)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
populateAugmentedSubmitFields(
|
||||
json::Value& jvResult,
|
||||
std::shared_ptr<Transaction> const& transaction)
|
||||
{
|
||||
auto const submitResult = transaction->getSubmitResult();
|
||||
|
||||
jvResult[jss::accepted] = submitResult.any();
|
||||
jvResult[jss::applied] = submitResult.applied;
|
||||
jvResult[jss::broadcast] = submitResult.broadcast;
|
||||
jvResult[jss::queued] = submitResult.queued;
|
||||
jvResult[jss::kept] = submitResult.kept;
|
||||
|
||||
if (auto currentLedgerState = transaction->getCurrentLedgerState())
|
||||
{
|
||||
jvResult[jss::account_sequence_next] =
|
||||
safeCast<json::Value::UInt>(currentLedgerState->accountSeqNext);
|
||||
jvResult[jss::account_sequence_available] =
|
||||
safeCast<json::Value::UInt>(currentLedgerState->accountSeqAvail);
|
||||
jvResult[jss::open_ledger_cost] = to_string(currentLedgerState->minFeeRequired);
|
||||
jvResult[jss::validated_ledger_index] =
|
||||
safeCast<json::Value::UInt>(currentLedgerState->validatedLedger);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] static XRPAmount
|
||||
getTxFee(Application const& app, Config const& config, json::Value tx)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,21 @@ class TxQ;
|
||||
|
||||
namespace rpc {
|
||||
|
||||
/**
|
||||
* Populate augmented submit fields into a JSON result.
|
||||
* This helper populates the submit result flags (accepted, applied,
|
||||
* broadcast, queued, kept) and current ledger state fields
|
||||
* (account_sequence_next, account_sequence_available, open_ledger_cost,
|
||||
* validated_ledger_index) from a Transaction pointer.
|
||||
*
|
||||
* @param jvResult The JSON result to populate
|
||||
* @param transaction The transaction containing the submit result and state
|
||||
*/
|
||||
void
|
||||
populateAugmentedSubmitFields(
|
||||
json::Value& jvResult,
|
||||
std::shared_ptr<Transaction> const& transaction);
|
||||
|
||||
json::Value
|
||||
getCurrentNetworkFee(
|
||||
Role const role,
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
@@ -14,7 +13,6 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
#include <xrpl/tx/apply.h>
|
||||
@@ -155,24 +153,7 @@ doSubmit(rpc::JsonContext& context)
|
||||
jvResult[jss::engine_result_code] = transaction->getResult();
|
||||
jvResult[jss::engine_result_message] = sHuman;
|
||||
|
||||
auto const submitResult = transaction->getSubmitResult();
|
||||
|
||||
jvResult[jss::accepted] = submitResult.any();
|
||||
jvResult[jss::applied] = submitResult.applied;
|
||||
jvResult[jss::broadcast] = submitResult.broadcast;
|
||||
jvResult[jss::queued] = submitResult.queued;
|
||||
jvResult[jss::kept] = submitResult.kept;
|
||||
|
||||
if (auto currentLedgerState = transaction->getCurrentLedgerState())
|
||||
{
|
||||
jvResult[jss::account_sequence_next] =
|
||||
safeCast<json::Value::UInt>(currentLedgerState->accountSeqNext);
|
||||
jvResult[jss::account_sequence_available] =
|
||||
safeCast<json::Value::UInt>(currentLedgerState->accountSeqAvail);
|
||||
jvResult[jss::open_ledger_cost] = to_string(currentLedgerState->minFeeRequired);
|
||||
jvResult[jss::validated_ledger_index] =
|
||||
safeCast<json::Value::UInt>(currentLedgerState->validatedLedger);
|
||||
}
|
||||
rpc::populateAugmentedSubmitFields(jvResult, transaction);
|
||||
}
|
||||
|
||||
return jvResult;
|
||||
|
||||
Reference in new issue
Block a user