Compare commits

...
Author SHA1 Message Date
Ed Hennis 5883d756db Merge remote-tracking branch 'upstream/develop' into hooks-merge
* upstream/develop: (518 commits)
  Set version to 2.4.0-b1
  fix: Add header for set_difference (5197)
  fix: allow overlapping types in `Expected` (5218)
  Add MPTIssue to STIssue (5200)
  Antithesis instrumentation improvements (5213)
  Enforce levelization in libxrpl with CMake (5111)
  refactor: clean up `LedgerEntry.cpp` (5199)
  test: Add more test cases for Base58 parser (5174)
  test: Check for some unlikely null dereferences in tests (5004)
  Add Antithesis intrumentation (5042)
  Set version to 2.3.0
  refactor(AMMClawback): move tfClawTwoAssets check (5201)
  Add a new serialized type: STNumber (5121)
  fix: check for valid ammID field in amm_info RPC (5188)
  Set version to 2.3.0-rc2
  fix: include `index` in `server_definitions` RPC (5190)
  Fix ledger_entry crash on invalid credentials request (5189)
  Set version to 2.3.0-rc1
  Replace Uint192 with Hash192 in server_definitions response (5177)
  Fix potential deadlock (5124)
  ...
2024-12-20 13:00:48 -05:00
RichardAH df8d8796f5 Introduce in-development Hooks amendment (#4225)
* Hooks V2 squashed from
  https://github.com/XRPL-Labs/xrpld-hooks/tree/006524a10aa406d09742dae69a9cf271bdea0d26

* Include changes from code review

Introduce "Hooks" functionality with the new SetHook transactor. This
significantly expands the scope of transaction processing capabilities
with an implementation that supports the execution of programmable
scripts that attach to accounts and execute during transactions. This
provides programmability and allows for advanced use cases involving
transaction validation, automation, and complex multi-signature
arrangements. The core transaction pipeline executes hooks, manages
their states, and handles associated fees. Adjustments across the code
enable seamless integration of hooks with existing functionality, e.g.
signer lists. This is a large advancement in the architecture and
functionality of the system.

Additional testing, including performance testing, and reviews,
including security reviews, are necessary before this change should be
considered production-ready. Additionally, there is a need to modify the
change so that it can build on Windows, which is currently a supported
platform for development purposes.
2024-02-22 08:47:59 -08:00
94 changed files with 13890 additions and 450 deletions

No files matched your search

+13
View File
@@ -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)
+7
View File
@@ -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()
+52
View File
@@ -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 ()
+1
View File
@@ -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',
]
+194
View File
@@ -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"
+92
View File
@@ -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")
+11
View File
@@ -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>
+3 -2
View File
@@ -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,
+1 -1
View File
@@ -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
+9
View File
@@ -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'),
};
+30 -5
View File
@@ -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)
+2 -3
View File
@@ -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,
+26
View File
@@ -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
+1
View File
@@ -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,
+51
View File
@@ -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
+3
View File
@@ -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;
+6
View File
@@ -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,
};
//------------------------------------------------------------------------------
-2
View File
@@ -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;
-3
View File
@@ -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
+22
View File
@@ -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;
};
+6
View File
@@ -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
+7 -3
View File
@@ -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},
}))
+8
View File
@@ -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
+3 -1
View File
@@ -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)
"+"
+55 -8
View File
@@ -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(),
{
+2 -2
View File
@@ -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");
+3 -1
View File
@@ -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
//
+6 -1
View File
@@ -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)
+4 -1
View File
@@ -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
+3 -1
View File
@@ -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
+2
View File
@@ -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."),
+2
View File
@@ -45,6 +45,8 @@ TxFormats::TxFormats()
{sfTxnSignature, soeOPTIONAL},
{sfSigners, soeOPTIONAL}, // submit_multisigned
{sfNetworkID, soeOPTIONAL},
{sfEmitDetails, soeOPTIONAL},
{sfFirstLedgerSequence, soeOPTIONAL},
};
#pragma push_macro("UNWRAP")
+18
View File
@@ -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;
}
+5
View File
@@ -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;
}
+2 -4
View File
@@ -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();
+1 -1
View File
@@ -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();
+2
View File
@@ -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_;
}
};
+6 -1
View File
@@ -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);
});
+371
View File
@@ -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
+305
View File
@@ -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); \
}
+924
View File
@@ -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
+4
View File
@@ -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;
+65 -2
View File
@@ -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);
+5 -1
View File
@@ -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).
+168 -7
View File
@@ -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);
+12
View File
@@ -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);
+35 -6
View File
@@ -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;
}
+44
View File
@@ -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()
{
+4
View File
@@ -67,11 +67,15 @@ private:
TER
applyUNLModify();
TER
applyEmitFailure();
};
using EnableAmendment = Change;
using SetFee = Change;
using UNLModify = Change;
using EmitFailure = Change;
} // namespace ripple
+3 -1
View File
@@ -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_);
+39 -7
View File
@@ -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;
+15
View File
@@ -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))
{
+232 -22
View File
@@ -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);
+27 -7
View File
@@ -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:
+279 -49
View File
@@ -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);
}
+1
View File
@@ -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
+17
View File
@@ -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
+100
View File
@@ -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>
+767 -2
View File
@@ -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
+79
View File
@@ -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
+1
View File
@@ -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."};
+41 -39
View File
@@ -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
+1 -1
View File
@@ -39,7 +39,7 @@ enum ApplyFlags : std::uint32_t {
tapRETRY = 0x20,
// Transaction came from a privileged source
tapUNLIMITED = 0x400,
tapUNLIMITED = 0x400
};
constexpr ApplyFlags
+42
View File
@@ -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
View File
@@ -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.
+152 -136
View File
@@ -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)
+11 -2
View File
@@ -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);
+126
View File
@@ -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
+7
View File
@@ -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_;
+12 -1
View File
@@ -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
+1
View File
@@ -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));
}
+1 -120
View File
@@ -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
+1
View File
@@ -18,6 +18,7 @@
//==============================================================================
#include <xrpld/ledger/ReadView.h>
#include <xrpl/protocol/Rules.h>
namespace ripple {
+75
View File
@@ -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)
{
+83
View File
@@ -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},
+19
View File
@@ -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) <
+1
View File
@@ -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},
+83
View File
@@ -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
+20
View File
@@ -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
+130
View File
@@ -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
+52 -3
View File
@@ -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;
+2
View File
@@ -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&);
+16
View File
@@ -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},
+3 -2
View File
@@ -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))