diff --git a/.github/scripts/levelization/README.md b/.github/scripts/levelization/README.md index 31c6d34b6b..f3ba1e2518 100644 --- a/.github/scripts/levelization/README.md +++ b/.github/scripts/levelization/README.md @@ -72,15 +72,15 @@ It generates many files of [results](results): desired as described above. In a perfect repo, this file will be empty. This file is committed to the repo, and is used by the [levelization - Github workflow](../../workflows/check-levelization.yml) to validate + Github workflow](../../workflows/reusable-check-levelization.yml) to validate that nothing changed. - [`ordering.txt`](results/ordering.txt): A list showing relationships between modules where there are no loops as they actually exist, as opposed to how they are desired as described above. This file is committed to the repo, and is used by the [levelization - Github workflow](../../workflows/check-levelization.yml) to validate + Github workflow](../../workflows/reusable-check-levelization.yml) to validate that nothing changed. -- [`levelization.yml`](../../workflows/check-levelization.yml) +- [`levelization.yml`](../../workflows/reusable-check-levelization.yml) Github Actions workflow to test that levelization loops haven't changed. Unfortunately, if changes are detected, it can't tell if they are improvements or not, so if you have resolved any issues or diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index 13de36e2a5..55df4c2672 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -138,6 +138,7 @@ test.toplevel > test.csf test.toplevel > xrpl.json test.unit_test > xrpl.basics tests.libxrpl > xrpl.basics +tests.libxrpl > xrpl.net xrpl.json > xrpl.basics xrpl.ledger > xrpl.basics xrpl.ledger > xrpl.protocol diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 9befd31e71..a206bbf041 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -50,8 +50,8 @@ jobs: files: | # These paths are unique to `on-pr.yml`. .github/scripts/levelization/** - .github/workflows/check-levelization.yml - .github/workflows/notify-clio.yml + .github/workflows/reusable-check-levelization.yml + .github/workflows/reusable-notify-clio.yml .github/workflows/on-pr.yml # Keep the paths below in sync with those in `on-trigger.yml`. @@ -59,7 +59,7 @@ jobs: .github/actions/build-test/** .github/actions/setup-conan/** .github/scripts/strategy-matrix/** - .github/workflows/build-test.yml + .github/workflows/reusable-build-test.yml .github/workflows/reusable-strategy-matrix.yml .codecov.yml cmake/** @@ -93,12 +93,12 @@ jobs: check-levelization: needs: should-run if: ${{ needs.should-run.outputs.go == 'true' }} - uses: ./.github/workflows/check-levelization.yml + uses: ./.github/workflows/reusable-check-levelization.yml build-test: needs: should-run if: ${{ needs.should-run.outputs.go == 'true' }} - uses: ./.github/workflows/build-test.yml + uses: ./.github/workflows/reusable-build-test.yml strategy: matrix: os: [linux, macos, windows] @@ -112,7 +112,7 @@ jobs: - should-run - build-test if: ${{ needs.should-run.outputs.go == 'true' && contains(fromJSON('["release", "master"]'), github.ref_name) }} - uses: ./.github/workflows/notify-clio.yml + uses: ./.github/workflows/reusable-notify-clio.yml secrets: clio_notify_token: ${{ secrets.CLIO_NOTIFY_TOKEN }} conan_remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }} diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 06abbd3f17..7b5bda021f 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -14,7 +14,7 @@ on: - master paths: # These paths are unique to `on-trigger.yml`. - - ".github/workflows/check-missing-commits.yml" + - ".github/workflows/reusable-check-missing-commits.yml" - ".github/workflows/on-trigger.yml" - ".github/workflows/publish-docs.yml" @@ -23,7 +23,7 @@ on: - ".github/actions/build-test/**" - ".github/actions/setup-conan/**" - ".github/scripts/strategy-matrix/**" - - ".github/workflows/build-test.yml" + - ".github/workflows/reusable-build-test.yml" - ".github/workflows/reusable-strategy-matrix.yml" - ".codecov.yml" - "cmake/**" @@ -71,10 +71,10 @@ defaults: jobs: check-missing-commits: if: ${{ github.event_name == 'push' && github.ref_type == 'branch' && contains(fromJSON('["develop", "release"]'), github.ref_name) }} - uses: ./.github/workflows/check-missing-commits.yml + uses: ./.github/workflows/reusable-check-missing-commits.yml build-test: - uses: ./.github/workflows/build-test.yml + uses: ./.github/workflows/reusable-build-test.yml strategy: matrix: os: [linux, macos, windows] diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index ead137308d..9b85a3bd11 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -7,8 +7,9 @@ on: workflow_dispatch: jobs: + # Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks. run-hooks: uses: XRPLF/actions/.github/workflows/pre-commit.yml@af1b0f0d764cda2e5435f5ac97b240d4bd4d95d3 with: runs_on: ubuntu-latest - container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit" }' + container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-d1496b8" }' diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 2fcdd581d1..efd89a5b22 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -27,7 +27,7 @@ env: jobs: publish: runs-on: ubuntu-latest - container: ghcr.io/xrplf/ci/tools-rippled-documentation + container: ghcr.io/xrplf/ci/tools-rippled-documentation:sha-d1496b8 permissions: contents: write steps: diff --git a/.github/workflows/build-test.yml b/.github/workflows/reusable-build-test.yml similarity index 97% rename from .github/workflows/build-test.yml rename to .github/workflows/reusable-build-test.yml index 634ed42690..2197e88a42 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/reusable-build-test.yml @@ -63,7 +63,7 @@ jobs: matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} max-parallel: 10 runs-on: ${{ matrix.architecture.runner }} - container: ${{ inputs.os == 'linux' && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version) || null }} + container: ${{ inputs.os == 'linux' && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-5dd7158', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version) || null }} steps: - name: Check strategy matrix run: | diff --git a/.github/workflows/check-levelization.yml b/.github/workflows/reusable-check-levelization.yml similarity index 100% rename from .github/workflows/check-levelization.yml rename to .github/workflows/reusable-check-levelization.yml diff --git a/.github/workflows/check-missing-commits.yml b/.github/workflows/reusable-check-missing-commits.yml similarity index 100% rename from .github/workflows/check-missing-commits.yml rename to .github/workflows/reusable-check-missing-commits.yml diff --git a/.github/workflows/notify-clio.yml b/.github/workflows/reusable-notify-clio.yml similarity index 98% rename from .github/workflows/notify-clio.yml rename to .github/workflows/reusable-notify-clio.yml index 692904ff12..2d6fa63796 100644 --- a/.github/workflows/notify-clio.yml +++ b/.github/workflows/reusable-notify-clio.yml @@ -40,7 +40,7 @@ jobs: upload: if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} runs-on: ubuntu-latest - container: ghcr.io/xrplf/ci/ubuntu-noble:gcc-13 + container: ghcr.io/xrplf/ci/ubuntu-noble:gcc-13-sha-5dd7158 steps: - name: Checkout repository uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index c52b3c89d3..98db52a436 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -56,7 +56,7 @@ jobs: matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} max-parallel: 10 runs-on: ${{ matrix.architecture.runner }} - container: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version) || null }} + container: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-5dd7158', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version) || null }} steps: - name: Cleanup workspace diff --git a/external/wamr/conandata.yml b/external/wamr/conandata.yml deleted file mode 100644 index d475ad987c..0000000000 --- a/external/wamr/conandata.yml +++ /dev/null @@ -1,6 +0,0 @@ -patches: - 2.4.1: - - patch_description: add metering to iwasm interpreter - patch_file: patches/ripp_metering.patch - patch_type: conan - diff --git a/external/wamr/conanfile.py b/external/wamr/conanfile.py index 429c509795..7057ef9808 100644 --- a/external/wamr/conanfile.py +++ b/external/wamr/conanfile.py @@ -1,10 +1,6 @@ from conan 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, -) +#from conan.tools.files import (apply_conandata_patches, export_conandata_patches,) from conan.tools.scm import Git # import os @@ -16,7 +12,7 @@ class WamrConan(ConanFile): name = "wamr" version = "2.4.1" license = "Apache License v2.0" - url = "https://github.com/bytecodealliance/wasm-micro-runtime.git" + url = "https://github.com/ripple/wasm-micro-runtime.git" description = "Webassembly micro runtime" package_type = "library" settings = "os", "compiler", "build_type", "arch" @@ -25,7 +21,7 @@ class WamrConan(ConanFile): # requires = [("llvm/20.1.1@")] def export_sources(self): - export_conandata_patches(self) + #export_conandata_patches(self) pass # def build_requirements(self): @@ -41,8 +37,8 @@ class WamrConan(ConanFile): def source(self): git = Git(self) git.fetch_commit( - url="https://github.com/bytecodealliance/wasm-micro-runtime.git", - commit="b124f70345d712bead5c0c2393acb2dc583511de", + url="https://github.com/ripple/wasm-micro-runtime.git", + commit="a87e56fe8042bbe3ed99a514333ddef42e6c2a41", ) # get(self, **self.conan_data["sources"][self.version], strip_root=True) @@ -71,7 +67,7 @@ class WamrConan(ConanFile): deps.generate() def build(self): - apply_conandata_patches(self) + #apply_conandata_patches(self) cmake = CMake(self) cmake.verbose = True cmake.configure() diff --git a/external/wamr/patches/ripp_metering.patch b/external/wamr/patches/ripp_metering.patch deleted file mode 100644 index 2e8ec70d7a..0000000000 --- a/external/wamr/patches/ripp_metering.patch +++ /dev/null @@ -1,901 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 4b28fa89..7d523a3d 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) - -@@ -170,7 +170,7 @@ if (MINGW) - endif () - - if (WIN32) -- target_link_libraries(vmlib PRIVATE ntdll) -+ target_link_libraries(vmlib PUBLIC ntdll) - endif() - - set (WAMR_PUBLIC_HEADERS -diff --git a/core/iwasm/aot/aot_runtime.c b/core/iwasm/aot/aot_runtime.c -index d2621fb2..6c96a844 100644 ---- a/core/iwasm/aot/aot_runtime.c -+++ b/core/iwasm/aot/aot_runtime.c -@@ -5611,7 +5611,7 @@ aot_resolve_import_func(AOTModule *module, AOTImportFunc *import_func) - import_func->func_ptr_linked = wasm_native_resolve_symbol( - import_func->module_name, import_func->func_name, - import_func->func_type, &import_func->signature, -- &import_func->attachment, &import_func->call_conv_raw); -+ &import_func->attachment, NULL, &import_func->call_conv_raw); - #if WASM_ENABLE_MULTI_MODULE != 0 - if (!import_func->func_ptr_linked) { - if (!wasm_runtime_is_built_in_module(import_func->module_name)) { -diff --git a/core/iwasm/common/wasm_c_api.c b/core/iwasm/common/wasm_c_api.c -index 269ec577..34eb7c34 100644 ---- a/core/iwasm/common/wasm_c_api.c -+++ b/core/iwasm/common/wasm_c_api.c -@@ -3242,10 +3242,20 @@ wasm_func_copy(const wasm_func_t *func) - - cloned->func_idx_rt = func->func_idx_rt; - cloned->inst_comm_rt = func->inst_comm_rt; -+ cloned->gas = func->gas; - - RETURN_OBJ(cloned, wasm_func_delete) - } - -+uint32_t -+wasm_func_set_gas(wasm_func_t *func, uint32_t gas) -+{ -+ if(!func) return 0; -+ -+ func->gas = gas; -+ return gas; -+} -+ - own wasm_functype_t * - wasm_func_type(const wasm_func_t *func) - { -@@ -4998,11 +5008,11 @@ wasm_instance_new_with_args_ex(wasm_store_t *store, const wasm_module_t *module, - goto failed; - } - -+ WASMModuleInstance *wasm_module_inst = NULL; - /* create the c-api func import list */ - #if WASM_ENABLE_INTERP != 0 - if (instance->inst_comm_rt->module_type == Wasm_Module_Bytecode) { -- WASMModuleInstance *wasm_module_inst = -- (WASMModuleInstance *)instance->inst_comm_rt; -+ wasm_module_inst = (WASMModuleInstance *)instance->inst_comm_rt; - p_func_imports = &(wasm_module_inst->c_api_func_imports); - import_func_count = MODULE_INTERP(module)->import_function_count; - } -@@ -5052,6 +5062,13 @@ wasm_instance_new_with_args_ex(wasm_store_t *store, const wasm_module_t *module, - } - bh_assert(func_import->func_ptr_linked); - -+ // fill gas -+ if(wasm_module_inst) { -+ WASMFunctionInstance *fi = wasm_module_inst->e->functions + func_host->func_idx_rt; -+ if(fi) fi->gas = func_host->gas; -+ } -+ -+ - func_import++; - } - -@@ -5389,3 +5406,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_c_api_internal.h b/core/iwasm/common/wasm_c_api_internal.h -index 49a17a96..19a85980 100644 ---- a/core/iwasm/common/wasm_c_api_internal.h -+++ b/core/iwasm/common/wasm_c_api_internal.h -@@ -142,6 +142,10 @@ struct wasm_func_t { - void (*finalizer)(void *); - } cb_env; - } u; -+ -+ // gas cost for import func -+ uint32 gas; -+ - /* - * an index in both functions runtime instance lists - * of interpreter mode and aot mode -diff --git a/core/iwasm/common/wasm_exec_env.c b/core/iwasm/common/wasm_exec_env.c -index 47752950..5f26d886 100644 ---- a/core/iwasm/common/wasm_exec_env.c -+++ b/core/iwasm/common/wasm_exec_env.c -@@ -86,7 +86,7 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst, - #endif - - #if WASM_ENABLE_INSTRUCTION_METERING != 0 -- exec_env->instructions_to_execute = -1; -+ exec_env->instructions_to_execute = INT64_MAX; - #endif - - return exec_env; -diff --git a/core/iwasm/common/wasm_exec_env.h b/core/iwasm/common/wasm_exec_env.h -index 5d80312f..b2ecce2e 100644 ---- a/core/iwasm/common/wasm_exec_env.h -+++ b/core/iwasm/common/wasm_exec_env.h -@@ -89,7 +89,7 @@ typedef struct WASMExecEnv { - - #if WASM_ENABLE_INSTRUCTION_METERING != 0 - /* instructions to execute */ -- int instructions_to_execute; -+ int64 instructions_to_execute; - #endif - - #if WASM_ENABLE_FAST_JIT != 0 -diff --git a/core/iwasm/common/wasm_native.c b/core/iwasm/common/wasm_native.c -index 060bb2c3..9221c36a 100644 ---- a/core/iwasm/common/wasm_native.c -+++ b/core/iwasm/common/wasm_native.c -@@ -180,9 +180,9 @@ native_symbol_cmp(const void *native_symbol1, const void *native_symbol2) - ((const NativeSymbol *)native_symbol2)->symbol); - } - --static void * -+static NativeSymbol * - lookup_symbol(NativeSymbol *native_symbols, uint32 n_native_symbols, -- const char *symbol, const char **p_signature, void **p_attachment) -+ const char *symbol) - { - NativeSymbol *native_symbol, key = { 0 }; - -@@ -190,9 +190,7 @@ lookup_symbol(NativeSymbol *native_symbols, uint32 n_native_symbols, - - if ((native_symbol = bsearch(&key, native_symbols, n_native_symbols, - sizeof(NativeSymbol), native_symbol_cmp))) { -- *p_signature = native_symbol->signature; -- *p_attachment = native_symbol->attachment; -- return native_symbol->func_ptr; -+ return native_symbol; - } - - return NULL; -@@ -205,25 +203,36 @@ lookup_symbol(NativeSymbol *native_symbols, uint32 n_native_symbols, - void * - wasm_native_resolve_symbol(const char *module_name, const char *field_name, - const WASMFuncType *func_type, -- const char **p_signature, void **p_attachment, -+ const char **p_signature, void **p_attachment, uint32_t *gas, - bool *p_call_conv_raw) - { - NativeSymbolsNode *node, *node_next; - const char *signature = NULL; - void *func_ptr = NULL, *attachment = NULL; -+ NativeSymbol *native_symbol = NULL; - - node = g_native_symbols_list; - while (node) { - node_next = node->next; - if (!strcmp(node->module_name, module_name)) { -- if ((func_ptr = -+ if ((native_symbol = - lookup_symbol(node->native_symbols, node->n_native_symbols, -- field_name, &signature, &attachment)) -+ field_name)) - || (field_name[0] == '_' -- && (func_ptr = lookup_symbol( -+ && (native_symbol = lookup_symbol( - node->native_symbols, node->n_native_symbols, -- field_name + 1, &signature, &attachment)))) -- break; -+ field_name + 1)))) -+ { -+ func_ptr = native_symbol->func_ptr; -+ if(func_ptr) -+ { -+ if(gas) -+ *gas = native_symbol->gas; -+ signature = native_symbol->signature; -+ attachment = native_symbol->attachment; -+ break; -+ } -+ } - } - node = node_next; - } -diff --git a/core/iwasm/common/wasm_native.h b/core/iwasm/common/wasm_native.h -index 9a6afee1..0fe4739f 100644 ---- a/core/iwasm/common/wasm_native.h -+++ b/core/iwasm/common/wasm_native.h -@@ -52,7 +52,7 @@ wasm_native_lookup_libc_builtin_global(const char *module_name, - void * - wasm_native_resolve_symbol(const char *module_name, const char *field_name, - const WASMFuncType *func_type, -- const char **p_signature, void **p_attachment, -+ const char **p_signature, void **p_attachment, uint32_t *gas, - bool *p_call_conv_raw); - - bool -diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c -index 943b46fc..d026777e 100644 ---- a/core/iwasm/common/wasm_runtime_common.c -+++ b/core/iwasm/common/wasm_runtime_common.c -@@ -2344,10 +2344,18 @@ wasm_runtime_access_exce_check_guard_page() - #if WASM_ENABLE_INSTRUCTION_METERING != 0 - void - wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env, -- int instructions_to_execute) -+ int64 instructions_to_execute) - { -+ if(instructions_to_execute == -1) -+ instructions_to_execute = INT64_MAX; - exec_env->instructions_to_execute = instructions_to_execute; - } -+ -+int64 -+wasm_runtime_get_instruction_count_limit(WASMExecEnv *exec_env) -+{ -+ return exec_env->instructions_to_execute; -+} - #endif - - WASMFuncType * -@@ -7412,7 +7420,7 @@ wasm_runtime_is_import_func_linked(const char *module_name, - const char *func_name) - { - return wasm_native_resolve_symbol(module_name, func_name, NULL, NULL, NULL, -- NULL); -+ NULL, NULL); - } - - bool -@@ -7869,13 +7877,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; -@@ -7885,6 +7894,7 @@ wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env) - "native stack overflow"); - return false; - } -+#endif - return true; - } - -@@ -7907,7 +7917,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 324620be..54155a0c 100644 ---- a/core/iwasm/common/wasm_runtime_common.h -+++ b/core/iwasm/common/wasm_runtime_common.h -@@ -833,7 +833,10 @@ wasm_runtime_set_native_stack_boundary(WASMExecEnv *exec_env, - /* See wasm_export.h for description */ - WASM_RUNTIME_API_EXTERN void - wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env, -- int instructions_to_execute); -+ int64 instructions_to_execute); -+WASM_RUNTIME_API_EXTERN int64 -+wasm_runtime_get_instruction_count_limit(WASMExecEnv *exec_env); -+ - #endif - - #if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0 -diff --git a/core/iwasm/include/lib_export.h b/core/iwasm/include/lib_export.h -index 0ca668f5..93bcf807 100644 ---- a/core/iwasm/include/lib_export.h -+++ b/core/iwasm/include/lib_export.h -@@ -24,6 +24,8 @@ typedef struct NativeSymbol { - /* attachment which can be retrieved in native API by - calling wasm_runtime_get_function_attachment(exec_env) */ - void *attachment; -+ // gas cost for import func -+ uint32_t gas; - } NativeSymbol; - - /* clang-format off */ -diff --git a/core/iwasm/include/wasm_c_api.h b/core/iwasm/include/wasm_c_api.h -index 241a0eec..1141744c 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 -@@ -592,6 +594,8 @@ WASM_API_EXTERN size_t wasm_func_result_arity(const wasm_func_t*); - WASM_API_EXTERN own wasm_trap_t* wasm_func_call( - const wasm_func_t*, const wasm_val_vec_t* args, wasm_val_vec_t* results); - -+WASM_API_EXTERN own uint32_t wasm_func_set_gas(wasm_func_t*, uint32_t); -+ - - // Global Instances - -@@ -701,6 +705,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 81efb8f6..f752a970 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"))) -@@ -1874,7 +1876,14 @@ wasm_runtime_set_native_stack_boundary(wasm_exec_env_t exec_env, - */ - WASM_RUNTIME_API_EXTERN void - wasm_runtime_set_instruction_count_limit(wasm_exec_env_t exec_env, -- int instruction_count); -+ 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: -diff --git a/core/iwasm/interpreter/wasm.h b/core/iwasm/interpreter/wasm.h -index 0dd73958..b7cad5f2 100644 ---- a/core/iwasm/interpreter/wasm.h -+++ b/core/iwasm/interpreter/wasm.h -@@ -617,6 +617,9 @@ typedef struct WASMFunctionImport { - WASMModule *import_module; - WASMFunction *import_func_linked; - #endif -+ // gas cost for import func -+ uint32 gas; -+ - } WASMFunctionImport; - - #if WASM_ENABLE_TAGS != 0 -diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c -index edc473f2..55071613 100644 ---- a/core/iwasm/interpreter/wasm_interp_classic.c -+++ b/core/iwasm/interpreter/wasm_interp_classic.c -@@ -1547,13 +1547,14 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global) - } - - #if WASM_ENABLE_INSTRUCTION_METERING != 0 --#define CHECK_INSTRUCTION_LIMIT() \ -- if (instructions_left == 0) { \ -- wasm_set_exception(module, "instruction limit exceeded"); \ -- goto got_exception; \ -- } \ -- else if (instructions_left > 0) \ -- instructions_left--; -+#define CHECK_INSTRUCTION_LIMIT() \ -+ do { \ -+ --instructions_left; \ -+ if (instructions_left < 0) { \ -+ wasm_set_exception(module, "instruction limit exceeded"); \ -+ goto got_exception; \ -+ } \ -+ } while (0) - #else - #define CHECK_INSTRUCTION_LIMIT() (void)0 - #endif -@@ -1603,10 +1604,9 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - uint32 cache_index, type_index, param_cell_num, cell_num; - - #if WASM_ENABLE_INSTRUCTION_METERING != 0 -- int instructions_left = -1; -- if (exec_env) { -+ int64 instructions_left = INT64_MAX; -+ if (exec_env) - instructions_left = exec_env->instructions_to_execute; -- } - #endif - - #if WASM_ENABLE_EXCE_HANDLING != 0 -@@ -6849,6 +6849,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_left; -+#endif -+ - if (!prev_frame->ip) { - /* Called from native. */ - return; -@@ -6889,6 +6894,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_left; -+#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 36d4538f..4d03603e 100644 ---- a/core/iwasm/interpreter/wasm_interp_fast.c -+++ b/core/iwasm/interpreter/wasm_interp_fast.c -@@ -90,14 +90,14 @@ typedef float64 CellType_F64; - } while (0) - - #if WASM_ENABLE_INSTRUCTION_METERING != 0 --#define CHECK_INSTRUCTION_LIMIT() \ -- if (instructions_left == 0) { \ -- wasm_set_exception(module, "instruction limit exceeded"); \ -- goto got_exception; \ -- } \ -- else if (instructions_left > 0) \ -- instructions_left--; -- -+#define CHECK_INSTRUCTION_LIMIT() \ -+ do { \ -+ --instructions_left; \ -+ if (instructions_left < 0) { \ -+ wasm_set_exception(module, "instruction limit exceeded"); \ -+ goto got_exception; \ -+ } \ -+ } while (0) - #else - #define CHECK_INSTRUCTION_LIMIT() (void)0 - #endif -@@ -1438,7 +1438,6 @@ wasm_interp_dump_op_count() - do { \ - const void *p_label_addr = *(void **)frame_ip; \ - frame_ip += sizeof(void *); \ -- CHECK_INSTRUCTION_LIMIT(); \ - goto *p_label_addr; \ - } while (0) - #else -@@ -1450,7 +1449,6 @@ wasm_interp_dump_op_count() - /* int32 relative offset was emitted in 64-bit target */ \ - p_label_addr = label_base + (int32)LOAD_U32_WITH_2U16S(frame_ip); \ - frame_ip += sizeof(int32); \ -- CHECK_INSTRUCTION_LIMIT(); \ - goto *p_label_addr; \ - } while (0) - #else -@@ -1461,17 +1459,18 @@ wasm_interp_dump_op_count() - /* uint32 label address was emitted in 32-bit target */ \ - p_label_addr = (void *)(uintptr_t)LOAD_U32_WITH_2U16S(frame_ip); \ - frame_ip += sizeof(int32); \ -- CHECK_INSTRUCTION_LIMIT(); \ - goto *p_label_addr; \ - } 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 */ - -@@ -1540,10 +1539,9 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - uint8 opcode = 0, local_type, *global_addr; - - #if WASM_ENABLE_INSTRUCTION_METERING != 0 -- int instructions_left = -1; -- if (exec_env) { -+ int64 instructions_left = INT64_MAX; -+ if (exec_env) - instructions_left = exec_env->instructions_to_execute; -- } - #endif - #if !defined(OS_ENABLE_HW_BOUND_CHECK) \ - || WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS == 0 -@@ -4012,7 +4010,15 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - } - - /* constant instructions */ -+#ifdef ENABLE_FLOAT_POINT - HANDLE_OP(WASM_OP_F64_CONST) -+#else -+ HANDLE_OP(WASM_OP_F64_CONST) -+ { -+ wasm_set_exception(module, "opcode disabled"); -+ goto got_exception; -+ } -+#endif - HANDLE_OP(WASM_OP_I64_CONST) - { - uint8 *orig_ip = frame_ip; -@@ -4025,7 +4031,15 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - HANDLE_OP_END(); - } - -+#ifdef ENABLE_FLOAT_POINT -+ HANDLE_OP(WASM_OP_F32_CONST) -+#else - HANDLE_OP(WASM_OP_F32_CONST) -+ { -+ wasm_set_exception(module, "opcode disabled"); -+ goto got_exception; -+ } -+#endif - HANDLE_OP(WASM_OP_I32_CONST) - { - uint8 *orig_ip = frame_ip; -@@ -4172,6 +4186,8 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - HANDLE_OP_END(); - } - -+#ifdef ENABLE_FLOAT_POINT -+ - /* comparison instructions of f32 */ - HANDLE_OP(WASM_OP_F32_EQ) - { -@@ -4245,6 +4261,24 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - DEF_OP_CMP(float64, F64, >=); - HANDLE_OP_END(); - } -+#else -+ HANDLE_OP(WASM_OP_F32_EQ) -+ HANDLE_OP(WASM_OP_F32_NE) -+ HANDLE_OP(WASM_OP_F32_LT) -+ HANDLE_OP(WASM_OP_F32_GT) -+ HANDLE_OP(WASM_OP_F32_LE) -+ HANDLE_OP(WASM_OP_F32_GE) -+ HANDLE_OP(WASM_OP_F64_EQ) -+ HANDLE_OP(WASM_OP_F64_NE) -+ HANDLE_OP(WASM_OP_F64_LT) -+ HANDLE_OP(WASM_OP_F64_GT) -+ HANDLE_OP(WASM_OP_F64_LE) -+ HANDLE_OP(WASM_OP_F64_GE) -+ { -+ wasm_set_exception(module, "opcode disabled"); -+ goto got_exception; -+ } -+#endif - - /* numeric instructions of i32 */ - HANDLE_OP(WASM_OP_I32_CLZ) -@@ -4573,6 +4607,8 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - HANDLE_OP_END(); - } - -+#ifdef ENABLE_FLOAT_POINT -+ - /* numeric instructions of f32 */ - HANDLE_OP(WASM_OP_F32_ABS) - { -@@ -4784,6 +4820,43 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - HANDLE_OP_END(); - } - -+#else -+ -+ HANDLE_OP(WASM_OP_F32_ABS) -+ HANDLE_OP(WASM_OP_F32_NEG) -+ HANDLE_OP(WASM_OP_F32_CEIL) -+ HANDLE_OP(WASM_OP_F32_FLOOR) -+ HANDLE_OP(WASM_OP_F32_TRUNC) -+ HANDLE_OP(WASM_OP_F32_NEAREST) -+ HANDLE_OP(WASM_OP_F32_SQRT) -+ HANDLE_OP(WASM_OP_F32_ADD) -+ HANDLE_OP(WASM_OP_F32_SUB) -+ HANDLE_OP(WASM_OP_F32_MUL) -+ HANDLE_OP(WASM_OP_F32_DIV) -+ HANDLE_OP(WASM_OP_F32_MIN) -+ HANDLE_OP(WASM_OP_F32_MAX) -+ HANDLE_OP(WASM_OP_F32_COPYSIGN) -+ HANDLE_OP(WASM_OP_F64_ABS) -+ HANDLE_OP(WASM_OP_F64_NEG) -+ HANDLE_OP(WASM_OP_F64_CEIL) -+ HANDLE_OP(WASM_OP_F64_FLOOR) -+ HANDLE_OP(WASM_OP_F64_TRUNC) -+ HANDLE_OP(WASM_OP_F64_NEAREST) -+ HANDLE_OP(WASM_OP_F64_SQRT) -+ HANDLE_OP(WASM_OP_F64_ADD) -+ HANDLE_OP(WASM_OP_F64_SUB) -+ HANDLE_OP(WASM_OP_F64_MUL) -+ HANDLE_OP(WASM_OP_F64_DIV) -+ HANDLE_OP(WASM_OP_F64_MIN) -+ HANDLE_OP(WASM_OP_F64_MAX) -+ HANDLE_OP(WASM_OP_F64_COPYSIGN) -+ { -+ wasm_set_exception(module, "opcode disabled"); -+ goto got_exception; -+ } -+ -+#endif //ENABLE_FLOAT_POINT -+ - /* conversions of i32 */ - HANDLE_OP(WASM_OP_I32_WRAP_I64) - { -@@ -4792,6 +4865,8 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - HANDLE_OP_END(); - } - -+ -+#ifdef ENABLE_FLOAT_POINT - HANDLE_OP(WASM_OP_I32_TRUNC_S_F32) - { - /* We don't use INT32_MIN/INT32_MAX/UINT32_MIN/UINT32_MAX, -@@ -4821,6 +4896,19 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - HANDLE_OP_END(); - } - -+#else -+ -+ HANDLE_OP(WASM_OP_I32_TRUNC_S_F32) -+ HANDLE_OP(WASM_OP_I32_TRUNC_U_F32) -+ HANDLE_OP(WASM_OP_I32_TRUNC_S_F64) -+ HANDLE_OP(WASM_OP_I32_TRUNC_U_F64) -+ { -+ wasm_set_exception(module, "opcode disabled"); -+ goto got_exception; -+ } -+ -+#endif //ENABLE_FLOAT_POINT -+ - /* conversions of i64 */ - HANDLE_OP(WASM_OP_I64_EXTEND_S_I32) - { -@@ -4834,6 +4922,8 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - HANDLE_OP_END(); - } - -+#ifdef ENABLE_FLOAT_POINT -+ - HANDLE_OP(WASM_OP_I64_TRUNC_S_F32) - { - DEF_OP_TRUNC_F32(-9223373136366403584.0f, -@@ -4937,6 +5027,32 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - HANDLE_OP_END(); - } - -+#else -+ HANDLE_OP(WASM_OP_I64_TRUNC_S_F32) -+ HANDLE_OP(WASM_OP_I64_TRUNC_U_F32) -+ HANDLE_OP(WASM_OP_I64_TRUNC_S_F64) -+ HANDLE_OP(WASM_OP_I64_TRUNC_U_F64) -+ HANDLE_OP(WASM_OP_F32_CONVERT_S_I32) -+ HANDLE_OP(WASM_OP_F32_CONVERT_U_I32) -+ HANDLE_OP(WASM_OP_F32_CONVERT_S_I64) -+ HANDLE_OP(WASM_OP_F32_CONVERT_U_I64) -+ HANDLE_OP(WASM_OP_F32_DEMOTE_F64) -+ HANDLE_OP(WASM_OP_F64_CONVERT_S_I32) -+ HANDLE_OP(WASM_OP_F64_CONVERT_U_I32) -+ HANDLE_OP(WASM_OP_F64_CONVERT_S_I64) -+ HANDLE_OP(WASM_OP_F64_CONVERT_U_I64) -+ HANDLE_OP(WASM_OP_F64_PROMOTE_F32) -+ HANDLE_OP(WASM_OP_I32_REINTERPRET_F32) -+ HANDLE_OP(WASM_OP_F32_REINTERPRET_I32) -+ HANDLE_OP(WASM_OP_I64_REINTERPRET_F64) -+ HANDLE_OP(WASM_OP_F64_REINTERPRET_I64) -+ { -+ wasm_set_exception(module, "opcode disabled"); -+ goto got_exception; -+ } -+ -+#endif //ENABLE_FLOAT_POINT -+ - HANDLE_OP(EXT_OP_COPY_STACK_TOP) - { - addr1 = GET_OFFSET(); -@@ -5108,6 +5224,8 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - { - GET_OPCODE(); - switch (opcode) { -+ -+#ifdef ENABLE_FLOAT_POINT - case WASM_OP_I32_TRUNC_SAT_S_F32: - DEF_OP_TRUNC_SAT_F32(-2147483904.0f, 2147483648.0f, - true, true); -@@ -5140,6 +5258,9 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - DEF_OP_TRUNC_SAT_F64(-1.0, 18446744073709551616.0, - false, false); - break; -+ -+#endif -+ - #if WASM_ENABLE_BULK_MEMORY != 0 - case WASM_OP_MEMORY_INIT: - { -@@ -7672,6 +7793,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, - { - wasm_interp_call_func_native(module, exec_env, cur_func, - prev_frame); -+ instructions_left -= cur_func->gas; - } - - #if WASM_ENABLE_TAIL_CALL != 0 || WASM_ENABLE_GC != 0 -@@ -7784,6 +7906,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_left; -+#endif -+ - if (!prev_frame->ip) - /* Called from native. */ - return; -@@ -7812,6 +7939,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_left; -+#endif - return; - - #if WASM_ENABLE_LABELS_AS_VALUES == 0 -diff --git a/core/iwasm/interpreter/wasm_mini_loader.c b/core/iwasm/interpreter/wasm_mini_loader.c -index 771538a1..d6e6a6b8 100644 ---- a/core/iwasm/interpreter/wasm_mini_loader.c -+++ b/core/iwasm/interpreter/wasm_mini_loader.c -@@ -805,6 +805,7 @@ load_function_import(const uint8 **p_buf, const uint8 *buf_end, - const char *linked_signature = NULL; - void *linked_attachment = NULL; - bool linked_call_conv_raw = false; -+ uint32_t gas = 0; - - read_leb_uint32(p, p_end, declare_type_index); - *p_buf = p; -@@ -816,7 +817,7 @@ load_function_import(const uint8 **p_buf, const uint8 *buf_end, - /* check built-in modules */ - linked_func = wasm_native_resolve_symbol( - sub_module_name, function_name, declare_func_type, &linked_signature, -- &linked_attachment, &linked_call_conv_raw); -+ &linked_attachment, &gas, &linked_call_conv_raw); - - function->module_name = (char *)sub_module_name; - function->field_name = (char *)function_name; -@@ -825,6 +826,7 @@ load_function_import(const uint8 **p_buf, const uint8 *buf_end, - function->signature = linked_signature; - function->attachment = linked_attachment; - function->call_conv_raw = linked_call_conv_raw; -+ function->gas = gas; - return true; - } - -diff --git a/core/iwasm/interpreter/wasm_runtime.c b/core/iwasm/interpreter/wasm_runtime.c -index b4aa483d..2d74e469 100644 ---- a/core/iwasm/interpreter/wasm_runtime.c -+++ b/core/iwasm/interpreter/wasm_runtime.c -@@ -168,7 +168,7 @@ wasm_resolve_import_func(const WASMModule *module, WASMFunctionImport *function) - #endif - function->func_ptr_linked = wasm_native_resolve_symbol( - function->module_name, function->field_name, function->func_type, -- &function->signature, &function->attachment, &function->call_conv_raw); -+ &function->signature, &function->attachment, &function->gas, &function->call_conv_raw); - - if (function->func_ptr_linked) { - return true; -@@ -820,6 +820,7 @@ functions_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, - function->param_count = - (uint16)function->u.func_import->func_type->param_count; - function->param_types = function->u.func_import->func_type->types; -+ function->gas = import->u.function.gas; - function->local_cell_num = 0; - function->local_count = 0; - function->local_types = NULL; -diff --git a/core/iwasm/interpreter/wasm_runtime.h b/core/iwasm/interpreter/wasm_runtime.h -index 16c670f0..5ddac567 100644 ---- a/core/iwasm/interpreter/wasm_runtime.h -+++ b/core/iwasm/interpreter/wasm_runtime.h -@@ -237,6 +237,10 @@ struct WASMFunctionInstance { - WASMFunctionImport *func_import; - WASMFunction *func; - } u; -+ -+ // gas cost for import func -+ uint32 gas; -+ - #if WASM_ENABLE_MULTI_MODULE != 0 - WASMModuleInstance *import_module_inst; - WASMFunctionInstance *import_func_inst; -diff --git a/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c b/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c -index a68c0749..cafb6915 100644 ---- a/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c -+++ b/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c -@@ -1038,16 +1038,16 @@ print_f64_wrapper(wasm_exec_env_t exec_env, double f64) - - /* clang-format off */ - #define REG_NATIVE_FUNC(func_name, signature) \ -- { #func_name, func_name##_wrapper, signature, NULL } -+ { #func_name, func_name##_wrapper, signature, NULL, 0 } - /* clang-format on */ - - static NativeSymbol native_symbols_libc_builtin[] = { - REG_NATIVE_FUNC(printf, "($*)i"), - REG_NATIVE_FUNC(sprintf, "($$*)i"), - REG_NATIVE_FUNC(snprintf, "(*~$*)i"), -- { "vprintf", printf_wrapper, "($*)i", NULL }, -- { "vsprintf", sprintf_wrapper, "($$*)i", NULL }, -- { "vsnprintf", snprintf_wrapper, "(*~$*)i", NULL }, -+ { "vprintf", printf_wrapper, "($*)i", NULL, 0 }, -+ { "vsprintf", sprintf_wrapper, "($$*)i", NULL, 0 }, -+ { "vsnprintf", snprintf_wrapper, "(*~$*)i", NULL, 0 }, - REG_NATIVE_FUNC(puts, "($)i"), - REG_NATIVE_FUNC(putchar, "(i)i"), - REG_NATIVE_FUNC(memcmp, "(**~)i"), -diff --git a/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c b/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c -index f7dfea0b..c01e80a9 100644 ---- a/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c -+++ b/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c -@@ -2269,7 +2269,7 @@ wasi_sched_yield(wasm_exec_env_t exec_env) - - /* clang-format off */ - #define REG_NATIVE_FUNC(func_name, signature) \ -- { #func_name, wasi_##func_name, signature, NULL } -+ { #func_name, wasi_##func_name, signature, NULL, 0 } - /* clang-format on */ - - static NativeSymbol native_symbols_libc_wasi[] = { -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/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 9dc40dc8e5..ce9583dace 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -36,7 +36,7 @@ XRPL_FIX (IncludeKeyletFields, Supported::no, VoteBehavior::DefaultNo XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo) -XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo) +XRPL_FIX (PriceOracleOrder, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (AMMClawbackRounding, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo) diff --git a/src/libxrpl/net/HTTPClient.cpp b/src/libxrpl/net/HTTPClient.cpp index 964be32dd8..74b8b61ca6 100644 --- a/src/libxrpl/net/HTTPClient.cpp +++ b/src/libxrpl/net/HTTPClient.cpp @@ -383,7 +383,7 @@ public: static boost::regex reStatus{ "\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK static boost::regex reSize{ - "\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'"}; + "\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'", boost::regex::icase}; static boost::regex reBody{"\\`.*\\r\\n\\r\\n(.*)\\'"}; boost::smatch smMatch; diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index ba3d379219..4fe0a62e3b 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -19,11 +19,203 @@ #include +#include +#include +#include + #include +#include +#include +#include +#include +#include +#include namespace ripple { namespace test { +struct FeeSettingsFields +{ + std::optional baseFee = std::nullopt; + std::optional reserveBase = std::nullopt; + std::optional reserveIncrement = std::nullopt; + std::optional referenceFeeUnits = std::nullopt; + std::optional baseFeeDrops = std::nullopt; + std::optional reserveBaseDrops = std::nullopt; + std::optional reserveIncrementDrops = std::nullopt; +}; + +STTx +createFeeTx( + Rules const& rules, + std::uint32_t seq, + FeeSettingsFields const& fields) +{ + auto fill = [&](auto& obj) { + obj.setAccountID(sfAccount, AccountID()); + obj.setFieldU32(sfLedgerSequence, seq); + + if (rules.enabled(featureXRPFees)) + { + // New XRPFees format - all three fields are REQUIRED + obj.setFieldAmount( + sfBaseFeeDrops, + fields.baseFeeDrops ? *fields.baseFeeDrops : XRPAmount{0}); + obj.setFieldAmount( + sfReserveBaseDrops, + fields.reserveBaseDrops ? *fields.reserveBaseDrops + : XRPAmount{0}); + obj.setFieldAmount( + sfReserveIncrementDrops, + fields.reserveIncrementDrops ? *fields.reserveIncrementDrops + : XRPAmount{0}); + } + else + { + // Legacy format - all four fields are REQUIRED + obj.setFieldU64(sfBaseFee, fields.baseFee ? *fields.baseFee : 0); + obj.setFieldU32( + sfReserveBase, fields.reserveBase ? *fields.reserveBase : 0); + obj.setFieldU32( + sfReserveIncrement, + fields.reserveIncrement ? *fields.reserveIncrement : 0); + obj.setFieldU32( + sfReferenceFeeUnits, + fields.referenceFeeUnits ? *fields.referenceFeeUnits : 0); + } + }; + return STTx(ttFEE, fill); +} + +STTx +createInvalidFeeTx( + Rules const& rules, + std::uint32_t seq, + bool missingRequiredFields = true, + bool wrongFeatureFields = false, + std::uint32_t uniqueValue = 42) +{ + auto fill = [&](auto& obj) { + obj.setAccountID(sfAccount, AccountID()); + obj.setFieldU32(sfLedgerSequence, seq); + + if (wrongFeatureFields) + { + if (rules.enabled(featureXRPFees)) + { + obj.setFieldU64(sfBaseFee, 10 + uniqueValue); + obj.setFieldU32(sfReserveBase, 200000); + obj.setFieldU32(sfReserveIncrement, 50000); + obj.setFieldU32(sfReferenceFeeUnits, 10); + } + else + { + obj.setFieldAmount(sfBaseFeeDrops, XRPAmount{10 + uniqueValue}); + obj.setFieldAmount(sfReserveBaseDrops, XRPAmount{200000}); + obj.setFieldAmount(sfReserveIncrementDrops, XRPAmount{50000}); + } + } + else if (!missingRequiredFields) + { + // Create valid transaction (all required fields present) + if (rules.enabled(featureXRPFees)) + { + obj.setFieldAmount(sfBaseFeeDrops, XRPAmount{10 + uniqueValue}); + obj.setFieldAmount(sfReserveBaseDrops, XRPAmount{200000}); + obj.setFieldAmount(sfReserveIncrementDrops, XRPAmount{50000}); + } + else + { + obj.setFieldU64(sfBaseFee, 10 + uniqueValue); + obj.setFieldU32(sfReserveBase, 200000); + obj.setFieldU32(sfReserveIncrement, 50000); + obj.setFieldU32(sfReferenceFeeUnits, 10); + } + } + // If missingRequiredFields is true, we don't add the required fields + // (default behavior) + }; + return STTx(ttFEE, fill); +} + +bool +applyFeeAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx) +{ + auto const res = + apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal); + return res.ter == tesSUCCESS; +} + +bool +verifyFeeObject( + std::shared_ptr const& ledger, + Rules const& rules, + FeeSettingsFields const& expected) +{ + auto const feeObject = ledger->read(keylet::fees()); + if (!feeObject) + return false; + + auto checkEquality = [&](auto const& field, auto const& expected) { + if (!feeObject->isFieldPresent(field)) + return false; + return feeObject->at(field) == expected; + }; + + if (rules.enabled(featureXRPFees)) + { + if (feeObject->isFieldPresent(sfBaseFee) || + feeObject->isFieldPresent(sfReserveBase) || + feeObject->isFieldPresent(sfReserveIncrement) || + feeObject->isFieldPresent(sfReferenceFeeUnits)) + return false; + + if (!checkEquality( + sfBaseFeeDrops, expected.baseFeeDrops.value_or(XRPAmount{0}))) + return false; + if (!checkEquality( + sfReserveBaseDrops, + expected.reserveBaseDrops.value_or(XRPAmount{0}))) + return false; + if (!checkEquality( + sfReserveIncrementDrops, + expected.reserveIncrementDrops.value_or(XRPAmount{0}))) + return false; + } + else + { + if (feeObject->isFieldPresent(sfBaseFeeDrops) || + feeObject->isFieldPresent(sfReserveBaseDrops) || + feeObject->isFieldPresent(sfReserveIncrementDrops)) + return false; + + // Read sfBaseFee as a hex string and compare to expected.baseFee + if (!checkEquality(sfBaseFee, expected.baseFee)) + return false; + if (!checkEquality(sfReserveBase, expected.reserveBase)) + return false; + if (!checkEquality(sfReserveIncrement, expected.reserveIncrement)) + return false; + if (!checkEquality(sfReferenceFeeUnits, expected.referenceFeeUnits)) + return false; + } + + return true; +} + +std::vector +getTxs(std::shared_ptr const& txSet) +{ + std::vector txs; + for (auto i = txSet->begin(); i != txSet->end(); ++i) + { + auto const data = i->slice(); + auto serialIter = SerialIter(data); + txs.push_back(STTx(serialIter)); + } + return txs; +}; + class FeeVote_test : public beast::unit_test::suite { void @@ -93,10 +285,517 @@ class FeeVote_test : public beast::unit_test::suite } } + void + testBasic() + { + testcase("Basic SetFee transaction"); + + // Test with XRPFees disabled (legacy format) + { + jtx::Env env(*this, jtx::testable_amendments() - featureXRPFees); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + // Test successful fee transaction with legacy fields + + FeeSettingsFields fields{ + .baseFee = 10, + .reserveBase = 200000, + .reserveIncrement = 50000, + .referenceFeeUnits = 10}; + auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields); + + OpenView accum(ledger.get()); + BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx)); + accum.apply(*ledger); + + // Verify fee object was created/updated correctly + BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields)); + } + + // Test with XRPFees enabled (new format) + { + jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + FeeSettingsFields fields{ + .baseFeeDrops = XRPAmount{10}, + .reserveBaseDrops = XRPAmount{200000}, + .reserveIncrementDrops = XRPAmount{50000}}; + // Test successful fee transaction with new fields + auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields); + + OpenView accum(ledger.get()); + BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx)); + accum.apply(*ledger); + + // Verify fee object was created/updated correctly + BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields)); + } + } + + void + testTransactionValidation() + { + testcase("Fee Transaction Validation"); + + { + jtx::Env env(*this, jtx::testable_amendments() - featureXRPFees); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + // Test transaction with missing required legacy fields + auto invalidTx = createInvalidFeeTx( + ledger->rules(), ledger->seq(), true, false, 1); + OpenView accum(ledger.get()); + BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx)); + + // Test transaction with new format fields when XRPFees is disabled + auto disallowedTx = createInvalidFeeTx( + ledger->rules(), ledger->seq(), false, true, 2); + BEAST_EXPECT(!applyFeeAndTestResult(env, accum, disallowedTx)); + } + + { + jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + // Test transaction with missing required new fields + auto invalidTx = createInvalidFeeTx( + ledger->rules(), ledger->seq(), true, false, 3); + OpenView accum(ledger.get()); + BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx)); + + // Test transaction with legacy fields when XRPFees is enabled + auto disallowedTx = createInvalidFeeTx( + ledger->rules(), ledger->seq(), false, true, 4); + BEAST_EXPECT(!applyFeeAndTestResult(env, accum, disallowedTx)); + } + } + + void + testPseudoTransactionProperties() + { + testcase("Pseudo Transaction Properties"); + + jtx::Env env(*this, jtx::testable_amendments()); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + auto feeTx = createFeeTx( + ledger->rules(), + ledger->seq(), + {.baseFeeDrops = XRPAmount{10}, + .reserveBaseDrops = XRPAmount{200000}, + .reserveIncrementDrops = XRPAmount{50000}}); + + // Verify pseudo-transaction properties + BEAST_EXPECT(feeTx.getAccountID(sfAccount) == AccountID()); + BEAST_EXPECT(feeTx.getFieldAmount(sfFee) == XRPAmount{0}); + BEAST_EXPECT(feeTx.getSigningPubKey().empty()); + BEAST_EXPECT(feeTx.getSignature().empty()); + BEAST_EXPECT(!feeTx.isFieldPresent(sfSigners)); + BEAST_EXPECT(feeTx.getFieldU32(sfSequence) == 0); + BEAST_EXPECT(!feeTx.isFieldPresent(sfPreviousTxnID)); + + // But can be applied to a closed ledger + { + OpenView closedAccum(ledger.get()); + BEAST_EXPECT(applyFeeAndTestResult(env, closedAccum, feeTx)); + } + } + + void + testMultipleFeeUpdates() + { + testcase("Multiple Fee Updates"); + + jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + FeeSettingsFields fields1{ + .baseFeeDrops = XRPAmount{10}, + .reserveBaseDrops = XRPAmount{200000}, + .reserveIncrementDrops = XRPAmount{50000}}; + auto feeTx1 = createFeeTx(ledger->rules(), ledger->seq(), fields1); + + { + OpenView accum(ledger.get()); + BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx1)); + accum.apply(*ledger); + } + + BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields1)); + + // Apply second fee transaction with different values + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + FeeSettingsFields fields2{ + .baseFeeDrops = XRPAmount{20}, + .reserveBaseDrops = XRPAmount{300000}, + .reserveIncrementDrops = XRPAmount{75000}}; + auto feeTx2 = createFeeTx(ledger->rules(), ledger->seq(), fields2); + + { + OpenView accum(ledger.get()); + BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx2)); + accum.apply(*ledger); + } + + // Verify second update overwrote the first + BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields2)); + } + + void + testWrongLedgerSequence() + { + testcase("Wrong Ledger Sequence"); + + jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + // Test transaction with wrong ledger sequence + auto feeTx = createFeeTx( + ledger->rules(), + ledger->seq() + 5, // Wrong sequence (should be ledger->seq()) + {.baseFeeDrops = XRPAmount{10}, + .reserveBaseDrops = XRPAmount{200000}, + .reserveIncrementDrops = XRPAmount{50000}}); + + OpenView accum(ledger.get()); + + // The transaction should still succeed as long as other fields are + // valid + // The ledger sequence field is only used for informational purposes + BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx)); + } + + void + testPartialFieldUpdates() + { + testcase("Partial Field Updates"); + + jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + FeeSettingsFields fields1{ + .baseFeeDrops = XRPAmount{10}, + .reserveBaseDrops = XRPAmount{200000}, + .reserveIncrementDrops = XRPAmount{50000}}; + auto feeTx1 = createFeeTx(ledger->rules(), ledger->seq(), fields1); + + { + OpenView accum(ledger.get()); + BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx1)); + accum.apply(*ledger); + } + + BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields1)); + + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + // Apply partial update (only some fields) + FeeSettingsFields fields2{ + .baseFeeDrops = XRPAmount{20}, + .reserveBaseDrops = XRPAmount{200000}}; + auto feeTx2 = createFeeTx(ledger->rules(), ledger->seq(), fields2); + + { + OpenView accum(ledger.get()); + BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx2)); + accum.apply(*ledger); + } + + // Verify the partial update worked + BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields2)); + } + + void + testSingleInvalidTransaction() + { + testcase("Single Invalid Transaction"); + + jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + + // Test invalid transaction with non-zero account - this should fail + // validation + auto invalidTx = STTx(ttFEE, [&](auto& obj) { + obj.setAccountID( + sfAccount, + AccountID(1)); // Should be zero (this makes it invalid) + obj.setFieldU32(sfLedgerSequence, ledger->seq()); + obj.setFieldAmount(sfBaseFeeDrops, XRPAmount{10}); + obj.setFieldAmount(sfReserveBaseDrops, XRPAmount{200000}); + obj.setFieldAmount(sfReserveIncrementDrops, XRPAmount{50000}); + }); + + OpenView accum(ledger.get()); + BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx)); + } + + void + testDoValidation() + { + testcase("doValidation"); + + using namespace jtx; + + FeeSetup setup; + setup.reference_fee = 42; + setup.account_reserve = 1234567; + setup.owner_reserve = 7654321; + + // Test with XRPFees enabled + { + Env env(*this, testable_amendments() | featureXRPFees); + auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote")); + + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + auto sec = randomSecretKey(); + auto pub = derivePublicKey(KeyType::secp256k1, sec); + + auto val = std::make_shared( + env.app().timeKeeper().now(), + pub, + sec, + calcNodeID(pub), + [](STValidation& v) { + v.setFieldU32(sfLedgerSequence, 12345); + }); + + // Use the current ledger's fees as the "current" fees for + // doValidation + auto const& currentFees = ledger->fees(); + + feeVote->doValidation(currentFees, ledger->rules(), *val); + + BEAST_EXPECT(val->isFieldPresent(sfBaseFeeDrops)); + BEAST_EXPECT( + val->getFieldAmount(sfBaseFeeDrops) == + XRPAmount(setup.reference_fee)); + } + + // Test with XRPFees disabled (legacy format) + { + Env env(*this, testable_amendments() - featureXRPFees); + auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote")); + + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + auto sec = randomSecretKey(); + auto pub = derivePublicKey(KeyType::secp256k1, sec); + + auto val = std::make_shared( + env.app().timeKeeper().now(), + pub, + sec, + calcNodeID(pub), + [](STValidation& v) { + v.setFieldU32(sfLedgerSequence, 12345); + }); + + auto const& currentFees = ledger->fees(); + + feeVote->doValidation(currentFees, ledger->rules(), *val); + + // In legacy mode, should vote using legacy fields + BEAST_EXPECT(val->isFieldPresent(sfBaseFee)); + BEAST_EXPECT(val->getFieldU64(sfBaseFee) == setup.reference_fee); + } + } + + void + testDoVoting() + { + testcase("doVoting"); + + using namespace jtx; + + FeeSetup setup; + setup.reference_fee = 42; + setup.account_reserve = 1234567; + setup.owner_reserve = 7654321; + + Env env(*this, testable_amendments() | featureXRPFees); + + // establish what the current fees are + BEAST_EXPECT( + env.current()->fees().base == XRPAmount{UNIT_TEST_REFERENCE_FEE}); + BEAST_EXPECT(env.current()->fees().reserve == XRPAmount{200'000'000}); + BEAST_EXPECT(env.current()->fees().increment == XRPAmount{50'000'000}); + + auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote")); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // doVoting requires a flag ledger (every 256th ledger) + // We need to create a ledger at sequence 256 to make it a flag ledger + for (int i = 0; i < 256 - 1; ++i) + { + ledger = std::make_shared( + *ledger, env.app().timeKeeper().closeTime()); + } + BEAST_EXPECT(ledger->isFlagLedger()); + + // Create some mock validations with fee votes + std::vector> validations; + + for (int i = 0; i < 5; i++) + { + auto sec = randomSecretKey(); + auto pub = derivePublicKey(KeyType::secp256k1, sec); + + auto val = std::make_shared( + env.app().timeKeeper().now(), + pub, + sec, + calcNodeID(pub), + [&](STValidation& v) { + v.setFieldU32(sfLedgerSequence, ledger->seq()); + // Vote for different fees than current + v.setFieldAmount( + sfBaseFeeDrops, XRPAmount{setup.reference_fee}); + v.setFieldAmount( + sfReserveBaseDrops, XRPAmount{setup.account_reserve}); + v.setFieldAmount( + sfReserveIncrementDrops, + XRPAmount{setup.owner_reserve}); + }); + if (i % 2) + val->setTrusted(); + validations.push_back(val); + } + + auto txSet = std::make_shared( + SHAMapType::TRANSACTION, env.app().getNodeFamily()); + + // This should not throw since we have a flag ledger + feeVote->doVoting(ledger, validations, txSet); + + auto const txs = getTxs(txSet); + BEAST_EXPECT(txs.size() == 1); + auto const& feeTx = txs[0]; + + BEAST_EXPECT(feeTx.getTxnType() == ttFEE); + + BEAST_EXPECT(feeTx.getAccountID(sfAccount) == AccountID()); + BEAST_EXPECT(feeTx.getFieldU32(sfLedgerSequence) == ledger->seq() + 1); + + BEAST_EXPECT(feeTx.isFieldPresent(sfBaseFeeDrops)); + BEAST_EXPECT(feeTx.isFieldPresent(sfReserveBaseDrops)); + BEAST_EXPECT(feeTx.isFieldPresent(sfReserveIncrementDrops)); + + // The legacy fields should NOT be present + BEAST_EXPECT(!feeTx.isFieldPresent(sfBaseFee)); + BEAST_EXPECT(!feeTx.isFieldPresent(sfReserveBase)); + BEAST_EXPECT(!feeTx.isFieldPresent(sfReserveIncrement)); + BEAST_EXPECT(!feeTx.isFieldPresent(sfReferenceFeeUnits)); + + // Check the values + BEAST_EXPECT( + feeTx.getFieldAmount(sfBaseFeeDrops) == + XRPAmount{setup.reference_fee}); + BEAST_EXPECT( + feeTx.getFieldAmount(sfReserveBaseDrops) == + XRPAmount{setup.account_reserve}); + BEAST_EXPECT( + feeTx.getFieldAmount(sfReserveIncrementDrops) == + XRPAmount{setup.owner_reserve}); + } + void run() override { testSetup(); + testBasic(); + testTransactionValidation(); + testPseudoTransactionProperties(); + testMultipleFeeUpdates(); + testWrongLedgerSequence(); + testPartialFieldUpdates(); + testSingleInvalidTransaction(); + testDoValidation(); + testDoVoting(); } }; diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index a3b62bd4f7..2b004c3b52 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -768,6 +768,24 @@ private: expectUntrusted(lists.at(7)); expectTrusted(lists.at(2)); + // try empty or mangled manifest + checkResult( + trustedKeys->applyLists( + "", version, {{blob7, sig7, {}}, {blob6, sig6, {}}}, siteUri), + publisherPublic, + ListDisposition::invalid, + ListDisposition::invalid); + + checkResult( + trustedKeys->applyLists( + base64_encode("not a manifest"), + version, + {{blob7, sig7, {}}, {blob6, sig6, {}}}, + siteUri), + publisherPublic, + ListDisposition::invalid, + ListDisposition::invalid); + // do not use list from untrusted publisher auto const untrustedManifest = base64_encode(makeManifestString( randomMasterKey(), diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index 68c6fa6cb3..f97283c955 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -12,3 +12,5 @@ xrpl_add_test(basics) target_link_libraries(xrpl.test.basics PRIVATE xrpl.imports.test) xrpl_add_test(crypto) target_link_libraries(xrpl.test.crypto PRIVATE xrpl.imports.test) +xrpl_add_test(net) +target_link_libraries(xrpl.test.net PRIVATE xrpl.imports.test) diff --git a/src/tests/libxrpl/net/HTTPClient.cpp b/src/tests/libxrpl/net/HTTPClient.cpp new file mode 100644 index 0000000000..4d50c47220 --- /dev/null +++ b/src/tests/libxrpl/net/HTTPClient.cpp @@ -0,0 +1,346 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace ripple; + +namespace { + +// Simple HTTP server using Beast for testing +class TestHTTPServer +{ +private: + boost::asio::io_context ioc_; + boost::asio::ip::tcp::acceptor acceptor_; + boost::asio::ip::tcp::endpoint endpoint_; + std::atomic running_{true}; + unsigned short port_; + + // Custom headers to return + std::map custom_headers_; + std::string response_body_; + unsigned int status_code_{200}; + +public: + TestHTTPServer() : acceptor_(ioc_), port_(0) + { + // Bind to any available port + endpoint_ = {boost::asio::ip::tcp::v4(), 0}; + acceptor_.open(endpoint_.protocol()); + acceptor_.set_option(boost::asio::socket_base::reuse_address(true)); + acceptor_.bind(endpoint_); + acceptor_.listen(); + + // Get the actual port that was assigned + port_ = acceptor_.local_endpoint().port(); + + accept(); + } + + ~TestHTTPServer() + { + stop(); + } + + boost::asio::io_context& + ioc() + { + return ioc_; + } + + unsigned short + port() const + { + return port_; + } + + void + setHeader(std::string const& name, std::string const& value) + { + custom_headers_[name] = value; + } + + void + setResponseBody(std::string const& body) + { + response_body_ = body; + } + + void + setStatusCode(unsigned int code) + { + status_code_ = code; + } + +private: + void + stop() + { + running_ = false; + acceptor_.close(); + } + + void + accept() + { + if (!running_) + return; + + acceptor_.async_accept( + ioc_, + endpoint_, + [&](boost::system::error_code const& error, + boost::asio::ip::tcp::socket peer) { + if (!running_) + return; + + if (!error) + { + handleConnection(std::move(peer)); + } + }); + } + + void + handleConnection(boost::asio::ip::tcp::socket socket) + { + try + { + // Read the HTTP request + boost::beast::flat_buffer buffer; + boost::beast::http::request req; + boost::beast::http::read(socket, buffer, req); + + // Create response + boost::beast::http::response res; + res.version(req.version()); + res.result(status_code_); + res.set(boost::beast::http::field::server, "TestServer"); + + // Add custom headers + for (auto const& [name, value] : custom_headers_) + { + res.set(name, value); + } + + // Set body and prepare payload first + res.body() = response_body_; + res.prepare_payload(); + + // Override Content-Length with custom headers after prepare_payload + // This allows us to test case-insensitive header parsing + for (auto const& [name, value] : custom_headers_) + { + if (boost::iequals(name, "Content-Length")) + { + res.erase(boost::beast::http::field::content_length); + res.set(name, value); + } + } + + // Send response + boost::beast::http::write(socket, res); + + // Shutdown socket gracefully + boost::system::error_code ec; + socket.shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); + } + catch (std::exception const&) + { + // Connection handling errors are expected + } + + if (running_) + accept(); + } +}; + +// Helper function to run HTTP client test +bool +runHTTPTest( + TestHTTPServer& server, + std::string const& path, + std::atomic& completed, + std::atomic& result_status, + std::string& result_data, + boost::system::error_code& result_error) +{ + // Create a null journal for testing + beast::Journal j{beast::Journal::getNullSink()}; + + // Initialize HTTPClient SSL context + HTTPClient::initializeSSLContext("", "", false, j); + + HTTPClient::get( + false, // no SSL + server.ioc(), + "127.0.0.1", + server.port(), + path, + 1024, // max response size + std::chrono::seconds(5), + [&](boost::system::error_code const& ec, + int status, + std::string const& data) -> bool { + result_error = ec; + result_status = status; + result_data = data; + completed = true; + return false; // don't retry + }, + j); + + // Run the IO context until completion + auto start = std::chrono::steady_clock::now(); + while (!completed && + std::chrono::steady_clock::now() - start < std::chrono::seconds(10)) + { + if (server.ioc().run_one() == 0) + { + break; + } + } + + return completed; +} + +} // anonymous namespace + +TEST_CASE("HTTPClient case insensitive Content-Length") +{ + // Test different cases of Content-Length header + std::vector header_cases = { + "Content-Length", // Standard case + "content-length", // Lowercase - this tests the regex icase fix + "CONTENT-LENGTH", // Uppercase + "Content-length", // Mixed case + "content-Length" // Mixed case 2 + }; + + for (auto const& header_name : header_cases) + { + TestHTTPServer server; + std::string test_body = "Hello World!"; + server.setResponseBody(test_body); + server.setHeader(header_name, std::to_string(test_body.size())); + + std::atomic completed{false}; + std::atomic result_status{0}; + std::string result_data; + boost::system::error_code result_error; + + bool test_completed = runHTTPTest( + server, + "/test", + completed, + result_status, + result_data, + result_error); + + // Verify results + CHECK(test_completed); + CHECK(!result_error); + CHECK(result_status == 200); + CHECK(result_data == test_body); + } +} + +TEST_CASE("HTTPClient basic HTTP request") +{ + TestHTTPServer server; + std::string test_body = "Test response body"; + server.setResponseBody(test_body); + server.setHeader("Content-Type", "text/plain"); + + std::atomic completed{false}; + std::atomic result_status{0}; + std::string result_data; + boost::system::error_code result_error; + + bool test_completed = runHTTPTest( + server, "/basic", completed, result_status, result_data, result_error); + + CHECK(test_completed); + CHECK(!result_error); + CHECK(result_status == 200); + CHECK(result_data == test_body); +} + +TEST_CASE("HTTPClient empty response") +{ + TestHTTPServer server; + server.setResponseBody(""); // Empty body + server.setHeader("Content-Length", "0"); + + std::atomic completed{false}; + std::atomic result_status{0}; + std::string result_data; + boost::system::error_code result_error; + + bool test_completed = runHTTPTest( + server, "/empty", completed, result_status, result_data, result_error); + + CHECK(test_completed); + CHECK(!result_error); + CHECK(result_status == 200); + CHECK(result_data.empty()); +} + +TEST_CASE("HTTPClient different status codes") +{ + std::vector status_codes = {200, 404, 500}; + + for (auto status : status_codes) + { + TestHTTPServer server; + server.setStatusCode(status); + server.setResponseBody("Status " + std::to_string(status)); + + std::atomic completed{false}; + std::atomic result_status{0}; + std::string result_data; + boost::system::error_code result_error; + + bool test_completed = runHTTPTest( + server, + "/status", + completed, + result_status, + result_data, + result_error); + + CHECK(test_completed); + CHECK(!result_error); + CHECK(result_status == static_cast(status)); + } +} diff --git a/src/tests/libxrpl/net/main.cpp b/src/tests/libxrpl/net/main.cpp new file mode 100644 index 0000000000..be9fc14bbf --- /dev/null +++ b/src/tests/libxrpl/net/main.cpp @@ -0,0 +1,21 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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. +*/ +//============================================================================== + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include diff --git a/src/xrpld/app/misc/ValidatorList.h b/src/xrpld/app/misc/ValidatorList.h index 1f5d728824..9a2018cbd4 100644 --- a/src/xrpld/app/misc/ValidatorList.h +++ b/src/xrpld/app/misc/ValidatorList.h @@ -877,7 +877,7 @@ private: verify( lock_guard const&, Json::Value& list, - std::string const& manifest, + Manifest manifest, std::string const& blob, std::string const& signature); diff --git a/src/xrpld/app/misc/detail/ValidatorList.cpp b/src/xrpld/app/misc/detail/ValidatorList.cpp index 1ddb51c9dd..2b45cec3be 100644 --- a/src/xrpld/app/misc/detail/ValidatorList.cpp +++ b/src/xrpld/app/misc/detail/ValidatorList.cpp @@ -1149,21 +1149,33 @@ ValidatorList::applyList( Json::Value list; auto const& manifest = localManifest ? *localManifest : globalManifest; - auto [result, pubKeyOpt] = verify(lock, list, manifest, blob, signature); + auto m = deserializeManifest(base64_decode(manifest)); + if (!m) + { + JLOG(j_.warn()) << "UNL manifest cannot be deserialized"; + return PublisherListStats{ListDisposition::invalid}; + } + + auto [result, pubKeyOpt] = + verify(lock, list, std::move(*m), blob, signature); if (!pubKeyOpt) { - JLOG(j_.info()) << "ValidatorList::applyList unable to retrieve the " - "master public key from the verify function\n"; + JLOG(j_.warn()) + << "UNL manifest is signed with an unrecognized master public key"; return PublisherListStats{result}; } if (!publicKeyType(*pubKeyOpt)) - { - JLOG(j_.info()) << "ValidatorList::applyList Invalid Public Key type" - " retrieved from the verify function\n "; + { // LCOV_EXCL_START + // This is an impossible situation because we will never load an + // invalid public key type (see checks in `ValidatorList::load`) however + // we can only arrive here if the key used by the manifest matched one of + // the loaded keys + UNREACHABLE( + "ripple::ValidatorList::applyList : invalid public key type"); return PublisherListStats{result}; - } + } // LCOV_EXCL_STOP PublicKey pubKey = *pubKeyOpt; if (result > ListDisposition::pending) @@ -1356,19 +1368,17 @@ std::pair> ValidatorList::verify( ValidatorList::lock_guard const& lock, Json::Value& list, - std::string const& manifest, + Manifest manifest, std::string const& blob, std::string const& signature) { - auto m = deserializeManifest(base64_decode(manifest)); - - if (!m || !publisherLists_.count(m->masterKey)) + if (!publisherLists_.count(manifest.masterKey)) return {ListDisposition::untrusted, {}}; - PublicKey masterPubKey = m->masterKey; - auto const revoked = m->revoked(); + PublicKey masterPubKey = manifest.masterKey; + auto const revoked = manifest.revoked(); - auto const result = publisherManifests_.applyManifest(std::move(*m)); + auto const result = publisherManifests_.applyManifest(std::move(manifest)); if (revoked && result == ManifestDisposition::accepted) { @@ -1796,7 +1806,7 @@ ValidatorList::getAvailable( if (!keyBlob || !publicKeyType(makeSlice(*keyBlob))) { - JLOG(j_.info()) << "Invalid requested validator list publisher key: " + JLOG(j_.warn()) << "Invalid requested validator list publisher key: " << pubKey; return {}; } diff --git a/src/xrpld/core/detail/semaphore.h b/src/xrpld/core/detail/semaphore.h index 3b64265bb1..fbeb79c66a 100644 --- a/src/xrpld/core/detail/semaphore.h +++ b/src/xrpld/core/detail/semaphore.h @@ -17,6 +17,34 @@ */ //============================================================================== +/** + * + * TODO: Remove ripple::basic_semaphore (and this file) and use + * std::counting_semaphore. + * + * Background: + * - PR: https://github.com/XRPLF/rippled/pull/5512/files + * - std::counting_semaphore had a bug fixed in both GCC and Clang: + * * GCC PR 104928: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104928 + * * LLVM PR 79265: https://github.com/llvm/llvm-project/pull/79265 + * + * GCC: + * According to GCC Bugzilla PR104928 + * (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104928#c15), the fix is + * scheduled for inclusion in GCC 16.0 (see comment #15, Target + * Milestone: 16.0). It is not included in GCC 14.x or earlier, and there is no + * indication that it will be backported to GCC 13.x or 14.x branches. + * + * Clang: + * The fix for is included in Clang 19.1.0+ + * + * Once the minimum compiler version is updated to > GCC 16.0 or Clang 19.1.0, + * we can remove this file. + * + * WARNING: Avoid using std::counting_semaphore until the minimum compiler + * version is updated. + */ + #ifndef RIPPLE_CORE_SEMAPHORE_H_INCLUDED #define RIPPLE_CORE_SEMAPHORE_H_INCLUDED