From e7ce3909d2dc2d44c34366119c2f7cc9569bf64a Mon Sep 17 00:00:00 2001 From: CJ Cobb Date: Tue, 4 Feb 2020 12:31:17 -0800 Subject: [PATCH] gRPC support for account_tx and tx - Add support for all transaction types and ledger object types to gRPC implementation of tx and account_tx. - Create common handlers for tx and account_tx. - Remove mutex and abort() from gRPC server. JobQueue is stopped before gRPC server, with all coroutines executed to completion, so no need for synchronization. --- Builds/CMake/RippledCore.cmake | 7 + Builds/CMake/deps/gRPC.cmake | 2 +- src/ripple/app/main/GRPCServer.cpp | 129 +- src/ripple/app/main/GRPCServer.h | 33 +- src/ripple/app/misc/NetworkOPs.cpp | 114 +- src/ripple/app/misc/NetworkOPs.h | 31 +- src/ripple/app/misc/impl/AccountTxPaging.cpp | 148 +- src/ripple/app/misc/impl/AccountTxPaging.h | 20 +- .../proto/org/xrpl/rpc/v1/account.proto | 14 + .../proto/{ => org/xrpl}/rpc/v1/amount.proto | 36 +- src/ripple/proto/org/xrpl/rpc/v1/common.proto | 476 ++++ .../xrpl/rpc/v1/get_account_info.proto} | 51 +- .../v1/get_account_transaction_history.proto | 68 + .../proto/org/xrpl/rpc/v1/get_fee.proto | 57 + .../org/xrpl/rpc/v1/get_transaction.proto | 56 + src/ripple/proto/org/xrpl/rpc/v1/ledger.proto | 37 + .../org/xrpl/rpc/v1/ledger_objects.proto | 331 +++ .../proto/{ => org/xrpl}/rpc/v1/meta.proto | 52 +- .../proto/{ => org/xrpl}/rpc/v1/submit.proto | 17 +- .../proto/org/xrpl/rpc/v1/transaction.proto | 319 +++ .../proto/org/xrpl/rpc/v1/xrp_ledger.proto | 30 + src/ripple/proto/rpc/v1/fee.proto | 51 - src/ripple/proto/rpc/v1/ledger_objects.proto | 175 -- src/ripple/proto/rpc/v1/transaction.proto | 96 - src/ripple/proto/rpc/v1/tx.proto | 40 - src/ripple/proto/rpc/v1/xrp_ledger.proto | 25 - src/ripple/rpc/DeliveredAmount.h | 25 +- src/ripple/rpc/GRPCHandlers.h | 22 +- src/ripple/rpc/Status.h | 2 +- src/ripple/rpc/handlers/AccountInfo.cpp | 19 +- src/ripple/rpc/handlers/AccountTx.cpp | 693 ++++-- src/ripple/rpc/handlers/AccountTxOld.cpp | 4 +- src/ripple/rpc/handlers/AccountTxSwitch.cpp | 4 +- src/ripple/rpc/handlers/Fee1.cpp | 18 +- src/ripple/rpc/handlers/Handlers.h | 4 +- src/ripple/rpc/handlers/Submit.cpp | 10 +- src/ripple/rpc/handlers/Tx.cpp | 470 ++-- src/ripple/rpc/impl/DeliveredAmount.cpp | 284 ++- src/ripple/rpc/impl/GRPCHelpers.cpp | 1779 +++++++++++++++ src/ripple/rpc/impl/GRPCHelpers.h | 87 + src/ripple/rpc/impl/Handler.cpp | 4 +- src/ripple/rpc/impl/RPCHelpers.cpp | 927 ++------ src/ripple/rpc/impl/RPCHelpers.h | 76 +- src/test/app/AccountTxPaging_test.cpp | 1908 +++++++++++++++++ src/test/app/Check_test.cpp | 19 - src/test/jtx.h | 3 + src/test/jtx/account_txn_id.h | 42 + src/test/jtx/impl/account_txn_id.cpp | 35 + src/test/jtx/impl/invoice_id.cpp | 36 + src/test/jtx/impl/last_ledger_sequence.cpp | 37 + src/test/jtx/invoice_id.h | 42 + src/test/jtx/last_ledger_sequence.h | 44 + src/test/rpc/AccountInfo_test.cpp | 23 +- src/test/rpc/Fee_test.cpp | 24 +- src/test/rpc/GRPCTestClientBase.h | 6 +- src/test/rpc/Submit_test.cpp | 4 +- src/test/rpc/Tx_test.cpp | 538 ++++- 57 files changed, 7498 insertions(+), 2106 deletions(-) create mode 100644 src/ripple/proto/org/xrpl/rpc/v1/account.proto rename src/ripple/proto/{ => org/xrpl}/rpc/v1/amount.proto (60%) create mode 100644 src/ripple/proto/org/xrpl/rpc/v1/common.proto rename src/ripple/proto/{rpc/v1/account_info.proto => org/xrpl/rpc/v1/get_account_info.proto} (58%) create mode 100644 src/ripple/proto/org/xrpl/rpc/v1/get_account_transaction_history.proto create mode 100644 src/ripple/proto/org/xrpl/rpc/v1/get_fee.proto create mode 100644 src/ripple/proto/org/xrpl/rpc/v1/get_transaction.proto create mode 100644 src/ripple/proto/org/xrpl/rpc/v1/ledger.proto create mode 100644 src/ripple/proto/org/xrpl/rpc/v1/ledger_objects.proto rename src/ripple/proto/{ => org/xrpl}/rpc/v1/meta.proto (62%) rename src/ripple/proto/{ => org/xrpl}/rpc/v1/submit.proto (67%) create mode 100644 src/ripple/proto/org/xrpl/rpc/v1/transaction.proto create mode 100644 src/ripple/proto/org/xrpl/rpc/v1/xrp_ledger.proto delete mode 100644 src/ripple/proto/rpc/v1/fee.proto delete mode 100644 src/ripple/proto/rpc/v1/ledger_objects.proto delete mode 100644 src/ripple/proto/rpc/v1/transaction.proto delete mode 100644 src/ripple/proto/rpc/v1/tx.proto delete mode 100644 src/ripple/proto/rpc/v1/xrp_ledger.proto create mode 100644 src/ripple/rpc/impl/GRPCHelpers.cpp create mode 100644 src/ripple/rpc/impl/GRPCHelpers.h create mode 100644 src/test/jtx/account_txn_id.h create mode 100644 src/test/jtx/impl/account_txn_id.cpp create mode 100644 src/test/jtx/impl/invoice_id.cpp create mode 100644 src/test/jtx/impl/last_ledger_sequence.cpp create mode 100644 src/test/jtx/invoice_id.h create mode 100644 src/test/jtx/last_ledger_sequence.h diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 1831a2da2d..6e7feb69af 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -3,6 +3,7 @@ core functionality, useable by some client software perhaps #]===================================================================] + file (GLOB_RECURSE rb_headers src/ripple/beast/*.h src/ripple/beast/*.hpp) @@ -613,6 +614,7 @@ target_sources (rippled PRIVATE src/ripple/rpc/handlers/WalletPropose.cpp src/ripple/rpc/impl/DeliveredAmount.cpp src/ripple/rpc/impl/Handler.cpp + src/ripple/rpc/impl/GRPCHelpers.cpp src/ripple/rpc/impl/LegacyPathFind.cpp src/ripple/rpc/impl/RPCHandler.cpp src/ripple/rpc/impl/RPCHelpers.cpp @@ -621,6 +623,7 @@ target_sources (rippled PRIVATE src/ripple/rpc/impl/ShardArchiveHandler.cpp src/ripple/rpc/impl/Status.cpp src/ripple/rpc/impl/TransactionSign.cpp + #[===============================[ main sources: subdir: server @@ -786,6 +789,7 @@ target_sources (rippled PRIVATE src/test/jtx/impl/ManualTimeKeeper.cpp src/test/jtx/impl/WSClient.cpp src/test/jtx/impl/acctdelete.cpp + src/test/jtx/impl/account_txn_id.cpp src/test/jtx/impl/amount.cpp src/test/jtx/impl/balance.cpp src/test/jtx/impl/check.cpp @@ -794,7 +798,9 @@ target_sources (rippled PRIVATE src/test/jtx/impl/envconfig.cpp src/test/jtx/impl/fee.cpp src/test/jtx/impl/flags.cpp + src/test/jtx/impl/invoice_id.cpp src/test/jtx/impl/jtx_json.cpp + src/test/jtx/impl/last_ledger_sequence.cpp src/test/jtx/impl/memo.cpp src/test/jtx/impl/multisign.cpp src/test/jtx/impl/offer.cpp @@ -812,6 +818,7 @@ target_sources (rippled PRIVATE src/test/jtx/impl/trust.cpp src/test/jtx/impl/txflags.cpp src/test/jtx/impl/utility.cpp + #[===============================[ test sources: subdir: ledger diff --git a/Builds/CMake/deps/gRPC.cmake b/Builds/CMake/deps/gRPC.cmake index 0092113ead..ac2f702030 100644 --- a/Builds/CMake/deps/gRPC.cmake +++ b/Builds/CMake/deps/gRPC.cmake @@ -309,7 +309,7 @@ set (GRPC_GEN_DIR "${CMAKE_BINARY_DIR}/proto_gen_grpc") file (MAKE_DIRECTORY ${GRPC_GEN_DIR}) set (GRPC_PROTO_SRCS) set (GRPC_PROTO_HDRS) -set (GRPC_PROTO_ROOT "${CMAKE_SOURCE_DIR}/src/ripple/proto/rpc") +set (GRPC_PROTO_ROOT "${CMAKE_SOURCE_DIR}/src/ripple/proto/org") file(GLOB_RECURSE GRPC_DEFINITION_FILES LIST_DIRECTORIES false "${GRPC_PROTO_ROOT}/*.proto") foreach(file ${GRPC_DEFINITION_FILES}) get_filename_component(_abs_file ${file} ABSOLUTE) diff --git a/src/ripple/app/main/GRPCServer.cpp b/src/ripple/app/main/GRPCServer.cpp index dfd6f55296..4baae0890a 100644 --- a/src/ripple/app/main/GRPCServer.cpp +++ b/src/ripple/app/main/GRPCServer.cpp @@ -41,7 +41,7 @@ getEndpoint(std::string const& peer) template GRPCServerImpl::CallData::CallData( - rpc::v1::XRPLedgerAPIService::AsyncService& service, + org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService& service, grpc::ServerCompletionQueue& cq, Application& app, BindListener bindListener, @@ -52,7 +52,6 @@ GRPCServerImpl::CallData::CallData( , cq_(cq) , finished_(false) , app_(app) - , aborted_(false) , responder_(&ctx_) , bindListener_(std::move(bindListener)) , handler_(std::move(handler)) @@ -87,20 +86,31 @@ GRPCServerImpl::CallData::process() std::shared_ptr> thisShared = this->shared_from_this(); - app_.getJobQueue().postCoro( + + // Need to set finished to true before processing the response, + // because as soon as the response is posted to the completion + // queue (via responder_.Finish(...) or responder_.FinishWithError(...)), + // the CallData object is returned as a tag in handleRpcs(). + // handleRpcs() checks the finished variable, and if true, destroys + // the object. Setting finished to true before calling process + // ensures that finished is always true when this CallData object + // is returned as a tag in handleRpcs(), after sending the response + finished_ = true; + auto coro = app_.getJobQueue().postCoro( JobType::jtRPC, "gRPC-Client", [thisShared](std::shared_ptr coro) { - std::lock_guard lock{thisShared->mut_}; - - // Do nothing if call has been aborted due to server shutdown - // or if handler was already executed - if (thisShared->aborted_ || thisShared->finished_) - return; thisShared->process(coro); - thisShared->finished_ = true; }); + + // If coro is null, then the JobQueue has already been shutdown + if (!coro) + { + grpc::Status status{grpc::StatusCode::INTERNAL, + "Job Queue is already stopped"}; + responder_.FinishWithError(status, this); + } } template @@ -141,7 +151,7 @@ GRPCServerImpl::CallData::process( if (conditionMetRes != rpcSUCCESS) { RPC::ErrorInfo errorInfo = RPC::get_error_info(conditionMetRes); - grpc::Status status{grpc::StatusCode::INTERNAL, + grpc::Status status{grpc::StatusCode::FAILED_PRECONDITION, errorInfo.message.c_str()}; responder_.FinishWithError(status, this); } @@ -163,21 +173,9 @@ template bool GRPCServerImpl::CallData::isFinished() { - // Need to lock here because this object can be returned from cq_.Next(..) - // as soon as the response is sent, which could be before finished_ is set - // to true, causing the handler to be executed twice - std::lock_guard lock{mut_}; return finished_; } -template -void -GRPCServerImpl::CallData::abort() -{ - std::lock_guard lock{mut_}; - aborted_ = true; -} - template Resource::Charge GRPCServerImpl::CallData::getLoadType() @@ -202,7 +200,8 @@ GRPCServerImpl::CallData::getUsage() return app_.getResourceManager().newInboundEndpoint(endpoint.get()); } -GRPCServerImpl::GRPCServerImpl(Application& app) : app_(app) +GRPCServerImpl::GRPCServerImpl(Application& app) + : app_(app), journal_(app_.journal("gRPC Server")) { // if present, get endpoint from config if (app_.config().exists("port_grpc")) @@ -233,9 +232,24 @@ GRPCServerImpl::GRPCServerImpl(Application& app) : app_(app) void GRPCServerImpl::shutdown() { + JLOG(journal_.debug()) << "Shutting down"; + + //The below call cancels all "listeners" (CallData objects that are waiting + //for a request, as opposed to processing a request), and blocks until all + //requests being processed are completed. CallData objects in the midst of + //processing requests need to actually send data back to the client, via + //responder_.Finish(...) or responder_.FinishWithError(...), for this call + //to unblock. Each cancelled listener is returned via cq_.Next(...) with ok + //set to false server_->Shutdown(); - // Always shutdown the completion queue after the server. + JLOG(journal_.debug()) << "Server has been shutdown"; + + // Always shutdown the completion queue after the server. This call allows + // cq_.Next() to return false, once all events posted to the completion + // queue have been processed. See handleRpcs() for more details. cq_->Shutdown(); + JLOG(journal_.debug()) << "Completion Queue has been shutdown"; + } void @@ -265,21 +279,33 @@ GRPCServerImpl::handleRpcs() // memory address of a CallData instance. // The return value of Next should always be checked. This return value // tells us whether there is any kind of event or cq_ is shutting down. + // When cq_.Next(...) returns false, all work has been completed and the + // loop can exit. When the server is shutdown, each CallData object that is + // listening for a request is forceably cancelled, and is returned by + // cq_->Next() with ok set to false. Then, each CallData object processing + // a request must complete (by sending data to the client), each of which + // will be returned from cq_->Next() with ok set to true. After all + // cancelled listeners and all CallData objects processing requests are + // returned via cq_->Next(), cq_->Next() will return false, causing the + // loop to exit. while (cq_->Next(&tag, &ok)) { auto ptr = static_cast(tag); - // if ok is false, event was terminated as part of a shutdown sequence - // need to abort any further processing + JLOG(journal_.trace()) << "Processing CallData object." + << " ptr = " << ptr + << " ok = " << ok; + if (!ok) { - // abort first, then erase. Otherwise, erase can delete object - ptr->abort(); + JLOG(journal_.debug()) << "Request listener cancelled. " + << "Destroying object"; erase(ptr); } else { if (!ptr->isFinished()) { + JLOG(journal_.debug()) << "Received new request. Processing"; // ptr is now processing a request, so create a new CallData // object to handle additional requests auto cloned = ptr->clone(); @@ -289,10 +315,13 @@ GRPCServerImpl::handleRpcs() } else { + + JLOG(journal_.debug()) << "Sent response. Destroying object"; erase(ptr); } } } + JLOG(journal_.debug()) << "Completion Queue drained"; } // create a CallData instance for each RPC @@ -306,58 +335,76 @@ GRPCServerImpl::setupListeners() }; { - using cd = CallData; + using cd = CallData; addToRequests(std::make_shared( service_, *cq_, app_, - &rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetFee, + &org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetFee, doFeeGrpc, RPC::NEEDS_CURRENT_LEDGER, Resource::feeReferenceRPC)); } { using cd = CallData< - rpc::v1::GetAccountInfoRequest, - rpc::v1::GetAccountInfoResponse>; + org::xrpl::rpc::v1::GetAccountInfoRequest, + org::xrpl::rpc::v1::GetAccountInfoResponse>; addToRequests(std::make_shared( service_, *cq_, app_, - &rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetAccountInfo, + &org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetAccountInfo, doAccountInfoGrpc, - RPC::NEEDS_CURRENT_LEDGER, + RPC::NO_CONDITION, Resource::feeReferenceRPC)); } { - using cd = CallData; + using cd = CallData< + org::xrpl::rpc::v1::GetTransactionRequest, + org::xrpl::rpc::v1::GetTransactionResponse>; addToRequests(std::make_shared( service_, *cq_, app_, - &rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetTx, + &org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetTransaction, doTxGrpc, RPC::NEEDS_CURRENT_LEDGER, Resource::feeReferenceRPC)); } { using cd = CallData< - rpc::v1::SubmitTransactionRequest, - rpc::v1::SubmitTransactionResponse>; + org::xrpl::rpc::v1::SubmitTransactionRequest, + org::xrpl::rpc::v1::SubmitTransactionResponse>; addToRequests(std::make_shared( service_, *cq_, app_, - &rpc::v1::XRPLedgerAPIService::AsyncService:: + &org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService:: RequestSubmitTransaction, doSubmitGrpc, RPC::NEEDS_CURRENT_LEDGER, Resource::feeMediumBurdenRPC)); } + + { + using cd = CallData< + org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest, + org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse>; + + addToRequests(std::make_shared( + service_, + *cq_, + app_, + &org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService:: + RequestGetAccountTransactionHistory, + doAccountTxGrpc, + RPC::NO_CONDITION, + Resource::feeMediumBurdenRPC)); + } return requests; }; @@ -368,6 +415,8 @@ GRPCServerImpl::start() if (serverAddress_.empty()) return false; + JLOG(journal_.info()) << "Starting gRPC server at " << serverAddress_; + grpc::ServerBuilder builder; // Listen on the given address without any authentication mechanism. builder.AddListeningPort(serverAddress_, grpc::InsecureServerCredentials()); diff --git a/src/ripple/app/main/GRPCServer.h b/src/ripple/app/main/GRPCServer.h index 3326fab61a..5175e2e256 100644 --- a/src/ripple/app/main/GRPCServer.h +++ b/src/ripple/app/main/GRPCServer.h @@ -32,7 +32,7 @@ #include #include -#include "rpc/v1/xrp_ledger.grpc.pb.h" +#include "org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h" #include namespace ripple { @@ -54,10 +54,6 @@ public: virtual void process() = 0; - // abort processing this request. called when server shutsdown - virtual void - abort() = 0; - // create a new instance of this CallData object, with the same type //(same template parameters) as original. This is called when a CallData // object starts processing a request. Creating a new instance allows the @@ -81,7 +77,7 @@ private: std::vector> requests_; // The gRPC service defined by the .proto files - rpc::v1::XRPLedgerAPIService::AsyncService service_; + org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService service_; std::unique_ptr server_; @@ -89,12 +85,14 @@ private: std::string serverAddress_; + beast::Journal journal_; + // typedef for function to bind a listener // This is always of the form: - // rpc::v1::XRPLedgerAPIService::AsyncService::Request[RPC NAME] + // org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::Request[RPC NAME] template using BindListener = std::function*, @@ -142,7 +140,7 @@ private: private: // The means of communication with the gRPC runtime for an asynchronous // server. - rpc::v1::XRPLedgerAPIService::AsyncService& service_; + org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService& service_; // The producer-consumer queue for asynchronous server notifications. grpc::ServerCompletionQueue& cq_; @@ -153,16 +151,14 @@ private: grpc::ServerContext ctx_; // true if finished processing request - bool finished_; + // Note, this variable does not need to be atomic, since it is + // currently only accessed from one thread. However, isFinished(), + // which returns the value of this variable, is public facing. In the + // interest of avoiding future concurrency bugs, we make it atomic. + std::atomic_bool finished_; Application& app_; - // mutex for signaling abort - std::mutex mut_; - - // whether the call should be aborted, due to server shutdown - bool aborted_; - // What we get from the client. Request request_; @@ -191,7 +187,7 @@ private: // asynchronous server) and the completion queue "cq" used for // asynchronous communication with the gRPC runtime. explicit CallData( - rpc::v1::XRPLedgerAPIService::AsyncService& service, + org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService& service, grpc::ServerCompletionQueue& cq, Application& app, BindListener bindListener, @@ -210,9 +206,6 @@ private: virtual bool isFinished() override; - virtual void - abort() override; - std::shared_ptr clone() override; diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 25d4b5f914..28302d83c4 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -434,15 +434,21 @@ public: bool binary, bool count, bool bUnlimited); // Client information retrieval functions. + using NetworkOPs::AccountTxMarker; using NetworkOPs::AccountTxs; AccountTxs getAccountTxs ( AccountID const& account, std::int32_t minLedger, std::int32_t maxLedger, bool descending, std::uint32_t offset, int limit, bool bUnlimited) override; - AccountTxs getTxsAccount ( - AccountID const& account, std::int32_t minLedger, - std::int32_t maxLedger, bool forward, Json::Value& token, int limit, + AccountTxs + getTxsAccount( + AccountID const& account, + std::int32_t minLedger, + std::int32_t maxLedger, + bool forward, + std::optional& marker, + int limit, bool bUnlimited) override; using NetworkOPs::txnMetaLedgerType; @@ -454,11 +460,16 @@ public: std::int32_t maxLedger, bool descending, std::uint32_t offset, int limit, bool bUnlimited) override; + MetaTxsList - getTxsAccountB ( - AccountID const& account, std::int32_t minLedger, - std::int32_t maxLedger, bool forward, Json::Value& token, - int limit, bool bUnlimited) override; + getTxsAccountB( + AccountID const& account, + std::int32_t minLedger, + std::int32_t maxLedger, + bool forward, + std::optional& marker, + int limit, + bool bUnlimited) override; // // Monitoring: publisher side. @@ -2193,7 +2204,7 @@ std::vector NetworkOPsImp::getAccountTxsB ( rangeCheckedCast(ledgerSeq.value_or (0)); ret.emplace_back ( - strHex (rawTxn), strHex (txnMeta), seq); + std::move(rawTxn), std::move(txnMeta), seq); } } @@ -2201,59 +2212,80 @@ std::vector NetworkOPsImp::getAccountTxsB ( } NetworkOPsImp::AccountTxs -NetworkOPsImp::getTxsAccount ( - AccountID const& account, std::int32_t minLedger, - std::int32_t maxLedger, bool forward, Json::Value& token, - int limit, bool bUnlimited) +NetworkOPsImp::getTxsAccount( + AccountID const& account, + std::int32_t minLedger, + std::int32_t maxLedger, + bool forward, + std::optional& marker, + int limit, + bool bUnlimited) { - static std::uint32_t const page_length (200); + static std::uint32_t const page_length(200); Application& app = app_; NetworkOPsImp::AccountTxs ret; auto bound = [&ret, &app]( - std::uint32_t ledger_index, - std::string const& status, - Blob const& rawTxn, - Blob const& rawMeta) - { - convertBlobsToTxResult ( - ret, ledger_index, status, rawTxn, rawMeta, app); + std::uint32_t ledger_index, + std::string const& status, + Blob const& rawTxn, + Blob const& rawMeta) { + convertBlobsToTxResult(ret, ledger_index, status, rawTxn, rawMeta, app); }; - accountTxPage(app_.getTxnDB (), app_.accountIDCache(), - std::bind(saveLedgerAsync, std::ref(app_), - std::placeholders::_1), bound, account, minLedger, - maxLedger, forward, token, limit, bUnlimited, - page_length); + accountTxPage( + app_.getTxnDB(), + app_.accountIDCache(), + std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1), + bound, + account, + minLedger, + maxLedger, + forward, + marker, + limit, + bUnlimited, + page_length); return ret; } NetworkOPsImp::MetaTxsList -NetworkOPsImp::getTxsAccountB ( - AccountID const& account, std::int32_t minLedger, - std::int32_t maxLedger, bool forward, Json::Value& token, - int limit, bool bUnlimited) +NetworkOPsImp::getTxsAccountB( + AccountID const& account, + std::int32_t minLedger, + std::int32_t maxLedger, + bool forward, + std::optional& marker, + int limit, + bool bUnlimited) { - static const std::uint32_t page_length (500); + static const std::uint32_t page_length(500); MetaTxsList ret; auto bound = [&ret]( - std::uint32_t ledgerIndex, - std::string const& status, - Blob const& rawTxn, - Blob const& rawMeta) - { - ret.emplace_back (strHex(rawTxn), strHex (rawMeta), ledgerIndex); + std::uint32_t ledgerIndex, + std::string const& status, + Blob const& rawTxn, + Blob const& rawMeta) { + ret.emplace_back(std::move(rawTxn), std::move(rawMeta), ledgerIndex); }; - accountTxPage(app_.getTxnDB (), app_.accountIDCache(), - std::bind(saveLedgerAsync, std::ref(app_), - std::placeholders::_1), bound, account, minLedger, - maxLedger, forward, token, limit, bUnlimited, - page_length); + accountTxPage( + app_.getTxnDB(), + app_.accountIDCache(), + std::bind(saveLedgerAsync, std::ref(app_), std::placeholders::_1), + bound, + account, + minLedger, + maxLedger, + forward, + marker, + limit, + bUnlimited, + page_length); return ret; } diff --git a/src/ripple/app/misc/NetworkOPs.h b/src/ripple/app/misc/NetworkOPs.h index 8561207917..a1562ddc44 100644 --- a/src/ripple/app/misc/NetworkOPs.h +++ b/src/ripple/app/misc/NetworkOPs.h @@ -212,6 +212,12 @@ public: virtual void updateLocalTx (ReadView const& newValidLedger) = 0; virtual std::size_t getLocalTxCount () = 0; + struct AccountTxMarker + { + uint32_t ledgerSeq = 0; + uint32_t txnSeq = 0; + }; + // client information retrieval functions using AccountTx = std::pair, TxMeta::pointer>; using AccountTxs = std::vector; @@ -221,21 +227,32 @@ public: std::int32_t minLedger, std::int32_t maxLedger, bool descending, std::uint32_t offset, int limit, bool bUnlimited) = 0; - virtual AccountTxs getTxsAccount ( + virtual AccountTxs + getTxsAccount( AccountID const& account, - std::int32_t minLedger, std::int32_t maxLedger, bool forward, - Json::Value& token, int limit, bool bUnlimited) = 0; + std::int32_t minLedger, + std::int32_t maxLedger, + bool forward, + std::optional& marker, + int limit, + bool bUnlimited) = 0; - using txnMetaLedgerType = std::tuple; + using txnMetaLedgerType = std::tuple; using MetaTxsList = std::vector; virtual MetaTxsList getAccountTxsB (AccountID const& account, std::int32_t minLedger, std::int32_t maxLedger, bool descending, std::uint32_t offset, int limit, bool bUnlimited) = 0; - virtual MetaTxsList getTxsAccountB (AccountID const& account, - std::int32_t minLedger, std::int32_t maxLedger, bool forward, - Json::Value& token, int limit, bool bUnlimited) = 0; + virtual MetaTxsList + getTxsAccountB( + AccountID const& account, + std::int32_t minLedger, + std::int32_t maxLedger, + bool forward, + std::optional& marker, + int limit, + bool bUnlimited) = 0; //-------------------------------------------------------------------------- // diff --git a/src/ripple/app/misc/impl/AccountTxPaging.cpp b/src/ripple/app/misc/impl/AccountTxPaging.cpp index b62c85b599..85b70641ba 100644 --- a/src/ripple/app/misc/impl/AccountTxPaging.cpp +++ b/src/ripple/app/misc/impl/AccountTxPaging.cpp @@ -61,24 +61,25 @@ saveLedgerAsync (Application& app, std::uint32_t seq) } void -accountTxPage ( +accountTxPage( DatabaseCon& connection, AccountIDCache const& idCache, - std::function const& onUnsavedLedger, - std::function const& onTransaction, + std::function const& onUnsavedLedger, + std::function const& onTransaction, AccountID const& account, std::int32_t minLedger, std::int32_t maxLedger, bool forward, - Json::Value& token, + std::optional& marker, int limit, bool bAdmin, std::uint32_t page_length) { - bool lookingForMarker = token.isObject(); + bool lookingForMarker = marker.has_value(); std::uint32_t numberOfResults; @@ -97,24 +98,14 @@ accountTxPage ( if (lookingForMarker) { - try - { - if (!token.isMember(jss::ledger) || !token.isMember(jss::seq)) - return; - findLedger = token[jss::ledger].asInt(); - findSeq = token[jss::seq].asInt(); - } - catch (std::exception const&) - { - return; - } + findLedger = marker->ledgerSeq; + findSeq = marker->txnSeq; } - // We're using the token reference both for passing inputs and outputs, so - // we need to clear it in between. - token = Json::nullValue; + // marker is also an output parameter, so need to reset + marker.reset(); - static std::string const prefix ( + static std::string const prefix( R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq, Status,RawTxn,TxnMeta FROM AccountTransactions INNER JOIN Transactions @@ -128,22 +119,20 @@ accountTxPage ( if (forward && (findLedger == 0)) { - sql = boost::str (boost::format( - prefix + - (R"(AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u' + sql = boost::str( + boost::format( + prefix + (R"(AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u' ORDER BY AccountTransactions.LedgerSeq ASC, AccountTransactions.TxnSeq ASC - LIMIT %u;)")) - % idCache.toBase58(account) - % minLedger - % maxLedger - % queryLimit); + LIMIT %u;)")) % + idCache.toBase58(account) % minLedger % maxLedger % queryLimit); } else if (forward && (findLedger != 0)) { auto b58acct = idCache.toBase58(account); - sql = boost::str (boost::format( - (R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq, + sql = boost::str( + boost::format(( + R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq, Status,RawTxn,TxnMeta FROM AccountTransactions, Transactions WHERE (AccountTransactions.TransID = Transactions.TransID AND @@ -157,33 +146,26 @@ accountTxPage ( ORDER BY AccountTransactions.LedgerSeq ASC, AccountTransactions.TxnSeq ASC LIMIT %u; - )")) - % b58acct - % (findLedger + 1) - % maxLedger - % b58acct - % findLedger - % findSeq - % queryLimit); + )")) % + b58acct % (findLedger + 1) % maxLedger % b58acct % findLedger % + findSeq % queryLimit); } else if (!forward && (findLedger == 0)) { - sql = boost::str (boost::format( - prefix + - (R"(AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u' + sql = boost::str( + boost::format( + prefix + (R"(AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u' ORDER BY AccountTransactions.LedgerSeq DESC, AccountTransactions.TxnSeq DESC - LIMIT %u;)")) - % idCache.toBase58(account) - % minLedger - % maxLedger - % queryLimit); + LIMIT %u;)")) % + idCache.toBase58(account) % minLedger % maxLedger % queryLimit); } else if (!forward && (findLedger != 0)) { auto b58acct = idCache.toBase58(account); - sql = boost::str (boost::format( - (R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq, + sql = boost::str( + boost::format(( + R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq, Status,RawTxn,TxnMeta FROM AccountTransactions, Transactions WHERE (AccountTransactions.TransID = Transactions.TransID AND @@ -197,24 +179,19 @@ accountTxPage ( ORDER BY AccountTransactions.LedgerSeq DESC, AccountTransactions.TxnSeq DESC LIMIT %u; - )")) - % b58acct - % minLedger - % (findLedger - 1) - % b58acct - % findLedger - % findSeq - % queryLimit); + )")) % + b58acct % minLedger % (findLedger - 1) % b58acct % findLedger % + findSeq % queryLimit); } else { - assert (false); + assert(false); // sql is empty return; } { - auto db (connection.checkoutDb()); + auto db(connection.checkoutDb()); Blob rawData; Blob rawMeta; @@ -222,55 +199,59 @@ accountTxPage ( boost::optional ledgerSeq; boost::optional txnSeq; boost::optional status; - soci::blob txnData (*db); - soci::blob txnMeta (*db); + soci::blob txnData(*db); + soci::blob txnMeta(*db); soci::indicator dataPresent, metaPresent; - soci::statement st = (db->prepare << sql, - soci::into (ledgerSeq), - soci::into (txnSeq), - soci::into (status), - soci::into (txnData, dataPresent), - soci::into (txnMeta, metaPresent)); + soci::statement st = + (db->prepare << sql, + soci::into(ledgerSeq), + soci::into(txnSeq), + soci::into(status), + soci::into(txnData, dataPresent), + soci::into(txnMeta, metaPresent)); - st.execute (); + st.execute(); - while (st.fetch ()) + while (st.fetch()) { if (lookingForMarker) { - if (findLedger == ledgerSeq.value_or (0) && - findSeq == txnSeq.value_or (0)) + if (findLedger == ledgerSeq.value_or(0) && + findSeq == txnSeq.value_or(0)) { lookingForMarker = false; } } else if (numberOfResults == 0) { - token = Json::objectValue; - token[jss::ledger] = rangeCheckedCast(ledgerSeq.value_or (0)); - token[jss::seq] = txnSeq.value_or (0); + marker = { + rangeCheckedCast(ledgerSeq.value_or(0)), + txnSeq.value_or(0)}; break; } if (!lookingForMarker) { if (dataPresent == soci::i_ok) - convert (txnData, rawData); + convert(txnData, rawData); else - rawData.clear (); + rawData.clear(); if (metaPresent == soci::i_ok) - convert (txnMeta, rawMeta); + convert(txnMeta, rawMeta); else - rawMeta.clear (); + rawMeta.clear(); // Work around a bug that could leave the metadata missing if (rawMeta.size() == 0) - onUnsavedLedger(ledgerSeq.value_or (0)); + onUnsavedLedger(ledgerSeq.value_or(0)); - onTransaction(rangeCheckedCast(ledgerSeq.value_or (0)), - *status, rawData, rawMeta); + onTransaction( + rangeCheckedCast(ledgerSeq.value_or(0)), + *status, + rawData, + rawMeta); --numberOfResults; } } @@ -278,5 +259,4 @@ accountTxPage ( return; } - } diff --git a/src/ripple/app/misc/impl/AccountTxPaging.h b/src/ripple/app/misc/impl/AccountTxPaging.h index bd7e772b14..4319adf244 100644 --- a/src/ripple/app/misc/impl/AccountTxPaging.h +++ b/src/ripple/app/misc/impl/AccountTxPaging.h @@ -44,23 +44,23 @@ void saveLedgerAsync (Application& app, std::uint32_t seq); void -accountTxPage ( - DatabaseCon& database, +accountTxPage( + DatabaseCon& connection, AccountIDCache const& idCache, - std::function const& onUnsavedLedger, - std::function const&, + std::function const& onUnsavedLedger, + std::function const& onTransaction, AccountID const& account, std::int32_t minLedger, std::int32_t maxLedger, bool forward, - Json::Value& token, + std::optional& marker, int limit, bool bAdmin, - std::uint32_t pageLength); - + std::uint32_t page_length); } #endif diff --git a/src/ripple/proto/org/xrpl/rpc/v1/account.proto b/src/ripple/proto/org/xrpl/rpc/v1/account.proto new file mode 100644 index 0000000000..40e9a7a4f4 --- /dev/null +++ b/src/ripple/proto/org/xrpl/rpc/v1/account.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; + +// A representation of an account address +// Next field: 2 +message AccountAddress +{ + // base58 encoding of an account + string address = 1; +} + diff --git a/src/ripple/proto/rpc/v1/amount.proto b/src/ripple/proto/org/xrpl/rpc/v1/amount.proto similarity index 60% rename from src/ripple/proto/rpc/v1/amount.proto rename to src/ripple/proto/org/xrpl/rpc/v1/amount.proto index 45e8931df0..64ea2f6416 100644 --- a/src/ripple/proto/rpc/v1/amount.proto +++ b/src/ripple/proto/org/xrpl/rpc/v1/amount.proto @@ -1,29 +1,32 @@ syntax = "proto3"; -package rpc.v1; +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; -message CurrencyAmount { - oneof amount { +import "org/xrpl/rpc/v1/account.proto"; + +// Next field: 3 +message CurrencyAmount +{ + oneof amount + { XRPDropsAmount xrp_amount = 1; IssuedCurrencyAmount issued_currency_amount = 2; } } // A representation of an amount of XRP. -message XRPDropsAmount { - - uint64 drops = 1; -} - -// A representation of an account address -message AccountAddress { - - //base58 encoding of an account - string address = 1; +// Next field: 2 +message XRPDropsAmount +{ + uint64 drops = 1 [jstype=JS_STRING]; } // A representation of an amount of issued currency. -message IssuedCurrencyAmount { +// Next field: 4 +message IssuedCurrencyAmount +{ // The currency used to value the amount. Currency currency = 1; @@ -34,8 +37,9 @@ message IssuedCurrencyAmount { AccountAddress issuer = 3; } -message Currency { - +// Next field: 3 +message Currency +{ // 3 character ASCII code string name = 1; diff --git a/src/ripple/proto/org/xrpl/rpc/v1/common.proto b/src/ripple/proto/org/xrpl/rpc/v1/common.proto new file mode 100644 index 0000000000..3cc3c73ae4 --- /dev/null +++ b/src/ripple/proto/org/xrpl/rpc/v1/common.proto @@ -0,0 +1,476 @@ +syntax = "proto3"; + +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; + +import "org/xrpl/rpc/v1/amount.proto"; +import "org/xrpl/rpc/v1/account.proto"; + +// These fields are used in many different messsage types. They can be present +// in one or more transactions, as well as metadata of one or more transactions. +// Each is defined as its own message type with a single field "value", to +// ensure the field is the correct type everywhere it's used + + +// *** Messages wrapping uint32 *** + +message CancelAfter +{ + // time in seconds since Ripple epoch + uint32 value = 1; +} + +message ClearFlag +{ + uint32 value = 1; +} + +message CloseTime +{ + // time in seconds since Ripple epoch + uint32 value = 1; +} + +message Date +{ + // time in seconds since Ripple epoch + uint32 value = 1; +} + +message DestinationTag +{ + uint32 value = 1; +} + +message Expiration +{ + // time in seconds since Ripple epoch + uint32 value = 1; +} + +message FinishAfter +{ + // time in seconds since Ripple epoch + uint32 value = 1; +} + +message Flags +{ + uint32 value = 1; +} + +message HighQualityIn +{ + uint32 value = 1; +} + +message HighQualityOut +{ + uint32 value = 1; +} + +message LastLedgerSequence +{ + uint32 value = 1; +} + +message LowQualityIn +{ + uint32 value = 1; +} + +message LowQualityOut +{ + uint32 value = 1; +} + +message OfferSequence +{ + uint32 value = 1; +} + +message OwnerCount +{ + uint32 value = 1; +} + +message PreviousTransactionLedgerSequence +{ + uint32 value = 1; +} + +message QualityIn +{ + uint32 value = 1; +} + +message QualityOut +{ + uint32 value = 1; +} + +message ReferenceFeeUnits +{ + uint32 value = 1; +} + +message ReserveBase +{ + // in drops + uint32 value = 1; +} + +message ReserveIncrement +{ + // in drops + uint32 value = 1; +} + +message Sequence +{ + uint32 value = 1; +} + +message SetFlag +{ + uint32 value = 1; +} + +message SettleDelay +{ + uint32 value = 1; +} + +message SignerListID +{ + uint32 value = 1; +} + +message SignerQuorum +{ + uint32 value = 1; +} + +message SignerWeight +{ + // is actually uint16 + uint32 value = 1; +} + +message SourceTag +{ + uint32 value = 1; +} + +message TickSize +{ + // is actually uint8 + uint32 value = 1; +} + +message TransferRate +{ + uint32 value = 1; +} + + +// *** Messages wrapping uint64 *** + +message BaseFee +{ + // in drops + uint64 value = 1 [jstype=JS_STRING]; +} + +message BookNode +{ + uint64 value = 1 [jstype=JS_STRING]; +} + +message DestinationNode +{ + uint64 value = 1 [jstype=JS_STRING]; +} + +message HighNode +{ + uint64 value = 1 [jstype=JS_STRING]; +} + +message IndexNext +{ + uint64 value = 1 [jstype=JS_STRING]; +} + +message IndexPrevious +{ + uint64 value = 1 [jstype=JS_STRING]; +} + +message LowNode +{ + uint64 value = 1 [jstype=JS_STRING]; +} + +message OwnerNode +{ + uint64 value = 1 [jstype=JS_STRING]; +} + + +// *** Messages wrapping 16 bytes *** + +message EmailHash +{ + bytes value = 1; +} + + +// *** Messages wrapping 20 bytes *** + +message TakerGetsIssuer +{ + // 20 bytes + bytes value = 1; +} + +message TakerPaysIssuer +{ + // 20 bytes + bytes value = 1; +} + + +// *** Messages wrapping 32 bytes *** + +message AccountTransactionID +{ + // 32 bytes + bytes value = 1; +} + +message BookDirectory +{ + // 32 btes + bytes value = 1; +} + +message Channel +{ + // 32 bytes + bytes value = 1; +} + +message CheckID +{ + // 32 bytes + bytes value = 1; +} + +message Hash +{ + // 32 bytes + bytes value = 1; +} + +message Index +{ + // 32 bytes + bytes value = 1; +} + +message InvoiceID +{ + // 32 bytes + bytes value = 1; +} + +message PreviousTransactionID +{ + // 32 bytes + bytes value = 1; +} + +message RootIndex +{ + // 32 bytes + bytes value = 1; +} + + +// *** Messages wrapping variable length byte arrays *** + +message Condition +{ + bytes value = 1; +} + +message Fulfillment +{ + bytes value = 1; +} + +message MemoData +{ + bytes value = 1; +} + +message MemoFormat +{ + bytes value = 1; +} + +message MemoType +{ + bytes value = 1; +} + +message MessageKey +{ + bytes value = 1; +} + +message PublicKey +{ + bytes value = 1; +} + +message PaymentChannelSignature +{ + bytes value = 1; +} + +message SigningPublicKey +{ + bytes value = 1; +} + +message TransactionSignature +{ + bytes value = 1; +} + + +// *** Messages wrapping a Currency value *** + +message TakerGetsCurreny +{ + Currency value = 1; +} + +message TakerPaysCurrency +{ + Currency value = 1; +} + + +// *** Messages wrapping a CurrencyAmount *** + +message Amount +{ + // Note, CurrencyAmount is a oneof, that can represent an XRP drops amount + // or an Issued Currency amount. However, in some transaction types/ledger + // objects, this value can only be in drops. For instance, the Amount field + // of a Payment transaction can be specified in XRP drops or an Issued + // Currency amount, but the Amount field of a PaymentChannelClaim + // transaction can only be an XRP drops amount. + CurrencyAmount value = 1; +} + +message Balance +{ + CurrencyAmount value = 1; +} + +message DeliverMin +{ + CurrencyAmount value = 1; +} + +message DeliveredAmount +{ + CurrencyAmount value = 1; +} + +message HighLimit +{ + CurrencyAmount value = 1; +} + +message LimitAmount +{ + CurrencyAmount value = 1; +} + +message LowLimit +{ + CurrencyAmount value = 1; +} + +message SendMax +{ + CurrencyAmount value = 1; +} + +message TakerGets +{ + CurrencyAmount value = 1; +} + +message TakerPays +{ + CurrencyAmount value = 1; +} + + +// *** Messages wrapping an AccountAddress *** + +message Account +{ + AccountAddress value = 1; +} + +message Authorize +{ + AccountAddress value = 1; +} + +message Destination +{ + AccountAddress value = 1; +} + +message Owner +{ + AccountAddress value = 1; +} + +message RegularKey +{ + AccountAddress value = 1; +} + +message Unauthorize +{ + AccountAddress value = 1; +} + + +// *** Messages wrapping a string *** + +message Domain +{ + string value = 1; +} + + +// *** Aggregate type messages + +// Next field: 3 +message SignerEntry +{ + Account account = 1; + + SignerWeight signer_weight = 2; +} diff --git a/src/ripple/proto/rpc/v1/account_info.proto b/src/ripple/proto/org/xrpl/rpc/v1/get_account_info.proto similarity index 58% rename from src/ripple/proto/rpc/v1/account_info.proto rename to src/ripple/proto/org/xrpl/rpc/v1/get_account_info.proto index ab7c01aab5..b533ebe6a6 100644 --- a/src/ripple/proto/rpc/v1/account_info.proto +++ b/src/ripple/proto/org/xrpl/rpc/v1/get_account_info.proto @@ -1,12 +1,19 @@ syntax = "proto3"; -package rpc.v1; +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; -import "rpc/v1/ledger_objects.proto"; -import "rpc/v1/amount.proto"; +import "org/xrpl/rpc/v1/ledger_objects.proto"; +import "org/xrpl/rpc/v1/amount.proto"; +import "org/xrpl/rpc/v1/account.proto"; +import "org/xrpl/rpc/v1/ledger.proto"; +import "org/xrpl/rpc/v1/common.proto"; // A request to get info about an account. -message GetAccountInfoRequest { +// Next field: 6 +message GetAccountInfoRequest +{ // The address to get info about. AccountAddress account = 1; @@ -19,25 +26,10 @@ message GetAccountInfoRequest { bool signer_lists = 5; } -message LedgerSpecifier { - enum Shortcut { - SHORTCUT_UNSPECIFIED = 0; - SHORTCUT_VALIDATED = 1; - SHORTCUT_CLOSED = 2; - SHORTCUT_CURRENT = 3; - } - - oneof ledger { - Shortcut shortcut = 1; - uint32 sequence = 2; - // 32 bytes - bytes hash = 3; - } -} - // Response to GetAccountInfo RPC -message GetAccountInfoResponse { - +// Next field: 6 +message GetAccountInfoResponse +{ AccountRoot account_data = 1; SignerList signer_list = 2; @@ -50,8 +42,9 @@ message GetAccountInfoResponse { } // Aggregate data about queued transactions -message QueueData { - +// Next field: 7 +message QueueData +{ uint32 txn_count = 1; bool auth_change_queued = 2; @@ -66,16 +59,18 @@ message QueueData { } // Data about a single queued transaction -message QueuedTransaction { +// Next field: 7 +message QueuedTransaction +{ bool auth_change = 1; XRPDropsAmount fee = 2; - uint64 fee_level = 3; + uint64 fee_level = 3 [jstype=JS_STRING]; XRPDropsAmount max_spend_drops = 4; - uint32 sequence = 5; + Sequence sequence = 5; - uint32 last_ledger_sequence = 6; + LastLedgerSequence last_ledger_sequence = 6; } diff --git a/src/ripple/proto/org/xrpl/rpc/v1/get_account_transaction_history.proto b/src/ripple/proto/org/xrpl/rpc/v1/get_account_transaction_history.proto new file mode 100644 index 0000000000..5b9e677c4a --- /dev/null +++ b/src/ripple/proto/org/xrpl/rpc/v1/get_account_transaction_history.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +import "org/xrpl/rpc/v1/get_transaction.proto"; +import "org/xrpl/rpc/v1/account.proto"; +import "org/xrpl/rpc/v1/ledger.proto"; + +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; + +// Next field: 8 +message GetAccountTransactionHistoryRequest +{ + AccountAddress account = 1; + + // What ledger to include results from. Specifying a not yet validated + // ledger results in an error. Not specifying a ledger uses the entire + // range of validated ledgers available to the server. + oneof ledger + { + LedgerSpecifier ledger_specifier = 2; + LedgerRange ledger_range = 3; + }; + + // Return results as binary blobs. Defaults to false. + bool binary = 4; + + // If set to true, returns values indexed by older ledger first. + // Default to false. + bool forward = 5; + + // Limit the number of results. Server may choose a lower limit. + // If this value is 0, the limit is ignored and the number of results + // returned is determined by the server + uint32 limit = 6; + + // Marker to resume where previous request left off + // Used for pagination + Marker marker = 7; +} + + +// Next field: 8 +message GetAccountTransactionHistoryResponse +{ + AccountAddress account = 1; + + uint32 ledger_index_min = 2; + + uint32 ledger_index_max = 3; + + uint32 limit = 4; + + Marker marker = 5; + + repeated GetTransactionResponse transactions = 6; + + bool validated = 7; +} + +// Next field: 3 +message Marker +{ + uint32 ledger_index = 1; + + uint32 account_sequence = 2; +} + diff --git a/src/ripple/proto/org/xrpl/rpc/v1/get_fee.proto b/src/ripple/proto/org/xrpl/rpc/v1/get_fee.proto new file mode 100644 index 0000000000..5d805d6814 --- /dev/null +++ b/src/ripple/proto/org/xrpl/rpc/v1/get_fee.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; + +import "org/xrpl/rpc/v1/amount.proto"; + +// A request for the current transaction fee on the ledger. +// Next field: 1 +message GetFeeRequest +{ +} + +// Response to a GetFee RPC +// Next field: 8 +message GetFeeResponse +{ + uint64 current_ledger_size = 1 [jstype=JS_STRING]; + + uint64 current_queue_size = 2 [jstype=JS_STRING]; + + Fee fee = 3; + + uint64 expected_ledger_size = 4 [jstype=JS_STRING]; + + uint32 ledger_current_index = 5; + + FeeLevels levels = 6; + + uint64 max_queue_size = 7 [jstype=JS_STRING]; + +} + +// Next field: 5 +message Fee +{ + XRPDropsAmount base_fee = 1; + + XRPDropsAmount median_fee = 2; + + XRPDropsAmount minimum_fee = 3; + + XRPDropsAmount open_ledger_fee = 4; +} + +// Next field: 5 +message FeeLevels +{ + uint64 median_level = 1 [jstype=JS_STRING]; + + uint64 minimum_level = 2 [jstype=JS_STRING]; + + uint64 open_ledger_level = 3 [jstype=JS_STRING]; + + uint64 reference_level = 4 [jstype=JS_STRING]; +} diff --git a/src/ripple/proto/org/xrpl/rpc/v1/get_transaction.proto b/src/ripple/proto/org/xrpl/rpc/v1/get_transaction.proto new file mode 100644 index 0000000000..f872619ad7 --- /dev/null +++ b/src/ripple/proto/org/xrpl/rpc/v1/get_transaction.proto @@ -0,0 +1,56 @@ +syntax = "proto3"; + +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; + +import "org/xrpl/rpc/v1/meta.proto"; +import "org/xrpl/rpc/v1/ledger.proto"; +import "org/xrpl/rpc/v1/transaction.proto"; +import "org/xrpl/rpc/v1/common.proto"; + +// Next field: 4 +message GetTransactionRequest { + + // hash of the transaction. 32 bytes + // ATTN: this is in binary, not hex. The JSON API accepts a hex string for + // a transaction hash, but here we need that hex string converted into its + // binary form. Each pair of hex characters should be converted into its + // corresponding byte. For example, the 4 character hex string "00FF" + // should be converted to a 2 byte array: [0, 255] + bytes hash = 1; + + // if true, return data in binary format. defaults to false + bool binary = 2; + + // search only specified range. optional + LedgerRange ledger_range = 3; +} + +// Next field: 9 +message GetTransactionResponse { + + oneof serialized_transaction { + + Transaction transaction = 1; + // Variable length + bytes transaction_binary = 2; + }; + // Sequence number of ledger that contains this transaction + uint32 ledger_index = 3; + + // 32 bytes + bytes hash = 4; + + // whether the ledger has been validated + bool validated = 5; + + // metadata about the transaction + oneof serialized_meta { + Meta meta = 6; + // Variable length + bytes meta_binary = 7; + } + + Date date = 8; +} diff --git a/src/ripple/proto/org/xrpl/rpc/v1/ledger.proto b/src/ripple/proto/org/xrpl/rpc/v1/ledger.proto new file mode 100644 index 0000000000..e5a7f067b4 --- /dev/null +++ b/src/ripple/proto/org/xrpl/rpc/v1/ledger.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; + +// Next field: 4 +message LedgerSpecifier +{ + // Next field: 4 + enum Shortcut + { + SHORTCUT_UNSPECIFIED = 0; + SHORTCUT_VALIDATED = 1; + SHORTCUT_CLOSED = 2; + SHORTCUT_CURRENT = 3; + } + + oneof ledger + { + Shortcut shortcut = 1; + uint32 sequence = 2; + // 32 bytes + bytes hash = 3; + } +} + +// Next field: 3 +message LedgerRange +{ + uint32 ledger_index_min = 1; + + // Note, if ledger_index_min is non-zero and ledger_index_max is 0, the + // software will use the max validated ledger in place of ledger_index_max + uint32 ledger_index_max = 2; +}; + diff --git a/src/ripple/proto/org/xrpl/rpc/v1/ledger_objects.proto b/src/ripple/proto/org/xrpl/rpc/v1/ledger_objects.proto new file mode 100644 index 0000000000..9bcaa2672d --- /dev/null +++ b/src/ripple/proto/org/xrpl/rpc/v1/ledger_objects.proto @@ -0,0 +1,331 @@ +syntax = "proto3"; + +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; + +import "org/xrpl/rpc/v1/common.proto"; + +// Next field: 13 +message LedgerObject +{ + oneof object + { + AccountRoot account_root = 1; + Amendments amendments = 2; + Check check = 3; + DepositPreauthObject deposit_preauth = 4; + DirectoryNode directory_node = 5; + Escrow escrow = 6; + FeeSettings fee_settings = 7; + LedgerHashes ledger_hashes = 8; + Offer offer = 9; + PayChannel pay_channel = 10; + RippleState ripple_state = 11; + SignerList signer_list = 12; + } +} + +// Next field: 13 +enum LedgerEntryType +{ + LEDGER_ENTRY_TYPE_UNSPECIFIED = 0; + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT = 1; + LEDGER_ENTRY_TYPE_AMENDMENTS = 2; + LEDGER_ENTRY_TYPE_CHECK = 3; + LEDGER_ENTRY_TYPE_DEPOSIT_PREAUTH = 4; + LEDGER_ENTRY_TYPE_DIRECTORY_NODE = 5; + LEDGER_ENTRY_TYPE_ESCROW = 6; + LEDGER_ENTRY_TYPE_FEE_SETTINGS = 7; + LEDGER_ENTRY_TYPE_LEDGER_HASHES = 8; + LEDGER_ENTRY_TYPE_OFFER = 9; + LEDGER_ENTRY_TYPE_PAY_CHANNEL = 10; + LEDGER_ENTRY_TYPE_RIPPLE_STATE = 11; + LEDGER_ENTRY_TYPE_SIGNER_LIST = 12; +} + +// Next field: 15 +message AccountRoot +{ + Account account = 1; + + Balance balance = 2; + + Sequence sequence = 3; + + Flags flags = 4; + + OwnerCount owner_count = 5; + + PreviousTransactionID previous_transaction_id = 6; + + PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 7; + + AccountTransactionID account_transaction_id = 8; + + Domain domain = 9; + + EmailHash email_hash = 10; + + MessageKey message_key = 11; + + RegularKey regular_key = 12; + + TickSize tick_size = 13; + + TransferRate transfer_rate = 14; +} + +// Next field: 4 +message Amendments +{ + // Next field: 2 + message Amendment + { + // 32 bytes + bytes value = 1; + } + + // Next field: 3 + message Majority + { + Amendment amendment = 1; + + CloseTime close_time = 2; + } + + repeated Amendment amendments = 1; + + repeated Majority majorities = 2; + + Flags flags = 3; +} + +// Next field: 14 +message Check +{ + Account account = 1; + + Destination destination = 2; + + Flags flags = 3; + + OwnerNode owner_node = 4; + + PreviousTransactionID previous_transaction_id = 5; + + PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 6; + + SendMax send_max = 7; + + Sequence sequence = 8; + + DestinationNode destination_node = 9; + + DestinationTag destination_tag = 10; + + Expiration expiration = 11; + + InvoiceID invoice_id = 12; + + SourceTag source_tag = 13; +} + +// Next field: 7 +message DepositPreauthObject +{ + Account account = 1; + + Authorize authorize = 2; + + Flags flags = 3; + + OwnerNode owner_node = 4; + + PreviousTransactionID previous_transaction_id = 5; + + PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 6; +} + +// Next field: 11 +message DirectoryNode +{ + Flags flags = 1; + + RootIndex root_index = 2; + + repeated Index indexes = 3; + + IndexNext index_next = 4; + + IndexPrevious index_previous = 5; + + Owner owner = 6; + + TakerPaysCurrency taker_pays_currency = 7; + + TakerPaysIssuer taker_pays_issuer = 8; + + TakerGetsCurreny taker_gets_currency = 9; + + TakerGetsIssuer taker_gets_issuer = 10; +} + +// Next field: 14 +message Escrow +{ + Account account = 1; + + Destination destination = 2; + + Amount amount = 3; + + Condition condition = 4; + + CancelAfter cancel_after = 5; + + FinishAfter finish_after = 6; + + Flags flags = 7; + + SourceTag source_tag = 8; + + DestinationTag destination_tag = 9; + + OwnerNode owner_node = 10; + + DestinationNode destination_node = 11; + + PreviousTransactionID previous_transaction_id = 12; + + PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 13; +} + +// Next field: 6 +message FeeSettings +{ + BaseFee base_fee = 1; + + ReferenceFeeUnits reference_fee_units = 2; + + ReserveBase reserve_base = 3; + + ReserveIncrement reserve_increment = 4; + + Flags flags = 5; +} + +// Next field: 4 +message LedgerHashes +{ + LastLedgerSequence last_ledger_sequence = 1; + + repeated Hash hashes = 2; + + Flags flags = 3; +} + +// Next field: 12 +message Offer +{ + Account account = 1; + + Sequence sequence = 2; + + Flags flags = 3; + + TakerPays taker_pays = 4; + + TakerGets taker_gets = 5; + + BookDirectory book_directory = 6; + + BookNode book_node = 7; + + OwnerNode owner_node = 8; + + Expiration expiration = 9; + + PreviousTransactionID previous_transaction_id = 10; + + PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 11; +} + +// Next field: 13 +message PayChannel +{ + Account account = 1; + + Destination destination = 2; + + Amount amount = 3; + + Balance balance = 4; + + PublicKey public_key = 5; + + SettleDelay settle_delay = 6; + + OwnerNode owner_node = 7; + + PreviousTransactionID previous_transaction_id = 8; + + PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 9; + + Flags flags = 10; + + Expiration expiration = 11; + + CancelAfter cancel_after = 12; + + SourceTag source_tag = 13; + + DestinationTag destination_tag = 14; + +} + +// Next field: 13 +message RippleState +{ + Balance balance = 1; + + Flags flags = 2; + + LowLimit low_limit = 3; + + HighLimit high_limit = 4; + + LowNode low_node = 5; + + HighNode high_node = 6; + + LowQualityIn low_quality_in = 7; + + LowQualityOut low_quality_out = 8; + + HighQualityIn high_quality_in = 9; + + HighQualityOut high_quality_out = 10; + + PreviousTransactionID previous_transaction_id = 11; + + PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 12; +} + +// Next field: 8 +message SignerList +{ + Flags flags = 1; + + PreviousTransactionID previous_transaction_id = 2; + + PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 3; + + OwnerNode owner_node = 4; + + repeated SignerEntry signer_entries = 5; + + SignerListID signer_list_id = 6; + + SignerQuorum signer_quorum = 7; +} diff --git a/src/ripple/proto/rpc/v1/meta.proto b/src/ripple/proto/org/xrpl/rpc/v1/meta.proto similarity index 62% rename from src/ripple/proto/rpc/v1/meta.proto rename to src/ripple/proto/org/xrpl/rpc/v1/meta.proto index b5c0c2a957..d71fdacc98 100644 --- a/src/ripple/proto/rpc/v1/meta.proto +++ b/src/ripple/proto/org/xrpl/rpc/v1/meta.proto @@ -1,25 +1,32 @@ syntax = "proto3"; -package rpc.v1; +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; -import "rpc/v1/amount.proto"; -import "rpc/v1/ledger_objects.proto"; +import "org/xrpl/rpc/v1/ledger_objects.proto"; +import "org/xrpl/rpc/v1/common.proto"; -message Meta { +// Next field: 5 +message Meta +{ // index in ledger - uint64 transaction_index = 1; + uint64 transaction_index = 1 [jstype=JS_STRING]; // result code indicating whether the transaction succeeded or failed TransactionResult transaction_result = 2; repeated AffectedNode affected_nodes = 3; - CurrencyAmount delivered_amount = 4; + DeliveredAmount delivered_amount = 4; } -message TransactionResult { - - enum ResultType { +// Next field: 3 +message TransactionResult +{ + // Next field: 7 + enum ResultType + { RESULT_TYPE_UNSPECIFIED = 0; // Claimed cost only RESULT_TYPE_TEC = 1; @@ -42,41 +49,42 @@ message TransactionResult { string result = 2; } -message AffectedNode { - +// Next field: 6 +message AffectedNode +{ LedgerEntryType ledger_entry_type = 1; // 32 bytes bytes ledger_index = 2; - oneof node { + oneof node + { CreatedNode created_node = 3; DeletedNode deleted_node = 4; ModifiedNode modified_node = 5; } } -message CreatedNode { - +// Next field: 2 +message CreatedNode +{ LedgerObject new_fields = 1; - } -message DeletedNode { - +// Next field: 2 +message DeletedNode +{ LedgerObject final_fields = 1; - } +// Next field: 5 message ModifiedNode { LedgerObject final_fields = 1; LedgerObject previous_fields = 2; - // 32 bytes - bytes previous_transaction_id = 3; - - uint32 previous_transaction_ledger_sequence = 4; + PreviousTransactionID previous_transaction_id = 3; + PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 4; } diff --git a/src/ripple/proto/rpc/v1/submit.proto b/src/ripple/proto/org/xrpl/rpc/v1/submit.proto similarity index 67% rename from src/ripple/proto/rpc/v1/submit.proto rename to src/ripple/proto/org/xrpl/rpc/v1/submit.proto index 3f4a812ddf..582f7be700 100644 --- a/src/ripple/proto/rpc/v1/submit.proto +++ b/src/ripple/proto/org/xrpl/rpc/v1/submit.proto @@ -1,11 +1,15 @@ syntax = "proto3"; -package rpc.v1; +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; -import "rpc/v1/meta.proto"; +import "org/xrpl/rpc/v1/meta.proto"; // A request to submit the signed transaction to the ledger. -message SubmitTransactionRequest { +// Next field: 3 +message SubmitTransactionRequest +{ // The signed transaction to submit. bytes signed_transaction = 1; @@ -13,11 +17,14 @@ message SubmitTransactionRequest { } // A response when a signed transaction is submitted to the ledger. -message SubmitTransactionResponse { +// Next field: 5 +message SubmitTransactionResponse +{ // Code indicating the preliminary result of the transaction. TransactionResult engine_result = 1; - // Numeric code indicating the preliminary result of the transaction, directly correlated to engine_result. + // Numeric code indicating the preliminary result of the transaction, + // directly correlated to engine_result. int64 engine_result_code = 2; // Human-readable explanation of the transaction's preliminary result. diff --git a/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto b/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto new file mode 100644 index 0000000000..7f896a9a42 --- /dev/null +++ b/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto @@ -0,0 +1,319 @@ +syntax = "proto3"; + +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; + +import "org/xrpl/rpc/v1/common.proto"; +import "org/xrpl/rpc/v1/amount.proto"; +import "org/xrpl/rpc/v1/account.proto"; + +// A message encompassing all transaction types +// Next field: 30 +message Transaction +{ + Account account = 1; + + XRPDropsAmount fee = 2; + + Sequence sequence = 3; + + // Data specific to the type of transaction + oneof transaction_data + { + Payment payment = 4; + + AccountSet account_set = 13; + + AccountDelete account_delete = 14; + + CheckCancel check_cancel = 15; + + CheckCash check_cash = 16; + + CheckCreate check_create = 17; + + DepositPreauth deposit_preauth = 18; + + EscrowCancel escrow_cancel = 19; + + EscrowCreate escrow_create = 20; + + EscrowFinish escrow_finish = 21; + + OfferCancel offer_cancel = 22; + + OfferCreate offer_create = 23; + + PaymentChannelClaim payment_channel_claim = 24; + + PaymentChannelCreate payment_channel_create= 25; + + PaymentChannelFund payment_channel_fund = 26; + + SetRegularKey set_regular_key = 27; + + SignerListSet signer_list_set = 28; + + TrustSet trust_set = 29; + + } + + SigningPublicKey signing_public_key = 5; + + TransactionSignature transaction_signature = 6; + + Flags flags = 7; + + LastLedgerSequence last_ledger_sequence = 8; + + SourceTag source_tag = 9; + + repeated Memo memos = 10; + + repeated Signer signers = 11; + + AccountTransactionID account_transaction_id = 12; +} + +// Next field: 4 +message Memo +{ + MemoData memo_data = 1; + + MemoFormat memo_format = 2; + + MemoType memo_type = 3; +} + +// Next field: 4 +message Signer +{ + Account account = 1; + + TransactionSignature transaction_signature = 2; + + SigningPublicKey signing_public_key = 3; +} + +// Next field: 8 +message AccountSet +{ + ClearFlag clear_flag = 1; + + Domain domain = 2; + + EmailHash email_hash = 3; + + MessageKey message_key = 4; + + SetFlag set_flag = 5; + + TransferRate transfer_rate = 6; + + TickSize tick_size = 7; +} + +// Next field: 3 +message AccountDelete +{ + Destination destination = 1; + + DestinationTag destination_tag = 2; +} + +// Next field: 2 +message CheckCancel +{ + CheckID check_id = 1; +} + +// Next field: 4 +message CheckCash +{ + CheckID check_id = 1; + + oneof amount_oneof + { + Amount amount = 2; + + DeliverMin deliver_min = 3; + } +} + +// Next field: 6 +message CheckCreate +{ + Destination destination = 1; + + SendMax send_max = 2; + + DestinationTag destination_tag = 3; + + Expiration expiration = 4; + + InvoiceID invoice_id = 5; +} + +// Next field: 3 +message DepositPreauth +{ + oneof authorization_oneof + { + Authorize authorize = 1; + + Unauthorize unauthorize = 2; + } +} + +// Next field: 3 +message EscrowCancel +{ + Owner owner = 1; + + OfferSequence offer_sequence = 2; +} + +// Next field: 7 +message EscrowCreate +{ + Amount amount = 1; + + Destination destination = 2; + + CancelAfter cancel_after = 3; + + FinishAfter finish_after = 4; + + Condition condition = 5; + + DestinationTag destination_tag = 6; +} + +// Next field: 5 +message EscrowFinish +{ + Owner owner = 1; + + OfferSequence offer_sequence = 2; + + Condition condition = 3; + + Fulfillment fulfillment = 4; +} + +// Next field: 2 +message OfferCancel +{ + OfferSequence offer_sequence = 1; +} + +// Next field: 5 +message OfferCreate +{ + Expiration expiration = 1; + + OfferSequence offer_sequence = 2; + + TakerGets taker_gets = 3; + + TakerPays taker_pays = 4; +} + +// Next field: 8 +message Payment +{ + // Next field: 4 + message PathElement + { + AccountAddress account = 1; + + Currency currency = 2; + + AccountAddress issuer = 3; + } + + // Next field: 2 + message Path + { + repeated PathElement elements = 1; + } + + Amount amount = 1; + + Destination destination = 2; + + DestinationTag destination_tag = 3; + + InvoiceID invoice_id = 4; + + repeated Path paths = 5; + + SendMax send_max = 6; + + DeliverMin deliver_min = 7; +} + +// Next field: 6 +message PaymentChannelClaim +{ + Channel channel = 1; + + Balance balance = 2; + + Amount amount = 3; + + PaymentChannelSignature payment_channel_signature = 4; + + PublicKey public_key = 5; +} + +// Next field: 7 +message PaymentChannelCreate +{ + Amount amount = 1; + + Destination destination = 2; + + SettleDelay settle_delay = 3; + + PublicKey public_key = 4; + + CancelAfter cancel_after = 5; + + DestinationTag destination_tag = 6; +} + +// Next field: 4 +message PaymentChannelFund +{ + Channel channel = 1; + + Amount amount = 2; + + Expiration expiration = 3; +} + +// Next field: 2 +message SetRegularKey +{ + RegularKey regular_key = 1; +} + +// Next field: 3 +message SignerListSet +{ + SignerQuorum signer_quorum = 1; + + repeated SignerEntry signer_entries = 2; +} + +// Next field: 4 +message TrustSet +{ + LimitAmount limit_amount = 1; + + QualityIn quality_in = 2; + + QualityOut quality_out = 3; +} diff --git a/src/ripple/proto/org/xrpl/rpc/v1/xrp_ledger.proto b/src/ripple/proto/org/xrpl/rpc/v1/xrp_ledger.proto new file mode 100644 index 0000000000..444f97d12b --- /dev/null +++ b/src/ripple/proto/org/xrpl/rpc/v1/xrp_ledger.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package org.xrpl.rpc.v1; +option java_package = "org.xrpl.rpc.v1"; +option java_multiple_files = true; + +import "org/xrpl/rpc/v1/get_account_info.proto"; +import "org/xrpl/rpc/v1/get_fee.proto"; +import "org/xrpl/rpc/v1/submit.proto"; +import "org/xrpl/rpc/v1/get_transaction.proto"; +import "org/xrpl/rpc/v1/get_account_transaction_history.proto"; + + +// RPCs available to interact with the XRP Ledger. +service XRPLedgerAPIService { + + // Get account info for an account on the XRP Ledger. + rpc GetAccountInfo (GetAccountInfoRequest) returns (GetAccountInfoResponse); + + // Get the fee for a transaction on the XRP Ledger. + rpc GetFee (GetFeeRequest) returns (GetFeeResponse); + + // Submit a signed transaction to the XRP Ledger. + rpc SubmitTransaction (SubmitTransactionRequest) returns (SubmitTransactionResponse); + + // Get the status of a transaction + rpc GetTransaction(GetTransactionRequest) returns (GetTransactionResponse); + + rpc GetAccountTransactionHistory(GetAccountTransactionHistoryRequest) returns (GetAccountTransactionHistoryResponse); +} diff --git a/src/ripple/proto/rpc/v1/fee.proto b/src/ripple/proto/rpc/v1/fee.proto deleted file mode 100644 index 76340c3095..0000000000 --- a/src/ripple/proto/rpc/v1/fee.proto +++ /dev/null @@ -1,51 +0,0 @@ -syntax = "proto3"; - -package rpc.v1; - -import "rpc/v1/amount.proto"; - -// A request for the current transaction fee on the ledger. -message GetFeeRequest { - -} - -// Response to a GetFee RPC -message GetFeeResponse { - - uint64 current_ledger_size = 1; - - uint64 current_queue_size = 2; - - Fee drops = 3; - - uint64 expected_ledger_size = 4; - - uint32 ledger_current_index = 5; - - FeeLevels levels = 6; - - uint64 max_queue_size = 7; - -} - -message Fee { - - XRPDropsAmount base_fee = 1; - - XRPDropsAmount median_fee = 2; - - XRPDropsAmount minimum_fee = 3; - - XRPDropsAmount open_ledger_fee = 4; -} - -message FeeLevels { - - uint64 median_level = 1; - - uint64 minimum_level = 2; - - uint64 open_ledger_level = 3; - - uint64 reference_level = 4; -} diff --git a/src/ripple/proto/rpc/v1/ledger_objects.proto b/src/ripple/proto/rpc/v1/ledger_objects.proto deleted file mode 100644 index d197aecf5f..0000000000 --- a/src/ripple/proto/rpc/v1/ledger_objects.proto +++ /dev/null @@ -1,175 +0,0 @@ -syntax = "proto3"; - -package rpc.v1; - -import "rpc/v1/amount.proto"; - -message LedgerObject { - - oneof object { - AccountRoot account_root = 1; - RippleState ripple_state = 2; - Offer offer = 3; - SignerList signer_list = 4; - DirectoryNode directory_node = 5; - } -} - -enum LedgerEntryType { - LEDGER_ENTRY_TYPE_UNSPECIFIED = 0; - LEDGER_ENTRY_TYPE_ACCOUNT_ROOT = 1; - LEDGER_ENTRY_TYPE_AMENDMENTS = 2; - LEDGER_ENTRY_TYPE_CHECK = 3; - LEDGER_ENTRY_TYPE_DEPOSIT_PREAUTH = 4; - LEDGER_ENTRY_TYPE_DIRECTORY_NODE = 5; - LEDGER_ENTRY_TYPE_ESCROW = 6; - LEDGER_ENTRY_TYPE_FEE_SETTINGS = 7; - LEDGER_ENTRY_TYPE_LEDGER_HASHES = 8; - LEDGER_ENTRY_TYPE_OFFER = 9; - LEDGER_ENTRY_TYPE_PAY_CHANNEL = 10; - LEDGER_ENTRY_TYPE_RIPPLE_STATE = 11; - LEDGER_ENTRY_TYPE_SIGNER_LIST = 12; -} - -message DirectoryNode { - - uint32 flags = 1; - - // 32 bytes - bytes root_index = 2; - - repeated bytes indexes = 3; - - uint64 index_next = 4; - - uint64 index_previous = 5; - - string owner = 6; - - Currency taker_pays_currency = 7; - - // 20 bytes - bytes taker_pays_issuer = 8; - - Currency taker_gets_currency = 9; - - // 20 bytes - bytes taker_gets_issuer = 10; -} - -message SignerList { - - uint32 flags = 1; - - // 32 bytes - bytes previous_txn_id = 2; - - uint32 previous_transaction_ledger_sequence = 3; - - uint64 owner_node = 4; - - repeated SignerEntry signer_entries = 5; - - uint32 signer_list_id = 6; - - uint32 signer_quorum = 7; -} - -message SignerEntry { - - AccountAddress account = 1; - - // this is actually uint16, but protobuf can't express uint16 - uint32 signer_weight = 2; -} - -message AccountRoot { - - AccountAddress account = 1; - - XRPDropsAmount balance = 2; - - uint32 sequence = 3; - - uint32 flags = 4; - - uint32 owner_count = 5; - - // 32 bytes - bytes previous_transaction_id = 6; - - uint32 previous_transaction_ledger_sequence = 7; - - // 32 bytes - bytes account_transaction_id = 8; - - // Variable length - bytes domain = 9; - - // 16 bytes - bytes email_hash = 10; - - // Variable length - bytes message_key = 11; - - // base58 encoding - string regular_key = 12; - - uint32 tick_size = 13; - - uint32 transfer_rate = 14; -} - -message RippleState { - - CurrencyAmount balance = 1; - - uint32 flags = 2; - - CurrencyAmount low_limit = 3; - - CurrencyAmount high_limit = 4; - - uint64 low_node = 5; - - uint64 high_node = 6; - - uint32 low_quality_in = 7; - - uint32 low_quality_out = 8; - - uint32 high_quality_in = 9; - - uint32 high_quality_out = 10; - - // 32 bytes - bytes previous_transaction_id = 11; - - uint32 previous_transaction_ledger_sequence = 12; -} - -message Offer { - - string account = 1; - - uint32 sequence = 2; - - uint32 flags = 3; - - CurrencyAmount taker_pays = 4; - - CurrencyAmount taker_gets = 5; - - bytes book_directory = 6; - - uint64 book_node = 7; - - uint64 owner_node = 8; - - uint32 expiration = 9; - - // 32 bytes - bytes previous_transaction_id = 10; - - uint32 previous_transaction_ledger_sequence = 11; -} diff --git a/src/ripple/proto/rpc/v1/transaction.proto b/src/ripple/proto/rpc/v1/transaction.proto deleted file mode 100644 index 5afb62731e..0000000000 --- a/src/ripple/proto/rpc/v1/transaction.proto +++ /dev/null @@ -1,96 +0,0 @@ -syntax = "proto3"; - -package rpc.v1; - -import "rpc/v1/amount.proto"; - -// A class encompassing all transactions. -message Transaction { - // The account originating the transaction. - AccountAddress account = 1; - - // The fee attached to the transaction. - XRPDropsAmount fee = 2; - - // The sequence number for the transaction. - uint32 sequence = 3; - - // Data specific to a the type of transaction being submitted. - oneof transaction_data { - Payment payment = 4; - } - - // Public key of the account which signed the transaction. Variable length - bytes signing_public_key = 5; - - // Variable length - bytes signature = 6; - - uint32 flags = 7; - - uint32 last_ledger_sequence = 8; - - uint32 source_tag = 9; - - repeated Memo memos = 10; - - repeated Signer signers = 11; - - bytes account_transaction_id = 12; -} - -message Memo { - - // Variable length - bytes memo_data = 1; - - // Variable length - bytes memo_format = 2; - - // Variable length - bytes memo_type = 3; -} - -message Signer { - - AccountAddress account = 1; - - // Variable length - bytes transaction_signature = 2; - - // Variable length - bytes signing_public_key = 3; -} - -message Payment { - // The amount of currency to pay, in either issued currency or XRP. - CurrencyAmount amount = 1; - - // The destination of the payment. - AccountAddress destination = 2; - - uint32 destination_tag = 3; - - // 32 bytes - bytes invoice_id = 4; - - repeated Path paths = 5; - - CurrencyAmount send_max = 6; - - CurrencyAmount deliver_min = 7; -} - -message Path { - - repeated PathElement elements = 1; -} - -message PathElement { - - AccountAddress account = 1; - - Currency currency = 2; - - AccountAddress issuer = 3; -} diff --git a/src/ripple/proto/rpc/v1/tx.proto b/src/ripple/proto/rpc/v1/tx.proto deleted file mode 100644 index fb81f85aa3..0000000000 --- a/src/ripple/proto/rpc/v1/tx.proto +++ /dev/null @@ -1,40 +0,0 @@ -syntax = "proto3"; - -package rpc.v1; - -import "rpc/v1/transaction.proto"; -import "rpc/v1/meta.proto"; - -message GetTxRequest { - // hash of the transaction. 32 bytes - bytes hash = 1; - - // if true, return data in binary format - bool binary = 2; -} - -message GetTxResponse { - - // The actual transaction - oneof serialized_transaction { - Transaction transaction = 1; - // Variable length - bytes transaction_binary = 2; - }; - // Sequence number of ledger that contains this transaction - uint32 ledger_index = 3; - - // 32 bytes - bytes hash = 4; - - // whether the ledger has been validated - bool validated = 5; - - // metadata about the transaction - oneof serialized_meta { - Meta meta = 6; - // Variable length - bytes meta_binary = 7; - } - -} diff --git a/src/ripple/proto/rpc/v1/xrp_ledger.proto b/src/ripple/proto/rpc/v1/xrp_ledger.proto deleted file mode 100644 index 15e3b95c6a..0000000000 --- a/src/ripple/proto/rpc/v1/xrp_ledger.proto +++ /dev/null @@ -1,25 +0,0 @@ -syntax = "proto3"; - -package rpc.v1; - -import "rpc/v1/account_info.proto"; -import "rpc/v1/fee.proto"; -import "rpc/v1/submit.proto"; -import "rpc/v1/tx.proto"; - - -// RPCs available to interact with the XRP Ledger. -service XRPLedgerAPIService { - - // Get account info for an account on the XRP Ledger. - rpc GetAccountInfo (GetAccountInfoRequest) returns (GetAccountInfoResponse); - - // Get the fee for a transaction on the XRP Ledger. - rpc GetFee (GetFeeRequest) returns (GetFeeResponse); - - // Submit a signed transaction to the XRP Ledger. - rpc SubmitTransaction (SubmitTransactionRequest) returns (SubmitTransactionResponse); - - // Get the status of a transaction - rpc GetTx(GetTxRequest) returns (GetTxResponse); -} diff --git a/src/ripple/rpc/DeliveredAmount.h b/src/ripple/rpc/DeliveredAmount.h index 84ace1f3f2..409c5dd587 100644 --- a/src/ripple/rpc/DeliveredAmount.h +++ b/src/ripple/rpc/DeliveredAmount.h @@ -20,8 +20,12 @@ #ifndef RIPPLE_RPC_DELIVEREDAMOUNT_H_INCLUDED #define RIPPLE_RPC_DELIVEREDAMOUNT_H_INCLUDED +#include +#include +#include + +#include #include -#include namespace Json { class Value; @@ -53,23 +57,22 @@ void insertDeliveredAmount( Json::Value& meta, ReadView const&, - std::shared_ptr serializedTx, + std::shared_ptr const& serializedTx, TxMeta const&); void insertDeliveredAmount( Json::Value& meta, - JsonContext&, - std::shared_ptr, - TxMeta const&); - -void -insertDeliveredAmount( - rpc::v1::CurrencyAmount& proto, - Context&, - std::shared_ptr, + RPC::JsonContext const&, + std::shared_ptr const&, TxMeta const&); +std::optional +getDeliveredAmount( + RPC::Context const& context, + std::shared_ptr const& serializedTx, + TxMeta const& transactionMeta, + LedgerIndex const& ledgerIndex); /** @} */ } // RPC diff --git a/src/ripple/rpc/GRPCHandlers.h b/src/ripple/rpc/GRPCHandlers.h index 5719efe8c1..09e3c09d87 100644 --- a/src/ripple/rpc/GRPCHandlers.h +++ b/src/ripple/rpc/GRPCHandlers.h @@ -22,7 +22,7 @@ #include #include -#include +#include namespace ripple { @@ -34,18 +34,22 @@ namespace ripple { * the status will be sent to the client, and the response will be ommitted */ -std::pair -doAccountInfoGrpc(RPC::GRPCContext& context); +std::pair +doAccountInfoGrpc(RPC::GRPCContext& context); -std::pair -doFeeGrpc(RPC::GRPCContext& context); +std::pair +doFeeGrpc(RPC::GRPCContext& context); -std::pair -doSubmitGrpc(RPC::GRPCContext& context); +std::pair +doSubmitGrpc(RPC::GRPCContext& context); // NOTE, this only supports Payment transactions at this time -std::pair -doTxGrpc(RPC::GRPCContext& context); +std::pair +doTxGrpc(RPC::GRPCContext& context); + +std::pair +doAccountTxGrpc( + RPC::GRPCContext& context); } // namespace ripple diff --git a/src/ripple/rpc/Status.h b/src/ripple/rpc/Status.h index 1f497a9231..7ab9acc468 100644 --- a/src/ripple/rpc/Status.h +++ b/src/ripple/rpc/Status.h @@ -106,7 +106,7 @@ public: /** Apply the Status to a JsonObject */ template - void inject (Object& object) + void inject (Object& object) const { if (auto ec = toErrorCode()) { diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/ripple/rpc/handlers/AccountInfo.cpp index c2ce26f31d..592c769178 100644 --- a/src/ripple/rpc/handlers/AccountInfo.cpp +++ b/src/ripple/rpc/handlers/AccountInfo.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include namespace ripple { @@ -184,15 +185,15 @@ Json::Value doAccountInfo (RPC::JsonContext& context) return result; } -std::pair -doAccountInfoGrpc(RPC::GRPCContext& context) +std::pair +doAccountInfoGrpc(RPC::GRPCContext& context) { // Return values - rpc::v1::GetAccountInfoResponse result; + org::xrpl::rpc::v1::GetAccountInfoResponse result; grpc::Status status = grpc::Status::OK; // input - rpc::v1::GetAccountInfoRequest& params = context.params; + org::xrpl::rpc::v1::GetAccountInfoRequest& params = context.params; // get ledger std::shared_ptr ledger; @@ -233,7 +234,7 @@ doAccountInfoGrpc(RPC::GRPCContext& context) auto const sleAccepted = ledger->read(keylet::account(accountID)); if (sleAccepted) { - RPC::populateAccountRoot(*result.mutable_account_data(), *sleAccepted); + RPC::convert(*result.mutable_account_data(), *sleAccepted); // signer lists if (params.signer_lists()) @@ -241,9 +242,9 @@ doAccountInfoGrpc(RPC::GRPCContext& context) auto const sleSigners = ledger->read(keylet::signers(accountID)); if (sleSigners) { - rpc::v1::SignerList& signerListProto = + org::xrpl::rpc::v1::SignerList& signerListProto = *result.mutable_signer_list(); - RPC::populateSignerList(signerListProto, *sleSigners); + RPC::convert(signerListProto, *sleSigners); } } @@ -259,8 +260,8 @@ doAccountInfoGrpc(RPC::GRPCContext& context) } auto const txs = context.app.getTxQ().getAccountTxs(accountID, *ledger); - rpc::v1::QueueData& queueData = *result.mutable_queue_data(); - RPC::populateQueueData(queueData, txs); + org::xrpl::rpc::v1::QueueData& queueData = *result.mutable_queue_data(); + RPC::convert(queueData, txs); } } else diff --git a/src/ripple/rpc/handlers/AccountTx.cpp b/src/ripple/rpc/handlers/AccountTx.cpp index ecb036f587..2046a575ca 100644 --- a/src/ripple/rpc/handlers/AccountTx.cpp +++ b/src/ripple/rpc/handlers/AccountTx.cpp @@ -25,16 +25,481 @@ #include #include #include -#include #include +#include #include #include #include -#include #include +#include +#include + + +#include namespace ripple { +using LedgerSequence = uint32_t; +using LedgerHash = uint256; +using LedgerShortcut = RPC::LedgerShortcut; + +using AccountTxMarker = NetworkOPs::AccountTxMarker; + +struct LedgerRange +{ + uint32_t min; + uint32_t max; +}; + +using LedgerSpecifier = + std::variant; + +struct AccountTxArgs +{ + AccountID account; + std::optional ledger; + bool binary = false; + bool forward = false; + uint32_t limit = 0; + std::optional marker; +}; + +using TxnsData = NetworkOPs::AccountTxs; +using TxnsDataBinary = NetworkOPs::MetaTxsList; +using TxnDataBinary = NetworkOPs::txnMetaLedgerType; + +struct AccountTxResult +{ + std::variant transactions; + LedgerRange ledgerRange; + uint32_t limit; + std::optional marker; +}; + +// parses args into a ledger specifier, or returns a grpc status object on error +std::variant, grpc::Status> +parseLedgerArgs( + org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest const& params) +{ + grpc::Status status; + if (params.has_ledger_range()) + { + uint32_t min = params.ledger_range().ledger_index_min(); + uint32_t max = params.ledger_range().ledger_index_max(); + + // if min is set but not max, need to set max + if (min != 0 && max == 0) + { + max = UINT32_MAX; + } + + return LedgerRange{min, max}; + } + else if (params.has_ledger_specifier()) + { + LedgerSpecifier ledger; + + auto& specifier = params.ledger_specifier(); + using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase; + LedgerCase ledgerCase = specifier.ledger_case(); + + if (ledgerCase == LedgerCase::kShortcut) + { + using LedgerSpecifier = org::xrpl::rpc::v1::LedgerSpecifier; + + if (specifier.shortcut() == LedgerSpecifier::SHORTCUT_VALIDATED) + ledger = LedgerShortcut::VALIDATED; + else if (specifier.shortcut() == LedgerSpecifier::SHORTCUT_CLOSED) + ledger = LedgerShortcut::CLOSED; + else if (specifier.shortcut() == LedgerSpecifier::SHORTCUT_CURRENT) + ledger = LedgerShortcut::CURRENT; + else + return {}; + } + else if (ledgerCase == LedgerCase::kSequence) + { + ledger = specifier.sequence(); + } + else if (ledgerCase == LedgerCase::kHash) + { + if (uint256::size() != specifier.hash().size()) + { + grpc::Status errorStatus{grpc::StatusCode::INVALID_ARGUMENT, + "ledger hash malformed"}; + return errorStatus; + } + ledger = uint256::fromVoid(specifier.hash().data()); + } + return ledger; + } + return std::optional{}; +} + +// parses args into a ledger specifier, or returns a Json object on error +std::variant, Json::Value> +parseLedgerArgs(Json::Value const& params) +{ + Json::Value response; + if (params.isMember(jss::ledger_index_min) || + params.isMember(jss::ledger_index_max)) + { + uint32_t min = params.isMember(jss::ledger_index_min) && + params[jss::ledger_index_min].asInt() >= 0 + ? params[jss::ledger_index_min].asUInt() + : 0; + uint32_t max = params.isMember(jss::ledger_index_max) && + params[jss::ledger_index_max].asInt() >= 0 + ? params[jss::ledger_index_max].asUInt() + : UINT32_MAX; + + return LedgerRange{min, max}; + } + else if (params.isMember(jss::ledger_hash)) + { + auto& hashValue = params[jss::ledger_hash]; + if (!hashValue.isString()) + { + RPC::Status status{rpcINVALID_PARAMS, "ledgerHashNotString"}; + status.inject(response); + return response; + } + + LedgerHash hash; + if (!hash.SetHex(hashValue.asString())) + { + RPC::Status status{rpcINVALID_PARAMS, "ledgerHashMalformed"}; + status.inject(response); + return response; + } + return hash; + } + else if (params.isMember(jss::ledger_index)) + { + LedgerSpecifier ledger; + if (params[jss::ledger_index].isNumeric()) + ledger = params[jss::ledger_index].asInt(); + else + { + std::string ledgerStr = params[jss::ledger_index].asString(); + + if (ledgerStr == "current" || ledgerStr.empty()) + ledger = LedgerShortcut::CURRENT; + else if (ledgerStr == "closed") + ledger = LedgerShortcut::CLOSED; + else if (ledgerStr == "validated") + ledger = LedgerShortcut::VALIDATED; + else + { + RPC::Status status{rpcINVALID_PARAMS, + "ledger_index string malformed"}; + status.inject(response); + return response; + } + } + return ledger; + } + return std::optional{}; +} + +std::variant +getLedgerRange( + RPC::Context& context, + std::optional const& ledgerSpecifier) +{ + std::uint32_t uValidatedMin; + std::uint32_t uValidatedMax; + bool bValidated = + context.ledgerMaster.getValidatedRange(uValidatedMin, uValidatedMax); + + if (!bValidated) + { + // Don't have a validated ledger range. + return rpcLGR_IDXS_INVALID; + } + + std::uint32_t uLedgerMin = uValidatedMin; + std::uint32_t uLedgerMax = uValidatedMax; + // Does request specify a ledger or ledger range? + if (ledgerSpecifier) + { + auto const status = std::visit( + [&](auto const& ls) -> RPC::Status { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + if (ls.min > uValidatedMin) + { + uLedgerMin = ls.min; + } + if (ls.max < uValidatedMax) + { + uLedgerMax = ls.max; + } + if (uLedgerMax < uLedgerMin) + return rpcLGR_IDXS_INVALID; + } + else + { + std::shared_ptr ledgerView; + auto const status = getLedger(ledgerView, ls, context); + if (!ledgerView) + { + return status; + } + + bool validated = RPC::isValidated( + context.ledgerMaster, *ledgerView, context.app); + + if (!validated || ledgerView->info().seq > uValidatedMax || + ledgerView->info().seq < uValidatedMin) + { + return rpcLGR_NOT_VALIDATED; + } + uLedgerMin = uLedgerMax = ledgerView->info().seq; + } + return RPC::Status::OK; + }, + *ledgerSpecifier); + + if (status) + return status; + } + return LedgerRange{uLedgerMin, uLedgerMax}; +} + +std::pair +doAccountTxHelp(RPC::Context& context, AccountTxArgs const& args) +{ + AccountTxResult result; + context.loadType = Resource::feeMediumBurdenRPC; + + auto lgrRange = getLedgerRange(context, args.ledger); + if (auto stat = std::get_if(&lgrRange)) + { + // An error occurred getting the requested ledger range + return {result, *stat}; + } + + result.ledgerRange = std::get(lgrRange); + + result.marker = args.marker; + if (args.binary) + { + result.transactions = context.netOps.getTxsAccountB( + args.account, + result.ledgerRange.min, + result.ledgerRange.max, + args.forward, + result.marker, + args.limit, + isUnlimited(context.role)); + } + else + { + result.transactions = context.netOps.getTxsAccount( + args.account, + result.ledgerRange.min, + result.ledgerRange.max, + args.forward, + result.marker, + args.limit, + isUnlimited(context.role)); + } + + result.limit = args.limit; + + return {result, rpcSUCCESS}; +} + +std::pair< + org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse, + grpc::Status> +populateProtoResponse( + std::pair const& res, + AccountTxArgs const& args, + RPC::GRPCContext< + org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest> const& context) +{ + org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse response; + grpc::Status status = grpc::Status::OK; + + RPC::Status const& error = res.second; + if (error.toErrorCode() != rpcSUCCESS) + { + if (error.toErrorCode() == rpcLGR_NOT_FOUND) + { + status = {grpc::StatusCode::NOT_FOUND, error.message()}; + } + else + { + status = {grpc::StatusCode::INVALID_ARGUMENT, error.message()}; + } + } + else + { + AccountTxResult const& result = res.first; + + // account_tx always returns validated data + response.set_validated(true); + response.set_limit(result.limit); + response.mutable_account()->set_address( + context.params.account().address()); + response.set_ledger_index_min(result.ledgerRange.min); + response.set_ledger_index_max(result.ledgerRange.max); + + if (auto txnsData = std::get_if(&result.transactions)) + { + assert(!args.binary); + for (auto const& [txn, txnMeta] : *txnsData) + { + if (txn) + { + auto txnProto = response.add_transactions(); + + RPC::convert( + *txnProto->mutable_transaction(), + txn->getSTransaction()); + + // account_tx always returns validated data + txnProto->set_validated(true); + txnProto->set_ledger_index(txn->getLedger()); + auto& hash = txn->getID(); + txnProto->set_hash(hash.data(), hash.size()); + auto closeTime = + context.app.getLedgerMaster().getCloseTimeBySeq( + txn->getLedger()); + if (closeTime) + txnProto->mutable_date()->set_value( + closeTime->time_since_epoch().count()); + if (txnMeta) + { + if (!txnMeta->hasDeliveredAmount()) + { + std::optional amount = getDeliveredAmount( + context, + txn->getSTransaction(), + *txnMeta, + txn->getLedger()); + if (amount) + { + txnMeta->setDeliveredAmount(*amount); + } + } + RPC::convert(*txnProto->mutable_meta(), txnMeta); + } + } + } + } + else + { + assert(args.binary); + + for (auto const& binaryData : + std::get(result.transactions)) + { + auto txnProto = response.add_transactions(); + Blob const& txnBlob = std::get<0>(binaryData); + txnProto->set_transaction_binary( + txnBlob.data(), txnBlob.size()); + + Blob const& metaBlob = std::get<1>(binaryData); + txnProto->set_meta_binary(metaBlob.data(), metaBlob.size()); + + txnProto->set_ledger_index(std::get<2>(binaryData)); + + // account_tx always returns validated data + txnProto->set_validated(true); + + auto closeTime = + context.app.getLedgerMaster().getCloseTimeBySeq( + std::get<2>(binaryData)); + if (closeTime) + txnProto->mutable_date()->set_value( + closeTime->time_since_epoch().count()); + } + } + + if (result.marker) + { + response.mutable_marker()->set_ledger_index( + result.marker->ledgerSeq); + response.mutable_marker()->set_account_sequence( + result.marker->txnSeq); + } + } + return {response, status}; +} + +Json::Value +populateJsonResponse( + std::pair const& res, + AccountTxArgs const& args, + RPC::JsonContext const& context) +{ + Json::Value response; + RPC::Status const& error = res.second; + if (error.toErrorCode() != rpcSUCCESS) + { + error.inject(response); + } + else + { + AccountTxResult const& result = res.first; + response[jss::validated] = true; + response[jss::limit] = result.limit; + response[jss::account] = context.params[jss::account].asString(); + response[jss::ledger_index_min] = result.ledgerRange.min; + response[jss::ledger_index_max] = result.ledgerRange.max; + + Json::Value& jvTxns = (response[jss::transactions] = Json::arrayValue); + + if (auto txnsData = std::get_if(&result.transactions)) + { + assert(!args.binary); + for (auto const& [txn, txnMeta] : *txnsData) + { + if (txn) + { + Json::Value& jvObj = jvTxns.append(Json::objectValue); + + jvObj[jss::tx] = txn->getJson(JsonOptions::include_date); + if (txnMeta) + { + jvObj[jss::meta] = + txnMeta->getJson(JsonOptions::include_date); + jvObj[jss::validated] = true; + insertDeliveredAmount( + jvObj[jss::meta], context, txn, *txnMeta); + } + } + } + } + else + { + assert(args.binary); + + for (auto const& binaryData : + std::get(result.transactions)) + { + Json::Value& jvObj = jvTxns.append(Json::objectValue); + + jvObj[jss::tx_blob] = strHex(std::get<0>(binaryData)); + jvObj[jss::meta] = strHex(std::get<1>(binaryData)); + jvObj[jss::ledger_index] = std::get<2>(binaryData); + jvObj[jss::validated] = true; + } + } + + if (result.marker) + { + response[jss::marker] = Json::objectValue; + response[jss::marker][jss::ledger] = result.marker->ledgerSeq; + response[jss::marker][jss::seq] = result.marker->txnSeq; + } + } + return response; +} + // { // account: account, // ledger_index_min: ledger_index // optional, defaults to earliest @@ -42,161 +507,107 @@ namespace ripple { // binary: boolean, // optional, defaults to false // forward: boolean, // optional, defaults to false // limit: integer, // optional -// marker: opaque // optional, resume previous query +// marker: object {ledger: ledger_index, seq: txn_sequence} // optional, +// resume previous query // } -Json::Value doAccountTx (RPC::JsonContext& context) +Json::Value +doAccountTxJson(RPC::JsonContext& context) { auto& params = context.params; + AccountTxArgs args; + Json::Value response; - int limit = params.isMember (jss::limit) ? - params[jss::limit].asUInt () : -1; - bool bBinary = params.isMember (jss::binary) && params[jss::binary].asBool (); - bool bForward = params.isMember (jss::forward) && params[jss::forward].asBool (); - std::uint32_t uLedgerMin; - std::uint32_t uLedgerMax; - std::uint32_t uValidatedMin; - std::uint32_t uValidatedMax; - bool bValidated = context.ledgerMaster.getValidatedRange ( - uValidatedMin, uValidatedMax); + args.limit = params.isMember(jss::limit) ? params[jss::limit].asUInt() : 0; + args.binary = params.isMember(jss::binary) && params[jss::binary].asBool(); + args.forward = + params.isMember(jss::forward) && params[jss::forward].asBool(); - if (!bValidated) + if (!params.isMember(jss::account)) + return rpcError(rpcINVALID_PARAMS); + + auto const account = + parseBase58(params[jss::account].asString()); + if (!account) + return rpcError(rpcACT_MALFORMED); + + args.account = *account; + + auto parseRes = parseLedgerArgs(params); + if (auto jv = std::get_if(&parseRes)) { - // Don't have a validated ledger range. - return rpcError (rpcLGR_IDXS_INVALID); - } - - if (!params.isMember (jss::account)) - return rpcError (rpcINVALID_PARAMS); - - auto const account = parseBase58( - params[jss::account].asString()); - if (! account) - return rpcError (rpcACT_MALFORMED); - - context.loadType = Resource::feeMediumBurdenRPC; - - if (params.isMember (jss::ledger_index_min) || - params.isMember (jss::ledger_index_max)) - { - std::int64_t iLedgerMin = params.isMember (jss::ledger_index_min) - ? params[jss::ledger_index_min].asInt () : -1; - std::int64_t iLedgerMax = params.isMember (jss::ledger_index_max) - ? params[jss::ledger_index_max].asInt () : -1; - - uLedgerMin = iLedgerMin == -1 ? uValidatedMin : - ((iLedgerMin >= uValidatedMin) ? iLedgerMin : uValidatedMin); - uLedgerMax = iLedgerMax == -1 ? uValidatedMax : - ((iLedgerMax <= uValidatedMax) ? iLedgerMax : uValidatedMax); - - if (uLedgerMax < uLedgerMin) - return rpcError (rpcLGR_IDXS_INVALID); - } - else if(params.isMember (jss::ledger_hash) || - params.isMember (jss::ledger_index)) - { - std::shared_ptr ledger; - auto ret = RPC::lookupLedger (ledger, context); - - if (! ledger) - return ret; - - if (! ret[jss::validated].asBool() || - (ledger->info().seq > uValidatedMax) || - (ledger->info().seq < uValidatedMin)) - { - return rpcError (rpcLGR_NOT_VALIDATED); - } - - uLedgerMin = uLedgerMax = ledger->info().seq; + return *jv; } else { - uLedgerMin = uValidatedMin; - uLedgerMax = uValidatedMax; + args.ledger = std::get>(parseRes); } - Json::Value resumeToken; - if (params.isMember(jss::marker)) - resumeToken = params[jss::marker]; - -#ifndef DEBUG - - try { -#endif - Json::Value ret (Json::objectValue); - - ret[jss::account] = context.app.accountIDCache().toBase58(*account); - Json::Value& jvTxns = (ret[jss::transactions] = Json::arrayValue); - - if (bBinary) + auto& token = params[jss::marker]; + if (!token.isMember(jss::ledger) || !token.isMember(jss::seq) || + !token[jss::ledger].isConvertibleTo(Json::ValueType::uintValue) || + !token[jss::seq].isConvertibleTo(Json::ValueType::uintValue)) { - auto txns = context.netOps.getTxsAccountB ( - *account, uLedgerMin, uLedgerMax, bForward, resumeToken, limit, - isUnlimited (context.role)); - - for (auto& it: txns) - { - Json::Value& jvObj = jvTxns.append (Json::objectValue); - - jvObj[jss::tx_blob] = std::get<0> (it); - jvObj[jss::meta] = std::get<1> (it); - - std::uint32_t uLedgerIndex = std::get<2> (it); - - jvObj[jss::ledger_index] = uLedgerIndex; - jvObj[jss::validated] = bValidated && - uValidatedMin <= uLedgerIndex && - uValidatedMax >= uLedgerIndex; - } + RPC::Status status{ + rpcINVALID_PARAMS, + "invalid marker. Provide ledger index via ledger field, and " + "transaction sequence number via seq field"}; + status.inject(response); + return response; } - else - { - auto txns = context.netOps.getTxsAccount ( - *account, uLedgerMin, uLedgerMax, bForward, resumeToken, limit, - isUnlimited (context.role)); - - for (auto const& [txn, txMeta]: txns) - { - Json::Value& jvObj = jvTxns.append (Json::objectValue); - - if (txn) - jvObj[jss::tx] = - txn->getJson (JsonOptions::include_date); - - if (txMeta) - { - auto metaJ = txMeta->getJson (JsonOptions::include_date); - insertDeliveredAmount (metaJ, context, txn, *txMeta); - jvObj[jss::meta] = std::move(metaJ); - - std::uint32_t uLedgerIndex = txMeta->getLgrSeq (); - - jvObj[jss::validated] = bValidated && - uValidatedMin <= uLedgerIndex && - uValidatedMax >= uLedgerIndex; - } - } - } - - //Add information about the original query - ret[jss::ledger_index_min] = uLedgerMin; - ret[jss::ledger_index_max] = uLedgerMax; - if (params.isMember (jss::limit)) - ret[jss::limit] = limit; - if (resumeToken) - ret[jss::marker] = resumeToken; - - return ret; -#ifndef DEBUG - } - catch (std::exception const&) - { - return rpcError (rpcINTERNAL); + args.marker = {token[jss::ledger].asUInt(), token[jss::seq].asUInt()}; } -#endif + auto res = doAccountTxHelp(context, args); + return populateJsonResponse(res, args, context); } -} // ripple +std::pair< + org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse, + grpc::Status> +doAccountTxGrpc( + RPC::GRPCContext& + context) +{ + // return values + org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse response; + grpc::Status status = grpc::Status::OK; + AccountTxArgs args; + + auto& request = context.params; + + auto const account = parseBase58(request.account().address()); + if (!account) + { + return { + {}, + {grpc::StatusCode::INVALID_ARGUMENT, "Could not decode account"}}; + } + + args.account = *account; + args.limit = request.limit(); + args.binary = request.binary(); + args.forward = request.forward(); + + if (request.has_marker()) + { + args.marker = {request.marker().ledger_index(), + request.marker().account_sequence()}; + } + + auto parseRes = parseLedgerArgs(request); + if (auto stat = std::get_if(&parseRes)) + { + return {response, *stat}; + } + else + { + args.ledger = std::get>(parseRes); + } + + auto res = doAccountTxHelp(context, args); + return populateProtoResponse(res, args, context); +} + +} // namespace ripple diff --git a/src/ripple/rpc/handlers/AccountTxOld.cpp b/src/ripple/rpc/handlers/AccountTxOld.cpp index b87faae068..08b8c9102f 100644 --- a/src/ripple/rpc/handlers/AccountTxOld.cpp +++ b/src/ripple/rpc/handlers/AccountTxOld.cpp @@ -155,8 +155,8 @@ Json::Value doAccountTxOld (RPC::JsonContext& context) Json::Value& jvObj = jvTxns.append (Json::objectValue); std::uint32_t uLedgerIndex = std::get<2> (*it); - jvObj[jss::tx_blob] = std::get<0> (*it); - jvObj[jss::meta] = std::get<1> (*it); + jvObj[jss::tx_blob] = strHex(std::get<0> (*it)); + jvObj[jss::meta] = strHex(std::get<1> (*it)); jvObj[jss::ledger_index] = uLedgerIndex; jvObj[jss::validated] = bValidated diff --git a/src/ripple/rpc/handlers/AccountTxSwitch.cpp b/src/ripple/rpc/handlers/AccountTxSwitch.cpp index 39e771f2c6..c414e88f54 100644 --- a/src/ripple/rpc/handlers/AccountTxSwitch.cpp +++ b/src/ripple/rpc/handlers/AccountTxSwitch.cpp @@ -26,7 +26,7 @@ namespace ripple { Json::Value doAccountTxOld (RPC::JsonContext& context); - Json::Value doAccountTx (RPC::JsonContext& context); +Json::Value doAccountTxJson (RPC::JsonContext& context); // Temporary switching code until the old account_tx is removed Json::Value doAccountTxSwitch (RPC::JsonContext& context) @@ -39,7 +39,7 @@ Json::Value doAccountTxSwitch (RPC::JsonContext& context) { return doAccountTxOld(context); } - return doAccountTx(context); + return doAccountTxJson(context); } } // ripple diff --git a/src/ripple/rpc/handlers/Fee1.cpp b/src/ripple/rpc/handlers/Fee1.cpp index ea6673fa5c..3ced82df97 100644 --- a/src/ripple/rpc/handlers/Fee1.cpp +++ b/src/ripple/rpc/handlers/Fee1.cpp @@ -38,10 +38,10 @@ namespace ripple return context.params; } -std::pair -doFeeGrpc(RPC::GRPCContext& context) +std::pair +doFeeGrpc(RPC::GRPCContext& context) { - rpc::v1::GetFeeResponse reply; + org::xrpl::rpc::v1::GetFeeResponse reply; grpc::Status status = grpc::Status::OK; Application& app = context.app; @@ -62,23 +62,23 @@ doFeeGrpc(RPC::GRPCContext& context) reply.set_max_queue_size(*metrics.txQMaxSize); // fee levels data - rpc::v1::FeeLevels& levels = *reply.mutable_levels(); + org::xrpl::rpc::v1::FeeLevels& levels = *reply.mutable_levels(); levels.set_median_level(metrics.medFeeLevel.fee()); levels.set_minimum_level(metrics.minProcessingFeeLevel.fee()); levels.set_open_ledger_level(metrics.openLedgerFeeLevel.fee()); levels.set_reference_level(metrics.referenceFeeLevel.fee()); // fee data - rpc::v1::Fee& drops = *reply.mutable_drops(); + org::xrpl::rpc::v1::Fee& fee = *reply.mutable_fee(); auto const baseFee = view->fees().base; - drops.mutable_base_fee()->set_drops( + fee.mutable_base_fee()->set_drops( toDrops(metrics.referenceFeeLevel, baseFee).second.drops()); - drops.mutable_minimum_fee()->set_drops( + fee.mutable_minimum_fee()->set_drops( toDrops(metrics.minProcessingFeeLevel, baseFee).second.drops()); - drops.mutable_median_fee()->set_drops( + fee.mutable_median_fee()->set_drops( toDrops(metrics.medFeeLevel, baseFee).second.drops()); - drops.mutable_open_ledger_fee()->set_drops( + fee.mutable_open_ledger_fee()->set_drops( (toDrops(metrics.openLedgerFeeLevel - FeeLevel64{1}, baseFee).second + 1) .drops()); diff --git a/src/ripple/rpc/handlers/Handlers.h b/src/ripple/rpc/handlers/Handlers.h index 127e8cfd15..805e156402 100644 --- a/src/ripple/rpc/handlers/Handlers.h +++ b/src/ripple/rpc/handlers/Handlers.h @@ -30,9 +30,9 @@ Json::Value doAccountLines (RPC::JsonContext&); Json::Value doAccountChannels (RPC::JsonContext&); Json::Value doAccountObjects (RPC::JsonContext&); Json::Value doAccountOffers (RPC::JsonContext&); -Json::Value doAccountTx (RPC::JsonContext&); Json::Value doAccountTxSwitch (RPC::JsonContext&); Json::Value doAccountTxOld (RPC::JsonContext&); +Json::Value doAccountTxJson (RPC::JsonContext&); Json::Value doBookOffers (RPC::JsonContext&); Json::Value doBlackList (RPC::JsonContext&); Json::Value doCanDelete (RPC::JsonContext&); @@ -79,7 +79,7 @@ Json::Value doSubmit (RPC::JsonContext&); Json::Value doSubmitMultiSigned (RPC::JsonContext&); Json::Value doSubscribe (RPC::JsonContext&); Json::Value doTransactionEntry (RPC::JsonContext&); -Json::Value doTx (RPC::JsonContext&); +Json::Value doTxJson (RPC::JsonContext&); Json::Value doTxHistory (RPC::JsonContext&); Json::Value doUnlList (RPC::JsonContext&); Json::Value doUnsubscribe (RPC::JsonContext&); diff --git a/src/ripple/rpc/handlers/Submit.cpp b/src/ripple/rpc/handlers/Submit.cpp index e25ca661fc..c64e1f45fb 100644 --- a/src/ripple/rpc/handlers/Submit.cpp +++ b/src/ripple/rpc/handlers/Submit.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace ripple { @@ -183,11 +184,11 @@ Json::Value doSubmit (RPC::JsonContext& context) } } -std::pair -doSubmitGrpc(RPC::GRPCContext& context) +std::pair +doSubmitGrpc(RPC::GRPCContext& context) { // return values - rpc::v1::SubmitTransactionResponse result; + org::xrpl::rpc::v1::SubmitTransactionResponse result; grpc::Status status = grpc::Status::OK; // input @@ -261,8 +262,7 @@ doSubmitGrpc(RPC::GRPCContext& context) // return preliminary result if (temUNCERTAIN != tpTrans->getResult()) { - RPC::populateTransactionResultType( - *result.mutable_engine_result(), tpTrans->getResult()); + RPC::convert(*result.mutable_engine_result(), tpTrans->getResult()); std::string sToken; std::string sHuman; diff --git a/src/ripple/rpc/handlers/Tx.cpp b/src/ripple/rpc/handlers/Tx.cpp index 12607ecfed..9268509557 100644 --- a/src/ripple/rpc/handlers/Tx.cpp +++ b/src/ripple/rpc/handlers/Tx.cpp @@ -27,7 +27,9 @@ #include #include #include +#include #include +#include namespace ripple { @@ -64,13 +66,6 @@ isValidated(LedgerMaster& ledgerMaster, std::uint32_t seq, uint256 const& hash) return ledgerMaster.getHashBySeq (seq) == hash; } -static -bool -isValidated (RPC::JsonContext& context, std::uint32_t seq, uint256 const& hash) -{ - return isValidated(context.ledgerMaster, seq, hash); -} - bool getMetaHex (Ledger const& ledger, uint256 const& transID, std::string& hex) @@ -91,206 +86,103 @@ getMetaHex (Ledger const& ledger, return true; } -Json::Value doTx (RPC::JsonContext& context) +enum class SearchedAll { no, yes, unknown }; + +struct TxResult { - if (!context.params.isMember (jss::transaction)) - return rpcError (rpcINVALID_PARAMS); + Transaction::pointer txn; + std::variant, Blob> meta; + bool validated = false; + SearchedAll searchedAll; +}; - bool binary = context.params.isMember (jss::binary) - && context.params[jss::binary].asBool (); +struct TxArgs +{ + uint256 hash; + bool binary = false; + std::optional> ledgerRange; +}; - auto const txid = context.params[jss::transaction].asString (); - - if (!isHexTxID (txid)) - return rpcError (rpcNOT_IMPL); +std::pair +doTxHelp(RPC::Context& context, TxArgs const& args) +{ + TxResult result; ClosedInterval range; - auto rangeProvided = context.params.isMember (jss::min_ledger) && - context.params.isMember (jss::max_ledger); - - if (rangeProvided) + if (args.ledgerRange) { - try - { - auto const& min = context.params[jss::min_ledger].asUInt (); - auto const& max = context.params[jss::max_ledger].asUInt (); + constexpr uint16_t MAX_RANGE = 1000; - constexpr uint16_t MAX_RANGE = 1000; + if (args.ledgerRange->second < args.ledgerRange->first) + return {result, rpcINVALID_LGR_RANGE}; - if (max < min) - return rpcError (rpcINVALID_LGR_RANGE); + if (args.ledgerRange->second - args.ledgerRange->first > MAX_RANGE) + return {result, rpcEXCESSIVE_LGR_RANGE}; - if (max - min > MAX_RANGE) - return rpcError (rpcEXCESSIVE_LGR_RANGE); - - range = ClosedInterval (min, max); - } - catch (...) - { - // One of the calls to `asUInt ()` failed. - return rpcError (rpcINVALID_LGR_RANGE); - } + range = ClosedInterval( + args.ledgerRange->first, args.ledgerRange->second); } - using pointer = Transaction::pointer; - - auto ec {rpcSUCCESS}; - pointer txn; - - if (rangeProvided) - { - boost::variant v = - context.app.getMasterTransaction().fetch( - from_hex_text(txid), range, ec); - - if (v.which () == 1) - { - auto jvResult = Json::Value (Json::objectValue); - - jvResult[jss::searched_all] = boost::get (v); - - return rpcError (rpcTXN_NOT_FOUND, jvResult); - } - else - txn = boost::get (v); - } - else - txn = context.app.getMasterTransaction().fetch( - from_hex_text(txid), ec); - - if (ec == rpcDB_DESERIALIZATION) - return rpcError (ec); - - if (!txn) - return rpcError (rpcTXN_NOT_FOUND); - - Json::Value ret = txn->getJson (JsonOptions::include_date, binary); - - if (txn->getLedger () == 0) - return ret; - - if (auto lgr = context.ledgerMaster.getLedgerBySeq (txn->getLedger ())) - { - bool okay = false; - - if (binary) - { - std::string meta; - - if (getMetaHex (*lgr, txn->getID (), meta)) - { - ret[jss::meta] = meta; - okay = true; - } - } - else - { - auto rawMeta = lgr->txRead (txn->getID()).second; - if (rawMeta) - { - auto txMeta = std::make_shared( - txn->getID(), lgr->seq(), *rawMeta); - okay = true; - auto meta = txMeta->getJson (JsonOptions::none); - insertDeliveredAmount (meta, context, txn, *txMeta); - ret[jss::meta] = std::move(meta); - } - } - - if (okay) - ret[jss::validated] = isValidated ( - context, lgr->info().seq, lgr->info().hash); - } - - return ret; -} - -std::pair -doTxGrpc(RPC::GRPCContext& context) -{ - // return values - rpc::v1::GetTxResponse result; - grpc::Status status = grpc::Status::OK; - - // input - rpc::v1::GetTxRequest& request = context.params; - - std::string const& hashBytes = request.hash(); - uint256 hash = uint256::fromVoid(hashBytes.data()); - - // hash is included in the response - result.set_hash(request.hash()); - + std::shared_ptr txn; auto ec{rpcSUCCESS}; - // get the transaction - std::shared_ptr txn = - context.app.getMasterTransaction().fetch(hash, ec); + result.searchedAll = SearchedAll::unknown; + if (args.ledgerRange) + { + boost::variant, bool> v = + context.app.getMasterTransaction().fetch(args.hash, range, ec); + + if (v.which() == 1) + { + result.searchedAll = + boost::get(v) ? SearchedAll::yes : SearchedAll::no; + return {result, rpcTXN_NOT_FOUND}; + } + else + { + txn = boost::get>(v); + } + } + else + { + txn = context.app.getMasterTransaction().fetch(args.hash, ec); + } if (ec == rpcDB_DESERIALIZATION) { - auto errorInfo = RPC::get_error_info(ec); - grpc::Status errorStatus{grpc::StatusCode::INTERNAL, - errorInfo.message.c_str()}; - return {result, errorStatus}; + return {result, ec}; } if (!txn) { - grpc::Status errorStatus{grpc::StatusCode::NOT_FOUND, "txn not found"}; - return {result, errorStatus}; - } - - std::shared_ptr stTxn = txn->getSTransaction(); - if (stTxn->getTxnType() != ttPAYMENT) - { - auto getTypeStr = [&stTxn]() { - return TxFormats::getInstance() - .findByType(stTxn->getTxnType()) - ->getName(); - }; - - grpc::Status errorStatus{grpc::StatusCode::UNIMPLEMENTED, - "txn type not supported: " + getTypeStr()}; - return {result, errorStatus}; + return {result, rpcTXN_NOT_FOUND}; } // populate transaction data - if (request.binary()) + result.txn = txn; + if (txn->getLedger() == 0) { - Serializer s = stTxn->getSerializer(); - result.set_transaction_binary(s.data(), s.size()); + return {result, rpcSUCCESS}; } - else - { - RPC::populateTransaction(*result.mutable_transaction(), stTxn); - } - - result.set_ledger_index(txn->getLedger()); std::shared_ptr ledger = context.ledgerMaster.getLedgerBySeq(txn->getLedger()); // get meta data if (ledger) { - if (request.binary()) + bool ok = false; + if (args.binary) { SHAMapTreeNode::TNType type; auto const item = ledger->txMap().peekItem(txn->getID(), type); if (item && type == SHAMapTreeNode::tnTRANSACTION_MD) { + ok = true; SerialIter it(item->slice()); it.skip(it.getVLDataLength()); // skip transaction Blob blob = it.getVL(); - Slice slice = makeSlice(blob); - result.set_meta_binary(slice.data(), slice.size()); - - bool validated = isValidated( - context.ledgerMaster, - ledger->info().seq, - ledger->info().hash); - result.set_validated(validated); + result.meta = std::move(blob); } } else @@ -298,25 +190,239 @@ doTxGrpc(RPC::GRPCContext& context) auto rawMeta = ledger->txRead(txn->getID()).second; if (rawMeta) { - auto txMeta = std::make_shared( + ok = true; + result.meta = std::make_shared( txn->getID(), ledger->seq(), *rawMeta); - - bool validated = isValidated( - context.ledgerMaster, - ledger->info().seq, - ledger->info().hash); - result.set_validated(validated); - - RPC::populateMeta(*result.mutable_meta(), txMeta); - insertDeliveredAmount( - *result.mutable_meta()->mutable_delivered_amount(), - context, - txn, - *txMeta); } } + if (ok) + { + result.validated = isValidated( + context.ledgerMaster, ledger->info().seq, ledger->info().hash); + } } - return {result, status}; + + return {result, rpcSUCCESS}; +} + +std::pair +populateProtoResponse( + std::pair const& res, + TxArgs const& args, + RPC::GRPCContext const& context) +{ + org::xrpl::rpc::v1::GetTransactionResponse response; + grpc::Status status = grpc::Status::OK; + RPC::Status const& error = res.second; + TxResult const& result = res.first; + // handle errors + if (error.toErrorCode() != rpcSUCCESS) + { + if (error.toErrorCode() == rpcTXN_NOT_FOUND && + result.searchedAll != SearchedAll::unknown) + { + status = { + grpc::StatusCode::NOT_FOUND, + "txn not found. searched_all = " + + to_string( + (result.searchedAll == SearchedAll::yes ? "true" + : "false"))}; + } + else + { + if (error.toErrorCode() == rpcTXN_NOT_FOUND) + status = {grpc::StatusCode::NOT_FOUND, "txn not found"}; + else + status = {grpc::StatusCode::INTERNAL, error.message()}; + } + } + // no errors + else if (result.txn) + { + auto& txn = result.txn; + + std::shared_ptr stTxn = txn->getSTransaction(); + if (args.binary) + { + Serializer s = stTxn->getSerializer(); + response.set_transaction_binary(s.data(), s.size()); + } + else + { + RPC::convert(*response.mutable_transaction(), stTxn); + } + + response.set_hash(context.params.hash()); + + auto ledgerIndex = txn->getLedger(); + response.set_ledger_index(ledgerIndex); + if (ledgerIndex) + { + auto ct = + context.app.getLedgerMaster().getCloseTimeBySeq(ledgerIndex); + if (ct) + response.mutable_date()->set_value( + ct->time_since_epoch().count()); + } + + RPC::convert( + *response.mutable_meta()->mutable_transaction_result(), + txn->getResult()); + response.mutable_meta()->mutable_transaction_result()->set_result( + transToken(txn->getResult())); + + // populate binary metadata + if (auto blob = std::get_if(&result.meta)) + { + assert(args.binary); + Slice slice = makeSlice(*blob); + response.set_meta_binary(slice.data(), slice.size()); + } + // populate meta data + else if (auto m = std::get_if>(&result.meta)) + { + auto& meta = *m; + if (meta) + { + RPC::convert(*response.mutable_meta(), meta); + auto amt = + getDeliveredAmount(context, stTxn, *meta, txn->getLedger()); + if (amt) + { + RPC::convert( + *response.mutable_meta()->mutable_delivered_amount(), + *amt); + } + } + } + response.set_validated(result.validated); + } + return {response, status}; +} + +Json::Value +populateJsonResponse( + std::pair const& res, + TxArgs const& args, + RPC::JsonContext const& context) +{ + Json::Value response; + RPC::Status const& error = res.second; + TxResult const& result = res.first; + // handle errors + if (error.toErrorCode() != rpcSUCCESS) + { + if (error.toErrorCode() == rpcTXN_NOT_FOUND && + result.searchedAll != SearchedAll::unknown) + { + response = Json::Value(Json::objectValue); + response[jss::searched_all] = + (result.searchedAll == SearchedAll::yes); + error.inject(response); + } + else + { + error.inject(response); + } + } + // no errors + else if (result.txn) + { + response = result.txn->getJson(JsonOptions::include_date, args.binary); + + // populate binary metadata + if (auto blob = std::get_if(&result.meta)) + { + assert(args.binary); + response[jss::meta] = strHex(makeSlice(*blob)); + } + // populate meta data + else if (auto m = std::get_if>(&result.meta)) + { + auto& meta = *m; + if (meta) + { + response[jss::meta] = meta->getJson(JsonOptions::none); + insertDeliveredAmount( + response[jss::meta], context, result.txn, *meta); + } + } + response[jss::validated] = result.validated; + } + return response; +} + +Json::Value +doTxJson(RPC::JsonContext& context) +{ + // Deserialize and validate JSON arguments + + if (!context.params.isMember(jss::transaction)) + return rpcError(rpcINVALID_PARAMS); + + std::string txHash = context.params[jss::transaction].asString(); + if (!isHexTxID(txHash)) + return rpcError(rpcNOT_IMPL); + + TxArgs args; + args.hash = from_hex_text(txHash); + + args.binary = context.params.isMember(jss::binary) && + context.params[jss::binary].asBool(); + + if (context.params.isMember(jss::min_ledger) && + context.params.isMember(jss::max_ledger)) + { + try + { + args.ledgerRange = std::make_pair( + context.params[jss::min_ledger].asUInt(), + context.params[jss::max_ledger].asUInt()); + } + catch (...) + { + // One of the calls to `asUInt ()` failed. + return rpcError(rpcINVALID_LGR_RANGE); + } + } + + std::pair res = doTxHelp(context, args); + return populateJsonResponse(res, args, context); +} + +std::pair +doTxGrpc(RPC::GRPCContext& context) +{ + // return values + org::xrpl::rpc::v1::GetTransactionResponse response; + grpc::Status status = grpc::Status::OK; + + // input + org::xrpl::rpc::v1::GetTransactionRequest& request = context.params; + + TxArgs args; + + std::string const& hashBytes = request.hash(); + args.hash = uint256::fromVoid(hashBytes.data()); + if (args.hash.size() != hashBytes.size()) + { + grpc::Status errorStatus{grpc::StatusCode::INVALID_ARGUMENT, + "ledger hash malformed"}; + return {response, errorStatus}; + } + + args.binary = request.binary(); + + if (request.ledger_range().ledger_index_min() != 0 && + request.ledger_range().ledger_index_max() != 0) + { + args.ledgerRange = std::make_pair( + request.ledger_range().ledger_index_min(), + request.ledger_range().ledger_index_max()); + } + + std::pair res = doTxHelp(context, args); + return populateProtoResponse(res, args, context); } } // namespace ripple diff --git a/src/ripple/rpc/impl/DeliveredAmount.cpp b/src/ripple/rpc/impl/DeliveredAmount.cpp index 29b3a4e86e..6f0ed94be1 100644 --- a/src/ripple/rpc/impl/DeliveredAmount.cpp +++ b/src/ripple/rpc/impl/DeliveredAmount.cpp @@ -39,43 +39,24 @@ namespace RPC { would be calculated even when not needed, and in some circumstances they are not trivial to compute. - GetFix1623Enabled is a callable that returns a bool GetLedgerIndex is a callable that returns a LedgerIndex GetCloseTime is a callable that returns a boost::optional */ -template -void -insertDeliveredAmount( - Json::Value& meta, - GetFix1623Enabled const& getFix1623Enabled, +template +std::optional +getDeliveredAmount( GetLedgerIndex const& getLedgerIndex, GetCloseTime const& getCloseTime, - std::shared_ptr serializedTx, + std::shared_ptr const& serializedTx, TxMeta const& transactionMeta) { - { - TxType const tt{serializedTx->getTxnType()}; - if (tt != ttPAYMENT && - tt != ttCHECK_CASH && - tt != ttACCOUNT_DELETE) - return; - - if (tt == ttCHECK_CASH && - !getFix1623Enabled()) - return; - } - - // if the transaction failed nothing could have been delivered. - if (transactionMeta.getResultTER() != tesSUCCESS) - return; + if (!serializedTx) + return {}; if (transactionMeta.hasDeliveredAmount()) { - meta[jss::delivered_amount] = - transactionMeta.getDeliveredAmount() - .getJson(JsonOptions::include_date); - return; + return transactionMeta.getDeliveredAmount(); } if (serializedTx->isFieldPresent(sfAmount)) @@ -93,156 +74,161 @@ insertDeliveredAmount( if (getLedgerIndex() >= 4594095 || getCloseTime() > NetClock::time_point{446000000s}) { - meta[jss::delivered_amount] = - serializedTx->getFieldAmount(sfAmount) - .getJson(JsonOptions::include_date); - return; + return serializedTx->getFieldAmount(sfAmount); } } - // report "unavailable" which cannot be parsed into a sensible amount. - meta[jss::delivered_amount] = Json::Value("unavailable"); + return {}; +} + +// Returns true if transaction meta could contain a delivered amount field, +// based on transaction type, transaction result and whether fix1623 is enabled +template +bool +canHaveDeliveredAmountHelp( + GetFix1623Enabled const& getFix1623Enabled, + std::shared_ptr const& serializedTx, + TxMeta const& transactionMeta) +{ + if (!serializedTx) + return false; + + { + TxType const tt{serializedTx->getTxnType()}; + if (tt != ttPAYMENT && tt != ttCHECK_CASH && tt != ttACCOUNT_DELETE) + return false; + + if (tt == ttCHECK_CASH && !getFix1623Enabled()) + return false; + } + + // if the transaction failed nothing could have been delivered. + if (transactionMeta.getResultTER() != tesSUCCESS) + return false; + + return true; +} + +// Returns true if transaction meta could contain a delivered amount field, +// based on transaction type, transaction result and whether fix1623 is enabled +bool +canHaveDeliveredAmount( + RPC::Context const& context, + std::shared_ptr const& serializedTx, + TxMeta const& transactionMeta) +{ + // These lambdas are used to compute the values lazily + auto const getFix1623Enabled = [&context]() -> bool { + auto const view = context.app.openLedger().current(); + if (!view) + return false; + return view->rules().enabled(fix1623); + }; + + return canHaveDeliveredAmountHelp( + getFix1623Enabled, serializedTx, transactionMeta); } void insertDeliveredAmount( Json::Value& meta, ReadView const& ledger, - std::shared_ptr serializedTx, + std::shared_ptr const& serializedTx, TxMeta const& transactionMeta) { - if (!serializedTx) - return; - auto const info = ledger.info(); auto const getFix1623Enabled = [&ledger] { return ledger.rules().enabled(fix1623); }; - auto const getLedgerIndex = [&info] { - return info.seq; - }; - auto const getCloseTime = [&info] { - return info.closeTime; - }; - insertDeliveredAmount( - meta, - getFix1623Enabled, - getLedgerIndex, - getCloseTime, - std::move(serializedTx), - transactionMeta); + if (canHaveDeliveredAmountHelp( + getFix1623Enabled, serializedTx, transactionMeta)) + { + auto const getLedgerIndex = [&info] { return info.seq; }; + auto const getCloseTime = [&info] { return info.closeTime; }; + + auto amt = getDeliveredAmount( + getLedgerIndex, + getCloseTime, + std::move(serializedTx), + transactionMeta); + if (amt) + { + meta[jss::delivered_amount] = + amt->getJson(JsonOptions::include_date); + } + else + { + // report "unavailable" which cannot be parsed into a sensible + // amount. + meta[jss::delivered_amount] = Json::Value("unavailable"); + } + } +} + +template +std::optional +getDeliveredAmount( + RPC::Context const& context, + std::shared_ptr const& serializedTx, + TxMeta const& transactionMeta, + GetLedgerIndex const& getLedgerIndex) +{ + if (canHaveDeliveredAmount(context, serializedTx, transactionMeta)) + { + auto const getCloseTime = + [&context, + &getLedgerIndex]() -> boost::optional { + return context.ledgerMaster.getCloseTimeBySeq(getLedgerIndex()); + }; + return getDeliveredAmount( + getLedgerIndex, + getCloseTime, + std::move(serializedTx), + transactionMeta); + } + + return {}; +} + +std::optional +getDeliveredAmount( + RPC::Context const& context, + std::shared_ptr const& serializedTx, + TxMeta const& transactionMeta, + LedgerIndex const& ledgerIndex) +{ + return getDeliveredAmount( + context, serializedTx, transactionMeta, [&ledgerIndex]() { + return ledgerIndex; + }); } void insertDeliveredAmount( Json::Value& meta, - RPC::JsonContext& context, - std::shared_ptr transaction, + RPC::JsonContext const& context, + std::shared_ptr const& transaction, TxMeta const& transactionMeta) { - if (!transaction) - return; - - auto const serializedTx = transaction->getSTransaction (); - if (! serializedTx) - return; - - - // These lambdas are used to compute the values lazily - auto const getFix1623Enabled = [&context]() -> bool { - auto const view = context.app.openLedger().current(); - if (!view) - return false; - return view->rules().enabled(fix1623); - }; - auto const getLedgerIndex = [&transaction]() -> LedgerIndex { - return transaction->getLedger(); - }; - auto const getCloseTime = - [&context, &transaction]() -> boost::optional { - return context.ledgerMaster.getCloseTimeBySeq(transaction->getLedger()); - }; - - insertDeliveredAmount( - meta, - getFix1623Enabled, - getLedgerIndex, - getCloseTime, - std::move(serializedTx), - transactionMeta); -} - -// TODO get rid of the code duplication between this function and the preceding -// function -void -insertDeliveredAmount( - rpc::v1::CurrencyAmount& proto, - RPC::Context& context, - std::shared_ptr transaction, - TxMeta const& transactionMeta) -{ - if (!transaction) - return; - auto const serializedTx = transaction->getSTransaction(); - if (!serializedTx) - return; - - // These lambdas are used to compute the values lazily - auto const getFix1623Enabled = [&context]() -> bool { - auto const view = context.app.openLedger().current(); - if (!view) - return false; - return view->rules().enabled(fix1623); - }; - auto const getLedgerIndex = [&transaction]() -> LedgerIndex { - return transaction->getLedger(); - }; - auto const getCloseTime = - [&context, &transaction]() -> boost::optional { - return context.ledgerMaster.getCloseTimeBySeq(transaction->getLedger()); - }; - + if (canHaveDeliveredAmount(context, serializedTx, transactionMeta)) { - TxType const tt{serializedTx->getTxnType()}; - if (tt != ttPAYMENT && - tt != ttCHECK_CASH && - tt != ttACCOUNT_DELETE) - return; + auto amt = getDeliveredAmount( + context, serializedTx, transactionMeta, [&transaction]() { + return transaction->getLedger(); + }); - if (tt == ttCHECK_CASH && - !getFix1623Enabled()) - return; - } - - // if the transaction failed nothing could have been delivered. - if (transactionMeta.getResultTER() != tesSUCCESS) - return; - - if (transactionMeta.hasDeliveredAmount()) - { - populateAmount(proto, transactionMeta.getDeliveredAmount()); - return; - } - - if (serializedTx->isFieldPresent(sfAmount)) - { - using namespace std::chrono_literals; - - // Ledger 4594095 is the first ledger in which the DeliveredAmount field - // was present when a partial payment was made and its absence indicates - // that the amount delivered is listed in the Amount field. - // - // If the ledger closed long after the DeliveredAmount code was deployed - // then its absence indicates that the amount delivered is listed in the - // Amount field. DeliveredAmount went live January 24, 2014. - // 446000000 is in Feb 2014, well after DeliveredAmount went live - if (getLedgerIndex() >= 4594095 || - getCloseTime() > NetClock::time_point{446000000s}) + if (amt) { - populateAmount(proto, serializedTx->getFieldAmount(sfAmount)); - return; + meta[jss::delivered_amount] = + amt->getJson(JsonOptions::include_date); + } + else + { + // report "unavailable" which cannot be parsed into a sensible + // amount. + meta[jss::delivered_amount] = Json::Value("unavailable"); } } } diff --git a/src/ripple/rpc/impl/GRPCHelpers.cpp b/src/ripple/rpc/impl/GRPCHelpers.cpp new file mode 100644 index 0000000000..1d2f5af69b --- /dev/null +++ b/src/ripple/rpc/impl/GRPCHelpers.cpp @@ -0,0 +1,1779 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2020 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 + +namespace ripple { +namespace RPC { + +// In the below populateProto* functions, getProto is a function that returns +// a reference to the mutable protobuf message to be populated. The reason this +// is a function, as opposed to just a pointer or reference to the object, +// is that there is no way to get a non-const reference, and getting a pointer +// to the proto object causes default initialization of the object. However, +// if the corresponding field is not present in the STObject, we don't want to +// initialize the proto object. To get around this, getProto is a function that +// is called only if the field is present in the STObject +template +void +populateProtoPrimitive( + L const& getProto, + STObject const& from, + TypedField const& field) +{ + if (!from.isFieldPresent(field)) + return; + + if constexpr (std::is_integral_v) + { + getProto()->set_value(from[field]); + } + else + { + auto v = from[field]; + getProto()->set_value(v.data(), v.size()); + } +} + +template +void +populateProtoVLasString( + T const& getProto, + STObject const& from, + SF_Blob const& field) +{ + if (from.isFieldPresent(field)) + { + auto data = from.getFieldVL(field); + getProto()->set_value( + reinterpret_cast(data.data()), data.size()); + } +} + +template +void +populateProtoVec256( + T const& getProto, + STObject const& from, + SF_Vec256 const& field) +{ + if (from.isFieldPresent(field)) + { + const STVector256& vec = from.getFieldV256(field); + for (size_t i = 0; i < vec.size(); ++i) + { + uint256 const& elt = vec[i]; + getProto()->set_value(elt.data(), elt.size()); + } + } +} + +template +void +populateProtoAccount( + T const& getProto, + STObject const& from, + SF_Account const& field) +{ + if (from.isFieldPresent(field)) + { + getProto()->mutable_value()->set_address( + toBase58(from.getAccountID(field))); + } +} + +template +void +populateProtoAmount( + T const& getProto, + STObject const& from, + SF_Amount const& field) +{ + if (from.isFieldPresent(field)) + { + auto amount = from.getFieldAmount(field); + convert(*getProto(), amount); + } +} + +template +void +populateProtoCurrency( + T const& getProto, + STObject const& from, + SF_U160 const& field) +{ + if (from.isFieldPresent(field)) + { + auto cur = from.getFieldH160(field); + auto proto = getProto()->mutable_value(); + proto->set_code(cur.data(), cur.size()); + proto->set_name(to_string(cur)); + } +} + +template +void +populateProtoArray( + T const& getProto, + R const& populateProto, + STObject const& from, + SField const& outerField, + SField const& innerField) +{ + if (from.isFieldPresent(outerField) && + from.peekAtField(outerField).getSType() == SerializedTypeID::STI_ARRAY) + { + auto arr = from.getFieldArray(outerField); + for (auto it = arr.begin(); it != arr.end(); ++it) + { + populateProto(*it, *getProto()); + } + } +} + +template +void +populateClearFlag(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_clear_flag(); }, from, sfClearFlag); +} + +template +void +populateDomain(T& to, STObject const& from) +{ + populateProtoVLasString( + [&to]() { return to.mutable_domain(); }, from, sfDomain); +} + +template +void +populateEmailHash(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_email_hash(); }, from, sfEmailHash); +} + +template +void +populateMessageKey(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_message_key(); }, from, sfMessageKey); +} + +template +void +populateSetFlag(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_set_flag(); }, from, sfSetFlag); +} + +template +void +populateTransferRate(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_transfer_rate(); }, from, sfTransferRate); +} + +template +void +populateTickSize(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_tick_size(); }, from, sfTickSize); +} + +template +void +populateExpiration(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_expiration(); }, from, sfExpiration); +} + +template +void +populateOfferSequence(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_offer_sequence(); }, from, sfOfferSequence); +} + +template +void +populateTakerGets(T& to, STObject const& from) +{ + populateProtoAmount( + [&to]() { return to.mutable_taker_gets(); }, from, sfTakerGets); +} + +template +void +populateTakerPays(T& to, STObject const& from) +{ + populateProtoAmount( + [&to]() { return to.mutable_taker_pays(); }, from, sfTakerPays); +} + +template +void +populateDestination(T& to, STObject const& from) +{ + populateProtoAccount( + [&to]() { return to.mutable_destination(); }, from, sfDestination); +} + +template +void +populateCheckID(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_check_id(); }, from, sfCheckID); +} + +template +void +populateAmount(T& to, STObject const& from) +{ + populateProtoAmount( + [&to]() { return to.mutable_amount(); }, from, sfAmount); +} + +template +void +populateDeliverMin(T& to, STObject const& from) +{ + populateProtoAmount( + [&to]() { return to.mutable_deliver_min(); }, from, sfDeliverMin); +} + +template +void +populateSendMax(T& to, STObject const& from) +{ + populateProtoAmount( + [&to]() { return to.mutable_send_max(); }, from, sfSendMax); +} + +template +void +populateDeliveredAmount(T& to, STObject const& from) +{ + populateProtoAmount( + [&to]() { return to.mutable_delivered_amount(); }, + from, + sfDeliveredAmount); +} + +template +void +populateDestinationTag(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_destination_tag(); }, + from, + sfDestinationTag); +} + +template +void +populateInvoiceID(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_invoice_id(); }, from, sfInvoiceID); +} + +template +void +populateAuthorize(T& to, STObject const& from) +{ + populateProtoAccount( + [&to]() { return to.mutable_authorize(); }, from, sfAuthorize); +} + +template +void +populateUnauthorize(T& to, STObject const& from) +{ + populateProtoAccount( + [&to]() { return to.mutable_unauthorize(); }, from, sfUnauthorize); +} + +template +void +populateOwner(T& to, STObject const& from) +{ + populateProtoAccount([&to]() { return to.mutable_owner(); }, from, sfOwner); +} + +template +void +populateCancelAfter(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_cancel_after(); }, from, sfCancelAfter); +} + +template +void +populateFinishAfter(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_finish_after(); }, from, sfFinishAfter); +} + +template +void +populateCondition(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_condition(); }, from, sfCondition); +} + +template +void +populateFulfillment(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_fulfillment(); }, from, sfFulfillment); +} + +template +void +populateChannel(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_channel(); }, from, sfPayChannel); +} + +template +void +populateBalance(T& to, STObject const& from) +{ + populateProtoAmount( + [&to]() { return to.mutable_balance(); }, from, sfBalance); +} + +template +void +populatePaymentChannelSignature(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_payment_channel_signature(); }, + from, + sfSignature); +} + +template +void +populatePublicKey(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_public_key(); }, from, sfPublicKey); +} + +template +void +populateSettleDelay(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_settle_delay(); }, from, sfSettleDelay); +} + +template +void +populateRegularKey(T& to, STObject const& from) +{ + populateProtoAccount( + [&to]() { return to.mutable_regular_key(); }, from, sfRegularKey); +} + +template +void +populateSignerQuorum(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_signer_quorum(); }, from, sfSignerQuorum); +} +template +void +populateLimitAmount(T& to, STObject const& from) +{ + populateProtoAmount( + [&to]() { return to.mutable_limit_amount(); }, from, sfLimitAmount); +} +template +void +populateQualityIn(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_quality_in(); }, from, sfQualityIn); +} + +template +void +populateQualityOut(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_quality_out(); }, from, sfQualityOut); +} + +template +void +populateAccount(T& to, STObject const& from) +{ + populateProtoAccount( + [&to]() { return to.mutable_account(); }, from, sfAccount); +} + +template +void +populateFee(T& to, STObject const& from) +{ + if (from.isFieldPresent(sfFee)) + { + to.mutable_fee()->set_drops(from.getFieldAmount(sfFee).xrp().drops()); + } +} + +template +void +populateSigningPublicKey(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_signing_public_key(); }, + from, + sfSigningPubKey); +} + +template +void +populateTransactionSignature(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_transaction_signature(); }, + from, + sfTxnSignature); +} + +template +void +populateFlags(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_flags(); }, from, sfFlags); +} + +template +void +populateLastLedgerSequence(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_last_ledger_sequence(); }, + from, + sfLastLedgerSequence); +} + +template +void +populateSourceTag(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_source_tag(); }, from, sfSourceTag); +} + +template +void +populateAccountTransactionID(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_account_transaction_id(); }, + from, + sfAccountTxnID); +} + +template +void +populateMemoData(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_memo_data(); }, from, sfMemoData); +} + +template +void +populateMemoFormat(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_memo_format(); }, from, sfMemoFormat); +} + +template +void +populateMemoType(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_memo_type(); }, from, sfMemoType); +} + +template +void +populateSequence(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_sequence(); }, from, sfSequence); +} + +template +void +populateAmendment(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_amendment(); }, from, sfAmendment); +} + +template +void +populateCloseTime(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_close_time(); }, from, sfCloseTime); +} + +template +void +populateSignerWeight(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_signer_weight(); }, from, sfSignerWeight); +} + +template +void +populateAmendments(T& to, STObject const& from) +{ + populateProtoVec256( + [&to]() { return to.add_amendments(); }, from, sfAmendments); +} + +template +void +populateOwnerCount(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_owner_count(); }, from, sfOwnerCount); +} + +template +void +populatePreviousTransactionID(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_previous_transaction_id(); }, + from, + sfPreviousTxnID); +} + +template +void +populatePreviousTransactionLedgerSequence(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_previous_transaction_ledger_sequence(); }, + from, + sfPreviousTxnLgrSeq); +} + +template +void +populateLowLimit(T& to, STObject const& from) +{ + populateProtoAmount( + [&to]() { return to.mutable_low_limit(); }, from, sfLowLimit); +} + +template +void +populateHighLimit(T& to, STObject const& from) +{ + populateProtoAmount( + [&to]() { return to.mutable_high_limit(); }, from, sfHighLimit); +} + +template +void +populateLowNode(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_low_node(); }, from, sfLowNode); +} + +template +void +populateHighNode(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_high_node(); }, from, sfHighNode); +} + +template +void +populateLowQualityIn(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_low_quality_in(); }, from, sfLowQualityIn); +} + +template +void +populateLowQualityOut(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_low_quality_out(); }, + from, + sfLowQualityOut); +} + +template +void +populateHighQualityIn(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_high_quality_in(); }, + from, + sfHighQualityIn); +} + +template +void +populateHighQualityOut(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_high_quality_out(); }, + from, + sfHighQualityOut); +} + +template +void +populateBookDirectory(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_book_directory(); }, from, sfBookDirectory); +} + +template +void +populateBookNode(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_book_node(); }, from, sfBookNode); +} + +template +void +populateOwnerNode(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_owner_node(); }, from, sfOwnerNode); +} + +template +void +populateSignerListID(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_signer_list_id(); }, from, sfSignerListID); +} + +template +void +populateHashes(T& to, STObject const& from) +{ + populateProtoVec256([&to]() { return to.add_hashes(); }, from, sfHashes); +} + +template +void +populateIndexes(T& to, STObject const& from) +{ + populateProtoVec256([&to]() { return to.add_indexes(); }, from, sfIndexes); +} + +template +void +populateRootIndex(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_root_index(); }, from, sfRootIndex); +} + +template +void +populateIndexNext(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_index_next(); }, from, sfIndexNext); +} + +template +void +populateIndexPrevious(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_index_previous(); }, from, sfIndexPrevious); +} + +template +void +populateTakerPaysCurrency(T& to, STObject const& from) +{ + populateProtoCurrency( + [&to]() { return to.mutable_taker_pays_currency(); }, + from, + sfTakerPaysCurrency); +} + +template +void +populateTakerPaysIssuer(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_taker_pays_issuer(); }, + from, + sfTakerPaysIssuer); +} + +template +void +populateTakerGetsCurrency(T& to, STObject const& from) +{ + populateProtoCurrency( + [&to]() { return to.mutable_taker_gets_currency(); }, + from, + sfTakerGetsCurrency); +} + +template +void +populateTakerGetsIssuer(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_taker_gets_issuer(); }, + from, + sfTakerGetsIssuer); +} + +template +void +populateDestinationNode(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_destination_node(); }, + from, + sfDestinationNode); +} + +template +void +populateBaseFee(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_base_fee(); }, from, sfBaseFee); +} + +template +void +populateReferenceFeeUnits(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_reference_fee_units(); }, + from, + sfReferenceFeeUnits); +} + +template +void +populateReserveBase(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_reserve_base(); }, from, sfReserveBase); +} + +template +void +populateReserveIncrement(T& to, STObject const& from) +{ + populateProtoPrimitive( + [&to]() { return to.mutable_reserve_increment(); }, + from, + sfReserveIncrement); +} + +template +void +populateSignerEntries(T& to, STObject const& from) +{ + populateProtoArray( + [&to]() { return to.add_signer_entries(); }, + [](auto& innerObj, auto& innerProto) { + populateAccount(innerProto, innerObj); + populateSignerWeight(innerProto, innerObj); + }, + from, + sfSignerEntries, + sfSignerEntry); +} + +template +void +populateMemos(T& to, STObject const& from) +{ + populateProtoArray( + [&to]() { return to.add_memos(); }, + [](auto& innerObj, auto& innerProto) { + populateMemoData(innerProto, innerObj); + populateMemoType(innerProto, innerObj); + populateMemoFormat(innerProto, innerObj); + }, + from, + sfMemos, + sfMemo); +} + +template +void +populateSigners(T& to, STObject const& from) +{ + populateProtoArray( + [&to]() { return to.add_signers(); }, + [](auto& innerObj, auto& innerProto) { + populateAccount(innerProto, innerObj); + populateTransactionSignature(innerProto, innerObj); + populateSigningPublicKey(innerProto, innerObj); + }, + from, + sfSigners, + sfSigner); +} + +template +void +populateMajorities(T& to, STObject const& from) +{ + populateProtoArray( + [&to]() { return to.add_majorities(); }, + [](auto innerObj, auto innerProto) { + populateAmendment(innerProto, innerObj); + populateCloseTime(innerProto, innerObj); + }, + from, + sfMajorities, + sfMajority); +} + +void +convert(org::xrpl::rpc::v1::TransactionResult& to, TER from) +{ + if (isTecClaim(from)) + { + to.set_result_type( + org::xrpl::rpc::v1::TransactionResult::RESULT_TYPE_TEC); + } + if (isTefFailure(from)) + { + to.set_result_type( + org::xrpl::rpc::v1::TransactionResult::RESULT_TYPE_TEF); + } + if (isTelLocal(from)) + { + to.set_result_type( + org::xrpl::rpc::v1::TransactionResult::RESULT_TYPE_TEL); + } + if (isTemMalformed(from)) + { + to.set_result_type( + org::xrpl::rpc::v1::TransactionResult::RESULT_TYPE_TEM); + } + if (isTerRetry(from)) + { + to.set_result_type( + org::xrpl::rpc::v1::TransactionResult::RESULT_TYPE_TER); + } + if (isTesSuccess(from)) + { + to.set_result_type( + org::xrpl::rpc::v1::TransactionResult::RESULT_TYPE_TES); + } +} + +void +convert(org::xrpl::rpc::v1::AccountSet& to, STObject const& from) +{ + populateClearFlag(to, from); + + populateDomain(to, from); + + populateEmailHash(to, from); + + populateMessageKey(to, from); + + populateSetFlag(to, from); + + populateTransferRate(to, from); + + populateTickSize(to, from); +} + +void +convert(org::xrpl::rpc::v1::OfferCreate& to, STObject const& from) +{ + populateExpiration(to, from); + + populateOfferSequence(to, from); + + populateTakerGets(to, from); + + populateTakerPays(to, from); +} + +void +convert(org::xrpl::rpc::v1::OfferCancel& to, STObject const& from) +{ + populateOfferSequence(to, from); +} + +void +convert(org::xrpl::rpc::v1::AccountDelete& to, STObject const& from) +{ + populateDestination(to, from); +} + +void +convert(org::xrpl::rpc::v1::CheckCancel& to, STObject const& from) +{ + populateCheckID(to, from); +} + +void +convert(org::xrpl::rpc::v1::CheckCash& to, STObject const& from) +{ + populateCheckID(to, from); + + populateAmount(to, from); + + populateDeliverMin(to, from); +} + +void +convert(org::xrpl::rpc::v1::CheckCreate& to, STObject const& from) +{ + populateDestination(to, from); + + populateSendMax(to, from); + + populateDestinationTag(to, from); + + populateExpiration(to, from); + + populateInvoiceID(to, from); +} + +void +convert(org::xrpl::rpc::v1::DepositPreauth& to, STObject const& from) +{ + populateAuthorize(to, from); + + populateUnauthorize(to, from); +} + +void +convert(org::xrpl::rpc::v1::EscrowCancel& to, STObject const& from) +{ + populateOwner(to, from); + + populateOfferSequence(to, from); +} + +void +convert(org::xrpl::rpc::v1::EscrowCreate& to, STObject const& from) +{ + populateAmount(to, from); + + populateDestination(to, from); + + populateCancelAfter(to, from); + + populateFinishAfter(to, from); + + populateCondition(to, from); + + populateDestinationTag(to, from); +} + +void +convert(org::xrpl::rpc::v1::EscrowFinish& to, STObject const& from) +{ + populateOwner(to, from); + + populateOfferSequence(to, from); + + populateCondition(to, from); + + populateFulfillment(to, from); +} + +void +convert(org::xrpl::rpc::v1::PaymentChannelClaim& to, STObject const& from) +{ + populateChannel(to, from); + + populateBalance(to, from); + + populateAmount(to, from); + + populatePaymentChannelSignature(to, from); + + populatePublicKey(to, from); +} + +void +convert(org::xrpl::rpc::v1::PaymentChannelCreate& to, STObject const& from) +{ + populateAmount(to, from); + + populateDestination(to, from); + + populateSettleDelay(to, from); + + populatePublicKey(to, from); + + populateCancelAfter(to, from); + + populateDestinationTag(to, from); +} + +void +convert(org::xrpl::rpc::v1::PaymentChannelFund& to, STObject const& from) +{ + populateChannel(to, from); + + populateAmount(to, from); + + populateExpiration(to, from); +} + +void +convert(org::xrpl::rpc::v1::SetRegularKey& to, STObject const& from) +{ + populateRegularKey(to, from); +} + +void +convert(org::xrpl::rpc::v1::SignerListSet& to, STObject const& from) +{ + populateSignerQuorum(to, from); + + populateSignerEntries(to, from); +} + +void +convert(org::xrpl::rpc::v1::TrustSet& to, STObject const& from) +{ + populateLimitAmount(to, from); + + populateQualityIn(to, from); + + populateQualityOut(to, from); +} + +void +convert(org::xrpl::rpc::v1::Payment& to, STObject const& from) +{ + populateAmount(to, from); + + populateDestination(to, from); + + populateDestinationTag(to, from); + + populateInvoiceID(to, from); + + populateSendMax(to, from); + + populateDeliverMin(to, from); + + if (from.isFieldPresent(sfPaths)) + { + // populate path data + STPathSet const& pathset = from.getFieldPathSet(sfPaths); + for (auto it = pathset.begin(); it < pathset.end(); ++it) + { + STPath const& path = *it; + + org::xrpl::rpc::v1::Payment_Path* protoPath = to.add_paths(); + + for (auto it2 = path.begin(); it2 != path.end(); ++it2) + { + org::xrpl::rpc::v1::Payment_PathElement* protoElement = + protoPath->add_elements(); + STPathElement const& elt = *it2; + + if (elt.isOffer()) + { + if (elt.hasCurrency()) + { + Currency const& currency = elt.getCurrency(); + protoElement->mutable_currency()->set_name( + to_string(currency)); + } + if (elt.hasIssuer()) + { + AccountID const& issuer = elt.getIssuerID(); + protoElement->mutable_issuer()->set_address( + toBase58(issuer)); + } + } + else if (elt.isAccount()) + { + AccountID const& pathAccount = elt.getAccountID(); + protoElement->mutable_account()->set_address( + toBase58(pathAccount)); + } + } + } + } +} + +void +convert(org::xrpl::rpc::v1::AccountRoot& to, STObject const& from) +{ + populateAccount(to, from); + + populateBalance(to, from); + + populateSequence(to, from); + + populateFlags(to, from); + + populateOwnerCount(to, from); + + populatePreviousTransactionID(to, from); + + populatePreviousTransactionLedgerSequence(to, from); + + populateAccountTransactionID(to, from); + + populateDomain(to, from); + + populateEmailHash(to, from); + + populateMessageKey(to, from); + + populateRegularKey(to, from); + + populateTickSize(to, from); + + populateTransferRate(to, from); +} + +void +convert(org::xrpl::rpc::v1::Amendments& to, STObject const& from) +{ + populateAmendments(to, from); + + populateMajorities(to, from); +} + +void +convert(org::xrpl::rpc::v1::Check& to, STObject const& from) +{ + populateAccount(to, from); + + populateDestination(to, from); + + populateFlags(to, from); + + populateOwnerNode(to, from); + + populatePreviousTransactionID(to, from); + + populatePreviousTransactionLedgerSequence(to, from); + + populateSendMax(to, from); + + populateSequence(to, from); + + populateDestinationNode(to, from); + + populateDestinationTag(to, from); + + populateExpiration(to, from); + + populateInvoiceID(to, from); + + populateSourceTag(to, from); +} + +void +convert(org::xrpl::rpc::v1::DepositPreauthObject& to, STObject const& from) +{ + populateAccount(to, from); + + populateAuthorize(to, from); + + populateFlags(to, from); + + populateOwnerNode(to, from); + + populatePreviousTransactionID(to, from); + + populatePreviousTransactionLedgerSequence(to, from); +} + +void +convert(org::xrpl::rpc::v1::FeeSettings& to, STObject const& from) +{ + populateBaseFee(to, from); + + populateReferenceFeeUnits(to, from); + + populateReserveBase(to, from); + + populateReserveIncrement(to, from); + + populateFlags(to, from); +} + +void +convert(org::xrpl::rpc::v1::Escrow& to, STObject const& from) +{ + populateAccount(to, from); + + populateDestination(to, from); + + populateAmount(to, from); + + populateCondition(to, from); + + populateCancelAfter(to, from); + + populateFinishAfter(to, from); + + populateFlags(to, from); + + populateSourceTag(to, from); + + populateDestinationTag(to, from); + + populateOwnerNode(to, from); + + populateDestinationNode(to, from); + + populatePreviousTransactionID(to, from); + + populatePreviousTransactionLedgerSequence(to, from); +} + +void +convert(org::xrpl::rpc::v1::LedgerHashes& to, STObject const& from) +{ + populateLastLedgerSequence(to, from); + + populateHashes(to, from); + + populateFlags(to, from); +} + +void +convert(org::xrpl::rpc::v1::PayChannel& to, STObject const& from) +{ + populateAccount(to, from); + + populateAmount(to, from); + + populateBalance(to, from); + + populatePublicKey(to, from); + + populateSettleDelay(to, from); + + populateOwnerNode(to, from); + + populatePreviousTransactionID(to, from); + + populatePreviousTransactionLedgerSequence(to, from); + + populateFlags(to, from); + + populateExpiration(to, from); + + populateCancelAfter(to, from); + + populateSourceTag(to, from); + + populateDestinationTag(to, from); +} + +void +convert(org::xrpl::rpc::v1::DirectoryNode& to, STObject const& from) +{ + populateFlags(to, from); + + populateRootIndex(to, from); + + populateIndexes(to, from); + + populateIndexNext(to, from); + + populateIndexPrevious(to, from); + + populateTakerPaysIssuer(to, from); + + populateTakerPaysCurrency(to, from); + + populateTakerGetsCurrency(to, from); + + populateTakerGetsIssuer(to, from); +} + +void +convert(org::xrpl::rpc::v1::Offer& to, STObject const& from) +{ + populateAccount(to, from); + + populateSequence(to, from); + + populateFlags(to, from); + + populateTakerPays(to, from); + + populateTakerGets(to, from); + + populateBookDirectory(to, from); + + populateBookNode(to, from); +} + +void +convert(org::xrpl::rpc::v1::RippleState& to, STObject const& from) +{ + populateBalance(to, from); + + populateFlags(to, from); + + populateLowNode(to, from); + + populateHighNode(to, from); + + populateLowQualityIn(to, from); + + populateLowQualityOut(to, from); + + populateHighQualityIn(to, from); + + populateHighQualityOut(to, from); + + populatePreviousTransactionID(to, from); + + populatePreviousTransactionLedgerSequence(to, from); +} + +void +convert(org::xrpl::rpc::v1::SignerList& to, STObject const& from) +{ + populateFlags(to, from); + + populatePreviousTransactionID(to, from); + + populatePreviousTransactionLedgerSequence(to, from); + + populateOwnerNode(to, from); + + populateSignerEntries(to, from); + + populateSignerQuorum(to, from); + + populateSignerListID(to, from); +} + +void +setLedgerEntryType( + org::xrpl::rpc::v1::AffectedNode& proto, + std::uint16_t lgrType) +{ + switch (lgrType) + { + case ltACCOUNT_ROOT: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_ACCOUNT_ROOT); + break; + case ltDIR_NODE: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_DIRECTORY_NODE); + break; + case ltRIPPLE_STATE: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_RIPPLE_STATE); + break; + case ltSIGNER_LIST: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_SIGNER_LIST); + break; + case ltOFFER: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_OFFER); + break; + case ltLEDGER_HASHES: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_LEDGER_HASHES); + break; + case ltAMENDMENTS: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_AMENDMENTS); + break; + case ltFEE_SETTINGS: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_FEE_SETTINGS); + break; + case ltESCROW: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_ESCROW); + break; + case ltPAYCHAN: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_PAY_CHANNEL); + break; + case ltCHECK: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_CHECK); + break; + case ltDEPOSIT_PREAUTH: + proto.set_ledger_entry_type( + org::xrpl::rpc::v1::LEDGER_ENTRY_TYPE_DEPOSIT_PREAUTH); + break; + } +} + +template +void +convert(T& to, STObject& from, std::uint16_t type) +{ + switch (type) + { + case ltACCOUNT_ROOT: + RPC::convert(*to.mutable_account_root(), from); + break; + case ltAMENDMENTS: + RPC::convert(*to.mutable_amendments(), from); + break; + case ltDIR_NODE: + RPC::convert(*to.mutable_directory_node(), from); + break; + case ltRIPPLE_STATE: + RPC::convert(*to.mutable_ripple_state(), from); + break; + case ltSIGNER_LIST: + RPC::convert(*to.mutable_signer_list(), from); + break; + case ltOFFER: + RPC::convert(*to.mutable_offer(), from); + break; + case ltLEDGER_HASHES: + RPC::convert(*to.mutable_ledger_hashes(), from); + break; + case ltFEE_SETTINGS: + RPC::convert(*to.mutable_fee_settings(), from); + break; + case ltESCROW: + RPC::convert(*to.mutable_escrow(), from); + break; + case ltPAYCHAN: + RPC::convert(*to.mutable_pay_channel(), from); + break; + case ltCHECK: + RPC::convert(*to.mutable_check(), from); + break; + case ltDEPOSIT_PREAUTH: + RPC::convert(*to.mutable_deposit_preauth(), from); + break; + } +} + +template +void +populateFields( + T const& getProto, + STObject& obj, + SField const& field, + uint16_t lgrType) +{ + // final fields + if (obj.isFieldPresent(field)) + { + STObject& data = obj.getField(field).downcast(); + + convert(*getProto(), data, lgrType); + } +} + +template +void +populateFinalFields(T const& getProto, STObject& obj, uint16_t lgrType) +{ + populateFields(getProto, obj, sfFinalFields, lgrType); +} + +template +void +populatePreviousFields(T const& getProto, STObject& obj, uint16_t lgrType) +{ + populateFields(getProto, obj, sfPreviousFields, lgrType); +} + +template +void +populateNewFields(T const& getProto, STObject& obj, uint16_t lgrType) +{ + populateFields(getProto, obj, sfNewFields, lgrType); +} + +void +convert(org::xrpl::rpc::v1::Meta& to, std::shared_ptr const& from) +{ + to.set_transaction_index(from->getIndex()); + + convert(*to.mutable_transaction_result(), from->getResultTER()); + to.mutable_transaction_result()->set_result( + transToken(from->getResultTER())); + + STArray& nodes = from->getNodes(); + for (auto it = nodes.begin(); it != nodes.end(); ++it) + { + STObject& obj = *it; + org::xrpl::rpc::v1::AffectedNode* node = to.add_affected_nodes(); + + // ledger index + uint256 ledgerIndex = obj.getFieldH256(sfLedgerIndex); + node->set_ledger_index(ledgerIndex.data(), ledgerIndex.size()); + + // ledger entry type + std::uint16_t lgrType = obj.getFieldU16(sfLedgerEntryType); + setLedgerEntryType(*node, lgrType); + + // modified node + if (obj.getFName() == sfModifiedNode) + { + populateFinalFields( + [&node]() { + return node->mutable_modified_node() + ->mutable_final_fields(); + }, + obj, + lgrType); + + populatePreviousFields( + [&node]() { + return node->mutable_modified_node() + ->mutable_previous_fields(); + }, + obj, + lgrType); + + populatePreviousTransactionID(*node->mutable_modified_node(), obj); + + populatePreviousTransactionLedgerSequence( + *node->mutable_modified_node(), obj); + } + // created node + else if (obj.getFName() == sfCreatedNode) + { + populateNewFields( + [&node]() { + return node->mutable_created_node()->mutable_new_fields(); + }, + obj, + lgrType); + } + // deleted node + else if (obj.getFName() == sfDeletedNode) + { + populateFinalFields( + [&node]() { + return node->mutable_deleted_node()->mutable_final_fields(); + }, + obj, + lgrType); + } + } +} + +void +convert( + org::xrpl::rpc::v1::QueueData& to, + std::map const& from) +{ + if (!from.empty()) + { + to.set_txn_count(from.size()); + to.set_lowest_sequence(from.begin()->first); + to.set_highest_sequence(from.rbegin()->first); + + boost::optional anyAuthChanged(false); + boost::optional totalSpend(0); + + for (auto const& [txSeq, txDetails] : from) + { + org::xrpl::rpc::v1::QueuedTransaction& qt = *to.add_transactions(); + + qt.mutable_sequence()->set_value(txSeq); + qt.set_fee_level(txDetails.feeLevel.fee()); + if (txDetails.lastValid) + qt.mutable_last_ledger_sequence()->set_value( + *txDetails.lastValid); + + if (txDetails.consequences) + { + qt.mutable_fee()->set_drops( + txDetails.consequences->fee.drops()); + auto spend = txDetails.consequences->potentialSpend + + txDetails.consequences->fee; + qt.mutable_max_spend_drops()->set_drops(spend.drops()); + if (totalSpend) + *totalSpend += spend; + auto authChanged = + txDetails.consequences->category == TxConsequences::blocker; + if (authChanged) + anyAuthChanged.emplace(authChanged); + qt.set_auth_change(authChanged); + } + else + { + if (anyAuthChanged && !*anyAuthChanged) + anyAuthChanged.reset(); + totalSpend.reset(); + } + } + + if (anyAuthChanged) + to.set_auth_change_queued(*anyAuthChanged); + if (totalSpend) + to.mutable_max_spend_drops_total()->set_drops( + (*totalSpend).drops()); + } +} + +void +convert( + org::xrpl::rpc::v1::Transaction& to, + std::shared_ptr const& from) +{ + STObject const& fromObj = *from; + + populateAccount(to, fromObj); + + populateFee(to, fromObj); + + populateSequence(to, fromObj); + + populateSigningPublicKey(to, fromObj); + + populateTransactionSignature(to, fromObj); + + populateFlags(to, fromObj); + + populateLastLedgerSequence(to, fromObj); + + populateSourceTag(to, fromObj); + + populateAccountTransactionID(to, fromObj); + + populateMemos(to, fromObj); + + populateSigners(to, fromObj); + + auto type = safe_cast(fromObj.getFieldU16(sfTransactionType)); + + switch (type) + { + case TxType::ttPAYMENT: + convert(*to.mutable_payment(), fromObj); + break; + case TxType::ttESCROW_CREATE: + convert(*to.mutable_escrow_create(), fromObj); + break; + case TxType::ttESCROW_FINISH: + convert(*to.mutable_escrow_finish(), fromObj); + break; + case TxType::ttACCOUNT_SET: + convert(*to.mutable_account_set(), fromObj); + break; + case TxType::ttESCROW_CANCEL: + convert(*to.mutable_escrow_cancel(), fromObj); + break; + case TxType::ttREGULAR_KEY_SET: + convert(*to.mutable_set_regular_key(), fromObj); + break; + case TxType::ttOFFER_CREATE: + convert(*to.mutable_offer_create(), fromObj); + break; + case TxType::ttOFFER_CANCEL: + convert(*to.mutable_offer_cancel(), fromObj); + break; + case TxType::ttSIGNER_LIST_SET: + convert(*to.mutable_signer_list_set(), fromObj); + break; + case TxType::ttPAYCHAN_CREATE: + convert(*to.mutable_payment_channel_create(), fromObj); + break; + case TxType::ttPAYCHAN_FUND: + convert(*to.mutable_payment_channel_fund(), fromObj); + break; + case TxType::ttPAYCHAN_CLAIM: + convert(*to.mutable_payment_channel_claim(), fromObj); + break; + case TxType::ttCHECK_CREATE: + convert(*to.mutable_check_create(), fromObj); + break; + case TxType::ttCHECK_CASH: + convert(*to.mutable_check_cash(), fromObj); + break; + case TxType::ttCHECK_CANCEL: + convert(*to.mutable_check_cancel(), fromObj); + break; + case TxType::ttDEPOSIT_PREAUTH: + convert(*to.mutable_deposit_preauth(), fromObj); + break; + case TxType::ttTRUST_SET: + convert(*to.mutable_trust_set(), fromObj); + break; + case TxType::ttACCOUNT_DELETE: + convert(*to.mutable_account_delete(), fromObj); + break; + default: + break; + } +} + +} // namespace RPC +} // namespace ripple diff --git a/src/ripple/rpc/impl/GRPCHelpers.h b/src/ripple/rpc/impl/GRPCHelpers.h new file mode 100644 index 0000000000..bc856431da --- /dev/null +++ b/src/ripple/rpc/impl/GRPCHelpers.h @@ -0,0 +1,87 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2020 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_RPC_GRPCHELPERS_H_INCLUDED +#define RIPPLE_RPC_GRPCHELPERS_H_INCLUDED + +#include "org/xrpl/rpc/v1/get_account_info.pb.h" +#include "org/xrpl/rpc/v1/ledger_objects.pb.h" +#include "org/xrpl/rpc/v1/meta.pb.h" +#include "org/xrpl/rpc/v1/transaction.pb.h" + +#include +#include +#include +#include +#include + +#include + +namespace ripple { +namespace RPC { + +void +convert(org::xrpl::rpc::v1::Meta& to, std::shared_ptr const& from); + +void +convert( + org::xrpl::rpc::v1::QueueData& to, + std::map const& from); + +void +convert( + org::xrpl::rpc::v1::Transaction& to, + std::shared_ptr const& from); + +void +convert(org::xrpl::rpc::v1::TransactionResult& to, TER from); + +void +convert(org::xrpl::rpc::v1::AccountRoot& to, STObject const& from); + +void +convert(org::xrpl::rpc::v1::SignerList& to, STObject const& from); + +template +void +convert(T& to, STAmount const& from) +{ + if (from.native()) + { + to.mutable_value()->mutable_xrp_amount()->set_drops(from.xrp().drops()); + } + else + { + Issue const& issue = from.issue(); + + org::xrpl::rpc::v1::IssuedCurrencyAmount* issued = + to.mutable_value()->mutable_issued_currency_amount(); + + issued->mutable_currency()->set_name(to_string(issue.currency)); + issued->mutable_currency()->set_code( + issue.currency.data(), Currency::size()); + issued->mutable_issuer()->set_address(toBase58(issue.account)); + issued->set_value(to_string(from.iou())); + } +} + +} // namespace RPC +} // namespace ripple + +#endif diff --git a/src/ripple/rpc/impl/Handler.cpp b/src/ripple/rpc/impl/Handler.cpp index 06325247ed..bfc6d7681c 100644 --- a/src/ripple/rpc/impl/Handler.cpp +++ b/src/ripple/rpc/impl/Handler.cpp @@ -65,7 +65,7 @@ Handler const handlerArray[] { { "account_channels", byRef (&doAccountChannels), Role::USER, NO_CONDITION }, { "account_objects", byRef (&doAccountObjects), Role::USER, NO_CONDITION }, { "account_offers", byRef (&doAccountOffers), Role::USER, NO_CONDITION }, - { "account_tx", byRef (&doAccountTxSwitch), Role::USER, NO_CONDITION }, + { "account_tx", byRef (&doAccountTxJson), Role::USER, NO_CONDITION }, { "blacklist", byRef (&doBlackList), Role::ADMIN, NO_CONDITION }, { "book_offers", byRef (&doBookOffers), Role::USER, NO_CONDITION }, { "can_delete", byRef (&doCanDelete), Role::ADMIN, NO_CONDITION }, @@ -112,7 +112,7 @@ Handler const handlerArray[] { { "crawl_shards", byRef (&doCrawlShards), Role::ADMIN, NO_CONDITION }, { "stop", byRef (&doStop), Role::ADMIN, NO_CONDITION }, { "transaction_entry", byRef (&doTransactionEntry), Role::USER, NO_CONDITION }, - { "tx", byRef (&doTx), Role::USER, NEEDS_NETWORK_CONNECTION }, + { "tx", byRef (&doTxJson), Role::USER, NEEDS_NETWORK_CONNECTION }, { "tx_history", byRef (&doTxHistory), Role::USER, NO_CONDITION }, { "unl_list", byRef (&doUnlList), Role::ADMIN, NO_CONDITION }, { "validation_create", byRef (&doValidationCreate), Role::ADMIN, NO_CONDITION }, diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/ripple/rpc/impl/RPCHelpers.cpp index d28c4ca3db..fcc77fb90e 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/ripple/rpc/impl/RPCHelpers.cpp @@ -29,6 +29,8 @@ #include #include +#include + namespace ripple { namespace RPC { @@ -199,12 +201,10 @@ template Status ledgerFromRequest(T& ledger, JsonContext& context) { - static auto const minSequenceGap = 10; ledger.reset(); auto& params = context.params; - auto& ledgerMaster = context.ledgerMaster; auto indexValue = params[jss::ledger_index]; auto hashValue = params[jss::ledger_hash]; @@ -225,74 +225,32 @@ ledgerFromRequest(T& ledger, JsonContext& context) return {rpcINVALID_PARAMS, "ledgerHashNotString"}; uint256 ledgerHash; - if (! ledgerHash.SetHex (hashValue.asString ())) + if(!ledgerHash.SetHex (hashValue.asString ())) return {rpcINVALID_PARAMS, "ledgerHashMalformed"}; - - ledger = ledgerMaster.getLedgerByHash (ledgerHash); - if (ledger == nullptr) - return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; + return getLedger(ledger, ledgerHash, context); } else if (indexValue.isNumeric()) { - ledger = ledgerMaster.getLedgerBySeq (indexValue.asInt ()); - - if (ledger == nullptr) - { - auto cur = ledgerMaster.getCurrentLedger(); - if (cur->info().seq == indexValue.asInt()) - ledger = cur; - } - - if (ledger == nullptr) - return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; - - if (ledger->info().seq > ledgerMaster.getValidLedgerIndex() && - isValidatedOld(ledgerMaster, context.app.config().standalone())) - { - ledger.reset(); - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - } + return getLedger(ledger, indexValue.asInt(),context); } else { - if (isValidatedOld (ledgerMaster, context.app.config().standalone())) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; auto const index = indexValue.asString (); if (index == "validated") { - ledger = ledgerMaster.getValidatedLedger (); - if (ledger == nullptr) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - - assert (! ledger->open()); + return getLedger(ledger, LedgerShortcut::VALIDATED, context); } else { if (index.empty () || index == "current") - { - ledger = ledgerMaster.getCurrentLedger (); - assert (ledger->open()); - } + return getLedger(ledger, LedgerShortcut::CURRENT, context); else if (index == "closed") - { - ledger = ledgerMaster.getClosedLedger (); - assert (! ledger->open()); - } + return getLedger(ledger, LedgerShortcut::CLOSED, context); else { return {rpcINVALID_PARAMS, "ledgerIndexMalformed"}; } - - if (ledger == nullptr) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - - if (ledger->info().seq + minSequenceGap < - ledgerMaster.getValidLedgerIndex ()) - { - ledger.reset (); - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - } } } @@ -304,88 +262,49 @@ template Status ledgerFromRequest( T& ledger, - GRPCContext& context) + GRPCContext& context) { - static auto const minSequenceGap = 10; - ledger.reset(); - rpc::v1::GetAccountInfoRequest& request = context.params; - auto& ledgerMaster = context.ledgerMaster; + org::xrpl::rpc::v1::GetAccountInfoRequest& request = context.params; - using LedgerCase = rpc::v1::LedgerSpecifier::LedgerCase; + using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase; LedgerCase ledgerCase = request.ledger().ledger_case(); - - if (ledgerCase == LedgerCase::kHash) + switch (ledgerCase) { - uint256 ledgerHash = uint256::fromVoid(request.ledger().hash().data()); - if (ledgerHash.size() != request.ledger().hash().size()) - return {rpcINVALID_PARAMS, "ledgerHashMalformed"}; - - ledger = ledgerMaster.getLedgerByHash(ledgerHash); - if (ledger == nullptr) - return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; - } - else if (ledgerCase == LedgerCase::kSequence) - { - ledger = ledgerMaster.getLedgerBySeq(request.ledger().sequence()); - - if (ledger == nullptr) + case LedgerCase::kHash: { - auto cur = ledgerMaster.getCurrentLedger(); - if (cur->info().seq == request.ledger().sequence()) - ledger = cur; + uint256 ledgerHash = + uint256::fromVoid(request.ledger().hash().data()); + return getLedger(ledger, ledgerHash, context); } - - if (ledger == nullptr) - return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; - - if (ledger->info().seq > ledgerMaster.getValidLedgerIndex() && - isValidatedOld(ledgerMaster, context.app.config().standalone())) + case LedgerCase::kSequence: + return getLedger(ledger, request.ledger().sequence(), context); + case LedgerCase::kShortcut: + [[fallthrough]]; + case LedgerCase::LEDGER_NOT_SET: { - ledger.reset(); - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - } - } - else if ( - ledgerCase == LedgerCase::kShortcut || - ledgerCase == LedgerCase::LEDGER_NOT_SET) - { - if (isValidatedOld(ledgerMaster, context.app.config().standalone())) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - - auto const shortcut = request.ledger().shortcut(); - if (shortcut == rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED) - { - ledger = ledgerMaster.getValidatedLedger(); - if (ledger == nullptr) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - - assert(!ledger->open()); - } - else - { - // note, if unspecified, defaults to current ledger - if (shortcut == rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED || - shortcut == rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT) + auto const shortcut = request.ledger().shortcut(); + if (shortcut == + org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED) + return getLedger(ledger, LedgerShortcut::VALIDATED, context); + else { - ledger = ledgerMaster.getCurrentLedger(); - assert(ledger->open()); - } - else if (shortcut == rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED) - { - ledger = ledgerMaster.getClosedLedger(); - assert(!ledger->open()); - } - - if (ledger == nullptr) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - - if (ledger->info().seq + minSequenceGap < - ledgerMaster.getValidLedgerIndex()) - { - ledger.reset(); - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + // note, if unspecified, defaults to current ledger + if (shortcut == + org::xrpl::rpc::v1::LedgerSpecifier:: + SHORTCUT_UNSPECIFIED || + shortcut == + org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT) + { + return getLedger(ledger, LedgerShortcut::CURRENT, context); + } + else if ( + shortcut == + org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED) + { + return getLedger(ledger, LedgerShortcut::CLOSED, context); + } } } } @@ -397,7 +316,103 @@ ledgerFromRequest( template Status ledgerFromRequest<>( std::shared_ptr&, - GRPCContext&); + GRPCContext&); + +Status +getLedger( + std::shared_ptr& ledger, + uint256 const& ledgerHash, + Context& context) +{ + ledger = context.ledgerMaster.getLedgerByHash(ledgerHash); + if (ledger == nullptr) + return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; + return Status::OK; +} + +template +Status +getLedger(T& ledger, uint32_t ledgerIndex, Context& context) +{ + ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex); + if (ledger == nullptr) + { + auto cur = context.ledgerMaster.getCurrentLedger(); + if (cur->info().seq == ledgerIndex) + { + ledger = cur; + } + } + + if (ledger == nullptr) + return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; + + if (ledger->info().seq > context.ledgerMaster.getValidLedgerIndex() && + isValidatedOld(context.ledgerMaster, context.app.config().standalone())) + { + ledger.reset(); + return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + } + + return Status::OK; +} + + +template +Status +getLedger(T& ledger, LedgerShortcut shortcut, Context& context) +{ + if (isValidatedOld (context.ledgerMaster, context.app.config().standalone())) + return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + + if (shortcut == LedgerShortcut::VALIDATED) + { + ledger = context.ledgerMaster.getValidatedLedger (); + if (ledger == nullptr) + return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + + assert (! ledger->open()); + } + else + { + if (shortcut == LedgerShortcut::CURRENT) + { + ledger = context.ledgerMaster.getCurrentLedger (); + assert (ledger->open()); + } + else if (shortcut == LedgerShortcut::CLOSED) + { + ledger = context.ledgerMaster.getClosedLedger (); + assert (! ledger->open()); + } + else + { + return {rpcINVALID_PARAMS, "ledgerIndexMalformed"}; + } + + if (ledger == nullptr) + return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + + static auto const minSequenceGap = 10; + + if (ledger->info().seq + minSequenceGap < + context.ledgerMaster.getValidLedgerIndex ()) + { + ledger.reset (); + return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + } + } + return Status::OK; +} + +//Explicit instantiaion of above three functions +template Status +getLedger<>(std::shared_ptr&, + uint32_t, Context&); + +template Status +getLedger<>(std::shared_ptr&, + LedgerShortcut shortcut, Context&); bool isValidated(LedgerMaster& ledgerMaster, ReadView const& ledger, @@ -823,672 +838,6 @@ chooseLedgerEntryType(Json::Value const& params) return result; } -void -populateAccountRoot(rpc::v1::AccountRoot& proto, STObject const& obj) -{ - if (obj.isFieldPresent(sfAccount)) - { - AccountID account = obj.getAccountID(sfAccount); - proto.mutable_account()->set_address(toBase58(account)); - } - if (obj.isFieldPresent(sfBalance)) - { - STAmount amount = obj.getFieldAmount(sfBalance); - proto.mutable_balance()->set_drops(amount.xrp().drops()); - } - if (obj.isFieldPresent(sfSequence)) - { - proto.set_sequence(obj.getFieldU32(sfSequence)); - } - if (obj.isFieldPresent(sfFlags)) - { - proto.set_flags(obj.getFieldU32(sfFlags)); - } - if (obj.isFieldPresent(sfOwnerCount)) - { - proto.set_owner_count(obj.getFieldU32(sfOwnerCount)); - } - if (obj.isFieldPresent(sfPreviousTxnID)) - { - auto field = obj.getFieldH256(sfPreviousTxnID); - proto.set_previous_transaction_id(field.data(), field.size()); - } - if (obj.isFieldPresent(sfPreviousTxnLgrSeq)) - { - proto.set_previous_transaction_ledger_sequence( - obj.getFieldU32(sfPreviousTxnLgrSeq)); - } - if (obj.isFieldPresent(sfAccountTxnID)) - { - auto field = obj.getFieldH256(sfAccountTxnID); - proto.set_account_transaction_id(field.data(), field.size()); - } - if (obj.isFieldPresent(sfDomain)) - { - auto field = obj.getFieldH256(sfDomain); - proto.set_domain(field.data(), field.size()); - } - if (obj.isFieldPresent(sfEmailHash)) - { - auto field = obj.getFieldH128(sfEmailHash); - proto.set_email_hash(field.data(), field.size()); - } - if (obj.isFieldPresent(sfMessageKey)) - { - auto field = obj.getFieldVL(sfMessageKey); - proto.set_message_key(field.data(), field.size()); - } - if (obj.isFieldPresent(sfRegularKey)) - { - proto.set_regular_key(toBase58(obj.getAccountID(sfRegularKey))); - } - if (obj.isFieldPresent(sfTickSize)) - { - proto.set_tick_size(obj.getFieldU8(sfTickSize)); - } - if (obj.isFieldPresent(sfTransferRate)) - { - proto.set_transfer_rate(obj.getFieldU32(sfTransferRate)); - } -} - -void -populateRippleState(rpc::v1::RippleState& proto, STObject const& obj) -{ - if (obj.isFieldPresent(sfBalance)) - { - STAmount amount = obj.getFieldAmount(sfBalance); - populateAmount(*proto.mutable_balance(), amount); - } - if (obj.isFieldPresent(sfFlags)) - { - proto.set_flags(obj.getFieldU32(sfFlags)); - } - if (obj.isFieldPresent(sfLowLimit)) - { - STAmount amount = obj.getFieldAmount(sfLowLimit); - populateAmount(*proto.mutable_low_limit(), amount); - } - if (obj.isFieldPresent(sfHighLimit)) - { - STAmount amount = obj.getFieldAmount(sfHighLimit); - populateAmount(*proto.mutable_high_limit(), amount); - } - if (obj.isFieldPresent(sfLowNode)) - { - proto.set_low_node(obj.getFieldU64(sfLowNode)); - } - if (obj.isFieldPresent(sfHighNode)) - { - proto.set_high_node(obj.getFieldU64(sfHighNode)); - } - if (obj.isFieldPresent(sfLowQualityIn)) - { - proto.set_low_quality_in(obj.getFieldU32(sfLowQualityIn)); - } - if (obj.isFieldPresent(sfLowQualityOut)) - { - proto.set_low_quality_out(obj.getFieldU32(sfLowQualityOut)); - } - if (obj.isFieldPresent(sfHighQualityIn)) - { - proto.set_high_quality_in(obj.getFieldU32(sfHighQualityIn)); - } - if (obj.isFieldPresent(sfHighQualityOut)) - { - proto.set_high_quality_out(obj.getFieldU32(sfHighQualityOut)); - } -} - -void -populateOffer(rpc::v1::Offer& proto, STObject const& obj) -{ - if (obj.isFieldPresent(sfAccount)) - { - AccountID account = obj.getAccountID(sfAccount); - proto.set_account(toBase58(account)); - } - if (obj.isFieldPresent(sfSequence)) - { - proto.set_sequence(obj.getFieldU32(sfSequence)); - } - if (obj.isFieldPresent(sfFlags)) - { - proto.set_flags(obj.getFieldU32(sfFlags)); - } - if (obj.isFieldPresent(sfTakerPays)) - { - STAmount amount = obj.getFieldAmount(sfTakerPays); - populateAmount(*proto.mutable_taker_pays(), amount); - } - if (obj.isFieldPresent(sfTakerGets)) - { - STAmount amount = obj.getFieldAmount(sfTakerGets); - populateAmount(*proto.mutable_taker_gets(), amount); - } - if (obj.isFieldPresent(sfBookDirectory)) - { - auto field = obj.getFieldVL(sfBookDirectory); - proto.set_book_directory(field.data(), field.size()); - } - if (obj.isFieldPresent(sfBookNode)) - { - proto.set_book_node(obj.getFieldU64(sfBookNode)); - } - if (obj.isFieldPresent(sfExpiration)) - { - proto.set_expiration(obj.getFieldU32(sfExpiration)); - } -} - -void -populateSignerList(rpc::v1::SignerList& proto, STObject const& obj) -{ - proto.set_flags(obj.getFieldU32(sfFlags)); - - auto prevTxnID = obj.getFieldH256(sfPreviousTxnID); - proto.set_previous_txn_id(prevTxnID.data(), prevTxnID.size()); - - proto.set_previous_transaction_ledger_sequence( - obj.getFieldU32(sfPreviousTxnLgrSeq)); - - proto.set_owner_node(obj.getFieldU64(sfOwnerNode)); - - proto.set_signer_list_id(obj.getFieldU32(sfSignerListID)); - - proto.set_signer_quorum(obj.getFieldU32(sfSignerQuorum)); - - STArray const& signerEntries = obj.getFieldArray(sfSignerEntries); - - for (auto it = signerEntries.begin(); it != signerEntries.end(); ++it) - { - rpc::v1::SignerEntry& signerEntryProto = *proto.add_signer_entries(); - - signerEntryProto.mutable_account()->set_address( - toBase58(it->getAccountID(sfAccount))); - signerEntryProto.set_signer_weight(it->getFieldU16(sfSignerWeight)); - } -} - -void -populateQueueData( - rpc::v1::QueueData& proto, - std::map const& txs) -{ - if (!txs.empty()) - { - proto.set_txn_count(txs.size()); - proto.set_lowest_sequence(txs.begin()->first); - proto.set_highest_sequence(txs.rbegin()->first); - - boost::optional anyAuthChanged(false); - boost::optional totalSpend(0); - - for (auto const& [txSeq, txDetails] : txs) - { - rpc::v1::QueuedTransaction& qt = *proto.add_transactions(); - - qt.set_sequence(txSeq); - qt.set_fee_level(txDetails.feeLevel.fee()); - if (txDetails.lastValid) - qt.set_last_ledger_sequence(*txDetails.lastValid); - - if (txDetails.consequences) - { - qt.mutable_fee()->set_drops( - txDetails.consequences->fee.drops()); - auto spend = txDetails.consequences->potentialSpend + - txDetails.consequences->fee; - qt.mutable_max_spend_drops()->set_drops(spend.drops()); - if (totalSpend) - *totalSpend += spend; - auto authChanged = - txDetails.consequences->category == TxConsequences::blocker; - if (authChanged) - anyAuthChanged.emplace(authChanged); - qt.set_auth_change(authChanged); - } - else - { - if (anyAuthChanged && !*anyAuthChanged) - anyAuthChanged.reset(); - totalSpend.reset(); - } - } - - if (anyAuthChanged) - proto.set_auth_change_queued(*anyAuthChanged); - if (totalSpend) - proto.mutable_max_spend_drops_total()->set_drops( - (*totalSpend).drops()); - } -} - -void -populateDirectoryNode(rpc::v1::DirectoryNode& proto, STObject const& obj) -{ - if (obj.isFieldPresent(sfOwner)) - { - AccountID ownerAccount = obj.getAccountID(sfAccount); - proto.set_owner(toBase58(ownerAccount)); - } - if (obj.isFieldPresent(sfTakerPaysCurrency)) - { - uint160 tpCurr = obj.getFieldH160(sfTakerPaysCurrency); - proto.mutable_taker_pays_currency()->set_code( - tpCurr.data(), tpCurr.size()); - } - if (obj.isFieldPresent(sfTakerPaysIssuer)) - { - uint160 tpIss = obj.getFieldH160(sfTakerPaysIssuer); - proto.set_taker_pays_issuer(tpIss.data(), tpIss.size()); - } - if (obj.isFieldPresent(sfTakerGetsCurrency)) - { - uint160 tgCurr = obj.getFieldH160(sfTakerGetsCurrency); - proto.mutable_taker_gets_currency()->set_code( - tgCurr.data(), tgCurr.size()); - } - if (obj.isFieldPresent(sfTakerGetsIssuer)) - { - uint160 tgIss = obj.getFieldH160(sfTakerGetsIssuer); - proto.set_taker_gets_issuer(tgIss.data(), tgIss.size()); - } - if (obj.isFieldPresent(sfIndexes)) - { - const STVector256& vec = obj.getFieldV256(sfIndexes); - for (size_t i = 0; i < vec.size(); ++i) - { - uint256 const& elt = vec[i]; - proto.add_indexes(elt.data(), elt.size()); - } - } - if (obj.isFieldPresent(sfRootIndex)) - { - uint256 rootIndex = obj.getFieldH256(sfRootIndex); - proto.set_root_index(rootIndex.data(), rootIndex.size()); - } - if (obj.isFieldPresent(sfIndexNext)) - { - proto.set_index_next(obj.getFieldU64(sfIndexNext)); - } - if (obj.isFieldPresent(sfIndexPrevious)) - { - proto.set_index_previous(obj.getFieldU64(sfIndexPrevious)); - } -} - -void -populateLedgerEntryType(rpc::v1::AffectedNode& proto, std::uint16_t lgrType) -{ - switch (lgrType) - { - case ltACCOUNT_ROOT: - proto.set_ledger_entry_type( - rpc::v1::LEDGER_ENTRY_TYPE_ACCOUNT_ROOT); - break; - case ltDIR_NODE: - proto.set_ledger_entry_type( - rpc::v1::LEDGER_ENTRY_TYPE_DIRECTORY_NODE); - break; - case ltRIPPLE_STATE: - proto.set_ledger_entry_type( - rpc::v1::LEDGER_ENTRY_TYPE_RIPPLE_STATE); - break; - case ltSIGNER_LIST: - proto.set_ledger_entry_type(rpc::v1::LEDGER_ENTRY_TYPE_SIGNER_LIST); - break; - case ltOFFER: - proto.set_ledger_entry_type(rpc::v1::LEDGER_ENTRY_TYPE_OFFER); - break; - case ltLEDGER_HASHES: - proto.set_ledger_entry_type( - rpc::v1::LEDGER_ENTRY_TYPE_LEDGER_HASHES); - break; - case ltAMENDMENTS: - proto.set_ledger_entry_type(rpc::v1::LEDGER_ENTRY_TYPE_AMENDMENTS); - break; - case ltFEE_SETTINGS: - proto.set_ledger_entry_type( - rpc::v1::LEDGER_ENTRY_TYPE_FEE_SETTINGS); - break; - case ltESCROW: - proto.set_ledger_entry_type(rpc::v1::LEDGER_ENTRY_TYPE_ESCROW); - break; - case ltPAYCHAN: - proto.set_ledger_entry_type(rpc::v1::LEDGER_ENTRY_TYPE_PAY_CHANNEL); - break; - case ltCHECK: - proto.set_ledger_entry_type(rpc::v1::LEDGER_ENTRY_TYPE_CHECK); - break; - case ltDEPOSIT_PREAUTH: - proto.set_ledger_entry_type( - rpc::v1::LEDGER_ENTRY_TYPE_DEPOSIT_PREAUTH); - break; - } -} - -template -void -populateFields(T& proto, STObject const& obj, std::uint16_t type) -{ - if (type == ltACCOUNT_ROOT) - { - RPC::populateAccountRoot(*proto.mutable_account_root(), obj); - } - else if (type == ltRIPPLE_STATE) - { - RPC::populateRippleState(*proto.mutable_ripple_state(), obj); - } - else if (type == ltOFFER) - { - RPC::populateOffer(*proto.mutable_offer(), obj); - } - else if (type == ltDIR_NODE) - { - RPC::populateDirectoryNode(*proto.mutable_directory_node(), obj); - } - else - { - // Ledger object not supported by protobuf/grpc yet - } -} - -void -populateMeta(rpc::v1::Meta& proto, std::shared_ptr txMeta) -{ - proto.set_transaction_index(txMeta->getIndex()); - - populateTransactionResultType( - *proto.mutable_transaction_result(), txMeta->getResultTER()); - proto.mutable_transaction_result()->set_result( - transToken(txMeta->getResultTER())); - - STArray& nodes = txMeta->getNodes(); - for (auto it = nodes.begin(); it != nodes.end(); ++it) - { - STObject& obj = *it; - rpc::v1::AffectedNode* node = proto.add_affected_nodes(); - - // ledger index - uint256 ledgerIndex = obj.getFieldH256(sfLedgerIndex); - node->set_ledger_index(ledgerIndex.data(), ledgerIndex.size()); - - // ledger entry type - std::uint16_t lgrType = obj.getFieldU16(sfLedgerEntryType); - populateLedgerEntryType(*node, lgrType); - - // modified node - if (obj.getFName() == sfModifiedNode) - { - // final fields - if (obj.isFieldPresent(sfFinalFields)) - { - STObject& finalFields = - obj.getField(sfFinalFields).downcast(); - - rpc::v1::LedgerObject* finalFieldsProto = - node->mutable_modified_node()->mutable_final_fields(); - - populateFields(*finalFieldsProto, finalFields, lgrType); - } - // previous fields - if (obj.isFieldPresent(sfPreviousFields)) - { - STObject& prevFields = - obj.getField(sfPreviousFields).downcast(); - - rpc::v1::LedgerObject* prevFieldsProto = - node->mutable_modified_node()->mutable_previous_fields(); - - populateFields(*prevFieldsProto, prevFields, lgrType); - } - - // prev txn id and prev txn ledger seq - uint256 prevTxnId = obj.getFieldH256(sfPreviousTxnID); - node->mutable_modified_node()->set_previous_transaction_id( - prevTxnId.data(), prevTxnId.size()); - - node->mutable_modified_node() - ->set_previous_transaction_ledger_sequence( - obj.getFieldU32(sfPreviousTxnLgrSeq)); - } - // created node - else if (obj.getFName() == sfCreatedNode) - { - // new fields - if (obj.isFieldPresent(sfNewFields)) - { - STObject& newFields = - obj.getField(sfNewFields).downcast(); - - rpc::v1::LedgerObject* newFieldsProto = - node->mutable_created_node()->mutable_new_fields(); - - populateFields(*newFieldsProto, newFields, lgrType); - } - } - // deleted node - else if (obj.getFName() == sfDeletedNode) - { - // final fields - if (obj.isFieldPresent(sfFinalFields)) - { - STObject& finalFields = - obj.getField(sfFinalFields).downcast(); - - rpc::v1::LedgerObject* finalFieldsProto = - node->mutable_deleted_node()->mutable_final_fields(); - - populateFields(*finalFieldsProto, finalFields, lgrType); - } - } - } -} - -void -populateAmount(rpc::v1::CurrencyAmount& proto, STAmount const& amount) -{ - if (amount.native()) - { - proto.mutable_xrp_amount()->set_drops(amount.xrp().drops()); - } - else - { - rpc::v1::IssuedCurrencyAmount* issued = - proto.mutable_issued_currency_amount(); - Issue const& issue = amount.issue(); - Currency currency = issue.currency; - issued->mutable_currency()->set_name(to_string(issue.currency)); - issued->mutable_currency()->set_code(currency.data(), currency.size()); - issued->set_value(to_string(amount.iou())); - issued->mutable_issuer()->set_address(toBase58(issue.account)); - } -} - -void -populateTransaction( - rpc::v1::Transaction& proto, - std::shared_ptr txnSt) -{ - AccountID account = txnSt->getAccountID(sfAccount); - proto.mutable_account()->set_address(toBase58(account)); - - STAmount amount = txnSt->getFieldAmount(sfAmount); - populateAmount(*proto.mutable_payment()->mutable_amount(), amount); - - AccountID accountDest = txnSt->getAccountID(sfDestination); - proto.mutable_payment()->mutable_destination()->set_address( - toBase58(accountDest)); - - STAmount fee = txnSt->getFieldAmount(sfFee); - proto.mutable_fee()->set_drops(fee.xrp().drops()); - - proto.set_sequence(txnSt->getFieldU32(sfSequence)); - - Blob signingPubKey = txnSt->getFieldVL(sfSigningPubKey); - proto.set_signing_public_key(signingPubKey.data(), signingPubKey.size()); - - proto.set_flags(txnSt->getFieldU32(sfFlags)); - - proto.set_last_ledger_sequence(txnSt->getFieldU32(sfLastLedgerSequence)); - - Blob blob = txnSt->getFieldVL(sfTxnSignature); - proto.set_signature(blob.data(), blob.size()); - - if (txnSt->isFieldPresent(sfSourceTag)) - { - proto.set_source_tag(txnSt->getFieldU32(sfSourceTag)); - } - - if (txnSt->isFieldPresent(sfAccountTxnID)) - { - auto field = txnSt->getFieldH256(sfAccountTxnID); - proto.set_account_transaction_id(field.data(), field.size()); - } - - if (txnSt->isFieldPresent(sfMemos)) - { - auto memos = txnSt->getFieldArray(sfMemos); - for (auto it = memos.begin(); it != memos.end(); ++it) - { - rpc::v1::Memo* elt = proto.add_memos(); - auto memo = it->getField(sfMemo).downcast(); - if (memo.isFieldPresent(sfMemoData)) - { - auto memoData = memo.getFieldVL(sfMemoData); - elt->set_memo_data(memoData.data(), memoData.size()); - } - if (memo.isFieldPresent(sfMemoFormat)) - { - auto memoFormat = memo.getFieldVL(sfMemoFormat); - elt->set_memo_format(memoFormat.data(), memoFormat.size()); - } - if (memo.isFieldPresent(sfMemoType)) - { - auto memoType = memo.getFieldVL(sfMemoType); - elt->set_memo_type(memoType.data(), memoType.size()); - } - } - } - - if (txnSt->isFieldPresent(sfSigners)) - { - auto signers = txnSt->getFieldArray(sfSigners); - - for (auto it = signers.begin(); it != signers.end(); ++it) - { - rpc::v1::Signer* elt = proto.add_signers(); - auto signer = it->getField(sfSigner).downcast(); - if (signer.isFieldPresent(sfAccount)) - { - elt->mutable_account()->set_address( - toBase58(signer.getAccountID(sfAccount))); - } - if (signer.isFieldPresent(sfTxnSignature)) - { - auto sig = signer.getFieldVL(sfTxnSignature); - elt->set_transaction_signature(sig.data(), sig.size()); - } - if (signer.isFieldPresent(sfSigningPubKey)) - { - auto pubKey = signer.getFieldVL(sfSigningPubKey); - elt->set_signing_public_key(pubKey.data(), pubKey.size()); - } - } - } - - if (safe_cast(txnSt->getFieldU16(sfTransactionType)) == - TxType::ttPAYMENT) - { - if (txnSt->isFieldPresent(sfSendMax)) - { - STAmount const& sendMax = txnSt->getFieldAmount(sfSendMax); - populateAmount( - *proto.mutable_payment()->mutable_send_max(), sendMax); - } - - if (txnSt->isFieldPresent(sfInvoiceID)) - { - auto invoice = txnSt->getFieldH256(sfInvoiceID); - proto.mutable_payment()->set_invoice_id( - invoice.data(), invoice.size()); - } - - if (txnSt->isFieldPresent(sfDestinationTag)) - { - proto.mutable_payment()->set_destination_tag( - txnSt->getFieldU32(sfDestinationTag)); - } - - // populate path data - STPathSet const& pathset = txnSt->getFieldPathSet(sfPaths); - for (auto it = pathset.begin(); it < pathset.end(); ++it) - { - STPath const& path = *it; - - rpc::v1::Path* protoPath = proto.mutable_payment()->add_paths(); - - for (auto it2 = path.begin(); it2 != path.end(); ++it2) - { - rpc::v1::PathElement* protoElement = protoPath->add_elements(); - STPathElement const& elt = *it2; - - if (elt.isOffer()) - { - if (elt.hasCurrency()) - { - Currency const& currency = elt.getCurrency(); - protoElement->mutable_currency()->set_name( - to_string(currency)); - } - if (elt.hasIssuer()) - { - AccountID const& issuer = elt.getIssuerID(); - protoElement->mutable_issuer()->set_address( - toBase58(issuer)); - } - } - else - { - AccountID const& pathAccount = elt.getAccountID(); - protoElement->mutable_account()->set_address( - toBase58(pathAccount)); - } - } - } - } -} - -void -populateTransactionResultType(rpc::v1::TransactionResult& proto, TER result) -{ - if (isTecClaim(result)) - { - proto.set_result_type(rpc::v1::TransactionResult::RESULT_TYPE_TEC); - } - if (isTefFailure(result)) - { - proto.set_result_type(rpc::v1::TransactionResult::RESULT_TYPE_TEF); - } - if (isTelLocal(result)) - { - proto.set_result_type(rpc::v1::TransactionResult::RESULT_TYPE_TEL); - } - if (isTemMalformed(result)) - { - proto.set_result_type(rpc::v1::TransactionResult::RESULT_TYPE_TEM); - } - if (isTerRetry(result)) - { - proto.set_result_type(rpc::v1::TransactionResult::RESULT_TYPE_TER); - } - if (isTesSuccess(result)) - { - proto.set_result_type(rpc::v1::TransactionResult::RESULT_TYPE_TES); - } -} - beast::SemanticVersion const firstVersion("1.0.0"); beast::SemanticVersion const goodVersion("1.0.0"); beast::SemanticVersion const lastVersion("1.0.0"); diff --git a/src/ripple/rpc/impl/RPCHelpers.h b/src/ripple/rpc/impl/RPCHelpers.h index eaa8b0cd43..65cfc6844f 100644 --- a/src/ripple/rpc/impl/RPCHelpers.h +++ b/src/ripple/rpc/impl/RPCHelpers.h @@ -29,9 +29,9 @@ #include #include #include -#include +#include #include -#include +#include namespace Json { class Value; @@ -82,6 +82,37 @@ getAccountObjects (ReadView const& ledger, AccountID const& account, boost::optional> const& typeFilter, uint256 dirIndex, uint256 const& entryIndex, std::uint32_t const limit, Json::Value& jvResult); +/** Get ledger by hash + If there is no error in the return value, the ledger pointer will have + been filled +*/ +Status +getLedger(std::shared_ptr& ledger, uint256 const & ledgerHash, Context& context); + + +/** Get ledger by sequence + If there is no error in the return value, the ledger pointer will have + been filled +*/ +template +Status +getLedger(T& ledger, uint32_t ledgerIndex, Context& context); + +enum LedgerShortcut +{ + CURRENT, + CLOSED, + VALIDATED +}; +/** Get ledger specified in shortcut. + If there is no error in the return value, the ledger pointer will have + been filled +*/ +template +Status +getLedger(T& ledger, LedgerShortcut shortcut, Context& context); + + /** Look up a ledger from a request and fill a Json::Result with either an error, or data representing a ledger. @@ -103,7 +134,7 @@ template Status ledgerFromRequest( T& ledger, - GRPCContext& context); + GRPCContext& context); bool isValidated(LedgerMaster& ledgerMaster, ReadView const& ledger, @@ -204,45 +235,6 @@ std::pair */ unsigned int getAPIVersionNumber(const Json::Value & value); -/* - * For all of the below populate* functions, the proto argument is an - * output parameter, and is populated with the data stored in the - * serialized object - */ -void -populateAccountRoot(rpc::v1::AccountRoot& proto, STObject const& obj); - -void -populateRippleState(rpc::v1::RippleState& proto, STObject const& obj); - -void -populateOffer(rpc::v1::Offer& proto, STObject const& obj); - -void -populateSignerList(rpc::v1::SignerList& proto, STObject const& obj); - -void -populateQueueData( - rpc::v1::QueueData& proto, - std::map const& txs); - -void -populateDirectoryNode(rpc::v1::DirectoryNode& proto, STObject const& obj); - -void -populateMeta(rpc::v1::Meta& proto, std::shared_ptr txMeta); - -void -populateTransaction( - rpc::v1::Transaction& proto, - std::shared_ptr txnSt); - -void -populateAmount(rpc::v1::CurrencyAmount& proto, STAmount const& amount); - -void -populateTransactionResultType(rpc::v1::TransactionResult& proto, TER result); - } // RPC } // ripple diff --git a/src/test/app/AccountTxPaging_test.cpp b/src/test/app/AccountTxPaging_test.cpp index f58b4f4a5d..7370ab63c4 100644 --- a/src/test/app/AccountTxPaging_test.cpp +++ b/src/test/app/AccountTxPaging_test.cpp @@ -22,6 +22,11 @@ #include #include +#include +#include +#include + + namespace ripple { class AccountTxPaging_test : public beast::unit_test::suite @@ -258,11 +263,1914 @@ class AccountTxPaging_test : public beast::unit_test::suite } } + class GrpcAccountTxClient : public test::GRPCTestClientBase + { + public: + org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest request; + org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse reply; + + explicit GrpcAccountTxClient(std::string const& port) + : GRPCTestClientBase(port) + { + } + + void + AccountTx() + { + status = + stub_->GetAccountTransactionHistory(&context, request, &reply); + } + }; + + bool + checkTransaction( + org::xrpl::rpc::v1::GetTransactionResponse const& tx, + int sequence, + int ledger) + { + return ( + tx.transaction().sequence().value() == sequence && + tx.ledger_index() == ledger); + } + + std::pair< + org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse, + grpc::Status> + nextBinary( + std::string grpcPort, + test::jtx::Env& env, + std::string const& account = "", + int ledger_min = -1, + int ledger_max = -1, + int limit = -1, + bool forward = false, + org::xrpl::rpc::v1::Marker* marker = nullptr) + { + GrpcAccountTxClient client{grpcPort}; + auto& request = client.request; + if (account != "") + request.mutable_account()->set_address(account); + if (ledger_min != -1) + request.mutable_ledger_range()->set_ledger_index_min(ledger_min); + if (ledger_max != -1) + request.mutable_ledger_range()->set_ledger_index_max(ledger_max); + request.set_forward(forward); + request.set_binary(true); + if (limit != -1) + request.set_limit(limit); + if (marker) + { + *request.mutable_marker() = *marker; + } + + client.AccountTx(); + return {client.reply, client.status}; + } + + std::pair< + org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse, + grpc::Status> + next( + std::string grpcPort, + test::jtx::Env& env, + std::string const& account = "", + int ledger_min = -1, + int ledger_max = -1, + int limit = -1, + bool forward = false, + org::xrpl::rpc::v1::Marker* marker = nullptr) + { + GrpcAccountTxClient client{grpcPort}; + auto& request = client.request; + if (account != "") + request.mutable_account()->set_address(account); + if (ledger_min != -1) + request.mutable_ledger_range()->set_ledger_index_min(ledger_min); + if (ledger_max != -1) + request.mutable_ledger_range()->set_ledger_index_max(ledger_max); + request.set_forward(forward); + if (limit != -1) + request.set_limit(limit); + if (marker) + { + *request.mutable_marker() = *marker; + } + + client.AccountTx(); + return {client.reply, client.status}; + } + + std::pair< + org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse, + grpc::Status> + nextWithSeq( + std::string grpcPort, + test::jtx::Env& env, + std::string const& account = "", + int ledger_seq = -1, + int limit = -1, + bool forward = false, + org::xrpl::rpc::v1::Marker* marker = nullptr) + { + GrpcAccountTxClient client{grpcPort}; + auto& request = client.request; + if (account != "") + request.mutable_account()->set_address(account); + if (ledger_seq != -1) + request.mutable_ledger_specifier()->set_sequence(ledger_seq); + request.set_forward(forward); + if (limit != -1) + request.set_limit(limit); + if (marker) + { + *request.mutable_marker() = *marker; + } + + client.AccountTx(); + return {client.reply, client.status}; + } + + std::pair< + org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse, + grpc::Status> + nextWithHash( + std::string grpcPort, + test::jtx::Env& env, + std::string const& account = "", + uint256 const& hash = beast::zero, + int limit = -1, + bool forward = false, + org::xrpl::rpc::v1::Marker* marker = nullptr) + { + GrpcAccountTxClient client{grpcPort}; + auto& request = client.request; + if (account != "") + request.mutable_account()->set_address(account); + if (hash != beast::zero) + request.mutable_ledger_specifier()->set_hash( + hash.data(), hash.size()); + request.set_forward(forward); + if (limit != -1) + request.set_limit(limit); + if (marker) + { + *request.mutable_marker() = *marker; + } + + client.AccountTx(); + return {client.reply, client.status}; + } + + void + testAccountTxParametersGrpc() + { + testcase("Test Account_tx Grpc"); + + using namespace test::jtx; + std::unique_ptr config = envconfig(addGrpcConfig); + std::string grpcPort = *(*config)["port_grpc"].get("port"); + Env env(*this, std::move(config)); + + Account A1{"A1"}; + env.fund(XRP(10000), A1); + env.close(); + + // Ledger 3 has the two txs associated with funding the account + // All other ledgers have no txs + + auto hasTxs = [](auto res) { + return res.second.error_code() == 0 && + (res.first.transactions().size() == 2) && + //(res.transactions()[0u].transaction().has_account_set()) && + (res.first.transactions()[1u].transaction().has_payment()); + }; + auto noTxs = [](auto res) { + return res.second.error_code() == 0 && + (res.first.transactions().size() == 0); + }; + + auto isErr = [](auto res, auto expect) { + return res.second.error_code() == expect; + }; + + BEAST_EXPECT( + isErr(next(grpcPort, env, ""), grpc::StatusCode::INVALID_ARGUMENT)); + + BEAST_EXPECT(isErr( + next(grpcPort, env, "0xDEADBEEF"), + grpc::StatusCode::INVALID_ARGUMENT)); + + BEAST_EXPECT(hasTxs(next(grpcPort, env, A1.human()))); + + // Ledger min/max index + { + BEAST_EXPECT(hasTxs(next(grpcPort, env, A1.human()))); + + BEAST_EXPECT(hasTxs(next(grpcPort, env, A1.human(), 0, 100))); + + BEAST_EXPECT(noTxs(next(grpcPort, env, A1.human(), 1, 2))); + + BEAST_EXPECT(isErr( + next(grpcPort, env, A1.human(), 2, 1), + grpc::StatusCode::INVALID_ARGUMENT)); + } + + // Ledger index min only + { + BEAST_EXPECT(hasTxs(next(grpcPort, env, A1.human(), -1))); + + BEAST_EXPECT(hasTxs(next(grpcPort, env, A1.human(), 1))); + + BEAST_EXPECT(isErr( + next(grpcPort, env, A1.human(), env.current()->info().seq), + grpc::StatusCode::INVALID_ARGUMENT)); + } + + // Ledger index max only + { + BEAST_EXPECT(hasTxs(next(grpcPort, env, A1.human(), -1, -1))); + + BEAST_EXPECT(hasTxs(next( + grpcPort, env, A1.human(), -1, env.current()->info().seq))); + + BEAST_EXPECT(hasTxs( + next(grpcPort, env, A1.human(), -1, env.closed()->info().seq))); + + BEAST_EXPECT(noTxs(next( + grpcPort, env, A1.human(), -1, env.closed()->info().seq - 1))); + } + // Ledger Sequence + { + BEAST_EXPECT(hasTxs(nextWithSeq( + grpcPort, env, A1.human(), env.closed()->info().seq))); + + BEAST_EXPECT(noTxs(nextWithSeq( + grpcPort, env, A1.human(), env.closed()->info().seq - 1))); + + BEAST_EXPECT(isErr( + nextWithSeq( + grpcPort, env, A1.human(), env.current()->info().seq), + grpc::StatusCode::INVALID_ARGUMENT)); + + BEAST_EXPECT(isErr( + nextWithSeq( + grpcPort, env, A1.human(), env.current()->info().seq + 1), + grpc::StatusCode::NOT_FOUND)); + } + + // Ledger Hash + { + BEAST_EXPECT(hasTxs(nextWithHash( + grpcPort, env, A1.human(), env.closed()->info().hash))); + + BEAST_EXPECT(noTxs(nextWithHash( + grpcPort, env, A1.human(), env.closed()->info().parentHash))); + } + } + + struct TxCheck + { + uint32_t sequence; + uint32_t ledgerIndex; + std::string hash; + std::function + checkTxn; + }; + + void + testAccountTxContentsGrpc() + { + testcase("Test AccountTx context grpc"); + // Get results for all transaction types that can be associated + // with an account. Start by generating all transaction types. + using namespace test::jtx; + using namespace std::chrono_literals; + + std::unique_ptr config = envconfig(addGrpcConfig); + std::string grpcPort = *(*config)["port_grpc"].get("port"); + Env env(*this, std::move(config)); + // Set time to this value (or greater) to get delivered_amount in meta + env.timeKeeper().set(NetClock::time_point{446000001s}); + Account const alice{"alice"}; + Account const alie{"alie"}; + Account const gw{"gw"}; + auto const USD{gw["USD"]}; + + std::vector> txns; + + env.fund(XRP(1000000), alice, gw); + env.close(); + + // AccountSet + env(noop(alice)); + + txns.emplace_back(env.tx()); + // Payment + env(pay(alice, gw, XRP(100)), stag(42), dtag(24), last_ledger_seq(20)); + + txns.emplace_back(env.tx()); + // Regular key set + env(regkey(alice, alie)); + env.close(); + + txns.emplace_back(env.tx()); + // Trust and Offers + env(trust(alice, USD(200)), sig(alie)); + + txns.emplace_back(env.tx()); + std::uint32_t const offerSeq{env.seq(alice)}; + env(offer(alice, USD(50), XRP(150)), sig(alie)); + + txns.emplace_back(env.tx()); + env.close(); + + { + Json::Value cancelOffer; + cancelOffer[jss::Account] = alice.human(); + cancelOffer[jss::OfferSequence] = offerSeq; + cancelOffer[jss::TransactionType] = jss::OfferCancel; + env(cancelOffer, sig(alie)); + } + env.close(); + + txns.emplace_back(env.tx()); + + // SignerListSet + env(signers(alice, 1, {{"bogie", 1}, {"demon", 1}, {gw, 1}}), + sig(alie)); + + txns.emplace_back(env.tx()); + // Escrow + { + // Create an escrow. Requires either a CancelAfter or FinishAfter. + auto escrow = [](Account const& account, + Account const& to, + STAmount const& amount) { + Json::Value escro; + escro[jss::TransactionType] = jss::EscrowCreate; + escro[jss::Flags] = tfUniversal; + escro[jss::Account] = account.human(); + escro[jss::Destination] = to.human(); + escro[jss::Amount] = amount.getJson(JsonOptions::none); + return escro; + }; + + NetClock::time_point const nextTime{env.now() + 2s}; + + Json::Value escrowWithFinish{escrow(alice, alice, XRP(500))}; + escrowWithFinish[sfFinishAfter.jsonName] = + nextTime.time_since_epoch().count(); + + std::uint32_t const escrowFinishSeq{env.seq(alice)}; + env(escrowWithFinish, sig(alie)); + + txns.emplace_back(env.tx()); + Json::Value escrowWithCancel{escrow(alice, alice, XRP(500))}; + escrowWithCancel[sfFinishAfter.jsonName] = + nextTime.time_since_epoch().count(); + escrowWithCancel[sfCancelAfter.jsonName] = + nextTime.time_since_epoch().count() + 1; + + std::uint32_t const escrowCancelSeq{env.seq(alice)}; + env(escrowWithCancel, sig(alie)); + env.close(); + + txns.emplace_back(env.tx()); + { + Json::Value escrowFinish; + escrowFinish[jss::TransactionType] = jss::EscrowFinish; + escrowFinish[jss::Flags] = tfUniversal; + escrowFinish[jss::Account] = alice.human(); + escrowFinish[sfOwner.jsonName] = alice.human(); + escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq; + env(escrowFinish, sig(alie)); + + txns.emplace_back(env.tx()); + } + { + Json::Value escrowCancel; + escrowCancel[jss::TransactionType] = jss::EscrowCancel; + escrowCancel[jss::Flags] = tfUniversal; + escrowCancel[jss::Account] = alice.human(); + escrowCancel[sfOwner.jsonName] = alice.human(); + escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq; + env(escrowCancel, sig(alie)); + + txns.emplace_back(env.tx()); + } + env.close(); + } + + // PayChan + { + std::uint32_t payChanSeq{env.seq(alice)}; + Json::Value payChanCreate; + payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate; + payChanCreate[jss::Flags] = tfUniversal; + payChanCreate[jss::Account] = alice.human(); + payChanCreate[jss::Destination] = gw.human(); + payChanCreate[jss::Amount] = + XRP(500).value().getJson(JsonOptions::none); + payChanCreate[sfSettleDelay.jsonName] = + NetClock::duration{100s}.count(); + payChanCreate[sfPublicKey.jsonName] = strHex(alice.pk().slice()); + env(payChanCreate, sig(alie)); + env.close(); + + txns.emplace_back(env.tx()); + std::string const payChanIndex{ + strHex(keylet::payChan(alice, gw, payChanSeq).key)}; + + { + Json::Value payChanFund; + payChanFund[jss::TransactionType] = jss::PaymentChannelFund; + payChanFund[jss::Flags] = tfUniversal; + payChanFund[jss::Account] = alice.human(); + payChanFund[sfPayChannel.jsonName] = payChanIndex; + payChanFund[jss::Amount] = + XRP(200).value().getJson(JsonOptions::none); + env(payChanFund, sig(alie)); + env.close(); + + txns.emplace_back(env.tx()); + } + { + Json::Value payChanClaim; + payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim; + payChanClaim[jss::Flags] = tfClose; + payChanClaim[jss::Account] = gw.human(); + payChanClaim[sfPayChannel.jsonName] = payChanIndex; + payChanClaim[sfPublicKey.jsonName] = strHex(alice.pk().slice()); + env(payChanClaim); + env.close(); + + txns.emplace_back(env.tx()); + } + } + + // Check + { + uint256 const aliceCheckId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, gw, XRP(300)), sig(alie)); + + auto txn = env.tx(); + uint256 const gwCheckId{getCheckIndex(gw, env.seq(gw))}; + env(check::create(gw, alice, XRP(200))); + env.close(); + + // need to switch the order of the previous 2 txns, since they are + // in the same ledger and account_tx returns them in a different + // order + txns.emplace_back(env.tx()); + txns.emplace_back(txn); + env(check::cash(alice, gwCheckId, XRP(200)), sig(alie)); + + txns.emplace_back(env.tx()); + env(check::cancel(alice, aliceCheckId), sig(alie)); + + txns.emplace_back(env.tx()); + env.close(); + } + + // Deposit preauthorization. + env(deposit::auth(alice, gw), sig(alie)); + env.close(); + + txns.emplace_back(env.tx()); + // Multi Sig with memo + auto const baseFee = env.current()->fees().base; + env(noop(alice), + msig(gw), + fee(2 * baseFee), + memo("data", "format", "type")); + env.close(); + + txns.emplace_back(env.tx()); + if (!BEAST_EXPECT(txns.size() == 20)) + return; + // Setup is done. Look at the transactions returned by account_tx. + + static const TxCheck txCheck[]{ + {21, + 15, + strHex(txns[txns.size() - 1]->getTransactionID()), + [this, &txns](auto res) { + auto txnJson = + txns[txns.size() - 1]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_account_set()) && + BEAST_EXPECT(res.has_fee()) && + BEAST_EXPECT(res.fee().drops() == 20) && + BEAST_EXPECT(res.memos_size() == 1) && + BEAST_EXPECT(res.memos(0).has_memo_data()) && + BEAST_EXPECT(res.memos(0).memo_data().value() == "data") && + BEAST_EXPECT(res.memos(0).has_memo_format()) && + BEAST_EXPECT( + res.memos(0).memo_format().value() == "format") && + BEAST_EXPECT(res.memos(0).has_memo_type()) && + BEAST_EXPECT(res.memos(0).memo_type().value() == "type") && + BEAST_EXPECT(res.has_signing_public_key()) && + BEAST_EXPECT(res.signing_public_key().value() == "") && + BEAST_EXPECT(res.signers_size() == 1) && + BEAST_EXPECT(res.signers(0).has_account()) && + BEAST_EXPECT( + res.signers(0).account().value().address() == + txnJson["Signers"][0u]["Signer"]["Account"]) && + BEAST_EXPECT(res.signers(0).has_transaction_signature()) && + BEAST_EXPECT( + strHex(res.signers(0) + .transaction_signature() + .value()) == + txnJson["Signers"][0u]["Signer"]["TxnSignature"]) && + BEAST_EXPECT(res.signers(0).has_signing_public_key()) && + BEAST_EXPECT( + strHex( + res.signers(0).signing_public_key().value()) == + txnJson["Signers"][0u]["Signer"]["SigningPubKey"]); + }}, + {20, + 14, + strHex(txns[txns.size() - 2]->getTransactionID()), + [&txns, this](auto res) { + return BEAST_EXPECT(res.has_deposit_preauth()) && + BEAST_EXPECT( + res.deposit_preauth() + .authorize() + .value() + .address() == + // TODO do them all like this + txns[txns.size() - 2]->getJson( + JsonOptions::none)["Authorize"]); + }}, + {19, + 13, + strHex(txns[txns.size() - 3]->getTransactionID()), + [&txns, this](auto res) { + return BEAST_EXPECT(res.has_check_cancel()) && + BEAST_EXPECT( + strHex(res.check_cancel().check_id().value()) == + + txns[txns.size() - 3]->getJson( + JsonOptions::none)["CheckID"]); + }}, + {18, + 13, + strHex(txns[txns.size() - 4]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 4]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_check_cash()) && + BEAST_EXPECT( + strHex(res.check_cash().check_id().value()) == + txnJson["CheckID"]) && + BEAST_EXPECT(res.check_cash() + .amount() + .value() + .has_xrp_amount()) && + BEAST_EXPECT( + res.check_cash() + .amount() + .value() + .xrp_amount() + .drops() == txnJson["Amount"].asUInt()); + }}, + {17, + 12, + strHex(txns[txns.size() - 5]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 5]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_check_create()) && + BEAST_EXPECT( + res.check_create() + .destination() + .value() + .address() == txnJson["Destination"]) && + BEAST_EXPECT(res.check_create() + .send_max() + .value() + .has_xrp_amount()) && + BEAST_EXPECT( + res.check_create() + .send_max() + .value() + .xrp_amount() + .drops() == txnJson["SendMax"].asUInt()); + }}, + {5, + 12, + strHex(txns[txns.size() - 6]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 6]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_check_create()) && + BEAST_EXPECT( + res.check_create() + .destination() + .value() + .address() == txnJson["Destination"]) && + BEAST_EXPECT(res.check_create() + .send_max() + .value() + .has_xrp_amount()) && + BEAST_EXPECT( + res.check_create() + .send_max() + .value() + .xrp_amount() + .drops() == + + txnJson["SendMax"].asUInt()); + }}, + {4, + 11, + strHex(txns[txns.size() - 7]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 7]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_payment_channel_claim()) && + BEAST_EXPECT( + strHex(res.payment_channel_claim() + .channel() + .value()) == txnJson["Channel"]) && + BEAST_EXPECT( + strHex(res.payment_channel_claim() + .public_key() + .value()) == txnJson["PublicKey"]); + }}, + {16, + 10, + strHex(txns[txns.size() - 8]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 8]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_payment_channel_fund()) && + BEAST_EXPECT( + strHex( + res.payment_channel_fund().channel().value()) == + txnJson["Channel"]) && + BEAST_EXPECT(res.payment_channel_fund() + .amount() + .value() + .has_xrp_amount()) && + BEAST_EXPECT( + res.payment_channel_fund() + .amount() + .value() + .xrp_amount() + .drops() == txnJson["Amount"].asUInt()); + }}, + {15, + 9, + strHex(txns[txns.size() - 9]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 9]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_payment_channel_create()) && + BEAST_EXPECT(res.payment_channel_create() + .amount() + .value() + .has_xrp_amount()) && + BEAST_EXPECT( + res.payment_channel_create() + .amount() + .value() + .xrp_amount() + .drops() == txnJson["Amount"].asUInt()) && + BEAST_EXPECT( + res.payment_channel_create() + .destination() + .value() + .address() == txnJson["Destination"]) && + BEAST_EXPECT( + res.payment_channel_create() + .settle_delay() + .value() == txnJson["SettleDelay"].asUInt()) && + BEAST_EXPECT( + strHex(res.payment_channel_create() + .public_key() + .value()) == txnJson["PublicKey"]); + }}, + {14, + 8, + strHex(txns[txns.size() - 10]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 10]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_escrow_cancel()) && + BEAST_EXPECT( + res.escrow_cancel().owner().value().address() == + txnJson["Owner"]) && + BEAST_EXPECT( + res.escrow_cancel().offer_sequence().value() == + txnJson["OfferSequence"].asUInt() + + ); + }}, + {13, + 8, + strHex(txns[txns.size() - 11]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 11]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_escrow_finish()) && + BEAST_EXPECT( + res.escrow_finish().owner().value().address() == + txnJson["Owner"]) && + BEAST_EXPECT( + res.escrow_finish().offer_sequence().value() == + txnJson["OfferSequence"].asUInt() + + ); + }}, + {12, + 7, + strHex(txns[txns.size() - 12]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 12]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_escrow_create()) && + BEAST_EXPECT(res.escrow_create() + .amount() + .value() + .has_xrp_amount()) && + BEAST_EXPECT( + res.escrow_create() + .amount() + .value() + .xrp_amount() + .drops() == txnJson["Amount"].asUInt()) && + BEAST_EXPECT( + res.escrow_create() + .destination() + .value() + .address() == txnJson["Destination"]) && + BEAST_EXPECT( + res.escrow_create().cancel_after().value() == + txnJson["CancelAfter"].asUInt()) && + BEAST_EXPECT( + res.escrow_create().finish_after().value() == + txnJson["FinishAfter"].asUInt()); + }}, + {11, + 7, + strHex(txns[txns.size() - 13]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 13]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_escrow_create()) && + BEAST_EXPECT(res.escrow_create() + .amount() + .value() + .has_xrp_amount()) && + BEAST_EXPECT( + res.escrow_create() + .amount() + .value() + .xrp_amount() + .drops() == txnJson["Amount"].asUInt()) && + BEAST_EXPECT( + res.escrow_create() + .destination() + .value() + .address() == txnJson["Destination"]) && + BEAST_EXPECT( + res.escrow_create().finish_after().value() == + txnJson["FinishAfter"].asUInt()); + }}, + {10, + 7, + strHex(txns[txns.size() - 14]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 14]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_signer_list_set()) && + BEAST_EXPECT( + res.signer_list_set().signer_quorum().value() == + txnJson["SignerQuorum"].asUInt()) && + BEAST_EXPECT( + res.signer_list_set().signer_entries().size() == + 3) && + BEAST_EXPECT( + res.signer_list_set() + .signer_entries()[0] + .account() + .value() + .address() == + txnJson["SignerEntries"][0u]["SignerEntry"] + ["Account"]) && + BEAST_EXPECT( + res.signer_list_set() + .signer_entries()[0] + .signer_weight() + .value() == + txnJson["SignerEntries"][0u]["SignerEntry"] + ["SignerWeight"] + .asUInt()) && + BEAST_EXPECT( + res.signer_list_set() + .signer_entries()[1] + .account() + .value() + .address() == + txnJson["SignerEntries"][1u]["SignerEntry"] + ["Account"]) && + BEAST_EXPECT( + res.signer_list_set() + .signer_entries()[1] + .signer_weight() + .value() == + txnJson["SignerEntries"][1u]["SignerEntry"] + ["SignerWeight"] + .asUInt()) && + BEAST_EXPECT( + res.signer_list_set() + .signer_entries()[2] + .account() + .value() + .address() == + txnJson["SignerEntries"][2u]["SignerEntry"] + ["Account"]) && + BEAST_EXPECT( + res.signer_list_set() + .signer_entries()[2] + .signer_weight() + .value() == + txnJson["SignerEntries"][2u]["SignerEntry"] + ["SignerWeight"] + .asUInt()); + }}, + {9, + 6, + strHex(txns[txns.size() - 15]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 15]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_offer_cancel()) && + BEAST_EXPECT( + res.offer_cancel().offer_sequence().value() == + txnJson["OfferSequence"].asUInt()); + }}, + {8, + 5, + strHex(txns[txns.size() - 16]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 16]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_offer_create()) && + BEAST_EXPECT(res.offer_create() + .taker_gets() + .value() + .has_xrp_amount()) && + BEAST_EXPECT( + res.offer_create() + .taker_gets() + .value() + .xrp_amount() + .drops() == txnJson["TakerGets"].asUInt()) && + BEAST_EXPECT(res.offer_create() + .taker_pays() + .value() + .has_issued_currency_amount()) && + BEAST_EXPECT( + res.offer_create() + .taker_pays() + .value() + .issued_currency_amount() + .currency() + .name() == txnJson["TakerPays"]["currency"]) && + BEAST_EXPECT( + res.offer_create() + .taker_pays() + .value() + .issued_currency_amount() + .value() == txnJson["TakerPays"]["value"]) && + BEAST_EXPECT( + res.offer_create() + .taker_pays() + .value() + .issued_currency_amount() + .issuer() + .address() == txnJson["TakerPays"]["issuer"]); + }}, + {7, + 5, + strHex(txns[txns.size() - 17]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 17]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_trust_set()) && + BEAST_EXPECT(res.trust_set() + .limit_amount() + .value() + .has_issued_currency_amount()) && + BEAST_EXPECT( + res.trust_set() + .limit_amount() + .value() + .issued_currency_amount() + .currency() + .name() == + txnJson["LimitAmount"]["currency"]) && + BEAST_EXPECT( + res.trust_set() + .limit_amount() + .value() + .issued_currency_amount() + .value() == txnJson["LimitAmount"]["value"]) && + BEAST_EXPECT( + res.trust_set() + .limit_amount() + .value() + .issued_currency_amount() + .issuer() + .address() == txnJson["LimitAmount"]["issuer"]); + }}, + {6, + 4, + strHex(txns[txns.size() - 18]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 18]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_set_regular_key()) && + BEAST_EXPECT( + res.set_regular_key() + .regular_key() + .value() + .address() == txnJson["RegularKey"]); + }}, + {5, + 4, + strHex(txns[txns.size() - 19]->getTransactionID()), + [&txns, this](auto res) { + auto txnJson = + txns[txns.size() - 19]->getJson(JsonOptions::none); + return BEAST_EXPECT(res.has_payment()) && + BEAST_EXPECT( + res.payment().amount().value().has_xrp_amount()) && + BEAST_EXPECT( + res.payment() + .amount() + .value() + .xrp_amount() + .drops() == txnJson["Amount"].asUInt()) && + BEAST_EXPECT( + res.payment().destination().value().address() == + txnJson["Destination"]) && + BEAST_EXPECT(res.has_source_tag()) && + BEAST_EXPECT( + res.source_tag().value() == + txnJson["SourceTag"].asUInt()) && + BEAST_EXPECT(res.payment().has_destination_tag()) && + BEAST_EXPECT( + res.payment().destination_tag().value() == + txnJson["DestinationTag"].asUInt()) && + BEAST_EXPECT(res.has_last_ledger_sequence()) && + BEAST_EXPECT( + res.last_ledger_sequence().value() == + txnJson["LastLedgerSequence"].asUInt()) && + BEAST_EXPECT(res.has_transaction_signature()) && + BEAST_EXPECT(res.has_account()) && + BEAST_EXPECT( + res.account().value().address() == + txnJson["Account"]) && + BEAST_EXPECT(res.has_flags()) && + BEAST_EXPECT( + res.flags().value() == txnJson["Flags"].asUInt()); + }}, + {4, + 4, + strHex(txns[txns.size() - 20]->getTransactionID()), + [this](auto res) { return BEAST_EXPECT(res.has_account_set()); }}, + {3, + 3, + "9CE54C3B934E473A995B477E92EC229F99CED5B62BF4D2ACE4DC42719103AE2F", + [this](auto res) { + return BEAST_EXPECT(res.has_account_set()) && + BEAST_EXPECT(res.account_set().set_flag().value() == 8); + }}, + {1, + 3, + "2B5054734FA43C6C7B54F61944FAD6178ACD5D0272B39BA7FCD32A5D3932FBFF", + [&alice, this](auto res) { + return BEAST_EXPECT(res.has_payment()) && + BEAST_EXPECT( + res.payment().amount().value().has_xrp_amount()) && + BEAST_EXPECT( + res.payment() + .amount() + .value() + .xrp_amount() + .drops() == 1000000000010) && + BEAST_EXPECT( + res.payment().destination().value().address() == + alice.human()); + }}}; + + using MetaCheck = + std::function; + static const MetaCheck txMetaCheck[]{ + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](org::xrpl::rpc::v1::AffectedNode const& + entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 3) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DEPOSIT_PREAUTH; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 1) && + BEAST_EXPECT(meta.affected_nodes_size() == 5) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_CHECK; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 2); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 5) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_CHECK; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 2); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 1) && + BEAST_EXPECT(meta.affected_nodes_size() == 5) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_CHECK; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 2); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 5) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_CHECK; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 2); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 5) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_PAY_CHANNEL; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 2); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 2) && + + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_PAY_CHANNEL; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 5) && + + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_PAY_CHANNEL; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 2); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 1) && + BEAST_EXPECT(meta.affected_nodes_size() == 3) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ESCROW; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 3) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ESCROW; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 2) && + BEAST_EXPECT(meta.affected_nodes_size() == 3) && + + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ESCROW; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 1) && + BEAST_EXPECT(meta.affected_nodes_size() == 3) && + + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ESCROW; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 3) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_SIGNER_LIST; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 4) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_OFFER; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 1) && + BEAST_EXPECT(meta.affected_nodes_size() == 4) && + + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_OFFER; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 5) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_DIRECTORY_NODE; + }) == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_RIPPLE_STATE; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 2) && + BEAST_EXPECT(meta.affected_nodes_size() == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 1) && + BEAST_EXPECT(meta.affected_nodes_size() == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 2); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 2) && + BEAST_EXPECT(meta.affected_nodes_size() == 1) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 1); + }}, + {[this](auto meta) { + return BEAST_EXPECT(meta.transaction_index() == 0) && + BEAST_EXPECT(meta.affected_nodes_size() == 2) && + BEAST_EXPECT( + std::count_if( + meta.affected_nodes().begin(), + meta.affected_nodes().end(), + [](auto entry) { + return entry.ledger_entry_type() == + org::xrpl::rpc::v1::LedgerEntryType:: + LEDGER_ENTRY_TYPE_ACCOUNT_ROOT; + }) == 2); + }}}; + + auto doCheck = [this](auto txn, auto txCheck) { + return BEAST_EXPECT(txn.has_transaction()) && + BEAST_EXPECT(txn.validated()) && + BEAST_EXPECT(strHex(txn.hash()) == txCheck.hash) && + BEAST_EXPECT(txn.ledger_index() == txCheck.ledgerIndex) && + BEAST_EXPECT( + txn.transaction().sequence().value() == + txCheck.sequence) && + txCheck.checkTxn(txn.transaction()); + }; + + auto doMetaCheck = [this](auto txn, auto txMetaCheck) { + return BEAST_EXPECT(txn.has_meta()) && + BEAST_EXPECT(txn.meta().has_transaction_result()) && + BEAST_EXPECT( + txn.meta().transaction_result().result_type() == + org::xrpl::rpc::v1::TransactionResult:: + RESULT_TYPE_TES) && + BEAST_EXPECT( + txn.meta().transaction_result().result() == + "tesSUCCESS") && + txMetaCheck(txn.meta()); + }; + + auto [res, status] = next(grpcPort, env, alice.human()); + + if (!BEAST_EXPECT(status.error_code() == 0)) + return; + + if (!BEAST_EXPECT( + res.transactions().size() == + std::extent::value)) + return; + for (int i = 0; i < res.transactions().size(); ++i) + { + BEAST_EXPECT(doCheck(res.transactions()[i], txCheck[i])); + BEAST_EXPECT(doMetaCheck(res.transactions()[i], txMetaCheck[i])); + } + + // test binary representation + std::tie(res, status) = nextBinary(grpcPort, env, alice.human()); + + // txns vector does not contain the first two transactions returned by + // account_tx + if (!BEAST_EXPECT(res.transactions().size() == txns.size() + 2)) + return; + + std::reverse(txns.begin(), txns.end()); + for (int i = 0; i < txns.size(); ++i) + { + auto toByteString = [](auto data) { + const char* bytes = reinterpret_cast(data.data()); + return std::string(bytes, data.size()); + }; + + auto tx = txns[i]; + Serializer s = tx->getSerializer(); + std::string bin = toByteString(s); + + BEAST_EXPECT(res.transactions(i).transaction_binary() == bin); + } + } + + void + testAccountTxPagingGrpc() + { + testcase("Test Account_tx Grpc"); + + using namespace test::jtx; + std::unique_ptr config = envconfig(addGrpcConfig); + std::string grpcPort = *(*config)["port_grpc"].get("port"); + Env env(*this, std::move(config)); + + Account A1{"A1"}; + Account A2{"A2"}; + Account A3{"A3"}; + + env.fund(XRP(10000), A1, A2, A3); + env.close(); + + env.trust(A3["USD"](1000), A1); + env.trust(A2["USD"](1000), A1); + env.trust(A3["USD"](1000), A2); + env.close(); + + for (auto i = 0; i < 5; ++i) + { + env(pay(A2, A1, A2["USD"](2))); + env(pay(A3, A1, A3["USD"](2))); + env(offer(A1, XRP(11), A1["USD"](1))); + env(offer(A2, XRP(10), A2["USD"](1))); + env(offer(A3, XRP(9), A3["USD"](1))); + env.close(); + } + + /* The sequence/ledger for A3 are as follows: + * seq ledger_index + * 3 ----> 3 + * 1 ----> 3 + * 2 ----> 4 + * 2 ----> 4 + * 2 ----> 5 + * 3 ----> 5 + * 4 ----> 6 + * 5 ----> 6 + * 6 ----> 7 + * 7 ----> 7 + * 8 ----> 8 + * 9 ----> 8 + * 10 ----> 9 + * 11 ----> 9 + */ + + // page through the results in several ways. + { + // limit = 2, 3 batches giving the first 6 txs + auto [res, status] = next(grpcPort, env, A3.human(), 2, 5, 2, true); + + auto txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 2)) + return; + + BEAST_EXPECT(checkTransaction(txs[0u], 3, 3)); + BEAST_EXPECT(checkTransaction(txs[1u], 3, 3)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, env, A3.human(), 2, 5, 2, true, res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 2)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 4, 4)); + BEAST_EXPECT(checkTransaction(txs[1u], 4, 4)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, env, A3.human(), 2, 5, 2, true, res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 2)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 4, 5)); + BEAST_EXPECT(checkTransaction(txs[1u], 5, 5)); + BEAST_EXPECT(!res.has_marker()); + return; + } + + { + // limit 1, 3 requests giving the first 3 txs + auto [res, status] = next(grpcPort, env, A3.human(), 3, 9, 1, true); + auto txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 1)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 3, 3)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, env, A3.human(), 3, 9, 1, true, res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 1)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 3, 3)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, env, A3.human(), 3, 9, 1, true, res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 1)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 4, 4)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + // continue with limit 3, to end of all txs + std::tie(res, status) = next( + grpcPort, env, A3.human(), 3, 9, 3, true, res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 3)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 4, 4)); + BEAST_EXPECT(checkTransaction(txs[1u], 4, 5)); + BEAST_EXPECT(checkTransaction(txs[2u], 5, 5)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, env, A3.human(), 3, 9, 3, true, res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 3)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 6, 6)); + BEAST_EXPECT(checkTransaction(txs[1u], 7, 6)); + BEAST_EXPECT(checkTransaction(txs[2u], 8, 7)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, env, A3.human(), 3, 9, 3, true, res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 3)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 9, 7)); + BEAST_EXPECT(checkTransaction(txs[1u], 10, 8)); + BEAST_EXPECT(checkTransaction(txs[2u], 11, 8)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, env, A3.human(), 3, 9, 3, true, res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 2)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 12, 9)); + BEAST_EXPECT(checkTransaction(txs[1u], 13, 9)); + BEAST_EXPECT(!res.has_marker()); + } + + { + // limit 2, descending, 2 batches giving last 4 txs + auto [res, status] = + next(grpcPort, env, A3.human(), 3, 9, 2, false); + auto txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 2)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 13, 9)); + BEAST_EXPECT(checkTransaction(txs[1u], 12, 9)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, + env, + A3.human(), + 3, + 9, + 2, + false, + res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 2)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 11, 8)); + BEAST_EXPECT(checkTransaction(txs[1u], 10, 8)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + // continue with limit 3 until all txs have been seen + std::tie(res, status) = next( + grpcPort, + env, + A3.human(), + 3, + 9, + 3, + false, + res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 3)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 9, 7)); + BEAST_EXPECT(checkTransaction(txs[1u], 8, 7)); + BEAST_EXPECT(checkTransaction(txs[2u], 7, 6)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, + env, + A3.human(), + 3, + 9, + 3, + false, + res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 3)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 6, 6)); + BEAST_EXPECT(checkTransaction(txs[1u], 5, 5)); + BEAST_EXPECT(checkTransaction(txs[2u], 4, 5)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, + env, + A3.human(), + 3, + 9, + 3, + false, + res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 3)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 4, 4)); + BEAST_EXPECT(checkTransaction(txs[1u], 4, 4)); + BEAST_EXPECT(checkTransaction(txs[2u], 3, 3)); + if (!BEAST_EXPECT(res.has_marker())) + return; + + std::tie(res, status) = next( + grpcPort, + env, + A3.human(), + 3, + 9, + 3, + false, + res.mutable_marker()); + txs = res.transactions(); + if (!BEAST_EXPECT(txs.size() == 1)) + return; + BEAST_EXPECT(checkTransaction(txs[0u], 3, 3)); + BEAST_EXPECT(!res.has_marker()); + } + } + public: void run() override { testAccountTxPaging(); + testAccountTxPagingGrpc(); + testAccountTxParametersGrpc(); + testAccountTxContentsGrpc(); } }; diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 0b47064ae4..6f43a28ede 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -82,25 +82,6 @@ public: } }; -/** Set InvoiceID on a JTx. */ -class invoice_id -{ -private: - uint256 const id_; - -public: - explicit invoice_id (uint256 const& id) - : id_{id} - { - } - - void - operator()(Env&, JTx& jt) const - { - jt[sfInvoiceID.jsonName] = to_string (id_); - } -}; - } // namespace jtx } // namespace test diff --git a/src/test/jtx.h b/src/test/jtx.h index 3944dd9347..ac44f796a7 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -34,8 +35,10 @@ #include #include #include +#include #include #include +#include #include #include #include diff --git a/src/test/jtx/account_txn_id.h b/src/test/jtx/account_txn_id.h new file mode 100644 index 0000000000..52b65d3255 --- /dev/null +++ b/src/test/jtx/account_txn_id.h @@ -0,0 +1,42 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +#ifndef RIPPLE_TEST_JTX_ACCOUNT_TXN_ID_H_INCLUDED +#define RIPPLE_TEST_JTX_ACCOUNT_TXN_ID_H_INCLUDED + +#include + +namespace ripple { +namespace test { +namespace jtx { + +struct account_txn_id +{ + private: + uint256 hash_; +public: + explicit account_txn_id(uint256 const& hash) : hash_(hash) {} + +void +operator()(Env&, JTx& jt) const; +}; +} // jtx +} // test +} // ripple +#endif diff --git a/src/test/jtx/impl/account_txn_id.cpp b/src/test/jtx/impl/account_txn_id.cpp new file mode 100644 index 0000000000..2120b48e20 --- /dev/null +++ b/src/test/jtx/impl/account_txn_id.cpp @@ -0,0 +1,35 @@ +//------------------------------------------------------------------------------ +/* + 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 + +namespace ripple { +namespace test { +namespace jtx { + +void +account_txn_id::operator()(Env&, JTx& jt) const +{ + if (!hash_.isZero()) + jt["AccountTxnID"] = strHex(hash_); +} + +} // jtx +} // test +} // ripple diff --git a/src/test/jtx/impl/invoice_id.cpp b/src/test/jtx/impl/invoice_id.cpp new file mode 100644 index 0000000000..ab44c5dc02 --- /dev/null +++ b/src/test/jtx/impl/invoice_id.cpp @@ -0,0 +1,36 @@ + +//------------------------------------------------------------------------------ +/* + 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 + +namespace ripple { +namespace test { +namespace jtx { + +void +invoice_id::operator()(Env&, JTx& jt) const +{ + if (!hash_.isZero()) + jt["InvoiceID"] = strHex(hash_); +} + +} // jtx +} // test +} // ripple diff --git a/src/test/jtx/impl/last_ledger_sequence.cpp b/src/test/jtx/impl/last_ledger_sequence.cpp new file mode 100644 index 0000000000..69b3a75aca --- /dev/null +++ b/src/test/jtx/impl/last_ledger_sequence.cpp @@ -0,0 +1,37 @@ +//------------------------------------------------------------------------------ +/* + 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 { +namespace test { +namespace jtx { + + +void +last_ledger_seq::operator()(Env&, JTx& jt) const +{ + jt["LastLedgerSequence"] = num_; +} + +} // jtx +} // test +} // ripple + diff --git a/src/test/jtx/invoice_id.h b/src/test/jtx/invoice_id.h new file mode 100644 index 0000000000..fdca09c19e --- /dev/null +++ b/src/test/jtx/invoice_id.h @@ -0,0 +1,42 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +#ifndef RIPPLE_TEST_JTX_INVOICE_ID_H_INCLUDED +#define RIPPLE_TEST_JTX_INVOICE_ID_H_INCLUDED + +#include + +namespace ripple { +namespace test { +namespace jtx { + +struct invoice_id +{ + private: + uint256 hash_; +public: + explicit invoice_id(uint256 const& hash) : hash_(hash) {} + +void +operator()(Env&, JTx& jt) const; +}; +} // jtx +} // test +} // ripple +#endif diff --git a/src/test/jtx/last_ledger_sequence.h b/src/test/jtx/last_ledger_sequence.h new file mode 100644 index 0000000000..3126369570 --- /dev/null +++ b/src/test/jtx/last_ledger_sequence.h @@ -0,0 +1,44 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +#ifndef RIPPLE_TEST_JTX_LAST_LEDGER_SEQUENCE_H_INCLUDED +#define RIPPLE_TEST_JTX_LAST_LEDGER_SEQUENCE_H_INCLUDED + +#include + +namespace ripple { +namespace test { +namespace jtx { + +struct last_ledger_seq +{ + private: + std::uint32_t num_; +public: + explicit last_ledger_seq(std::uint32_t num) : num_(num) {} + +void +operator()(Env&, JTx& jt) const; +}; + +} // jtx +} // test +} // ripple + +#endif diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 0899426abc..ebb57cc734 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -324,8 +324,8 @@ public: class GetAccountInfoClient : public GRPCTestClientBase { public: - rpc::v1::GetAccountInfoRequest request; - rpc::v1::GetAccountInfoResponse reply; + org::xrpl::rpc::v1::GetAccountInfoRequest request; + org::xrpl::rpc::v1::GetAccountInfoResponse reply; explicit GetAccountInfoClient(std::string const& port) : GRPCTestClientBase(port) @@ -358,11 +358,10 @@ public: client.GetAccountInfo(); if (!BEAST_EXPECT(client.status.ok())) { - std::cout << client.reply.DebugString() << std::endl; return; } BEAST_EXPECT( - client.reply.account_data().account().address() == + client.reply.account_data().account().value().address() == alice.human()); } { @@ -374,13 +373,13 @@ public: if (!BEAST_EXPECT(client.status.ok())) return; BEAST_EXPECT( - client.reply.account_data().balance().drops() == + client.reply.account_data().balance().value().xrp_amount().drops() == 1000 * 1000 * 1000); BEAST_EXPECT( - client.reply.account_data().account().address() == + client.reply.account_data().account().value().address() == alice.human()); BEAST_EXPECT( - client.reply.account_data().sequence() == env.seq(alice)); + client.reply.account_data().sequence().value() == env.seq(alice)); BEAST_EXPECT(client.reply.queue_data().txn_count() == 0); } } @@ -473,7 +472,7 @@ public: { return; } - BEAST_EXPECT(client.reply.account_data().owner_count() == 1); + BEAST_EXPECT(client.reply.account_data().owner_count().value() == 1); BEAST_EXPECT(client.reply.signer_list().signer_entries_size() == 1); } @@ -518,16 +517,16 @@ public: { return; } - BEAST_EXPECT(client.reply.account_data().owner_count() == 1); + BEAST_EXPECT(client.reply.account_data().owner_count().value() == 1); auto& signerList = client.reply.signer_list(); - BEAST_EXPECT(signerList.signer_quorum() == 4); + BEAST_EXPECT(signerList.signer_quorum().value() == 4); BEAST_EXPECT(signerList.signer_entries_size() == 8); for (int i = 0; i < 8; ++i) { - BEAST_EXPECT(signerList.signer_entries(i).signer_weight() == 1); + BEAST_EXPECT(signerList.signer_entries(i).signer_weight().value() == 1); BEAST_EXPECT( accounts.erase( - signerList.signer_entries(i).account().address()) == 1); + signerList.signer_entries(i).account().value().address()) == 1); } BEAST_EXPECT(accounts.size() == 0); } diff --git a/src/test/rpc/Fee_test.cpp b/src/test/rpc/Fee_test.cpp index 887603cb82..04aa6f6fbe 100644 --- a/src/test/rpc/Fee_test.cpp +++ b/src/test/rpc/Fee_test.cpp @@ -23,13 +23,13 @@ #include #include #include +#include #include +#include #include #include #include -#include -#include namespace ripple { namespace test { @@ -39,8 +39,8 @@ class Fee_test : public beast::unit_test::suite class GrpcFeeClient : public GRPCTestClientBase { public: - rpc::v1::GetFeeRequest request; - rpc::v1::GetFeeResponse reply; + org::xrpl::rpc::v1::GetFeeRequest request; + org::xrpl::rpc::v1::GetFeeResponse reply; explicit GrpcFeeClient(std::string const& grpcPort) : GRPCTestClientBase(grpcPort) @@ -54,12 +54,12 @@ class Fee_test : public beast::unit_test::suite } }; - std::pair + std::pair grpcGetFee(std::string const& grpcPort) { GrpcFeeClient client(grpcPort); client.GetFee(); - return std::pair( + return std::pair( client.status.ok(), client.reply); } @@ -104,29 +104,29 @@ class Fee_test : public beast::unit_test::suite BEAST_EXPECT(reply.max_queue_size() == *metrics.txQMaxSize); // fee levels data - rpc::v1::FeeLevels& levels = *reply.mutable_levels(); + org::xrpl::rpc::v1::FeeLevels& levels = *reply.mutable_levels(); BEAST_EXPECT(levels.median_level() == metrics.medFeeLevel); BEAST_EXPECT(levels.minimum_level() == metrics.minProcessingFeeLevel); BEAST_EXPECT(levels.open_ledger_level() == metrics.openLedgerFeeLevel); BEAST_EXPECT(levels.reference_level() == metrics.referenceFeeLevel); // fee data - rpc::v1::Fee& drops = *reply.mutable_drops(); + org::xrpl::rpc::v1::Fee& fee = *reply.mutable_fee(); auto const baseFee = view->fees().base; BEAST_EXPECT( - drops.base_fee().drops() == + fee.base_fee().drops() == toDrops(metrics.referenceFeeLevel, baseFee).second); BEAST_EXPECT( - drops.minimum_fee().drops() == + fee.minimum_fee().drops() == toDrops(metrics.minProcessingFeeLevel, baseFee).second); BEAST_EXPECT( - drops.median_fee().drops() == + fee.median_fee().drops() == toDrops(metrics.medFeeLevel, baseFee).second); auto openLedgerFee = toDrops(metrics.openLedgerFeeLevel - FeeLevel64{1}, baseFee) .second + 1; - BEAST_EXPECT(drops.open_ledger_fee().drops() == openLedgerFee.drops()); + BEAST_EXPECT(fee.open_ledger_fee().drops() == openLedgerFee.drops()); } public: diff --git a/src/test/rpc/GRPCTestClientBase.h b/src/test/rpc/GRPCTestClientBase.h index 29d2e3598f..ef5b9955d4 100644 --- a/src/test/rpc/GRPCTestClientBase.h +++ b/src/test/rpc/GRPCTestClientBase.h @@ -20,7 +20,7 @@ #ifndef RIPPLED_GRPCTESTCLIENTBASE_H #define RIPPLED_GRPCTESTCLIENTBASE_H -#include +#include #include namespace ripple { @@ -29,7 +29,7 @@ namespace test { struct GRPCTestClientBase { explicit GRPCTestClientBase(std::string const& port) - : stub_(rpc::v1::XRPLedgerAPIService::NewStub(grpc::CreateChannel( + : stub_(org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(grpc::CreateChannel( beast::IP::Endpoint( boost::asio::ip::make_address(getEnvLocalhostAddr()), std::stoi(port)) @@ -40,7 +40,7 @@ struct GRPCTestClientBase grpc::Status status; grpc::ClientContext context; - std::unique_ptr stub_; + std::unique_ptr stub_; }; } // namespace test diff --git a/src/test/rpc/Submit_test.cpp b/src/test/rpc/Submit_test.cpp index f048f6aa83..2c7e2707bf 100644 --- a/src/test/rpc/Submit_test.cpp +++ b/src/test/rpc/Submit_test.cpp @@ -36,8 +36,8 @@ public: class SubmitClient : public GRPCTestClientBase { public: - rpc::v1::SubmitTransactionRequest request; - rpc::v1::SubmitTransactionResponse reply; + org::xrpl::rpc::v1::SubmitTransactionRequest request; + org::xrpl::rpc::v1::SubmitTransactionResponse reply; explicit SubmitClient(std::string const& port) : GRPCTestClientBase(port) diff --git a/src/test/rpc/Tx_test.cpp b/src/test/rpc/Tx_test.cpp index fe668fda59..a43c5af9d6 100644 --- a/src/test/rpc/Tx_test.cpp +++ b/src/test/rpc/Tx_test.cpp @@ -29,9 +29,12 @@ #include #include +#include #include #include +#include + namespace ripple { namespace test { @@ -46,16 +49,23 @@ class Tx_test : public beast::unit_test::suite } void - cmpAmount(const rpc::v1::CurrencyAmount& proto_amount, STAmount amount) + cmpAmount( + const org::xrpl::rpc::v1::CurrencyAmount& proto_amount, + STAmount amount) { if (amount.native()) { + if (!BEAST_EXPECT(proto_amount.has_xrp_amount())) + return; BEAST_EXPECT( proto_amount.xrp_amount().drops() == amount.xrp().drops()); } else { - rpc::v1::IssuedCurrencyAmount issuedCurrency = + if (!BEAST_EXPECT(proto_amount.has_issued_currency_amount())) + return; + + org::xrpl::rpc::v1::IssuedCurrencyAmount issuedCurrency = proto_amount.issued_currency_amount(); Issue const& issue = amount.issue(); Currency currency = issue.currency; @@ -70,60 +80,183 @@ class Tx_test : public beast::unit_test::suite } void - cmpTx(const rpc::v1::Transaction& proto, std::shared_ptr txnSt) + cmpPaymentTx( + const org::xrpl::rpc::v1::Transaction& proto, + std::shared_ptr txnSt) { + if (!BEAST_EXPECT(proto.has_payment())) + return; + + if (!BEAST_EXPECT( + safe_cast(txnSt->getFieldU16(sfTransactionType)) == + TxType::ttPAYMENT)) + return; + AccountID account = txnSt->getAccountID(sfAccount); - BEAST_EXPECT(proto.account().address() == toBase58(account)); + + if (!BEAST_EXPECT(proto.has_account())) + return; + BEAST_EXPECT(proto.account().value().address() == toBase58(account)); STAmount amount = txnSt->getFieldAmount(sfAmount); - cmpAmount(proto.payment().amount(), amount); + if (!BEAST_EXPECT(proto.payment().has_amount())) + return; + cmpAmount(proto.payment().amount().value(), amount); AccountID accountDest = txnSt->getAccountID(sfDestination); + if (!BEAST_EXPECT(proto.payment().has_destination())) + return; BEAST_EXPECT( - proto.payment().destination().address() == toBase58(accountDest)); + proto.payment().destination().value().address() == + toBase58(accountDest)); STAmount fee = txnSt->getFieldAmount(sfFee); + if (!BEAST_EXPECT(proto.has_fee())) + return; BEAST_EXPECT(proto.fee().drops() == fee.xrp().drops()); - BEAST_EXPECT(proto.sequence() == txnSt->getFieldU32(sfSequence)); + if (!BEAST_EXPECT(proto.has_sequence())) + return; + BEAST_EXPECT( + proto.sequence().value() == txnSt->getFieldU32(sfSequence)); + + if (!BEAST_EXPECT(proto.has_signing_public_key())) + return; Blob signingPubKey = txnSt->getFieldVL(sfSigningPubKey); - BEAST_EXPECT(proto.signing_public_key() == toByteString(signingPubKey)); - - BEAST_EXPECT(proto.flags() == txnSt->getFieldU32(sfFlags)); - BEAST_EXPECT( - proto.last_ledger_sequence() == - txnSt->getFieldU32(sfLastLedgerSequence)); + proto.signing_public_key().value() == toByteString(signingPubKey)); - Blob blob = txnSt->getFieldVL(sfTxnSignature); - BEAST_EXPECT(proto.signature() == toByteString(blob)); + if (txnSt->isFieldPresent(sfFlags)) + { + if (!BEAST_EXPECT(proto.has_flags())) + return; + BEAST_EXPECT(proto.flags().value() == txnSt->getFieldU32(sfFlags)); + } + else + { + BEAST_EXPECT(!proto.has_flags()); + } + + if (txnSt->isFieldPresent(sfLastLedgerSequence)) + { + if (!BEAST_EXPECT(proto.has_last_ledger_sequence())) + return; + + BEAST_EXPECT( + proto.last_ledger_sequence().value() == + txnSt->getFieldU32(sfLastLedgerSequence)); + } + else + { + BEAST_EXPECT(!proto.has_last_ledger_sequence()); + } + + if (txnSt->isFieldPresent(sfTxnSignature)) + { + if (!BEAST_EXPECT(proto.has_transaction_signature())) + return; + + Blob blob = txnSt->getFieldVL(sfTxnSignature); + BEAST_EXPECT( + proto.transaction_signature().value() == toByteString(blob)); + } if (txnSt->isFieldPresent(sfSendMax)) { + if (!BEAST_EXPECT(proto.payment().has_send_max())) + return; STAmount const& send_max = txnSt->getFieldAmount(sfSendMax); - cmpAmount(proto.payment().send_max(), send_max); + cmpAmount(proto.payment().send_max().value(), send_max); + } + else + { + BEAST_EXPECT(!proto.payment().has_send_max()); } if (txnSt->isFieldPresent(sfAccountTxnID)) { + if (!BEAST_EXPECT(proto.has_account_transaction_id())) + return; auto field = txnSt->getFieldH256(sfAccountTxnID); - BEAST_EXPECT(proto.account_transaction_id() == toByteString(field)); + BEAST_EXPECT( + proto.account_transaction_id().value() == toByteString(field)); + } + else + { + BEAST_EXPECT(!proto.has_account_transaction_id()); + } + + if (txnSt->isFieldPresent(sfSourceTag)) + { + if (!BEAST_EXPECT(proto.has_source_tag())) + return; + BEAST_EXPECT( + proto.source_tag().value() == txnSt->getFieldU32(sfSourceTag)); + } + else + { + BEAST_EXPECT(!proto.has_source_tag()); + } + + if (txnSt->isFieldPresent(sfDestinationTag)) + { + if (!BEAST_EXPECT(proto.payment().has_destination_tag())) + return; + + BEAST_EXPECT( + proto.payment().destination_tag().value() == + txnSt->getFieldU32(sfDestinationTag)); + } + else + { + BEAST_EXPECT(!proto.payment().has_destination_tag()); + } + + if (txnSt->isFieldPresent(sfInvoiceID)) + { + if (!BEAST_EXPECT(proto.payment().has_invoice_id())) + return; + + auto field = txnSt->getFieldH256(sfInvoiceID); + BEAST_EXPECT( + proto.payment().invoice_id().value() == toByteString(field)); + } + else + { + BEAST_EXPECT(!proto.payment().has_invoice_id()); + } + + if (txnSt->isFieldPresent(sfDeliverMin)) + { + if (!BEAST_EXPECT(proto.payment().has_deliver_min())) + return; + STAmount const& deliverMin = txnSt->getFieldAmount(sfDeliverMin); + cmpAmount(proto.payment().deliver_min().value(), deliverMin); + } + else + { + BEAST_EXPECT(!proto.payment().has_deliver_min()); } - // populate path data STPathSet const& pathset = txnSt->getFieldPathSet(sfPaths); + if (!BEAST_EXPECT(pathset.size() == proto.payment().paths_size())) + return; + int ind = 0; for (auto it = pathset.begin(); it < pathset.end(); ++it) { STPath const& path = *it; - const rpc::v1::Path& protoPath = proto.payment().paths(ind++); + const org::xrpl::rpc::v1::Payment_Path& protoPath = + proto.payment().paths(ind++); + if (!BEAST_EXPECT(protoPath.elements_size() == path.size())) + continue; int ind2 = 0; for (auto it2 = path.begin(); it2 != path.end(); ++it2) { - const rpc::v1::PathElement& protoElement = + const org::xrpl::rpc::v1::Payment_PathElement& protoElement = protoPath.elements(ind2++); STPathElement const& elt = *it2; @@ -132,47 +265,224 @@ class Tx_test : public beast::unit_test::suite if (elt.hasCurrency()) { Currency const& currency = elt.getCurrency(); - BEAST_EXPECT( - protoElement.currency().name() == - to_string(currency)); + if (BEAST_EXPECT(protoElement.has_currency())) + { + BEAST_EXPECT( + protoElement.currency().name() == + to_string(currency)); + } + } + else + { + BEAST_EXPECT(!protoElement.has_currency()); } if (elt.hasIssuer()) { AccountID const& issuer = elt.getIssuerID(); - BEAST_EXPECT( - protoElement.issuer().address() == - toBase58(issuer)); + if (BEAST_EXPECT(protoElement.has_issuer())) + { + BEAST_EXPECT( + protoElement.issuer().address() == + toBase58(issuer)); + } + } + else + { + BEAST_EXPECT(!protoElement.has_issuer()); } } else { - AccountID const& path_account = elt.getAccountID(); - BEAST_EXPECT( - protoElement.account().address() == - toBase58(path_account)); + if (BEAST_EXPECT(protoElement.has_account())) + { + AccountID const& path_account = elt.getAccountID(); + BEAST_EXPECT( + protoElement.account().address() == + toBase58(path_account)); + } + else + { + BEAST_EXPECT(!protoElement.has_account()); + } + + BEAST_EXPECT(!protoElement.has_issuer()); + BEAST_EXPECT(!protoElement.has_currency()); } } } + + if (txnSt->isFieldPresent(sfMemos)) + { + auto arr = txnSt->getFieldArray(sfMemos); + if (BEAST_EXPECT(proto.memos_size() == arr.size())) + { + for (size_t i = 0; i < arr.size(); ++i) + { + auto protoMemo = proto.memos(i); + auto stMemo = arr[i]; + + if (stMemo.isFieldPresent(sfMemoData)) + { + if (BEAST_EXPECT(protoMemo.has_memo_data())) + { + BEAST_EXPECT( + protoMemo.memo_data().value() == + toByteString(stMemo.getFieldVL(sfMemoData))); + } + } + else + { + BEAST_EXPECT(!protoMemo.has_memo_data()); + } + + if (stMemo.isFieldPresent(sfMemoType)) + { + if (BEAST_EXPECT(protoMemo.has_memo_type())) + { + BEAST_EXPECT( + protoMemo.memo_type().value() == + toByteString(stMemo.getFieldVL(sfMemoType))); + } + } + else + { + BEAST_EXPECT(!protoMemo.has_memo_type()); + } + + if (stMemo.isFieldPresent(sfMemoFormat)) + { + if (BEAST_EXPECT(protoMemo.has_memo_format())) + { + BEAST_EXPECT( + protoMemo.memo_format().value() == + toByteString(stMemo.getFieldVL(sfMemoFormat))); + } + } + else + { + BEAST_EXPECT(!protoMemo.has_memo_format()); + } + } + } + } + else + { + BEAST_EXPECT(proto.memos_size() == 0); + } + + if (txnSt->isFieldPresent(sfSigners)) + { + auto arr = txnSt->getFieldArray(sfSigners); + if (BEAST_EXPECT(proto.signers_size() == arr.size())) + { + for (size_t i = 0; i < arr.size(); ++i) + { + auto protoSigner = proto.signers(i); + auto stSigner = arr[i]; + + if (stSigner.isFieldPresent(sfAccount)) + { + if (BEAST_EXPECT(protoSigner.has_account())) + { + BEAST_EXPECT( + protoSigner.account().value().address() == + toBase58(stSigner.getAccountID(sfAccount))); + } + } + else + { + BEAST_EXPECT(!protoSigner.has_account()); + } + + if (stSigner.isFieldPresent(sfTxnSignature)) + { + if (BEAST_EXPECT( + protoSigner.has_transaction_signature())) + { + Blob blob = stSigner.getFieldVL(sfTxnSignature); + BEAST_EXPECT( + protoSigner.transaction_signature().value() == + toByteString(blob)); + } + } + else + { + BEAST_EXPECT(!protoSigner.has_transaction_signature()); + } + + if (stSigner.isFieldPresent(sfSigningPubKey)) + { + if (BEAST_EXPECT(protoSigner.has_signing_public_key())) + { + Blob signingPubKey = + stSigner.getFieldVL(sfSigningPubKey); + BEAST_EXPECT( + protoSigner.signing_public_key().value() == + toByteString(signingPubKey)); + } + } + else + { + BEAST_EXPECT(!protoSigner.has_signing_public_key()); + } + } + } + } + else + { + BEAST_EXPECT(proto.signers_size() == 0); + } } void - cmpMeta(const rpc::v1::Meta& proto, std::shared_ptr txMeta) + cmpMeta( + const org::xrpl::rpc::v1::Meta& proto, + std::shared_ptr txMeta) { BEAST_EXPECT(proto.transaction_index() == txMeta->getIndex()); BEAST_EXPECT( proto.transaction_result().result() == transToken(txMeta->getResultTER())); - rpc::v1::TransactionResult r; + org::xrpl::rpc::v1::TransactionResult r; - RPC::populateTransactionResultType(r, txMeta->getResultTER()); + RPC::convert(r, txMeta->getResultTER()); BEAST_EXPECT( proto.transaction_result().result_type() == r.result_type()); + } - if (txMeta->hasDeliveredAmount()) + void + cmpDeliveredAmount( + const org::xrpl::rpc::v1::Meta& meta, + const org::xrpl::rpc::v1::Transaction& txn, + const std::shared_ptr expMeta, + const std::shared_ptr expTxn, + bool checkAmount = true) + { + if (expMeta->hasDeliveredAmount()) { - cmpAmount(proto.delivered_amount(), txMeta->getDeliveredAmount()); + if (!BEAST_EXPECT(meta.has_delivered_amount())) + return; + cmpAmount( + meta.delivered_amount().value(), expMeta->getDeliveredAmount()); + } + else + { + if (expTxn->isFieldPresent(sfAmount)) + { + using namespace std::chrono_literals; + if (checkAmount) + { + cmpAmount( + meta.delivered_amount().value(), + expTxn->getFieldAmount(sfAmount)); + } + } + else + { + BEAST_EXPECT(!meta.has_delivered_amount()); + } } } @@ -180,8 +490,8 @@ class Tx_test : public beast::unit_test::suite class GrpcTxClient : public GRPCTestClientBase { public: - rpc::v1::GetTxRequest request; - rpc::v1::GetTxResponse reply; + org::xrpl::rpc::v1::GetTransactionRequest request; + org::xrpl::rpc::v1::GetTransactionResponse reply; explicit GrpcTxClient(std::string const& port) : GRPCTestClientBase(port) @@ -191,7 +501,7 @@ class Tx_test : public beast::unit_test::suite void Tx() { - status = stub_->GetTx(&context, request, &reply); + status = stub_->GetTransaction(&context, request, &reply); } }; @@ -205,33 +515,137 @@ class Tx_test : public beast::unit_test::suite std::string grpcPort = *(*config)["port_grpc"].get("port"); Env env(*this, std::move(config)); + using namespace std::chrono_literals; + // Set time to this value (or greater) to get delivered_amount in meta + env.timeKeeper().set(NetClock::time_point{446000001s}); + auto grpcTx = [&grpcPort](auto hash, auto binary) { GrpcTxClient client(grpcPort); client.request.set_hash(&hash, sizeof(hash)); client.request.set_binary(binary); client.Tx(); - return std::pair( + return std::pair( client.status.ok(), client.reply); }; Account A1{"A1"}; Account A2{"A2"}; + Account A3{"A3"}; env.fund(XRP(10000), A1); env.fund(XRP(10000), A2); env.close(); env.trust(A2["USD"](1000), A1); env.close(); + env(fset(A2, 5)); // set asfAccountTxnID flag + + // SignerListSet + env(signers(A2, 1, {{"bogie", 1}, {"demon", 1}, {A1, 1}, {A3, 1}}), + sig(A2)); + env.close(); std::vector> txns; auto const startLegSeq = env.current()->info().seq; + + uint256 prevHash; for (int i = 0; i < 14; ++i) { + auto const baseFee = env.current()->fees().base; + auto txfee = fee(i + (2 * baseFee)); + auto lls = last_ledger_seq(i + startLegSeq + 20); + auto dsttag = dtag(i * 456); + auto srctag = stag(i * 321); + auto sm = sendmax(A2["USD"](1000)); + auto dm = delivermin(A2["USD"](50)); + auto txf = txflags(131072); // partial payment flag + auto txnid = account_txn_id(prevHash); + auto inv = invoice_id(prevHash); + auto mem1 = memo("foo", "bar", "baz"); + auto mem2 = memo("dragons", "elves", "goblins"); + if (i & 1) - env(pay(A2, A1, A2["USD"](100))); + { + if (i & 2) + { + env(pay(A2, A1, A2["USD"](100)), + txfee, + srctag, + dsttag, + lls, + sm, + dm, + txf, + txnid, + inv, + mem1, + mem2, + sig(A2)); + } + else + { + env(pay(A2, A1, A2["USD"](100)), + txfee, + srctag, + dsttag, + lls, + sm, + dm, + txf, + txnid, + inv, + mem1, + mem2, + msig(A3)); + } + } else - env(pay(A2, A1, A2["XRP"](200))); + { + if (i & 2) + { + env(pay(A2, A1, A2["XRP"](200)), + txfee, + srctag, + dsttag, + lls, + txnid, + inv, + mem1, + mem2, + sig(A2)); + } + else + { + env(pay(A2, A1, A2["XRP"](200)), + txfee, + srctag, + dsttag, + lls, + txnid, + inv, + mem1, + mem2, + msig(A3)); + } + } txns.emplace_back(env.tx()); + prevHash = txns.back()->getTransactionID(); env.close(); } + + // Payment with Paths + auto const gw = Account("gateway"); + auto const USD = gw["USD"]; + env.fund(XRP(10000), "alice", "bob", gw); + env.trust(USD(600), "alice"); + env.trust(USD(700), "bob"); + env(pay(gw, "alice", USD(70))); + txns.emplace_back(env.tx()); + env.close(); + env(pay(gw, "bob", USD(50))); + txns.emplace_back(env.tx()); + env.close(); + env(pay("alice", "bob", Account("bob")["USD"](5)), path(gw)); + txns.emplace_back(env.tx()); + env.close(); + auto const endLegSeq = env.closed()->info().seq; // Find the existing transactions @@ -257,7 +671,7 @@ class Tx_test : public beast::unit_test::suite } else { - cmpTx(result.second.transaction(), tx); + cmpPaymentTx(result.second.transaction(), tx); } if (ledger && !b) @@ -269,6 +683,11 @@ class Tx_test : public beast::unit_test::suite id, ledger->seq(), *rawMeta); cmpMeta(result.second.meta(), txMeta); + cmpDeliveredAmount( + result.second.meta(), + result.second.transaction(), + txMeta, + tx); } } } @@ -299,6 +718,41 @@ class Tx_test : public beast::unit_test::suite BEAST_EXPECT(result.first == false); } + + // non final transaction + env(pay(A2, A1, A2["XRP"](200))); + auto res = grpcTx(env.tx()->getTransactionID(), false); + BEAST_EXPECT(res.first); + BEAST_EXPECT(res.second.has_transaction()); + if (!BEAST_EXPECT(res.second.has_meta())) + return; + if (!BEAST_EXPECT(res.second.meta().has_transaction_result())) + return; + + BEAST_EXPECT( + res.second.meta().transaction_result().result() == "tesSUCCESS"); + BEAST_EXPECT( + res.second.meta().transaction_result().result_type() == + org::xrpl::rpc::v1::TransactionResult::RESULT_TYPE_TES); + BEAST_EXPECT(!res.second.validated()); + BEAST_EXPECT(!res.second.meta().has_delivered_amount()); + env.close(); + + res = grpcTx(env.tx()->getTransactionID(), false); + BEAST_EXPECT(res.first); + BEAST_EXPECT(res.second.has_transaction()); + if (!BEAST_EXPECT(res.second.has_meta())) + return; + if (!BEAST_EXPECT(res.second.meta().has_transaction_result())) + return; + + BEAST_EXPECT( + res.second.meta().transaction_result().result() == "tesSUCCESS"); + BEAST_EXPECT( + res.second.meta().transaction_result().result_type() == + org::xrpl::rpc::v1::TransactionResult::RESULT_TYPE_TES); + BEAST_EXPECT(res.second.validated()); + BEAST_EXPECT(res.second.meta().has_delivered_amount()); } public: