Merge branch 'develop' into bthomee/proto

This commit is contained in:
Bart Thomee
2025-07-24 13:48:34 -04:00
33 changed files with 959 additions and 3344 deletions

View File

@@ -11,6 +11,15 @@ jobs:
runs-on: ubuntu-24.04
container: ghcr.io/xrplf/ci/tools-rippled-clang-format
steps:
# For jobs running in containers, $GITHUB_WORKSPACE and ${{ github.workspace }} might not be the
# same directory. The actions/checkout step is *supposed* to checkout into $GITHUB_WORKSPACE and
# then add it to safe.directory (see instructions at https://github.com/actions/checkout)
# but that's apparently not happening for some container images. We can't be sure what is actually
# happening, so let's pre-emptively add both directories to safe.directory. There's a
# Github issue opened in 2022 and not resolved in 2025 https://github.com/actions/runner/issues/2058 ¯\_(ツ)_/¯
- run: |
git config --global --add safe.directory $GITHUB_WORKSPACE
git config --global --add safe.directory ${{ github.workspace }}
- uses: actions/checkout@v4
- name: Format first-party sources
run: |

View File

@@ -24,8 +24,6 @@ env:
CONAN_GLOBAL_CONF: |
core.download:parallel={{os.cpu_count()}}
core.upload:parallel={{os.cpu_count()}}
core:default_build_profile=libxrpl
core:default_profile=libxrpl
tools.build:jobs={{ (os.cpu_count() * 4/5) | int }}
tools.build:verbosity=verbose
tools.compilation:verbosity=verbose

View File

@@ -19,14 +19,12 @@ concurrency:
# This part of Conan configuration is specific to this workflow only; we do not want
# to pollute conan/profiles directory with settings which might not work for others
env:
#CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
CONAN_GLOBAL_CONF: |
core.download:parallel={{ os.cpu_count() }}
core.upload:parallel={{ os.cpu_count() }}
core:default_build_profile=libxrpl
core:default_profile=libxrpl
tools.build:jobs={{ (os.cpu_count() * 4/5) | int }}
tools.build:verbosity=verbose
tools.compilation:verbosity=verbose
@@ -359,37 +357,44 @@ jobs:
cmake --build .
./example | grep '^[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+'
# NOTE we are not using dependencies built above because it lags with
# compiler versions. Instrumentation requires clang version 16 or
# later
instrumentation-build:
if: ${{ github.event_name == 'push' || github.event.pull_request.draft != true || contains(github.event.pull_request.labels.*.name, 'DraftRunCI') }}
needs: dependencies
runs-on: [self-hosted, heavy]
container: ghcr.io/xrplf/ci/debian-bookworm:clang-16
env:
build_dir: .build
steps:
- name: download cache
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: linux-clang-Debug
- name: extract cache
run: |
mkdir -p ${CONAN_HOME}
tar -xzf conan.tar.gz -C ${CONAN_HOME}
- name: check environment
run: |
echo ${PATH} | tr ':' '\n'
conan --version
cmake --version
env | sort
ls ${CONAN_HOME}
- name: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: dependencies
uses: ./.github/actions/dependencies
with:
configuration: Debug
- name: prepare environment
run: |
mkdir ${GITHUB_WORKSPACE}/.build
echo "SOURCE_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV
echo "BUILD_DIR=$GITHUB_WORKSPACE/.build" >> $GITHUB_ENV
- name: configure Conan
run: |
echo "${CONAN_GLOBAL_CONF}" >> $(conan config home)/global.conf
conan config install conan/profiles/ -tf $(conan config home)/profiles/
conan profile show
- name: build dependencies
run: |
cd ${BUILD_DIR}
conan install ${SOURCE_DIR} \
--output-folder ${BUILD_DIR} \
--build missing \
--settings:all build_type=Debug
mkdir -p ${build_dir}
echo "SOURCE_DIR=$(pwd)" >> $GITHUB_ENV
echo "BUILD_DIR=$(pwd)/${build_dir}" >> $GITHUB_ENV
- name: build with instrumentation
run: |

View File

@@ -27,8 +27,6 @@ env:
CONAN_GLOBAL_CONF: |
core.download:parallel={{os.cpu_count()}}
core.upload:parallel={{os.cpu_count()}}
core:default_build_profile=libxrpl
core:default_profile=libxrpl
tools.build:jobs=24
tools.build:verbosity=verbose
tools.compilation:verbosity=verbose

View File

@@ -9,6 +9,7 @@
[settings]
os={{ os }}
arch={{ arch }}
build_type=Debug
compiler={{compiler}}
compiler.version={{ compiler_version }}
compiler.cppstd=20
@@ -17,3 +18,20 @@ compiler.runtime=static
{% else %}
compiler.libcxx={{detect_api.detect_libcxx(compiler, version, compiler_exe)}}
{% endif %}
[conf]
{% if compiler == "clang" and compiler_version >= 19 %}
tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw']
{% endif %}
{% if compiler == "apple-clang" and compiler_version >= 17 %}
tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw']
{% endif %}
{% if compiler == "clang" and compiler_version == 16 %}
tools.build:cxxflags=['-DBOOST_ASIO_DISABLE_CONCEPTS']
{% endif %}
{% if compiler == "gcc" and compiler_version < 13 %}
tools.build:cxxflags=['-Wno-restrict']
{% endif %}
[tool_requires]
!cmake/*: cmake/[>=3 <4]

View File

@@ -55,6 +55,15 @@ class Xrpl(ConanFile):
'date/*:header_only': True,
'grpc/*:shared': False,
'grpc/*:secure': True,
'grpc/*:codegen': False,
'grpc/*:csharp_ext': False,
'grpc/*:csharp_plugin': False,
'grpc/*:node_plugin': False,
'grpc/*:objective_c_plugin': False,
'grpc/*:php_plugin': False,
'grpc/*:python_plugin': False,
'grpc/*:ruby_plugin': False,
'grpc/*:otel_plugin': False,
'libarchive/*:shared': False,
'libarchive/*:with_acl': False,
'libarchive/*:with_bzip2': False,
@@ -143,8 +152,6 @@ class Xrpl(ConanFile):
tc.variables['static'] = self.options.static
tc.variables['unity'] = self.options.unity
tc.variables['xrpld'] = self.options.xrpld
if self.settings.compiler == 'clang' and self.settings.compiler.version == 16:
tc.extra_cxxflags = ["-DBOOST_ASIO_DISABLE_CONCEPTS"]
tc.generate()
def build(self):

View File

@@ -31,38 +31,28 @@ namespace beast {
template <class Generator>
void
rngfill(void* buffer, std::size_t bytes, Generator& g)
rngfill(void* const buffer, std::size_t const bytes, Generator& g)
{
using result_type = typename Generator::result_type;
constexpr std::size_t result_size = sizeof(result_type);
while (bytes >= sizeof(result_type))
std::uint8_t* const buffer_start = static_cast<std::uint8_t*>(buffer);
std::size_t const complete_iterations = bytes / result_size;
std::size_t const bytes_remaining = bytes % result_size;
for (std::size_t count = 0; count < complete_iterations; ++count)
{
auto const v = g();
std::memcpy(buffer, &v, sizeof(v));
buffer = reinterpret_cast<std::uint8_t*>(buffer) + sizeof(v);
bytes -= sizeof(v);
result_type const v = g();
std::size_t const offset = count * result_size;
std::memcpy(buffer_start + offset, &v, result_size);
}
XRPL_ASSERT(
bytes < sizeof(result_type), "beast::rngfill(void*) : maximum bytes");
#ifdef __GNUC__
// gcc 11.1 (falsely) warns about an array-bounds overflow in release mode.
// gcc 12.1 (also falsely) warns about an string overflow in release mode.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#pragma GCC diagnostic ignored "-Wstringop-overflow"
#endif
if (bytes > 0)
if (bytes_remaining > 0)
{
auto const v = g();
std::memcpy(buffer, &v, bytes);
result_type const v = g();
std::size_t const offset = complete_iterations * result_size;
std::memcpy(buffer_start + offset, &v, bytes_remaining);
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
}
template <

View File

@@ -53,19 +53,19 @@
* then change the macro parameter in features.macro to
* `VoteBehavior::DefaultYes`. The communication process is beyond
* the scope of these instructions.
* 5) If a supported feature (`Supported::yes`) was _ever_ in a released
* version, it can never be changed back to `Supported::no`, because
* it _may_ still become enabled at any time. This would cause newer
* versions of `rippled` to become amendment blocked.
* Instead, to prevent newer versions from voting on the feature, use
* `VoteBehavior::Obsolete`. Obsolete features can not be voted for
* by any versions of `rippled` built with that setting, but will still
* work correctly if they get enabled. If a feature remains obsolete
* for long enough that _all_ clients that could vote for it are
* amendment blocked, the feature can be removed from the code
* as if it was unsupported.
*
* 5) A feature marked as Obsolete can mean either:
* 1) It is in the ledger (marked as Supported::yes) and it is on its way to
* become Retired
* 2) The feature is not in the ledger (has always been marked as
* Supported::no) and the code to support it has been removed
*
* If we want to discontinue a feature that we've never fully supported and
* the feature has never been enabled, we should remove all the related
* code, and mark the feature as "abandoned". To do this:
*
* 1) Open features.macro, move the feature to the abandoned section and
* change the macro to XRPL_ABANDON
*
* When a feature has been enabled for several years, the conditional code
* may be removed, and the feature "retired". To retire a feature:
@@ -99,13 +99,10 @@ namespace detail {
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE
#pragma push_macro("XRPL_ABANDON")
#undef XRPL_ABANDON
#define XRPL_FEATURE(name, supported, vote) +1
#define XRPL_FIX(name, supported, vote) +1
#define XRPL_RETIRE(name) +1
#define XRPL_ABANDON(name) +1
// This value SHOULD be equal to the number of amendments registered in
// Feature.cpp. Because it's only used to reserve storage, and determine how
@@ -122,8 +119,6 @@ static constexpr std::size_t numFeatures =
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE")
#undef XRPL_ABANDON
#pragma pop_macro("XRPL_ABANDON")
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -365,13 +360,10 @@ foreachFeature(FeatureBitset bs, F&& f)
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE
#pragma push_macro("XRPL_ABANDON")
#undef XRPL_ABANDON
#define XRPL_FEATURE(name, supported, vote) extern uint256 const feature##name;
#define XRPL_FIX(name, supported, vote) extern uint256 const fix##name;
#define XRPL_RETIRE(name)
#define XRPL_ABANDON(name)
#include <xrpl/protocol/detail/features.macro>
@@ -381,8 +373,6 @@ foreachFeature(FeatureBitset bs, F&& f)
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE")
#undef XRPL_ABANDON
#pragma pop_macro("XRPL_ABANDON")
} // namespace ripple

View File

@@ -26,9 +26,6 @@
#if !defined(XRPL_RETIRE)
#error "undefined macro: XRPL_RETIRE"
#endif
#if !defined(XRPL_ABANDON)
#error "undefined macro: XRPL_ABANDON"
#endif
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
@@ -133,11 +130,6 @@ XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete)
XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete)
XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete)
// The following amendments were never supported, never enabled, and
// we've abanded them. These features should never be in the ledger,
// and we've removed all the related code.
XRPL_ABANDON(OwnerPaysFee)
// The following amendments have been active for at least two years. Their
// pre-amendment code has been removed and the identifiers are deprecated.
// All known amendments and amendments that may appear in a validated

View File

@@ -482,8 +482,7 @@ LEDGER_ENTRY(ltDELEGATE, 0x0083, Delegate, delegate, ({
}))
/** A ledger object representing a single asset vault.
\sa keylet::mptoken
\sa keylet::vault
*/
LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
{sfPreviousTxnID, soeREQUIRED},

View File

@@ -409,6 +409,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::de
{sfTransferFee, soeOPTIONAL},
{sfMaximumAmount, soeOPTIONAL},
{sfMPTokenMetadata, soeOPTIONAL},
{sfDomainID, soeOPTIONAL},
}))
/** This transaction type destroys a MPTokensIssuance instance */
@@ -420,6 +421,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation::
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, ({
{sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL},
{sfDomainID, soeOPTIONAL},
}))
/** This transaction type authorizes a MPToken instance */
@@ -478,7 +480,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({
{sfAsset, soeREQUIRED, soeMPTSupported},
{sfAssetsMaximum, soeOPTIONAL},
{sfMPTokenMetadata, soeOPTIONAL},
{sfDomainID, soeOPTIONAL}, // PermissionedDomainID
{sfDomainID, soeOPTIONAL},
{sfWithdrawalPolicy, soeOPTIONAL},
{sfData, soeOPTIONAL},
}))
@@ -487,7 +489,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({
TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, ({
{sfVaultID, soeREQUIRED},
{sfAssetsMaximum, soeOPTIONAL},
{sfDomainID, soeOPTIONAL}, // PermissionedDomainID
{sfDomainID, soeOPTIONAL},
{sfData, soeOPTIONAL},
}))

View File

@@ -254,7 +254,7 @@ FeatureCollections::registerFeature(
{
check(!readOnly, "Attempting to register a feature after startup.");
check(
support == Supported::yes || vote != VoteBehavior::DefaultYes,
support == Supported::yes || vote == VoteBehavior::DefaultNo,
"Invalid feature parameters. Must be supported to be up-voted.");
Feature const* i = getByName(name);
if (!i)
@@ -268,7 +268,7 @@ FeatureCollections::registerFeature(
features.emplace_back(name, f);
auto const getAmendmentSupport = [=]() {
if (vote == VoteBehavior::Obsolete && support == Supported::yes)
if (vote == VoteBehavior::Obsolete)
return AmendmentSupport::Retired;
return support == Supported::yes ? AmendmentSupport::Supported
: AmendmentSupport::Unsupported;
@@ -398,14 +398,6 @@ retireFeature(std::string const& name)
return registerFeature(name, Supported::yes, VoteBehavior::Obsolete);
}
// Abandoned features are not in the ledger and have no code controlled by the
// feature. They were never supported, and cannot be voted on.
uint256
abandonFeature(std::string const& name)
{
return registerFeature(name, Supported::no, VoteBehavior::Obsolete);
}
/** Tell FeatureCollections when registration is complete. */
bool
registrationIsDone()
@@ -440,8 +432,6 @@ featureToName(uint256 const& f)
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE
#pragma push_macro("XRPL_ABANDON")
#undef XRPL_ABANDON
#define XRPL_FEATURE(name, supported, vote) \
uint256 const feature##name = registerFeature(#name, supported, vote);
@@ -453,11 +443,6 @@ featureToName(uint256 const& f)
[[deprecated("The referenced amendment has been retired")]] \
[[maybe_unused]] \
uint256 const retired##name = retireFeature(#name);
#define XRPL_ABANDON(name) \
[[deprecated("The referenced amendment has been abandoned")]] \
[[maybe_unused]] \
uint256 const abandoned##name = abandonFeature(#name);
// clang-format on
#include <xrpl/protocol/detail/features.macro>
@@ -468,8 +453,6 @@ featureToName(uint256 const& f)
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE")
#undef XRPL_ABANDON
#pragma pop_macro("XRPL_ABANDON")
// All of the features should now be registered, since variables in a cpp file
// are initialized from top to bottom.

View File

@@ -3652,14 +3652,18 @@ class Batch_test : public beast::unit_test::suite
{
// Submit a tx with tfInnerBatchTxn
uint256 const txBad = submitTx(tfInnerBatchTxn);
BEAST_EXPECT(env.app().getHashRouter().getFlags(txBad) == 0);
BEAST_EXPECT(
env.app().getHashRouter().getFlags(txBad) ==
HashRouterFlags::UNDEFINED);
}
// Validate: NetworkOPs::processTransaction()
{
uint256 const txid = processTxn(tfInnerBatchTxn);
// HashRouter::getFlags() should return SF_BAD
BEAST_EXPECT(env.app().getHashRouter().getFlags(txid) == SF_BAD);
// HashRouter::getFlags() should return LedgerFlags::BAD
BEAST_EXPECT(
env.app().getHashRouter().getFlags(txid) ==
HashRouterFlags::BAD);
}
}

View File

@@ -45,15 +45,19 @@ class HashRouter_test : public beast::unit_test::suite
TestStopwatch stopwatch;
HashRouter router(getSetup(2s, 1s), stopwatch);
uint256 const key1(1);
uint256 const key2(2);
uint256 const key3(3);
HashRouterFlags key1(HashRouterFlags::PRIVATE1);
HashRouterFlags key2(HashRouterFlags::PRIVATE2);
HashRouterFlags key3(HashRouterFlags::PRIVATE3);
auto const ukey1 = uint256{static_cast<std::uint64_t>(key1)};
auto const ukey2 = uint256{static_cast<std::uint64_t>(key2)};
auto const ukey3 = uint256{static_cast<std::uint64_t>(key3)};
// t=0
router.setFlags(key1, 11111);
BEAST_EXPECT(router.getFlags(key1) == 11111);
router.setFlags(key2, 22222);
BEAST_EXPECT(router.getFlags(key2) == 22222);
router.setFlags(ukey1, HashRouterFlags::PRIVATE1);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1);
router.setFlags(ukey2, HashRouterFlags::PRIVATE2);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE2);
// key1 : 0
// key2 : 0
// key3: null
@@ -62,7 +66,7 @@ class HashRouter_test : public beast::unit_test::suite
// Because we are accessing key1 here, it
// will NOT be expired for another two ticks
BEAST_EXPECT(router.getFlags(key1) == 11111);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1);
// key1 : 1
// key2 : 0
// key3 null
@@ -70,9 +74,9 @@ class HashRouter_test : public beast::unit_test::suite
++stopwatch;
// t=3
router.setFlags(key3, 33333); // force expiration
BEAST_EXPECT(router.getFlags(key1) == 11111);
BEAST_EXPECT(router.getFlags(key2) == 0);
router.setFlags(ukey3, HashRouterFlags::PRIVATE3); // force expiration
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::UNDEFINED);
}
void
@@ -83,15 +87,21 @@ class HashRouter_test : public beast::unit_test::suite
TestStopwatch stopwatch;
HashRouter router(getSetup(2s, 1s), stopwatch);
uint256 const key1(1);
uint256 const key2(2);
uint256 const key3(3);
uint256 const key4(4);
HashRouterFlags key1(HashRouterFlags::PRIVATE1);
HashRouterFlags key2(HashRouterFlags::PRIVATE2);
HashRouterFlags key3(HashRouterFlags::PRIVATE3);
HashRouterFlags key4(HashRouterFlags::PRIVATE4);
auto const ukey1 = uint256{static_cast<std::uint64_t>(key1)};
auto const ukey2 = uint256{static_cast<std::uint64_t>(key2)};
auto const ukey3 = uint256{static_cast<std::uint64_t>(key3)};
auto const ukey4 = uint256{static_cast<std::uint64_t>(key4)};
BEAST_EXPECT(key1 != key2 && key2 != key3 && key3 != key4);
// t=0
router.setFlags(key1, 12345);
BEAST_EXPECT(router.getFlags(key1) == 12345);
router.setFlags(ukey1, HashRouterFlags::BAD);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::BAD);
// key1 : 0
// key2 : null
// key3 : null
@@ -103,26 +113,27 @@ class HashRouter_test : public beast::unit_test::suite
// so key1 will be expired after the second
// call to setFlags.
// t=1
router.setFlags(key2, 9999);
BEAST_EXPECT(router.getFlags(key1) == 12345);
BEAST_EXPECT(router.getFlags(key2) == 9999);
router.setFlags(ukey2, HashRouterFlags::PRIVATE5);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::BAD);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
// key1 : 1
// key2 : 1
// key3 : null
++stopwatch;
// t=2
BEAST_EXPECT(router.getFlags(key2) == 9999);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
// key1 : 1
// key2 : 2
// key3 : null
++stopwatch;
// t=3
router.setFlags(key3, 2222);
BEAST_EXPECT(router.getFlags(key1) == 0);
BEAST_EXPECT(router.getFlags(key2) == 9999);
BEAST_EXPECT(router.getFlags(key3) == 2222);
router.setFlags(ukey3, HashRouterFlags::BAD);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::BAD);
// key1 : 3
// key2 : 3
// key3 : 3
@@ -130,10 +141,10 @@ class HashRouter_test : public beast::unit_test::suite
++stopwatch;
// t=4
// No insertion, no expiration
router.setFlags(key1, 7654);
BEAST_EXPECT(router.getFlags(key1) == 7654);
BEAST_EXPECT(router.getFlags(key2) == 9999);
BEAST_EXPECT(router.getFlags(key3) == 2222);
router.setFlags(ukey1, HashRouterFlags::SAVED);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::SAVED);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::BAD);
// key1 : 4
// key2 : 4
// key3 : 4
@@ -142,11 +153,11 @@ class HashRouter_test : public beast::unit_test::suite
++stopwatch;
// t=6
router.setFlags(key4, 7890);
BEAST_EXPECT(router.getFlags(key1) == 0);
BEAST_EXPECT(router.getFlags(key2) == 0);
BEAST_EXPECT(router.getFlags(key3) == 0);
BEAST_EXPECT(router.getFlags(key4) == 7890);
router.setFlags(ukey4, HashRouterFlags::TRUSTED);
BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(ukey4) == HashRouterFlags::TRUSTED);
// key1 : 6
// key2 : 6
// key3 : 6
@@ -168,18 +179,18 @@ class HashRouter_test : public beast::unit_test::suite
uint256 const key4(4);
BEAST_EXPECT(key1 != key2 && key2 != key3 && key3 != key4);
int flags = 12345; // This value is ignored
HashRouterFlags flags(HashRouterFlags::BAD); // This value is ignored
router.addSuppression(key1);
BEAST_EXPECT(router.addSuppressionPeer(key2, 15));
BEAST_EXPECT(router.addSuppressionPeer(key3, 20, flags));
BEAST_EXPECT(flags == 0);
BEAST_EXPECT(flags == HashRouterFlags::UNDEFINED);
++stopwatch;
BEAST_EXPECT(!router.addSuppressionPeer(key1, 2));
BEAST_EXPECT(!router.addSuppressionPeer(key2, 3));
BEAST_EXPECT(!router.addSuppressionPeer(key3, 4, flags));
BEAST_EXPECT(flags == 0);
BEAST_EXPECT(flags == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.addSuppressionPeer(key4, 5));
}
@@ -192,9 +203,9 @@ class HashRouter_test : public beast::unit_test::suite
HashRouter router(getSetup(2s, 1s), stopwatch);
uint256 const key1(1);
BEAST_EXPECT(router.setFlags(key1, 10));
BEAST_EXPECT(!router.setFlags(key1, 10));
BEAST_EXPECT(router.setFlags(key1, 20));
BEAST_EXPECT(router.setFlags(key1, HashRouterFlags::PRIVATE1));
BEAST_EXPECT(!router.setFlags(key1, HashRouterFlags::PRIVATE1));
BEAST_EXPECT(router.setFlags(key1, HashRouterFlags::PRIVATE2));
}
void
@@ -250,7 +261,7 @@ class HashRouter_test : public beast::unit_test::suite
HashRouter router(getSetup(5s, 1s), stopwatch);
uint256 const key(1);
HashRouter::PeerShortID peer = 1;
int flags;
HashRouterFlags flags;
BEAST_EXPECT(router.shouldProcess(key, peer, flags, 1s));
BEAST_EXPECT(!router.shouldProcess(key, peer, flags, 1s));
@@ -364,6 +375,39 @@ class HashRouter_test : public beast::unit_test::suite
}
}
void
testFlagsOps()
{
testcase("Bitwise Operations");
using HF = HashRouterFlags;
using UHF = std::underlying_type_t<HF>;
HF f1 = HF::BAD;
HF f2 = HF::SAVED;
HF combined = f1 | f2;
BEAST_EXPECT(
static_cast<UHF>(combined) ==
(static_cast<UHF>(f1) | static_cast<UHF>(f2)));
HF temp = f1;
temp |= f2;
BEAST_EXPECT(temp == combined);
HF intersect = combined & f1;
BEAST_EXPECT(intersect == f1);
HF temp2 = combined;
temp2 &= f1;
BEAST_EXPECT(temp2 == f1);
BEAST_EXPECT(any(f1));
BEAST_EXPECT(any(f2));
BEAST_EXPECT(any(combined));
BEAST_EXPECT(!any(HF::UNDEFINED));
}
public:
void
run() override
@@ -375,6 +419,7 @@ public:
testRelay();
testProcess();
testSetup();
testFlagsOps();
}
};

View File

@@ -18,10 +18,16 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/credentials.h>
#include <test/jtx/permissioned_domains.h>
#include <test/jtx/trust.h>
#include <test/jtx/xchain_bridge.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
@@ -61,6 +67,48 @@ class MPToken_test : public beast::unit_test::suite
.metadata = "test",
.err = temMALFORMED});
if (!features[featureSingleAssetVault])
{
// tries to set DomainID when SAV is disabled
mptAlice.create(
{.maxAmt = 100,
.assetScale = 0,
.metadata = "test",
.flags = tfMPTRequireAuth,
.domainID = uint256(42),
.err = temDISABLED});
}
else if (!features[featurePermissionedDomains])
{
// tries to set DomainID when PD is disabled
mptAlice.create(
{.maxAmt = 100,
.assetScale = 0,
.metadata = "test",
.flags = tfMPTRequireAuth,
.domainID = uint256(42),
.err = temDISABLED});
}
else
{
// tries to set DomainID when RequireAuth is not set
mptAlice.create(
{.maxAmt = 100,
.assetScale = 0,
.metadata = "test",
.domainID = uint256(42),
.err = temMALFORMED});
// tries to set zero DomainID
mptAlice.create(
{.maxAmt = 100,
.assetScale = 0,
.metadata = "test",
.flags = tfMPTRequireAuth,
.domainID = beast::zero,
.err = temMALFORMED});
}
// tries to set a txfee greater than max
mptAlice.create(
{.maxAmt = 100,
@@ -140,6 +188,48 @@ class MPToken_test : public beast::unit_test::suite
BEAST_EXPECT(
result[sfMaximumAmount.getJsonName()] == "9223372036854775807");
}
if (features[featureSingleAssetVault])
{
// Add permissioned domain
Account const credIssuer1{"credIssuer1"};
std::string const credType = "credential";
pdomain::Credentials const credentials1{
{.issuer = credIssuer1, .credType = credType}};
{
Env env{*this, features};
env.fund(XRP(1000), credIssuer1);
env(pdomain::setTx(credIssuer1, credentials1));
auto const domainId1 = [&]() {
auto tx = env.tx()->getJson(JsonOptions::none);
return pdomain::getNewDomain(env.meta());
}();
MPTTester mptAlice(env, alice);
mptAlice.create({
.maxAmt = maxMPTokenAmount, // 9'223'372'036'854'775'807
.assetScale = 1,
.transferFee = 10,
.metadata = "123",
.ownerCount = 1,
.flags = tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow |
tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback,
.domainID = domainId1,
});
// Get the hash for the most recent transaction.
std::string const txHash{
env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
Json::Value const result = env.rpc("tx", txHash)[jss::result];
BEAST_EXPECT(
result[sfMaximumAmount.getJsonName()] ==
"9223372036854775807");
}
}
}
void
@@ -499,6 +589,59 @@ class MPToken_test : public beast::unit_test::suite
.flags = 0x00000008,
.err = temINVALID_FLAG});
if (!features[featureSingleAssetVault])
{
// test invalid flags - nothing is being changed
mptAlice.set(
{.account = alice,
.flags = 0x00000000,
.err = tecNO_PERMISSION});
mptAlice.set(
{.account = alice,
.holder = bob,
.flags = 0x00000000,
.err = tecNO_PERMISSION});
// cannot set DomainID since SAV is not enabled
mptAlice.set(
{.account = alice,
.domainID = uint256(42),
.err = temDISABLED});
}
else
{
// test invalid flags - nothing is being changed
mptAlice.set(
{.account = alice,
.flags = 0x00000000,
.err = temMALFORMED});
mptAlice.set(
{.account = alice,
.holder = bob,
.flags = 0x00000000,
.err = temMALFORMED});
if (!features[featurePermissionedDomains])
{
// cannot set DomainID since PD is not enabled
mptAlice.set(
{.account = alice,
.domainID = uint256(42),
.err = temDISABLED});
}
else
{
// cannot set DomainID since Holder is set
mptAlice.set(
{.account = alice,
.holder = bob,
.domainID = uint256(42),
.err = temMALFORMED});
}
}
// set both lock and unlock flags at the same time will fail
mptAlice.set(
{.account = alice,
@@ -582,6 +725,53 @@ class MPToken_test : public beast::unit_test::suite
mptAlice.set(
{.holder = cindy, .flags = tfMPTLock, .err = tecNO_DST});
}
if (features[featureSingleAssetVault] &&
features[featurePermissionedDomains])
{
// Add permissioned domain
Account const credIssuer1{"credIssuer1"};
std::string const credType = "credential";
pdomain::Credentials const credentials1{
{.issuer = credIssuer1, .credType = credType}};
{
Env env{*this, features};
MPTTester mptAlice(env, alice);
mptAlice.create({});
// Trying to set DomainID on a public MPTokenIssuance
mptAlice.set(
{.domainID = uint256(42), .err = tecNO_PERMISSION});
mptAlice.set(
{.domainID = beast::zero, .err = tecNO_PERMISSION});
}
{
Env env{*this, features};
MPTTester mptAlice(env, alice);
mptAlice.create({.flags = tfMPTRequireAuth});
// Trying to set non-existing DomainID
mptAlice.set(
{.domainID = uint256(42), .err = tecOBJECT_NOT_FOUND});
// Trying to lock but locking is disabled
mptAlice.set(
{.flags = tfMPTUnlock,
.domainID = uint256(42),
.err = tecNO_PERMISSION});
mptAlice.set(
{.flags = tfMPTUnlock,
.domainID = beast::zero,
.err = tecNO_PERMISSION});
}
}
}
void
@@ -590,71 +780,136 @@ class MPToken_test : public beast::unit_test::suite
testcase("Enabled set transaction");
using namespace test::jtx;
// Test locking and unlocking
Env env{*this, features};
Account const alice("alice"); // issuer
Account const bob("bob"); // holder
MPTTester mptAlice(env, alice, {.holders = {bob}});
// create a mptokenissuance with locking
mptAlice.create(
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock});
mptAlice.authorize({.account = bob, .holderCount = 1});
// locks bob's mptoken
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
// trying to lock bob's mptoken again will still succeed
// but no changes to the objects
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
// alice locks the mptissuance
mptAlice.set({.account = alice, .flags = tfMPTLock});
// alice tries to lock up both mptissuance and mptoken again
// it will not change the flags and both will remain locked.
mptAlice.set({.account = alice, .flags = tfMPTLock});
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
// alice unlocks bob's mptoken
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock});
// locks up bob's mptoken again
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
if (!features[featureSingleAssetVault])
{
// Delete bobs' mptoken even though it is locked
mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize});
// Test locking and unlocking
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
// create a mptokenissuance with locking
mptAlice.create(
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock});
mptAlice.authorize({.account = bob, .holderCount = 1});
// locks bob's mptoken
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
// trying to lock bob's mptoken again will still succeed
// but no changes to the objects
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
// alice locks the mptissuance
mptAlice.set({.account = alice, .flags = tfMPTLock});
// alice tries to lock up both mptissuance and mptoken again
// it will not change the flags and both will remain locked.
mptAlice.set({.account = alice, .flags = tfMPTLock});
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
// alice unlocks bob's mptoken
mptAlice.set(
{.account = alice,
.holder = bob,
.flags = tfMPTUnlock,
.err = tecOBJECT_NOT_FOUND});
{.account = alice, .holder = bob, .flags = tfMPTUnlock});
return;
// locks up bob's mptoken again
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
if (!features[featureSingleAssetVault])
{
// Delete bobs' mptoken even though it is locked
mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize});
mptAlice.set(
{.account = alice,
.holder = bob,
.flags = tfMPTUnlock,
.err = tecOBJECT_NOT_FOUND});
return;
}
// Cannot delete locked MPToken
mptAlice.authorize(
{.account = bob,
.flags = tfMPTUnauthorize,
.err = tecNO_PERMISSION});
// alice unlocks mptissuance
mptAlice.set({.account = alice, .flags = tfMPTUnlock});
// alice unlocks bob's mptoken
mptAlice.set(
{.account = alice, .holder = bob, .flags = tfMPTUnlock});
// alice unlocks mptissuance and bob's mptoken again despite that
// they are already unlocked. Make sure this will not change the
// flags
mptAlice.set(
{.account = alice, .holder = bob, .flags = tfMPTUnlock});
mptAlice.set({.account = alice, .flags = tfMPTUnlock});
}
// Cannot delete locked MPToken
mptAlice.authorize(
{.account = bob,
.flags = tfMPTUnauthorize,
.err = tecNO_PERMISSION});
if (features[featureSingleAssetVault])
{
// Add permissioned domain
std::string const credType = "credential";
// alice unlocks mptissuance
mptAlice.set({.account = alice, .flags = tfMPTUnlock});
// Test setting and resetting domain ID
Env env{*this, features};
// alice unlocks bob's mptoken
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock});
auto const domainId1 = [&]() {
Account const credIssuer1{"credIssuer1"};
env.fund(XRP(1000), credIssuer1);
// alice unlocks mptissuance and bob's mptoken again despite that
// they are already unlocked. Make sure this will not change the
// flags
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock});
mptAlice.set({.account = alice, .flags = tfMPTUnlock});
pdomain::Credentials const credentials1{
{.issuer = credIssuer1, .credType = credType}};
env(pdomain::setTx(credIssuer1, credentials1));
return [&]() {
auto tx = env.tx()->getJson(JsonOptions::none);
return pdomain::getNewDomain(env.meta());
}();
}();
auto const domainId2 = [&]() {
Account const credIssuer2{"credIssuer2"};
env.fund(XRP(1000), credIssuer2);
pdomain::Credentials const credentials2{
{.issuer = credIssuer2, .credType = credType}};
env(pdomain::setTx(credIssuer2, credentials2));
return [&]() {
auto tx = env.tx()->getJson(JsonOptions::none);
return pdomain::getNewDomain(env.meta());
}();
}();
MPTTester mptAlice(env, alice, {.holders = {bob}});
// create a mptokenissuance with auth.
mptAlice.create(
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTRequireAuth});
BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
// reset "domain not set" to "domain not set", i.e. no change
mptAlice.set({.domainID = beast::zero});
BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
// reset "domain not set" to domain1
mptAlice.set({.domainID = domainId1});
BEAST_EXPECT(mptAlice.checkDomainID(domainId1));
// reset domain1 to domain2
mptAlice.set({.domainID = domainId2});
BEAST_EXPECT(mptAlice.checkDomainID(domainId2));
// reset domain to "domain not set"
mptAlice.set({.domainID = beast::zero});
BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
}
}
void
@@ -889,6 +1144,200 @@ class MPToken_test : public beast::unit_test::suite
mptAlice.pay(bob, alice, 100, tecNO_AUTH);
}
if (features[featureSingleAssetVault] &&
features[featurePermissionedDomains])
{
// If RequireAuth is enabled and domain is a match, payment succeeds
{
Env env{*this, features};
std::string const credType = "credential";
Account const credIssuer1{"credIssuer1"};
env.fund(XRP(1000), credIssuer1, bob);
auto const domainId1 = [&]() {
pdomain::Credentials const credentials1{
{.issuer = credIssuer1, .credType = credType}};
env(pdomain::setTx(credIssuer1, credentials1));
return [&]() {
auto tx = env.tx()->getJson(JsonOptions::none);
return pdomain::getNewDomain(env.meta());
}();
}();
// bob is authorized via domain
env(credentials::create(bob, credIssuer1, credType));
env(credentials::accept(bob, credIssuer1, credType));
env.close();
MPTTester mptAlice(env, alice, {});
env.close();
mptAlice.create({
.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTRequireAuth | tfMPTCanTransfer,
.domainID = domainId1,
});
mptAlice.authorize({.account = bob});
env.close();
// bob is authorized via domain
mptAlice.pay(alice, bob, 100);
mptAlice.set({.domainID = beast::zero});
// bob is no longer authorized
mptAlice.pay(alice, bob, 100, tecNO_AUTH);
}
{
Env env{*this, features};
std::string const credType = "credential";
Account const credIssuer1{"credIssuer1"};
env.fund(XRP(1000), credIssuer1, bob);
auto const domainId1 = [&]() {
pdomain::Credentials const credentials1{
{.issuer = credIssuer1, .credType = credType}};
env(pdomain::setTx(credIssuer1, credentials1));
return [&]() {
auto tx = env.tx()->getJson(JsonOptions::none);
return pdomain::getNewDomain(env.meta());
}();
}();
// bob is authorized via domain
env(credentials::create(bob, credIssuer1, credType));
env(credentials::accept(bob, credIssuer1, credType));
env.close();
MPTTester mptAlice(env, alice, {});
env.close();
mptAlice.create({
.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTRequireAuth | tfMPTCanTransfer,
.domainID = domainId1,
});
// bob creates an empty MPToken
mptAlice.authorize({.account = bob});
// alice authorizes bob to hold funds
mptAlice.authorize({.account = alice, .holder = bob});
// alice sends 100 MPT to bob
mptAlice.pay(alice, bob, 100);
// alice UNAUTHORIZES bob
mptAlice.authorize(
{.account = alice,
.holder = bob,
.flags = tfMPTUnauthorize});
// bob is still authorized, via domain
mptAlice.pay(bob, alice, 10);
mptAlice.set({.domainID = beast::zero});
// bob fails to send back to alice because he is no longer
// authorize to move his funds!
mptAlice.pay(bob, alice, 10, tecNO_AUTH);
}
{
Env env{*this, features};
std::string const credType = "credential";
// credIssuer1 is the owner of domainId1 and a credential issuer
Account const credIssuer1{"credIssuer1"};
// credIssuer2 is the owner of domainId2 and a credential issuer
// Note, domainId2 also lists credentials issued by credIssuer1
Account const credIssuer2{"credIssuer2"};
env.fund(XRP(1000), credIssuer1, credIssuer2, bob, carol);
auto const domainId1 = [&]() {
pdomain::Credentials const credentials{
{.issuer = credIssuer1, .credType = credType}};
env(pdomain::setTx(credIssuer1, credentials));
return [&]() {
auto tx = env.tx()->getJson(JsonOptions::none);
return pdomain::getNewDomain(env.meta());
}();
}();
auto const domainId2 = [&]() {
pdomain::Credentials const credentials{
{.issuer = credIssuer1, .credType = credType},
{.issuer = credIssuer2, .credType = credType}};
env(pdomain::setTx(credIssuer2, credentials));
return [&]() {
auto tx = env.tx()->getJson(JsonOptions::none);
return pdomain::getNewDomain(env.meta());
}();
}();
// bob is authorized via credIssuer1 which is recognized by both
// domainId1 and domainId2
env(credentials::create(bob, credIssuer1, credType));
env(credentials::accept(bob, credIssuer1, credType));
env.close();
// carol is authorized via credIssuer2, only recognized by
// domainId2
env(credentials::create(carol, credIssuer2, credType));
env(credentials::accept(carol, credIssuer2, credType));
env.close();
MPTTester mptAlice(env, alice, {});
env.close();
mptAlice.create({
.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTRequireAuth | tfMPTCanTransfer,
.domainID = domainId1,
});
// bob and carol create an empty MPToken
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = carol});
env.close();
// alice sends 50 MPT to bob but cannot send to carol
mptAlice.pay(alice, bob, 50);
mptAlice.pay(alice, carol, 50, tecNO_AUTH);
env.close();
// bob cannot send to carol because they are not on the same
// domain (since credIssuer2 is not recognized by domainId1)
mptAlice.pay(bob, carol, 10, tecNO_AUTH);
env.close();
// alice updates domainID to domainId2 which recognizes both
// credIssuer1 and credIssuer2
mptAlice.set({.domainID = domainId2});
// alice can now send to carol
mptAlice.pay(alice, carol, 10);
env.close();
// bob can now send to carol because both are in the same
// domain
mptAlice.pay(bob, carol, 10);
env.close();
// bob loses his authorization and can no longer send MPT
env(credentials::deleteCred(
credIssuer1, bob, credIssuer1, credType));
env.close();
mptAlice.pay(bob, carol, 10, tecNO_AUTH);
mptAlice.pay(bob, alice, 10, tecNO_AUTH);
}
}
// Non-issuer cannot send to each other if MPTCanTransfer isn't set
{
Env env(*this, features);
@@ -1340,10 +1789,8 @@ class MPToken_test : public beast::unit_test::suite
}
void
testDepositPreauth()
testDepositPreauth(FeatureBitset features)
{
testcase("DepositPreauth");
using namespace test::jtx;
Account const alice("alice"); // issuer
Account const bob("bob"); // holder
@@ -1352,8 +1799,11 @@ class MPToken_test : public beast::unit_test::suite
char const credType[] = "abcde";
if (features[featureCredentials])
{
Env env(*this);
testcase("DepositPreauth");
Env env(*this, features);
env.fund(XRP(50000), diana, dpIssuer);
env.close();
@@ -2297,6 +2747,8 @@ public:
// MPTokenIssuanceCreate
testCreateValidation(all - featureSingleAssetVault);
testCreateValidation(
(all | featureSingleAssetVault) - featurePermissionedDomains);
testCreateValidation(all | featureSingleAssetVault);
testCreateEnabled(all - featureSingleAssetVault);
testCreateEnabled(all | featureSingleAssetVault);
@@ -2314,7 +2766,11 @@ public:
testAuthorizeEnabled(all | featureSingleAssetVault);
// MPTokenIssuanceSet
testSetValidation(all);
testSetValidation(all - featureSingleAssetVault);
testSetValidation(
(all | featureSingleAssetVault) - featurePermissionedDomains);
testSetValidation(all | featureSingleAssetVault);
testSetEnabled(all - featureSingleAssetVault);
testSetEnabled(all | featureSingleAssetVault);
@@ -2323,8 +2779,9 @@ public:
testClawback(all);
// Test Direct Payment
testPayment(all);
testDepositPreauth();
testPayment(all | featureSingleAssetVault);
testDepositPreauth(all);
testDepositPreauth(all - featureCredentials);
// Test MPT Amount is invalid in Tx, which don't support MPT
testMPTInvalidInTx(all);

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
#include <test/jtx.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
@@ -99,6 +100,8 @@ MPTTester::create(MPTCreate const& arg)
jv[sfMPTokenMetadata] = strHex(*arg.metadata);
if (arg.maxAmt)
jv[sfMaximumAmount] = std::to_string(*arg.maxAmt);
if (arg.domainID)
jv[sfDomainID] = to_string(*arg.domainID);
if (submit(arg, jv) != tesSUCCESS)
{
// Verify issuance doesn't exist
@@ -235,6 +238,8 @@ MPTTester::set(MPTSet const& arg)
jv[sfHolder] = arg.holder->human();
if (arg.delegate)
jv[sfDelegate] = arg.delegate->human();
if (arg.domainID)
jv[sfDomainID] = to_string(*arg.domainID);
if (submit(arg, jv) == tesSUCCESS && arg.flags.value_or(0))
{
auto require = [&](std::optional<Account> const& holder,
@@ -272,6 +277,16 @@ MPTTester::forObject(
return false;
}
[[nodiscard]] bool
MPTTester::checkDomainID(std::optional<uint256> expected) const
{
return forObject([&](SLEP const& sle) -> bool {
if (sle->isFieldPresent(sfDomainID))
return expected == sle->getFieldH256(sfDomainID);
return (!expected.has_value());
});
}
[[nodiscard]] bool
MPTTester::checkMPTokenAmount(
Account const& holder_,

View File

@@ -106,6 +106,7 @@ struct MPTCreate
std::optional<std::uint32_t> holderCount = std::nullopt;
bool fund = true;
std::optional<std::uint32_t> flags = {0};
std::optional<uint256> domainID = std::nullopt;
std::optional<TER> err = std::nullopt;
};
@@ -139,6 +140,7 @@ struct MPTSet
std::optional<std::uint32_t> holderCount = std::nullopt;
std::optional<std::uint32_t> flags = std::nullopt;
std::optional<Account> delegate = std::nullopt;
std::optional<uint256> domainID = std::nullopt;
std::optional<TER> err = std::nullopt;
};
@@ -165,6 +167,9 @@ public:
void
set(MPTSet const& set = {});
[[nodiscard]] bool
checkDomainID(std::optional<uint256> expected) const;
[[nodiscard]] bool
checkMPTokenAmount(Account const& holder, std::int64_t expectedAmount)
const;

View File

@@ -131,6 +131,9 @@ public:
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 2);
BEAST_EXPECT(
jv[jss::result][jss::network_id] ==
env.app().config().NETWORK_ID);
}
{
@@ -139,7 +142,8 @@ public:
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
return jv[jss::ledger_index] == 3;
return jv[jss::ledger_index] == 3 &&
jv[jss::network_id] == env.app().config().NETWORK_ID;
}));
}
@@ -149,7 +153,8 @@ public:
// Check stream update
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
return jv[jss::ledger_index] == 4;
return jv[jss::ledger_index] == 4 &&
jv[jss::network_id] == env.app().config().NETWORK_ID;
}));
}
@@ -509,6 +514,11 @@ public:
if (!jv.isMember(jss::validated_hash))
return false;
uint32_t netID = env.app().config().NETWORK_ID;
if (!jv.isMember(jss::network_id) ||
jv[jss::network_id] != netID)
return false;
// Certain fields are only added on a flag ledger.
bool const isFlagLedger =
(env.closed()->info().seq + 1) % 256 == 0;
@@ -567,6 +577,7 @@ public:
jv[jss::streams][0u] = "ledger";
jr = env.rpc("json", "subscribe", to_string(jv))[jss::result];
BEAST_EXPECT(jr[jss::status] == "success");
BEAST_EXPECT(jr[jss::network_id] == env.app().config().NETWORK_ID);
jr = env.rpc("json", "unsubscribe", to_string(jv))[jss::result];
BEAST_EXPECT(jr[jss::status] == "success");

View File

@@ -996,7 +996,8 @@ pendSaveValidated(
bool isSynchronous,
bool isCurrent)
{
if (!app.getHashRouter().setFlags(ledger->info().hash, SF_SAVED))
if (!app.getHashRouter().setFlags(
ledger->info().hash, HashRouterFlags::SAVED))
{
// We have tried to save this ledger recently
auto stream = app.journal("Ledger").debug();

View File

@@ -65,7 +65,10 @@ HashRouter::addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer)
}
bool
HashRouter::addSuppressionPeer(uint256 const& key, PeerShortID peer, int& flags)
HashRouter::addSuppressionPeer(
uint256 const& key,
PeerShortID peer,
HashRouterFlags& flags)
{
std::lock_guard lock(mutex_);
@@ -79,7 +82,7 @@ bool
HashRouter::shouldProcess(
uint256 const& key,
PeerShortID peer,
int& flags,
HashRouterFlags& flags,
std::chrono::seconds tx_interval)
{
std::lock_guard lock(mutex_);
@@ -91,7 +94,7 @@ HashRouter::shouldProcess(
return s.shouldProcess(suppressionMap_.clock().now(), tx_interval);
}
int
HashRouterFlags
HashRouter::getFlags(uint256 const& key)
{
std::lock_guard lock(mutex_);
@@ -100,9 +103,10 @@ HashRouter::getFlags(uint256 const& key)
}
bool
HashRouter::setFlags(uint256 const& key, int flags)
HashRouter::setFlags(uint256 const& key, HashRouterFlags flags)
{
XRPL_ASSERT(flags, "ripple::HashRouter::setFlags : valid input");
XRPL_ASSERT(
static_cast<bool>(flags), "ripple::HashRouter::setFlags : valid input");
std::lock_guard lock(mutex_);

View File

@@ -31,20 +31,59 @@
namespace ripple {
// TODO convert these macros to int constants or an enum
#define SF_BAD 0x02 // Temporarily bad
#define SF_SAVED 0x04
#define SF_HELD 0x08 // Held by LedgerMaster after potential processing failure
#define SF_TRUSTED 0x10 // comes from trusted source
enum class HashRouterFlags : std::uint16_t {
// Public flags
UNDEFINED = 0x00,
BAD = 0x02, // Temporarily bad
SAVED = 0x04,
HELD = 0x08, // Held by LedgerMaster after potential processing failure
TRUSTED = 0x10, // Comes from a trusted source
// Private flags, used internally in apply.cpp.
// Do not attempt to read, set, or reuse.
#define SF_PRIVATE1 0x0100
#define SF_PRIVATE2 0x0200
#define SF_PRIVATE3 0x0400
#define SF_PRIVATE4 0x0800
#define SF_PRIVATE5 0x1000
#define SF_PRIVATE6 0x2000
// Private flags (used internally in apply.cpp)
// Do not attempt to read, set, or reuse.
PRIVATE1 = 0x0100,
PRIVATE2 = 0x0200,
PRIVATE3 = 0x0400,
PRIVATE4 = 0x0800,
PRIVATE5 = 0x1000,
PRIVATE6 = 0x2000
};
constexpr HashRouterFlags
operator|(HashRouterFlags lhs, HashRouterFlags rhs)
{
return static_cast<HashRouterFlags>(
static_cast<std::underlying_type_t<HashRouterFlags>>(lhs) |
static_cast<std::underlying_type_t<HashRouterFlags>>(rhs));
}
constexpr HashRouterFlags&
operator|=(HashRouterFlags& lhs, HashRouterFlags rhs)
{
lhs = lhs | rhs;
return lhs;
}
constexpr HashRouterFlags
operator&(HashRouterFlags lhs, HashRouterFlags rhs)
{
return static_cast<HashRouterFlags>(
static_cast<std::underlying_type_t<HashRouterFlags>>(lhs) &
static_cast<std::underlying_type_t<HashRouterFlags>>(rhs));
}
constexpr HashRouterFlags&
operator&=(HashRouterFlags& lhs, HashRouterFlags rhs)
{
lhs = lhs & rhs;
return lhs;
}
constexpr bool
any(HashRouterFlags flags)
{
return static_cast<std::underlying_type_t<HashRouterFlags>>(flags) != 0;
}
class Config;
@@ -101,14 +140,14 @@ private:
peers_.insert(peer);
}
int
HashRouterFlags
getFlags(void) const
{
return flags_;
}
void
setFlags(int flagsToSet)
setFlags(HashRouterFlags flagsToSet)
{
flags_ |= flagsToSet;
}
@@ -154,7 +193,7 @@ private:
}
private:
int flags_ = 0;
HashRouterFlags flags_ = HashRouterFlags::UNDEFINED;
std::set<PeerShortID> peers_;
// This could be generalized to a map, if more
// than one flag needs to expire independently.
@@ -190,14 +229,17 @@ public:
addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer);
bool
addSuppressionPeer(uint256 const& key, PeerShortID peer, int& flags);
addSuppressionPeer(
uint256 const& key,
PeerShortID peer,
HashRouterFlags& flags);
// Add a peer suppression and return whether the entry should be processed
bool
shouldProcess(
uint256 const& key,
PeerShortID peer,
int& flags,
HashRouterFlags& flags,
std::chrono::seconds tx_interval);
/** Set the flags on a hash.
@@ -205,9 +247,9 @@ public:
@return `true` if the flags were changed. `false` if unchanged.
*/
bool
setFlags(uint256 const& key, int flags);
setFlags(uint256 const& key, HashRouterFlags flags);
int
HashRouterFlags
getFlags(uint256 const& key);
/** Determines whether the hashed item should be relayed.

View File

@@ -1207,7 +1207,7 @@ NetworkOPsImp::submitTransaction(std::shared_ptr<STTx const> const& iTrans)
auto const txid = trans->getTransactionID();
auto const flags = app_.getHashRouter().getFlags(txid);
if ((flags & SF_BAD) != 0)
if ((flags & HashRouterFlags::BAD) != HashRouterFlags::UNDEFINED)
{
JLOG(m_journal.warn()) << "Submitted transaction cached bad";
return;
@@ -1251,7 +1251,7 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr<Transaction>& transaction)
{
auto const newFlags = app_.getHashRouter().getFlags(transaction->getID());
if ((newFlags & SF_BAD) != 0)
if ((newFlags & HashRouterFlags::BAD) != HashRouterFlags::UNDEFINED)
{
// cached bad
JLOG(m_journal.warn()) << transaction->getID() << ": cached bad!\n";
@@ -1270,7 +1270,8 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr<Transaction>& transaction)
{
transaction->setStatus(INVALID);
transaction->setResult(temINVALID_FLAG);
app_.getHashRouter().setFlags(transaction->getID(), SF_BAD);
app_.getHashRouter().setFlags(
transaction->getID(), HashRouterFlags::BAD);
return false;
}
@@ -1289,7 +1290,8 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr<Transaction>& transaction)
JLOG(m_journal.info()) << "Transaction has bad signature: " << reason;
transaction->setStatus(INVALID);
transaction->setResult(temBAD_SIGNATURE);
app_.getHashRouter().setFlags(transaction->getID(), SF_BAD);
app_.getHashRouter().setFlags(
transaction->getID(), HashRouterFlags::BAD);
return false;
}
@@ -1412,7 +1414,8 @@ NetworkOPsImp::processTransactionSet(CanonicalTXSet const& set)
JLOG(m_journal.trace())
<< "Exception checking transaction: " << reason;
}
app_.getHashRouter().setFlags(tx->getTransactionID(), SF_BAD);
app_.getHashRouter().setFlags(
tx->getTransactionID(), HashRouterFlags::BAD);
continue;
}
@@ -1538,7 +1541,8 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
e.transaction->setResult(e.result);
if (isTemMalformed(e.result))
app_.getHashRouter().setFlags(e.transaction->getID(), SF_BAD);
app_.getHashRouter().setFlags(
e.transaction->getID(), HashRouterFlags::BAD);
#ifdef DEBUG
if (e.result != tesSUCCESS)
@@ -1626,7 +1630,8 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
// (5) ledgers into the future. (Remember that an
// unseated optional compares as less than all seated
// values, so it has to be checked explicitly first.)
// 3. The SF_HELD flag is not set on the txID. (setFlags
// 3. The HashRouterFlags::BAD flag is not set on the txID.
// (setFlags
// checks before setting. If the flag is set, it returns
// false, which means it's been held once without one of
// the other conditions, so don't hold it again. Time's
@@ -1635,7 +1640,7 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
if (e.local ||
(ledgersLeft && ledgersLeft <= LocalTxs::holdLedgers) ||
app_.getHashRouter().setFlags(
e.transaction->getID(), SF_HELD))
e.transaction->getID(), HashRouterFlags::HELD))
{
// transaction should be held
JLOG(m_journal.debug())
@@ -2410,6 +2415,7 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
jvObj[jss::flags] = val->getFlags();
jvObj[jss::signing_time] = *(*val)[~sfSigningTime];
jvObj[jss::data] = strHex(val->getSerializer().slice());
jvObj[jss::network_id] = app_.config().NETWORK_ID;
if (auto version = (*val)[~sfServerVersion])
jvObj[jss::server_version] = std::to_string(*version);
@@ -3114,6 +3120,8 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
jvObj[jss::ledger_time] = Json::Value::UInt(
lpAccepted->info().closeTime.time_since_epoch().count());
jvObj[jss::network_id] = app_.config().NETWORK_ID;
if (!lpAccepted->rules().enabled(featureXRPFees))
jvObj[jss::fee_ref] = Config::FEE_UNITS_DEPRECATED;
jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped();
@@ -4172,6 +4180,7 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
jvResult[jss::reserve_base] =
lpClosed->fees().accountReserve(0).jsonClipped();
jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
jvResult[jss::network_id] = app_.config().NETWORK_ID;
}
if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger())

View File

@@ -26,9 +26,9 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/st.h>
namespace ripple {
@@ -311,374 +311,6 @@ CreateOffer::checkAcceptAsset(
return tesSUCCESS;
}
bool
CreateOffer::dry_offer(ApplyView& view, Offer const& offer)
{
if (offer.fully_consumed())
return true;
auto const amount = accountFunds(
view,
offer.owner(),
offer.amount().out,
fhZERO_IF_FROZEN,
ctx_.app.journal("View"));
return (amount <= beast::zero);
}
std::pair<bool, Quality>
CreateOffer::select_path(
bool have_direct,
OfferStream const& direct,
bool have_bridge,
OfferStream const& leg1,
OfferStream const& leg2)
{
// If we don't have any viable path, why are we here?!
XRPL_ASSERT(
have_direct || have_bridge,
"ripple::CreateOffer::select_path : valid inputs");
// If there's no bridged path, the direct is the best by default.
if (!have_bridge)
return std::make_pair(true, direct.tip().quality());
Quality const bridged_quality(
composed_quality(leg1.tip().quality(), leg2.tip().quality()));
if (have_direct)
{
// We compare the quality of the composed quality of the bridged
// offers and compare it against the direct offer to pick the best.
Quality const direct_quality(direct.tip().quality());
if (bridged_quality < direct_quality)
return std::make_pair(true, direct_quality);
}
// Either there was no direct offer, or it didn't have a better quality
// than the bridge.
return std::make_pair(false, bridged_quality);
}
bool
CreateOffer::reachedOfferCrossingLimit(Taker const& taker) const
{
auto const crossings =
taker.get_direct_crossings() + (2 * taker.get_bridge_crossings());
// The crossing limit is part of the Ripple protocol and
// changing it is a transaction-processing change.
return crossings >= 850;
}
std::pair<TER, Amounts>
CreateOffer::bridged_cross(
Taker& taker,
ApplyView& view,
ApplyView& view_cancel,
NetClock::time_point const when)
{
auto const& takerAmount = taker.original_offer();
XRPL_ASSERT(
!isXRP(takerAmount.in) && !isXRP(takerAmount.out),
"ripple::CreateOffer::bridged_cross : neither is XRP");
if (isXRP(takerAmount.in) || isXRP(takerAmount.out))
Throw<std::logic_error>("Bridging with XRP and an endpoint.");
OfferStream offers_direct(
view,
view_cancel,
Book(taker.issue_in(), taker.issue_out(), std::nullopt),
when,
stepCounter_,
j_);
OfferStream offers_leg1(
view,
view_cancel,
Book(taker.issue_in(), xrpIssue(), std::nullopt),
when,
stepCounter_,
j_);
OfferStream offers_leg2(
view,
view_cancel,
Book(xrpIssue(), taker.issue_out(), std::nullopt),
when,
stepCounter_,
j_);
TER cross_result = tesSUCCESS;
// Note the subtle distinction here: self-offers encountered in the
// bridge are taken, but self-offers encountered in the direct book
// are not.
bool have_bridge = offers_leg1.step() && offers_leg2.step();
bool have_direct = step_account(offers_direct, taker);
int count = 0;
auto viewJ = ctx_.app.journal("View");
// Modifying the order or logic of the operations in the loop will cause
// a protocol breaking change.
while (have_direct || have_bridge)
{
bool leg1_consumed = false;
bool leg2_consumed = false;
bool direct_consumed = false;
auto const [use_direct, quality] = select_path(
have_direct, offers_direct, have_bridge, offers_leg1, offers_leg2);
// We are always looking at the best quality; we are done with
// crossing as soon as we cross the quality boundary.
if (taker.reject(quality))
break;
count++;
if (use_direct)
{
if (auto stream = j_.debug())
{
stream << count << " Direct:";
stream << " offer: " << offers_direct.tip();
stream << " in: " << offers_direct.tip().amount().in;
stream << " out: " << offers_direct.tip().amount().out;
stream << " owner: " << offers_direct.tip().owner();
stream << " funds: "
<< accountFunds(
view,
offers_direct.tip().owner(),
offers_direct.tip().amount().out,
fhIGNORE_FREEZE,
viewJ);
}
cross_result = taker.cross(offers_direct.tip());
JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result);
if (dry_offer(view, offers_direct.tip()))
{
direct_consumed = true;
have_direct = step_account(offers_direct, taker);
}
}
else
{
if (auto stream = j_.debug())
{
auto const owner1_funds_before = accountFunds(
view,
offers_leg1.tip().owner(),
offers_leg1.tip().amount().out,
fhIGNORE_FREEZE,
viewJ);
auto const owner2_funds_before = accountFunds(
view,
offers_leg2.tip().owner(),
offers_leg2.tip().amount().out,
fhIGNORE_FREEZE,
viewJ);
stream << count << " Bridge:";
stream << " offer1: " << offers_leg1.tip();
stream << " in: " << offers_leg1.tip().amount().in;
stream << " out: " << offers_leg1.tip().amount().out;
stream << " owner: " << offers_leg1.tip().owner();
stream << " funds: " << owner1_funds_before;
stream << " offer2: " << offers_leg2.tip();
stream << " in: " << offers_leg2.tip().amount().in;
stream << " out: " << offers_leg2.tip().amount().out;
stream << " owner: " << offers_leg2.tip().owner();
stream << " funds: " << owner2_funds_before;
}
cross_result = taker.cross(offers_leg1.tip(), offers_leg2.tip());
JLOG(j_.debug()) << "Bridge Result: " << transToken(cross_result);
if (view.rules().enabled(fixTakerDryOfferRemoval))
{
// have_bridge can be true the next time 'round only if
// neither of the OfferStreams are dry.
leg1_consumed = dry_offer(view, offers_leg1.tip());
if (leg1_consumed)
have_bridge &= offers_leg1.step();
leg2_consumed = dry_offer(view, offers_leg2.tip());
if (leg2_consumed)
have_bridge &= offers_leg2.step();
}
else
{
// This old behavior may leave an empty offer in the book for
// the second leg.
if (dry_offer(view, offers_leg1.tip()))
{
leg1_consumed = true;
have_bridge = (have_bridge && offers_leg1.step());
}
if (dry_offer(view, offers_leg2.tip()))
{
leg2_consumed = true;
have_bridge = (have_bridge && offers_leg2.step());
}
}
}
if (cross_result != tesSUCCESS)
{
cross_result = tecFAILED_PROCESSING;
break;
}
if (taker.done())
{
JLOG(j_.debug()) << "The taker reports he's done during crossing!";
break;
}
if (reachedOfferCrossingLimit(taker))
{
JLOG(j_.debug()) << "The offer crossing limit has been exceeded!";
break;
}
// Postcondition: If we aren't done, then we *must* have consumed at
// least one offer fully.
XRPL_ASSERT(
direct_consumed || leg1_consumed || leg2_consumed,
"ripple::CreateOffer::bridged_cross : consumed an offer");
if (!direct_consumed && !leg1_consumed && !leg2_consumed)
Throw<std::logic_error>(
"bridged crossing: nothing was fully consumed.");
}
return std::make_pair(cross_result, taker.remaining_offer());
}
std::pair<TER, Amounts>
CreateOffer::direct_cross(
Taker& taker,
ApplyView& view,
ApplyView& view_cancel,
NetClock::time_point const when)
{
OfferStream offers(
view,
view_cancel,
Book(taker.issue_in(), taker.issue_out(), std::nullopt),
when,
stepCounter_,
j_);
TER cross_result(tesSUCCESS);
int count = 0;
bool have_offer = step_account(offers, taker);
// Modifying the order or logic of the operations in the loop will cause
// a protocol breaking change.
while (have_offer)
{
bool direct_consumed = false;
auto& offer(offers.tip());
// We are done with crossing as soon as we cross the quality boundary
if (taker.reject(offer.quality()))
break;
count++;
if (auto stream = j_.debug())
{
stream << count << " Direct:";
stream << " offer: " << offer;
stream << " in: " << offer.amount().in;
stream << " out: " << offer.amount().out;
stream << "quality: " << offer.quality();
stream << " owner: " << offer.owner();
stream << " funds: "
<< accountFunds(
view,
offer.owner(),
offer.amount().out,
fhIGNORE_FREEZE,
ctx_.app.journal("View"));
}
cross_result = taker.cross(offer);
JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result);
if (dry_offer(view, offer))
{
direct_consumed = true;
have_offer = step_account(offers, taker);
}
if (cross_result != tesSUCCESS)
{
cross_result = tecFAILED_PROCESSING;
break;
}
if (taker.done())
{
JLOG(j_.debug()) << "The taker reports he's done during crossing!";
break;
}
if (reachedOfferCrossingLimit(taker))
{
JLOG(j_.debug()) << "The offer crossing limit has been exceeded!";
break;
}
// Postcondition: If we aren't done, then we *must* have consumed the
// offer on the books fully!
XRPL_ASSERT(
direct_consumed,
"ripple::CreateOffer::direct_cross : consumed an offer");
if (!direct_consumed)
Throw<std::logic_error>(
"direct crossing: nothing was fully consumed.");
}
return std::make_pair(cross_result, taker.remaining_offer());
}
// Step through the stream for as long as possible, skipping any offers
// that are from the taker or which cross the taker's threshold.
// Return false if the is no offer in the book, true otherwise.
bool
CreateOffer::step_account(OfferStream& stream, Taker const& taker)
{
while (stream.step())
{
auto const& offer = stream.tip();
// This offer at the tip crosses the taker's threshold. We're done.
if (taker.reject(offer.quality()))
return true;
// This offer at the tip is not from the taker. We're done.
if (offer.owner() != taker.account())
return true;
}
// We ran out of offers. Can't advance.
return false;
}
std::pair<TER, Amounts>
CreateOffer::flowCross(
PaymentSandbox& psb,
@@ -883,21 +515,6 @@ CreateOffer::flowCross(
return {tecINTERNAL, takerAmount};
}
std::pair<TER, Amounts>
CreateOffer::cross(
Sandbox& sb,
Sandbox& sbCancel,
Amounts const& takerAmount,
std::optional<uint256> const& domainID)
{
PaymentSandbox psbFlow{&sb};
PaymentSandbox psbCancelFlow{&sbCancel};
auto const ret = flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
psbFlow.apply(sb);
psbCancelFlow.apply(sbCancel);
return ret;
}
std::string
CreateOffer::format_amount(STAmount const& amount)
{
@@ -907,20 +524,6 @@ CreateOffer::format_amount(STAmount const& amount)
return txt;
}
void
CreateOffer::preCompute()
{
cross_type_ = CrossType::IouToIou;
bool const pays_xrp = ctx_.tx.getFieldAmount(sfTakerPays).native();
bool const gets_xrp = ctx_.tx.getFieldAmount(sfTakerGets).native();
if (pays_xrp && !gets_xrp)
cross_type_ = CrossType::IouToXrp;
else if (gets_xrp && !pays_xrp)
cross_type_ = CrossType::XrpToIou;
return Transactor::preCompute();
}
TER
CreateOffer::applyHybrid(
Sandbox& sb,
@@ -1084,11 +687,6 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
// We reverse pays and gets because during crossing we are taking.
Amounts const takerAmount(saTakerGets, saTakerPays);
// The amount of the offer that is unfilled after crossing has been
// performed. It may be equal to the original amount (didn't cross),
// empty (fully crossed), or something in-between.
Amounts place_offer;
JLOG(j_.debug()) << "Attempting cross: "
<< to_string(takerAmount.in.issue()) << " -> "
<< to_string(takerAmount.out.issue());
@@ -1101,8 +699,17 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
stream << " out: " << format_amount(takerAmount.out);
}
// The amount of the offer that is unfilled after crossing has been
// performed. It may be equal to the original amount (didn't cross),
// empty (fully crossed), or something in-between.
Amounts place_offer;
PaymentSandbox psbFlow{&sb};
PaymentSandbox psbCancelFlow{&sbCancel};
std::tie(result, place_offer) =
cross(sb, sbCancel, takerAmount, domainID);
flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
psbFlow.apply(sb);
psbCancelFlow.apply(sbCancel);
// We expect the implementation of cross to succeed
// or give a tec.

View File

@@ -20,10 +20,10 @@
#ifndef RIPPLE_TX_CREATEOFFER_H_INCLUDED
#define RIPPLE_TX_CREATEOFFER_H_INCLUDED
#include <xrpld/app/tx/detail/OfferStream.h>
#include <xrpld/app/tx/detail/Taker.h>
#include <xrpld/app/tx/detail/Transactor.h>
#include <xrpl/protocol/Quality.h>
namespace ripple {
class PaymentSandbox;
@@ -36,8 +36,7 @@ public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
/** Construct a Transactor subclass that creates an offer in the ledger. */
explicit CreateOffer(ApplyContext& ctx)
: Transactor(ctx), stepCounter_(1000, j_)
explicit CreateOffer(ApplyContext& ctx) : Transactor(ctx)
{
}
@@ -52,10 +51,6 @@ public:
static TER
preclaim(PreclaimContext const& ctx);
/** Gather information beyond what the Transactor base class gathers. */
void
preCompute() override;
/** Precondition: fee collection is likely. Attempt to create the offer. */
TER
doApply() override;
@@ -73,42 +68,6 @@ private:
beast::Journal const j,
Issue const& issue);
bool
dry_offer(ApplyView& view, Offer const& offer);
static std::pair<bool, Quality>
select_path(
bool have_direct,
OfferStream const& direct,
bool have_bridge,
OfferStream const& leg1,
OfferStream const& leg2);
std::pair<TER, Amounts>
bridged_cross(
Taker& taker,
ApplyView& view,
ApplyView& view_cancel,
NetClock::time_point const when);
std::pair<TER, Amounts>
direct_cross(
Taker& taker,
ApplyView& view,
ApplyView& view_cancel,
NetClock::time_point const when);
// Step through the stream for as long as possible, skipping any offers
// that are from the taker or which cross the taker's threshold.
// Return false if the is no offer in the book, true otherwise.
static bool
step_account(OfferStream& stream, Taker const& taker);
// True if the number of offers that have been crossed
// exceeds the limit.
bool
reachedOfferCrossingLimit(Taker const& taker) const;
// Use the payment flow code to perform offer crossing.
std::pair<TER, Amounts>
flowCross(
@@ -117,17 +76,6 @@ private:
Amounts const& takerAmount,
std::optional<uint256> const& domainID);
// Temporary
// This is a central location that invokes both versions of cross
// so the results can be compared. Eventually this layer will be
// removed once flowCross is determined to be stable.
std::pair<TER, Amounts>
cross(
Sandbox& sb,
Sandbox& sbCancel,
Amounts const& takerAmount,
std::optional<uint256> const& domainID);
static std::string
format_amount(STAmount const& amount);
@@ -139,13 +87,6 @@ private:
STAmount const& saTakerPays,
STAmount const& saTakerGets,
std::function<void(SLE::ref, std::optional<uint256>)> const& setDir);
private:
// What kind of offer we are placing
CrossType cross_type_;
// The number of steps to take through order books while crossing
OfferStream::StepCounter stepCounter_;
};
using OfferCreate = CreateOffer;

View File

@@ -34,13 +34,13 @@
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
namespace ripple {
// During an EscrowFinish, the transaction must specify both
// a condition and a fulfillment. We track whether that
// fulfillment matches and validates the condition.
#define SF_CF_INVALID SF_PRIVATE5
#define SF_CF_VALID SF_PRIVATE6
namespace ripple {
constexpr HashRouterFlags SF_CF_INVALID = HashRouterFlags::PRIVATE5;
constexpr HashRouterFlags SF_CF_VALID = HashRouterFlags::PRIVATE6;
/*
Escrow
@@ -663,7 +663,7 @@ EscrowFinish::preflight(PreflightContext const& ctx)
// If we haven't checked the condition, check it
// now. Whether it passes or not isn't important
// in preflight.
if (!(flags & (SF_CF_INVALID | SF_CF_VALID)))
if (!any(flags & (SF_CF_INVALID | SF_CF_VALID)))
{
if (checkCondition(*fb, *cb))
router.setFlags(id, SF_CF_VALID);
@@ -1064,7 +1064,7 @@ EscrowFinish::doApply()
// It's unlikely that the results of the check will
// expire from the hash router, but if it happens,
// simply re-run the check.
if (cb && !(flags & (SF_CF_INVALID | SF_CF_VALID)))
if (cb && !any(flags & (SF_CF_INVALID | SF_CF_VALID)))
{
auto const fb = ctx_.tx[~sfFulfillment];
@@ -1081,7 +1081,7 @@ EscrowFinish::doApply()
// If the check failed, then simply return an error
// and don't look at anything else.
if (flags & SF_CF_INVALID)
if (any(flags & SF_CF_INVALID))
return tecCRYPTOCONDITION_ERROR;
// Check against condition in the ledger entry:

View File

@@ -31,6 +31,11 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
if (!ctx.rules.enabled(featureMPTokensV1))
return temDISABLED;
if (ctx.tx.isFieldPresent(sfDomainID) &&
!(ctx.rules.enabled(featurePermissionedDomains) &&
ctx.rules.enabled(featureSingleAssetVault)))
return temDISABLED;
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
@@ -48,6 +53,16 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (auto const domain = ctx.tx[~sfDomainID])
{
if (*domain == beast::zero)
return temMALFORMED;
// Domain present implies that MPTokenIssuance is not public
if ((ctx.tx.getFlags() & tfMPTRequireAuth) == 0)
return temMALFORMED;
}
if (auto const metadata = ctx.tx[~sfMPTokenMetadata])
{
if (metadata->length() == 0 ||
@@ -142,6 +157,7 @@ MPTokenIssuanceCreate::doApply()
.assetScale = tx[~sfAssetScale],
.transferFee = tx[~sfTransferFee],
.metadata = tx[~sfMPTokenMetadata],
.domainId = tx[~sfDomainID],
});
return result ? tesSUCCESS : result.error();
}

View File

@@ -21,6 +21,7 @@
#include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/TxFlags.h>
namespace ripple {
@@ -31,6 +32,14 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
if (!ctx.rules.enabled(featureMPTokensV1))
return temDISABLED;
if (ctx.tx.isFieldPresent(sfDomainID) &&
!(ctx.rules.enabled(featurePermissionedDomains) &&
ctx.rules.enabled(featureSingleAssetVault)))
return temDISABLED;
if (ctx.tx.isFieldPresent(sfDomainID) && ctx.tx.isFieldPresent(sfHolder))
return temMALFORMED;
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
@@ -48,6 +57,13 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
if (holderID && accountID == holderID)
return temMALFORMED;
if (ctx.rules.enabled(featureSingleAssetVault))
{
// Is this transaction actually changing anything ?
if (txFlags == 0 && !ctx.tx.isFieldPresent(sfDomainID))
return temMALFORMED;
}
return preflight2(ctx);
}
@@ -97,9 +113,14 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
if (!sleMptIssuance)
return tecOBJECT_NOT_FOUND;
// if the mpt has disabled locking
if (!((*sleMptIssuance)[sfFlags] & lsfMPTCanLock))
return tecNO_PERMISSION;
if (!sleMptIssuance->isFlag(lsfMPTCanLock))
{
// For readability two separate `if` rather than `||` of two conditions
if (!ctx.view.rules().enabled(featureSingleAssetVault))
return tecNO_PERMISSION;
else if (ctx.tx.isFlag(tfMPTLock) || ctx.tx.isFlag(tfMPTUnlock))
return tecNO_PERMISSION;
}
// ensure it is issued by the tx submitter
if ((*sleMptIssuance)[sfIssuer] != ctx.tx[sfAccount])
@@ -117,6 +138,20 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
return tecOBJECT_NOT_FOUND;
}
if (auto const domain = ctx.tx[~sfDomainID])
{
if (not sleMptIssuance->isFlag(lsfMPTRequireAuth))
return tecNO_PERMISSION;
if (*domain != beast::zero)
{
auto const sleDomain =
ctx.view.read(keylet::permissionedDomain(*domain));
if (!sleDomain)
return tecOBJECT_NOT_FOUND;
}
}
return tesSUCCESS;
}
@@ -126,6 +161,7 @@ MPTokenIssuanceSet::doApply()
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
auto const txFlags = ctx_.tx.getFlags();
auto const holderID = ctx_.tx[~sfHolder];
auto const domainID = ctx_.tx[~sfDomainID];
std::shared_ptr<SLE> sle;
if (holderID)
@@ -147,6 +183,24 @@ MPTokenIssuanceSet::doApply()
if (flagsIn != flagsOut)
sle->setFieldU32(sfFlags, flagsOut);
if (domainID)
{
// This is enforced in preflight.
XRPL_ASSERT(
sle->getType() == ltMPTOKEN_ISSUANCE,
"MPTokenIssuanceSet::doApply : modifying MPTokenIssuance");
if (*domainID != beast::zero)
{
sle->setFieldH256(sfDomainID, *domainID);
}
else
{
if (sle->isFieldPresent(sfDomainID))
sle->makeFieldAbsent(sfDomainID);
}
}
view().update(sle);
return tesSUCCESS;

View File

@@ -1,863 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/tx/detail/Taker.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
namespace ripple {
static std::string
format_amount(STAmount const& amount)
{
std::string txt = amount.getText();
txt += "/";
txt += to_string(amount.issue().currency);
return txt;
}
BasicTaker::BasicTaker(
CrossType cross_type,
AccountID const& account,
Amounts const& amount,
Quality const& quality,
std::uint32_t flags,
Rate const& rate_in,
Rate const& rate_out,
beast::Journal journal)
: account_(account)
, quality_(quality)
, threshold_(quality_)
, sell_(flags & tfSell)
, original_(amount)
, remaining_(amount)
, issue_in_(remaining_.in.issue())
, issue_out_(remaining_.out.issue())
, m_rate_in(rate_in)
, m_rate_out(rate_out)
, cross_type_(cross_type)
, journal_(journal)
{
XRPL_ASSERT(
remaining_.in > beast::zero,
"ripple::BasicTaker::BasicTaker : positive remaining in");
XRPL_ASSERT(
remaining_.out > beast::zero,
"ripple::BasicTaker::BasicTaker : positive remaining out");
XRPL_ASSERT(
m_rate_in.value, "ripple::BasicTaker::BasicTaker : nonzero rate in");
XRPL_ASSERT(
m_rate_out.value, "ripple::BasicTaker::BasicTaker : nonzero rate out");
// If we are dealing with a particular flavor, make sure that it's the
// flavor we expect:
XRPL_ASSERT(
cross_type != CrossType::XrpToIou ||
(isXRP(issue_in()) && !isXRP(issue_out())),
"ripple::BasicTaker::BasicTaker : valid cross to IOU");
XRPL_ASSERT(
cross_type != CrossType::IouToXrp ||
(!isXRP(issue_in()) && isXRP(issue_out())),
"ripple::BasicTaker::BasicTaker : valid cross to XRP");
// And make sure we're not crossing XRP for XRP
XRPL_ASSERT(
!isXRP(issue_in()) || !isXRP(issue_out()),
"ripple::BasicTaker::BasicTaker : not crossing XRP for XRP");
// If this is a passive order, we adjust the quality so as to prevent offers
// at the same quality level from being consumed.
if (flags & tfPassive)
++threshold_;
}
Rate
BasicTaker::effective_rate(
Rate const& rate,
Issue const& issue,
AccountID const& from,
AccountID const& to)
{
// If there's a transfer rate, the issuer is not involved
// and the sender isn't the same as the recipient, return
// the actual transfer rate.
if (rate != parityRate && from != to && from != issue.account &&
to != issue.account)
{
return rate;
}
return parityRate;
}
bool
BasicTaker::unfunded() const
{
if (get_funds(account(), remaining_.in) > beast::zero)
return false;
JLOG(journal_.debug()) << "Unfunded: taker is out of funds.";
return true;
}
bool
BasicTaker::done() const
{
// We are done if we have consumed all the input currency
if (remaining_.in <= beast::zero)
{
JLOG(journal_.debug())
<< "Done: all the input currency has been consumed.";
return true;
}
// We are done if using buy semantics and we received the
// desired amount of output currency
if (!sell_ && (remaining_.out <= beast::zero))
{
JLOG(journal_.debug()) << "Done: the desired amount has been received.";
return true;
}
// We are done if the taker is out of funds
if (unfunded())
{
JLOG(journal_.debug()) << "Done: taker out of funds.";
return true;
}
return false;
}
Amounts
BasicTaker::remaining_offer() const
{
// If the taker is done, then there's no offer to place.
if (done())
return Amounts(remaining_.in.zeroed(), remaining_.out.zeroed());
// Avoid math altogether if we didn't cross.
if (original_ == remaining_)
return original_;
if (sell_)
{
XRPL_ASSERT(
remaining_.in > beast::zero,
"ripple::BasicTaker::remaining_offer : positive remaining in");
// We scale the output based on the remaining input:
return Amounts(
remaining_.in,
divRound(remaining_.in, quality_.rate(), issue_out_, true));
}
XRPL_ASSERT(
remaining_.out > beast::zero,
"ripple::BasicTaker::remaining_offer : positive remaining out");
// We scale the input based on the remaining output:
return Amounts(
mulRound(remaining_.out, quality_.rate(), issue_in_, true),
remaining_.out);
}
Amounts const&
BasicTaker::original_offer() const
{
return original_;
}
// TODO: the presence of 'output' is an artifact caused by the fact that
// Amounts carry issue information which should be decoupled.
static STAmount
qual_div(STAmount const& amount, Quality const& quality, STAmount const& output)
{
auto result = divide(amount, quality.rate(), output.issue());
return std::min(result, output);
}
static STAmount
qual_mul(STAmount const& amount, Quality const& quality, STAmount const& output)
{
auto result = multiply(amount, quality.rate(), output.issue());
return std::min(result, output);
}
void
BasicTaker::log_flow(char const* description, Flow const& flow)
{
auto stream = journal_.debug();
if (!stream)
return;
stream << description;
if (isXRP(issue_in()))
stream << " order in: " << format_amount(flow.order.in);
else
stream << " order in: " << format_amount(flow.order.in)
<< " (issuer: " << format_amount(flow.issuers.in) << ")";
if (isXRP(issue_out()))
stream << " order out: " << format_amount(flow.order.out);
else
stream << " order out: " << format_amount(flow.order.out)
<< " (issuer: " << format_amount(flow.issuers.out) << ")";
}
BasicTaker::Flow
BasicTaker::flow_xrp_to_iou(
Amounts const& order,
Quality quality,
STAmount const& owner_funds,
STAmount const& taker_funds,
Rate const& rate_out)
{
Flow f;
f.order = order;
f.issuers.out = multiply(f.order.out, rate_out);
log_flow("flow_xrp_to_iou", f);
// Clamp on owner balance
if (owner_funds < f.issuers.out)
{
f.issuers.out = owner_funds;
f.order.out = divide(f.issuers.out, rate_out);
f.order.in = qual_mul(f.order.out, quality, f.order.in);
log_flow("(clamped on owner balance)", f);
}
// Clamp if taker wants to limit the output
if (!sell_ && remaining_.out < f.order.out)
{
f.order.out = remaining_.out;
f.order.in = qual_mul(f.order.out, quality, f.order.in);
f.issuers.out = multiply(f.order.out, rate_out);
log_flow("(clamped on taker output)", f);
}
// Clamp on the taker's funds
if (taker_funds < f.order.in)
{
f.order.in = taker_funds;
f.order.out = qual_div(f.order.in, quality, f.order.out);
f.issuers.out = multiply(f.order.out, rate_out);
log_flow("(clamped on taker funds)", f);
}
// Clamp on remaining offer if we are not handling the second leg
// of an autobridge.
if (cross_type_ == CrossType::XrpToIou && (remaining_.in < f.order.in))
{
f.order.in = remaining_.in;
f.order.out = qual_div(f.order.in, quality, f.order.out);
f.issuers.out = multiply(f.order.out, rate_out);
log_flow("(clamped on taker input)", f);
}
return f;
}
BasicTaker::Flow
BasicTaker::flow_iou_to_xrp(
Amounts const& order,
Quality quality,
STAmount const& owner_funds,
STAmount const& taker_funds,
Rate const& rate_in)
{
Flow f;
f.order = order;
f.issuers.in = multiply(f.order.in, rate_in);
log_flow("flow_iou_to_xrp", f);
// Clamp on owner's funds
if (owner_funds < f.order.out)
{
f.order.out = owner_funds;
f.order.in = qual_mul(f.order.out, quality, f.order.in);
f.issuers.in = multiply(f.order.in, rate_in);
log_flow("(clamped on owner funds)", f);
}
// Clamp if taker wants to limit the output and we are not the
// first leg of an autobridge.
if (!sell_ && cross_type_ == CrossType::IouToXrp)
{
if (remaining_.out < f.order.out)
{
f.order.out = remaining_.out;
f.order.in = qual_mul(f.order.out, quality, f.order.in);
f.issuers.in = multiply(f.order.in, rate_in);
log_flow("(clamped on taker output)", f);
}
}
// Clamp on the taker's input offer
if (remaining_.in < f.order.in)
{
f.order.in = remaining_.in;
f.issuers.in = multiply(f.order.in, rate_in);
f.order.out = qual_div(f.order.in, quality, f.order.out);
log_flow("(clamped on taker input)", f);
}
// Clamp on the taker's input balance
if (taker_funds < f.issuers.in)
{
f.issuers.in = taker_funds;
f.order.in = divide(f.issuers.in, rate_in);
f.order.out = qual_div(f.order.in, quality, f.order.out);
log_flow("(clamped on taker funds)", f);
}
return f;
}
BasicTaker::Flow
BasicTaker::flow_iou_to_iou(
Amounts const& order,
Quality quality,
STAmount const& owner_funds,
STAmount const& taker_funds,
Rate const& rate_in,
Rate const& rate_out)
{
Flow f;
f.order = order;
f.issuers.in = multiply(f.order.in, rate_in);
f.issuers.out = multiply(f.order.out, rate_out);
log_flow("flow_iou_to_iou", f);
// Clamp on owner balance
if (owner_funds < f.issuers.out)
{
f.issuers.out = owner_funds;
f.order.out = divide(f.issuers.out, rate_out);
f.order.in = qual_mul(f.order.out, quality, f.order.in);
f.issuers.in = multiply(f.order.in, rate_in);
log_flow("(clamped on owner funds)", f);
}
// Clamp on taker's offer
if (!sell_ && remaining_.out < f.order.out)
{
f.order.out = remaining_.out;
f.order.in = qual_mul(f.order.out, quality, f.order.in);
f.issuers.out = multiply(f.order.out, rate_out);
f.issuers.in = multiply(f.order.in, rate_in);
log_flow("(clamped on taker output)", f);
}
// Clamp on the taker's input offer
if (remaining_.in < f.order.in)
{
f.order.in = remaining_.in;
f.issuers.in = multiply(f.order.in, rate_in);
f.order.out = qual_div(f.order.in, quality, f.order.out);
f.issuers.out = multiply(f.order.out, rate_out);
log_flow("(clamped on taker input)", f);
}
// Clamp on the taker's input balance
if (taker_funds < f.issuers.in)
{
f.issuers.in = taker_funds;
f.order.in = divide(f.issuers.in, rate_in);
f.order.out = qual_div(f.order.in, quality, f.order.out);
f.issuers.out = multiply(f.order.out, rate_out);
log_flow("(clamped on taker funds)", f);
}
return f;
}
// Calculates the direct flow through the specified offer
BasicTaker::Flow
BasicTaker::do_cross(Amounts offer, Quality quality, AccountID const& owner)
{
auto const owner_funds = get_funds(owner, offer.out);
auto const taker_funds = get_funds(account(), offer.in);
Flow result;
if (cross_type_ == CrossType::XrpToIou)
{
result = flow_xrp_to_iou(
offer,
quality,
owner_funds,
taker_funds,
out_rate(owner, account()));
}
else if (cross_type_ == CrossType::IouToXrp)
{
result = flow_iou_to_xrp(
offer,
quality,
owner_funds,
taker_funds,
in_rate(owner, account()));
}
else
{
result = flow_iou_to_iou(
offer,
quality,
owner_funds,
taker_funds,
in_rate(owner, account()),
out_rate(owner, account()));
}
if (!result.sanity_check())
Throw<std::logic_error>("Computed flow fails sanity check.");
remaining_.out -= result.order.out;
remaining_.in -= result.order.in;
XRPL_ASSERT(
remaining_.in >= beast::zero,
"ripple::BasicTaker::do_cross : minimum remaining in");
return result;
}
// Calculates the bridged flow through the specified offers
std::pair<BasicTaker::Flow, BasicTaker::Flow>
BasicTaker::do_cross(
Amounts offer1,
Quality quality1,
AccountID const& owner1,
Amounts offer2,
Quality quality2,
AccountID const& owner2)
{
XRPL_ASSERT(
!offer1.in.native(),
"ripple::BasicTaker::do_cross : offer1 in is not XRP");
XRPL_ASSERT(
offer1.out.native(),
"ripple::BasicTaker::do_cross : offer1 out is XRP");
XRPL_ASSERT(
offer2.in.native(), "ripple::BasicTaker::do_cross : offer2 in is XRP");
XRPL_ASSERT(
!offer2.out.native(),
"ripple::BasicTaker::do_cross : offer2 out is not XRP");
// If the taker owns the first leg of the offer, then the taker's available
// funds aren't the limiting factor for the input - the offer itself is.
auto leg1_in_funds = get_funds(account(), offer1.in);
if (account() == owner1)
{
JLOG(journal_.trace()) << "The taker owns the first leg of a bridge.";
leg1_in_funds = std::max(leg1_in_funds, offer1.in);
}
// If the taker owns the second leg of the offer, then the taker's available
// funds are not the limiting factor for the output - the offer itself is.
auto leg2_out_funds = get_funds(owner2, offer2.out);
if (account() == owner2)
{
JLOG(journal_.trace()) << "The taker owns the second leg of a bridge.";
leg2_out_funds = std::max(leg2_out_funds, offer2.out);
}
// The amount available to flow via XRP is the amount that the owner of the
// first leg of the bridge has, up to the first leg's output.
//
// But, when both legs of a bridge are owned by the same person, the amount
// of XRP that can flow between the two legs is, essentially, infinite
// since all the owner is doing is taking out XRP of his left pocket
// and putting it in his right pocket. In that case, we set the available
// XRP to the largest of the two offers.
auto xrp_funds = get_funds(owner1, offer1.out);
if (owner1 == owner2)
{
JLOG(journal_.trace())
<< "The bridge endpoints are owned by the same account.";
xrp_funds = std::max(offer1.out, offer2.in);
}
if (auto stream = journal_.debug())
{
stream << "Available bridge funds:";
stream << " leg1 in: " << format_amount(leg1_in_funds);
stream << " leg2 out: " << format_amount(leg2_out_funds);
stream << " xrp: " << format_amount(xrp_funds);
}
auto const leg1_rate = in_rate(owner1, account());
auto const leg2_rate = out_rate(owner2, account());
// Attempt to determine the maximal flow that can be achieved across each
// leg independent of the other.
auto flow1 =
flow_iou_to_xrp(offer1, quality1, xrp_funds, leg1_in_funds, leg1_rate);
if (!flow1.sanity_check())
Throw<std::logic_error>("Computed flow1 fails sanity check.");
auto flow2 =
flow_xrp_to_iou(offer2, quality2, leg2_out_funds, xrp_funds, leg2_rate);
if (!flow2.sanity_check())
Throw<std::logic_error>("Computed flow2 fails sanity check.");
// We now have the maximal flows across each leg individually. We need to
// equalize them, so that the amount of XRP that flows out of the first leg
// is the same as the amount of XRP that flows into the second leg. We take
// the side which is the limiting factor (if any) and adjust the other.
if (flow1.order.out < flow2.order.in)
{
// Adjust the second leg of the offer down:
flow2.order.in = flow1.order.out;
flow2.order.out = qual_div(flow2.order.in, quality2, flow2.order.out);
flow2.issuers.out = multiply(flow2.order.out, leg2_rate);
log_flow("Balancing: adjusted second leg down", flow2);
}
else if (flow1.order.out > flow2.order.in)
{
// Adjust the first leg of the offer down:
flow1.order.out = flow2.order.in;
flow1.order.in = qual_mul(flow1.order.out, quality1, flow1.order.in);
flow1.issuers.in = multiply(flow1.order.in, leg1_rate);
log_flow("Balancing: adjusted first leg down", flow2);
}
if (flow1.order.out != flow2.order.in)
Throw<std::logic_error>("Bridged flow is out of balance.");
remaining_.out -= flow2.order.out;
remaining_.in -= flow1.order.in;
return std::make_pair(flow1, flow2);
}
//==============================================================================
Taker::Taker(
CrossType cross_type,
ApplyView& view,
AccountID const& account,
Amounts const& offer,
std::uint32_t flags,
beast::Journal journal)
: BasicTaker(
cross_type,
account,
offer,
Quality(offer),
flags,
calculateRate(view, offer.in.getIssuer(), account),
calculateRate(view, offer.out.getIssuer(), account),
journal)
, view_(view)
, xrp_flow_(0)
, direct_crossings_(0)
, bridge_crossings_(0)
{
XRPL_ASSERT(
issue_in() == offer.in.issue(),
"ripple::Taker::Taker : issue in is a match");
XRPL_ASSERT(
issue_out() == offer.out.issue(),
"ripple::Taker::Taker : issue out is a match");
if (auto stream = journal_.debug())
{
stream << "Crossing as: " << to_string(account);
if (isXRP(issue_in()))
stream << " Offer in: " << format_amount(offer.in);
else
stream << " Offer in: " << format_amount(offer.in)
<< " (issuer: " << issue_in().account << ")";
if (isXRP(issue_out()))
stream << " Offer out: " << format_amount(offer.out);
else
stream << " Offer out: " << format_amount(offer.out)
<< " (issuer: " << issue_out().account << ")";
stream << " Balance: "
<< format_amount(get_funds(account, offer.in));
}
}
void
Taker::consume_offer(Offer& offer, Amounts const& order)
{
if (order.in < beast::zero)
Throw<std::logic_error>("flow with negative input.");
if (order.out < beast::zero)
Throw<std::logic_error>("flow with negative output.");
JLOG(journal_.debug()) << "Consuming from offer " << offer;
if (auto stream = journal_.trace())
{
auto const& available = offer.amount();
stream << " in:" << format_amount(available.in);
stream << " out:" << format_amount(available.out);
}
offer.consume(view_, order);
}
STAmount
Taker::get_funds(AccountID const& account, STAmount const& amount) const
{
return accountFunds(view_, account, amount, fhZERO_IF_FROZEN, journal_);
}
TER
Taker::transferXRP(
AccountID const& from,
AccountID const& to,
STAmount const& amount)
{
if (!isXRP(amount))
Throw<std::logic_error>("Using transferXRP with IOU");
if (from == to)
return tesSUCCESS;
// Transferring zero is equivalent to not doing a transfer
if (amount == beast::zero)
return tesSUCCESS;
return ripple::transferXRP(view_, from, to, amount, journal_);
}
TER
Taker::redeemIOU(
AccountID const& account,
STAmount const& amount,
Issue const& issue)
{
if (isXRP(amount))
Throw<std::logic_error>("Using redeemIOU with XRP");
if (account == issue.account)
return tesSUCCESS;
// Transferring zero is equivalent to not doing a transfer
if (amount == beast::zero)
return tesSUCCESS;
// If we are trying to redeem some amount, then the account
// must have a credit balance.
if (get_funds(account, amount) <= beast::zero)
Throw<std::logic_error>("redeemIOU has no funds to redeem");
auto ret = ripple::redeemIOU(view_, account, amount, issue, journal_);
if (get_funds(account, amount) < beast::zero)
Throw<std::logic_error>("redeemIOU redeemed more funds than available");
return ret;
}
TER
Taker::issueIOU(
AccountID const& account,
STAmount const& amount,
Issue const& issue)
{
if (isXRP(amount))
Throw<std::logic_error>("Using issueIOU with XRP");
if (account == issue.account)
return tesSUCCESS;
// Transferring zero is equivalent to not doing a transfer
if (amount == beast::zero)
return tesSUCCESS;
return ripple::issueIOU(view_, account, amount, issue, journal_);
}
// Performs funds transfers to fill the given offer and adjusts offer.
TER
Taker::fill(BasicTaker::Flow const& flow, Offer& offer)
{
// adjust offer
consume_offer(offer, flow.order);
TER result = tesSUCCESS;
if (cross_type() != CrossType::XrpToIou)
{
XRPL_ASSERT(
!isXRP(flow.order.in), "ripple::Taker::fill : order in is not XRP");
if (result == tesSUCCESS)
result =
redeemIOU(account(), flow.issuers.in, flow.issuers.in.issue());
if (result == tesSUCCESS)
result =
issueIOU(offer.owner(), flow.order.in, flow.order.in.issue());
}
else
{
XRPL_ASSERT(
isXRP(flow.order.in), "ripple::Taker::fill : order in is XRP");
if (result == tesSUCCESS)
result = transferXRP(account(), offer.owner(), flow.order.in);
}
// Now send funds from the account whose offer we're taking
if (cross_type() != CrossType::IouToXrp)
{
XRPL_ASSERT(
!isXRP(flow.order.out),
"ripple::Taker::fill : order out is not XRP");
if (result == tesSUCCESS)
result = redeemIOU(
offer.owner(), flow.issuers.out, flow.issuers.out.issue());
if (result == tesSUCCESS)
result =
issueIOU(account(), flow.order.out, flow.order.out.issue());
}
else
{
XRPL_ASSERT(
isXRP(flow.order.out), "ripple::Taker::fill : order out is XRP");
if (result == tesSUCCESS)
result = transferXRP(offer.owner(), account(), flow.order.out);
}
if (result == tesSUCCESS)
direct_crossings_++;
return result;
}
// Performs bridged funds transfers to fill the given offers and adjusts offers.
TER
Taker::fill(
BasicTaker::Flow const& flow1,
Offer& leg1,
BasicTaker::Flow const& flow2,
Offer& leg2)
{
// Adjust offers accordingly
consume_offer(leg1, flow1.order);
consume_offer(leg2, flow2.order);
TER result = tesSUCCESS;
// Taker to leg1: IOU
if (leg1.owner() != account())
{
if (result == tesSUCCESS)
result = redeemIOU(
account(), flow1.issuers.in, flow1.issuers.in.issue());
if (result == tesSUCCESS)
result =
issueIOU(leg1.owner(), flow1.order.in, flow1.order.in.issue());
}
// leg1 to leg2: bridging over XRP
if (result == tesSUCCESS)
result = transferXRP(leg1.owner(), leg2.owner(), flow1.order.out);
// leg2 to Taker: IOU
if (leg2.owner() != account())
{
if (result == tesSUCCESS)
result = redeemIOU(
leg2.owner(), flow2.issuers.out, flow2.issuers.out.issue());
if (result == tesSUCCESS)
result =
issueIOU(account(), flow2.order.out, flow2.order.out.issue());
}
if (result == tesSUCCESS)
{
bridge_crossings_++;
xrp_flow_ += flow1.order.out;
}
return result;
}
TER
Taker::cross(Offer& offer)
{
// In direct crossings, at least one leg must not be XRP.
if (isXRP(offer.amount().in) && isXRP(offer.amount().out))
return tefINTERNAL;
auto const amount =
do_cross(offer.amount(), offer.quality(), offer.owner());
return fill(amount, offer);
}
TER
Taker::cross(Offer& leg1, Offer& leg2)
{
// In bridged crossings, XRP must can't be the input to the first leg
// or the output of the second leg.
if (isXRP(leg1.amount().in) || isXRP(leg2.amount().out))
return tefINTERNAL;
auto ret = do_cross(
leg1.amount(),
leg1.quality(),
leg1.owner(),
leg2.amount(),
leg2.quality(),
leg2.owner());
return fill(ret.first, leg1, ret.second, leg2);
}
Rate
Taker::calculateRate(
ApplyView const& view,
AccountID const& issuer,
AccountID const& account)
{
return isXRP(issuer) || (account == issuer) ? parityRate
: transferRate(view, issuer);
}
} // namespace ripple

View File

@@ -1,341 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_BOOK_TAKER_H_INCLUDED
#define RIPPLE_APP_BOOK_TAKER_H_INCLUDED
#include <xrpld/app/tx/detail/Offer.h>
#include <xrpld/ledger/View.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
namespace ripple {
/** The flavor of an offer crossing */
enum class CrossType { XrpToIou, IouToXrp, IouToIou };
/** State for the active party during order book or payment operations. */
class BasicTaker
{
private:
AccountID account_;
Quality quality_;
Quality threshold_;
bool sell_;
// The original in and out quantities.
Amounts const original_;
// The amounts still left over for us to try and take.
Amounts remaining_;
// The issuers for the input and output
Issue const& issue_in_;
Issue const& issue_out_;
// The rates that will be paid when the input and output currencies are
// transfered and the currency issuer isn't involved:
Rate const m_rate_in;
Rate const m_rate_out;
// The type of crossing that we are performing
CrossType cross_type_;
protected:
beast::Journal const journal_;
struct Flow
{
explicit Flow() = default;
Amounts order;
Amounts issuers;
bool
sanity_check() const
{
using beast::zero;
if (isXRP(order.in) && isXRP(order.out))
return false;
return order.in >= zero && order.out >= zero &&
issuers.in >= zero && issuers.out >= zero;
}
};
private:
void
log_flow(char const* description, Flow const& flow);
Flow
flow_xrp_to_iou(
Amounts const& offer,
Quality quality,
STAmount const& owner_funds,
STAmount const& taker_funds,
Rate const& rate_out);
Flow
flow_iou_to_xrp(
Amounts const& offer,
Quality quality,
STAmount const& owner_funds,
STAmount const& taker_funds,
Rate const& rate_in);
Flow
flow_iou_to_iou(
Amounts const& offer,
Quality quality,
STAmount const& owner_funds,
STAmount const& taker_funds,
Rate const& rate_in,
Rate const& rate_out);
// Calculates the transfer rate that we should use when calculating
// flows for a particular issue between two accounts.
static Rate
effective_rate(
Rate const& rate,
Issue const& issue,
AccountID const& from,
AccountID const& to);
// The transfer rate for the input currency between the given accounts
Rate
in_rate(AccountID const& from, AccountID const& to) const
{
return effective_rate(m_rate_in, original_.in.issue(), from, to);
}
// The transfer rate for the output currency between the given accounts
Rate
out_rate(AccountID const& from, AccountID const& to) const
{
return effective_rate(m_rate_out, original_.out.issue(), from, to);
}
public:
BasicTaker() = delete;
BasicTaker(BasicTaker const&) = delete;
BasicTaker(
CrossType cross_type,
AccountID const& account,
Amounts const& amount,
Quality const& quality,
std::uint32_t flags,
Rate const& rate_in,
Rate const& rate_out,
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()});
virtual ~BasicTaker() = default;
/** Returns the amount remaining on the offer.
This is the amount at which the offer should be placed. It may either
be for the full amount when there were no crossing offers, or for zero
when the offer fully crossed, or any amount in between.
It is always at the original offer quality (quality_)
*/
Amounts
remaining_offer() const;
/** Returns the amount that the offer was originally placed at. */
Amounts const&
original_offer() const;
/** Returns the account identifier of the taker. */
AccountID const&
account() const noexcept
{
return account_;
}
/** Returns `true` if the quality does not meet the taker's requirements. */
bool
reject(Quality const& quality) const noexcept
{
return quality < threshold_;
}
/** Returns the type of crossing that is being performed */
CrossType
cross_type() const
{
return cross_type_;
}
/** Returns the Issue associated with the input of the offer */
Issue const&
issue_in() const
{
return issue_in_;
}
/** Returns the Issue associated with the output of the offer */
Issue const&
issue_out() const
{
return issue_out_;
}
/** Returns `true` if the taker has run out of funds. */
bool
unfunded() const;
/** Returns `true` if order crossing should not continue.
Order processing is stopped if the taker's order quantities have
been reached, or if the taker has run out of input funds.
*/
bool
done() const;
/** Perform direct crossing through given offer.
@return an `Amounts` describing the flow achieved during cross
*/
BasicTaker::Flow
do_cross(Amounts offer, Quality quality, AccountID const& owner);
/** Perform bridged crossing through given offers.
@return a pair of `Amounts` describing the flow achieved during cross
*/
std::pair<BasicTaker::Flow, BasicTaker::Flow>
do_cross(
Amounts offer1,
Quality quality1,
AccountID const& owner1,
Amounts offer2,
Quality quality2,
AccountID const& owner2);
virtual STAmount
get_funds(AccountID const& account, STAmount const& funds) const = 0;
};
//------------------------------------------------------------------------------
class Taker : public BasicTaker
{
public:
Taker() = delete;
Taker(Taker const&) = delete;
Taker(
CrossType cross_type,
ApplyView& view,
AccountID const& account,
Amounts const& offer,
std::uint32_t flags,
beast::Journal journal);
~Taker() = default;
void
consume_offer(Offer& offer, Amounts const& order);
STAmount
get_funds(AccountID const& account, STAmount const& funds) const override;
STAmount const&
get_xrp_flow() const
{
return xrp_flow_;
}
std::uint32_t
get_direct_crossings() const
{
return direct_crossings_;
}
std::uint32_t
get_bridge_crossings() const
{
return bridge_crossings_;
}
/** Perform a direct or bridged offer crossing as appropriate.
Funds will be transferred accordingly, and offers will be adjusted.
@return tesSUCCESS if successful, or an error code otherwise.
*/
/** @{ */
TER
cross(Offer& offer);
TER
cross(Offer& leg1, Offer& leg2);
/** @} */
private:
static Rate
calculateRate(
ApplyView const& view,
AccountID const& issuer,
AccountID const& account);
TER
fill(BasicTaker::Flow const& flow, Offer& offer);
TER
fill(
BasicTaker::Flow const& flow1,
Offer& leg1,
BasicTaker::Flow const& flow2,
Offer& leg2);
TER
transferXRP(
AccountID const& from,
AccountID const& to,
STAmount const& amount);
TER
redeemIOU(
AccountID const& account,
STAmount const& amount,
Issue const& issue);
TER
issueIOU(
AccountID const& account,
STAmount const& amount,
Issue const& issue);
private:
// The underlying ledger entry we are dealing with
ApplyView& view_;
// The amount of XRP that flowed if we were autobridging
STAmount xrp_flow_;
// The number direct crossings that we performed
std::uint32_t direct_crossings_;
// The number autobridged crossings that we performed
std::uint32_t bridge_crossings_;
};
} // namespace ripple
#endif

View File

@@ -27,11 +27,16 @@
namespace ripple {
// These are the same flags defined as SF_PRIVATE1-4 in HashRouter.h
#define SF_SIGBAD SF_PRIVATE1 // Signature is bad
#define SF_SIGGOOD SF_PRIVATE2 // Signature is good
#define SF_LOCALBAD SF_PRIVATE3 // Local checks failed
#define SF_LOCALGOOD SF_PRIVATE4 // Local checks passed
// These are the same flags defined as HashRouterFlags::PRIVATE1-4 in
// HashRouter.h
constexpr HashRouterFlags SF_SIGBAD =
HashRouterFlags::PRIVATE1; // Signature is bad
constexpr HashRouterFlags SF_SIGGOOD =
HashRouterFlags::PRIVATE2; // Signature is good
constexpr HashRouterFlags SF_LOCALBAD =
HashRouterFlags::PRIVATE3; // Local checks failed
constexpr HashRouterFlags SF_LOCALGOOD =
HashRouterFlags::PRIVATE4; // Local checks passed
//------------------------------------------------------------------------------
@@ -66,11 +71,11 @@ checkValidity(
return {Validity::Valid, ""};
}
if (flags & SF_SIGBAD)
if (any(flags & SF_SIGBAD))
// Signature is known bad
return {Validity::SigBad, "Transaction has bad signature."};
if (!(flags & SF_SIGGOOD))
if (!any(flags & SF_SIGGOOD))
{
// Don't know signature state. Check it.
auto const requireCanonicalSig =
@@ -88,12 +93,12 @@ checkValidity(
}
// Signature is now known good
if (flags & SF_LOCALBAD)
if (any(flags & SF_LOCALBAD))
// ...but the local checks
// are known bad.
return {Validity::SigGoodOnly, "Local checks failed."};
if (flags & SF_LOCALGOOD)
if (any(flags & SF_LOCALGOOD))
// ...and the local checks
// are known good.
return {Validity::Valid, ""};
@@ -112,7 +117,7 @@ checkValidity(
void
forceValidity(HashRouter& router, uint256 const& txid, Validity validity)
{
int flags = 0;
HashRouterFlags flags = HashRouterFlags::UNDEFINED;
switch (validity)
{
case Validity::Valid:
@@ -125,7 +130,7 @@ forceValidity(HashRouter& router, uint256 const& txid, Validity validity)
// would be silly to call directly
break;
}
if (flags)
if (any(flags))
router.setFlags(txid, flags);
}

View File

@@ -1296,13 +1296,13 @@ PeerImp::handleTransaction(
}
// LCOV_EXCL_STOP
int flags;
HashRouterFlags flags;
constexpr std::chrono::seconds tx_interval = 10s;
if (!app_.getHashRouter().shouldProcess(txID, id_, flags, tx_interval))
{
// we have seen this transaction recently
if (flags & SF_BAD)
if (any(flags & HashRouterFlags::BAD))
{
fee_.update(Resource::feeUselessData, "known bad");
JLOG(p_journal_.debug()) << "Ignoring known bad tx " << txID;
@@ -1329,7 +1329,7 @@ PeerImp::handleTransaction(
{
// Skip local checks if a server we trust
// put the transaction in its open ledger
flags |= SF_TRUSTED;
flags |= HashRouterFlags::TRUSTED;
}
// for non-validator nodes only -- localPublicKey is set for
@@ -2841,7 +2841,7 @@ PeerImp::doTransactions(
void
PeerImp::checkTransaction(
int flags,
HashRouterFlags flags,
bool checkSignature,
std::shared_ptr<STTx const> const& stx,
bool batch)
@@ -2866,7 +2866,8 @@ PeerImp::checkTransaction(
(stx->getFieldU32(sfLastLedgerSequence) <
app_.getLedgerMaster().getValidLedgerIndex()))
{
app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD);
app_.getHashRouter().setFlags(
stx->getTransactionID(), HashRouterFlags::BAD);
charge(Resource::feeUselessData, "expired tx");
return;
}
@@ -2925,8 +2926,10 @@ PeerImp::checkTransaction(
<< "Exception checking transaction: " << validReason;
}
// Probably not necessary to set SF_BAD, but doesn't hurt.
app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD);
// Probably not necessary to set HashRouterFlags::BAD, but
// doesn't hurt.
app_.getHashRouter().setFlags(
stx->getTransactionID(), HashRouterFlags::BAD);
charge(
Resource::feeInvalidSignature,
"check transaction signature failure");
@@ -2949,12 +2952,13 @@ PeerImp::checkTransaction(
JLOG(p_journal_.trace())
<< "Exception checking transaction: " << reason;
}
app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD);
app_.getHashRouter().setFlags(
stx->getTransactionID(), HashRouterFlags::BAD);
charge(Resource::feeInvalidSignature, "tx (impossible)");
return;
}
bool const trusted(flags & SF_TRUSTED);
bool const trusted = any(flags & HashRouterFlags::TRUSTED);
app_.getOPs().processTransaction(
tx, trusted, false, NetworkOPs::FailHard::no);
}
@@ -2962,7 +2966,8 @@ PeerImp::checkTransaction(
{
JLOG(p_journal_.warn())
<< "Exception in " << __func__ << ": " << ex.what();
app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD);
app_.getHashRouter().setFlags(
stx->getTransactionID(), HashRouterFlags::BAD);
using namespace std::string_literals;
charge(Resource::feeInvalidData, "tx "s + ex.what());
}

View File

@@ -22,6 +22,7 @@
#include <xrpld/app/consensus/RCLCxPeerPos.h>
#include <xrpld/app/ledger/detail/LedgerReplayMsgHandler.h>
#include <xrpld/app/misc/HashRouter.h>
#include <xrpld/overlay/Squelch.h>
#include <xrpld/overlay/detail/OverlayImpl.h>
#include <xrpld/overlay/detail/ProtocolVersion.h>
@@ -612,7 +613,7 @@ private:
void
checkTransaction(
int flags,
HashRouterFlags flags,
bool checkSignature,
std::shared_ptr<STTx const> const& stx,
bool batch);