From 7d867b806d70fc41fb45e3e61b719397033b272c Mon Sep 17 00:00:00 2001 From: CJ Cobb Date: Wed, 16 Oct 2019 12:44:25 -0700 Subject: [PATCH] Add gRPC support (#3127): * add support for AccountInfo, Fee and Submit RPCs * add partial support for Tx RPC (only supports Payments) --- .gitignore | 3 + .travis.yml | 4 +- Builds/CMake/RippledCore.cmake | 8 +- Builds/CMake/RippledSettings.cmake | 4 +- Builds/CMake/deps/Protobuf.cmake | 63 +- Builds/CMake/deps/gRPC.cmake | 343 ++++++++ .../containers/centos-builder/centos_setup.sh | 4 +- .../containers/packaging/dpkg/debian/control | 2 +- Builds/containers/packaging/dpkg/debian/rules | 1 - Builds/containers/packaging/rpm/rippled.spec | 4 +- Builds/containers/shared/build_deps.sh | 34 + .../containers/ubuntu-builder/ubuntu_setup.sh | 4 +- CMakeLists.txt | 1 + bin/sh/install-vcpkg.sh | 2 +- cfg/rippled-example.cfg | 4 + src/ripple/app/main/Application.cpp | 17 +- src/ripple/app/main/GRPCServer.cpp | 408 +++++++++ src/ripple/app/main/GRPCServer.h | 263 ++++++ src/ripple/overlay/impl/Message.cpp | 3 +- src/ripple/proto/rpc/v1/account_info.proto | 81 ++ src/ripple/proto/rpc/v1/amount.proto | 44 + src/ripple/proto/rpc/v1/fee.proto | 51 ++ src/ripple/proto/rpc/v1/ledger_objects.proto | 175 ++++ src/ripple/proto/rpc/v1/meta.proto | 82 ++ src/ripple/proto/rpc/v1/submit.proto | 28 + 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/Context.h | 29 +- src/ripple/rpc/DeliveredAmount.h | 10 + src/ripple/rpc/GRPCHandlers.h | 52 ++ src/ripple/rpc/RPCHandler.h | 4 +- src/ripple/rpc/handlers/AccountChannels.cpp | 2 +- .../rpc/handlers/AccountCurrenciesHandler.cpp | 2 +- src/ripple/rpc/handlers/AccountInfo.cpp | 96 ++- src/ripple/rpc/handlers/AccountLines.cpp | 2 +- src/ripple/rpc/handlers/AccountObjects.cpp | 2 +- src/ripple/rpc/handlers/AccountOffers.cpp | 2 +- src/ripple/rpc/handlers/AccountTx.cpp | 2 +- src/ripple/rpc/handlers/AccountTxOld.cpp | 2 +- src/ripple/rpc/handlers/AccountTxSwitch.cpp | 6 +- src/ripple/rpc/handlers/BlackList.cpp | 2 +- src/ripple/rpc/handlers/BookOffers.cpp | 2 +- src/ripple/rpc/handlers/CanDelete.cpp | 2 +- src/ripple/rpc/handlers/Connect.cpp | 2 +- src/ripple/rpc/handlers/ConsensusInfo.cpp | 2 +- src/ripple/rpc/handlers/CrawlShards.cpp | 2 +- src/ripple/rpc/handlers/DepositAuthorized.cpp | 2 +- src/ripple/rpc/handlers/DownloadShard.cpp | 2 +- src/ripple/rpc/handlers/Feature1.cpp | 2 +- src/ripple/rpc/handlers/Fee1.cpp | 53 +- src/ripple/rpc/handlers/FetchInfo.cpp | 2 +- src/ripple/rpc/handlers/GatewayBalances.cpp | 2 +- src/ripple/rpc/handlers/GetCounts.cpp | 2 +- src/ripple/rpc/handlers/Handlers.h | 124 +-- src/ripple/rpc/handlers/LedgerAccept.cpp | 2 +- .../rpc/handlers/LedgerCleanerHandler.cpp | 2 +- src/ripple/rpc/handlers/LedgerClosed.cpp | 2 +- src/ripple/rpc/handlers/LedgerCurrent.cpp | 2 +- src/ripple/rpc/handlers/LedgerData.cpp | 2 +- src/ripple/rpc/handlers/LedgerEntry.cpp | 2 +- src/ripple/rpc/handlers/LedgerHandler.cpp | 2 +- src/ripple/rpc/handlers/LedgerHandler.h | 6 +- src/ripple/rpc/handlers/LedgerHeader.cpp | 2 +- src/ripple/rpc/handlers/LedgerRequest.cpp | 2 +- src/ripple/rpc/handlers/LogLevel.cpp | 2 +- src/ripple/rpc/handlers/LogRotate.cpp | 2 +- src/ripple/rpc/handlers/NoRippleCheck.cpp | 4 +- src/ripple/rpc/handlers/OwnerInfo.cpp | 2 +- src/ripple/rpc/handlers/PathFind.cpp | 2 +- src/ripple/rpc/handlers/PayChanClaim.cpp | 4 +- src/ripple/rpc/handlers/Peers.cpp | 2 +- src/ripple/rpc/handlers/Ping.cpp | 4 +- src/ripple/rpc/handlers/Print.cpp | 2 +- src/ripple/rpc/handlers/Random.cpp | 4 +- src/ripple/rpc/handlers/Reservations.cpp | 6 +- src/ripple/rpc/handlers/RipplePathFind.cpp | 2 +- src/ripple/rpc/handlers/ServerInfo.cpp | 2 +- src/ripple/rpc/handlers/ServerState.cpp | 2 +- src/ripple/rpc/handlers/SignFor.cpp | 2 +- src/ripple/rpc/handlers/SignHandler.cpp | 2 +- src/ripple/rpc/handlers/Stop.cpp | 4 +- src/ripple/rpc/handlers/Submit.cpp | 104 ++- src/ripple/rpc/handlers/SubmitMultiSigned.cpp | 2 +- src/ripple/rpc/handlers/Subscribe.cpp | 2 +- src/ripple/rpc/handlers/TransactionEntry.cpp | 2 +- src/ripple/rpc/handlers/Tx.cpp | 133 ++- src/ripple/rpc/handlers/TxHistory.cpp | 2 +- src/ripple/rpc/handlers/UnlList.cpp | 2 +- src/ripple/rpc/handlers/Unsubscribe.cpp | 2 +- src/ripple/rpc/handlers/ValidationCreate.cpp | 2 +- .../rpc/handlers/ValidatorListSites.cpp | 2 +- src/ripple/rpc/handlers/Validators.cpp | 2 +- src/ripple/rpc/handlers/Version.h | 2 +- src/ripple/rpc/handlers/WalletPropose.cpp | 2 +- src/ripple/rpc/impl/DeliveredAmount.cpp | 81 +- src/ripple/rpc/impl/Handler.cpp | 4 +- src/ripple/rpc/impl/Handler.h | 54 +- src/ripple/rpc/impl/RPCHandler.cpp | 51 +- src/ripple/rpc/impl/RPCHelpers.cpp | 799 +++++++++++++++++- src/ripple/rpc/impl/RPCHelpers.h | 76 +- src/ripple/rpc/impl/ServerHandlerImp.cpp | 42 +- src/ripple/unity/app_main1.cpp | 1 + src/test/app/Path_test.cpp | 28 +- src/test/jtx/envconfig.h | 15 +- src/test/jtx/impl/envconfig.cpp | 15 +- src/test/rpc/AccountInfo_test.cpp | 232 ++++- src/test/rpc/Fee_test.cpp | 143 ++++ src/test/rpc/GRPCTestClientBase.h | 48 ++ src/test/rpc/Submit_test.cpp | 281 ++++++ src/test/rpc/Tx_test.cpp | 314 +++++++ src/test/unity/rpc_test_unity.cpp | 3 + 112 files changed, 4428 insertions(+), 296 deletions(-) create mode 100644 Builds/CMake/deps/gRPC.cmake create mode 100644 src/ripple/app/main/GRPCServer.cpp create mode 100644 src/ripple/app/main/GRPCServer.h create mode 100644 src/ripple/proto/rpc/v1/account_info.proto create mode 100644 src/ripple/proto/rpc/v1/amount.proto create mode 100644 src/ripple/proto/rpc/v1/fee.proto create mode 100644 src/ripple/proto/rpc/v1/ledger_objects.proto create mode 100644 src/ripple/proto/rpc/v1/meta.proto create mode 100644 src/ripple/proto/rpc/v1/submit.proto create mode 100644 src/ripple/proto/rpc/v1/transaction.proto create mode 100644 src/ripple/proto/rpc/v1/tx.proto create mode 100644 src/ripple/proto/rpc/v1/xrp_ledger.proto create mode 100644 src/ripple/rpc/GRPCHandlers.h create mode 100644 src/test/rpc/Fee_test.cpp create mode 100644 src/test/rpc/GRPCTestClientBase.h create mode 100644 src/test/rpc/Submit_test.cpp create mode 100644 src/test/rpc/Tx_test.cpp diff --git a/.gitignore b/.gitignore index a21fee6417f..f6ed188fba0 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,9 @@ build .nih_c tags TAGS +GTAGS +GRTAGS +GPATH bin/rippled Debug/*.* Release/*.* diff --git a/.travis.yml b/.travis.yml index b45d7198005..2dbe21446c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ services: env: global: - - DOCKER_IMAGE="mellery451/rippled-ci-builder:2019-08-26" + - DOCKER_IMAGE="mellery451/rippled-ci-builder:2019-11-27_grpc" - CMAKE_EXTRA_ARGS="-Dwerr=ON -Dwextra=ON" - NINJA_BUILD=true # change this if we get more VM capacity @@ -225,6 +225,8 @@ matrix: addons: homebrew: packages: + - protobuf + - grpc - bash - ninja - cmake diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 00ce8999007..bf457579825 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -435,6 +435,7 @@ else () src/ripple/app/main/Application.cpp src/ripple/app/main/BasicApp.cpp src/ripple/app/main/CollectorManager.cpp + src/ripple/app/main/GRPCServer.cpp src/ripple/app/main/LoadManager.cpp src/ripple/app/main/Main.cpp src/ripple/app/main/NodeIdentity.cpp @@ -969,6 +970,7 @@ else () src/test/rpc/DepositAuthorized_test.cpp src/test/rpc/DeliveredAmount_test.cpp src/test/rpc/Feature_test.cpp + src/test/rpc/Fee_test.cpp src/test/rpc/GatewayBalances_test.cpp src/test/rpc/GetCounts_test.cpp src/test/rpc/JSONRPC_test.cpp @@ -987,10 +989,12 @@ else () src/test/rpc/RobustTransaction_test.cpp src/test/rpc/ServerInfo_test.cpp src/test/rpc/Status_test.cpp + src/test/rpc/Submit_test.cpp src/test/rpc/Subscribe_test.cpp src/test/rpc/Transaction_test.cpp src/test/rpc/TransactionEntry_test.cpp src/test/rpc/TransactionHistory_test.cpp + src/test/rpc/Tx_test.cpp src/test/rpc/ValidatorRPC_test.cpp src/test/rpc/Version_test.cpp #[===============================[ @@ -1016,10 +1020,12 @@ target_link_libraries (rippled Ripple::boost Ripple::opts Ripple::libs - Ripple::xrpl_core) + Ripple::xrpl_core + ) exclude_if_included (rippled) # define a macro for tests that might need to # be exluded or run differently in CI environment if (is_ci) target_compile_definitions(rippled PRIVATE RIPPLED_RUNNING_IN_CI) endif () + diff --git a/Builds/CMake/RippledSettings.cmake b/Builds/CMake/RippledSettings.cmake index edfdab9cf8d..010a94b439f 100644 --- a/Builds/CMake/RippledSettings.cmake +++ b/Builds/CMake/RippledSettings.cmake @@ -44,7 +44,9 @@ endif () option (jemalloc "Enables jemalloc for heap profiling" OFF) option (werr "treat warnings as errors" OFF) option (local_protobuf - "Force use of a local build of protobuf instead of system version." OFF) + "Force a local build of protobuf instead of looking for an installed version." OFF) +option (local_grpc + "Force a local build of gRPC instead of looking for an installed version." OFF) # this one is a string and therefore can't be an option set (san "" CACHE STRING "On gcc & clang, add sanitizer instrumentation") diff --git a/Builds/CMake/deps/Protobuf.cmake b/Builds/CMake/deps/Protobuf.cmake index 4ca55d1fa7f..cf54c1fb7de 100644 --- a/Builds/CMake/deps/Protobuf.cmake +++ b/Builds/CMake/deps/Protobuf.cmake @@ -8,8 +8,8 @@ if (static) set (Protobuf_USE_STATIC_LIBS ON) endif () -find_package (Protobuf) -if (local_protobuf OR NOT TARGET protobuf::libprotobuf) +find_package (Protobuf 3.8) +if (local_protobuf OR NOT Protobuf_FOUND) message (STATUS "using local protobuf build.") if (WIN32) # protobuf prepends lib even on windows @@ -25,11 +25,12 @@ if (local_protobuf OR NOT TARGET protobuf::libprotobuf) ExternalProject_Add (protobuf_src PREFIX ${nih_cache_path} GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git - GIT_TAG v3.6.1 + GIT_TAG v3.8.0 SOURCE_SUBDIR cmake CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_INSTALL_PREFIX=/_installed_ -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_BUILD_EXAMPLES=OFF -Dprotobuf_BUILD_PROTOC_BINARIES=ON @@ -51,51 +52,67 @@ if (local_protobuf OR NOT TARGET protobuf::libprotobuf) --build . --config $ $<$:--parallel ${ep_procs}> - $<$: - COMMAND - ${CMAKE_COMMAND} -E copy - /$/${pbuf_lib_pre}protobuf$<$:_d>${ep_lib_suffix} - - COMMAND - ${CMAKE_COMMAND} -E copy - /$/protoc${CMAKE_EXECUTABLE_SUFFIX} - - > TEST_COMMAND "" - INSTALL_COMMAND "" + INSTALL_COMMAND + ${CMAKE_COMMAND} -E env --unset=DESTDIR ${CMAKE_COMMAND} --build . --config $ --target install BUILD_BYPRODUCTS - /${pbuf_lib_pre}protobuf${ep_lib_suffix} - /${pbuf_lib_pre}protobuf_d${ep_lib_suffix} - /protoc${CMAKE_EXECUTABLE_SUFFIX} + /_installed_/lib/${pbuf_lib_pre}protobuf${ep_lib_suffix} + /_installed_/lib/${pbuf_lib_pre}protobuf_d${ep_lib_suffix} + /_installed_/lib/${pbuf_lib_pre}protoc${ep_lib_suffix} + /_installed_/lib/${pbuf_lib_pre}protoc_d${ep_lib_suffix} + /_installed_/bin/protoc${CMAKE_EXECUTABLE_SUFFIX} ) ExternalProject_Get_Property (protobuf_src BINARY_DIR) ExternalProject_Get_Property (protobuf_src SOURCE_DIR) if (CMAKE_VERBOSE_MAKEFILE) print_ep_logs (protobuf_src) endif () + exclude_if_included (protobuf_src) if (NOT TARGET protobuf::libprotobuf) add_library (protobuf::libprotobuf STATIC IMPORTED GLOBAL) endif () - file (MAKE_DIRECTORY ${SOURCE_DIR}/src) + file (MAKE_DIRECTORY ${BINARY_DIR}/_installed_/include) set_target_properties (protobuf::libprotobuf PROPERTIES IMPORTED_LOCATION_DEBUG - ${BINARY_DIR}/${pbuf_lib_pre}protobuf_d${ep_lib_suffix} + ${BINARY_DIR}/_installed_/lib/${pbuf_lib_pre}protobuf_d${ep_lib_suffix} IMPORTED_LOCATION_RELEASE - ${BINARY_DIR}/${pbuf_lib_pre}protobuf${ep_lib_suffix} + ${BINARY_DIR}/_installed_/lib/${pbuf_lib_pre}protobuf${ep_lib_suffix} INTERFACE_INCLUDE_DIRECTORIES - ${SOURCE_DIR}/src) + ${BINARY_DIR}/_installed_/include) add_dependencies (protobuf::libprotobuf protobuf_src) - exclude_if_included (protobuf_src) exclude_if_included (protobuf::libprotobuf) + if (NOT TARGET protobuf::libprotoc) + add_library (protobuf::libprotoc STATIC IMPORTED GLOBAL) + endif () + set_target_properties (protobuf::libprotoc PROPERTIES + IMPORTED_LOCATION_DEBUG + ${BINARY_DIR}/_installed_/lib/${pbuf_lib_pre}protoc_d${ep_lib_suffix} + IMPORTED_LOCATION_RELEASE + ${BINARY_DIR}/_installed_/lib/${pbuf_lib_pre}protoc${ep_lib_suffix} + INTERFACE_INCLUDE_DIRECTORIES + ${BINARY_DIR}/_installed_/include) + add_dependencies (protobuf::libprotoc protobuf_src) + exclude_if_included (protobuf::libprotoc) + if (NOT TARGET protobuf::protoc) add_executable (protobuf::protoc IMPORTED) exclude_if_included (protobuf::protoc) endif () set_target_properties (protobuf::protoc PROPERTIES - IMPORTED_LOCATION "${BINARY_DIR}/protoc${CMAKE_EXECUTABLE_SUFFIX}") + IMPORTED_LOCATION "${BINARY_DIR}/_installed_/bin/protoc${CMAKE_EXECUTABLE_SUFFIX}") add_dependencies (protobuf::protoc protobuf_src) +else () + if (NOT TARGET protobuf::protoc) + if (EXISTS "${Protobuf_PROTOC_EXECUTABLE}") + add_executable (protobuf::protoc IMPORTED) + set_target_properties (protobuf::protoc PROPERTIES + IMPORTED_LOCATION "${Protobuf_PROTOC_EXECUTABLE}") + else () + message (FATAL_ERROR "Protobuf import failed") + endif () + endif () endif () file (MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/proto_gen) diff --git a/Builds/CMake/deps/gRPC.cmake b/Builds/CMake/deps/gRPC.cmake new file mode 100644 index 00000000000..97e4bbfd003 --- /dev/null +++ b/Builds/CMake/deps/gRPC.cmake @@ -0,0 +1,343 @@ + +# currently linking to unsecure versions...if we switch, we'll +# need to add ssl as a link dependency to the grpc targets +option (use_secure_grpc "use TLS version of grpc libs." OFF) +if (use_secure_grpc) + set (grpc_suffix "") +else () + set (grpc_suffix "_unsecure") +endif () + +find_package (PkgConfig QUIET) +if (PKG_CONFIG_FOUND) + pkg_check_modules (grpc QUIET "grpc${grpc_suffix}>=1.25" "grpc++${grpc_suffix}" gpr) +endif () + +if (grpc_FOUND) + message (STATUS "Found gRPC using pkg-config. Using ${grpc_gpr_PREFIX}.") +endif () + +add_executable (gRPC::grpc_cpp_plugin IMPORTED) +exclude_if_included (gRPC::grpc_cpp_plugin) + +if (grpc_FOUND AND NOT local_grpc) + # use installed grpc (via pkg-config) + macro (add_imported_grpc libname_) + if (static) + set (_search "${CMAKE_STATIC_LIBRARY_PREFIX}${libname_}${CMAKE_STATIC_LIBRARY_SUFFIX}") + else () + set (_search "${CMAKE_SHARED_LIBRARY_PREFIX}${libname_}${CMAKE_SHARED_LIBRARY_SUFFIX}") + endif() + find_library(_found_${libname_} + NAMES ${_search} + HINTS ${grpc_LIBRARY_DIRS}) + if (_found_${libname_}) + message (STATUS "importing ${libname_} as ${_found_${libname_}}") + else () + message (FATAL_ERROR "using pkg-config for grpc, can't find ${_search}") + endif () + add_library ("gRPC::${libname_}" STATIC IMPORTED GLOBAL) + set_target_properties ("gRPC::${libname_}" PROPERTIES IMPORTED_LOCATION ${_found_${libname_}}) + if (grpc_INCLUDE_DIRS) + set_target_properties ("gRPC::${libname_}" PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${grpc_INCLUDE_DIRS}) + endif () + target_link_libraries (ripple_libs INTERFACE "gRPC::${libname_}") + exclude_if_included ("gRPC::${libname_}") + endmacro () + + set_target_properties (gRPC::grpc_cpp_plugin PROPERTIES + IMPORTED_LOCATION "${grpc_gpr_PREFIX}/bin/grpc_cpp_plugin${CMAKE_EXECUTABLE_SUFFIX}") + + pkg_check_modules (cares QUIET libcares) + if (cares_FOUND) + if (static) + set (_search "${CMAKE_STATIC_LIBRARY_PREFIX}cares${CMAKE_STATIC_LIBRARY_SUFFIX}") + else () + set (_search "${CMAKE_SHARED_LIBRARY_PREFIX}cares${CMAKE_SHARED_LIBRARY_SUFFIX}") + endif() + find_library(_cares + NAMES ${_search} + HINTS ${cares_LIBRARY_DIRS}) + if (NOT _cares) + message (FATAL_ERROR "using pkg-config for grpc, can't find c-ares") + endif () + add_library (c-ares::cares STATIC IMPORTED GLOBAL) + set_target_properties (c-ares::cares PROPERTIES IMPORTED_LOCATION ${_cares}) + if (cares_INCLUDE_DIRS) + set_target_properties (c-ares::cares PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${cares_INCLUDE_DIRS}) + endif () + exclude_if_included (c-ares::cares) + else () + message (FATAL_ERROR "using pkg-config for grpc, can't find c-ares") + endif () +else () + #[===========================[ + c-ares (grpc requires) + #]===========================] + ExternalProject_Add (c-ares_src + PREFIX ${nih_cache_path} + GIT_REPOSITORY https://github.com/c-ares/c-ares.git + GIT_TAG cares-1_15_0 + CMAKE_ARGS + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + $<$:-DCMAKE_VERBOSE_MAKEFILE=ON> + -DCMAKE_DEBUG_POSTFIX=_d + $<$>:-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}> + -DCMAKE_INSTALL_PREFIX=/_installed_ + -DCARES_SHARED=OFF + -DCARES_STATIC=ON + -DCARES_STATIC_PIC=ON + -DCARES_INSTALL=ON + -DCARES_MSVC_STATIC_RUNTIME=ON + $<$: + "-DCMAKE_C_FLAGS=-GR -Gd -fp:precise -FS -MP" + > + LOG_BUILD ON + LOG_CONFIGURE ON + BUILD_COMMAND + ${CMAKE_COMMAND} + --build . + --config $ + $<$:--parallel ${ep_procs}> + TEST_COMMAND "" + INSTALL_COMMAND + ${CMAKE_COMMAND} -E env --unset=DESTDIR ${CMAKE_COMMAND} --build . --config $ --target install + BUILD_BYPRODUCTS + /_installed_/lib/${ep_lib_prefix}cares${ep_lib_suffix} + /_installed_/lib/${ep_lib_prefix}cares_d${ep_lib_suffix} + ) + exclude_if_included (c-ares_src) + ExternalProject_Get_Property (c-ares_src BINARY_DIR) + set (cares_binary_dir "${BINARY_DIR}") + + add_library (c-ares::cares STATIC IMPORTED GLOBAL) + file (MAKE_DIRECTORY ${BINARY_DIR}/_installed_/include) + set_target_properties (c-ares::cares PROPERTIES + IMPORTED_LOCATION_DEBUG + ${BINARY_DIR}/_installed_/lib/${ep_lib_prefix}cares_d${ep_lib_suffix} + IMPORTED_LOCATION_RELEASE + ${BINARY_DIR}/_installed_/lib/${ep_lib_prefix}cares${ep_lib_suffix} + INTERFACE_INCLUDE_DIRECTORIES + ${BINARY_DIR}/_installed_/include) + add_dependencies (c-ares::cares c-ares_src) + exclude_if_included (c-ares::cares) + + if (NOT has_zlib) + #[===========================[ + zlib (grpc requires) + #]===========================] + if (MSVC) + set (zlib_debug_postfix "d") # zlib cmake sets this internally for MSVC, so we really don't have a choice + set (zlib_base "zlibstatic") + else () + set (zlib_debug_postfix "_d") + set (zlib_base "z") + endif () + ExternalProject_Add (zlib_src + PREFIX ${nih_cache_path} + GIT_REPOSITORY https://github.com/madler/zlib.git + GIT_TAG v1.2.11 + CMAKE_ARGS + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + $<$:-DCMAKE_VERBOSE_MAKEFILE=ON> + -DCMAKE_DEBUG_POSTFIX=${zlib_debug_postfix} + $<$>:-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}> + -DCMAKE_INSTALL_PREFIX=/_installed_ + -DBUILD_SHARED_LIBS=OFF + $<$: + "-DCMAKE_C_FLAGS=-GR -Gd -fp:precise -FS -MP" + "-DCMAKE_C_FLAGS_DEBUG=-MTd" + "-DCMAKE_C_FLAGS_RELEASE=-MT" + > + LOG_BUILD ON + LOG_CONFIGURE ON + BUILD_COMMAND + ${CMAKE_COMMAND} + --build . + --config $ + $<$:--parallel ${ep_procs}> + TEST_COMMAND "" + INSTALL_COMMAND + ${CMAKE_COMMAND} -E env --unset=DESTDIR ${CMAKE_COMMAND} --build . --config $ --target install + BUILD_BYPRODUCTS + /_installed_/lib/${ep_lib_prefix}${zlib_base}${ep_lib_suffix} + /_installed_/lib/${ep_lib_prefix}${zlib_base}${zlib_debug_postfix}${ep_lib_suffix} + ) + exclude_if_included (zlib_src) + ExternalProject_Get_Property (zlib_src BINARY_DIR) + set (zlib_binary_dir "${BINARY_DIR}") + + add_library (ZLIB::ZLIB STATIC IMPORTED GLOBAL) + file (MAKE_DIRECTORY ${BINARY_DIR}/_installed_/include) + set_target_properties (ZLIB::ZLIB PROPERTIES + IMPORTED_LOCATION_DEBUG + ${BINARY_DIR}/_installed_/lib/${ep_lib_prefix}${zlib_base}${zlib_debug_postfix}${ep_lib_suffix} + IMPORTED_LOCATION_RELEASE + ${BINARY_DIR}/_installed_/lib/${ep_lib_prefix}${zlib_base}${ep_lib_suffix} + INTERFACE_INCLUDE_DIRECTORIES + ${BINARY_DIR}/_installed_/include) + add_dependencies (ZLIB::ZLIB zlib_src) + exclude_if_included (ZLIB::ZLIB) + endif () + + #[===========================[ + grpc + #]===========================] + ExternalProject_Add (grpc_src + PREFIX ${nih_cache_path} + GIT_REPOSITORY https://github.com/grpc/grpc.git + GIT_TAG v1.25.0 + CMAKE_ARGS + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + $<$:-DCMAKE_VERBOSE_MAKEFILE=ON> + $<$:-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}> + $<$:-DVCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}> + -DCMAKE_DEBUG_POSTFIX=_d + $<$>:-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}> + -DgRPC_BUILD_TESTS=OFF + -DgRPC_BUILD_CSHARP_EXT=OFF + -DgRPC_MSVC_STATIC_RUNTIME=ON + -DgRPC_INSTALL=OFF + -DgRPC_CARES_PROVIDER=package + -Dc-ares_DIR=${cares_binary_dir}/_installed_/lib/cmake/c-ares + -DgRPC_SSL_PROVIDER=package + -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} + -DgRPC_PROTOBUF_PROVIDER=package + -DProtobuf_USE_STATIC_LIBS=$,$>>,OFF,ON> + -DProtobuf_INCLUDE_DIR=$,:_:> + -DProtobuf_LIBRARY=$,$,$> + -DProtobuf_PROTOC_LIBRARY=$,$,$> + -DProtobuf_PROTOC_EXECUTABLE=$ + -DgRPC_ZLIB_PROVIDER=package + $<$>:-DZLIB_ROOT=${zlib_binary_dir}/_installed_> + $<$: + "-DCMAKE_CXX_FLAGS=-GR -Gd -fp:precise -FS -EHa -MP" + "-DCMAKE_C_FLAGS=-GR -Gd -fp:precise -FS -MP" + > + LOG_BUILD ON + LOG_CONFIGURE ON + BUILD_COMMAND + ${CMAKE_COMMAND} + --build . + --config $ + $<$:--parallel ${ep_procs}> + $<$: + COMMAND + ${CMAKE_COMMAND} -E copy + /$/${ep_lib_prefix}grpc${grpc_suffix}$<$:_d>${ep_lib_suffix} + /$/${ep_lib_prefix}grpc++${grpc_suffix}$<$:_d>${ep_lib_suffix} + /$/${ep_lib_prefix}address_sorting$<$:_d>${ep_lib_suffix} + /$/${ep_lib_prefix}gpr$<$:_d>${ep_lib_suffix} + /$/grpc_cpp_plugin${CMAKE_EXECUTABLE_SUFFIX} + + > + LIST_SEPARATOR :_: + TEST_COMMAND "" + INSTALL_COMMAND "" + DEPENDS c-ares_src + BUILD_BYPRODUCTS + /${ep_lib_prefix}grpc${grpc_suffix}${ep_lib_suffix} + /${ep_lib_prefix}grpc${grpc_suffix}_d${ep_lib_suffix} + /${ep_lib_prefix}grpc++${grpc_suffix}${ep_lib_suffix} + /${ep_lib_prefix}grpc++${grpc_suffix}_d${ep_lib_suffix} + /${ep_lib_prefix}address_sorting${ep_lib_suffix} + /${ep_lib_prefix}address_sorting_d${ep_lib_suffix} + /${ep_lib_prefix}gpr${ep_lib_suffix} + /${ep_lib_prefix}gpr_d${ep_lib_suffix} + /grpc_cpp_plugin${CMAKE_EXECUTABLE_SUFFIX} + ) + if (TARGET protobuf_src) + ExternalProject_Add_StepDependencies(grpc_src build protobuf_src) + endif () + exclude_if_included (grpc_src) + ExternalProject_Get_Property (grpc_src BINARY_DIR) + ExternalProject_Get_Property (grpc_src SOURCE_DIR) + set (grpc_binary_dir "${BINARY_DIR}") + set (grpc_source_dir "${SOURCE_DIR}") + if (CMAKE_VERBOSE_MAKEFILE) + print_ep_logs (grpc_src) + endif () + file (MAKE_DIRECTORY ${SOURCE_DIR}/include) + + macro (add_imported_grpc libname_) + add_library ("gRPC::${libname_}" STATIC IMPORTED GLOBAL) + set_target_properties ("gRPC::${libname_}" PROPERTIES + IMPORTED_LOCATION_DEBUG + ${grpc_binary_dir}/${ep_lib_prefix}${libname_}_d${ep_lib_suffix} + IMPORTED_LOCATION_RELEASE + ${grpc_binary_dir}/${ep_lib_prefix}${libname_}${ep_lib_suffix} + INTERFACE_INCLUDE_DIRECTORIES + ${grpc_source_dir}/include) + add_dependencies ("gRPC::${libname_}" grpc_src) + target_link_libraries (ripple_libs INTERFACE "gRPC::${libname_}") + exclude_if_included ("gRPC::${libname_}") + endmacro () + + set_target_properties (gRPC::grpc_cpp_plugin PROPERTIES + IMPORTED_LOCATION "${grpc_binary_dir}/grpc_cpp_plugin${CMAKE_EXECUTABLE_SUFFIX}") + add_dependencies (gRPC::grpc_cpp_plugin grpc_src) +endif () + +add_imported_grpc (gpr) +add_imported_grpc ("grpc${grpc_suffix}") +add_imported_grpc ("grpc++${grpc_suffix}") +add_imported_grpc (address_sorting) + +target_link_libraries ("gRPC::grpc${grpc_suffix}" INTERFACE c-ares::cares gRPC::gpr gRPC::address_sorting ZLIB::ZLIB) +target_link_libraries ("gRPC::grpc++${grpc_suffix}" INTERFACE "gRPC::grpc${grpc_suffix}" gRPC::gpr) + +#[=================================[ + generate protobuf sources for + grpc defs and bundle into a + static lib +#]=================================] +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") +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) + get_filename_component(_abs_dir ${_abs_file} DIRECTORY) + get_filename_component(_basename ${file} NAME_WE) + get_filename_component(_proto_inc ${GRPC_PROTO_ROOT} DIRECTORY) # updir one level + file(RELATIVE_PATH _rel_root_file ${_proto_inc} ${_abs_file}) + get_filename_component(_rel_root_dir ${_rel_root_file} DIRECTORY) + file(RELATIVE_PATH _rel_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_abs_dir}) + + set (src_1 "${GRPC_GEN_DIR}/${_rel_root_dir}/${_basename}.grpc.pb.cc") + set (src_2 "${GRPC_GEN_DIR}/${_rel_root_dir}/${_basename}.pb.cc") + set (hdr_1 "${GRPC_GEN_DIR}/${_rel_root_dir}/${_basename}.grpc.pb.h") + set (hdr_2 "${GRPC_GEN_DIR}/${_rel_root_dir}/${_basename}.pb.h") + add_custom_command( + OUTPUT ${src_1} ${src_2} ${hdr_1} ${hdr_2} + COMMAND protobuf::protoc + ARGS --grpc_out=${GRPC_GEN_DIR} + --cpp_out=${GRPC_GEN_DIR} + --plugin=protoc-gen-grpc=$ + -I ${_proto_inc} -I ${_rel_dir} + ${_abs_file} + DEPENDS ${_abs_file} protobuf::protoc gRPC::grpc_cpp_plugin + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Running gRPC C++ protocol buffer compiler on ${file}" + VERBATIM) + set_source_files_properties(${src_1} ${src_2} ${hdr_1} ${hdr_2} PROPERTIES GENERATED TRUE) + list(APPEND GRPC_PROTO_SRCS ${src_1} ${src_2}) + list(APPEND GRPC_PROTO_HDRS ${hdr_1} ${hdr_2}) +endforeach() + +add_library (grpc_pbufs STATIC ${GRPC_PROTO_SRCS} ${GRPC_PROTO_HDRS}) +#target_include_directories (grpc_pbufs PRIVATE src) +target_include_directories (grpc_pbufs SYSTEM PUBLIC ${GRPC_GEN_DIR}) +target_link_libraries (grpc_pbufs protobuf::libprotobuf "gRPC::grpc++${grpc_suffix}") +target_compile_options (grpc_pbufs + PUBLIC + $<$: + --system-header-prefix="google/protobuf" + -Wno-deprecated-dynamic-exception-spec + >) +add_library (Ripple::grpc_pbufs ALIAS grpc_pbufs) +target_link_libraries (ripple_libs INTERFACE Ripple::grpc_pbufs) +exclude_if_included (grpc_pbufs) diff --git a/Builds/containers/centos-builder/centos_setup.sh b/Builds/containers/centos-builder/centos_setup.sh index 4a864d436fc..722dc3c5158 100755 --- a/Builds/containers/centos-builder/centos_setup.sh +++ b/Builds/containers/centos-builder/centos_setup.sh @@ -7,7 +7,7 @@ yum -y upgrade yum -y update yum -y install epel-release centos-release-scl yum -y install \ - wget curl time gcc-c++ time yum-utils \ + wget curl time gcc-c++ time yum-utils autoconf pkgconfig \ libstdc++-static rpm-build gnupg which make cmake \ devtoolset-7 devtoolset-7-gdb devtoolset-7-libasan-devel devtoolset-7-libtsan-devel devtoolset-7-libubsan-devel \ devtoolset-8 devtoolset-8-gdb devtoolset-8-binutils devtoolset-8-libstdc++-devel \ @@ -22,8 +22,6 @@ yum -y install \ python-devel python27-python-devel rh-python35-python-devel \ python27 rh-python35 \ ninja-build git svn \ - protobuf protobuf-static protobuf-c-devel \ - protobuf-compiler protobuf-devel \ swig perl-Digest-MD5 python2-pip if [ "${CI_USE}" = true ] ; then diff --git a/Builds/containers/packaging/dpkg/debian/control b/Builds/containers/packaging/dpkg/debian/control index e09c0895ec8..e976b6aca2c 100644 --- a/Builds/containers/packaging/dpkg/debian/control +++ b/Builds/containers/packaging/dpkg/debian/control @@ -2,7 +2,7 @@ Source: rippled Section: misc Priority: extra Maintainer: Ripple Labs Inc. -Build-Depends: cmake, debhelper (>=9), libprotobuf-dev, libssl-dev, zlib1g-dev, dh-systemd, ninja-build +Build-Depends: cmake, debhelper (>=9), zlib1g-dev, dh-systemd, ninja-build Standards-Version: 3.9.7 Homepage: http://ripple.com/ diff --git a/Builds/containers/packaging/dpkg/debian/rules b/Builds/containers/packaging/dpkg/debian/rules index 1eccd9a44be..c1f7b67c093 100755 --- a/Builds/containers/packaging/dpkg/debian/rules +++ b/Builds/containers/packaging/dpkg/debian/rules @@ -20,7 +20,6 @@ override_dh_auto_configure: -DCMAKE_INSTALL_PREFIX=/opt/ripple \ -DCMAKE_BUILD_TYPE=Release \ -Dstatic=true \ - -Dlocal_protobuf=ON \ -Dvalidator_keys=ON \ -DCMAKE_VERBOSE_MAKEFILE=ON diff --git a/Builds/containers/packaging/rpm/rippled.spec b/Builds/containers/packaging/rpm/rippled.spec index fd9c04515ae..951f8b39852 100644 --- a/Builds/containers/packaging/rpm/rippled.spec +++ b/Builds/containers/packaging/rpm/rippled.spec @@ -12,7 +12,7 @@ License: MIT URL: http://ripple.com/ Source0: rippled.tar.gz -BuildRequires: protobuf-static openssl-static cmake zlib-static ninja-build +BuildRequires: cmake zlib-static ninja-build %description rippled @@ -32,7 +32,7 @@ core library for development of standalone applications that sign transactions. cd rippled mkdir -p bld.release cd bld.release -cmake .. -G Ninja -DCMAKE_INSTALL_PREFIX=%{_prefix} -DCMAKE_BUILD_TYPE=Release -Dstatic=true -DCMAKE_VERBOSE_MAKEFILE=ON -Dlocal_protobuf=ON -Dvalidator_keys=ON +cmake .. -G Ninja -DCMAKE_INSTALL_PREFIX=%{_prefix} -DCMAKE_BUILD_TYPE=Release -Dstatic=true -DCMAKE_VERBOSE_MAKEFILE=ON -Dvalidator_keys=ON cmake --build . --parallel --target rippled --target validator-keys -- -v %pre diff --git a/Builds/containers/shared/build_deps.sh b/Builds/containers/shared/build_deps.sh index 6287e3561d9..dc760ba4a64 100755 --- a/Builds/containers/shared/build_deps.sh +++ b/Builds/containers/shared/build_deps.sh @@ -36,6 +36,40 @@ rm -f openssl-${OPENSSL_VER}.tar.gz rm -rf openssl-${OPENSSL_VER} LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}:/opt/local/openssl/lib /opt/local/openssl/bin/openssl version -a +cd /tmp +wget https://github.com/protocolbuffers/protobuf/releases/download/v3.10.1/protobuf-all-3.10.1.tar.gz +tar xf protobuf-all-3.10.1.tar.gz +cd protobuf-3.10.1 +./autogen.sh +./configure +make -j$(nproc) +make install +ldconfig +cd .. +rm -f protobuf-all-3.10.1.tar.gz +rm -rf protobuf-3.10.1 + +cd /tmp +wget https://github.com/c-ares/c-ares/releases/download/cares-1_15_0/c-ares-1.15.0.tar.gz +tar xf c-ares-1.15.0.tar.gz +cd c-ares-1.15.0 +./configure +make -j$(nproc) +make install +cd .. +rm -f c-ares-1.15.0.tar.gz +rm -rf c-ares-1.15.0 + +cd /tmp +wget https://github.com/grpc/grpc/archive/v1.25.0.tar.gz +tar xf v1.25.0.tar.gz +cd grpc-1.25.0 +make -j$(nproc) +make install +cd .. +rm -f xf v1.25.0.tar.gz +rm -rf grpc-1.25.0 + if [ "${CI_USE}" = true ] ; then cd /tmp wget https://github.com/doxygen/doxygen/archive/Release_1_8_16.tar.gz diff --git a/Builds/containers/ubuntu-builder/ubuntu_setup.sh b/Builds/containers/ubuntu-builder/ubuntu_setup.sh index caf491a9f2d..3471d6b28ff 100755 --- a/Builds/containers/ubuntu-builder/ubuntu_setup.sh +++ b/Builds/containers/ubuntu-builder/ubuntu_setup.sh @@ -30,8 +30,8 @@ apt-get -y clean apt-get -y update apt-get -y --fix-missing install \ - make cmake ninja-build \ - protobuf-compiler libprotobuf-dev openssl libssl-dev \ + make cmake ninja-build autoconf libtool pkg-config \ + openssl libssl-dev \ liblzma-dev libbz2-dev zlib1g-dev \ libjemalloc-dev \ python-pip \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e4204e2cb3..136f962419f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ include(deps/Rocksdb) include(deps/Nudb) include(deps/date) include(deps/Protobuf) +include(deps/gRPC) ### diff --git a/bin/sh/install-vcpkg.sh b/bin/sh/install-vcpkg.sh index 984fad7fc42..28c328d0a8c 100755 --- a/bin/sh/install-vcpkg.sh +++ b/bin/sh/install-vcpkg.sh @@ -17,7 +17,7 @@ if [[ -d "${VCPKG_DIR}" ]] ; then rm -rf "${VCPKG_DIR}" fi -git clone --branch 2019.06 https://github.com/Microsoft/vcpkg.git ${VCPKG_DIR} +git clone --branch 2019.10 https://github.com/Microsoft/vcpkg.git ${VCPKG_DIR} pushd ${VCPKG_DIR} if [[ -z ${COMSPEC:-} ]]; then chmod +x ./bootstrap-vcpkg.sh diff --git a/cfg/rippled-example.cfg b/cfg/rippled-example.cfg index c43f0e2d1f6..32b30f3ef04 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/rippled-example.cfg @@ -1168,6 +1168,10 @@ ip = 127.0.0.1 admin = 127.0.0.1 protocol = ws +#[port_grpc] +#port = 50051 +#ip = 0.0.0.0 + #[port_ws_public] #port = 6005 #ip = 127.0.0.1 diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 0e4e2aaf133..a499845e9f1 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -66,6 +66,7 @@ #include #include +#include #include #include @@ -391,6 +392,8 @@ class ApplicationImp io_latency_sampler m_io_latency_sampler; + std::unique_ptr grpcServer_; + //-------------------------------------------------------------------------- static @@ -552,6 +555,7 @@ class ApplicationImp , m_io_latency_sampler (m_collectorManager->collector()->make_event ("ios_latency"), logs_->journal("Application"), std::chrono::milliseconds (100), get_io_service()) + , grpcServer_(std::make_unique(*this)) { if (shardStore_) { @@ -1339,6 +1343,7 @@ bool ApplicationImp::setup() logs_->silent (config_->silent()); m_jobQueue->setThreadCount (config_->WORKERS, config_->standalone()); + grpcServer_->run(); if (!config_->standalone()) timeKeeper_->run(config_->SNTP_SERVERS); @@ -1583,9 +1588,15 @@ bool ApplicationImp::setup() Resource::Charge loadType = Resource::feeReferenceRPC; Resource::Consumer c; - RPC::Context context { journal ("RPCHandler"), jvCommand, *this, - loadType, getOPs (), getLedgerMaster(), c, Role::ADMIN, - RPC::ApiMaximumSupportedVersion}; + RPC::JsonContext context{{journal("RPCHandler"), + *this, + loadType, + getOPs(), + getLedgerMaster(), + c, + Role::ADMIN}, + jvCommand, + RPC::ApiMaximumSupportedVersion}; Json::Value jvResult; RPC::doCommand (context, jvResult); diff --git a/src/ripple/app/main/GRPCServer.cpp b/src/ripple/app/main/GRPCServer.cpp new file mode 100644 index 00000000000..dfd6f55296d --- /dev/null +++ b/src/ripple/app/main/GRPCServer.cpp @@ -0,0 +1,408 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include + +namespace ripple { + +namespace { + +// helper function. strips scheme from endpoint string +std::string +getEndpoint(std::string const& peer) +{ + std::size_t first = peer.find_first_of(":"); + std::size_t last = peer.find_last_of(":"); + std::string peerClean(peer); + if (first != last) + { + peerClean = peer.substr(first + 1); + } + return peerClean; +} +} // namespace + +template +GRPCServerImpl::CallData::CallData( + rpc::v1::XRPLedgerAPIService::AsyncService& service, + grpc::ServerCompletionQueue& cq, + Application& app, + BindListener bindListener, + Handler handler, + RPC::Condition requiredCondition, + Resource::Charge loadType) + : service_(service) + , cq_(cq) + , finished_(false) + , app_(app) + , aborted_(false) + , responder_(&ctx_) + , bindListener_(std::move(bindListener)) + , handler_(std::move(handler)) + , requiredCondition_(std::move(requiredCondition)) + , loadType_(std::move(loadType)) +{ + // Bind a listener. When a request is received, "this" will be returned + // from CompletionQueue::Next + bindListener_(service_, &ctx_, &request_, &responder_, &cq_, &cq_, this); +} + +template +std::shared_ptr +GRPCServerImpl::CallData::clone() +{ + return std::make_shared>( + service_, + cq_, + app_, + bindListener_, + handler_, + requiredCondition_, + loadType_); +} + +template +void +GRPCServerImpl::CallData::process() +{ + // sanity check + BOOST_ASSERT(!finished_); + + std::shared_ptr> thisShared = + this->shared_from_this(); + 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; + }); +} + +template +void +GRPCServerImpl::CallData::process( + std::shared_ptr coro) +{ + try + { + auto usage = getUsage(); + if (usage.disconnect()) + { + grpc::Status status{grpc::StatusCode::RESOURCE_EXHAUSTED, + "usage balance exceeds threshhold"}; + responder_.FinishWithError(status, this); + } + else + { + auto loadType = getLoadType(); + usage.charge(loadType); + auto role = getRole(); + + RPC::GRPCContext context{{app_.journal("gRPCServer"), + app_, + loadType, + app_.getOPs(), + app_.getLedgerMaster(), + usage, + role, + coro, + InfoSub::pointer()}, + request_}; + + // Make sure we can currently handle the rpc + error_code_i conditionMetRes = + RPC::conditionMet(requiredCondition_, context); + + if (conditionMetRes != rpcSUCCESS) + { + RPC::ErrorInfo errorInfo = RPC::get_error_info(conditionMetRes); + grpc::Status status{grpc::StatusCode::INTERNAL, + errorInfo.message.c_str()}; + responder_.FinishWithError(status, this); + } + else + { + std::pair result = handler_(context); + responder_.Finish(result.first, result.second, this); + } + } + } + catch (std::exception const& ex) + { + grpc::Status status{grpc::StatusCode::INTERNAL, ex.what()}; + responder_.FinishWithError(status, this); + } +} + +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() +{ + return loadType_; +} + +template +Role +GRPCServerImpl::CallData::getRole() +{ + return Role::USER; +} + +template +Resource::Consumer +GRPCServerImpl::CallData::getUsage() +{ + std::string peer = getEndpoint(ctx_.peer()); + boost::optional endpoint = + beast::IP::Endpoint::from_string_checked(peer); + return app_.getResourceManager().newInboundEndpoint(endpoint.get()); +} + +GRPCServerImpl::GRPCServerImpl(Application& app) : app_(app) +{ + // if present, get endpoint from config + if (app_.config().exists("port_grpc")) + { + Section section = app_.config().section("port_grpc"); + + std::pair ipPair = section.find("ip"); + if (!ipPair.second) + return; + + std::pair portPair = section.find("port"); + if (!portPair.second) + return; + try + { + beast::IP::Endpoint endpoint( + boost::asio::ip::make_address(ipPair.first), + std::stoi(portPair.first)); + + serverAddress_ = endpoint.to_string(); + } + catch (std::exception const&) + { + } + } +} + +void +GRPCServerImpl::shutdown() +{ + server_->Shutdown(); + // Always shutdown the completion queue after the server. + cq_->Shutdown(); +} + +void +GRPCServerImpl::handleRpcs() +{ + // This collection should really be an unordered_set. However, to delete + // from the unordered_set, we need a shared_ptr, but cq_.Next() (see below + // while loop) sets the tag to a raw pointer. + std::vector> requests = setupListeners(); + + auto erase = [&requests](Processor* ptr) { + auto it = std::find_if( + requests.begin(), + requests.end(), + [ptr](std::shared_ptr& sPtr) { + return sPtr.get() == ptr; + }); + BOOST_ASSERT(it != requests.end()); + it->swap(requests.back()); + requests.pop_back(); + }; + + void* tag; // uniquely identifies a request. + bool ok; + // Block waiting to read the next event from the completion queue. The + // event is uniquely identified by its tag, which in this case is the + // 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. + 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 + if (!ok) + { + // abort first, then erase. Otherwise, erase can delete object + ptr->abort(); + erase(ptr); + } + else + { + if (!ptr->isFinished()) + { + // ptr is now processing a request, so create a new CallData + // object to handle additional requests + auto cloned = ptr->clone(); + requests.push_back(cloned); + // process the request + ptr->process(); + } + else + { + erase(ptr); + } + } + } +} + +// create a CallData instance for each RPC +std::vector> +GRPCServerImpl::setupListeners() +{ + std::vector> requests; + + auto addToRequests = [&requests](auto callData) { + requests.push_back(std::move(callData)); + }; + + { + using cd = CallData; + + addToRequests(std::make_shared( + service_, + *cq_, + app_, + &rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetFee, + doFeeGrpc, + RPC::NEEDS_CURRENT_LEDGER, + Resource::feeReferenceRPC)); + } + { + using cd = CallData< + rpc::v1::GetAccountInfoRequest, + rpc::v1::GetAccountInfoResponse>; + + addToRequests(std::make_shared( + service_, + *cq_, + app_, + &rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetAccountInfo, + doAccountInfoGrpc, + RPC::NEEDS_CURRENT_LEDGER, + Resource::feeReferenceRPC)); + } + { + using cd = CallData; + + addToRequests(std::make_shared( + service_, + *cq_, + app_, + &rpc::v1::XRPLedgerAPIService::AsyncService::RequestGetTx, + doTxGrpc, + RPC::NEEDS_CURRENT_LEDGER, + Resource::feeReferenceRPC)); + } + { + using cd = CallData< + rpc::v1::SubmitTransactionRequest, + rpc::v1::SubmitTransactionResponse>; + + addToRequests(std::make_shared( + service_, + *cq_, + app_, + &rpc::v1::XRPLedgerAPIService::AsyncService:: + RequestSubmitTransaction, + doSubmitGrpc, + RPC::NEEDS_CURRENT_LEDGER, + Resource::feeMediumBurdenRPC)); + } + return requests; +}; + +bool +GRPCServerImpl::start() +{ + // if config does not specify a grpc server address, don't start + if (serverAddress_.empty()) + return false; + + grpc::ServerBuilder builder; + // Listen on the given address without any authentication mechanism. + builder.AddListeningPort(serverAddress_, grpc::InsecureServerCredentials()); + // Register "service_" as the instance through which we'll communicate with + // clients. In this case it corresponds to an *asynchronous* service. + builder.RegisterService(&service_); + // Get hold of the completion queue used for the asynchronous communication + // with the gRPC runtime. + cq_ = builder.AddCompletionQueue(); + // Finally assemble the server. + server_ = builder.BuildAndStart(); + + return true; +} + +void +GRPCServer::run() +{ + // Start the server and setup listeners + if ((running_ = impl_.start())) + { + thread_ = std::thread([this]() { + // Start the event loop and begin handling requests + this->impl_.handleRpcs(); + }); + } +} + +GRPCServer::~GRPCServer() +{ + if (running_) + { + impl_.shutdown(); + thread_.join(); + } +} + +} // namespace ripple diff --git a/src/ripple/app/main/GRPCServer.h b/src/ripple/app/main/GRPCServer.h new file mode 100644 index 00000000000..3326fab61a3 --- /dev/null +++ b/src/ripple/app/main/GRPCServer.h @@ -0,0 +1,263 @@ +//------------------------------------------------------------------------------ +/* + 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_CORE_GRPCSERVER_H_INCLUDED +#define RIPPLE_CORE_GRPCSERVER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rpc/v1/xrp_ledger.grpc.pb.h" +#include + +namespace ripple { + +// Interface that CallData implements +class Processor +{ +public: + virtual ~Processor() = default; + + Processor() = default; + + Processor(const Processor&) = delete; + + Processor& + operator=(const Processor&) = delete; + + // process a request that has arrived. Can only be called once per instance + 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 + // server to handle additional requests while the first is being processed + virtual std::shared_ptr + clone() = 0; + + // true if this object has finished processing the request. Object will be + // deleted once this function returns true + virtual bool + isFinished() = 0; +}; + +class GRPCServerImpl final +{ +private: + // CompletionQueue returns events that have occurred, or events that have + // been cancelled + std::unique_ptr cq_; + + std::vector> requests_; + + // The gRPC service defined by the .proto files + rpc::v1::XRPLedgerAPIService::AsyncService service_; + + std::unique_ptr server_; + + Application& app_; + + std::string serverAddress_; + + // typedef for function to bind a listener + // This is always of the form: + // rpc::v1::XRPLedgerAPIService::AsyncService::Request[RPC NAME] + template + using BindListener = std::function*, + grpc::CompletionQueue*, + grpc::ServerCompletionQueue*, + void*)>; + + // typedef for actual handler (that populates a response) + // handlers are defined in rpc/GRPCHandlers.h + template + using Handler = std::function( + RPC::GRPCContext&)>; + +public: + explicit GRPCServerImpl(Application& app); + + GRPCServerImpl(const GRPCServerImpl&) = delete; + + GRPCServerImpl& + operator=(const GRPCServerImpl&) = delete; + + void + shutdown(); + + // setup the server and listeners + // returns true if server started successfully + bool + start(); + + // the main event loop + void + handleRpcs(); + + // Create a CallData object for each RPC. Return created objects in vector + std::vector> + setupListeners(); + +private: + // Class encompasing the state and logic needed to serve a request. + template + class CallData + : public Processor, + public std::enable_shared_from_this> + { + private: + // The means of communication with the gRPC runtime for an asynchronous + // server. + rpc::v1::XRPLedgerAPIService::AsyncService& service_; + + // The producer-consumer queue for asynchronous server notifications. + grpc::ServerCompletionQueue& cq_; + + // Context for the rpc, allowing to tweak aspects of it such as the use + // of compression, authentication, as well as to send metadata back to + // the client. + grpc::ServerContext ctx_; + + // true if finished processing request + 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_; + + // What we send back to the client. + Response reply_; + + // The means to get back to the client. + grpc::ServerAsyncResponseWriter responder_; + + // Function that creates a listener for specific request type + BindListener bindListener_; + + // Function that processes a request + Handler handler_; + + // Condition required for this RPC + RPC::Condition requiredCondition_; + + // Load type for this RPC + Resource::Charge loadType_; + + public: + virtual ~CallData() = default; + + // Take in the "service" instance (in this case representing an + // asynchronous server) and the completion queue "cq" used for + // asynchronous communication with the gRPC runtime. + explicit CallData( + rpc::v1::XRPLedgerAPIService::AsyncService& service, + grpc::ServerCompletionQueue& cq, + Application& app, + BindListener bindListener, + Handler handler, + RPC::Condition requiredCondition, + Resource::Charge loadType); + + CallData(const CallData&) = delete; + + CallData& + operator=(const CallData&) = delete; + + virtual void + process() override; + + virtual bool + isFinished() override; + + virtual void + abort() override; + + std::shared_ptr + clone() override; + + private: + // process the request. Called inside the coroutine passed to JobQueue + void + process(std::shared_ptr coro); + + // return load type of this RPC + Resource::Charge + getLoadType(); + + // return the Role required for this RPC + // for now, we are only supporting RPC's that require Role::USER for + // gRPC + Role + getRole(); + + // register endpoint with ResourceManager and return usage + Resource::Consumer + getUsage(); + + }; // CallData + +}; // GRPCServerImpl + +class GRPCServer +{ +public: + explicit GRPCServer(Application& app) : impl_(app){}; + + GRPCServer(const GRPCServer&) = delete; + + GRPCServer& + operator=(const GRPCServer&) = delete; + + void + run(); + + ~GRPCServer(); + +private: + GRPCServerImpl impl_; + std::thread thread_; + bool running_; +}; +} // namespace ripple +#endif diff --git a/src/ripple/overlay/impl/Message.cpp b/src/ripple/overlay/impl/Message.cpp index 7e0b81fa986..f263e81d912 100644 --- a/src/ripple/overlay/impl/Message.cpp +++ b/src/ripple/overlay/impl/Message.cpp @@ -27,7 +27,8 @@ namespace ripple { Message::Message (::google::protobuf::Message const& message, int type) : mCategory(TrafficCount::categorize(message, type, false)) { - unsigned const messageBytes = message.ByteSize (); + auto const messageBytes = message.ByteSizeLong(); + assert (messageBytes != 0); /** Number of bytes in a message header. */ diff --git a/src/ripple/proto/rpc/v1/account_info.proto b/src/ripple/proto/rpc/v1/account_info.proto new file mode 100644 index 00000000000..ab7c01aab51 --- /dev/null +++ b/src/ripple/proto/rpc/v1/account_info.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; + +package rpc.v1; + +import "rpc/v1/ledger_objects.proto"; +import "rpc/v1/amount.proto"; + +// A request to get info about an account. +message GetAccountInfoRequest { + // The address to get info about. + AccountAddress account = 1; + + bool strict = 2; + + LedgerSpecifier ledger = 3; + + bool queue = 4; + + 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 { + + AccountRoot account_data = 1; + + SignerList signer_list = 2; + + uint32 ledger_index = 3; + + QueueData queue_data = 4; + + bool validated = 5; +} + +// Aggregate data about queued transactions +message QueueData { + + uint32 txn_count = 1; + + bool auth_change_queued = 2; + + uint32 lowest_sequence = 3; + + uint32 highest_sequence = 4; + + XRPDropsAmount max_spend_drops_total = 5; + + repeated QueuedTransaction transactions = 6; +} + +// Data about a single queued transaction +message QueuedTransaction { + bool auth_change = 1; + + XRPDropsAmount fee = 2; + + uint64 fee_level = 3; + + XRPDropsAmount max_spend_drops = 4; + + uint32 sequence = 5; + + uint32 last_ledger_sequence = 6; +} diff --git a/src/ripple/proto/rpc/v1/amount.proto b/src/ripple/proto/rpc/v1/amount.proto new file mode 100644 index 00000000000..45e8931df03 --- /dev/null +++ b/src/ripple/proto/rpc/v1/amount.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package rpc.v1; + +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; +} + +// A representation of an amount of issued currency. +message IssuedCurrencyAmount { + // The currency used to value the amount. + Currency currency = 1; + + // The value of the amount. 8 bytes + string value = 2; + + // Unique account address of the entity issuing the currency. + AccountAddress issuer = 3; +} + +message Currency { + + // 3 character ASCII code + string name = 1; + + // 160 bit currency code. 20 bytes + bytes code = 2; +} diff --git a/src/ripple/proto/rpc/v1/fee.proto b/src/ripple/proto/rpc/v1/fee.proto new file mode 100644 index 00000000000..76340c3095a --- /dev/null +++ b/src/ripple/proto/rpc/v1/fee.proto @@ -0,0 +1,51 @@ +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 new file mode 100644 index 00000000000..d197aecf5f7 --- /dev/null +++ b/src/ripple/proto/rpc/v1/ledger_objects.proto @@ -0,0 +1,175 @@ +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/meta.proto b/src/ripple/proto/rpc/v1/meta.proto new file mode 100644 index 00000000000..b5c0c2a957f --- /dev/null +++ b/src/ripple/proto/rpc/v1/meta.proto @@ -0,0 +1,82 @@ +syntax = "proto3"; + +package rpc.v1; + +import "rpc/v1/amount.proto"; +import "rpc/v1/ledger_objects.proto"; + +message Meta { + // index in ledger + uint64 transaction_index = 1; + + // result code indicating whether the transaction succeeded or failed + TransactionResult transaction_result = 2; + + repeated AffectedNode affected_nodes = 3; + + CurrencyAmount delivered_amount = 4; +} + +message TransactionResult { + + enum ResultType { + RESULT_TYPE_UNSPECIFIED = 0; + // Claimed cost only + RESULT_TYPE_TEC = 1; + // Failure + RESULT_TYPE_TEF = 2; + // Local error + RESULT_TYPE_TEL = 3; + // Malformed transaction + RESULT_TYPE_TEM = 4; + // Retry + RESULT_TYPE_TER = 5; + // Success + RESULT_TYPE_TES = 6; + } + + // category of the transaction result + ResultType result_type = 1; + + // full result string, i.e. tesSUCCESS + string result = 2; +} + +message AffectedNode { + + LedgerEntryType ledger_entry_type = 1; + + // 32 bytes + bytes ledger_index = 2; + + oneof node { + CreatedNode created_node = 3; + DeletedNode deleted_node = 4; + ModifiedNode modified_node = 5; + } +} + +message CreatedNode { + + LedgerObject new_fields = 1; + +} + +message DeletedNode { + + LedgerObject final_fields = 1; + +} + +message ModifiedNode { + + LedgerObject final_fields = 1; + + LedgerObject previous_fields = 2; + + // 32 bytes + bytes previous_transaction_id = 3; + + uint32 previous_transaction_ledger_sequence = 4; + +} diff --git a/src/ripple/proto/rpc/v1/submit.proto b/src/ripple/proto/rpc/v1/submit.proto new file mode 100644 index 00000000000..3f4a812ddff --- /dev/null +++ b/src/ripple/proto/rpc/v1/submit.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package rpc.v1; + +import "rpc/v1/meta.proto"; + +// A request to submit the signed transaction to the ledger. +message SubmitTransactionRequest { + // The signed transaction to submit. + bytes signed_transaction = 1; + + bool fail_hard = 2; +} + +// A response when a signed transaction is submitted to the ledger. +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. + int64 engine_result_code = 2; + + // Human-readable explanation of the transaction's preliminary result. + string engine_result_message = 3; + + // 32 bytes + bytes hash = 4; +} diff --git a/src/ripple/proto/rpc/v1/transaction.proto b/src/ripple/proto/rpc/v1/transaction.proto new file mode 100644 index 00000000000..5afb62731ee --- /dev/null +++ b/src/ripple/proto/rpc/v1/transaction.proto @@ -0,0 +1,96 @@ +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 new file mode 100644 index 00000000000..fb81f85aa3f --- /dev/null +++ b/src/ripple/proto/rpc/v1/tx.proto @@ -0,0 +1,40 @@ +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 new file mode 100644 index 00000000000..15e3b95c6af --- /dev/null +++ b/src/ripple/proto/rpc/v1/xrp_ledger.proto @@ -0,0 +1,25 @@ +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/Context.h b/src/ripple/rpc/Context.h index 2c7ca557219..b6b31419e62 100644 --- a/src/ripple/rpc/Context.h +++ b/src/ripple/rpc/Context.h @@ -37,6 +37,19 @@ namespace RPC { /** The context of information needed to call an RPC. */ struct Context +{ + beast::Journal const j; + Application& app; + Resource::Charge& loadType; + NetworkOPs& netOps; + LedgerMaster& ledgerMaster; + Resource::Consumer& consumer; + Role role; + std::shared_ptr coro{}; + InfoSub::pointer infoSub{}; +}; + +struct JsonContext : public Context { /** * Data passed in from HTTP headers. @@ -47,20 +60,18 @@ struct Context boost::string_view forwardedFor; }; - beast::Journal const j; Json::Value params; - Application& app; - Resource::Charge& loadType; - NetworkOPs& netOps; - LedgerMaster& ledgerMaster; - Resource::Consumer& consumer; - Role role; + unsigned int apiVersion; - std::shared_ptr coro {}; - InfoSub::pointer infoSub {}; Headers headers {}; }; +template +struct GRPCContext : public Context +{ + RequestType params; +}; + } // RPC } // ripple diff --git a/src/ripple/rpc/DeliveredAmount.h b/src/ripple/rpc/DeliveredAmount.h index b6925b88172..84ace1f3f21 100644 --- a/src/ripple/rpc/DeliveredAmount.h +++ b/src/ripple/rpc/DeliveredAmount.h @@ -21,6 +21,7 @@ #define RIPPLE_RPC_DELIVEREDAMOUNT_H_INCLUDED #include +#include namespace Json { class Value; @@ -35,6 +36,8 @@ class STTx; namespace RPC { +struct JsonContext; + struct Context; /** @@ -56,6 +59,13 @@ insertDeliveredAmount( void insertDeliveredAmount( Json::Value& meta, + JsonContext&, + std::shared_ptr, + TxMeta const&); + +void +insertDeliveredAmount( + rpc::v1::CurrencyAmount& proto, Context&, std::shared_ptr, TxMeta const&); diff --git a/src/ripple/rpc/GRPCHandlers.h b/src/ripple/rpc/GRPCHandlers.h new file mode 100644 index 00000000000..5719efe8c1b --- /dev/null +++ b/src/ripple/rpc/GRPCHandlers.h @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------ +/* + 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_GRPCHANDLER_H_INCLUDED +#define RIPPLE_RPC_GRPCHANDLER_H_INCLUDED + +#include +#include +#include + +namespace ripple { + +/* + * These handlers are for gRPC. They each take in a protobuf message that is + * nested inside RPC::GRPCContext, where T is the request type + * The return value is the response type, as well as a status + * If the status is not Status::OK (meaning an error occurred), then only + * the status will be sent to the client, and the response will be ommitted + */ + +std::pair +doAccountInfoGrpc(RPC::GRPCContext& context); + +std::pair +doFeeGrpc(RPC::GRPCContext& context); + +std::pair +doSubmitGrpc(RPC::GRPCContext& context); + +// NOTE, this only supports Payment transactions at this time +std::pair +doTxGrpc(RPC::GRPCContext& context); + +} // namespace ripple + +#endif diff --git a/src/ripple/rpc/RPCHandler.h b/src/ripple/rpc/RPCHandler.h index 13cd521f95b..5e6081e56a8 100644 --- a/src/ripple/rpc/RPCHandler.h +++ b/src/ripple/rpc/RPCHandler.h @@ -28,10 +28,10 @@ namespace ripple { namespace RPC { -struct Context; +struct JsonContext; /** Execute an RPC command and store the results in a Json::Value. */ -Status doCommand (RPC::Context&, Json::Value&); +Status doCommand (RPC::JsonContext&, Json::Value&); Role roleRequired (unsigned int version, std::string const& method ); diff --git a/src/ripple/rpc/handlers/AccountChannels.cpp b/src/ripple/rpc/handlers/AccountChannels.cpp index d13a8c98218..601d98dca43 100644 --- a/src/ripple/rpc/handlers/AccountChannels.cpp +++ b/src/ripple/rpc/handlers/AccountChannels.cpp @@ -63,7 +63,7 @@ void addChannel (Json::Value& jsonLines, SLE const& line) // limit: integer // optional // marker: opaque // optional, resume previous query // } -Json::Value doAccountChannels (RPC::Context& context) +Json::Value doAccountChannels (RPC::JsonContext& context) { auto const& params (context.params); if (! params.isMember (jss::account)) diff --git a/src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp b/src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp index a9531c5f122..8f95877484f 100644 --- a/src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp +++ b/src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp @@ -28,7 +28,7 @@ namespace ripple { -Json::Value doAccountCurrencies (RPC::Context& context) +Json::Value doAccountCurrencies (RPC::JsonContext& context) { auto& params = context.params; diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/ripple/rpc/handlers/AccountInfo.cpp index c800164870c..c2ce26f31da 100644 --- a/src/ripple/rpc/handlers/AccountInfo.cpp +++ b/src/ripple/rpc/handlers/AccountInfo.cpp @@ -17,7 +17,6 @@ */ //============================================================================== - #include #include #include @@ -27,7 +26,9 @@ #include #include #include +#include #include +#include namespace ripple { @@ -47,7 +48,7 @@ namespace ripple { // } // TODO(tom): what is that "default"? -Json::Value doAccountInfo (RPC::Context& context) +Json::Value doAccountInfo (RPC::JsonContext& context) { auto& params = context.params; @@ -183,4 +184,93 @@ Json::Value doAccountInfo (RPC::Context& context) return result; } -} // ripple +std::pair +doAccountInfoGrpc(RPC::GRPCContext& context) +{ + // Return values + rpc::v1::GetAccountInfoResponse result; + grpc::Status status = grpc::Status::OK; + + // input + rpc::v1::GetAccountInfoRequest& params = context.params; + + // get ledger + std::shared_ptr ledger; + auto lgrStatus = RPC::ledgerFromRequest(ledger, context); + if (lgrStatus || !ledger) + { + grpc::Status errorStatus; + if (lgrStatus.toErrorCode() == rpcINVALID_PARAMS) + { + errorStatus = grpc::Status( + grpc::StatusCode::INVALID_ARGUMENT, lgrStatus.message()); + } + else + { + errorStatus = + grpc::Status(grpc::StatusCode::NOT_FOUND, lgrStatus.message()); + } + return {result, errorStatus}; + } + + result.set_ledger_index(ledger->info().seq); + result.set_validated( + RPC::isValidated(context.ledgerMaster, *ledger, context.app)); + + // decode account + AccountID accountID; + std::string strIdent = params.account().address(); + error_code_i code = + RPC::accountFromStringWithCode(accountID, strIdent, params.strict()); + if (code != rpcSUCCESS) + { + grpc::Status errorStatus{grpc::StatusCode::INVALID_ARGUMENT, + "invalid account"}; + return {result, errorStatus}; + } + + // get account data + auto const sleAccepted = ledger->read(keylet::account(accountID)); + if (sleAccepted) + { + RPC::populateAccountRoot(*result.mutable_account_data(), *sleAccepted); + + // signer lists + if (params.signer_lists()) + { + auto const sleSigners = ledger->read(keylet::signers(accountID)); + if (sleSigners) + { + rpc::v1::SignerList& signerListProto = + *result.mutable_signer_list(); + RPC::populateSignerList(signerListProto, *sleSigners); + } + } + + // queued transactions + if (params.queue()) + { + if (!ledger->open()) + { + grpc::Status errorStatus{ + grpc::StatusCode::INVALID_ARGUMENT, + "requested queue but ledger is not open"}; + return {result, errorStatus}; + } + auto const txs = + context.app.getTxQ().getAccountTxs(accountID, *ledger); + rpc::v1::QueueData& queueData = *result.mutable_queue_data(); + RPC::populateQueueData(queueData, txs); + } + } + else + { + grpc::Status errorStatus{grpc::StatusCode::NOT_FOUND, + "account not found"}; + return {result, errorStatus}; + } + + return {result, status}; +} + +} // namespace ripple diff --git a/src/ripple/rpc/handlers/AccountLines.cpp b/src/ripple/rpc/handlers/AccountLines.cpp index f3e3db3f3cb..40d6b1f529a 100644 --- a/src/ripple/rpc/handlers/AccountLines.cpp +++ b/src/ripple/rpc/handlers/AccountLines.cpp @@ -78,7 +78,7 @@ void addLine (Json::Value& jsonLines, RippleState const& line) // limit: integer // optional // marker: opaque // optional, resume previous query // } -Json::Value doAccountLines (RPC::Context& context) +Json::Value doAccountLines (RPC::JsonContext& context) { auto const& params (context.params); if (! params.isMember (jss::account)) diff --git a/src/ripple/rpc/handlers/AccountObjects.cpp b/src/ripple/rpc/handlers/AccountObjects.cpp index 0c35071ed73..a560a9a706f 100644 --- a/src/ripple/rpc/handlers/AccountObjects.cpp +++ b/src/ripple/rpc/handlers/AccountObjects.cpp @@ -46,7 +46,7 @@ namespace ripple { } */ -Json::Value doAccountObjects (RPC::Context& context) +Json::Value doAccountObjects (RPC::JsonContext& context) { auto const& params = context.params; if (! params.isMember (jss::account)) diff --git a/src/ripple/rpc/handlers/AccountOffers.cpp b/src/ripple/rpc/handlers/AccountOffers.cpp index ab58390414b..bd2dfd3d13b 100644 --- a/src/ripple/rpc/handlers/AccountOffers.cpp +++ b/src/ripple/rpc/handlers/AccountOffers.cpp @@ -53,7 +53,7 @@ void appendOfferJson (std::shared_ptr const& offer, // limit: integer // optional // marker: opaque // optional, resume previous query // } -Json::Value doAccountOffers (RPC::Context& context) +Json::Value doAccountOffers (RPC::JsonContext& context) { auto const& params (context.params); if (! params.isMember (jss::account)) diff --git a/src/ripple/rpc/handlers/AccountTx.cpp b/src/ripple/rpc/handlers/AccountTx.cpp index 2496cd268fe..ecb036f587f 100644 --- a/src/ripple/rpc/handlers/AccountTx.cpp +++ b/src/ripple/rpc/handlers/AccountTx.cpp @@ -44,7 +44,7 @@ namespace ripple { // limit: integer, // optional // marker: opaque // optional, resume previous query // } -Json::Value doAccountTx (RPC::Context& context) +Json::Value doAccountTx (RPC::JsonContext& context) { auto& params = context.params; diff --git a/src/ripple/rpc/handlers/AccountTxOld.cpp b/src/ripple/rpc/handlers/AccountTxOld.cpp index 704e2ec26c2..b87faae068a 100644 --- a/src/ripple/rpc/handlers/AccountTxOld.cpp +++ b/src/ripple/rpc/handlers/AccountTxOld.cpp @@ -43,7 +43,7 @@ namespace ripple { // offset: integer, // optional, defaults to 0 // limit: integer // optional // } -Json::Value doAccountTxOld (RPC::Context& context) +Json::Value doAccountTxOld (RPC::JsonContext& context) { std::uint32_t offset = context.params.isMember (jss::offset) diff --git a/src/ripple/rpc/handlers/AccountTxSwitch.cpp b/src/ripple/rpc/handlers/AccountTxSwitch.cpp index 13a2caddadc..39e771f2c62 100644 --- a/src/ripple/rpc/handlers/AccountTxSwitch.cpp +++ b/src/ripple/rpc/handlers/AccountTxSwitch.cpp @@ -25,11 +25,11 @@ namespace ripple { -Json::Value doAccountTxOld (RPC::Context& context); - Json::Value doAccountTx (RPC::Context& context); +Json::Value doAccountTxOld (RPC::JsonContext& context); + Json::Value doAccountTx (RPC::JsonContext& context); // Temporary switching code until the old account_tx is removed -Json::Value doAccountTxSwitch (RPC::Context& context) +Json::Value doAccountTxSwitch (RPC::JsonContext& context) { if (context.params.isMember(jss::offset) || context.params.isMember(jss::count) || diff --git a/src/ripple/rpc/handlers/BlackList.cpp b/src/ripple/rpc/handlers/BlackList.cpp index 56ad3b34fd2..cfd3f589754 100644 --- a/src/ripple/rpc/handlers/BlackList.cpp +++ b/src/ripple/rpc/handlers/BlackList.cpp @@ -24,7 +24,7 @@ namespace ripple { -Json::Value doBlackList (RPC::Context& context) +Json::Value doBlackList (RPC::JsonContext& context) { auto& rm = context.app.getResourceManager(); if (context.params.isMember(jss::threshold)) diff --git a/src/ripple/rpc/handlers/BookOffers.cpp b/src/ripple/rpc/handlers/BookOffers.cpp index 6733f6be566..91a68579932 100644 --- a/src/ripple/rpc/handlers/BookOffers.cpp +++ b/src/ripple/rpc/handlers/BookOffers.cpp @@ -31,7 +31,7 @@ namespace ripple { -Json::Value doBookOffers (RPC::Context& context) +Json::Value doBookOffers (RPC::JsonContext& context) { // VFALCO TODO Here is a terrible place for this kind of business // logic. It needs to be moved elsewhere and documented, diff --git a/src/ripple/rpc/handlers/CanDelete.cpp b/src/ripple/rpc/handlers/CanDelete.cpp index de46f6604ab..2db6c9f6587 100644 --- a/src/ripple/rpc/handlers/CanDelete.cpp +++ b/src/ripple/rpc/handlers/CanDelete.cpp @@ -30,7 +30,7 @@ namespace ripple { // can_delete [||now|always|never] -Json::Value doCanDelete (RPC::Context& context) +Json::Value doCanDelete (RPC::JsonContext& context) { if (! context.app.getSHAMapStore().advisoryDelete()) return RPC::make_error(rpcNOT_ENABLED); diff --git a/src/ripple/rpc/handlers/Connect.cpp b/src/ripple/rpc/handlers/Connect.cpp index 7e3cd9b5015..3aba09e54df 100644 --- a/src/ripple/rpc/handlers/Connect.cpp +++ b/src/ripple/rpc/handlers/Connect.cpp @@ -33,7 +33,7 @@ namespace ripple { // port: // } // XXX Might allow domain for manual connections. -Json::Value doConnect (RPC::Context& context) +Json::Value doConnect (RPC::JsonContext& context) { if (context.app.config().standalone()) return "cannot connect in standalone mode"; diff --git a/src/ripple/rpc/handlers/ConsensusInfo.cpp b/src/ripple/rpc/handlers/ConsensusInfo.cpp index 96512f0c5b0..ae6fd01095d 100644 --- a/src/ripple/rpc/handlers/ConsensusInfo.cpp +++ b/src/ripple/rpc/handlers/ConsensusInfo.cpp @@ -25,7 +25,7 @@ namespace ripple { -Json::Value doConsensusInfo (RPC::Context& context) +Json::Value doConsensusInfo (RPC::JsonContext& context) { Json::Value ret (Json::objectValue); diff --git a/src/ripple/rpc/handlers/CrawlShards.cpp b/src/ripple/rpc/handlers/CrawlShards.cpp index 0aea3e6ed32..b33deea119a 100644 --- a/src/ripple/rpc/handlers/CrawlShards.cpp +++ b/src/ripple/rpc/handlers/CrawlShards.cpp @@ -40,7 +40,7 @@ namespace ripple { } */ Json::Value -doCrawlShards(RPC::Context& context) +doCrawlShards(RPC::JsonContext& context) { if (context.role != Role::ADMIN) return rpcError(rpcNO_PERMISSION); diff --git a/src/ripple/rpc/handlers/DepositAuthorized.cpp b/src/ripple/rpc/handlers/DepositAuthorized.cpp index 8965ba6a43f..e6f650cf272 100644 --- a/src/ripple/rpc/handlers/DepositAuthorized.cpp +++ b/src/ripple/rpc/handlers/DepositAuthorized.cpp @@ -34,7 +34,7 @@ namespace ripple { // ledger_index : // } -Json::Value doDepositAuthorized (RPC::Context& context) +Json::Value doDepositAuthorized (RPC::JsonContext& context) { Json::Value const& params = context.params; diff --git a/src/ripple/rpc/handlers/DownloadShard.cpp b/src/ripple/rpc/handlers/DownloadShard.cpp index 832c0a6a14d..6b943f665b5 100644 --- a/src/ripple/rpc/handlers/DownloadShard.cpp +++ b/src/ripple/rpc/handlers/DownloadShard.cpp @@ -47,7 +47,7 @@ namespace ripple { } */ Json::Value -doDownloadShard(RPC::Context& context) +doDownloadShard(RPC::JsonContext& context) { if (context.role != Role::ADMIN) return rpcError(rpcNO_PERMISSION); diff --git a/src/ripple/rpc/handlers/Feature1.cpp b/src/ripple/rpc/handlers/Feature1.cpp index 478c88fcb8b..78ad4283c09 100644 --- a/src/ripple/rpc/handlers/Feature1.cpp +++ b/src/ripple/rpc/handlers/Feature1.cpp @@ -33,7 +33,7 @@ namespace ripple { // feature : // vetoed : true/false // } -Json::Value doFeature (RPC::Context& context) +Json::Value doFeature (RPC::JsonContext& context) { // Get majority amendment status diff --git a/src/ripple/rpc/handlers/Fee1.cpp b/src/ripple/rpc/handlers/Fee1.cpp index 7693d37a150..ea6673fa5c4 100644 --- a/src/ripple/rpc/handlers/Fee1.cpp +++ b/src/ripple/rpc/handlers/Fee1.cpp @@ -21,12 +21,14 @@ #include #include #include +#include #include #include +#include namespace ripple { - Json::Value doFee(RPC::Context& context) + Json::Value doFee(RPC::JsonContext& context) { auto result = context.app.getTxQ().doRPC(context.app); if (result.type() == Json::objectValue) @@ -35,4 +37,51 @@ namespace ripple RPC::inject_error(rpcINTERNAL, context.params); return context.params; } -} // ripple + +std::pair +doFeeGrpc(RPC::GRPCContext& context) +{ + rpc::v1::GetFeeResponse reply; + grpc::Status status = grpc::Status::OK; + + Application& app = context.app; + auto const view = app.openLedger().current(); + if (!view) + { + BOOST_ASSERT(false); + return {reply, status}; + } + + auto const metrics = app.getTxQ().getMetrics(*view); + + // current ledger data + reply.set_current_ledger_size(metrics.txInLedger); + reply.set_current_queue_size(metrics.txCount); + reply.set_expected_ledger_size(metrics.txPerLedger); + reply.set_ledger_current_index(view->info().seq); + reply.set_max_queue_size(*metrics.txQMaxSize); + + // fee levels data + 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(); + auto const baseFee = view->fees().base; + drops.mutable_base_fee()->set_drops( + toDrops(metrics.referenceFeeLevel, baseFee).second.drops()); + drops.mutable_minimum_fee()->set_drops( + toDrops(metrics.minProcessingFeeLevel, baseFee).second.drops()); + drops.mutable_median_fee()->set_drops( + toDrops(metrics.medFeeLevel, baseFee).second.drops()); + + drops.mutable_open_ledger_fee()->set_drops( + (toDrops(metrics.openLedgerFeeLevel - FeeLevel64{1}, baseFee).second + + 1) + .drops()); + return {reply, status}; +} +} // namespace ripple diff --git a/src/ripple/rpc/handlers/FetchInfo.cpp b/src/ripple/rpc/handlers/FetchInfo.cpp index 6df5db1fc7e..c90aecc8596 100644 --- a/src/ripple/rpc/handlers/FetchInfo.cpp +++ b/src/ripple/rpc/handlers/FetchInfo.cpp @@ -24,7 +24,7 @@ namespace ripple { -Json::Value doFetchInfo (RPC::Context& context) +Json::Value doFetchInfo (RPC::JsonContext& context) { Json::Value ret (Json::objectValue); diff --git a/src/ripple/rpc/handlers/GatewayBalances.cpp b/src/ripple/rpc/handlers/GatewayBalances.cpp index f9c1913932e..e206495f707 100644 --- a/src/ripple/rpc/handlers/GatewayBalances.cpp +++ b/src/ripple/rpc/handlers/GatewayBalances.cpp @@ -49,7 +49,7 @@ namespace ripple { // gateway_balances [] [ [ // optional, defaults to 10 // } -Json::Value doGetCounts (RPC::Context& context) +Json::Value doGetCounts (RPC::JsonContext& context) { int minCount = 10; diff --git a/src/ripple/rpc/handlers/Handlers.h b/src/ripple/rpc/handlers/Handlers.h index 15608246136..bbcf07ac770 100644 --- a/src/ripple/rpc/handlers/Handlers.h +++ b/src/ripple/rpc/handlers/Handlers.h @@ -24,68 +24,68 @@ namespace ripple { -Json::Value doAccountCurrencies (RPC::Context&); -Json::Value doAccountInfo (RPC::Context&); -Json::Value doAccountLines (RPC::Context&); -Json::Value doAccountChannels (RPC::Context&); -Json::Value doAccountObjects (RPC::Context&); -Json::Value doAccountOffers (RPC::Context&); -Json::Value doAccountTx (RPC::Context&); -Json::Value doAccountTxSwitch (RPC::Context&); -Json::Value doAccountTxOld (RPC::Context&); -Json::Value doBookOffers (RPC::Context&); -Json::Value doBlackList (RPC::Context&); -Json::Value doCanDelete (RPC::Context&); -Json::Value doChannelAuthorize (RPC::Context&); -Json::Value doChannelVerify (RPC::Context&); -Json::Value doConnect (RPC::Context&); -Json::Value doConsensusInfo (RPC::Context&); -Json::Value doDepositAuthorized (RPC::Context&); -Json::Value doDownloadShard (RPC::Context&); -Json::Value doFeature (RPC::Context&); -Json::Value doFee (RPC::Context&); -Json::Value doFetchInfo (RPC::Context&); -Json::Value doGatewayBalances (RPC::Context&); -Json::Value doGetCounts (RPC::Context&); -Json::Value doLedgerAccept (RPC::Context&); -Json::Value doLedgerCleaner (RPC::Context&); -Json::Value doLedgerClosed (RPC::Context&); -Json::Value doLedgerCurrent (RPC::Context&); -Json::Value doLedgerData (RPC::Context&); -Json::Value doLedgerEntry (RPC::Context&); -Json::Value doLedgerHeader (RPC::Context&); -Json::Value doLedgerRequest (RPC::Context&); -Json::Value doLogLevel (RPC::Context&); -Json::Value doLogRotate (RPC::Context&); -Json::Value doNoRippleCheck (RPC::Context&); -Json::Value doOwnerInfo (RPC::Context&); -Json::Value doPathFind (RPC::Context&); -Json::Value doPeers (RPC::Context&); -Json::Value doPing (RPC::Context&); -Json::Value doPrint (RPC::Context&); -Json::Value doRandom (RPC::Context&); -Json::Value doPeerReservationsAdd (RPC::Context&); -Json::Value doPeerReservationsDel (RPC::Context&); -Json::Value doPeerReservationsList (RPC::Context&); -Json::Value doRipplePathFind (RPC::Context&); -Json::Value doServerInfo (RPC::Context&); // for humans -Json::Value doServerState (RPC::Context&); // for machines -Json::Value doSign (RPC::Context&); -Json::Value doSignFor (RPC::Context&); -Json::Value doCrawlShards (RPC::Context&); -Json::Value doStop (RPC::Context&); -Json::Value doSubmit (RPC::Context&); -Json::Value doSubmitMultiSigned (RPC::Context&); -Json::Value doSubscribe (RPC::Context&); -Json::Value doTransactionEntry (RPC::Context&); -Json::Value doTx (RPC::Context&); -Json::Value doTxHistory (RPC::Context&); -Json::Value doUnlList (RPC::Context&); -Json::Value doUnsubscribe (RPC::Context&); -Json::Value doValidationCreate (RPC::Context&); -Json::Value doWalletPropose (RPC::Context&); -Json::Value doValidators (RPC::Context&); -Json::Value doValidatorListSites (RPC::Context&); +Json::Value doAccountCurrencies (RPC::JsonContext&); +Json::Value doAccountInfo (RPC::JsonContext&); +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 doBookOffers (RPC::JsonContext&); +Json::Value doBlackList (RPC::JsonContext&); +Json::Value doCanDelete (RPC::JsonContext&); +Json::Value doChannelAuthorize (RPC::JsonContext&); +Json::Value doChannelVerify (RPC::JsonContext&); +Json::Value doConnect (RPC::JsonContext&); +Json::Value doConsensusInfo (RPC::JsonContext&); +Json::Value doDepositAuthorized (RPC::JsonContext&); +Json::Value doDownloadShard (RPC::JsonContext&); +Json::Value doFeature (RPC::JsonContext&); +Json::Value doFee (RPC::JsonContext&); +Json::Value doFetchInfo (RPC::JsonContext&); +Json::Value doGatewayBalances (RPC::JsonContext&); +Json::Value doGetCounts (RPC::JsonContext&); +Json::Value doLedgerAccept (RPC::JsonContext&); +Json::Value doLedgerCleaner (RPC::JsonContext&); +Json::Value doLedgerClosed (RPC::JsonContext&); +Json::Value doLedgerCurrent (RPC::JsonContext&); +Json::Value doLedgerData (RPC::JsonContext&); +Json::Value doLedgerEntry (RPC::JsonContext&); +Json::Value doLedgerHeader (RPC::JsonContext&); +Json::Value doLedgerRequest (RPC::JsonContext&); +Json::Value doLogLevel (RPC::JsonContext&); +Json::Value doLogRotate (RPC::JsonContext&); +Json::Value doNoRippleCheck (RPC::JsonContext&); +Json::Value doOwnerInfo (RPC::JsonContext&); +Json::Value doPathFind (RPC::JsonContext&); +Json::Value doPeers (RPC::JsonContext&); +Json::Value doPing (RPC::JsonContext&); +Json::Value doPrint (RPC::JsonContext&); +Json::Value doRandom (RPC::JsonContext&); +Json::Value doPeerReservationsAdd (RPC::JsonContext&); +Json::Value doPeerReservationsDel (RPC::JsonContext&); +Json::Value doPeerReservationsList (RPC::JsonContext&); +Json::Value doRipplePathFind (RPC::JsonContext&); +Json::Value doServerInfo (RPC::JsonContext&); // for humans +Json::Value doServerState (RPC::JsonContext&); // for machines +Json::Value doSign (RPC::JsonContext&); +Json::Value doSignFor (RPC::JsonContext&); +Json::Value doCrawlShards (RPC::JsonContext&); +Json::Value doStop (RPC::JsonContext&); +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 doTxHistory (RPC::JsonContext&); +Json::Value doUnlList (RPC::JsonContext&); +Json::Value doUnsubscribe (RPC::JsonContext&); +Json::Value doValidationCreate (RPC::JsonContext&); +Json::Value doWalletPropose (RPC::JsonContext&); +Json::Value doValidators (RPC::JsonContext&); +Json::Value doValidatorListSites (RPC::JsonContext&); } // ripple #endif diff --git a/src/ripple/rpc/handlers/LedgerAccept.cpp b/src/ripple/rpc/handlers/LedgerAccept.cpp index 7587f8ef9b9..81806774795 100644 --- a/src/ripple/rpc/handlers/LedgerAccept.cpp +++ b/src/ripple/rpc/handlers/LedgerAccept.cpp @@ -31,7 +31,7 @@ namespace ripple { -Json::Value doLedgerAccept (RPC::Context& context) +Json::Value doLedgerAccept (RPC::JsonContext& context) { std::unique_lock lock{context.app.getMasterMutex()}; Json::Value jvResult; diff --git a/src/ripple/rpc/handlers/LedgerCleanerHandler.cpp b/src/ripple/rpc/handlers/LedgerCleanerHandler.cpp index 434d772687e..a4ac3c6eac9 100644 --- a/src/ripple/rpc/handlers/LedgerCleanerHandler.cpp +++ b/src/ripple/rpc/handlers/LedgerCleanerHandler.cpp @@ -25,7 +25,7 @@ namespace ripple { -Json::Value doLedgerCleaner (RPC::Context& context) +Json::Value doLedgerCleaner (RPC::JsonContext& context) { context.app.getLedgerMaster().doLedgerCleaner (context.params); return RPC::makeObjectValue ("Cleaner configured"); diff --git a/src/ripple/rpc/handlers/LedgerClosed.cpp b/src/ripple/rpc/handlers/LedgerClosed.cpp index 9a8fbcb9347..f374f2763db 100644 --- a/src/ripple/rpc/handlers/LedgerClosed.cpp +++ b/src/ripple/rpc/handlers/LedgerClosed.cpp @@ -25,7 +25,7 @@ namespace ripple { -Json::Value doLedgerClosed (RPC::Context& context) +Json::Value doLedgerClosed (RPC::JsonContext& context) { auto ledger = context.ledgerMaster.getClosedLedger (); assert (ledger); diff --git a/src/ripple/rpc/handlers/LedgerCurrent.cpp b/src/ripple/rpc/handlers/LedgerCurrent.cpp index c85b0f4fbca..ecfcff3b5df 100644 --- a/src/ripple/rpc/handlers/LedgerCurrent.cpp +++ b/src/ripple/rpc/handlers/LedgerCurrent.cpp @@ -26,7 +26,7 @@ namespace ripple { -Json::Value doLedgerCurrent (RPC::Context& context) +Json::Value doLedgerCurrent (RPC::JsonContext& context) { Json::Value jvResult; jvResult[jss::ledger_current_index] = diff --git a/src/ripple/rpc/handlers/LedgerData.cpp b/src/ripple/rpc/handlers/LedgerData.cpp index 924c7ae58d0..ded8aa08e1d 100644 --- a/src/ripple/rpc/handlers/LedgerData.cpp +++ b/src/ripple/rpc/handlers/LedgerData.cpp @@ -40,7 +40,7 @@ namespace ripple { // ledger_index: chosen ledger's index // state: array of state nodes // marker: resume point, if any -Json::Value doLedgerData (RPC::Context& context) +Json::Value doLedgerData (RPC::JsonContext& context) { std::shared_ptr lpLedger; auto const& params = context.params; diff --git a/src/ripple/rpc/handlers/LedgerEntry.cpp b/src/ripple/rpc/handlers/LedgerEntry.cpp index 336ece18743..752a1292090 100644 --- a/src/ripple/rpc/handlers/LedgerEntry.cpp +++ b/src/ripple/rpc/handlers/LedgerEntry.cpp @@ -35,7 +35,7 @@ namespace ripple { // ledger_index : // ... // } -Json::Value doLedgerEntry (RPC::Context& context) +Json::Value doLedgerEntry (RPC::JsonContext& context) { std::shared_ptr lpLedger; auto jvResult = RPC::lookupLedger (lpLedger, context); diff --git a/src/ripple/rpc/handlers/LedgerHandler.cpp b/src/ripple/rpc/handlers/LedgerHandler.cpp index b24273c6333..d9b1cf695d1 100644 --- a/src/ripple/rpc/handlers/LedgerHandler.cpp +++ b/src/ripple/rpc/handlers/LedgerHandler.cpp @@ -31,7 +31,7 @@ namespace ripple { namespace RPC { -LedgerHandler::LedgerHandler (Context& context) : context_ (context) +LedgerHandler::LedgerHandler (JsonContext& context) : context_ (context) { } diff --git a/src/ripple/rpc/handlers/LedgerHandler.h b/src/ripple/rpc/handlers/LedgerHandler.h index 736e70a92ba..cccd2e805d6 100644 --- a/src/ripple/rpc/handlers/LedgerHandler.h +++ b/src/ripple/rpc/handlers/LedgerHandler.h @@ -38,7 +38,7 @@ class Object; namespace ripple { namespace RPC { -struct Context; +struct JsonContext; // ledger [id|index|current|closed] [full] // { @@ -48,7 +48,7 @@ struct Context; class LedgerHandler { public: - explicit LedgerHandler (Context&); + explicit LedgerHandler (JsonContext&); Status check (); @@ -71,7 +71,7 @@ class LedgerHandler { } private: - Context& context_; + JsonContext& context_; std::shared_ptr ledger_; std::vector queueTxs_; Json::Value result_; diff --git a/src/ripple/rpc/handlers/LedgerHeader.cpp b/src/ripple/rpc/handlers/LedgerHeader.cpp index b7f0028b639..ff810cbbb46 100644 --- a/src/ripple/rpc/handlers/LedgerHeader.cpp +++ b/src/ripple/rpc/handlers/LedgerHeader.cpp @@ -29,7 +29,7 @@ namespace ripple { // ledger_hash : // ledger_index : // } -Json::Value doLedgerHeader (RPC::Context& context) +Json::Value doLedgerHeader (RPC::JsonContext& context) { std::shared_ptr lpLedger; auto jvResult = RPC::lookupLedger (lpLedger, context); diff --git a/src/ripple/rpc/handlers/LedgerRequest.cpp b/src/ripple/rpc/handlers/LedgerRequest.cpp index 8826d7f90d6..0489a1e660b 100644 --- a/src/ripple/rpc/handlers/LedgerRequest.cpp +++ b/src/ripple/rpc/handlers/LedgerRequest.cpp @@ -34,7 +34,7 @@ namespace ripple { // ledger_hash : // ledger_index : // } -Json::Value doLedgerRequest (RPC::Context& context) +Json::Value doLedgerRequest (RPC::JsonContext& context) { auto const hasHash = context.params.isMember (jss::ledger_hash); auto const hasIndex = context.params.isMember (jss::ledger_index); diff --git a/src/ripple/rpc/handlers/LogLevel.cpp b/src/ripple/rpc/handlers/LogLevel.cpp index 3acb22a1939..87577a7115d 100644 --- a/src/ripple/rpc/handlers/LogLevel.cpp +++ b/src/ripple/rpc/handlers/LogLevel.cpp @@ -28,7 +28,7 @@ namespace ripple { -Json::Value doLogLevel (RPC::Context& context) +Json::Value doLogLevel (RPC::JsonContext& context) { // log_level if (!context.params.isMember (jss::severity)) diff --git a/src/ripple/rpc/handlers/LogRotate.cpp b/src/ripple/rpc/handlers/LogRotate.cpp index 20bf2fbf718..1f722abe878 100644 --- a/src/ripple/rpc/handlers/LogRotate.cpp +++ b/src/ripple/rpc/handlers/LogRotate.cpp @@ -24,7 +24,7 @@ namespace ripple { -Json::Value doLogRotate (RPC::Context& context) +Json::Value doLogRotate (RPC::JsonContext& context) { context.app.getPerfLog().rotate(); return RPC::makeObjectValue (context.app.logs().rotate()); diff --git a/src/ripple/rpc/handlers/NoRippleCheck.cpp b/src/ripple/rpc/handlers/NoRippleCheck.cpp index 2b8bfd1c8d2..bbfece2dd2e 100644 --- a/src/ripple/rpc/handlers/NoRippleCheck.cpp +++ b/src/ripple/rpc/handlers/NoRippleCheck.cpp @@ -32,7 +32,7 @@ namespace ripple { static void fillTransaction ( - RPC::Context& context, + RPC::JsonContext& context, Json::Value& txArray, AccountID const& accountID, std::uint32_t& sequence, @@ -55,7 +55,7 @@ static void fillTransaction ( // role: gateway|user // account role to assume // transactions: true // optional, reccommend transactions // } -Json::Value doNoRippleCheck (RPC::Context& context) +Json::Value doNoRippleCheck (RPC::JsonContext& context) { auto const& params (context.params); if (! params.isMember (jss::account)) diff --git a/src/ripple/rpc/handlers/OwnerInfo.cpp b/src/ripple/rpc/handlers/OwnerInfo.cpp index be289390958..a50527c78c2 100644 --- a/src/ripple/rpc/handlers/OwnerInfo.cpp +++ b/src/ripple/rpc/handlers/OwnerInfo.cpp @@ -30,7 +30,7 @@ namespace ripple { // { // 'ident' : , // } -Json::Value doOwnerInfo (RPC::Context& context) +Json::Value doOwnerInfo (RPC::JsonContext& context) { if (!context.params.isMember (jss::account) && !context.params.isMember (jss::ident)) diff --git a/src/ripple/rpc/handlers/PathFind.cpp b/src/ripple/rpc/handlers/PathFind.cpp index 51c2662c569..d58dd71a8ab 100644 --- a/src/ripple/rpc/handlers/PathFind.cpp +++ b/src/ripple/rpc/handlers/PathFind.cpp @@ -29,7 +29,7 @@ namespace ripple { -Json::Value doPathFind (RPC::Context& context) +Json::Value doPathFind (RPC::JsonContext& context) { if (context.app.config().PATH_SEARCH_MAX == 0) return rpcError (rpcNOT_SUPPORTED); diff --git a/src/ripple/rpc/handlers/PayChanClaim.cpp b/src/ripple/rpc/handlers/PayChanClaim.cpp index d5ba31b1f0d..4fe4e6a0aad 100644 --- a/src/ripple/rpc/handlers/PayChanClaim.cpp +++ b/src/ripple/rpc/handlers/PayChanClaim.cpp @@ -40,7 +40,7 @@ namespace ripple { // channel_id: 256-bit channel id // drops: 64-bit uint (as string) // } -Json::Value doChannelAuthorize (RPC::Context& context) +Json::Value doChannelAuthorize (RPC::JsonContext& context) { auto const& params (context.params); for (auto const& p : {jss::channel_id, jss::amount}) @@ -94,7 +94,7 @@ Json::Value doChannelAuthorize (RPC::Context& context) // drops: 64-bit uint (as string) // signature: signature to verify // } -Json::Value doChannelVerify (RPC::Context& context) +Json::Value doChannelVerify (RPC::JsonContext& context) { auto const& params (context.params); for (auto const& p : diff --git a/src/ripple/rpc/handlers/Peers.cpp b/src/ripple/rpc/handlers/Peers.cpp index d18c2fd0f13..21c0833c5d2 100644 --- a/src/ripple/rpc/handlers/Peers.cpp +++ b/src/ripple/rpc/handlers/Peers.cpp @@ -27,7 +27,7 @@ namespace ripple { -Json::Value doPeers (RPC::Context& context) +Json::Value doPeers (RPC::JsonContext& context) { Json::Value jvResult (Json::objectValue); diff --git a/src/ripple/rpc/handlers/Ping.cpp b/src/ripple/rpc/handlers/Ping.cpp index 7c8ec41dae3..70afbfda80f 100644 --- a/src/ripple/rpc/handlers/Ping.cpp +++ b/src/ripple/rpc/handlers/Ping.cpp @@ -25,10 +25,10 @@ namespace ripple { namespace RPC { -struct Context; +struct JsonContext; } // RPC -Json::Value doPing (RPC::Context& context) +Json::Value doPing (RPC::JsonContext& context) { Json::Value ret(Json::objectValue); switch (context.role) diff --git a/src/ripple/rpc/handlers/Print.cpp b/src/ripple/rpc/handlers/Print.cpp index 7c876c91ac3..bfaba2d9c94 100644 --- a/src/ripple/rpc/handlers/Print.cpp +++ b/src/ripple/rpc/handlers/Print.cpp @@ -25,7 +25,7 @@ namespace ripple { -Json::Value doPrint (RPC::Context& context) +Json::Value doPrint (RPC::JsonContext& context) { JsonPropertyStream stream; if (context.params.isObject() diff --git a/src/ripple/rpc/handlers/Random.cpp b/src/ripple/rpc/handlers/Random.cpp index fff9a6467e0..aadb64e2b57 100644 --- a/src/ripple/rpc/handlers/Random.cpp +++ b/src/ripple/rpc/handlers/Random.cpp @@ -28,14 +28,14 @@ namespace ripple { namespace RPC { -struct Context; +struct JsonContext; } // Result: // { // random: // } -Json::Value doRandom (RPC::Context& context) +Json::Value doRandom (RPC::JsonContext& context) { // TODO(tom): the try/catch is almost certainly redundant, we catch at the // top level too. diff --git a/src/ripple/rpc/handlers/Reservations.cpp b/src/ripple/rpc/handlers/Reservations.cpp index 9007b17406e..fd6b70cead3 100644 --- a/src/ripple/rpc/handlers/Reservations.cpp +++ b/src/ripple/rpc/handlers/Reservations.cpp @@ -33,7 +33,7 @@ namespace ripple { Json::Value -doPeerReservationsAdd(RPC::Context& context) +doPeerReservationsAdd(RPC::JsonContext& context) { auto const& params = context.params; @@ -86,7 +86,7 @@ doPeerReservationsAdd(RPC::Context& context) } Json::Value -doPeerReservationsDel(RPC::Context& context) +doPeerReservationsDel(RPC::JsonContext& context) { auto const& params = context.params; @@ -113,7 +113,7 @@ doPeerReservationsDel(RPC::Context& context) } Json::Value -doPeerReservationsList(RPC::Context& context) +doPeerReservationsList(RPC::JsonContext& context) { auto const& reservations = context.app.peerReservations().list(); // Enumerate the reservations in context.app.peerReservations() diff --git a/src/ripple/rpc/handlers/RipplePathFind.cpp b/src/ripple/rpc/handlers/RipplePathFind.cpp index c3216235a0f..86076355d84 100644 --- a/src/ripple/rpc/handlers/RipplePathFind.cpp +++ b/src/ripple/rpc/handlers/RipplePathFind.cpp @@ -28,7 +28,7 @@ namespace ripple { // This interface is deprecated. -Json::Value doRipplePathFind (RPC::Context& context) +Json::Value doRipplePathFind (RPC::JsonContext& context) { if (context.app.config().PATH_SEARCH_MAX == 0) return rpcError (rpcNOT_SUPPORTED); diff --git a/src/ripple/rpc/handlers/ServerInfo.cpp b/src/ripple/rpc/handlers/ServerInfo.cpp index 90bd949525c..288f072b43f 100644 --- a/src/ripple/rpc/handlers/ServerInfo.cpp +++ b/src/ripple/rpc/handlers/ServerInfo.cpp @@ -27,7 +27,7 @@ namespace ripple { -Json::Value doServerInfo (RPC::Context& context) +Json::Value doServerInfo (RPC::JsonContext& context) { Json::Value ret (Json::objectValue); diff --git a/src/ripple/rpc/handlers/ServerState.cpp b/src/ripple/rpc/handlers/ServerState.cpp index ab9d932fc10..a57a2956bab 100644 --- a/src/ripple/rpc/handlers/ServerState.cpp +++ b/src/ripple/rpc/handlers/ServerState.cpp @@ -27,7 +27,7 @@ namespace ripple { -Json::Value doServerState (RPC::Context& context) +Json::Value doServerState (RPC::JsonContext& context) { Json::Value ret (Json::objectValue); diff --git a/src/ripple/rpc/handlers/SignFor.cpp b/src/ripple/rpc/handlers/SignFor.cpp index 2a4ab53067a..29b99f85963 100644 --- a/src/ripple/rpc/handlers/SignFor.cpp +++ b/src/ripple/rpc/handlers/SignFor.cpp @@ -31,7 +31,7 @@ namespace ripple { // account: // secret: // } -Json::Value doSignFor (RPC::Context& context) +Json::Value doSignFor (RPC::JsonContext& context) { if (context.role != Role::ADMIN && !context.app.config().canSign()) { diff --git a/src/ripple/rpc/handlers/SignHandler.cpp b/src/ripple/rpc/handlers/SignHandler.cpp index a19535bbbe7..e9922560875 100644 --- a/src/ripple/rpc/handlers/SignHandler.cpp +++ b/src/ripple/rpc/handlers/SignHandler.cpp @@ -29,7 +29,7 @@ namespace ripple { // tx_json: , // secret: // } -Json::Value doSign (RPC::Context& context) +Json::Value doSign (RPC::JsonContext& context) { if (context.role != Role::ADMIN && !context.app.config().canSign()) { diff --git a/src/ripple/rpc/handlers/Stop.cpp b/src/ripple/rpc/handlers/Stop.cpp index c7d500b81cc..9418b69f5f8 100644 --- a/src/ripple/rpc/handlers/Stop.cpp +++ b/src/ripple/rpc/handlers/Stop.cpp @@ -26,10 +26,10 @@ namespace ripple { namespace RPC { -struct Context; +struct JsonContext; } -Json::Value doStop (RPC::Context& context) +Json::Value doStop (RPC::JsonContext& context) { std::unique_lock lock{context.app.getMasterMutex()}; context.app.signalStop (); diff --git a/src/ripple/rpc/handlers/Submit.cpp b/src/ripple/rpc/handlers/Submit.cpp index c1ecd687457..e25ca661fcb 100644 --- a/src/ripple/rpc/handlers/Submit.cpp +++ b/src/ripple/rpc/handlers/Submit.cpp @@ -26,10 +26,12 @@ #include #include #include +#include +#include namespace ripple { -static NetworkOPs::FailHard getFailHard (RPC::Context const& context) +static NetworkOPs::FailHard getFailHard (RPC::JsonContext const& context) { return NetworkOPs::doFailHard ( context.params.isMember ("fail_hard") @@ -40,7 +42,7 @@ static NetworkOPs::FailHard getFailHard (RPC::Context const& context) // tx_json: , // secret: // } -Json::Value doSubmit (RPC::Context& context) +Json::Value doSubmit (RPC::JsonContext& context) { context.loadType = Resource::feeMediumBurdenRPC; @@ -181,4 +183,100 @@ Json::Value doSubmit (RPC::Context& context) } } -} // ripple +std::pair +doSubmitGrpc(RPC::GRPCContext& context) +{ + // return values + rpc::v1::SubmitTransactionResponse result; + grpc::Status status = grpc::Status::OK; + + // input + auto request = context.params; + + std::string const& tx = request.signed_transaction(); + + // convert to blob + Blob blob{tx.begin(), tx.end()}; + + // serialize + SerialIter sitTrans(makeSlice(blob)); + std::shared_ptr stpTrans; + try + { + stpTrans = std::make_shared(std::ref(sitTrans)); + } + catch (std::exception& e) + { + grpc::Status errorStatus{ + grpc::StatusCode::INVALID_ARGUMENT, + "invalid transaction: " + std::string(e.what())}; + return {result, errorStatus}; + } + + // check validity + { + if (!context.app.checkSigs()) + forceValidity( + context.app.getHashRouter(), + stpTrans->getTransactionID(), + Validity::SigGoodOnly); + auto [validity, reason] = checkValidity( + context.app.getHashRouter(), + *stpTrans, + context.ledgerMaster.getCurrentLedger()->rules(), + context.app.config()); + if (validity != Validity::Valid) + { + grpc::Status errorStatus{grpc::StatusCode::INVALID_ARGUMENT, + "invalid transaction: " + reason}; + return {result, errorStatus}; + } + } + + std::string reason; + auto tpTrans = std::make_shared(stpTrans, reason, context.app); + if (tpTrans->getStatus() != NEW) + { + grpc::Status errorStatus{grpc::StatusCode::INVALID_ARGUMENT, + "invalid transaction: " + reason}; + return {result, errorStatus}; + } + + try + { + auto const failType = NetworkOPs::doFailHard(request.fail_hard()); + + // submit to network + context.netOps.processTransaction( + tpTrans, isUnlimited(context.role), true, failType); + } + catch (std::exception& e) + { + grpc::Status errorStatus{ + grpc::StatusCode::INVALID_ARGUMENT, + "invalid transaction : " + std::string(e.what())}; + return {result, errorStatus}; + } + + // return preliminary result + if (temUNCERTAIN != tpTrans->getResult()) + { + RPC::populateTransactionResultType( + *result.mutable_engine_result(), tpTrans->getResult()); + + std::string sToken; + std::string sHuman; + + transResultInfo(tpTrans->getResult(), sToken, sHuman); + + result.mutable_engine_result()->set_result(sToken); + result.set_engine_result_code(TERtoInt(tpTrans->getResult())); + result.set_engine_result_message(sHuman); + + uint256 hash = tpTrans->getID(); + result.set_hash(hash.data(), hash.size()); + } + return {result, status}; +} + +} // namespace ripple diff --git a/src/ripple/rpc/handlers/SubmitMultiSigned.cpp b/src/ripple/rpc/handlers/SubmitMultiSigned.cpp index 96b1065455b..274a106b6f1 100644 --- a/src/ripple/rpc/handlers/SubmitMultiSigned.cpp +++ b/src/ripple/rpc/handlers/SubmitMultiSigned.cpp @@ -30,7 +30,7 @@ namespace ripple { // SigningAccounts , // tx_json: , // } -Json::Value doSubmitMultiSigned (RPC::Context& context) +Json::Value doSubmitMultiSigned (RPC::JsonContext& context) { // Bail if multisign is not enabled. if (! context.app.getLedgerMaster().getValidatedRules(). diff --git a/src/ripple/rpc/handlers/Subscribe.cpp b/src/ripple/rpc/handlers/Subscribe.cpp index 3279f2123d2..3bff17d8c1f 100644 --- a/src/ripple/rpc/handlers/Subscribe.cpp +++ b/src/ripple/rpc/handlers/Subscribe.cpp @@ -33,7 +33,7 @@ namespace ripple { -Json::Value doSubscribe (RPC::Context& context) +Json::Value doSubscribe (RPC::JsonContext& context) { InfoSub::pointer ispSub; Json::Value jvResult (Json::objectValue); diff --git a/src/ripple/rpc/handlers/TransactionEntry.cpp b/src/ripple/rpc/handlers/TransactionEntry.cpp index da6879d0824..7f3ef6805a7 100644 --- a/src/ripple/rpc/handlers/TransactionEntry.cpp +++ b/src/ripple/rpc/handlers/TransactionEntry.cpp @@ -32,7 +32,7 @@ namespace ripple { // // XXX In this case, not specify either ledger does not mean ledger current. It // means any ledger. -Json::Value doTransactionEntry (RPC::Context& context) +Json::Value doTransactionEntry (RPC::JsonContext& context) { std::shared_ptr lpLedger; Json::Value jvResult = RPC::lookupLedger (lpLedger, context); diff --git a/src/ripple/rpc/handlers/Tx.cpp b/src/ripple/rpc/handlers/Tx.cpp index c5e302fcf72..12607ecfedf 100644 --- a/src/ripple/rpc/handlers/Tx.cpp +++ b/src/ripple/rpc/handlers/Tx.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace ripple { @@ -52,15 +53,22 @@ isHexTxID (std::string const& txid) static bool -isValidated (RPC::Context& context, std::uint32_t seq, uint256 const& hash) +isValidated(LedgerMaster& ledgerMaster, std::uint32_t seq, uint256 const& hash) { - if (!context.ledgerMaster.haveLedger (seq)) + if (!ledgerMaster.haveLedger (seq)) return false; - if (seq > context.ledgerMaster.getValidatedLedger ()->info().seq) + if (seq > ledgerMaster.getValidatedLedger ()->info().seq) return false; - return context.ledgerMaster.getHashBySeq (seq) == 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 @@ -83,7 +91,7 @@ getMetaHex (Ledger const& ledger, return true; } -Json::Value doTx (RPC::Context& context) +Json::Value doTx (RPC::JsonContext& context) { if (!context.params.isMember (jss::transaction)) return rpcError (rpcINVALID_PARAMS); @@ -198,4 +206,117 @@ Json::Value doTx (RPC::Context& context) return ret; } -} // ripple +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()); + + auto ec{rpcSUCCESS}; + + // get the transaction + std::shared_ptr txn = + context.app.getMasterTransaction().fetch(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}; + } + 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}; + } + + // populate transaction data + if (request.binary()) + { + Serializer s = stTxn->getSerializer(); + result.set_transaction_binary(s.data(), s.size()); + } + 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()) + { + SHAMapTreeNode::TNType type; + auto const item = ledger->txMap().peekItem(txn->getID(), type); + + if (item && type == SHAMapTreeNode::tnTRANSACTION_MD) + { + 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); + } + } + else + { + auto rawMeta = ledger->txRead(txn->getID()).second; + if (rawMeta) + { + auto txMeta = 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); + } + } + } + return {result, status}; +} + +} // namespace ripple diff --git a/src/ripple/rpc/handlers/TxHistory.cpp b/src/ripple/rpc/handlers/TxHistory.cpp index 05400bc8ac6..f6d7cbf9907 100644 --- a/src/ripple/rpc/handlers/TxHistory.cpp +++ b/src/ripple/rpc/handlers/TxHistory.cpp @@ -34,7 +34,7 @@ namespace ripple { // { // start: // } -Json::Value doTxHistory (RPC::Context& context) +Json::Value doTxHistory (RPC::JsonContext& context) { context.loadType = Resource::feeMediumBurdenRPC; diff --git a/src/ripple/rpc/handlers/UnlList.cpp b/src/ripple/rpc/handlers/UnlList.cpp index ff016421d9a..a0cb9495d22 100644 --- a/src/ripple/rpc/handlers/UnlList.cpp +++ b/src/ripple/rpc/handlers/UnlList.cpp @@ -24,7 +24,7 @@ namespace ripple { -Json::Value doUnlList (RPC::Context& context) +Json::Value doUnlList (RPC::JsonContext& context) { Json::Value obj (Json::objectValue); diff --git a/src/ripple/rpc/handlers/Unsubscribe.cpp b/src/ripple/rpc/handlers/Unsubscribe.cpp index 799189ae590..8e5bb5f03b9 100644 --- a/src/ripple/rpc/handlers/Unsubscribe.cpp +++ b/src/ripple/rpc/handlers/Unsubscribe.cpp @@ -28,7 +28,7 @@ namespace ripple { -Json::Value doUnsubscribe (RPC::Context& context) +Json::Value doUnsubscribe (RPC::JsonContext& context) { InfoSub::pointer ispSub; diff --git a/src/ripple/rpc/handlers/ValidationCreate.cpp b/src/ripple/rpc/handlers/ValidationCreate.cpp index 4086a60d6b4..54ce3322373 100644 --- a/src/ripple/rpc/handlers/ValidationCreate.cpp +++ b/src/ripple/rpc/handlers/ValidationCreate.cpp @@ -42,7 +42,7 @@ validationSeed (Json::Value const& params) // // This command requires Role::ADMIN access because it makes // no sense to ask an untrusted server for this. -Json::Value doValidationCreate (RPC::Context& context) +Json::Value doValidationCreate (RPC::JsonContext& context) { Json::Value obj (Json::objectValue); diff --git a/src/ripple/rpc/handlers/ValidatorListSites.cpp b/src/ripple/rpc/handlers/ValidatorListSites.cpp index d51aa8214fc..20989de20b3 100644 --- a/src/ripple/rpc/handlers/ValidatorListSites.cpp +++ b/src/ripple/rpc/handlers/ValidatorListSites.cpp @@ -24,7 +24,7 @@ namespace ripple { Json::Value -doValidatorListSites(RPC::Context& context) +doValidatorListSites(RPC::JsonContext& context) { return context.app.validatorSites().getJson(); } diff --git a/src/ripple/rpc/handlers/Validators.cpp b/src/ripple/rpc/handlers/Validators.cpp index 0159bcddbf9..cf46eee5437 100644 --- a/src/ripple/rpc/handlers/Validators.cpp +++ b/src/ripple/rpc/handlers/Validators.cpp @@ -24,7 +24,7 @@ namespace ripple { Json::Value -doValidators(RPC::Context& context) +doValidators(RPC::JsonContext& context) { return context.app.validators().getJson(); } diff --git a/src/ripple/rpc/handlers/Version.h b/src/ripple/rpc/handlers/Version.h index 15916ec6d50..6c62cf53eee 100644 --- a/src/ripple/rpc/handlers/Version.h +++ b/src/ripple/rpc/handlers/Version.h @@ -28,7 +28,7 @@ namespace RPC { class VersionHandler { public: - explicit VersionHandler (Context&) {} + explicit VersionHandler (JsonContext&) {} Status check() { diff --git a/src/ripple/rpc/handlers/WalletPropose.cpp b/src/ripple/rpc/handlers/WalletPropose.cpp index ab1692f312a..687a820c22e 100644 --- a/src/ripple/rpc/handlers/WalletPropose.cpp +++ b/src/ripple/rpc/handlers/WalletPropose.cpp @@ -64,7 +64,7 @@ estimate_entropy (std::string const& input) // { // passphrase: // } -Json::Value doWalletPropose (RPC::Context& context) +Json::Value doWalletPropose (RPC::JsonContext& context) { return walletPropose (context.params); } diff --git a/src/ripple/rpc/impl/DeliveredAmount.cpp b/src/ripple/rpc/impl/DeliveredAmount.cpp index f6261e0db1e..29b3a4e86ea 100644 --- a/src/ripple/rpc/impl/DeliveredAmount.cpp +++ b/src/ripple/rpc/impl/DeliveredAmount.cpp @@ -137,7 +137,7 @@ insertDeliveredAmount( void insertDeliveredAmount( Json::Value& meta, - RPC::Context& context, + RPC::JsonContext& context, std::shared_ptr transaction, TxMeta const& transactionMeta) { @@ -172,5 +172,80 @@ insertDeliveredAmount( std::move(serializedTx), transactionMeta); } -} // RPC -} // ripple + +// 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()); + }; + + { + 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 (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}) + { + populateAmount(proto, serializedTx->getFieldAmount(sfAmount)); + return; + } + } +} + +} // namespace RPC +} // namespace ripple diff --git a/src/ripple/rpc/impl/Handler.cpp b/src/ripple/rpc/impl/Handler.cpp index c34795470ca..69b2a3e6276 100644 --- a/src/ripple/rpc/impl/Handler.cpp +++ b/src/ripple/rpc/impl/Handler.cpp @@ -30,7 +30,7 @@ namespace { template Handler::Method byRef (Function const& f) { - return [f] (Context& context, Json::Value& result) + return [f] (JsonContext& context, Json::Value& result) { result = f (context); if (result.type() != Json::objectValue) @@ -44,7 +44,7 @@ Handler::Method byRef (Function const& f) } template -Status handle (Context& context, Object& object) +Status handle (JsonContext& context, Object& object) { HandlerImpl handler (context); diff --git a/src/ripple/rpc/impl/Handler.h b/src/ripple/rpc/impl/Handler.h index 15014c8fa83..446f699be45 100644 --- a/src/ripple/rpc/impl/Handler.h +++ b/src/ripple/rpc/impl/Handler.h @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include #include namespace Json { @@ -43,7 +46,7 @@ enum Condition { struct Handler { template - using Method = std::function ; + using Method = std::function ; const char* name_; Method valueMethod_; @@ -66,6 +69,55 @@ Json::Value makeObjectValue ( /** Return names of all methods. */ std::vector getHandlerNames(); +template +error_code_i conditionMet(Condition condition_required, T& context) +{ + if ((condition_required & NEEDS_NETWORK_CONNECTION) && + (context.netOps.getOperatingMode () < OperatingMode::SYNCING)) + { + JLOG (context.j.info()) + << "Insufficient network mode for RPC: " + << context.netOps.strOperatingMode (); + + return rpcNO_NETWORK; + } + + if (context.app.getOPs().isAmendmentBlocked() && + (condition_required & NEEDS_CURRENT_LEDGER || + condition_required & NEEDS_CLOSED_LEDGER)) + { + return rpcAMENDMENT_BLOCKED; + } + + if (!context.app.config().standalone() && + condition_required & NEEDS_CURRENT_LEDGER) + { + if (context.ledgerMaster.getValidatedLedgerAge () > + Tuning::maxValidatedLedgerAge) + { + return rpcNO_CURRENT; + } + + auto const cID = context.ledgerMaster.getCurrentLedgerIndex (); + auto const vID = context.ledgerMaster.getValidLedgerIndex (); + + if (cID + 10 < vID) + { + JLOG (context.j.debug()) << "Current ledger ID(" << cID << + ") is less than validated ledger ID(" << vID << ")"; + return rpcNO_CURRENT; + } + } + + if ((condition_required & NEEDS_CLOSED_LEDGER) && + !context.ledgerMaster.getClosedLedger ()) + { + return rpcNO_CLOSED; + } + + return rpcSUCCESS; +} + } // RPC } // ripple diff --git a/src/ripple/rpc/impl/RPCHandler.cpp b/src/ripple/rpc/impl/RPCHandler.cpp index 1b6d81a7efc..756d1bf4ccd 100644 --- a/src/ripple/rpc/impl/RPCHandler.cpp +++ b/src/ripple/rpc/impl/RPCHandler.cpp @@ -111,7 +111,7 @@ namespace { */ -error_code_i fillHandler (Context& context, +error_code_i fillHandler (JsonContext& context, Handler const * & result) { if (! isUnlimited (context.role)) @@ -149,47 +149,10 @@ error_code_i fillHandler (Context& context, if (handler->role_ == Role::ADMIN && context.role != Role::ADMIN) return rpcNO_PERMISSION; - if ((handler->condition_ & NEEDS_NETWORK_CONNECTION) && - (context.netOps.getOperatingMode () < OperatingMode::SYNCING)) + error_code_i res = conditionMet(handler->condition_,context); + if(res != rpcSUCCESS) { - JLOG (context.j.info()) - << "Insufficient network mode for RPC: " - << context.netOps.strOperatingMode (); - - return rpcNO_NETWORK; - } - - if (context.app.getOPs().isAmendmentBlocked() && - (handler->condition_ & NEEDS_CURRENT_LEDGER || - handler->condition_ & NEEDS_CLOSED_LEDGER)) - { - return rpcAMENDMENT_BLOCKED; - } - - if (!context.app.config().standalone() && - handler->condition_ & NEEDS_CURRENT_LEDGER) - { - if (context.ledgerMaster.getValidatedLedgerAge () > - Tuning::maxValidatedLedgerAge) - { - return rpcNO_CURRENT; - } - - auto const cID = context.ledgerMaster.getCurrentLedgerIndex (); - auto const vID = context.ledgerMaster.getValidLedgerIndex (); - - if (cID + 10 < vID) - { - JLOG (context.j.debug()) << "Current ledger ID(" << cID << - ") is less than validated ledger ID(" << vID << ")"; - return rpcNO_CURRENT; - } - } - - if ((handler->condition_ & NEEDS_CLOSED_LEDGER) && - !context.ledgerMaster.getClosedLedger ()) - { - return rpcNO_CLOSED; + return res; } result = handler; @@ -198,7 +161,7 @@ error_code_i fillHandler (Context& context, template Status callMethod ( - Context& context, Method method, std::string const& name, Object& result) + JsonContext& context, Method method, std::string const& name, Object& result) { static std::atomic requestId {0}; auto& perfLog = context.app.getPerfLog(); @@ -228,7 +191,7 @@ Status callMethod ( template void getResult ( - Context& context, Method method, Object& object, std::string const& name) + JsonContext& context, Method method, Object& object, std::string const& name) { auto&& result = Json::addObject (object, jss::result); if (auto status = callMethod (context, method, name, result)) @@ -261,7 +224,7 @@ void getResult ( } // namespace Status doCommand ( - RPC::Context& context, Json::Value& result) + RPC::JsonContext& context, Json::Value& result) { Handler const * handler = nullptr; if (auto error = fillHandler (context, handler)) diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/ripple/rpc/impl/RPCHelpers.cpp index 5409e73bc0d..d28c4ca3db1 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/ripple/rpc/impl/RPCHelpers.cpp @@ -49,20 +49,20 @@ accountFromStringStrict(std::string const& account) return result; } -Json::Value -accountFromString( +error_code_i +accountFromStringWithCode( AccountID& result, std::string const& strIdent, bool bStrict) { if (auto accountID = accountFromStringStrict (strIdent)) { result = *accountID; - return Json::objectValue; + return rpcSUCCESS; } if (bStrict) { auto id = deprecatedParseBitcoinAccountID (strIdent); - return rpcError (id ? rpcACT_BITCOIN : rpcACT_MALFORMED); + return id ? rpcACT_BITCOIN : rpcACT_MALFORMED; } // We allow the use of the seeds which is poor practice @@ -70,14 +70,26 @@ accountFromString( auto const seed = parseGenericSeed (strIdent); if (!seed) - return rpcError (rpcBAD_SEED); + return rpcBAD_SEED; auto const keypair = generateKeyPair ( KeyType::secp256k1, *seed); result = calcAccountID (keypair.first); - return Json::objectValue; + return rpcSUCCESS; +} + +Json::Value +accountFromString( + AccountID& result, std::string const& strIdent, bool bStrict) +{ + + error_code_i code = accountFromStringWithCode(result, strIdent, bStrict); + if(code != rpcSUCCESS) + return rpcError(code); + else + return Json::objectValue; } bool @@ -185,7 +197,7 @@ isValidatedOld(LedgerMaster& ledgerMaster, bool standalone) template Status -ledgerFromRequest(T& ledger, Context& context) +ledgerFromRequest(T& ledger, JsonContext& context) { static auto const minSequenceGap = 10; @@ -286,6 +298,106 @@ ledgerFromRequest(T& ledger, Context& context) return Status::OK; } +} // namespace + +template +Status +ledgerFromRequest( + T& ledger, + GRPCContext& context) +{ + static auto const minSequenceGap = 10; + + ledger.reset(); + + rpc::v1::GetAccountInfoRequest& request = context.params; + auto& ledgerMaster = context.ledgerMaster; + + using LedgerCase = rpc::v1::LedgerSpecifier::LedgerCase; + LedgerCase ledgerCase = request.ledger().ledger_case(); + + if (ledgerCase == LedgerCase::kHash) + { + 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) + { + auto cur = ledgerMaster.getCurrentLedger(); + if (cur->info().seq == request.ledger().sequence()) + 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"}; + } + } + 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) + { + 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"}; + } + } + } + + return Status::OK; +} + +// explicit instantiation of above function +template Status +ledgerFromRequest<>( + std::shared_ptr&, + GRPCContext&); bool isValidated(LedgerMaster& ledgerMaster, ReadView const& ledger, @@ -334,7 +446,6 @@ isValidated(LedgerMaster& ledgerMaster, ReadView const& ledger, return true; } -} // namespace // The previous version of the lookupLedger command would accept the // "ledger_index" argument as a string and silently treat it as a request to @@ -356,7 +467,7 @@ isValidated(LedgerMaster& ledgerMaster, ReadView const& ledger, // optionally the fields "ledger_hash", "ledger_index" and // "ledger_current_index", if they are defined. Status -lookupLedger(std::shared_ptr& ledger, Context& context, +lookupLedger(std::shared_ptr& ledger, JsonContext& context, Json::Value& result) { if (auto status = ledgerFromRequest (ledger, context)) @@ -379,7 +490,7 @@ lookupLedger(std::shared_ptr& ledger, Context& context, } Json::Value -lookupLedger(std::shared_ptr& ledger, Context& context) +lookupLedger(std::shared_ptr& ledger, JsonContext& context) { Json::Value result; if (auto status = lookupLedger (ledger, context, result)) @@ -433,7 +544,7 @@ injectSLE(Json::Value& jv, SLE const& sle) boost::optional readLimitField(unsigned int& limit, Tuning::LimitRange const& range, - Context const& context) + JsonContext const& context) { limit = range.rdefault; if (auto const& jvLimit = context.params[jss::limit]) @@ -712,6 +823,672 @@ 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 981f352dbbc..eaa8b0cd43e 100644 --- a/src/ripple/rpc/impl/RPCHelpers.h +++ b/src/ripple/rpc/impl/RPCHelpers.h @@ -22,10 +22,16 @@ #include #include + #include +#include #include #include +#include +#include +#include #include +#include namespace Json { class Value; @@ -38,7 +44,7 @@ class Transaction; namespace RPC { -struct Context; +struct JsonContext; /** Get an AccountID from an account ID or public key. */ boost::optional @@ -52,6 +58,16 @@ Json::Value accountFromString (AccountID& result, std::string const& strIdent, bool bStrict = false); +/** Decode account ID from string + @param[out] result account ID decoded from string + @param strIdent public key, account ID, or regular seed. + @param bStrict Only allow account id or public key. + @return code representing error, or rpcSUCCES on success +*/ +error_code_i +accountFromStringWithCode (AccountID& result, std::string const& strIdent, + bool bStrict = false); + /** Gathers all objects for an account in a ledger. @param ledger Ledger to search account objects. @param account AccountID to find objects for. @@ -73,7 +89,7 @@ getAccountObjects (ReadView const& ledger, AccountID const& account, been filled. */ Json::Value -lookupLedger (std::shared_ptr&, Context&); +lookupLedger (std::shared_ptr&, JsonContext&); /** Look up a ledger from a request and fill a Json::Result with the data representing a ledger. @@ -81,7 +97,17 @@ lookupLedger (std::shared_ptr&, Context&); If the returned Status is OK, the ledger pointer will have been filled. */ Status -lookupLedger (std::shared_ptr&, Context&, Json::Value& result); +lookupLedger (std::shared_ptr&, JsonContext&, Json::Value& result); + +template +Status +ledgerFromRequest( + T& ledger, + GRPCContext& context); + +bool +isValidated(LedgerMaster& ledgerMaster, ReadView const& ledger, + Application& app); hash_set parseAccountIds(Json::Value const& jvArray); @@ -97,13 +123,13 @@ parseAccountIds(Json::Value const& jvArray); void injectSLE(Json::Value& jv, SLE const& sle); -/** Retrieve the limit value from a Context, or set a default - +/** Retrieve the limit value from a JsonContext, or set a default - then restrict the limit by max and min if not an ADMIN request. If there is an error, return it as JSON. */ boost::optional -readLimitField(unsigned int& limit, Tuning::LimitRange const&, Context const&); +readLimitField(unsigned int& limit, Tuning::LimitRange const&, JsonContext const&); boost::optional getSeedFromRPC(Json::Value const& params, Json::Value& error); @@ -177,6 +203,46 @@ std::pair * @return the api version number */ 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/ripple/rpc/impl/ServerHandlerImp.cpp b/src/ripple/rpc/impl/ServerHandlerImp.cpp index 9ec6e8808dc..45b205ceb7b 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.cpp +++ b/src/ripple/rpc/impl/ServerHandlerImp.cpp @@ -442,20 +442,19 @@ ServerHandlerImp::processSession( } else { - RPC::Context context{ - app_.journal("RPCHandler"), - jv, - app_, - loadType, - app_.getOPs(), - app_.getLedgerMaster(), - is->getConsumer(), - role, - apiVersion, - coro, - is, - {is->user(), is->forwarded_for()} - }; + RPC::JsonContext context{{app_.journal("RPCHandler"), + app_, + loadType, + app_.getOPs(), + app_.getLedgerMaster(), + is->getConsumer(), + role, + coro, + is}, + jv, + apiVersion, + {is->user(), is->forwarded_for()}}; + RPC::doCommand(context, jr[jss::result]); } } @@ -818,9 +817,18 @@ ServerHandlerImp::processRequest (Port const& port, Resource::Charge loadType = Resource::feeReferenceRPC; - RPC::Context context {m_journal, params, app_, loadType, m_networkOPs, - app_.getLedgerMaster(), usage, role, apiVersion, coro, InfoSub::pointer(), - {user, forwardedFor}}; + RPC::JsonContext context{{m_journal, + app_, + loadType, + m_networkOPs, + app_.getLedgerMaster(), + usage, + role, + coro, + InfoSub::pointer()}, + params, + apiVersion, + {user, forwardedFor}}; Json::Value result; RPC::doCommand (context, result); usage.charge (loadType); diff --git a/src/ripple/unity/app_main1.cpp b/src/ripple/unity/app_main1.cpp index b69669db7de..1111ee94517 100644 --- a/src/ripple/unity/app_main1.cpp +++ b/src/ripple/unity/app_main1.cpp @@ -21,3 +21,4 @@ #include #include #include +#include diff --git a/src/test/app/Path_test.cpp b/src/test/app/Path_test.cpp index 589ced35c28..a801cc4eef7 100644 --- a/src/test/app/Path_test.cpp +++ b/src/test/app/Path_test.cpp @@ -221,9 +221,17 @@ class Path_test : public beast::unit_test::suite auto& app = env.app(); Resource::Charge loadType = Resource::feeReferenceRPC; Resource::Consumer c; - RPC::Context context { env.journal, {}, app, loadType, - app.getOPs(), app.getLedgerMaster(), c, Role::USER, - RPC::APIVersionIfUnspecified}; + + RPC::JsonContext context{{env.journal, + app, + loadType, + app.getOPs(), + app.getLedgerMaster(), + c, + Role::USER}, + {}, + RPC::APIVersionIfUnspecified, + {}}; Json::Value params = Json::objectValue; params[jss::command] = "ripple_path_find"; @@ -321,9 +329,17 @@ class Path_test : public beast::unit_test::suite auto& app = env.app(); Resource::Charge loadType = Resource::feeReferenceRPC; Resource::Consumer c; - RPC::Context context {env.journal, {}, app, loadType, - app.getOPs(), app.getLedgerMaster(), c, Role::USER, - RPC::APIVersionIfUnspecified}; + + RPC::JsonContext context{{env.journal, + app, + loadType, + app.getOPs(), + app.getLedgerMaster(), + c, + Role::USER}, + {}, + RPC::APIVersionIfUnspecified, + {}}; Json::Value result; gate g; // Test RPC::Tuning::max_src_cur source currencies. diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index 93d0c21a6e2..7cee741732e 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -115,9 +115,18 @@ validator(std::unique_ptr, std::string const&); std::unique_ptr port_increment(std::unique_ptr, int); -} // jtx -} // test -} // ripple +/// @brief add a grpc address and port to config +/// +/// This is intended for use with envconfig, for tests that require a grpc +/// server. If this function is not called, grpc server will not start +/// +/// +/// @param cfg config instance to be modified +std::unique_ptr addGrpcConfig(std::unique_ptr); + +} // namespace jtx +} // namespace test +} // namespace ripple #endif diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index 04e50e433ba..cd0c0312be7 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -108,6 +108,15 @@ port_increment(std::unique_ptr cfg, int increment) return cfg; } -} // jtx -} // test -} // ripple +std::unique_ptr +addGrpcConfig(std::unique_ptr cfg) +{ + std::string port_grpc = std::to_string(port_base + 3); + (*cfg)["port_grpc"].set("ip", getEnvLocalhostAddr()); + (*cfg)["port_grpc"].set("port", port_grpc); + return cfg; +} + +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 1dc4f094e53..0899426abcc 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -21,6 +21,12 @@ #include #include +#include +#include +#include +#include +#include + namespace ripple { namespace test { @@ -314,16 +320,232 @@ class AccountInfo_test : public beast::unit_test::suite } } - void run() override + // gRPC stuff + class GetAccountInfoClient : public GRPCTestClientBase + { + public: + rpc::v1::GetAccountInfoRequest request; + rpc::v1::GetAccountInfoResponse reply; + + explicit GetAccountInfoClient(std::string const& port) + : GRPCTestClientBase(port) + { + } + + void + GetAccountInfo() + { + status = stub_->GetAccountInfo(&context, request, &reply); + } + }; + + void + testSimpleGrpc() + { + testcase("gRPC simple"); + + using namespace jtx; + std::unique_ptr config = envconfig(addGrpcConfig); + std::string grpcPort = *(*config)["port_grpc"].get("port"); + Env env(*this, std::move(config)); + Account const alice{"alice"}; + env.fund(drops(1000 * 1000 * 1000), alice); + + { + // most simple case + GetAccountInfoClient client(grpcPort); + client.request.mutable_account()->set_address(alice.human()); + client.GetAccountInfo(); + if (!BEAST_EXPECT(client.status.ok())) + { + std::cout << client.reply.DebugString() << std::endl; + return; + } + BEAST_EXPECT( + client.reply.account_data().account().address() == + alice.human()); + } + { + GetAccountInfoClient client(grpcPort); + client.request.mutable_account()->set_address(alice.human()); + client.request.set_queue(true); + client.request.mutable_ledger()->set_sequence(3); + client.GetAccountInfo(); + if (!BEAST_EXPECT(client.status.ok())) + return; + BEAST_EXPECT( + client.reply.account_data().balance().drops() == + 1000 * 1000 * 1000); + BEAST_EXPECT( + client.reply.account_data().account().address() == + alice.human()); + BEAST_EXPECT( + client.reply.account_data().sequence() == env.seq(alice)); + BEAST_EXPECT(client.reply.queue_data().txn_count() == 0); + } + } + + void + testErrorsGrpc() + { + testcase("gRPC errors"); + + using namespace jtx; + std::unique_ptr config = envconfig(addGrpcConfig); + std::string grpcPort = *(*config)["port_grpc"].get("port"); + Env env(*this, std::move(config)); + auto getClient = [&grpcPort]() { + return GetAccountInfoClient(grpcPort); + }; + Account const alice{"alice"}; + env.fund(drops(1000 * 1000 * 1000), alice); + + { + // bad address + auto client = getClient(); + client.request.mutable_account()->set_address("deadbeef"); + client.GetAccountInfo(); + BEAST_EXPECT(!client.status.ok()); + } + { + // no account + Account const bogie{"bogie"}; + auto client = getClient(); + client.request.mutable_account()->set_address(bogie.human()); + client.GetAccountInfo(); + BEAST_EXPECT(!client.status.ok()); + } + { + // bad ledger_index + auto client = getClient(); + client.request.mutable_account()->set_address(alice.human()); + client.request.mutable_ledger()->set_sequence(0); + client.GetAccountInfo(); + BEAST_EXPECT(!client.status.ok()); + } + } + + void + testSignerListsGrpc() + { + testcase("gRPC singer lists"); + + using namespace jtx; + std::unique_ptr config = envconfig(addGrpcConfig); + std::string grpcPort = *(*config)["port_grpc"].get("port"); + Env env(*this, std::move(config)); + auto getClient = [&grpcPort]() { + return GetAccountInfoClient(grpcPort); + }; + + Account const alice{"alice"}; + env.fund(drops(1000 * 1000 * 1000), alice); + + { + auto client = getClient(); + client.request.mutable_account()->set_address(alice.human()); + client.request.set_signer_lists(true); + client.GetAccountInfo(); + if (!BEAST_EXPECT(client.status.ok())) + return; + BEAST_EXPECT(client.reply.signer_list().signer_entries_size() == 0); + } + + // Give alice a SignerList. + Account const bogie{"bogie"}; + Json::Value const smallSigners = signers(alice, 2, {{bogie, 3}}); + env(smallSigners); + { + auto client = getClient(); + client.request.mutable_account()->set_address(alice.human()); + client.request.set_signer_lists(false); + client.GetAccountInfo(); + if (!BEAST_EXPECT(client.status.ok())) + return; + BEAST_EXPECT(client.reply.signer_list().signer_entries_size() == 0); + } + { + auto client = getClient(); + client.request.mutable_account()->set_address(alice.human()); + client.request.set_signer_lists(true); + client.GetAccountInfo(); + if (!BEAST_EXPECT(client.status.ok())) + { + return; + } + BEAST_EXPECT(client.reply.account_data().owner_count() == 1); + BEAST_EXPECT(client.reply.signer_list().signer_entries_size() == 1); + } + + // Give alice a big signer list + Account const demon{"demon"}; + Account const ghost{"ghost"}; + Account const haunt{"haunt"}; + Account const jinni{"jinni"}; + Account const phase{"phase"}; + Account const shade{"shade"}; + Account const spook{"spook"}; + Json::Value const bigSigners = signers( + alice, + 4, + { + {bogie, 1}, + {demon, 1}, + {ghost, 1}, + {haunt, 1}, + {jinni, 1}, + {phase, 1}, + {shade, 1}, + {spook, 1}, + }); + env(bigSigners); + + std::set accounts; + accounts.insert(bogie.human()); + accounts.insert(demon.human()); + accounts.insert(ghost.human()); + accounts.insert(haunt.human()); + accounts.insert(jinni.human()); + accounts.insert(phase.human()); + accounts.insert(shade.human()); + accounts.insert(spook.human()); + { + auto client = getClient(); + client.request.mutable_account()->set_address(alice.human()); + client.request.set_signer_lists(true); + client.GetAccountInfo(); + if (!BEAST_EXPECT(client.status.ok())) + { + return; + } + BEAST_EXPECT(client.reply.account_data().owner_count() == 1); + auto& signerList = client.reply.signer_list(); + BEAST_EXPECT(signerList.signer_quorum() == 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( + accounts.erase( + signerList.signer_entries(i).account().address()) == 1); + } + BEAST_EXPECT(accounts.size() == 0); + } + } + + void + run() override { testErrors(); testSignerLists(); testSignerListsV2(); + testSimpleGrpc(); + testErrorsGrpc(); + testSignerListsGrpc(); } }; -BEAST_DEFINE_TESTSUITE(AccountInfo,app,ripple); - -} -} +BEAST_DEFINE_TESTSUITE(AccountInfo, app, ripple); +} // namespace test +} // namespace ripple diff --git a/src/test/rpc/Fee_test.cpp b/src/test/rpc/Fee_test.cpp new file mode 100644 index 00000000000..887603cb82a --- /dev/null +++ b/src/test/rpc/Fee_test.cpp @@ -0,0 +1,143 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ripple { +namespace test { + +class Fee_test : public beast::unit_test::suite +{ + class GrpcFeeClient : public GRPCTestClientBase + { + public: + rpc::v1::GetFeeRequest request; + rpc::v1::GetFeeResponse reply; + + explicit GrpcFeeClient(std::string const& grpcPort) + : GRPCTestClientBase(grpcPort) + { + } + + void + GetFee() + { + status = stub_->GetFee(&context, request, &reply); + } + }; + + std::pair + grpcGetFee(std::string const& grpcPort) + { + GrpcFeeClient client(grpcPort); + client.GetFee(); + return std::pair( + client.status.ok(), client.reply); + } + + void + testFeeGrpc() + { + testcase("Test Fee 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"}; + env.fund(XRP(10000), A1); + env.fund(XRP(10000), A2); + env.close(); + env.trust(A2["USD"](1000), A1); + env.close(); + for (int i = 0; i < 7; ++i) + { + env(pay(A2, A1, A2["USD"](100))); + if (i == 4) + env.close(); + } + + auto view = env.current(); + + auto const metrics = env.app().getTxQ().getMetrics(*env.current()); + + auto const result = grpcGetFee(grpcPort); + + BEAST_EXPECT(result.first == true); + + auto reply = result.second; + + // current ledger data + BEAST_EXPECT(reply.current_ledger_size() == metrics.txInLedger); + BEAST_EXPECT(reply.current_queue_size() == metrics.txCount); + BEAST_EXPECT(reply.expected_ledger_size() == metrics.txPerLedger); + BEAST_EXPECT(reply.ledger_current_index() == view->info().seq); + BEAST_EXPECT(reply.max_queue_size() == *metrics.txQMaxSize); + + // fee levels data + 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(); + auto const baseFee = view->fees().base; + BEAST_EXPECT( + drops.base_fee().drops() == + toDrops(metrics.referenceFeeLevel, baseFee).second); + BEAST_EXPECT( + drops.minimum_fee().drops() == + toDrops(metrics.minProcessingFeeLevel, baseFee).second); + BEAST_EXPECT( + drops.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()); + } + +public: + void + run() override + { + testFeeGrpc(); + } +}; + +BEAST_DEFINE_TESTSUITE(Fee, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/rpc/GRPCTestClientBase.h b/src/test/rpc/GRPCTestClientBase.h new file mode 100644 index 00000000000..29d2e3598ff --- /dev/null +++ b/src/test/rpc/GRPCTestClientBase.h @@ -0,0 +1,48 @@ +//------------------------------------------------------------------------------ +/* + 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 RIPPLED_GRPCTESTCLIENTBASE_H +#define RIPPLED_GRPCTESTCLIENTBASE_H + +#include +#include + +namespace ripple { +namespace test { + +struct GRPCTestClientBase +{ + explicit GRPCTestClientBase(std::string const& port) + : stub_(rpc::v1::XRPLedgerAPIService::NewStub(grpc::CreateChannel( + beast::IP::Endpoint( + boost::asio::ip::make_address(getEnvLocalhostAddr()), + std::stoi(port)) + .to_string(), + grpc::InsecureChannelCredentials()))) + { + } + + grpc::Status status; + grpc::ClientContext context; + std::unique_ptr stub_; +}; + +} // namespace test +} // namespace ripple +#endif // RIPPLED_GRPCTESTCLIENTBASE_H diff --git a/src/test/rpc/Submit_test.cpp b/src/test/rpc/Submit_test.cpp new file mode 100644 index 00000000000..f048f6aa837 --- /dev/null +++ b/src/test/rpc/Submit_test.cpp @@ -0,0 +1,281 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include + +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class Submit_test : public beast::unit_test::suite +{ +public: + class SubmitClient : public GRPCTestClientBase + { + public: + rpc::v1::SubmitTransactionRequest request; + rpc::v1::SubmitTransactionResponse reply; + + explicit SubmitClient(std::string const& port) + : GRPCTestClientBase(port) + { + } + + void + SubmitTransaction() + { + status = stub_->SubmitTransaction(&context, request, &reply); + } + }; + + struct TestData + { + std::string xrpTxBlob; + std::string xrpTxHash; + std::string usdTxBlob; + std::string usdTxHash; + const static int fund = 10000; + } testData; + + void + fillTestData() + { + testcase("fill test data"); + + using namespace jtx; + Env env(*this, envconfig(addGrpcConfig)); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(TestData::fund), "alice", "bob"); + env.trust(bob["USD"](TestData::fund), alice); + env.close(); + + auto toBinary = [](std::string const& text) { + std::string binary; + for (size_t i = 0; i < text.size(); ++i) + { + unsigned int c = charUnHex(text[i]); + c = c << 4; + ++i; + c = c | charUnHex(text[i]); + binary.push_back(c); + } + + return binary; + }; + + // use a websocket client to fill transaction blobs + auto wsc = makeWSClient(env.app().config()); + { + Json::Value jrequestXrp; + jrequestXrp[jss::secret] = toBase58(generateSeed("alice")); + jrequestXrp[jss::tx_json] = + pay("alice", "bob", XRP(TestData::fund / 2)); + Json::Value jreply_xrp = wsc->invoke("sign", jrequestXrp); + + if (!BEAST_EXPECT(jreply_xrp.isMember(jss::result))) + return; + if (!BEAST_EXPECT(jreply_xrp[jss::result].isMember(jss::tx_blob))) + return; + testData.xrpTxBlob = + toBinary(jreply_xrp[jss::result][jss::tx_blob].asString()); + if (!BEAST_EXPECT(jreply_xrp[jss::result].isMember(jss::tx_json))) + return; + if (!BEAST_EXPECT( + jreply_xrp[jss::result][jss::tx_json].isMember(jss::hash))) + return; + testData.xrpTxHash = toBinary( + jreply_xrp[jss::result][jss::tx_json][jss::hash].asString()); + } + { + Json::Value jrequestUsd; + jrequestUsd[jss::secret] = toBase58(generateSeed("bob")); + jrequestUsd[jss::tx_json] = + pay("bob", "alice", bob["USD"](TestData::fund / 2)); + Json::Value jreply_usd = wsc->invoke("sign", jrequestUsd); + + if (!BEAST_EXPECT(jreply_usd.isMember(jss::result))) + return; + if (!BEAST_EXPECT(jreply_usd[jss::result].isMember(jss::tx_blob))) + return; + testData.usdTxBlob = + toBinary(jreply_usd[jss::result][jss::tx_blob].asString()); + if (!BEAST_EXPECT(jreply_usd[jss::result].isMember(jss::tx_json))) + return; + if (!BEAST_EXPECT( + jreply_usd[jss::result][jss::tx_json].isMember(jss::hash))) + return; + testData.usdTxHash = toBinary( + jreply_usd[jss::result][jss::tx_json][jss::hash].asString()); + } + } + + void + testSubmitGoodBlobGrpc() + { + testcase("Submit good blobs, XRP, USD, and same transaction twice"); + + using namespace jtx; + std::unique_ptr config = envconfig(addGrpcConfig); + std::string grpcPort = *(*config)["port_grpc"].get("port"); + Env env(*this, std::move(config)); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(TestData::fund), "alice", "bob"); + env.trust(bob["USD"](TestData::fund), alice); + env.close(); + + auto getClient = [&grpcPort]() { return SubmitClient(grpcPort); }; + + // XRP + { + auto client = getClient(); + client.request.set_signed_transaction(testData.xrpTxBlob); + client.SubmitTransaction(); + if (!BEAST_EXPECT(client.status.ok())) + { + return; + } + BEAST_EXPECT(client.reply.engine_result().result() == "tesSUCCESS"); + BEAST_EXPECT(client.reply.engine_result_code() == 0); + BEAST_EXPECT(client.reply.hash() == testData.xrpTxHash); + } + // USD + { + auto client = getClient(); + client.request.set_signed_transaction(testData.usdTxBlob); + client.SubmitTransaction(); + if (!BEAST_EXPECT(client.status.ok())) + { + return; + } + BEAST_EXPECT(client.reply.engine_result().result() == "tesSUCCESS"); + BEAST_EXPECT(client.reply.engine_result_code() == 0); + BEAST_EXPECT(client.reply.hash() == testData.usdTxHash); + } + // USD, error, same transaction again + { + auto client = getClient(); + client.request.set_signed_transaction(testData.usdTxBlob); + client.SubmitTransaction(); + if (!BEAST_EXPECT(client.status.ok())) + { + return; + } + BEAST_EXPECT(client.reply.engine_result().result() == "tefALREADY"); + BEAST_EXPECT(client.reply.engine_result_code() == -198); + } + } + + void + testSubmitErrorBlobGrpc() + { + testcase("Submit error, bad blob, no account"); + + using namespace jtx; + std::unique_ptr config = envconfig(addGrpcConfig); + std::string grpcPort = *(*config)["port_grpc"].get("port"); + Env env(*this, std::move(config)); + + auto getClient = [&grpcPort]() { return SubmitClient(grpcPort); }; + + // short transaction blob, cannot parse + { + auto client = getClient(); + client.request.set_signed_transaction("deadbeef"); + client.SubmitTransaction(); + BEAST_EXPECT(!client.status.ok()); + } + // bad blob with correct length, cannot parse + { + auto client = getClient(); + auto xrpTxBlobCopy(testData.xrpTxBlob); + std::reverse(xrpTxBlobCopy.begin(), xrpTxBlobCopy.end()); + client.request.set_signed_transaction(xrpTxBlobCopy); + client.SubmitTransaction(); + BEAST_EXPECT(!client.status.ok()); + } + // good blob, can parse but no account + { + auto client = getClient(); + client.request.set_signed_transaction(testData.xrpTxBlob); + client.SubmitTransaction(); + if (!BEAST_EXPECT(client.status.ok())) + { + return; + } + BEAST_EXPECT( + client.reply.engine_result().result() == "terNO_ACCOUNT"); + BEAST_EXPECT(client.reply.engine_result_code() == -96); + } + } + + void + testSubmitInsufficientFundsGrpc() + { + testcase("Submit good blobs but insufficient funds"); + + using namespace jtx; + std::unique_ptr config = envconfig(addGrpcConfig); + std::string grpcPort = *(*config)["port_grpc"].get("port"); + Env env(*this, std::move(config)); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + // fund 1000 (TestData::fund/10) XRP, the transaction sends 5000 + // (TestData::fund/2) XRP, so insufficient funds + env.fund(XRP(TestData::fund / 10), "alice", "bob"); + env.trust(bob["USD"](TestData::fund), alice); + env.close(); + + { + SubmitClient client(grpcPort); + client.request.set_signed_transaction(testData.xrpTxBlob); + client.SubmitTransaction(); + if (!BEAST_EXPECT(client.status.ok())) + { + return; + } + BEAST_EXPECT( + client.reply.engine_result().result() == "tecUNFUNDED_PAYMENT"); + BEAST_EXPECT(client.reply.engine_result_code() == 104); + } + } + + void + run() override + { + fillTestData(); + testSubmitGoodBlobGrpc(); + testSubmitErrorBlobGrpc(); + testSubmitInsufficientFundsGrpc(); + } +}; + +BEAST_DEFINE_TESTSUITE(Submit, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/rpc/Tx_test.cpp b/src/test/rpc/Tx_test.cpp new file mode 100644 index 00000000000..fe668fda59c --- /dev/null +++ b/src/test/rpc/Tx_test.cpp @@ -0,0 +1,314 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace ripple { +namespace test { + +class Tx_test : public beast::unit_test::suite +{ + template + std::string + toByteString(T const& data) + { + const char* bytes = reinterpret_cast(data.data()); + return {bytes, data.size()}; + } + + void + cmpAmount(const rpc::v1::CurrencyAmount& proto_amount, STAmount amount) + { + if (amount.native()) + { + BEAST_EXPECT( + proto_amount.xrp_amount().drops() == amount.xrp().drops()); + } + else + { + rpc::v1::IssuedCurrencyAmount issuedCurrency = + proto_amount.issued_currency_amount(); + Issue const& issue = amount.issue(); + Currency currency = issue.currency; + BEAST_EXPECT( + issuedCurrency.currency().name() == to_string(currency)); + BEAST_EXPECT( + issuedCurrency.currency().code() == toByteString(currency)); + BEAST_EXPECT(issuedCurrency.value() == to_string(amount.iou())); + BEAST_EXPECT( + issuedCurrency.issuer().address() == toBase58(issue.account)); + } + } + + void + cmpTx(const rpc::v1::Transaction& proto, std::shared_ptr txnSt) + { + AccountID account = txnSt->getAccountID(sfAccount); + BEAST_EXPECT(proto.account().address() == toBase58(account)); + + STAmount amount = txnSt->getFieldAmount(sfAmount); + cmpAmount(proto.payment().amount(), amount); + + AccountID accountDest = txnSt->getAccountID(sfDestination); + BEAST_EXPECT( + proto.payment().destination().address() == toBase58(accountDest)); + + STAmount fee = txnSt->getFieldAmount(sfFee); + BEAST_EXPECT(proto.fee().drops() == fee.xrp().drops()); + + BEAST_EXPECT(proto.sequence() == txnSt->getFieldU32(sfSequence)); + + 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)); + + Blob blob = txnSt->getFieldVL(sfTxnSignature); + BEAST_EXPECT(proto.signature() == toByteString(blob)); + + if (txnSt->isFieldPresent(sfSendMax)) + { + STAmount const& send_max = txnSt->getFieldAmount(sfSendMax); + cmpAmount(proto.payment().send_max(), send_max); + } + + if (txnSt->isFieldPresent(sfAccountTxnID)) + { + auto field = txnSt->getFieldH256(sfAccountTxnID); + BEAST_EXPECT(proto.account_transaction_id() == toByteString(field)); + } + + // populate path data + STPathSet const& pathset = txnSt->getFieldPathSet(sfPaths); + 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++); + + int ind2 = 0; + for (auto it2 = path.begin(); it2 != path.end(); ++it2) + { + const rpc::v1::PathElement& protoElement = + protoPath.elements(ind2++); + STPathElement const& elt = *it2; + + if (elt.isOffer()) + { + if (elt.hasCurrency()) + { + Currency const& currency = elt.getCurrency(); + BEAST_EXPECT( + protoElement.currency().name() == + to_string(currency)); + } + if (elt.hasIssuer()) + { + AccountID const& issuer = elt.getIssuerID(); + BEAST_EXPECT( + protoElement.issuer().address() == + toBase58(issuer)); + } + } + else + { + AccountID const& path_account = elt.getAccountID(); + BEAST_EXPECT( + protoElement.account().address() == + toBase58(path_account)); + } + } + } + } + + void + cmpMeta(const 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; + + RPC::populateTransactionResultType(r, txMeta->getResultTER()); + + BEAST_EXPECT( + proto.transaction_result().result_type() == r.result_type()); + + if (txMeta->hasDeliveredAmount()) + { + cmpAmount(proto.delivered_amount(), txMeta->getDeliveredAmount()); + } + } + + // gRPC stuff + class GrpcTxClient : public GRPCTestClientBase + { + public: + rpc::v1::GetTxRequest request; + rpc::v1::GetTxResponse reply; + + explicit GrpcTxClient(std::string const& port) + : GRPCTestClientBase(port) + { + } + + void + Tx() + { + status = stub_->GetTx(&context, request, &reply); + } + }; + + void + testTxGrpc() + { + testcase("Test 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)); + + 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( + client.status.ok(), client.reply); + }; + + Account A1{"A1"}; + Account A2{"A2"}; + env.fund(XRP(10000), A1); + env.fund(XRP(10000), A2); + env.close(); + env.trust(A2["USD"](1000), A1); + env.close(); + std::vector> txns; + auto const startLegSeq = env.current()->info().seq; + for (int i = 0; i < 14; ++i) + { + if (i & 1) + env(pay(A2, A1, A2["USD"](100))); + else + env(pay(A2, A1, A2["XRP"](200))); + txns.emplace_back(env.tx()); + env.close(); + } + auto const endLegSeq = env.closed()->info().seq; + + // Find the existing transactions + auto& ledgerMaster = env.app().getLedgerMaster(); + int index = startLegSeq; + for (auto&& tx : txns) + { + auto id = tx->getTransactionID(); + auto ledger = ledgerMaster.getLedgerBySeq(index); + + for (bool b : {false, true}) + { + auto const result = grpcTx(id, b); + + BEAST_EXPECT(result.first == true); + BEAST_EXPECT(result.second.ledger_index() == index); + BEAST_EXPECT(result.second.validated() == true); + if (b) + { + Serializer s = tx->getSerializer(); + BEAST_EXPECT( + result.second.transaction_binary() == toByteString(s)); + } + else + { + cmpTx(result.second.transaction(), tx); + } + + if (ledger && !b) + { + auto rawMeta = ledger->txRead(id).second; + if (rawMeta) + { + auto txMeta = std::make_shared( + id, ledger->seq(), *rawMeta); + + cmpMeta(result.second.meta(), txMeta); + } + } + } + index++; + } + + // Find not existing transaction + auto const tx = env.jt(noop(A1), seq(env.seq(A1))).stx; + for (bool b : {false, true}) + { + auto const result = grpcTx(tx->getTransactionID(), b); + + BEAST_EXPECT(result.first == false); + } + + // Delete one transaction + const auto deletedLedger = (startLegSeq + endLegSeq) / 2; + { + // Remove one of the ledgers from the database directly + auto db = env.app().getTxnDB().checkoutDb(); + *db << "DELETE FROM Transactions WHERE LedgerSeq == " + << deletedLedger << ";"; + } + + for (bool b : {false, true}) + { + auto const result = grpcTx(tx->getTransactionID(), b); + + BEAST_EXPECT(result.first == false); + } + } + +public: + void + run() override + { + testTxGrpc(); + } +}; + +BEAST_DEFINE_TESTSUITE(Tx, app, ripple); +} // namespace test +} // namespace ripple diff --git a/src/test/unity/rpc_test_unity.cpp b/src/test/unity/rpc_test_unity.cpp index cb8a2833581..4aed870c8f0 100644 --- a/src/test/unity/rpc_test_unity.cpp +++ b/src/test/unity/rpc_test_unity.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -48,9 +49,11 @@ #include #include #include +#include #include #include #include #include +#include #include #include