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 runs-on: ubuntu-24.04
container: ghcr.io/xrplf/ci/tools-rippled-clang-format container: ghcr.io/xrplf/ci/tools-rippled-clang-format
steps: 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 - uses: actions/checkout@v4
- name: Format first-party sources - name: Format first-party sources
run: | run: |

View File

@@ -24,8 +24,6 @@ env:
CONAN_GLOBAL_CONF: | CONAN_GLOBAL_CONF: |
core.download:parallel={{os.cpu_count()}} core.download:parallel={{os.cpu_count()}}
core.upload: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:jobs={{ (os.cpu_count() * 4/5) | int }}
tools.build:verbosity=verbose tools.build:verbosity=verbose
tools.compilation: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 # 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 # to pollute conan/profiles directory with settings which might not work for others
env: 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_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }} CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
CONAN_GLOBAL_CONF: | CONAN_GLOBAL_CONF: |
core.download:parallel={{ os.cpu_count() }} core.download:parallel={{ os.cpu_count() }}
core.upload: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:jobs={{ (os.cpu_count() * 4/5) | int }}
tools.build:verbosity=verbose tools.build:verbosity=verbose
tools.compilation:verbosity=verbose tools.compilation:verbosity=verbose
@@ -359,37 +357,44 @@ jobs:
cmake --build . cmake --build .
./example | grep '^[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+' ./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: 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] runs-on: [self-hosted, heavy]
container: ghcr.io/xrplf/ci/debian-bookworm:clang-16 container: ghcr.io/xrplf/ci/debian-bookworm:clang-16
env:
build_dir: .build
steps: 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 - name: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: dependencies
uses: ./.github/actions/dependencies
with:
configuration: Debug
- name: prepare environment - name: prepare environment
run: | run: |
mkdir ${GITHUB_WORKSPACE}/.build mkdir -p ${build_dir}
echo "SOURCE_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV echo "SOURCE_DIR=$(pwd)" >> $GITHUB_ENV
echo "BUILD_DIR=$GITHUB_WORKSPACE/.build" >> $GITHUB_ENV echo "BUILD_DIR=$(pwd)/${build_dir}" >> $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
- name: build with instrumentation - name: build with instrumentation
run: | run: |

View File

@@ -27,8 +27,6 @@ env:
CONAN_GLOBAL_CONF: | CONAN_GLOBAL_CONF: |
core.download:parallel={{os.cpu_count()}} core.download:parallel={{os.cpu_count()}}
core.upload: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:jobs=24
tools.build:verbosity=verbose tools.build:verbosity=verbose
tools.compilation:verbosity=verbose tools.compilation:verbosity=verbose

View File

@@ -9,6 +9,7 @@
[settings] [settings]
os={{ os }} os={{ os }}
arch={{ arch }} arch={{ arch }}
build_type=Debug
compiler={{compiler}} compiler={{compiler}}
compiler.version={{ compiler_version }} compiler.version={{ compiler_version }}
compiler.cppstd=20 compiler.cppstd=20
@@ -17,3 +18,20 @@ compiler.runtime=static
{% else %} {% else %}
compiler.libcxx={{detect_api.detect_libcxx(compiler, version, compiler_exe)}} compiler.libcxx={{detect_api.detect_libcxx(compiler, version, compiler_exe)}}
{% endif %} {% 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, 'date/*:header_only': True,
'grpc/*:shared': False, 'grpc/*:shared': False,
'grpc/*:secure': True, '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/*:shared': False,
'libarchive/*:with_acl': False, 'libarchive/*:with_acl': False,
'libarchive/*:with_bzip2': False, 'libarchive/*:with_bzip2': False,
@@ -143,8 +152,6 @@ class Xrpl(ConanFile):
tc.variables['static'] = self.options.static tc.variables['static'] = self.options.static
tc.variables['unity'] = self.options.unity tc.variables['unity'] = self.options.unity
tc.variables['xrpld'] = self.options.xrpld 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() tc.generate()
def build(self): def build(self):

View File

@@ -31,38 +31,28 @@ namespace beast {
template <class Generator> template <class Generator>
void 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; 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(); result_type const v = g();
std::memcpy(buffer, &v, sizeof(v)); std::size_t const offset = count * result_size;
buffer = reinterpret_cast<std::uint8_t*>(buffer) + sizeof(v); std::memcpy(buffer_start + offset, &v, result_size);
bytes -= sizeof(v);
} }
XRPL_ASSERT( if (bytes_remaining > 0)
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)
{ {
auto const v = g(); result_type const v = g();
std::memcpy(buffer, &v, bytes); 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 < template <

View File

@@ -53,19 +53,19 @@
* then change the macro parameter in features.macro to * then change the macro parameter in features.macro to
* `VoteBehavior::DefaultYes`. The communication process is beyond * `VoteBehavior::DefaultYes`. The communication process is beyond
* the scope of these instructions. * 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 * When a feature has been enabled for several years, the conditional code
* may be removed, and the feature "retired". To retire a feature: * may be removed, and the feature "retired". To retire a feature:
@@ -99,13 +99,10 @@ namespace detail {
#undef XRPL_FIX #undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE") #pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE #undef XRPL_RETIRE
#pragma push_macro("XRPL_ABANDON")
#undef XRPL_ABANDON
#define XRPL_FEATURE(name, supported, vote) +1 #define XRPL_FEATURE(name, supported, vote) +1
#define XRPL_FIX(name, supported, vote) +1 #define XRPL_FIX(name, supported, vote) +1
#define XRPL_RETIRE(name) +1 #define XRPL_RETIRE(name) +1
#define XRPL_ABANDON(name) +1
// This value SHOULD be equal to the number of amendments registered in // 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 // 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") #pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE #undef XRPL_FEATURE
#pragma pop_macro("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. /** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated Whether they are enabled depends on the Rules defined in the validated
@@ -365,13 +360,10 @@ foreachFeature(FeatureBitset bs, F&& f)
#undef XRPL_FIX #undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE") #pragma push_macro("XRPL_RETIRE")
#undef 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_FEATURE(name, supported, vote) extern uint256 const feature##name;
#define XRPL_FIX(name, supported, vote) extern uint256 const fix##name; #define XRPL_FIX(name, supported, vote) extern uint256 const fix##name;
#define XRPL_RETIRE(name) #define XRPL_RETIRE(name)
#define XRPL_ABANDON(name)
#include <xrpl/protocol/detail/features.macro> #include <xrpl/protocol/detail/features.macro>
@@ -381,8 +373,6 @@ foreachFeature(FeatureBitset bs, F&& f)
#pragma pop_macro("XRPL_FIX") #pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE #undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE") #pragma pop_macro("XRPL_FEATURE")
#undef XRPL_ABANDON
#pragma pop_macro("XRPL_ABANDON")
} // namespace ripple } // namespace ripple

View File

@@ -26,9 +26,6 @@
#if !defined(XRPL_RETIRE) #if !defined(XRPL_RETIRE)
#error "undefined macro: XRPL_RETIRE" #error "undefined macro: XRPL_RETIRE"
#endif #endif
#if !defined(XRPL_ABANDON)
#error "undefined macro: XRPL_ABANDON"
#endif
// Add new amendments to the top of this list. // Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order. // 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(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete)
XRPL_FEATURE(CryptoConditionsSuite, 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 // The following amendments have been active for at least two years. Their
// pre-amendment code has been removed and the identifiers are deprecated. // pre-amendment code has been removed and the identifiers are deprecated.
// All known amendments and amendments that may appear in a validated // 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. /** A ledger object representing a single asset vault.
\sa keylet::vault
\sa keylet::mptoken
*/ */
LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
{sfPreviousTxnID, soeREQUIRED}, {sfPreviousTxnID, soeREQUIRED},

View File

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

View File

@@ -254,7 +254,7 @@ FeatureCollections::registerFeature(
{ {
check(!readOnly, "Attempting to register a feature after startup."); check(!readOnly, "Attempting to register a feature after startup.");
check( check(
support == Supported::yes || vote != VoteBehavior::DefaultYes, support == Supported::yes || vote == VoteBehavior::DefaultNo,
"Invalid feature parameters. Must be supported to be up-voted."); "Invalid feature parameters. Must be supported to be up-voted.");
Feature const* i = getByName(name); Feature const* i = getByName(name);
if (!i) if (!i)
@@ -268,7 +268,7 @@ FeatureCollections::registerFeature(
features.emplace_back(name, f); features.emplace_back(name, f);
auto const getAmendmentSupport = [=]() { auto const getAmendmentSupport = [=]() {
if (vote == VoteBehavior::Obsolete && support == Supported::yes) if (vote == VoteBehavior::Obsolete)
return AmendmentSupport::Retired; return AmendmentSupport::Retired;
return support == Supported::yes ? AmendmentSupport::Supported return support == Supported::yes ? AmendmentSupport::Supported
: AmendmentSupport::Unsupported; : AmendmentSupport::Unsupported;
@@ -398,14 +398,6 @@ retireFeature(std::string const& name)
return registerFeature(name, Supported::yes, VoteBehavior::Obsolete); 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. */ /** Tell FeatureCollections when registration is complete. */
bool bool
registrationIsDone() registrationIsDone()
@@ -440,8 +432,6 @@ featureToName(uint256 const& f)
#undef XRPL_FIX #undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE") #pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE #undef XRPL_RETIRE
#pragma push_macro("XRPL_ABANDON")
#undef XRPL_ABANDON
#define XRPL_FEATURE(name, supported, vote) \ #define XRPL_FEATURE(name, supported, vote) \
uint256 const feature##name = registerFeature(#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")]] \ [[deprecated("The referenced amendment has been retired")]] \
[[maybe_unused]] \ [[maybe_unused]] \
uint256 const retired##name = retireFeature(#name); 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 // clang-format on
#include <xrpl/protocol/detail/features.macro> #include <xrpl/protocol/detail/features.macro>
@@ -468,8 +453,6 @@ featureToName(uint256 const& f)
#pragma pop_macro("XRPL_FIX") #pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE #undef XRPL_FEATURE
#pragma pop_macro("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 // All of the features should now be registered, since variables in a cpp file
// are initialized from top to bottom. // 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 // Submit a tx with tfInnerBatchTxn
uint256 const txBad = submitTx(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() // Validate: NetworkOPs::processTransaction()
{ {
uint256 const txid = processTxn(tfInnerBatchTxn); uint256 const txid = processTxn(tfInnerBatchTxn);
// HashRouter::getFlags() should return SF_BAD // HashRouter::getFlags() should return LedgerFlags::BAD
BEAST_EXPECT(env.app().getHashRouter().getFlags(txid) == SF_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; TestStopwatch stopwatch;
HashRouter router(getSetup(2s, 1s), stopwatch); HashRouter router(getSetup(2s, 1s), stopwatch);
uint256 const key1(1); HashRouterFlags key1(HashRouterFlags::PRIVATE1);
uint256 const key2(2); HashRouterFlags key2(HashRouterFlags::PRIVATE2);
uint256 const key3(3); 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 // t=0
router.setFlags(key1, 11111); router.setFlags(ukey1, HashRouterFlags::PRIVATE1);
BEAST_EXPECT(router.getFlags(key1) == 11111); BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1);
router.setFlags(key2, 22222); router.setFlags(ukey2, HashRouterFlags::PRIVATE2);
BEAST_EXPECT(router.getFlags(key2) == 22222); BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE2);
// key1 : 0 // key1 : 0
// key2 : 0 // key2 : 0
// key3: null // key3: null
@@ -62,7 +66,7 @@ class HashRouter_test : public beast::unit_test::suite
// Because we are accessing key1 here, it // Because we are accessing key1 here, it
// will NOT be expired for another two ticks // will NOT be expired for another two ticks
BEAST_EXPECT(router.getFlags(key1) == 11111); BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1);
// key1 : 1 // key1 : 1
// key2 : 0 // key2 : 0
// key3 null // key3 null
@@ -70,9 +74,9 @@ class HashRouter_test : public beast::unit_test::suite
++stopwatch; ++stopwatch;
// t=3 // t=3
router.setFlags(key3, 33333); // force expiration router.setFlags(ukey3, HashRouterFlags::PRIVATE3); // force expiration
BEAST_EXPECT(router.getFlags(key1) == 11111); BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1);
BEAST_EXPECT(router.getFlags(key2) == 0); BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::UNDEFINED);
} }
void void
@@ -83,15 +87,21 @@ class HashRouter_test : public beast::unit_test::suite
TestStopwatch stopwatch; TestStopwatch stopwatch;
HashRouter router(getSetup(2s, 1s), stopwatch); HashRouter router(getSetup(2s, 1s), stopwatch);
uint256 const key1(1); HashRouterFlags key1(HashRouterFlags::PRIVATE1);
uint256 const key2(2); HashRouterFlags key2(HashRouterFlags::PRIVATE2);
uint256 const key3(3); HashRouterFlags key3(HashRouterFlags::PRIVATE3);
uint256 const key4(4); 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); BEAST_EXPECT(key1 != key2 && key2 != key3 && key3 != key4);
// t=0 // t=0
router.setFlags(key1, 12345); router.setFlags(ukey1, HashRouterFlags::BAD);
BEAST_EXPECT(router.getFlags(key1) == 12345); BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::BAD);
// key1 : 0 // key1 : 0
// key2 : null // key2 : null
// key3 : null // key3 : null
@@ -103,26 +113,27 @@ class HashRouter_test : public beast::unit_test::suite
// so key1 will be expired after the second // so key1 will be expired after the second
// call to setFlags. // call to setFlags.
// t=1 // t=1
router.setFlags(key2, 9999);
BEAST_EXPECT(router.getFlags(key1) == 12345); router.setFlags(ukey2, HashRouterFlags::PRIVATE5);
BEAST_EXPECT(router.getFlags(key2) == 9999); BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::BAD);
BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
// key1 : 1 // key1 : 1
// key2 : 1 // key2 : 1
// key3 : null // key3 : null
++stopwatch; ++stopwatch;
// t=2 // t=2
BEAST_EXPECT(router.getFlags(key2) == 9999); BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
// key1 : 1 // key1 : 1
// key2 : 2 // key2 : 2
// key3 : null // key3 : null
++stopwatch; ++stopwatch;
// t=3 // t=3
router.setFlags(key3, 2222); router.setFlags(ukey3, HashRouterFlags::BAD);
BEAST_EXPECT(router.getFlags(key1) == 0); BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(key2) == 9999); BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
BEAST_EXPECT(router.getFlags(key3) == 2222); BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::BAD);
// key1 : 3 // key1 : 3
// key2 : 3 // key2 : 3
// key3 : 3 // key3 : 3
@@ -130,10 +141,10 @@ class HashRouter_test : public beast::unit_test::suite
++stopwatch; ++stopwatch;
// t=4 // t=4
// No insertion, no expiration // No insertion, no expiration
router.setFlags(key1, 7654); router.setFlags(ukey1, HashRouterFlags::SAVED);
BEAST_EXPECT(router.getFlags(key1) == 7654); BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::SAVED);
BEAST_EXPECT(router.getFlags(key2) == 9999); BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5);
BEAST_EXPECT(router.getFlags(key3) == 2222); BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::BAD);
// key1 : 4 // key1 : 4
// key2 : 4 // key2 : 4
// key3 : 4 // key3 : 4
@@ -142,11 +153,11 @@ class HashRouter_test : public beast::unit_test::suite
++stopwatch; ++stopwatch;
// t=6 // t=6
router.setFlags(key4, 7890); router.setFlags(ukey4, HashRouterFlags::TRUSTED);
BEAST_EXPECT(router.getFlags(key1) == 0); BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(key2) == 0); BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(key3) == 0); BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.getFlags(key4) == 7890); BEAST_EXPECT(router.getFlags(ukey4) == HashRouterFlags::TRUSTED);
// key1 : 6 // key1 : 6
// key2 : 6 // key2 : 6
// key3 : 6 // key3 : 6
@@ -168,18 +179,18 @@ class HashRouter_test : public beast::unit_test::suite
uint256 const key4(4); uint256 const key4(4);
BEAST_EXPECT(key1 != key2 && key2 != key3 && key3 != key4); 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); router.addSuppression(key1);
BEAST_EXPECT(router.addSuppressionPeer(key2, 15)); BEAST_EXPECT(router.addSuppressionPeer(key2, 15));
BEAST_EXPECT(router.addSuppressionPeer(key3, 20, flags)); BEAST_EXPECT(router.addSuppressionPeer(key3, 20, flags));
BEAST_EXPECT(flags == 0); BEAST_EXPECT(flags == HashRouterFlags::UNDEFINED);
++stopwatch; ++stopwatch;
BEAST_EXPECT(!router.addSuppressionPeer(key1, 2)); BEAST_EXPECT(!router.addSuppressionPeer(key1, 2));
BEAST_EXPECT(!router.addSuppressionPeer(key2, 3)); BEAST_EXPECT(!router.addSuppressionPeer(key2, 3));
BEAST_EXPECT(!router.addSuppressionPeer(key3, 4, flags)); BEAST_EXPECT(!router.addSuppressionPeer(key3, 4, flags));
BEAST_EXPECT(flags == 0); BEAST_EXPECT(flags == HashRouterFlags::UNDEFINED);
BEAST_EXPECT(router.addSuppressionPeer(key4, 5)); BEAST_EXPECT(router.addSuppressionPeer(key4, 5));
} }
@@ -192,9 +203,9 @@ class HashRouter_test : public beast::unit_test::suite
HashRouter router(getSetup(2s, 1s), stopwatch); HashRouter router(getSetup(2s, 1s), stopwatch);
uint256 const key1(1); uint256 const key1(1);
BEAST_EXPECT(router.setFlags(key1, 10)); BEAST_EXPECT(router.setFlags(key1, HashRouterFlags::PRIVATE1));
BEAST_EXPECT(!router.setFlags(key1, 10)); BEAST_EXPECT(!router.setFlags(key1, HashRouterFlags::PRIVATE1));
BEAST_EXPECT(router.setFlags(key1, 20)); BEAST_EXPECT(router.setFlags(key1, HashRouterFlags::PRIVATE2));
} }
void void
@@ -250,7 +261,7 @@ class HashRouter_test : public beast::unit_test::suite
HashRouter router(getSetup(5s, 1s), stopwatch); HashRouter router(getSetup(5s, 1s), stopwatch);
uint256 const key(1); uint256 const key(1);
HashRouter::PeerShortID peer = 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));
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: public:
void void
run() override run() override
@@ -375,6 +419,7 @@ public:
testRelay(); testRelay();
testProcess(); testProcess();
testSetup(); testSetup();
testFlagsOps();
} }
}; };

View File

@@ -18,10 +18,16 @@
//============================================================================== //==============================================================================
#include <test/jtx.h> #include <test/jtx.h>
#include <test/jtx/credentials.h>
#include <test/jtx/permissioned_domains.h>
#include <test/jtx/trust.h> #include <test/jtx/trust.h>
#include <test/jtx/xchain_bridge.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/Feature.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
namespace ripple { namespace ripple {
@@ -61,6 +67,48 @@ class MPToken_test : public beast::unit_test::suite
.metadata = "test", .metadata = "test",
.err = temMALFORMED}); .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 // tries to set a txfee greater than max
mptAlice.create( mptAlice.create(
{.maxAmt = 100, {.maxAmt = 100,
@@ -140,6 +188,48 @@ class MPToken_test : public beast::unit_test::suite
BEAST_EXPECT( BEAST_EXPECT(
result[sfMaximumAmount.getJsonName()] == "9223372036854775807"); 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 void
@@ -499,6 +589,59 @@ class MPToken_test : public beast::unit_test::suite
.flags = 0x00000008, .flags = 0x00000008,
.err = temINVALID_FLAG}); .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 // set both lock and unlock flags at the same time will fail
mptAlice.set( mptAlice.set(
{.account = alice, {.account = alice,
@@ -582,6 +725,53 @@ class MPToken_test : public beast::unit_test::suite
mptAlice.set( mptAlice.set(
{.holder = cindy, .flags = tfMPTLock, .err = tecNO_DST}); {.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 void
@@ -590,12 +780,13 @@ class MPToken_test : public beast::unit_test::suite
testcase("Enabled set transaction"); testcase("Enabled set transaction");
using namespace test::jtx; using namespace test::jtx;
// Test locking and unlocking
Env env{*this, features};
Account const alice("alice"); // issuer Account const alice("alice"); // issuer
Account const bob("bob"); // holder Account const bob("bob"); // holder
{
// Test locking and unlocking
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}}); MPTTester mptAlice(env, alice, {.holders = {bob}});
// create a mptokenissuance with locking // create a mptokenissuance with locking
@@ -620,7 +811,8 @@ class MPToken_test : public beast::unit_test::suite
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
// alice unlocks bob's mptoken // alice unlocks bob's mptoken
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); mptAlice.set(
{.account = alice, .holder = bob, .flags = tfMPTUnlock});
// locks up bob's mptoken again // locks up bob's mptoken again
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
@@ -648,15 +840,78 @@ class MPToken_test : public beast::unit_test::suite
mptAlice.set({.account = alice, .flags = tfMPTUnlock}); mptAlice.set({.account = alice, .flags = tfMPTUnlock});
// alice unlocks bob's mptoken // alice unlocks bob's mptoken
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); mptAlice.set(
{.account = alice, .holder = bob, .flags = tfMPTUnlock});
// alice unlocks mptissuance and bob's mptoken again despite that // alice unlocks mptissuance and bob's mptoken again despite that
// they are already unlocked. Make sure this will not change the // they are already unlocked. Make sure this will not change the
// flags // flags
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); mptAlice.set(
{.account = alice, .holder = bob, .flags = tfMPTUnlock});
mptAlice.set({.account = alice, .flags = tfMPTUnlock}); mptAlice.set({.account = alice, .flags = tfMPTUnlock});
} }
if (features[featureSingleAssetVault])
{
// Add permissioned domain
std::string const credType = "credential";
// Test setting and resetting domain ID
Env env{*this, features};
auto const domainId1 = [&]() {
Account const credIssuer1{"credIssuer1"};
env.fund(XRP(1000), credIssuer1);
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 void
testPayment(FeatureBitset features) testPayment(FeatureBitset features)
{ {
@@ -889,6 +1144,200 @@ class MPToken_test : public beast::unit_test::suite
mptAlice.pay(bob, alice, 100, tecNO_AUTH); 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 // Non-issuer cannot send to each other if MPTCanTransfer isn't set
{ {
Env env(*this, features); Env env(*this, features);
@@ -1340,10 +1789,8 @@ class MPToken_test : public beast::unit_test::suite
} }
void void
testDepositPreauth() testDepositPreauth(FeatureBitset features)
{ {
testcase("DepositPreauth");
using namespace test::jtx; using namespace test::jtx;
Account const alice("alice"); // issuer Account const alice("alice"); // issuer
Account const bob("bob"); // holder Account const bob("bob"); // holder
@@ -1352,8 +1799,11 @@ class MPToken_test : public beast::unit_test::suite
char const credType[] = "abcde"; char const credType[] = "abcde";
if (features[featureCredentials])
{ {
Env env(*this); testcase("DepositPreauth");
Env env(*this, features);
env.fund(XRP(50000), diana, dpIssuer); env.fund(XRP(50000), diana, dpIssuer);
env.close(); env.close();
@@ -2297,6 +2747,8 @@ public:
// MPTokenIssuanceCreate // MPTokenIssuanceCreate
testCreateValidation(all - featureSingleAssetVault); testCreateValidation(all - featureSingleAssetVault);
testCreateValidation(
(all | featureSingleAssetVault) - featurePermissionedDomains);
testCreateValidation(all | featureSingleAssetVault); testCreateValidation(all | featureSingleAssetVault);
testCreateEnabled(all - featureSingleAssetVault); testCreateEnabled(all - featureSingleAssetVault);
testCreateEnabled(all | featureSingleAssetVault); testCreateEnabled(all | featureSingleAssetVault);
@@ -2314,7 +2766,11 @@ public:
testAuthorizeEnabled(all | featureSingleAssetVault); testAuthorizeEnabled(all | featureSingleAssetVault);
// MPTokenIssuanceSet // MPTokenIssuanceSet
testSetValidation(all); testSetValidation(all - featureSingleAssetVault);
testSetValidation(
(all | featureSingleAssetVault) - featurePermissionedDomains);
testSetValidation(all | featureSingleAssetVault);
testSetEnabled(all - featureSingleAssetVault); testSetEnabled(all - featureSingleAssetVault);
testSetEnabled(all | featureSingleAssetVault); testSetEnabled(all | featureSingleAssetVault);
@@ -2323,8 +2779,9 @@ public:
testClawback(all); testClawback(all);
// Test Direct Payment // Test Direct Payment
testPayment(all); testPayment(all | featureSingleAssetVault);
testDepositPreauth(); testDepositPreauth(all);
testDepositPreauth(all - featureCredentials);
// Test MPT Amount is invalid in Tx, which don't support MPT // Test MPT Amount is invalid in Tx, which don't support MPT
testMPTInvalidInTx(all); testMPTInvalidInTx(all);

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -996,7 +996,8 @@ pendSaveValidated(
bool isSynchronous, bool isSynchronous,
bool isCurrent) 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 // We have tried to save this ledger recently
auto stream = app.journal("Ledger").debug(); auto stream = app.journal("Ledger").debug();

View File

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

View File

@@ -31,20 +31,59 @@
namespace ripple { namespace ripple {
// TODO convert these macros to int constants or an enum enum class HashRouterFlags : std::uint16_t {
#define SF_BAD 0x02 // Temporarily bad // Public flags
#define SF_SAVED 0x04 UNDEFINED = 0x00,
#define SF_HELD 0x08 // Held by LedgerMaster after potential processing failure BAD = 0x02, // Temporarily bad
#define SF_TRUSTED 0x10 // comes from trusted source 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. // Private flags (used internally in apply.cpp)
// Do not attempt to read, set, or reuse. // Do not attempt to read, set, or reuse.
#define SF_PRIVATE1 0x0100 PRIVATE1 = 0x0100,
#define SF_PRIVATE2 0x0200 PRIVATE2 = 0x0200,
#define SF_PRIVATE3 0x0400 PRIVATE3 = 0x0400,
#define SF_PRIVATE4 0x0800 PRIVATE4 = 0x0800,
#define SF_PRIVATE5 0x1000 PRIVATE5 = 0x1000,
#define SF_PRIVATE6 0x2000 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; class Config;
@@ -101,14 +140,14 @@ private:
peers_.insert(peer); peers_.insert(peer);
} }
int HashRouterFlags
getFlags(void) const getFlags(void) const
{ {
return flags_; return flags_;
} }
void void
setFlags(int flagsToSet) setFlags(HashRouterFlags flagsToSet)
{ {
flags_ |= flagsToSet; flags_ |= flagsToSet;
} }
@@ -154,7 +193,7 @@ private:
} }
private: private:
int flags_ = 0; HashRouterFlags flags_ = HashRouterFlags::UNDEFINED;
std::set<PeerShortID> peers_; std::set<PeerShortID> peers_;
// This could be generalized to a map, if more // This could be generalized to a map, if more
// than one flag needs to expire independently. // than one flag needs to expire independently.
@@ -190,14 +229,17 @@ public:
addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer); addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer);
bool 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 // Add a peer suppression and return whether the entry should be processed
bool bool
shouldProcess( shouldProcess(
uint256 const& key, uint256 const& key,
PeerShortID peer, PeerShortID peer,
int& flags, HashRouterFlags& flags,
std::chrono::seconds tx_interval); std::chrono::seconds tx_interval);
/** Set the flags on a hash. /** Set the flags on a hash.
@@ -205,9 +247,9 @@ public:
@return `true` if the flags were changed. `false` if unchanged. @return `true` if the flags were changed. `false` if unchanged.
*/ */
bool bool
setFlags(uint256 const& key, int flags); setFlags(uint256 const& key, HashRouterFlags flags);
int HashRouterFlags
getFlags(uint256 const& key); getFlags(uint256 const& key);
/** Determines whether the hashed item should be relayed. /** 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 txid = trans->getTransactionID();
auto const flags = app_.getHashRouter().getFlags(txid); 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"; JLOG(m_journal.warn()) << "Submitted transaction cached bad";
return; return;
@@ -1251,7 +1251,7 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr<Transaction>& transaction)
{ {
auto const newFlags = app_.getHashRouter().getFlags(transaction->getID()); auto const newFlags = app_.getHashRouter().getFlags(transaction->getID());
if ((newFlags & SF_BAD) != 0) if ((newFlags & HashRouterFlags::BAD) != HashRouterFlags::UNDEFINED)
{ {
// cached bad // cached bad
JLOG(m_journal.warn()) << transaction->getID() << ": cached bad!\n"; JLOG(m_journal.warn()) << transaction->getID() << ": cached bad!\n";
@@ -1270,7 +1270,8 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr<Transaction>& transaction)
{ {
transaction->setStatus(INVALID); transaction->setStatus(INVALID);
transaction->setResult(temINVALID_FLAG); transaction->setResult(temINVALID_FLAG);
app_.getHashRouter().setFlags(transaction->getID(), SF_BAD); app_.getHashRouter().setFlags(
transaction->getID(), HashRouterFlags::BAD);
return false; return false;
} }
@@ -1289,7 +1290,8 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr<Transaction>& transaction)
JLOG(m_journal.info()) << "Transaction has bad signature: " << reason; JLOG(m_journal.info()) << "Transaction has bad signature: " << reason;
transaction->setStatus(INVALID); transaction->setStatus(INVALID);
transaction->setResult(temBAD_SIGNATURE); transaction->setResult(temBAD_SIGNATURE);
app_.getHashRouter().setFlags(transaction->getID(), SF_BAD); app_.getHashRouter().setFlags(
transaction->getID(), HashRouterFlags::BAD);
return false; return false;
} }
@@ -1412,7 +1414,8 @@ NetworkOPsImp::processTransactionSet(CanonicalTXSet const& set)
JLOG(m_journal.trace()) JLOG(m_journal.trace())
<< "Exception checking transaction: " << reason; << "Exception checking transaction: " << reason;
} }
app_.getHashRouter().setFlags(tx->getTransactionID(), SF_BAD); app_.getHashRouter().setFlags(
tx->getTransactionID(), HashRouterFlags::BAD);
continue; continue;
} }
@@ -1538,7 +1541,8 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
e.transaction->setResult(e.result); e.transaction->setResult(e.result);
if (isTemMalformed(e.result)) if (isTemMalformed(e.result))
app_.getHashRouter().setFlags(e.transaction->getID(), SF_BAD); app_.getHashRouter().setFlags(
e.transaction->getID(), HashRouterFlags::BAD);
#ifdef DEBUG #ifdef DEBUG
if (e.result != tesSUCCESS) if (e.result != tesSUCCESS)
@@ -1626,7 +1630,8 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
// (5) ledgers into the future. (Remember that an // (5) ledgers into the future. (Remember that an
// unseated optional compares as less than all seated // unseated optional compares as less than all seated
// values, so it has to be checked explicitly first.) // 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 // checks before setting. If the flag is set, it returns
// false, which means it's been held once without one of // false, which means it's been held once without one of
// the other conditions, so don't hold it again. Time's // 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 || if (e.local ||
(ledgersLeft && ledgersLeft <= LocalTxs::holdLedgers) || (ledgersLeft && ledgersLeft <= LocalTxs::holdLedgers) ||
app_.getHashRouter().setFlags( app_.getHashRouter().setFlags(
e.transaction->getID(), SF_HELD)) e.transaction->getID(), HashRouterFlags::HELD))
{ {
// transaction should be held // transaction should be held
JLOG(m_journal.debug()) JLOG(m_journal.debug())
@@ -2410,6 +2415,7 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
jvObj[jss::flags] = val->getFlags(); jvObj[jss::flags] = val->getFlags();
jvObj[jss::signing_time] = *(*val)[~sfSigningTime]; jvObj[jss::signing_time] = *(*val)[~sfSigningTime];
jvObj[jss::data] = strHex(val->getSerializer().slice()); jvObj[jss::data] = strHex(val->getSerializer().slice());
jvObj[jss::network_id] = app_.config().NETWORK_ID;
if (auto version = (*val)[~sfServerVersion]) if (auto version = (*val)[~sfServerVersion])
jvObj[jss::server_version] = std::to_string(*version); 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( jvObj[jss::ledger_time] = Json::Value::UInt(
lpAccepted->info().closeTime.time_since_epoch().count()); lpAccepted->info().closeTime.time_since_epoch().count());
jvObj[jss::network_id] = app_.config().NETWORK_ID;
if (!lpAccepted->rules().enabled(featureXRPFees)) if (!lpAccepted->rules().enabled(featureXRPFees))
jvObj[jss::fee_ref] = Config::FEE_UNITS_DEPRECATED; jvObj[jss::fee_ref] = Config::FEE_UNITS_DEPRECATED;
jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped(); jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped();
@@ -4172,6 +4180,7 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
jvResult[jss::reserve_base] = jvResult[jss::reserve_base] =
lpClosed->fees().accountReserve(0).jsonClipped(); lpClosed->fees().accountReserve(0).jsonClipped();
jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
jvResult[jss::network_id] = app_.config().NETWORK_ID;
} }
if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger()) if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger())

View File

@@ -26,9 +26,9 @@
#include <xrpl/basics/base_uint.h> #include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/WrappedSink.h> #include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/STAmount.h> #include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h> #include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/st.h> #include <xrpl/protocol/st.h>
namespace ripple { namespace ripple {
@@ -311,374 +311,6 @@ CreateOffer::checkAcceptAsset(
return tesSUCCESS; 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> std::pair<TER, Amounts>
CreateOffer::flowCross( CreateOffer::flowCross(
PaymentSandbox& psb, PaymentSandbox& psb,
@@ -883,21 +515,6 @@ CreateOffer::flowCross(
return {tecINTERNAL, takerAmount}; 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 std::string
CreateOffer::format_amount(STAmount const& amount) CreateOffer::format_amount(STAmount const& amount)
{ {
@@ -907,20 +524,6 @@ CreateOffer::format_amount(STAmount const& amount)
return txt; 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 TER
CreateOffer::applyHybrid( CreateOffer::applyHybrid(
Sandbox& sb, Sandbox& sb,
@@ -1084,11 +687,6 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
// We reverse pays and gets because during crossing we are taking. // We reverse pays and gets because during crossing we are taking.
Amounts const takerAmount(saTakerGets, saTakerPays); 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: " JLOG(j_.debug()) << "Attempting cross: "
<< to_string(takerAmount.in.issue()) << " -> " << to_string(takerAmount.in.issue()) << " -> "
<< to_string(takerAmount.out.issue()); << to_string(takerAmount.out.issue());
@@ -1101,8 +699,17 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
stream << " out: " << format_amount(takerAmount.out); 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) = 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 // We expect the implementation of cross to succeed
// or give a tec. // or give a tec.

View File

@@ -20,10 +20,10 @@
#ifndef RIPPLE_TX_CREATEOFFER_H_INCLUDED #ifndef RIPPLE_TX_CREATEOFFER_H_INCLUDED
#define 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 <xrpld/app/tx/detail/Transactor.h>
#include <xrpl/protocol/Quality.h>
namespace ripple { namespace ripple {
class PaymentSandbox; class PaymentSandbox;
@@ -36,8 +36,7 @@ public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
/** Construct a Transactor subclass that creates an offer in the ledger. */ /** Construct a Transactor subclass that creates an offer in the ledger. */
explicit CreateOffer(ApplyContext& ctx) explicit CreateOffer(ApplyContext& ctx) : Transactor(ctx)
: Transactor(ctx), stepCounter_(1000, j_)
{ {
} }
@@ -52,10 +51,6 @@ public:
static TER static TER
preclaim(PreclaimContext const& ctx); 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. */ /** Precondition: fee collection is likely. Attempt to create the offer. */
TER TER
doApply() override; doApply() override;
@@ -73,42 +68,6 @@ private:
beast::Journal const j, beast::Journal const j,
Issue const& issue); 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. // Use the payment flow code to perform offer crossing.
std::pair<TER, Amounts> std::pair<TER, Amounts>
flowCross( flowCross(
@@ -117,17 +76,6 @@ private:
Amounts const& takerAmount, Amounts const& takerAmount,
std::optional<uint256> const& domainID); 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 static std::string
format_amount(STAmount const& amount); format_amount(STAmount const& amount);
@@ -139,13 +87,6 @@ private:
STAmount const& saTakerPays, STAmount const& saTakerPays,
STAmount const& saTakerGets, STAmount const& saTakerGets,
std::function<void(SLE::ref, std::optional<uint256>)> const& setDir); 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; using OfferCreate = CreateOffer;

View File

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

View File

@@ -31,6 +31,11 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
if (!ctx.rules.enabled(featureMPTokensV1)) if (!ctx.rules.enabled(featureMPTokensV1))
return temDISABLED; 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)) if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret; return ret;
@@ -48,6 +53,16 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
return temMALFORMED; 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 (auto const metadata = ctx.tx[~sfMPTokenMetadata])
{ {
if (metadata->length() == 0 || if (metadata->length() == 0 ||
@@ -142,6 +157,7 @@ MPTokenIssuanceCreate::doApply()
.assetScale = tx[~sfAssetScale], .assetScale = tx[~sfAssetScale],
.transferFee = tx[~sfTransferFee], .transferFee = tx[~sfTransferFee],
.metadata = tx[~sfMPTokenMetadata], .metadata = tx[~sfMPTokenMetadata],
.domainId = tx[~sfDomainID],
}); });
return result ? tesSUCCESS : result.error(); return result ? tesSUCCESS : result.error();
} }

View File

@@ -21,6 +21,7 @@
#include <xrpld/app/tx/detail/MPTokenIssuanceSet.h> #include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/TxFlags.h> #include <xrpl/protocol/TxFlags.h>
namespace ripple { namespace ripple {
@@ -31,6 +32,14 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
if (!ctx.rules.enabled(featureMPTokensV1)) if (!ctx.rules.enabled(featureMPTokensV1))
return temDISABLED; 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)) if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret; return ret;
@@ -48,6 +57,13 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
if (holderID && accountID == holderID) if (holderID && accountID == holderID)
return temMALFORMED; 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); return preflight2(ctx);
} }
@@ -97,9 +113,14 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
if (!sleMptIssuance) if (!sleMptIssuance)
return tecOBJECT_NOT_FOUND; return tecOBJECT_NOT_FOUND;
// if the mpt has disabled locking if (!sleMptIssuance->isFlag(lsfMPTCanLock))
if (!((*sleMptIssuance)[sfFlags] & lsfMPTCanLock)) {
// For readability two separate `if` rather than `||` of two conditions
if (!ctx.view.rules().enabled(featureSingleAssetVault))
return tecNO_PERMISSION; return tecNO_PERMISSION;
else if (ctx.tx.isFlag(tfMPTLock) || ctx.tx.isFlag(tfMPTUnlock))
return tecNO_PERMISSION;
}
// ensure it is issued by the tx submitter // ensure it is issued by the tx submitter
if ((*sleMptIssuance)[sfIssuer] != ctx.tx[sfAccount]) if ((*sleMptIssuance)[sfIssuer] != ctx.tx[sfAccount])
@@ -117,6 +138,20 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
return tecOBJECT_NOT_FOUND; 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; return tesSUCCESS;
} }
@@ -126,6 +161,7 @@ MPTokenIssuanceSet::doApply()
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID]; auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
auto const txFlags = ctx_.tx.getFlags(); auto const txFlags = ctx_.tx.getFlags();
auto const holderID = ctx_.tx[~sfHolder]; auto const holderID = ctx_.tx[~sfHolder];
auto const domainID = ctx_.tx[~sfDomainID];
std::shared_ptr<SLE> sle; std::shared_ptr<SLE> sle;
if (holderID) if (holderID)
@@ -147,6 +183,24 @@ MPTokenIssuanceSet::doApply()
if (flagsIn != flagsOut) if (flagsIn != flagsOut)
sle->setFieldU32(sfFlags, 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); view().update(sle);
return tesSUCCESS; 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 { namespace ripple {
// These are the same flags defined as SF_PRIVATE1-4 in HashRouter.h // These are the same flags defined as HashRouterFlags::PRIVATE1-4 in
#define SF_SIGBAD SF_PRIVATE1 // Signature is bad // HashRouter.h
#define SF_SIGGOOD SF_PRIVATE2 // Signature is good constexpr HashRouterFlags SF_SIGBAD =
#define SF_LOCALBAD SF_PRIVATE3 // Local checks failed HashRouterFlags::PRIVATE1; // Signature is bad
#define SF_LOCALGOOD SF_PRIVATE4 // Local checks passed 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, ""}; return {Validity::Valid, ""};
} }
if (flags & SF_SIGBAD) if (any(flags & SF_SIGBAD))
// Signature is known bad // Signature is known bad
return {Validity::SigBad, "Transaction has bad signature."}; return {Validity::SigBad, "Transaction has bad signature."};
if (!(flags & SF_SIGGOOD)) if (!any(flags & SF_SIGGOOD))
{ {
// Don't know signature state. Check it. // Don't know signature state. Check it.
auto const requireCanonicalSig = auto const requireCanonicalSig =
@@ -88,12 +93,12 @@ checkValidity(
} }
// Signature is now known good // Signature is now known good
if (flags & SF_LOCALBAD) if (any(flags & SF_LOCALBAD))
// ...but the local checks // ...but the local checks
// are known bad. // are known bad.
return {Validity::SigGoodOnly, "Local checks failed."}; return {Validity::SigGoodOnly, "Local checks failed."};
if (flags & SF_LOCALGOOD) if (any(flags & SF_LOCALGOOD))
// ...and the local checks // ...and the local checks
// are known good. // are known good.
return {Validity::Valid, ""}; return {Validity::Valid, ""};
@@ -112,7 +117,7 @@ checkValidity(
void void
forceValidity(HashRouter& router, uint256 const& txid, Validity validity) forceValidity(HashRouter& router, uint256 const& txid, Validity validity)
{ {
int flags = 0; HashRouterFlags flags = HashRouterFlags::UNDEFINED;
switch (validity) switch (validity)
{ {
case Validity::Valid: case Validity::Valid:
@@ -125,7 +130,7 @@ forceValidity(HashRouter& router, uint256 const& txid, Validity validity)
// would be silly to call directly // would be silly to call directly
break; break;
} }
if (flags) if (any(flags))
router.setFlags(txid, flags); router.setFlags(txid, flags);
} }

View File

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

View File

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