Compare commits

...
Author SHA1 Message Date
Olek 6a6fed5dce More hostfunctions (#5451)
* Bug fixes:
- Fix bugs found during schedule table tests
- Add more tests
- Add parameters passing for runEscrowWasm function

* Add new host-functions
 fix wamr logging
 add runtime passing through HF
 fix runEscrowWasm interface

* Improve logs

* Fix logging bug

* Set 4k limit for update_data HF

* allHF wasm module fixes
2025-05-30 19:01:27 -04:00
Mayukha Vadari 1f8aece8cd feat: add a GasUsed parameter to the metadata (#5456) 2025-05-29 16:36:55 -04:00
Mayukha Vadari 6c6f8cd4f9 Merge remote-tracking branch 'upstream/develop' into develop3 2025-05-29 13:05:11 -04:00
Mayukha Vadari fb1311e013 uncomment???? 2025-05-28 14:00:50 -04:00
Mayukha Vadari ce31acf030 debug comments 2025-05-28 13:48:38 -04:00
Mayukha Vadari 31ad5ac63b Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop3 2025-05-27 18:29:41 -04:00
Mayukha Vadari 1ede0bdec4 fix: fix fixtures (#5445) 2025-05-23 17:37:14 -04:00
Mayukha Vadari aef32ead2c better WASM logging to match rippled (#5395)
* basic logging

* pass in Journal

* log level based on journal level

* clean up

* attempt at adding WAMR logging properly

* improve logline

* maybe_unused

* fix

* fix

* fix segfault

* add test
2025-05-23 10:31:02 -04:00
Mayukha Vadari 5b43ec7f73 refactor: switch function name from ready to finish (#5430) 2025-05-20 16:12:19 -04:00
Olek 1e9ff88a00 Fix CI build issues
* Mac build fix
* Windows build fix
* Windows instruction counter fix
2025-05-08 12:39:37 -04:00
Mayukha Vadari bb9bb5f5c5 Merge branch 'ripple/smart-escrow' into develop2 2025-05-01 18:44:06 -04:00
Mayukha Vadari c533abd8b6 Update size and compute cap defaults (#5417) 2025-05-01 18:41:51 -04:00
Olek bb9bc764bc Switch to WAMR (#5416)
* Switch to WAMR
2025-05-01 18:02:06 -04:00
Mayukha Vadari b4b53a6cb7 Merge branch 'ripple/smart-escrow' into develop2 2025-04-29 15:25:54 -04:00
Mayukha Vadari 9c0204906c fix reference fee tests 2025-04-29 15:25:00 -04:00
Mayukha Vadari 4670b373c1 try to fix tests 2025-04-29 14:10:27 -04:00
Mayukha Vadari f03b5883bd More host functions (#5411)
* getNFT

* escrow keylet

* account keylet

* credential keylet

* oracle keylet

* hook everything in

* fix stuff
2025-04-29 12:39:12 -04:00
Mayukha Vadari f8b2fe4dd5 fix imports 2025-04-28 17:43:15 -04:00
Mayukha Vadari be4a0c9c2b Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop2 2025-04-28 17:14:28 -04:00
Mayukha Vadari f37d52d8e9 Set up fees for WASM processing (#5393)
* set up fields

* throw error if allowance is too high

* votable gas price

* fix comments

* hook everything together

* make test less flaky (hopefully)

* fix other tests

* fix some tests

* fix tests

* clean up

* add more tests

* uncomment other tests

* respond to comments

* fix build

* respond to comments
2025-04-24 08:47:13 -04:00
Mayukha Vadari 177cdaf550 Connect votable gas limit into VM (#5360)
* [WIP] add gas limit

* [WIP] host function escrow tests

* finish test

* uncomment out tests
2025-03-25 10:55:33 -04:00
pwang200 1573a443b7 smart escrow devnet 1 host functions (#5353)
* devnet 1 host functions

* clang-format

* fix build issues
2025-03-24 17:07:17 -04:00
911c0466c0 Merge develop into ripple/smart-escrow (#5357)
* Set version to 2.4.0

* refactor: Remove unused and add missing includes (#5293)

The codebase is filled with includes that are unused, and which thus can be removed. At the same time, the files often do not include all headers that contain the definitions used in those files. This change uses clang-format and clang-tidy to clean up the includes, with minor manual intervention to ensure the code compiles on all platforms.

* refactor: Calculate numFeatures automatically (#5324)

Requiring manual updates of numFeatures is an annoying manual process that is easily forgotten, and leads to frequent merge conflicts. This change takes advantage of the `XRPL_FEATURE` and `XRPL_FIX` macros, and adds a new `XRPL_RETIRE` macro to automatically set `numFeatures`.

* refactor: Improve ordering of headers with clang-format (#5343)

Removes all manual header groupings from source and header files by leveraging clang-format options.

* Rename "deadlock" to "stall" in `LoadManager` (#5341)

What the LoadManager class does is stall detection, which is not the same as deadlock detection. In the condition of severe CPU starvation, LoadManager will currently intentionally crash rippled reporting `LogicError: Deadlock detected`. This error message is misleading as the condition being detected is not a deadlock. This change fixes and refactors the code in response.

* Adds hub.xrpl-commons.org as a new Bootstrap Cluster (#5263)

* fix: Error message for ledger_entry rpc (#5344)

Changes the error to `malformedAddress` for `permissioned_domain` in the `ledger_entry` rpc, when the account is not a string. This change makes it more clear to a user what is wrong with their request.

* fix: Handle invalid marker parameter in grpc call (#5317)

The `end_marker` is used to limit the range of ledger entries to fetch. If `end_marker` is less than `marker`, a crash can occur. This change adds an additional check.

* fix: trust line RPC no ripple flag (#5345)

The Trustline RPC `no_ripple` flag gets set depending on `lsfDefaultRipple` flag, which is not a flag of a trustline but of the account root. The `lsfDefaultRipple` flag does not provide any insight if this particular trust line has `lsfLowNoRipple` or `lsfHighNoRipple` flag set, so it should not be used here at all. This change simplifies the logic.

* refactor: Updates Conan dependencies: RocksDB (#5335)

Updates RocksDB to version 9.7.3, the latest version supported in Conan 1.x. A patch for 9.7.4 that fixes a memory leak is included.

* fix: Remove null pointer deref, just do abort (#5338)

This change removes the existing undefined behavior from `LogicError`, so we can be certain that there will be always a stacktrace.

De-referencing a null pointer is an old trick to generate `SIGSEGV`, which would typically also create a stacktrace. However it is also an undefined behaviour and compilers can do something else. A more robust way to create a stacktrace while crashing the program is to use `std::abort`, which we have also used in this location for a long time. If we combine the two, we might not get the expected behaviour - namely, the nullpointer deref followed by `std::abort`, as handled in certain compiler versions may not immediately cause a crash. We have observed stacktrace being wiped instead, and thread put in indeterminate state, then stacktrace created without any useful information.

* chore: Add PR number to payload (#5310)

This PR adds one more payload field to the libXRPL compatibility check workflow - the PR number itself.

* chore: Update link to ripple-binary-codec (#5355)

The link to ripple-binary-codec's definitions.json appears to be outdated. The updated link is also documented here: https://xrpl.org/docs/references/protocol/binary-format#definitions-file

* Prevent consensus from getting stuck in the establish phase (#5277)

- Detects if the consensus process is "stalled". If it is, then we can declare a 
  consensus and end successfully even if we do not have 80% agreement on
  our proposal.
  - "Stalled" is defined as:
    - We have a close time consensus
    - Each disputed transaction is individually stalled:
      - It has been in the final "stuck" 95% requirement for at least 2
        (avMIN_ROUNDS) "inner rounds" of phaseEstablish,
      - and either all of the other trusted proposers or this validator, if proposing,
        have had the same vote(s) for at least 4 (avSTALLED_ROUNDS) "inner
        rounds", and at least 80% of the validators (including this one, if
        appropriate) agree about the vote (whether yes or no).
- If we have been in the establish phase for more than 10x the previous
  consensus establish phase's time, then consensus is considered "expired",
  and we will leave the round, which sends a partial validation (indicating
  that the node is moving on without validating). Two restrictions avoid
  prematurely exiting, or having an extended exit in extreme situations.
  - The 10x time is clamped to be within a range of 15s
    (ledgerMAX_CONSENSUS) to 120s (ledgerABANDON_CONSENSUS).
  - If consensus has not had an opportunity to walk through all avalanche
    states (defined as not going through 8 "inner rounds" of phaseEstablish),
    then ConsensusState::Expired is treated as ConsensusState::No.
- When enough nodes leave the round, any remaining nodes will see they've
  fallen behind, and move on, too, generally before hitting the timeout. Any
  validations or partial validations sent during this time will help the
  consensus process bring the nodes back together.

---------

Co-authored-by: Michael Legleux <[email protected]>
Co-authored-by: Bart <[email protected]>
Co-authored-by: Ed Hennis <[email protected]>
Co-authored-by: Bronek Kozicki <[email protected]>
Co-authored-by: Darius Tumas <[email protected]>
Co-authored-by: Sergey Kuznetsov <[email protected]>
Co-authored-by: cyan317 <[email protected]>
Co-authored-by: Vlad <[email protected]>
Co-authored-by: Alex Kremer <[email protected]>
2025-03-20 16:47:14 -04:00
Mayukha VadariandPeng Wang b6a95f9970 PoC Smart Escrows (#5340)
* wasmedge in unittest

* add WashVM.h and cpp

* accountID comparison (vector<u8>) working

* json decode tx and ledger object with two buffers working

* wasm return a buffer working

* add a failure test case to P2P3

* host function return ledger sqn

* instruction gas and host function gas

* basics

* add scaffold

* add amendment check

* working PoC

* get test working

* fix clang-format

* prototype #2

* p2p3

* [WIP] P4

* P5

* add calculateBaseFee

* add FinishFunction preflight checks (+ tests)

* additional reserve for sfFinishFunction

* higher fees for EscrowFinish

* rename amendment to SmartEscrow

* make fee voting changes, add basic tests

* clean up

* clean up

* clean up

* more cleanup

* add subscribe tests

* add more tests

* undo formatting

* undo formatting

* remove bad comment

* more debugging statements

* fix clang-format

* fix rebase issues

* fix more rebase issues

* more rebase fixes

* add source code for wasm

* respond to comments

* add const

---------

Co-authored-by: Peng Wang <[email protected]>
2025-03-20 14:08:06 -04:00
81 changed files with 18261 additions and 236 deletions

No files matched your search

+38 -38
View File
@@ -1,5 +1,5 @@
---
Language: Cpp
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false
@@ -19,52 +19,52 @@ AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: true
AfterClass: true
AfterControlStatement: true
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
BreakBeforeBinaryOperators: false
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
ColumnLimit: 80
CommentPragmas: "^ IWYU pragma:"
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ Q_FOREACH, BOOST_FOREACH ]
IncludeBlocks: Regroup
ForEachMacros: [Q_FOREACH, BOOST_FOREACH]
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<(test)/'
Priority: 0
- Regex: '^<(xrpld)/'
Priority: 1
- Regex: '^<(xrpl)/'
Priority: 2
- Regex: '^<(boost)/'
Priority: 3
- Regex: '^.*/'
Priority: 4
- Regex: '^.*\.h'
Priority: 5
- Regex: '.*'
Priority: 6
IncludeIsMainRegex: '$'
- Regex: "^<(test)/"
Priority: 0
- Regex: "^<(xrpld)/"
Priority: 1
- Regex: "^<(xrpl)/"
Priority: 2
- Regex: "^<(boost)/"
Priority: 3
- Regex: "^.*/"
Priority: 4
- Regex: '^.*\.h'
Priority: 5
- Regex: ".*"
Priority: 6
IncludeIsMainRegex: "$"
IndentCaseLabels: true
IndentFunctionDeclarationAfterType: false
IndentRequiresClause: true
IndentWidth: 4
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
@@ -78,20 +78,20 @@ PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: true
ReflowComments: true
RequiresClausePosition: OwnLine
SortIncludes: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 8
UseTab: Never
QualifierAlignment: Right
Standard: Cpp11
TabWidth: 8
UseTab: Never
QualifierAlignment: Right
+1
View File
@@ -17,6 +17,7 @@ runs:
conan export external/rocksdb rocksdb/9.7.3@
conan export external/soci soci/4.0.3@
conan export external/nudb nudb/2.0.8@
conan export -k external/wamr wamr/2.2.0@
- name: add Ripple Conan remote
shell: bash
run: |
+1
View File
@@ -408,6 +408,7 @@ jobs:
conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]' default
conan export external/snappy snappy/1.1.10@
conan export external/soci soci/4.0.3@
conan export -k external/wamr wamr/2.2.0@
- name: build dependencies
run: |
+11
View File
@@ -204,6 +204,17 @@ It fixes some source files to add missing `#include`s.
conan export --version 2.0.8 external/nudb
```
Export our [Conan recipe for WAMR](./external/wamr).
It add metering and expose some internal structures.
```
# Conan 1.x
conan export external/wamr wamr/2.2.0@
# Conan 2.x
conan export --version 2.2.0 external/wamr
```
### Build and Test
1. Create a build directory and move into it.
+1
View File
@@ -115,6 +115,7 @@ endif()
find_package(nudb REQUIRED)
find_package(date REQUIRED)
find_package(xxHash REQUIRED)
find_package(wamr REQUIRED)
target_link_libraries(ripple_libs INTERFACE
ed25519::ed25519
+33
View File
@@ -1249,6 +1249,39 @@
# Example:
# owner_reserve = 2000000 # 2 XRP
#
# extension_compute_limit = <gas>
#
# The extension compute limit is the maximum amount of gas that can be
# consumed by a single transaction. The gas limit is used to prevent
# transactions from consuming too many resources.
#
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# extension_compute_limit = 1000000 # 1 million gas
#
# extension_size_limit = <bytes>
#
# The extension size limit is the maximum size of a WASM extension in
# bytes. The size limit is used to prevent extensions from consuming
# too many resources.
#
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# extension_size_limit = 100000 # 100 kb
#
# gas_price = <bytes>
#
# The gas price is the conversion between WASM gas and its price in drops.
#
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# gas_price = 1000000 # 1 drop per gas
#-------------------------------------------------------------------------------
#
# 9. Misc Settings
+1
View File
@@ -65,6 +65,7 @@ target_link_libraries(xrpl.imports.main
xrpl.libpb
xxHash::xxhash
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
wamr::wamr
)
include(add_module)
+3
View File
@@ -2,6 +2,7 @@ from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
import re
class Xrpl(ConanFile):
name = 'xrpl'
@@ -32,6 +33,7 @@ class Xrpl(ConanFile):
'soci/4.0.3',
'xxhash/0.8.2',
'zlib/1.3.1',
'wamr/2.2.0',
]
tool_requires = [
@@ -125,6 +127,7 @@ class Xrpl(ConanFile):
self.folders.generators = 'build/generators'
generators = 'CMakeDeps'
def generate(self):
tc = CMakeToolchain(self)
tc.variables['tests'] = self.options.tests
+6
View File
@@ -0,0 +1,6 @@
patches:
2.2.0:
- patch_description: add metering to iwasm interpreter
patch_file: patches/ripp_metering.patch
patch_type: conan
+91
View File
@@ -0,0 +1,91 @@
from conans import ConanFile, tools
from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps, cmake_layout
from conan.tools.files import (
apply_conandata_patches,
export_conandata_patches,
# get,
)
# import os
required_conan_version = ">=1.55.0"
class WamrConan(ConanFile):
name = "wamr"
version = "2.2.0"
license = "Apache License v2.0"
url = "https://github.com/bytecodealliance/wasm-micro-runtime.git"
description = "Webassembly micro runtime"
package_type = "library"
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": False, "fPIC": True}
generators = "CMakeToolchain", "CMakeDeps"
# requires = [("llvm/20.1.1@")]
def export_sources(self):
export_conandata_patches(self)
pass
# def build_requirements(self):
# self.tool_requires("llvm/20.1.1")
def config_options(self):
if self.settings.os == "Windows":
del self.options.fPIC
def layout(self):
cmake_layout(self, src_folder="src")
def source(self):
git = tools.Git()
git.clone(
"https://github.com/bytecodealliance/wasm-micro-runtime.git",
"c883fafead005e87ad3122b05409886f507c1cb0",
shallow=True,
)
# get(self, **self.conan_data["sources"][self.version], strip_root=True)
def generate(self):
tc = CMakeToolchain(self)
tc.variables["WAMR_BUILD_INTERP"] = 1
tc.variables["WAMR_BUILD_FAST_INTERP"] = 1
tc.variables["WAMR_BUILD_INSTRUCTION_METERING"] = 1
tc.variables["WAMR_BUILD_AOT"] = 0
tc.variables["WAMR_BUILD_JIT"] = 0
tc.variables["WAMR_BUILD_FAST_JIT"] = 0
tc.variables["WAMR_DISABLE_HW_BOUND_CHECK"] = 1
tc.variables["WAMR_DISABLE_STACK_HW_BOUND_CHECK"] = 1
tc.variables["WAMR_BH_LOG"] = "wamr_log_to_rippled"
# tc.variables["WAMR_BUILD_FAST_JIT"] = 0 if self.settings.os == "Windows" else 1
# ll_dep = self.dependencies["llvm"]
# self.output.info(f"-----------package_folder: {type(ll_dep.__dict__)}")
# tc.variables["LLVM_DIR"] = os.path.join(ll_dep.package_folder, "lib", "cmake", "llvm")
tc.generate()
# This generates "foo-config.cmake" and "bar-config.cmake" in self.generators_folder
deps = CMakeDeps(self)
deps.generate()
def build(self):
apply_conandata_patches(self)
cmake = CMake(self)
cmake.verbose = True
cmake.configure()
cmake.build()
# self.run(f'echo {self.source_folder}')
# Explicit way:
# self.run('cmake %s/hello %s' % (self.source_folder, cmake.command_line))
# self.run("cmake --build . %s" % cmake.build_config)
def package(self):
cmake = CMake(self)
cmake.verbose = True
cmake.install()
def package_info(self):
self.cpp_info.libs = ["iwasm"]
self.cpp_info.names["cmake_find_package"] = "wamr"
self.cpp_info.names["cmake_find_package_multi"] = "wamr"
+470
View File
@@ -0,0 +1,470 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 551991f8..5f48a0b8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-cmake_minimum_required (VERSION 3.14)
+cmake_minimum_required (VERSION 3.20)
option(BUILD_SHARED_LIBS "Build using shared libraries" OFF)
diff --git a/build-scripts/config_common.cmake b/build-scripts/config_common.cmake
index 1cb50235..bd103022 100644
--- a/build-scripts/config_common.cmake
+++ b/build-scripts/config_common.cmake
@@ -669,6 +669,10 @@ if (WAMR_BUILD_AOT_VALIDATOR EQUAL 1)
message (" AOT validator enabled")
add_definitions (-DWASM_ENABLE_AOT_VALIDATOR=1)
endif ()
+if (WAMR_BUILD_INSTRUCTION_METERING EQUAL 1)
+ message (" Instruction metering enabled")
+ add_definitions (-DWASM_ENABLE_INSTRUCTION_METERING=1)
+endif ()
########################################
# Show Phase4 Wasm proposals status.
diff --git a/core/config.h b/core/config.h
index cb1189c9..a4e1499e 100644
--- a/core/config.h
+++ b/core/config.h
@@ -716,4 +716,8 @@ unless used elsewhere */
#define WASM_ENABLE_AOT_VALIDATOR 0
#endif
+#ifndef WASM_ENABLE_INSTRUCTION_METERING
+#define WASM_ENABLE_INSTRUCTION_METERING 0
+#endif
+
#endif /* end of _CONFIG_H_ */
diff --git a/core/iwasm/common/wasm_c_api.c b/core/iwasm/common/wasm_c_api.c
index 269ec577..bc6fd01b 100644
--- a/core/iwasm/common/wasm_c_api.c
+++ b/core/iwasm/common/wasm_c_api.c
@@ -5389,3 +5389,8 @@ wasm_instance_get_wasm_func_exec_time(const wasm_instance_t *instance,
return -1.0;
#endif
}
+
+wasm_exec_env_t wasm_instance_exec_env(const wasm_instance_t*instance)
+{
+ return wasm_runtime_get_exec_env_singleton(instance->inst_comm_rt);
+}
diff --git a/core/iwasm/common/wasm_exec_env.c b/core/iwasm/common/wasm_exec_env.c
index e33fd9f3..d1ff9c41 100644
--- a/core/iwasm/common/wasm_exec_env.c
+++ b/core/iwasm/common/wasm_exec_env.c
@@ -85,6 +85,12 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst,
wasm_runtime_dump_exec_env_mem_consumption(exec_env);
#endif
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ exec_env->instructions_to_execute = -1;
+ for(int i = 0; i < 256; ++i)
+ exec_env->instructions_schedule[i] = 1;
+#endif
+
return exec_env;
#ifdef OS_ENABLE_HW_BOUND_CHECK
diff --git a/core/iwasm/common/wasm_exec_env.h b/core/iwasm/common/wasm_exec_env.h
index ce0c1fa7..2713a092 100644
--- a/core/iwasm/common/wasm_exec_env.h
+++ b/core/iwasm/common/wasm_exec_env.h
@@ -87,6 +87,12 @@ typedef struct WASMExecEnv {
uint8 *bottom;
} wasm_stack;
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ /* instructions to execute */
+ int64 instructions_to_execute;
+ int64 instructions_schedule[256];
+#endif
+
#if WASM_ENABLE_FAST_JIT != 0
/**
* Cache for
diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c
index d33c0272..21bfdf29 100644
--- a/core/iwasm/common/wasm_runtime_common.c
+++ b/core/iwasm/common/wasm_runtime_common.c
@@ -2285,6 +2285,31 @@ wasm_runtime_access_exce_check_guard_page()
}
#endif
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+
+void
+wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
+ int64 instructions_to_execute)
+{
+ exec_env->instructions_to_execute = instructions_to_execute;
+}
+
+int64
+wasm_runtime_get_instruction_count_limit(WASMExecEnv *exec_env)
+{
+ return exec_env->instructions_to_execute;
+}
+
+void
+wasm_runtime_set_instruction_schedule(WASMExecEnv *exec_env,
+ int64 const *instructions_schedule)
+{
+ for(int i = 0; i < 256; ++i)
+ exec_env->instructions_schedule[i] = instructions_schedule[i];
+}
+
+#endif
+
WASMFuncType *
wasm_runtime_get_function_type(const WASMFunctionInstanceCommon *function,
uint32 module_type)
@@ -7792,13 +7817,14 @@ wasm_runtime_get_module_name(wasm_module_t module)
bool
wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env)
{
+#if WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
uint8 *boundary = exec_env->native_stack_boundary;
RECORD_STACK_USAGE(exec_env, (uint8 *)&boundary);
if (boundary == NULL) {
/* the platform doesn't support os_thread_get_stack_boundary */
return true;
}
-#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
+#if defined(OS_ENABLE_HW_BOUND_CHECK)
uint32 page_size = os_getpagesize();
uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT;
boundary = boundary + page_size * guard_page_count;
@@ -7808,6 +7834,7 @@ wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env)
"native stack overflow");
return false;
}
+#endif
return true;
}
@@ -7830,7 +7857,7 @@ wasm_runtime_detect_native_stack_overflow_size(WASMExecEnv *exec_env,
boundary = boundary - WASM_STACK_GUARD_SIZE + requested_size;
if ((uint8 *)&boundary < boundary) {
wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env),
- "native stack overflow");
+ "native s stack overflow");
return false;
}
return true;
diff --git a/core/iwasm/common/wasm_runtime_common.h b/core/iwasm/common/wasm_runtime_common.h
index 8ac032bf..5ca5d489 100644
--- a/core/iwasm/common/wasm_runtime_common.h
+++ b/core/iwasm/common/wasm_runtime_common.h
@@ -791,9 +791,25 @@ WASM_RUNTIME_API_EXTERN void
wasm_runtime_set_native_stack_boundary(WASMExecEnv *exec_env,
uint8 *native_stack_boundary);
-#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+
/* See wasm_export.h for description */
WASM_RUNTIME_API_EXTERN void
+wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
+ int64 instructions_to_execute);
+WASM_RUNTIME_API_EXTERN int64
+wasm_runtime_get_instruction_count_limit(WASMExecEnv *exec_env);
+
+WASM_RUNTIME_API_EXTERN void
+wasm_runtime_set_instruction_schedule(WASMExecEnv *exec_env,
+ int64 const *instructions_schedule);
+
+#endif
+
+#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
+/* See wasm_export.h for description */
+WASM_RUNTIME_API_EXTERN
+void
wasm_runtime_set_bounds_checks(WASMModuleInstanceCommon *module_inst,
bool enable);
diff --git a/core/iwasm/include/wasm_c_api.h b/core/iwasm/include/wasm_c_api.h
index 241a0eec..9eb0dde1 100644
--- a/core/iwasm/include/wasm_c_api.h
+++ b/core/iwasm/include/wasm_c_api.h
@@ -19,8 +19,10 @@
#if defined(_MSC_BUILD)
#if defined(COMPILING_WASM_RUNTIME_API)
#define WASM_API_EXTERN __declspec(dllexport)
-#else
+#elif defined(_DLL)
#define WASM_API_EXTERN __declspec(dllimport)
+#else
+#define WASM_API_EXTERN
#endif
#else
#define WASM_API_EXTERN
@@ -701,6 +703,11 @@ WASM_API_EXTERN double wasm_instance_sum_wasm_exec_time(const wasm_instance_t*);
// func_name. If the function is not found, return 0.
WASM_API_EXTERN double wasm_instance_get_wasm_func_exec_time(const wasm_instance_t*, const char *);
+struct WASMExecEnv;
+typedef struct WASMExecEnv *wasm_exec_env_t;
+
+WASM_API_EXTERN wasm_exec_env_t wasm_instance_exec_env(const wasm_instance_t*);
+
///////////////////////////////////////////////////////////////////////////////
// Convenience
diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h
index b73a0364..3fd0949f 100644
--- a/core/iwasm/include/wasm_export.h
+++ b/core/iwasm/include/wasm_export.h
@@ -20,8 +20,10 @@
#if defined(_MSC_BUILD)
#if defined(COMPILING_WASM_RUNTIME_API)
#define WASM_RUNTIME_API_EXTERN __declspec(dllexport)
-#else
+#elif defined(_DLL)
#define WASM_RUNTIME_API_EXTERN __declspec(dllimport)
+#else
+#define WASM_RUNTIME_API_EXTERN
#endif
#elif defined(__GNUC__) || defined(__clang__)
#define WASM_RUNTIME_API_EXTERN __attribute__((visibility("default")))
@@ -1821,6 +1823,27 @@ WASM_RUNTIME_API_EXTERN void
wasm_runtime_set_native_stack_boundary(wasm_exec_env_t exec_env,
uint8_t *native_stack_boundary);
+/**
+ * Set the instruction count limit to the execution environment.
+ * By default the instruction count limit is -1, which means no limit.
+ * However, if the instruction count limit is set to a positive value,
+ * the execution will be terminated when the instruction count reaches
+ * the limit.
+ *
+ * @param exec_env the execution environment
+ * @param instruction_count the instruction count limit
+ */
+WASM_RUNTIME_API_EXTERN void
+wasm_runtime_set_instruction_count_limit(wasm_exec_env_t exec_env,
+ int64_t instruction_count);
+
+WASM_RUNTIME_API_EXTERN int64_t
+wasm_runtime_get_instruction_count_limit(wasm_exec_env_t exec_env);
+
+WASM_RUNTIME_API_EXTERN void
+wasm_runtime_set_instruction_schedule(wasm_exec_env_t exec_env,
+ int64_t const *instructions_schedule);
+
/**
* Dump runtime memory consumption, including:
* Exec env memory consumption
diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c
index 41ac4c72..bd8f714a 100644
--- a/core/iwasm/interpreter/wasm_interp_classic.c
+++ b/core/iwasm/interpreter/wasm_interp_classic.c
@@ -1516,10 +1516,13 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst,
} \
os_mutex_unlock(&exec_env->wait_lock); \
} \
+ CHECK_INSTRUCTION_LIMIT(); \
goto *handle_table[*frame_ip++]; \
} while (0)
#else
-#define HANDLE_OP_END() FETCH_OPCODE_AND_DISPATCH()
+#define HANDLE_OP_END() \
+ CHECK_INSTRUCTION_LIMIT(); \
+ FETCH_OPCODE_AND_DISPATCH()
#endif
#else /* else of WASM_ENABLE_LABELS_AS_VALUES */
@@ -1542,9 +1545,12 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst,
} \
os_mutex_unlock(&exec_env->wait_lock); \
} \
+ CHECK_INSTRUCTION_LIMIT(); \
continue;
#else
-#define HANDLE_OP_END() continue
+#define HANDLE_OP_END() \
+ CHECK_INSTRUCTION_LIMIT(); \
+ continue;
#endif
#endif /* end of WASM_ENABLE_LABELS_AS_VALUES */
@@ -1562,6 +1568,20 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global)
#endif
}
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+#define CHECK_INSTRUCTION_LIMIT() \
+ if (instructions_to_execute >= 0) \
+ { \
+ instructions_to_execute -= instructions_schedule[opcode];\
+ if (instructions_to_execute < 0) { \
+ wasm_set_exception(module, "instruction limit exceeded"); \
+ goto got_exception; \
+ } \
+ }
+#else
+#define CHECK_INSTRUCTION_LIMIT() (void)0
+#endif
+
static void
wasm_interp_call_func_bytecode(WASMModuleInstance *module,
WASMExecEnv *exec_env,
@@ -1605,6 +1625,17 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
uint32 local_idx, local_offset, global_idx;
uint8 local_type, *global_addr;
uint32 cache_index, type_index, param_cell_num, cell_num;
+
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ int64 instructions_to_execute = -1;
+ int64 const *instructions_schedule = NULL;
+ if(exec_env)
+ {
+ instructions_to_execute = exec_env->instructions_to_execute;
+ instructions_schedule = exec_env->instructions_schedule;
+ }
+#endif
+
#if WASM_ENABLE_EXCE_HANDLING != 0
int32_t exception_tag_index;
#endif
@@ -6859,6 +6890,11 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
FREE_FRAME(exec_env, frame);
wasm_exec_env_set_cur_frame(exec_env, prev_frame);
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ if(exec_env)
+ exec_env->instructions_to_execute = instructions_to_execute;
+#endif
+
if (!prev_frame->ip) {
/* Called from native. */
return;
@@ -6899,6 +6935,12 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
}
#endif
SYNC_ALL_TO_FRAME();
+
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ if(exec_env)
+ exec_env->instructions_to_execute = instructions_to_execute;
+#endif
+
return;
#if WASM_ENABLE_LABELS_AS_VALUES == 0
diff --git a/core/iwasm/interpreter/wasm_interp_fast.c b/core/iwasm/interpreter/wasm_interp_fast.c
index f33ad60e..9cbf2010 100644
--- a/core/iwasm/interpreter/wasm_interp_fast.c
+++ b/core/iwasm/interpreter/wasm_interp_fast.c
@@ -105,6 +105,19 @@ typedef float64 CellType_F64;
goto unaligned_atomic; \
} while (0)
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+#define CHECK_INSTRUCTION_LIMIT() \
+ if (instructions_to_execute >= 0) { \
+ instructions_to_execute -= instructions_schedule[opcode]; \
+ if (instructions_to_execute < 0) { \
+ wasm_set_exception(module, "instruction limit exceeded"); \
+ goto got_exception; \
+ } \
+ }
+#else
+#define CHECK_INSTRUCTION_LIMIT() (void)0
+#endif
+
static inline uint32
rotl32(uint32 n, uint32 c)
{
@@ -1466,12 +1479,14 @@ wasm_interp_dump_op_count()
} while (0)
#endif
#endif /* end of WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS */
-#define HANDLE_OP_END() FETCH_OPCODE_AND_DISPATCH()
+#define HANDLE_OP_END() CHECK_INSTRUCTION_LIMIT(); FETCH_OPCODE_AND_DISPATCH()
#else /* else of WASM_ENABLE_LABELS_AS_VALUES */
#define HANDLE_OP(opcode) case opcode:
-#define HANDLE_OP_END() continue
+#define HANDLE_OP_END() \
+ CHECK_INSTRUCTION_LIMIT(); \
+ continue
#endif /* end of WASM_ENABLE_LABELS_AS_VALUES */
@@ -1538,6 +1553,16 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
uint8 *maddr = NULL;
uint32 local_idx, local_offset, global_idx;
uint8 opcode = 0, local_type, *global_addr;
+
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ int64 instructions_to_execute = -1;
+ int64 const *instructions_schedule = NULL;
+ if (exec_env) {
+ instructions_to_execute = exec_env->instructions_to_execute;
+ instructions_schedule = exec_env->instructions_schedule;
+ }
+#endif
+
#if !defined(OS_ENABLE_HW_BOUND_CHECK) \
|| WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS == 0
#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
@@ -7761,6 +7786,11 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
FREE_FRAME(exec_env, frame);
wasm_exec_env_set_cur_frame(exec_env, (WASMRuntimeFrame *)prev_frame);
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ if (exec_env)
+ exec_env->instructions_to_execute = instructions_to_execute;
+#endif
+
if (!prev_frame->ip)
/* Called from native. */
return;
@@ -7789,6 +7819,10 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
got_exception:
SYNC_ALL_TO_FRAME();
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
+ if (exec_env)
+ exec_env->instructions_to_execute = instructions_to_execute;
+#endif
return;
#if WASM_ENABLE_LABELS_AS_VALUES == 0
diff --git a/core/shared/platform/include/platform_wasi_types.h b/core/shared/platform/include/platform_wasi_types.h
index ac1a95ea..e23b500e 100644
--- a/core/shared/platform/include/platform_wasi_types.h
+++ b/core/shared/platform/include/platform_wasi_types.h
@@ -36,7 +36,11 @@ extern "C" {
#if WASM_ENABLE_UVWASI != 0 || WASM_ENABLE_LIBC_WASI == 0
#define assert_wasi_layout(expr, message) /* nothing */
#else
-#define assert_wasi_layout(expr, message) _Static_assert(expr, message)
+ #ifndef _MSC_VER
+ #define assert_wasi_layout(expr, message) _Static_assert(expr, message)
+ #else
+ #define assert_wasi_layout(expr, message) static_assert(expr, message)
+ #endif
#endif
assert_wasi_layout(_Alignof(int8_t) == 1, "non-wasi data layout");
diff --git a/doc/build_wamr.md b/doc/build_wamr.md
index 6425450b..94dd9628 100644
--- a/doc/build_wamr.md
+++ b/doc/build_wamr.md
@@ -327,6 +327,10 @@ And the wasm app can calls below APIs to allocate/free memory from/to the shared
- **WAMR_BUILD_SHRUNK_MEMORY**=1/0, default to enable if not set
> Note: When enabled, this feature will reduce memory usage by decreasing the size of the linear memory, particularly when the `memory.grow` opcode is not used and memory usage is somewhat predictable.
+## **Instruction metering**
+- **WAMR_BUILD_INSTRUCTION_METERING**=1/0, default to disable if not set
+> Note: Enabling this feature allows limiting the number of instructions a wasm module instance can execute. Use the `wasm_runtime_set_instruction_count_limit(...)` API before calling `wasm_runtime_call_*(...)` APIs to enforce this limit.
+
## **Combination of configurations:**
We can combine the configurations. For example, if we want to disable interpreter, enable AOT and WASI, we can run command:
+2 -2
View File
@@ -451,8 +451,8 @@ mulDivU(Source1 value, Dest mul, Source2 div)
}
using namespace boost::multiprecision;
uint128_t product;
using uint128 = boost::multiprecision::uint128_t;
uint128 product;
product = multiply(
product,
static_cast<std::uint64_t>(value.value()),
+6
View File
@@ -24,6 +24,8 @@
namespace ripple {
constexpr std::uint32_t MICRO_DROPS_PER_DROP{1'000'000};
/** Reflects the fee settings for a particular ledger.
The fees are always the same for any transactions applied
@@ -34,6 +36,10 @@ struct Fees
XRPAmount base{0}; // Reference tx cost (drops)
XRPAmount reserve{0}; // Reserve base (drops)
XRPAmount increment{0}; // Reserve increment (drops)
std::uint32_t extensionComputeLimit{
0}; // Extension compute limit (instructions)
std::uint32_t extensionSizeLimit{0}; // Extension size limit (bytes)
std::uint32_t gasPrice{0}; // price of WASM gas (micro-drops)
explicit Fees() = default;
Fees(Fees const&) = default;
+6
View File
@@ -230,6 +230,12 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
Keylet
escrow(AccountID const& src, std::uint32_t seq) noexcept;
inline Keylet
escrow(uint256 const& key) noexcept
{
return {ltESCROW, key};
}
/** A PaymentChannel */
Keylet
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
+2 -1
View File
@@ -149,11 +149,12 @@ mulRatio(
bool roundUp)
{
using namespace boost::multiprecision;
using int128 = boost::multiprecision::int128_t;
if (!den)
Throw<std::runtime_error>("division by zero");
int128_t const amt128(amt.value());
int128 const amt128(amt.value());
auto const neg = amt.value() < 0;
auto const m = amt128 * num;
auto r = m / den;
+4
View File
@@ -126,6 +126,10 @@ std::uint8_t constexpr vaultStrategyFirstComeFirstServe = 1;
* another vault; counted from 0 */
std::uint8_t constexpr maxAssetCheckDepth = 5;
/** The maximum length of a Data field in Escrow object that can be updated by
* Wasm code */
std::size_t constexpr maxWasmDataLength = 4 * 1024;
/** A ledger index. */
using LedgerIndex = std::uint32_t;
+3
View File
@@ -185,6 +185,8 @@ enum TEFcodes : TERUnderlyingType {
tefNO_TICKET,
tefNFTOKEN_IS_NOT_TRANSFERABLE,
tefINVALID_LEDGER_FIX_TYPE,
tefNO_WASM,
tefWASM_FIELD_NOT_INCLUDED,
};
//------------------------------------------------------------------------------
@@ -360,6 +362,7 @@ enum TECcodes : TERUnderlyingType {
tecWRONG_ASSET = 194,
tecLIMIT_EXCEEDED = 195,
tecPSEUDO_ACCOUNT = 196,
tecWASM_REJECTED = 197,
};
//------------------------------------------------------------------------------
+26 -8
View File
@@ -46,10 +46,7 @@ private:
CtorHelper);
public:
TxMeta(
uint256 const& transactionID,
std::uint32_t ledger,
std::optional<uint256> parentBatchId = std::nullopt);
TxMeta(uint256 const& transactionID, std::uint32_t ledger);
TxMeta(uint256 const& txID, std::uint32_t ledger, Blob const&);
TxMeta(uint256 const& txID, std::uint32_t ledger, std::string const&);
TxMeta(uint256 const& txID, std::uint32_t ledger, STObject const&);
@@ -136,7 +133,7 @@ public:
void
setParentBatchId(uint256 const& parentBatchId)
{
mParentBatchId = parentBatchId;
parentBatchId_ = parentBatchId;
}
uint256
@@ -145,13 +142,33 @@ public:
XRPL_ASSERT(
hasParentBatchId(),
"ripple::TxMeta::getParentBatchId : non-null batch id");
return *mParentBatchId;
return *parentBatchId_;
}
bool
hasParentBatchId() const
{
return static_cast<bool>(mParentBatchId);
return static_cast<bool>(parentBatchId_);
}
void
setGasUsed(std::uint32_t const& gasUsed)
{
gasUsed_ = gasUsed;
}
std::uint32_t
getGasUsed() const
{
XRPL_ASSERT(
hasGasUsed(), "ripple::TxMeta::getGasUsed : non-null batch id");
return *gasUsed_;
}
bool
hasGasUsed() const
{
return static_cast<bool>(gasUsed_);
}
private:
@@ -161,7 +178,8 @@ private:
int mResult;
std::optional<STAmount> mDelivered;
std::optional<uint256> mParentBatchId;
std::optional<std::uint32_t> gasUsed_;
std::optional<uint256> parentBatchId_;
STArray mNodes;
};
+2 -1
View File
@@ -286,11 +286,12 @@ mulRatio(
bool roundUp)
{
using namespace boost::multiprecision;
using int128 = boost::multiprecision::int128_t;
if (!den)
Throw<std::runtime_error>("division by zero");
int128_t const amt128(amt.drops());
int128 const amt128(amt.drops());
auto const neg = amt.drops() < 0;
auto const m = amt128 * num;
auto r = m / den;
+1 -1
View File
@@ -32,11 +32,11 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(SmartEscrow, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
// Check flags in Credential transactions
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
@@ -316,6 +316,12 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
// New fields
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
{sfPreviousTxnID, soeOPTIONAL},
{sfPreviousTxnLgrSeq, soeOPTIONAL},
}))
@@ -345,6 +351,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
{sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL},
{sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
{sfSourceTag, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED},
+7 -1
View File
@@ -114,6 +114,11 @@ TYPED_SFIELD(sfVoteWeight, UINT32, 48)
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 53)
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 54)
TYPED_SFIELD(sfGasPrice, UINT32, 55)
TYPED_SFIELD(sfComputationAllowance, UINT32, 56)
TYPED_SFIELD(sfGasUsed, UINT32, 57)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -232,7 +237,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
// currency amount (AMM)
// currency amount (more)
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
@@ -274,6 +279,7 @@ TYPED_SFIELD(sfAssetClass, VL, 28)
TYPED_SFIELD(sfProvider, VL, 29)
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
TYPED_SFIELD(sfCredentialType, VL, 31)
TYPED_SFIELD(sfFinishFunction, VL, 32)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
@@ -44,10 +44,12 @@ TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, ({
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, ({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL},
{sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
}))
/** This transaction type completes an existing escrow. */
@@ -57,6 +59,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, ({
{sfFulfillment, soeOPTIONAL},
{sfCondition, soeOPTIONAL},
{sfCredentialIDs, soeOPTIONAL},
{sfComputationAllowance, soeOPTIONAL},
}))
@@ -543,6 +546,9 @@ TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, ({
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
}))
/** This system-generated transaction type is used to update the network's negative UNL
+3
View File
@@ -268,6 +268,9 @@ JSS(expected_date_UTC); // out: any (warnings)
JSS(expected_ledger_size); // out: TxQ
JSS(expiration); // out: AccountOffers, AccountChannels,
// ValidatorList, amm_info
JSS(extension_compute); // out: NetworkOps
JSS(extension_size); // out: NetworkOps
JSS(gas_price); // out: NetworkOps
JSS(fail_hard); // in: Sign, Submit
JSS(failed); // out: InboundLedger
JSS(feature); // in: Feature
+1 -2
View File
@@ -1052,8 +1052,7 @@ parseArray(
Json::Value const objectFields(json[i][objectName]);
std::stringstream ss;
ss << json_name << "."
<< "[" << i << "]." << objectName;
ss << json_name << "." << "[" << i << "]." << objectName;
auto ret = parseObject(
ss.str(), objectFields, nameField, depth + 1, error);
+4
View File
@@ -78,6 +78,10 @@ STValidation::validationFormat()
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
// featureSmartEscrow
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
};
// clang-format on
+3
View File
@@ -126,6 +126,7 @@ transResults()
MAKE_ERROR(tecWRONG_ASSET, "Wrong asset given."),
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -149,6 +150,8 @@ transResults()
MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."),
MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."),
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
MAKE_ERROR(tefNO_WASM, "There is no WASM code to run, but a WASM-specific field was included."),
MAKE_ERROR(tefWASM_FIELD_NOT_INCLUDED, "WASM code requires a field to be included that was not included."),
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
+8 -7
View File
@@ -56,9 +56,10 @@ TxMeta::TxMeta(
if (obj.isFieldPresent(sfDeliveredAmount))
setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount));
if (obj.isFieldPresent(sfParentBatchID))
setParentBatchId(obj.getFieldH256(sfParentBatchID));
if (obj.isFieldPresent(sfGasUsed))
setGasUsed(obj.getFieldU32(sfGasUsed));
}
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
@@ -82,6 +83,9 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
if (obj.isFieldPresent(sfParentBatchID))
setParentBatchId(obj.getFieldH256(sfParentBatchID));
if (obj.isFieldPresent(sfGasUsed))
setGasUsed(obj.getFieldU32(sfGasUsed));
}
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec)
@@ -97,15 +101,11 @@ TxMeta::TxMeta(
{
}
TxMeta::TxMeta(
uint256 const& transactionID,
std::uint32_t ledger,
std::optional<uint256> parentBatchId)
TxMeta::TxMeta(uint256 const& transactionID, std::uint32_t ledger)
: mTransactionID(transactionID)
, mLedger(ledger)
, mIndex(static_cast<std::uint32_t>(-1))
, mResult(255)
, mParentBatchId(parentBatchId)
, mNodes(sfAffectedNodes)
{
mNodes.reserve(32);
@@ -241,9 +241,10 @@ TxMeta::getAsObject() const
metaData.emplace_back(mNodes);
if (hasDeliveredAmount())
metaData.setFieldAmount(sfDeliveredAmount, getDeliveredAmount());
if (hasParentBatchId())
metaData.setFieldH256(sfParentBatchID, getParentBatchId());
if (hasGasUsed())
metaData.setFieldU32(sfGasUsed, getGasUsed());
return metaData;
}
+608 -1
View File
@@ -17,8 +17,10 @@
*/
//==============================================================================
#include <test/app/wasm_fixtures/fixtures.h>
#include <test/jtx.h>
#include <xrpld/app/misc/WasmVM.h>
#include <xrpld/app/tx/applySteps.h>
#include <xrpld/ledger/Dir.h>
@@ -28,6 +30,7 @@
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <filesystem>
#include <iterator>
namespace ripple {
@@ -1548,7 +1551,7 @@ struct Escrow_test : public beast::unit_test::suite
Account const alice{"alice"};
Account const bob{"bob"};
Account const carol{"carol"};
Account const dillon{"dillon "};
Account const dillon{"dillon"};
Account const zelda{"zelda"};
char const credType[] = "abcde";
@@ -1685,6 +1688,604 @@ struct Escrow_test : public beast::unit_test::suite
}
}
void
testCreateFinishFunctionPreflight()
{
testcase("Test preflight checks involving FinishFunction");
using namespace jtx;
using namespace std::chrono;
Account const alice{"alice"};
Account const carol{"carol"};
// Tests whether the ledger index is >= 5
// #[no_mangle]
// pub fn finish() -> bool {
// unsafe { host_lib::getLedgerSqn() >= 5}
// }
static auto wasmHex = ledgerSqnHex;
{
// featureSmartEscrow disabled
Env env(*this, supported_amendments() - featureSmartEscrow);
env.fund(XRP(5000), alice, carol);
XRPAmount const txnFees = env.current()->fees().base + 1000;
auto escrowCreate = escrow(alice, carol, XRP(1000));
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
fee(txnFees),
ter(temDISABLED));
env.close();
}
{
// FinishFunction > max length
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
cfg->FEES.extension_size_limit = 10; // 10 bytes
return cfg;
}));
XRPAmount const txnFees = env.current()->fees().base + 1000;
// create escrow
env.fund(XRP(5000), alice, carol);
auto escrowCreate = escrow(alice, carol, XRP(500));
// 11-byte string
std::string longWasmHex = "00112233445566778899AA";
env(escrowCreate,
finish_function(longWasmHex),
cancel_time(env.now() + 100s),
fee(txnFees),
ter(temMALFORMED));
env.close();
}
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
cfg->START_UP = Config::FRESH;
return cfg;
}));
XRPAmount const txnFees = env.current()->fees().base * 10 + 1000;
// create escrow
env.fund(XRP(5000), alice, carol);
auto escrowCreate = escrow(alice, carol, XRP(500));
// Success situations
{
// FinishFunction + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
}
{
// FinishFunction + Condition + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
condition(cb1),
fee(txnFees));
env.close();
}
{
// FinishFunction + FinishAfter + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
finish_time(env.now() + 2s),
fee(txnFees));
env.close();
}
{
// FinishFunction + FinishAfter + Condition + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
condition(cb1),
finish_time(env.now() + 2s),
fee(txnFees));
env.close();
}
// Failure situations (i.e. all other combinations)
{
// only FinishFunction
env(escrowCreate,
finish_function(wasmHex),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + FinishAfter
env(escrowCreate,
finish_function(wasmHex),
finish_time(env.now() + 2s),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + Condition
env(escrowCreate,
finish_function(wasmHex),
condition(cb1),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + FinishAfter + Condition
env(escrowCreate,
finish_function(wasmHex),
condition(cb1),
finish_time(env.now() + 2s),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction 0 length
env(escrowCreate,
finish_function(""),
cancel_time(env.now() + 100s),
fee(txnFees),
ter(temMALFORMED));
env.close();
}
}
void
testFinishWasmFailures()
{
testcase("EscrowFinish Smart Escrow failures");
using namespace jtx;
using namespace std::chrono;
Account const alice{"alice"};
Account const carol{"carol"};
// Tests whether the ledger index is >= 5
// #[no_mangle]
// pub fn finish() -> bool {
// unsafe { host_lib::getLedgerSqn() >= 5}
// }
static auto wasmHex = ledgerSqnHex;
{
// featureSmartEscrow disabled
Env env(*this, supported_amendments() - featureSmartEscrow);
env.fund(XRP(5000), alice, carol);
XRPAmount const txnFees = env.current()->fees().base + 1000;
env(finish(carol, alice, 1),
fee(txnFees),
comp_allowance(4),
ter(temDISABLED));
env.close();
}
{
// ComputationAllowance > max compute limit
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
cfg->FEES.extension_compute_limit = 1'000; // in gas
return cfg;
}));
env.fund(XRP(5000), alice, carol);
// Run past the flag ledger so that a Fee change vote occurs and
// updates FeeSettings. (It also activates all supported
// amendments.)
for (auto i = env.current()->seq(); i <= 257; ++i)
env.close();
auto const allowance = 1'001;
env(finish(carol, alice, 1),
fee(env.current()->fees().base + allowance),
comp_allowance(allowance),
ter(temBAD_LIMIT));
}
Env env(*this);
// Run past the flag ledger so that a Fee change vote occurs and
// updates FeeSettings. (It also activates all supported
// amendments.)
for (auto i = env.current()->seq(); i <= 257; ++i)
env.close();
XRPAmount const txnFees = env.current()->fees().base + 1000;
env.fund(XRP(5000), alice, carol);
// create escrow
auto const seq = env.seq(alice);
env(escrow(alice, carol, XRP(500)),
finish_function(wasmHex),
cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
{
// no ComputationAllowance field
env(finish(carol, alice, seq), ter(tefWASM_FIELD_NOT_INCLUDED));
}
{
// not enough fees
// This function takes 4 gas
// In testing, 1 gas costs 1 drop
auto const finishFee = env.current()->fees().base + 3;
env(finish(carol, alice, seq),
fee(finishFee),
comp_allowance(4),
ter(telINSUF_FEE_P));
}
{
// not enough gas
// This function takes 4 gas
// In testing, 1 gas costs 1 drop
auto const finishFee = env.current()->fees().base + 4;
env(finish(carol, alice, seq),
fee(finishFee),
comp_allowance(2),
ter(tecFAILED_PROCESSING));
}
{
// ComputationAllowance field included w/no FinishFunction on
// escrow
auto const seq2 = env.seq(alice);
env(escrow(alice, carol, XRP(500)),
finish_time(env.now() + 10s),
cancel_time(env.now() + 100s));
env.close();
auto const allowance = 100;
env(finish(carol, alice, seq2),
fee(env.current()->fees().base + allowance),
comp_allowance(allowance),
ter(tefNO_WASM));
}
}
void
testFinishFunction()
{
testcase("Example escrow function");
using namespace jtx;
using namespace std::chrono;
Account const alice{"alice"};
Account const carol{"carol"};
// Tests whether the ledger index is >= 5
// #[no_mangle]
// pub fn finish() -> bool {
// unsafe { host_lib::getLedgerSqn() >= 5}
// }
static auto wasmHex = ledgerSqnHex;
std::uint32_t constexpr allowance = 4;
{
// basic FinishFunction situation
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(alice, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(alice, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
env(finish(alice, alice, seq),
fee(txnFees),
comp_allowance(allowance),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + Condition
Env env(*this);
env.fund(XRP(5000), alice, carol);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto const seq = env.seq(alice);
// create escrow
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount const createFee = env.current()->fees().base + 1000;
env(escrowCreate,
finish_function(wasmHex),
condition(cb1),
cancel_time(env.now() + 100s),
fee(createFee));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - createFee));
env.require(balance(carol, XRP(5000)));
XRPAmount const txnFees =
env.current()->fees().base * 34 + 1000;
// no fulfillment provided, function fails
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecCRYPTOCONDITION_ERROR));
// fulfillment provided, function fails
env(finish(carol, alice, seq),
condition(cb1),
fulfillment(fb1),
comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// no fulfillment provided, function succeeds
env(finish(alice, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecCRYPTOCONDITION_ERROR));
// wrong fulfillment provided, function succeeds
env(finish(alice, alice, seq),
condition(cb1),
fulfillment(fb2),
comp_allowance(allowance),
fee(txnFees),
ter(tecCRYPTOCONDITION_ERROR));
// fulfillment provided, function succeeds, tx succeeds
env(finish(alice, alice, seq),
condition(cb1),
fulfillment(fb1),
comp_allowance(allowance),
fee(txnFees),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + FinishAfter
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
auto const ts = env.now() + 97s;
env(escrowCreate,
finish_function(wasmHex),
finish_time(ts),
cancel_time(env.now() + 1000s),
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// finish time hasn't passed, function fails
env(finish(carol, alice, seq),
comp_allowance(4),
fee(txnFees + 1),
ter(tecNO_PERMISSION));
env.close();
// finish time hasn't passed, function succeeds
for (; env.now() < ts; env.close())
env(finish(carol, alice, seq),
comp_allowance(4),
fee(txnFees + 2),
ter(tecNO_PERMISSION));
env(finish(carol, alice, seq),
comp_allowance(4),
fee(txnFees + 1),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + FinishAfter #2
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
finish_function(wasmHex),
finish_time(env.now() + 2s),
cancel_time(env.now() + 100s),
fee(txnFees));
// Don't close the ledger here
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// finish time hasn't passed, function fails
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecNO_PERMISSION));
env.close();
// finish time has passed, function fails
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// finish time has passed, function succeeds, tx succeeds
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
}
void
testAllHostFunctions()
{
testcase("Test all host functions");
using namespace jtx;
using namespace std::chrono;
// TODO: create wasm module for all host functions
static auto wasmHex = allHostFunctionsHex;
// let sender = get_tx_account_id();
// let owner = get_current_escrow_account_id();
// let dest = get_current_escrow_destination();
// let dest_balance = get_account_balance(dest);
// let escrow_data = get_current_escrow_data();
// let ed_str = String::from_utf8(escrow_data).unwrap();
// let threshold_balance = ed_str.parse::<u64>().unwrap();
// let pl_time = host_lib::getParentLedgerTime();
// let e_time = get_current_escrow_finish_after();
// sender == owner && dest_balance <= threshold_balance &&
// pl_time >= e_time
Account const alice{"alice"};
Account const carol{"carol"};
{
// basic FinishFunction situation
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
finish_function(wasmHex),
finish_time(env.now() + 11s),
cancel_time(env.now() + 100s),
data("1000000000"), // 1000 XRP in drops
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// TODO: figure out why this can't be 2412
auto const allowance = 3'600;
// FinishAfter time hasn't passed
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecNO_PERMISSION));
env.close();
// tx sender not escrow creator (alice)
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// destination balance is too high
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// reduce the destination balance
env(pay(carol, alice, XRP(4500)));
env.close();
// tx sender not escrow creator (alice)
env(finish(carol, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
env(finish(alice, alice, seq),
comp_allowance(allowance),
fee(txnFees),
ter(tesSUCCESS));
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == 487);
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
}
void
run() override
{
@@ -1700,6 +2301,12 @@ struct Escrow_test : public beast::unit_test::suite
testConsequences();
testEscrowWithTickets();
testCredentials();
testCreateFinishFunctionPreflight();
testFinishWasmFailures();
testFinishFunction();
// TODO: Update module with new host functions
testAllHostFunctions();
}
};
+46 -4
View File
@@ -37,36 +37,62 @@ class FeeVote_test : public beast::unit_test::suite
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
BEAST_EXPECT(
setup.extension_compute_limit ==
defaultSetup.extension_compute_limit);
BEAST_EXPECT(
setup.extension_size_limit ==
defaultSetup.extension_size_limit);
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
}
{
Section config;
config.append(
{"reference_fee = 50",
"account_reserve = 1234567",
"owner_reserve = 1234"});
"owner_reserve = 1234",
"extension_compute_limit = 100",
"extension_size_limit = 200",
" gas_price = 300"});
auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == 50);
BEAST_EXPECT(setup.account_reserve == 1234567);
BEAST_EXPECT(setup.owner_reserve == 1234);
BEAST_EXPECT(setup.extension_compute_limit == 100);
BEAST_EXPECT(setup.extension_size_limit == 200);
BEAST_EXPECT(setup.gas_price == 300);
}
{
Section config;
config.append(
{"reference_fee = blah",
"account_reserve = yada",
"owner_reserve = foo"});
"owner_reserve = foo",
"extension_compute_limit = bar",
"extension_size_limit = baz",
"gas_price = qux"});
// Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
BEAST_EXPECT(
setup.extension_compute_limit ==
defaultSetup.extension_compute_limit);
BEAST_EXPECT(
setup.extension_size_limit ==
defaultSetup.extension_size_limit);
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
}
{
Section config;
config.append(
{"reference_fee = -50",
"account_reserve = -1234567",
"owner_reserve = -1234"});
"owner_reserve = -1234",
"extension_compute_limit = -100",
"extension_size_limit = -200",
"gas_price = -300"});
// Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
@@ -74,6 +100,12 @@ class FeeVote_test : public beast::unit_test::suite
setup.account_reserve == static_cast<std::uint32_t>(-1234567));
BEAST_EXPECT(
setup.owner_reserve == static_cast<std::uint32_t>(-1234));
BEAST_EXPECT(
setup.extension_compute_limit ==
static_cast<std::uint32_t>(-100));
BEAST_EXPECT(
setup.extension_size_limit == static_cast<std::uint32_t>(-200));
BEAST_EXPECT(setup.gas_price == static_cast<std::uint32_t>(-300));
}
{
auto const big64 = std::to_string(
@@ -84,12 +116,22 @@ class FeeVote_test : public beast::unit_test::suite
config.append(
{"reference_fee = " + big64,
"account_reserve = " + big64,
"owner_reserve = " + big64});
"owner_reserve = " + big64,
"extension_compute_limit = " + big64,
"extension_size_limit = " + big64,
"gas_price = " + big64});
// Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
BEAST_EXPECT(
setup.extension_compute_limit ==
defaultSetup.extension_compute_limit);
BEAST_EXPECT(
setup.extension_size_limit ==
defaultSetup.extension_size_limit);
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
}
}
+8 -1
View File
@@ -50,6 +50,12 @@ struct PseudoTx_test : public beast::unit_test::suite
obj[sfReserveIncrement] = 0;
obj[sfReferenceFeeUnits] = 0;
}
if (rules.enabled(featureSmartEscrow))
{
obj[sfExtensionComputeLimit] = 0;
obj[sfExtensionSizeLimit] = 0;
obj[sfGasPrice] = 0;
}
}));
res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) {
@@ -116,9 +122,10 @@ struct PseudoTx_test : public beast::unit_test::suite
{
using namespace test::jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const xrpFees{featureXRPFees};
testPrevented(all - featureXRPFees - featureSmartEscrow);
testPrevented(all - featureXRPFees);
testPrevented(all - featureSmartEscrow);
testPrevented(all);
testAllowed();
}
+801
View File
@@ -0,0 +1,801 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/app/wasm_fixtures/fixtures.h>
#include <test/jtx.h>
#include <xrpld/app/misc/WasmHostFunc.h>
#include <xrpld/app/misc/WasmVM.h>
#include <iwasm/wasm_c_api.h>
#ifdef _DEBUG
// #define DEBUG_OUTPUT 1
#endif
namespace ripple {
namespace test {
using Add_proto = int32_t(int32_t, int32_t);
static wasm_trap_t*
Add(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
{
int32_t Val1 = params->data[0].of.i32;
int32_t Val2 = params->data[1].of.i32;
// printf("Host function \"Add\": %d + %d\n", Val1, Val2);
results->data[0] = WASM_I32_VAL(Val1 + Val2);
return nullptr;
}
struct TestLedgerDataProvider
{
jtx::Env* env;
public:
TestLedgerDataProvider(jtx::Env* env_) : env(env_)
{
}
int32_t
get_ledger_sqn()
{
return (int32_t)env->current()->seq();
}
};
using getLedgerSqn_proto = std::int32_t();
static wasm_trap_t*
getLedgerSqn_wrap(void* env, wasm_val_vec_t const*, wasm_val_vec_t* results)
{
auto sqn = reinterpret_cast<TestLedgerDataProvider*>(env)->get_ledger_sqn();
results->data[0] = WASM_I32_VAL(sqn);
results->num_elems = 1;
return nullptr;
}
struct TestHostFunctionsOld : public HostFunctions
{
test::jtx::Env* env_;
Bytes accountID_;
Bytes data_;
int clock_drift_ = 0;
test::StreamSink sink_;
beast::Journal jlog_;
void const* rt_ = nullptr;
public:
explicit TestHostFunctionsOld(test::jtx::Env* env, int cd = 0)
: env_(env)
, clock_drift_(cd)
, sink_(beast::severities::kDebug)
, jlog_(sink_)
{
std::string s = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
accountID_ = Bytes{s.begin(), s.end()};
std::string t = "10000";
data_ = Bytes{t.begin(), t.end()};
}
virtual void
setRT(void const* rt) override
{
rt_ = rt;
}
virtual void const*
getRT() const override
{
return rt_;
}
test::StreamSink&
getSink()
{
return sink_;
}
beast::Journal
getJournal() override
{
return jlog_;
}
int32_t
getLedgerSqn() override
{
return (int32_t)env_->current()->seq();
}
int32_t
getParentLedgerTime() override
{
return env_->current()->parentCloseTime().time_since_epoch().count() +
clock_drift_;
}
Expected<Bytes, int32_t>
getTxField(SField const& fname) override
{
return accountID_;
}
Expected<Bytes, int32_t>
getLedgerObjField(int32_t cacheIdx, SField const& fname) override
{
return data_;
}
Expected<Bytes, int32_t>
getCurrentLedgerObjField(SField const& fname) override
{
if (fname.getName() == "Destination" || fname.getName() == "Account")
return accountID_;
else if (fname.getName() == "Data")
return data_;
else if (fname.getName() == "FinishAfter")
{
auto t =
env_->current()->parentCloseTime().time_since_epoch().count();
std::string s = std::to_string(t);
return Bytes{s.begin(), s.end()};
}
return Unexpected(-1);
}
};
struct TestHostFunctions : public HostFunctions
{
test::jtx::Env& env_;
AccountID accountID_;
Bytes data_;
int clock_drift_ = 0;
void const* rt_ = nullptr;
public:
explicit TestHostFunctions(test::jtx::Env& env, int cd = 0)
: env_(env), clock_drift_(cd)
{
auto opt = parseBase58<AccountID>("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh");
if (opt)
accountID_ = *opt;
std::string t = "10000";
data_ = Bytes{t.begin(), t.end()};
}
virtual void
setRT(void const* rt) override
{
rt_ = rt;
}
virtual void const*
getRT() const override
{
return rt_;
}
beast::Journal
getJournal() override
{
return env_.journal;
}
int32_t
getLedgerSqn() override
{
return static_cast<int32_t>(env_.current()->seq());
}
int32_t
getParentLedgerTime() override
{
return env_.current()->parentCloseTime().time_since_epoch().count() +
clock_drift_;
}
virtual int32_t
cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx) override
{
return 1;
}
Expected<Bytes, int32_t>
getTxField(SField const& fname) override
{
if (fname == sfAccount)
return Bytes(accountID_.begin(), accountID_.end());
else if (fname == sfFee)
{
int64_t x = 235;
uint8_t const* p = reinterpret_cast<uint8_t const*>(&x);
return Bytes{p, p + sizeof(x)};
}
else if (fname == sfSequence)
{
int32_t x = getLedgerSqn();
uint8_t const* p = reinterpret_cast<uint8_t const*>(&x);
return Bytes{p, p + sizeof(x)};
}
return Bytes();
}
Expected<Bytes, int32_t>
getTxNestedField(Slice const& locator) override
{
uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41,
0xbf, 0x49, 0xd2, 0x45, 0x9f, 0xa4, 0xa0, 0x34,
0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92, 0xfc, 0xee,
0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00};
return Bytes(&a[0], &a[sizeof(a)]);
}
Expected<Bytes, int32_t>
getLedgerObjField(int32_t cacheIdx, SField const& fname) override
{
// auto const& sn = fname.getName();
if (fname == sfBalance)
{
int64_t x = 10'000;
uint8_t const* p = reinterpret_cast<uint8_t const*>(&x);
return Bytes{p, p + sizeof(x)};
}
return data_;
}
Expected<Bytes, int32_t>
getCurrentLedgerObjField(SField const& fname) override
{
auto const& sn = fname.getName();
if (sn == "Destination" || sn == "Account")
return Bytes(accountID_.begin(), accountID_.end());
else if (sn == "Data")
return data_;
else if (sn == "FinishAfter")
{
auto t =
env_.current()->parentCloseTime().time_since_epoch().count();
std::string s = std::to_string(t);
return Bytes{s.begin(), s.end()};
}
return Unexpected(-1);
}
int32_t
getTxArrayLen(SField const& fname) override
{
return 32;
}
int32_t
getTxNestedArrayLen(Slice const& locator) override
{
return 32;
}
int32_t
updateData(Bytes const& data) override
{
return 0;
}
Expected<Bytes, int32_t>
accountKeylet(AccountID const& account) override
{
if (!account)
return Unexpected(HF_ERR_INVALID_ACCOUNT);
auto const keylet = keylet::account(account);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
int32_t
trace(std::string const& msg, Bytes const& data, bool asHex) override
{
#ifdef DEBUG_OUTPUT
auto& j = std::cerr;
#else
auto j = getJournal().trace();
#endif
j << msg;
if (!asHex)
j << std::string_view(
reinterpret_cast<char const*>(data.data()), data.size());
else
{
auto const hex =
boost::algorithm::hex(std::string(data.begin(), data.end()));
j << hex;
}
#ifdef DEBUG_OUTPUT
j << std::endl;
#endif
return msg.size() + data.size() * (asHex ? 2 : 1);
}
int32_t
traceNum(std::string const& msg, int64_t data) override
{
#ifdef DEBUG_OUTPUT
auto& j = std::cerr;
#else
auto j = getJournal().trace();
#endif
j << msg << data;
#ifdef DEBUG_OUTPUT
j << std::endl;
#endif
return msg.size() + sizeof(data);
}
};
struct Wasm_test : public beast::unit_test::suite
{
void
testWasmFib()
{
testcase("Wasm fibo");
auto const ws = boost::algorithm::unhex(fib32Hex);
Bytes const wasm(ws.begin(), ws.end());
auto& engine = WasmEngine::instance();
auto const r = engine.run(wasm, "fib", wasmParams(10));
BEAST_EXPECT(r.has_value() && (r->result == 55));
}
void
testWasmSha()
{
testcase("Wasm sha");
auto const ws = boost::algorithm::unhex(sha512PureHex);
Bytes const wasm(ws.begin(), ws.end());
auto& engine = WasmEngine::instance();
auto const r =
engine.run(wasm, "sha512_process", wasmParams(sha512PureHex));
BEAST_EXPECT(r.has_value() && (r->result == 34432));
}
void
testWasmB58()
{
testcase("Wasm base58");
auto const ws = boost::algorithm::unhex(b58Hex);
Bytes const wasm(ws.begin(), ws.end());
auto& engine = WasmEngine::instance();
Bytes outb;
outb.resize(1024);
auto const minsz = std::min(
static_cast<std::uint32_t>(512),
static_cast<std::uint32_t>(b58Hex.size()));
auto const s = std::string_view(b58Hex.c_str(), minsz);
auto const r = engine.run(wasm, "b58enco", wasmParams(outb, s));
BEAST_EXPECT(r.has_value() && r->result);
}
void
testWasmSP1Verifier()
{
testcase("Wasm sp1 zkproof verifier");
auto const ws = boost::algorithm::unhex(sp1_wasm);
Bytes const wasm(ws.begin(), ws.end());
auto& engine = WasmEngine::instance();
auto const r = engine.run(wasm, "sp1_groth16_verifier");
BEAST_EXPECT(r.has_value() && r->result);
}
void
testWasmBG16Verifier()
{
testcase("Wasm BG16 zkproof verifier");
auto const ws = boost::algorithm::unhex(zkProofHex);
Bytes const wasm(ws.begin(), ws.end());
auto& engine = WasmEngine::instance();
auto const r = engine.run(wasm, "bellman_groth16_test");
BEAST_EXPECT(r.has_value() && r->result);
}
void
testWasmLedgerSqn()
{
testcase("Wasm get ledger sequence");
auto wasmStr = boost::algorithm::unhex(ledgerSqnHex);
Bytes wasm(wasmStr.begin(), wasmStr.end());
using namespace test::jtx;
Env env{*this};
TestLedgerDataProvider ledgerDataProvider(&env);
std::string const funcName("finish");
std::vector<WasmImportFunc> imports;
WASM_IMPORT_FUNC(imports, getLedgerSqn, &ledgerDataProvider);
auto& engine = WasmEngine::instance();
auto r = engine.run(
wasm, funcName, {}, imports, nullptr, 1'000'000, env.journal);
if (BEAST_EXPECT(r.has_value()))
BEAST_EXPECT(!r->result);
env.close();
env.close();
env.close();
env.close();
// empty module - run the same module
r = engine.run(
{}, funcName, {}, imports, nullptr, 1'000'000, env.journal);
if (BEAST_EXPECT(r.has_value()))
BEAST_EXPECT(r->result);
}
void
testWasmCheckJson()
{
testcase("Wasm check json");
using namespace test::jtx;
Env env{*this};
auto const wasmStr = boost::algorithm::unhex(checkJsonHex);
Bytes const wasm(wasmStr.begin(), wasmStr.end());
std::string const funcName("check_accountID");
{
std::string str = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
Bytes data(str.begin(), str.end());
auto re = runEscrowWasm(
wasm, funcName, wasmParams(data), nullptr, -1, env.journal);
if (BEAST_EXPECT(re.has_value()))
BEAST_EXPECT(re.value().result);
}
{
std::string str = "rHb9CJAWyB4rj91VRWn96DkukG4bwdty00";
Bytes data(str.begin(), str.end());
auto re = runEscrowWasm(
wasm, funcName, wasmParams(data), nullptr, -1, env.journal);
if (BEAST_EXPECT(re.has_value()))
BEAST_EXPECT(!re.value().result);
}
}
void
testWasmCompareJson()
{
testcase("Wasm compare json");
using namespace test::jtx;
Env env{*this};
auto wasmStr = boost::algorithm::unhex(compareJsonHex);
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
std::string funcName("compare_accountID");
std::vector<uint8_t> const tx_data(tx_js.begin(), tx_js.end());
std::vector<uint8_t> const lo_data(lo_js.begin(), lo_js.end());
auto re = runEscrowWasm(
wasm,
funcName,
wasmParams(tx_data, lo_data),
nullptr,
-1,
env.journal);
if (BEAST_EXPECT(re.has_value()))
BEAST_EXPECT(re.value().result);
std::vector<uint8_t> const lo_data2(lo_js2.begin(), lo_js2.end());
re = runEscrowWasm(
wasm,
funcName,
wasmParams(tx_data, lo_data2),
nullptr,
-1,
env.journal);
if (BEAST_EXPECT(re.has_value()))
BEAST_EXPECT(!re.value().result);
}
void
testWasmLib()
{
testcase("wasmtime lib test");
// clang-format off
/* The WASM module buffer. */
Bytes const wasm = {/* WASM header */
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
/* Type section */
0x01, 0x07, 0x01,
/* function type {i32, i32} -> {i32} */
0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
/* Import section */
0x02, 0x13, 0x01,
/* module name: "extern" */
0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
/* extern name: "func-add" */
0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
/* import desc: func 0 */
0x00, 0x00,
/* Function section */
0x03, 0x02, 0x01, 0x00,
/* Export section */
0x07, 0x0A, 0x01,
/* export name: "addTwo" */
0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
/* export desc: func 0 */
0x00, 0x01,
/* Code section */
0x0A, 0x0A, 0x01,
/* code body */
0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B};
// clang-format on
auto& vm = WasmEngine::instance();
std::vector<WasmImportFunc> imports;
WasmImpFunc<Add_proto>(
imports, "func-add", reinterpret_cast<void*>(&Add));
auto res = vm.run(wasm, "addTwo", wasmParams(1234, 5678), imports);
// if (res) printf("invokeAdd get the result: %d\n", res.value());
BEAST_EXPECT(res.has_value() && res->result == 6912);
}
void
testBadWasm()
{
testcase("bad wasm test");
using namespace test::jtx;
Env env{*this};
HostFunctions hfs;
auto wasmHex = "00000000";
auto wasmStr = boost::algorithm::unhex(std::string(wasmHex));
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
std::string funcName("mock_escrow");
auto re = runEscrowWasm(wasm, funcName, {}, &hfs, 15, env.journal);
BEAST_EXPECT(re.error());
}
void
testEscrowWasmDN1()
{
testcase("escrow wasm devnet 1 test");
std::string const wasmHex = allHostFunctionsHex;
std::string const wasmStr = boost::algorithm::unhex(wasmHex);
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
// let sender = get_tx_account_id();
// let owner = get_current_escrow_account_id();
// let dest = get_current_escrow_destination();
// let dest_balance = get_account_balance(dest);
// let escrow_data = get_current_escrow_data();
// let ed_str = String::from_utf8(escrow_data).unwrap();
// let threshold_balance = ed_str.parse::<u64>().unwrap();
// let pl_time = host_lib::getParentLedgerTime();
// let e_time = get_current_escrow_finish_after();
// sender == owner && dest_balance <= threshold_balance &&
// pl_time >= e_time
using namespace test::jtx;
Env env{*this};
{
TestHostFunctions nfs(env, 0);
std::string funcName("finish");
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100000);
if (BEAST_EXPECT(re.has_value()))
{
BEAST_EXPECT(re.value().result);
// std::cout << "good case result " << re.value().result
// << " cost: " << re.value().cost << std::endl;
}
}
env.close();
env.close();
env.close();
env.close();
{ // fail because current time < escrow_finish_after time
TestHostFunctions nfs(env, -1);
std::string funcName("finish");
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100000);
if (BEAST_EXPECT(re.has_value()))
{
BEAST_EXPECT(!re.value().result);
// std::cout << "bad case (current time < escrow_finish_after "
// "time) result "
// << re.value().result << " cost: " <<
// re.value().cost
// << std::endl;
}
}
{ // fail because trying to access nonexistent field
struct BadTestHostFunctions : public TestHostFunctions
{
explicit BadTestHostFunctions(Env& env) : TestHostFunctions(env)
{
}
Expected<Bytes, int32_t>
getTxField(SField const& fname) override
{
return Unexpected(-1);
}
};
BadTestHostFunctions nfs(env);
std::string funcName("finish");
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100000);
BEAST_EXPECT(re.error());
// std::cout << "bad case (access nonexistent field) result "
// << re.error() << std::endl;
}
{ // fail because trying to allocate more than MAX_PAGES memory
struct BadTestHostFunctions : public TestHostFunctions
{
explicit BadTestHostFunctions(Env& env) : TestHostFunctions(env)
{
}
Expected<Bytes, int32_t>
getTxField(SField const& fname) override
{
return Bytes((MAX_PAGES + 1) * 64 * 1024, 1);
}
};
BadTestHostFunctions nfs(env);
std::string funcName("finish");
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100000);
BEAST_EXPECT(!re);
// std::cout << "bad case (more than MAX_PAGES) result "
// << re.error() << std::endl;
}
{ // fail because recursion too deep
auto wasmHex = deepRecursionHex;
auto wasmStr = boost::algorithm::unhex(std::string(wasmHex));
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
TestHostFunctionsOld nfs(&env);
std::string funcName("recursive");
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 1000'000'000);
BEAST_EXPECT(re.error());
// std::cout << "bad case (deep recursion) result " << re.error()
// << std::endl;
auto const& sink = nfs.getSink();
auto countSubstr = [](std::string const& str,
std::string const& substr) {
std::size_t pos = 0;
int occurrences = 0;
while ((pos = str.find(substr, pos)) != std::string::npos)
{
occurrences++;
pos += substr.length();
}
return occurrences;
};
BEAST_EXPECT(
countSubstr(
sink.messages().str(), "WAMR error: failed to call func") ==
1);
BEAST_EXPECT(
countSubstr(
sink.messages().str(),
"WAMR Exception: wasm operand stack overflow") == 1);
}
}
void
testEscrowWasmDN2()
{
testcase("wasm devnet 3 test");
std::string const funcName("finish");
using namespace test::jtx;
Env env(*this);
{
std::string const wasmHex = xrplStdExampleHex;
std::string const wasmStr = boost::algorithm::unhex(wasmHex);
std::vector<uint8_t> const wasm(wasmStr.begin(), wasmStr.end());
TestHostFunctions nfs(env, 0);
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100'000);
if (BEAST_EXPECT(re.has_value()))
{
BEAST_EXPECT(re->result);
// std::cout << "good case result " << re.value().result
// << " cost: " << re.value().cost << std::endl;
}
}
env.close();
env.close();
env.close();
env.close();
env.close();
{
std::string const wasmHex = hostFunctions2Hex;
std::string const wasmStr = boost::algorithm::unhex(wasmHex);
std::vector<uint8_t> const wasm(wasmStr.begin(), wasmStr.end());
TestHostFunctions nfs(env, 0);
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100'000);
if (BEAST_EXPECT(re.has_value()))
{
BEAST_EXPECT(re->result);
// std::cout << "good case result " << re.value().result
// << " cost: " << re.value().cost << std::endl;
}
}
}
void
run() override
{
using namespace test::jtx;
testWasmLib();
testBadWasm();
testWasmCheckJson();
testWasmCompareJson();
testWasmLedgerSqn();
testWasmFib();
testWasmSha();
testWasmB58();
// runing too long
// testWasmSP1Verifier();
// testWasmBG16Verifier();
// TODO: needs fix for new host functions interface
// testEscrowWasmDN1();
testEscrowWasmDN2();
}
};
BEAST_DEFINE_TESTSUITE(Wasm, app, ripple);
} // namespace test
} // namespace ripple
File diff suppressed because it is too large. Load diff
+56
View File
@@ -0,0 +1,56 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 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.
*/
//==============================================================================
#pragma once
// TODO: consider moving these to separate files (and figure out the build)
#include <string>
extern std::string const ledgerSqnHex;
extern std::string const allHostFunctionsHex;
extern std::string const deepRecursionHex;
extern std::string const tx_js;
extern std::string const lo_js;
extern std::string const lo_js2;
extern std::string const fib32Hex;
extern std::string const fib64Hex;
extern std::string const b58Hex;
extern std::string const sha512PureHex;
extern std::string const checkJsonHex;
extern std::string const compareJsonHex;
extern std::string const zkProofHex;
extern std::string const sp1_wasm;
extern std::string const xrplStdExampleHex;
extern std::string const hostFunctions2Hex;
@@ -68,8 +68,8 @@ class ByzantineFailureSim_test : public beast::unit_test::suite
for (TrustGraph<Peer*>::ForkInfo const& fi :
sim.trustGraph.forkablePairs(0.8))
{
std::cout << "Can fork " << PeerGroup{fi.unlA} << " "
<< " " << PeerGroup{fi.unlB} << " overlap " << fi.overlap
std::cout << "Can fork " << PeerGroup{fi.unlA} << " " << " "
<< PeerGroup{fi.unlB} << " overlap " << fi.overlap
<< " required " << fi.required << "\n";
};
+70
View File
@@ -354,6 +354,76 @@ public:
}
};
struct finish_function
{
private:
std::string value_;
public:
explicit finish_function(std::string func) : value_(func)
{
}
explicit finish_function(Slice const& func) : value_(strHex(func))
{
}
template <size_t N>
explicit finish_function(std::array<std::uint8_t, N> const& f)
: finish_function(makeSlice(f))
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfFinishFunction.jsonName] = value_;
}
};
struct data
{
private:
std::string value_;
public:
explicit data(std::string func) : value_(func)
{
}
explicit data(Slice const& func) : value_(strHex(func))
{
}
template <size_t N>
explicit data(std::array<std::uint8_t, N> const& f) : data(makeSlice(f))
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfData.jsonName] = value_;
}
};
struct comp_allowance
{
private:
std::uint32_t value_;
public:
explicit comp_allowance(std::uint32_t const& value) : value_(value)
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfComputationAllowance.jsonName] = value_;
}
};
/* Payment Channel */
/******************************************************************************/
+5 -7
View File
@@ -220,9 +220,8 @@ public:
getList_ = [blob = blob, sig, manifest, version](int interval) {
// Build the contents of a version 1 format UNL file
std::stringstream l;
l << "{\"blob\":\"" << blob << "\""
<< ",\"signature\":\"" << sig << "\""
<< ",\"manifest\":\"" << manifest << "\""
l << "{\"blob\":\"" << blob << "\"" << ",\"signature\":\"" << sig
<< "\"" << ",\"manifest\":\"" << manifest << "\""
<< ",\"refresh_interval\": " << interval
<< ",\"version\":" << version << '}';
return l.str();
@@ -257,15 +256,14 @@ public:
std::stringstream l;
for (auto const& info : blobInfo)
{
l << "{\"blob\":\"" << info.blob << "\""
<< ",\"signature\":\"" << info.signature << "\"},";
l << "{\"blob\":\"" << info.blob << "\"" << ",\"signature\":\""
<< info.signature << "\"},";
}
std::string blobs = l.str();
blobs.pop_back();
l.str(std::string());
l << "{\"blobs_v2\": [ " << blobs << "],\"manifest\":\"" << manifest
<< "\""
<< ",\"refresh_interval\": " << interval
<< "\"" << ",\"refresh_interval\": " << interval
<< ",\"version\":" << (version + 1) << '}';
return l.str();
};
+5
View File
@@ -33,9 +33,14 @@ setupConfigForUnitTests(Config& cfg)
using namespace jtx;
// Default fees to old values, so tests don't have to worry about changes in
// Config.h
// NOTE: For new `FEES` fields, you need to wait for the first flag ledger
// to close for the values to be activated.
cfg.FEES.reference_fee = UNIT_TEST_REFERENCE_FEE;
cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
cfg.FEES.owner_reserve = XRP(50).value().xrp().drops();
cfg.FEES.extension_compute_limit = 1'000'000;
cfg.FEES.extension_size_limit = 100'000;
cfg.FEES.gas_price = 1'000'000; // 1 drop = 1,000,000 micro-drops
// The Beta API (currently v2) is always available to tests
cfg.BETA_RPC_API = true;
+2 -3
View File
@@ -897,9 +897,8 @@ protected:
printPeers(std::string const& msg, std::uint16_t validator = 0)
{
auto peers = network_.overlay().getPeers(network_.validator(validator));
std::cout << msg << " "
<< "num peers " << (int)network_.overlay().getNumPeers()
<< std::endl;
std::cout << msg << " " << "num peers "
<< (int)network_.overlay().getNumPeers() << std::endl;
for (auto& [k, v] : peers)
std::cout << k << ":" << (int)std::get<reduce_relay::PeerState>(v)
<< " ";
+18 -2
View File
@@ -522,6 +522,22 @@ public:
if (jv.isMember(jss::reserve_inc) != isFlagLedger)
return false;
if (env.closed()->rules().enabled(featureSmartEscrow))
{
if (jv.isMember(jss::extension_compute) != isFlagLedger)
return false;
if (jv.isMember(jss::extension_size) != isFlagLedger)
return false;
}
else
{
if (jv.isMember(jss::extension_compute))
return false;
if (jv.isMember(jss::extension_size))
return false;
}
return true;
};
@@ -1305,14 +1321,14 @@ public:
{
using namespace test::jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const xrpFees{featureXRPFees};
testServer();
testLedger();
testTransactions_APIv1();
testTransactions_APIv2();
testManifests();
testValidations(all - xrpFees);
testValidations(all - featureXRPFees - featureSmartEscrow);
testValidations(all - featureSmartEscrow);
testValidations(all);
testSubErrors(true);
testSubErrors(false);
+44 -5
View File
@@ -221,6 +221,15 @@ Ledger::Ledger(
sle->at(sfReserveIncrement) = *f;
sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED;
}
if (std::find(
amendments.begin(), amendments.end(), featureSmartEscrow) !=
amendments.end())
{
sle->at(sfExtensionComputeLimit) =
config.FEES.extension_compute_limit;
sle->at(sfExtensionSizeLimit) = config.FEES.extension_size_limit;
sle->at(sfGasPrice) = config.FEES.gas_price;
}
rawInsert(sle);
}
@@ -612,6 +621,7 @@ Ledger::setup()
{
bool oldFees = false;
bool newFees = false;
bool extensionFees = false;
{
auto const baseFee = sle->at(~sfBaseFee);
auto const reserveBase = sle->at(~sfReserveBase);
@@ -629,6 +639,7 @@ Ledger::setup()
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
auto const reserveIncrementXRP =
sle->at(~sfReserveIncrementDrops);
auto assign = [&ret](
XRPAmount& dest,
std::optional<STAmount> const& src) {
@@ -645,12 +656,35 @@ Ledger::setup()
assign(fees_.increment, reserveIncrementXRP);
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
}
{
auto const extensionComputeLimit =
sle->at(~sfExtensionComputeLimit);
auto const extensionSizeLimit = sle->at(~sfExtensionSizeLimit);
auto const gasPrice = sle->at(~sfGasPrice);
auto assign = [](std::uint32_t& dest,
std::optional<std::uint32_t> const& src) {
if (src)
{
dest = src.value();
}
};
assign(fees_.extensionComputeLimit, extensionComputeLimit);
assign(fees_.extensionSizeLimit, extensionSizeLimit);
assign(fees_.gasPrice, gasPrice);
extensionFees =
extensionComputeLimit || extensionSizeLimit || gasPrice;
}
if (oldFees && newFees)
// Should be all of one or the other, but not both
ret = false;
if (!rules_.enabled(featureXRPFees) && newFees)
// Can't populate the new fees before the amendment is enabled
ret = false;
if (!rules_.enabled(featureSmartEscrow) && extensionFees)
// Can't populate the extension fees before the amendment is
// enabled
ret = false;
}
}
catch (SHAMapMissingNode const&)
@@ -669,15 +703,20 @@ Ledger::setup()
void
Ledger::defaultFees(Config const& config)
{
XRPL_ASSERT(
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0,
"ripple::Ledger::defaultFees : zero fees");
assert(
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 &&
fees_.extensionComputeLimit == 0 && fees_.extensionSizeLimit == 0 &&
fees_.gasPrice == 0);
if (fees_.base == 0)
fees_.base = config.FEES.reference_fee;
if (fees_.reserve == 0)
fees_.reserve = config.FEES.account_reserve;
if (fees_.increment == 0)
fees_.increment = config.FEES.owner_reserve;
if (fees_.extensionComputeLimit == 0)
fees_.extensionComputeLimit = config.FEES.extension_compute_limit;
if (fees_.extensionSizeLimit == 0)
fees_.extensionSizeLimit = config.FEES.extension_size_limit;
if (fees_.gasPrice == 0)
fees_.gasPrice = config.FEES.gas_price;
}
std::shared_ptr<SLE>
+28 -30
View File
@@ -225,30 +225,30 @@ log_metadata_difference(
{
JLOG(j.debug()) << "MISMATCH on TX " << tx
<< ": Different result and index!";
JLOG(j.debug()) << " Built:"
<< " Result: " << builtMetaData->getResult()
<< " Index: " << builtMetaData->getIndex();
JLOG(j.debug()) << " Valid:"
<< " Result: " << validMetaData->getResult()
<< " Index: " << validMetaData->getIndex();
JLOG(j.debug())
<< " Built:" << " Result: " << builtMetaData->getResult()
<< " Index: " << builtMetaData->getIndex();
JLOG(j.debug())
<< " Valid:" << " Result: " << validMetaData->getResult()
<< " Index: " << validMetaData->getIndex();
}
else if (result_diff)
{
JLOG(j.debug())
<< "MISMATCH on TX " << tx << ": Different result!";
JLOG(j.debug()) << " Built:"
<< " Result: " << builtMetaData->getResult();
JLOG(j.debug()) << " Valid:"
<< " Result: " << validMetaData->getResult();
JLOG(j.debug())
<< " Built:" << " Result: " << builtMetaData->getResult();
JLOG(j.debug())
<< " Valid:" << " Result: " << validMetaData->getResult();
}
else if (index_diff)
{
JLOG(j.debug())
<< "MISMATCH on TX " << tx << ": Different index!";
JLOG(j.debug()) << " Built:"
<< " Index: " << builtMetaData->getIndex();
JLOG(j.debug()) << " Valid:"
<< " Index: " << validMetaData->getIndex();
JLOG(j.debug())
<< " Built:" << " Index: " << builtMetaData->getIndex();
JLOG(j.debug())
<< " Valid:" << " Index: " << validMetaData->getIndex();
}
}
else
@@ -267,12 +267,12 @@ log_metadata_difference(
JLOG(j.debug()) << "MISMATCH on TX " << tx
<< ": Different result and nodes!";
JLOG(j.debug())
<< " Built:"
<< " Result: " << builtMetaData->getResult() << " Nodes:\n"
<< " Built:" << " Result: " << builtMetaData->getResult()
<< " Nodes:\n"
<< builtNodes.getJson(JsonOptions::none);
JLOG(j.debug())
<< " Valid:"
<< " Result: " << validMetaData->getResult() << " Nodes:\n"
<< " Valid:" << " Result: " << validMetaData->getResult()
<< " Nodes:\n"
<< validNodes.getJson(JsonOptions::none);
}
else if (index_diff)
@@ -280,23 +280,21 @@ log_metadata_difference(
JLOG(j.debug()) << "MISMATCH on TX " << tx
<< ": Different index and nodes!";
JLOG(j.debug())
<< " Built:"
<< " Index: " << builtMetaData->getIndex() << " Nodes:\n"
<< " Built:" << " Index: " << builtMetaData->getIndex()
<< " Nodes:\n"
<< builtNodes.getJson(JsonOptions::none);
JLOG(j.debug())
<< " Valid:"
<< " Index: " << validMetaData->getIndex() << " Nodes:\n"
<< " Valid:" << " Index: " << validMetaData->getIndex()
<< " Nodes:\n"
<< validNodes.getJson(JsonOptions::none);
}
else // nodes_diff
{
JLOG(j.debug())
<< "MISMATCH on TX " << tx << ": Different nodes!";
JLOG(j.debug()) << " Built:"
<< " Nodes:\n"
JLOG(j.debug()) << " Built:" << " Nodes:\n"
<< builtNodes.getJson(JsonOptions::none);
JLOG(j.debug()) << " Valid:"
<< " Nodes:\n"
JLOG(j.debug()) << " Valid:" << " Nodes:\n"
<< validNodes.getJson(JsonOptions::none);
}
}
@@ -353,10 +351,10 @@ LedgerHistory::handleMismatch(
if (!builtLedger || !validLedger)
{
JLOG(j_.error()) << "MISMATCH cannot be analyzed:"
<< " builtLedger: " << to_string(built) << " -> "
<< builtLedger << " validLedger: " << to_string(valid)
<< " -> " << validLedger;
JLOG(j_.error()) << "MISMATCH cannot be analyzed:" << " builtLedger: "
<< to_string(built) << " -> " << builtLedger
<< " validLedger: " << to_string(valid) << " -> "
<< validLedger;
return;
}
@@ -100,8 +100,8 @@ TimeoutCounter::invokeOnTimer()
if (!progress_)
{
++timeouts_;
JLOG(journal_.debug()) << "Timeout(" << timeouts_ << ") "
<< " acquiring " << hash_;
JLOG(journal_.debug())
<< "Timeout(" << timeouts_ << ") " << " acquiring " << hash_;
onTimer(false, sl);
}
else
+2 -2
View File
@@ -447,8 +447,8 @@ GRPCServerImpl::handleRpcs()
if (!ok)
{
JLOG(journal_.debug()) << "Request listener cancelled. "
<< "Destroying object";
JLOG(journal_.debug())
<< "Request listener cancelled. " << "Destroying object";
erase(ptr);
}
else
+3 -3
View File
@@ -382,9 +382,9 @@ changeSpotPriceQuality(
{
JLOG(j.error())
<< "changeSpotPriceQuality failed: " << to_string(pool.in)
<< " " << to_string(pool.out) << " "
<< " " << quality << " " << tfee << " "
<< to_string(amounts.in) << " " << to_string(amounts.out);
<< " " << to_string(pool.out) << " " << " " << quality
<< " " << tfee << " " << to_string(amounts.in) << " "
<< to_string(amounts.out);
Throw<std::runtime_error>("changeSpotPriceQuality failed");
}
else
+79 -6
View File
@@ -28,10 +28,10 @@ namespace ripple {
namespace detail {
template <typename value_type>
class VotableValue
{
private:
using value_type = XRPAmount;
value_type const current_; // The current setting
value_type const target_; // The setting we want
std::map<value_type, int> voteMap_;
@@ -66,8 +66,9 @@ public:
getVotes() const;
};
auto
VotableValue::getVotes() const -> std::pair<value_type, bool>
template <typename value_type>
std::pair<value_type, bool>
VotableValue<value_type>::getVotes() const
{
value_type ourVote = current_;
int weight = 0;
@@ -190,6 +191,33 @@ FeeVoteImpl::doValidation(
"reserve increment",
sfReserveIncrement);
}
if (rules.enabled(featureSmartEscrow))
{
auto vote = [&v, this](
auto const current,
std::uint32_t target,
char const* name,
auto const& sfield) {
if (current != target)
{
JLOG(journal_.info())
<< "Voting for " << name << " of " << target;
v[sfield] = target;
}
};
vote(
lastFees.extensionComputeLimit,
target_.extension_compute_limit,
"extension compute limit",
sfExtensionComputeLimit);
vote(
lastFees.extensionSizeLimit,
target_.extension_size_limit,
"extension size limit",
sfExtensionSizeLimit);
vote(lastFees.gasPrice, target_.gas_price, "gas price", sfGasPrice);
}
}
void
@@ -212,11 +240,22 @@ FeeVoteImpl::doVoting(
detail::VotableValue incReserveVote(
lastClosedLedger->fees().increment, target_.owner_reserve);
detail::VotableValue extensionComputeVote(
lastClosedLedger->fees().extensionComputeLimit,
target_.extension_compute_limit);
detail::VotableValue extensionSizeVote(
lastClosedLedger->fees().extensionSizeLimit,
target_.extension_size_limit);
detail::VotableValue gasPriceVote(
lastClosedLedger->fees().gasPrice, target_.gas_price);
auto const& rules = lastClosedLedger->rules();
if (rules.enabled(featureXRPFees))
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue& value,
detail::VotableValue<XRPAmount>& value,
SF_AMOUNT const& xrpField) {
if (auto const field = ~val->at(~xrpField);
field && field->native())
@@ -245,7 +284,7 @@ FeeVoteImpl::doVoting(
else
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue& value,
detail::VotableValue<XRPAmount>& value,
auto const& valueField) {
if (auto const field = val->at(~valueField))
{
@@ -276,6 +315,30 @@ FeeVoteImpl::doVoting(
doVote(val, incReserveVote, sfReserveIncrement);
}
}
if (rules.enabled(featureSmartEscrow))
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue<std::uint32_t>& value,
SF_UINT32 const& extensionField) {
if (auto const field = ~val->at(~extensionField); field)
{
value.addVote(field.value());
}
else
{
value.noVote();
}
};
for (auto const& val : set)
{
if (!val->isTrusted())
continue;
doVote(val, extensionComputeVote, sfExtensionComputeLimit);
doVote(val, extensionSizeVote, sfExtensionSizeLimit);
doVote(val, gasPriceVote, sfGasPrice);
}
}
// choose our positions
// TODO: Use structured binding once LLVM 16 is the minimum supported
@@ -284,11 +347,15 @@ FeeVoteImpl::doVoting(
auto const baseFee = baseFeeVote.getVotes();
auto const baseReserve = baseReserveVote.getVotes();
auto const incReserve = incReserveVote.getVotes();
auto const extensionCompute = extensionComputeVote.getVotes();
auto const extensionSize = extensionSizeVote.getVotes();
auto const gasPrice = gasPriceVote.getVotes();
auto const seq = lastClosedLedger->info().seq + 1;
// add transactions to our position
if (baseFee.second || baseReserve.second || incReserve.second)
if (baseFee.second || baseReserve.second || incReserve.second ||
extensionCompute.second || extensionSize.second || gasPrice.second)
{
JLOG(journal_.warn())
<< "We are voting for a fee change: " << baseFee.first << "/"
@@ -316,6 +383,12 @@ FeeVoteImpl::doVoting(
incReserveVote.current());
obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED;
}
if (rules.enabled(featureSmartEscrow))
{
obj[sfExtensionComputeLimit] = extensionCompute.first;
obj[sfExtensionSizeLimit] = extensionSize.first;
obj[sfGasPrice] = gasPrice.first;
}
});
uint256 txID = feeTx.getTransactionID();
+46 -2
View File
@@ -2466,6 +2466,18 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
reserveIncXRP && reserveIncXRP->native())
jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped();
if (auto const extensionComputeLimit =
~val->at(~sfExtensionComputeLimit);
extensionComputeLimit)
jvObj[jss::extension_compute] = *extensionComputeLimit;
if (auto const extensionSizeLimit = ~val->at(~sfExtensionSizeLimit);
extensionSizeLimit)
jvObj[jss::extension_size] = *extensionSizeLimit;
if (auto const gasPrice = ~val->at(~sfGasPrice); gasPrice)
jvObj[jss::gas_price] = *gasPrice;
// NOTE Use MultiApiJson to publish two slightly different JSON objects
// for consumers supporting different API versions
MultiApiJson multiObj{jvObj};
@@ -2915,12 +2927,22 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
l[jss::seq] = Json::UInt(lpClosed->info().seq);
l[jss::hash] = to_string(lpClosed->info().hash);
bool const smartEscrowEnabled =
m_ledgerMaster.getValidatedLedger()->rules().enabled(
featureSmartEscrow);
if (!human)
{
l[jss::base_fee] = baseFee.jsonClipped();
l[jss::reserve_base] =
lpClosed->fees().accountReserve(0).jsonClipped();
l[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
if (smartEscrowEnabled)
{
l[jss::extension_compute] =
lpClosed->fees().extensionComputeLimit;
l[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
l[jss::gas_price] = lpClosed->fees().gasPrice;
}
l[jss::close_time] = Json::Value::UInt(
lpClosed->info().closeTime.time_since_epoch().count());
}
@@ -2930,6 +2952,13 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
l[jss::reserve_base_xrp] =
lpClosed->fees().accountReserve(0).decimalXRP();
l[jss::reserve_inc_xrp] = lpClosed->fees().increment.decimalXRP();
if (smartEscrowEnabled)
{
l[jss::extension_compute] =
lpClosed->fees().extensionComputeLimit;
l[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
l[jss::gas_price] = lpClosed->fees().gasPrice;
}
if (auto const closeOffset = app_.timeKeeper().closeOffset();
std::abs(closeOffset.count()) >= 60)
@@ -3120,6 +3149,14 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
lpAccepted->fees().accountReserve(0).jsonClipped();
jvObj[jss::reserve_inc] =
lpAccepted->fees().increment.jsonClipped();
if (lpAccepted->rules().enabled(featureSmartEscrow))
{
jvObj[jss::extension_compute] =
lpAccepted->fees().extensionComputeLimit;
jvObj[jss::extension_size] =
lpAccepted->fees().extensionSizeLimit;
jvObj[jss::gas_price] = lpAccepted->fees().gasPrice;
}
jvObj[jss::txn_count] = Json::UInt(alpAccepted->size());
@@ -3489,8 +3526,8 @@ NetworkOPsImp::pubAccountTransaction(
}
JLOG(m_journal.trace())
<< "pubAccountTransaction: "
<< "proposed=" << iProposed << ", accepted=" << iAccepted;
<< "pubAccountTransaction: " << "proposed=" << iProposed
<< ", accepted=" << iAccepted;
if (!notify.empty() || !accountHistoryNotify.empty())
{
@@ -4170,6 +4207,13 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
jvResult[jss::reserve_base] =
lpClosed->fees().accountReserve(0).jsonClipped();
jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
if (lpClosed->rules().enabled(featureSmartEscrow))
{
jvResult[jss::extension_compute] =
lpClosed->fees().extensionComputeLimit;
jvResult[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
jvResult[jss::gas_price] = lpClosed->fees().gasPrice;
}
}
if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger())
+956
View File
@@ -0,0 +1,956 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/misc/WamrVM.h>
#include <xrpl/basics/Log.h>
#include <memory>
#ifdef _DEBUG
// #define DEBUG_OUTPUT 1
// #define DEBUG_OUTPUT_WAMR 1
#endif
namespace ripple {
namespace {
static log_level_t
getLogLevel(beast::severities::Severity severity)
{
using namespace beast::severities;
switch (severity)
{
case kTrace:
return WASM_LOG_LEVEL_VERBOSE;
case kDebug:
return WASM_LOG_LEVEL_DEBUG;
case kInfo:
case kWarning:
return WASM_LOG_LEVEL_WARNING;
case kError:
return WASM_LOG_LEVEL_ERROR;
default:
UNREACHABLE("WAMR invalid severity");
[[fallthrough]];
case kFatal:
case kNone:
break;
}
return WASM_LOG_LEVEL_FATAL;
}
static beast::severities::Severity
getLogLevel(uint32_t severity)
{
using namespace beast::severities;
switch (severity)
{
case WASM_LOG_LEVEL_VERBOSE:
return kTrace;
case WASM_LOG_LEVEL_DEBUG:
return kDebug;
case WASM_LOG_LEVEL_WARNING:
return kWarning;
case WASM_LOG_LEVEL_ERROR:
return kError;
default:
UNREACHABLE("WAMR invalid reverse severity");
[[fallthrough]];
case WASM_LOG_LEVEL_FATAL:
break;
}
return kFatal;
}
// This function is called from WAMR to log messages.
extern "C" void
wamr_log_to_rippled(
uint32_t logLevel,
char const* file,
int line,
char const* fmt,
...)
{
beast::Journal j = WasmEngine::instance().getJournal();
// Format the variadic args
char const* safeFile = file ? file : "<null>";
std::ostringstream oss;
oss << "WAMR (" << safeFile << ":" << line << "): ";
va_list args;
va_start(args, fmt);
char formatted[4096];
vsnprintf(formatted, sizeof(formatted), fmt, args);
formatted[sizeof(formatted) - 1] = '\0';
va_end(args);
oss << formatted;
j.stream(getLogLevel(logLevel)) << oss.str();
#ifdef DEBUG_OUTPUT_WAMR
std::cerr << oss.str() << std::endl;
#endif
//
}
void
print_wasm_error(std::string_view msg, wasm_trap_t* trap, beast::Journal jlog)
{
#ifdef DEBUG_OUTPUT
auto& j = std::cerr;
#else
auto j = jlog.error();
#endif
j << "WAMR Error: " << msg;
if (trap)
{
wasm_byte_vec_t error_message;
wasm_trap_message(trap, &error_message);
if (error_message.num_elems)
{
j <<
#ifdef DEBUG_OUTPUT
"\nWAMR "
#else
"WAMR "
#endif
<< error_message.data;
}
if (error_message.size)
wasm_byte_vec_delete(&error_message);
wasm_trap_delete(trap);
}
#ifdef DEBUG_OUTPUT
j << std::endl;
#endif
}
} // namespace
InstancePtr
InstanceWrapper::init(
wasm_store_t* s,
wasm_module_t* m,
int32_t maxPages,
wasm_extern_vec_t* expt,
wasm_extern_vec_t const& imports,
beast::Journal j)
{
wasm_trap_t* trap = nullptr;
InstantiationArgs inst_args{
128 * 1024,
256 * 1024,
static_cast<uint32_t>(maxPages > 0 ? maxPages : 0)};
InstancePtr mi = InstancePtr(
wasm_instance_new_with_args_ex(s, m, &imports, &trap, &inst_args),
&wasm_instance_delete);
if (!mi || trap)
{
print_wasm_error("can't create instance", trap, j);
throw std::runtime_error("WAMR can't create instance");
}
wasm_instance_exports(mi.get(), expt);
return mi;
}
InstanceWrapper::InstanceWrapper()
: exports{0, nullptr, 0, 0, nullptr}
, mod_inst(nullptr, &wasm_instance_delete)
{
}
InstanceWrapper::InstanceWrapper(InstanceWrapper&& o)
: exports{0, nullptr, 0, 0, nullptr}
, mod_inst(nullptr, &wasm_instance_delete)
{
*this = std::move(o);
}
InstanceWrapper::InstanceWrapper(
wasm_store_t* s,
wasm_module_t* m,
int32_t maxPages,
int64_t gas,
wasm_extern_vec_t const& imports,
beast::Journal j)
: exports WASM_EMPTY_VEC
, mod_inst(init(s, m, maxPages, &exports, imports, j))
, exec_env(wasm_instance_exec_env(mod_inst.get()))
, j_(j)
{
wasm_runtime_set_instruction_count_limit(exec_env, gas);
}
InstanceWrapper::~InstanceWrapper()
{
if (exports.size)
wasm_extern_vec_delete(&exports);
}
InstanceWrapper&
InstanceWrapper::operator=(InstanceWrapper&& o)
{
if (this == &o)
return *this;
if (exports.size)
wasm_extern_vec_delete(&exports);
exports = o.exports;
o.exports = {0, nullptr, 0, 0, nullptr};
mod_inst = std::move(o.mod_inst);
exec_env = o.exec_env;
o.exec_env = nullptr;
j_ = o.j_;
return *this;
}
InstanceWrapper::operator bool() const
{
return static_cast<bool>(mod_inst);
}
FuncInfo
InstanceWrapper::getFunc(
std::string_view funcName,
wasm_exporttype_vec_t const& export_types) const
{
wasm_func_t* f = nullptr;
wasm_functype_t* ft = nullptr;
if (!mod_inst)
throw std::runtime_error("WAMR no module instance");
if (!export_types.num_elems)
throw std::runtime_error("WAMR no export");
if (export_types.num_elems != exports.num_elems)
throw std::runtime_error("WAMR invalid export");
for (unsigned i = 0; i < export_types.num_elems; ++i)
{
auto const* exp_type(export_types.data[i]);
wasm_name_t const* name = wasm_exporttype_name(exp_type);
wasm_externtype_t const* exn_type = wasm_exporttype_type(exp_type);
if (wasm_externtype_kind(exn_type) == WASM_EXTERN_FUNC)
{
if (funcName == std::string_view(name->data, name->size - 1))
{
auto* exn(exports.data[i]);
if (wasm_extern_kind(exn) != WASM_EXTERN_FUNC)
throw std::runtime_error("WAMR invalid export");
ft = wasm_externtype_as_functype(
const_cast<wasm_externtype_t*>(exn_type));
f = wasm_extern_as_func(exn);
break;
}
}
}
if (!f || !ft)
throw std::runtime_error(
"WAMR can't find function <" + std::string(funcName) + ">");
return {f, ft};
}
wmem
InstanceWrapper::getMem() const
{
if (!mod_inst)
throw std::runtime_error("WAMR no module instance");
wasm_memory_t* mem = nullptr;
for (unsigned i = 0; i < exports.num_elems; ++i)
{
auto* e(exports.data[i]);
if (wasm_extern_kind(e) == WASM_EXTERN_MEMORY)
{
mem = wasm_extern_as_memory(e);
break;
}
}
if (!mem)
throw std::runtime_error("WAMR no memory exported");
return {
reinterpret_cast<std::uint8_t*>(wasm_memory_data(mem)),
wasm_memory_data_size(mem)};
}
std::int64_t
InstanceWrapper::getGas() const
{
return exec_env ? wasm_runtime_get_instruction_count_limit(exec_env) : 0;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
ModulePtr
ModuleWrapper::init(wasm_store_t* s, Bytes const& wasmBin, beast::Journal j)
{
wasm_byte_vec_t const code{
wasmBin.size(),
(char*)(wasmBin.data()),
wasmBin.size(),
sizeof(std::remove_reference_t<decltype(wasmBin)>::value_type),
nullptr};
ModulePtr m = ModulePtr(wasm_module_new(s, &code), &wasm_module_delete);
if (!m)
{
print_wasm_error("can't create module", nullptr, j);
throw std::runtime_error("WAMR can't create module");
}
return m;
}
ModuleWrapper::ModuleWrapper()
: module(nullptr, &wasm_module_delete)
, export_types{0, nullptr, 0, 0, nullptr}
{
}
ModuleWrapper::ModuleWrapper(ModuleWrapper&& o)
: module(nullptr, &wasm_module_delete)
, export_types{0, nullptr, 0, 0, nullptr}
{
*this = std::move(o);
}
ModuleWrapper::ModuleWrapper(
wasm_store_t* s,
Bytes const& wasmBin,
bool instantiate,
int32_t maxPages,
int64_t gas,
std::vector<WasmImportFunc> const& imports,
beast::Journal j)
: module(init(s, wasmBin, j))
, export_types{0, nullptr, 0, 0, nullptr}
, j_(j)
{
wasm_module_exports(module.get(), &export_types);
if (instantiate)
{
auto wimports = buildImports(s, imports);
addInstance(s, maxPages, gas, wimports);
}
}
ModuleWrapper::~ModuleWrapper()
{
if (export_types.size)
wasm_exporttype_vec_delete(&export_types);
}
ModuleWrapper&
ModuleWrapper::operator=(ModuleWrapper&& o)
{
if (this == &o)
return *this;
module = std::move(o.module);
mod_inst = std::move(o.mod_inst);
if (export_types.size)
wasm_exporttype_vec_delete(&export_types);
export_types = o.export_types;
o.export_types = {0, nullptr, 0, 0, nullptr};
j_ = o.j_;
return *this;
}
ModuleWrapper::operator bool() const
{
return mod_inst;
}
void
ModuleWrapper::makeImpParams(wasm_valtype_vec_t& v, WasmImportFunc const& imp)
{
auto const paramSize = imp.params.size();
if (paramSize)
{
wasm_valtype_vec_new(&v, paramSize, nullptr);
v.num_elems = paramSize;
}
else
v = WASM_EMPTY_VEC;
for (unsigned i = 0; i < paramSize; ++i)
{
auto const vt = imp.params[i];
switch (vt)
{
case WT_I32:
v.data[i] = wasm_valtype_new_i32();
break;
case WT_I64:
v.data[i] = wasm_valtype_new_i64();
break;
case WT_F32:
v.data[i] = wasm_valtype_new_f32();
break;
case WT_F64:
v.data[i] = wasm_valtype_new_f64();
break;
default:
throw std::runtime_error("WAMR Invalid import type");
}
}
}
void
ModuleWrapper::makeImpReturn(wasm_valtype_vec_t& v, WasmImportFunc const& imp)
{
if (imp.result)
{
wasm_valtype_vec_new(&v, 1, nullptr);
v.num_elems = 1;
switch (*imp.result)
{
case WT_I32:
v.data[0] = wasm_valtype_new_i32();
break;
case WT_I64:
v.data[0] = wasm_valtype_new_i64();
break;
case WT_F32:
v.data[0] = wasm_valtype_new_f32();
break;
case WT_F64:
v.data[0] = wasm_valtype_new_f64();
break;
default:
throw std::runtime_error("WAMR Invalid return type");
}
}
else
v = WASM_EMPTY_VEC;
}
wasm_extern_vec_t
ModuleWrapper::buildImports(
wasm_store_t* s,
std::vector<WasmImportFunc> const& imports)
{
wasm_importtype_vec_t importTypes = WASM_EMPTY_VEC;
wasm_module_imports(module.get(), &importTypes);
std::
unique_ptr<wasm_importtype_vec_t, decltype(&wasm_importtype_vec_delete)>
itDeleter(&importTypes, &wasm_importtype_vec_delete);
wasm_extern_vec_t wimports = WASM_EMPTY_VEC;
if (!importTypes.num_elems)
return wimports;
wasm_extern_vec_new(&wimports, importTypes.size, nullptr);
wimports.num_elems = importTypes.num_elems;
unsigned impCnt = 0;
for (unsigned i = 0; i < importTypes.num_elems; ++i)
{
wasm_importtype_t const* importtype = importTypes.data[i];
if (wasm_importtype_is_linked(importtype))
{
// create a placeholder
wimports.data[i] = wasm_extern_new_empty(
s, wasm_externtype_kind(wasm_importtype_type(importtype)));
++impCnt;
continue;
}
// wasm_name_t const* mn = wasm_importtype_module(importtype);
// auto modName = std::string_view(mn->data, mn->num_elems - 1);
wasm_name_t const* fn = wasm_importtype_name(importtype);
auto fieldName = std::string_view(fn->data, fn->num_elems - 1);
// for multi-module support
// if ((W_ENV != modName) && (W_HOST_LIB != modName))
// continue;
bool impSet = false;
for (auto const& imp : imports)
{
if (imp.name != fieldName)
continue;
wasm_valtype_vec_t params, results;
makeImpReturn(results, imp);
makeImpParams(params, imp);
using ftype_ptr = std::
unique_ptr<wasm_functype_t, decltype(&wasm_functype_delete)>;
ftype_ptr ftype(
wasm_functype_new(&params, &results), &wasm_functype_delete);
wasm_func_t* func = wasm_func_new_with_env(
s,
ftype.get(),
reinterpret_cast<wasm_func_callback_with_env_t>(imp.wrap),
imp.udata,
nullptr);
wimports.data[i] = wasm_func_as_extern(func);
++impCnt;
impSet = true;
break;
}
if (!impSet)
{
print_wasm_error(
std::string("Import not found: ") + fieldName.data(),
nullptr,
j_);
}
}
if (impCnt != importTypes.num_elems)
{
print_wasm_error(
std::string("Imports not finished: ") +
std::to_string(wimports.num_elems) + "/" +
std::to_string(importTypes.num_elems),
nullptr,
j_);
}
return wimports;
}
FuncInfo
ModuleWrapper::getFunc(std::string_view funcName) const
{
return mod_inst.getFunc(funcName, export_types);
}
wmem
ModuleWrapper::getMem() const
{
return mod_inst.getMem();
}
InstanceWrapper const&
ModuleWrapper::getInstance(int) const
{
return mod_inst;
}
int
ModuleWrapper::addInstance(
wasm_store_t* s,
int32_t maxPages,
int64_t gas,
wasm_extern_vec_t const& imports)
{
mod_inst = {s, module.get(), maxPages, gas, imports, j_};
return 0;
}
// int
// my_module_t::delInstance(int i)
// {
// if (i >= mod_inst.size())
// return -1;
// if (!mod_inst[i])
// mod_inst[i] = my_mod_inst_t();
// return i;
// }
std::int64_t
ModuleWrapper::getGas()
{
return mod_inst.getGas();
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// void
// WamrEngine::clearModules()
// {
// modules.clear();
// store.reset(); // to free the memory before creating new store
// store = {wasm_store_new(engine.get()), &wasm_store_delete};
// }
WamrEngine::WamrEngine()
: engine(wasm_engine_new(), &wasm_engine_delete)
, store(nullptr, &wasm_store_delete)
{
wasm_runtime_set_default_running_mode(Mode_Interp);
wasm_runtime_set_log_level(WASM_LOG_LEVEL_FATAL);
// wasm_runtime_set_log_level(WASM_LOG_LEVEL_VERBOSE);
}
int
WamrEngine::addModule(
Bytes const& wasmCode,
bool instantiate,
int64_t gas,
std::vector<WasmImportFunc> const& imports)
{
module.reset();
store.reset(); // to free the memory before creating new store
store = {wasm_store_new(engine.get()), &wasm_store_delete};
module = std::make_unique<ModuleWrapper>(
store.get(), wasmCode, instantiate, defMaxPages, gas, imports, j_);
if (!module)
{
print_wasm_error("can't create module wrapper", nullptr, j_);
throw std::runtime_error("WAMR can't create module wrapper");
}
return module ? 0 : -1;
}
// int
// WamrEngine::addInstance()
// {
// return module->addInstance(store.get(), defMaxPages);
// }
FuncInfo
WamrEngine::getFunc(std::string_view funcName)
{
return module->getFunc(funcName);
}
std::vector<wasm_val_t>
WamrEngine::convertParams(std::vector<WasmParam> const& params)
{
std::vector<wasm_val_t> v;
v.reserve(params.size());
for (auto const& p : params)
{
switch (p.type)
{
case WT_I32:
v.push_back(WASM_I32_VAL(p.of.i32));
break;
case WT_I64:
v.push_back(WASM_I64_VAL(p.of.i64));
break;
case WT_F32:
v.push_back(WASM_F32_VAL(p.of.f32));
break;
case WT_F64:
v.push_back(WASM_F64_VAL(p.of.f64));
break;
case WT_U8V: {
auto const sz = p.of.u8v.sz;
auto const ptr = allocate(sz);
auto mem = getMem();
memcpy(mem.p + ptr, p.of.u8v.d, sz);
v.push_back(WASM_I32_VAL(ptr));
v.push_back(WASM_I32_VAL(sz));
}
break;
default:
break;
}
}
return v;
}
void
WamrEngine::add_param(std::vector<wasm_val_t>& in, int32_t p)
{
in.emplace_back();
auto& el(in.back());
memset(&el, 0, sizeof(el));
el = WASM_I32_VAL(p); // WASM_I32;
}
void
WamrEngine::add_param(std::vector<wasm_val_t>& in, int64_t p)
{
in.emplace_back();
auto& el(in.back());
el = WASM_I64_VAL(p);
}
template <int NR, class... Types>
WamrResult
WamrEngine::call(std::string_view func, Types&&... args)
{
// Lookup our export function
auto f = getFunc(func);
return call<NR>(f, std::forward<Types>(args)...);
}
template <int NR, class... Types>
WamrResult
WamrEngine::call(FuncInfo const& f, Types&&... args)
{
std::vector<wasm_val_t> in;
return call<NR>(f, in, std::forward<Types>(args)...);
}
template <int NR, class... Types>
WamrResult
WamrEngine::call(FuncInfo const& f, std::vector<wasm_val_t>& in)
{
// wasm_val_t rs[1] = {WASM_I32_VAL(0)};
WamrResult ret(NR);
// if (NR) { wasm_val_vec_new_uninitialized(&ret, NR); //
// wasm_val_vec_new(&ret, NR, &rs[0]); // ret = WASM_ARRAY_VEC(rs); }
auto const* ftp = wasm_functype_params(f.second);
if (ftp->num_elems != in.size())
{
print_wasm_error("invalid num of params to call func", nullptr, j_);
return ret;
}
wasm_val_vec_t const inv = in.empty()
? wasm_val_vec_t WASM_EMPTY_VEC
: wasm_val_vec_t{
in.size(),
in.data(),
in.size(),
sizeof(std::remove_reference_t<decltype(in)>::value_type),
nullptr};
wasm_trap_t* trap = wasm_func_call(f.first, &inv, &ret.r);
if (trap)
{
ret.f = true;
print_wasm_error("failed to call func", trap, j_);
}
// assert(results[0].kind == WASM_I32);
// if (NR) printf("Result P5: %d\n", ret[0].of.i32);
return ret;
}
template <int NR, class... Types>
WamrResult
WamrEngine::call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
std::int32_t p,
Types&&... args)
{
add_param(in, p);
return call<NR>(f, in, std::forward<Types>(args)...);
}
template <int NR, class... Types>
WamrResult
WamrEngine::call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
std::int64_t p,
Types&&... args)
{
add_param(in, p);
return call<NR>(f, in, std::forward<Types>(args)...);
}
template <int NR, class... Types>
WamrResult
WamrEngine::call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
uint8_t const* d,
std::size_t sz,
Types&&... args)
{
auto const ptr = allocate(sz);
auto mem = getMem();
memcpy(mem.p + ptr, d, sz);
add_param(in, ptr);
add_param(in, static_cast<int32_t>(sz));
return call<NR>(f, in, std::forward<Types>(args)...);
}
template <int NR, class... Types>
WamrResult
WamrEngine::call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
Bytes const& p,
Types&&... args)
{
return call<NR>(f, in, p.data(), p.size(), std::forward<Types>(args)...);
}
Expected<WasmResult<int32_t>, TER>
WamrEngine::run(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
std::vector<WasmImportFunc> const& imports,
HostFunctions* hfs,
int64_t gas,
beast::Journal j)
{
j_ = j;
wasm_runtime_set_log_level(
std::min(getLogLevel(j_.sink().threshold()), WASM_LOG_LEVEL_ERROR));
try
{
return runHlp(wasmCode, funcName, params, imports, hfs, gas);
}
catch (std::exception const& e)
{
print_wasm_error(std::string("exception: ") + e.what(), nullptr, j_);
}
catch (...)
{
print_wasm_error(std::string("unknown exception"), nullptr, j_);
}
return Unexpected<TER>(tecFAILED_PROCESSING);
}
Expected<WasmResult<int32_t>, TER>
WamrEngine::runHlp(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
std::vector<WasmImportFunc> const& imports,
HostFunctions* hfs,
int64_t gas)
{
// #ifdef DEBUG_OUTPUT
// auto& j = std::cerr;
// #else
// auto j = j_.debug();
// #endif
// Create and instantiate the module.
if (!wasmCode.empty())
{
[[maybe_unused]] int const m = addModule(wasmCode, true, gas, imports);
}
if (!module || !module->mod_inst)
{
print_wasm_error("no instance to run", nullptr, j_);
return Unexpected<TER>(tecFAILED_PROCESSING);
}
if (hfs)
hfs->setRT(&getRT());
// Call main
auto f = getFunc(!funcName.empty() ? funcName : "_start");
auto p = convertParams(params);
auto res = call<1>(f, p);
if (res.f)
{
return Unexpected<TER>(tecFAILED_PROCESSING);
}
else if (!res.r.num_elems)
{
print_wasm_error(
"<" + std::string(funcName) + "> return nothing", nullptr, j_);
return Unexpected<TER>(tecFAILED_PROCESSING);
}
assert(res.r.data[0].kind == WASM_I32);
WasmResult<int32_t> const ret{res.r.data[0].of.i32, gas - module->getGas()};
// j << "WAMR Res: " << ret.result << " cost: " << ret.cost << std::endl;
return ret;
}
std::int32_t
WamrEngine::initMaxPages(std::int32_t def)
{
defMaxPages = def;
return def;
}
std::int64_t
WamrEngine::getGas()
{
return module ? module->getGas() : 0;
}
wmem
WamrEngine::getMem() const
{
return module ? module->getMem() : wmem();
}
InstanceWrapper const&
WamrEngine::getRT(int m, int i)
{
if (!module)
throw std::runtime_error("WAMR no module");
return module->getInstance(i);
}
int32_t
WamrEngine::allocate(int32_t sz)
{
auto res = call<1>(W_ALLOC, static_cast<int32_t>(sz));
if (res.f || !res.r.num_elems || (res.r.data[0].kind != WASM_I32) ||
!res.r.data[0].of.i32)
throw std::runtime_error(
"WAMR can't allocate memory, " + std::to_string(sz) + " bytes");
return res.r.data[0].of.i32;
}
wasm_trap_t*
WamrEngine::newTrap(std::string_view txt)
{
wasm_message_t msg = WASM_EMPTY_VEC;
if (!txt.empty())
wasm_name_new(&msg, txt.size(), txt.data());
return wasm_trap_new(store.get(), &msg);
}
beast::Journal
WamrEngine::getJournal() const
{
return j_;
}
} // namespace ripple
+301
View File
@@ -0,0 +1,301 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 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.
*/
//==============================================================================
#pragma once
#include <xrpld/app/misc/WasmVM.h>
#include <iwasm/wasm_c_api.h>
#include <iwasm/wasm_export.h>
namespace ripple {
struct WamrResult
{
wasm_val_vec_t r;
bool f;
WamrResult(unsigned N = 0) : r{0, nullptr, 0, 0, nullptr}, f(false)
{
if (N)
wasm_val_vec_new_uninitialized(&r, N);
}
~WamrResult()
{
if (r.size)
wasm_val_vec_delete(&r);
}
WamrResult(WamrResult const&) = delete;
WamrResult&
operator=(WamrResult const&) = delete;
WamrResult(WamrResult&& o)
{
*this = std::move(o);
}
WamrResult&
operator=(WamrResult&& o)
{
r = o.r;
o.r = {0, nullptr, 0, 0, nullptr};
f = o.f;
o.f = false;
return *this;
}
// operator wasm_val_vec_t &() {return r;}
};
using ModulePtr = std::unique_ptr<wasm_module_t, decltype(&wasm_module_delete)>;
using InstancePtr =
std::unique_ptr<wasm_instance_t, decltype(&wasm_instance_delete)>;
using FuncInfo = std::pair<wasm_func_t const*, wasm_functype_t const*>;
struct InstanceWrapper
{
wasm_extern_vec_t exports;
InstancePtr mod_inst;
wasm_exec_env_t exec_env = nullptr;
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
private:
static InstancePtr
init(
wasm_store_t* s,
wasm_module_t* m,
int32_t maxPages,
wasm_extern_vec_t* expt,
wasm_extern_vec_t const& imports,
beast::Journal j);
public:
InstanceWrapper();
InstanceWrapper(InstanceWrapper&& o);
InstanceWrapper&
operator=(InstanceWrapper&& o);
InstanceWrapper(
wasm_store_t* s,
wasm_module_t* m,
int32_t maxPages,
int64_t gas,
wasm_extern_vec_t const& imports,
beast::Journal j);
~InstanceWrapper();
operator bool() const;
FuncInfo
getFunc(
std::string_view funcName,
wasm_exporttype_vec_t const& export_types) const;
wmem
getMem() const;
std::int64_t
getGas() const;
};
struct ModuleWrapper
{
ModulePtr module;
InstanceWrapper mod_inst;
wasm_exporttype_vec_t export_types;
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
private:
static ModulePtr
init(wasm_store_t* s, Bytes const& wasmBin, beast::Journal j);
public:
ModuleWrapper();
ModuleWrapper(ModuleWrapper&& o);
ModuleWrapper&
operator=(ModuleWrapper&& o);
ModuleWrapper(
wasm_store_t* s,
Bytes const& wasmBin,
bool instantiate,
int32_t maxPages,
int64_t gas,
std::vector<WasmImportFunc> const& imports,
beast::Journal j);
~ModuleWrapper();
operator bool() const;
FuncInfo
getFunc(std::string_view funcName) const;
wmem
getMem() const;
InstanceWrapper const&
getInstance(int i = 0) const;
int
addInstance(
wasm_store_t* s,
int32_t maxPages,
int64_t gas,
wasm_extern_vec_t const& imports = WASM_EMPTY_VEC);
std::int64_t
getGas();
private:
static void
makeImpParams(wasm_valtype_vec_t& v, WasmImportFunc const& imp);
static void
makeImpReturn(wasm_valtype_vec_t& v, WasmImportFunc const& imp);
wasm_extern_vec_t
buildImports(wasm_store_t* s, std::vector<WasmImportFunc> const& imports);
};
class WamrEngine
{
std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)> engine;
std::unique_ptr<wasm_store_t, decltype(&wasm_store_delete)> store;
std::unique_ptr<ModuleWrapper> module;
std::int32_t defMaxPages = -1;
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
public:
WamrEngine();
~WamrEngine() = default;
Expected<WasmResult<int32_t>, TER>
run(Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
std::vector<WasmImportFunc> const& imports,
HostFunctions* hfs,
int64_t gas,
beast::Journal j);
std::int32_t
initMaxPages(std::int32_t def);
std::int64_t
getGas();
// Host functions helper functionality
wasm_trap_t*
newTrap(std::string_view msg);
beast::Journal
getJournal() const;
private:
InstanceWrapper const&
getRT(int m = 0, int i = 0);
wmem
getMem() const;
int32_t
allocate(int32_t size);
Expected<WasmResult<int32_t>, TER>
runHlp(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
std::vector<WasmImportFunc> const& imports,
HostFunctions* hfs,
int64_t gas);
int
addModule(
Bytes const& wasmCode,
bool instantiate,
int64_t gas,
std::vector<WasmImportFunc> const& imports);
void
clearModules();
// int addInstance();
int32_t
runFunc(std::string_view const funcName, int32_t p);
int32_t
makeModule(
Bytes const& wasmCode,
wasm_extern_vec_t const& imports = WASM_EMPTY_VEC);
FuncInfo
getFunc(std::string_view funcName);
std::vector<wasm_val_t>
convertParams(std::vector<WasmParam> const& params);
void
add_param(std::vector<wasm_val_t>& in, int32_t p);
void
add_param(std::vector<wasm_val_t>& in, int64_t p);
template <int NR, class... Types>
inline WamrResult
call(std::string_view func, Types&&... args);
template <int NR, class... Types>
inline WamrResult
call(FuncInfo const& f, Types&&... args);
template <int NR, class... Types>
inline WamrResult
call(FuncInfo const& f, std::vector<wasm_val_t>& in);
template <int NR, class... Types>
inline WamrResult
call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
std::int32_t p,
Types&&... args);
template <int NR, class... Types>
inline WamrResult
call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
std::int64_t p,
Types&&... args);
template <int NR, class... Types>
inline WamrResult
call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
uint8_t const* d,
std::size_t sz,
Types&&... args);
template <int NR, class... Types>
inline WamrResult
call(
FuncInfo const& f,
std::vector<wasm_val_t>& in,
Bytes const& p,
Types&&... args);
};
} // namespace ripple
+224
View File
@@ -0,0 +1,224 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 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.
*/
//==============================================================================
#pragma once
#include <xrpld/app/misc/WasmParamsHelper.h>
#include <xrpl/basics/Expected.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/TER.h>
namespace ripple {
int32_t const HF_ERR_INTERNAL = -1;
int32_t const HF_ERR_FIELD_NOT_FOUND = -2;
int32_t const HF_ERR_BUFFER_TOO_SMALL = -3;
int32_t const HF_ERR_NO_ARRAY = -4;
int32_t const HF_ERR_NOT_LEAF_FIELD = -5;
int32_t const HF_ERR_LOCATOR_MALFORMED = -6;
int32_t const HF_ERR_SLOT_OUT_RANGE = -7;
int32_t const HF_ERR_SLOTS_FULL = -8;
int32_t const HF_ERR_INVALID_SLOT = -9;
int32_t const HF_ERR_LEDGER_OBJ_NOT_FOUND = -10;
int32_t const HF_ERR_DECODING = -11;
int32_t const HF_ERR_DATA_FIELD_TOO_LARGE = -12;
int32_t const HF_ERR_OUT_OF_BOUNDS = -13;
int32_t const HF_ERR_NO_MEM_EXPORTED = -14;
int32_t const HF_ERR_INVALID_PARAMS = -15;
int32_t const HF_ERR_INVALID_ACCOUNT = -16;
struct HostFunctions
{
virtual void
setRT(void const*)
{
}
virtual void const*
getRT() const
{
return nullptr;
}
virtual beast::Journal
getJournal()
{
return beast::Journal{beast::Journal::getNullSink()};
}
virtual int32_t
getLedgerSqn()
{
return 1;
}
virtual int32_t
getParentLedgerTime()
{
return 1;
}
virtual Hash
getParentLedgerHash()
{
return {};
}
virtual int32_t
cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx)
{
return HF_ERR_INTERNAL;
}
virtual Expected<Bytes, int32_t>
getTxField(SField const& fname)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getCurrentLedgerObjField(SField const& fname)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getLedgerObjField(int32_t cacheIdx, SField const& fname)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getTxNestedField(Slice const& locator)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getCurrentLedgerObjNestedField(Slice const& locator)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual int32_t
getTxArrayLen(SField const& fname)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
getCurrentLedgerObjArrayLen(SField const& fname)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
getTxNestedArrayLen(Slice const& locator)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
getCurrentLedgerObjNestedArrayLen(Slice const& locator)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
updateData(Bytes const& data)
{
return HF_ERR_INTERNAL;
}
virtual Hash
computeSha512HalfHash(Bytes const& data)
{
return Hash{};
}
virtual Expected<Bytes, int32_t>
accountKeylet(AccountID const& account)
{
return Bytes{};
}
virtual Expected<Bytes, int32_t>
credentialKeylet(
AccountID const& subject,
AccountID const& issuer,
Bytes const& credentialType)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
escrowKeylet(AccountID const& account, std::uint32_t seq)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
oracleKeylet(AccountID const& account, std::uint32_t docId)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual Expected<Bytes, int32_t>
getNFT(AccountID const& account, uint256 const& nftId)
{
return Unexpected(HF_ERR_INTERNAL);
}
virtual int32_t
trace(std::string const& msg, Bytes const& data, bool asHex)
{
return HF_ERR_INTERNAL;
}
virtual int32_t
traceNum(std::string const& msg, int64_t data)
{
return HF_ERR_INTERNAL;
}
virtual ~HostFunctions() = default;
};
} // namespace ripple
+496
View File
@@ -0,0 +1,496 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/misc/WasmHostFuncImpl.h>
#include <xrpld/app/tx/detail/NFTokenUtils.h>
#include <xrpl/protocol/STBitString.h>
#include <xrpl/protocol/digest.h>
#ifdef _DEBUG
// #define DEBUG_OUTPUT 1
// #define DEBUG_OUTPUT_WAMR 1
#endif
namespace ripple {
int32_t
WasmHostFunctionsImpl::getLedgerSqn()
{
return ctx.view().seq();
}
int32_t
WasmHostFunctionsImpl::getParentLedgerTime()
{
return ctx.view().parentCloseTime().time_since_epoch().count(); // TODO try
}
Hash
WasmHostFunctionsImpl::getParentLedgerHash()
{
return ctx.view().info().parentHash;
}
int32_t
WasmHostFunctionsImpl::cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx)
{
if (cacheIdx < 0 || cacheIdx > MAX_CACHE)
return HF_ERR_SLOT_OUT_RANGE;
if (!cacheIdx)
{
for (cacheIdx = 0; cacheIdx < MAX_CACHE; ++cacheIdx)
if (!cache[cacheIdx])
break;
}
else
--cacheIdx;
if (cacheIdx >= MAX_CACHE)
return HF_ERR_SLOTS_FULL;
cache[cacheIdx] = ctx.view().read(keylet);
return cache[cacheIdx] ? cacheIdx + 1 : HF_ERR_LEDGER_OBJ_NOT_FOUND;
}
Bytes
getAnyFieldData(STBase const& obj)
{
// auto const& fname = obj.getFName();
if (STI_ACCOUNT == obj.getSType())
{
auto const& super(static_cast<STAccount const&>(obj));
auto const& data = super.value();
return {data.begin(), data.end()};
}
else if (STI_AMOUNT == obj.getSType())
{
auto const& super(static_cast<STAmount const&>(obj));
int64_t const data = super.xrp().drops();
auto const* b = reinterpret_cast<uint8_t const*>(&data);
auto const* e = reinterpret_cast<uint8_t const*>(&data + 1);
return {b, e};
}
else if (STI_VL == obj.getSType())
{
auto const& super(static_cast<STBlob const&>(obj));
auto const& data = super.value();
return {data.begin(), data.end()};
}
else if (STI_UINT256 == obj.getSType())
{
auto const& super(static_cast<STBitString<256> const&>(obj));
auto const& data = super.value();
return {data.begin(), data.end()};
}
else if (STI_UINT32 == obj.getSType())
{
auto const& super(static_cast<STInteger<std::uint32_t> const&>(obj));
std::uint32_t const data = super.value();
auto const* b = reinterpret_cast<uint8_t const*>(&data);
auto const* e = reinterpret_cast<uint8_t const*>(&data + 1);
return {b, e};
}
Serializer msg;
obj.add(msg);
return msg.getData();
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getTxField(SField const& fname)
{
auto const* field = ctx.tx.peekAtPField(fname);
if (!field)
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
return getAnyFieldData(*field);
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getCurrentLedgerObjField(SField const& fname)
{
auto const sle = ctx.view().read(leKey);
if (!sle)
return Unexpected(HF_ERR_LEDGER_OBJ_NOT_FOUND);
auto const* field = sle->peekAtPField(fname);
if (!field)
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
return getAnyFieldData(*field);
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getLedgerObjField(int32_t cacheIdx, SField const& fname)
{
--cacheIdx;
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
return Unexpected(HF_ERR_SLOT_OUT_RANGE);
if (!cache[cacheIdx])
return Unexpected(HF_ERR_INVALID_SLOT);
auto const* field = cache[cacheIdx]->peekAtPField(fname);
if (!field)
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
return getAnyFieldData(*field);
}
static Expected<STBase const*, int32_t>
locateField(STObject const* obj, Slice const& loc)
{
if (loc.size() % 4)
return Unexpected(HF_ERR_LOCATOR_MALFORMED);
int32_t const* l = reinterpret_cast<int32_t const*>(loc.data());
int32_t const sz = loc.size() / 4;
STBase const* field = nullptr;
auto const& m = SField::getKnownCodeToField();
for (int i = 0; i < sz; ++i)
{
int32_t const c = l[i];
if (!field)
{
auto const it = m.find(c);
if (it == m.end())
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
auto const& fname(*it->second);
field = obj->peekAtPField(fname);
if (!field)
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
}
else if (STI_ARRAY == field->getSType())
{
auto const* arr = static_cast<STArray const*>(field);
if (c >= arr->size())
return Unexpected(HF_ERR_LOCATOR_MALFORMED);
field = &(arr->operator[](c));
}
else if (STI_OBJECT == field->getSType())
{
auto const* o = static_cast<STObject const*>(field);
auto const it = m.find(c);
if (it == m.end())
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
auto const& fname(*it->second);
field = o->peekAtPField(fname);
if (!field)
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
}
}
if (!field || (STI_OBJECT == field->getSType()) ||
(STI_ARRAY == field->getSType()))
return Unexpected(HF_ERR_LOCATOR_MALFORMED);
return field;
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getTxNestedField(Slice const& locator)
{
auto const r = locateField(&ctx.tx, locator);
if (!r.has_value())
return Unexpected(r.error());
auto const* field = r.value();
return getAnyFieldData(*field);
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getCurrentLedgerObjNestedField(Slice const& locator)
{
auto const sle = ctx.view().read(leKey);
if (!sle)
return Unexpected(HF_ERR_LEDGER_OBJ_NOT_FOUND);
auto const r = locateField(sle.get(), locator);
if (!r.has_value())
return Unexpected(r.error());
auto const* field = r.value();
return getAnyFieldData(*field);
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getLedgerObjNestedField(
int32_t cacheIdx,
Slice const& locator)
{
--cacheIdx;
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
return Unexpected(HF_ERR_SLOT_OUT_RANGE);
if (!cache[cacheIdx])
return Unexpected(HF_ERR_INVALID_SLOT);
auto const r = locateField(cache[cacheIdx].get(), locator);
if (!r.has_value())
return Unexpected(r.error());
auto const* field = r.value();
return getAnyFieldData(*field);
}
int32_t
WasmHostFunctionsImpl::getTxArrayLen(SField const& fname)
{
if (fname.fieldType != STI_ARRAY)
return HF_ERR_NO_ARRAY;
auto const* field = ctx.tx.peekAtPField(fname);
if (!field)
return HF_ERR_FIELD_NOT_FOUND;
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::getCurrentLedgerObjArrayLen(SField const& fname)
{
if (fname.fieldType != STI_ARRAY)
return HF_ERR_NO_ARRAY;
auto const sle = ctx.view().read(leKey);
if (!sle)
return HF_ERR_LEDGER_OBJ_NOT_FOUND;
auto const* field = sle->peekAtPField(fname);
if (!field)
return HF_ERR_FIELD_NOT_FOUND;
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::getLedgerObjArrayLen(
int32_t cacheIdx,
SField const& fname)
{
if (fname.fieldType != STI_ARRAY)
return HF_ERR_NO_ARRAY;
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
return HF_ERR_SLOT_OUT_RANGE;
if (!cache[cacheIdx])
return HF_ERR_INVALID_SLOT;
auto const* field = cache[cacheIdx]->peekAtPField(fname);
if (!field)
return HF_ERR_FIELD_NOT_FOUND;
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::getTxNestedArrayLen(Slice const& locator)
{
auto const r = locateField(&ctx.tx, locator);
if (!r.has_value())
return r.error();
auto const* field = r.value();
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::getCurrentLedgerObjNestedArrayLen(Slice const& locator)
{
auto const sle = ctx.view().read(leKey);
if (!sle)
return HF_ERR_LEDGER_OBJ_NOT_FOUND;
auto const r = locateField(sle.get(), locator);
if (!r.has_value())
return r.error();
auto const* field = r.value();
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::getLedgerObjNestedArrayLen(
int32_t cacheIdx,
Slice const& locator)
{
--cacheIdx;
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
return HF_ERR_SLOT_OUT_RANGE;
if (!cache[cacheIdx])
return HF_ERR_INVALID_SLOT;
auto const r = locateField(cache[cacheIdx].get(), locator);
if (!r.has_value())
return r.error();
auto const* field = r.value();
if (field->getSType() != STI_ARRAY)
return HF_ERR_NO_ARRAY;
int32_t const sz = static_cast<STArray const*>(field)->size();
return sz;
}
int32_t
WasmHostFunctionsImpl::updateData(Bytes const& data)
{
auto sle = ctx.view().peek(leKey);
if (!sle)
return HF_ERR_LEDGER_OBJ_NOT_FOUND;
sle->setFieldVL(sfData, data);
ctx.view().update(sle);
return 0;
}
Hash
WasmHostFunctionsImpl::computeSha512HalfHash(Bytes const& data)
{
auto const hash = sha512Half(data);
return hash;
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::accountKeylet(AccountID const& account)
{
if (!account)
return Unexpected(HF_ERR_INVALID_ACCOUNT);
auto const keylet = keylet::account(account);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::credentialKeylet(
AccountID const& subject,
AccountID const& issuer,
Bytes const& credentialType)
{
if (!subject || !issuer || credentialType.empty() ||
credentialType.size() > maxCredentialTypeLength)
return Unexpected(HF_ERR_INVALID_PARAMS);
auto const keylet =
keylet::credential(subject, issuer, makeSlice(credentialType));
return Bytes{keylet.key.begin(), keylet.key.end()};
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::escrowKeylet(AccountID const& account, std::uint32_t seq)
{
if (!account)
return Unexpected(HF_ERR_INVALID_ACCOUNT);
auto const keylet = keylet::escrow(account, seq);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::oracleKeylet(
AccountID const& account,
std::uint32_t documentId)
{
if (!account || account.isZero())
return Unexpected(HF_ERR_INVALID_ACCOUNT);
auto const keylet = keylet::oracle(account, documentId);
return Bytes{keylet.key.begin(), keylet.key.end()};
}
Expected<Bytes, int32_t>
WasmHostFunctionsImpl::getNFT(AccountID const& account, uint256 const& nftId)
{
if (!account || !nftId)
return Unexpected(HF_ERR_INVALID_PARAMS);
auto obj = nft::findToken(ctx.view(), account, nftId);
if (!obj)
return Unexpected(HF_ERR_LEDGER_OBJ_NOT_FOUND);
Slice const s = obj->at(sfURI);
return Bytes(s.begin(), s.end());
}
int32_t
WasmHostFunctionsImpl::trace(
std::string const& msg,
Bytes const& data,
bool asHex)
{
#ifdef DEBUG_OUTPUT
auto j = ctx.journal.error();
#else
auto j = ctx.journal.trace();
#endif
j << msg;
if (!asHex)
j << std::string_view(
reinterpret_cast<char const*>(data.data()), data.size());
else
{
auto const hex =
boost::algorithm::hex(std::string(data.begin(), data.end()));
j << hex;
}
return msg.size() + data.size() * (asHex ? 2 : 1);
}
int32_t
WasmHostFunctionsImpl::traceNum(std::string const& msg, int64_t data)
{
#ifdef DEBUG_OUTPUT
auto j = ctx.journal.error();
#else
auto j = ctx.journal.trace();
#endif
j << msg << data;
return msg.size() + sizeof(data);
}
} // namespace ripple
+139
View File
@@ -0,0 +1,139 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 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.
*/
//==============================================================================
#pragma once
#include <xrpld/app/misc/WasmHostFunc.h>
#include <xrpld/app/tx/detail/ApplyContext.h>
namespace ripple {
class WasmHostFunctionsImpl : public HostFunctions
{
ApplyContext& ctx;
Keylet leKey;
static int constexpr MAX_CACHE = 256;
std::array<std::shared_ptr<SLE const>, MAX_CACHE> cache;
void const* rt_ = nullptr;
public:
WasmHostFunctionsImpl(ApplyContext& ctx, Keylet const& leKey)
: ctx(ctx), leKey(leKey)
{
}
virtual void
setRT(void const* rt) override
{
rt_ = rt;
}
virtual void const*
getRT() const override
{
return rt_;
}
beast::Journal
getJournal() override
{
return ctx.journal;
}
int32_t
getLedgerSqn() override;
int32_t
getParentLedgerTime() override;
Hash
getParentLedgerHash() override;
int32_t
cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx) override;
Expected<Bytes, int32_t>
getTxField(SField const& fname) override;
Expected<Bytes, int32_t>
getCurrentLedgerObjField(SField const& fname) override;
Expected<Bytes, int32_t>
getLedgerObjField(int32_t cacheIdx, SField const& fname) override;
Expected<Bytes, int32_t>
getTxNestedField(Slice const& locator) override;
Expected<Bytes, int32_t>
getCurrentLedgerObjNestedField(Slice const& locator) override;
Expected<Bytes, int32_t>
getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) override;
int32_t
getTxArrayLen(SField const& fname) override;
int32_t
getCurrentLedgerObjArrayLen(SField const& fname) override;
int32_t
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) override;
int32_t
getTxNestedArrayLen(Slice const& locator) override;
int32_t
getCurrentLedgerObjNestedArrayLen(Slice const& locator) override;
int32_t
getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) override;
int32_t
updateData(Bytes const& data) override;
Hash
computeSha512HalfHash(Bytes const& data) override;
Expected<Bytes, int32_t>
accountKeylet(AccountID const& account) override;
Expected<Bytes, int32_t>
credentialKeylet(
AccountID const& subject,
AccountID const& issuer,
Bytes const& credentialType) override;
Expected<Bytes, int32_t>
escrowKeylet(AccountID const& account, std::uint32_t seq) override;
Expected<Bytes, int32_t>
oracleKeylet(AccountID const& account, std::uint32_t documentId) override;
Expected<Bytes, int32_t>
getNFT(AccountID const& account, uint256 const& nftId) override;
int32_t
trace(std::string const& msg, Bytes const& data, bool asHex) override;
int32_t
traceNum(std::string const& msg, int64_t data) override;
};
} // namespace ripple
+749
View File
@@ -0,0 +1,749 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/misc/WamrVM.h>
#include <xrpld/app/misc/WasmHostFunc.h>
#include <xrpld/app/misc/WasmHostFuncWrapper.h>
#include <xrpld/app/tx/detail/NFTokenUtils.h>
#include <xrpl/protocol/digest.h>
namespace ripple {
static int32_t
setData(
InstanceWrapper const* rt,
int32_t dst,
int32_t dsz,
uint8_t const* src,
int32_t ssz)
{
auto mem = rt ? rt->getMem() : wmem();
if (!mem.s)
return HF_ERR_NO_MEM_EXPORTED;
if (dst + dsz > mem.s)
return HF_ERR_OUT_OF_BOUNDS;
if (ssz > dsz)
return HF_ERR_BUFFER_TOO_SMALL;
memcpy(mem.p + dst, src, ssz);
return ssz;
}
static Expected<Bytes, int32_t>
getData(InstanceWrapper const* rt, int32_t src, int32_t ssz)
{
auto mem = rt ? rt->getMem() : wmem();
if (!mem.s)
return Unexpected(HF_ERR_NO_MEM_EXPORTED);
if (src + ssz > mem.s)
return Unexpected(HF_ERR_OUT_OF_BOUNDS);
Bytes data(mem.p + src, mem.p + src + ssz);
return data;
}
static Expected<AccountID, int32_t>
getDataAccount(InstanceWrapper const* rt, int32_t ptr, int32_t sz)
{
auto const r = getData(rt, ptr, sz);
if (r->size() < AccountID::bytes)
return Unexpected(HF_ERR_INVALID_PARAMS);
return AccountID::fromVoid(r->data());
}
static Expected<std::string, int32_t>
getDataString(InstanceWrapper const* rt, int32_t src, int32_t ssz)
{
auto mem = rt ? rt->getMem() : wmem();
if (!mem.s)
return Unexpected(HF_ERR_NO_MEM_EXPORTED);
if (src + ssz > mem.s)
return Unexpected(HF_ERR_OUT_OF_BOUNDS);
std::string data(mem.p + src, mem.p + src + ssz);
return data;
}
#define RET(x) \
results->data[0] = WASM_I32_VAL(x); \
results->num_elems = 1; \
return nullptr;
wasm_trap_t*
getLedgerSqnOld_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
int32_t const sqn = hf->getLedgerSqn();
RET(sqn);
}
wasm_trap_t*
getLedgerSqn_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
int32_t const sqn = hf->getLedgerSqn();
RET(setData(
rt,
params->data[0].of.i32,
params->data[1].of.i32,
reinterpret_cast<uint8_t const*>(&sqn),
static_cast<int32_t>(sizeof(sqn))));
}
wasm_trap_t*
getParentLedgerTime_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
int32_t const ltime = hf->getParentLedgerTime();
RET(setData(
rt,
params->data[0].of.i32,
params->data[1].of.i32,
reinterpret_cast<uint8_t const*>(&ltime),
static_cast<int32_t>(sizeof(ltime))));
}
wasm_trap_t*
getParentLedgerHash_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
Hash const hash = hf->getParentLedgerHash();
RET(setData(
rt,
params->data[0].of.i32,
params->data[1].of.i32,
hash.data(),
static_cast<int32_t>(hash.size())));
}
wasm_trap_t*
cacheLedgerObj_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
if (r->size() < uint256::bytes)
{
RET(HF_ERR_INVALID_PARAMS);
}
uint256 const key(uint256::fromVoid(r->data()));
int32_t const idx =
hf->cacheLedgerObj(keylet::unchecked(key), params->data[2].of.i32);
RET(idx);
}
wasm_trap_t*
getTxField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[0].of.i32);
if (it == m.end())
{
RET(HF_ERR_FIELD_NOT_FOUND);
}
auto const& fname(*it->second);
auto fieldData = hf->getTxField(fname);
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[1].of.i32,
params->data[2].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getCurrentLedgerObjField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[0].of.i32);
if (it == m.end())
{
RET(HF_ERR_FIELD_NOT_FOUND);
}
auto const& fname(*it->second);
auto fieldData = hf->getCurrentLedgerObjField(fname);
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[1].of.i32,
params->data[2].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getLedgerObjField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[1].of.i32);
if (it == m.end())
{
RET(HF_ERR_FIELD_NOT_FOUND);
}
auto const& fname(*it->second);
auto fieldData = hf->getLedgerObjField(params->data[0].of.i32, fname);
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[2].of.i32,
params->data[3].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getTxNestedField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
auto fieldData = hf->getTxNestedField(makeSlice(r.value()));
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[2].of.i32,
params->data[3].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getCurrentLedgerObjNestedField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
auto fieldData = hf->getCurrentLedgerObjNestedField(makeSlice(r.value()));
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[2].of.i32,
params->data[3].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getLedgerObjNestedField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[1].of.i32, params->data[2].of.i32);
if (!r)
{
RET(r.error());
}
auto fieldData = hf->getLedgerObjNestedField(
params->data[0].of.i32, makeSlice(r.value()));
if (!fieldData)
{
RET(fieldData.error());
}
RET(setData(
rt,
params->data[3].of.i32,
params->data[4].of.i32,
fieldData->data(),
fieldData->size()));
}
wasm_trap_t*
getTxArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[0].of.i32);
if (it == m.end())
{
RET(HF_ERR_FIELD_NOT_FOUND);
}
auto const& fname(*it->second);
int32_t sz = hf->getTxArrayLen(fname);
RET(sz);
}
wasm_trap_t*
getCurrentLedgerObjArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[0].of.i32);
if (it == m.end())
{
RET(HF_ERR_FIELD_NOT_FOUND);
}
auto const& fname(*it->second);
int32_t sz = hf->getCurrentLedgerObjArrayLen(fname);
RET(sz);
}
wasm_trap_t*
getLedgerObjArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const& m = SField::getKnownCodeToField();
auto const it = m.find(params->data[1].of.i32);
if (it == m.end())
{
RET(HF_ERR_FIELD_NOT_FOUND);
}
auto const& fname(*it->second);
int32_t sz = hf->getLedgerObjArrayLen(params->data[0].of.i32, fname);
RET(sz);
}
wasm_trap_t*
getTxNestedArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
int32_t sz = hf->getTxNestedArrayLen(makeSlice(r.value()));
RET(sz);
}
wasm_trap_t*
getCurrentLedgerObjNestedArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
int32_t sz = hf->getCurrentLedgerObjNestedArrayLen(makeSlice(r.value()));
RET(sz);
}
wasm_trap_t*
getLedgerObjNestedArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[1].of.i32, params->data[2].of.i32);
if (!r)
{
RET(r.error());
}
int32_t sz = hf->getLedgerObjNestedArrayLen(
params->data[0].of.i32, makeSlice(r.value()));
RET(sz);
}
wasm_trap_t*
updateData_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
if (params->data[1].of.i32 > maxWasmDataLength)
{
RET(HF_ERR_DATA_FIELD_TOO_LARGE)
}
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
RET(hf->updateData(r.value()));
}
wasm_trap_t*
computeSha512HalfHash_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!r)
{
RET(r.error());
}
auto const hash = hf->computeSha512HalfHash(r.value());
RET(setData(
rt,
params->data[2].of.i32,
params->data[3].of.i32,
hash.data(),
hash.size()));
}
wasm_trap_t*
accountKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const acc =
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!acc)
{
RET(acc.error());
}
auto const k = hf->accountKeylet(acc.value());
if (!k)
{
RET(k.error());
}
RET(setData(
rt,
params->data[2].of.i32,
params->data[3].of.i32,
k->data(),
k->size()));
}
wasm_trap_t*
credentialKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const subject =
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!subject)
{
RET(subject.error());
}
auto const issuer =
getDataAccount(rt, params->data[2].of.i32, params->data[3].of.i32);
if (!issuer)
{
RET(issuer.error());
}
auto const credType =
getData(rt, params->data[4].of.i32, params->data[5].of.i32);
if (!credType)
{
RET(credType.error());
}
auto const k =
hf->credentialKeylet(subject.value(), issuer.value(), credType.value());
if (!k)
{
RET(k.error());
}
RET(setData(
rt,
params->data[6].of.i32,
params->data[7].of.i32,
k->data(),
k->size()));
}
wasm_trap_t*
escrowKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const acc =
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!acc)
{
RET(acc.error());
}
auto const k = hf->escrowKeylet(acc.value(), params->data[2].of.i32);
if (!k)
{
RET(k.error());
}
RET(setData(
rt,
params->data[3].of.i32,
params->data[4].of.i32,
k->data(),
k->size()));
}
wasm_trap_t*
oracleKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const acc =
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!acc)
{
RET(acc.error());
}
auto const k = hf->oracleKeylet(acc.value(), params->data[2].of.i32);
if (!k)
{
RET(k.error());
}
RET(setData(
rt,
params->data[3].of.i32,
params->data[4].of.i32,
k->data(),
k->size()));
}
wasm_trap_t*
getNFT_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const acc =
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!acc)
{
RET(acc.error());
}
auto const nftRaw =
getData(rt, params->data[2].of.i32, params->data[3].of.i32);
if (!nftRaw)
{
RET(nftRaw.error());
}
if (nftRaw->size() < uint256::bytes * 2)
{
RET(HF_ERR_INVALID_PARAMS);
}
uint256 const ntfId(uint256::fromVoid(nftRaw->data()));
auto const nftURI = hf->getNFT(acc.value(), ntfId);
if (!nftURI)
{
RET(nftURI.error());
}
RET(setData(
rt,
params->data[4].of.i32,
params->data[5].of.i32,
nftURI->data(),
nftURI->size()));
}
wasm_trap_t*
trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const msg =
getDataString(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!msg)
{
RET(msg.error());
}
auto const data =
getData(rt, params->data[2].of.i32, params->data[3].of.i32);
if (!data)
{
RET(data.error());
}
auto const e = hf->trace(msg.value(), data.value(), params->data[4].of.i32);
RET(e);
}
wasm_trap_t*
traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
{
auto* hf = reinterpret_cast<HostFunctions*>(env);
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
auto const msg =
getDataString(rt, params->data[0].of.i32, params->data[1].of.i32);
if (!msg)
{
RET(msg.error());
}
auto const e = hf->traceNum(msg.value(), params->data[2].of.i64);
RET(e);
}
} // namespace ripple
+222
View File
@@ -0,0 +1,222 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 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.
*/
//==============================================================================
#pragma once
#include <xrpld/app/misc/WamrVM.h>
namespace ripple {
using getLedgerSqnOld_proto = int32_t();
wasm_trap_t*
getLedgerSqnOld_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getLedgerSqn_proto = int32_t(uint8_t*, int32_t);
wasm_trap_t*
getLedgerSqn_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getParentLedgerTime_proto = int32_t(uint8_t*, int32_t);
wasm_trap_t*
getParentLedgerTime_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getParentLedgerHash_proto = int32_t(uint8_t*, int32_t);
wasm_trap_t*
getParentLedgerHash_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using cacheLedgerObj_proto = int32_t(uint8_t const*, int32_t, int32_t);
wasm_trap_t*
cacheLedgerObj_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getTxField_proto = int32_t(int32_t, uint8_t*, int32_t);
wasm_trap_t*
getTxField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getCurrentLedgerObjField_proto = int32_t(int32_t, uint8_t*, int32_t);
wasm_trap_t*
getCurrentLedgerObjField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getLedgerObjField_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t);
wasm_trap_t*
getLedgerObjField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getTxNestedField_proto =
int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
getTxNestedField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getCurrentLedgerObjNestedField_proto =
int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
getCurrentLedgerObjNestedField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getLedgerObjNestedField_proto =
int32_t(int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
getLedgerObjNestedField_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getTxArrayLen_proto = int32_t(int32_t);
wasm_trap_t*
getTxArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getCurrentLedgerObjArrayLen_proto = int32_t(int32_t);
wasm_trap_t*
getCurrentLedgerObjArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getLedgerObjArrayLen_proto = int32_t(int32_t, int32_t);
wasm_trap_t*
getLedgerObjArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getTxNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
wasm_trap_t*
getTxNestedArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getCurrentLedgerObjNestedArrayLen_proto =
int32_t(uint8_t const*, int32_t);
wasm_trap_t*
getCurrentLedgerObjNestedArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getLedgerObjNestedArrayLen_proto =
int32_t(int32_t, uint8_t const*, int32_t);
wasm_trap_t*
getLedgerObjNestedArrayLen_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using updateData_proto = int32_t(uint8_t const*, int32_t);
wasm_trap_t*
updateData_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using computeSha512HalfHash_proto =
int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
computeSha512HalfHash_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using accountKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
wasm_trap_t*
accountKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using credentialKeylet_proto = int32_t(
uint8_t const*,
int32_t,
uint8_t const*,
int32_t,
uint8_t const*,
int32_t,
uint8_t*,
int32_t);
wasm_trap_t*
credentialKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using escrowKeylet_proto =
int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t);
wasm_trap_t*
escrowKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using oracleKeylet_proto =
int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t);
wasm_trap_t*
oracleKeylet_wrap(
void* env,
wasm_val_vec_t const* params,
wasm_val_vec_t* results);
using getNFT_proto = int32_t(
uint8_t const*,
int32_t,
uint8_t const*,
int32_t,
uint8_t*,
int32_t);
wasm_trap_t*
getNFT_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using trace_proto =
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t);
wasm_trap_t*
trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
using traceNum_proto = int32_t(uint8_t const*, int32_t, int64_t);
wasm_trap_t*
traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
} // namespace ripple
+269
View File
@@ -0,0 +1,269 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 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.
*/
//==============================================================================
#pragma once
#include <xrpl/basics/base_uint.h>
#include <boost/function_types/function_arity.hpp>
#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/mpl/vector.hpp>
#include <optional>
#include <string>
#include <vector>
namespace bft = boost::function_types;
namespace ripple {
using Bytes = std::vector<std::uint8_t>;
using Hash = ripple::uint256;
struct wmem
{
std::uint8_t* p = nullptr;
std::size_t s = 0;
};
template <typename T>
struct WasmResult
{
T result;
int64_t cost;
};
typedef WasmResult<bool> EscrowResult;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
enum WasmTypes { WT_I32, WT_I64, WT_F32, WT_F64, WT_U8V };
struct WasmImportFunc
{
std::string name;
std::optional<WasmTypes> result;
std::vector<WasmTypes> params;
void* udata = nullptr;
// wasm_func_callback_with_env_t
void* wrap = nullptr;
};
#define WASM_IMPORT_FUNC(v, f, ...) \
WasmImpFunc<f##_proto>( \
v, #f, reinterpret_cast<void*>(&f##_wrap), ##__VA_ARGS__)
#define WASM_IMPORT_FUNC2(v, f, n, ...) \
WasmImpFunc<f##_proto>( \
v, n, reinterpret_cast<void*>(&f##_wrap), ##__VA_ARGS__)
template <int N, int C, typename mpl>
void
WasmImpArgs(WasmImportFunc& e)
{
if constexpr (N < C)
{
using at = typename boost::mpl::at_c<mpl, N>::type;
if constexpr (std::is_pointer_v<at>)
e.params.push_back(WT_I32);
else if constexpr (std::is_same_v<at, std::int32_t>)
e.params.push_back(WT_I32);
else if constexpr (std::is_same_v<at, std::int64_t>)
e.params.push_back(WT_I64);
else if constexpr (std::is_same_v<at, float>)
e.params.push_back(WT_F32);
else if constexpr (std::is_same_v<at, double>)
e.params.push_back(WT_F64);
else
static_assert(std::is_pointer_v<at>, "Unsupported argument type");
return WasmImpArgs<N + 1, C, mpl>(e);
}
return;
}
template <typename rt>
void
WasmImpRet(WasmImportFunc& e)
{
if constexpr (std::is_pointer_v<rt>)
e.result = WT_I32;
else if constexpr (std::is_same_v<rt, std::int32_t>)
e.result = WT_I32;
else if constexpr (std::is_same_v<rt, std::int64_t>)
e.result = WT_I64;
else if constexpr (std::is_same_v<rt, float>)
e.result = WT_F32;
else if constexpr (std::is_same_v<rt, double>)
e.result = WT_F64;
else if constexpr (std::is_void_v<rt>)
e.result.reset();
#if (defined(__GNUC__) && (__GNUC__ >= 14)) || \
((defined(__clang_major__)) && (__clang_major__ >= 18))
else
static_assert(false, "Unsupported return type");
#endif
}
template <typename F>
void
WasmImpFuncHelper(WasmImportFunc& e)
{
using rt = typename bft::result_type<F>::type;
using pt = typename bft::parameter_types<F>::type;
// typename boost::mpl::at_c<mpl, N>::type
WasmImpRet<rt>(e);
WasmImpArgs<0, bft::function_arity<F>::value, pt>(e);
// WasmImpWrap(e, std::forward<F>(f));
}
template <typename F>
void
WasmImpFunc(
std::vector<WasmImportFunc>& v,
std::string_view imp_name,
void* f_wrap,
void* data = nullptr)
{
WasmImportFunc e;
e.name = imp_name;
e.udata = data;
e.wrap = f_wrap;
WasmImpFuncHelper<F>(e);
v.push_back(std::move(e));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct WasmParamVec
{
std::uint8_t const* d = nullptr;
std::int32_t sz = 0;
};
struct WasmParam
{
WasmTypes type = WT_I32;
union
{
std::int32_t i32;
std::int64_t i64 = 0;
float f32;
double f64;
WasmParamVec u8v;
} of;
};
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::int32_t p, Types&&... args)
{
v.push_back({.type = WT_I32, .of = {.i32 = p}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::int64_t p, Types&&... args)
{
v.push_back({.type = WT_I64, .of = {.i64 = p}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, float p, Types&&... args)
{
v.push_back({.type = WT_F32, .of = {.f32 = p}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, double p, Types&&... args)
{
v.push_back({.type = WT_F64, .of = {.f64 = p}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(
std::vector<WasmParam>& v,
std::uint8_t const* dt,
std::int32_t sz,
Types&&... args)
{
v.push_back({.type = WT_U8V, .of = {.u8v = {.d = dt, .sz = sz}}});
wasmParamsHlp(v, std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, Bytes const& p, Types&&... args)
{
wasmParamsHlp(
v,
p.data(),
static_cast<std::int32_t>(p.size()),
std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(
std::vector<WasmParam>& v,
std::string_view const& p,
Types&&... args)
{
wasmParamsHlp(
v,
reinterpret_cast<std::uint8_t const*>(p.data()),
static_cast<std::int32_t>(p.size()),
std::forward<Types>(args)...);
}
template <class... Types>
inline void
wasmParamsHlp(std::vector<WasmParam>& v, std::string const& p, Types&&... args)
{
wasmParamsHlp(
v,
reinterpret_cast<std::uint8_t const*>(p.c_str()),
static_cast<std::int32_t>(p.size()),
std::forward<Types>(args)...);
}
inline void
wasmParamsHlp(std::vector<WasmParam>& v)
{
return;
}
template <class... Types>
inline std::vector<WasmParam>
wasmParams(Types&&... args)
{
std::vector<WasmParam> v;
v.reserve(sizeof...(args));
wasmParamsHlp(v, std::forward<Types>(args)...);
return v;
}
} // namespace ripple
+177
View File
@@ -0,0 +1,177 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 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/misc/WamrVM.h>
#include <xrpld/app/misc/WasmHostFunc.h>
#include <xrpld/app/misc/WasmHostFuncWrapper.h>
#include <xrpl/basics/Log.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <memory>
namespace ripple {
Expected<EscrowResult, TER>
runEscrowWasm(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
HostFunctions* hfs,
int64_t gasLimit,
beast::Journal j)
{
// create VM and set cost limit
auto& vm = WasmEngine::instance();
vm.initMaxPages(MAX_PAGES);
std::vector<WasmImportFunc> imports;
if (hfs)
{
// TODO: remove after escrow_test wasm module will be updated
WASM_IMPORT_FUNC2(imports, getLedgerSqnOld, "getLedgerSqn", hfs);
WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn", hfs);
WASM_IMPORT_FUNC2(
imports, getParentLedgerTime, "get_parent_ledger_time", hfs);
WASM_IMPORT_FUNC2(
imports, getParentLedgerHash, "get_parent_ledger_hash", hfs);
WASM_IMPORT_FUNC2(imports, cacheLedgerObj, "cache_ledger_obj", hfs);
WASM_IMPORT_FUNC2(imports, getTxField, "get_tx_field", hfs);
WASM_IMPORT_FUNC2(
imports,
getCurrentLedgerObjField,
"get_current_ledger_obj_field",
hfs);
WASM_IMPORT_FUNC2(
imports, getLedgerObjField, "get_ledger_obj_field", hfs);
WASM_IMPORT_FUNC2(
imports, getTxNestedField, "get_tx_nested_field", hfs);
WASM_IMPORT_FUNC2(
imports,
getCurrentLedgerObjNestedField,
"get_current_ledger_obj_nested_field",
hfs);
WASM_IMPORT_FUNC2(
imports,
getLedgerObjNestedField,
"get_ledger_obj_nested_field",
hfs);
WASM_IMPORT_FUNC2(imports, getTxArrayLen, "get_tx_array_len", hfs);
WASM_IMPORT_FUNC2(
imports,
getCurrentLedgerObjArrayLen,
"get_current_ledger_obj_array_len",
hfs);
WASM_IMPORT_FUNC2(
imports, getLedgerObjArrayLen, "get_ledger_obj_array_len", hfs);
WASM_IMPORT_FUNC2(
imports, getTxNestedArrayLen, "get_tx_nested_array_len", hfs);
WASM_IMPORT_FUNC2(
imports,
getCurrentLedgerObjNestedArrayLen,
"get_current_ledger_obj_nested_array_len",
hfs);
WASM_IMPORT_FUNC2(
imports,
getLedgerObjNestedArrayLen,
"get_ledger_obj_nested_array_len",
hfs);
WASM_IMPORT_FUNC2(imports, updateData, "update_data", hfs);
WASM_IMPORT_FUNC2(
imports, computeSha512HalfHash, "compute_sha512_half", hfs);
WASM_IMPORT_FUNC2(imports, accountKeylet, "account_keylet", hfs);
WASM_IMPORT_FUNC2(imports, credentialKeylet, "credential_keylet", hfs);
WASM_IMPORT_FUNC2(imports, escrowKeylet, "escrow_keylet", hfs);
WASM_IMPORT_FUNC2(imports, oracleKeylet, "oracle_keylet", hfs);
WASM_IMPORT_FUNC2(imports, getNFT, "get_NFT", hfs);
WASM_IMPORT_FUNC(imports, trace, hfs);
WASM_IMPORT_FUNC2(imports, traceNum, "trace_num", hfs);
}
auto ret = vm.run(
wasmCode,
funcName,
params,
imports,
hfs,
gasLimit,
hfs ? hfs->getJournal() : j);
// std::cout << "runEscrowWasm, mod size: " << wasmCode.size()
// << ", gasLimit: " << gasLimit << ", funcName: " << funcName;
if (!ret.has_value())
{
// std::cout << ", error: " << ret.error() << std::endl;
return Unexpected<TER>(ret.error());
}
// std::cout << ", ret: " << ret->result << ", gas spent: " << ret->cost
// << std::endl;
return EscrowResult{ret->result > 0, ret->cost};
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
WasmEngine::WasmEngine() : impl(std::make_unique<WamrEngine>())
{
}
WasmEngine&
WasmEngine::instance()
{
static WasmEngine e;
return e;
}
Expected<WasmResult<int32_t>, TER>
WasmEngine::run(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params,
std::vector<WasmImportFunc> const& imports,
HostFunctions* hfs,
int64_t gasLimit,
beast::Journal j)
{
return impl->run(wasmCode, funcName, params, imports, hfs, gasLimit, j);
}
std::int32_t
WasmEngine::initMaxPages(std::int32_t def)
{
return impl->initMaxPages(def);
}
void*
WasmEngine::newTrap(std::string_view msg)
{
return impl->newTrap(msg);
}
beast::Journal
WasmEngine::getJournal() const
{
return impl->getJournal();
}
} // namespace ripple
+90
View File
@@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 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.
*/
//==============================================================================
#pragma once
#include <xrpld/app/misc/WasmHostFunc.h>
#include <string_view>
namespace ripple {
static std::string_view const W_ENV = "env";
static std::string_view const W_HOST_LIB = "host_lib";
static std::string_view const W_MEM = "memory";
static std::string_view const W_STORE = "store";
static std::string_view const W_LOAD = "load";
static std::string_view const W_SIZE = "size";
static std::string_view const W_ALLOC = "allocate";
static std::string_view const W_DEALLOC = "deallocate";
static std::string_view const W_PROC_EXIT = "proc_exit";
uint32_t const MAX_PAGES = 128; // 8MB = 64KB*128
class WamrEngine;
class WasmEngine
{
std::unique_ptr<WamrEngine> const impl;
WasmEngine();
WasmEngine(WasmEngine const&) = delete;
WasmEngine(WasmEngine&&) = delete;
WasmEngine&
operator=(WasmEngine const&) = delete;
WasmEngine&
operator=(WasmEngine&&) = delete;
public:
~WasmEngine() = default;
static WasmEngine&
instance();
Expected<WasmResult<int32_t>, TER>
run(Bytes const& wasmCode,
std::string_view funcName = {},
std::vector<WasmParam> const& params = {},
std::vector<WasmImportFunc> const& imports = {},
HostFunctions* hfs = nullptr,
int64_t gasLimit = -1,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
std::int32_t
initMaxPages(std::int32_t def);
// Host functions helper functionality
void*
newTrap(std::string_view msg = {});
beast::Journal
getJournal() const;
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expected<EscrowResult, TER>
runEscrowWasm(
Bytes const& wasmCode,
std::string_view funcName,
std::vector<WasmParam> const& params = {},
HostFunctions* hfs = nullptr,
int64_t gasLimit = -1,
beast::Journal j = beast::Journal(beast::Journal::getNullSink()));
} // namespace ripple
+2 -2
View File
@@ -150,8 +150,8 @@ ammLPHolds(
}
amount.setIssuer(ammAccount);
JLOG(j.trace()) << "ammLPHolds:"
<< " lpAccount=" << to_string(lpAccount)
JLOG(j.trace()) << "ammLPHolds:" << " lpAccount="
<< to_string(lpAccount)
<< " amount=" << amount.getFullText();
}
+5 -5
View File
@@ -1504,11 +1504,11 @@ TxQ::accept(Application& app, OpenView& view)
}
else
{
JLOG(j_.debug()) << "Queued transaction " << candidateIter->txID
<< " failed with " << transToken(txnResult)
<< ". Leave in queue."
<< " Applied: " << didApply
<< ". Flags: " << candidateIter->flags;
JLOG(j_.debug())
<< "Queued transaction " << candidateIter->txID
<< " failed with " << transToken(txnResult)
<< ". Leave in queue." << " Applied: " << didApply
<< ". Flags: " << candidateIter->flags;
if (account.retryPenalty && candidateIter->retriesRemaining > 2)
candidateIter->retriesRemaining = 1;
else
+1 -2
View File
@@ -236,8 +236,7 @@ Pathfinder::findPaths(
mSource = STPathElement(account, mSrcCurrency, issuer);
auto issuerString =
mSrcIssuer ? to_string(*mSrcIssuer) : std::string("none");
JLOG(j_.trace()) << "findPaths>"
<< " mSrcAccount=" << mSrcAccount
JLOG(j_.trace()) << "findPaths>" << " mSrcAccount=" << mSrcAccount
<< " mDstAccount=" << mDstAccount
<< " mDstAmount=" << mDstAmount.getFullText()
<< " mSrcCurrency=" << mSrcCurrency
+1 -2
View File
@@ -190,8 +190,7 @@ protected:
logStringImpl(char const* name) const
{
std::ostringstream ostr;
ostr << name << ": "
<< "\ninIss: " << book_.in.account
ostr << name << ": " << "\ninIss: " << book_.in.account
<< "\noutIss: " << book_.out.account
<< "\ninCur: " << book_.in.currency
<< "\noutCur: " << book_.out.currency;
+1 -2
View File
@@ -205,8 +205,7 @@ protected:
logStringImpl(char const* name) const
{
std::ostringstream ostr;
ostr << name << ": "
<< "\nSrc: " << src_ << "\nDst: " << dst_;
ostr << name << ": " << "\nSrc: " << src_ << "\nDst: " << dst_;
return ostr.str();
}
@@ -132,8 +132,7 @@ protected:
logStringImpl(char const* name) const
{
std::ostringstream ostr;
ostr << name << ": "
<< "\nAcc: " << acc_;
ostr << name << ": " << "\nAcc: " << acc_;
return ostr.str();
}
+2 -2
View File
@@ -238,8 +238,8 @@ rangeCheckedCast(C c)
/* This should never happen */
UNREACHABLE("ripple::rangeCheckedCast : domain error");
JLOG(debugLog().error())
<< "rangeCheckedCast domain error:"
<< " value = " << c << " min = " << std::numeric_limits<T>::lowest()
<< "rangeCheckedCast domain error:" << " value = " << c
<< " min = " << std::numeric_limits<T>::lowest()
<< " max: " << std::numeric_limits<T>::max();
}
+7
View File
@@ -106,6 +106,13 @@ public:
view_->deliver(amount);
}
/** Sets the gas used in the metadata */
void
setGasUsed(std::uint32_t const& gasUsed)
{
view_->setGasUsed(gasUsed);
}
/** Discard changes and start fresh. */
void
discard();
+45 -42
View File
@@ -192,8 +192,8 @@ Batch::preflight(PreflightContext const& ctx)
if (flags & tfBatchMask)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
<< "invalid flags.";
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]:" << "invalid flags.";
return temINVALID_FLAG;
}
@@ -201,23 +201,24 @@ Batch::preflight(PreflightContext const& ctx)
flags &
(tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent)) != 1)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
<< "too many flags.";
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]:" << "too many flags.";
return temINVALID_FLAG;
}
auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
if (rawTxns.size() <= 1)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
<< "txns array must have at least 2 entries.";
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]:" << "txns array must have at least 2 entries.";
return temARRAY_EMPTY;
}
if (rawTxns.size() > maxBatchTxCount)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
<< "txns array exceeds 8 entries.";
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
<< "]:" << "txns array exceeds 8 entries.";
return temARRAY_TOO_LARGE;
}
@@ -232,50 +233,53 @@ Batch::preflight(PreflightContext const& ctx)
auto const hash = stx.getTransactionID();
if (!uniqueHashes.emplace(hash).second)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "duplicate Txn found. "
<< "txID: " << hash;
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "duplicate Txn found. " << "txID: " << hash;
return temREDUNDANT;
}
if (stx.getFieldU16(sfTransactionType) == ttBATCH)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "batch cannot have an inner batch txn. "
<< "txID: " << hash;
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "batch cannot have an inner batch txn. "
<< "txID: " << hash;
return temINVALID;
}
if (!(stx.getFlags() & tfInnerBatchTxn))
{
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]: "
<< "inner txn must have the tfInnerBatchTxn flag. "
<< "BatchTrace[" << parentBatchId
<< "]: " << "inner txn must have the tfInnerBatchTxn flag. "
<< "txID: " << hash;
return temINVALID_FLAG;
}
if (stx.isFieldPresent(sfTxnSignature))
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "inner txn cannot include TxnSignature. "
<< "txID: " << hash;
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "inner txn cannot include TxnSignature. "
<< "txID: " << hash;
return temBAD_SIGNATURE;
}
if (stx.isFieldPresent(sfSigners))
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "inner txn cannot include Signers. "
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
<< "]: " << "inner txn cannot include Signers. "
<< "txID: " << hash;
return temBAD_SIGNER;
}
if (!stx.getSigningPubKey().empty())
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "inner txn SigningPubKey must be empty. "
<< "txID: " << hash;
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "inner txn SigningPubKey must be empty. "
<< "txID: " << hash;
return temBAD_REGKEY;
}
@@ -284,10 +288,10 @@ Batch::preflight(PreflightContext const& ctx)
ctx.app, ctx.rules, parentBatchId, stx, tapBATCH, ctx.j);
preflightResult.ter != tesSUCCESS)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "inner txn preflight failed: "
<< transHuman(preflightResult.ter) << " "
<< "txID: " << hash;
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "inner txn preflight failed: "
<< transHuman(preflightResult.ter) << " " << "txID: " << hash;
return temINVALID_INNER_BATCH;
}
@@ -295,8 +299,8 @@ Batch::preflight(PreflightContext const& ctx)
if (auto const fee = stx.getFieldAmount(sfFee);
!fee.native() || fee.xrp() != beast::zero)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "inner txn must have a fee of 0. "
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
<< "]: " << "inner txn must have a fee of 0. "
<< "txID: " << hash;
return temBAD_FEE;
}
@@ -333,8 +337,7 @@ Batch::preflight(PreflightContext const& ctx)
{
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]: "
<< "duplicate sequence found: "
<< "txID: " << hash;
<< "duplicate sequence found: " << "txID: " << hash;
return temREDUNDANT;
}
}
@@ -346,8 +349,7 @@ Batch::preflight(PreflightContext const& ctx)
{
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]: "
<< "duplicate ticket found: "
<< "txID: " << hash;
<< "duplicate ticket found: " << "txID: " << hash;
return temREDUNDANT;
}
}
@@ -373,8 +375,8 @@ Batch::preflight(PreflightContext const& ctx)
// Check that the batch signers array is not too large.
if (signers.size() > maxBatchTxCount)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "signers array exceeds 8 entries.";
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
<< "]: " << "signers array exceeds 8 entries.";
return temARRAY_TOO_LARGE;
}
@@ -397,8 +399,8 @@ Batch::preflight(PreflightContext const& ctx)
if (!batchSigners.insert(signerAccount).second)
{
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId << "]: "
<< "duplicate signer found: " << signerAccount;
<< "BatchTrace[" << parentBatchId
<< "]: " << "duplicate signer found: " << signerAccount;
return temREDUNDANT;
}
@@ -406,8 +408,9 @@ Batch::preflight(PreflightContext const& ctx)
// Remove it if it does, as it can be crossed off the list.
if (requiredSigners.erase(signerAccount) == 0)
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "no account signature for inner txn.";
JLOG(ctx.j.debug())
<< "BatchTrace[" << parentBatchId
<< "]: " << "no account signature for inner txn.";
return temBAD_SIGNER;
}
}
@@ -427,8 +430,8 @@ Batch::preflight(PreflightContext const& ctx)
if (!requiredSigners.empty())
{
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
<< "invalid batch signers.";
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
<< "]: " << "invalid batch signers.";
return temBAD_SIGNER;
}
return tesSUCCESS;
+4 -4
View File
@@ -35,8 +35,8 @@ CancelOffer::preflight(PreflightContext const& ctx)
if (uTxFlags & tfUniversalMask)
{
JLOG(ctx.j.trace()) << "Malformed transaction: "
<< "Invalid flags set.";
JLOG(ctx.j.trace())
<< "Malformed transaction: " << "Invalid flags set.";
return temINVALID_FLAG;
}
@@ -63,8 +63,8 @@ CancelOffer::preclaim(PreclaimContext const& ctx)
if ((*sle)[sfSequence] <= offerSequence)
{
JLOG(ctx.j.trace()) << "Malformed transaction: "
<< "Sequence " << offerSequence << " is invalid.";
JLOG(ctx.j.trace()) << "Malformed transaction: " << "Sequence "
<< offerSequence << " is invalid.";
return temBAD_SEQUENCE;
}
+20
View File
@@ -130,6 +130,20 @@ Change::preclaim(PreclaimContext const& ctx)
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
return temDISABLED;
}
if (ctx.view.rules().enabled(featureSmartEscrow))
{
if (!ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
!ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
!ctx.tx.isFieldPresent(sfGasPrice))
return temMALFORMED;
}
else
{
if (ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
ctx.tx.isFieldPresent(sfGasPrice))
return temDISABLED;
}
return tesSUCCESS;
case ttAMENDMENT:
case ttUNL_MODIFY:
@@ -377,6 +391,12 @@ Change::applyFee()
set(feeObject, ctx_.tx, sfReserveBase);
set(feeObject, ctx_.tx, sfReserveIncrement);
}
if (view().rules().enabled(featureSmartEscrow))
{
set(feeObject, ctx_.tx, sfExtensionComputeLimit);
set(feeObject, ctx_.tx, sfExtensionSizeLimit);
set(feeObject, ctx_.tx, sfGasPrice);
}
view().update(feeObject);
+205 -16
View File
@@ -19,6 +19,8 @@
#include <xrpld/app/misc/CredentialHelpers.h>
#include <xrpld/app/misc/HashRouter.h>
#include <xrpld/app/misc/WasmHostFuncImpl.h>
#include <xrpld/app/misc/WasmVM.h>
#include <xrpld/app/tx/detail/Escrow.h>
#include <xrpld/conditions/Condition.h>
#include <xrpld/conditions/Fulfillment.h>
@@ -32,6 +34,8 @@
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
#include <algorithm>
// During an EscrowFinish, the transaction must specify both
// a condition and a fulfillment. We track whether that
// fulfillment matches and validates the condition.
@@ -82,9 +86,28 @@ EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
}
XRPAmount
EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)};
if (tx.isFieldPresent(sfFinishFunction))
{
// TODO: make this fee increase based on the extra compute run
txnFees += 1000;
}
return txnFees;
}
NotTEC
EscrowCreate::preflight(PreflightContext const& ctx)
{
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
!ctx.rules.enabled(featureSmartEscrow))
{
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
return temDISABLED;
}
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;
@@ -107,14 +130,23 @@ EscrowCreate::preflight(PreflightContext const& ctx)
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
return temBAD_EXPIRATION;
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
!ctx.tx.isFieldPresent(sfCancelAfter))
return temBAD_EXPIRATION;
if (ctx.rules.enabled(fix1571))
{
// In the absence of a FinishAfter, the escrow can be finished
// immediately, which can be confusing. When creating an escrow,
// we want to ensure that either a FinishAfter time is explicitly
// specified or a completion condition is attached.
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition])
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] &&
!ctx.tx[~sfFinishFunction])
{
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
"Condition, or FinishFunction.";
return temMALFORMED;
}
}
if (auto const cb = ctx.tx[~sfCondition])
@@ -139,6 +171,19 @@ EscrowCreate::preflight(PreflightContext const& ctx)
return temDISABLED;
}
if (ctx.tx.isFieldPresent(sfFinishFunction))
{
auto const code = ctx.tx.getFieldVL(sfFinishFunction);
if (code.size() == 0 ||
code.size() > ctx.app.config().FEES.extension_size_limit)
{
JLOG(ctx.j.debug())
<< "EscrowCreate.FinishFunction bad size " << code.size();
return temMALFORMED;
}
// TODO: add check to ensure this is valid WASM code
}
return preflight2(ctx);
}
@@ -243,6 +288,8 @@ EscrowCreate::doApply()
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
(*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction];
(*slep)[~sfData] = ctx_.tx[~sfData];
ctx_.view().insert(slep);
@@ -266,8 +313,13 @@ EscrowCreate::doApply()
}
// Deduct owner's balance, increment owner count
// TODO: determine actual reserve based on FinishFunction size
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
adjustOwnerCount(
ctx_.view(),
sle,
ctx_.tx.isFieldPresent(sfFinishFunction) ? 2 : 1,
ctx_.journal);
ctx_.view().update(sle);
return tesSUCCESS;
@@ -303,6 +355,13 @@ EscrowFinish::preflight(PreflightContext const& ctx)
!ctx.rules.enabled(featureCredentials))
return temDISABLED;
if (ctx.tx.isFieldPresent(sfComputationAllowance) &&
!ctx.rules.enabled(featureSmartEscrow))
{
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
return temDISABLED;
}
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
@@ -312,7 +371,10 @@ EscrowFinish::preflight(PreflightContext const& ctx)
// If you specify a condition, then you must also specify
// a fulfillment.
if (static_cast<bool>(cb) != static_cast<bool>(fb))
{
JLOG(ctx.j.debug()) << "Condition != Fulfillment";
return temMALFORMED;
}
// Verify the transaction signature. If it doesn't work
// then don't do any more work.
@@ -341,6 +403,16 @@ EscrowFinish::preflight(PreflightContext const& ctx)
}
}
if (auto const allowance = ctx.tx[~sfComputationAllowance]; allowance)
{
if (*allowance > ctx.app.config().FEES.extension_compute_limit)
{
JLOG(ctx.j.debug())
<< "ComputationAllowance too large: " << *allowance;
return temBAD_LIMIT;
}
}
if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err))
return err;
@@ -356,7 +428,10 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
{
extraFee += view.fees().base * (32 + (fb->size() / 16));
}
if (auto const allowance = tx[~sfComputationAllowance]; allowance)
{
extraFee += (*allowance) * view.fees().gasPrice / MICRO_DROPS_PER_DROP;
}
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
@@ -366,6 +441,34 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
if (!ctx.view.rules().enabled(featureCredentials))
return Transactor::preclaim(ctx);
if (ctx.view.rules().enabled(featureSmartEscrow))
{
// this check is done in doApply before this amendment is enabled
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
if (slep->isFieldPresent(sfFinishFunction))
{
if (!ctx.tx.isFieldPresent(sfComputationAllowance))
{
JLOG(ctx.j.debug())
<< "FinishFunction requires ComputationAllowance";
return tefWASM_FIELD_NOT_INCLUDED;
}
}
else
{
if (ctx.tx.isFieldPresent(sfComputationAllowance))
{
JLOG(ctx.j.debug()) << "FinishFunction not present, "
"ComputationAllowance present";
return tefNO_WASM;
}
}
}
if (auto const err = credentials::valid(ctx, ctx.tx[sfAccount]);
!isTesSuccess(err))
return err;
@@ -379,7 +482,15 @@ EscrowFinish::doApply()
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
auto const slep = ctx_.view().peek(k);
if (!slep)
return tecNO_TARGET;
return ctx_.view().rules().enabled(featureSmartEscrow) ? tecINTERNAL
: tecNO_TARGET;
// Order of processing the release conditions (in order of performance):
// FinishAfter/CancelAfter
// Destination validity (after SmartEscrow is enabled)
// Condition/Fulfillment
// Destination validity (before SmartEscrow is enabled)
// FinishFunction
// 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
@@ -390,11 +501,17 @@ EscrowFinish::doApply()
// Too soon: can't execute before the finish time
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
{
JLOG(j_.debug()) << "Too soon";
return tecNO_PERMISSION;
}
// Too late: can't execute after the cancel time
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
{
JLOG(j_.debug()) << "Too late";
return tecNO_PERMISSION;
}
}
else
{
@@ -402,13 +519,35 @@ EscrowFinish::doApply()
if ((*slep)[~sfFinishAfter] &&
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
(*slep)[sfFinishAfter])
{
JLOG(j_.debug()) << "Too soon?";
return tecNO_PERMISSION;
}
// Too late?
if ((*slep)[~sfCancelAfter] &&
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
(*slep)[sfCancelAfter])
{
JLOG(j_.debug()) << "Too late?";
return tecNO_PERMISSION;
}
}
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
if (ctx_.view().rules().enabled(featureSmartEscrow))
{
// NOTE: Escrow payments cannot be used to fund accounts.
if (!sled)
return tecNO_DST;
if (ctx_.view().rules().enabled(featureDepositAuth))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!isTesSuccess(err))
return err;
}
}
// Check cryptocondition fulfillment
@@ -458,17 +597,59 @@ EscrowFinish::doApply()
return tecCRYPTOCONDITION_ERROR;
}
// NOTE: Escrow payments cannot be used to fund accounts.
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
if (!sled)
return tecNO_DST;
if (ctx_.view().rules().enabled(featureDepositAuth))
if (!ctx_.view().rules().enabled(featureSmartEscrow))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!isTesSuccess(err))
return err;
// NOTE: Escrow payments cannot be used to fund accounts.
if (!sled)
return tecNO_DST;
if (ctx_.view().rules().enabled(featureDepositAuth))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!isTesSuccess(err))
return err;
}
}
// Execute custom release function
if ((*slep)[~sfFinishFunction])
{
JLOG(j_.trace())
<< "The escrow has a finish function, running WASM code...";
// WASM execution
auto const wasmStr = slep->getFieldVL(sfFinishFunction);
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
std::string funcName("finish");
WasmHostFunctionsImpl ledgerDataProvider(ctx_, k);
if (!ctx_.tx.isFieldPresent(sfComputationAllowance))
{
// already checked above, this check is just in case
return tecINTERNAL;
}
std::uint32_t allowance = ctx_.tx[sfComputationAllowance];
auto re =
runEscrowWasm(wasm, funcName, {}, &ledgerDataProvider, allowance);
JLOG(j_.trace()) << "Escrow WASM ran";
if (re.has_value())
{
auto reValue = re.value().result;
// TODO: better error handling for this conversion
ctx_.setGasUsed(static_cast<uint32_t>(re.value().cost));
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue)
<< ", cost: " << re.value().cost;
if (!reValue)
{
// ctx_.view().update(slep);
return tecWASM_REJECTED;
}
}
else
{
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error());
return re.error();
}
}
AccountID const account = (*slep)[sfAccount];
@@ -501,7 +682,11 @@ EscrowFinish::doApply()
// Adjust source owner count
auto const sle = ctx_.view().peek(keylet::account(account));
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
adjustOwnerCount(
ctx_.view(),
sle,
slep->isFieldPresent(sfFinishFunction) ? -2 : -1,
ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger
@@ -583,7 +768,11 @@ 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];
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
adjustOwnerCount(
ctx_.view(),
sle,
slep->isFieldPresent(sfFinishFunction) ? -2 : -1,
ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger
+3
View File
@@ -36,6 +36,9 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static NotTEC
preflight(PreflightContext const& ctx);
+38 -5
View File
@@ -297,9 +297,9 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
if (balance < feePaid)
{
JLOG(ctx.j.trace()) << "Insufficient balance:"
<< " balance=" << to_string(balance)
<< " paid=" << to_string(feePaid);
JLOG(ctx.j.trace())
<< "Insufficient balance:" << " balance=" << to_string(balance)
<< " paid=" << to_string(feePaid);
if ((balance > beast::zero) && !ctx.view.open())
{
@@ -952,6 +952,22 @@ removeExpiredCredentials(
}
}
static void
modifyWasmDataFields(
ApplyView& view,
std::vector<std::pair<uint256, Blob>> const& wasmObjects,
beast::Journal viewJ)
{
for (auto const& [index, data] : wasmObjects)
{
if (auto const sle = view.peek(keylet::escrow(index)))
{
sle->setFieldVL(sfData, data);
view.update(sle);
}
}
}
static void
removeDeletedTrustLines(
ApplyView& view,
@@ -1109,6 +1125,7 @@ Transactor::operator()()
else if (
(result == tecOVERSIZE) || (result == tecKILLED) ||
(result == tecINCOMPLETE) || (result == tecEXPIRED) ||
(result == tecWASM_REJECTED) ||
(isTecClaimHardFail(result, view().flags())))
{
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
@@ -1121,13 +1138,16 @@ Transactor::operator()()
std::vector<uint256> removedTrustLines;
std::vector<uint256> expiredNFTokenOffers;
std::vector<uint256> expiredCredentials;
std::vector<std::pair<uint256, Blob>> modifiedWasmObjects;
bool const doOffers =
((result == tecOVERSIZE) || (result == tecKILLED));
bool const doLines = (result == tecINCOMPLETE);
bool const doNFTokenOffers = (result == tecEXPIRED);
bool const doCredentials = (result == tecEXPIRED);
if (doOffers || doLines || doNFTokenOffers || doCredentials)
bool const doWasmData = (result == tecWASM_REJECTED);
if (doOffers || doLines || doNFTokenOffers || doCredentials ||
doWasmData)
{
ctx_.visit([doOffers,
&removedOffers,
@@ -1136,7 +1156,9 @@ Transactor::operator()()
doNFTokenOffers,
&expiredNFTokenOffers,
doCredentials,
&expiredCredentials](
&expiredCredentials,
doWasmData,
&modifiedWasmObjects](
uint256 const& index,
bool isDelete,
std::shared_ptr<SLE const> const& before,
@@ -1171,6 +1193,13 @@ Transactor::operator()()
(before->getType() == ltCREDENTIAL))
expiredCredentials.push_back(index);
}
if (doWasmData && before && after &&
(before->getType() == ltESCROW))
{
modifiedWasmObjects.push_back(
std::make_pair(index, after->getFieldVL(sfData)));
}
});
}
@@ -1200,6 +1229,10 @@ Transactor::operator()()
removeExpiredCredentials(
view(), expiredCredentials, ctx_.app.journal("View"));
if (result == tecWASM_REJECTED)
modifyWasmDataFields(
view(), modifiedWasmObjects, ctx_.app.journal("View"));
applied = isTecClaim(result);
}
+6 -8
View File
@@ -1121,8 +1121,8 @@ Consensus<Adaptor>::checkLedger(std::unique_ptr<std::stringstream> const& clog)
auto netLgr =
adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
CLOG(clog) << "network ledgerid " << netLgr << ", "
<< "previous ledger " << prevLedgerID_ << ". ";
CLOG(clog) << "network ledgerid " << netLgr << ", " << "previous ledger "
<< prevLedgerID_ << ". ";
if (netLgr != prevLedgerID_)
{
@@ -1213,8 +1213,7 @@ Consensus<Adaptor>::phaseOpen(std::unique_ptr<std::stringstream> const& clog)
adaptor_.parms().ledgerIDLE_INTERVAL,
2 * previousLedger_.closeTimeResolution());
CLOG(clog) << "idle interval set to " << idleInterval.count()
<< "ms based on "
<< "ledgerIDLE_INTERVAL: "
<< "ms based on " << "ledgerIDLE_INTERVAL: "
<< adaptor_.parms().ledgerIDLE_INTERVAL.count()
<< ", previous ledger close time resolution: "
<< previousLedger_.closeTimeResolution().count() << "ms. ";
@@ -1262,8 +1261,7 @@ Consensus<Adaptor>::shouldPause(
<< "roundTime: " << result_->roundTime.read().count() << ", "
<< "max consensus time: " << parms.ledgerMAX_CONSENSUS.count() << ", "
<< "validators: " << totalValidators << ", "
<< "laggards: " << laggards << ", "
<< "offline: " << offline << ", "
<< "laggards: " << laggards << ", " << "offline: " << offline << ", "
<< "quorum: " << quorum << ")";
if (!ahead || !laggards || !totalValidators || !adaptor_.validator() ||
@@ -1624,8 +1622,8 @@ Consensus<Adaptor>::updateOurPositions(
if (!haveCloseTimeConsensus_)
{
JLOG(j_.debug())
<< "No CT consensus:"
<< " Proposers:" << currPeerPositions_.size()
<< "No CT consensus:" << " Proposers:"
<< currPeerPositions_.size()
<< " Mode:" << to_string(mode_.get())
<< " Thresh:" << threshConsensus
<< " Pos:" << consensusCloseTime.time_since_epoch().count();
+9
View File
@@ -73,6 +73,15 @@ struct FeeSetup
/** The per-owned item reserve requirement in drops. */
XRPAmount owner_reserve{2 * DROPS_PER_XRP};
/** The compute limit for Feature Extensions. */
std::uint32_t extension_compute_limit{1'000'000};
/** The WASM size limit for Feature Extensions. */
std::uint32_t extension_size_limit{100'000};
/** The price of 1 WASM gas, in micro-drops. */
std::uint32_t gas_price{1'000'000};
/* (Remember to update the example cfg files when changing any of these
* values.) */
};
+6
View File
@@ -1140,6 +1140,12 @@ setup_FeeVote(Section const& section)
setup.account_reserve = temp;
if (set(temp, "owner_reserve", section))
setup.owner_reserve = temp;
if (set(temp, "extension_compute_limit", section))
setup.extension_compute_limit = temp;
if (set(temp, "extension_size_limit", section))
setup.extension_size_limit = temp;
if (set(temp, "gas_price", section))
setup.gas_price = temp;
}
return setup;
}
+7
View File
@@ -75,6 +75,12 @@ public:
deliver_ = amount;
}
void
setGasUsed(std::uint32_t const& gasUsed)
{
gasUsed_ = gasUsed;
}
/** Get the number of modified entries
*/
std::size_t
@@ -93,6 +99,7 @@ public:
private:
std::optional<STAmount> deliver_;
std::optional<std::uint32_t> gasUsed_;
};
} // namespace ripple
+6 -2
View File
@@ -117,6 +117,7 @@ ApplyStateTable::apply(
TER ter,
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
std::optional<std::uint32_t> const& gasUsed,
bool isDryRun,
beast::Journal j)
{
@@ -127,11 +128,14 @@ ApplyStateTable::apply(
std::optional<TxMeta> metadata;
if (!to.open() || isDryRun)
{
TxMeta meta(tx.getTransactionID(), to.seq(), parentBatchId);
TxMeta meta(tx.getTransactionID(), to.seq());
if (deliver)
meta.setDeliveredAmount(*deliver);
if (parentBatchId)
meta.setParentBatchId(*parentBatchId);
if (gasUsed)
meta.setGasUsed(*gasUsed);
Mods newMod;
for (auto& item : items_)
{
@@ -73,6 +73,7 @@ public:
TER ter,
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
std::optional<std::uint32_t> const& gasUsed,
bool isDryRun,
beast::Journal j);
+2 -1
View File
@@ -35,7 +35,8 @@ ApplyViewImpl::apply(
bool isDryRun,
beast::Journal j)
{
return items_.apply(to, tx, ter, deliver_, parentBatchId, isDryRun, j);
return items_.apply(
to, tx, ter, deliver_, parentBatchId, gasUsed_, isDryRun, j);
}
std::size_t
+2 -4
View File
@@ -459,8 +459,7 @@ accountHolds(
amount.clear(Issue{currency, issuer});
}
JLOG(j.trace()) << "accountHolds:"
<< " account=" << to_string(account)
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(account)
<< " amount=" << amount.getFullText();
return view.balanceHook(account, issuer, amount);
@@ -641,8 +640,7 @@ xrpLiquid(
STAmount const amount =
(balance < reserve) ? STAmount{0} : balance - reserve;
JLOG(j.trace()) << "accountHolds:"
<< " account=" << to_string(id)
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id)
<< " amount=" << amount.getFullText()
<< " fullBalance=" << fullBalance.getFullText()
<< " balance=" << balance.getFullText()
+2 -2
View File
@@ -1272,8 +1272,8 @@ PeerImp::handleTransaction(
{
// If we've never been in synch, there's nothing we can do
// with a transaction
JLOG(p_journal_.debug()) << "Ignoring incoming transaction: "
<< "Need network ledger";
JLOG(p_journal_.debug())
<< "Ignoring incoming transaction: " << "Need network ledger";
return;
}
+3 -3
View File
@@ -1132,9 +1132,9 @@ public:
}
else
{
JLOG(m_journal.error()) << beast::leftw(18) << "Logic failed "
<< "'" << source->name() << "' fetch, "
<< results.error.message();
JLOG(m_journal.error())
<< beast::leftw(18) << "Logic failed " << "'" << source->name()
<< "' fetch, " << results.error.message();
}
}