mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-21 19:18:22 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5883d756db | ||
|
|
df8d8796f5 |
No files matched your search
@@ -16,6 +16,8 @@ set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(Boost_NO_BOOST_CMAKE ON)
|
||||
|
||||
# make GIT_COMMIT_HASH define available to all sources
|
||||
find_package(Git)
|
||||
if(Git_FOUND)
|
||||
@@ -83,6 +85,17 @@ find_package(LibArchive REQUIRED)
|
||||
find_package(SOCI REQUIRED)
|
||||
find_package(SQLite3 REQUIRED)
|
||||
|
||||
# https://conan.io/center/recipes/wasmedge?version=0.9.0
|
||||
option(wasmedge "Enable WASM" ON)
|
||||
if(wasmedge)
|
||||
find_package(wasmedge REQUIRED)
|
||||
set_target_properties(wasmedge::wasmedge PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS RIPPLE_WASMEDGE_AVAILABLE=1
|
||||
)
|
||||
target_link_libraries(ripple_libs INTERFACE wasmedge::wasmedge)
|
||||
# include(deps/WasmEdge)
|
||||
endif()
|
||||
|
||||
option(rocksdb "Enable RocksDB" ON)
|
||||
if(rocksdb)
|
||||
find_package(RocksDB REQUIRED)
|
||||
|
||||
@@ -215,6 +215,13 @@ if(xrpld)
|
||||
# these two seem to produce conflicts in beast teardown template methods
|
||||
src/test/rpc/ValidatorRPC_test.cpp
|
||||
src/test/ledger/Invariants_test.cpp
|
||||
# These depend on wasmedge.h, which has some duplicate declarations
|
||||
src/ripple/app/hook/impl/applyHook.cpp
|
||||
src/ripple/app/misc/NetworkOPs.cpp
|
||||
src/ripple/app/tx/impl/applySteps.cpp
|
||||
src/ripple/app/tx/impl/SetHook.cpp
|
||||
src/ripple/app/tx/impl/Transactor.cpp
|
||||
src/ripple/rpc/handlers/Fee1.cpp
|
||||
PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE)
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,52 @@
|
||||
#[===================================================================[
|
||||
NIH dep: wasmedge: web assembly runtime for hooks.
|
||||
#]===================================================================]
|
||||
|
||||
if (is_root_project) # WasmEdge not needed in the case of xrpl_core inclusion build
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(OLD_DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
|
||||
set(CMAKE_DEBUG_POSTFIX _d)
|
||||
endif ()
|
||||
add_library (wasmedge INTERFACE)
|
||||
ExternalProject_Add (wasmedge_src
|
||||
PREFIX ${nih_cache_path}
|
||||
GIT_REPOSITORY https://github.com/WasmEdge/WasmEdge.git
|
||||
GIT_TAG 0.9.0
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
||||
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
|
||||
$<$<BOOL:${CMAKE_VERBOSE_MAKEFILE}>:-DCMAKE_VERBOSE_MAKEFILE=ON>
|
||||
-DCMAKE_DEBUG_POSTFIX=_d
|
||||
$<$<NOT:$<BOOL:${is_multiconfig}>>:-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}>
|
||||
$<$<BOOL:${MSVC}>:
|
||||
"-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 $<CONFIG>
|
||||
$<$<VERSION_GREATER_EQUAL:${CMAKE_VERSION},3.12>:--parallel ${ep_procs}>
|
||||
TEST_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
BUILD_BYPRODUCTS
|
||||
<BINARY_DIR>/lib/api/libwasmedge_c_${CMAKE_DEBUG_POSTFIX}.so
|
||||
)
|
||||
ExternalProject_Get_Property (wasmedge_src BINARY_DIR)
|
||||
set (wasmedge_src_BINARY_DIR "${BINARY_DIR}")
|
||||
add_dependencies (wasmedge wasmedge_src)
|
||||
target_include_directories (ripple_libs SYSTEM INTERFACE "${wasmedge_src_BINARY_DIR}/include/api")
|
||||
target_link_libraries (wasmedge
|
||||
INTERFACE
|
||||
Boost::thread
|
||||
Boost::system)
|
||||
target_link_libraries (wasmedge INTERFACE
|
||||
"${wasmedge_src_BINARY_DIR}/lib/api/libwasmedge_c${CMAKE_DEBUG_POSTFIX}.so")
|
||||
target_link_libraries (ripple_libs INTERFACE wasmedge)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_DEBUG_POSTFIX ${OLD_DEBUG_POSTFIX})
|
||||
endif ()
|
||||
endif ()
|
||||
@@ -30,6 +30,7 @@ class Xrpl(ConanFile):
|
||||
'nudb/2.0.8',
|
||||
'openssl/1.1.1u',
|
||||
'soci/4.0.3',
|
||||
'wasmedge/0.9.0',
|
||||
'xxhash/0.8.2',
|
||||
'zlib/1.2.13',
|
||||
]
|
||||
|
||||
Vendored
+194
@@ -0,0 +1,194 @@
|
||||
sources:
|
||||
"0.13.5":
|
||||
Windows:
|
||||
"x86_64":
|
||||
Visual Studio:
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.13.5/WasmEdge-0.13.5-windows.zip"
|
||||
sha256: "db533289ba26ec557b5193593c9ed03db75be3bc7aa737e2caa5b56b8eef888a"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.13.5/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Linux:
|
||||
"x86_64":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.13.5/WasmEdge-0.13.5-manylinux2014_x86_64.tar.gz"
|
||||
sha256: "3686e0226871bf17b62ec57e1c15778c2947834b90af0dfad14f2e0202bf9284"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.13.5/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.13.5/WasmEdge-0.13.5-manylinux2014_aarch64.tar.gz"
|
||||
sha256: "472de88e0257c539c120b33fdd1805e1e95063121acc2df1d5626e4676b93529"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Macos:
|
||||
"x86_64":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.13.5/WasmEdge-0.13.5-darwin_x86_64.tar.gz"
|
||||
sha256: "b7fdfaf59805951241f47690917b501ddfa06d9b6f7e0262e44e784efe4a7b33"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.13.5/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.13.5/WasmEdge-0.13.5-darwin_arm64.tar.gz"
|
||||
sha256: "acc93721210294ced0887352f360e42e46dcc05332e6dd78c1452fb3a35d5255"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.13.5/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Android:
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.13.5/WasmEdge-0.13.5-android_aarch64.tar.gz"
|
||||
sha256: "59a0d68a0c7368b51cc65cb5a44a68037d79fd449883ef42792178d57c8784a8"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.13.5/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"0.11.2":
|
||||
Windows:
|
||||
"x86_64":
|
||||
Visual Studio:
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-0.11.2-windows.zip"
|
||||
sha256: "ca49b98c0cf5f187e08c3ba71afc8d71365fde696f10b4219379a4a4d1a91e6d"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.2/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Linux:
|
||||
"x86_64":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-0.11.2-manylinux2014_x86_64.tar.gz"
|
||||
sha256: "784bf1eb25928e2cf02aa88e9372388fad682b4a188485da3cd9162caeedf143"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.2/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-0.11.2-manylinux2014_aarch64.tar.gz"
|
||||
sha256: "a2766a4c1edbaea298a30e5431a4e795003a10d8398a933d923f23d4eb4fa5d1"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Macos:
|
||||
"x86_64":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-0.11.2-darwin_x86_64.tar.gz"
|
||||
sha256: "aedec53f29b1e0b657e46e67dba3e2f32a2924f4d9136e60073ea1aba3073e70"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.2/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-0.11.2-darwin_arm64.tar.gz"
|
||||
sha256: "fe391df90e1eee69cf1e976f5ddf60c20f29b651710aaa4fc03e2ab4fe52c0d3"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.2/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Android:
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.2/WasmEdge-0.11.2-android_aarch64.tar.gz"
|
||||
sha256: "69e308f5927c753b2bb5639569d10219b60598174d8b304bdf310093fd7b2464"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.2/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"0.11.1":
|
||||
Windows:
|
||||
"x86_64":
|
||||
Visual Studio:
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.1/WasmEdge-0.11.1-windows.zip"
|
||||
sha256: "c86f6384555a0484a5dd81faba5636bba78f5e3d6eaf627d880e34843f9e24bf"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Linux:
|
||||
"x86_64":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.1/WasmEdge-0.11.1-manylinux2014_x86_64.tar.gz"
|
||||
sha256: "76ce4ea0eb86adfa52c73f6c6b44383626d94990e0923cae8b1e6f060ef2bf5b"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.1/WasmEdge-0.11.1-manylinux2014_aarch64.tar.gz"
|
||||
sha256: "cb9ea32932360463991cfda80e09879b2cf6c69737f12f3f2b371cd0af4e9ce8"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Macos:
|
||||
"x86_64":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.1/WasmEdge-0.11.1-darwin_x86_64.tar.gz"
|
||||
sha256: "56df2b00669c25b8143ea2c17370256cd6a33f3b316d3b47857dd38d603cb69a"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.1/WasmEdge-0.11.1-darwin_arm64.tar.gz"
|
||||
sha256: "82f7da1a7a36ec1923fb045193784dd090a03109e84da042af97297205a71f08"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Android:
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.11.1/WasmEdge-0.11.1-android_aarch64.tar.gz"
|
||||
sha256: "af8694e93bf72ac5506450d4caebccc340fbba254dca3d58ec0712e96ec9dedd"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.11.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"0.10.0":
|
||||
Windows:
|
||||
"x86_64":
|
||||
Visual Studio:
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.10.0/WasmEdge-0.10.0-windows.zip"
|
||||
sha256: "63b8a02cced52a723aa283dba02bbe887656256ecca69bb0fff17872c0fb5ebc"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.10.0/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Linux:
|
||||
"x86_64":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.10.0/WasmEdge-0.10.0-manylinux2014_x86_64.tar.gz"
|
||||
sha256: "4c1ffca9fd8cbdeb8f0951ddaffbbefe81ae123d5b80f61e80ea8d9b56853cde"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.10.0/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.10.0/WasmEdge-0.10.0-manylinux2014_aarch64.tar.gz"
|
||||
sha256: "c000bf96d0a73a1d360659246c0806c2ce78620b6f78c1147fbf9e2be0280bd9"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.10.0/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"0.9.1":
|
||||
Windows:
|
||||
"x86_64":
|
||||
Visual Studio:
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.9.1/WasmEdge-0.9.1-windows.zip"
|
||||
sha256: "68240d8aee23d44db5cc252d8c1cf5d0c77ab709a122af2747a4b836ba461671"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.9.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Linux:
|
||||
"x86_64":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.9.1/WasmEdge-0.9.1-manylinux2014_x86_64.tar.gz"
|
||||
sha256: "bcb6fe3d6e30db0d0aa267ec3bd9b7248f8c8c387620cef4049d682d293c8371"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.9.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.9.1/WasmEdge-0.9.1-manylinux2014_aarch64.tar.gz"
|
||||
sha256: "515bcac3520cd546d9d14372b7930ab48b43f1c5dc258a9f61a82b22c0107eef"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.9.1/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"0.9.0":
|
||||
Windows:
|
||||
"x86_64":
|
||||
Visual Studio:
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.9.0/WasmEdge-0.9.0-windows.zip"
|
||||
sha256: "f81bfea4cf09053510e3e74c16c1ee010fc93def8a7e78744443b950f0011c3b"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.9.0/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Linux:
|
||||
"x86_64":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.9.0/WasmEdge-0.9.0-manylinux2014_x86_64.tar.gz"
|
||||
sha256: "27847f15e4294e707486458e857d7cb11806481bb67a26f076a717a1446827ed"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.9.0/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.9.0/WasmEdge-0.9.0-manylinux2014_aarch64.tar.gz"
|
||||
sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.9.0/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Macos:
|
||||
"armv8":
|
||||
"gcc":
|
||||
- url: "https://github.com/WasmEdge/WasmEdge/releases/download/0.9.0/WasmEdge-0.9.0-darwin_arm64.tar.gz"
|
||||
sha256: "236a407a646f746ab78a1d0a39fa4e85fe28eae219b1635ba49f908d7944686d"
|
||||
- url: "https://raw.githubusercontent.com/WasmEdge/WasmEdge/0.9.0/LICENSE"
|
||||
sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
from conan import ConanFile
|
||||
from conan.tools.files import get, copy, download
|
||||
from conan.tools.scm import Version
|
||||
from conan.errors import ConanInvalidConfiguration
|
||||
|
||||
import os
|
||||
|
||||
required_conan_version = ">=1.53.0"
|
||||
|
||||
class WasmedgeConan(ConanFile):
|
||||
name = "wasmedge"
|
||||
description = ("WasmEdge is a lightweight, high-performance, and extensible WebAssembly runtime"
|
||||
"for cloud native, edge, and decentralized applications."
|
||||
"It powers serverless apps, embedded functions, microservices, smart contracts, and IoT devices.")
|
||||
license = "Apache-2.0"
|
||||
url = "https://github.com/conan-io/conan-center-index"
|
||||
homepage = "https://github.com/WasmEdge/WasmEdge/"
|
||||
topics = ("webassembly", "wasm", "wasi", "emscripten")
|
||||
package_type = "shared-library"
|
||||
settings = "os", "arch", "compiler", "build_type"
|
||||
|
||||
@property
|
||||
def _compiler_alias(self):
|
||||
return {
|
||||
"Visual Studio": "Visual Studio",
|
||||
# "Visual Studio": "msvc",
|
||||
"msvc": "msvc",
|
||||
}.get(str(self.info.settings.compiler), "gcc")
|
||||
|
||||
def configure(self):
|
||||
self.settings.compiler.rm_safe("libcxx")
|
||||
self.settings.compiler.rm_safe("cppstd")
|
||||
|
||||
def validate(self):
|
||||
try:
|
||||
self.conan_data["sources"][self.version][str(self.settings.os)][str(self.settings.arch)][self._compiler_alias]
|
||||
except KeyError:
|
||||
raise ConanInvalidConfiguration("Binaries for this combination of version/os/arch/compiler are not available")
|
||||
|
||||
def package_id(self):
|
||||
del self.info.settings.compiler.version
|
||||
self.info.settings.compiler = self._compiler_alias
|
||||
|
||||
def build(self):
|
||||
# This is packaging binaries so the download needs to be in build
|
||||
get(self, **self.conan_data["sources"][self.version][str(self.settings.os)][str(self.settings.arch)][self._compiler_alias][0],
|
||||
destination=self.source_folder, strip_root=True)
|
||||
download(self, filename="LICENSE",
|
||||
**self.conan_data["sources"][self.version][str(self.settings.os)][str(self.settings.arch)][self._compiler_alias][1])
|
||||
|
||||
def package(self):
|
||||
copy(self, pattern="*.h", dst=os.path.join(self.package_folder, "include"), src=os.path.join(self.source_folder, "include"), keep_path=True)
|
||||
copy(self, pattern="*.inc", dst=os.path.join(self.package_folder, "include"), src=os.path.join(self.source_folder, "include"), keep_path=True)
|
||||
|
||||
srclibdir = os.path.join(self.source_folder, "lib64" if self.settings.os == "Linux" else "lib")
|
||||
srcbindir = os.path.join(self.source_folder, "bin")
|
||||
dstlibdir = os.path.join(self.package_folder, "lib")
|
||||
dstbindir = os.path.join(self.package_folder, "bin")
|
||||
if Version(self.version) >= "0.11.1":
|
||||
copy(self, pattern="wasmedge.lib", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
copy(self, pattern="wasmedge.dll", src=srcbindir, dst=dstbindir, keep_path=False)
|
||||
copy(self, pattern="libwasmedge.so*", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
copy(self, pattern="libwasmedge*.dylib", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
else:
|
||||
copy(self, pattern="wasmedge_c.lib", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
copy(self, pattern="wasmedge_c.dll", src=srcbindir, dst=dstbindir, keep_path=False)
|
||||
copy(self, pattern="libwasmedge_c.so*", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
copy(self, pattern="libwasmedge_c*.dylib", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
|
||||
copy(self, pattern="wasmedge*", src=srcbindir, dst=dstbindir, keep_path=False)
|
||||
copy(self, pattern="LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"), keep_path=False)
|
||||
|
||||
def package_info(self):
|
||||
if Version(self.version) >= "0.11.1":
|
||||
self.cpp_info.libs = ["wasmedge"]
|
||||
else:
|
||||
self.cpp_info.libs = ["wasmedge_c"]
|
||||
|
||||
bindir = os.path.join(self.package_folder, "bin")
|
||||
self.output.info("Appending PATH environment variable: {}".format(bindir))
|
||||
self.env_info.PATH.append(bindir)
|
||||
|
||||
if self.settings.os == "Windows":
|
||||
self.cpp_info.system_libs.append("ws2_32")
|
||||
self.cpp_info.system_libs.append("wsock32")
|
||||
self.cpp_info.system_libs.append("shlwapi")
|
||||
|
||||
if self.settings.os in ["Linux", "FreeBSD"]:
|
||||
self.cpp_info.system_libs.append("m")
|
||||
self.cpp_info.system_libs.append("dl")
|
||||
self.cpp_info.system_libs.append("rt")
|
||||
self.cpp_info.system_libs.append("pthread")
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace beast {
|
||||
@@ -454,6 +455,16 @@ hash_append(Hasher& h, std::tuple<T...> const& t) noexcept
|
||||
detail::tuple_hash(h, t, std::index_sequence_for<T...>{});
|
||||
}
|
||||
|
||||
// variant
|
||||
|
||||
template <class Hasher, class... T>
|
||||
inline std::enable_if_t<
|
||||
!is_contiguously_hashable<std::variant<T...>, Hasher>::value>
|
||||
hash_append(Hasher& h, std::variant<T...> const& v) noexcept
|
||||
{
|
||||
std::visit([&](auto const& arg) { hash_append(h, arg); }, v);
|
||||
}
|
||||
|
||||
// shared_ptr
|
||||
|
||||
template <class Hasher, class T>
|
||||
|
||||
@@ -68,7 +68,8 @@ enum error_code_i {
|
||||
|
||||
// Ledger state
|
||||
rpcACT_NOT_FOUND = 19,
|
||||
// unused 20,
|
||||
rpcNAMESPACE_NOT_FOUND = 20,
|
||||
|
||||
rpcLGR_NOT_FOUND = 21,
|
||||
rpcLGR_NOT_VALIDATED = 22,
|
||||
rpcMASTER_DISABLED = 23,
|
||||
@@ -90,7 +91,7 @@ enum error_code_i {
|
||||
rpcACT_MALFORMED = 35,
|
||||
rpcALREADY_MULTISIG = 36,
|
||||
rpcALREADY_SINGLE_SIG = 37,
|
||||
// unused 38,
|
||||
rpcNAMESPACE_MALFORMED = 38,
|
||||
// unused 39,
|
||||
rpcBAD_FEATURE = 40,
|
||||
rpcBAD_ISSUER = 41,
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 83;
|
||||
static constexpr std::size_t numFeatures = 85;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
|
||||
@@ -85,6 +85,15 @@ enum class HashPrefix : std::uint32_t {
|
||||
/** Payment Channel Claim */
|
||||
paymentChannelClaim = detail::make_hash_prefix('C', 'L', 'M'),
|
||||
|
||||
/** Emit Transaction Nonce */
|
||||
emitTxnNonce = detail::make_hash_prefix('E', 'T', 'X'),
|
||||
|
||||
/** Random entropy for hook developers to use */
|
||||
hookNonce = detail::make_hash_prefix('N', 'C', 'E'),
|
||||
|
||||
/* Hash of a Hook's actual code */
|
||||
hookDefinition = detail::make_hash_prefix('W', 'S', 'M'),
|
||||
|
||||
/** Credentials signature */
|
||||
credential = detail::make_hash_prefix('C', 'R', 'D'),
|
||||
};
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
using UInt32or256 = std::variant<uint32_t, uint256>;
|
||||
|
||||
class SeqProxy;
|
||||
/** Keylet computation funclets.
|
||||
|
||||
@@ -52,6 +54,26 @@ class SeqProxy;
|
||||
*/
|
||||
namespace keylet {
|
||||
|
||||
/** The (fixed) index of the object containing the emitted txns for the ledger.
|
||||
*/
|
||||
Keylet const&
|
||||
emittedDir() noexcept;
|
||||
|
||||
Keylet
|
||||
emitted(uint256 const& id) noexcept;
|
||||
|
||||
Keylet
|
||||
hookDefinition(uint256 const& hash) noexcept;
|
||||
|
||||
Keylet
|
||||
hook(AccountID const& id) noexcept;
|
||||
|
||||
Keylet
|
||||
hookState(AccountID const& id, uint256 const& key, uint256 const& ns) noexcept;
|
||||
|
||||
Keylet
|
||||
hookStateDir(AccountID const& id, uint256 const& ns) noexcept;
|
||||
|
||||
/** AccountID root */
|
||||
Keylet
|
||||
account(AccountID const& id) noexcept;
|
||||
@@ -127,7 +149,7 @@ line(AccountID const& id, Issue const& issue) noexcept
|
||||
/** An offer from an account */
|
||||
/** @{ */
|
||||
Keylet
|
||||
offer(AccountID const& id, std::uint32_t seq) noexcept;
|
||||
offer(AccountID const& id, UInt32or256 const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
offer(uint256 const& key) noexcept
|
||||
@@ -176,7 +198,7 @@ signers(AccountID const& account) noexcept;
|
||||
/** A Check */
|
||||
/** @{ */
|
||||
Keylet
|
||||
check(AccountID const& id, std::uint32_t seq) noexcept;
|
||||
check(AccountID const& id, UInt32or256 const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
check(uint256 const& key) noexcept
|
||||
@@ -228,11 +250,14 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
|
||||
|
||||
/** An escrow entry */
|
||||
Keylet
|
||||
escrow(AccountID const& src, std::uint32_t seq) noexcept;
|
||||
escrow(AccountID const& src, UInt32or256 const& seq) noexcept;
|
||||
|
||||
/** A PaymentChannel */
|
||||
Keylet
|
||||
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
|
||||
payChan(
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
UInt32or256 const& seq) noexcept;
|
||||
|
||||
/** NFT page keylets
|
||||
|
||||
@@ -256,7 +281,7 @@ nftpage(Keylet const& k, uint256 const& token);
|
||||
|
||||
/** An offer from an account to buy or sell an NFT */
|
||||
Keylet
|
||||
nftoffer(AccountID const& owner, std::uint32_t seq);
|
||||
nftoffer(AccountID const& owner, UInt32or256 const& seq);
|
||||
|
||||
inline Keylet
|
||||
nftoffer(uint256 const& offer)
|
||||
|
||||
@@ -107,8 +107,8 @@ enum LedgerEntryType : std::uint16_t
|
||||
*/
|
||||
ltCONTRACT [[deprecated("This object type is not supported and should not be used.")]] = 0x0063,
|
||||
|
||||
/** A legacy, deprecated type.
|
||||
|
||||
/** A legacy, deprecated type.
|
||||
\deprecated **This object type is not supported and should not be used.**
|
||||
Support for this type of object was never implemented.
|
||||
No objects of this type were ever created.
|
||||
@@ -134,9 +134,7 @@ enum LedgerSpecificFlags {
|
||||
lsfDefaultRipple =
|
||||
0x00800000, // True, trust lines allow rippling by default
|
||||
lsfDepositAuth = 0x01000000, // True, all deposits require authorization
|
||||
/* // reserved for Hooks amendment
|
||||
lsfTshCollect = 0x02000000, // True, allow TSH collect-calls to acc hooks
|
||||
*/
|
||||
lsfDisallowIncomingNFTokenOffer =
|
||||
0x04000000, // True, reject new incoming NFT offers
|
||||
lsfDisallowIncomingCheck =
|
||||
@@ -171,6 +169,7 @@ enum LedgerSpecificFlags {
|
||||
// ltDIR_NODE
|
||||
lsfNFTokenBuyOffers = 0x00000001,
|
||||
lsfNFTokenSellOffers = 0x00000002,
|
||||
lsfEmittedDir = 0x00000004,
|
||||
|
||||
// ltNFTOKEN_OFFER
|
||||
lsfSellNFToken = 0x00000001,
|
||||
|
||||
@@ -38,6 +38,32 @@ serializePayChanAuthorization(
|
||||
msg.add64(amt.drops());
|
||||
}
|
||||
|
||||
inline void
|
||||
serializePayChanAuthorization(
|
||||
Serializer& msg,
|
||||
uint256 const& key,
|
||||
IOUAmount const& amt,
|
||||
Currency const& cur,
|
||||
AccountID const& iss)
|
||||
{
|
||||
msg.add32(HashPrefix::paymentChannelClaim);
|
||||
msg.addBitString(key);
|
||||
if (amt == beast::zero)
|
||||
msg.add64(STAmount::cIssuedCurrency);
|
||||
else if (amt.signum() == -1) // 512 = not native
|
||||
msg.add64(
|
||||
amt.mantissa() |
|
||||
(static_cast<std::uint64_t>(amt.exponent() + 512 + 97)
|
||||
<< (64 - 10)));
|
||||
else // 256 = positive
|
||||
msg.add64(
|
||||
amt.mantissa() |
|
||||
(static_cast<std::uint64_t>(amt.exponent() + 512 + 256 + 97)
|
||||
<< (64 - 10)));
|
||||
msg.addBitString(cur);
|
||||
msg.addBitString(iss);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -72,6 +72,7 @@ private:
|
||||
DigestAwareReadView const& ledger,
|
||||
std::unordered_set<uint256, beast::uhash<>> const& presets);
|
||||
|
||||
public:
|
||||
Rules(
|
||||
std::unordered_set<uint256, beast::uhash<>> const& presets,
|
||||
std::optional<uint256> const& digest,
|
||||
|
||||
@@ -684,6 +684,57 @@ isXRP(STAmount const& amount)
|
||||
return amount.native();
|
||||
}
|
||||
|
||||
inline bool
|
||||
isFakeXRP(STAmount const& amount)
|
||||
{
|
||||
if (amount.native())
|
||||
return false;
|
||||
|
||||
return isFakeXRP(amount.issue().currency);
|
||||
}
|
||||
|
||||
/** returns true iff adding or subtracting results in less than or equal to
|
||||
* 0.01% precision loss **/
|
||||
inline bool
|
||||
isAddable(STAmount const& amt1, STAmount const& amt2)
|
||||
{
|
||||
// special case: adding anything to zero is always fine
|
||||
if (amt1 == beast::zero || amt2 == beast::zero)
|
||||
return true;
|
||||
|
||||
// special case: adding two xrp amounts together.
|
||||
// this is just an overflow check
|
||||
if (isXRP(amt1) && isXRP(amt2))
|
||||
{
|
||||
XRPAmount A = (amt1.signum() == -1 ? -(amt1.xrp()) : amt1.xrp());
|
||||
XRPAmount B = (amt2.signum() == -1 ? -(amt2.xrp()) : amt2.xrp());
|
||||
|
||||
XRPAmount finalAmt = A + B;
|
||||
return (finalAmt >= A && finalAmt >= B);
|
||||
}
|
||||
|
||||
static const STAmount one{IOUAmount{1, 0}, noIssue()};
|
||||
static const STAmount maxLoss{IOUAmount{1, -4}, noIssue()};
|
||||
|
||||
STAmount A = amt1;
|
||||
STAmount B = amt2;
|
||||
|
||||
if (isXRP(A))
|
||||
A = STAmount{IOUAmount{A.xrp().drops(), -6}, noIssue()};
|
||||
|
||||
if (isXRP(B))
|
||||
B = STAmount{IOUAmount{B.xrp().drops(), -6}, noIssue()};
|
||||
|
||||
A.setIssue(noIssue());
|
||||
B.setIssue(noIssue());
|
||||
|
||||
STAmount lhs = divide((A - B) + B, A, noIssue()) - one;
|
||||
STAmount rhs = divide((B - A) + A, B, noIssue()) - one;
|
||||
|
||||
return ((rhs.negative() ? -rhs : rhs) + (lhs.negative() ? -lhs : lhs)) <=
|
||||
maxLoss;
|
||||
}
|
||||
|
||||
// Since `canonicalize` does not have access to a ledger, this is needed to put
|
||||
// the low-level routine stAmountCanonicalize on an amendment switch. Only
|
||||
// transactions need to use this switchover. Outside of a transaction it's safe
|
||||
|
||||
@@ -38,6 +38,9 @@ public:
|
||||
using iterator = list_type::iterator;
|
||||
using const_iterator = list_type::const_iterator;
|
||||
|
||||
// RH NOTE: please don't delete this constructor
|
||||
STArray(std::vector<STObject> const& v, SField const& f);
|
||||
|
||||
STArray() = default;
|
||||
STArray(STArray const&) = default;
|
||||
|
||||
|
||||
@@ -119,6 +119,8 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temINVALID_ACCOUNT_ID,
|
||||
temCANNOT_PREAUTH_SELF,
|
||||
temINVALID_COUNT,
|
||||
temHOOK_DATA_TOO_LARGE,
|
||||
temHOOK_REJECTED,
|
||||
|
||||
temUNCERTAIN, // An internal intermediate result; should never be returned.
|
||||
temUNKNOWN, // An internal intermediate result; should never be returned.
|
||||
@@ -225,6 +227,8 @@ enum TERcodes : TERUnderlyingType {
|
||||
terQUEUED, // Transaction is being held in TxQ until fee drops
|
||||
terPRE_TICKET, // Ticket is not yet in ledger but might be on its way
|
||||
terNO_AMM, // AMM doesn't exist for the asset pair
|
||||
terNO_HOOK // Transaction requires a non-existent hook definition
|
||||
// (referenced by sfHookHash)
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -344,6 +348,8 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecARRAY_TOO_LARGE = 191,
|
||||
tecLOCKED = 192,
|
||||
tecBAD_CREDENTIALS = 193,
|
||||
tecREQUIRES_FLAG = 194,
|
||||
tecPRECISION_LOSS = 195,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -83,9 +83,7 @@ constexpr std::uint32_t asfGlobalFreeze = 7;
|
||||
constexpr std::uint32_t asfDefaultRipple = 8;
|
||||
constexpr std::uint32_t asfDepositAuth = 9;
|
||||
constexpr std::uint32_t asfAuthorizedNFTokenMinter = 10;
|
||||
/* // reserved for Hooks amendment
|
||||
constexpr std::uint32_t asfTshCollect = 11;
|
||||
*/
|
||||
constexpr std::uint32_t asfDisallowIncomingNFTokenOffer = 12;
|
||||
constexpr std::uint32_t asfDisallowIncomingCheck = 13;
|
||||
constexpr std::uint32_t asfDisallowIncomingPayChan = 14;
|
||||
|
||||
@@ -74,9 +74,6 @@ enum TxType : std::uint16_t
|
||||
|
||||
/** This identifier was never used, but the slot is reserved for historical purposes. */
|
||||
ttSPINAL_TAP [[deprecated("This transaction type is not supported and should not be used.")]] = 11,
|
||||
|
||||
/** This transaction type installs a hook. */
|
||||
ttHOOK_SET [[maybe_unused]] = 22,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -75,6 +75,9 @@ public:
|
||||
return mIndex;
|
||||
}
|
||||
|
||||
void
|
||||
setResult(TER res, int index);
|
||||
|
||||
void
|
||||
setAffectedNode(uint256 const&, SField const& type, std::uint16_t nodeType);
|
||||
STObject&
|
||||
@@ -113,6 +116,24 @@ public:
|
||||
mDelivered = delivered;
|
||||
}
|
||||
|
||||
STArray const&
|
||||
getHookExecutions() const
|
||||
{
|
||||
return *mHookExecutions;
|
||||
}
|
||||
|
||||
void
|
||||
setHookExecutions(STArray const& hookExecutions)
|
||||
{
|
||||
mHookExecutions = hookExecutions;
|
||||
}
|
||||
|
||||
bool
|
||||
hasHookExecutions() const
|
||||
{
|
||||
return static_cast<bool>(mHookExecutions);
|
||||
}
|
||||
|
||||
STAmount
|
||||
getDeliveredAmount() const
|
||||
{
|
||||
@@ -135,6 +156,7 @@ private:
|
||||
int mResult;
|
||||
|
||||
std::optional<STAmount> mDelivered;
|
||||
std::optional<STArray> mHookExecutions;
|
||||
|
||||
STArray mNodes;
|
||||
};
|
||||
|
||||
@@ -82,6 +82,12 @@ isXRP(Currency const& c)
|
||||
return c == beast::zero;
|
||||
}
|
||||
|
||||
inline bool
|
||||
isFakeXRP(Currency const& c)
|
||||
{
|
||||
return c == badCurrency();
|
||||
}
|
||||
|
||||
/** Returns "", "XRP", or three letter ISO code. */
|
||||
std::string
|
||||
to_string(Currency const& c);
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(Hooks, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PaychanAndEscrowForTokens, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Credentials, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(AMMClawback, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (AMMv1_2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -62,6 +62,32 @@ LEDGER_ENTRY(ltCHECK, 0x0043, Check, ({
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
LEDGER_ENTRY(ltHOOK_DEFINITION, 0x0044, HookDefinition, ({
|
||||
{sfHookHash, soeREQUIRED},
|
||||
{sfHookOn, soeREQUIRED},
|
||||
{sfHookNamespace, soeREQUIRED},
|
||||
{sfHookParameters, soeREQUIRED},
|
||||
{sfHookApiVersion, soeREQUIRED},
|
||||
{sfCreateCode, soeREQUIRED},
|
||||
{sfHookSetTxnID, soeREQUIRED},
|
||||
{sfReferenceCount, soeREQUIRED},
|
||||
{sfFee, soeREQUIRED},
|
||||
{sfHookCallbackFee, soeOPTIONAL}
|
||||
}))
|
||||
|
||||
LEDGER_ENTRY(ltEMITTED, 0x0045, Emitted, ({
|
||||
{sfEmittedTxn, soeOPTIONAL},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
}))
|
||||
|
||||
LEDGER_ENTRY(ltHOOK, 0x0048, Hook, ({
|
||||
{sfAccount, soeOPTIONAL},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfHooks, soeREQUIRED}
|
||||
}))
|
||||
|
||||
/** The ledger object which tracks the DID.
|
||||
|
||||
\sa keylet::did
|
||||
@@ -155,6 +181,8 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, ({
|
||||
{sfBurnedNFTokens, soeDEFAULT},
|
||||
{sfFirstNFTokenSequence, soeOPTIONAL},
|
||||
{sfAMMID, soeOPTIONAL},
|
||||
{sfHookStateCount, soeOPTIONAL},
|
||||
{sfHookNamespaces, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** A ledger object which contains a list of object identifiers.
|
||||
@@ -169,6 +197,7 @@ LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, ({
|
||||
{sfTakerGetsCurrency, soeOPTIONAL}, // order book directories
|
||||
{sfTakerGetsIssuer, soeOPTIONAL}, // order book directories
|
||||
{sfExchangeRate, soeOPTIONAL}, // order book directories
|
||||
{sfReferenceCount, soeOPTIONAL}, // for hook state directories
|
||||
{sfIndexes, soeREQUIRED},
|
||||
{sfRootIndex, soeREQUIRED},
|
||||
{sfIndexNext, soeOPTIONAL},
|
||||
@@ -286,6 +315,8 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, ({
|
||||
{sfHighNode, soeOPTIONAL},
|
||||
{sfHighQualityIn, soeOPTIONAL},
|
||||
{sfHighQualityOut, soeOPTIONAL},
|
||||
{sfLockedBalance, soeOPTIONAL},
|
||||
{sfLockCount, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** The ledger object which lists the network's fee settings.
|
||||
@@ -341,6 +372,12 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, ({
|
||||
{sfDestinationNode, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
LEDGER_ENTRY(ltHOOK_STATE, 0x0076, HookState, ({
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfHookStateKey, soeREQUIRED},
|
||||
{sfHookStateData, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object describing a single unidirectional XRP payment channel.
|
||||
|
||||
\sa keylet::payChan
|
||||
|
||||
@@ -108,7 +108,7 @@ TYPED_SFIELD(sfMintedNFTokens, UINT32, 43)
|
||||
TYPED_SFIELD(sfBurnedNFTokens, UINT32, 44)
|
||||
TYPED_SFIELD(sfHookStateCount, UINT32, 45)
|
||||
TYPED_SFIELD(sfEmitGeneration, UINT32, 46)
|
||||
// 47 reserved for Hooks
|
||||
TYPED_SFIELD(sfLockCount, UINT32, 47)
|
||||
TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
@@ -190,6 +190,9 @@ TYPED_SFIELD(sfHookStateKey, UINT256, 30)
|
||||
TYPED_SFIELD(sfHookHash, UINT256, 31)
|
||||
TYPED_SFIELD(sfHookNamespace, UINT256, 32)
|
||||
TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
|
||||
TYPED_SFIELD(sfOfferID, UINT256, 34)
|
||||
TYPED_SFIELD(sfEscrowID, UINT256, 35)
|
||||
|
||||
|
||||
// number (common)
|
||||
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
||||
@@ -214,8 +217,8 @@ TYPED_SFIELD(sfMinimumOffer, AMOUNT, 16)
|
||||
TYPED_SFIELD(sfRippleEscrow, AMOUNT, 17)
|
||||
TYPED_SFIELD(sfDeliveredAmount, AMOUNT, 18)
|
||||
TYPED_SFIELD(sfNFTokenBrokerFee, AMOUNT, 19)
|
||||
|
||||
// Reserve 20 & 21 for Hooks.
|
||||
TYPED_SFIELD(sfHookCallbackFee, AMOUNT, 20)
|
||||
TYPED_SFIELD(sfLockedBalance, AMOUNT, 21)
|
||||
|
||||
// currency amount (fees)
|
||||
TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
|
||||
@@ -294,6 +297,7 @@ TYPED_SFIELD(sfHashes, VECTOR256, 2)
|
||||
TYPED_SFIELD(sfAmendments, VECTOR256, 3)
|
||||
TYPED_SFIELD(sfNFTokenOffers, VECTOR256, 4)
|
||||
TYPED_SFIELD(sfCredentialIDs, VECTOR256, 5)
|
||||
TYPED_SFIELD(sfHookNamespaces, VECTOR256, 6)
|
||||
|
||||
// path set
|
||||
UNTYPED_SFIELD(sfPaths, PATHSET, 1)
|
||||
|
||||
@@ -54,6 +54,7 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
|
||||
TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
{sfEscrowID, soeOPTIONAL}, // keylet as alternative to offerseq
|
||||
{sfFulfillment, soeOPTIONAL},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
@@ -78,6 +79,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
|
||||
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
{sfEscrowID, soeOPTIONAL}, // keylet as alternative to offerseq
|
||||
}))
|
||||
|
||||
/** This transaction type sets or clears an account's "regular key". */
|
||||
@@ -93,11 +95,13 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
|
||||
{sfTakerGets, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
{sfOfferSequence, soeOPTIONAL},
|
||||
{sfOfferID, soeOPTIONAL}, // keylet as alternative to offerseq
|
||||
}))
|
||||
|
||||
/** This transaction type cancels existing offers to trade one asset for another. */
|
||||
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
{sfOfferID, soeOPTIONAL}, // keylet as alternative to offerseq
|
||||
}))
|
||||
|
||||
// 9 deprecated
|
||||
@@ -187,7 +191,10 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
// 22 reserved
|
||||
/** This transaction type installs a hook. */
|
||||
TRANSACTION(ttHOOK_SET, 22, SetHook, ({
|
||||
{sfHooks, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction mints a new NFT. */
|
||||
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
|
||||
@@ -484,3 +491,7 @@ TRANSACTION(ttUNL_MODIFY, 102, UNLModify, ({
|
||||
{sfUNLModifyValidator, soeREQUIRED},
|
||||
}))
|
||||
|
||||
TRANSACTION(ttEMIT_FAILURE, 103, EmitFailure, ({
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfTransactionHash, soeREQUIRED},
|
||||
}))
|
||||
@@ -69,12 +69,16 @@ JSS(DeliverMax); // out: alias to Amount
|
||||
JSS(DeliverMin); // in: TransactionSign
|
||||
JSS(Destination); // in: TransactionSign; field.
|
||||
JSS(DirectoryNode); // ledger type.
|
||||
JSS(Emitted); // ledger type.
|
||||
JSS(EPrice); // in: AMM Deposit option
|
||||
JSS(Escrow); // ledger type.
|
||||
JSS(Fee); // in/out: TransactionSign; field.
|
||||
JSS(FeeSettings); // ledger type.
|
||||
JSS(Flags); // in/out: TransactionSign; field.
|
||||
JSS(Holder); // field.
|
||||
JSS(Hook); // ledger type.
|
||||
JSS(HookState); // ledger type.
|
||||
JSS(HookDefinition);
|
||||
JSS(Invalid); //
|
||||
JSS(Issuer); // in: Credential transactions
|
||||
JSS(LastLedgerSequence); // in: TransactionSign; field
|
||||
@@ -303,6 +307,7 @@ JSS(features); // out: Feature
|
||||
JSS(fee); // out: NetworkOPs, Peers
|
||||
JSS(fee_base); // out: NetworkOPs
|
||||
JSS(fee_div_max); // in: TransactionSign
|
||||
JSS(fee_hooks_drops); // out: Fee rpc call
|
||||
JSS(fee_level); // out: AccountInfo
|
||||
JSS(fee_mult_max); // in: TransactionSign
|
||||
JSS(fee_ref); // out: NetworkOPs, DEPRECATED
|
||||
@@ -332,6 +337,7 @@ JSS(high); // out: BookChanges
|
||||
JSS(highest_sequence); // out: AccountInfo
|
||||
JSS(highest_ticket); // out: AccountInfo
|
||||
JSS(historical_perminute); // historical_perminute.
|
||||
JSS(hook_hash); // in: LedgerEntry
|
||||
JSS(holders); // out: MPTHolders
|
||||
JSS(hostid); // out: NetworkOPs
|
||||
JSS(hotwallet); // in: GatewayBalances
|
||||
@@ -456,6 +462,8 @@ JSS(minimum_fee); // out: TxQ
|
||||
JSS(minimum_level); // out: TxQ
|
||||
JSS(missingCommand); // error
|
||||
JSS(name); // out: AmendmentTableImpl, PeerImp
|
||||
JSS(namespace_entries); // out: AccountNamespace
|
||||
JSS(namespace_id); // in/out: AccountNamespace
|
||||
JSS(needed_state_hashes); // out: InboundLedger
|
||||
JSS(needed_transaction_hashes); // out: InboundLedger
|
||||
JSS(network_id); // out: NetworkOPs
|
||||
|
||||
@@ -34,7 +34,9 @@ namespace BuildInfo {
|
||||
//------------------------------------------------------------------------------
|
||||
// clang-format off
|
||||
char const* const versionString = "2.4.0-b1"
|
||||
// clang-format on
|
||||
// clang-format on
|
||||
|
||||
"-hooks"
|
||||
|
||||
#if defined(DEBUG) || defined(SANITIZER)
|
||||
"+"
|
||||
|
||||
@@ -61,11 +61,17 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
FEE_SETTINGS = 'e',
|
||||
TICKET = 'T',
|
||||
SIGNER_LIST = 'S',
|
||||
XRP_PAYMENT_CHANNEL = 'x',
|
||||
PAYMENT_CHANNEL = 'x',
|
||||
CHECK = 'C',
|
||||
DEPOSIT_PREAUTH = 'p',
|
||||
DEPOSIT_PREAUTH_CREDENTIALS = 'P',
|
||||
NEGATIVE_UNL = 'N',
|
||||
HOOK = 'H',
|
||||
HOOK_STATE_DIR = 'J',
|
||||
HOOK_STATE = 'v',
|
||||
HOOK_DEFINITION = 'D',
|
||||
EMITTED = 'E',
|
||||
EMITTED_DIR = 'F',
|
||||
NFTOKEN_OFFER = 'q',
|
||||
NFTOKEN_BUY_OFFERS = 'h',
|
||||
NFTOKEN_SELL_OFFERS = 'i',
|
||||
@@ -155,6 +161,45 @@ makeMptID(std::uint32_t sequence, AccountID const& account)
|
||||
|
||||
namespace keylet {
|
||||
|
||||
Keylet const&
|
||||
emittedDir() noexcept
|
||||
{
|
||||
static Keylet const ret{
|
||||
ltDIR_NODE, indexHash(LedgerNameSpace::EMITTED_DIR)};
|
||||
return ret;
|
||||
}
|
||||
|
||||
Keylet
|
||||
hookStateDir(AccountID const& id, uint256 const& ns) noexcept
|
||||
{
|
||||
return {ltDIR_NODE, indexHash(LedgerNameSpace::HOOK_STATE_DIR, id, ns)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
emitted(uint256 const& id) noexcept
|
||||
{
|
||||
return {ltEMITTED, indexHash(LedgerNameSpace::EMITTED, id)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
hook(AccountID const& id) noexcept
|
||||
{
|
||||
return {ltHOOK, indexHash(LedgerNameSpace::HOOK, id)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
hookDefinition(uint256 const& hash) noexcept
|
||||
{
|
||||
return {
|
||||
ltHOOK_DEFINITION, indexHash(LedgerNameSpace::HOOK_DEFINITION, hash)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
hookState(AccountID const& id, uint256 const& key, uint256 const& ns) noexcept
|
||||
{
|
||||
return {ltHOOK_STATE, indexHash(LedgerNameSpace::HOOK_STATE, id, key, ns)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
account(AccountID const& id) noexcept
|
||||
{
|
||||
@@ -246,7 +291,7 @@ line(
|
||||
}
|
||||
|
||||
Keylet
|
||||
offer(AccountID const& id, std::uint32_t seq) noexcept
|
||||
offer(AccountID const& id, UInt32or256 const& seq) noexcept
|
||||
{
|
||||
return {ltOFFER, indexHash(LedgerNameSpace::OFFER, id, seq)};
|
||||
}
|
||||
@@ -308,7 +353,7 @@ signers(AccountID const& account) noexcept
|
||||
}
|
||||
|
||||
Keylet
|
||||
check(AccountID const& id, std::uint32_t seq) noexcept
|
||||
check(AccountID const& id, UInt32or256 const& seq) noexcept
|
||||
{
|
||||
return {ltCHECK, indexHash(LedgerNameSpace::CHECK, id, seq)};
|
||||
}
|
||||
@@ -361,17 +406,19 @@ page(uint256 const& key, std::uint64_t index) noexcept
|
||||
}
|
||||
|
||||
Keylet
|
||||
escrow(AccountID const& src, std::uint32_t seq) noexcept
|
||||
escrow(AccountID const& src, UInt32or256 const& seq) noexcept
|
||||
{
|
||||
return {ltESCROW, indexHash(LedgerNameSpace::ESCROW, src, seq)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept
|
||||
payChan(
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
UInt32or256 const& seq) noexcept
|
||||
{
|
||||
return {
|
||||
ltPAYCHAN,
|
||||
indexHash(LedgerNameSpace::XRP_PAYMENT_CHANNEL, src, dst, seq)};
|
||||
ltPAYCHAN, indexHash(LedgerNameSpace::PAYMENT_CHANNEL, src, dst, seq)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
@@ -399,7 +446,7 @@ nftpage(Keylet const& k, uint256 const& token)
|
||||
}
|
||||
|
||||
Keylet
|
||||
nftoffer(AccountID const& owner, std::uint32_t seq)
|
||||
nftoffer(AccountID const& owner, UInt32or256 const& seq)
|
||||
{
|
||||
return {
|
||||
ltNFTOKEN_OFFER, indexHash(LedgerNameSpace::NFTOKEN_OFFER, owner, seq)};
|
||||
|
||||
@@ -27,6 +27,14 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{
|
||||
// inner objects with the default fields have to be
|
||||
// constructed with STObject::makeInnerObject()
|
||||
add(sfEmitDetails.jsonName.c_str(),
|
||||
sfEmitDetails.getCode(),
|
||||
{{sfEmitGeneration, soeREQUIRED},
|
||||
{sfEmitBurden, soeREQUIRED},
|
||||
{sfEmitParentTxnID, soeREQUIRED},
|
||||
{sfEmitNonce, soeREQUIRED},
|
||||
{sfEmitCallback, soeOPTIONAL},
|
||||
{sfEmitHookHash, soeREQUIRED}});
|
||||
|
||||
add(sfSignerEntry.jsonName.c_str(),
|
||||
sfSignerEntry.getCode(),
|
||||
@@ -58,6 +66,50 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfFirstLedgerSequence, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfHookExecution.jsonName.c_str(),
|
||||
sfHookExecution.getCode(),
|
||||
{{sfHookResult, soeREQUIRED},
|
||||
{sfHookHash, soeREQUIRED},
|
||||
{sfHookAccount, soeREQUIRED},
|
||||
{sfHookReturnCode, soeREQUIRED},
|
||||
{sfHookReturnString, soeREQUIRED},
|
||||
{sfHookInstructionCount, soeREQUIRED},
|
||||
{sfHookExecutionIndex, soeREQUIRED},
|
||||
{sfHookStateChangeCount, soeREQUIRED},
|
||||
{sfHookEmitCount, soeREQUIRED}});
|
||||
|
||||
add(sfHookDefinition.jsonName.c_str(),
|
||||
sfHookDefinition.getCode(),
|
||||
{{sfCreateCode, soeREQUIRED},
|
||||
{sfHookNamespace, soeREQUIRED},
|
||||
{sfHookParameters, soeREQUIRED},
|
||||
{sfHookOn, soeREQUIRED},
|
||||
{sfHookApiVersion, soeREQUIRED},
|
||||
{sfFlags, soeREQUIRED},
|
||||
{sfFee, soeREQUIRED}});
|
||||
|
||||
add(sfHook.jsonName.c_str(),
|
||||
sfHook.getCode(),
|
||||
{{sfHookHash, soeOPTIONAL},
|
||||
{sfCreateCode, soeOPTIONAL},
|
||||
{sfHookGrants, soeOPTIONAL},
|
||||
{sfHookNamespace, soeOPTIONAL},
|
||||
{sfHookParameters, soeOPTIONAL},
|
||||
{sfHookOn, soeOPTIONAL},
|
||||
{sfHookApiVersion, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
|
||||
add(sfHookGrant.jsonName.c_str(),
|
||||
sfHookGrant.getCode(),
|
||||
{{sfHookHash, soeREQUIRED},
|
||||
{sfAuthorize, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
|
||||
add(sfHookParameter.jsonName.c_str(),
|
||||
sfHookParameter.getCode(),
|
||||
{{sfHookParameterName, soeREQUIRED},
|
||||
{sfHookParameterValue, soeREQUIRED}});
|
||||
|
||||
add(sfNFToken.jsonName.c_str(),
|
||||
sfNFToken.getCode(),
|
||||
{
|
||||
|
||||
@@ -70,8 +70,8 @@ TypedField<T>::TypedField(private_access_tag_t pat, Args&&... args)
|
||||
##__VA_ARGS__);
|
||||
|
||||
// SFields which, for historical reasons, do not follow naming conventions.
|
||||
SField const sfInvalid(access, -1);
|
||||
SField const sfGeneric(access, 0);
|
||||
SField const sfInvalid{access, -1};
|
||||
SField const sfGeneric{access, 0};
|
||||
// The following two fields aren't used anywhere, but they break tests/have
|
||||
// downstream effects.
|
||||
SField const sfHash(access, STI_UINT256, 257, "hash");
|
||||
|
||||
@@ -44,7 +44,9 @@ SOTemplate::SOTemplate(
|
||||
// Make sure that this field hasn't already been assigned
|
||||
//
|
||||
if (getIndex(sField) != -1)
|
||||
Throw<std::runtime_error>("Duplicate field index for SOTemplate.");
|
||||
Throw<std::runtime_error>(
|
||||
std::string("Duplicate field index for SOTemplate. ") +
|
||||
sField.fieldName);
|
||||
|
||||
// Add the field to the index mapping table
|
||||
//
|
||||
|
||||
@@ -51,6 +51,11 @@ STArray::STArray(SField const& f, std::size_t n) : STBase(f)
|
||||
v_.reserve(n);
|
||||
}
|
||||
|
||||
STArray::STArray(std::vector<STObject> const& v, SField const& f) : STBase(f)
|
||||
{
|
||||
v_ = v;
|
||||
}
|
||||
|
||||
STArray::STArray(SerialIter& sit, SField const& f, int depth) : STBase(f)
|
||||
{
|
||||
while (!sit.empty())
|
||||
@@ -74,7 +79,7 @@ STArray::STArray(SerialIter& sit, SField const& f, int depth) : STBase(f)
|
||||
{
|
||||
JLOG(debugLog().error())
|
||||
<< "Unknown field: " << type << "/" << field;
|
||||
Throw<std::runtime_error>("Unknown field");
|
||||
Throw<std::runtime_error>("Unknown field - In Array");
|
||||
}
|
||||
|
||||
if (fn.fieldType != STI_OBJECT)
|
||||
|
||||
@@ -239,7 +239,10 @@ STObject::set(SerialIter& sit, int depth)
|
||||
{
|
||||
JLOG(debugLog().error()) << "Unknown field: field_type=" << type
|
||||
<< ", field_name=" << field;
|
||||
Throw<std::runtime_error>("Unknown field");
|
||||
std::stringstream ss;
|
||||
ss << "Unknown field in Object t=" << type << " f=" << field;
|
||||
|
||||
Throw<std::runtime_error>(ss.str().c_str());
|
||||
}
|
||||
|
||||
// Unflatten the field
|
||||
|
||||
@@ -615,8 +615,10 @@ isPseudoTx(STObject const& tx)
|
||||
auto t = tx[~sfTransactionType];
|
||||
if (!t)
|
||||
return false;
|
||||
|
||||
auto tt = safe_cast<TxType>(*t);
|
||||
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY;
|
||||
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY ||
|
||||
tt == ttEMIT_FAILURE;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -118,6 +118,8 @@ transResults()
|
||||
MAKE_ERROR(tecLOCKED, "Fund is locked."),
|
||||
MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."),
|
||||
|
||||
MAKE_ERROR(tecHOOK_REJECTED, "Rejected by hook on sending or receiving account."),
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The IOU amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."),
|
||||
|
||||
@@ -45,6 +45,8 @@ TxFormats::TxFormats()
|
||||
{sfTxnSignature, soeOPTIONAL},
|
||||
{sfSigners, soeOPTIONAL}, // submit_multisigned
|
||||
{sfNetworkID, soeOPTIONAL},
|
||||
{sfEmitDetails, soeOPTIONAL},
|
||||
{sfFirstLedgerSequence, soeOPTIONAL},
|
||||
};
|
||||
|
||||
#pragma push_macro("UNWRAP")
|
||||
|
||||
@@ -43,6 +43,9 @@ TxMeta::TxMeta(
|
||||
|
||||
if (obj.isFieldPresent(sfDeliveredAmount))
|
||||
setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount));
|
||||
|
||||
if (obj.isFieldPresent(sfHookExecutions))
|
||||
setHookExecutions(obj.getFieldArray(sfHookExecutions));
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
@@ -63,6 +66,9 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
|
||||
if (obj.isFieldPresent(sfDeliveredAmount))
|
||||
setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount));
|
||||
|
||||
if (obj.isFieldPresent(sfHookExecutions))
|
||||
setHookExecutions(obj.getFieldArray(sfHookExecutions));
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec)
|
||||
@@ -208,6 +214,14 @@ TxMeta::getAffectedNode(uint256 const& node)
|
||||
return *(mNodes.begin()); // Silence compiler warning.
|
||||
}
|
||||
|
||||
// used for mock TxMeta objects
|
||||
void
|
||||
TxMeta::setResult(TER res, int index)
|
||||
{
|
||||
mResult = TERtoInt(res);
|
||||
mIndex = index;
|
||||
}
|
||||
|
||||
STObject
|
||||
TxMeta::getAsObject() const
|
||||
{
|
||||
@@ -218,6 +232,10 @@ TxMeta::getAsObject() const
|
||||
metaData.emplace_back(mNodes);
|
||||
if (hasDeliveredAmount())
|
||||
metaData.setFieldAmount(sfDeliveredAmount, getDeliveredAmount());
|
||||
|
||||
if (hasHookExecutions())
|
||||
metaData.setFieldArray(sfHookExecutions, getHookExecutions());
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
|
||||
@@ -282,7 +282,9 @@ decodeBase58(std::string const& s)
|
||||
}
|
||||
|
||||
if (remain > 64)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// Allocate enough space in big-endian base256 representation.
|
||||
// log(58) / log(256), rounded up.
|
||||
@@ -291,7 +293,9 @@ decodeBase58(std::string const& s)
|
||||
{
|
||||
auto carry = alphabetReverse[*psz];
|
||||
if (carry == -1)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
// Apply "b256 = b256 * 58 + carry".
|
||||
for (auto iter = b256.rbegin(); iter != b256.rend(); ++iter)
|
||||
{
|
||||
@@ -312,6 +316,7 @@ decodeBase58(std::string const& s)
|
||||
result.assign(zeroes, 0x00);
|
||||
while (iter != b256.end())
|
||||
result.push_back(*(iter++));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -183,7 +183,6 @@ public:
|
||||
{spook, 1}}),
|
||||
ter(temBAD_QUORUM));
|
||||
|
||||
// clang-format off
|
||||
// Make a signer list that's too big. Should fail. (Even with
|
||||
// ExpandedSignerList)
|
||||
Account const spare("spare", KeyType::secp256k1);
|
||||
@@ -202,9 +201,8 @@ public:
|
||||
{acc25, 1}, {acc26, 1}, {acc27, 1},
|
||||
{acc28, 1}, {acc29, 1}, {acc30, 1},
|
||||
{acc31, 1}, {acc32, 1}, {acc33, 1}}
|
||||
: std::vector<signer>{{bogie, 1}, {demon, 1}, {ghost, 1},
|
||||
{haunt, 1}, {jinni, 1}, {phase, 1},
|
||||
{shade, 1}, {spook, 1}, {spare, 1}}),
|
||||
: std::vector<
|
||||
signer>{{bogie, 1}, {demon, 1}, {ghost, 1}, {haunt, 1}, {jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}, {spare, 1}}),
|
||||
ter(temMALFORMED));
|
||||
// clang-format on
|
||||
env.close();
|
||||
|
||||
@@ -1155,7 +1155,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(ownerCount(env, buyer) == 0);
|
||||
|
||||
// The buy offer must be present in the ledger.
|
||||
uint256 const missingOfferIndex = keylet::nftoffer(alice, 1).key;
|
||||
uint256 const missingOfferIndex = keylet::nftoffer(alice, 1U).key;
|
||||
env(token::acceptBuyOffer(buyer, missingOfferIndex),
|
||||
ter(tecOBJECT_NOT_FOUND));
|
||||
env.close();
|
||||
|
||||
@@ -45,6 +45,8 @@ struct signer
|
||||
std::optional<uint256> tag_ = std::nullopt)
|
||||
: weight(weight_), account(std::move(account_)), tag(std::move(tag_))
|
||||
{
|
||||
if (tag_)
|
||||
tag = *tag_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
#include <xrpld/app/misc/NegativeUNLVote.h>
|
||||
#include <xrpld/app/misc/NetworkOPs.h>
|
||||
#include <xrpld/app/misc/Transaction.h>
|
||||
#include <xrpld/app/misc/TxQ.h>
|
||||
#include <xrpld/app/misc/ValidatorKeys.h>
|
||||
#include <xrpld/app/misc/ValidatorList.h>
|
||||
@@ -188,6 +189,11 @@ RCLConsensus::Adaptor::share(RCLCxPeerPos const& peerPos)
|
||||
void
|
||||
RCLConsensus::Adaptor::share(RCLCxTx const& tx)
|
||||
{
|
||||
// RH TODO: never broadcast emitted transactions
|
||||
// fix below:
|
||||
// if (tx.isFieldPresent(sfEmitDetails))
|
||||
// return;
|
||||
|
||||
// If we didn't relay this transaction recently, relay it to all peers
|
||||
if (app_.getHashRouter().shouldRelay(tx.id()))
|
||||
{
|
||||
@@ -665,7 +671,6 @@ RCLConsensus::Adaptor::doAccept(
|
||||
tapNONE,
|
||||
"consensus",
|
||||
[&](OpenView& view, beast::Journal j) {
|
||||
// Stuff the ledger with transactions from the queue.
|
||||
return app_.getTxQ().accept(app_, view);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
#ifndef HOOKENUM_INCLUDED
|
||||
#define HOOKENUM_INCLUDED 1
|
||||
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
enum HookSetOperation : int8_t {
|
||||
hsoINVALID = -1,
|
||||
hsoNOOP = 0,
|
||||
hsoCREATE = 1,
|
||||
hsoINSTALL = 2,
|
||||
hsoDELETE = 3,
|
||||
hsoNSDELETE = 4,
|
||||
hsoUPDATE = 5
|
||||
};
|
||||
|
||||
enum HookSetFlags : uint8_t {
|
||||
hsfOVERRIDE = 0b00000001U, // override or delete hook
|
||||
hsfNSDELETE = 0b00000010U, // delete namespace
|
||||
hsfCOLLECT = 0b00000100U, // allow collect calls on this hook
|
||||
};
|
||||
} // namespace ripple
|
||||
|
||||
namespace hook {
|
||||
enum TSHFlags : uint8_t {
|
||||
tshNONE = 0b000,
|
||||
tshROLLBACK = 0b001,
|
||||
tshCOLLECT = 0b010,
|
||||
tshMIXED = 0b100,
|
||||
};
|
||||
|
||||
namespace log {
|
||||
/*
|
||||
* Hook log-codes are not necessarily errors. Each type of Hook log line
|
||||
* contains a code in round parens like so: HookSet(5)[rAcc...]: message The
|
||||
* log-code gives an external tool an easy way to handle and report the status
|
||||
* of a hook to end users and developers.
|
||||
*/
|
||||
enum hook_log_code : uint16_t {
|
||||
/* HookSet log-codes */
|
||||
AMENDMENT_DISABLED =
|
||||
1, // attempt to HookSet when amendment is not yet enabled.
|
||||
API_ILLEGAL = 2, // HookSet object contained HookApiVersion for existing
|
||||
// HookDefinition
|
||||
API_INVALID =
|
||||
3, // HookSet object contained HookApiVersion for unrecognised hook API
|
||||
API_MISSING =
|
||||
4, // HookSet object did not contain HookApiVersion but should have
|
||||
BLOCK_ILLEGAL = 5, // a block end instruction moves execution below depth 0
|
||||
// {{}}`}` <= like this
|
||||
CALL_ILLEGAL = 6, // wasm tries to call a non-whitelisted function
|
||||
CALL_INDIRECT =
|
||||
7, // wasm used call indirect instruction which is disallowed
|
||||
CREATE_FLAG = 8, // create operation requires hsfOVERRIDE flag
|
||||
DELETE_FIELD = 9, //
|
||||
DELETE_FLAG = 10, // delete operation requires hsfOVERRIDE flag
|
||||
DELETE_NOTHING = 11, // delete operation would delete nothing
|
||||
EXPORTS_MISSING =
|
||||
12, // hook did not export *any* functions (should be cbak, hook)
|
||||
EXPORT_CBAK_FUNC =
|
||||
13, // hook did not export correct func def int64_t cbak(uint32_t)
|
||||
EXPORT_HOOK_FUNC =
|
||||
14, // hook did not export correct func def int64_t hook(uint32_t)
|
||||
EXPORT_MISSING =
|
||||
15, // distinct from export*S*_missing, either hook or cbak is missing
|
||||
FLAGS_INVALID = 16, // HookSet flags were invalid for specified operation
|
||||
FUNCS_MISSING = 17, // hook did not include function code for any functions
|
||||
FUNC_PARAM_INVALID = 18, // parameter types may only be i32 i64 u32 u64
|
||||
FUNC_RETURN_COUNT = 19, // a function type is defined in the wasm which
|
||||
// returns > 1 return value
|
||||
FUNC_RETURN_INVALID =
|
||||
20, // a function type does not return i32 i64 u32 or u64
|
||||
FUNC_TYPELESS =
|
||||
21, // hook defined hook/cbak but their type is not defined in wasm
|
||||
FUNC_TYPE_INVALID =
|
||||
22, // malformed and illegal wasm in the func type section
|
||||
GRANTS_EMPTY = 23, // HookSet object contained an empty grants array (you
|
||||
// should remove it)
|
||||
GRANTS_EXCESS =
|
||||
24, // HookSet object cotnained a grants array with too many grants
|
||||
GRANTS_FIELD =
|
||||
25, // HookSet object contained a grant without Authorize or HookHash
|
||||
GRANTS_ILLEGAL = 26, // Hookset object contained grants array which
|
||||
// contained a non Grant object
|
||||
GUARD_IMPORT = 27, // guard import is missing
|
||||
GUARD_MISSING = 28, // guard call missing at top of loop
|
||||
GUARD_PARAMETERS =
|
||||
29, // guard called but did not use constant expressions for params
|
||||
HASH_OR_CODE =
|
||||
30, // HookSet object can contain only one of CreateCode and HookHash
|
||||
HOOKON_MISSING =
|
||||
31, // HookSet object did not contain HookOn but should have
|
||||
HOOKS_ARRAY_BAD =
|
||||
32, // attempt to HookSet with a Hooks array containing a non-Hook obj
|
||||
HOOKS_ARRAY_BLANK = 33, // all hook set objs were blank
|
||||
HOOKS_ARRAY_EMPTY = 34, // attempt to HookSet with an empty Hooks array
|
||||
HOOKS_ARRAY_MISSING = 35, // attempt to HookSet without a Hooks array
|
||||
HOOKS_ARRAY_TOO_BIG = 36, // attempt to HookSet with a Hooks array beyond
|
||||
// the chain size limit
|
||||
HOOK_ADD = 37, // Informational: adding ltHook to directory
|
||||
HOOK_DEF_MISSING = 38, // attempt to reference a hook definition (by hash)
|
||||
// that is not on ledger
|
||||
HOOK_DELETE = 39, // unable to delete ltHook from owner
|
||||
HOOK_INVALID_FIELD =
|
||||
40, // HookSetObj contained an illegal/unexpected field
|
||||
HOOK_PARAMS_COUNT =
|
||||
41, // hookset obj would create too many hook parameters
|
||||
HOOK_PARAM_SIZE = 42, // hookset obj sets a parameter or value that exceeds
|
||||
// max allowable size
|
||||
IMPORTS_MISSING = 43, // hook must import guard, and accept/rollback
|
||||
IMPORT_ILLEGAL = 44, // attempted import of a non-whitelisted function
|
||||
IMPORT_MODULE_BAD =
|
||||
45, // hook attempted to specify no or a bad import module
|
||||
IMPORT_MODULE_ENV =
|
||||
46, // hook attempted to specify import module not named env
|
||||
IMPORT_NAME_BAD = 47, // import name was too short or too long
|
||||
INSTALL_FLAG = 48, // install operation requires hsoOVERRIDE
|
||||
INSTALL_MISSING = 49, // install operation specifies hookhash which doesn't
|
||||
// exist on the ledger
|
||||
INSTRUCTION_COUNT =
|
||||
50, // worst case execution instruction count as computed by HookSet
|
||||
INSTRUCTION_EXCESS =
|
||||
51, // worst case execution instruction count was too large
|
||||
MEMORY_GROW = 52, // memory.grow instruction is present but disallowed
|
||||
NAMESPACE_MISSING = 53, // HookSet object lacked HookNamespace
|
||||
NSDELETE = 54, // Informational: a namespace is being deleted
|
||||
NSDELETE_ACCOUNT =
|
||||
55, // nsdelete tried to delete ns from a non-existing account
|
||||
NSDELETE_COUNT = 56, // namespace state count less than 0 / overflow
|
||||
NSDELETE_DIR = 57, // could not delete directory node in ledger
|
||||
NSDELETE_DIRECTORY =
|
||||
58, // nsdelete operation failed to delete ns directory
|
||||
NSDELETE_DIR_ENTRY =
|
||||
59, // nsdelete operation failed due to bad entry in ns directory
|
||||
NSDELETE_ENTRY =
|
||||
60, // nsdelete operation failed due to missing hook state entry
|
||||
NSDELETE_FIELD = 61,
|
||||
NSDELETE_FLAGS = 62,
|
||||
NSDELETE_NONSTATE = 63, // nsdelete operation failed due to the presence of
|
||||
// a non-hookstate obj
|
||||
NSDELETE_NOTHING = 64, // hsfNSDELETE provided but nothing to delete
|
||||
OPERATION_INVALID =
|
||||
65, // could not deduce an operation from the provided hookset obj
|
||||
OVERRIDE_MISSING = 66, // HookSet object was trying to update or delete a
|
||||
// hook but lacked hsfOVERRIDE
|
||||
PARAMETERS_FIELD = 67, // HookParameters contained a HookParameter with an
|
||||
// invalid key in it
|
||||
PARAMETERS_ILLEGAL =
|
||||
68, // HookParameters contained something other than a HookParameter
|
||||
PARAMETERS_NAME = 69, // HookParameters contained a HookParameter which
|
||||
// lacked ParameterName field
|
||||
PARAM_HOOK_CBAK = 70, // hook and cbak must take exactly one u32 parameter
|
||||
RETURN_HOOK_CBAK = 71, // hook and cbak must retunr i64
|
||||
SHORT_HOOK = 72, // web assembly byte code ended abruptly
|
||||
TYPE_INVALID =
|
||||
73, // malformed and illegal wasm specifying an illegal local var type
|
||||
WASM_BAD_MAGIC = 74, // wasm magic number missing or not wasm
|
||||
WASM_INVALID = 75, // set hook operation would set invalid wasm
|
||||
WASM_PARSE_LOOP = 76, // wasm section parsing resulted in an infinite loop
|
||||
WASM_SMOKE_TEST =
|
||||
77, // Informational: first attempt to load wasm into wasm runtime
|
||||
WASM_TEST_FAILURE = 78, // the smoke test failed
|
||||
WASM_TOO_BIG = 79, // set hook would exceed maximum hook size
|
||||
WASM_TOO_SMALL = 80,
|
||||
WASM_VALIDATION =
|
||||
81, // a generic error while parsing wasm, usually leb128 overflow
|
||||
HOOK_CBAK_DIFF_TYPES =
|
||||
82, // hook and cbak function definitions were different
|
||||
// RH NOTE: only HookSet msgs got log codes, possibly all Hook log lines
|
||||
// should get a code?
|
||||
};
|
||||
}; // namespace log
|
||||
}; // namespace hook
|
||||
|
||||
namespace hook_api {
|
||||
|
||||
namespace keylet_code {
|
||||
enum keylet_code : uint32_t {
|
||||
HOOK = 1,
|
||||
HOOK_STATE = 2,
|
||||
ACCOUNT = 3,
|
||||
AMENDMENTS = 4,
|
||||
CHILD = 5,
|
||||
SKIP = 6,
|
||||
FEES = 7,
|
||||
NEGATIVE_UNL = 8,
|
||||
LINE = 9,
|
||||
OFFER = 10,
|
||||
QUALITY = 11,
|
||||
EMITTED_DIR = 12,
|
||||
TICKET = 13,
|
||||
SIGNERS = 14,
|
||||
CHECK = 15,
|
||||
DEPOSIT_PREAUTH = 16,
|
||||
UNCHECKED = 17,
|
||||
OWNER_DIR = 18,
|
||||
PAGE = 19,
|
||||
ESCROW = 20,
|
||||
PAYCHAN = 21,
|
||||
EMITTED = 22
|
||||
};
|
||||
}
|
||||
|
||||
namespace compare_mode {
|
||||
enum compare_mode : uint32_t { EQUAL = 1, LESS = 2, GREATER = 4 };
|
||||
}
|
||||
|
||||
enum hook_return_code : int64_t {
|
||||
SUCCESS =
|
||||
0, // return codes > 0 are reserved for hook apis to return "success"
|
||||
OUT_OF_BOUNDS =
|
||||
-1, // could not read or write to a pointer to provided by hook
|
||||
INTERNAL_ERROR = -2, // eg directory is corrupt
|
||||
TOO_BIG = -3, // something you tried to store was too big
|
||||
TOO_SMALL = -4, // something you tried to store or provide was too small
|
||||
DOESNT_EXIST = -5, // something you requested wasn't found
|
||||
NO_FREE_SLOTS =
|
||||
-6, // when trying to load an object there is a maximum of 255 slots
|
||||
INVALID_ARGUMENT = -7, // self explanatory
|
||||
ALREADY_SET =
|
||||
-8, // returned when a one-time parameter was already set by the hook
|
||||
PREREQUISITE_NOT_MET =
|
||||
-9, // returned if a required param wasn't set, before calling
|
||||
FEE_TOO_LARGE = -10, // returned if the attempted operation would result in
|
||||
// an absurd fee
|
||||
EMISSION_FAILURE =
|
||||
-11, // returned if an emitted tx was not accepted by rippled
|
||||
TOO_MANY_NONCES = -12, // a hook has a maximum of 256 nonces
|
||||
TOO_MANY_EMITTED_TXN =
|
||||
-13, // a hook has emitted more than its stated number of emitted txn
|
||||
NOT_IMPLEMENTED =
|
||||
-14, // an api was called that is reserved for a future version
|
||||
INVALID_ACCOUNT =
|
||||
-15, // an api expected an account id but got something else
|
||||
GUARD_VIOLATION =
|
||||
-16, // a guarded loop or function iterated over its maximum
|
||||
INVALID_FIELD = -17, // the field requested is returning sfInvalid
|
||||
PARSE_ERROR = -18, // hook asked hookapi to parse something the contents of
|
||||
// which was invalid
|
||||
RC_ROLLBACK = -19, // hook should terminate due to a rollback() call
|
||||
RC_ACCEPT = -20, // hook should temrinate due to an accept() call
|
||||
NO_SUCH_KEYLET = -21, // invalid keylet or keylet type
|
||||
NOT_AN_ARRAY =
|
||||
-22, // if a count of an sle is requested but its not STI_ARRAY
|
||||
NOT_AN_OBJECT =
|
||||
-23, // if a subfield is requested from something that isn't an object
|
||||
INVALID_FLOAT =
|
||||
-10024, // specially selected value that will never be a valid exponent
|
||||
DIVISION_BY_ZERO = -25,
|
||||
MANTISSA_OVERSIZED = -26,
|
||||
MANTISSA_UNDERSIZED = -27,
|
||||
EXPONENT_OVERSIZED = -28,
|
||||
EXPONENT_UNDERSIZED = -29,
|
||||
XFL_OVERFLOW = -30, // if an operation with a float results in an overflow
|
||||
NOT_IOU_AMOUNT = -31,
|
||||
NOT_AN_AMOUNT = -32,
|
||||
CANT_RETURN_NEGATIVE = -33,
|
||||
NOT_AUTHORIZED = -34,
|
||||
PREVIOUS_FAILURE_PREVENTS_RETRY = -35,
|
||||
TOO_MANY_PARAMS = -36,
|
||||
INVALID_TXN = -37,
|
||||
RESERVE_INSUFFICIENT =
|
||||
-38, // setting a new state object would exceed account reserve
|
||||
COMPLEX_NOT_SUPPORTED = -39,
|
||||
DOES_NOT_MATCH =
|
||||
-40, // two keylets were required to be the same type but werent
|
||||
INVALID_KEY = -41, // user supplied key was not valid
|
||||
|
||||
};
|
||||
|
||||
enum ExitType : uint8_t {
|
||||
UNSET = 0,
|
||||
WASM_ERROR = 1,
|
||||
ROLLBACK = 2,
|
||||
ACCEPT = 3,
|
||||
};
|
||||
|
||||
const uint8_t max_slots = 255;
|
||||
const uint8_t max_nonce = 255;
|
||||
const uint8_t max_emit = 255;
|
||||
const uint8_t max_params = 16;
|
||||
const double fee_base_multiplier = 1.1f;
|
||||
|
||||
// RH NOTE: Find descriptions of api functions in ./impl/applyHook.cpp and
|
||||
// hookapi.h (include for hooks)
|
||||
static const std::set<std::string> import_whitelist{
|
||||
"accept",
|
||||
"emit",
|
||||
"etxn_burden",
|
||||
"etxn_details",
|
||||
"etxn_fee_base",
|
||||
"etxn_generation",
|
||||
"etxn_reserve",
|
||||
"etxn_nonce",
|
||||
"float_compare",
|
||||
"float_divide",
|
||||
"float_exponent",
|
||||
"float_exponent_set",
|
||||
"float_invert",
|
||||
"float_mantissa",
|
||||
"float_mantissa_set",
|
||||
"float_mulratio",
|
||||
"float_multiply",
|
||||
"float_int",
|
||||
"float_negate",
|
||||
"float_one",
|
||||
"float_set",
|
||||
"float_sign",
|
||||
"float_sign_set",
|
||||
"float_sto",
|
||||
"float_sto_set",
|
||||
"float_sum",
|
||||
"float_log",
|
||||
"float_root",
|
||||
"fee_base",
|
||||
"_g",
|
||||
"hook_account",
|
||||
"hook_hash",
|
||||
"ledger_seq",
|
||||
"ledger_last_hash",
|
||||
"ledger_last_time",
|
||||
"ledger_nonce",
|
||||
"ledger_keylet",
|
||||
"otxn_burden",
|
||||
"otxn_field",
|
||||
"otxn_field_txt",
|
||||
"otxn_slot",
|
||||
"otxn_generation",
|
||||
"otxn_id",
|
||||
"otxn_type",
|
||||
"rollback",
|
||||
"slot",
|
||||
"slot_clear",
|
||||
"slot_count",
|
||||
"slot_id",
|
||||
"slot_set",
|
||||
"slot_size",
|
||||
"slot_subarray",
|
||||
"slot_subfield",
|
||||
"slot_type",
|
||||
"slot_float",
|
||||
"state",
|
||||
"state_foreign",
|
||||
"state_set",
|
||||
"state_foreign_set",
|
||||
"trace",
|
||||
"trace_num",
|
||||
"trace_float",
|
||||
"trace_slot",
|
||||
"util_accid",
|
||||
"util_raddr",
|
||||
"util_sha512h",
|
||||
"util_verify",
|
||||
"sto_subarray",
|
||||
"sto_subfield",
|
||||
"sto_validate",
|
||||
"sto_emplace",
|
||||
"sto_erase",
|
||||
"util_keylet",
|
||||
"hook_pos",
|
||||
"hook_param",
|
||||
"hook_param_set",
|
||||
"hook_skip",
|
||||
"hook_again",
|
||||
"hook_namespace",
|
||||
"meta_slot"};
|
||||
}; // namespace hook_api
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* RH NOTE:
|
||||
* This file contains macros for converting the hook api definitions into the
|
||||
* currently used wasm runtime. Web assembly runtimes are more or less fungible,
|
||||
* and at time of writing hooks has moved to WasmEdge from SSVM and before that
|
||||
* from wasmer. After the first move it was decided there should be a relatively
|
||||
* static interface for the definition and programming of the hook api itself,
|
||||
* with the runtime-specific behaviour hidden away by templates or macros.
|
||||
* Macros are more expressive and can themselves include templates so macros
|
||||
* were then used.
|
||||
*/
|
||||
|
||||
#define LPAREN (
|
||||
#define LPAREN (
|
||||
#define RPAREN )
|
||||
#define COMMA ,
|
||||
#define EXPAND(...) __VA_ARGS__
|
||||
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
|
||||
#define CAT2(L, R) CAT2_(L, R)
|
||||
#define CAT2_(L, R) L##R
|
||||
#define PRIMITIVE_CAT(a, ...) a##__VA_ARGS__
|
||||
#define EMPTY()
|
||||
#define DEFER(id) id EMPTY()
|
||||
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
|
||||
#define VA_NARGS_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
|
||||
#define VA_NARGS(__drop, ...) \
|
||||
VA_NARGS_IMPL(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
|
||||
#define FIRST(a, b) a
|
||||
#define SECOND(a, b) b
|
||||
#define STRIP_TYPES(...) FOR_VARS(SECOND, 0, __VA_ARGS__)
|
||||
|
||||
#define DELIM_0 ,
|
||||
#define DELIM_1
|
||||
#define DELIM_2 ;
|
||||
#define DELIM(S) DELIM_##S
|
||||
|
||||
#define FOR_VAR_1(T, S, D) SEP(T, D)
|
||||
#define FOR_VAR_2(T, S, a, b) FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_1(T, S, b)
|
||||
#define FOR_VAR_3(T, S, a, ...) \
|
||||
FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_2(T, S, __VA_ARGS__)
|
||||
#define FOR_VAR_4(T, S, a, ...) \
|
||||
FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_3(T, S, __VA_ARGS__)
|
||||
#define FOR_VAR_5(T, S, a, ...) \
|
||||
FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_4(T, S, __VA_ARGS__)
|
||||
#define FOR_VAR_6(T, S, a, ...) \
|
||||
FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_5(T, S, __VA_ARGS__)
|
||||
#define FOR_VAR_7(T, S, a, ...) \
|
||||
FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_6(T, S, __VA_ARGS__)
|
||||
#define FOR_VAR_8(T, S, a, ...) \
|
||||
FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_7(T, S, __VA_ARGS__)
|
||||
#define FOR_VAR_9(T, S, a, ...) \
|
||||
FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_8(T, S, __VA_ARGS__)
|
||||
#define FOR_VAR_10(T, S, a, ...) \
|
||||
FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_9(T, S, __VA_ARGS__)
|
||||
#define FOR_VARS(T, S, ...) \
|
||||
DEFER(CAT(FOR_VAR_, VA_NARGS(NULL, __VA_ARGS__)) \
|
||||
CAT(LPAREN T COMMA S COMMA OBSTRUCT(__VA_ARGS__) RPAREN))
|
||||
|
||||
#define SEP(OP, D) EXPAND(OP CAT2(SEP_, D) RPAREN)
|
||||
#define SEP_uint32_t LPAREN uint32_t COMMA
|
||||
#define SEP_int32_t LPAREN int32_t COMMA
|
||||
#define SEP_uint64_t LPAREN uint64_t COMMA
|
||||
#define SEP_int64_t LPAREN int64_t COMMA
|
||||
|
||||
#define VAL_uint32_t WasmEdge_ValueGetI32(in[_stack++])
|
||||
#define VAL_int32_t WasmEdge_ValueGetI32(in[_stack++])
|
||||
#define VAL_uint64_t WasmEdge_ValueGetI64(in[_stack++])
|
||||
#define VAL_int64_t WasmEdge_ValueGetI64(in[_stack++])
|
||||
|
||||
#define VAR_ASSIGN(T, V) T V = CAT(VAL_##T)
|
||||
|
||||
#define RET_uint32_t(return_code) WasmEdge_ValueGenI32(return_code)
|
||||
#define RET_int32_t(return_code) WasmEdge_ValueGenI32(return_code)
|
||||
#define RET_uint64_t(return_code) WasmEdge_ValueGenI64(return_code)
|
||||
#define RET_int64_t(return_code) WasmEdge_ValueGenI64(return_code)
|
||||
|
||||
#define RET_ASSIGN(T, return_code) CAT2(RET_, T(return_code))
|
||||
|
||||
#define TYP_uint32_t WasmEdge_ValType_I32
|
||||
#define TYP_int32_t WasmEdge_ValType_I32
|
||||
#define TYP_uint64_t WasmEdge_ValType_I64
|
||||
#define TYP_int64_t WasmEdge_ValType_I64
|
||||
|
||||
#define WASM_VAL_TYPE(T, b) CAT2(TYP_, T)
|
||||
|
||||
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
|
||||
R F(hook::HookContext& hookCtx, \
|
||||
WasmEdge_MemoryInstanceContext& memoryCtx, \
|
||||
__VA_ARGS__); \
|
||||
extern WasmEdge_Result WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
WasmEdge_MemoryInstanceContext* memCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out); \
|
||||
extern WasmEdge_ValType WasmFunctionParams##F[]; \
|
||||
extern WasmEdge_ValType WasmFunctionResult##F[]; \
|
||||
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
|
||||
extern WasmEdge_String WasmFunctionName##F;
|
||||
|
||||
#define DECLARE_HOOK_FUNCNARG(R, F) \
|
||||
R F(hook::HookContext& hookCtx, \
|
||||
WasmEdge_MemoryInstanceContext& memoryCtx); \
|
||||
extern WasmEdge_Result WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
WasmEdge_MemoryInstanceContext* memCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out); \
|
||||
extern WasmEdge_ValType WasmFunctionResult##F[]; \
|
||||
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
|
||||
extern WasmEdge_String WasmFunctionName##F;
|
||||
|
||||
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
|
||||
WasmEdge_Result hook_api::WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
WasmEdge_MemoryInstanceContext* memCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out) \
|
||||
{ \
|
||||
int _stack = 0; \
|
||||
FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__); \
|
||||
hook::HookContext* hookCtx = \
|
||||
reinterpret_cast<hook::HookContext*>(data_ptr); \
|
||||
R return_code = \
|
||||
hook_api::F(*hookCtx, *memCtx, STRIP_TYPES(__VA_ARGS__)); \
|
||||
if (return_code == RC_ROLLBACK || return_code == RC_ACCEPT) \
|
||||
return WasmEdge_Result_Terminate; \
|
||||
out[0] = RET_ASSIGN(R, return_code); \
|
||||
return WasmEdge_Result_Success; \
|
||||
}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionParams##F[] = { \
|
||||
FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__)}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
|
||||
WASM_VAL_TYPE(R, dummy)}; \
|
||||
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
|
||||
WasmEdge_FunctionTypeCreate( \
|
||||
WasmFunctionParams##F, \
|
||||
VA_NARGS(NULL, __VA_ARGS__), \
|
||||
WasmFunctionResult##F, \
|
||||
1); \
|
||||
WasmEdge_String hook_api::WasmFunctionName##F = \
|
||||
WasmEdge_StringCreateByCString(#F); \
|
||||
R hook_api::F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
WasmEdge_MemoryInstanceContext& memoryCtx, \
|
||||
__VA_ARGS__)
|
||||
|
||||
#define DEFINE_HOOK_FUNCNARG(R, F) \
|
||||
WasmEdge_Result hook_api::WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
WasmEdge_MemoryInstanceContext* memCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out) \
|
||||
{ \
|
||||
hook::HookContext* hookCtx = \
|
||||
reinterpret_cast<hook::HookContext*>(data_ptr); \
|
||||
R return_code = hook_api::F(*hookCtx, *memCtx); \
|
||||
if (return_code == RC_ROLLBACK || return_code == RC_ACCEPT) \
|
||||
return WasmEdge_Result_Terminate; \
|
||||
out[0] = CAT2(RET_, R(return_code)); \
|
||||
return WasmEdge_Result_Success; \
|
||||
}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
|
||||
WASM_VAL_TYPE(R, dummy)}; \
|
||||
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
|
||||
WasmEdge_FunctionTypeCreate({}, 0, WasmFunctionResult##F, 1); \
|
||||
WasmEdge_String hook_api::WasmFunctionName##F = \
|
||||
WasmEdge_StringCreateByCString(#F); \
|
||||
R hook_api::F( \
|
||||
hook::HookContext& hookCtx, WasmEdge_MemoryInstanceContext& memoryCtx)
|
||||
|
||||
#define COMPUTE_HOOK_DATA_OWNER_COUNT(state_count) \
|
||||
(std::ceil((double)state_count / (double)5.0))
|
||||
|
||||
#define HOOK_SETUP() \
|
||||
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
|
||||
[[maybe_unused]] auto& view = applyCtx.view(); \
|
||||
[[maybe_unused]] auto j = applyCtx.app.journal("View"); \
|
||||
[[maybe_unused]] unsigned char* memory = \
|
||||
WasmEdge_MemoryInstanceGetPointer(&memoryCtx, 0, 0); \
|
||||
[[maybe_unused]] const uint64_t memory_length = \
|
||||
WasmEdge_MemoryInstanceGetPageSize(&memoryCtx) * WasmEdge_kPageSize;
|
||||
|
||||
#define WRITE_WASM_MEMORY( \
|
||||
bytes_written, \
|
||||
guest_dst_ptr, \
|
||||
guest_dst_len, \
|
||||
host_src_ptr, \
|
||||
host_src_len, \
|
||||
host_memory_ptr, \
|
||||
guest_memory_length) \
|
||||
{ \
|
||||
int64_t bytes_to_write = std::min( \
|
||||
static_cast<int64_t>(host_src_len), \
|
||||
static_cast<int64_t>(guest_dst_len)); \
|
||||
if (guest_dst_ptr + bytes_to_write > guest_memory_length) \
|
||||
{ \
|
||||
JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: " << __func__ \
|
||||
<< " tried to retreive blob of " << host_src_len \
|
||||
<< " bytes past end of wasm memory"; \
|
||||
return OUT_OF_BOUNDS; \
|
||||
} \
|
||||
WasmEdge_MemoryInstanceSetData( \
|
||||
&memoryCtx, \
|
||||
reinterpret_cast<const uint8_t*>(host_src_ptr), \
|
||||
guest_dst_ptr, \
|
||||
bytes_to_write); \
|
||||
bytes_written += bytes_to_write; \
|
||||
}
|
||||
|
||||
#define WRITE_WASM_MEMORY_AND_RETURN( \
|
||||
guest_dst_ptr, \
|
||||
guest_dst_len, \
|
||||
host_src_ptr, \
|
||||
host_src_len, \
|
||||
host_memory_ptr, \
|
||||
guest_memory_length) \
|
||||
{ \
|
||||
int64_t bytes_written = 0; \
|
||||
WRITE_WASM_MEMORY( \
|
||||
bytes_written, \
|
||||
guest_dst_ptr, \
|
||||
guest_dst_len, \
|
||||
host_src_ptr, \
|
||||
host_src_len, \
|
||||
host_memory_ptr, \
|
||||
guest_memory_length); \
|
||||
return bytes_written; \
|
||||
}
|
||||
|
||||
#define RETURN_HOOK_TRACE(read_ptr, read_len, t) \
|
||||
{ \
|
||||
int rl = read_len; \
|
||||
if (rl > 1024) \
|
||||
rl = 1024; \
|
||||
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length)) \
|
||||
{ \
|
||||
return OUT_OF_BOUNDS; \
|
||||
} \
|
||||
else if (read_ptr == 0 && read_len == 0) \
|
||||
{ \
|
||||
JLOG(j.trace()) << "HookTrace[" << HC_ACC() << "]: " << t; \
|
||||
} \
|
||||
else if (is_UTF16LE(memory + read_ptr, rl)) \
|
||||
{ \
|
||||
uint8_t output[1024]; \
|
||||
int len = rl / 2; \
|
||||
for (int i = 0; i < len && i < 512; ++i) \
|
||||
output[i] = memory[read_ptr + i * 2]; \
|
||||
JLOG(j.trace()) \
|
||||
<< "HookTrace[" << HC_ACC() << "]: " \
|
||||
<< std::string_view((const char*)output, (size_t)(len)) << " " \
|
||||
<< t; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
JLOG(j.trace()) \
|
||||
<< "HookTrace[" << HC_ACC() << "]: " \
|
||||
<< std::string_view( \
|
||||
(const char*)(memory + read_ptr), (size_t)rl) \
|
||||
<< " " << t; \
|
||||
} \
|
||||
return 0; \
|
||||
}
|
||||
// ptr = pointer inside the wasm memory space
|
||||
#define NOT_IN_BOUNDS(ptr, len, memory_length) \
|
||||
((static_cast<uint64_t>(ptr) > static_cast<uint64_t>(memory_length)) || \
|
||||
((static_cast<uint64_t>(ptr) + static_cast<uint64_t>(len)) > \
|
||||
static_cast<uint64_t>(memory_length)))
|
||||
|
||||
#define HOOK_EXIT(read_ptr, read_len, error_code, exit_type) \
|
||||
{ \
|
||||
if (read_len > 256) \
|
||||
read_len = 256; \
|
||||
if (read_ptr) \
|
||||
{ \
|
||||
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length)) \
|
||||
{ \
|
||||
JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: " \
|
||||
<< "Tried to accept/rollback but specified " \
|
||||
"memory outside of the wasm instance " \
|
||||
<< "limit when specifying a reason string"; \
|
||||
return OUT_OF_BOUNDS; \
|
||||
} \
|
||||
/* assembly script and some other languages use utf16 for strings \
|
||||
*/ \
|
||||
if (is_UTF16LE(read_ptr + memory, read_len)) \
|
||||
{ \
|
||||
uint8_t output[128]; \
|
||||
int len = read_len / 2; /* is_UTF16LE will only return true if \
|
||||
read_len is even */ \
|
||||
for (int i = 0; i < len; ++i) \
|
||||
output[i] = memory[read_ptr + i * 2]; \
|
||||
hookCtx.result.exitReason = \
|
||||
std::string((const char*)(output), (size_t)len); \
|
||||
} \
|
||||
else \
|
||||
hookCtx.result.exitReason = std::string( \
|
||||
(const char*)(memory + read_ptr), (size_t)read_len); \
|
||||
} \
|
||||
hookCtx.result.exitType = exit_type; \
|
||||
hookCtx.result.exitCode = error_code; \
|
||||
return ( \
|
||||
exit_type == hook_api::ExitType::ACCEPT ? RC_ACCEPT \
|
||||
: RC_ROLLBACK); \
|
||||
}
|
||||
@@ -0,0 +1,924 @@
|
||||
#ifndef APPLY_HOOK_INCLUDED
|
||||
#define APPLY_HOOK_INCLUDED 1
|
||||
#include <xrpld/app/hook/Enum.h>
|
||||
#include <xrpld/app/hook/Macro.h>
|
||||
#include <xrpld/app/misc/Transaction.h>
|
||||
#include <xrpld/app/tx/detail/ApplyContext.h>
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <any>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <wasmedge/wasmedge.h>
|
||||
|
||||
namespace hook {
|
||||
struct HookContext;
|
||||
struct HookResult;
|
||||
bool
|
||||
isEmittedTxn(ripple::STTx const& tx);
|
||||
|
||||
// This map type acts as both a read and write cache for hook execution
|
||||
// and is preserved across the execution of the set of hook chains
|
||||
// being executed in the current transaction. It is committed to lgr
|
||||
// only upon tesSuccess for the otxn.
|
||||
using HookStateMap = std::map<
|
||||
ripple::AccountID, // account that owns the state
|
||||
std::pair<
|
||||
int64_t, // remaining available ownercount
|
||||
std::map<
|
||||
ripple::uint256, // namespace
|
||||
std::map<
|
||||
ripple::uint256, // key
|
||||
std::pair<
|
||||
bool, // is modified from ledger value
|
||||
ripple::Blob>>>>>; // the value
|
||||
|
||||
using namespace ripple;
|
||||
static const std::map<uint16_t, uint8_t> TSHAllowances = {
|
||||
{ttPAYMENT, tshROLLBACK},
|
||||
{ttESCROW_CREATE, tshROLLBACK},
|
||||
{ttESCROW_FINISH, tshROLLBACK},
|
||||
{ttACCOUNT_SET, tshNONE},
|
||||
{ttESCROW_CANCEL, tshCOLLECT},
|
||||
{ttREGULAR_KEY_SET, tshROLLBACK},
|
||||
{ttOFFER_CREATE, tshCOLLECT},
|
||||
{ttOFFER_CANCEL, tshNONE},
|
||||
{ttTICKET_CREATE, tshNONE},
|
||||
{ttSIGNER_LIST_SET, tshROLLBACK},
|
||||
{ttPAYCHAN_CREATE, tshROLLBACK},
|
||||
{ttPAYCHAN_FUND, tshCOLLECT},
|
||||
{ttPAYCHAN_CLAIM, tshCOLLECT},
|
||||
{ttCHECK_CREATE, tshROLLBACK},
|
||||
{ttCHECK_CASH, tshROLLBACK},
|
||||
{ttCHECK_CANCEL, tshCOLLECT},
|
||||
{ttDEPOSIT_PREAUTH, tshROLLBACK},
|
||||
{ttTRUST_SET, tshCOLLECT},
|
||||
{ttACCOUNT_DELETE, tshROLLBACK},
|
||||
{ttHOOK_SET, tshNONE},
|
||||
{ttNFTOKEN_MINT, tshROLLBACK},
|
||||
{ttNFTOKEN_BURN, tshCOLLECT},
|
||||
{ttNFTOKEN_CREATE_OFFER, tshROLLBACK},
|
||||
{ttNFTOKEN_CANCEL_OFFER, tshCOLLECT},
|
||||
{ttNFTOKEN_ACCEPT_OFFER, tshROLLBACK}};
|
||||
|
||||
/*
|
||||
ttNFTOKEN_MINT = 25,
|
||||
|
||||
ttNFTOKEN_BURN = 26,
|
||||
|
||||
ttNFTOKEN_CREATE_OFFER = 27,
|
||||
|
||||
ttNFTOKEN_CANCEL_OFFER = 28,
|
||||
|
||||
ttNFTOKEN_ACCEPT_OFFER = 29,
|
||||
*/
|
||||
|
||||
std::vector<std::pair<AccountID, bool>>
|
||||
getTransactionalStakeHolders(STTx const& tx, ReadView const& rv);
|
||||
} // namespace hook
|
||||
|
||||
namespace hook_api {
|
||||
|
||||
#define TER_TO_HOOK_RETURN_CODE(x) (((TERtoInt(x)) << 16) * -1)
|
||||
|
||||
// for debugging if you want a lot of output change these to if (1)
|
||||
#define HOOK_DBG 1
|
||||
#define DBG_PRINTF \
|
||||
if (HOOK_DBG) \
|
||||
printf
|
||||
#define DBG_FPRINTF \
|
||||
if (HOOK_DBG) \
|
||||
fprintf
|
||||
|
||||
DECLARE_HOOK_FUNCTION(int32_t, _g, uint32_t guard_id, uint32_t maxiter);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
accept,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
int64_t error_code);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
rollback,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
int64_t error_code);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
util_raddr,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
util_accid,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
util_verify,
|
||||
uint32_t dread_ptr,
|
||||
uint32_t dread_len,
|
||||
uint32_t sread_ptr,
|
||||
uint32_t sread_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
sto_validate,
|
||||
uint32_t tread_ptr,
|
||||
uint32_t tread_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
sto_subfield,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t field_id);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
sto_subarray,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t array_id);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
sto_emplace,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t sread_ptr,
|
||||
uint32_t sread_len,
|
||||
uint32_t fread_ptr,
|
||||
uint32_t fread_len,
|
||||
uint32_t field_id);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
sto_erase,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t field_id);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
util_sha512h,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
util_keylet,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t keylet_type,
|
||||
uint32_t a,
|
||||
uint32_t b,
|
||||
uint32_t c,
|
||||
uint32_t d,
|
||||
uint32_t e,
|
||||
uint32_t f);
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, etxn_burden);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
etxn_details,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
etxn_fee_base,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, etxn_reserve, uint32_t count);
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, etxn_generation);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
etxn_nonce,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
emit,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_set, int32_t exponent, int64_t mantissa);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_multiply, int64_t float1, int64_t float2);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
float_mulratio,
|
||||
int64_t float1,
|
||||
uint32_t round_up,
|
||||
uint32_t numerator,
|
||||
uint32_t denominator);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_negate, int64_t float1);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
float_compare,
|
||||
int64_t float1,
|
||||
int64_t float2,
|
||||
uint32_t mode);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_sum, int64_t float1, int64_t float2);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
float_sto,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t cread_ptr,
|
||||
uint32_t cread_len,
|
||||
uint32_t iread_ptr,
|
||||
uint32_t iread_len,
|
||||
int64_t float1,
|
||||
uint32_t field_code);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
float_sto_set,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_invert, int64_t float1);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_divide, int64_t float1, int64_t float2);
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, float_one);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_exponent, int64_t float1);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
float_exponent_set,
|
||||
int64_t float1,
|
||||
int32_t exponent);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_mantissa, int64_t float1);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
float_mantissa_set,
|
||||
int64_t float1,
|
||||
int64_t mantissa);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_sign, int64_t float1);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
float_sign_set,
|
||||
int64_t float1,
|
||||
uint32_t negative);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
float_int,
|
||||
int64_t float1,
|
||||
uint32_t decimal_places,
|
||||
uint32_t abs);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_log, int64_t float1);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_root, int64_t float1, uint32_t n);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
hook_account,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
hook_hash,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
int32_t hook_no);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
hook_namespace,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t aread_ptr,
|
||||
uint32_t aread_len,
|
||||
uint32_t hread_ptr,
|
||||
uint32_t hread_len);
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, fee_base);
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, ledger_seq);
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, ledger_last_time);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
ledger_last_hash,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
ledger_nonce,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
ledger_keylet,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t lread_ptr,
|
||||
uint32_t lread_len,
|
||||
uint32_t hread_ptr,
|
||||
uint32_t hread_len);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
hook_param_set,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len,
|
||||
uint32_t hread_ptr,
|
||||
uint32_t hread_len);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
hook_param,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, hook_again);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
hook_skip,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t flags);
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, hook_pos);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
slot,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t slot);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, slot_clear, uint32_t slot);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, slot_count, uint32_t slot);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
slot_id,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t slot);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
slot_set,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
int32_t slot);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, slot_size, uint32_t slot);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
slot_subarray,
|
||||
uint32_t parent_slot,
|
||||
uint32_t array_id,
|
||||
uint32_t new_slot);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
slot_subfield,
|
||||
uint32_t parent_slot,
|
||||
uint32_t field_id,
|
||||
uint32_t new_slot);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, slot_type, uint32_t slot_no, uint32_t flags);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, slot_float, uint32_t slot_no);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
state_set,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
state_foreign_set,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len,
|
||||
uint32_t nread_ptr,
|
||||
uint32_t nread_len,
|
||||
uint32_t aread_ptr,
|
||||
uint32_t aread_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
state,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
state_foreign,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t kread_ptr,
|
||||
uint32_t kread_len,
|
||||
uint32_t nread_ptr,
|
||||
uint32_t nread_len,
|
||||
uint32_t aread_ptr,
|
||||
uint32_t aread_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
trace_slot,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
uint32_t slot);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
trace,
|
||||
uint32_t mread_ptr,
|
||||
uint32_t mread_len,
|
||||
uint32_t dread_ptr,
|
||||
uint32_t dread_len,
|
||||
uint32_t as_hex);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
trace_num,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
int64_t number);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
trace_float,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len,
|
||||
int64_t float1);
|
||||
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, otxn_burden);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
otxn_field,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t field_id);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
otxn_field_txt,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t field_id);
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, otxn_generation);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
otxn_id,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t flags);
|
||||
DECLARE_HOOK_FUNCNARG(int64_t, otxn_type);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, otxn_slot, uint32_t slot_no);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(int64_t, meta_slot, uint32_t slot_no);
|
||||
|
||||
} /* end namespace hook_api */
|
||||
|
||||
namespace hook {
|
||||
|
||||
bool
|
||||
canHook(ripple::TxType txType, uint64_t hookOn);
|
||||
|
||||
struct HookResult;
|
||||
|
||||
HookResult
|
||||
apply(
|
||||
ripple::uint256 const& hookSetTxnID, /* this is the txid of the sethook,
|
||||
used for caching (one day) */
|
||||
ripple::uint256 const&
|
||||
hookHash, /* hash of the actual hook byte code, used for metadata */
|
||||
ripple::uint256 const& hookNamespace,
|
||||
ripple::Blob const& wasm,
|
||||
std::map<
|
||||
std::vector<uint8_t>, /* param name */
|
||||
std::vector<uint8_t> /* param value */
|
||||
> const& hookParams,
|
||||
std::map<
|
||||
ripple::uint256, /* hook hash */
|
||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>>> const&
|
||||
hookParamOverrides,
|
||||
HookStateMap& stateMap,
|
||||
ripple::ApplyContext& applyCtx,
|
||||
ripple::AccountID const& account, /* the account the hook is INSTALLED ON
|
||||
not always the otxn account */
|
||||
bool hasCallback,
|
||||
bool isCallback,
|
||||
bool isStrongTSH,
|
||||
uint32_t wasmParam,
|
||||
uint8_t hookChainPosition,
|
||||
// result of apply() if this is weak exec
|
||||
std::shared_ptr<STObject const> const& provisionalMeta);
|
||||
|
||||
struct HookContext;
|
||||
|
||||
uint32_t
|
||||
maxHookStateDataSize(void);
|
||||
uint32_t
|
||||
maxHookWasmSize(void);
|
||||
uint32_t
|
||||
maxHookParameterKeySize(void);
|
||||
uint32_t
|
||||
maxHookParameterValueSize(void);
|
||||
|
||||
uint32_t
|
||||
maxHookChainLength(void);
|
||||
|
||||
int64_t
|
||||
computeExecutionFee(uint64_t instructionCount);
|
||||
int64_t
|
||||
computeCreationFee(uint64_t byteCount);
|
||||
|
||||
struct HookResult
|
||||
{
|
||||
ripple::uint256 const hookSetTxnID;
|
||||
ripple::uint256 const hookHash;
|
||||
ripple::Keylet const accountKeylet;
|
||||
ripple::Keylet const ownerDirKeylet;
|
||||
ripple::Keylet const hookKeylet;
|
||||
ripple::AccountID const account;
|
||||
ripple::AccountID const otxnAccount;
|
||||
ripple::uint256 const hookNamespace;
|
||||
|
||||
std::queue<std::shared_ptr<ripple::Transaction>>
|
||||
emittedTxn{}; // etx stored here until accept/rollback
|
||||
HookStateMap& stateMap;
|
||||
uint16_t changedStateCount = 0;
|
||||
std::map<
|
||||
ripple::uint256, // hook hash
|
||||
std::map<
|
||||
std::vector<uint8_t>, // hook param name
|
||||
std::vector<uint8_t> // hook param value
|
||||
>>
|
||||
hookParamOverrides;
|
||||
|
||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>> const& hookParams;
|
||||
std::set<ripple::uint256> hookSkips;
|
||||
hook_api::ExitType exitType = hook_api::ExitType::ROLLBACK;
|
||||
std::string exitReason{""};
|
||||
int64_t exitCode{-1};
|
||||
uint64_t instructionCount{0};
|
||||
bool hasCallback = false; // true iff this hook wasm has a cbak function
|
||||
bool isCallback =
|
||||
false; // true iff this hook execution is a callback in action
|
||||
bool isStrong = false;
|
||||
uint32_t wasmParam = 0;
|
||||
uint32_t overrideCount = 0;
|
||||
uint8_t hookChainPosition = 0;
|
||||
bool foreignStateSetDisabled = false;
|
||||
bool executeAgainAsWeak =
|
||||
false; // hook_again allows strong pre-apply to nominate
|
||||
// additional weak post-apply execution
|
||||
std::shared_ptr<STObject const> provisionalMeta;
|
||||
};
|
||||
|
||||
class HookExecutor;
|
||||
|
||||
struct SlotEntry
|
||||
{
|
||||
std::vector<uint8_t> id;
|
||||
std::shared_ptr<const ripple::STObject> storage;
|
||||
const ripple::STBase* entry; // raw pointer into the storage, that can be
|
||||
// freely pointed around inside
|
||||
};
|
||||
|
||||
struct HookContext
|
||||
{
|
||||
ripple::ApplyContext& applyCtx;
|
||||
// slots are used up by requesting objects from inside the hook
|
||||
// the map stores pairs consisting of a memory view and whatever shared or
|
||||
// unique ptr is required to keep the underlying object alive for the
|
||||
// duration of the hook's execution slot number -> { keylet or hash, {
|
||||
// pointer to current object, storage for that object } }
|
||||
std::map<int, SlotEntry> slot{};
|
||||
uint8_t slot_counter{1};
|
||||
std::queue<int> slot_free{};
|
||||
int64_t expected_etxn_count{-1}; // make this a 64bit int so the uint32
|
||||
// from the hookapi cant overflow it
|
||||
uint8_t emit_nonce_counter{
|
||||
0}; // incremented whenever nonce is called to ensure unique nonces
|
||||
uint8_t ledger_nonce_counter{0};
|
||||
std::map<ripple::uint256, bool> nonce_used{};
|
||||
uint32_t generation =
|
||||
0; // used for caching, only generated when txn_generation is called
|
||||
uint64_t burden =
|
||||
0; // used for caching, only generated when txn_burden is called
|
||||
std::map<uint32_t, uint32_t>
|
||||
guard_map{}; // iteration guard map <id -> upto_iteration>
|
||||
HookResult result;
|
||||
std::optional<ripple::STObject>
|
||||
emitFailure; // if this is a callback from a failed
|
||||
// emitted txn then this optional becomes
|
||||
// populated with the SLE
|
||||
const HookExecutor* module = 0;
|
||||
};
|
||||
|
||||
ripple::TER
|
||||
setHookState(
|
||||
ripple::ApplyContext& applyCtx,
|
||||
ripple::AccountID const& acc,
|
||||
ripple::uint256 const& ns,
|
||||
ripple::uint256 const& key,
|
||||
ripple::Slice const& data);
|
||||
|
||||
// write hook execution metadata and remove emitted transaction ledger entries
|
||||
ripple::TER
|
||||
finalizeHookResult(
|
||||
hook::HookResult& hookResult,
|
||||
ripple::ApplyContext&,
|
||||
bool doEmit);
|
||||
|
||||
// write state map to ledger
|
||||
ripple::TER
|
||||
finalizeHookState(
|
||||
HookStateMap const&,
|
||||
ripple::ApplyContext&,
|
||||
ripple::uint256 const&);
|
||||
|
||||
// if the txn being executed was an emitted txn then this removes it from the
|
||||
// emission directory
|
||||
ripple::TER
|
||||
removeEmissionEntry(ripple::ApplyContext& applyCtx);
|
||||
|
||||
bool /* retval of true means an error */
|
||||
gatherHookParameters(
|
||||
std::shared_ptr<ripple::STLedgerEntry> const& hookDef,
|
||||
ripple::STObject const* hookObj,
|
||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>>& parameters,
|
||||
beast::Journal const& j_);
|
||||
|
||||
// RH TODO: call destruct for these on rippled shutdown
|
||||
#define ADD_HOOK_FUNCTION(F, ctx) \
|
||||
{ \
|
||||
WasmEdge_FunctionInstanceContext* hf = \
|
||||
WasmEdge_FunctionInstanceCreate( \
|
||||
hook_api::WasmFunctionType##F, \
|
||||
hook_api::WasmFunction##F, \
|
||||
(void*)(&ctx), \
|
||||
0); \
|
||||
WasmEdge_ImportObjectAddFunction( \
|
||||
importObj, hook_api::WasmFunctionName##F, hf); \
|
||||
}
|
||||
|
||||
#define HR_ACC() hookResult.account << "-" << hookResult.otxnAccount
|
||||
#define HC_ACC() hookCtx.result.account << "-" << hookCtx.result.otxnAccount
|
||||
|
||||
// create these once at boot and keep them
|
||||
static WasmEdge_String exportName = WasmEdge_StringCreateByCString("env");
|
||||
static WasmEdge_String tableName = WasmEdge_StringCreateByCString("table");
|
||||
static auto* tableType = WasmEdge_TableTypeCreate(
|
||||
WasmEdge_RefType_FuncRef,
|
||||
{.HasMax = true, .Min = 10, .Max = 20});
|
||||
static auto* memType =
|
||||
WasmEdge_MemoryTypeCreate({.HasMax = true, .Min = 1, .Max = 1});
|
||||
static WasmEdge_String memName = WasmEdge_StringCreateByCString("memory");
|
||||
static WasmEdge_String cbakFunctionName =
|
||||
WasmEdge_StringCreateByCString("cbak");
|
||||
static WasmEdge_String hookFunctionName =
|
||||
WasmEdge_StringCreateByCString("hook");
|
||||
|
||||
// see: lib/system/allocator.cpp
|
||||
#define WasmEdge_kPageSize 65536ULL
|
||||
|
||||
/**
|
||||
* HookExecutor is effectively a two-part function:
|
||||
* The first part sets up the Hook Api inside the wasm import, ready for use
|
||||
* (this is done during object construction.)
|
||||
* The second part is actually executing webassembly instructions
|
||||
* this is done during execteWasm function.
|
||||
* The instance is single use.
|
||||
*/
|
||||
class HookExecutor
|
||||
{
|
||||
private:
|
||||
bool spent = false; // a HookExecutor can only be used once
|
||||
|
||||
public:
|
||||
HookContext hookCtx;
|
||||
WasmEdge_ImportObjectContext* importObj;
|
||||
|
||||
/**
|
||||
* Validate that a web assembly blob can be loaded by wasmedge
|
||||
*/
|
||||
static std::optional<std::string>
|
||||
validateWasm(const void* wasm, size_t len)
|
||||
{
|
||||
std::optional<std::string> ret;
|
||||
WasmEdge_ConfigureContext* confCtx = WasmEdge_ConfigureCreate();
|
||||
WasmEdge_VMContext* vmCtx = WasmEdge_VMCreate(confCtx, NULL);
|
||||
WasmEdge_Result res = WasmEdge_VMLoadWasmFromBuffer(
|
||||
vmCtx, reinterpret_cast<const uint8_t*>(wasm), len);
|
||||
if (!WasmEdge_ResultOK(res))
|
||||
ret = "VMLoadWasmFromBuffer failed";
|
||||
else
|
||||
{
|
||||
res = WasmEdge_VMValidate(vmCtx);
|
||||
if (!WasmEdge_ResultOK(res))
|
||||
ret = "VMValidate failed";
|
||||
}
|
||||
WasmEdge_VMDelete(vmCtx);
|
||||
WasmEdge_ConfigureDelete(confCtx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute web assembly byte code against the constructed Hook Context
|
||||
* Once execution has occured the exector is spent and cannot be used again
|
||||
* and should be destructed Information about the execution is populated
|
||||
* into hookCtx
|
||||
*/
|
||||
void
|
||||
executeWasm(
|
||||
const void* wasm,
|
||||
size_t len,
|
||||
bool callback,
|
||||
uint32_t wasmParam,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// HookExecutor can only execute once
|
||||
assert(!spent);
|
||||
|
||||
spent = true;
|
||||
|
||||
JLOG(j.trace()) << "HookInfo[" << HC_ACC()
|
||||
<< "]: creating wasm instance";
|
||||
|
||||
WasmEdge_ConfigureContext* confCtx = WasmEdge_ConfigureCreate();
|
||||
WasmEdge_ConfigureStatisticsSetInstructionCounting(confCtx, true);
|
||||
WasmEdge_VMContext* vmCtx = WasmEdge_VMCreate(confCtx, NULL);
|
||||
|
||||
WasmEdge_Result res =
|
||||
WasmEdge_VMRegisterModuleFromImport(vmCtx, this->importObj);
|
||||
if (!WasmEdge_ResultOK(res))
|
||||
{
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
JLOG(j.trace())
|
||||
<< "HookError[" << HC_ACC() << "]: Import phase failed "
|
||||
<< WasmEdge_ResultGetMessage(res);
|
||||
}
|
||||
else
|
||||
{
|
||||
WasmEdge_Value params[1] = {
|
||||
WasmEdge_ValueGenI32((int64_t)wasmParam)};
|
||||
WasmEdge_Value returns[1];
|
||||
|
||||
/*
|
||||
printf("executing hook wasm:\n");
|
||||
for (int j = 0; j < len; j++)
|
||||
{
|
||||
if (j % 16 == 0)
|
||||
printf("0x%08X:\t", j);
|
||||
|
||||
printf("%02X%s", (reinterpret_cast<const uint8_t*>(wasm))[j],
|
||||
(j % 16 == 15 ? "\n" :
|
||||
(j % 4 == 3 ? " " :
|
||||
(j % 2 == 1 ? " " : ""))));
|
||||
}
|
||||
printf("\n----\n");
|
||||
*/
|
||||
|
||||
res = WasmEdge_VMRunWasmFromBuffer(
|
||||
vmCtx,
|
||||
reinterpret_cast<const uint8_t*>(wasm),
|
||||
len,
|
||||
callback ? cbakFunctionName : hookFunctionName,
|
||||
params,
|
||||
1,
|
||||
returns,
|
||||
1);
|
||||
|
||||
if (!WasmEdge_ResultOK(res))
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "HookError[" << HC_ACC() << "]: WASM VM error "
|
||||
<< WasmEdge_ResultGetMessage(res);
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* statsCtx = WasmEdge_VMGetStatisticsContext(vmCtx);
|
||||
hookCtx.result.instructionCount =
|
||||
WasmEdge_StatisticsGetInstrCount(statsCtx);
|
||||
}
|
||||
}
|
||||
|
||||
WasmEdge_ConfigureDelete(confCtx);
|
||||
WasmEdge_VMDelete(vmCtx);
|
||||
}
|
||||
|
||||
HookExecutor(HookContext& ctx)
|
||||
: hookCtx(ctx), importObj(WasmEdge_ImportObjectCreate(exportName))
|
||||
{
|
||||
ctx.module = this;
|
||||
|
||||
WasmEdge_LogSetDebugLevel();
|
||||
|
||||
ADD_HOOK_FUNCTION(_g, ctx);
|
||||
ADD_HOOK_FUNCTION(accept, ctx);
|
||||
ADD_HOOK_FUNCTION(rollback, ctx);
|
||||
ADD_HOOK_FUNCTION(util_raddr, ctx);
|
||||
ADD_HOOK_FUNCTION(util_accid, ctx);
|
||||
ADD_HOOK_FUNCTION(util_verify, ctx);
|
||||
ADD_HOOK_FUNCTION(util_sha512h, ctx);
|
||||
ADD_HOOK_FUNCTION(sto_validate, ctx);
|
||||
ADD_HOOK_FUNCTION(sto_subfield, ctx);
|
||||
ADD_HOOK_FUNCTION(sto_subarray, ctx);
|
||||
ADD_HOOK_FUNCTION(sto_emplace, ctx);
|
||||
ADD_HOOK_FUNCTION(sto_erase, ctx);
|
||||
ADD_HOOK_FUNCTION(util_keylet, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(emit, ctx);
|
||||
ADD_HOOK_FUNCTION(etxn_burden, ctx);
|
||||
ADD_HOOK_FUNCTION(etxn_fee_base, ctx);
|
||||
ADD_HOOK_FUNCTION(etxn_details, ctx);
|
||||
ADD_HOOK_FUNCTION(etxn_reserve, ctx);
|
||||
ADD_HOOK_FUNCTION(etxn_generation, ctx);
|
||||
ADD_HOOK_FUNCTION(etxn_nonce, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(float_set, ctx);
|
||||
ADD_HOOK_FUNCTION(float_multiply, ctx);
|
||||
ADD_HOOK_FUNCTION(float_mulratio, ctx);
|
||||
ADD_HOOK_FUNCTION(float_negate, ctx);
|
||||
ADD_HOOK_FUNCTION(float_compare, ctx);
|
||||
ADD_HOOK_FUNCTION(float_sum, ctx);
|
||||
ADD_HOOK_FUNCTION(float_sto, ctx);
|
||||
ADD_HOOK_FUNCTION(float_sto_set, ctx);
|
||||
ADD_HOOK_FUNCTION(float_invert, ctx);
|
||||
ADD_HOOK_FUNCTION(float_mantissa, ctx);
|
||||
ADD_HOOK_FUNCTION(float_exponent, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(float_divide, ctx);
|
||||
ADD_HOOK_FUNCTION(float_one, ctx);
|
||||
ADD_HOOK_FUNCTION(float_mantissa, ctx);
|
||||
ADD_HOOK_FUNCTION(float_mantissa_set, ctx);
|
||||
ADD_HOOK_FUNCTION(float_exponent, ctx);
|
||||
ADD_HOOK_FUNCTION(float_exponent_set, ctx);
|
||||
ADD_HOOK_FUNCTION(float_sign, ctx);
|
||||
ADD_HOOK_FUNCTION(float_sign_set, ctx);
|
||||
ADD_HOOK_FUNCTION(float_int, ctx);
|
||||
ADD_HOOK_FUNCTION(float_log, ctx);
|
||||
ADD_HOOK_FUNCTION(float_root, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(otxn_burden, ctx);
|
||||
ADD_HOOK_FUNCTION(otxn_generation, ctx);
|
||||
ADD_HOOK_FUNCTION(otxn_field_txt, ctx);
|
||||
ADD_HOOK_FUNCTION(otxn_field, ctx);
|
||||
ADD_HOOK_FUNCTION(otxn_id, ctx);
|
||||
ADD_HOOK_FUNCTION(otxn_type, ctx);
|
||||
ADD_HOOK_FUNCTION(otxn_slot, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_account, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_hash, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_again, ctx);
|
||||
ADD_HOOK_FUNCTION(fee_base, ctx);
|
||||
ADD_HOOK_FUNCTION(ledger_seq, ctx);
|
||||
ADD_HOOK_FUNCTION(ledger_last_hash, ctx);
|
||||
ADD_HOOK_FUNCTION(ledger_last_time, ctx);
|
||||
ADD_HOOK_FUNCTION(ledger_nonce, ctx);
|
||||
ADD_HOOK_FUNCTION(ledger_keylet, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(hook_param, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_param_set, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_skip, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_pos, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_namespace, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(state, ctx);
|
||||
ADD_HOOK_FUNCTION(state_foreign, ctx);
|
||||
ADD_HOOK_FUNCTION(state_set, ctx);
|
||||
ADD_HOOK_FUNCTION(state_foreign_set, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(slot, ctx);
|
||||
ADD_HOOK_FUNCTION(slot_clear, ctx);
|
||||
ADD_HOOK_FUNCTION(slot_count, ctx);
|
||||
ADD_HOOK_FUNCTION(slot_id, ctx);
|
||||
ADD_HOOK_FUNCTION(slot_set, ctx);
|
||||
ADD_HOOK_FUNCTION(slot_size, ctx);
|
||||
ADD_HOOK_FUNCTION(slot_subarray, ctx);
|
||||
ADD_HOOK_FUNCTION(slot_subfield, ctx);
|
||||
ADD_HOOK_FUNCTION(slot_type, ctx);
|
||||
ADD_HOOK_FUNCTION(slot_float, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(trace, ctx);
|
||||
ADD_HOOK_FUNCTION(trace_slot, ctx);
|
||||
ADD_HOOK_FUNCTION(trace_num, ctx);
|
||||
ADD_HOOK_FUNCTION(trace_float, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(meta_slot, ctx);
|
||||
|
||||
WasmEdge_TableInstanceContext* hostTable =
|
||||
WasmEdge_TableInstanceCreate(tableType);
|
||||
WasmEdge_ImportObjectAddTable(importObj, tableName, hostTable);
|
||||
WasmEdge_MemoryInstanceContext* hostMem =
|
||||
WasmEdge_MemoryInstanceCreate(memType);
|
||||
WasmEdge_ImportObjectAddMemory(importObj, memName, hostMem);
|
||||
}
|
||||
|
||||
~HookExecutor()
|
||||
{
|
||||
WasmEdge_ImportObjectDelete(importObj);
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -112,6 +112,10 @@ public:
|
||||
std::shared_ptr<OpenView const>
|
||||
current() const;
|
||||
|
||||
// not mutex guarded, only use if you are sure
|
||||
std::shared_ptr<OpenView const>
|
||||
current_unsafe() const;
|
||||
|
||||
/** Modify the open ledger
|
||||
|
||||
Thread safety:
|
||||
|
||||
@@ -951,6 +951,9 @@ LedgerMaster::checkAccept(std::shared_ptr<Ledger const> const& ledger)
|
||||
{
|
||||
// Can we accept this ledger as our new last fully-validated ledger
|
||||
|
||||
JLOG(m_journal.info()) << "=========> checkAccept (" << ledger->info().seq
|
||||
<< ")\n";
|
||||
|
||||
if (!canBeCurrent(ledger))
|
||||
return;
|
||||
|
||||
@@ -980,6 +983,9 @@ LedgerMaster::checkAccept(std::shared_ptr<Ledger const> const& ledger)
|
||||
ledger->setValidated();
|
||||
ledger->setFull();
|
||||
setValidLedger(ledger);
|
||||
|
||||
JLOG(m_journal.info()) << "=========> checkAccept (" << ledger->info().seq
|
||||
<< ") = validated\n";
|
||||
if (!mPubLedger)
|
||||
{
|
||||
pendSaveValidated(app_, ledger, true, true);
|
||||
|
||||
@@ -53,6 +53,12 @@ OpenLedger::current() const
|
||||
return current_;
|
||||
}
|
||||
|
||||
std::shared_ptr<OpenView const>
|
||||
OpenLedger::current_unsafe() const
|
||||
{
|
||||
return current_;
|
||||
}
|
||||
|
||||
bool
|
||||
OpenLedger::modify(modify_type const& f)
|
||||
{
|
||||
@@ -121,6 +127,11 @@ OpenLedger::accept(
|
||||
{
|
||||
auto const& tx = txpair.first;
|
||||
auto const txId = tx->getTransactionID();
|
||||
|
||||
// skip emitted txns
|
||||
if (tx->isFieldPresent(sfEmitDetails))
|
||||
continue;
|
||||
|
||||
if (auto const toSkip = app.getHashRouter().shouldRelay(txId))
|
||||
{
|
||||
JLOG(j_.debug()) << "Relaying recovered tx " << txId;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <xrpld/app/consensus/RCLConsensus.h>
|
||||
#include <xrpld/app/consensus/RCLValidations.h>
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/ledger/AcceptedLedger.h>
|
||||
#include <xrpld/app/ledger/InboundLedgers.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
@@ -1140,6 +1141,16 @@ NetworkOPsImp::submitTransaction(std::shared_ptr<STTx const> const& iTrans)
|
||||
return;
|
||||
}
|
||||
|
||||
auto const view = m_ledgerMaster.getCurrentLedger();
|
||||
|
||||
// Enforce Network bar for emitted txn
|
||||
if (view->rules().enabled(featureHooks) && hook::isEmittedTxn(*iTrans))
|
||||
{
|
||||
JLOG(m_journal.warn())
|
||||
<< "Submitted transaction invalid: EmitDetails present.";
|
||||
return;
|
||||
}
|
||||
|
||||
// this is an asynchronous interface
|
||||
auto const trans = sterilize(*iTrans);
|
||||
|
||||
@@ -1193,6 +1204,24 @@ NetworkOPsImp::processTransaction(
|
||||
FailHard failType)
|
||||
{
|
||||
auto ev = m_job_queue.makeLoadEvent(jtTXN_PROC, "ProcessTXN");
|
||||
|
||||
auto const view = m_ledgerMaster.getCurrentLedger();
|
||||
|
||||
// This function is called by several different parts of the codebase
|
||||
// under no circumstances will we ever accept an emitted txn from the
|
||||
// network. Emitted txns are *always* and *only* inserted by TxQ::accept,
|
||||
// and only arise from processing ltEMITTED out of the EMITTED_DIR. This
|
||||
// isn't always an error because a fetch pack etc might include an emitted
|
||||
// txn and if this is a deliberate attempt to send an emitted txn over the
|
||||
// network it was already billed in PeerImp
|
||||
if (view->rules().enabled(featureHooks) &&
|
||||
hook::isEmittedTxn(*transaction->getSTransaction()))
|
||||
{
|
||||
// RH NOTE: cannot set SF_BAD because if the tx will be generated by a
|
||||
// hook we are about to execute
|
||||
return;
|
||||
}
|
||||
|
||||
auto const newFlags = app_.getHashRouter().getFlags(transaction->getID());
|
||||
|
||||
if ((newFlags & SF_BAD) != 0)
|
||||
@@ -1207,7 +1236,6 @@ NetworkOPsImp::processTransaction(
|
||||
// NOTE eahennis - I think this check is redundant,
|
||||
// but I'm not 100% sure yet.
|
||||
// If so, only cost is looking up HashRouter flags.
|
||||
auto const view = m_ledgerMaster.getCurrentLedger();
|
||||
auto const [validity, reason] = checkValidity(
|
||||
app_.getHashRouter(),
|
||||
*transaction->getSTransaction(),
|
||||
@@ -1244,6 +1272,20 @@ NetworkOPsImp::doTransactionAsync(
|
||||
{
|
||||
std::lock_guard lock(mMutex);
|
||||
|
||||
auto const view = m_ledgerMaster.getCurrentLedger();
|
||||
|
||||
// Enforce Network bar for emitted txn
|
||||
if (view->rules().enabled(featureHooks) &&
|
||||
hook::isEmittedTxn(*transaction->getSTransaction()))
|
||||
{
|
||||
JLOG(m_journal.info())
|
||||
<< "Transaction received over network has EmitDetails, discarding.";
|
||||
// RH NOTE: cannot set SF_BAD because if the tx will be generated by a
|
||||
// hook we are about to execute then this would poison consensus for
|
||||
// that emitted tx RH TODO: (severely) charge peer who sent
|
||||
return;
|
||||
}
|
||||
|
||||
if (transaction->getApplying())
|
||||
return;
|
||||
|
||||
@@ -1269,6 +1311,20 @@ NetworkOPsImp::doTransactionSync(
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
|
||||
auto const view = m_ledgerMaster.getCurrentLedger();
|
||||
|
||||
// Enforce Network bar for emitted txn
|
||||
if (view->rules().enabled(featureHooks) &&
|
||||
hook::isEmittedTxn(*transaction->getSTransaction()))
|
||||
{
|
||||
JLOG(m_journal.info())
|
||||
<< "Transaction received over network has EmitDetails, discarding.";
|
||||
// RH NOTE: cannot set SF_BAD because if the tx will be generated by a
|
||||
// hook we are about to execute then this would poison consensus for
|
||||
// that emitted tx RH TODO: (severely) charge peer who sent
|
||||
return;
|
||||
}
|
||||
|
||||
if (!transaction->getApplying())
|
||||
{
|
||||
mTransactions.push_back(
|
||||
@@ -1475,7 +1531,10 @@ NetworkOPsImp::apply(std::unique_lock<std::mutex>& batchLock)
|
||||
auto const toSkip =
|
||||
app_.getHashRouter().shouldRelay(e.transaction->getID());
|
||||
|
||||
if (toSkip)
|
||||
bool const isEmitted =
|
||||
hook::isEmittedTxn(*(e.transaction->getSTransaction()));
|
||||
|
||||
if (toSkip && !isEmitted)
|
||||
{
|
||||
protocol::TMTransaction tx;
|
||||
Serializer s;
|
||||
@@ -2777,6 +2836,10 @@ NetworkOPsImp::pubProposedTransaction(
|
||||
std::shared_ptr<STTx const> const& transaction,
|
||||
TER result)
|
||||
{
|
||||
// never publish emitted txns
|
||||
if (hook::isEmittedTxn(*transaction))
|
||||
return;
|
||||
|
||||
MultiApiJson jvObj =
|
||||
transJson(transaction, result, false, ledger, std::nullopt);
|
||||
|
||||
|
||||
@@ -217,6 +217,8 @@ public:
|
||||
FeeLevel64 feeLevel;
|
||||
/// LastValidLedger field of the queued transaction, if any
|
||||
std::optional<LedgerIndex> lastValid;
|
||||
/// FirstValidLedger field of the queued transaction, if any
|
||||
std::optional<LedgerIndex> firstValid;
|
||||
/** Potential @ref TxConsequences of applying the queued transaction
|
||||
to the open ledger.
|
||||
*/
|
||||
@@ -357,7 +359,8 @@ public:
|
||||
@returns a `Json objectvalue`
|
||||
*/
|
||||
Json::Value
|
||||
doRPC(Application& app) const;
|
||||
doRPC(Application& app, std::optional<XRPAmount> hookFee = std::nullopt)
|
||||
const;
|
||||
|
||||
private:
|
||||
// Implementation for nextQueuableSeq(). The passed lock must be held.
|
||||
@@ -526,6 +529,7 @@ private:
|
||||
AccountID const account;
|
||||
/// Expiration ledger for the transaction
|
||||
/// (`sfLastLedgerSequence` field).
|
||||
std::optional<LedgerIndex> const firstValid;
|
||||
std::optional<LedgerIndex> const lastValid;
|
||||
/// Transaction SeqProxy number
|
||||
/// (`sfSequence` or `sfTicketSequence` field).
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
#include <xrpld/app/misc/TxQ.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
@@ -52,7 +53,10 @@ getFeeLevelPaid(ReadView const& view, STTx const& tx)
|
||||
return std::pair{baseFee + mod, feePaid + mod};
|
||||
}();
|
||||
|
||||
XRPL_ASSERT(baseFee.signum() > 0, "ripple::getFeeLevelPaid : positive fee");
|
||||
// RH TODO: check if >= 0 is appropriate for hooks / emitted txn
|
||||
// this was previously > 0 and a crash bug in tn2
|
||||
XRPL_ASSERT(
|
||||
baseFee.signum() >= 0, "ripple::getFeeLevelPaid : positive fee");
|
||||
if (effectiveFeePaid.signum() <= 0 || baseFee.signum() <= 0)
|
||||
{
|
||||
return FeeLevel64(0);
|
||||
@@ -70,6 +74,14 @@ getLastLedgerSequence(STTx const& tx)
|
||||
return tx.getFieldU32(sfLastLedgerSequence);
|
||||
}
|
||||
|
||||
static std::optional<LedgerIndex>
|
||||
getFirstLedgerSequence(STTx const& tx)
|
||||
{
|
||||
if (!tx.isFieldPresent(sfFirstLedgerSequence))
|
||||
return std::nullopt;
|
||||
return tx.getFieldU32(sfFirstLedgerSequence);
|
||||
}
|
||||
|
||||
static FeeLevel64
|
||||
increase(FeeLevel64 level, std::uint32_t increasePercent)
|
||||
{
|
||||
@@ -285,6 +297,7 @@ TxQ::MaybeTx::MaybeTx(
|
||||
, feeLevel(feeLevel_)
|
||||
, txID(txID_)
|
||||
, account(txn_->getAccountID(sfAccount))
|
||||
, firstValid(getFirstLedgerSequence(*txn_))
|
||||
, lastValid(getLastLedgerSequence(*txn_))
|
||||
, seqProxy(txn_->getSeqProxy())
|
||||
, retriesRemaining(retriesAllowed)
|
||||
@@ -1445,6 +1458,150 @@ TxQ::accept(Application& app, OpenView& view)
|
||||
|
||||
auto const metricsSnapshot = feeMetrics_.getSnapshot();
|
||||
|
||||
// Inject emitted transactions if any
|
||||
if (view.rules().enabled(featureHooks))
|
||||
do
|
||||
{
|
||||
Keylet const emittedDirKeylet{keylet::emittedDir()};
|
||||
if (dirIsEmpty(view, emittedDirKeylet))
|
||||
break;
|
||||
|
||||
std::shared_ptr<SLE const> sleDirNode{};
|
||||
unsigned int uDirEntry{0};
|
||||
uint256 dirEntry{beast::zero};
|
||||
|
||||
if (!cdirFirst(
|
||||
view,
|
||||
emittedDirKeylet.key,
|
||||
sleDirNode,
|
||||
uDirEntry,
|
||||
dirEntry))
|
||||
break;
|
||||
|
||||
do
|
||||
{
|
||||
Keylet const itemKeylet{ltCHILD, dirEntry};
|
||||
auto sleItem = view.read(itemKeylet);
|
||||
if (!sleItem)
|
||||
{
|
||||
// Directory node has an invalid index. Bail out.
|
||||
JLOG(j_.fatal())
|
||||
<< "EmittedTxn processing: directory node in ledger "
|
||||
<< view.seq()
|
||||
<< " has index to object that is missing: "
|
||||
<< to_string(dirEntry);
|
||||
break;
|
||||
}
|
||||
|
||||
LedgerEntryType const nodeType{
|
||||
safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
|
||||
|
||||
if (nodeType != ltEMITTED)
|
||||
{
|
||||
JLOG(j_.fatal())
|
||||
<< "EmittedTxn processing: emitted directory contained "
|
||||
"non ltEMITTED type";
|
||||
break;
|
||||
}
|
||||
|
||||
JLOG(j_.info()) << "Processing emitted txn: " << *sleItem;
|
||||
|
||||
auto const& emitted =
|
||||
const_cast<ripple::STLedgerEntry&>(*sleItem)
|
||||
.getField(sfEmittedTxn)
|
||||
.downcast<STObject>();
|
||||
|
||||
auto s = std::make_shared<ripple::Serializer>();
|
||||
emitted.add(*s);
|
||||
SerialIter sitTrans(s->slice()); // do we need slice?
|
||||
try
|
||||
{
|
||||
auto const& stpTrans =
|
||||
std::make_shared<STTx const>(std::ref(sitTrans));
|
||||
|
||||
if (!stpTrans->isFieldPresent(sfEmitDetails) ||
|
||||
!stpTrans->isFieldPresent(sfFirstLedgerSequence) ||
|
||||
!stpTrans->isFieldPresent(sfLastLedgerSequence))
|
||||
{
|
||||
JLOG(j_.warn()) << "Hook: Emission failure: "
|
||||
<< "sfEmitDetails or "
|
||||
"sfFirst/LastLedgerSeq missing.";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto seq = view.info().seq;
|
||||
auto txnHash = stpTrans->getTransactionID();
|
||||
|
||||
if (stpTrans->getFieldU32(sfLastLedgerSequence) < seq)
|
||||
{
|
||||
JLOG(j_.trace()) << "Hook: Emission failure, adding "
|
||||
"cleanup pseudotxn to ledger "
|
||||
<< seq;
|
||||
|
||||
auto const& emitDetails =
|
||||
const_cast<ripple::STTx&>(*stpTrans)
|
||||
.getField(sfEmitDetails)
|
||||
.downcast<STObject>();
|
||||
|
||||
STTx efTx(
|
||||
ttEMIT_FAILURE,
|
||||
[seq, txnHash, emitDetails](auto& obj) {
|
||||
obj[sfLedgerSequence] = seq;
|
||||
obj[sfTransactionHash] = txnHash;
|
||||
obj.emplace_back(emitDetails);
|
||||
/*std::unique_ptr<STBase> ed =
|
||||
std::make_unique<STBase>(emitDetails);
|
||||
ed->setFName(sfEmitDetails);
|
||||
obj.set(std::move(ed));*/
|
||||
});
|
||||
|
||||
uint256 txID = efTx.getTransactionID();
|
||||
|
||||
auto s = std::make_shared<ripple::Serializer>();
|
||||
efTx.add(*s);
|
||||
|
||||
// RH TODO: should this txn be added in a different way
|
||||
// to prevent any chance of failure
|
||||
app.getHashRouter().setFlags(txID, SF_PRIVATE2);
|
||||
view.rawTxInsert(txID, std::move(s), nullptr);
|
||||
ledgerChanged = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto fls = stpTrans->getFieldU32(sfFirstLedgerSequence);
|
||||
if (fls > view.info().seq)
|
||||
{
|
||||
JLOG(j_.info())
|
||||
<< "Holding TX " << stpTrans->getTransactionID()
|
||||
<< " for future ledger.";
|
||||
continue;
|
||||
}
|
||||
|
||||
// execution to here means we are adding the tx to the local
|
||||
// set
|
||||
if (fls >= view.info().seq)
|
||||
{
|
||||
app.getHashRouter().setFlags(txnHash, SF_PRIVATE2);
|
||||
view.rawTxInsert(
|
||||
stpTrans->getTransactionID(),
|
||||
std::move(s),
|
||||
nullptr);
|
||||
ledgerChanged = true;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
JLOG(j_.fatal())
|
||||
<< "EmittedTxn Processing: Failure: " << e.what()
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
} while (cdirNext(
|
||||
view, emittedDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
|
||||
|
||||
} while (0);
|
||||
|
||||
for (auto candidateIter = byFee_.begin(); candidateIter != byFee_.end();)
|
||||
{
|
||||
auto& account = byAccount_.at(candidateIter->account);
|
||||
@@ -1856,7 +2013,7 @@ TxQ::getTxs() const
|
||||
}
|
||||
|
||||
Json::Value
|
||||
TxQ::doRPC(Application& app) const
|
||||
TxQ::doRPC(Application& app, std::optional<XRPAmount> hookFee) const
|
||||
{
|
||||
auto const view = app.openLedger().current();
|
||||
if (!view)
|
||||
@@ -1887,11 +2044,15 @@ TxQ::doRPC(Application& app) const
|
||||
// If the base fee is 0 drops, but escalation has kicked in, treat the
|
||||
// base fee as if it is 1 drop, which makes the rest of the math
|
||||
// work.
|
||||
auto const effectiveBaseFee = [&baseFee, &metrics]() {
|
||||
if (!baseFee && metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
|
||||
return XRPAmount{1};
|
||||
return baseFee;
|
||||
}();
|
||||
auto const effectiveBaseFee =
|
||||
[&baseFee, &metrics]() {
|
||||
if (!baseFee &&
|
||||
metrics.openLedgerFeeLevel != metrics.referenceFeeLevel)
|
||||
return XRPAmount{1};
|
||||
return baseFee;
|
||||
}() +
|
||||
(hookFee.value_or(beast::zero));
|
||||
|
||||
auto& drops = ret[jss::drops] = Json::Value();
|
||||
|
||||
drops[jss::base_fee] = to_string(baseFee);
|
||||
|
||||
@@ -101,6 +101,12 @@ public:
|
||||
view_->rawDestroyXRP(fee);
|
||||
}
|
||||
|
||||
TxMeta
|
||||
generateProvisionalMeta()
|
||||
{
|
||||
return view_->generateProvisionalMeta(base_, tx, journal);
|
||||
}
|
||||
|
||||
/** Applies all invariant checkers one by one.
|
||||
|
||||
@param result the result generated by processing this transaction.
|
||||
@@ -110,6 +116,12 @@ public:
|
||||
TER
|
||||
checkInvariants(TER const result, XRPAmount const fee);
|
||||
|
||||
bool
|
||||
emitted()
|
||||
{
|
||||
return tx.isFieldPresent(sfEmitDetails);
|
||||
}
|
||||
|
||||
private:
|
||||
TER
|
||||
failInvariantCheck(TER const result);
|
||||
|
||||
@@ -60,11 +60,23 @@ CancelOffer::preclaim(PreclaimContext const& ctx)
|
||||
if (!sle)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
bool hooksEnabled = ctx.view.rules().enabled(featureHooks);
|
||||
|
||||
auto const offerID = ctx.tx[~sfOfferID];
|
||||
|
||||
if ((*sle)[sfSequence] <= offerSequence)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: " << "Sequence "
|
||||
<< offerSequence << " is invalid.";
|
||||
return temBAD_SEQUENCE;
|
||||
if (hooksEnabled && offerID && offerSequence == 0)
|
||||
{
|
||||
// pass, here the txn provides offerID instead of offerSequence
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "Malformed transaction: "
|
||||
<< "Sequence " << offerSequence << " is invalid.";
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -81,13 +93,30 @@ CancelOffer::doApply()
|
||||
if (!sle)
|
||||
return tefINTERNAL;
|
||||
|
||||
if (auto sleOffer = view().peek(keylet::offer(account_, offerSequence)))
|
||||
bool hooksEnabled = view().rules().enabled(featureHooks);
|
||||
|
||||
std::optional<uint256> offerID;
|
||||
|
||||
if (hooksEnabled)
|
||||
offerID = ctx_.tx[~sfOfferID];
|
||||
|
||||
Keylet cancel = hooksEnabled && offerID && offerSequence == 0
|
||||
? Keylet(ltOFFER, *offerID)
|
||||
: keylet::offer(account_, offerSequence);
|
||||
|
||||
if (auto sleOffer = view().peek(cancel))
|
||||
{
|
||||
JLOG(j_.debug()) << "Trying to cancel offer #" << offerSequence;
|
||||
if (offerID)
|
||||
JLOG(j_.debug()) << "Trying to cancel offer :" << *offerID;
|
||||
else
|
||||
JLOG(j_.debug()) << "Trying to cancel offer #" << offerSequence;
|
||||
return offerDelete(view(), sleOffer, ctx_.app.journal("View"));
|
||||
}
|
||||
|
||||
JLOG(j_.debug()) << "Offer #" << offerSequence << " can't be found.";
|
||||
if (offerID)
|
||||
JLOG(j_.debug()) << "Offer :" << *offerID << " can't be found.";
|
||||
else
|
||||
JLOG(j_.debug()) << "Offer #" << offerSequence << " can't be found.";
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
case ttEMIT_FAILURE:
|
||||
return tesSUCCESS;
|
||||
default:
|
||||
return temUNKNOWN;
|
||||
@@ -148,6 +149,8 @@ Change::doApply()
|
||||
return applyFee();
|
||||
case ttUNL_MODIFY:
|
||||
return applyUNLModify();
|
||||
case ttEMIT_FAILURE:
|
||||
return applyEmitFailure();
|
||||
default:
|
||||
UNREACHABLE("ripple::Change::doApply : invalid transaction type");
|
||||
return tefFAILURE;
|
||||
@@ -382,6 +385,47 @@ Change::applyFee()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
Change::applyEmitFailure()
|
||||
{
|
||||
uint256 txnID(ctx_.tx.getFieldH256(sfTransactionHash));
|
||||
do
|
||||
{
|
||||
JLOG(j_.warn()) << "HookEmit[" << txnID
|
||||
<< "]: ttEmitFailure removing emitted txn";
|
||||
|
||||
auto key = keylet::emitted(txnID);
|
||||
|
||||
auto const& sle = view().peek(key);
|
||||
|
||||
if (!sle)
|
||||
{
|
||||
// RH NOTE: This will now be the normal execution path, the
|
||||
// alternative will only occur if something went really wrong with
|
||||
// the hook callback
|
||||
// JLOG(j_.warn())
|
||||
// << "HookError[" << txnID << "]: ttEmitFailure
|
||||
// (Change) tried to remove already removed
|
||||
// emittedtxn";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!view().dirRemove(
|
||||
keylet::emittedDir(),
|
||||
sle->getFieldU64(sfOwnerNode),
|
||||
key,
|
||||
false))
|
||||
{
|
||||
JLOG(j_.fatal()) << "HookError[" << txnID
|
||||
<< "]: ttEmitFailure (Change) tefBAD_LEDGER";
|
||||
return tefBAD_LEDGER;
|
||||
}
|
||||
|
||||
view().erase(sle);
|
||||
} while (0);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
Change::applyUNLModify()
|
||||
{
|
||||
|
||||
@@ -67,11 +67,15 @@ private:
|
||||
|
||||
TER
|
||||
applyUNLModify();
|
||||
|
||||
TER
|
||||
applyEmitFailure();
|
||||
};
|
||||
|
||||
using EnableAmendment = Change;
|
||||
using SetFee = Change;
|
||||
using UNLModify = Change;
|
||||
using EmitFailure = Change;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -185,7 +185,9 @@ CreateCheck::doApply()
|
||||
// Note that we use the value from the sequence or ticket as the
|
||||
// Check sequence. For more explanation see comments in SeqProxy.h.
|
||||
std::uint32_t const seq = ctx_.tx.getSeqProxy().value();
|
||||
Keylet const checkKeylet = keylet::check(account_, seq);
|
||||
|
||||
Keylet const checkKeylet = keylet::check(account_, seqID(ctx_));
|
||||
|
||||
auto sleCheck = std::make_shared<SLE>(checkKeylet);
|
||||
|
||||
sleCheck->setAccountID(sfAccount, account_);
|
||||
|
||||
@@ -73,13 +73,22 @@ CreateOffer::preflight(PreflightContext const& ctx)
|
||||
return temBAD_EXPIRATION;
|
||||
}
|
||||
|
||||
if (auto const cancelSequence = tx[~sfOfferSequence];
|
||||
cancelSequence && *cancelSequence == 0)
|
||||
auto const offerID = tx[~sfOfferID];
|
||||
|
||||
auto const cancelSequence = tx[~sfOfferSequence];
|
||||
if (cancelSequence && *cancelSequence == 0)
|
||||
{
|
||||
JLOG(j.debug()) << "Malformed offer: bad cancel sequence";
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
|
||||
if (offerID && cancelSequence)
|
||||
{
|
||||
JLOG(j.debug()) << "Malformed offer: expect exactly zero or one of: "
|
||||
"sfOfferID, sfCancelSequence";
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
|
||||
STAmount saTakerPays = tx[sfTakerPays];
|
||||
STAmount saTakerGets = tx[sfTakerGets];
|
||||
|
||||
@@ -163,6 +172,15 @@ CreateOffer::preclaim(PreclaimContext const& ctx)
|
||||
return tecUNFUNDED_OFFER;
|
||||
}
|
||||
|
||||
bool hooksEnabled = ctx.view.rules().enabled(featureHooks);
|
||||
auto const offerID = ctx.tx[~sfOfferID];
|
||||
|
||||
if (!hooksEnabled && offerID)
|
||||
return temDISABLED;
|
||||
|
||||
if (offerID && cancelSequence)
|
||||
return temBAD_SEQUENCE;
|
||||
|
||||
// This can probably be simplified to make sure that you cancel sequences
|
||||
// before the transaction sequence number.
|
||||
if (cancelSequence && (uAccountSequence <= *cancelSequence))
|
||||
@@ -929,6 +947,8 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
{
|
||||
using beast::zero;
|
||||
|
||||
bool hooksEnabled = sb.rules().enabled(featureHooks);
|
||||
|
||||
std::uint32_t const uTxFlags = ctx_.tx.getFlags();
|
||||
|
||||
bool const bPassive(uTxFlags & tfPassive);
|
||||
@@ -940,6 +960,10 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
auto saTakerGets = ctx_.tx[sfTakerGets];
|
||||
|
||||
auto const cancelSequence = ctx_.tx[~sfOfferSequence];
|
||||
std::optional<uint256> offerID;
|
||||
if (hooksEnabled)
|
||||
offerID =
|
||||
ctx_.tx[~sfOfferID]; // this can be used in place of cancel seq
|
||||
|
||||
// Note that we we use the value from the sequence or ticket as the
|
||||
// offer sequence. For more explanation see comments in SeqProxy.h.
|
||||
@@ -955,17 +979,22 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
TER result = tesSUCCESS;
|
||||
|
||||
// Process a cancellation request that's passed along with an offer.
|
||||
if (cancelSequence)
|
||||
if (cancelSequence || offerID)
|
||||
{
|
||||
auto const sleCancel =
|
||||
sb.peek(keylet::offer(account_, *cancelSequence));
|
||||
Keylet cancel = offerID ? Keylet(ltOFFER, *offerID)
|
||||
: keylet::offer(account_, *cancelSequence);
|
||||
|
||||
auto const sleCancel = sb.peek(cancel);
|
||||
|
||||
// It's not an error to not find the offer to cancel: it might have
|
||||
// been consumed or removed. If it is found, however, it's an error
|
||||
// to fail to delete it.
|
||||
if (sleCancel)
|
||||
{
|
||||
JLOG(j_.debug()) << "Create cancels order " << *cancelSequence;
|
||||
if (!offerID)
|
||||
JLOG(j_.debug()) << "Create cancels order " << *cancelSequence;
|
||||
else
|
||||
JLOG(j_.debug()) << "Create cancels order " << *offerID;
|
||||
result = offerDelete(sb, sleCancel, viewJ);
|
||||
}
|
||||
}
|
||||
@@ -1179,7 +1208,7 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
}
|
||||
|
||||
// We need to place the remainder of the offer into its order book.
|
||||
auto const offer_index = keylet::offer(account_, offerSequence);
|
||||
Keylet offer_index = keylet::offer(account_, seqID(ctx_));
|
||||
|
||||
// Add offer to owner's directory.
|
||||
auto const ownerNode = sb.dirInsert(
|
||||
@@ -1257,7 +1286,10 @@ CreateOffer::doApply()
|
||||
|
||||
auto const result = applyGuts(sb, sbCancel);
|
||||
if (result.second)
|
||||
{
|
||||
sb.apply(ctx_.rawView());
|
||||
addWeakTSHFromSandbox(sb);
|
||||
}
|
||||
else
|
||||
sbCancel.apply(ctx_.rawView());
|
||||
return result.first;
|
||||
|
||||
@@ -294,6 +294,14 @@ DeleteAccount::preclaim(PreclaimContext const& ctx)
|
||||
ctx.view.seq()))
|
||||
return tecTOO_SOON;
|
||||
|
||||
// do not allow the account to be removed if there are hooks installed or
|
||||
// one or more hook states when these fields are completely empty the field
|
||||
// is made absent so this test is sufficient these fields cannot be
|
||||
// populated unless hooks is enabled so the rules do not need to be checked
|
||||
if (sleAccount->isFieldPresent(sfHookNamespaces) ||
|
||||
sleAccount->isFieldPresent(sfHooks))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
// Verify that the account does not own any objects that would prevent
|
||||
// the account from being deleted.
|
||||
Keylet const ownerDirKeylet{keylet::ownerDir(account)};
|
||||
@@ -358,6 +366,13 @@ DeleteAccount::doApply()
|
||||
if (!src || !dst)
|
||||
return tefBAD_LEDGER;
|
||||
|
||||
// do not allow the account to be removed if there are hooks installed or
|
||||
// one or more hook states when these fields are completely empty the field
|
||||
// is made absent so this test is sufficient these fields cannot be
|
||||
// populated unless hooks is enabled so the rules do not need to be checked
|
||||
if (src->isFieldPresent(sfHookNamespaces) || src->isFieldPresent(sfHooks))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureDepositAuth) &&
|
||||
ctx_.tx.isFieldPresent(sfCredentialIDs))
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
@@ -94,7 +95,8 @@ after(NetClock::time_point now, std::uint32_t mark)
|
||||
TxConsequences
|
||||
EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
return TxConsequences{
|
||||
ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -106,8 +108,25 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (!isXRP(ctx.tx[sfAmount]))
|
||||
return temBAD_AMOUNT;
|
||||
STAmount const amount{ctx.tx[sfAmount]};
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (!isLegalNet(amount))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (isFakeXRP(amount))
|
||||
return temBAD_CURRENCY;
|
||||
|
||||
if (ctx.tx[sfAccount] == amount.getIssuer())
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "Malformed transaction: Cannot escrow own tokens to self.";
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx[sfAmount] <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
@@ -211,17 +230,74 @@ EscrowCreate::doApply()
|
||||
if (!sle)
|
||||
return tefINTERNAL;
|
||||
|
||||
STAmount const amount{ctx_.tx[sfAmount]};
|
||||
|
||||
std::shared_ptr<SLE> sleLine;
|
||||
|
||||
auto const balance = STAmount((*sle)[sfBalance]).xrp();
|
||||
auto const reserve =
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Check reserve and funds availability
|
||||
if (isXRP(amount))
|
||||
{
|
||||
auto const balance = STAmount((*sle)[sfBalance]).xrp();
|
||||
auto const reserve =
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (balance < reserve + STAmount(ctx_.tx[sfAmount]).xrp())
|
||||
return tecUNFUNDED;
|
||||
// pass
|
||||
}
|
||||
else
|
||||
{
|
||||
// preflight will prevent this ever firing, included
|
||||
// defensively for completeness
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
// check if the escrow is capable of being
|
||||
// finished before we allow it to be created
|
||||
{
|
||||
TER result = trustTransferAllowed(
|
||||
ctx_.view(),
|
||||
{account, ctx_.tx[sfDestination]},
|
||||
amount.issue(),
|
||||
ctx_.journal);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "EscrowCreate::doApply trustTransferAllowed result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// perform the lock as a dry run before
|
||||
// we modify anything on-ledger
|
||||
sleLine = ctx_.view().peek(
|
||||
keylet::line(account, amount.getIssuer(), amount.getCurrency()));
|
||||
if (!sleLine)
|
||||
{
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "EscrowCreate::doApply trustAdjustLockedBalance trustline "
|
||||
"missing "
|
||||
<< account << "-" << amount.getIssuer() << "/"
|
||||
<< amount.getCurrency();
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
{
|
||||
TER result = trustAdjustLockedBalance(
|
||||
ctx_.view(), sleLine, amount, 1, ctx_.journal, DryRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "EscrowCreate::doApply trustAdjustLockedBalance (dry) "
|
||||
"result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Check destination account
|
||||
@@ -243,8 +319,9 @@ EscrowCreate::doApply()
|
||||
|
||||
// Create escrow in ledger. Note that we we use the value from the
|
||||
// sequence or ticket. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const escrowKeylet =
|
||||
keylet::escrow(account, ctx_.tx.getSeqProxy().value());
|
||||
|
||||
Keylet const escrowKeylet = keylet::escrow(account, seqID(ctx_));
|
||||
|
||||
auto const slep = std::make_shared<SLE>(escrowKeylet);
|
||||
(*slep)[sfAmount] = ctx_.tx[sfAmount];
|
||||
(*slep)[sfAccount] = account;
|
||||
@@ -277,7 +354,26 @@ EscrowCreate::doApply()
|
||||
}
|
||||
|
||||
// Deduct owner's balance, increment owner count
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
if (isXRP(amount))
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens) ||
|
||||
!sleLine)
|
||||
return tefINTERNAL;
|
||||
|
||||
// do the lock-up for real now
|
||||
TER result = trustAdjustLockedBalance(
|
||||
ctx_.view(), sleLine, amount, 1, ctx_.journal, WetRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "EscrowCreate::doApply trustAdjustLockedBalance (wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
@@ -387,11 +483,28 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
EscrowFinish::doApply()
|
||||
{
|
||||
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
|
||||
bool hooksEnabled = view().rules().enabled(featureHooks);
|
||||
|
||||
if (!hooksEnabled && ctx_.tx.isFieldPresent(sfEscrowID))
|
||||
return temDISABLED;
|
||||
|
||||
std::optional<uint256> escrowID = ctx_.tx[~sfEscrowID];
|
||||
|
||||
if (escrowID && ctx_.tx[sfOfferSequence] != 0)
|
||||
return temMALFORMED;
|
||||
|
||||
Keylet k = escrowID
|
||||
? Keylet(ltESCROW, *escrowID)
|
||||
: keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
|
||||
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
auto amount = slep->getFieldAmount(sfAmount);
|
||||
|
||||
// If a cancel time is present, a finish operation should only succeed prior
|
||||
// to that time. fix1571 corrects a logic error in the check that would make
|
||||
// a finish only succeed strictly after the cancel time.
|
||||
@@ -482,7 +595,31 @@ EscrowFinish::doApply()
|
||||
return err;
|
||||
}
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
// perform a dry run of the transfer before we
|
||||
// change anything on-ledger
|
||||
TER result = trustTransferLockedBalance(
|
||||
ctx_.view(),
|
||||
account_, // txn signing account
|
||||
sle, // src account
|
||||
sled, // dst account
|
||||
amount, // xfer amount
|
||||
-1,
|
||||
j_,
|
||||
DryRun // dry run
|
||||
);
|
||||
|
||||
JLOG(j_.trace())
|
||||
<< "EscrowFinish::doApply trustTransferLockedBalance (dry) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// Remove escrow from owner directory
|
||||
{
|
||||
@@ -506,12 +643,35 @@ EscrowFinish::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer amount to destination
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount];
|
||||
if (isXRP(amount))
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount];
|
||||
else
|
||||
{
|
||||
// all the significant complexity of checking the validity of this
|
||||
// transfer and ensuring the lines exist etc is hidden away in this
|
||||
// function, all we need to do is call it and return if unsuccessful.
|
||||
TER result = trustTransferLockedBalance(
|
||||
ctx_.view(),
|
||||
account_, // txn signing account
|
||||
sle, // src account
|
||||
sled, // dst account
|
||||
amount, // xfer amount
|
||||
-1,
|
||||
j_,
|
||||
WetRun // wet run;
|
||||
);
|
||||
|
||||
JLOG(j_.trace())
|
||||
<< "EscrowFinish::doApply trustTransferLockedBalance (wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
ctx_.view().update(sled);
|
||||
|
||||
// Adjust source owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
@@ -538,7 +698,20 @@ EscrowCancel::preflight(PreflightContext const& ctx)
|
||||
TER
|
||||
EscrowCancel::doApply()
|
||||
{
|
||||
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
|
||||
bool hooksEnabled = view().rules().enabled(featureHooks);
|
||||
|
||||
if (!hooksEnabled && ctx_.tx.isFieldPresent(sfEscrowID))
|
||||
return temDISABLED;
|
||||
|
||||
std::optional<uint256> escrowID = ctx_.tx[~sfEscrowID];
|
||||
|
||||
if (escrowID && ctx_.tx[sfOfferSequence] != 0)
|
||||
return temMALFORMED;
|
||||
|
||||
Keylet k = escrowID
|
||||
? Keylet(ltESCROW, *escrowID)
|
||||
: keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
|
||||
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
@@ -565,6 +738,25 @@ EscrowCancel::doApply()
|
||||
}
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
auto amount = slep->getFieldAmount(sfAmount);
|
||||
|
||||
std::shared_ptr<SLE> sleLine;
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
sleLine = ctx_.view().peek(
|
||||
keylet::line(account, amount.getIssuer(), amount.getCurrency()));
|
||||
|
||||
// dry run before we make any changes to ledger
|
||||
if (TER result = trustAdjustLockedBalance(
|
||||
ctx_.view(), sleLine, -amount, -1, ctx_.journal, DryRun);
|
||||
result != tesSUCCESS)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Remove escrow from owner directory
|
||||
{
|
||||
@@ -591,9 +783,27 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer amount back to owner, decrement owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
|
||||
// Transfer amount back to the owner (or unlock it in TL case)
|
||||
if (isXRP(amount))
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
// unlock previously locked tokens from source line
|
||||
TER result = trustAdjustLockedBalance(
|
||||
ctx_.view(), sleLine, -amount, -1, ctx_.journal, WetRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "EscrowCancel::doApply trustAdjustLockedBalance (wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// Decrement owner count
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
|
||||
@@ -101,11 +101,14 @@ XRPNotCreated::visitEntry(
|
||||
drops_ -= (*before)[sfBalance].xrp().drops();
|
||||
break;
|
||||
case ltPAYCHAN:
|
||||
drops_ -=
|
||||
((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
|
||||
if (isXRP((*before)[sfAmount]))
|
||||
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance])
|
||||
.xrp()
|
||||
.drops();
|
||||
break;
|
||||
case ltESCROW:
|
||||
drops_ -= (*before)[sfAmount].xrp().drops();
|
||||
if (isXRP((*before)[sfAmount]))
|
||||
drops_ -= (*before)[sfAmount].xrp().drops();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -120,13 +123,13 @@ XRPNotCreated::visitEntry(
|
||||
drops_ += (*after)[sfBalance].xrp().drops();
|
||||
break;
|
||||
case ltPAYCHAN:
|
||||
if (!isDelete)
|
||||
if (!isDelete && isXRP((*after)[sfAmount]))
|
||||
drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
|
||||
.xrp()
|
||||
.drops();
|
||||
break;
|
||||
case ltESCROW:
|
||||
if (!isDelete)
|
||||
if (!isDelete && isXRP((*after)[sfAmount]))
|
||||
drops_ += (*after)[sfAmount].xrp().drops();
|
||||
break;
|
||||
default:
|
||||
@@ -287,12 +290,25 @@ NoZeroEscrow::visitEntry(
|
||||
|
||||
bool
|
||||
NoZeroEscrow::finalize(
|
||||
STTx const&,
|
||||
STTx const& txn,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
ReadView const& rv,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// bypass this invariant check for IOU escrows
|
||||
if (bad_ && rv.rules().enabled(featurePaychanAndEscrowForTokens) &&
|
||||
txn.isFieldPresent(sfTransactionType))
|
||||
{
|
||||
uint16_t tt = txn.getFieldU16(sfTransactionType);
|
||||
if (tt == ttESCROW_CANCEL || tt == ttESCROW_FINISH)
|
||||
return true;
|
||||
|
||||
if (txn.isFieldPresent(sfAmount) &&
|
||||
!isXRP(txn.getFieldAmount(sfAmount)))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bad_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
|
||||
@@ -474,6 +490,10 @@ LedgerEntryTypesMatch::visitEntry(
|
||||
case ltCHECK:
|
||||
case ltDEPOSIT_PREAUTH:
|
||||
case ltNEGATIVE_UNL:
|
||||
case ltHOOK:
|
||||
case ltHOOK_DEFINITION:
|
||||
case ltHOOK_STATE:
|
||||
case ltEMITTED:
|
||||
case ltNFTOKEN_PAGE:
|
||||
case ltNFTOKEN_OFFER:
|
||||
case ltAMM:
|
||||
|
||||
@@ -121,6 +121,29 @@ closeChannel(
|
||||
beast::Journal j)
|
||||
{
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
auto const amount = (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
|
||||
std::shared_ptr<SLE> sleLine;
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
sleLine = view.peek(
|
||||
keylet::line(src, amount.getIssuer(), amount.getCurrency()));
|
||||
|
||||
// dry run
|
||||
TER result =
|
||||
trustAdjustLockedBalance(view, sleLine, -amount, -1, j, DryRun);
|
||||
|
||||
JLOG(j.trace()) << "closeChannel: trustAdjustLockedBalance(dry) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// Remove PayChan from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
@@ -153,8 +176,21 @@ closeChannel(
|
||||
XRPL_ASSERT(
|
||||
(*slep)[sfAmount] >= (*slep)[sfBalance],
|
||||
"ripple::closeChannel : minimum channel amount");
|
||||
(*sle)[sfBalance] =
|
||||
(*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
|
||||
if (isXRP(amount))
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + amount;
|
||||
else
|
||||
{
|
||||
TER result =
|
||||
trustAdjustLockedBalance(view, sleLine, -amount, -1, j, WetRun);
|
||||
|
||||
JLOG(j.trace()) << "closeChannel: trustAdjustLockedBalance(wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
adjustOwnerCount(view, sle, -1, j);
|
||||
view.update(sle);
|
||||
|
||||
@@ -168,7 +204,8 @@ closeChannel(
|
||||
TxConsequences
|
||||
PayChanCreate::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
return TxConsequences{
|
||||
ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -180,7 +217,27 @@ PayChanCreate::preflight(PreflightContext const& ctx)
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero))
|
||||
STAmount const amount{ctx.tx[sfAmount]};
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (!isLegalNet(amount))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (isFakeXRP(amount))
|
||||
return temBAD_CURRENCY;
|
||||
|
||||
if (ctx.tx[sfAccount] == amount.getIssuer())
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "Malformed transaction: Cannot paychan own tokens to self.";
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx[sfAmount] <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
|
||||
@@ -200,21 +257,54 @@ PayChanCreate::preclaim(PreclaimContext const& ctx)
|
||||
if (!sle)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// Check reserve and funds availability
|
||||
{
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve =
|
||||
ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
STAmount const amount{ctx.tx[sfAmount]};
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve =
|
||||
ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
|
||||
if (balance < reserve + ctx.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto const dst = ctx.tx[sfDestination];
|
||||
|
||||
// Check reserve and funds availability
|
||||
if (isXRP(amount) && balance < reserve + ctx.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
else
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tecINTERNAL;
|
||||
|
||||
// check for any possible bars to a channel existing
|
||||
// between these accounts for this asset
|
||||
{
|
||||
TER result = trustTransferAllowed(
|
||||
ctx.view, {account, dst}, amount.issue(), ctx.j);
|
||||
JLOG(ctx.j.trace())
|
||||
<< "PayChanCreate::preclaim trustTransferAllowed result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// check if the amount can be locked
|
||||
{
|
||||
auto sleLine = ctx.view.read(keylet::line(
|
||||
account, amount.getIssuer(), amount.getCurrency()));
|
||||
TER result = trustAdjustLockedBalance(
|
||||
ctx.view, sleLine, amount, 1, ctx.j, DryRun);
|
||||
|
||||
JLOG(ctx.j.trace()) << "PayChanCreate::preclaim "
|
||||
"trustAdjustLockedBalance(dry) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Check destination account
|
||||
auto const sled = ctx.view.read(keylet::account(dst));
|
||||
@@ -254,12 +344,15 @@ PayChanCreate::doApply()
|
||||
|
||||
auto const dst = ctx_.tx[sfDestination];
|
||||
|
||||
STAmount const amount{ctx_.tx[sfAmount]};
|
||||
|
||||
// Create PayChan in ledger.
|
||||
//
|
||||
// Note that we we use the value from the sequence or ticket as the
|
||||
// payChan sequence. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const payChanKeylet =
|
||||
keylet::payChan(account, dst, ctx_.tx.getSeqProxy().value());
|
||||
|
||||
Keylet const payChanKeylet = keylet::payChan(account, dst, seqID(ctx_));
|
||||
|
||||
auto const slep = std::make_shared<SLE>(payChanKeylet);
|
||||
|
||||
// Funds held in this channel
|
||||
@@ -298,7 +391,30 @@ PayChanCreate::doApply()
|
||||
}
|
||||
|
||||
// Deduct owner's balance, increment owner count
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
if (isXRP(amount))
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
auto sleLine = ctx_.view().peek(
|
||||
keylet::line(account, amount.getIssuer(), amount.getCurrency()));
|
||||
|
||||
if (!sleLine)
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
|
||||
TER result = trustAdjustLockedBalance(
|
||||
ctx_.view(), sleLine, amount, 1, ctx_.journal, WetRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "PayChanCreate::doApply trustAdjustLockedBalance(wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return tefINTERNAL;
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
@@ -310,7 +426,8 @@ PayChanCreate::doApply()
|
||||
TxConsequences
|
||||
PayChanFund::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
return TxConsequences{
|
||||
ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::zero};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -322,7 +439,27 @@ PayChanFund::preflight(PreflightContext const& ctx)
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero))
|
||||
STAmount const amount{ctx.tx[sfAmount]};
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (!isLegalNet(amount))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (isFakeXRP(amount))
|
||||
return temBAD_CURRENCY;
|
||||
|
||||
if (ctx.tx[sfAccount] == amount.getIssuer())
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "Malformed transaction: Cannot escrow own tokens to self.";
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.tx[sfAmount] <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return preflight2(ctx);
|
||||
@@ -336,10 +473,32 @@ PayChanFund::doApply()
|
||||
if (!slep)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
STAmount const amount{ctx_.tx[sfAmount]};
|
||||
|
||||
std::shared_ptr<SLE> sleLine; // if XRP or featurePaychanAndEscrowForTokens
|
||||
// not enabled this remains null
|
||||
|
||||
// if this is a Fund operation on an IOU then perform a dry run here
|
||||
if (!isXRP(amount) &&
|
||||
ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
{
|
||||
sleLine = ctx_.view().peek(keylet::line(
|
||||
(*slep)[sfAccount], amount.getIssuer(), amount.getCurrency()));
|
||||
|
||||
TER result = trustAdjustLockedBalance(
|
||||
ctx_.view(), sleLine, amount, 1, ctx_.journal, DryRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "PayChanFund::doApply trustAdjustLockedBalance(dry) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
auto const txAccount = ctx_.tx[sfAccount];
|
||||
auto const expiration = (*slep)[~sfExpiration];
|
||||
|
||||
{
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime =
|
||||
@@ -372,19 +531,6 @@ PayChanFund::doApply()
|
||||
if (!sle)
|
||||
return tefINTERNAL;
|
||||
|
||||
{
|
||||
// Check reserve and funds availability
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve =
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (balance < reserve + ctx_.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
// do not allow adding funds if dst does not exist
|
||||
if (AccountID const dst = (*slep)[sfDestination];
|
||||
!ctx_.view().read(keylet::account(dst)))
|
||||
@@ -392,12 +538,41 @@ PayChanFund::doApply()
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
// Check reserve and funds availability
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve =
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (isXRP(amount))
|
||||
{
|
||||
if (balance < reserve + amount)
|
||||
return tecUNFUNDED;
|
||||
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
|
||||
ctx_.view().update(sle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
TER result = trustAdjustLockedBalance(
|
||||
ctx_.view(), sleLine, amount, 1, ctx_.journal, WetRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "PayChanFund::doApply trustAdjustLockedBalance(wet) result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return tefINTERNAL;
|
||||
}
|
||||
|
||||
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
|
||||
ctx_.view().update(slep);
|
||||
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
ctx_.view().update(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -414,12 +589,27 @@ PayChanClaim::preflight(PreflightContext const& ctx)
|
||||
return ret;
|
||||
|
||||
auto const bal = ctx.tx[~sfBalance];
|
||||
if (bal && (!isXRP(*bal) || *bal <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
if (bal)
|
||||
{
|
||||
if (!isXRP(*bal) &&
|
||||
!ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (*bal <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
auto const amt = ctx.tx[~sfAmount];
|
||||
if (amt && (!isXRP(*amt) || *amt <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (amt)
|
||||
{
|
||||
if (!isXRP(*amt) &&
|
||||
!ctx.rules.enabled(featurePaychanAndEscrowForTokens))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (*amt <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (bal && amt && *bal > *amt)
|
||||
return temBAD_AMOUNT;
|
||||
@@ -443,8 +633,8 @@ PayChanClaim::preflight(PreflightContext const& ctx)
|
||||
// The signature isn't needed if txAccount == src, but if it's
|
||||
// present, check it
|
||||
|
||||
auto const reqBalance = bal->xrp();
|
||||
auto const authAmt = amt ? amt->xrp() : reqBalance;
|
||||
auto const reqBalance = *bal;
|
||||
auto const authAmt = *amt ? *amt : reqBalance;
|
||||
|
||||
if (reqBalance > authAmt)
|
||||
return temBAD_AMOUNT;
|
||||
@@ -455,7 +645,17 @@ PayChanClaim::preflight(PreflightContext const& ctx)
|
||||
|
||||
PublicKey const pk(ctx.tx[sfPublicKey]);
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization(msg, k.key, authAmt);
|
||||
|
||||
if (isXRP(authAmt))
|
||||
serializePayChanAuthorization(msg, k.key, authAmt.xrp());
|
||||
else
|
||||
serializePayChanAuthorization(
|
||||
msg,
|
||||
k.key,
|
||||
authAmt.iou(),
|
||||
authAmt.getCurrency(),
|
||||
authAmt.getIssuer());
|
||||
|
||||
if (!verify(pk, msg.slice(), *sig, /*canonical*/ true))
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
@@ -507,9 +707,9 @@ PayChanClaim::doApply()
|
||||
|
||||
if (ctx_.tx[~sfBalance])
|
||||
{
|
||||
auto const chanBalance = slep->getFieldAmount(sfBalance).xrp();
|
||||
auto const chanFunds = slep->getFieldAmount(sfAmount).xrp();
|
||||
auto const reqBalance = ctx_.tx[sfBalance].xrp();
|
||||
auto const chanBalance = slep->getFieldAmount(sfBalance);
|
||||
auto const chanFunds = slep->getFieldAmount(sfAmount);
|
||||
auto const reqBalance = ctx_.tx[sfBalance];
|
||||
|
||||
if (txAccount == dst && !ctx_.tx[~sfSignature])
|
||||
return temBAD_SIGNATURE;
|
||||
@@ -528,7 +728,7 @@ PayChanClaim::doApply()
|
||||
// nothing requested
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
|
||||
auto const sled = ctx_.view().peek(keylet::account(dst));
|
||||
auto sled = ctx_.view().peek(keylet::account(dst));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
@@ -536,6 +736,7 @@ PayChanClaim::doApply()
|
||||
// featureDepositAuth to remove the bug.
|
||||
bool const depositAuth{ctx_.view().rules().enabled(featureDepositAuth)};
|
||||
if (!depositAuth &&
|
||||
// RH TODO: does this condition need to be changed for IOU paychans?
|
||||
(txAccount == src && (sled->getFlags() & lsfDisallowXRP)))
|
||||
return tecNO_TARGET;
|
||||
|
||||
@@ -547,11 +748,40 @@ PayChanClaim::doApply()
|
||||
}
|
||||
|
||||
(*slep)[sfBalance] = ctx_.tx[sfBalance];
|
||||
XRPAmount const reqDelta = reqBalance - chanBalance;
|
||||
STAmount const reqDelta = reqBalance - chanBalance;
|
||||
XRPL_ASSERT(
|
||||
reqDelta >= beast::zero,
|
||||
"ripple::PayChanClaim::doApply : minimum balance delta");
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
|
||||
if (isXRP(reqDelta))
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
|
||||
else
|
||||
{
|
||||
// xfer locked tokens to satisfy claim
|
||||
// RH NOTE: there's no ledger modification before this point so
|
||||
// no reason to do a dry run first
|
||||
if (!ctx_.view().rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
auto sleSrcAcc = ctx_.view().peek(keylet::account(src));
|
||||
TER result = trustTransferLockedBalance(
|
||||
ctx_.view(),
|
||||
txAccount,
|
||||
sleSrcAcc,
|
||||
sled,
|
||||
reqDelta,
|
||||
0,
|
||||
ctx_.journal,
|
||||
WetRun);
|
||||
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "PayChanClaim::doApply trustTransferLockedBalance(wet) "
|
||||
"result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
ctx_.view().update(sled);
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
@@ -430,6 +430,7 @@ Payment::doApply()
|
||||
// on the TER. But always applying *should*
|
||||
// be safe.
|
||||
pv.apply(ctx_.rawView());
|
||||
addWeakTSHFromSandbox(pv);
|
||||
}
|
||||
|
||||
// TODO: is this right? If the amount is the correct amount, was
|
||||
|
||||
@@ -445,6 +445,23 @@ SetAccount::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// TshCollect
|
||||
//
|
||||
if (view().rules().enabled(featureHooks))
|
||||
{
|
||||
if (uSetFlag == asfTshCollect)
|
||||
{
|
||||
JLOG(j_.trace()) << "Set lsfTshCollect.";
|
||||
uFlagsOut |= lsfTshCollect;
|
||||
}
|
||||
else if (uClearFlag == asfTshCollect)
|
||||
{
|
||||
JLOG(j_.trace()) << "Clear lsfTshCollect.";
|
||||
uFlagsOut &= ~lsfTshCollect;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// EmailHash
|
||||
//
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,100 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2014 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_SETHOOK_H_INCLUDED
|
||||
#define RIPPLE_TX_SETHOOK_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/Enum.h>
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/tx/detail/SignerEntries.h>
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
struct SetHookCtx
|
||||
{
|
||||
beast::Journal j;
|
||||
STTx const& tx;
|
||||
Application& app;
|
||||
};
|
||||
|
||||
class SetHook : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
|
||||
|
||||
explicit SetHook(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
affectsSubsequentTransactionAuth(STTx const& tx)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
void
|
||||
preCompute() override;
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const&);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
private:
|
||||
TER
|
||||
setHook();
|
||||
|
||||
TER
|
||||
destroyNamespace(
|
||||
SetHookCtx& ctx,
|
||||
ApplyView& view,
|
||||
const AccountID& account,
|
||||
uint256 ns);
|
||||
|
||||
TER
|
||||
removeHookFromLedger(
|
||||
Application& app,
|
||||
ApplyView& view,
|
||||
Keylet const& accountKeylet,
|
||||
Keylet const& ownerDirKeylet,
|
||||
Keylet const& hookKeylet);
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <xrpld/app/tx/detail/SignerEntries.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/hook/Enum.h>
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/misc/CredentialHelpers.h>
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
@@ -25,7 +27,9 @@
|
||||
#include <xrpld/app/tx/detail/SignerEntries.h>
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/ledger/PaymentSandbox.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
#include <xrpld/ledger/detail/ApplyViewBase.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
@@ -35,6 +39,9 @@
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <limits>
|
||||
#include <set>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Performs early sanity checks on the txid */
|
||||
@@ -108,6 +115,12 @@ preflight1(PreflightContext const& ctx)
|
||||
return temBAD_FEE;
|
||||
}
|
||||
|
||||
// if a hook emitted this transaction we bypass signature checks
|
||||
// there is a bar to circularing emitted transactions on the network
|
||||
// in their prevalidated form so this is safe
|
||||
if (ctx.rules.enabled(featureHooks) && hook::isEmittedTxn(ctx.tx))
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const spk = ctx.tx.getSigningPubKey();
|
||||
|
||||
if (!spk.empty() && !publicKeyType(makeSlice(spk)))
|
||||
@@ -162,6 +175,66 @@ Transactor::Transactor(ApplyContext& ctx)
|
||||
{
|
||||
}
|
||||
|
||||
// RH NOTE: this only computes one chain at a time, so if there is a receiving
|
||||
// side to a txn then it must seperately be computed by a second call here
|
||||
XRPAmount
|
||||
Transactor::calculateHookChainFee(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
Keylet const& hookKeylet,
|
||||
bool collectCallsOnly)
|
||||
{
|
||||
std::shared_ptr<SLE const> hookSLE = view.read(hookKeylet);
|
||||
if (!hookSLE)
|
||||
return XRPAmount{0};
|
||||
|
||||
XRPAmount fee{0};
|
||||
|
||||
auto const& hooks = hookSLE->getFieldArray(sfHooks);
|
||||
|
||||
for (auto const& hook : hooks)
|
||||
{
|
||||
ripple::STObject const* hookObj =
|
||||
dynamic_cast<ripple::STObject const*>(&hook);
|
||||
|
||||
if (!hookObj->isFieldPresent(sfHookHash)) // skip blanks
|
||||
continue;
|
||||
|
||||
uint256 const& hash = hookObj->getFieldH256(sfHookHash);
|
||||
|
||||
std::shared_ptr<SLE const> hookDef =
|
||||
view.read(keylet::hookDefinition(hash));
|
||||
|
||||
// this is an edge case that happens when a hook is deleted and executed
|
||||
// at the same ledger the fee calculation for it can no longer occur
|
||||
if (!hookDef)
|
||||
{
|
||||
printf("calculateHookChainFee edge case\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the hook can fire
|
||||
uint64_t hookOn =
|
||||
(hookObj->isFieldPresent(sfHookOn)
|
||||
? hookObj->getFieldU64(sfHookOn)
|
||||
: hookDef->getFieldU64(sfHookOn));
|
||||
|
||||
uint32_t flags = 0;
|
||||
if (hookObj->isFieldPresent(sfFlags))
|
||||
flags = hookObj->getFieldU32(sfFlags);
|
||||
else
|
||||
flags = hookDef->getFieldU32(sfFlags);
|
||||
|
||||
if (hook::canHook(tx.getTxnType(), hookOn) &&
|
||||
(!collectCallsOnly || (flags & hook::hsfCOLLECT)))
|
||||
{
|
||||
fee += hookDef->getFieldAmount(sfFee).xrp();
|
||||
}
|
||||
}
|
||||
|
||||
return fee;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
@@ -177,7 +250,54 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
std::size_t const signerCount =
|
||||
tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
|
||||
|
||||
return baseFee + (signerCount * baseFee);
|
||||
XRPAmount hookExecutionFee{0};
|
||||
uint64_t burden{1};
|
||||
if (view.rules().enabled(featureHooks))
|
||||
{
|
||||
// if this is a "cleanup" txn we regard it as already paid up
|
||||
if (tx.getFieldU16(sfTransactionType) == ttEMIT_FAILURE)
|
||||
return XRPAmount{0};
|
||||
|
||||
// if the txn is an emitted txn then we add the callback fee
|
||||
// if the txn is NOT an emitted txn then we process the sending
|
||||
// account's hook chain
|
||||
if (tx.isFieldPresent(sfEmitDetails))
|
||||
{
|
||||
STObject const& emitDetails = const_cast<ripple::STTx&>(tx)
|
||||
.getField(sfEmitDetails)
|
||||
.downcast<STObject>();
|
||||
|
||||
uint256 const& callbackHookHash =
|
||||
emitDetails.getFieldH256(sfEmitHookHash);
|
||||
|
||||
std::shared_ptr<SLE const> hookDef =
|
||||
view.read(keylet::hookDefinition(callbackHookHash));
|
||||
|
||||
if (hookDef && hookDef->isFieldPresent(sfHookCallbackFee))
|
||||
hookExecutionFee +=
|
||||
hookDef->getFieldAmount(sfHookCallbackFee).xrp();
|
||||
|
||||
assert(emitDetails.isFieldPresent(sfEmitBurden));
|
||||
|
||||
burden = emitDetails.getFieldU64(sfEmitBurden);
|
||||
}
|
||||
else
|
||||
hookExecutionFee += calculateHookChainFee(
|
||||
view, tx, keylet::hook(tx.getAccountID(sfAccount)));
|
||||
|
||||
// find any additional stakeholders whose hooks will be executed and
|
||||
// charged to this transaction
|
||||
std::vector<std::pair<AccountID, bool>> tsh =
|
||||
hook::getTransactionalStakeHolders(tx, view);
|
||||
|
||||
for (auto& [tshAcc, canRollback] : tsh)
|
||||
if (canRollback)
|
||||
hookExecutionFee +=
|
||||
calculateHookChainFee(view, tx, keylet::hook(tshAcc));
|
||||
}
|
||||
|
||||
// RH NOTE: hookExecutionFee = 0, burden = 1 if hooks is not enabled
|
||||
return baseFee * burden + (signerCount * baseFee) + hookExecutionFee;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
@@ -284,6 +404,20 @@ Transactor::checkSeqProxy(
|
||||
SeqProxy const t_seqProx = tx.getSeqProxy();
|
||||
SeqProxy const a_seq = SeqProxy::sequence((*sle)[sfSequence]);
|
||||
|
||||
// pass all emitted tx provided their seq is 0
|
||||
if (view.rules().enabled(featureHooks) && hook::isEmittedTxn(tx))
|
||||
{
|
||||
// this is more strictly enforced in the emit() hook api
|
||||
// here this is only acting as a sanity check in case of bugs
|
||||
if (!tx.isFieldPresent(sfFirstLedgerSequence))
|
||||
return tefINTERNAL;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// reserved for emitted tx only at this time
|
||||
if (tx.isFieldPresent(sfFirstLedgerSequence))
|
||||
return tefINTERNAL;
|
||||
|
||||
if (t_seqProx.isSeq())
|
||||
{
|
||||
if (tx.isFieldPresent(sfTicketSequence) &&
|
||||
@@ -370,6 +504,11 @@ Transactor::consumeSeqProxy(SLE::pointer const& sleAccount)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sleAccount, "ripple::Transactor::consumeSeqProxy : non-null account");
|
||||
|
||||
// do not update sequence of sfAccountTxnID for emitted tx
|
||||
if (ctx_.emitted())
|
||||
return tesSUCCESS;
|
||||
|
||||
SeqProxy const seqProx = ctx_.tx.getSeqProxy();
|
||||
if (seqProx.isSeq())
|
||||
{
|
||||
@@ -486,6 +625,10 @@ Transactor::apply()
|
||||
NotTEC
|
||||
Transactor::checkSign(PreclaimContext const& ctx)
|
||||
{
|
||||
// hook emitted transactions do not have signatures
|
||||
if (ctx.view.rules().enabled(featureHooks) && hook::isEmittedTxn(ctx.tx))
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the pk is empty, then we must be multi-signing.
|
||||
if (ctx.tx.getSigningPubKey().empty())
|
||||
return checkMultiSign(ctx);
|
||||
@@ -813,7 +956,12 @@ removeDeletedTrustLines(
|
||||
std::pair<TER, XRPAmount>
|
||||
Transactor::reset(XRPAmount fee)
|
||||
{
|
||||
ApplyViewImpl& avi = dynamic_cast<ApplyViewImpl&>(ctx_.view());
|
||||
std::vector<STObject> hookMeta;
|
||||
avi.copyHookMetaData(hookMeta);
|
||||
ctx_.discard();
|
||||
ApplyViewImpl& avi2 = dynamic_cast<ApplyViewImpl&>(ctx_.view());
|
||||
avi2.setHookMetaData(std::move(hookMeta));
|
||||
|
||||
auto const txnAcct =
|
||||
view().peek(keylet::account(ctx_.tx.getAccountID(sfAccount)));
|
||||
@@ -851,6 +999,522 @@ Transactor::reset(XRPAmount fee)
|
||||
return {ter, fee};
|
||||
}
|
||||
|
||||
TER
|
||||
Transactor::executeHookChain(
|
||||
std::shared_ptr<ripple::STLedgerEntry const> const& hookSLE,
|
||||
hook::HookStateMap& stateMap,
|
||||
std::vector<hook::HookResult>& results,
|
||||
ripple::AccountID const& account,
|
||||
bool strong,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta)
|
||||
{
|
||||
std::set<uint256> hookSkips;
|
||||
std::map<uint256, std::map<std::vector<uint8_t>, std::vector<uint8_t>>>
|
||||
hookParamOverrides{};
|
||||
|
||||
auto const& hooks = hookSLE->getFieldArray(sfHooks);
|
||||
uint8_t hook_no = 0;
|
||||
|
||||
for (auto const& hook : hooks)
|
||||
{
|
||||
ripple::STObject const* hookObj =
|
||||
dynamic_cast<ripple::STObject const*>(&hook);
|
||||
|
||||
if (!hookObj->isFieldPresent(sfHookHash)) // skip blanks
|
||||
continue;
|
||||
|
||||
// lookup hook definition
|
||||
uint256 const& hookHash = hookObj->getFieldH256(sfHookHash);
|
||||
|
||||
if (hookSkips.find(hookHash) != hookSkips.end())
|
||||
{
|
||||
JLOG(j_.trace()) << "HookInfo: Skipping " << hookHash;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& hookDef =
|
||||
ctx_.view().peek(keylet::hookDefinition(hookHash));
|
||||
if (!hookDef)
|
||||
{
|
||||
JLOG(j_.warn()) << "HookError[]: Failure: hook def missing (send)";
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if the hook can fire
|
||||
uint64_t hookOn =
|
||||
(hookObj->isFieldPresent(sfHookOn)
|
||||
? hookObj->getFieldU64(sfHookOn)
|
||||
: hookDef->getFieldU64(sfHookOn));
|
||||
|
||||
if (!hook::canHook(ctx_.tx.getTxnType(), hookOn))
|
||||
continue; // skip if it can't
|
||||
|
||||
uint32_t flags =
|
||||
(hookObj->isFieldPresent(sfFlags) ? hookObj->getFieldU32(sfFlags)
|
||||
: hookDef->getFieldU32(sfFlags));
|
||||
|
||||
JLOG(j_.trace()) << "HookChainExecution: " << hookHash
|
||||
<< " strong:" << strong
|
||||
<< " flags&hsfCOLLECT: " << (flags & hsfCOLLECT);
|
||||
|
||||
// skip weakly executed hooks that lack a collect flag
|
||||
if (!strong && !(flags & hsfCOLLECT))
|
||||
continue;
|
||||
|
||||
// fetch the namespace either from the hook object of, if absent, the
|
||||
// hook def
|
||||
uint256 const& ns =
|
||||
(hookObj->isFieldPresent(sfHookNamespace)
|
||||
? hookObj->getFieldH256(sfHookNamespace)
|
||||
: hookDef->getFieldH256(sfHookNamespace));
|
||||
|
||||
// gather parameters
|
||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>> parameters;
|
||||
if (hook::gatherHookParameters(hookDef, hookObj, parameters, j_))
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "HookError[]: Failure: gatherHookParameters failed)";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
bool hasCallback = hookDef->isFieldPresent(sfHookCallbackFee);
|
||||
|
||||
results.push_back(hook::apply(
|
||||
hookDef->getFieldH256(sfHookSetTxnID),
|
||||
hookHash,
|
||||
ns,
|
||||
hookDef->getFieldVL(sfCreateCode),
|
||||
parameters,
|
||||
hookParamOverrides,
|
||||
stateMap,
|
||||
ctx_,
|
||||
account,
|
||||
hasCallback,
|
||||
false,
|
||||
strong,
|
||||
(strong ? 0 : 1UL), // 0 = strong, 1 = weak
|
||||
hook_no,
|
||||
provisionalMeta));
|
||||
|
||||
executedHookCount_++;
|
||||
|
||||
hook::HookResult& hookResult = results.back();
|
||||
|
||||
if (hookResult.exitType != hook_api::ExitType::ACCEPT)
|
||||
{
|
||||
if (results.back().exitType == hook_api::ExitType::WASM_ERROR)
|
||||
return temMALFORMED;
|
||||
return tecHOOK_REJECTED;
|
||||
}
|
||||
|
||||
// gather skips
|
||||
for (uint256 const& hash : hookResult.hookSkips)
|
||||
if (hookSkips.find(hash) == hookSkips.end())
|
||||
hookSkips.emplace(hash);
|
||||
|
||||
// gather overrides
|
||||
auto const& resultOverrides = hookResult.hookParamOverrides;
|
||||
for (auto const& [hash, params] : resultOverrides)
|
||||
{
|
||||
if (hookParamOverrides.find(hash) == hookParamOverrides.end())
|
||||
hookParamOverrides[hash] = {};
|
||||
|
||||
auto& overrides = hookParamOverrides[hash];
|
||||
for (auto const& [k, v] : params)
|
||||
overrides[k] = v;
|
||||
}
|
||||
|
||||
hook_no++;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
Transactor::doHookCallback(
|
||||
std::shared_ptr<STObject const> const& provisionalMeta)
|
||||
{
|
||||
// Finally check if there is a callback
|
||||
if (!ctx_.tx.isFieldPresent(sfEmitDetails))
|
||||
return;
|
||||
|
||||
auto const& emitDetails = const_cast<ripple::STTx&>(ctx_.tx)
|
||||
.getField(sfEmitDetails)
|
||||
.downcast<STObject>();
|
||||
|
||||
// callbacks are optional so if there isn't a callback then skip
|
||||
if (!emitDetails.isFieldPresent(sfEmitCallback))
|
||||
return;
|
||||
|
||||
AccountID const& callbackAccountID =
|
||||
emitDetails.getAccountID(sfEmitCallback);
|
||||
uint256 const& callbackHookHash = emitDetails.getFieldH256(sfEmitHookHash);
|
||||
|
||||
auto const& hooksCallback = view().peek(keylet::hook(callbackAccountID));
|
||||
auto const& hookDef = view().peek(keylet::hookDefinition(callbackHookHash));
|
||||
if (!hookDef)
|
||||
{
|
||||
JLOG(j_.warn()) << "HookError[]: Hook def missing on callback";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hookDef->isFieldPresent(sfHookCallbackFee))
|
||||
{
|
||||
JLOG(j_.trace()) << "HookInfo[" << callbackAccountID
|
||||
<< "]: Callback specified by emitted txn "
|
||||
<< "but hook lacks a cbak function, skipping.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hooksCallback)
|
||||
{
|
||||
JLOG(j_.warn()) << "HookError[]: Hook missing on callback";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hooksCallback->isFieldPresent(sfHooks))
|
||||
{
|
||||
JLOG(j_.warn()) << "HookError[]: Hooks Array missing on callback";
|
||||
return;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
auto const& hooks = hooksCallback->getFieldArray(sfHooks);
|
||||
uint8_t hook_no = 0;
|
||||
for (auto const& hook : hooks)
|
||||
{
|
||||
hook_no++;
|
||||
|
||||
STObject const* hookObj = dynamic_cast<STObject const*>(&hook);
|
||||
|
||||
if (!hookObj->isFieldPresent(sfHookHash)) // skip blanks
|
||||
continue;
|
||||
|
||||
if (hookObj->getFieldH256(sfHookHash) != callbackHookHash)
|
||||
continue;
|
||||
|
||||
// fetch the namespace either from the hook object of, if absent, the
|
||||
// hook def
|
||||
uint256 const& ns =
|
||||
(hookObj->isFieldPresent(sfHookNamespace)
|
||||
? hookObj->getFieldH256(sfHookNamespace)
|
||||
: hookDef->getFieldH256(sfHookNamespace));
|
||||
|
||||
executedHookCount_++;
|
||||
|
||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>> parameters;
|
||||
if (hook::gatherHookParameters(hookDef, hookObj, parameters, j_))
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "HookError[]: Failure: gatherHookParameters failed)";
|
||||
return;
|
||||
}
|
||||
|
||||
found = true;
|
||||
|
||||
// this call will clean up ltEMITTED_NODE as well
|
||||
try
|
||||
{
|
||||
hook::HookStateMap stateMap;
|
||||
|
||||
hook::HookResult callbackResult = hook::apply(
|
||||
hookDef->getFieldH256(sfHookSetTxnID),
|
||||
callbackHookHash,
|
||||
ns,
|
||||
hookDef->getFieldVL(sfCreateCode),
|
||||
parameters,
|
||||
{},
|
||||
stateMap,
|
||||
ctx_,
|
||||
callbackAccountID,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
safe_cast<TxType>(ctx_.tx.getFieldU16(sfTransactionType)) ==
|
||||
ttEMIT_FAILURE
|
||||
? 1UL
|
||||
: 0UL,
|
||||
hook_no - 1,
|
||||
provisionalMeta);
|
||||
|
||||
bool success =
|
||||
callbackResult.exitType == hook_api::ExitType::ACCEPT;
|
||||
|
||||
// write any state changes if cbak resulted in accept()
|
||||
if (success)
|
||||
hook::finalizeHookState(
|
||||
stateMap, ctx_, ctx_.tx.getTransactionID());
|
||||
|
||||
// write the final result
|
||||
ripple::TER result =
|
||||
finalizeHookResult(callbackResult, ctx_, success);
|
||||
|
||||
JLOG(j_.trace()) << "HookInfo[" << callbackAccountID << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount) << "]: "
|
||||
<< "Callback finalizeHookResult = " << result;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
JLOG(j_.fatal()) << "HookError[" << callbackAccountID << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount) << "]: "
|
||||
<< "]: Callback failure " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
JLOG(j_.warn()) << "HookError[" << callbackAccountID << "]: Hookhash "
|
||||
<< callbackHookHash << " not found on callback account";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Transactor::addWeakTSHFromSandbox(detail::ApplyViewBase const& pv)
|
||||
{
|
||||
// If Hooks are enabled then non-issuers who have their TL balance
|
||||
// modified by the execution of the path have the opportunity to have their
|
||||
// weak hooks executed.
|
||||
if (ctx_.view().rules().enabled(featureHooks))
|
||||
{
|
||||
// anyone whose balance changed as a result of this Pathing is a weak
|
||||
// TSH
|
||||
auto bc = pv.balanceChanges(view());
|
||||
|
||||
for (auto const& entry : bc)
|
||||
{
|
||||
std::tuple<AccountID, AccountID, Currency> const& tpl = entry.first;
|
||||
Currency const& cur = std::get<2>(tpl);
|
||||
if (isXRP(cur))
|
||||
continue;
|
||||
|
||||
AccountID const& lowAcc = std::get<0>(tpl);
|
||||
AccountID const& highAcc = std::get<1>(tpl);
|
||||
STAmount const& amt = entry.second;
|
||||
additionalWeakTSH_.emplace(amt >= beast::zero ? lowAcc : highAcc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TER
|
||||
Transactor::doTSH(
|
||||
bool strong, // only strong iff true, only weak iff false
|
||||
hook::HookStateMap& stateMap,
|
||||
std::vector<hook::HookResult>& results,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta)
|
||||
{
|
||||
auto& view = ctx_.view();
|
||||
|
||||
std::vector<std::pair<AccountID, bool>> tsh =
|
||||
hook::getTransactionalStakeHolders(ctx_.tx, view);
|
||||
|
||||
// add the extra TSH marked out by the specific transactor (if applicable)
|
||||
if (!strong)
|
||||
for (auto& weakTsh : additionalWeakTSH_)
|
||||
tsh.emplace_back(weakTsh, false);
|
||||
|
||||
// we use a vector above for order preservation
|
||||
// but we also don't want to execute any hooks
|
||||
// twice, so keep track as we go with a map
|
||||
std::set<AccountID> alreadyProcessed;
|
||||
|
||||
for (auto& [tshAccountID, canRollback] : tsh)
|
||||
{
|
||||
// this isn't an error because transactors may
|
||||
// blindly nominate any TSHes they find but
|
||||
// obviously we will never execute OTXN account
|
||||
// as a TSH because they already had first execution
|
||||
if (tshAccountID == account_)
|
||||
continue;
|
||||
|
||||
if (alreadyProcessed.find(tshAccountID) != alreadyProcessed.end())
|
||||
continue;
|
||||
|
||||
alreadyProcessed.emplace(tshAccountID);
|
||||
|
||||
// only process the relevant ones
|
||||
if ((!canRollback && strong) || (canRollback && !strong))
|
||||
continue;
|
||||
|
||||
auto klTshHook = keylet::hook(tshAccountID);
|
||||
|
||||
auto tshHook = view.read(klTshHook);
|
||||
if (!(tshHook && tshHook->isFieldPresent(sfHooks)))
|
||||
continue;
|
||||
|
||||
// scoping here allows tshAcc to leave scope before
|
||||
// hook execution, which is probably safer
|
||||
{
|
||||
// check if the TSH exists and/or has any hooks
|
||||
auto tshAcc = view.peek(keylet::account(tshAccountID));
|
||||
if (!tshAcc)
|
||||
continue;
|
||||
|
||||
// compute and deduct fees for the TSH if applicable
|
||||
XRPAmount tshFee =
|
||||
calculateHookChainFee(view, ctx_.tx, klTshHook, !canRollback);
|
||||
|
||||
// no hooks to execute, skip tsh
|
||||
if (tshFee == 0)
|
||||
continue;
|
||||
|
||||
assert(tshFee >= beast::zero);
|
||||
|
||||
STAmount priorBalance = tshAcc->getFieldAmount(sfBalance);
|
||||
|
||||
if (canRollback)
|
||||
{
|
||||
// this is not a collect call so we will force the tsh's fee to
|
||||
// 0 the otxn paid the fee for this tsh chain execution already.
|
||||
tshFee = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is a collect call so first check if the tsh can accept
|
||||
uint32_t tshFlags = tshAcc->getFieldU32(sfFlags);
|
||||
if (!canRollback && !(tshFlags & lsfTshCollect))
|
||||
{
|
||||
// this TSH doesn't allow collect calls, skip
|
||||
JLOG(j_.trace()) << "HookInfo[" << account_ << "]: TSH acc "
|
||||
<< tshAccountID << " "
|
||||
<< "hook chain execution skipped due to "
|
||||
"lack of lsfTshCollect flag.";
|
||||
continue;
|
||||
}
|
||||
|
||||
// now check if they can afford this collect call
|
||||
auto const uOwnerCount = tshAcc->getFieldU32(sfOwnerCount);
|
||||
auto const reserve = view.fees().accountReserve(uOwnerCount);
|
||||
|
||||
if (tshFee + reserve > priorBalance)
|
||||
{
|
||||
JLOG(j_.trace()) << "HookInfo[" << account_ << "]: TSH acc "
|
||||
<< tshAccountID << " "
|
||||
<< "hook chain execution skipped due to "
|
||||
"lack of TSH acc funds.";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (tshFee > beast::zero)
|
||||
{
|
||||
STAmount finalBalance = priorBalance - tshFee;
|
||||
assert(finalBalance >= beast::zero);
|
||||
assert(finalBalance < priorBalance);
|
||||
|
||||
tshAcc->setFieldAmount(sfBalance, finalBalance);
|
||||
view.update(tshAcc);
|
||||
ctx_.destroyXRP(tshFee);
|
||||
}
|
||||
}
|
||||
|
||||
// execution to here means we can run the TSH's hook chain
|
||||
TER tshResult = executeHookChain(
|
||||
tshHook, stateMap, results, tshAccountID, strong, provisionalMeta);
|
||||
|
||||
if (canRollback && (tshResult != tesSUCCESS))
|
||||
return tshResult;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
Transactor::doAgainAsWeak(
|
||||
AccountID const& hookAccountID,
|
||||
std::set<uint256> const& hookHashes,
|
||||
hook::HookStateMap& stateMap,
|
||||
std::vector<hook::HookResult>& results,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta)
|
||||
{
|
||||
auto const& hooksArray = view().peek(keylet::hook(hookAccountID));
|
||||
if (!hooksArray)
|
||||
{
|
||||
JLOG(j_.warn()) << "HookError[]: Hook missing on aaw account: "
|
||||
<< hookAccountID;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hooksArray->isFieldPresent(sfHooks))
|
||||
{
|
||||
JLOG(j_.warn()) << "HookError[]: Hooks Array missing on aaw";
|
||||
return;
|
||||
}
|
||||
|
||||
auto const& hooks = hooksArray->getFieldArray(sfHooks);
|
||||
uint8_t hook_no = 0;
|
||||
for (auto const& hook : hooks)
|
||||
{
|
||||
hook_no++;
|
||||
|
||||
STObject const* hookObj = dynamic_cast<STObject const*>(&hook);
|
||||
|
||||
if (!hookObj->isFieldPresent(sfHookHash)) // skip blanks
|
||||
continue;
|
||||
|
||||
uint256 const& hookHash = hookObj->getFieldH256(sfHookHash);
|
||||
|
||||
if (hookHashes.find(hookObj->getFieldH256(sfHookHash)) ==
|
||||
hookHashes.end())
|
||||
continue;
|
||||
|
||||
auto const& hookDef = view().peek(keylet::hookDefinition(hookHash));
|
||||
if (!hookDef)
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "HookError[]: Hook def missing on aaw, hash: " << hookHash;
|
||||
continue;
|
||||
}
|
||||
|
||||
// fetch the namespace either from the hook object of, if absent, the
|
||||
// hook def
|
||||
uint256 const& ns =
|
||||
(hookObj->isFieldPresent(sfHookNamespace)
|
||||
? hookObj->getFieldH256(sfHookNamespace)
|
||||
: hookDef->getFieldH256(sfHookNamespace));
|
||||
|
||||
executedHookCount_++;
|
||||
|
||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>> parameters;
|
||||
if (hook::gatherHookParameters(hookDef, hookObj, parameters, j_))
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "HookError[]: Failure: gatherHookParameters failed)";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
hook::HookResult aawResult = hook::apply(
|
||||
hookDef->getFieldH256(sfHookSetTxnID),
|
||||
hookHash,
|
||||
ns,
|
||||
hookDef->getFieldVL(sfCreateCode),
|
||||
parameters,
|
||||
{},
|
||||
stateMap,
|
||||
ctx_,
|
||||
hookAccountID,
|
||||
hookDef->isFieldPresent(sfHookCallbackFee),
|
||||
false,
|
||||
false,
|
||||
2UL, // param 2 = aaw
|
||||
hook_no - 1,
|
||||
provisionalMeta);
|
||||
|
||||
results.push_back(aawResult);
|
||||
|
||||
JLOG(j_.trace()) << "HookInfo[" << hookAccountID << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount) << "]: "
|
||||
<< " aaw Hook ExitCode = " << aawResult.exitCode;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
JLOG(j_.fatal()) << "HookError[" << hookAccountID << "-"
|
||||
<< ctx_.tx.getAccountID(sfAccount) << "]: "
|
||||
<< "]: aaw failure " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The sole purpose of this function is to provide a convenient, named
|
||||
// location to set a breakpoint, to be used when replaying transactions.
|
||||
void
|
||||
@@ -896,6 +1560,68 @@ Transactor::operator()()
|
||||
}
|
||||
|
||||
auto result = ctx_.preclaimResult;
|
||||
|
||||
bool const hooksEnabled = view().rules().enabled(featureHooks);
|
||||
|
||||
// AgainAsWeak map stores information about accounts whose strongly executed
|
||||
// hooks request an additional weak execution after the otxn has finished
|
||||
// application to the ledger
|
||||
std::map<AccountID, std::set<uint256>> aawMap;
|
||||
|
||||
// Pre-application (Strong TSH) Hooks are executed here
|
||||
// These TSH have the right to rollback.
|
||||
// Weak TSH and callback are executed post-application.
|
||||
if (hooksEnabled && (result == tesSUCCESS || result == tecHOOK_REJECTED))
|
||||
{
|
||||
// this state map will be shared across all hooks in this execution
|
||||
// chain and any associated chains which are executed during this
|
||||
// transaction also this map can get large so
|
||||
hook::HookStateMap stateMap;
|
||||
|
||||
auto const& accountID = ctx_.tx.getAccountID(sfAccount);
|
||||
std::vector<hook::HookResult> hookResults;
|
||||
|
||||
auto const& hooksOriginator = view().read(keylet::hook(accountID));
|
||||
|
||||
// First check if the Sending account has any hooks that can be fired
|
||||
if (hooksOriginator && hooksOriginator->isFieldPresent(sfHooks) &&
|
||||
!ctx_.emitted())
|
||||
result = executeHookChain(
|
||||
hooksOriginator, stateMap, hookResults, accountID, true, {});
|
||||
|
||||
if (isTesSuccess(result))
|
||||
{
|
||||
// Next check if there are any transactional stake holders whose
|
||||
// hooks need to be executed here. Note these are only strong TSH
|
||||
// (who have the right to rollback the txn), any weak TSH will be
|
||||
// executed after doApply has been successful (callback as well)
|
||||
|
||||
result = doTSH(true, stateMap, hookResults, {});
|
||||
}
|
||||
|
||||
// write state if all chains executed successfully
|
||||
if (isTesSuccess(result))
|
||||
hook::finalizeHookState(stateMap, ctx_, ctx_.tx.getTransactionID());
|
||||
|
||||
// write hook results
|
||||
// this happens irrespective of whether final result was a tesSUCCESS
|
||||
// because it contains error codes that any failed hooks would have
|
||||
// returned for meta
|
||||
|
||||
for (auto& hookResult : hookResults)
|
||||
{
|
||||
hook::finalizeHookResult(hookResult, ctx_, isTesSuccess(result));
|
||||
if (isTesSuccess(result) && hookResult.executeAgainAsWeak)
|
||||
{
|
||||
if (aawMap.find(hookResult.account) == aawMap.end())
|
||||
aawMap[hookResult.account] = {hookResult.hookHash};
|
||||
else
|
||||
aawMap[hookResult.account].emplace(hookResult.hookHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fall through allows normal apply
|
||||
if (result == tesSUCCESS)
|
||||
result = apply();
|
||||
|
||||
@@ -909,6 +1635,7 @@ Transactor::operator()()
|
||||
stream << "preclaim result: " << transToken(result);
|
||||
|
||||
bool applied = isTesSuccess(result);
|
||||
|
||||
auto fee = ctx_.tx.getFieldAmount(sfFee).xrp();
|
||||
|
||||
if (ctx_.size() > oversizeMetaDataCap)
|
||||
@@ -918,7 +1645,6 @@ Transactor::operator()()
|
||||
{
|
||||
// If the tapFAIL_HARD flag is set, a tec result
|
||||
// must not do anything
|
||||
|
||||
ctx_.discard();
|
||||
applied = false;
|
||||
}
|
||||
@@ -1047,6 +1773,45 @@ Transactor::operator()()
|
||||
applied = false;
|
||||
}
|
||||
|
||||
// Post-application (Weak TSH/AAW) Hooks are executed here.
|
||||
// These TSH do not have the ability to rollback.
|
||||
// The callback, if any, is also executed here.
|
||||
if (applied && hooksEnabled)
|
||||
{
|
||||
// weakly executed hooks have access to a provisional TxMeta
|
||||
// for this tx application.
|
||||
TxMeta meta = ctx_.generateProvisionalMeta();
|
||||
meta.setResult(result, 0);
|
||||
|
||||
std::shared_ptr<STObject const> proMeta =
|
||||
std::make_shared<STObject const>(meta.getAsObject());
|
||||
|
||||
// perform callback logic if applicable
|
||||
if (ctx_.tx.isFieldPresent(sfEmitDetails))
|
||||
doHookCallback(proMeta);
|
||||
|
||||
// remove emission entry if this is an emitted transaction
|
||||
hook::removeEmissionEntry(ctx_);
|
||||
|
||||
// process weak TSH
|
||||
hook::HookStateMap stateMap;
|
||||
std::vector<hook::HookResult> weakResults;
|
||||
|
||||
doTSH(false, stateMap, weakResults, proMeta);
|
||||
|
||||
// execute any hooks that nominated for 'again as weak'
|
||||
for (auto const& [accID, hookHashes] : aawMap)
|
||||
doAgainAsWeak(accID, hookHashes, stateMap, weakResults, proMeta);
|
||||
|
||||
// write hook results
|
||||
hook::finalizeHookState(stateMap, ctx_, ctx_.tx.getTransactionID());
|
||||
for (auto& weakResult : weakResults)
|
||||
hook::finalizeHookResult(weakResult, ctx_, isTesSuccess(result));
|
||||
|
||||
if (ctx_.size() > oversizeMetaDataCap)
|
||||
result = tecOVERSIZE;
|
||||
}
|
||||
|
||||
if (applied)
|
||||
{
|
||||
// Transaction succeeded fully or (retries are not allowed and the
|
||||
|
||||
@@ -20,10 +20,14 @@
|
||||
#ifndef RIPPLE_APP_TX_TRANSACTOR_H_INCLUDED
|
||||
#define RIPPLE_APP_TX_TRANSACTOR_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/tx/applySteps.h>
|
||||
#include <xrpld/app/tx/detail/ApplyContext.h>
|
||||
#include <xrpld/ledger/PaymentSandbox.h>
|
||||
#include <xrpld/ledger/detail/ApplyViewBase.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <variant>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -99,6 +103,7 @@ protected:
|
||||
|
||||
public:
|
||||
enum ConsequencesFactoryType { Normal, Blocker, Custom };
|
||||
|
||||
/** Process the transaction. */
|
||||
std::pair<TER, bool>
|
||||
operator()();
|
||||
@@ -141,6 +146,15 @@ public:
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
// Returns a list of zero or more accounts which are
|
||||
// not the originator of the transaction but which are
|
||||
// stakeholders in the transaction. The bool parameter
|
||||
// determines whether or not the specified account has
|
||||
// permission for their hook/s to cause a rollback on
|
||||
// the transaction.
|
||||
static std::vector<std::pair<AccountID, bool>>
|
||||
getTransactionalStakeHolders(STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
@@ -158,7 +172,61 @@ public:
|
||||
uint256 const& ticketIndex,
|
||||
beast::Journal j);
|
||||
|
||||
// Hooks
|
||||
|
||||
static XRPAmount
|
||||
calculateHookChainFee(
|
||||
ReadView const& view,
|
||||
STTx const& tx,
|
||||
Keylet const& hookKeylet,
|
||||
bool collectCallsOnly = false);
|
||||
|
||||
protected:
|
||||
void
|
||||
doHookCallback(std::shared_ptr<STObject const> const& provisionalMeta);
|
||||
|
||||
TER
|
||||
doTSH(
|
||||
bool strong, // only do strong TSH iff true, otheriwse only weak
|
||||
hook::HookStateMap& stateMap,
|
||||
std::vector<hook::HookResult>& result,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta);
|
||||
|
||||
// Execute a hook "Again As Weak" is a feature that allows
|
||||
// a hook that which is being executed pre-application of the otxn
|
||||
// to request an additional post-application execution.
|
||||
void
|
||||
doAgainAsWeak(
|
||||
AccountID const& hookAccountID,
|
||||
std::set<uint256> const& hookHashes,
|
||||
hook::HookStateMap& stateMap,
|
||||
std::vector<hook::HookResult>& results,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta);
|
||||
|
||||
TER
|
||||
executeHookChain(
|
||||
std::shared_ptr<ripple::STLedgerEntry const> const& hookSLE,
|
||||
hook::HookStateMap& stateMap,
|
||||
std::vector<hook::HookResult>& results,
|
||||
ripple::AccountID const& account,
|
||||
bool strong,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta);
|
||||
|
||||
void
|
||||
addWeakTSHFromSandbox(detail::ApplyViewBase const& pv);
|
||||
|
||||
// hooks amendment fields, these are unpopulated and unused unless
|
||||
// featureHooks is enabled
|
||||
int executedHookCount_ =
|
||||
0; // record how many hooks have executed across the whole transactor
|
||||
std::set<AccountID>
|
||||
additionalWeakTSH_; // any TSH that needs weak hook execution at the
|
||||
// end of the transactor, who isn't able to be
|
||||
// deduced until after apply i.e. pathing
|
||||
// participants, crossed offers
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
TER
|
||||
apply();
|
||||
|
||||
@@ -214,6 +282,17 @@ preflight1(PreflightContext const& ctx);
|
||||
NotTEC
|
||||
preflight2(PreflightContext const& ctx);
|
||||
|
||||
template <class C>
|
||||
inline static std::variant<uint32_t, uint256>
|
||||
seqID(C const& ctx_)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureHooks) &&
|
||||
ctx_.tx.isFieldPresent(sfEmitDetails))
|
||||
return ctx_.tx.getTransactionID();
|
||||
|
||||
return ctx_.tx.getSeqProxy().value();
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -42,6 +42,7 @@ checkValidity(
|
||||
{
|
||||
auto const id = tx.getTransactionID();
|
||||
auto const flags = router.getFlags(id);
|
||||
|
||||
if (flags & SF_SIGBAD)
|
||||
// Signature is known bad
|
||||
return {Validity::SigBad, "Transaction has bad signature."};
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#include <xrpld/app/tx/detail/PayChan.h>
|
||||
#include <xrpld/app/tx/detail/Payment.h>
|
||||
#include <xrpld/app/tx/detail/SetAccount.h>
|
||||
#include <xrpld/app/tx/detail/SetHook.h>
|
||||
#include <xrpld/app/tx/detail/SetOracle.h>
|
||||
#include <xrpld/app/tx/detail/SetRegularKey.h>
|
||||
#include <xrpld/app/tx/detail/SetSignerList.h>
|
||||
@@ -298,15 +299,15 @@ preflight(
|
||||
beast::Journal j)
|
||||
{
|
||||
PreflightContext const pfctx(app, tx, rules, flags, j);
|
||||
try
|
||||
{
|
||||
return {pfctx, invoke_preflight(pfctx)};
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(j.fatal()) << "apply: " << e.what();
|
||||
return {pfctx, {tefEXCEPTION, TxConsequences{tx}}};
|
||||
}
|
||||
// try
|
||||
// {
|
||||
return {pfctx, invoke_preflight(pfctx)};
|
||||
// }
|
||||
// catch (std::exception const& e)
|
||||
// {
|
||||
// JLOG(j.fatal()) << "apply: " << e.what();
|
||||
// return {pfctx, {tefEXCEPTION, TxConsequences{tx}}};
|
||||
// }
|
||||
}
|
||||
|
||||
PreclaimResult
|
||||
@@ -342,17 +343,17 @@ preclaim(
|
||||
preflightResult.flags,
|
||||
preflightResult.j);
|
||||
}
|
||||
try
|
||||
{
|
||||
if (ctx->preflightResult != tesSUCCESS)
|
||||
return {*ctx, ctx->preflightResult};
|
||||
return {*ctx, invoke_preclaim(*ctx)};
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(ctx->j.fatal()) << "apply: " << e.what();
|
||||
return {*ctx, tefEXCEPTION};
|
||||
}
|
||||
// try
|
||||
// {
|
||||
if (ctx->preflightResult != tesSUCCESS)
|
||||
return {*ctx, ctx->preflightResult};
|
||||
return {*ctx, invoke_preclaim(*ctx)};
|
||||
// }
|
||||
// catch (std::exception const& e)
|
||||
// {
|
||||
// JLOG(ctx->j.fatal()) << "apply: " << e.what();
|
||||
// return {*ctx, tefEXCEPTION};
|
||||
// }
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
@@ -376,25 +377,26 @@ doApply(PreclaimResult const& preclaimResult, Application& app, OpenView& view)
|
||||
// info to recover.
|
||||
return {tefEXCEPTION, false};
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!preclaimResult.likelyToClaimFee)
|
||||
return {preclaimResult.ter, false};
|
||||
ApplyContext ctx(
|
||||
app,
|
||||
view,
|
||||
preclaimResult.tx,
|
||||
preclaimResult.ter,
|
||||
calculateBaseFee(view, preclaimResult.tx),
|
||||
preclaimResult.flags,
|
||||
preclaimResult.j);
|
||||
return invoke_apply(ctx);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(preclaimResult.j.fatal()) << "apply: " << e.what();
|
||||
return {tefEXCEPTION, false};
|
||||
}
|
||||
// try
|
||||
// {
|
||||
if (!preclaimResult.likelyToClaimFee)
|
||||
return {preclaimResult.ter, false};
|
||||
|
||||
ApplyContext ctx(
|
||||
app,
|
||||
view,
|
||||
preclaimResult.tx,
|
||||
preclaimResult.ter,
|
||||
calculateBaseFee(view, preclaimResult.tx),
|
||||
preclaimResult.flags,
|
||||
preclaimResult.j);
|
||||
return invoke_apply(ctx);
|
||||
// }
|
||||
// catch (std::exception const& e)
|
||||
// {
|
||||
// JLOG(preclaimResult.j.fatal()) << "apply: " << e.what();
|
||||
// return {tefEXCEPTION, false};
|
||||
// }
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -39,7 +39,7 @@ enum ApplyFlags : std::uint32_t {
|
||||
tapRETRY = 0x20,
|
||||
|
||||
// Transaction came from a privileged source
|
||||
tapUNLIMITED = 0x400,
|
||||
tapUNLIMITED = 0x400
|
||||
};
|
||||
|
||||
constexpr ApplyFlags
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Editable, discardable view that can build metadata for one tx.
|
||||
@@ -68,6 +73,42 @@ public:
|
||||
deliver_ = amount;
|
||||
}
|
||||
|
||||
TxMeta
|
||||
generateProvisionalMeta(
|
||||
OpenView const& to,
|
||||
STTx const& tx,
|
||||
beast::Journal j);
|
||||
|
||||
/* Set hook metadata for a hook execution
|
||||
* Takes ownership / use std::move
|
||||
*/
|
||||
void
|
||||
addHookMetaData(STObject&& hookExecution)
|
||||
{
|
||||
hookExecution_.push_back(std::move(hookExecution));
|
||||
}
|
||||
|
||||
void
|
||||
setHookMetaData(std::vector<STObject>&& vec)
|
||||
{
|
||||
hookExecution_ = std::move(vec);
|
||||
}
|
||||
|
||||
void
|
||||
copyHookMetaData(std::vector<STObject>& into)
|
||||
{
|
||||
std::copy(
|
||||
hookExecution_.begin(),
|
||||
hookExecution_.end(),
|
||||
std::back_inserter(into));
|
||||
}
|
||||
|
||||
uint16_t
|
||||
nextHookExecutionIndex()
|
||||
{
|
||||
return hookExecution_.size();
|
||||
}
|
||||
|
||||
/** Get the number of modified entries
|
||||
*/
|
||||
std::size_t
|
||||
@@ -86,6 +127,7 @@ public:
|
||||
|
||||
private:
|
||||
std::optional<STAmount> deliver_;
|
||||
std::vector<STObject> hookExecution_;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
+622
-1
@@ -25,7 +25,9 @@
|
||||
#include <xrpld/ledger/OpenView.h>
|
||||
#include <xrpld/ledger/RawView.h>
|
||||
#include <xrpld/ledger/ReadView.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
@@ -34,11 +36,12 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
@@ -524,6 +527,624 @@ transferXRP(
|
||||
STAmount const& amount,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//
|
||||
// Trustline Locking and Transfer (PaychanAndEscrowForTokens)
|
||||
//
|
||||
|
||||
// In functions white require a `RunType`
|
||||
// pass DryRun (don't apply changes) or WetRun (do apply changes)
|
||||
// to allow compile time evaluation of which types and calls to use
|
||||
|
||||
// For all functions below that take a Dry/Wet run parameter
|
||||
// View may be ReadView const or ApplyView for DryRuns.
|
||||
// View *must* be ApplyView for a WetRun.
|
||||
// Passed SLEs must be non-const for WetRun.
|
||||
#define DryRun RunType<bool, true>()
|
||||
#define WetRun RunType<bool, false>()
|
||||
template <class T, T V>
|
||||
struct RunType
|
||||
{
|
||||
// see:
|
||||
// http://alumni.media.mit.edu/~rahimi/compile-time-flags/
|
||||
constexpr
|
||||
operator T() const
|
||||
{
|
||||
static_assert(std::is_same<bool, T>::value);
|
||||
return V;
|
||||
}
|
||||
|
||||
constexpr T
|
||||
operator!() const
|
||||
{
|
||||
static_assert(std::is_same<bool, T>::value);
|
||||
return !(V);
|
||||
}
|
||||
};
|
||||
|
||||
// allow party lists to be logged easily
|
||||
template <class T>
|
||||
std::ostream&
|
||||
operator<<(std::ostream& lhs, std::vector<T> const& rhs)
|
||||
{
|
||||
lhs << "{";
|
||||
for (int i = 0; i < rhs.size(); ++i)
|
||||
lhs << rhs[i] << (i < rhs.size() - 1 ? ", " : "");
|
||||
lhs << "}";
|
||||
return lhs;
|
||||
}
|
||||
// Return true iff the acc side of line is in default state
|
||||
bool
|
||||
isTrustDefault(
|
||||
std::shared_ptr<SLE> const& acc, // side to check
|
||||
std::shared_ptr<SLE> const& line); // line to check
|
||||
|
||||
/** Lock or unlock a TrustLine balance.
|
||||
If positive deltaAmt lock the amount.
|
||||
If negative deltaAmt unlock the amount.
|
||||
*/
|
||||
template <class V, class S, class R>
|
||||
[[nodiscard]] TER
|
||||
trustAdjustLockedBalance(
|
||||
V& view,
|
||||
S& sleLine,
|
||||
STAmount const& deltaAmt,
|
||||
int deltaLockCount, // if +1 lockCount is increased, -1 is decreased, 0
|
||||
// unchanged
|
||||
beast::Journal const& j,
|
||||
R dryRun)
|
||||
{
|
||||
static_assert(
|
||||
(std::is_same<V, ReadView const>::value &&
|
||||
std::is_same<S, std::shared_ptr<SLE const>>::value) ||
|
||||
(std::is_same<V, ApplyView>::value &&
|
||||
std::is_same<S, std::shared_ptr<SLE>>::value));
|
||||
|
||||
// dry runs are explicit in code, but really the view type determines
|
||||
// what occurs here, so this combination is invalid.
|
||||
|
||||
static_assert(!(std::is_same<V, ReadView const>::value && !dryRun));
|
||||
|
||||
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
if (!sleLine)
|
||||
return tecINTERNAL;
|
||||
|
||||
auto const issuer = deltaAmt.getIssuer();
|
||||
|
||||
STAmount lowLimit = sleLine->getFieldAmount(sfLowLimit);
|
||||
|
||||
// the account which is modifying the LockedBalance is always
|
||||
// the side that isn't the issuer, so if the low side is the
|
||||
// issuer then the high side is the account.
|
||||
bool const high = lowLimit.getIssuer() == issuer;
|
||||
|
||||
std::vector<AccountID> parties{
|
||||
high ? sleLine->getFieldAmount(sfHighLimit).getIssuer()
|
||||
: lowLimit.getIssuer()};
|
||||
|
||||
// check for freezes & auth
|
||||
{
|
||||
TER result = trustTransferAllowed(view, parties, deltaAmt.issue(), j);
|
||||
|
||||
JLOG(j.trace())
|
||||
<< "trustAdjustLockedBalance: trustTransferAllowed result="
|
||||
<< result;
|
||||
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// pull the TL balance from the account's perspective
|
||||
STAmount balance = high ? -(*sleLine)[sfBalance] : (*sleLine)[sfBalance];
|
||||
|
||||
// this would mean somehow the issuer is trying to lock balance
|
||||
if (balance < beast::zero)
|
||||
return tecINTERNAL;
|
||||
|
||||
if (deltaAmt == beast::zero)
|
||||
return tesSUCCESS;
|
||||
|
||||
// can't lock or unlock a zero balance
|
||||
if (balance == beast::zero)
|
||||
{
|
||||
JLOG(j.trace()) << "trustAdjustLockedBalance failed, zero balance";
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
}
|
||||
|
||||
STAmount priorLockedBalance{sfLockedBalance, deltaAmt.issue()};
|
||||
if (sleLine->isFieldPresent(sfLockedBalance))
|
||||
priorLockedBalance =
|
||||
high ? -(*sleLine)[sfLockedBalance] : (*sleLine)[sfLockedBalance];
|
||||
|
||||
uint32_t priorLockCount = 0;
|
||||
if (sleLine->isFieldPresent(sfLockCount))
|
||||
priorLockCount = sleLine->getFieldU32(sfLockCount);
|
||||
|
||||
uint32_t finalLockCount = priorLockCount + deltaLockCount;
|
||||
STAmount finalLockedBalance = priorLockedBalance + deltaAmt;
|
||||
|
||||
if (finalLockedBalance > balance)
|
||||
{
|
||||
JLOG(j.trace()) << "trustAdjustLockedBalance: "
|
||||
<< "lockedBalance(" << finalLockedBalance
|
||||
<< ") > balance(" << balance << ") = true\n";
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
}
|
||||
|
||||
if (finalLockedBalance < beast::zero)
|
||||
return tecINTERNAL;
|
||||
|
||||
// check if there is significant precision loss
|
||||
if (!isAddable(balance, deltaAmt) ||
|
||||
!isAddable(priorLockedBalance, deltaAmt) ||
|
||||
!isAddable(finalLockedBalance, balance))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
// sanity check possible overflows on the lock counter
|
||||
if ((deltaLockCount > 0 && priorLockCount > finalLockCount) ||
|
||||
(deltaLockCount < 0 && priorLockCount < finalLockCount) ||
|
||||
(deltaLockCount == 0 && priorLockCount != finalLockCount))
|
||||
return tecINTERNAL;
|
||||
|
||||
// we won't update any SLEs if it is a dry run
|
||||
if (dryRun)
|
||||
return tesSUCCESS;
|
||||
|
||||
if constexpr (
|
||||
std::is_same<V, ApplyView>::value &&
|
||||
std::is_same<S, std::shared_ptr<SLE>>::value)
|
||||
{
|
||||
if (finalLockedBalance == beast::zero || finalLockCount == 0)
|
||||
{
|
||||
sleLine->makeFieldAbsent(sfLockedBalance);
|
||||
sleLine->makeFieldAbsent(sfLockCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleLine->setFieldAmount(
|
||||
sfLockedBalance,
|
||||
high ? -finalLockedBalance : finalLockedBalance);
|
||||
sleLine->setFieldU32(sfLockCount, finalLockCount);
|
||||
}
|
||||
|
||||
view.update(sleLine);
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/** Check if a set of accounts can freely exchange the specified token.
|
||||
Read only, does not change any ledger object.
|
||||
May be called with ApplyView or ReadView.
|
||||
(including unlocking) is forbidden by any flag or condition.
|
||||
If parties contains 1 entry then noRipple is not a bar to xfer.
|
||||
If parties contains more than 1 entry then any party with noRipple
|
||||
on issuer side is a bar to xfer.
|
||||
*/
|
||||
template <class V>
|
||||
[[nodiscard]] TER
|
||||
trustTransferAllowed(
|
||||
V& view,
|
||||
std::vector<AccountID> const& parties,
|
||||
Issue const& issue,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
static_assert(
|
||||
std::is_same<V, ReadView const>::value ||
|
||||
std::is_same<V, ApplyView>::value);
|
||||
|
||||
typedef typename std::conditional<
|
||||
std::is_same<V, ApplyView>::value,
|
||||
std::shared_ptr<SLE>,
|
||||
std::shared_ptr<SLE const>>::type SLEPtr;
|
||||
|
||||
if (isFakeXRP(issue.currency))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const sleIssuerAcc = view.read(keylet::account(issue.account));
|
||||
|
||||
bool lockedBalanceAllowed =
|
||||
view.rules().enabled(featurePaychanAndEscrowForTokens);
|
||||
|
||||
// missing issuer is always a bar to xfer
|
||||
if (!sleIssuerAcc)
|
||||
return tecNO_ISSUER;
|
||||
|
||||
// issuer global freeze is always a bar to xfer
|
||||
if (isGlobalFrozen(view, issue.account))
|
||||
return tecFROZEN;
|
||||
|
||||
uint32_t issuerFlags = sleIssuerAcc->getFieldU32(sfFlags);
|
||||
|
||||
bool requireAuth = issuerFlags & lsfRequireAuth;
|
||||
|
||||
for (AccountID const& p : parties)
|
||||
{
|
||||
if (p == issue.account)
|
||||
continue;
|
||||
|
||||
auto const line =
|
||||
view.read(keylet::line(p, issue.account, issue.currency));
|
||||
if (!line)
|
||||
{
|
||||
if (requireAuth)
|
||||
{
|
||||
// the line doesn't exist, i.e. it is in default state
|
||||
// default state means the line has not been authed
|
||||
// therefore if auth is required by issuer then
|
||||
// this is now a bar to xfer
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
|
||||
// missing line is a line in default state, this is not
|
||||
// a general bar to xfer, however additional conditions
|
||||
// do attach to completing an xfer into a default line
|
||||
// but these are checked in trustTransferLockedBalance at
|
||||
// the point of transfer.
|
||||
continue;
|
||||
}
|
||||
|
||||
// sanity check the line, insane lines are a bar to xfer
|
||||
{
|
||||
// these "strange" old lines, if they even exist anymore are
|
||||
// always a bar to xfer
|
||||
if (line->getFieldAmount(sfLowLimit).getIssuer() ==
|
||||
line->getFieldAmount(sfHighLimit).getIssuer())
|
||||
return tecINTERNAL;
|
||||
|
||||
if (line->isFieldPresent(sfLockedBalance))
|
||||
{
|
||||
if (!lockedBalanceAllowed)
|
||||
{
|
||||
JLOG(j.warn()) << "trustTransferAllowed: "
|
||||
<< "sfLockedBalance found on line when "
|
||||
"amendment not enabled";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
STAmount lockedBalance = line->getFieldAmount(sfLockedBalance);
|
||||
STAmount balance = line->getFieldAmount(sfBalance);
|
||||
|
||||
if (lockedBalance.getCurrency() != balance.getCurrency())
|
||||
{
|
||||
JLOG(j.warn()) << "trustTansferAllowed: "
|
||||
<< "lockedBalance currency did not match "
|
||||
"balance currency";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check the bars to xfer ... these are:
|
||||
// any TL in the set has noRipple on the issuer's side
|
||||
// any TL in the set has a freeze on the issuer's side
|
||||
// any TL in the set has RequireAuth and the TL lacks lsf*Auth
|
||||
{
|
||||
bool pHigh = p > issue.account;
|
||||
|
||||
auto const flagIssuerNoRipple{
|
||||
pHigh ? lsfLowNoRipple : lsfHighNoRipple};
|
||||
auto const flagIssuerFreeze{pHigh ? lsfLowFreeze : lsfHighFreeze};
|
||||
auto const flagIssuerAuth{pHigh ? lsfLowAuth : lsfHighAuth};
|
||||
|
||||
uint32_t flags = line->getFieldU32(sfFlags);
|
||||
|
||||
if (flags & flagIssuerFreeze)
|
||||
{
|
||||
JLOG(j.trace()) << "trustTransferAllowed: "
|
||||
<< "parties=[" << parties << "], "
|
||||
<< "issuer: " << issue.account << " "
|
||||
<< "has freeze on party: " << p;
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
// if called with more than one party then any party
|
||||
// that has a noripple on the issuer side of their tl
|
||||
// blocks any possible xfer
|
||||
if (parties.size() > 1 && (flags & flagIssuerNoRipple))
|
||||
{
|
||||
JLOG(j.trace()) << "trustTransferAllowed: "
|
||||
<< "parties=[" << parties << "], "
|
||||
<< "issuer: " << issue.account << " "
|
||||
<< "has noRipple on party: " << p;
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
|
||||
// every party involved must be on an authed trustline if
|
||||
// the issuer has specified lsfRequireAuth
|
||||
if (requireAuth && !(flags & flagIssuerAuth))
|
||||
{
|
||||
JLOG(j.trace()) << "trustTransferAllowed: "
|
||||
<< "parties=[" << parties << "], "
|
||||
<< "issuer: " << issue.account << " "
|
||||
<< "requires TL auth which "
|
||||
<< "party: " << p << " "
|
||||
<< "does not possess.";
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/** Transfer a locked balance from one TL to an unlocked balance on another
|
||||
or create a line at the destination if the actingAcc has permission to.
|
||||
Used for resolving payment instruments that use locked TL balances.
|
||||
*/
|
||||
template <class V, class S, class R>
|
||||
[[nodiscard]] TER
|
||||
trustTransferLockedBalance(
|
||||
V& view,
|
||||
AccountID const& actingAccID, // the account whose tx is actioning xfer
|
||||
S& sleSrcAcc,
|
||||
S& sleDstAcc,
|
||||
STAmount const& amount, // issuer, currency are in this field
|
||||
int deltaLockCount, // -1 decrement, +1 increment, 0 unchanged
|
||||
beast::Journal const& j,
|
||||
R dryRun)
|
||||
{
|
||||
typedef typename std::conditional<
|
||||
std::is_same<V, ApplyView>::value && !dryRun,
|
||||
std::shared_ptr<SLE>,
|
||||
std::shared_ptr<SLE const>>::type SLEPtr;
|
||||
|
||||
auto peek = [&](Keylet& k) {
|
||||
if constexpr (std::is_same<V, ApplyView>::value && !dryRun)
|
||||
return view.peek(k);
|
||||
else
|
||||
return view.read(k);
|
||||
};
|
||||
|
||||
static_assert(std::is_same<V, ApplyView>::value || dryRun);
|
||||
|
||||
if (!view.rules().enabled(featurePaychanAndEscrowForTokens))
|
||||
return tefINTERNAL;
|
||||
|
||||
if (!sleSrcAcc || !sleDstAcc)
|
||||
{
|
||||
JLOG(j.warn()) << "trustTransferLockedBalance without sleSrc/sleDst";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if (amount <= beast::zero)
|
||||
{
|
||||
JLOG(j.warn()) << "trustTransferLockedBalance with non-positive amount";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
auto issuerAccID = amount.getIssuer();
|
||||
auto currency = amount.getCurrency();
|
||||
auto srcAccID = sleSrcAcc->getAccountID(sfAccount);
|
||||
auto dstAccID = sleDstAcc->getAccountID(sfAccount);
|
||||
|
||||
bool srcHigh = srcAccID > issuerAccID;
|
||||
bool dstHigh = dstAccID > issuerAccID;
|
||||
|
||||
// check for freezing, auth, no ripple and TL sanity
|
||||
{
|
||||
TER result = trustTransferAllowed(
|
||||
view, {srcAccID, dstAccID}, {currency, issuerAccID}, j);
|
||||
|
||||
JLOG(j.trace())
|
||||
<< "trustTransferLockedBalance: trustTransferAlowed result="
|
||||
<< result;
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
// ensure source line exists
|
||||
Keylet klSrcLine{keylet::line(srcAccID, issuerAccID, currency)};
|
||||
SLEPtr sleSrcLine = peek(klSrcLine);
|
||||
|
||||
if (!sleSrcLine)
|
||||
return tecNO_LINE;
|
||||
|
||||
// can't transfer a locked balance that does not exist
|
||||
if (!sleSrcLine->isFieldPresent(sfLockedBalance) ||
|
||||
!sleSrcLine->isFieldPresent(sfLockCount))
|
||||
{
|
||||
JLOG(j.trace()) << "trustTransferLockedBalance could not find "
|
||||
"sfLockedBalance/sfLockCount on source line";
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
}
|
||||
|
||||
// decrement source balance
|
||||
{
|
||||
STAmount priorBalance =
|
||||
srcHigh ? -((*sleSrcLine)[sfBalance]) : (*sleSrcLine)[sfBalance];
|
||||
|
||||
STAmount priorLockedBalance = srcHigh
|
||||
? -((*sleSrcLine)[sfLockedBalance])
|
||||
: (*sleSrcLine)[sfLockedBalance];
|
||||
|
||||
uint32_t priorLockCount = (*sleSrcLine)[sfLockCount];
|
||||
|
||||
AccountID srcIssuerAccID =
|
||||
sleSrcLine->getFieldAmount(srcHigh ? sfLowLimit : sfHighLimit)
|
||||
.getIssuer();
|
||||
|
||||
// check they have sufficient funds
|
||||
if (amount > priorLockedBalance)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "trustTransferLockedBalance amount > lockedBalance: "
|
||||
<< "amount=" << amount
|
||||
<< " lockedBalance=" << priorLockedBalance;
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
}
|
||||
|
||||
STAmount finalBalance = priorBalance - amount;
|
||||
|
||||
STAmount finalLockedBalance = priorLockedBalance - amount;
|
||||
|
||||
uint32_t finalLockCount = priorLockCount + deltaLockCount;
|
||||
|
||||
// check if there is significant precision loss
|
||||
if (!isAddable(priorBalance, amount) ||
|
||||
!isAddable(priorLockedBalance, amount))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
// sanity check possible overflows on the lock counter
|
||||
if ((deltaLockCount > 0 && priorLockCount > finalLockCount) ||
|
||||
(deltaLockCount < 0 && priorLockCount < finalLockCount) ||
|
||||
(deltaLockCount == 0 && priorLockCount != finalLockCount))
|
||||
return tecINTERNAL;
|
||||
|
||||
// this should never happen but defensively check it here before
|
||||
// updating sle
|
||||
if (finalBalance < beast::zero || finalLockedBalance < beast::zero)
|
||||
{
|
||||
JLOG(j.warn()) << "trustTransferLockedBalance results in a "
|
||||
"negative balance on source line";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if constexpr (!dryRun)
|
||||
{
|
||||
sleSrcLine->setFieldAmount(
|
||||
sfBalance, srcHigh ? -finalBalance : finalBalance);
|
||||
|
||||
if (finalLockedBalance == beast::zero || finalLockCount == 0)
|
||||
{
|
||||
sleSrcLine->makeFieldAbsent(sfLockedBalance);
|
||||
sleSrcLine->makeFieldAbsent(sfLockCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleSrcLine->setFieldAmount(
|
||||
sfLockedBalance,
|
||||
srcHigh ? -finalLockedBalance : finalLockedBalance);
|
||||
sleSrcLine->setFieldU32(sfLockCount, finalLockCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dstLow XNOR srcLow tells us if we need to flip the balance amount
|
||||
// on the destination line
|
||||
bool flipDstAmt = !((dstHigh && srcHigh) || (!dstHigh && !srcHigh));
|
||||
|
||||
// compute transfer fee, if any
|
||||
auto xferRate = transferRate(view, issuerAccID);
|
||||
|
||||
// the destination will sometimes get less depending on xfer rate
|
||||
// with any difference in tokens burned
|
||||
auto dstAmt = xferRate == parityRate
|
||||
? amount
|
||||
: multiplyRound(amount, xferRate, amount.issue(), true);
|
||||
|
||||
// check for a destination line
|
||||
Keylet klDstLine = keylet::line(dstAccID, issuerAccID, currency);
|
||||
SLEPtr sleDstLine = peek(klDstLine);
|
||||
|
||||
if (!sleDstLine)
|
||||
{
|
||||
// in most circumstances a missing destination line is a deal breaker
|
||||
if (actingAccID != dstAccID && srcAccID != dstAccID)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
STAmount dstBalanceDrops = sleDstAcc->getFieldAmount(sfBalance);
|
||||
|
||||
// no dst line exists, we might be able to create one...
|
||||
if (std::uint32_t const ownerCount = {sleDstAcc->at(sfOwnerCount)};
|
||||
dstBalanceDrops < view.fees().accountReserve(ownerCount + 1))
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
|
||||
// yes we can... we will
|
||||
|
||||
if constexpr (!dryRun)
|
||||
{
|
||||
// clang-format off
|
||||
if (TER const ter = trustCreate(
|
||||
view,
|
||||
!dstHigh, // is dest low?
|
||||
issuerAccID, // source
|
||||
dstAccID, // destination
|
||||
klDstLine.key, // ledger index
|
||||
sleDstAcc, // Account to add to
|
||||
false, // authorize account
|
||||
(sleDstAcc->getFlags() & lsfDefaultRipple) == 0,
|
||||
false, // freeze trust line
|
||||
flipDstAmt ? -dstAmt : dstAmt, // initial balance
|
||||
Issue(currency, dstAccID), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
j); // journal
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
else
|
||||
{
|
||||
// the dst line does exist, and it would have been checked above
|
||||
// in trustTransferAllowed for NoRipple and Freeze flags
|
||||
|
||||
// check the limit
|
||||
STAmount dstLimit =
|
||||
dstHigh ? (*sleDstLine)[sfHighLimit] : (*sleDstLine)[sfLowLimit];
|
||||
|
||||
STAmount priorBalance =
|
||||
dstHigh ? -((*sleDstLine)[sfBalance]) : (*sleDstLine)[sfBalance];
|
||||
|
||||
STAmount finalBalance = priorBalance + dstAmt;
|
||||
|
||||
if (finalBalance < priorBalance)
|
||||
{
|
||||
JLOG(j.warn()) << "trustTransferLockedBalance resulted in a "
|
||||
"lower/equal final balance on dest line";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if (finalBalance > dstLimit && actingAccID != dstAccID)
|
||||
{
|
||||
JLOG(j.trace()) << "trustTransferLockedBalance would increase dest "
|
||||
"line above limit without permission";
|
||||
return tecPATH_DRY;
|
||||
}
|
||||
|
||||
// check if there is significant precision loss
|
||||
if (!isAddable(priorBalance, dstAmt))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
if constexpr (!dryRun)
|
||||
sleDstLine->setFieldAmount(
|
||||
sfBalance, dstHigh ? -finalBalance : finalBalance);
|
||||
}
|
||||
|
||||
if constexpr (!dryRun)
|
||||
{
|
||||
static_assert(std::is_same<V, ApplyView>::value);
|
||||
|
||||
// check if source line ended up in default state and adjust owner count
|
||||
// if it did
|
||||
if (isTrustDefault(sleSrcAcc, sleSrcLine))
|
||||
{
|
||||
uint32_t flags = sleSrcLine->getFieldU32(sfFlags);
|
||||
LedgerSpecificFlags fReserve{
|
||||
srcHigh ? lsfHighReserve : lsfLowReserve};
|
||||
if (flags & fReserve)
|
||||
{
|
||||
sleSrcLine->setFieldU32(sfFlags, flags & ~fReserve);
|
||||
adjustOwnerCount(view, sleSrcAcc, -1, j);
|
||||
view.update(sleSrcAcc);
|
||||
}
|
||||
}
|
||||
|
||||
view.update(sleSrcLine);
|
||||
|
||||
// a destination line already existed and was updated
|
||||
if (sleDstLine)
|
||||
view.update(sleDstLine);
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/** Check if the account lacks required authorization.
|
||||
* Return tecNO_AUTH or tecNO_LINE if it does
|
||||
* and tesSUCCESS otherwise.
|
||||
|
||||
@@ -109,12 +109,162 @@ ApplyStateTable::visit(
|
||||
}
|
||||
}
|
||||
|
||||
// return a txmeta and a set of created nodes
|
||||
// does not modify ledger
|
||||
std::pair<TxMeta, ApplyStateTable::Mods>
|
||||
ApplyStateTable::generateTxMeta(
|
||||
OpenView const& to,
|
||||
STTx const& tx,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::vector<STObject> const& hookExecution,
|
||||
beast::Journal j)
|
||||
{
|
||||
TxMeta meta(tx.getTransactionID(), to.seq());
|
||||
if (deliver)
|
||||
meta.setDeliveredAmount(*deliver);
|
||||
|
||||
if (!hookExecution.empty())
|
||||
meta.setHookExecutions(STArray{hookExecution, sfHookExecutions});
|
||||
|
||||
Mods newMod;
|
||||
for (auto& item : items_)
|
||||
{
|
||||
SField const* type;
|
||||
switch (item.second.first)
|
||||
{
|
||||
default:
|
||||
case Action::cache:
|
||||
continue;
|
||||
case Action::erase:
|
||||
type = &sfDeletedNode;
|
||||
break;
|
||||
case Action::insert:
|
||||
type = &sfCreatedNode;
|
||||
break;
|
||||
case Action::modify:
|
||||
type = &sfModifiedNode;
|
||||
break;
|
||||
}
|
||||
auto const origNode = to.read(keylet::unchecked(item.first));
|
||||
auto curNode = item.second.second;
|
||||
if ((type == &sfModifiedNode) && (*curNode == *origNode))
|
||||
continue;
|
||||
std::uint16_t nodeType = curNode
|
||||
? curNode->getFieldU16(sfLedgerEntryType)
|
||||
: origNode->getFieldU16(sfLedgerEntryType);
|
||||
meta.setAffectedNode(item.first, *type, nodeType);
|
||||
if (type == &sfDeletedNode)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
origNode && curNode,
|
||||
"ripple::detail::ApplyStateTable::apply : valid nodes for "
|
||||
"deletion");
|
||||
threadOwners(to, meta, origNode, newMod, j);
|
||||
|
||||
STObject prevs(sfPreviousFields);
|
||||
for (auto const& obj : *origNode)
|
||||
{
|
||||
// go through the original node for
|
||||
// modified fields saved on modification
|
||||
if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) &&
|
||||
!curNode->hasMatchingEntry(obj))
|
||||
prevs.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!prevs.empty())
|
||||
meta.getAffectedNode(item.first).emplace_back(std::move(prevs));
|
||||
|
||||
STObject finals(sfFinalFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// go through the final node for final fields
|
||||
if (obj.getFName().shouldMeta(
|
||||
SField::sMD_Always | SField::sMD_DeleteFinal))
|
||||
finals.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!finals.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(finals));
|
||||
}
|
||||
else if (type == &sfModifiedNode)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
curNode && origNode,
|
||||
"ripple::detail::ApplyStateTable::apply : valid nodes for "
|
||||
"modification");
|
||||
|
||||
if (curNode->isThreadedType(to.rules())) // thread transaction to
|
||||
// node item modified
|
||||
threadItem(meta, curNode);
|
||||
|
||||
STObject prevs(sfPreviousFields);
|
||||
for (auto const& obj : *origNode)
|
||||
{
|
||||
// search the original node for values saved on modify
|
||||
if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) &&
|
||||
!curNode->hasMatchingEntry(obj))
|
||||
prevs.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!prevs.empty())
|
||||
meta.getAffectedNode(item.first).emplace_back(std::move(prevs));
|
||||
|
||||
STObject finals(sfFinalFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// search the final node for values saved always
|
||||
if (obj.getFName().shouldMeta(
|
||||
SField::sMD_Always | SField::sMD_ChangeNew))
|
||||
finals.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!finals.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(finals));
|
||||
}
|
||||
else if (type == &sfCreatedNode) // if created, thread to owner(s)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
curNode && !origNode,
|
||||
"ripple::detail::ApplyStateTable::apply : valid nodes for "
|
||||
"creation");
|
||||
threadOwners(to, meta, curNode, newMod, j);
|
||||
|
||||
if (curNode->isThreadedType(to.rules())) // always thread to self
|
||||
threadItem(meta, curNode);
|
||||
|
||||
STObject news(sfNewFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// save non-default values
|
||||
if (!obj.isDefault() &&
|
||||
obj.getFName().shouldMeta(
|
||||
SField::sMD_Create | SField::sMD_Always))
|
||||
news.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!news.empty())
|
||||
meta.getAffectedNode(item.first).emplace_back(std::move(news));
|
||||
}
|
||||
else
|
||||
{
|
||||
UNREACHABLE(
|
||||
"ripple::detail::ApplyStateTable::apply : unsupported "
|
||||
"operation type");
|
||||
}
|
||||
}
|
||||
|
||||
return {meta, newMod};
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::apply(
|
||||
OpenView& to,
|
||||
STTx const& tx,
|
||||
TER ter,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::vector<STObject> const& hookExecution,
|
||||
beast::Journal j)
|
||||
{
|
||||
// Build metadata and insert
|
||||
@@ -123,142 +273,8 @@ ApplyStateTable::apply(
|
||||
std::shared_ptr<Serializer> sMeta;
|
||||
if (!to.open())
|
||||
{
|
||||
TxMeta meta(tx.getTransactionID(), to.seq());
|
||||
if (deliver)
|
||||
meta.setDeliveredAmount(*deliver);
|
||||
Mods newMod;
|
||||
for (auto& item : items_)
|
||||
{
|
||||
SField const* type;
|
||||
switch (item.second.first)
|
||||
{
|
||||
default:
|
||||
case Action::cache:
|
||||
continue;
|
||||
case Action::erase:
|
||||
type = &sfDeletedNode;
|
||||
break;
|
||||
case Action::insert:
|
||||
type = &sfCreatedNode;
|
||||
break;
|
||||
case Action::modify:
|
||||
type = &sfModifiedNode;
|
||||
break;
|
||||
}
|
||||
auto const origNode = to.read(keylet::unchecked(item.first));
|
||||
auto curNode = item.second.second;
|
||||
if ((type == &sfModifiedNode) && (*curNode == *origNode))
|
||||
continue;
|
||||
std::uint16_t nodeType = curNode
|
||||
? curNode->getFieldU16(sfLedgerEntryType)
|
||||
: origNode->getFieldU16(sfLedgerEntryType);
|
||||
meta.setAffectedNode(item.first, *type, nodeType);
|
||||
if (type == &sfDeletedNode)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
origNode && curNode,
|
||||
"ripple::detail::ApplyStateTable::apply : valid nodes for "
|
||||
"deletion");
|
||||
threadOwners(to, meta, origNode, newMod, j);
|
||||
|
||||
STObject prevs(sfPreviousFields);
|
||||
for (auto const& obj : *origNode)
|
||||
{
|
||||
// go through the original node for
|
||||
// modified fields saved on modification
|
||||
if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) &&
|
||||
!curNode->hasMatchingEntry(obj))
|
||||
prevs.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!prevs.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(prevs));
|
||||
|
||||
STObject finals(sfFinalFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// go through the final node for final fields
|
||||
if (obj.getFName().shouldMeta(
|
||||
SField::sMD_Always | SField::sMD_DeleteFinal))
|
||||
finals.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!finals.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(finals));
|
||||
}
|
||||
else if (type == &sfModifiedNode)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
curNode && origNode,
|
||||
"ripple::detail::ApplyStateTable::apply : valid nodes for "
|
||||
"modification");
|
||||
|
||||
if (curNode->isThreadedType(
|
||||
to.rules())) // thread transaction to node
|
||||
// item modified
|
||||
threadItem(meta, curNode);
|
||||
|
||||
STObject prevs(sfPreviousFields);
|
||||
for (auto const& obj : *origNode)
|
||||
{
|
||||
// search the original node for values saved on modify
|
||||
if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) &&
|
||||
!curNode->hasMatchingEntry(obj))
|
||||
prevs.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!prevs.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(prevs));
|
||||
|
||||
STObject finals(sfFinalFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// search the final node for values saved always
|
||||
if (obj.getFName().shouldMeta(
|
||||
SField::sMD_Always | SField::sMD_ChangeNew))
|
||||
finals.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!finals.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(finals));
|
||||
}
|
||||
else if (type == &sfCreatedNode) // if created, thread to owner(s)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
curNode && !origNode,
|
||||
"ripple::detail::ApplyStateTable::apply : valid nodes for "
|
||||
"creation");
|
||||
threadOwners(to, meta, curNode, newMod, j);
|
||||
|
||||
if (curNode->isThreadedType(
|
||||
to.rules())) // always thread to self
|
||||
threadItem(meta, curNode);
|
||||
|
||||
STObject news(sfNewFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// save non-default values
|
||||
if (!obj.isDefault() &&
|
||||
obj.getFName().shouldMeta(
|
||||
SField::sMD_Create | SField::sMD_Always))
|
||||
news.emplace_back(obj);
|
||||
}
|
||||
|
||||
if (!news.empty())
|
||||
meta.getAffectedNode(item.first)
|
||||
.emplace_back(std::move(news));
|
||||
}
|
||||
else
|
||||
{
|
||||
UNREACHABLE(
|
||||
"ripple::detail::ApplyStateTable::apply : unsupported "
|
||||
"operation type");
|
||||
}
|
||||
}
|
||||
// generate meta
|
||||
auto [meta, newMod] = generateTxMeta(to, tx, deliver, hookExecution, j);
|
||||
|
||||
// add any new modified nodes to the modification set
|
||||
for (auto& mod : newMod)
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <xrpl/protocol/TxMeta.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace ripple {
|
||||
namespace detail {
|
||||
@@ -37,6 +38,7 @@ class ApplyStateTable
|
||||
{
|
||||
public:
|
||||
using key_type = ReadView::key_type;
|
||||
using Mods = hash_map<key_type, std::shared_ptr<SLE>>;
|
||||
|
||||
private:
|
||||
enum class Action {
|
||||
@@ -64,12 +66,21 @@ public:
|
||||
void
|
||||
apply(RawView& to) const;
|
||||
|
||||
std::pair<TxMeta, Mods>
|
||||
generateTxMeta(
|
||||
OpenView const& to,
|
||||
STTx const& tx,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::vector<STObject> const& hookExecution,
|
||||
beast::Journal j);
|
||||
|
||||
void
|
||||
apply(
|
||||
OpenView& to,
|
||||
STTx const& tx,
|
||||
TER ter,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::vector<STObject> const& hookExecution,
|
||||
beast::Journal j);
|
||||
|
||||
bool
|
||||
@@ -125,8 +136,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
using Mods = hash_map<key_type, std::shared_ptr<SLE>>;
|
||||
|
||||
static void
|
||||
threadItem(TxMeta& meta, std::shared_ptr<SLE> const& to);
|
||||
|
||||
|
||||
@@ -174,5 +174,131 @@ ApplyViewBase::rawDestroyXRP(XRPAmount const& fee)
|
||||
items_.destroyXRP(fee);
|
||||
}
|
||||
|
||||
std::map<std::tuple<AccountID, AccountID, Currency>, STAmount>
|
||||
ApplyViewBase::balanceChanges(ReadView const& view) const
|
||||
{
|
||||
using key_t = std::tuple<AccountID, AccountID, Currency>;
|
||||
// Map of delta trust lines. As a special case, when both ends of the trust
|
||||
// line are the same currency, then it's delta currency for that issuer. To
|
||||
// get the change in XRP balance, Account == root, issuer == root, currency
|
||||
// == XRP
|
||||
std::map<key_t, STAmount> result;
|
||||
|
||||
// populate a dictionary with low/high/currency/delta. This can be
|
||||
// compared with the other versions payment code.
|
||||
auto each = [&result](
|
||||
uint256 const& key,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after) {
|
||||
STAmount oldBalance;
|
||||
STAmount newBalance;
|
||||
AccountID lowID;
|
||||
AccountID highID;
|
||||
|
||||
// before is read from prev view
|
||||
if (isDelete)
|
||||
{
|
||||
if (!before)
|
||||
return;
|
||||
|
||||
auto const bt = before->getType();
|
||||
switch (bt)
|
||||
{
|
||||
case ltACCOUNT_ROOT:
|
||||
lowID = xrpAccount();
|
||||
highID = (*before)[sfAccount];
|
||||
oldBalance = (*before)[sfBalance];
|
||||
newBalance = oldBalance.zeroed();
|
||||
break;
|
||||
case ltRIPPLE_STATE:
|
||||
lowID = (*before)[sfLowLimit].getIssuer();
|
||||
highID = (*before)[sfHighLimit].getIssuer();
|
||||
oldBalance = (*before)[sfBalance];
|
||||
newBalance = oldBalance.zeroed();
|
||||
break;
|
||||
case ltOFFER:
|
||||
// TBD
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!before)
|
||||
{
|
||||
// insert
|
||||
auto const at = after->getType();
|
||||
switch (at)
|
||||
{
|
||||
case ltACCOUNT_ROOT:
|
||||
lowID = xrpAccount();
|
||||
highID = (*after)[sfAccount];
|
||||
newBalance = (*after)[sfBalance];
|
||||
oldBalance = newBalance.zeroed();
|
||||
break;
|
||||
case ltRIPPLE_STATE:
|
||||
lowID = (*after)[sfLowLimit].getIssuer();
|
||||
highID = (*after)[sfHighLimit].getIssuer();
|
||||
newBalance = (*after)[sfBalance];
|
||||
oldBalance = newBalance.zeroed();
|
||||
break;
|
||||
case ltOFFER:
|
||||
// TBD
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// modify
|
||||
auto const at = after->getType();
|
||||
XRPL_ASSERT(
|
||||
at == before->getType(),
|
||||
"ripple::PaymentSandbox::balanceChanges : after and before "
|
||||
"types matching");
|
||||
|
||||
switch (at)
|
||||
{
|
||||
case ltACCOUNT_ROOT:
|
||||
lowID = xrpAccount();
|
||||
highID = (*after)[sfAccount];
|
||||
oldBalance = (*before)[sfBalance];
|
||||
newBalance = (*after)[sfBalance];
|
||||
break;
|
||||
case ltRIPPLE_STATE:
|
||||
lowID = (*after)[sfLowLimit].getIssuer();
|
||||
highID = (*after)[sfHighLimit].getIssuer();
|
||||
oldBalance = (*before)[sfBalance];
|
||||
newBalance = (*after)[sfBalance];
|
||||
break;
|
||||
case ltOFFER:
|
||||
// TBD
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// The following are now set, put them in the map
|
||||
auto delta = newBalance - oldBalance;
|
||||
auto const cur = newBalance.getCurrency();
|
||||
result[std::make_tuple(lowID, highID, cur)] = delta;
|
||||
auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta);
|
||||
if (r.second)
|
||||
{
|
||||
r.first->second += delta;
|
||||
}
|
||||
|
||||
delta.negate();
|
||||
r = result.emplace(std::make_tuple(highID, highID, cur), delta);
|
||||
if (r.second)
|
||||
{
|
||||
r.first->second += delta;
|
||||
}
|
||||
};
|
||||
items_.visit(view, each);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ripple
|
||||
@@ -119,6 +119,13 @@ public:
|
||||
void
|
||||
rawDestroyXRP(XRPAmount const& feeDrops) override;
|
||||
|
||||
// Map of delta trust lines. As a special case, when both ends of the trust
|
||||
// line are the same currency, then it's delta currency for that issuer. To
|
||||
// get the change in XRP balance, Account == root, issuer == root, currency
|
||||
// == XRP
|
||||
std::map<std::tuple<AccountID, AccountID, Currency>, STAmount>
|
||||
balanceChanges(ReadView const& view) const;
|
||||
|
||||
protected:
|
||||
ApplyFlags flags_;
|
||||
ReadView const* base_;
|
||||
|
||||
@@ -31,7 +31,18 @@ ApplyViewImpl::ApplyViewImpl(ReadView const* base, ApplyFlags flags)
|
||||
void
|
||||
ApplyViewImpl::apply(OpenView& to, STTx const& tx, TER ter, beast::Journal j)
|
||||
{
|
||||
items_.apply(to, tx, ter, deliver_, j);
|
||||
items_.apply(to, tx, ter, deliver_, hookExecution_, j);
|
||||
}
|
||||
|
||||
TxMeta
|
||||
ApplyViewImpl::generateProvisionalMeta(
|
||||
OpenView const& to,
|
||||
STTx const& tx,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto [meta, _] = items_.generateTxMeta(to, tx, deliver_, hookExecution_, j);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
|
||||
@@ -267,6 +267,7 @@ OpenView::rawTxInsert(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(key),
|
||||
std::forward_as_tuple(txn, metaData));
|
||||
|
||||
if (!result.second)
|
||||
LogicError("rawTxInsert: duplicate TX id" + to_string(key));
|
||||
}
|
||||
|
||||
@@ -271,126 +271,7 @@ PaymentSandbox::apply(PaymentSandbox& to)
|
||||
std::map<std::tuple<AccountID, AccountID, Currency>, STAmount>
|
||||
PaymentSandbox::balanceChanges(ReadView const& view) const
|
||||
{
|
||||
using key_t = std::tuple<AccountID, AccountID, Currency>;
|
||||
// Map of delta trust lines. As a special case, when both ends of the trust
|
||||
// line are the same currency, then it's delta currency for that issuer. To
|
||||
// get the change in XRP balance, Account == root, issuer == root, currency
|
||||
// == XRP
|
||||
std::map<key_t, STAmount> result;
|
||||
|
||||
// populate a dictionary with low/high/currency/delta. This can be
|
||||
// compared with the other versions payment code.
|
||||
auto each = [&result](
|
||||
uint256 const& key,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after) {
|
||||
STAmount oldBalance;
|
||||
STAmount newBalance;
|
||||
AccountID lowID;
|
||||
AccountID highID;
|
||||
|
||||
// before is read from prev view
|
||||
if (isDelete)
|
||||
{
|
||||
if (!before)
|
||||
return;
|
||||
|
||||
auto const bt = before->getType();
|
||||
switch (bt)
|
||||
{
|
||||
case ltACCOUNT_ROOT:
|
||||
lowID = xrpAccount();
|
||||
highID = (*before)[sfAccount];
|
||||
oldBalance = (*before)[sfBalance];
|
||||
newBalance = oldBalance.zeroed();
|
||||
break;
|
||||
case ltRIPPLE_STATE:
|
||||
lowID = (*before)[sfLowLimit].getIssuer();
|
||||
highID = (*before)[sfHighLimit].getIssuer();
|
||||
oldBalance = (*before)[sfBalance];
|
||||
newBalance = oldBalance.zeroed();
|
||||
break;
|
||||
case ltOFFER:
|
||||
// TBD
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!before)
|
||||
{
|
||||
// insert
|
||||
auto const at = after->getType();
|
||||
switch (at)
|
||||
{
|
||||
case ltACCOUNT_ROOT:
|
||||
lowID = xrpAccount();
|
||||
highID = (*after)[sfAccount];
|
||||
newBalance = (*after)[sfBalance];
|
||||
oldBalance = newBalance.zeroed();
|
||||
break;
|
||||
case ltRIPPLE_STATE:
|
||||
lowID = (*after)[sfLowLimit].getIssuer();
|
||||
highID = (*after)[sfHighLimit].getIssuer();
|
||||
newBalance = (*after)[sfBalance];
|
||||
oldBalance = newBalance.zeroed();
|
||||
break;
|
||||
case ltOFFER:
|
||||
// TBD
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// modify
|
||||
auto const at = after->getType();
|
||||
XRPL_ASSERT(
|
||||
at == before->getType(),
|
||||
"ripple::PaymentSandbox::balanceChanges : after and before "
|
||||
"types matching");
|
||||
switch (at)
|
||||
{
|
||||
case ltACCOUNT_ROOT:
|
||||
lowID = xrpAccount();
|
||||
highID = (*after)[sfAccount];
|
||||
oldBalance = (*before)[sfBalance];
|
||||
newBalance = (*after)[sfBalance];
|
||||
break;
|
||||
case ltRIPPLE_STATE:
|
||||
lowID = (*after)[sfLowLimit].getIssuer();
|
||||
highID = (*after)[sfHighLimit].getIssuer();
|
||||
oldBalance = (*before)[sfBalance];
|
||||
newBalance = (*after)[sfBalance];
|
||||
break;
|
||||
case ltOFFER:
|
||||
// TBD
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// The following are now set, put them in the map
|
||||
auto delta = newBalance - oldBalance;
|
||||
auto const cur = newBalance.getCurrency();
|
||||
result[std::make_tuple(lowID, highID, cur)] = delta;
|
||||
auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta);
|
||||
if (r.second)
|
||||
{
|
||||
r.first->second += delta;
|
||||
}
|
||||
|
||||
delta.negate();
|
||||
r = result.emplace(std::make_tuple(highID, highID, cur), delta);
|
||||
if (r.second)
|
||||
{
|
||||
r.first->second += delta;
|
||||
}
|
||||
};
|
||||
items_.visit(view, each);
|
||||
return result;
|
||||
return ApplyViewBase::balanceChanges(view);
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
|
||||
@@ -302,6 +302,29 @@ accountHolds(
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
}
|
||||
|
||||
// If tokens can be escrowed then they can be locked in the trustline
|
||||
// which means we must never spend them until the escrow is released.
|
||||
if (view.rules().enabled(featurePaychanAndEscrowForTokens) &&
|
||||
sle->isFieldPresent(sfLockedBalance))
|
||||
{
|
||||
STAmount lockedBalance = sle->getFieldAmount(sfLockedBalance);
|
||||
STAmount spendableBalance =
|
||||
amount - (account > issuer ? -lockedBalance : lockedBalance);
|
||||
|
||||
// RH NOTE: this is defensively programmed, it should never fire
|
||||
// if something bad does happen the trustline acts as a frozen line.
|
||||
if (spendableBalance < beast::zero || spendableBalance > amount)
|
||||
{
|
||||
JLOG(j.error())
|
||||
<< "SpendableBalance has illegal value in accountHolds "
|
||||
<< spendableBalance;
|
||||
amount.clear(Issue{currency, issuer});
|
||||
}
|
||||
else
|
||||
amount = spendableBalance;
|
||||
}
|
||||
|
||||
amount.setIssuer(issuer);
|
||||
}
|
||||
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(account)
|
||||
@@ -1006,6 +1029,58 @@ trustDelete(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
isTrustDefault(
|
||||
std::shared_ptr<SLE> const& acc,
|
||||
std::shared_ptr<SLE> const& line)
|
||||
{
|
||||
assert(acc && line);
|
||||
|
||||
uint32_t tlFlags = line->getFieldU32(sfFlags);
|
||||
|
||||
AccountID highAccID = line->getFieldAmount(sfHighLimit).issue().account;
|
||||
AccountID lowAccID = line->getFieldAmount(sfLowLimit).issue().account;
|
||||
|
||||
AccountID accID = acc->getAccountID(sfAccount);
|
||||
|
||||
assert(accID == highAccID || accID == lowAccID);
|
||||
|
||||
bool high = accID == highAccID;
|
||||
|
||||
uint32_t acFlags = line->getFieldU32(sfFlags);
|
||||
|
||||
const auto fNoRipple{high ? lsfHighNoRipple : lsfLowNoRipple};
|
||||
const auto fFreeze{high ? lsfHighFreeze : lsfLowFreeze};
|
||||
|
||||
if (tlFlags & fFreeze)
|
||||
return false;
|
||||
|
||||
if ((acFlags & lsfDefaultRipple) && (tlFlags & fNoRipple))
|
||||
return false;
|
||||
|
||||
if (line->getFieldAmount(sfBalance) != beast::zero)
|
||||
return false;
|
||||
|
||||
if (line->isFieldPresent(sfLockedBalance))
|
||||
return false;
|
||||
|
||||
if (line->getFieldAmount(high ? sfHighLimit : sfLowLimit) != beast::zero)
|
||||
return false;
|
||||
|
||||
uint32_t qualityIn =
|
||||
line->getFieldU32(high ? sfHighQualityIn : sfLowQualityIn);
|
||||
uint32_t qualityOut =
|
||||
line->getFieldU32(high ? sfHighQualityOut : sfLowQualityOut);
|
||||
|
||||
if (qualityIn && qualityIn != QUALITY_ONE)
|
||||
return false;
|
||||
|
||||
if (qualityOut && qualityOut != QUALITY_ONE)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TER
|
||||
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
|
||||
{
|
||||
|
||||
@@ -473,6 +473,25 @@ private:
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
// fee [<txblob>]
|
||||
Json::Value
|
||||
parseFee(Json::Value const& jvParams)
|
||||
{
|
||||
Json::Value jvRequest(Json::objectValue);
|
||||
|
||||
if (jvParams.size() == 0)
|
||||
{
|
||||
return jvRequest;
|
||||
}
|
||||
else if (jvParams.size() > 1)
|
||||
{
|
||||
return rpcError(rpcINVALID_PARAMS);
|
||||
}
|
||||
|
||||
jvRequest[jss::tx_blob] = jvParams[0u].asString();
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
// get_counts [<min_count>]
|
||||
Json::Value
|
||||
parseGetCounts(Json::Value const& jvParams)
|
||||
@@ -727,6 +746,13 @@ private:
|
||||
return parseAccountRaw2(jvParams, jss::peer);
|
||||
}
|
||||
|
||||
// account_namespace <account> <namespace hex> [<ledger>]
|
||||
Json::Value
|
||||
parseAccountNamespace(Json::Value const& jvParams)
|
||||
{
|
||||
return parseAccountNamespaceRaw(jvParams);
|
||||
}
|
||||
|
||||
// account_channels <account> <account>|"" [<ledger>]
|
||||
Json::Value
|
||||
parseAccountChannels(Json::Value const& jvParams)
|
||||
@@ -807,6 +833,61 @@ private:
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
parseAccountNamespaceRaw(Json::Value const& jvParams)
|
||||
{
|
||||
auto const nParams = jvParams.size();
|
||||
Json::Value jvRequest(Json::objectValue);
|
||||
|
||||
for (auto i = 0; i < nParams; ++i)
|
||||
{
|
||||
std::string strParam = jvParams[i].asString();
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
// account
|
||||
if (parseBase58<PublicKey>(
|
||||
TokenType::AccountPublic, strParam) ||
|
||||
parseBase58<AccountID>(strParam) ||
|
||||
parseGenericSeed(strParam))
|
||||
{
|
||||
jvRequest[jss::account] = std::move(strParam);
|
||||
}
|
||||
else
|
||||
{
|
||||
return rpcError(rpcACT_MALFORMED);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == 1)
|
||||
{
|
||||
// namespace hex
|
||||
uint256 namespaceId;
|
||||
if (!namespaceId.parseHex(strParam))
|
||||
return rpcError(rpcNAMESPACE_MALFORMED);
|
||||
jvRequest[jss::namespace_id] = to_string(namespaceId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == 2)
|
||||
{
|
||||
// ledger index (optional)
|
||||
if (strParam.empty())
|
||||
break;
|
||||
|
||||
if (jvParseLedger(jvRequest, strParam))
|
||||
break;
|
||||
else
|
||||
return rpcError(rpcLGR_IDX_MALFORMED);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
parseAccountRaw2(Json::Value const& jvParams, char const* const acc2Field)
|
||||
{
|
||||
@@ -1174,6 +1255,7 @@ public:
|
||||
{"account_currencies", &RPCParser::parseAccountCurrencies, 1, 3},
|
||||
{"account_info", &RPCParser::parseAccountItems, 1, 3},
|
||||
{"account_lines", &RPCParser::parseAccountLines, 1, 5},
|
||||
{"account_namespace", &RPCParser::parseAccountNamespace, 2, 3},
|
||||
{"account_channels", &RPCParser::parseAccountChannels, 1, 3},
|
||||
{"account_nfts", &RPCParser::parseAccountItems, 1, 5},
|
||||
{"account_objects", &RPCParser::parseAccountItems, 1, 5},
|
||||
@@ -1189,6 +1271,7 @@ public:
|
||||
{"consensus_info", &RPCParser::parseAsIs, 0, 0},
|
||||
{"deposit_authorized", &RPCParser::parseDepositAuthorized, 2, 11},
|
||||
{"feature", &RPCParser::parseFeature, 0, 2},
|
||||
{"fee", &RPCParser::parseFee, 0, 1},
|
||||
{"fetch_info", &RPCParser::parseFetchInfo, 0, 1},
|
||||
{"gateway_balances", &RPCParser::parseGatewayBalances, 1, -1},
|
||||
{"get_counts", &RPCParser::parseGetCounts, 0, 1},
|
||||
|
||||
@@ -1251,6 +1251,15 @@ PeerImp::handleTransaction(
|
||||
auto stx = std::make_shared<STTx const>(sit);
|
||||
uint256 txID = stx->getTransactionID();
|
||||
|
||||
// Charge strongly for attempting to relay a txn with sfEmitDetails
|
||||
if (stx->isFieldPresent(sfEmitDetails))
|
||||
{
|
||||
JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing "
|
||||
"sfEmitDetails (handleTransaction).";
|
||||
// fee_ = Resource::feeHighBurdenPeer; // RH TODO: enable when
|
||||
// relay bug is fixed return;
|
||||
}
|
||||
|
||||
int flags;
|
||||
constexpr std::chrono::seconds tx_interval = 10s;
|
||||
|
||||
@@ -2757,6 +2766,16 @@ PeerImp::checkTransaction(
|
||||
// VFALCO TODO Rewrite to not use exceptions
|
||||
try
|
||||
{
|
||||
// charge strongly for relaying Hook emitted txns
|
||||
if (stx->isFieldPresent(sfEmitDetails))
|
||||
{
|
||||
JLOG(p_journal_.warn()) << "Ignoring Network relayed Tx containing "
|
||||
"sfEmitDetails (checkSignature).";
|
||||
// charge(Resource::feeHighBurdenPeer); //RH TODO: enable this
|
||||
// charging when relay bug fix
|
||||
return;
|
||||
}
|
||||
|
||||
// Expired?
|
||||
if (stx->isFieldPresent(sfLastLedgerSequence) &&
|
||||
(stx->getFieldU32(sfLastLedgerSequence) <
|
||||
|
||||
@@ -86,6 +86,7 @@ Handler const handlerArray[]{
|
||||
Role::USER,
|
||||
NO_CONDITION},
|
||||
{"account_lines", byRef(&doAccountLines), Role::USER, NO_CONDITION},
|
||||
{"account_namespace", byRef(&doAccountNamespace), Role::USER, NO_CONDITION},
|
||||
{"account_channels", byRef(&doAccountChannels), Role::USER, NO_CONDITION},
|
||||
{"account_nfts", byRef(&doAccountNFTs), Role::USER, NO_CONDITION},
|
||||
{"account_objects", byRef(&doAccountObjects), Role::USER, NO_CONDITION},
|
||||
|
||||
@@ -342,6 +342,89 @@ getAccountObjects(
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
getAccountNamespace(
|
||||
ReadView const& ledger,
|
||||
AccountID const& account,
|
||||
uint256 const& ns,
|
||||
uint256 dirIndex,
|
||||
uint256 const& entryIndex,
|
||||
std::uint32_t const limit,
|
||||
Json::Value& jvResult)
|
||||
{
|
||||
auto const root = keylet::hookStateDir(account, ns);
|
||||
auto found = false;
|
||||
|
||||
if (dirIndex.isZero())
|
||||
{
|
||||
dirIndex = root.key;
|
||||
found = true;
|
||||
}
|
||||
|
||||
auto dir = ledger.read({ltDIR_NODE, dirIndex});
|
||||
if (!dir)
|
||||
return false;
|
||||
|
||||
std::uint32_t i = 0;
|
||||
auto& jvObjects = (jvResult[jss::namespace_entries] = Json::arrayValue);
|
||||
for (;;)
|
||||
{
|
||||
auto const& entries = dir->getFieldV256(sfIndexes);
|
||||
auto iter = entries.begin();
|
||||
|
||||
if (!found)
|
||||
{
|
||||
iter = std::find(iter, entries.end(), entryIndex);
|
||||
if (iter == entries.end())
|
||||
return false;
|
||||
|
||||
found = true;
|
||||
}
|
||||
|
||||
for (; iter != entries.end(); ++iter)
|
||||
{
|
||||
auto const sleNode = ledger.read(keylet::child(*iter));
|
||||
|
||||
jvObjects.append(sleNode->getJson(JsonOptions::none));
|
||||
|
||||
if (++i == limit)
|
||||
{
|
||||
if (++iter != entries.end())
|
||||
{
|
||||
jvResult[jss::limit] = limit;
|
||||
jvResult[jss::marker] =
|
||||
to_string(dirIndex) + ',' + to_string(*iter);
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto const nodeIndex = dir->getFieldU64(sfIndexNext);
|
||||
if (nodeIndex == 0)
|
||||
return true;
|
||||
|
||||
dirIndex = keylet::page(root, nodeIndex).key;
|
||||
dir = ledger.read({ltDIR_NODE, dirIndex});
|
||||
if (!dir)
|
||||
return true;
|
||||
|
||||
if (i == limit)
|
||||
{
|
||||
auto const& e = dir->getFieldV256(sfIndexes);
|
||||
if (!e.empty())
|
||||
{
|
||||
jvResult[jss::limit] = limit;
|
||||
jvResult[jss::marker] =
|
||||
to_string(dirIndex) + ',' + to_string(*e.begin());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.pb.h>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
@@ -112,6 +113,25 @@ getAccountObjects(
|
||||
std::uint32_t const limit,
|
||||
Json::Value& jvResult);
|
||||
|
||||
/** Gathers all hook state objects for an account namespace in a ledger.
|
||||
@param ledger Ledger to search account objects.
|
||||
@param account AccountID to find objects for.
|
||||
@param ns Namespace ID to find objects for.
|
||||
@param dirIndex Begin gathering account objects from this directory.
|
||||
@param entryIndex Begin gathering objects from this directory node.
|
||||
@param limit Maximum number of objects to find.
|
||||
@param jvResult A JSON result that holds the request objects.
|
||||
*/
|
||||
bool
|
||||
getAccountNamespace(
|
||||
ReadView const& ledger,
|
||||
AccountID const& account,
|
||||
uint256 const& ns,
|
||||
uint256 dirIndex,
|
||||
uint256 const& entryIndex,
|
||||
std::uint32_t const limit,
|
||||
Json::Value& jvResult);
|
||||
|
||||
/** Get ledger by hash
|
||||
If there is no error in the return value, the ledger pointer will have
|
||||
been filled
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2014 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/ledger/ReadView.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
#include <xrpl/json/json_writer.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** RPC command that retreives hook state objects from a particular namespace in
|
||||
a particular account.
|
||||
{
|
||||
account: <account>|<account_public_key>
|
||||
namespace_id: <namespace hex>
|
||||
ledger_hash: <string> // optional
|
||||
ledger_index: <string | unsigned integer> // optional
|
||||
type: <string> // optional, defaults to all account objects types
|
||||
limit: <integer> // optional
|
||||
marker: <opaque> // optional, resume previous query
|
||||
}
|
||||
*/
|
||||
|
||||
Json::Value
|
||||
doAccountNamespace(RPC::JsonContext& context)
|
||||
{
|
||||
auto const& params = context.params;
|
||||
if (!params.isMember(jss::account))
|
||||
return RPC::missing_field_error(jss::account);
|
||||
|
||||
if (!params.isMember(jss::namespace_id))
|
||||
return RPC::missing_field_error(jss::namespace_id);
|
||||
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
auto result = RPC::lookupLedger(ledger, context);
|
||||
if (ledger == nullptr)
|
||||
return result;
|
||||
|
||||
AccountID accountID;
|
||||
{
|
||||
auto const strIdent = params[jss::account].asString();
|
||||
if (auto jv = RPC::accountFromString(accountID, strIdent))
|
||||
{
|
||||
for (auto it = jv.begin(); it != jv.end(); ++it)
|
||||
result[it.memberName()] = *it;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
auto const ns = params[jss::namespace_id].asString();
|
||||
|
||||
uint256 nsID = beast::zero;
|
||||
|
||||
if (!nsID.parseHex(ns))
|
||||
return rpcError(rpcINVALID_PARAMS);
|
||||
|
||||
if (!ledger->exists(keylet::account(accountID)))
|
||||
return rpcError(rpcACT_NOT_FOUND);
|
||||
|
||||
if (!ledger->exists(keylet::hookStateDir(accountID, nsID)))
|
||||
return rpcError(rpcNAMESPACE_NOT_FOUND);
|
||||
|
||||
unsigned int limit;
|
||||
if (auto err = readLimitField(limit, RPC::Tuning::accountObjects, context))
|
||||
return *err;
|
||||
|
||||
uint256 dirIndex;
|
||||
uint256 entryIndex;
|
||||
if (params.isMember(jss::marker))
|
||||
{
|
||||
auto const& marker = params[jss::marker];
|
||||
if (!marker.isString())
|
||||
return RPC::expected_field_error(jss::marker, "string");
|
||||
|
||||
std::stringstream ss(marker.asString());
|
||||
std::string s;
|
||||
if (!std::getline(ss, s, ','))
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
|
||||
if (!dirIndex.parseHex(s))
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
|
||||
if (!std::getline(ss, s, ','))
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
|
||||
if (!entryIndex.parseHex(s))
|
||||
return RPC::invalid_field_error(jss::marker);
|
||||
}
|
||||
|
||||
if (!RPC::getAccountNamespace(
|
||||
*ledger, accountID, nsID, dirIndex, entryIndex, limit, result))
|
||||
{
|
||||
result[jss::account_objects] = Json::arrayValue;
|
||||
}
|
||||
|
||||
result[jss::account] = toBase58(accountID);
|
||||
result[jss::namespace_id] = ns;
|
||||
context.loadType = Resource::feeMediumBurdenRPC;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -17,21 +17,70 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/misc/TxQ.h>
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/GRPCHandlers.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/FeeUnits.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
inline std::optional<XRPAmount>
|
||||
getHookFees(RPC::JsonContext const& context)
|
||||
{
|
||||
auto const& params(context.params);
|
||||
if (params.isMember(jss::tx_blob))
|
||||
{
|
||||
auto ret = strUnHex(context.params[jss::tx_blob].asString());
|
||||
|
||||
if (!ret || !ret->size())
|
||||
throw std::invalid_argument("Invalid tx_blob");
|
||||
|
||||
SerialIter sitTrans(makeSlice(*ret));
|
||||
|
||||
std::unique_ptr<STTx const> stpTrans;
|
||||
stpTrans = std::make_unique<STTx const>(std::ref(sitTrans));
|
||||
|
||||
if (!stpTrans->isFieldPresent(sfAccount))
|
||||
throw std::invalid_argument("No sfAccount specified");
|
||||
|
||||
return calculateBaseFee(
|
||||
*(context.app.openLedger().current()), *stpTrans);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
doFee(RPC::JsonContext& context)
|
||||
{
|
||||
auto result = context.app.getTxQ().doRPC(context.app);
|
||||
if (result.type() == Json::objectValue)
|
||||
return result;
|
||||
// get hook fees, if any
|
||||
std::optional<XRPAmount> hookFees;
|
||||
try
|
||||
{
|
||||
hookFees = getHookFees(context);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
Json::Value jvResult;
|
||||
jvResult[jss::error] = "invalidTransaction";
|
||||
jvResult[jss::error_exception] = e.what();
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
Json::Value jvResult = context.app.getTxQ().doRPC(context.app, hookFees);
|
||||
if (jvResult.type() == Json::objectValue)
|
||||
{
|
||||
if (hookFees)
|
||||
jvResult[jss::fee_hooks_drops] = to_string(*hookFees);
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
UNREACHABLE("ripple::doFee : invalid result type");
|
||||
RPC::inject_error(rpcINTERNAL, context.params);
|
||||
return context.params;
|
||||
|
||||
@@ -39,6 +39,8 @@ doAccountObjects(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doAccountOffers(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doAccountNamespace(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doAccountTxJson(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doAMMInfo(RPC::JsonContext&);
|
||||
|
||||
@@ -270,6 +270,21 @@ parseEscrow(Json::Value const& params, Json::Value& jvResult)
|
||||
return keylet::escrow(*id, params[jss::seq].asUInt()).key;
|
||||
}
|
||||
|
||||
std::optional<uint256>
|
||||
parseHookHash(Json::Value const& params, Json::Value& jvResult)
|
||||
{
|
||||
uint256 uNodeIndex;
|
||||
if (params.isObject() || (!uNodeIndex.parseHex(params.asString())))
|
||||
{
|
||||
jvResult[jss::error] = "malformedRequest";
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
return keylet::hookDefinition(uNodeIndex).key;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<uint256>
|
||||
parseOffer(Json::Value const& params, Json::Value& jvResult)
|
||||
{
|
||||
@@ -842,6 +857,7 @@ doLedgerEntry(RPC::JsonContext& context)
|
||||
{jss::did, parseDID, ltDID},
|
||||
{jss::directory, parseDirectory, ltDIR_NODE},
|
||||
{jss::escrow, parseEscrow, ltESCROW},
|
||||
{jss::hook_hash, parseHookHash, ltHOOK_DEFINITION},
|
||||
// TODO: add fee, hashes
|
||||
{jss::mpt_issuance, parseMPTokenIssuance, ltMPTOKEN_ISSUANCE},
|
||||
{jss::mptoken, parseMPToken, ltMPTOKEN},
|
||||
|
||||
@@ -71,8 +71,9 @@ LedgerHandler::check()
|
||||
{
|
||||
// Until some sane way to get full ledgers has been implemented,
|
||||
// disallow retrieving all state nodes.
|
||||
if (!isUnlimited(context_.role))
|
||||
return rpcNO_PERMISSION;
|
||||
// RH TODO: recomment this for prod
|
||||
// if (!isUnlimited(context_.role))
|
||||
// return rpcNO_PERMISSION;
|
||||
|
||||
if (context_.app.getFeeTrack().isLoadedLocal() &&
|
||||
!isUnlimited(context_.role))
|
||||
|
||||
Reference in new issue
Block a user