diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 52c78c580d1..321621b6034 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -24,7 +24,6 @@ add_library(xrpl::libxrpl ALIAS libxrpl) #]===============================] target_sources (xrpl_core PRIVATE src/ripple/beast/clock/basic_seconds_clock.cpp - src/ripple/beast/core/CurrentThreadName.cpp src/ripple/beast/core/SemanticVersion.cpp src/ripple/beast/hash/impl/xxhash.cpp src/ripple/beast/insight/impl/Collector.cpp @@ -56,6 +55,7 @@ target_sources (xrpl_core PRIVATE src/ripple/basics/impl/Log.cpp src/ripple/basics/impl/Number.cpp src/ripple/basics/impl/StringUtilities.cpp + src/ripple/basics/impl/ThreadUtilities.cpp #[===============================[ main sources: subdir: json @@ -200,6 +200,7 @@ install ( src/ripple/basics/TaggedCache.h src/ripple/basics/tagged_integer.h src/ripple/basics/ThreadSafetyAnalysis.h + src/ripple/basics/ThreadUtilities.h src/ripple/basics/ToString.h src/ripple/basics/UnorderedContainers.h src/ripple/basics/UptimeClock.h @@ -838,6 +839,7 @@ if (tests) src/test/basics/Slice_test.cpp src/test/basics/StringUtilities_test.cpp src/test/basics/TaggedCache_test.cpp + src/test/basics/ThreadName_test.cpp src/test/basics/XRPAmount_test.cpp src/test/basics/base64_test.cpp src/test/basics/base_uint_test.cpp @@ -855,7 +857,6 @@ if (tests) src/test/beast/LexicalCast_test.cpp src/test/beast/SemanticVersion_test.cpp src/test/beast/aged_associative_container_test.cpp - src/test/beast/beast_CurrentThreadName_test.cpp src/test/beast/beast_Journal_test.cpp src/test/beast/beast_PropertyStream_test.cpp src/test/beast/beast_Zero_test.cpp diff --git a/RELEASENOTES.md b/RELEASENOTES.md index e83855b79db..2300d476c45 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,135 @@ This document contains the release notes for `rippled`, the reference server imp Have new ideas? Need help with setting up your node? [Please open an issue here](https://github.com/xrplf/rippled/issues/new/choose). +# Introducing XRP Ledger version 1.12.0 + +Version 1.12.0 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release adds new features and bug fixes, and introduces these amendments: + +- `AMM` +- `Clawback` +- `fixReducedOffersV1` + +[Sign Up for Future Release Announcements](https://groups.google.com/g/ripple-server) + + + +## Action Required + +Three new amendments are now open for voting according to the XRP Ledger's [amendment process](https://xrpl.org/amendments.html), which enables protocol changes following two weeks of >80% support from trusted validators. + +If you operate an XRP Ledger server, upgrade to version 1.12.0 by September 20, 2023 to ensure service continuity. The exact time that protocol changes take effect depends on the voting decisions of the decentralized network. + + +## Install / Upgrade + +On supported platforms, see the [instructions on installing or updating `rippled`](https://xrpl.org/install-rippled.html). + + +## Changelog + +### New Features and Amendments + +- **`AMM`**: Introduces an automated market maker (AMM) protocol to the XRP Ledger's decentralized exchange, enabling you to trade assets without a counterparty. For more information about AMMs, see: [Automated Market Maker](https://opensource.ripple.com/docs/xls-30d-amm/amm-uc/). [#4294](https://github.com/XRPLF/rippled/pull/4294) + +- **`Clawback`**: Allows issuers to add the `lsfAllowTrustLineClawback` flag to an issuing account. This enables the account to recover, or _claw back_, issued tokens after they're distributed to accounts. For additional documentation on this feature, see: [#4553](https://github.com/XRPLF/rippled/pull/4553). + +- **`fixReducedOffersV1`**: Reduces the occurrence of order books that are blocked by reduced offers. [#4512](https://github.com/XRPLF/rippled/pull/4512) + +- Added binary hardening and linker flags to enhance security during the build process. [#4603](https://github.com/XRPLF/rippled/pull/4603) + +- Updated build dependencies to the most recent versions in Conan Center. [#4595](https://github.com/XRPLF/rippled/pull/4595) + +- Updated Conan recipe for NuDB. [#4615](https://github.com/XRPLF/rippled/pull/4615) + +- Added a pre-commit hook that runs the clang-format linter locally before committing changes. To install this feature, see: [CONTRIBUTING](https://github.com/XRPLF/xrpl-dev-portal/blob/master/CONTRIBUTING.md). [#4599](https://github.com/XRPLF/rippled/pull/4599) + +- Added quality-of-life improvements to workflows, using new [concurrency control](https://docs.github.com/en/actions/using-jobs/using-concurrency) features. [#4597](https://github.com/XRPLF/rippled/pull/4597) + +- Added an Artifactory to the `nix` workflow to improve build times. [#4556](https://github.com/XRPLF/rippled/pull/4556) + +- Added WebSocket and RPC port info to `server_info` responses. [#4427](https://github.com/XRPLF/rippled/pull/4427) + + + +### Bug Fixes and Performance Improvements + +- Fixed an incorrect error response when there are missing fields in the API v2 `ledger_entry` method. [#4552](https://github.com/XRPLF/rippled/pull/4552) + +- Updated checkout versions to resolve warnings during Github jobs. [#4598](https://github.com/XRPLF/rippled/pull/4598) + +- Added an error response to the API v2 `account_info` method when you include an invalid `signer_lists` value. [#4585](https://github.com/XRPLF/rippled/pull/4585) + +- Fixed an issue with the debug package build. [#4591](https://github.com/XRPLF/rippled/pull/4591) + +- Added additional error responses to the API v2 `AccountTx` method. [#4571](https://github.com/XRPLF/rippled/pull/4571) + +- Fixed build references to deleted `ServerHandlerImp`. [#4592](https://github.com/XRPLF/rippled/pull/4592) + +- Fixed package definitions for Conan. [#4485](https://github.com/XRPLF/rippled/pull/4485) + +- Changed the output type of the `mulDiv()` function from `std::pair` to `std::optional`. [#4243](https://github.com/XRPLF/rippled/pull/4243) + +- Updated `Handler::Condition` enum values to make the code less brittle. [#4239](https://github.com/XRPLF/rippled/pull/4239) + +- Renamed `ServerHandlerImp` to `ServerHandler`. [#4516](https://github.com/XRPLF/rippled/pull/4516) + +- Removed the deprecated `accepted`, `seq`, `hash`, and `totalCoins` fields from the `ledger` method. [#4244](https://github.com/XRPLF/rippled/pull/4244) + +- Replaced hand-rolled code with `std::from_chars` for better maintainability. [#4473](https://github.com/XRPLF/rippled/pull/4473) + +- Removed an unused `TypedField` move constructor. [#4567](https://github.com/XRPLF/rippled/pull/4567) + +- Enabled the `BETA_RPC_API` flag in the default unit tests config, making the API v2 available to all unit tests. [#4573](https://github.com/XRPLF/rippled/pull/4573) + + +### Docs + +- Updated build instructions with additional steps to take after updating dependencies. [#4623](https://github.com/XRPLF/rippled/pull/4623) + +- Updated contributing doc to clarify that beta releases should also be pushed to the `release` branch. [#4589](https://github.com/XRPLF/rippled/pull/4589) + + +[Full Commit Log](https://github.com/XRPLF/rippled/compare/1.11.0...1.12.0) + + +### GitHub + +The public source code repository for `rippled` is hosted on GitHub at . + +We welcome all contributions and invite everyone to join the community of XRP Ledger developers to help build the Internet of Value. + + +## Credits + +The following people contributed directly to this release: + +- Alphonse N. Mousse <39067955+a-noni-mousse@users.noreply.github.com> +- Arihant Kothari +- Chenna Keshava B S <21219765+ckeshava@users.noreply.github.com> +- Denis Angell +- Ed Hennis +- Elliot Lee +- Gregory Tsipenyuk +- Howard Hinnant +- Ikko Eltociear Ashimine +- John Freeman +- Manoj Doshi +- Mark Travis +- Mayukha Vadari +- Michael Legleux +- Peter Chen <34582813+PeterChen13579@users.noreply.github.com> +- RichardAH +- Rome Reginelli +- Scott Schurr +- Shawn Xie <35279399+shawnxie999@users.noreply.github.com> +- drlongle + +Bug Bounties and Responsible Disclosures: + +We welcome reviews of the rippled code and urge researchers to responsibly disclose any issues they may find. + +To report a bug, please send a detailed report to: + # Introducing XRP Ledger version 1.11.0 diff --git a/cfg/rippled-example.cfg b/cfg/rippled-example.cfg index 0a669313066..9891e44c885 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/rippled-example.cfg @@ -283,12 +283,14 @@ # ssl_cert # # Specifies the path to the SSL certificate file in PEM format. -# This is not needed if the chain includes it. +# This is not needed if the chain includes it. Use ssl_chain if +# your certificate includes one or more intermediates. # # ssl_chain # # If you need a certificate chain, specify the path to the # certificate chain here. The chain may include the end certificate. +# This must be used if the certificate includes intermediates. # # ssl_ciphers = # diff --git a/src/ripple/app/ledger/impl/LedgerCleaner.cpp b/src/ripple/app/ledger/impl/LedgerCleaner.cpp index e5ee6409d34..d63c24476d7 100644 --- a/src/ripple/app/ledger/impl/LedgerCleaner.cpp +++ b/src/ripple/app/ledger/impl/LedgerCleaner.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include namespace ripple { @@ -218,7 +218,7 @@ class LedgerCleanerImp : public LedgerCleaner void run() { - beast::setCurrentThreadName("LedgerCleaner"); + this_thread::set_name("LedgerCleaner"); JLOG(j_.debug()) << "Started"; while (true) diff --git a/src/ripple/app/main/BasicApp.cpp b/src/ripple/app/main/BasicApp.cpp index 5993df62fa7..a03d7255b3e 100644 --- a/src/ripple/app/main/BasicApp.cpp +++ b/src/ripple/app/main/BasicApp.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include BasicApp::BasicApp(std::size_t numberOfThreads) { @@ -28,7 +28,7 @@ BasicApp::BasicApp(std::size_t numberOfThreads) while (numberOfThreads--) { threads_.emplace_back([this, numberOfThreads]() { - beast::setCurrentThreadName( + ripple::this_thread::set_name( "io svc #" + std::to_string(numberOfThreads)); this->io_service_.run(); }); diff --git a/src/ripple/app/main/GRPCServer.cpp b/src/ripple/app/main/GRPCServer.cpp index 3a5e96b0ed9..eb2d4ce9a18 100644 --- a/src/ripple/app/main/GRPCServer.cpp +++ b/src/ripple/app/main/GRPCServer.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include @@ -692,9 +692,8 @@ GRPCServer::start() if (running_ = impl_.start(); running_) { thread_ = std::thread([this]() { - beast::setCurrentThreadName("rippled : GRPCServer"); // Start the event loop and begin handling requests - beast::setCurrentThreadName("rippled: grpc"); + this_thread::set_name("rippled: grpc"); this->impl_.handleRpcs(); }); } diff --git a/src/ripple/app/main/LoadManager.cpp b/src/ripple/app/main/LoadManager.cpp index 5e87063f000..df9d72f599a 100644 --- a/src/ripple/app/main/LoadManager.cpp +++ b/src/ripple/app/main/LoadManager.cpp @@ -21,8 +21,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -99,7 +99,7 @@ LoadManager::stop() void LoadManager::run() { - beast::setCurrentThreadName("LoadManager"); + this_thread::set_name("LoadManager"); using namespace std::chrono_literals; using clock_type = std::chrono::steady_clock; diff --git a/src/ripple/app/main/Main.cpp b/src/ripple/app/main/Main.cpp index 6693ac0f7bb..0ce8b085971 100644 --- a/src/ripple/app/main/Main.cpp +++ b/src/ripple/app/main/Main.cpp @@ -22,9 +22,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -348,8 +348,7 @@ run(int argc, char** argv) { using namespace std; - beast::setCurrentThreadName( - "rippled: main " + BuildInfo::getVersionString()); + this_thread::set_name("main " + BuildInfo::getVersionString()); po::variables_map vm; @@ -777,7 +776,7 @@ run(int argc, char** argv) } // We have an RPC command to process: - beast::setCurrentThreadName("rippled: rpc"); + this_thread::set_name("rippled: rpc"); return RPCCall::fromCommandLine( *config, vm["parameters"].as>(), *logs); } diff --git a/src/ripple/app/misc/SHAMapStoreImp.cpp b/src/ripple/app/misc/SHAMapStoreImp.cpp index d5cb07792dc..f396144cfd5 100644 --- a/src/ripple/app/misc/SHAMapStoreImp.cpp +++ b/src/ripple/app/misc/SHAMapStoreImp.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -286,7 +286,7 @@ SHAMapStoreImp::run() "Reporting does not support online_delete. Remove " "online_delete info from config"); } - beast::setCurrentThreadName("SHAMapStore"); + this_thread::set_name("SHAMapStore"); LedgerIndex lastRotated = state_db_.getState().lastRotated; netOPs_ = &app_.getOPs(); ledgerMaster_ = &app_.getLedgerMaster(); diff --git a/src/ripple/app/misc/impl/AMMUtils.cpp b/src/ripple/app/misc/impl/AMMUtils.cpp index dcb403296c1..1d787dbe4ca 100644 --- a/src/ripple/app/misc/impl/AMMUtils.cpp +++ b/src/ripple/app/misc/impl/AMMUtils.cpp @@ -200,14 +200,17 @@ deleteAMMTrustLines( keylet::ownerDir(ammAccountID), [&](LedgerEntryType nodeType, uint256 const&, - std::shared_ptr& sleItem) -> TER { + std::shared_ptr& sleItem) -> std::pair { + // Skip AMM + if (nodeType == LedgerEntryType::ltAMM) + return {tesSUCCESS, SkipEntry::Yes}; // Should only have the trustlines if (nodeType != LedgerEntryType::ltRIPPLE_STATE) { JLOG(j.error()) << "deleteAMMTrustLines: deleting non-trustline " << nodeType; - return tecINTERNAL; + return {tecINTERNAL, SkipEntry::No}; } // Trustlines must have zero balance @@ -216,10 +219,12 @@ deleteAMMTrustLines( JLOG(j.error()) << "deleteAMMTrustLines: deleting trustline with " "non-zero balance."; - return tecINTERNAL; + return {tecINTERNAL, SkipEntry::No}; } - return deleteAMMTrustLine(sb, sleItem, ammAccountID, j); + return { + deleteAMMTrustLine(sb, sleItem, ammAccountID, j), + SkipEntry::No}; }, j, maxTrustlinesToDelete); @@ -255,6 +260,12 @@ deleteAMMAccount( return ter; auto const ownerDirKeylet = keylet::ownerDir(ammAccountID); + if (!sb.dirRemove( + ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false)) + { + JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link"; + return tecINTERNAL; + } if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet)) { JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of " diff --git a/src/ripple/app/reporting/ETLSource.cpp b/src/ripple/app/reporting/ETLSource.cpp index be31f4fdfa9..213b17737b4 100644 --- a/src/ripple/app/reporting/ETLSource.cpp +++ b/src/ripple/app/reporting/ETLSource.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include diff --git a/src/ripple/app/reporting/ReportingETL.cpp b/src/ripple/app/reporting/ReportingETL.cpp index d8d6af36881..0b68ae1ffa1 100644 --- a/src/ripple/app/reporting/ReportingETL.cpp +++ b/src/ripple/app/reporting/ReportingETL.cpp @@ -19,8 +19,7 @@ #include #include - -#include +#include #include #include #include @@ -510,7 +509,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence) &startSequence, &writeConflict, &transformQueue]() { - beast::setCurrentThreadName("rippled: ReportingETL extract"); + this_thread::set_name("ETL extract"); uint32_t currentSequence = startSequence; // there are two stopping conditions here. @@ -562,7 +561,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence) &writeConflict, &loadQueue, &transformQueue]() { - beast::setCurrentThreadName("rippled: ReportingETL transform"); + this_thread::set_name("ETL transform"); assert(parent); parent = std::make_shared(*parent, NetClock::time_point{}); @@ -601,7 +600,7 @@ ReportingETL::runETLPipeline(uint32_t startSequence) &lastPublishedSequence, &loadQueue, &writeConflict]() { - beast::setCurrentThreadName("rippled: ReportingETL load"); + this_thread::set_name("ETL load"); size_t totalTransactions = 0; double totalTime = 0; while (!writeConflict) @@ -825,7 +824,7 @@ void ReportingETL::doWork() { worker_ = std::thread([this]() { - beast::setCurrentThreadName("rippled: ReportingETL worker"); + this_thread::set_name("ETL worker"); if (readOnly_) monitorReadOnly(); else diff --git a/src/ripple/app/tx/impl/AMMBid.cpp b/src/ripple/app/tx/impl/AMMBid.cpp index d059f88c76c..822e72203a6 100644 --- a/src/ripple/app/tx/impl/AMMBid.cpp +++ b/src/ripple/app/tx/impl/AMMBid.cpp @@ -223,9 +223,11 @@ applyBid( lptAMMBalance, toSTAmount(lptAMMBalance.issue(), burn), false); if (saBurn >= lptAMMBalance) { - JLOG(ctx_.journal.debug()) - << "AMM Bid: invalid burn " << burn << " " << lptAMMBalance; - return tecAMM_BALANCE; + // This error case should never occur. + JLOG(ctx_.journal.fatal()) + << "AMM Bid: LP Token burn exceeds AMM balance " << burn << " " + << lptAMMBalance; + return tecINTERNAL; } auto res = redeemIOU(sb, account_, saBurn, lpTokens.issue(), ctx_.journal); @@ -316,9 +318,10 @@ applyBid( auto const refund = fractionRemaining * pricePurchased; if (refund > *payPrice) { - JLOG(ctx_.journal.debug()) - << "AMM Bid: invalid refund " << refund << " " << *payPrice; - return {tecINSUFFICIENT_PAYMENT, false}; + // This error case should never occur. + JLOG(ctx_.journal.fatal()) << "AMM Bid: refund exceeds payPrice " + << refund << " " << *payPrice; + return {tecINTERNAL, false}; } res = accountSend( sb, diff --git a/src/ripple/app/tx/impl/AMMCreate.cpp b/src/ripple/app/tx/impl/AMMCreate.cpp index 0c6874953a3..55b1126fcd0 100644 --- a/src/ripple/app/tx/impl/AMMCreate.cpp +++ b/src/ripple/app/tx/impl/AMMCreate.cpp @@ -279,6 +279,20 @@ applyCreate( // AMM creator gets the auction slot and the voting slot. initializeFeeAuctionVote( ctx_.view(), ammSle, account_, lptIss, ctx_.tx[sfTradingFee]); + + // Add owner directory to link the root account and AMM object. + if (auto const page = sb.dirInsert( + keylet::ownerDir(*ammAccount), + ammSle->key(), + describeOwnerDir(*ammAccount))) + { + ammSle->setFieldU64(sfOwnerNode, *page); + } + else + { + JLOG(j_.debug()) << "AMM Instance: failed to insert owner dir"; + return {tecDIR_FULL, false}; + } sb.insert(ammSle); // Send LPT to LP. diff --git a/src/ripple/app/tx/impl/DeleteAccount.cpp b/src/ripple/app/tx/impl/DeleteAccount.cpp index eeffc66d382..67545723a5f 100644 --- a/src/ripple/app/tx/impl/DeleteAccount.cpp +++ b/src/ripple/app/tx/impl/DeleteAccount.cpp @@ -298,19 +298,19 @@ DeleteAccount::doApply() ownerDirKeylet, [&](LedgerEntryType nodeType, uint256 const& dirEntry, - std::shared_ptr& sleItem) -> TER { + std::shared_ptr& sleItem) -> std::pair { if (auto deleter = nonObligationDeleter(nodeType)) { TER const result{ deleter(ctx_.app, view(), account_, dirEntry, sleItem, j_)}; - return result; + return {result, SkipEntry::No}; } assert(!"Undeletable entry should be found in preclaim."); JLOG(j_.error()) << "DeleteAccount undeletable item not " "found in preclaim."; - return tecHAS_OBLIGATIONS; + return {tecHAS_OBLIGATIONS, SkipEntry::No}; }, ctx_.journal); if (ter != tesSUCCESS) diff --git a/src/ripple/beast/core/CurrentThreadName.h b/src/ripple/basics/ThreadUtilities.h similarity index 51% rename from src/ripple/beast/core/CurrentThreadName.h rename to src/ripple/basics/ThreadUtilities.h index 5adbb210880..f91dbd118e4 100644 --- a/src/ripple/beast/core/CurrentThreadName.h +++ b/src/ripple/basics/ThreadUtilities.h @@ -1,11 +1,7 @@ //------------------------------------------------------------------------------ /* - This file is part of Beast: https://github.com/vinniefalco/Beast - Copyright 2013, Vinnie Falco - - Portions of this file are from JUCE. - Copyright (c) 2013 - Raw Material Software Ltd. - Please visit http://www.juce.com + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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 @@ -21,31 +17,34 @@ */ //============================================================================== -#ifndef BEAST_CORE_CURRENT_THREAD_NAME_H_INCLUDED -#define BEAST_CORE_CURRENT_THREAD_NAME_H_INCLUDED +#ifndef RIPPLE_BASICS_THREADUTILITIES_H_INCLUDED +#define RIPPLE_BASICS_THREADUTILITIES_H_INCLUDED #include -#include +#include -namespace beast { +namespace ripple { -/** Changes the name of the caller thread. - Different OSes may place different length or content limits on this name. -*/ -void -setCurrentThreadName(std::string_view newThreadName); +std::string +get_name(std::thread::native_handle_type t); -/** Returns the name of the caller thread. +template +inline auto +get_name(Thread& t) -> decltype(t.native_handle(), t.join(), std::string{}) +{ + return get_name(t.native_handle()); +} - The name returned is the name as set by a call to setCurrentThreadName(). - If the thread name is set by an external force, then that name change - will not be reported. +namespace this_thread { - If no name has ever been set, then the empty string is returned. -*/ std::string -getCurrentThreadName(); +get_name(); + +void +set_name(std::string s); + +} // namespace this_thread -} // namespace beast +} // namespace ripple #endif diff --git a/src/ripple/basics/impl/ThreadUtilities.cpp b/src/ripple/basics/impl/ThreadUtilities.cpp new file mode 100644 index 00000000000..be1745c40a7 --- /dev/null +++ b/src/ripple/basics/impl/ThreadUtilities.cpp @@ -0,0 +1,140 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 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 { + +#ifdef __APPLE__ + +#include + +std::string +get_name(std::thread::native_handle_type t) +{ + char buffer[64]; + if (pthread_getname_np(t, buffer, sizeof(buffer)) != 0) + throw std::runtime_error("get_name failed\n"); + return buffer; +} + +namespace this_thread { + +std::string +get_name() +{ + return ripple::get_name(pthread_self()); +} + +void +set_name(std::string s) +{ + s.resize(15); + if (pthread_setname_np(s.data()) != 0) + throw std::runtime_error("this_thread::set_name failed\n"); +} + +} // namespace this_thread + +#endif // __APPLE__ + +#ifdef __linux__ + +#include + +std::string +get_name(std::thread::native_handle_type t) +{ + char buffer[64]; + if (pthread_getname_np(t, buffer, sizeof(buffer)) != 0) + throw std::runtime_error("get_name failed\n"); + return buffer; +} + +namespace this_thread { + +std::string +get_name() +{ + return ripple::get_name(pthread_self()); +} + +void +set_name(std::string s) +{ + s.resize(15); + if (pthread_setname_np(pthread_self(), s.data()) != 0) + throw std::runtime_error("this_thread::set_name failed\n"); +} + +} // namespace this_thread + +#endif // __linux__ + +#ifdef _WIN64 + +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include + +std::string +get_name(std::thread::native_handle_type t) +{ + wchar_t* unhandled_data{}; + HRESULT r = GetThreadDescription(t, &unhandled_data); + std::unique_ptr data{ + unhandled_data, LocalFree}; + if (FAILED(r)) + throw std::runtime_error("get_name failed\n"); + std::string s; + auto p = data.get(); + while (*p) + s.push_back(static_cast(*p++)); + return s; +} + +namespace this_thread { + +std::string +get_name() +{ + return ripple::get_name(GetCurrentThread()); +} + +void +set_name(std::string s) +{ + assert(s.size() <= 15); + s.resize(15); + std::wstring ws; + for (auto c : s) + ws += c; + HRESULT r = SetThreadDescription(GetCurrentThread(), ws.data()); + if (FAILED(r)) + throw std::runtime_error("this_thread::set_name failed\n"); +} + +} // namespace this_thread + +#endif // __WINDOWS__ + +} // namespace ripple diff --git a/src/ripple/beast/core/CurrentThreadName.cpp b/src/ripple/beast/core/CurrentThreadName.cpp deleted file mode 100644 index 80d275a1fff..00000000000 --- a/src/ripple/beast/core/CurrentThreadName.cpp +++ /dev/null @@ -1,125 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of Beast: https://github.com/vinniefalco/Beast - Copyright 2013, Vinnie Falco - - Portions of this file are from JUCE. - Copyright (c) 2013 - Raw Material Software Ltd. - Please visit http://www.juce.com - - 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 - -//------------------------------------------------------------------------------ - -#if BOOST_OS_WINDOWS -#include -#include - -namespace beast::detail { - -inline void -setCurrentThreadNameImpl(std::string_view name) -{ -#if DEBUG && BOOST_COMP_MSVC - // This technique is documented by Microsoft and works for all versions - // of Windows and Visual Studio provided that the process is being run - // under the Visual Studio debugger. For more details, see: - // https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code - -#pragma pack(push, 8) - struct THREADNAME_INFO - { - DWORD dwType; - LPCSTR szName; - DWORD dwThreadID; - DWORD dwFlags; - }; -#pragma pack(pop) - - THREADNAME_INFO ni; - - ni.dwType = 0x1000; - ni.szName = name.data(); - ni.dwThreadID = GetCurrentThreadId(); - ni.dwFlags = 0; - -#pragma warning(push) -#pragma warning(disable : 6320 6322) - __try - { - RaiseException( - 0x406d1388, 0, sizeof(ni) / sizeof(ULONG_PTR), (ULONG_PTR*)&ni); - } - __except (EXCEPTION_CONTINUE_EXECUTION) - { - } -#pragma warning(pop) -#endif -} - -} // namespace beast::detail -#endif // BOOST_OS_WINDOWS - -#if BOOST_OS_MACOS -#include - -namespace beast::detail { - -inline void -setCurrentThreadNameImpl(std::string_view name) -{ - pthread_setname_np(name.data()); -} - -} // namespace beast::detail -#endif // BOOST_OS_MACOS - -#if BOOST_OS_LINUX -#include - -namespace beast::detail { - -inline void -setCurrentThreadNameImpl(std::string_view name) -{ - pthread_setname_np(pthread_self(), name.data()); -} - -} // namespace beast::detail -#endif // BOOST_OS_LINUX - -namespace beast { - -namespace detail { -thread_local std::string threadName; -} // namespace detail - -std::string -getCurrentThreadName() -{ - return detail::threadName; -} - -void -setCurrentThreadName(std::string_view name) -{ - detail::threadName = name; - detail::setCurrentThreadNameImpl(name); -} - -} // namespace beast diff --git a/src/ripple/core/impl/Job.cpp b/src/ripple/core/impl/Job.cpp index 780a9f49cdf..952d513dc16 100644 --- a/src/ripple/core/impl/Job.cpp +++ b/src/ripple/core/impl/Job.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include @@ -61,7 +61,7 @@ Job::queue_time() const void Job::doJob() { - beast::setCurrentThreadName("doJob: " + mName); + this_thread::set_name("doJob: " + mName); m_loadEvent->start(); m_loadEvent->setName(mName); diff --git a/src/ripple/core/impl/SNTPClock.cpp b/src/ripple/core/impl/SNTPClock.cpp index 8651dcbe5a4..43bd35166df 100644 --- a/src/ripple/core/impl/SNTPClock.cpp +++ b/src/ripple/core/impl/SNTPClock.cpp @@ -18,8 +18,8 @@ //============================================================================== #include +#include #include -#include #include #include #include @@ -193,7 +193,7 @@ class SNTPClientImp : public SNTPClock void doRun() { - beast::setCurrentThreadName("rippled: SNTPClock"); + this_thread::set_name("SNTPClock"); io_service_.run(); } diff --git a/src/ripple/core/impl/Workers.cpp b/src/ripple/core/impl/Workers.cpp index 732e6f0ec8a..aac2bf1716a 100644 --- a/src/ripple/core/impl/Workers.cpp +++ b/src/ripple/core/impl/Workers.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include #include #include @@ -206,7 +206,7 @@ Workers::Worker::run() for (;;) { // Put the name back in case the callback changed it - beast::setCurrentThreadName(threadName_); + this_thread::set_name(threadName_); // Acquire a task or "internal task." // @@ -259,7 +259,7 @@ Workers::Worker::run() } // Set inactive thread name. - beast::setCurrentThreadName("(" + threadName_ + ")"); + this_thread::set_name("(" + threadName_ + ")"); // [1] We will be here when the paused list is popped // diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 2c8d2354e0c..5680114a79c 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -43,6 +43,7 @@ namespace ripple { enum class WaiveTransferFee : bool { No = false, Yes }; +enum class SkipEntry : bool { No = false, Yes }; //------------------------------------------------------------------------------ // @@ -458,6 +459,14 @@ transferXRP( [[nodiscard]] TER requireAuth(ReadView const& view, Issue const& issue, AccountID const& account); +/** Deleter function prototype. Returns the status of the entry deletion + * (if should not be skipped) and if the entry should be skipped. The status + * is always tesSUCCESS if the entry should be skipped. + */ +using EntryDeleter = std::function( + LedgerEntryType, + uint256 const&, + std::shared_ptr&)>; /** Cleanup owner directory entries on account delete. * Used for a regular and AMM accounts deletion. The caller * has to provide the deleter function, which handles details of @@ -469,8 +478,7 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account); cleanupOnAccountDelete( ApplyView& view, Keylet const& ownerDirKeylet, - std::function&)> - deleter, + EntryDeleter const& deleter, beast::Journal j, std::optional maxNodesToDelete = std::nullopt); diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 75fd35782b6..5050e8764e9 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -1531,8 +1531,7 @@ TER cleanupOnAccountDelete( ApplyView& view, Keylet const& ownerDirKeylet, - std::function&)> - deleter, + EntryDeleter const& deleter, beast::Journal j, std::optional maxNodesToDelete) { @@ -1567,8 +1566,8 @@ cleanupOnAccountDelete( // Deleter handles the details of specific account-owned object // deletion - if (auto const ter = deleter(nodeType, dirEntry, sleItem); - ter != tesSUCCESS) + auto const [ter, skipEntry] = deleter(nodeType, dirEntry, sleItem); + if (ter != tesSUCCESS) return ter; // dirFirst() and dirNext() are like iterators with exposed @@ -1580,21 +1579,22 @@ cleanupOnAccountDelete( // "iterator state" is invalid. // // 1. During the process of getting an entry from the - // directory uDirEntry was incremented from 0 to 1. + // directory uDirEntry was incremented from 'it' to 'it'+1. // - // 2. We then deleted the entry at index 0, which means the - // entry that was at 1 has now moved to 0. + // 2. We then deleted the entry at index 'it', which means the + // entry that was at 'it'+1 has now moved to 'it'. // - // 3. So we verify that uDirEntry is indeed 1. Then we jam it - // back to zero to "un-invalidate" the iterator. - assert(uDirEntry == 1); - if (uDirEntry != 1) + // 3. So we verify that uDirEntry is indeed 'it'+1. Then we jam it + // back to 'it' to "un-invalidate" the iterator. + assert(uDirEntry >= 1); + if (uDirEntry == 0) { JLOG(j.error()) << "DeleteAccount iterator re-validation failed."; return tefBAD_LEDGER; } - uDirEntry = 0; + if (skipEntry == SkipEntry::No) + uDirEntry--; } while ( dirNext(view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry)); diff --git a/src/ripple/nodestore/backend/RocksDBFactory.cpp b/src/ripple/nodestore/backend/RocksDBFactory.cpp index b34560dba89..11979ed2019 100644 --- a/src/ripple/nodestore/backend/RocksDBFactory.cpp +++ b/src/ripple/nodestore/backend/RocksDBFactory.cpp @@ -22,9 +22,9 @@ #if RIPPLE_ROCKSDB_AVAILABLE #include +#include #include #include -#include #include // VFALCO Bad dependency #include #include @@ -67,7 +67,7 @@ class RocksDBEnv : public rocksdb::EnvWrapper std::size_t const id(++n); std::stringstream ss; ss << "rocksdb #" << id; - beast::setCurrentThreadName(ss.str()); + this_thread::set_name(ss.str()); (*f)(a); } diff --git a/src/ripple/nodestore/impl/Database.cpp b/src/ripple/nodestore/impl/Database.cpp index 70416c873d5..e0c82d00d03 100644 --- a/src/ripple/nodestore/impl/Database.cpp +++ b/src/ripple/nodestore/impl/Database.cpp @@ -18,8 +18,8 @@ //============================================================================== #include +#include #include -#include #include #include #include @@ -63,8 +63,7 @@ Database::Database( [this](int i) { runningThreads_++; - beast::setCurrentThreadName( - "db prefetch #" + std::to_string(i)); + this_thread::set_name("prefetch " + std::to_string(i)); decltype(read_) read; diff --git a/src/ripple/perflog/impl/PerfLogImp.cpp b/src/ripple/perflog/impl/PerfLogImp.cpp index db5a188fc3e..4fe6eda2fc5 100644 --- a/src/ripple/perflog/impl/PerfLogImp.cpp +++ b/src/ripple/perflog/impl/PerfLogImp.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include #include @@ -255,7 +255,7 @@ PerfLogImp::openLog() void PerfLogImp::run() { - beast::setCurrentThreadName("perflog"); + this_thread::set_name("perflog"); lastLog_ = system_clock::now(); while (true) diff --git a/src/ripple/protocol/impl/BuildInfo.cpp b/src/ripple/protocol/impl/BuildInfo.cpp index af122ee3845..f6ca21e43e4 100644 --- a/src/ripple/protocol/impl/BuildInfo.cpp +++ b/src/ripple/protocol/impl/BuildInfo.cpp @@ -33,7 +33,7 @@ namespace BuildInfo { // and follow the format described at http://semver.org/ //------------------------------------------------------------------------------ // clang-format off -char const* const versionString = "1.12.0-rc3" +char const* const versionString = "1.12.0" // clang-format on #if defined(DEBUG) || defined(SANITIZER) diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 9192513457a..d9e7ca178c0 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -279,6 +279,7 @@ LedgerFormats::LedgerFormats() {sfLPTokenBalance, soeREQUIRED}, {sfAsset, soeREQUIRED}, {sfAsset2, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, }, commonFields); diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index a27a564e112..567e63699e2 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -160,6 +160,7 @@ JSS(alternatives); // out: PathRequest, RipplePathFind JSS(amendment_blocked); // out: NetworkOPs JSS(amendments); // in: AccountObjects, out: NetworkOPs JSS(amm); // out: amm_info +JSS(amm_account); // in: amm_info JSS(amount); // out: AccountChannels, amm_info JSS(amount2); // out: amm_info JSS(api_version); // in: many, out: Version diff --git a/src/ripple/resource/impl/ResourceManager.cpp b/src/ripple/resource/impl/ResourceManager.cpp index 1a7e74ec1f8..137bbf36eb7 100644 --- a/src/ripple/resource/impl/ResourceManager.cpp +++ b/src/ripple/resource/impl/ResourceManager.cpp @@ -18,8 +18,8 @@ //============================================================================== #include +#include #include -#include #include #include #include @@ -149,7 +149,7 @@ class ManagerImp : public Manager void run() { - beast::setCurrentThreadName("Resource::Manager"); + this_thread::set_name("Resrc::Manager"); for (;;) { logic_.periodicActivity(); diff --git a/src/ripple/rpc/handlers/AMMInfo.cpp b/src/ripple/rpc/handlers/AMMInfo.cpp index bcac9da171b..11e124afb44 100644 --- a/src/ripple/rpc/handlers/AMMInfo.cpp +++ b/src/ripple/rpc/handlers/AMMInfo.cpp @@ -74,51 +74,96 @@ doAMMInfo(RPC::JsonContext& context) { auto const& params(context.params); Json::Value result; - std::optional accountID; - - Issue issue1; - Issue issue2; - - if (!params.isMember(jss::asset) || !params.isMember(jss::asset2)) - { - RPC::inject_error(rpcINVALID_PARAMS, result); - return result; - } - - if (auto const i = getIssue(params[jss::asset], context.j); !i) - { - RPC::inject_error(i.error(), result); - return result; - } - else - issue1 = *i; - if (auto const i = getIssue(params[jss::asset2], context.j); !i) - { - RPC::inject_error(i.error(), result); - return result; - } - else - issue2 = *i; std::shared_ptr ledger; result = RPC::lookupLedger(ledger, context); if (!ledger) return result; - if (params.isMember(jss::account)) + struct ValuesFromContextParams { - accountID = getAccount(params[jss::account], result); - if (!accountID || !ledger->read(keylet::account(*accountID))) + std::optional accountID; + Issue issue1; + Issue issue2; + std::shared_ptr amm; + }; + + auto getValuesFromContextParams = + [&]() -> Expected { + std::optional accountID; + std::optional issue1; + std::optional issue2; + std::optional ammID; + + if ((params.isMember(jss::asset) != params.isMember(jss::asset2)) || + (params.isMember(jss::asset) == params.isMember(jss::amm_account))) + return Unexpected(rpcINVALID_PARAMS); + + if (params.isMember(jss::asset)) + { + if (auto const i = getIssue(params[jss::asset], context.j)) + issue1 = *i; + else + return Unexpected(i.error()); + } + + if (params.isMember(jss::asset2)) { - RPC::inject_error(rpcACT_MALFORMED, result); - return result; + if (auto const i = getIssue(params[jss::asset2], context.j)) + issue2 = *i; + else + return Unexpected(i.error()); } + + if (params.isMember(jss::amm_account)) + { + auto const id = getAccount(params[jss::amm_account], result); + if (!id) + return Unexpected(rpcACT_MALFORMED); + auto const sle = ledger->read(keylet::account(*id)); + if (!sle) + return Unexpected(rpcACT_MALFORMED); + ammID = sle->getFieldH256(sfAMMID); + } + + assert( + (issue1.has_value() == issue2.has_value()) && + (issue1.has_value() != ammID.has_value())); + + if (params.isMember(jss::account)) + { + accountID = getAccount(params[jss::account], result); + if (!accountID || !ledger->read(keylet::account(*accountID))) + return Unexpected(rpcACT_MALFORMED); + } + + auto const ammKeylet = [&]() { + if (issue1 && issue2) + return keylet::amm(*issue1, *issue2); + assert(ammID); + return keylet::amm(*ammID); + }(); + auto const amm = ledger->read(ammKeylet); + if (!amm) + return Unexpected(rpcACT_NOT_FOUND); + if (!issue1 && !issue2) + { + issue1 = (*amm)[sfAsset]; + issue2 = (*amm)[sfAsset2]; + } + + return ValuesFromContextParams{ + accountID, *issue1, *issue2, std::move(amm)}; + }; + + auto const r = getValuesFromContextParams(); + if (!r) + { + RPC::inject_error(r.error(), result); + return result; } - auto const ammKeylet = keylet::amm(issue1, issue2); - auto const amm = ledger->read(ammKeylet); - if (!amm) - return rpcError(rpcACT_NOT_FOUND); + auto const& [accountID, issue1, issue2, amm] = *r; auto const ammAccountID = amm->getAccountID(sfAccount); diff --git a/src/test/beast/beast_CurrentThreadName_test.cpp b/src/test/basics/ThreadName_test.cpp similarity index 81% rename from src/test/beast/beast_CurrentThreadName_test.cpp rename to src/test/basics/ThreadName_test.cpp index 6e46808f4b2..5cc2c1d609f 100644 --- a/src/test/beast/beast_CurrentThreadName_test.cpp +++ b/src/test/basics/ThreadName_test.cpp @@ -17,15 +17,13 @@ */ //============================================================================== -#include +#include #include -#include -#include namespace ripple { namespace test { -class CurrentThreadName_test : public beast::unit_test::suite +class ThreadName_test : public beast::unit_test::suite { private: static void @@ -34,26 +32,19 @@ class CurrentThreadName_test : public beast::unit_test::suite std::atomic* stop, std::atomic* state) { - // Verify that upon creation a thread has no name. - auto const initialThreadName = beast::getCurrentThreadName(); - // Set the new name. - beast::setCurrentThreadName(myName); + this_thread::set_name(myName); // Indicate to caller that the name is set. *state = 1; - // If there is an initial thread name then we failed. - if (!initialThreadName.empty()) - return; - // Wait until all threads have their names. while (!*stop) ; // Make sure the thread name that we set before is still there // (not overwritten by, for instance, another thread). - if (beast::getCurrentThreadName() == myName) + if (this_thread::get_name() == myName) *state = 2; } @@ -86,7 +77,7 @@ class CurrentThreadName_test : public beast::unit_test::suite } }; -BEAST_DEFINE_TESTSUITE(CurrentThreadName, core, beast); +BEAST_DEFINE_TESTSUITE(ThreadName, basics, ripple); } // namespace test } // namespace ripple diff --git a/src/test/jtx/AMM.h b/src/test/jtx/AMM.h index 3cf06bfe401..c7c6f3b8477 100644 --- a/src/test/jtx/AMM.h +++ b/src/test/jtx/AMM.h @@ -105,7 +105,10 @@ class AMM ammRpcInfo( std::optional const& account = std::nullopt, std::optional const& ledgerIndex = std::nullopt, - std::optional> tokens = std::nullopt) const; + std::optional issue1 = std::nullopt, + std::optional issue2 = std::nullopt, + std::optional const& ammAccount = std::nullopt, + bool ignoreParams = false) const; /** Verify the AMM balances. */ @@ -150,7 +153,8 @@ class AMM STAmount const& asset2, IOUAmount const& balance, std::optional const& account = std::nullopt, - std::optional const& ledger_index = std::nullopt) const; + std::optional const& ledger_index = std::nullopt, + std::optional const& ammAccount = std::nullopt) const; [[nodiscard]] bool ammExists() const; diff --git a/src/test/jtx/impl/AMM.cpp b/src/test/jtx/impl/AMM.cpp index c09d496f439..dee1cb1bf5b 100644 --- a/src/test/jtx/impl/AMM.cpp +++ b/src/test/jtx/impl/AMM.cpp @@ -136,26 +136,36 @@ Json::Value AMM::ammRpcInfo( std::optional const& account, std::optional const& ledgerIndex, - std::optional> tokens) const + std::optional issue1, + std::optional issue2, + std::optional const& ammAccount, + bool ignoreParams) const { Json::Value jv; if (account) jv[jss::account] = to_string(*account); if (ledgerIndex) jv[jss::ledger_index] = *ledgerIndex; - if (tokens) + if (!ignoreParams) { - jv[jss::asset] = - STIssue(sfAsset, tokens->first).getJson(JsonOptions::none); - jv[jss::asset2] = - STIssue(sfAsset2, tokens->second).getJson(JsonOptions::none); - } - else - { - jv[jss::asset] = - STIssue(sfAsset, asset1_.issue()).getJson(JsonOptions::none); - jv[jss::asset2] = - STIssue(sfAsset2, asset2_.issue()).getJson(JsonOptions::none); + if (issue1 || issue2) + { + if (issue1) + jv[jss::asset] = + STIssue(sfAsset, *issue1).getJson(JsonOptions::none); + if (issue2) + jv[jss::asset2] = + STIssue(sfAsset2, *issue2).getJson(JsonOptions::none); + } + else if (!ammAccount) + { + jv[jss::asset] = + STIssue(sfAsset, asset1_.issue()).getJson(JsonOptions::none); + jv[jss::asset2] = + STIssue(sfAsset2, asset2_.issue()).getJson(JsonOptions::none); + } + if (ammAccount) + jv[jss::amm_account] = to_string(*ammAccount); } auto jr = env_.rpc("json", "amm_info", to_string(jv)); if (jr.isObject() && jr.isMember(jss::result) && @@ -292,9 +302,11 @@ AMM::expectAmmRpcInfo( STAmount const& asset2, IOUAmount const& balance, std::optional const& account, - std::optional const& ledger_index) const + std::optional const& ledger_index, + std::optional const& ammAccount) const { - auto const jv = ammRpcInfo(account, ledger_index); + auto const jv = ammRpcInfo( + account, ledger_index, std::nullopt, std::nullopt, ammAccount); return expectAmmInfo(asset1, asset2, balance, jv); } diff --git a/src/test/overlay/short_read_test.cpp b/src/test/overlay/short_read_test.cpp index 434b4100852..cfcf6642d94 100644 --- a/src/test/overlay/short_read_test.cpp +++ b/src/test/overlay/short_read_test.cpp @@ -17,8 +17,8 @@ */ //============================================================================== +#include #include -#include #include #include @@ -630,7 +630,7 @@ class short_read_test : public beast::unit_test::suite short_read_test() : work_(io_context_.get_executor()) , thread_(std::thread([this]() { - beast::setCurrentThreadName("io_context"); + this_thread::set_name("io_context"); this->io_context_.run(); })) , context_(make_SSLContext("")) diff --git a/src/test/rpc/AMMInfo_test.cpp b/src/test/rpc/AMMInfo_test.cpp index 94795f48574..1d9642539a1 100644 --- a/src/test/rpc/AMMInfo_test.cpp +++ b/src/test/rpc/AMMInfo_test.cpp @@ -42,7 +42,7 @@ class AMMInfo_test : public jtx::AMMTestBase Account const gw("gw"); auto const USD = gw["USD"]; auto const jv = - ammAlice.ammRpcInfo({}, {}, {{USD.issue(), USD.issue()}}); + ammAlice.ammRpcInfo({}, {}, USD.issue(), USD.issue()); BEAST_EXPECT(jv[jss::error_message] == "Account not found."); }); @@ -52,6 +52,40 @@ class AMMInfo_test : public jtx::AMMTestBase auto const jv = ammAlice.ammRpcInfo(bogie.id()); BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); }); + + // Invalid parameters + testAMM([&](AMM& ammAlice, Env&) { + std::vector, + std::optional, + std::optional, + bool>> + vals = { + {xrpIssue(), std::nullopt, std::nullopt, false}, + {std::nullopt, USD.issue(), std::nullopt, false}, + {xrpIssue(), std::nullopt, ammAlice.ammAccount(), false}, + {std::nullopt, USD.issue(), ammAlice.ammAccount(), false}, + {xrpIssue(), USD.issue(), ammAlice.ammAccount(), false}, + {std::nullopt, std::nullopt, std::nullopt, true}}; + for (auto const& [iss1, iss2, acct, ignoreParams] : vals) + { + auto const jv = ammAlice.ammRpcInfo( + std::nullopt, std::nullopt, iss1, iss2, acct, ignoreParams); + BEAST_EXPECT(jv[jss::error_message] == "Invalid parameters."); + } + }); + + // Invalid AMM account id + testAMM([&](AMM& ammAlice, Env&) { + Account bogie("bogie"); + auto const jv = ammAlice.ammRpcInfo( + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + bogie.id()); + BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); + }); } void @@ -63,6 +97,13 @@ class AMMInfo_test : public jtx::AMMTestBase testAMM([&](AMM& ammAlice, Env&) { BEAST_EXPECT(ammAlice.expectAmmRpcInfo( XRP(10000), USD(10000), IOUAmount{10000000, 0})); + BEAST_EXPECT(ammAlice.expectAmmRpcInfo( + XRP(10000), + USD(10000), + IOUAmount{10000000, 0}, + std::nullopt, + std::nullopt, + ammAlice.ammAccount())); }); } @@ -91,53 +132,71 @@ class AMMInfo_test : public jtx::AMMTestBase env.fund(XRP(1000), bob, ed, bill); ammAlice.bid(alice, 100, std::nullopt, {carol, bob, ed, bill}); BEAST_EXPECT(ammAlice.expectAmmRpcInfo( - XRP(80000), USD(80000), IOUAmount{79994400})); - std::unordered_set authAccounts = { - carol.human(), bob.human(), ed.human(), bill.human()}; - auto const ammInfo = ammAlice.ammRpcInfo(); - auto const& amm = ammInfo[jss::amm]; - try + XRP(80000), + USD(80000), + IOUAmount{79994400}, + std::nullopt, + std::nullopt, + ammAlice.ammAccount())); + for (auto i = 0; i < 2; ++i) { - // votes - auto const voteSlots = amm[jss::vote_slots]; - for (std::uint8_t i = 0; i < 8; ++i) + std::unordered_set authAccounts = { + carol.human(), bob.human(), ed.human(), bill.human()}; + auto const ammInfo = i ? ammAlice.ammRpcInfo() + : ammAlice.ammRpcInfo( + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + ammAlice.ammAccount()); + auto const& amm = ammInfo[jss::amm]; + try { - if (!BEAST_EXPECT( - votes[voteSlots[i][jss::account].asString()] == - voteSlots[i][jss::trading_fee].asUInt() && - voteSlots[i][jss::vote_weight].asUInt() == 12500)) + // votes + auto const voteSlots = amm[jss::vote_slots]; + auto votesCopy = votes; + for (std::uint8_t i = 0; i < 8; ++i) + { + if (!BEAST_EXPECT( + votes[voteSlots[i][jss::account].asString()] == + voteSlots[i][jss::trading_fee].asUInt() && + voteSlots[i][jss::vote_weight].asUInt() == + 12500)) + return; + votes.erase(voteSlots[i][jss::account].asString()); + } + if (!BEAST_EXPECT(votes.empty())) return; - votes.erase(voteSlots[i][jss::account].asString()); - } - if (!BEAST_EXPECT(votes.empty())) - return; - - // bid - auto const auctionSlot = amm[jss::auction_slot]; - for (std::uint8_t i = 0; i < 4; ++i) - { - if (!BEAST_EXPECT(authAccounts.contains( + votes = votesCopy; + + // bid + auto const auctionSlot = amm[jss::auction_slot]; + for (std::uint8_t i = 0; i < 4; ++i) + { + if (!BEAST_EXPECT(authAccounts.contains( + auctionSlot[jss::auth_accounts][i][jss::account] + .asString()))) + return; + authAccounts.erase( auctionSlot[jss::auth_accounts][i][jss::account] - .asString()))) + .asString()); + } + if (!BEAST_EXPECT(authAccounts.empty())) return; - authAccounts.erase( - auctionSlot[jss::auth_accounts][i][jss::account] - .asString()); + BEAST_EXPECT( + auctionSlot[jss::account].asString() == alice.human() && + auctionSlot[jss::discounted_fee].asUInt() == 17 && + auctionSlot[jss::price][jss::value].asString() == + "5600" && + auctionSlot[jss::price][jss::currency].asString() == + to_string(ammAlice.lptIssue().currency) && + auctionSlot[jss::price][jss::issuer].asString() == + to_string(ammAlice.lptIssue().account)); + } + catch (std::exception const& e) + { + fail(e.what(), __FILE__, __LINE__); } - if (!BEAST_EXPECT(authAccounts.empty())) - return; - BEAST_EXPECT( - auctionSlot[jss::account].asString() == alice.human() && - auctionSlot[jss::discounted_fee].asUInt() == 17 && - auctionSlot[jss::price][jss::value].asString() == "5600" && - auctionSlot[jss::price][jss::currency].asString() == - to_string(ammAlice.lptIssue().currency) && - auctionSlot[jss::price][jss::issuer].asString() == - to_string(ammAlice.lptIssue().account)); - } - catch (std::exception const& e) - { - fail(e.what(), __FILE__, __LINE__); } }); } diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 7de5b73671e..90d4e54684f 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -552,10 +553,19 @@ class AccountObjects_test : public beast::unit_test::suite Env env(*this); // Make a lambda we can use to get "account_objects" easily. - auto acct_objs = [&env](Account const& acct, char const* type) { + auto acct_objs = [&env]( + AccountID const& acct, + std::optional const& type, + std::optional limit = std::nullopt, + std::optional marker = std::nullopt) { Json::Value params; - params[jss::account] = acct.human(); - params[jss::type] = type; + params[jss::account] = to_string(acct); + if (type) + params[jss::type] = *type; + if (limit) + params[jss::limit] = *limit; + if (marker) + params[jss::marker] = *marker; params[jss::ledger_index] = "validated"; return env.rpc("json", "account_objects", to_string(params)); }; @@ -585,6 +595,7 @@ class AccountObjects_test : public beast::unit_test::suite BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::signer_list), 0)); BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::state), 0)); BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::ticket), 0)); + BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::amm), 0)); // gw mints an NFT so we can find it. uint256 const nftID{token::getNextID(env, gw, 0u, tfTransferable)}; @@ -782,6 +793,67 @@ class AccountObjects_test : public beast::unit_test::suite BEAST_EXPECT(aobjs[0u]["LedgerEntryType"] == jss::Escrow); } } + { + // Make a lambda to get the types + auto getTypes = [&](Json::Value const& resp, + std::vector& typesOut) { + auto const objs = resp[jss::result][jss::account_objects]; + for (auto const& obj : resp[jss::result][jss::account_objects]) + typesOut.push_back( + obj[sfLedgerEntryType.fieldName].asString()); + std::sort(typesOut.begin(), typesOut.end()); + }; + // Make a lambda we can use to check the number of fetched + // account objects and their ledger type + auto expectObjects = + [&](Json::Value const& resp, + std::vector const& types) -> bool { + if (!acct_objs_is_size(resp, types.size())) + return false; + std::vector typesOut; + getTypes(resp, typesOut); + return types == typesOut; + }; + // Find AMM objects + AMM amm(env, gw, XRP(1'000), USD(1'000)); + amm.deposit(alice, USD(1)); + // AMM account has 4 objects: AMM object and 3 trustlines + auto const lines = getAccountLines(env, amm.ammAccount()); + BEAST_EXPECT(lines[jss::lines].size() == 3); + // request AMM only, doesn't depend on the limit + BEAST_EXPECT( + acct_objs_is_size(acct_objs(amm.ammAccount(), jss::amm), 1)); + // request first two objects + auto resp = acct_objs(amm.ammAccount(), std::nullopt, 2); + std::vector typesOut; + getTypes(resp, typesOut); + // request next two objects + resp = acct_objs( + amm.ammAccount(), + std::nullopt, + 10, + resp[jss::result][jss::marker].asString()); + getTypes(resp, typesOut); + BEAST_EXPECT( + (typesOut == + std::vector{ + jss::AMM.c_str(), + jss::RippleState.c_str(), + jss::RippleState.c_str(), + jss::RippleState.c_str()})); + // filter by state: there are three trustlines + resp = acct_objs(amm.ammAccount(), jss::state, 10); + BEAST_EXPECT(expectObjects( + resp, + {jss::RippleState.c_str(), + jss::RippleState.c_str(), + jss::RippleState.c_str()})); + // AMM account doesn't own offers + BEAST_EXPECT( + acct_objs_is_size(acct_objs(amm.ammAccount(), jss::offer), 0)); + // gw account doesn't own AMM object + BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::amm), 0)); + } // Run up the number of directory entries so gw has two // directory nodes.