diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 83752c4780..0d81f87791 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -11,6 +11,15 @@ jobs: runs-on: ubuntu-24.04 container: ghcr.io/xrplf/ci/tools-rippled-clang-format steps: + # For jobs running in containers, $GITHUB_WORKSPACE and ${{ github.workspace }} might not be the + # same directory. The actions/checkout step is *supposed* to checkout into $GITHUB_WORKSPACE and + # then add it to safe.directory (see instructions at https://github.com/actions/checkout) + # but that's apparently not happening for some container images. We can't be sure what is actually + # happening, so let's pre-emptively add both directories to safe.directory. There's a + # Github issue opened in 2022 and not resolved in 2025 https://github.com/actions/runner/issues/2058 ¯\_(ツ)_/¯ + - run: | + git config --global --add safe.directory $GITHUB_WORKSPACE + git config --global --add safe.directory ${{ github.workspace }} - uses: actions/checkout@v4 - name: Format first-party sources run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 599aa9d3d6..beff412a18 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -24,8 +24,6 @@ env: CONAN_GLOBAL_CONF: | core.download:parallel={{os.cpu_count()}} core.upload:parallel={{os.cpu_count()}} - core:default_build_profile=libxrpl - core:default_profile=libxrpl tools.build:jobs={{ (os.cpu_count() * 4/5) | int }} tools.build:verbosity=verbose tools.compilation:verbosity=verbose diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 493d370fa4..c3560dbab7 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -19,14 +19,12 @@ concurrency: # This part of Conan configuration is specific to this workflow only; we do not want # to pollute conan/profiles directory with settings which might not work for others env: - #CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev + CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }} CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }} CONAN_GLOBAL_CONF: | core.download:parallel={{ os.cpu_count() }} core.upload:parallel={{ os.cpu_count() }} - core:default_build_profile=libxrpl - core:default_profile=libxrpl tools.build:jobs={{ (os.cpu_count() * 4/5) | int }} tools.build:verbosity=verbose tools.compilation:verbosity=verbose @@ -359,37 +357,44 @@ jobs: cmake --build . ./example | grep '^[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+' - # NOTE we are not using dependencies built above because it lags with - # compiler versions. Instrumentation requires clang version 16 or - # later - instrumentation-build: - if: ${{ github.event_name == 'push' || github.event.pull_request.draft != true || contains(github.event.pull_request.labels.*.name, 'DraftRunCI') }} + needs: dependencies runs-on: [self-hosted, heavy] container: ghcr.io/xrplf/ci/debian-bookworm:clang-16 - + env: + build_dir: .build steps: + - name: download cache + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + name: linux-clang-Debug + + - name: extract cache + run: | + mkdir -p ${CONAN_HOME} + tar -xzf conan.tar.gz -C ${CONAN_HOME} + + - name: check environment + run: | + echo ${PATH} | tr ':' '\n' + conan --version + cmake --version + env | sort + ls ${CONAN_HOME} + - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - name: dependencies + uses: ./.github/actions/dependencies + with: + configuration: Debug + - name: prepare environment run: | - mkdir ${GITHUB_WORKSPACE}/.build - echo "SOURCE_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV - echo "BUILD_DIR=$GITHUB_WORKSPACE/.build" >> $GITHUB_ENV - - - name: configure Conan - run: | - echo "${CONAN_GLOBAL_CONF}" >> $(conan config home)/global.conf - conan config install conan/profiles/ -tf $(conan config home)/profiles/ - conan profile show - - name: build dependencies - run: | - cd ${BUILD_DIR} - conan install ${SOURCE_DIR} \ - --output-folder ${BUILD_DIR} \ - --build missing \ - --settings:all build_type=Debug + mkdir -p ${build_dir} + echo "SOURCE_DIR=$(pwd)" >> $GITHUB_ENV + echo "BUILD_DIR=$(pwd)/${build_dir}" >> $GITHUB_ENV - name: build with instrumentation run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index fbc6f1adc1..243a1f1894 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -27,8 +27,6 @@ env: CONAN_GLOBAL_CONF: | core.download:parallel={{os.cpu_count()}} core.upload:parallel={{os.cpu_count()}} - core:default_build_profile=libxrpl - core:default_profile=libxrpl tools.build:jobs=24 tools.build:verbosity=verbose tools.compilation:verbosity=verbose diff --git a/conan/profiles/libxrpl b/conan/profiles/default similarity index 50% rename from conan/profiles/libxrpl rename to conan/profiles/default index b037b8c4a2..0417704f8a 100644 --- a/conan/profiles/libxrpl +++ b/conan/profiles/default @@ -9,6 +9,7 @@ [settings] os={{ os }} arch={{ arch }} +build_type=Debug compiler={{compiler}} compiler.version={{ compiler_version }} compiler.cppstd=20 @@ -17,3 +18,20 @@ compiler.runtime=static {% else %} compiler.libcxx={{detect_api.detect_libcxx(compiler, version, compiler_exe)}} {% endif %} + +[conf] +{% if compiler == "clang" and compiler_version >= 19 %} +tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw'] +{% endif %} +{% if compiler == "apple-clang" and compiler_version >= 17 %} +tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw'] +{% endif %} +{% if compiler == "clang" and compiler_version == 16 %} +tools.build:cxxflags=['-DBOOST_ASIO_DISABLE_CONCEPTS'] +{% endif %} +{% if compiler == "gcc" and compiler_version < 13 %} +tools.build:cxxflags=['-Wno-restrict'] +{% endif %} + +[tool_requires] +!cmake/*: cmake/[>=3 <4] diff --git a/conanfile.py b/conanfile.py index e5a074f87a..866aba5be0 100644 --- a/conanfile.py +++ b/conanfile.py @@ -55,6 +55,15 @@ class Xrpl(ConanFile): 'date/*:header_only': True, 'grpc/*:shared': False, 'grpc/*:secure': True, + 'grpc/*:codegen': False, + 'grpc/*:csharp_ext': False, + 'grpc/*:csharp_plugin': False, + 'grpc/*:node_plugin': False, + 'grpc/*:objective_c_plugin': False, + 'grpc/*:php_plugin': False, + 'grpc/*:python_plugin': False, + 'grpc/*:ruby_plugin': False, + 'grpc/*:otel_plugin': False, 'libarchive/*:shared': False, 'libarchive/*:with_acl': False, 'libarchive/*:with_bzip2': False, @@ -143,8 +152,6 @@ class Xrpl(ConanFile): tc.variables['static'] = self.options.static tc.variables['unity'] = self.options.unity tc.variables['xrpld'] = self.options.xrpld - if self.settings.compiler == 'clang' and self.settings.compiler.version == 16: - tc.extra_cxxflags = ["-DBOOST_ASIO_DISABLE_CONCEPTS"] tc.generate() def build(self): diff --git a/include/xrpl/beast/utility/rngfill.h b/include/xrpl/beast/utility/rngfill.h index 2b5a9ba040..e1b47618ba 100644 --- a/include/xrpl/beast/utility/rngfill.h +++ b/include/xrpl/beast/utility/rngfill.h @@ -31,38 +31,28 @@ namespace beast { template void -rngfill(void* buffer, std::size_t bytes, Generator& g) +rngfill(void* const buffer, std::size_t const bytes, Generator& g) { using result_type = typename Generator::result_type; + constexpr std::size_t result_size = sizeof(result_type); - while (bytes >= sizeof(result_type)) + std::uint8_t* const buffer_start = static_cast(buffer); + std::size_t const complete_iterations = bytes / result_size; + std::size_t const bytes_remaining = bytes % result_size; + + for (std::size_t count = 0; count < complete_iterations; ++count) { - auto const v = g(); - std::memcpy(buffer, &v, sizeof(v)); - buffer = reinterpret_cast(buffer) + sizeof(v); - bytes -= sizeof(v); + result_type const v = g(); + std::size_t const offset = count * result_size; + std::memcpy(buffer_start + offset, &v, result_size); } - XRPL_ASSERT( - bytes < sizeof(result_type), "beast::rngfill(void*) : maximum bytes"); - -#ifdef __GNUC__ - // gcc 11.1 (falsely) warns about an array-bounds overflow in release mode. - // gcc 12.1 (also falsely) warns about an string overflow in release mode. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" -#pragma GCC diagnostic ignored "-Wstringop-overflow" -#endif - - if (bytes > 0) + if (bytes_remaining > 0) { - auto const v = g(); - std::memcpy(buffer, &v, bytes); + result_type const v = g(); + std::size_t const offset = complete_iterations * result_size; + std::memcpy(buffer_start + offset, &v, bytes_remaining); } - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif } template < diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index c55776a5ce..5844a70eb0 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -53,19 +53,19 @@ * then change the macro parameter in features.macro to * `VoteBehavior::DefaultYes`. The communication process is beyond * the scope of these instructions. + + * 5) If a supported feature (`Supported::yes`) was _ever_ in a released + * version, it can never be changed back to `Supported::no`, because + * it _may_ still become enabled at any time. This would cause newer + * versions of `rippled` to become amendment blocked. + * Instead, to prevent newer versions from voting on the feature, use + * `VoteBehavior::Obsolete`. Obsolete features can not be voted for + * by any versions of `rippled` built with that setting, but will still + * work correctly if they get enabled. If a feature remains obsolete + * for long enough that _all_ clients that could vote for it are + * amendment blocked, the feature can be removed from the code + * as if it was unsupported. * - * 5) A feature marked as Obsolete can mean either: - * 1) It is in the ledger (marked as Supported::yes) and it is on its way to - * become Retired - * 2) The feature is not in the ledger (has always been marked as - * Supported::no) and the code to support it has been removed - * - * If we want to discontinue a feature that we've never fully supported and - * the feature has never been enabled, we should remove all the related - * code, and mark the feature as "abandoned". To do this: - * - * 1) Open features.macro, move the feature to the abandoned section and - * change the macro to XRPL_ABANDON * * When a feature has been enabled for several years, the conditional code * may be removed, and the feature "retired". To retire a feature: @@ -99,13 +99,10 @@ namespace detail { #undef XRPL_FIX #pragma push_macro("XRPL_RETIRE") #undef XRPL_RETIRE -#pragma push_macro("XRPL_ABANDON") -#undef XRPL_ABANDON #define XRPL_FEATURE(name, supported, vote) +1 #define XRPL_FIX(name, supported, vote) +1 #define XRPL_RETIRE(name) +1 -#define XRPL_ABANDON(name) +1 // This value SHOULD be equal to the number of amendments registered in // Feature.cpp. Because it's only used to reserve storage, and determine how @@ -122,8 +119,6 @@ static constexpr std::size_t numFeatures = #pragma pop_macro("XRPL_FIX") #undef XRPL_FEATURE #pragma pop_macro("XRPL_FEATURE") -#undef XRPL_ABANDON -#pragma pop_macro("XRPL_ABANDON") /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -365,13 +360,10 @@ foreachFeature(FeatureBitset bs, F&& f) #undef XRPL_FIX #pragma push_macro("XRPL_RETIRE") #undef XRPL_RETIRE -#pragma push_macro("XRPL_ABANDON") -#undef XRPL_ABANDON #define XRPL_FEATURE(name, supported, vote) extern uint256 const feature##name; #define XRPL_FIX(name, supported, vote) extern uint256 const fix##name; #define XRPL_RETIRE(name) -#define XRPL_ABANDON(name) #include @@ -381,8 +373,6 @@ foreachFeature(FeatureBitset bs, F&& f) #pragma pop_macro("XRPL_FIX") #undef XRPL_FEATURE #pragma pop_macro("XRPL_FEATURE") -#undef XRPL_ABANDON -#pragma pop_macro("XRPL_ABANDON") } // namespace ripple diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 63c1b2258b..c83dacfa73 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -26,9 +26,6 @@ #if !defined(XRPL_RETIRE) #error "undefined macro: XRPL_RETIRE" #endif -#if !defined(XRPL_ABANDON) -#error "undefined macro: XRPL_ABANDON" -#endif // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. @@ -133,11 +130,6 @@ XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete) XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete) XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete) -// The following amendments were never supported, never enabled, and -// we've abanded them. These features should never be in the ledger, -// and we've removed all the related code. -XRPL_ABANDON(OwnerPaysFee) - // The following amendments have been active for at least two years. Their // pre-amendment code has been removed and the identifiers are deprecated. // All known amendments and amendments that may appear in a validated diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 46c6e60db3..11306ee0f5 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -482,8 +482,7 @@ LEDGER_ENTRY(ltDELEGATE, 0x0083, Delegate, delegate, ({ })) /** A ledger object representing a single asset vault. - - \sa keylet::mptoken + \sa keylet::vault */ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({ {sfPreviousTxnID, soeREQUIRED}, diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 1d59e71850..38665296cd 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -409,6 +409,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::de {sfTransferFee, soeOPTIONAL}, {sfMaximumAmount, soeOPTIONAL}, {sfMPTokenMetadata, soeOPTIONAL}, + {sfDomainID, soeOPTIONAL}, })) /** This transaction type destroys a MPTokensIssuance instance */ @@ -420,6 +421,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation:: TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, ({ {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, + {sfDomainID, soeOPTIONAL}, })) /** This transaction type authorizes a MPToken instance */ @@ -478,7 +480,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({ {sfAsset, soeREQUIRED, soeMPTSupported}, {sfAssetsMaximum, soeOPTIONAL}, {sfMPTokenMetadata, soeOPTIONAL}, - {sfDomainID, soeOPTIONAL}, // PermissionedDomainID + {sfDomainID, soeOPTIONAL}, {sfWithdrawalPolicy, soeOPTIONAL}, {sfData, soeOPTIONAL}, })) @@ -487,7 +489,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({ TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, ({ {sfVaultID, soeREQUIRED}, {sfAssetsMaximum, soeOPTIONAL}, - {sfDomainID, soeOPTIONAL}, // PermissionedDomainID + {sfDomainID, soeOPTIONAL}, {sfData, soeOPTIONAL}, })) diff --git a/src/libxrpl/protocol/Feature.cpp b/src/libxrpl/protocol/Feature.cpp index 478b155387..eeeee1c185 100644 --- a/src/libxrpl/protocol/Feature.cpp +++ b/src/libxrpl/protocol/Feature.cpp @@ -254,7 +254,7 @@ FeatureCollections::registerFeature( { check(!readOnly, "Attempting to register a feature after startup."); check( - support == Supported::yes || vote != VoteBehavior::DefaultYes, + support == Supported::yes || vote == VoteBehavior::DefaultNo, "Invalid feature parameters. Must be supported to be up-voted."); Feature const* i = getByName(name); if (!i) @@ -268,7 +268,7 @@ FeatureCollections::registerFeature( features.emplace_back(name, f); auto const getAmendmentSupport = [=]() { - if (vote == VoteBehavior::Obsolete && support == Supported::yes) + if (vote == VoteBehavior::Obsolete) return AmendmentSupport::Retired; return support == Supported::yes ? AmendmentSupport::Supported : AmendmentSupport::Unsupported; @@ -398,14 +398,6 @@ retireFeature(std::string const& name) return registerFeature(name, Supported::yes, VoteBehavior::Obsolete); } -// Abandoned features are not in the ledger and have no code controlled by the -// feature. They were never supported, and cannot be voted on. -uint256 -abandonFeature(std::string const& name) -{ - return registerFeature(name, Supported::no, VoteBehavior::Obsolete); -} - /** Tell FeatureCollections when registration is complete. */ bool registrationIsDone() @@ -440,8 +432,6 @@ featureToName(uint256 const& f) #undef XRPL_FIX #pragma push_macro("XRPL_RETIRE") #undef XRPL_RETIRE -#pragma push_macro("XRPL_ABANDON") -#undef XRPL_ABANDON #define XRPL_FEATURE(name, supported, vote) \ uint256 const feature##name = registerFeature(#name, supported, vote); @@ -453,11 +443,6 @@ featureToName(uint256 const& f) [[deprecated("The referenced amendment has been retired")]] \ [[maybe_unused]] \ uint256 const retired##name = retireFeature(#name); - -#define XRPL_ABANDON(name) \ - [[deprecated("The referenced amendment has been abandoned")]] \ - [[maybe_unused]] \ - uint256 const abandoned##name = abandonFeature(#name); // clang-format on #include @@ -468,8 +453,6 @@ featureToName(uint256 const& f) #pragma pop_macro("XRPL_FIX") #undef XRPL_FEATURE #pragma pop_macro("XRPL_FEATURE") -#undef XRPL_ABANDON -#pragma pop_macro("XRPL_ABANDON") // All of the features should now be registered, since variables in a cpp file // are initialized from top to bottom. diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index c8fcc4092b..92f286ca6a 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -3652,14 +3652,18 @@ class Batch_test : public beast::unit_test::suite { // Submit a tx with tfInnerBatchTxn uint256 const txBad = submitTx(tfInnerBatchTxn); - BEAST_EXPECT(env.app().getHashRouter().getFlags(txBad) == 0); + BEAST_EXPECT( + env.app().getHashRouter().getFlags(txBad) == + HashRouterFlags::UNDEFINED); } // Validate: NetworkOPs::processTransaction() { uint256 const txid = processTxn(tfInnerBatchTxn); - // HashRouter::getFlags() should return SF_BAD - BEAST_EXPECT(env.app().getHashRouter().getFlags(txid) == SF_BAD); + // HashRouter::getFlags() should return LedgerFlags::BAD + BEAST_EXPECT( + env.app().getHashRouter().getFlags(txid) == + HashRouterFlags::BAD); } } diff --git a/src/test/app/HashRouter_test.cpp b/src/test/app/HashRouter_test.cpp index 0737116f13..44170e152e 100644 --- a/src/test/app/HashRouter_test.cpp +++ b/src/test/app/HashRouter_test.cpp @@ -45,15 +45,19 @@ class HashRouter_test : public beast::unit_test::suite TestStopwatch stopwatch; HashRouter router(getSetup(2s, 1s), stopwatch); - uint256 const key1(1); - uint256 const key2(2); - uint256 const key3(3); + HashRouterFlags key1(HashRouterFlags::PRIVATE1); + HashRouterFlags key2(HashRouterFlags::PRIVATE2); + HashRouterFlags key3(HashRouterFlags::PRIVATE3); + + auto const ukey1 = uint256{static_cast(key1)}; + auto const ukey2 = uint256{static_cast(key2)}; + auto const ukey3 = uint256{static_cast(key3)}; // t=0 - router.setFlags(key1, 11111); - BEAST_EXPECT(router.getFlags(key1) == 11111); - router.setFlags(key2, 22222); - BEAST_EXPECT(router.getFlags(key2) == 22222); + router.setFlags(ukey1, HashRouterFlags::PRIVATE1); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1); + router.setFlags(ukey2, HashRouterFlags::PRIVATE2); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE2); // key1 : 0 // key2 : 0 // key3: null @@ -62,7 +66,7 @@ class HashRouter_test : public beast::unit_test::suite // Because we are accessing key1 here, it // will NOT be expired for another two ticks - BEAST_EXPECT(router.getFlags(key1) == 11111); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1); // key1 : 1 // key2 : 0 // key3 null @@ -70,9 +74,9 @@ class HashRouter_test : public beast::unit_test::suite ++stopwatch; // t=3 - router.setFlags(key3, 33333); // force expiration - BEAST_EXPECT(router.getFlags(key1) == 11111); - BEAST_EXPECT(router.getFlags(key2) == 0); + router.setFlags(ukey3, HashRouterFlags::PRIVATE3); // force expiration + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::UNDEFINED); } void @@ -83,15 +87,21 @@ class HashRouter_test : public beast::unit_test::suite TestStopwatch stopwatch; HashRouter router(getSetup(2s, 1s), stopwatch); - uint256 const key1(1); - uint256 const key2(2); - uint256 const key3(3); - uint256 const key4(4); + HashRouterFlags key1(HashRouterFlags::PRIVATE1); + HashRouterFlags key2(HashRouterFlags::PRIVATE2); + HashRouterFlags key3(HashRouterFlags::PRIVATE3); + HashRouterFlags key4(HashRouterFlags::PRIVATE4); + + auto const ukey1 = uint256{static_cast(key1)}; + auto const ukey2 = uint256{static_cast(key2)}; + auto const ukey3 = uint256{static_cast(key3)}; + auto const ukey4 = uint256{static_cast(key4)}; + BEAST_EXPECT(key1 != key2 && key2 != key3 && key3 != key4); // t=0 - router.setFlags(key1, 12345); - BEAST_EXPECT(router.getFlags(key1) == 12345); + router.setFlags(ukey1, HashRouterFlags::BAD); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::BAD); // key1 : 0 // key2 : null // key3 : null @@ -103,26 +113,27 @@ class HashRouter_test : public beast::unit_test::suite // so key1 will be expired after the second // call to setFlags. // t=1 - router.setFlags(key2, 9999); - BEAST_EXPECT(router.getFlags(key1) == 12345); - BEAST_EXPECT(router.getFlags(key2) == 9999); + + router.setFlags(ukey2, HashRouterFlags::PRIVATE5); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::BAD); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5); // key1 : 1 // key2 : 1 // key3 : null ++stopwatch; // t=2 - BEAST_EXPECT(router.getFlags(key2) == 9999); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5); // key1 : 1 // key2 : 2 // key3 : null ++stopwatch; // t=3 - router.setFlags(key3, 2222); - BEAST_EXPECT(router.getFlags(key1) == 0); - BEAST_EXPECT(router.getFlags(key2) == 9999); - BEAST_EXPECT(router.getFlags(key3) == 2222); + router.setFlags(ukey3, HashRouterFlags::BAD); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::UNDEFINED); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5); + BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::BAD); // key1 : 3 // key2 : 3 // key3 : 3 @@ -130,10 +141,10 @@ class HashRouter_test : public beast::unit_test::suite ++stopwatch; // t=4 // No insertion, no expiration - router.setFlags(key1, 7654); - BEAST_EXPECT(router.getFlags(key1) == 7654); - BEAST_EXPECT(router.getFlags(key2) == 9999); - BEAST_EXPECT(router.getFlags(key3) == 2222); + router.setFlags(ukey1, HashRouterFlags::SAVED); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::SAVED); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5); + BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::BAD); // key1 : 4 // key2 : 4 // key3 : 4 @@ -142,11 +153,11 @@ class HashRouter_test : public beast::unit_test::suite ++stopwatch; // t=6 - router.setFlags(key4, 7890); - BEAST_EXPECT(router.getFlags(key1) == 0); - BEAST_EXPECT(router.getFlags(key2) == 0); - BEAST_EXPECT(router.getFlags(key3) == 0); - BEAST_EXPECT(router.getFlags(key4) == 7890); + router.setFlags(ukey4, HashRouterFlags::TRUSTED); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::UNDEFINED); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::UNDEFINED); + BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::UNDEFINED); + BEAST_EXPECT(router.getFlags(ukey4) == HashRouterFlags::TRUSTED); // key1 : 6 // key2 : 6 // key3 : 6 @@ -168,18 +179,18 @@ class HashRouter_test : public beast::unit_test::suite uint256 const key4(4); BEAST_EXPECT(key1 != key2 && key2 != key3 && key3 != key4); - int flags = 12345; // This value is ignored + HashRouterFlags flags(HashRouterFlags::BAD); // This value is ignored router.addSuppression(key1); BEAST_EXPECT(router.addSuppressionPeer(key2, 15)); BEAST_EXPECT(router.addSuppressionPeer(key3, 20, flags)); - BEAST_EXPECT(flags == 0); + BEAST_EXPECT(flags == HashRouterFlags::UNDEFINED); ++stopwatch; BEAST_EXPECT(!router.addSuppressionPeer(key1, 2)); BEAST_EXPECT(!router.addSuppressionPeer(key2, 3)); BEAST_EXPECT(!router.addSuppressionPeer(key3, 4, flags)); - BEAST_EXPECT(flags == 0); + BEAST_EXPECT(flags == HashRouterFlags::UNDEFINED); BEAST_EXPECT(router.addSuppressionPeer(key4, 5)); } @@ -192,9 +203,9 @@ class HashRouter_test : public beast::unit_test::suite HashRouter router(getSetup(2s, 1s), stopwatch); uint256 const key1(1); - BEAST_EXPECT(router.setFlags(key1, 10)); - BEAST_EXPECT(!router.setFlags(key1, 10)); - BEAST_EXPECT(router.setFlags(key1, 20)); + BEAST_EXPECT(router.setFlags(key1, HashRouterFlags::PRIVATE1)); + BEAST_EXPECT(!router.setFlags(key1, HashRouterFlags::PRIVATE1)); + BEAST_EXPECT(router.setFlags(key1, HashRouterFlags::PRIVATE2)); } void @@ -250,7 +261,7 @@ class HashRouter_test : public beast::unit_test::suite HashRouter router(getSetup(5s, 1s), stopwatch); uint256 const key(1); HashRouter::PeerShortID peer = 1; - int flags; + HashRouterFlags flags; BEAST_EXPECT(router.shouldProcess(key, peer, flags, 1s)); BEAST_EXPECT(!router.shouldProcess(key, peer, flags, 1s)); @@ -364,6 +375,39 @@ class HashRouter_test : public beast::unit_test::suite } } + void + testFlagsOps() + { + testcase("Bitwise Operations"); + + using HF = HashRouterFlags; + using UHF = std::underlying_type_t; + + HF f1 = HF::BAD; + HF f2 = HF::SAVED; + HF combined = f1 | f2; + + BEAST_EXPECT( + static_cast(combined) == + (static_cast(f1) | static_cast(f2))); + + HF temp = f1; + temp |= f2; + BEAST_EXPECT(temp == combined); + + HF intersect = combined & f1; + BEAST_EXPECT(intersect == f1); + + HF temp2 = combined; + temp2 &= f1; + BEAST_EXPECT(temp2 == f1); + + BEAST_EXPECT(any(f1)); + BEAST_EXPECT(any(f2)); + BEAST_EXPECT(any(combined)); + BEAST_EXPECT(!any(HF::UNDEFINED)); + } + public: void run() override @@ -375,6 +419,7 @@ public: testRelay(); testProcess(); testSetup(); + testFlagsOps(); } }; diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 46b64e40f2..2cb47780ba 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -18,10 +18,16 @@ //============================================================================== #include +#include +#include #include #include +#include +#include #include +#include +#include #include namespace ripple { @@ -61,6 +67,48 @@ class MPToken_test : public beast::unit_test::suite .metadata = "test", .err = temMALFORMED}); + if (!features[featureSingleAssetVault]) + { + // tries to set DomainID when SAV is disabled + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .metadata = "test", + .flags = tfMPTRequireAuth, + .domainID = uint256(42), + .err = temDISABLED}); + } + else if (!features[featurePermissionedDomains]) + { + // tries to set DomainID when PD is disabled + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .metadata = "test", + .flags = tfMPTRequireAuth, + .domainID = uint256(42), + .err = temDISABLED}); + } + else + { + // tries to set DomainID when RequireAuth is not set + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .metadata = "test", + .domainID = uint256(42), + .err = temMALFORMED}); + + // tries to set zero DomainID + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .metadata = "test", + .flags = tfMPTRequireAuth, + .domainID = beast::zero, + .err = temMALFORMED}); + } + // tries to set a txfee greater than max mptAlice.create( {.maxAmt = 100, @@ -140,6 +188,48 @@ class MPToken_test : public beast::unit_test::suite BEAST_EXPECT( result[sfMaximumAmount.getJsonName()] == "9223372036854775807"); } + + if (features[featureSingleAssetVault]) + { + // Add permissioned domain + Account const credIssuer1{"credIssuer1"}; + std::string const credType = "credential"; + + pdomain::Credentials const credentials1{ + {.issuer = credIssuer1, .credType = credType}}; + + { + Env env{*this, features}; + env.fund(XRP(1000), credIssuer1); + + env(pdomain::setTx(credIssuer1, credentials1)); + auto const domainId1 = [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + + MPTTester mptAlice(env, alice); + mptAlice.create({ + .maxAmt = maxMPTokenAmount, // 9'223'372'036'854'775'807 + .assetScale = 1, + .transferFee = 10, + .metadata = "123", + .ownerCount = 1, + .flags = tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | + tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback, + .domainID = domainId1, + }); + + // Get the hash for the most recent transaction. + std::string const txHash{ + env.tx()->getJson(JsonOptions::none)[jss::hash].asString()}; + + Json::Value const result = env.rpc("tx", txHash)[jss::result]; + BEAST_EXPECT( + result[sfMaximumAmount.getJsonName()] == + "9223372036854775807"); + } + } } void @@ -499,6 +589,59 @@ class MPToken_test : public beast::unit_test::suite .flags = 0x00000008, .err = temINVALID_FLAG}); + if (!features[featureSingleAssetVault]) + { + // test invalid flags - nothing is being changed + mptAlice.set( + {.account = alice, + .flags = 0x00000000, + .err = tecNO_PERMISSION}); + + mptAlice.set( + {.account = alice, + .holder = bob, + .flags = 0x00000000, + .err = tecNO_PERMISSION}); + + // cannot set DomainID since SAV is not enabled + mptAlice.set( + {.account = alice, + .domainID = uint256(42), + .err = temDISABLED}); + } + else + { + // test invalid flags - nothing is being changed + mptAlice.set( + {.account = alice, + .flags = 0x00000000, + .err = temMALFORMED}); + + mptAlice.set( + {.account = alice, + .holder = bob, + .flags = 0x00000000, + .err = temMALFORMED}); + + if (!features[featurePermissionedDomains]) + { + // cannot set DomainID since PD is not enabled + mptAlice.set( + {.account = alice, + .domainID = uint256(42), + .err = temDISABLED}); + } + else + { + // cannot set DomainID since Holder is set + mptAlice.set( + {.account = alice, + .holder = bob, + .domainID = uint256(42), + .err = temMALFORMED}); + } + } + // set both lock and unlock flags at the same time will fail mptAlice.set( {.account = alice, @@ -582,6 +725,53 @@ class MPToken_test : public beast::unit_test::suite mptAlice.set( {.holder = cindy, .flags = tfMPTLock, .err = tecNO_DST}); } + + if (features[featureSingleAssetVault] && + features[featurePermissionedDomains]) + { + // Add permissioned domain + Account const credIssuer1{"credIssuer1"}; + std::string const credType = "credential"; + + pdomain::Credentials const credentials1{ + {.issuer = credIssuer1, .credType = credType}}; + + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice); + mptAlice.create({}); + + // Trying to set DomainID on a public MPTokenIssuance + mptAlice.set( + {.domainID = uint256(42), .err = tecNO_PERMISSION}); + + mptAlice.set( + {.domainID = beast::zero, .err = tecNO_PERMISSION}); + } + + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice); + mptAlice.create({.flags = tfMPTRequireAuth}); + + // Trying to set non-existing DomainID + mptAlice.set( + {.domainID = uint256(42), .err = tecOBJECT_NOT_FOUND}); + + // Trying to lock but locking is disabled + mptAlice.set( + {.flags = tfMPTUnlock, + .domainID = uint256(42), + .err = tecNO_PERMISSION}); + + mptAlice.set( + {.flags = tfMPTUnlock, + .domainID = beast::zero, + .err = tecNO_PERMISSION}); + } + } } void @@ -590,71 +780,136 @@ class MPToken_test : public beast::unit_test::suite testcase("Enabled set transaction"); using namespace test::jtx; - - // Test locking and unlocking - Env env{*this, features}; Account const alice("alice"); // issuer Account const bob("bob"); // holder - MPTTester mptAlice(env, alice, {.holders = {bob}}); - - // create a mptokenissuance with locking - mptAlice.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock}); - - mptAlice.authorize({.account = bob, .holderCount = 1}); - - // locks bob's mptoken - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); - - // trying to lock bob's mptoken again will still succeed - // but no changes to the objects - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); - - // alice locks the mptissuance - mptAlice.set({.account = alice, .flags = tfMPTLock}); - - // alice tries to lock up both mptissuance and mptoken again - // it will not change the flags and both will remain locked. - mptAlice.set({.account = alice, .flags = tfMPTLock}); - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); - - // alice unlocks bob's mptoken - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); - - // locks up bob's mptoken again - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); - if (!features[featureSingleAssetVault]) { - // Delete bobs' mptoken even though it is locked - mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize}); + // Test locking and unlocking + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // create a mptokenissuance with locking + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock}); + + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // locks bob's mptoken + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // trying to lock bob's mptoken again will still succeed + // but no changes to the objects + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // alice locks the mptissuance + mptAlice.set({.account = alice, .flags = tfMPTLock}); + + // alice tries to lock up both mptissuance and mptoken again + // it will not change the flags and both will remain locked. + mptAlice.set({.account = alice, .flags = tfMPTLock}); + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // alice unlocks bob's mptoken mptAlice.set( - {.account = alice, - .holder = bob, - .flags = tfMPTUnlock, - .err = tecOBJECT_NOT_FOUND}); + {.account = alice, .holder = bob, .flags = tfMPTUnlock}); - return; + // locks up bob's mptoken again + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + if (!features[featureSingleAssetVault]) + { + // Delete bobs' mptoken even though it is locked + mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize}); + + mptAlice.set( + {.account = alice, + .holder = bob, + .flags = tfMPTUnlock, + .err = tecOBJECT_NOT_FOUND}); + + return; + } + + // Cannot delete locked MPToken + mptAlice.authorize( + {.account = bob, + .flags = tfMPTUnauthorize, + .err = tecNO_PERMISSION}); + + // alice unlocks mptissuance + mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + + // alice unlocks bob's mptoken + mptAlice.set( + {.account = alice, .holder = bob, .flags = tfMPTUnlock}); + + // alice unlocks mptissuance and bob's mptoken again despite that + // they are already unlocked. Make sure this will not change the + // flags + mptAlice.set( + {.account = alice, .holder = bob, .flags = tfMPTUnlock}); + mptAlice.set({.account = alice, .flags = tfMPTUnlock}); } - // Cannot delete locked MPToken - mptAlice.authorize( - {.account = bob, - .flags = tfMPTUnauthorize, - .err = tecNO_PERMISSION}); + if (features[featureSingleAssetVault]) + { + // Add permissioned domain + std::string const credType = "credential"; - // alice unlocks mptissuance - mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + // Test setting and resetting domain ID + Env env{*this, features}; - // alice unlocks bob's mptoken - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); + auto const domainId1 = [&]() { + Account const credIssuer1{"credIssuer1"}; + env.fund(XRP(1000), credIssuer1); - // alice unlocks mptissuance and bob's mptoken again despite that - // they are already unlocked. Make sure this will not change the - // flags - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); - mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + pdomain::Credentials const credentials1{ + {.issuer = credIssuer1, .credType = credType}}; + + env(pdomain::setTx(credIssuer1, credentials1)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + + auto const domainId2 = [&]() { + Account const credIssuer2{"credIssuer2"}; + env.fund(XRP(1000), credIssuer2); + + pdomain::Credentials const credentials2{ + {.issuer = credIssuer2, .credType = credType}}; + + env(pdomain::setTx(credIssuer2, credentials2)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // create a mptokenissuance with auth. + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTRequireAuth}); + BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt)); + + // reset "domain not set" to "domain not set", i.e. no change + mptAlice.set({.domainID = beast::zero}); + BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt)); + + // reset "domain not set" to domain1 + mptAlice.set({.domainID = domainId1}); + BEAST_EXPECT(mptAlice.checkDomainID(domainId1)); + + // reset domain1 to domain2 + mptAlice.set({.domainID = domainId2}); + BEAST_EXPECT(mptAlice.checkDomainID(domainId2)); + + // reset domain to "domain not set" + mptAlice.set({.domainID = beast::zero}); + BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt)); + } } void @@ -889,6 +1144,200 @@ class MPToken_test : public beast::unit_test::suite mptAlice.pay(bob, alice, 100, tecNO_AUTH); } + if (features[featureSingleAssetVault] && + features[featurePermissionedDomains]) + { + // If RequireAuth is enabled and domain is a match, payment succeeds + { + Env env{*this, features}; + std::string const credType = "credential"; + Account const credIssuer1{"credIssuer1"}; + env.fund(XRP(1000), credIssuer1, bob); + + auto const domainId1 = [&]() { + pdomain::Credentials const credentials1{ + {.issuer = credIssuer1, .credType = credType}}; + + env(pdomain::setTx(credIssuer1, credentials1)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + // bob is authorized via domain + env(credentials::create(bob, credIssuer1, credType)); + env(credentials::accept(bob, credIssuer1, credType)); + env.close(); + + MPTTester mptAlice(env, alice, {}); + env.close(); + + mptAlice.create({ + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer, + .domainID = domainId1, + }); + + mptAlice.authorize({.account = bob}); + env.close(); + + // bob is authorized via domain + mptAlice.pay(alice, bob, 100); + mptAlice.set({.domainID = beast::zero}); + + // bob is no longer authorized + mptAlice.pay(alice, bob, 100, tecNO_AUTH); + } + + { + Env env{*this, features}; + std::string const credType = "credential"; + Account const credIssuer1{"credIssuer1"}; + env.fund(XRP(1000), credIssuer1, bob); + + auto const domainId1 = [&]() { + pdomain::Credentials const credentials1{ + {.issuer = credIssuer1, .credType = credType}}; + + env(pdomain::setTx(credIssuer1, credentials1)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + // bob is authorized via domain + env(credentials::create(bob, credIssuer1, credType)); + env(credentials::accept(bob, credIssuer1, credType)); + env.close(); + + MPTTester mptAlice(env, alice, {}); + env.close(); + + mptAlice.create({ + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer, + .domainID = domainId1, + }); + + // bob creates an empty MPToken + mptAlice.authorize({.account = bob}); + + // alice authorizes bob to hold funds + mptAlice.authorize({.account = alice, .holder = bob}); + + // alice sends 100 MPT to bob + mptAlice.pay(alice, bob, 100); + + // alice UNAUTHORIZES bob + mptAlice.authorize( + {.account = alice, + .holder = bob, + .flags = tfMPTUnauthorize}); + + // bob is still authorized, via domain + mptAlice.pay(bob, alice, 10); + + mptAlice.set({.domainID = beast::zero}); + + // bob fails to send back to alice because he is no longer + // authorize to move his funds! + mptAlice.pay(bob, alice, 10, tecNO_AUTH); + } + + { + Env env{*this, features}; + std::string const credType = "credential"; + // credIssuer1 is the owner of domainId1 and a credential issuer + Account const credIssuer1{"credIssuer1"}; + // credIssuer2 is the owner of domainId2 and a credential issuer + // Note, domainId2 also lists credentials issued by credIssuer1 + Account const credIssuer2{"credIssuer2"}; + env.fund(XRP(1000), credIssuer1, credIssuer2, bob, carol); + + auto const domainId1 = [&]() { + pdomain::Credentials const credentials{ + {.issuer = credIssuer1, .credType = credType}}; + + env(pdomain::setTx(credIssuer1, credentials)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + + auto const domainId2 = [&]() { + pdomain::Credentials const credentials{ + {.issuer = credIssuer1, .credType = credType}, + {.issuer = credIssuer2, .credType = credType}}; + + env(pdomain::setTx(credIssuer2, credentials)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + + // bob is authorized via credIssuer1 which is recognized by both + // domainId1 and domainId2 + env(credentials::create(bob, credIssuer1, credType)); + env(credentials::accept(bob, credIssuer1, credType)); + env.close(); + + // carol is authorized via credIssuer2, only recognized by + // domainId2 + env(credentials::create(carol, credIssuer2, credType)); + env(credentials::accept(carol, credIssuer2, credType)); + env.close(); + + MPTTester mptAlice(env, alice, {}); + env.close(); + + mptAlice.create({ + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer, + .domainID = domainId1, + }); + + // bob and carol create an empty MPToken + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + env.close(); + + // alice sends 50 MPT to bob but cannot send to carol + mptAlice.pay(alice, bob, 50); + mptAlice.pay(alice, carol, 50, tecNO_AUTH); + env.close(); + + // bob cannot send to carol because they are not on the same + // domain (since credIssuer2 is not recognized by domainId1) + mptAlice.pay(bob, carol, 10, tecNO_AUTH); + env.close(); + + // alice updates domainID to domainId2 which recognizes both + // credIssuer1 and credIssuer2 + mptAlice.set({.domainID = domainId2}); + // alice can now send to carol + mptAlice.pay(alice, carol, 10); + env.close(); + + // bob can now send to carol because both are in the same + // domain + mptAlice.pay(bob, carol, 10); + env.close(); + + // bob loses his authorization and can no longer send MPT + env(credentials::deleteCred( + credIssuer1, bob, credIssuer1, credType)); + env.close(); + + mptAlice.pay(bob, carol, 10, tecNO_AUTH); + mptAlice.pay(bob, alice, 10, tecNO_AUTH); + } + } + // Non-issuer cannot send to each other if MPTCanTransfer isn't set { Env env(*this, features); @@ -1340,10 +1789,8 @@ class MPToken_test : public beast::unit_test::suite } void - testDepositPreauth() + testDepositPreauth(FeatureBitset features) { - testcase("DepositPreauth"); - using namespace test::jtx; Account const alice("alice"); // issuer Account const bob("bob"); // holder @@ -1352,8 +1799,11 @@ class MPToken_test : public beast::unit_test::suite char const credType[] = "abcde"; + if (features[featureCredentials]) { - Env env(*this); + testcase("DepositPreauth"); + + Env env(*this, features); env.fund(XRP(50000), diana, dpIssuer); env.close(); @@ -2297,6 +2747,8 @@ public: // MPTokenIssuanceCreate testCreateValidation(all - featureSingleAssetVault); + testCreateValidation( + (all | featureSingleAssetVault) - featurePermissionedDomains); testCreateValidation(all | featureSingleAssetVault); testCreateEnabled(all - featureSingleAssetVault); testCreateEnabled(all | featureSingleAssetVault); @@ -2314,7 +2766,11 @@ public: testAuthorizeEnabled(all | featureSingleAssetVault); // MPTokenIssuanceSet - testSetValidation(all); + testSetValidation(all - featureSingleAssetVault); + testSetValidation( + (all | featureSingleAssetVault) - featurePermissionedDomains); + testSetValidation(all | featureSingleAssetVault); + testSetEnabled(all - featureSingleAssetVault); testSetEnabled(all | featureSingleAssetVault); @@ -2323,8 +2779,9 @@ public: testClawback(all); // Test Direct Payment - testPayment(all); - testDepositPreauth(); + testPayment(all | featureSingleAssetVault); + testDepositPreauth(all); + testDepositPreauth(all - featureCredentials); // Test MPT Amount is invalid in Tx, which don't support MPT testMPTInvalidInTx(all); diff --git a/src/test/app/Taker_test.cpp b/src/test/app/Taker_test.cpp deleted file mode 100644 index 3b3f338625..0000000000 --- a/src/test/app/Taker_test.cpp +++ /dev/null @@ -1,1394 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 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 - -#include - -namespace ripple { - -class Taker_test : public beast::unit_test::suite -{ - static bool const Buy = false; - static bool const Sell = true; - - class TestTaker : public BasicTaker - { - STAmount funds_; - STAmount cross_funds; - - public: - TestTaker( - CrossType cross_type, - Amounts const& amount, - Quality const& quality, - STAmount const& funds, - std::uint32_t flags, - Rate const& rate_in, - Rate const& rate_out) - : BasicTaker( - cross_type, - AccountID(0x4701), - amount, - quality, - flags, - rate_in, - rate_out) - , funds_(funds) - { - } - - void - set_funds(STAmount const& funds) - { - cross_funds = funds; - } - - STAmount - get_funds(AccountID const& owner, STAmount const& funds) const override - { - if (owner == account()) - return funds_; - - return cross_funds; - } - - Amounts - cross(Amounts offer, Quality quality) - { - if (reject(quality)) - return Amounts(offer.in.zeroed(), offer.out.zeroed()); - - // we need to emulate "unfunded offers" behavior - if (get_funds(AccountID(0x4702), offer.out) == beast::zero) - return Amounts(offer.in.zeroed(), offer.out.zeroed()); - - if (done()) - return Amounts(offer.in.zeroed(), offer.out.zeroed()); - - auto result = do_cross(offer, quality, AccountID(0x4702)); - - funds_ -= result.order.in; - - return result.order; - } - - std::pair - cross( - Amounts offer1, - Quality quality1, - Amounts offer2, - Quality quality2) - { - /* check if composed quality should be rejected */ - Quality const quality(composed_quality(quality1, quality2)); - - if (reject(quality)) - return std::make_pair( - Amounts{offer1.in.zeroed(), offer1.out.zeroed()}, - Amounts{offer2.in.zeroed(), offer2.out.zeroed()}); - - if (done()) - return std::make_pair( - Amounts{offer1.in.zeroed(), offer1.out.zeroed()}, - Amounts{offer2.in.zeroed(), offer2.out.zeroed()}); - - auto result = do_cross( - offer1, - quality1, - AccountID(0x4703), - offer2, - quality2, - AccountID(0x4704)); - - return std::make_pair(result.first.order, result.second.order); - } - }; - -private: - Issue const& - usd() const - { - static Issue const issue( - Currency(0x5553440000000000), AccountID(0x4985601)); - return issue; - } - - Issue const& - eur() const - { - static Issue const issue( - Currency(0x4555520000000000), AccountID(0x4985602)); - return issue; - } - - Issue const& - xrp() const - { - static Issue const issue(xrpCurrency(), xrpAccount()); - return issue; - } - - STAmount - parse_amount(std::string const& amount, Issue const& issue) - { - return amountFromString(issue, amount); - } - - Amounts - parse_amounts( - std::string const& amount_in, - Issue const& issue_in, - std::string const& amount_out, - Issue const& issue_out) - { - STAmount const in(parse_amount(amount_in, issue_in)); - STAmount const out(parse_amount(amount_out, issue_out)); - - return {in, out}; - } - - struct cross_attempt_offer - { - cross_attempt_offer(std::string const& in_, std::string const& out_) - : in(in_), out(out_) - { - } - - std::string in; - std::string out; - }; - -private: - std::string - format_amount(STAmount const& amount) - { - std::string txt = amount.getText(); - txt += "/"; - txt += to_string(amount.issue().currency); - return txt; - } - - void - attempt( - bool sell, - std::string name, - Quality taker_quality, - cross_attempt_offer const offer, - std::string const funds, - Quality cross_quality, - cross_attempt_offer const cross, - std::string const cross_funds, - cross_attempt_offer const flow, - Issue const& issue_in, - Issue const& issue_out, - Rate rate_in = parityRate, - Rate rate_out = parityRate) - { - Amounts taker_offer( - parse_amounts(offer.in, issue_in, offer.out, issue_out)); - - Amounts cross_offer( - parse_amounts(cross.in, issue_in, cross.out, issue_out)); - - CrossType cross_type; - - if (isXRP(issue_out)) - cross_type = CrossType::IouToXrp; - else if (isXRP(issue_in)) - cross_type = CrossType::XrpToIou; - else - cross_type = CrossType::IouToIou; - - // FIXME: We are always invoking the IOU-to-IOU taker. We should select - // the correct type dynamically. - TestTaker taker( - cross_type, - taker_offer, - taker_quality, - parse_amount(funds, issue_in), - sell ? tfSell : 0, - rate_in, - rate_out); - - taker.set_funds(parse_amount(cross_funds, issue_out)); - - auto result = taker.cross(cross_offer, cross_quality); - - Amounts const expected( - parse_amounts(flow.in, issue_in, flow.out, issue_out)); - - BEAST_EXPECT(expected == result); - - if (expected != result) - { - log << "Expected: " << format_amount(expected.in) << " : " - << format_amount(expected.out) << '\n' - << " Actual: " << format_amount(result.in) << " : " - << format_amount(result.out) << std::endl; - } - } - - Quality - get_quality(std::string in, std::string out) - { - return Quality(parse_amounts(in, xrp(), out, xrp())); - } - -public: - // Notation for clamp scenario descriptions: - // - // IN:OUT (with the last in the list being limiting factor) - // N = Nothing - // T = Taker Offer Balance - // A = Taker Account Balance - // B = Owner Account Balance - // - // (s) = sell semantics: taker wants unlimited output - // (b) = buy semantics: taker wants a limited amount out - - // NIKB TODO: Augment TestTaker so currencies and rates can be specified - // once without need for repetition. - void - test_xrp_to_iou() - { - testcase("XRP Quantization: input"); - - Quality q1 = get_quality("1", "1"); - - for (auto NumberSwitchOver : {false, true}) - { - NumberSO stNumberSO{NumberSwitchOver}; - // TAKER OWNER - // QUAL OFFER FUNDS QUAL OFFER FUNDS - // EXPECTED - // XRP USD - attempt( - Sell, - "N:N", - q1, - {"2", "2"}, - "2", - q1, - {"2", "2"}, - "2", - {"2", "2"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Sell, - "N:B", - q1, - {"2", "2"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"2", "1.8"}, - xrp(), - usd()); - } - else - { - attempt( - Sell, - "N:B", - q1, - {"2", "2"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - attempt( - Buy, - "N:T", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "N:BT", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Buy, - "N:TB", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - attempt( - Buy, - "N:TB", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "0.8", - {"0", "0.8"}, - xrp(), - usd()); - } - attempt( - Sell, - "T:N", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Sell, - "T:B", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - attempt( - Sell, - "T:B", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - attempt( - Buy, - "T:T", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "T:BT", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Buy, - "T:TB", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - attempt( - Buy, - "T:TB", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "0.8", - {"0", "0.8"}, - xrp(), - usd()); - } - - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "1", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Sell, - "A:B", - q1, - {"2", "2"}, - "1", - q1, - {"2", "2"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - attempt( - Sell, - "A:B", - q1, - {"2", "2"}, - "1", - q1, - {"2", "2"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - attempt( - Buy, - "A:T", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "3", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "A:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "2.4", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Buy, - "A:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - attempt( - Buy, - "A:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "0.8", - {"0", "0.8"}, - xrp(), - usd()); - } - - attempt( - Sell, - "TA:N", - q1, - {"2", "2"}, - "1", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Sell, - "TA:B", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - attempt( - Sell, - "TA:B", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - attempt( - Buy, - "TA:T", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "3", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Buy, - "TA:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "TA:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - attempt( - Buy, - "TA:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - attempt( - Buy, - "TA:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - - attempt( - Sell, - "AT:N", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "3", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Sell, - "AT:B", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - attempt( - Sell, - "AT:B", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - attempt( - Buy, - "AT:T", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "3", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Buy, - "AT:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "AT:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - attempt( - Buy, - "AT:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - attempt( - Buy, - "AT:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "0.8", - {"0", "0.8"}, - xrp(), - usd()); - } - } - } - - void - test_iou_to_xrp() - { - testcase("XRP Quantization: output"); - - for (auto NumberSwitchOver : {false, true}) - { - NumberSO stNumberSO{NumberSwitchOver}; - Quality q1 = get_quality("1", "1"); - - // TAKER OWNER - // QUAL OFFER FUNDS QUAL OFFER FUNDS - // EXPECTED - // USD XRP - attempt( - Sell, - "N:N", - q1, - {"3", "3"}, - "3", - q1, - {"3", "3"}, - "3", - {"3", "3"}, - usd(), - xrp()); - attempt( - Sell, - "N:B", - q1, - {"3", "3"}, - "3", - q1, - {"3", "3"}, - "2", - {"2", "2"}, - usd(), - xrp()); - if (NumberSwitchOver) - { - attempt( - Buy, - "N:T", - q1, - {"3", "3"}, - "2.5", - q1, - {"5", "5"}, - "5", - {"2.5", "3"}, - usd(), - xrp()); - attempt( - Buy, - "N:BT", - q1, - {"3", "3"}, - "1.5", - q1, - {"5", "5"}, - "4", - {"1.5", "2"}, - usd(), - xrp()); - } - else - { - attempt( - Buy, - "N:T", - q1, - {"3", "3"}, - "2.5", - q1, - {"5", "5"}, - "5", - {"2.5", "2"}, - usd(), - xrp()); - attempt( - Buy, - "N:BT", - q1, - {"3", "3"}, - "1.5", - q1, - {"5", "5"}, - "4", - {"1.5", "1"}, - usd(), - xrp()); - } - attempt( - Buy, - "N:TB", - q1, - {"3", "3"}, - "2.2", - q1, - {"5", "5"}, - "1", - {"1", "1"}, - usd(), - xrp()); - - attempt( - Sell, - "T:N", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - usd(), - xrp()); - attempt( - Sell, - "T:B", - q1, - {"2", "2"}, - "2", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - attempt( - Buy, - "T:T", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - usd(), - xrp()); - attempt( - Buy, - "T:BT", - q1, - {"1", "1"}, - "2", - q1, - {"3", "3"}, - "2", - {"1", "1"}, - usd(), - xrp()); - attempt( - Buy, - "T:TB", - q1, - {"2", "2"}, - "2", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - - if (NumberSwitchOver) - { - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "1.5", - q1, - {"2", "2"}, - "2", - {"1.5", "2"}, - usd(), - xrp()); - attempt( - Sell, - "A:B", - q1, - {"2", "2"}, - "1.8", - q1, - {"3", "3"}, - "2", - {"1.8", "2"}, - usd(), - xrp()); - } - else - { - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "1.5", - q1, - {"2", "2"}, - "2", - {"1.5", "1"}, - usd(), - xrp()); - attempt( - Sell, - "A:B", - q1, - {"2", "2"}, - "1.8", - q1, - {"3", "3"}, - "2", - {"1.8", "1"}, - usd(), - xrp()); - } - attempt( - Buy, - "A:T", - q1, - {"2", "2"}, - "1.2", - q1, - {"3", "3"}, - "3", - {"1.2", "1"}, - usd(), - xrp()); - if (NumberSwitchOver) - { - attempt( - Buy, - "A:BT", - q1, - {"2", "2"}, - "1.5", - q1, - {"4", "4"}, - "3", - {"1.5", "2"}, - usd(), - xrp()); - } - else - { - attempt( - Buy, - "A:BT", - q1, - {"2", "2"}, - "1.5", - q1, - {"4", "4"}, - "3", - {"1.5", "1"}, - usd(), - xrp()); - } - attempt( - Buy, - "A:TB", - q1, - {"2", "2"}, - "1.5", - q1, - {"4", "4"}, - "1", - {"1", "1"}, - usd(), - xrp()); - - if (NumberSwitchOver) - { - attempt( - Sell, - "TA:N", - q1, - {"2", "2"}, - "1.5", - q1, - {"2", "2"}, - "2", - {"1.5", "2"}, - usd(), - xrp()); - } - else - { - attempt( - Sell, - "TA:N", - q1, - {"2", "2"}, - "1.5", - q1, - {"2", "2"}, - "2", - {"1.5", "1"}, - usd(), - xrp()); - } - attempt( - Sell, - "TA:B", - q1, - {"2", "2"}, - "1.5", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - if (NumberSwitchOver) - { - attempt( - Buy, - "TA:T", - q1, - {"2", "2"}, - "1.5", - q1, - {"3", "3"}, - "3", - {"1.5", "2"}, - usd(), - xrp()); - attempt( - Buy, - "TA:BT", - q1, - {"2", "2"}, - "1.8", - q1, - {"4", "4"}, - "3", - {"1.8", "2"}, - usd(), - xrp()); - } - else - { - attempt( - Buy, - "TA:T", - q1, - {"2", "2"}, - "1.5", - q1, - {"3", "3"}, - "3", - {"1.5", "1"}, - usd(), - xrp()); - attempt( - Buy, - "TA:BT", - q1, - {"2", "2"}, - "1.8", - q1, - {"4", "4"}, - "3", - {"1.8", "1"}, - usd(), - xrp()); - } - attempt( - Buy, - "TA:TB", - q1, - {"2", "2"}, - "1.2", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - - attempt( - Sell, - "AT:N", - q1, - {"2", "2"}, - "2.5", - q1, - {"4", "4"}, - "4", - {"2", "2"}, - usd(), - xrp()); - attempt( - Sell, - "AT:B", - q1, - {"2", "2"}, - "2.5", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - attempt( - Buy, - "AT:T", - q1, - {"2", "2"}, - "2.5", - q1, - {"3", "3"}, - "3", - {"2", "2"}, - usd(), - xrp()); - attempt( - Buy, - "AT:BT", - q1, - {"2", "2"}, - "2.5", - q1, - {"4", "4"}, - "3", - {"2", "2"}, - usd(), - xrp()); - attempt( - Buy, - "AT:TB", - q1, - {"2", "2"}, - "2.5", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - } - } - - void - test_iou_to_iou() - { - testcase("IOU to IOU"); - - for (auto NumberSwitchOver : {false, true}) - { - NumberSO stNumberSO{NumberSwitchOver}; - Quality q1 = get_quality("1", "1"); - - // Highly exaggerated 50% transfer rate for the input and output: - Rate const rate{parityRate.value + (parityRate.value / 2)}; - - // TAKER OWNER - // QUAL OFFER FUNDS QUAL OFFER FUNDS - // EXPECTED - // EUR USD - attempt( - Sell, - "N:N", - q1, - {"2", "2"}, - "10", - q1, - {"2", "2"}, - "10", - {"2", "2"}, - eur(), - usd(), - rate, - rate); - if (NumberSwitchOver) - { - attempt( - Sell, - "N:B", - q1, - {"4", "4"}, - "10", - q1, - {"4", "4"}, - "4", - {"2.666666666666667", "2.666666666666667"}, - eur(), - usd(), - rate, - rate); - } - else - { - attempt( - Sell, - "N:B", - q1, - {"4", "4"}, - "10", - q1, - {"4", "4"}, - "4", - {"2.666666666666666", "2.666666666666666"}, - eur(), - usd(), - rate, - rate); - } - attempt( - Buy, - "N:T", - q1, - {"1", "1"}, - "10", - q1, - {"2", "2"}, - "10", - {"1", "1"}, - eur(), - usd(), - rate, - rate); - attempt( - Buy, - "N:BT", - q1, - {"2", "2"}, - "10", - q1, - {"6", "6"}, - "5", - {"2", "2"}, - eur(), - usd(), - rate, - rate); - attempt( - Buy, - "N:TB", - q1, - {"2", "2"}, - "2", - q1, - {"6", "6"}, - "1", - {"0.6666666666666667", "0.6666666666666667"}, - eur(), - usd(), - rate, - rate); - if (NumberSwitchOver) - { - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "2.5", - q1, - {"2", "2"}, - "10", - {"1.666666666666667", "1.666666666666667"}, - eur(), - usd(), - rate, - rate); - } - else - { - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "2.5", - q1, - {"2", "2"}, - "10", - {"1.666666666666666", "1.666666666666666"}, - eur(), - usd(), - rate, - rate); - } - } - } - - void - run() override - { - test_xrp_to_iou(); - test_iou_to_xrp(); - test_iou_to_iou(); - } -}; - -BEAST_DEFINE_TESTSUITE(Taker, tx, ripple); - -} // namespace ripple diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index d33432d316..9f7a611feb 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -19,6 +19,7 @@ #include +#include #include namespace ripple { @@ -99,6 +100,8 @@ MPTTester::create(MPTCreate const& arg) jv[sfMPTokenMetadata] = strHex(*arg.metadata); if (arg.maxAmt) jv[sfMaximumAmount] = std::to_string(*arg.maxAmt); + if (arg.domainID) + jv[sfDomainID] = to_string(*arg.domainID); if (submit(arg, jv) != tesSUCCESS) { // Verify issuance doesn't exist @@ -235,6 +238,8 @@ MPTTester::set(MPTSet const& arg) jv[sfHolder] = arg.holder->human(); if (arg.delegate) jv[sfDelegate] = arg.delegate->human(); + if (arg.domainID) + jv[sfDomainID] = to_string(*arg.domainID); if (submit(arg, jv) == tesSUCCESS && arg.flags.value_or(0)) { auto require = [&](std::optional const& holder, @@ -272,6 +277,16 @@ MPTTester::forObject( return false; } +[[nodiscard]] bool +MPTTester::checkDomainID(std::optional expected) const +{ + return forObject([&](SLEP const& sle) -> bool { + if (sle->isFieldPresent(sfDomainID)) + return expected == sle->getFieldH256(sfDomainID); + return (!expected.has_value()); + }); +} + [[nodiscard]] bool MPTTester::checkMPTokenAmount( Account const& holder_, diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 64eaa452f5..4756ca723d 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -106,6 +106,7 @@ struct MPTCreate std::optional holderCount = std::nullopt; bool fund = true; std::optional flags = {0}; + std::optional domainID = std::nullopt; std::optional err = std::nullopt; }; @@ -139,6 +140,7 @@ struct MPTSet std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; std::optional delegate = std::nullopt; + std::optional domainID = std::nullopt; std::optional err = std::nullopt; }; @@ -165,6 +167,9 @@ public: void set(MPTSet const& set = {}); + [[nodiscard]] bool + checkDomainID(std::optional expected) const; + [[nodiscard]] bool checkMPTokenAmount(Account const& holder, std::int64_t expectedAmount) const; diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 989afc0acc..e0db79bf53 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -131,6 +131,9 @@ public: BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); } BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 2); + BEAST_EXPECT( + jv[jss::result][jss::network_id] == + env.app().config().NETWORK_ID); } { @@ -139,7 +142,8 @@ public: // Check stream update BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { - return jv[jss::ledger_index] == 3; + return jv[jss::ledger_index] == 3 && + jv[jss::network_id] == env.app().config().NETWORK_ID; })); } @@ -149,7 +153,8 @@ public: // Check stream update BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { - return jv[jss::ledger_index] == 4; + return jv[jss::ledger_index] == 4 && + jv[jss::network_id] == env.app().config().NETWORK_ID; })); } @@ -509,6 +514,11 @@ public: if (!jv.isMember(jss::validated_hash)) return false; + uint32_t netID = env.app().config().NETWORK_ID; + if (!jv.isMember(jss::network_id) || + jv[jss::network_id] != netID) + return false; + // Certain fields are only added on a flag ledger. bool const isFlagLedger = (env.closed()->info().seq + 1) % 256 == 0; @@ -567,6 +577,7 @@ public: jv[jss::streams][0u] = "ledger"; jr = env.rpc("json", "subscribe", to_string(jv))[jss::result]; BEAST_EXPECT(jr[jss::status] == "success"); + BEAST_EXPECT(jr[jss::network_id] == env.app().config().NETWORK_ID); jr = env.rpc("json", "unsubscribe", to_string(jv))[jss::result]; BEAST_EXPECT(jr[jss::status] == "success"); diff --git a/src/xrpld/app/ledger/Ledger.cpp b/src/xrpld/app/ledger/Ledger.cpp index 3cdf0ab1a7..6de4f2cbde 100644 --- a/src/xrpld/app/ledger/Ledger.cpp +++ b/src/xrpld/app/ledger/Ledger.cpp @@ -996,7 +996,8 @@ pendSaveValidated( bool isSynchronous, bool isCurrent) { - if (!app.getHashRouter().setFlags(ledger->info().hash, SF_SAVED)) + if (!app.getHashRouter().setFlags( + ledger->info().hash, HashRouterFlags::SAVED)) { // We have tried to save this ledger recently auto stream = app.journal("Ledger").debug(); diff --git a/src/xrpld/app/misc/HashRouter.cpp b/src/xrpld/app/misc/HashRouter.cpp index dc87b2bce1..b241d6a98a 100644 --- a/src/xrpld/app/misc/HashRouter.cpp +++ b/src/xrpld/app/misc/HashRouter.cpp @@ -65,7 +65,10 @@ HashRouter::addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer) } bool -HashRouter::addSuppressionPeer(uint256 const& key, PeerShortID peer, int& flags) +HashRouter::addSuppressionPeer( + uint256 const& key, + PeerShortID peer, + HashRouterFlags& flags) { std::lock_guard lock(mutex_); @@ -79,7 +82,7 @@ bool HashRouter::shouldProcess( uint256 const& key, PeerShortID peer, - int& flags, + HashRouterFlags& flags, std::chrono::seconds tx_interval) { std::lock_guard lock(mutex_); @@ -91,7 +94,7 @@ HashRouter::shouldProcess( return s.shouldProcess(suppressionMap_.clock().now(), tx_interval); } -int +HashRouterFlags HashRouter::getFlags(uint256 const& key) { std::lock_guard lock(mutex_); @@ -100,9 +103,10 @@ HashRouter::getFlags(uint256 const& key) } bool -HashRouter::setFlags(uint256 const& key, int flags) +HashRouter::setFlags(uint256 const& key, HashRouterFlags flags) { - XRPL_ASSERT(flags, "ripple::HashRouter::setFlags : valid input"); + XRPL_ASSERT( + static_cast(flags), "ripple::HashRouter::setFlags : valid input"); std::lock_guard lock(mutex_); diff --git a/src/xrpld/app/misc/HashRouter.h b/src/xrpld/app/misc/HashRouter.h index d1d69623c1..60a0b01155 100644 --- a/src/xrpld/app/misc/HashRouter.h +++ b/src/xrpld/app/misc/HashRouter.h @@ -31,20 +31,59 @@ namespace ripple { -// TODO convert these macros to int constants or an enum -#define SF_BAD 0x02 // Temporarily bad -#define SF_SAVED 0x04 -#define SF_HELD 0x08 // Held by LedgerMaster after potential processing failure -#define SF_TRUSTED 0x10 // comes from trusted source +enum class HashRouterFlags : std::uint16_t { + // Public flags + UNDEFINED = 0x00, + BAD = 0x02, // Temporarily bad + SAVED = 0x04, + HELD = 0x08, // Held by LedgerMaster after potential processing failure + TRUSTED = 0x10, // Comes from a trusted source -// Private flags, used internally in apply.cpp. -// Do not attempt to read, set, or reuse. -#define SF_PRIVATE1 0x0100 -#define SF_PRIVATE2 0x0200 -#define SF_PRIVATE3 0x0400 -#define SF_PRIVATE4 0x0800 -#define SF_PRIVATE5 0x1000 -#define SF_PRIVATE6 0x2000 + // Private flags (used internally in apply.cpp) + // Do not attempt to read, set, or reuse. + PRIVATE1 = 0x0100, + PRIVATE2 = 0x0200, + PRIVATE3 = 0x0400, + PRIVATE4 = 0x0800, + PRIVATE5 = 0x1000, + PRIVATE6 = 0x2000 +}; + +constexpr HashRouterFlags +operator|(HashRouterFlags lhs, HashRouterFlags rhs) +{ + return static_cast( + static_cast>(lhs) | + static_cast>(rhs)); +} + +constexpr HashRouterFlags& +operator|=(HashRouterFlags& lhs, HashRouterFlags rhs) +{ + lhs = lhs | rhs; + return lhs; +} + +constexpr HashRouterFlags +operator&(HashRouterFlags lhs, HashRouterFlags rhs) +{ + return static_cast( + static_cast>(lhs) & + static_cast>(rhs)); +} + +constexpr HashRouterFlags& +operator&=(HashRouterFlags& lhs, HashRouterFlags rhs) +{ + lhs = lhs & rhs; + return lhs; +} + +constexpr bool +any(HashRouterFlags flags) +{ + return static_cast>(flags) != 0; +} class Config; @@ -101,14 +140,14 @@ private: peers_.insert(peer); } - int + HashRouterFlags getFlags(void) const { return flags_; } void - setFlags(int flagsToSet) + setFlags(HashRouterFlags flagsToSet) { flags_ |= flagsToSet; } @@ -154,7 +193,7 @@ private: } private: - int flags_ = 0; + HashRouterFlags flags_ = HashRouterFlags::UNDEFINED; std::set peers_; // This could be generalized to a map, if more // than one flag needs to expire independently. @@ -190,14 +229,17 @@ public: addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer); bool - addSuppressionPeer(uint256 const& key, PeerShortID peer, int& flags); + addSuppressionPeer( + uint256 const& key, + PeerShortID peer, + HashRouterFlags& flags); // Add a peer suppression and return whether the entry should be processed bool shouldProcess( uint256 const& key, PeerShortID peer, - int& flags, + HashRouterFlags& flags, std::chrono::seconds tx_interval); /** Set the flags on a hash. @@ -205,9 +247,9 @@ public: @return `true` if the flags were changed. `false` if unchanged. */ bool - setFlags(uint256 const& key, int flags); + setFlags(uint256 const& key, HashRouterFlags flags); - int + HashRouterFlags getFlags(uint256 const& key); /** Determines whether the hashed item should be relayed. diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index a7ddbe912c..3220ce99fc 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1207,7 +1207,7 @@ NetworkOPsImp::submitTransaction(std::shared_ptr const& iTrans) auto const txid = trans->getTransactionID(); auto const flags = app_.getHashRouter().getFlags(txid); - if ((flags & SF_BAD) != 0) + if ((flags & HashRouterFlags::BAD) != HashRouterFlags::UNDEFINED) { JLOG(m_journal.warn()) << "Submitted transaction cached bad"; return; @@ -1251,7 +1251,7 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr& transaction) { auto const newFlags = app_.getHashRouter().getFlags(transaction->getID()); - if ((newFlags & SF_BAD) != 0) + if ((newFlags & HashRouterFlags::BAD) != HashRouterFlags::UNDEFINED) { // cached bad JLOG(m_journal.warn()) << transaction->getID() << ": cached bad!\n"; @@ -1270,7 +1270,8 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr& transaction) { transaction->setStatus(INVALID); transaction->setResult(temINVALID_FLAG); - app_.getHashRouter().setFlags(transaction->getID(), SF_BAD); + app_.getHashRouter().setFlags( + transaction->getID(), HashRouterFlags::BAD); return false; } @@ -1289,7 +1290,8 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr& transaction) JLOG(m_journal.info()) << "Transaction has bad signature: " << reason; transaction->setStatus(INVALID); transaction->setResult(temBAD_SIGNATURE); - app_.getHashRouter().setFlags(transaction->getID(), SF_BAD); + app_.getHashRouter().setFlags( + transaction->getID(), HashRouterFlags::BAD); return false; } @@ -1412,7 +1414,8 @@ NetworkOPsImp::processTransactionSet(CanonicalTXSet const& set) JLOG(m_journal.trace()) << "Exception checking transaction: " << reason; } - app_.getHashRouter().setFlags(tx->getTransactionID(), SF_BAD); + app_.getHashRouter().setFlags( + tx->getTransactionID(), HashRouterFlags::BAD); continue; } @@ -1538,7 +1541,8 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) e.transaction->setResult(e.result); if (isTemMalformed(e.result)) - app_.getHashRouter().setFlags(e.transaction->getID(), SF_BAD); + app_.getHashRouter().setFlags( + e.transaction->getID(), HashRouterFlags::BAD); #ifdef DEBUG if (e.result != tesSUCCESS) @@ -1626,7 +1630,8 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) // (5) ledgers into the future. (Remember that an // unseated optional compares as less than all seated // values, so it has to be checked explicitly first.) - // 3. The SF_HELD flag is not set on the txID. (setFlags + // 3. The HashRouterFlags::BAD flag is not set on the txID. + // (setFlags // checks before setting. If the flag is set, it returns // false, which means it's been held once without one of // the other conditions, so don't hold it again. Time's @@ -1635,7 +1640,7 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) if (e.local || (ledgersLeft && ledgersLeft <= LocalTxs::holdLedgers) || app_.getHashRouter().setFlags( - e.transaction->getID(), SF_HELD)) + e.transaction->getID(), HashRouterFlags::HELD)) { // transaction should be held JLOG(m_journal.debug()) @@ -2410,6 +2415,7 @@ NetworkOPsImp::pubValidation(std::shared_ptr const& val) jvObj[jss::flags] = val->getFlags(); jvObj[jss::signing_time] = *(*val)[~sfSigningTime]; jvObj[jss::data] = strHex(val->getSerializer().slice()); + jvObj[jss::network_id] = app_.config().NETWORK_ID; if (auto version = (*val)[~sfServerVersion]) jvObj[jss::server_version] = std::to_string(*version); @@ -3114,6 +3120,8 @@ NetworkOPsImp::pubLedger(std::shared_ptr const& lpAccepted) jvObj[jss::ledger_time] = Json::Value::UInt( lpAccepted->info().closeTime.time_since_epoch().count()); + jvObj[jss::network_id] = app_.config().NETWORK_ID; + if (!lpAccepted->rules().enabled(featureXRPFees)) jvObj[jss::fee_ref] = Config::FEE_UNITS_DEPRECATED; jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped(); @@ -4172,6 +4180,7 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult) jvResult[jss::reserve_base] = lpClosed->fees().accountReserve(0).jsonClipped(); jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); + jvResult[jss::network_id] = app_.config().NETWORK_ID; } if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger()) diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index 9543a4fcd9..3cfae92cbd 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -26,9 +26,9 @@ #include #include #include -#include #include #include +#include #include namespace ripple { @@ -311,374 +311,6 @@ CreateOffer::checkAcceptAsset( return tesSUCCESS; } -bool -CreateOffer::dry_offer(ApplyView& view, Offer const& offer) -{ - if (offer.fully_consumed()) - return true; - auto const amount = accountFunds( - view, - offer.owner(), - offer.amount().out, - fhZERO_IF_FROZEN, - ctx_.app.journal("View")); - return (amount <= beast::zero); -} - -std::pair -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 -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("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( - "bridged crossing: nothing was fully consumed."); - } - - return std::make_pair(cross_result, taker.remaining_offer()); -} - -std::pair -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( - "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 CreateOffer::flowCross( PaymentSandbox& psb, @@ -883,21 +515,6 @@ CreateOffer::flowCross( return {tecINTERNAL, takerAmount}; } -std::pair -CreateOffer::cross( - Sandbox& sb, - Sandbox& sbCancel, - Amounts const& takerAmount, - std::optional const& domainID) -{ - PaymentSandbox psbFlow{&sb}; - PaymentSandbox psbCancelFlow{&sbCancel}; - auto const ret = flowCross(psbFlow, psbCancelFlow, takerAmount, domainID); - psbFlow.apply(sb); - psbCancelFlow.apply(sbCancel); - return ret; -} - std::string CreateOffer::format_amount(STAmount const& amount) { @@ -907,20 +524,6 @@ CreateOffer::format_amount(STAmount const& amount) return txt; } -void -CreateOffer::preCompute() -{ - cross_type_ = CrossType::IouToIou; - bool const pays_xrp = ctx_.tx.getFieldAmount(sfTakerPays).native(); - bool const gets_xrp = ctx_.tx.getFieldAmount(sfTakerGets).native(); - if (pays_xrp && !gets_xrp) - cross_type_ = CrossType::IouToXrp; - else if (gets_xrp && !pays_xrp) - cross_type_ = CrossType::XrpToIou; - - return Transactor::preCompute(); -} - TER CreateOffer::applyHybrid( Sandbox& sb, @@ -1084,11 +687,6 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) // We reverse pays and gets because during crossing we are taking. Amounts const takerAmount(saTakerGets, saTakerPays); - // The amount of the offer that is unfilled after crossing has been - // performed. It may be equal to the original amount (didn't cross), - // empty (fully crossed), or something in-between. - Amounts place_offer; - JLOG(j_.debug()) << "Attempting cross: " << to_string(takerAmount.in.issue()) << " -> " << to_string(takerAmount.out.issue()); @@ -1101,8 +699,17 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) stream << " out: " << format_amount(takerAmount.out); } + // The amount of the offer that is unfilled after crossing has been + // performed. It may be equal to the original amount (didn't cross), + // empty (fully crossed), or something in-between. + Amounts place_offer; + PaymentSandbox psbFlow{&sb}; + PaymentSandbox psbCancelFlow{&sbCancel}; + std::tie(result, place_offer) = - cross(sb, sbCancel, takerAmount, domainID); + flowCross(psbFlow, psbCancelFlow, takerAmount, domainID); + psbFlow.apply(sb); + psbCancelFlow.apply(sbCancel); // We expect the implementation of cross to succeed // or give a tec. diff --git a/src/xrpld/app/tx/detail/CreateOffer.h b/src/xrpld/app/tx/detail/CreateOffer.h index f995f4a5d6..6e3d6145b1 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.h +++ b/src/xrpld/app/tx/detail/CreateOffer.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_TX_CREATEOFFER_H_INCLUDED #define RIPPLE_TX_CREATEOFFER_H_INCLUDED -#include -#include #include +#include + namespace ripple { class PaymentSandbox; @@ -36,8 +36,7 @@ public: static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; /** Construct a Transactor subclass that creates an offer in the ledger. */ - explicit CreateOffer(ApplyContext& ctx) - : Transactor(ctx), stepCounter_(1000, j_) + explicit CreateOffer(ApplyContext& ctx) : Transactor(ctx) { } @@ -52,10 +51,6 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Gather information beyond what the Transactor base class gathers. */ - void - preCompute() override; - /** Precondition: fee collection is likely. Attempt to create the offer. */ TER doApply() override; @@ -73,42 +68,6 @@ private: beast::Journal const j, Issue const& issue); - bool - dry_offer(ApplyView& view, Offer const& offer); - - static std::pair - select_path( - bool have_direct, - OfferStream const& direct, - bool have_bridge, - OfferStream const& leg1, - OfferStream const& leg2); - - std::pair - bridged_cross( - Taker& taker, - ApplyView& view, - ApplyView& view_cancel, - NetClock::time_point const when); - - std::pair - direct_cross( - Taker& taker, - ApplyView& view, - ApplyView& view_cancel, - NetClock::time_point const when); - - // Step through the stream for as long as possible, skipping any offers - // that are from the taker or which cross the taker's threshold. - // Return false if the is no offer in the book, true otherwise. - static bool - step_account(OfferStream& stream, Taker const& taker); - - // True if the number of offers that have been crossed - // exceeds the limit. - bool - reachedOfferCrossingLimit(Taker const& taker) const; - // Use the payment flow code to perform offer crossing. std::pair flowCross( @@ -117,17 +76,6 @@ private: Amounts const& takerAmount, std::optional 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 - cross( - Sandbox& sb, - Sandbox& sbCancel, - Amounts const& takerAmount, - std::optional const& domainID); - static std::string format_amount(STAmount const& amount); @@ -139,13 +87,6 @@ private: STAmount const& saTakerPays, STAmount const& saTakerGets, std::function)> 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; diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index 8f7005d55c..c62c38c675 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -34,13 +34,13 @@ #include #include +namespace ripple { + // During an EscrowFinish, the transaction must specify both // a condition and a fulfillment. We track whether that // fulfillment matches and validates the condition. -#define SF_CF_INVALID SF_PRIVATE5 -#define SF_CF_VALID SF_PRIVATE6 - -namespace ripple { +constexpr HashRouterFlags SF_CF_INVALID = HashRouterFlags::PRIVATE5; +constexpr HashRouterFlags SF_CF_VALID = HashRouterFlags::PRIVATE6; /* Escrow @@ -663,7 +663,7 @@ EscrowFinish::preflight(PreflightContext const& ctx) // If we haven't checked the condition, check it // now. Whether it passes or not isn't important // in preflight. - if (!(flags & (SF_CF_INVALID | SF_CF_VALID))) + if (!any(flags & (SF_CF_INVALID | SF_CF_VALID))) { if (checkCondition(*fb, *cb)) router.setFlags(id, SF_CF_VALID); @@ -1064,7 +1064,7 @@ EscrowFinish::doApply() // It's unlikely that the results of the check will // expire from the hash router, but if it happens, // simply re-run the check. - if (cb && !(flags & (SF_CF_INVALID | SF_CF_VALID))) + if (cb && !any(flags & (SF_CF_INVALID | SF_CF_VALID))) { auto const fb = ctx_.tx[~sfFulfillment]; @@ -1081,7 +1081,7 @@ EscrowFinish::doApply() // If the check failed, then simply return an error // and don't look at anything else. - if (flags & SF_CF_INVALID) + if (any(flags & SF_CF_INVALID)) return tecCRYPTOCONDITION_ERROR; // Check against condition in the ledger entry: diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp index 1b96b27f24..da3b57c8fe 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -31,6 +31,11 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) if (!ctx.rules.enabled(featureMPTokensV1)) return temDISABLED; + if (ctx.tx.isFieldPresent(sfDomainID) && + !(ctx.rules.enabled(featurePermissionedDomains) && + ctx.rules.enabled(featureSingleAssetVault))) + return temDISABLED; + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; @@ -48,6 +53,16 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) return temMALFORMED; } + if (auto const domain = ctx.tx[~sfDomainID]) + { + if (*domain == beast::zero) + return temMALFORMED; + + // Domain present implies that MPTokenIssuance is not public + if ((ctx.tx.getFlags() & tfMPTRequireAuth) == 0) + return temMALFORMED; + } + if (auto const metadata = ctx.tx[~sfMPTokenMetadata]) { if (metadata->length() == 0 || @@ -142,6 +157,7 @@ MPTokenIssuanceCreate::doApply() .assetScale = tx[~sfAssetScale], .transferFee = tx[~sfTransferFee], .metadata = tx[~sfMPTokenMetadata], + .domainId = tx[~sfDomainID], }); return result ? tesSUCCESS : result.error(); } diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp index 06ea089526..e05862af37 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp @@ -21,6 +21,7 @@ #include #include +#include #include namespace ripple { @@ -31,6 +32,14 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) if (!ctx.rules.enabled(featureMPTokensV1)) return temDISABLED; + if (ctx.tx.isFieldPresent(sfDomainID) && + !(ctx.rules.enabled(featurePermissionedDomains) && + ctx.rules.enabled(featureSingleAssetVault))) + return temDISABLED; + + if (ctx.tx.isFieldPresent(sfDomainID) && ctx.tx.isFieldPresent(sfHolder)) + return temMALFORMED; + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; @@ -48,6 +57,13 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) if (holderID && accountID == holderID) return temMALFORMED; + if (ctx.rules.enabled(featureSingleAssetVault)) + { + // Is this transaction actually changing anything ? + if (txFlags == 0 && !ctx.tx.isFieldPresent(sfDomainID)) + return temMALFORMED; + } + return preflight2(ctx); } @@ -97,9 +113,14 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) if (!sleMptIssuance) return tecOBJECT_NOT_FOUND; - // if the mpt has disabled locking - if (!((*sleMptIssuance)[sfFlags] & lsfMPTCanLock)) - return tecNO_PERMISSION; + if (!sleMptIssuance->isFlag(lsfMPTCanLock)) + { + // For readability two separate `if` rather than `||` of two conditions + if (!ctx.view.rules().enabled(featureSingleAssetVault)) + return tecNO_PERMISSION; + else if (ctx.tx.isFlag(tfMPTLock) || ctx.tx.isFlag(tfMPTUnlock)) + return tecNO_PERMISSION; + } // ensure it is issued by the tx submitter if ((*sleMptIssuance)[sfIssuer] != ctx.tx[sfAccount]) @@ -117,6 +138,20 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) return tecOBJECT_NOT_FOUND; } + if (auto const domain = ctx.tx[~sfDomainID]) + { + if (not sleMptIssuance->isFlag(lsfMPTRequireAuth)) + return tecNO_PERMISSION; + + if (*domain != beast::zero) + { + auto const sleDomain = + ctx.view.read(keylet::permissionedDomain(*domain)); + if (!sleDomain) + return tecOBJECT_NOT_FOUND; + } + } + return tesSUCCESS; } @@ -126,6 +161,7 @@ MPTokenIssuanceSet::doApply() auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID]; auto const txFlags = ctx_.tx.getFlags(); auto const holderID = ctx_.tx[~sfHolder]; + auto const domainID = ctx_.tx[~sfDomainID]; std::shared_ptr sle; if (holderID) @@ -147,6 +183,24 @@ MPTokenIssuanceSet::doApply() if (flagsIn != flagsOut) sle->setFieldU32(sfFlags, flagsOut); + if (domainID) + { + // This is enforced in preflight. + XRPL_ASSERT( + sle->getType() == ltMPTOKEN_ISSUANCE, + "MPTokenIssuanceSet::doApply : modifying MPTokenIssuance"); + + if (*domainID != beast::zero) + { + sle->setFieldH256(sfDomainID, *domainID); + } + else + { + if (sle->isFieldPresent(sfDomainID)) + sle->makeFieldAbsent(sfDomainID); + } + } + view().update(sle); return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/Taker.cpp b/src/xrpld/app/tx/detail/Taker.cpp deleted file mode 100644 index 9bfd6dc1d3..0000000000 --- a/src/xrpld/app/tx/detail/Taker.cpp +++ /dev/null @@ -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 - -#include -#include - -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("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::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("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("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("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("flow with negative input."); - - if (order.out < beast::zero) - Throw("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("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("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("redeemIOU has no funds to redeem"); - - auto ret = ripple::redeemIOU(view_, account, amount, issue, journal_); - - if (get_funds(account, amount) < beast::zero) - Throw("redeemIOU redeemed more funds than available"); - - return ret; -} - -TER -Taker::issueIOU( - AccountID const& account, - STAmount const& amount, - Issue const& issue) -{ - if (isXRP(amount)) - Throw("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 diff --git a/src/xrpld/app/tx/detail/Taker.h b/src/xrpld/app/tx/detail/Taker.h deleted file mode 100644 index 3702a30deb..0000000000 --- a/src/xrpld/app/tx/detail/Taker.h +++ /dev/null @@ -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 -#include - -#include -#include -#include -#include -#include - -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 - 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 diff --git a/src/xrpld/app/tx/detail/apply.cpp b/src/xrpld/app/tx/detail/apply.cpp index 889a520032..e2e0adae45 100644 --- a/src/xrpld/app/tx/detail/apply.cpp +++ b/src/xrpld/app/tx/detail/apply.cpp @@ -27,11 +27,16 @@ namespace ripple { -// These are the same flags defined as SF_PRIVATE1-4 in HashRouter.h -#define SF_SIGBAD SF_PRIVATE1 // Signature is bad -#define SF_SIGGOOD SF_PRIVATE2 // Signature is good -#define SF_LOCALBAD SF_PRIVATE3 // Local checks failed -#define SF_LOCALGOOD SF_PRIVATE4 // Local checks passed +// These are the same flags defined as HashRouterFlags::PRIVATE1-4 in +// HashRouter.h +constexpr HashRouterFlags SF_SIGBAD = + HashRouterFlags::PRIVATE1; // Signature is bad +constexpr HashRouterFlags SF_SIGGOOD = + HashRouterFlags::PRIVATE2; // Signature is good +constexpr HashRouterFlags SF_LOCALBAD = + HashRouterFlags::PRIVATE3; // Local checks failed +constexpr HashRouterFlags SF_LOCALGOOD = + HashRouterFlags::PRIVATE4; // Local checks passed //------------------------------------------------------------------------------ @@ -66,11 +71,11 @@ checkValidity( return {Validity::Valid, ""}; } - if (flags & SF_SIGBAD) + if (any(flags & SF_SIGBAD)) // Signature is known bad return {Validity::SigBad, "Transaction has bad signature."}; - if (!(flags & SF_SIGGOOD)) + if (!any(flags & SF_SIGGOOD)) { // Don't know signature state. Check it. auto const requireCanonicalSig = @@ -88,12 +93,12 @@ checkValidity( } // Signature is now known good - if (flags & SF_LOCALBAD) + if (any(flags & SF_LOCALBAD)) // ...but the local checks // are known bad. return {Validity::SigGoodOnly, "Local checks failed."}; - if (flags & SF_LOCALGOOD) + if (any(flags & SF_LOCALGOOD)) // ...and the local checks // are known good. return {Validity::Valid, ""}; @@ -112,7 +117,7 @@ checkValidity( void forceValidity(HashRouter& router, uint256 const& txid, Validity validity) { - int flags = 0; + HashRouterFlags flags = HashRouterFlags::UNDEFINED; switch (validity) { case Validity::Valid: @@ -125,7 +130,7 @@ forceValidity(HashRouter& router, uint256 const& txid, Validity validity) // would be silly to call directly break; } - if (flags) + if (any(flags)) router.setFlags(txid, flags); } diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 1238833d0d..23b4760488 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -1296,13 +1296,13 @@ PeerImp::handleTransaction( } // LCOV_EXCL_STOP - int flags; + HashRouterFlags flags; constexpr std::chrono::seconds tx_interval = 10s; if (!app_.getHashRouter().shouldProcess(txID, id_, flags, tx_interval)) { // we have seen this transaction recently - if (flags & SF_BAD) + if (any(flags & HashRouterFlags::BAD)) { fee_.update(Resource::feeUselessData, "known bad"); JLOG(p_journal_.debug()) << "Ignoring known bad tx " << txID; @@ -1329,7 +1329,7 @@ PeerImp::handleTransaction( { // Skip local checks if a server we trust // put the transaction in its open ledger - flags |= SF_TRUSTED; + flags |= HashRouterFlags::TRUSTED; } // for non-validator nodes only -- localPublicKey is set for @@ -2841,7 +2841,7 @@ PeerImp::doTransactions( void PeerImp::checkTransaction( - int flags, + HashRouterFlags flags, bool checkSignature, std::shared_ptr const& stx, bool batch) @@ -2866,7 +2866,8 @@ PeerImp::checkTransaction( (stx->getFieldU32(sfLastLedgerSequence) < app_.getLedgerMaster().getValidLedgerIndex())) { - app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD); + app_.getHashRouter().setFlags( + stx->getTransactionID(), HashRouterFlags::BAD); charge(Resource::feeUselessData, "expired tx"); return; } @@ -2925,8 +2926,10 @@ PeerImp::checkTransaction( << "Exception checking transaction: " << validReason; } - // Probably not necessary to set SF_BAD, but doesn't hurt. - app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD); + // Probably not necessary to set HashRouterFlags::BAD, but + // doesn't hurt. + app_.getHashRouter().setFlags( + stx->getTransactionID(), HashRouterFlags::BAD); charge( Resource::feeInvalidSignature, "check transaction signature failure"); @@ -2949,12 +2952,13 @@ PeerImp::checkTransaction( JLOG(p_journal_.trace()) << "Exception checking transaction: " << reason; } - app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD); + app_.getHashRouter().setFlags( + stx->getTransactionID(), HashRouterFlags::BAD); charge(Resource::feeInvalidSignature, "tx (impossible)"); return; } - bool const trusted(flags & SF_TRUSTED); + bool const trusted = any(flags & HashRouterFlags::TRUSTED); app_.getOPs().processTransaction( tx, trusted, false, NetworkOPs::FailHard::no); } @@ -2962,7 +2966,8 @@ PeerImp::checkTransaction( { JLOG(p_journal_.warn()) << "Exception in " << __func__ << ": " << ex.what(); - app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD); + app_.getHashRouter().setFlags( + stx->getTransactionID(), HashRouterFlags::BAD); using namespace std::string_literals; charge(Resource::feeInvalidData, "tx "s + ex.what()); } diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index d5f8e4d179..5aa49fd152 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -612,7 +613,7 @@ private: void checkTransaction( - int flags, + HashRouterFlags flags, bool checkSignature, std::shared_ptr const& stx, bool batch);