From 7a14f7d2a5b945762144cbce749f9e5db3b34f29 Mon Sep 17 00:00:00 2001 From: crypto523 Date: Tue, 26 Mar 2024 15:27:15 +0100 Subject: [PATCH] Forkless state transition with upgradable WASM executor (#1716) Closes https://github.com/FuelLabs/fuel-core/issues/1547 ## Change overview The change adds the WASM version of the `fuel_core_executor::Executor` and the upgradable executor that works with native(std) and WASM(no-std) versions of the executor. Currently, it uses either the WASM or the std version. But in the follow-up PRs it will decide which version to use based on the block header. For now, this behavior is controlled by the "wasm-executor" feature. The CI runs tests two times, one with the WASM executor and the second with std, to verify that behaviors are the same. ## Details on how WASM integration is done The `fuel-core-upgradable-executor` uses the `wasmtime` library to compile and run the WASM bytecode. The `wastime::Engine` allows customization of how the `wastime::Module` compiles the bytecode. For simplicity, we are using the default configuration that works on all platforms for now, but later, it is possible to do more specific compilation. We are using `wastime::Linker` to add host functions that can be called from the WASM runtime via `extern "C"` API: ```rust #[link(wasm_import_module = "host_v0")] extern "C" { /// Returns the size of the value from the storage. pub(crate) fn storage_size_of_value( key_ptr: Ptr32<[u8]>, key_len: u32, column: u32, ) -> u64; /// Returns the value from the storage. pub(crate) fn storage_get( key_ptr: Ptr32<[u8]>, key_len: u32, column: u32, out_ptr: Ptr32Mut<[u8]>, out_len: u32, ) -> ReturnResult; } ``` The host functions are closures that capture data required for the work, such as the storage or relayer. Host functions have access to the `ExecutionState`, which accumulates intermediate data. Data is passed between the host and runtime through memory, where one side gives the pointer where data should be stored, and another side writes the memory using this pointer and the size. The host is not responsible for allocating and deallocating memory; the memory management is done within WASM runtime. Currently, when we return something from the runtime to the host, we "leak" it to avoid deallocation. It allows the host to read the value from the memory. Side changes: - The coinbase contract id is not a part of the `Config` anymore. It is part of the `Components` and seats along with the `gas_price` because it is defined by the block producer and can always be changed on the fly. - The usage of `anyhow::Error` in the `ExecutorError` was replaced by `String` because it doesn't implement `serde` ser/des. - The `ExecutionOptions`, `WriteOperation`, `ExecutionResult`, `TransactionExecutionResult`, `TransactionExecutionStatus`, `ExecutorError`, and `TransactionValidityError` support `serde` ser/des. - The `fuel-core-executor` only provides the `ExecutionInstance` without `Executor`. The logic of the `Executor` was moved to the `fuel-core-upgradable-executor`. --------- Co-authored-by: Voxelot --- .github/workflows/ci.yml | 4 + CHANGELOG.md | 1 + Cargo.lock | 490 ++++++++++++++- Cargo.toml | 9 +- ci_checks.sh | 2 + crates/database/src/lib.rs | 2 +- crates/fuel-core/Cargo.toml | 5 + crates/fuel-core/src/executor.rs | 545 ++++++----------- .../src/graphql_api/worker_service.rs | 11 +- crates/fuel-core/src/service/adapters.rs | 10 +- .../src/service/adapters/executor.rs | 2 +- .../src/service/adapters/producer.rs | 2 + crates/fuel-core/src/service/sub_services.rs | 7 +- crates/services/executor/Cargo.toml | 1 + crates/services/executor/src/config.rs | 16 - crates/services/executor/src/executor.rs | 256 ++------ crates/services/executor/src/lib.rs | 6 - crates/services/p2p/Cargo.toml | 2 +- .../services/producer/src/block_producer.rs | 2 + crates/services/txpool/src/mock_db.rs | 10 +- .../services/upgradable-executor/Cargo.toml | 46 ++ crates/services/upgradable-executor/build.rs | 68 +++ .../upgradable-executor/src/config.rs | 24 + .../upgradable-executor/src/executor.rs | 280 +++++++++ .../upgradable-executor/src/instance.rs | 574 ++++++++++++++++++ .../services/upgradable-executor/src/lib.rs | 38 ++ .../wasm-executor/Cargo.toml | 31 + .../wasm-executor/src/ext.rs | 281 +++++++++ .../wasm-executor/src/lib.rs | 7 + .../wasm-executor/src/main.rs | 116 ++++ .../wasm-executor/src/relayer.rs | 18 + .../wasm-executor/src/storage.rs | 35 ++ .../wasm-executor/src/tx_source.rs | 19 + .../wasm-executor/src/utils.rs | 73 +++ crates/storage/Cargo.toml | 3 + crates/storage/src/column.rs | 1 + crates/storage/src/kv_store.rs | 2 +- crates/storage/src/lib.rs | 2 +- crates/types/src/blockchain/header.rs | 1 + crates/types/src/blockchain/primitives.rs | 2 +- crates/types/src/services.rs | 1 + crates/types/src/services/block_producer.rs | 8 +- crates/types/src/services/executor.rs | 36 +- deployment/Dockerfile | 2 +- deployment/e2e-client.Dockerfile | 2 +- tests/Cargo.toml | 1 + 46 files changed, 2394 insertions(+), 660 deletions(-) delete mode 100644 crates/services/executor/src/config.rs create mode 100644 crates/services/upgradable-executor/Cargo.toml create mode 100644 crates/services/upgradable-executor/build.rs create mode 100644 crates/services/upgradable-executor/src/config.rs create mode 100644 crates/services/upgradable-executor/src/executor.rs create mode 100644 crates/services/upgradable-executor/src/instance.rs create mode 100644 crates/services/upgradable-executor/src/lib.rs create mode 100644 crates/services/upgradable-executor/wasm-executor/Cargo.toml create mode 100644 crates/services/upgradable-executor/wasm-executor/src/ext.rs create mode 100644 crates/services/upgradable-executor/wasm-executor/src/lib.rs create mode 100644 crates/services/upgradable-executor/wasm-executor/src/main.rs create mode 100644 crates/services/upgradable-executor/wasm-executor/src/relayer.rs create mode 100644 crates/services/upgradable-executor/wasm-executor/src/storage.rs create mode 100644 crates/services/upgradable-executor/wasm-executor/src/tx_source.rs create mode 100644 crates/services/upgradable-executor/wasm-executor/src/utils.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0de6f639..db13a359 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,8 @@ jobs: strategy: matrix: include: + - command: clippy + args: -p fuel-core-wasm-executor --target wasm32-unknown-unknown --no-default-features - command: clippy args: --all-targets --all-features - command: check @@ -122,6 +124,8 @@ jobs: args: --all-features --workspace --no-deps - command: make args: check --locked + - command: test + args: --workspace - command: test args: --all-features --workspace - command: test diff --git a/CHANGELOG.md b/CHANGELOG.md index c1b8a369..3b6957bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Description of the upcoming release here. ### Added +- [#1716](https://github.com/FuelLabs/fuel-core/pull/1716): Added support of WASM state transition along with upgradable execution that works with native(std) and WASM(non-std) executors. The `fuel-core` now requires a `wasm32-unknown-unknown` target to build. - [#1770](https://github.com/FuelLabs/fuel-core/pull/1770): Add the new L1 event type for forced transactions. - [#1767](https://github.com/FuelLabs/fuel-core/pull/1767): Added consensus parameters version and state transition version to the `ApplicationHeader` to describe what was used to produce this block. - [#1760](https://github.com/FuelLabs/fuel-core/pull/1760): Added tests to verify that the network operates with a custom chain id and base asset id. diff --git a/Cargo.lock b/Cargo.lock index ae99b4d1..698d4aa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,6 +182,12 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arrayref" version = "0.3.7" @@ -773,6 +779,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.65.1" @@ -1431,6 +1446,115 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-bforest" +version = "0.105.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d5521e2abca66bbb1ddeecbb6f6965c79160352ae1579b39f8c86183895c24" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.105.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef40a4338a47506e832ac3e53f7f1375bc59351f049a8379ff736dd02565bd95" +dependencies = [ + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.14.3", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.105.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24cd5d85985c070f73dfca07521d09086362d1590105ba44b0932bf33513b61" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.105.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0584c4363e3aa0a3c7cb98a778fbd5326a3709f117849a727da081d4051726c" + +[[package]] +name = "cranelift-control" +version = "0.105.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25ecede098c6553fdba362a8e4c9ecb8d40138363bff47f9712db75be7f0571" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.105.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea081a42f25dc4c5b248b87efdd87dcd3842a1050a37524ec5391e6172058cb" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-frontend" +version = "0.105.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9796e712f5af797e247784f7518e6b0a83a8907d73d51526982d86ecb3a58b68" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.105.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a66ccad5782f15c80e9dd5af0df4acfe6e3eee98e8f7354a2e5c8ec3104bdd" + +[[package]] +name = "cranelift-native" +version = "0.105.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "285e80df1d9b79ded9775b285df68b920a277b84f88a7228d2f5bc31fcdc58eb" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.105.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4135b0ab01fd16aa8f8821196e9e2fe15953552ccaef8ba5153be0ced04ef757" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools 0.10.5", + "log", + "smallvec", + "wasmparser", + "wasmtime-types", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -1918,6 +2042,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs" version = "4.0.0" @@ -2590,6 +2724,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "1.9.0" @@ -2742,6 +2882,7 @@ dependencies = [ "fuel-core-trace", "fuel-core-txpool", "fuel-core-types", + "fuel-core-upgradable-executor", "futures", "hex", "hyper", @@ -2851,7 +2992,7 @@ dependencies = [ "rand", "serde", "serde_json", - "serde_with 3.7.0", + "serde_with", "strum 0.25.0", "tempfile", "tracing", @@ -2951,6 +3092,7 @@ dependencies = [ "fuel-core-types", "hex", "parking_lot", + "serde", "tracing", ] @@ -3035,7 +3177,7 @@ dependencies = [ "quick-protobuf-codec 0.3.1", "rand", "serde", - "serde_with 1.14.0", + "serde_with", "sha2 0.10.8", "thiserror", "tokio", @@ -3139,6 +3281,7 @@ dependencies = [ "impl-tools", "itertools 0.12.1", "mockall", + "num_enum", "paste", "postcard", "primitive-types", @@ -3255,6 +3398,32 @@ dependencies = [ "zeroize", ] +[[package]] +name = "fuel-core-upgradable-executor" +version = "0.23.0" +dependencies = [ + "anyhow", + "fuel-core-executor", + "fuel-core-storage", + "fuel-core-types", + "fuel-core-wasm-executor", + "lazy_static", + "postcard", + "wasmtime", +] + +[[package]] +name = "fuel-core-wasm-executor" +version = "0.23.0" +dependencies = [ + "anyhow", + "fuel-core-executor", + "fuel-core-storage", + "fuel-core-types", + "postcard", + "proptest", +] + [[package]] name = "fuel-crypto" version = "0.47.1" @@ -3597,6 +3766,11 @@ name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +dependencies = [ + "fallible-iterator", + "indexmap 2.2.5", + "stable_deref_trait", +] [[package]] name = "glob" @@ -4192,6 +4366,7 @@ checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown 0.14.3", + "serde", ] [[package]] @@ -4422,6 +4597,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.153" @@ -5131,6 +5312,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -5168,6 +5358,15 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "memfd" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +dependencies = [ + "rustix 0.38.31", +] + [[package]] name = "memmap2" version = "0.9.4" @@ -5177,6 +5376,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -5581,6 +5789,9 @@ version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ + "crc32fast", + "hashbrown 0.14.3", + "indexmap 2.2.5", "memchr", ] @@ -6326,6 +6537,15 @@ version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + [[package]] name = "publicsuffix" version = "2.2.3" @@ -6591,6 +6811,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + [[package]] name = "regex" version = "1.10.3" @@ -7241,16 +7474,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "serde", - "serde_with_macros 1.5.2", -] - [[package]] name = "serde_with" version = "3.7.0" @@ -7263,22 +7486,10 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "serde_with_macros 3.7.0", + "serde_with_macros", "time", ] -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling 0.13.4", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "serde_with_macros" version = "3.7.0" @@ -7450,6 +7661,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + [[package]] name = "smallvec" version = "1.13.1" @@ -7564,6 +7781,12 @@ dependencies = [ "der", ] +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -7826,6 +8049,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + [[package]] name = "tempfile" version = "3.10.1" @@ -8739,6 +8968,217 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-encoder" +version = "0.41.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972f97a5d8318f908dded23594188a90bcd09365986b1163e66d70170e5287ae" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasmparser" +version = "0.121.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" +dependencies = [ + "bitflags 2.4.2", + "indexmap 2.2.5", + "semver", +] + +[[package]] +name = "wasmtime" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8106d7d22d63d1bcb940e22dcc7b03e46f0fc8bfbaf2fd7b6cb8f448f9449774" +dependencies = [ + "anyhow", + "bincode", + "bumpalo", + "cfg-if", + "gimli", + "indexmap 2.2.5", + "libc", + "log", + "object", + "once_cell", + "paste", + "rayon", + "rustix 0.38.31", + "serde", + "serde_derive", + "serde_json", + "target-lexicon", + "wasmparser", + "wasmtime-cache", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-jit-icache-coherence", + "wasmtime-runtime", + "windows-sys 0.52.0", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0cf02cea951ace34ee3b0e64b7f446c3519d1c95ad75bc5330f405e275ee8f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-cache" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3249204a71d728d53fb3eea18afd0473f87e520445707a4d567ac4da0bb3eb5d" +dependencies = [ + "anyhow", + "base64 0.21.7", + "bincode", + "directories-next", + "log", + "rustix 0.38.31", + "serde", + "serde_derive", + "sha2 0.10.8", + "toml 0.5.11", + "windows-sys 0.52.0", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "wasmtime-cranelift" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595abdb067acdc812ab0f21d8d46d5aa4022392aa7c3e0632c20bff9ec49ffb4" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log", + "object", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-cranelift-shared", + "wasmtime-environ", + "wasmtime-versioned-export-macros", +] + +[[package]] +name = "wasmtime-cranelift-shared" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c24c1fdea167b992d82ebe76471fd1cbe7b0b406bc72f9250f86353000134e" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", + "cranelift-native", + "gimli", + "object", + "target-lexicon", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3279d510005358141550d8a90a5fc989d7e81748e5759d582fe6bfdcbf074a04" +dependencies = [ + "anyhow", + "bincode", + "cranelift-entity", + "gimli", + "indexmap 2.2.5", + "log", + "object", + "serde", + "serde_derive", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "866634605089b4632b32226b54aa3670d72e1849f9fc425c7e50b3749c2e6df3" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "wasmtime-runtime" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11185c88cadf595d228f5ae4ff9b4badbf9ca98dcb37b0310c36e31fa74867f" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "indexmap 2.2.5", + "libc", + "log", + "mach", + "memfd", + "memoffset", + "paste", + "psm", + "rustix 0.38.31", + "sptr", + "wasm-encoder", + "wasmtime-asm-macros", + "wasmtime-environ", + "wasmtime-versioned-export-macros", + "wasmtime-wmemcheck", + "windows-sys 0.52.0", +] + +[[package]] +name = "wasmtime-types" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f32377cbd827bee06fcb2f6bf97b0477fdcc86888bbe6db7b9cab8e644082e0a" +dependencies = [ + "cranelift-entity", + "serde", + "serde_derive", + "thiserror", + "wasmparser", +] + +[[package]] +name = "wasmtime-versioned-export-macros" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab8d7566d206c42f8cf1d4ac90c5e40d3582e8eabad9b3b67e9e73c61fc47a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "wasmtime-wmemcheck" +version = "18.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3847d969bd203b8cd239f89581e52432a0f00b8c5c9bc917be2fccd7542c4f2f" + [[package]] name = "web-sys" version = "0.3.69" diff --git a/Cargo.toml b/Cargo.toml index 3b513e03..73b84cd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ members = [ "crates/services/relayer", "crates/services/sync", "crates/services/txpool", + "crates/services/upgradable-executor", + "crates/services/upgradable-executor/wasm-executor", "crates/storage", "crates/trace", "crates/types", @@ -63,17 +65,19 @@ fuel-core-services = { version = "0.23.0", path = "./crates/services" } fuel-core-consensus-module = { version = "0.23.0", path = "./crates/services/consensus_module" } fuel-core-bft = { version = "0.23.0", path = "./crates/services/consensus_module/bft" } fuel-core-poa = { version = "0.23.0", path = "./crates/services/consensus_module/poa" } -fuel-core-executor = { version = "0.23.0", path = "./crates/services/executor" } +fuel-core-executor = { version = "0.23.0", path = "./crates/services/executor", default-features = false } fuel-core-importer = { version = "0.23.0", path = "./crates/services/importer" } fuel-core-p2p = { version = "0.23.0", path = "./crates/services/p2p" } fuel-core-producer = { version = "0.23.0", path = "./crates/services/producer" } fuel-core-relayer = { version = "0.23.0", path = "./crates/services/relayer" } fuel-core-sync = { version = "0.23.0", path = "./crates/services/sync" } fuel-core-txpool = { version = "0.23.0", path = "./crates/services/txpool" } -fuel-core-storage = { version = "0.23.0", path = "./crates/storage" } +fuel-core-storage = { version = "0.23.0", path = "./crates/storage", default-features = false } fuel-core-trace = { version = "0.23.0", path = "./crates/trace" } fuel-core-types = { version = "0.23.0", path = "./crates/types", default-features = false } fuel-core-tests = { version = "0.0.0", path = "./tests" } +fuel-core-upgradable-executor = { version = "0.23.0", path = "./crates/services/upgradable-executor" } +fuel-core-wasm-executor = { version = "0.23.0", path = "./crates/services/upgradable-executor/wasm-executor" } fuel-core-xtask = { version = "0.0.0", path = "./xtask" } # Fuel dependencies @@ -110,6 +114,7 @@ reqwest = { version = "0.11.16", default-features = false, features = [ "cookies", ] } mockall = "0.11" +num_enum = "0.7" test-case = "3.3" impl-tools = "0.10" test-strategy = "0.3" diff --git a/ci_checks.sh b/ci_checks.sh index 22c3bb9f..5e042f03 100755 --- a/ci_checks.sh +++ b/ci_checks.sh @@ -12,6 +12,7 @@ npx prettier --check "**/Cargo.toml" && cargo +nightly fmt --all -- --check && cargo sort -w --check && source .github/workflows/scripts/verify_openssl.sh && +cargo clippy -p fuel-core-wasm-executor --target wasm32-unknown-unknown --no-default-features && cargo clippy --all-targets --all-features && cargo doc --all-features --workspace --no-deps && cargo make check --locked && @@ -21,6 +22,7 @@ cargo check -p fuel-core-storage --target wasm32-unknown-unknown --no-default-fe cargo check -p fuel-core-client --target wasm32-unknown-unknown --no-default-features && cargo check -p fuel-core-chain-config --target wasm32-unknown-unknown --no-default-features && cargo check -p fuel-core-executor --target wasm32-unknown-unknown --no-default-features && +cargo test --workspace && cargo test --all-features --workspace && cargo test -p fuel-core --no-default-features && cargo test -p fuel-core-client --no-default-features && diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs index c5273fa6..0a9000ab 100644 --- a/crates/database/src/lib.rs +++ b/crates/database/src/lib.rs @@ -78,7 +78,7 @@ impl From for StorageError { impl From for ExecutorError { fn from(e: Error) -> Self { - ExecutorError::StorageError(anyhow::anyhow!(StorageError::from(e))) + ExecutorError::StorageError(format!("{}", StorageError::from(e))) } } diff --git a/crates/fuel-core/Cargo.toml b/crates/fuel-core/Cargo.toml index 5e6f5fd3..655b50e5 100644 --- a/crates/fuel-core/Cargo.toml +++ b/crates/fuel-core/Cargo.toml @@ -35,6 +35,7 @@ fuel-core-storage = { workspace = true } fuel-core-sync = { workspace = true, optional = true } fuel-core-txpool = { workspace = true } fuel-core-types = { workspace = true, features = ["serde"] } +fuel-core-upgradable-executor = { workspace = true } futures = { workspace = true } hex = { version = "0.4", features = ["serde"] } hyper = { workspace = true } @@ -67,6 +68,9 @@ fuel-core-services = { path = "./../services", features = ["test-helpers"] } fuel-core-storage = { path = "./../storage", features = ["test-helpers"] } fuel-core-trace = { path = "./../trace" } fuel-core-types = { path = "./../types", features = ["test-helpers"] } +fuel-core-upgradable-executor = { workspace = true, features = [ + "test-helpers", +] } mockall = { workspace = true } proptest = { workspace = true } test-case = { workspace = true } @@ -80,3 +84,4 @@ rocksdb = ["dep:rocksdb", "dep:tempfile", "dep:num_cpus"] test-helpers = ["fuel-core-p2p?/test-helpers", "fuel-core-storage/test-helpers"] # features to enable in production, but increase build times rocksdb-production = ["rocksdb", "rocksdb/jemalloc"] +wasm-executor = ["fuel-core-upgradable-executor/wasm-executor"] diff --git a/crates/fuel-core/src/executor.rs b/crates/fuel-core/src/executor.rs index 3773bd83..4106bbcd 100644 --- a/crates/fuel-core/src/executor.rs +++ b/crates/fuel-core/src/executor.rs @@ -6,16 +6,9 @@ mod tests { use crate as fuel_core; use fuel_core::database::Database; use fuel_core_executor::{ - executor::{ - block_component::PartialBlockComponent, - ExecutionData, - ExecutionOptions, - Executor, - OnceTransactionsSource, - }, + executor::OnceTransactionsSource, ports::RelayerPort, refs::ContractRef, - Config, }; use fuel_core_storage::{ tables::{ @@ -121,7 +114,6 @@ mod tests { Event as ExecutorEvent, ExecutionBlock, ExecutionResult, - ExecutionType, ExecutionTypes, TransactionExecutionResult, TransactionValidityError, @@ -130,13 +122,16 @@ mod tests { }, tai64::Tai64, }; + use fuel_core_upgradable_executor::{ + config::Config, + executor::Executor, + }; use itertools::Itertools; use rand::{ prelude::StdRng, Rng, SeedableRng, }; - use std::sync::Arc; #[derive(Clone, Debug)] struct DisabledRelayer; @@ -172,11 +167,7 @@ mod tests { database: Database, config: Config, ) -> Executor { - Executor { - database_view_provider: database, - relayer_view_provider: DisabledRelayer, - config: Arc::new(config), - } + Executor::new(database, DisabledRelayer, config) } pub(crate) fn setup_executable_script() -> (Create, Script) { @@ -310,14 +301,11 @@ mod tests { skipped_transactions, .. } = producer - .execute_and_commit( - ExecutionTypes::Production(block.into()), - Default::default(), - ) + .execute_and_commit(ExecutionTypes::Production(block.into())) .unwrap(); - let validation_result = verifier - .execute_and_commit(ExecutionTypes::Validation(block), Default::default()); + let validation_result = + verifier.execute_and_commit(ExecutionTypes::Validation(block)); assert!(validation_result.is_ok()); assert!(skipped_transactions.is_empty()); } @@ -334,10 +322,7 @@ mod tests { skipped_transactions, .. } = producer - .execute_and_commit( - ExecutionBlock::Production(block.into()), - Default::default(), - ) + .execute_and_commit(ExecutionBlock::Production(block.into())) .unwrap(); assert!(skipped_transactions.is_empty()); @@ -405,12 +390,12 @@ mod tests { gas_price_factor, ..Default::default() }; + let consensus_parameters = ConsensusParameters { + fee_params, + ..Default::default() + }; let config = Config { - coinbase_recipient: recipient, - consensus_parameters: ConsensusParameters { - fee_params, - ..Default::default() - }, + consensus_parameters: consensus_parameters.clone(), ..Default::default() }; @@ -423,8 +408,8 @@ mod tests { let mut producer = create_executor(database.clone(), config); let expected_fee_amount_1 = TransactionFee::checked_from_tx( - producer.config.consensus_parameters.gas_costs(), - producer.config.consensus_parameters.fee_params(), + consensus_parameters.gas_costs(), + consensus_parameters.fee_params(), &script, price, ) @@ -452,12 +437,13 @@ mod tests { ]), gas_price: price, gas_limit: u64::MAX, + coinbase_recipient: recipient, }, )) .unwrap() .into(); producer - .database_view_provider + .storage_view_provider .commit_changes(changes) .unwrap(); @@ -487,7 +473,7 @@ mod tests { } let (asset_id, amount) = producer - .database_view_provider + .storage_view_provider .latest_view() .contract_balances(recipient, None, None) .next() @@ -506,8 +492,8 @@ mod tests { .clone(); let expected_fee_amount_2 = TransactionFee::checked_from_tx( - producer.config.consensus_parameters.gas_costs(), - producer.config.consensus_parameters.fee_params(), + consensus_parameters.gas_costs(), + consensus_parameters.fee_params(), &script, price, ) @@ -533,12 +519,13 @@ mod tests { ]), gas_price: price, gas_limit: u64::MAX, + coinbase_recipient: recipient, }, )) .unwrap() .into(); producer - .database_view_provider + .storage_view_provider .commit_changes(changes) .unwrap(); @@ -560,7 +547,7 @@ mod tests { ); assert_eq!( second_mint.input_contract().utxo_id, - UtxoId::new(first_mint.cached_id().expect("Id exists"), 0) + UtxoId::new(first_mint.id(&consensus_parameters.chain_id), 0) ); assert_eq!( second_mint.input_contract().tx_pointer, @@ -579,7 +566,7 @@ mod tests { panic!("Invalid coinbase transaction"); } let (asset_id, amount) = producer - .database_view_provider + .storage_view_provider .latest_view() .contract_balances(recipient, None, None) .next() @@ -604,11 +591,13 @@ mod tests { .transaction() .clone(); - let mut config = Config::default(); + let mut consensus_parameters = ConsensusParameters::default(); + consensus_parameters.fee_params.gas_price_factor = gas_price_factor; + let config = Config { + consensus_parameters, + ..Default::default() + }; let recipient = [1u8; 32].into(); - config.coinbase_recipient = recipient; - - config.consensus_parameters.fee_params.gas_price_factor = gas_price_factor; let producer = create_executor(Default::default(), config); @@ -616,6 +605,7 @@ mod tests { .execute_without_commit_with_source(ExecutionTypes::DryRun(Components { header_to_produce: Default::default(), transactions_source: OnceTransactionsSource::new(vec![script.into()]), + coinbase_recipient: recipient, gas_price: 0, gas_limit: u64::MAX, })) @@ -646,7 +636,6 @@ mod tests { ..Default::default() }; let config = Config { - coinbase_recipient: recipient, consensus_parameters: ConsensusParameters { fee_params, ..Default::default() @@ -660,7 +649,7 @@ mod tests { .insert(&recipient, &[]) .expect("Should insert coinbase contract"); - let producer = create_executor(database.clone(), config); + let producer = create_executor(database.clone(), config.clone()); let ExecutionResult { block: produced_block, @@ -675,6 +664,7 @@ mod tests { ]), gas_price: price, gas_limit: u64::MAX, + coinbase_recipient: recipient, }, )) .unwrap() @@ -685,20 +675,17 @@ mod tests { let mut validator = create_executor( Default::default(), // Use the same config as block producer - producer.config.as_ref().clone(), + config, ); let ExecutionResult { block: validated_block, .. } = validator - .execute_and_commit( - ExecutionBlock::Validation(produced_block), - Default::default(), - ) + .execute_and_commit(ExecutionBlock::Validation(produced_block)) .unwrap(); assert_eq!(validated_block.transactions(), produced_txs); let (asset_id, amount) = validator - .database_view_provider + .storage_view_provider .latest_view() .contract_balances(recipient, None, None) .next() @@ -747,21 +734,24 @@ mod tests { .transaction() .clone(); - let config = Config { - coinbase_recipient: config_coinbase, - ..Default::default() - }; - let mut producer = create_executor(Default::default(), config); + let mut producer = + create_executor(Default::default(), Default::default()); let mut block = Block::default(); *block.transactions_mut() = vec![script.clone().into()]; - let ExecutionResult { tx_status, .. } = producer - .execute_and_commit( + let (ExecutionResult { tx_status, .. }, changes) = producer + .execute_without_commit_with_coinbase( ExecutionBlock::Production(block.into()), - Default::default(), + config_coinbase, + 0, ) - .expect("Should execute the block"); + .expect("Should execute the block") + .into(); + producer + .storage_view_provider + .commit_changes(changes) + .unwrap(); let receipts = tx_status[0].result.receipts(); if let Some(Receipt::Return { val, .. }) = receipts.first() { @@ -812,7 +802,7 @@ mod tests { }, ); let validation_err = validator - .execute_and_commit(ExecutionBlock::Validation(block), Default::default()) + .execute_and_commit(ExecutionBlock::Validation(block)) .expect_err("Expected error because coinbase if invalid"); assert!(matches!( validation_err, @@ -838,7 +828,7 @@ mod tests { let mut validator = create_executor(Default::default(), Default::default()); let validation_err = validator - .execute_and_commit(ExecutionBlock::Validation(block), Default::default()) + .execute_and_commit(ExecutionBlock::Validation(block)) .expect_err("Expected error because coinbase if invalid"); assert!(matches!( validation_err, @@ -852,7 +842,7 @@ mod tests { let mut validator = create_executor(Default::default(), Default::default()); let validation_err = validator - .execute_and_commit(ExecutionBlock::Validation(block), Default::default()) + .execute_and_commit(ExecutionBlock::Validation(block)) .expect_err("Expected error because coinbase is missing"); assert!(matches!(validation_err, ExecutorError::MintMissing)); } @@ -874,7 +864,7 @@ mod tests { let mut validator = create_executor(Default::default(), Default::default()); let validation_err = validator - .execute_and_commit(ExecutionBlock::Validation(block), Default::default()) + .execute_and_commit(ExecutionBlock::Validation(block)) .expect_err("Expected error because coinbase if invalid"); assert!(matches!( @@ -900,11 +890,18 @@ mod tests { *block.transactions_mut() = vec![mint.into()]; block.header_mut().recalculate_metadata(); - let mut config = Config::default(); - config.consensus_parameters.base_asset_id = [1u8; 32].into(); + let consensus_parameters = ConsensusParameters { + base_asset_id: [1u8; 32].into(), + ..Default::default() + }; + + let config = Config { + consensus_parameters, + ..Default::default() + }; let mut validator = create_executor(Default::default(), config); let validation_err = validator - .execute_and_commit(ExecutionBlock::Validation(block), Default::default()) + .execute_and_commit(ExecutionBlock::Validation(block)) .expect_err("Expected error because coinbase if invalid"); assert!(matches!( @@ -932,7 +929,7 @@ mod tests { let mut validator = create_executor(Default::default(), Default::default()); let validation_err = validator - .execute_and_commit(ExecutionBlock::Validation(block), Default::default()) + .execute_and_commit(ExecutionBlock::Validation(block)) .expect_err("Expected error because coinbase if invalid"); assert!(matches!( validation_err, @@ -945,8 +942,14 @@ mod tests { #[test] fn executor_invalidates_missing_gas_input() { let mut rng = StdRng::seed_from_u64(2322u64); - let producer = create_executor(Default::default(), Default::default()); - let verifier = create_executor(Default::default(), Default::default()); + let consensus_parameters = ConsensusParameters::default(); + let config = Config { + consensus_parameters: consensus_parameters.clone(), + ..Default::default() + }; + let producer = create_executor(Default::default(), config.clone()); + + let verifier = create_executor(Default::default(), config); let gas_limit = 100; let max_fee = 1; @@ -963,22 +966,19 @@ mod tests { .finalize(); let tx: Transaction = script.into(); - let mut block = PartialFuelBlock { + let block = PartialFuelBlock { header: Default::default(), transactions: vec![tx.clone()], }; - let ExecutionData { + let ExecutionResult { skipped_transactions, + block, .. } = producer - .execute_block( - ExecutionType::Production(PartialBlockComponent::from_partial_block( - &mut block, - )), - Default::default(), - ) - .unwrap(); + .execute_without_commit(ExecutionTypes::Production(block)) + .unwrap() + .into_result(); let produce_result = &skipped_transactions[0].1; assert!(matches!( produce_result, @@ -990,23 +990,16 @@ mod tests { )); // Produced block is valid - verifier - .execute_block( - ExecutionType::Validation(PartialBlockComponent::from_partial_block( - &mut block, - )), - Default::default(), - ) - .unwrap(); + let ExecutionResult { mut block, .. } = verifier + .execute_without_commit(ExecutionTypes::Validation(block)) + .unwrap() + .into_result(); // Invalidate the block with Insufficient tx - block.transactions.insert(block.transactions.len() - 1, tx); - let verify_result = verifier.execute_block( - ExecutionType::Validation(PartialBlockComponent::from_partial_block( - &mut block, - )), - Default::default(), - ); + let len = block.transactions().len(); + block.transactions_mut().insert(len - 1, tx); + let verify_result = + verifier.execute_without_commit(ExecutionTypes::Validation(block)); assert!(matches!( verify_result, Err(ExecutorError::InvalidTransaction( @@ -1023,7 +1016,7 @@ mod tests { let verifier = create_executor(Default::default(), Default::default()); - let mut block = PartialFuelBlock { + let block = PartialFuelBlock { header: Default::default(), transactions: vec![ Transaction::default_test_tx(), @@ -1031,17 +1024,14 @@ mod tests { ], }; - let ExecutionData { + let ExecutionResult { skipped_transactions, + block, .. } = producer - .execute_block( - ExecutionType::Production(PartialBlockComponent::from_partial_block( - &mut block, - )), - Default::default(), - ) - .unwrap(); + .execute_without_commit(ExecutionTypes::Production(block)) + .unwrap() + .into_result(); let produce_result = &skipped_transactions[0].1; assert!(matches!( produce_result, @@ -1049,25 +1039,18 @@ mod tests { )); // Produced block is valid - verifier - .execute_block( - ExecutionType::Validation(PartialBlockComponent::from_partial_block( - &mut block, - )), - Default::default(), - ) - .unwrap(); + let ExecutionResult { mut block, .. } = verifier + .execute_without_commit(ExecutionTypes::Validation(block)) + .unwrap() + .into_result(); // Make the block invalid by adding of the duplicating transaction + let len = block.transactions().len(); block - .transactions - .insert(block.transactions.len() - 1, Transaction::default_test_tx()); - let verify_result = verifier.execute_block( - ExecutionType::Validation(PartialBlockComponent::from_partial_block( - &mut block, - )), - Default::default(), - ); + .transactions_mut() + .insert(len - 1, Transaction::default_test_tx()); + let verify_result = + verifier.execute_without_commit(ExecutionTypes::Validation(block)); assert!(matches!( verify_result, Err(ExecutorError::TransactionIdCollision(_)) @@ -1107,24 +1090,19 @@ mod tests { let verifier = create_executor(Default::default(), config); - let mut block = PartialFuelBlock { + let block = PartialFuelBlock { header: Default::default(), transactions: vec![tx.clone()], }; - let ExecutionData { + let ExecutionResult { skipped_transactions, + block, .. } = producer - .execute_block( - ExecutionType::Production(PartialBlockComponent::from_partial_block( - &mut block, - )), - ExecutionOptions { - utxo_validation: true, - }, - ) - .unwrap(); + .execute_without_commit(ExecutionTypes::Production(block)) + .unwrap() + .into_result(); let produce_result = &skipped_transactions[0].1; assert!(matches!( produce_result, @@ -1134,27 +1112,16 @@ mod tests { )); // Produced block is valid - verifier - .execute_block( - ExecutionType::Validation(PartialBlockComponent::from_partial_block( - &mut block, - )), - ExecutionOptions { - utxo_validation: true, - }, - ) - .unwrap(); + let ExecutionResult { mut block, .. } = verifier + .execute_without_commit(ExecutionTypes::Validation(block)) + .unwrap() + .into_result(); // Invalidate block by adding transaction with not existing coin - block.transactions.insert(block.transactions.len() - 1, tx); - let verify_result = verifier.execute_block( - ExecutionType::Validation(PartialBlockComponent::from_partial_block( - &mut block, - )), - ExecutionOptions { - utxo_validation: true, - }, - ); + let len = block.transactions().len(); + block.transactions_mut().insert(len - 1, tx); + let verify_result = + verifier.execute_without_commit(ExecutionTypes::Validation(block)); assert!(matches!( verify_result, Err(ExecutorError::TransactionValidity( @@ -1189,10 +1156,7 @@ mod tests { *block.transactions_mut() = vec![tx]; let ExecutionResult { mut block, .. } = producer - .execute_and_commit( - ExecutionBlock::Production(block.into()), - Default::default(), - ) + .execute_and_commit(ExecutionBlock::Production(block.into())) .unwrap(); // modify change amount @@ -1202,8 +1166,8 @@ mod tests { } } - let verify_result = verifier - .execute_and_commit(ExecutionBlock::Validation(block), Default::default()); + let verify_result = + verifier.execute_and_commit(ExecutionBlock::Validation(block)); assert!(matches!( verify_result, Err(ExecutorError::InvalidTransactionOutcome { transaction_id }) if transaction_id == tx_id @@ -1232,18 +1196,15 @@ mod tests { *block.transactions_mut() = vec![tx]; let ExecutionResult { mut block, .. } = producer - .execute_and_commit( - ExecutionBlock::Production(block.into()), - Default::default(), - ) + .execute_and_commit(ExecutionBlock::Production(block.into())) .unwrap(); // randomize transaction commitment block.header_mut().set_transaction_root(rng.gen()); block.header_mut().recalculate_metadata(); - let verify_result = verifier - .execute_and_commit(ExecutionBlock::Validation(block), Default::default()); + let verify_result = + verifier.execute_and_commit(ExecutionBlock::Validation(block)); assert!(matches!(verify_result, Err(ExecutorError::InvalidBlockId))) } @@ -1271,12 +1232,7 @@ mod tests { skipped_transactions, .. } = executor - .execute_and_commit( - ExecutionBlock::Production(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); let err = &skipped_transactions[0].1; @@ -1358,12 +1314,7 @@ mod tests { skipped_transactions, .. } = executor - .execute_and_commit( - ExecutionBlock::Production(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); // `tx2` should be skipped. assert_eq!(block.transactions().len(), 2 /* coinbase and `tx1` */); @@ -1422,12 +1373,7 @@ mod tests { skipped_transactions, .. } = executor - .execute_and_commit( - ExecutionBlock::Production(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); // `tx` should be skipped. assert_eq!(skipped_transactions.len(), 1); @@ -1478,12 +1424,7 @@ mod tests { skipped_transactions, .. } = executor - .execute_and_commit( - ExecutionBlock::Production(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); // `tx` should be skipped. assert_eq!(skipped_transactions.len(), 1); @@ -1524,7 +1465,7 @@ mod tests { skipped_transactions, .. } = executor - .execute_and_commit(ExecutionBlock::Production(block), Default::default()) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); assert_eq!( block.transactions().len(), @@ -1569,7 +1510,7 @@ mod tests { }; let ExecutionResult { block, .. } = executor - .execute_and_commit(ExecutionBlock::Production(block), Default::default()) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); // assert the tx coin is spent @@ -1628,7 +1569,7 @@ mod tests { let ExecutionResult { block, tx_status, .. } = executor - .execute_and_commit(ExecutionBlock::Production(block), Default::default()) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); // Assert the balance and state roots should be the same before and after execution. @@ -1685,7 +1626,7 @@ mod tests { let ExecutionResult { block, tx_status, .. } = executor - .execute_and_commit(ExecutionBlock::Production(block), Default::default()) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); // Assert the balance and state roots should be the same before and after execution. @@ -1788,7 +1729,7 @@ mod tests { let ExecutionResult { block, tx_status, .. } = executor - .execute_and_commit(ExecutionBlock::Production(block), Default::default()) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); let empty_state = (*sparse::empty_sum()).into(); @@ -1872,10 +1813,12 @@ mod tests { .clone(); let db = Database::default(); + let consensus_parameters = ConsensusParameters::default(); let mut executor = create_executor( db.clone(), Config { utxo_validation_default: false, + consensus_parameters: consensus_parameters.clone(), ..Default::default() }, ); @@ -1892,7 +1835,7 @@ mod tests { }; let ExecutionResult { block, .. } = executor - .execute_and_commit(ExecutionBlock::Production(block), Default::default()) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); let executed_tx = block.transactions()[1].as_script().unwrap(); @@ -1901,9 +1844,7 @@ mod tests { let mut new_tx = executed_tx.clone(); *new_tx.script_mut() = vec![]; - new_tx - .precompute(&executor.config.consensus_parameters.chain_id) - .unwrap(); + new_tx.precompute(&consensus_parameters.chain_id).unwrap(); let block = PartialFuelBlock { header: PartialBlockHeader { @@ -1924,6 +1865,7 @@ mod tests { transactions_source: OnceTransactionsSource::new(block.transactions), gas_price: 0, gas_limit: u64::MAX, + coinbase_recipient: Default::default(), })) .unwrap() .into_result(); @@ -1986,7 +1928,7 @@ mod tests { }; let _ = executor - .execute_and_commit(ExecutionBlock::Production(block), Default::default()) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); // Assert the balance root should not be affected. @@ -2060,12 +2002,7 @@ mod tests { }; let ExecutionResult { block, events, .. } = executor - .execute_and_commit( - ExecutionBlock::Production(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); // assert the tx coin is spent @@ -2121,10 +2058,7 @@ mod tests { skipped_transactions, .. } = setup - .execute_and_commit( - ExecutionBlock::Production(first_block), - Default::default(), - ) + .execute_and_commit(ExecutionBlock::Production(first_block)) .unwrap(); assert!(skipped_transactions.is_empty()); @@ -2134,19 +2068,14 @@ mod tests { skipped_transactions, .. } = producer - .execute_without_commit( - ExecutionBlock::Production(second_block), - Default::default(), - ) + .execute_without_commit(ExecutionBlock::Production(second_block)) .unwrap() .into_result(); assert!(skipped_transactions.is_empty()); let verifier = create_executor(db, Default::default()); - let verify_result = verifier.execute_without_commit( - ExecutionBlock::Validation(second_block), - Default::default(), - ); + let verify_result = + verifier.execute_without_commit(ExecutionBlock::Validation(second_block)); assert!(verify_result.is_ok()); } @@ -2200,10 +2129,7 @@ mod tests { let mut setup = create_executor(db.clone(), Default::default()); setup - .execute_and_commit( - ExecutionBlock::Production(first_block), - Default::default(), - ) + .execute_and_commit(ExecutionBlock::Production(first_block)) .unwrap(); let producer = create_executor(db.clone(), Default::default()); @@ -2212,10 +2138,7 @@ mod tests { block: mut second_block, .. } = producer - .execute_without_commit( - ExecutionBlock::Production(second_block), - Default::default(), - ) + .execute_without_commit(ExecutionBlock::Production(second_block)) .unwrap() .into_result(); // Corrupt the utxo_id of the contract output @@ -2229,10 +2152,8 @@ mod tests { } let verifier = create_executor(db, Default::default()); - let verify_result = verifier.execute_without_commit( - ExecutionBlock::Validation(second_block), - Default::default(), - ); + let verify_result = + verifier.execute_without_commit(ExecutionBlock::Validation(second_block)); assert!(matches!( verify_result, @@ -2256,7 +2177,7 @@ mod tests { }; let ExecutionResult { block, .. } = executor - .execute_and_commit(ExecutionBlock::Production(block), Default::default()) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); // ensure that all utxos with an amount are stored into the utxo set @@ -2307,7 +2228,7 @@ mod tests { }; executor - .execute_and_commit(ExecutionBlock::Production(block), Default::default()) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); for idx in 0..2 { @@ -2342,6 +2263,7 @@ mod tests { 1000, vec![], ) + .add_output(Output::change(rng.gen(), 1000, AssetId::BASE)) .finalize(); let message = message_from_input(&tx.inputs()[0], da_height); @@ -2381,21 +2303,11 @@ mod tests { }; let ExecutionResult { block, .. } = make_executor(&[&message]) - .execute_and_commit( - ExecutionBlock::Production(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block)) .expect("block execution failed unexpectedly"); make_executor(&[&message]) - .execute_and_commit( - ExecutionBlock::Validation(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Validation(block)) .expect("block validation failed unexpectedly"); } @@ -2424,7 +2336,7 @@ mod tests { }; let mut exec = make_executor(&messages); - let view = exec.database_view_provider.latest_view(); + let view = exec.storage_view_provider.latest_view(); assert!(!view.message_is_spent(message_coin.nonce()).unwrap()); assert!(!view.message_is_spent(message_data.nonce()).unwrap()); @@ -2432,17 +2344,12 @@ mod tests { skipped_transactions, .. } = exec - .execute_and_commit( - ExecutionBlock::Production(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); assert_eq!(skipped_transactions.len(), 0); // Successful execution consumes `message_coin` and `message_data`. - let view = exec.database_view_provider.latest_view(); + let view = exec.storage_view_provider.latest_view(); assert!(view.message_is_spent(message_coin.nonce()).unwrap()); assert!(view.message_is_spent(message_data.nonce()).unwrap()); assert_eq!( @@ -2478,7 +2385,7 @@ mod tests { }; let mut exec = make_executor(&messages); - let view = exec.database_view_provider.latest_view(); + let view = exec.storage_view_provider.latest_view(); assert!(!view.message_is_spent(message_coin.nonce()).unwrap()); assert!(!view.message_is_spent(message_data.nonce()).unwrap()); @@ -2486,17 +2393,12 @@ mod tests { skipped_transactions, .. } = exec - .execute_and_commit( - ExecutionBlock::Production(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block)) .unwrap(); assert_eq!(skipped_transactions.len(), 0); // We should spend only `message_coin`. The `message_data` should be unspent. - let view = exec.database_view_provider.latest_view(); + let view = exec.storage_view_provider.latest_view(); assert!(view.message_is_spent(message_coin.nonce()).unwrap()); assert!(!view.message_is_spent(message_data.nonce()).unwrap()); assert_eq!(*view.coin(&UtxoId::new(tx_id, 0)).unwrap().amount(), amount); @@ -2518,9 +2420,6 @@ mod tests { } = make_executor(&[]) // No messages in the db .execute_and_commit( ExecutionBlock::Production(block.clone().into()), - ExecutionOptions { - utxo_validation: true, - }, ) .unwrap(); let err = &skipped_transactions[0].1; @@ -2533,24 +2432,14 @@ mod tests { // Produced block is valid make_executor(&[]) // No messages in the db - .execute_and_commit( - ExecutionBlock::Validation(block.clone()), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Validation(block.clone())) .unwrap(); // Invalidate block by returning back `tx` with not existing message let index = block.transactions().len() - 1; block.transactions_mut().insert(index, tx); let res = make_executor(&[]) // No messages in the db - .execute_and_commit( - ExecutionBlock::Validation(block), - ExecutionOptions { - utxo_validation: true, - }, - ); + .execute_and_commit(ExecutionBlock::Validation(block)); assert!(matches!( res, Err(ExecutorError::TransactionValidity( @@ -2573,12 +2462,7 @@ mod tests { mut block, .. } = make_executor(&[&message]) - .execute_and_commit( - ExecutionBlock::Production(block.clone().into()), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block.clone().into())) .unwrap(); let err = &skipped_transactions[0].1; assert!(matches!( @@ -2590,23 +2474,14 @@ mod tests { // Produced block is valid make_executor(&[&message]) - .execute_and_commit( - ExecutionBlock::Validation(block.clone()), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Validation(block.clone())) .unwrap(); // Invalidate block by return back `tx` with not ready message. let index = block.transactions().len() - 1; block.transactions_mut().insert(index, tx); - let res = make_executor(&[&message]).execute_and_commit( - ExecutionBlock::Validation(block), - ExecutionOptions { - utxo_validation: true, - }, - ); + let res = make_executor(&[&message]) + .execute_and_commit(ExecutionBlock::Validation(block)); assert!(matches!( res, Err(ExecutorError::TransactionValidity( @@ -2631,12 +2506,7 @@ mod tests { skipped_transactions, .. } = make_executor(&[&message]) - .execute_and_commit( - ExecutionBlock::Production(block.clone().into()), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block.clone().into())) .unwrap(); let err = &skipped_transactions[0].1; assert!(matches!( @@ -2657,25 +2527,20 @@ mod tests { tx2.as_script_mut().unwrap().inputs_mut()[0] = tx1.as_script().unwrap().inputs()[0].clone(); - let mut block = PartialFuelBlock { + let block = PartialFuelBlock { header: Default::default(), transactions: vec![tx1, tx2.clone()], }; let exec = make_executor(&[&message]); - let ExecutionData { + let ExecutionResult { skipped_transactions, + block, .. } = exec - .execute_block( - ExecutionType::Production(PartialBlockComponent::from_partial_block( - &mut block, - )), - ExecutionOptions { - utxo_validation: true, - }, - ) - .unwrap(); + .execute_without_commit(ExecutionTypes::Production(block)) + .unwrap() + .into_result(); // One of two transactions is skipped. assert_eq!(skipped_transactions.len(), 1); let err = &skipped_transactions[0].1; @@ -2688,27 +2553,16 @@ mod tests { // Produced block is valid let exec = make_executor(&[&message]); - exec.execute_block( - ExecutionType::Validation(PartialBlockComponent::from_partial_block( - &mut block, - )), - ExecutionOptions { - utxo_validation: true, - }, - ) - .unwrap(); + let ExecutionResult { mut block, .. } = exec + .execute_without_commit(ExecutionTypes::Validation(block)) + .unwrap() + .into_result(); // Invalidate block by return back `tx2` transaction skipped during production. - block.transactions.insert(block.transactions.len() - 1, tx2); + let len = block.transactions().len(); + block.transactions_mut().insert(len - 1, tx2); let exec = make_executor(&[&message]); - let res = exec.execute_block( - ExecutionType::Validation(PartialBlockComponent::from_partial_block( - &mut block, - )), - ExecutionOptions { - utxo_validation: true, - }, - ); + let res = exec.execute_without_commit(ExecutionTypes::Validation(block)); assert!(matches!( res, Err(ExecutorError::TransactionValidity( @@ -2774,12 +2628,7 @@ mod tests { ); let ExecutionResult { tx_status, .. } = executor - .execute_and_commit( - ExecutionBlock::Production(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block)) .expect("Should execute the block"); let receipts = tx_status[0].result.receipts(); @@ -2843,12 +2692,7 @@ mod tests { ); let ExecutionResult { tx_status, .. } = executor - .execute_and_commit( - ExecutionBlock::Production(block), - ExecutionOptions { - utxo_validation: true, - }, - ) + .execute_and_commit(ExecutionBlock::Production(block)) .expect("Should execute the block"); let receipts = tx_status[0].result.receipts(); @@ -2862,8 +2706,10 @@ mod tests { let owner = Input::predicate_owner(&predicate); let amount = 1000; + let consensus_parameters = ConsensusParameters::default(); let config = Config { utxo_validation_default: true, + consensus_parameters: consensus_parameters.clone(), ..Default::default() }; @@ -2888,7 +2734,7 @@ mod tests { asset_id: Default::default(), }) .finalize(); - tx.estimate_predicates(&config.consensus_parameters.clone().into()) + tx.estimate_predicates(&consensus_parameters.clone().into()) .unwrap(); let db = &mut Database::default(); @@ -2920,6 +2766,7 @@ mod tests { .execute_without_commit_with_source(ExecutionTypes::Production(Components { header_to_produce: PartialBlockHeader::default(), transactions_source: OnceTransactionsSource::new(vec![tx.into()]), + coinbase_recipient: Default::default(), gas_price: 1, gas_limit: u64::MAX, })) @@ -3002,11 +2849,7 @@ mod tests { on_chain: Database, relayer: Database, ) -> Executor, Database> { - Executor { - database_view_provider: on_chain, - relayer_view_provider: relayer, - config: Arc::new(Default::default()), - } + Executor::new(on_chain, relayer, Default::default()) } struct Input { @@ -3086,10 +2929,7 @@ mod tests { let producer = create_relayer_executor(on_chain_db, relayer_db); let block = test_block(block_height.into(), block_da_height.into(), 0); let (result, changes) = producer - .execute_without_commit( - ExecutionTypes::Production(block.into()), - Default::default(), - )? + .execute_without_commit(ExecutionTypes::Production(block.into()))? .into(); // Then @@ -3148,10 +2988,7 @@ mod tests { // when let (result, _) = producer - .execute_without_commit( - ExecutionTypes::Production(block.into()), - Default::default(), - ) + .execute_without_commit(ExecutionTypes::Production(block.into())) .unwrap() .into(); @@ -3178,10 +3015,7 @@ mod tests { let producer = create_relayer_executor(on_chain_db, relayer_db); let block = test_block(block_height.into(), block_da_height.into(), 10); let (result, changes) = producer - .execute_without_commit( - ExecutionTypes::Production(block.into()), - Default::default(), - ) + .execute_without_commit(ExecutionTypes::Production(block.into())) .unwrap() .into(); @@ -3224,10 +3058,7 @@ mod tests { *block.transactions_mut() = vec![tx]; let producer = create_relayer_executor(on_chain_db, relayer_db); let (result, changes) = producer - .execute_without_commit( - ExecutionTypes::Production(block.into()), - Default::default(), - ) + .execute_without_commit(ExecutionTypes::Production(block.into())) .unwrap() .into(); diff --git a/crates/fuel-core/src/graphql_api/worker_service.rs b/crates/fuel-core/src/graphql_api/worker_service.rs index 9d1d2f5f..4fdc7501 100644 --- a/crates/fuel-core/src/graphql_api/worker_service.rs +++ b/crates/fuel-core/src/graphql_api/worker_service.rs @@ -47,6 +47,7 @@ use fuel_core_types::{ fuel_types::{ BlockHeight, Bytes32, + ChainId, }, services::{ block_importer::{ @@ -75,6 +76,7 @@ pub struct Task { tx_pool: TxPool, block_importer: BoxStream, database: D, + chain_id: ChainId, } impl Task @@ -89,7 +91,7 @@ where persist_transaction_status(&result, &mut transaction)?; // save the associated owner for each transaction in the block - index_tx_owners_for_block(block, &mut transaction)?; + index_tx_owners_for_block(block, &mut transaction, &self.chain_id)?; // save the transaction related information process_transactions(block.transactions().iter(), &mut transaction)?; @@ -172,6 +174,7 @@ where fn index_tx_owners_for_block( block: &Block, block_st_transaction: &mut T, + chain_id: &ChainId, ) -> anyhow::Result<()> where T: OffChainDatabase, @@ -183,9 +186,7 @@ where let tx_idx = u16::try_from(tx_idx).map_err(|e| { anyhow::anyhow!("The block has more than `u16::MAX` transactions, {}", e) })?; - let tx_id = tx.cached_id().expect( - "The imported block should contains only transactions with cached id", - ); + let tx_id = tx.id(chain_id); match tx { Transaction::Script(tx) => { inputs = tx.inputs().as_slice(); @@ -385,6 +386,7 @@ pub fn new_service( tx_pool: TxPool, block_importer: I, database: D, + chain_id: ChainId, ) -> ServiceRunner> where TxPool: ports::worker::TxPool, @@ -396,5 +398,6 @@ where tx_pool, block_importer, database, + chain_id, }) } diff --git a/crates/fuel-core/src/service/adapters.rs b/crates/fuel-core/src/service/adapters.rs index 58f0405f..0aadd0e0 100644 --- a/crates/fuel-core/src/service/adapters.rs +++ b/crates/fuel-core/src/service/adapters.rs @@ -9,7 +9,6 @@ use fuel_core_consensus_module::{ block_verifier::Verifier, RelayerConsensusConfig, }; -use fuel_core_executor::executor::Executor; use fuel_core_services::stream::BoxStream; use fuel_core_txpool::service::SharedState as TxPoolSharedState; #[cfg(feature = "p2p")] @@ -18,6 +17,7 @@ use fuel_core_types::{ fuel_types::BlockHeight, services::block_importer::SharedImportResult, }; +use fuel_core_upgradable_executor::executor::Executor; use std::sync::Arc; pub mod block_importer; @@ -87,13 +87,9 @@ impl ExecutorAdapter { pub fn new( database: Database, relayer_database: Database, - config: fuel_core_executor::Config, + config: fuel_core_upgradable_executor::config::Config, ) -> Self { - let executor = Executor { - database_view_provider: database, - relayer_view_provider: relayer_database, - config: Arc::new(config), - }; + let executor = Executor::new(database, relayer_database, config); Self { executor: Arc::new(executor), } diff --git a/crates/fuel-core/src/service/adapters/executor.rs b/crates/fuel-core/src/service/adapters/executor.rs index 7250c20e..07d3f2f4 100644 --- a/crates/fuel-core/src/service/adapters/executor.rs +++ b/crates/fuel-core/src/service/adapters/executor.rs @@ -43,7 +43,7 @@ impl ExecutorAdapter { block: ExecutionBlockWithSource, ) -> ExecutorResult> where - TxSource: fuel_core_executor::ports::TransactionsSource, + TxSource: fuel_core_executor::ports::TransactionsSource + Send + Sync + 'static, { self.executor.execute_without_commit_with_source(block) } diff --git a/crates/fuel-core/src/service/adapters/producer.rs b/crates/fuel-core/src/service/adapters/producer.rs index c60ce482..499f4be5 100644 --- a/crates/fuel-core/src/service/adapters/producer.rs +++ b/crates/fuel-core/src/service/adapters/producer.rs @@ -100,12 +100,14 @@ impl fuel_core_producer::ports::Executor> for ExecutorAdapter { header_to_produce, transactions_source, gas_price, + coinbase_recipient, gas_limit, } = component; self._execute_without_commit(ExecutionTypes::Production(Components { header_to_produce, transactions_source: OnceTransactionsSource::new(transactions_source), gas_price, + coinbase_recipient, gas_limit, })) } diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 9a764407..49e7819e 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -62,12 +62,8 @@ pub fn init_sub_services( let executor = ExecutorAdapter::new( database.on_chain().clone(), database.relayer().clone(), - fuel_core_executor::Config { + fuel_core_upgradable_executor::config::Config { consensus_parameters: config.chain_config.consensus_parameters.clone(), - coinbase_recipient: config - .block_producer - .coinbase_recipient - .unwrap_or_default(), backtrace: config.vm.backtrace, utxo_validation_default: config.utxo_validation, }, @@ -201,6 +197,7 @@ pub fn init_sub_services( tx_pool_adapter.clone(), importer_adapter.clone(), database.off_chain().clone(), + config.chain_config.consensus_parameters.chain_id, ); let graphql_config = GraphQLConfig { diff --git a/crates/services/executor/Cargo.toml b/crates/services/executor/Cargo.toml index 496dc05e..28e12809 100644 --- a/crates/services/executor/Cargo.toml +++ b/crates/services/executor/Cargo.toml @@ -16,6 +16,7 @@ fuel-core-storage = { workspace = true } fuel-core-types = { workspace = true, default-features = false } hex = { version = "0.4", features = ["serde"] } parking_lot = { workspace = true } +serde = { workspace = true } tracing = { workspace = true } [dev-dependencies] diff --git a/crates/services/executor/src/config.rs b/crates/services/executor/src/config.rs deleted file mode 100644 index 16f5bc70..00000000 --- a/crates/services/executor/src/config.rs +++ /dev/null @@ -1,16 +0,0 @@ -use fuel_core_types::fuel_tx::{ - ConsensusParameters, - ContractId, -}; - -#[derive(Clone, Debug, Default)] -pub struct Config { - /// Network-wide common parameters used for validating the chain - pub consensus_parameters: ConsensusParameters, - /// The `ContractId` of the fee recipient. - pub coinbase_recipient: ContractId, - /// Print execution backtraces if transaction execution reverts. - pub backtrace: bool, - /// Default mode for utxo_validation - pub utxo_validation_default: bool, -} diff --git a/crates/services/executor/src/executor.rs b/crates/services/executor/src/executor.rs index 208fb371..a4f29f19 100644 --- a/crates/services/executor/src/executor.rs +++ b/crates/services/executor/src/executor.rs @@ -5,7 +5,6 @@ use crate::{ TransactionsSource, }, refs::ContractRef, - Config, }; use block_component::*; use fuel_core_storage::{ @@ -21,7 +20,6 @@ use fuel_core_storage::{ SpentMessages, }, transactional::{ - AtomicView, Changes, ConflictPolicy, Modifiable, @@ -84,6 +82,7 @@ use fuel_core_types::{ Bytes32, Cacheable, Chargeable, + ConsensusParameters, Input, Mint, Output, @@ -116,7 +115,6 @@ use fuel_core_types::{ state::StateTransition, Backtrace as FuelBacktrace, Interpreter, - InterpreterError, }, services::{ block_producer::Components, @@ -137,10 +135,7 @@ use fuel_core_types::{ }, }; use parking_lot::Mutex as ParkingMutex; -use std::{ - borrow::Cow, - sync::Arc, -}; +use std::borrow::Cow; use tracing::{ debug, warn, @@ -172,127 +167,6 @@ impl TransactionsSource for OnceTransactionsSource { } } -/// The executor is used for block production and validation of the blocks. -#[derive(Clone, Debug)] -pub struct Executor { - pub database_view_provider: D, - pub relayer_view_provider: R, - pub config: Arc, -} - -impl Executor -where - R: AtomicView, - R::View: RelayerPort, - D: AtomicView + Modifiable, - D::View: KeyValueInspect, -{ - #[cfg(any(test, feature = "test-helpers"))] - /// Executes the block and commits the result of the execution into the inner `Database`. - pub fn execute_and_commit( - &mut self, - block: fuel_core_types::services::executor::ExecutionBlock, - options: ExecutionOptions, - ) -> ExecutorResult { - let (result, changes) = self.execute_without_commit(block, options)?.into(); - - self.database_view_provider.commit_changes(changes)?; - Ok(result) - } - - #[cfg(any(test, feature = "test-helpers"))] - pub fn execute_without_commit( - &self, - block: fuel_core_types::services::executor::ExecutionBlock, - options: ExecutionOptions, - ) -> ExecutorResult> { - let executor = ExecutionInstance { - database: self.database_view_provider.latest_view(), - relayer: self.relayer_view_provider.latest_view(), - config: self.config.clone(), - options, - }; - - let component = match block { - ExecutionTypes::DryRun(_) => { - panic!("It is not possible to commit the dry run result"); - } - ExecutionTypes::Production(block) => ExecutionTypes::Production(Components { - header_to_produce: block.header, - transactions_source: OnceTransactionsSource::new(block.transactions), - gas_price: 0, - gas_limit: u64::MAX, - }), - ExecutionTypes::Validation(block) => ExecutionTypes::Validation(block), - }; - - executor.execute_without_commit(component) - } -} - -impl Executor -where - R: AtomicView, - R::View: RelayerPort, - D: AtomicView, - D::View: KeyValueInspect, -{ - /// Executes the partial block and returns `ExecutionData` as a result. - #[cfg(any(test, feature = "test-helpers"))] - pub fn execute_block( - &self, - block: ExecutionType>, - options: ExecutionOptions, - ) -> ExecutorResult - where - TxSource: TransactionsSource, - { - let executor = ExecutionInstance { - database: self.database_view_provider.latest_view(), - relayer: self.relayer_view_provider.latest_view(), - config: self.config.clone(), - options, - }; - executor.execute_block(block) - } - - pub fn execute_without_commit_with_source( - &self, - block: ExecutionBlockWithSource, - ) -> ExecutorResult> - where - TxSource: TransactionsSource, - { - let executor = ExecutionInstance { - database: self.database_view_provider.latest_view(), - relayer: self.relayer_view_provider.latest_view(), - config: self.config.clone(), - options: self.config.as_ref().into(), - }; - executor.execute_inner(block) - } - - pub fn dry_run( - &self, - component: Components>, - utxo_validation: Option, - ) -> ExecutorResult> { - // fallback to service config value if no utxo_validation override is provided - let utxo_validation = - utxo_validation.unwrap_or(self.config.utxo_validation_default); - - let options = ExecutionOptions { utxo_validation }; - - let executor = ExecutionInstance { - database: self.database_view_provider.latest_view(), - relayer: self.relayer_view_provider.latest_view(), - config: self.config.clone(), - options, - }; - executor.dry_run(component) - } -} - /// Data that is generated after executing all transactions. #[derive(Default)] pub struct ExecutionData { @@ -309,18 +183,17 @@ pub struct ExecutionData { } /// Per-block execution options -#[derive(Copy, Clone, Default, Debug)] +#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)] pub struct ExecutionOptions { /// UTXO Validation flag, when disabled the executor skips signature and UTXO existence checks pub utxo_validation: bool, -} - -impl From<&Config> for ExecutionOptions { - fn from(value: &Config) -> Self { - Self { - utxo_validation: value.utxo_validation_default, - } - } + /// Print execution backtraces if transaction execution reverts. + pub backtrace: bool, + // TODO: It is a temporary workaround to pass the `consensus_params` + // into the WASM executor. Later WASM will fetch it from the storage directly. + // https://github.com/FuelLabs/fuel-core/issues/1753 + /// The configuration allows overriding the default consensus parameters. + pub consensus_params: Option, } /// The executor instance performs block production and validation. Given a block, it will execute all @@ -328,10 +201,10 @@ impl From<&Config> for ExecutionOptions { /// In production mode, block fields like transaction commitments are set based on the executed txs. /// In validation mode, the processed block commitments are compared with the proposed block. #[derive(Clone, Debug)] -struct ExecutionInstance { +pub struct ExecutionInstance { pub relayer: R, pub database: D, - pub config: Arc, + pub consensus_params: ConsensusParameters, pub options: ExecutionOptions, } @@ -349,49 +222,16 @@ where { self.execute_inner(block) } - - pub fn dry_run( - self, - component: Components>, - ) -> ExecutorResult> { - let component = Components { - header_to_produce: component.header_to_produce, - transactions_source: OnceTransactionsSource::new( - component.transactions_source, - ), - gas_price: component.gas_price, - gas_limit: component.gas_limit, - }; - - let ( - ExecutionResult { - skipped_transactions, - tx_status, - .. - }, - _temporary_db, - ) = self - .execute_without_commit(ExecutionTypes::DryRun(component))? - .into(); - - // If one of the transactions fails, return an error. - if let Some((_, err)) = skipped_transactions.into_iter().next() { - return Err(err) - } - - Ok(tx_status) - // drop `_temporary_db` without committing to avoid altering state. - } } // TODO: Make this module private after moving unit tests from `fuel-core` here. pub mod block_component { use super::*; - use fuel_core_types::fuel_tx::field::MintGasPrice; pub struct PartialBlockComponent<'a, TxSource> { pub empty_block: &'a mut PartialFuelBlock, pub transactions_source: TxSource, + pub coinbase_contract_id: ContractId, pub gas_price: u64, pub gas_limit: u64, /// The private marker to allow creation of the type only by constructor. @@ -401,15 +241,17 @@ pub mod block_component { impl<'a> PartialBlockComponent<'a, OnceTransactionsSource> { pub fn from_partial_block(block: &'a mut PartialFuelBlock) -> Self { let transaction = core::mem::take(&mut block.transactions); - let gas_price = if let Some(Transaction::Mint(mint)) = transaction.last() { - *mint.gas_price() - } else { - 0 - }; + let (gas_price, coinbase_contract_id) = + if let Some(Transaction::Mint(mint)) = transaction.last() { + (*mint.gas_price(), mint.input_contract().contract_id) + } else { + (0, Default::default()) + }; Self { empty_block: block, transactions_source: OnceTransactionsSource::new(transaction), + coinbase_contract_id, gas_price, gas_limit: u64::MAX, _marker: Default::default(), @@ -421,6 +263,7 @@ pub mod block_component { pub fn from_component( block: &'a mut PartialFuelBlock, transactions_source: TxSource, + coinbase_contract_id: ContractId, gas_price: u64, gas_limit: u64, ) -> Self { @@ -429,6 +272,7 @@ pub mod block_component { empty_block: block, transactions_source, gas_price, + coinbase_contract_id, gas_limit, _marker: Default::default(), } @@ -463,6 +307,7 @@ where let component = PartialBlockComponent::from_component( &mut block, component.transactions_source, + component.coinbase_recipient, component.gas_price, component.gas_limit, ); @@ -477,6 +322,7 @@ where let component = PartialBlockComponent::from_component( &mut block, component.transactions_source, + component.coinbase_recipient, component.gas_price, component.gas_limit, ); @@ -569,6 +415,7 @@ where let block = component.empty_block; let source = component.transactions_source; let gas_price = component.gas_price; + let coinbase_contract_id = component.coinbase_contract_id; let mut remaining_gas_limit = component.gas_limit; let block_height = *block.header.height(); @@ -602,11 +449,12 @@ where let mut tx_st_transaction = thread_block_transaction .write_transaction() .with_policy(ConflictPolicy::Overwrite); - let tx_id = tx.id(&self.config.consensus_parameters.chain_id); + let tx_id = tx.id(&self.consensus_params.chain_id); let result = self.execute_transaction( tx, &tx_id, &block.header, + coinbase_contract_id, gas_price, execution_data, execution_kind, @@ -660,8 +508,7 @@ where // After the execution of all transactions in production mode, we can set the final fee. if execution_kind == ExecutionKind::Production { - let amount_to_mint = if self.config.coinbase_recipient != ContractId::zeroed() - { + let amount_to_mint = if coinbase_contract_id != ContractId::zeroed() { execution_data.coinbase } else { 0 @@ -674,7 +521,7 @@ where balance_root: Bytes32::zeroed(), state_root: Bytes32::zeroed(), tx_pointer: TxPointer::new(BlockHeight::new(0), 0), - contract_id: self.config.coinbase_recipient, + contract_id: coinbase_contract_id, }, output::contract::Contract { input_index: 0, @@ -682,7 +529,7 @@ where state_root: Bytes32::zeroed(), }, amount_to_mint, - self.config.consensus_parameters.base_asset_id, + self.consensus_params.base_asset_id, gas_price, ); @@ -735,7 +582,7 @@ where let events = self .relayer .get_events(&da_height) - .map_err(|err| ExecutorError::RelayerError(err.into()))?; + .map_err(|err| ExecutorError::RelayerError(err.to_string()))?; for event in events { root_calculator.push(event.hash().as_ref()); match event { @@ -768,6 +615,7 @@ where tx: MaybeCheckedTransaction, tx_id: &TxId, header: &PartialBlockHeader, + coinbase_contract_id: ContractId, gas_price: Word, execution_data: &mut ExecutionData, execution_kind: ExecutionKind, @@ -791,7 +639,7 @@ where let block_height = *header.height(); let checked_tx = match tx { MaybeCheckedTransaction::Transaction(tx) => tx - .into_checked_basic(block_height, &self.config.consensus_parameters)? + .into_checked_basic(block_height, &self.consensus_params)? .into(), MaybeCheckedTransaction::CheckedTransaction(checked_tx) => checked_tx, }; @@ -800,6 +648,7 @@ where CheckedTransaction::Script(script) => self.execute_create_or_script( script, header, + coinbase_contract_id, gas_price, execution_data, tx_st_transaction, @@ -808,6 +657,7 @@ where CheckedTransaction::Create(create) => self.execute_create_or_script( create, header, + coinbase_contract_id, gas_price, execution_data, tx_st_transaction, @@ -816,6 +666,7 @@ where CheckedTransaction::Mint(mint) => self.execute_mint( mint, header, + coinbase_contract_id, gas_price, execution_data, tx_st_transaction, @@ -824,10 +675,12 @@ where } } + #[allow(clippy::too_many_arguments)] fn execute_mint( &self, checked_mint: Checked, header: &PartialBlockHeader, + coinbase_contract_id: ContractId, gas_price: Word, execution_data: &mut ExecutionData, block_st_transaction: &mut StorageTransaction, @@ -914,7 +767,7 @@ where let mut vm_db = VmStorage::new( &mut sub_block_db_commit, &header.consensus, - self.config.coinbase_recipient, + coinbase_contract_id, ); fuel_vm::interpreter::contract::balance_increase( @@ -923,7 +776,7 @@ where mint.mint_asset_id(), *mint.mint_amount(), ) - .map_err(|e| anyhow::anyhow!(format!("{e}"))) + .map_err(|e| format!("{e}")) .map_err(ExecutorError::CoinbaseCannotIncreaseBalance)?; sub_block_db_commit.commit()?; @@ -983,6 +836,7 @@ where &self, mut checked_tx: Checked, header: &PartialBlockHeader, + coinbase_contract_id: ContractId, gas_price: Word, execution_data: &mut ExecutionData, tx_st_transaction: &mut StorageTransaction, @@ -998,9 +852,7 @@ where if self.options.utxo_validation { checked_tx = checked_tx - .check_predicates(&CheckPredicateParams::from( - &self.config.consensus_parameters, - )) + .check_predicates(&CheckPredicateParams::from(&self.consensus_params)) .map_err(|e| { ExecutorError::TransactionValidity( TransactionValidityError::Validation(e), @@ -1016,7 +868,7 @@ where )?; // validate transaction signature checked_tx = checked_tx - .check_signatures(&self.config.consensus_parameters.chain_id) + .check_signatures(&self.consensus_params.chain_id) .map_err(TransactionValidityError::from)?; debug_assert!(checked_tx.checks().contains(Checks::Signatures)); } @@ -1039,16 +891,16 @@ where let vm_db = VmStorage::new( &mut sub_block_db_commit, &header.consensus, - self.config.coinbase_recipient, + coinbase_contract_id, ); let mut vm = Interpreter::with_storage( vm_db, - InterpreterParams::new(gas_price, &self.config.consensus_parameters), + InterpreterParams::new(gas_price, &self.consensus_params), ); - let gas_costs = &self.config.consensus_parameters.gas_costs; - let fee_params = &self.config.consensus_parameters.fee_params; + let gas_costs = &self.consensus_params.gas_costs; + let fee_params = &self.consensus_params.fee_params; let ready_tx = checked_tx .clone() @@ -1057,7 +909,7 @@ where let vm_result: StateTransition<_> = vm .transact(ready_tx) .map_err(|error| ExecutorError::VmExecution { - error: InterpreterError::Storage(anyhow::anyhow!(format!("{error:?}"))), + error: error.to_string(), transaction_id: tx_id, })? .into(); @@ -1066,8 +918,8 @@ where let (state, mut tx, receipts): (_, Tx, _) = vm_result.into_inner(); #[cfg(debug_assertions)] { - tx.precompute(&self.config.consensus_parameters.chain_id)?; - debug_assert_eq!(tx.id(&self.config.consensus_parameters.chain_id), tx_id); + tx.precompute(&self.consensus_params.chain_id)?; + debug_assert_eq!(tx.id(&self.consensus_params.chain_id), tx_id); } for (original_input, produced_input) in checked_tx @@ -1333,7 +1185,7 @@ where let message = db .storage::() .remove(nonce)? - .ok_or_else(|| ExecutorError::MessageAlreadySpent(*nonce))?; + .ok_or(ExecutorError::MessageAlreadySpent(*nonce))?; execution_data .events .push(ExecutorEvent::MessageConsumed(message)); @@ -1361,8 +1213,8 @@ where let fee = tx .refund_fee( - self.config.consensus_parameters.gas_costs(), - self.config.consensus_parameters.fee_params(), + self.consensus_params.gas_costs(), + self.consensus_params.fee_params(), used_gas, gas_price, ) @@ -1577,7 +1429,7 @@ where vm: &Interpreter, Tx>, receipts: &[Receipt], ) { - if self.config.backtrace { + if self.options.backtrace { if let Some(backtrace) = receipts .iter() .find_map(Receipt::result) diff --git a/crates/services/executor/src/lib.rs b/crates/services/executor/src/lib.rs index b1747080..9079243b 100644 --- a/crates/services/executor/src/lib.rs +++ b/crates/services/executor/src/lib.rs @@ -3,15 +3,9 @@ #![deny(unused_crate_dependencies)] #![deny(warnings)] -mod config; - pub mod executor; pub mod ports; pub mod refs; -pub struct BlockExecutor {} - -pub use config::Config; - #[cfg(test)] fuel_core_trace::enable_tracing!(); diff --git a/crates/services/p2p/Cargo.toml b/crates/services/p2p/Cargo.toml index ac3a8f32..2ab31a5d 100644 --- a/crates/services/p2p/Cargo.toml +++ b/crates/services/p2p/Cargo.toml @@ -43,7 +43,7 @@ quick-protobuf = "0.8.1" quick-protobuf-codec = "0.3.0" rand = { workspace = true } serde = { workspace = true, features = ["derive"] } -serde_with = "1.11" +serde_with = { workspace = true } sha2 = "0.10" thiserror = "1.0.47" tokio = { workspace = true, features = ["sync"] } diff --git a/crates/services/producer/src/block_producer.rs b/crates/services/producer/src/block_producer.rs index b964f682..16492f6b 100644 --- a/crates/services/producer/src/block_producer.rs +++ b/crates/services/producer/src/block_producer.rs @@ -126,6 +126,7 @@ where let component = Components { header_to_produce: header, transactions_source: source, + coinbase_recipient: self.config.coinbase_recipient.unwrap_or_default(), gas_price, gas_limit: max_gas, }; @@ -231,6 +232,7 @@ where let component = Components { header_to_produce: header, transactions_source: transactions.clone(), + coinbase_recipient: self.config.coinbase_recipient.unwrap_or_default(), gas_price, gas_limit: u64::MAX, }; diff --git a/crates/services/txpool/src/mock_db.rs b/crates/services/txpool/src/mock_db.rs index 0b83e7a5..962a59ed 100644 --- a/crates/services/txpool/src/mock_db.rs +++ b/crates/services/txpool/src/mock_db.rs @@ -69,13 +69,7 @@ impl MockDb { impl TxPoolDb for MockDb { fn utxo(&self, utxo_id: &UtxoId) -> StorageResult> { - Ok(self - .data - .lock() - .unwrap() - .coins - .get(utxo_id) - .map(Clone::clone)) + Ok(self.data.lock().unwrap().coins.get(utxo_id).cloned()) } fn contract_exist(&self, contract_id: &ContractId) -> StorageResult { @@ -88,7 +82,7 @@ impl TxPoolDb for MockDb { } fn message(&self, id: &Nonce) -> StorageResult> { - Ok(self.data.lock().unwrap().messages.get(id).map(Clone::clone)) + Ok(self.data.lock().unwrap().messages.get(id).cloned()) } fn is_message_spent(&self, id: &Nonce) -> StorageResult { diff --git a/crates/services/upgradable-executor/Cargo.toml b/crates/services/upgradable-executor/Cargo.toml new file mode 100644 index 00000000..a0d3dbcc --- /dev/null +++ b/crates/services/upgradable-executor/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "fuel-core-upgradable-executor" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +keywords = ["blockchain", "fuel", "fuel-vm", "upgradable"] +license = { workspace = true } +repository = { workspace = true } +description = "Fuel Block Upgradable Executor" +build = "build.rs" + +[dependencies] +anyhow = { workspace = true, optional = true } +fuel-core-executor = { workspace = true } +fuel-core-storage = { workspace = true } +fuel-core-types = { workspace = true } +fuel-core-wasm-executor = { workspace = true, optional = true } +lazy_static = { workspace = true, optional = true } +postcard = { workspace = true, optional = true } +wasmtime = { version = "18.0.1", default-features = false, features = [ + "cache", + "cranelift", + "parallel-compilation", + "pooling-allocator", + "runtime", +], optional = true } + +[dev-dependencies] +fuel-core-storage = { workspace = true, features = ["test-helpers"] } +fuel-core-types = { workspace = true, features = ["test-helpers"] } + +[features] +default = ["std"] +std = ["fuel-core-executor/std", "fuel-core-storage/std", "fuel-core-types/std"] +wasm-executor = [ + "dep:anyhow", + "dep:lazy_static", + "dep:postcard", + "dep:fuel-core-wasm-executor", + "dep:wasmtime", +] +test-helpers = [ + "fuel-core-storage/test-helpers", + "fuel-core-types/test-helpers", +] diff --git a/crates/services/upgradable-executor/build.rs b/crates/services/upgradable-executor/build.rs new file mode 100644 index 00000000..c4a6f9ea --- /dev/null +++ b/crates/services/upgradable-executor/build.rs @@ -0,0 +1,68 @@ +use std::{ + env, + path::{ + Path, + PathBuf, + }, + process::Command, +}; + +fn main() { + let wasm_executor_enabled = env::var_os("CARGO_FEATURE_WASM_EXECUTOR").is_some(); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=wasm-executor/src/*"); + + if !wasm_executor_enabled { + return; + } + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir); + + let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); + + let target_dir = format!("--target-dir={}", dest_path.to_string_lossy()); + + let args = vec![ + "build".to_owned(), + "-p".to_owned(), + "fuel-core-wasm-executor".to_owned(), + "--target=wasm32-unknown-unknown".to_owned(), + "--no-default-features".to_owned(), + "--release".to_owned(), + target_dir, + ]; + + let mut cargo = Command::new(cargo); + cargo.env("CARGO_PROFILE_RELEASE_LTO", "true"); + cargo.env("CARGO_PROFILE_RELEASE_PANIC", "abort"); + cargo.env("CARGO_PROFILE_RELEASE_CODEGEN_UNITS", "1"); + cargo.env("CARGO_PROFILE_RELEASE_OPT_LEVEL", "3"); + cargo.env("CARGO_PROFILE_RELEASE_STRIP", "symbols"); + cargo.current_dir(project_root()).args(args); + + let output = cargo.output(); + + match output { + Ok(output) => { + if !output.status.success() { + println!( + "cargo:warning=Got an error status during compiling WASM executor: {:?}", + output + ); + } + } + Err(err) => { + println!("cargo:warning={:?}", err); + } + } +} + +fn project_root() -> PathBuf { + Path::new(&env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(1) + .unwrap() + .to_path_buf() +} diff --git a/crates/services/upgradable-executor/src/config.rs b/crates/services/upgradable-executor/src/config.rs new file mode 100644 index 00000000..bf2e0140 --- /dev/null +++ b/crates/services/upgradable-executor/src/config.rs @@ -0,0 +1,24 @@ +use fuel_core_executor::executor::ExecutionOptions; +use fuel_core_types::fuel_tx::ConsensusParameters; + +#[derive(Clone, Debug, Default)] +pub struct Config { + /// Network-wide common parameters used for validating the chain. + /// The executor already has these parameters, and this field allows us + /// to override the existing value. + pub consensus_parameters: ConsensusParameters, + /// Print execution backtraces if transaction execution reverts. + pub backtrace: bool, + /// Default mode for utxo_validation + pub utxo_validation_default: bool, +} + +impl From<&Config> for ExecutionOptions { + fn from(value: &Config) -> Self { + Self { + utxo_validation: value.utxo_validation_default, + backtrace: value.backtrace, + consensus_params: Some(value.consensus_parameters.clone()), + } + } +} diff --git a/crates/services/upgradable-executor/src/executor.rs b/crates/services/upgradable-executor/src/executor.rs new file mode 100644 index 00000000..a65a1d26 --- /dev/null +++ b/crates/services/upgradable-executor/src/executor.rs @@ -0,0 +1,280 @@ +use crate::config::Config; +use fuel_core_executor::{ + executor::{ + ExecutionBlockWithSource, + ExecutionOptions, + OnceTransactionsSource, + }, + ports::{ + RelayerPort, + TransactionsSource, + }, +}; +use fuel_core_storage::{ + column::Column, + kv_store::KeyValueInspect, + transactional::{ + AtomicView, + Changes, + Modifiable, + }, +}; +#[cfg(any(test, feature = "test-helpers"))] +use fuel_core_types::services::executor::UncommittedResult; +use fuel_core_types::{ + blockchain::primitives::DaBlockHeight, + fuel_tx::Transaction, + fuel_types::BlockHeight, + services::{ + block_producer::Components, + executor::{ + ExecutionResult, + ExecutionTypes, + Result as ExecutorResult, + TransactionExecutionStatus, + }, + Uncommitted, + }, +}; +use std::sync::Arc; + +/// The upgradable executor supports the WASM version of the state transition function. +/// If the block has a version the same as a native executor, we will use it. +/// If not, the WASM version of the state transition function will be used +/// (if the database has a corresponding bytecode). +pub struct Executor { + pub storage_view_provider: S, + pub relayer_view_provider: R, + pub config: Arc, + #[cfg(feature = "wasm-executor")] + engine: wasmtime::Engine, + #[cfg(feature = "wasm-executor")] + module: wasmtime::Module, +} + +impl Executor { + pub fn new( + storage_view_provider: S, + relayer_view_provider: R, + config: Config, + ) -> Self { + Self { + storage_view_provider, + relayer_view_provider, + config: Arc::new(config), + #[cfg(feature = "wasm-executor")] + engine: crate::DEFAULT_ENGINE.clone(), + #[cfg(feature = "wasm-executor")] + module: crate::COMPILED_UNDERLYING_EXECUTOR.clone(), + } + } +} + +impl Executor +where + R: AtomicView, + R::View: RelayerPort + Send + Sync + 'static, + D: AtomicView + Modifiable, + D::View: KeyValueInspect + Send + Sync + 'static, +{ + #[cfg(any(test, feature = "test-helpers"))] + /// Executes the block and commits the result of the execution into the inner `Database`. + pub fn execute_and_commit( + &mut self, + block: fuel_core_types::services::executor::ExecutionBlock, + ) -> fuel_core_types::services::executor::Result { + let (result, changes) = self.execute_without_commit(block)?.into(); + + self.storage_view_provider.commit_changes(changes)?; + Ok(result) + } +} + +impl Executor +where + S: AtomicView, + S::View: KeyValueInspect + Send + Sync + 'static, + R: AtomicView, + R::View: RelayerPort + Send + Sync + 'static, +{ + #[cfg(any(test, feature = "test-helpers"))] + /// Executes the block and returns the result of the execution with storage changes. + pub fn execute_without_commit( + &self, + block: fuel_core_types::services::executor::ExecutionBlock, + ) -> fuel_core_types::services::executor::Result> { + self.execute_without_commit_with_coinbase(block, Default::default(), 0) + } + + #[cfg(any(test, feature = "test-helpers"))] + /// The analog of the [`Self::execute_without_commit`] method, + /// but with the ability to specify the coinbase recipient and the gas price. + pub fn execute_without_commit_with_coinbase( + &self, + block: fuel_core_types::services::executor::ExecutionBlock, + coinbase_recipient: fuel_core_types::fuel_types::ContractId, + gas_price: u64, + ) -> fuel_core_types::services::executor::Result> { + let component = match block { + ExecutionTypes::DryRun(_) => { + panic!("It is not possible to commit the dry run result"); + } + ExecutionTypes::Production(block) => ExecutionTypes::Production(Components { + header_to_produce: block.header, + transactions_source: OnceTransactionsSource::new(block.transactions), + coinbase_recipient, + gas_price, + gas_limit: u64::MAX, + }), + ExecutionTypes::Validation(block) => ExecutionTypes::Validation(block), + }; + + let option = self.config.as_ref().into(); + self.execute_inner(component, option) + } +} + +impl Executor +where + S: AtomicView, + S::View: KeyValueInspect + Send + Sync + 'static, + R: AtomicView, + R::View: RelayerPort + Send + Sync + 'static, +{ + /// Executes the block and returns the result of the execution without committing the changes. + pub fn execute_without_commit_with_source( + &self, + block: ExecutionBlockWithSource, + ) -> ExecutorResult> + where + TxSource: TransactionsSource + Send + Sync + 'static, + { + let options = self.config.as_ref().into(); + self.execute_inner(block, options) + } + + /// Executes the block and returns the result of the execution without committing + /// the changes in the dry run mode. + pub fn dry_run( + &self, + component: Components>, + utxo_validation: Option, + ) -> ExecutorResult> { + // fallback to service config value if no utxo_validation override is provided + let utxo_validation = + utxo_validation.unwrap_or(self.config.utxo_validation_default); + + let options = ExecutionOptions { + utxo_validation, + backtrace: self.config.backtrace, + consensus_params: Some(self.config.consensus_parameters.clone()), + }; + + let component = Components { + header_to_produce: component.header_to_produce, + transactions_source: OnceTransactionsSource::new( + component.transactions_source, + ), + coinbase_recipient: Default::default(), + gas_price: component.gas_price, + gas_limit: component.gas_limit, + }; + + let ExecutionResult { + skipped_transactions, + tx_status, + .. + } = self + .execute_inner(ExecutionTypes::DryRun(component), options)? + .into_result(); + + // If one of the transactions fails, return an error. + if let Some((_, err)) = skipped_transactions.into_iter().next() { + return Err(err) + } + + Ok(tx_status) + } + + fn execute_inner( + &self, + block: ExecutionBlockWithSource, + options: ExecutionOptions, + ) -> ExecutorResult> + where + TxSource: TransactionsSource + Send + Sync + 'static, + { + #[cfg(feature = "wasm-executor")] + return self.wasm_execute_inner(block, options); + + #[cfg(not(feature = "wasm-executor"))] + return self.native_execute_inner(block, options); + } + + #[cfg(feature = "wasm-executor")] + fn wasm_execute_inner( + &self, + block: ExecutionBlockWithSource, + options: ExecutionOptions, + ) -> ExecutorResult> + where + TxSource: TransactionsSource + Send + Sync + 'static, + { + let mut source = None; + let block = block.map_p(|component| { + let Components { + header_to_produce, + transactions_source, + coinbase_recipient, + gas_price, + gas_limit, + } = component; + + source = Some(transactions_source); + + Components { + header_to_produce, + transactions_source: (), + coinbase_recipient, + gas_price, + gas_limit, + } + }); + + let storage = self.storage_view_provider.latest_view(); + let relayer = self.relayer_view_provider.latest_view(); + + let instance = crate::instance::Instance::new(&self.engine) + .add_source(source)? + .add_storage(storage)? + .add_relayer(relayer)? + .add_input_data(block, options)?; + + instance.run(&self.module) + } + + #[cfg(not(feature = "wasm-executor"))] + fn native_execute_inner( + &self, + block: ExecutionBlockWithSource, + mut options: ExecutionOptions, + ) -> ExecutorResult> + where + TxSource: TransactionsSource + Send + Sync + 'static, + { + let storage = self.storage_view_provider.latest_view(); + let relayer = self.relayer_view_provider.latest_view(); + let consensus_params = options + .consensus_params + .take() + .unwrap_or_else(|| self.config.consensus_parameters.clone()); + + let instance = fuel_core_executor::executor::ExecutionInstance { + relayer, + database: storage, + consensus_params, + options, + }; + instance.execute_without_commit(block) + } +} diff --git a/crates/services/upgradable-executor/src/instance.rs b/crates/services/upgradable-executor/src/instance.rs new file mode 100644 index 00000000..7f26dcec --- /dev/null +++ b/crates/services/upgradable-executor/src/instance.rs @@ -0,0 +1,574 @@ +use fuel_core_executor::{ + executor::{ + ExecutionBlockWithSource, + ExecutionOptions, + }, + ports::{ + MaybeCheckedTransaction, + RelayerPort, + TransactionsSource, + }, +}; +use fuel_core_storage::{ + column::Column, + kv_store::{ + KeyValueInspect, + Value, + }, +}; +use fuel_core_types::{ + blockchain::primitives::DaBlockHeight, + fuel_tx::Transaction, + fuel_vm::checked_transaction::Checked, + services::executor::{ + Error as ExecutorError, + Result as ExecutorResult, + }, +}; +use fuel_core_wasm_executor::utils::{ + pack_exists_size_result, + unpack_ptr_and_len, + ReturnType, +}; +use std::{ + collections::HashMap, + sync::Arc, +}; +use wasmtime::{ + AsContextMut, + Caller, + Engine, + Func, + Linker, + Memory, + Module, + Store, +}; + +trait CallerHelper { + /// Writes the encoded data to the memory at the provided pointer. + fn write(&mut self, ptr: u32, encoded: &[u8]) -> anyhow::Result<()>; +} + +impl<'a> CallerHelper for Caller<'a, ExecutionState> { + fn write(&mut self, ptr: u32, encoded: &[u8]) -> anyhow::Result<()> { + let memory = self.data_mut().memory.expect("Memory is initialized; qed"); + let mut store = self.as_context_mut(); + memory + .write(&mut store, ptr as usize, encoded) + .map_err(|e| anyhow::anyhow!("Failed to write to the memory: {}", e)) + } +} + +/// The state used by the host functions provided for the WASM executor. +struct ExecutionState { + /// The memory used by the WASM module. + memory: Option, + next_transactions: HashMap>>, + relayer_events: HashMap, +} + +/// The WASM instance has several stages of initialization. +/// When the instance is fully initialized, it is possible to run it. +/// +/// Each stage has a state. Advancing the stage may add a new state that +/// will later be used by the next stages or a `run` function. +/// +/// Currently, the order of the definition of the host functions doesn't matter, +/// but later, the data from previous stages may be used to initialize new stages. +pub struct Instance { + store: Store, + linker: Linker, + stage: Stage, +} + +/// The stage indicates that the instance is fresh and newly created. +pub struct Created; + +impl Instance { + pub fn new(engine: &Engine) -> Instance { + Instance { + store: Store::new( + engine, + ExecutionState { + memory: None, + next_transactions: Default::default(), + relayer_events: Default::default(), + }, + ), + linker: Linker::new(engine), + stage: Created {}, + } + } +} + +impl Instance { + const LATEST_HOST_MODULE: &'static str = "host_v0"; + + /// Adds a new host function to the instance. + pub fn add_method(&mut self, method: &str, func: Func) -> ExecutorResult<()> { + self.linker + .define(&self.store, Self::LATEST_HOST_MODULE, method, func) + .map_err(|e| { + ExecutorError::Other(format!( + "Failed definition of the `{}` function: {}", + method, e + )) + })?; + Ok(()) + } +} + +/// This stage adds host functions to get transactions from the transaction source. +pub struct Source; + +impl Instance { + /// Adds host functions for the `source`. + pub fn add_source( + mut self, + source: Option, + ) -> ExecutorResult> + where + TxSource: TransactionsSource + Send + Sync + 'static, + { + let source = source.map(|source| Arc::new(source)); + let peek_next_txs_size = self.peek_next_txs_size(source.clone()); + self.add_method("peek_next_txs_size", peek_next_txs_size)?; + + let consume_next_txs = self.consume_next_txs(); + self.add_method("consume_next_txs", consume_next_txs)?; + + Ok(Instance { + store: self.store, + linker: self.linker, + stage: Source {}, + }) + } + + fn peek_next_txs_size(&mut self, source: Option>) -> Func + where + TxSource: TransactionsSource + Send + Sync + 'static, + { + let closure = move |mut caller: Caller<'_, ExecutionState>, + gas_limit: u64| + -> anyhow::Result { + let Some(source) = source.clone() else { + return Ok(0); + }; + + let txs: Vec<_> = source + .next(gas_limit) + .into_iter() + .map(|tx| match tx { + MaybeCheckedTransaction::CheckedTransaction(checked) => { + let checked: Checked = checked.into(); + let (tx, _) = checked.into(); + tx + } + MaybeCheckedTransaction::Transaction(tx) => tx, + }) + .collect(); + + let encoded_txs = postcard::to_allocvec(&txs).map_err(|e| { + ExecutorError::Other(format!( + "Failed encoding of the transactions for `peek_next_txs_size` function: {}", + e + )) + })?; + let encoded_size = u32::try_from(encoded_txs.len()).map_err(|e| { + ExecutorError::Other(format!( + "The encoded transactions are more than `u32::MAX`. We support only wasm32: {}", + e + )) + })?; + + caller + .data_mut() + .next_transactions + .entry(encoded_size) + .or_default() + .push(encoded_txs); + Ok(encoded_size) + }; + + Func::wrap(&mut self.store, closure) + } + + fn consume_next_txs(&mut self) -> Func { + let closure = move |mut caller: Caller<'_, ExecutionState>, + output_ptr: u32, + output_size: u32| + -> anyhow::Result<()> { + let encoded = caller + .data_mut() + .next_transactions + .get_mut(&output_size) + .and_then(|vector| vector.pop()) + .unwrap_or_default(); + + caller.write(output_ptr, &encoded) + }; + + Func::wrap(&mut self.store, closure) + } +} + +/// This stage adds host functions for the storage. +pub struct Storage; + +impl Instance { + /// Adds getters to the `storage`. + pub fn add_storage(mut self, storage: S) -> ExecutorResult> + where + S: KeyValueInspect + Send + Sync + 'static, + { + let storage = Arc::new(storage); + + let storage_size_of_value = self.storage_size_of_value(storage.clone()); + self.add_method("storage_size_of_value", storage_size_of_value)?; + + let storage_get = self.storage_get(storage); + self.add_method("storage_get", storage_get)?; + + Ok(Instance { + store: self.store, + linker: self.linker, + stage: Storage {}, + }) + } + + fn storage_size_of_value(&mut self, storage: Arc) -> Func + where + S: KeyValueInspect + Send + Sync + 'static, + { + let closure = move |caller: Caller<'_, ExecutionState>, + key_ptr: u32, + key_len: u32, + column: u32| + -> anyhow::Result { + let column = fuel_core_storage::column::Column::try_from(column) + .map_err(|e| anyhow::anyhow!("Unknown column: {}", e))?; + + let (ptr, len) = (key_ptr as usize, key_len as usize); + let memory = caller + .data() + .memory + .expect("Memory was initialized above; qed"); + + let key = &memory.data(&caller)[ptr..ptr.saturating_add(len)]; + if let Ok(value) = storage.size_of_value(key, column) { + let size = u32::try_from(value.unwrap_or_default()).map_err(|e| { + anyhow::anyhow!( + "The size of the value is more than `u32::MAX`. We support only wasm32: {}", + e + ) + })?; + + Ok(pack_exists_size_result(value.is_some(), size, 0)) + } else { + Ok(pack_exists_size_result(false, 0, 0)) + } + }; + + Func::wrap(&mut self.store, closure) + } + + fn storage_get(&mut self, storage: Arc) -> Func + where + S: KeyValueInspect + Send + Sync + 'static, + { + let closure = move |mut caller: Caller<'_, ExecutionState>, + key_ptr: u32, + key_len: u32, + column: u32, + out_ptr: u32, + out_len: u32| + -> anyhow::Result { + let column = fuel_core_storage::column::Column::try_from(column) + .map_err(|e| anyhow::anyhow!("Unknown column: {}", e))?; + let (ptr, len) = (key_ptr as usize, key_len as usize); + let memory = caller + .data() + .memory + .expect("Memory was initialized above; qed"); + + let key = &memory.data(&caller)[ptr..ptr.saturating_add(len)]; + if let Ok(value) = storage.get(key, column) { + let value = value.ok_or(anyhow::anyhow!("\ + The WASM executor should call `get` only after `storage_size_of_value`."))?; + + if value.len() != out_len as usize { + return Err(anyhow::anyhow!( + "The provided buffer size is not equal to the value size." + )); + } + + caller.write(out_ptr, value.as_slice())?; + Ok(0) + } else { + Ok(1) + } + }; + + Func::wrap(&mut self.store, closure) + } +} + +/// This stage adds host functions for the relayer. +pub struct Relayer; + +impl Instance { + /// Adds host functions for the `relayer`. + pub fn add_relayer(mut self, relayer: R) -> ExecutorResult> + where + R: RelayerPort + Send + Sync + 'static, + { + let relayer = Arc::new(relayer); + + let relayer_enabled = self.relayer_enabled(relayer.clone()); + self.add_method("relayer_enabled", relayer_enabled)?; + + let relayer_size_of_events = self.relayer_size_of_events(relayer); + self.add_method("relayer_size_of_events", relayer_size_of_events)?; + + let relayer_get_events = self.relayer_get_events(); + self.add_method("relayer_get_events", relayer_get_events)?; + + Ok(Instance { + store: self.store, + linker: self.linker, + stage: Relayer {}, + }) + } + + fn relayer_enabled(&mut self, relayer: Arc) -> Func + where + R: RelayerPort + Send + Sync + 'static, + { + let closure = + move |_: Caller<'_, ExecutionState>| -> u32 { relayer.enabled() as u32 }; + + Func::wrap(&mut self.store, closure) + } + + fn relayer_size_of_events(&mut self, relayer: Arc) -> Func + where + R: RelayerPort + Send + Sync + 'static, + { + let closure = move |mut caller: Caller<'_, ExecutionState>, + da_block_height: u64| + -> anyhow::Result { + let da_block_height: DaBlockHeight = da_block_height.into(); + + if let Some(encoded_events) = + caller.data().relayer_events.get(&da_block_height) + { + let encoded_size = u32::try_from(encoded_events.len()).map_err(|e| { + anyhow::anyhow!( + "The size of encoded events is more than `u32::MAX`. We support only wasm32: {}", + e + ) + })?; + Ok(pack_exists_size_result(true, encoded_size, 0)) + } else { + let events = relayer.get_events(&da_block_height); + + if let Ok(events) = events { + let encoded_events = + postcard::to_allocvec(&events).map_err(|e| anyhow::anyhow!(e))?; + let encoded_size = + u32::try_from(encoded_events.len()).map_err(|e| { + anyhow::anyhow!( + "The size of encoded events is more than `u32::MAX`. We support only wasm32: {}", + e + ) + })?; + + caller + .data_mut() + .relayer_events + .insert(da_block_height, encoded_events.into()); + Ok(pack_exists_size_result(true, encoded_size, 0)) + } else { + Ok(pack_exists_size_result(false, 0, 1)) + } + } + }; + + Func::wrap(&mut self.store, closure) + } + + fn relayer_get_events(&mut self) -> Func { + let closure = move |mut caller: Caller<'_, ExecutionState>, + da_block_height: u64, + out_ptr: u32| + -> anyhow::Result<()> { + let da_block_height: DaBlockHeight = da_block_height.into(); + let encoded_events = caller + .data() + .relayer_events + .get(&da_block_height) + .ok_or(anyhow::anyhow!( + "The `relayer_size_of_events` should be called before `relayer_get_events`" + ))? + .clone(); + + caller.write(out_ptr, encoded_events.as_ref())?; + Ok(()) + }; + + Func::wrap(&mut self.store, closure) + } +} + +/// This stage adds host functions - getter for the input data like `Block` and `ExecutionOptions`. +pub struct InputData { + input_component_size: u32, + input_options_size: u32, +} + +impl Instance { + /// Adds getters for the `block` and `options`. + pub fn add_input_data( + mut self, + block: ExecutionBlockWithSource<()>, + options: ExecutionOptions, + ) -> ExecutorResult> { + let encoded_block = postcard::to_allocvec(&block).map_err(|e| { + ExecutorError::Other(format!( + "Failed encoding of the block for `input` function: {}", + e + )) + })?; + let encoded_block_size = u32::try_from(encoded_block.len()).map_err(|e| { + ExecutorError::Other(format!( + "The encoded block is more than `u32::MAX`. We support only wasm32: {}", + e + )) + })?; + + let input_component = self.input_component(encoded_block, encoded_block_size); + self.add_method("input_component", input_component)?; + + let encoded_options = postcard::to_allocvec(&options).map_err(|e| { + ExecutorError::Other(format!( + "Failed encoding of the execution options for `input_options` function: {}", + e + )) + })?; + let encoded_options_size = u32::try_from(encoded_options.len()).map_err(|e| { + ExecutorError::Other(format!( + "The encoded option is more than `u32::MAX`. We support only wasm32: {}", + e + )) + })?; + + let input = self.input_options(encoded_options, encoded_options_size); + self.add_method("input_options", input)?; + + Ok(Instance { + store: self.store, + linker: self.linker, + stage: InputData { + input_component_size: encoded_block_size, + input_options_size: encoded_options_size, + }, + }) + } + + fn input_component( + &mut self, + encoded_block: Vec, + encoded_block_size: u32, + ) -> Func { + let closure = move |mut caller: Caller<'_, ExecutionState>, + out_ptr: u32, + out_len: u32| + -> anyhow::Result<()> { + if out_len != encoded_block_size { + return Err(anyhow::anyhow!( + "The provided buffer size is not equal to the encoded block size." + )); + } + + caller.write(out_ptr, &encoded_block) + }; + + Func::wrap(&mut self.store, closure) + } + + fn input_options( + &mut self, + encoded_options: Vec, + encoded_options_size: u32, + ) -> Func { + let closure = move |mut caller: Caller<'_, ExecutionState>, + out_ptr: u32, + out_len: u32| + -> anyhow::Result<()> { + if out_len != encoded_options_size { + return Err(anyhow::anyhow!( + "The provided buffer size is not equal to the encoded options size." + )); + } + + caller.write(out_ptr, &encoded_options) + }; + + Func::wrap(&mut self.store, closure) + } +} + +impl Instance { + /// Runs the WASM instance from the compiled `Module`. + pub fn run(self, module: &Module) -> ReturnType { + self.internal_run(module).map_err(|e| { + ExecutorError::Other(format!("Error with WASM initialization: {}", e)) + })? + } + + fn internal_run(mut self, module: &Module) -> anyhow::Result { + let instance = self + .linker + .instantiate(&mut self.store, module) + .map_err(|e| { + anyhow::anyhow!("Failed to instantiate the module: {}", e.to_string()) + })?; + + let memory_export = + instance + .get_export(&mut self.store, "memory") + .ok_or_else(|| { + anyhow::anyhow!("memory is not exported under `memory` name") + })?; + + let memory = memory_export.into_memory().ok_or_else(|| { + anyhow::anyhow!("the `memory` export should have memory type") + })?; + self.store.data_mut().memory = Some(memory); + + let run = instance + .get_typed_func::<(u32, u32), u64>(&mut self.store, "execute") + .map_err(|e| { + anyhow::anyhow!("Failed to get the `execute` function: {}", e.to_string()) + })?; + let result = run.call( + &mut self.store, + ( + self.stage.input_component_size, + self.stage.input_options_size, + ), + )?; + + let (ptr, len) = unpack_ptr_and_len(result); + let (ptr, len) = (ptr as usize, len as usize); + let memory = self + .store + .data() + .memory + .expect("Memory was initialized above; qed"); + let slice = &memory.data(&self.store)[ptr..ptr.saturating_add(len)]; + + postcard::from_bytes(slice).map_err(|e| anyhow::anyhow!(e)) + } +} diff --git a/crates/services/upgradable-executor/src/lib.rs b/crates/services/upgradable-executor/src/lib.rs new file mode 100644 index 00000000..6ecca2dd --- /dev/null +++ b/crates/services/upgradable-executor/src/lib.rs @@ -0,0 +1,38 @@ +#![deny(clippy::arithmetic_side_effects)] +#![deny(clippy::cast_possible_truncation)] +#![deny(unused_crate_dependencies)] +#![deny(warnings)] + +#[cfg(feature = "wasm-executor")] +use wasmtime::{ + Config, + Engine, + Module, +}; + +pub mod config; +pub mod executor; + +#[cfg(feature = "wasm-executor")] +pub mod instance; + +/// The WASM version of the underlying [`fuel_core_executor::executor::ExecutionInstance`]. +#[cfg(feature = "wasm-executor")] +pub const WASM_BYTECODE: &[u8] = include_bytes!(concat!( + env!("OUT_DIR"), + "/wasm32-unknown-unknown/release/fuel-core-wasm-executor.wasm" +)); + +#[cfg(feature = "wasm-executor")] +lazy_static::lazy_static! { + /// The default engine for the WASM executor. It is used to compile the WASM bytecode. + pub static ref DEFAULT_ENGINE: Engine = { + Engine::new(&Config::new()).expect("Failed to instantiate the `Engine`") + }; + + /// The default module compiles the WASM bytecode of the native executor. + /// It is used to create the WASM instance of the executor. + pub static ref COMPILED_UNDERLYING_EXECUTOR: Module = { + Module::new(&DEFAULT_ENGINE, WASM_BYTECODE).expect("Failed to compile the underlying executor") + }; +} diff --git a/crates/services/upgradable-executor/wasm-executor/Cargo.toml b/crates/services/upgradable-executor/wasm-executor/Cargo.toml new file mode 100644 index 00000000..32f4211e --- /dev/null +++ b/crates/services/upgradable-executor/wasm-executor/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "fuel-core-wasm-executor" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +keywords = ["blockchain", "fuel", "fuel-vm"] +license = { workspace = true } +repository = { workspace = true } +description = "Fuel Block WASM version of the Executor" + +[[bin]] +name = "fuel-core-wasm-executor" +path = "src/main.rs" + +[lib] +path = "src/lib.rs" + +[dependencies] +anyhow = { workspace = true } +fuel-core-executor = { workspace = true, default-features = false } +fuel-core-storage = { workspace = true, default-features = false } +fuel-core-types = { workspace = true, default-features = false } +postcard = { workspace = true } + +[dev-dependencies] +proptest = { workspace = true } + +[features] +default = ["std"] +std = [] diff --git a/crates/services/upgradable-executor/wasm-executor/src/ext.rs b/crates/services/upgradable-executor/wasm-executor/src/ext.rs new file mode 100644 index 00000000..e4eb5d92 --- /dev/null +++ b/crates/services/upgradable-executor/wasm-executor/src/ext.rs @@ -0,0 +1,281 @@ +use crate::utils::{ + unpack_exists_size_result, + InputType, +}; +use core::marker::PhantomData; +use fuel_core_executor::{ + executor::ExecutionOptions, + ports::MaybeCheckedTransaction, +}; +use fuel_core_types::{ + blockchain::primitives::DaBlockHeight, + fuel_tx::Transaction, + services::relayer::Event, +}; + +/// A wrapper around a `u32` representing a pointer for WASM. +#[derive(Debug)] +#[repr(transparent)] +pub struct Ptr32<'a, T> +where + T: ?Sized, +{ + /// The internal WASM raw pointer value. + _value: u32, + marker: PhantomData &'a T>, +} + +impl<'a, T> Ptr32<'a, T> +where + T: ?Sized, +{ + fn new(value: u32) -> Self { + Self { + _value: value, + marker: Default::default(), + } + } +} + +impl<'a, T> Ptr32<'a, [T]> { + /// Creates a new WASM pointer from the given slice. + pub fn from_slice(slice: &'a [T]) -> Self { + Self::new(slice.as_ptr() as u32) + } +} + +/// A wrapper around a `u32` representing a pointer for WASM. +#[derive(Debug)] +#[repr(transparent)] +pub struct Ptr32Mut<'a, T> +where + T: ?Sized, +{ + /// The internal WASM raw pointer value. + _value: u32, + marker: PhantomData &'a mut T>, +} + +impl<'a, T> Ptr32Mut<'a, T> +where + T: ?Sized, +{ + fn new(value: u32) -> Self { + Self { + _value: value, + marker: Default::default(), + } + } +} + +impl<'a, T> Ptr32Mut<'a, [T]> { + /// Creates a new WASM pointer from the given slice. + pub fn from_slice(slice: &'a mut [T]) -> Self { + Self::new(slice.as_ptr() as u32) + } +} + +mod host { + use super::*; + + #[link(wasm_import_module = "host_v0")] + extern "C" { + // Initialization API + + /// Returns the encoded component as an input to the executor. + pub(crate) fn input_component(output_ptr: Ptr32Mut<[u8]>, output_size: u32); + + /// Returns the encoded execution options as an input to the executor. + pub(crate) fn input_options(output_ptr: Ptr32Mut<[u8]>, output_size: u32); + + // TxSource API + + /// Returns the size of the next encoded transactions. + /// If the size is 0, there are no more transactions. + pub(crate) fn peek_next_txs_size(gas_limit: u64) -> u32; + + /// Consumes the next transactions from the host. + /// Calling this function before `peek_next_txs_size` do nothing. + pub(crate) fn consume_next_txs(output_ptr: Ptr32Mut<[u8]>, output_size: u32); + + // Storage API + + /// Returns the size of the value from the storage. + pub(crate) fn storage_size_of_value( + key_ptr: Ptr32<[u8]>, + key_len: u32, + column: u32, + ) -> u64; + + /// Returns the value from the storage. + pub(crate) fn storage_get( + key_ptr: Ptr32<[u8]>, + key_len: u32, + column: u32, + out_ptr: Ptr32Mut<[u8]>, + out_len: u32, + ) -> ReturnResult; + + // Relayer API + + /// Returns the `true` of relayer is enabled. + pub(crate) fn relayer_enabled() -> bool; + + /// Returns the size of the encoded events at `da_block_height`. + pub(crate) fn relayer_size_of_events(da_block_height: u64) -> u64; + + /// Writes the encoded events at `da_block_height` into the `output_ptr`. + pub(crate) fn relayer_get_events( + da_block_height: u64, + output_ptr: Ptr32Mut<[u8]>, + ); + } +} + +/// The result returned by the host function. +#[repr(transparent)] +pub struct ReturnResult(u16); + +/// Gets the `InputType` by using the host function. The `size` is the size of the encoded input. +pub fn input_component(size: usize) -> anyhow::Result { + let mut encoded_block = vec![0u8; size]; + let size = encoded_block.len(); + unsafe { + host::input_component( + Ptr32Mut::from_slice(encoded_block.as_mut_slice()), + u32::try_from(size).expect("We only support wasm32 target; qed"), + ) + }; + + let input: InputType = postcard::from_bytes(&encoded_block) + .map_err(|e| anyhow::anyhow!("Failed to decode the block: {:?}", e))?; + + Ok(input) +} + +/// Gets the `ExecutionOptions` by using the host function. The `size` is the size of the encoded input. +pub fn input_options(size: usize) -> anyhow::Result { + let mut encoded_block = vec![0u8; size]; + let size = encoded_block.len(); + unsafe { + host::input_options( + Ptr32Mut::from_slice(encoded_block.as_mut_slice()), + u32::try_from(size).expect("We only support wasm32 target; qed"), + ) + }; + + let input: ExecutionOptions = postcard::from_bytes(&encoded_block).map_err(|e| { + anyhow::anyhow!("Failed to decode the execution options: {:?}", e) + })?; + + Ok(input) +} + +/// Gets the next transactions by using the host function. +pub fn next_transactions(gas_limit: u64) -> anyhow::Result> { + let next_size = unsafe { host::peek_next_txs_size(gas_limit) }; + + if next_size == 0 { + return Ok(Vec::new()); + } + + let mut encoded_block = vec![0u8; next_size as usize]; + let size = encoded_block.len(); + + unsafe { + host::consume_next_txs( + Ptr32Mut::from_slice(encoded_block.as_mut_slice()), + u32::try_from(size).expect("We only support wasm32 target; qed"), + ) + }; + + let txs: Vec = postcard::from_bytes(&encoded_block) + .map_err(|e| anyhow::anyhow!("Failed to decode the transactions: {:?}", e))?; + + Ok(txs + .into_iter() + .map(MaybeCheckedTransaction::Transaction) + .collect()) +} + +/// Gets the size of the value under the `key` in the `column` from the storage. +pub fn size_of_value(key: &[u8], column: u32) -> anyhow::Result> { + let val = unsafe { + host::storage_size_of_value( + Ptr32::from_slice(key), + u32::try_from(key.len()).expect("We only support wasm32 target; qed"), + column, + ) + }; + + let (exists, size, result) = unpack_exists_size_result(val); + + if result != 0 { + return Err(anyhow::anyhow!( + "Failed to get the size of the value from the WASM storage" + )); + } + + if exists { + Ok(Some(size as usize)) + } else { + Ok(None) + } +} + +/// Gets the value under the `key` in the `column` from the storage and copies it to the `out`. +pub fn get(key: &[u8], column: u32, out: &mut [u8]) -> anyhow::Result<()> { + let output_size = out.len(); + let result = unsafe { + host::storage_get( + Ptr32::from_slice(key), + u32::try_from(key.len()).expect("We only support wasm32 target; qed"), + column, + Ptr32Mut::from_slice(out), + u32::try_from(output_size).expect("We only support wasm32 target; qed"), + ) + }; + + if result.0 != 0 { + return Err(anyhow::anyhow!( + "Failed to get the value from the WASM storage" + )); + } + + Ok(()) +} + +/// Returns `true` if the relayer is enabled. Uses a host function. +pub fn relayer_enabled() -> bool { + unsafe { host::relayer_enabled() } +} + +/// Gets the events at the `da_block_height` from the relayer by using host functions. +pub fn relayer_get_events(da_block_height: DaBlockHeight) -> anyhow::Result> { + let size = unsafe { host::relayer_size_of_events(da_block_height.into()) }; + let (exists, size, result) = unpack_exists_size_result(size); + + if result != 0 { + return Err(anyhow::anyhow!( + "Failed to get the size of events from the WASM relayer" + )); + } + + if !exists || size == 0 { + return Ok(vec![]); + } + + let mut encoded_events = vec![0u8; size as usize]; + + unsafe { + host::relayer_get_events( + da_block_height.into(), + Ptr32Mut::from_slice(encoded_events.as_mut_slice()), + ) + }; + + let events: Vec = postcard::from_bytes(&encoded_events) + .map_err(|e| anyhow::anyhow!("Failed to decode the events: {:?}", e))?; + + Ok(events) +} diff --git a/crates/services/upgradable-executor/wasm-executor/src/lib.rs b/crates/services/upgradable-executor/wasm-executor/src/lib.rs new file mode 100644 index 00000000..2bc89032 --- /dev/null +++ b/crates/services/upgradable-executor/wasm-executor/src/lib.rs @@ -0,0 +1,7 @@ +//! The library defines some common parts used by the WASM executor and upgradable executor. + +#![deny(clippy::arithmetic_side_effects)] +#![deny(clippy::cast_possible_truncation)] +#![deny(warnings)] + +pub mod utils; diff --git a/crates/services/upgradable-executor/wasm-executor/src/main.rs b/crates/services/upgradable-executor/wasm-executor/src/main.rs new file mode 100644 index 00000000..af08ebc0 --- /dev/null +++ b/crates/services/upgradable-executor/wasm-executor/src/main.rs @@ -0,0 +1,116 @@ +//! This is the main entry point for the wasm executor. +//! The module defines the `execute` function that the host will call. +//! The result of the execution is the `ExecutionResult` with the list of changes to the storage. +//! +//! During return, the result of the execution modules leaks the memory, +//! allowing the WASM runner to get access to the data. +//! +//! Currently, the WASM executor is designed only for one block execution per WASM instance. +//! But later, it will be improved, and the instance will be reusable. + +#![deny(clippy::arithmetic_side_effects)] +#![deny(clippy::cast_possible_truncation)] +#![deny(unused_crate_dependencies)] +#![deny(warnings)] + +use crate::{ + relayer::WasmRelayer, + storage::WasmStorage, + tx_source::WasmTxSource, + utils::{ + pack_ptr_and_len, + ReturnType, + }, +}; +use fuel_core_executor::executor::ExecutionInstance; +use fuel_core_types::{ + fuel_tx::ConsensusParameters, + services::{ + block_producer::Components, + executor::{ + Error as ExecutorError, + ExecutionResult, + }, + Uncommitted, + }, +}; +use fuel_core_wasm_executor as _; + +mod ext; +mod relayer; +mod storage; +mod tx_source; +pub mod utils; + +#[no_mangle] +pub extern "C" fn execute(component_len: u32, options_len: u32) -> u64 { + let result = execute_without_commit(component_len, options_len); + let encoded = postcard::to_allocvec(&result).expect("Failed to encode the result"); + let static_slice = encoded.leak(); + pack_ptr_and_len( + static_slice.as_ptr() as u32, + u32::try_from(static_slice.len()).expect("We only support wasm32 target; qed"), + ) +} + +pub fn execute_without_commit(component_len: u32, options_len: u32) -> ReturnType { + let block = ext::input_component(component_len as usize) + .map_err(|e| ExecutorError::Other(e.to_string()))?; + let mut options = ext::input_options(options_len as usize) + .map_err(|e| ExecutorError::Other(e.to_string()))?; + + let consensus_params = if let Some(consensus_params) = options.consensus_params.take() + { + consensus_params + } else { + ConsensusParameters::default() + }; + + let instance = ExecutionInstance { + relayer: WasmRelayer {}, + database: WasmStorage {}, + consensus_params, + options, + }; + + let block = block.map_p(|component| { + let Components { + header_to_produce, + gas_limit, + gas_price, + coinbase_recipient, + .. + } = component; + + Components { + header_to_produce, + gas_limit, + gas_price, + transactions_source: WasmTxSource::new(), + coinbase_recipient, + } + }); + + let ( + ExecutionResult { + block, + skipped_transactions, + tx_status, + events, + }, + changes, + ) = instance.execute_without_commit(block)?.into(); + + Ok(Uncommitted::new( + ExecutionResult { + block, + skipped_transactions, + tx_status, + events, + }, + changes, + )) +} + +// It is not used. It was added to make clippy happy. +fn main() {} diff --git a/crates/services/upgradable-executor/wasm-executor/src/relayer.rs b/crates/services/upgradable-executor/wasm-executor/src/relayer.rs new file mode 100644 index 00000000..940a903a --- /dev/null +++ b/crates/services/upgradable-executor/wasm-executor/src/relayer.rs @@ -0,0 +1,18 @@ +use crate::ext; +use fuel_core_executor::ports::RelayerPort; +use fuel_core_types::{ + blockchain::primitives::DaBlockHeight, + services::relayer::Event, +}; + +pub struct WasmRelayer; + +impl RelayerPort for WasmRelayer { + fn enabled(&self) -> bool { + ext::relayer_enabled() + } + + fn get_events(&self, da_block_height: &DaBlockHeight) -> anyhow::Result> { + ext::relayer_get_events(*da_block_height) + } +} diff --git a/crates/services/upgradable-executor/wasm-executor/src/storage.rs b/crates/services/upgradable-executor/wasm-executor/src/storage.rs new file mode 100644 index 00000000..add3fe77 --- /dev/null +++ b/crates/services/upgradable-executor/wasm-executor/src/storage.rs @@ -0,0 +1,35 @@ +use super::ext; +use fuel_core_storage::{ + column::Column, + kv_store::{ + KeyValueInspect, + Value, + }, + Result as StorageResult, +}; + +pub struct WasmStorage; + +impl KeyValueInspect for WasmStorage { + type Column = Column; + + fn size_of_value( + &self, + key: &[u8], + column: Self::Column, + ) -> StorageResult> { + ext::size_of_value(key, column.as_u32()).map_err(Into::into) + } + + fn get(&self, key: &[u8], column: Self::Column) -> StorageResult> { + let size = ext::size_of_value(key, column.as_u32())?; + + if let Some(size) = size { + let mut value = vec![0u8; size]; + ext::get(key, column.as_u32(), &mut value)?; + Ok(Some(value.into())) + } else { + Ok(None) + } + } +} diff --git a/crates/services/upgradable-executor/wasm-executor/src/tx_source.rs b/crates/services/upgradable-executor/wasm-executor/src/tx_source.rs new file mode 100644 index 00000000..772b8159 --- /dev/null +++ b/crates/services/upgradable-executor/wasm-executor/src/tx_source.rs @@ -0,0 +1,19 @@ +use crate::ext; +use fuel_core_executor::ports::{ + MaybeCheckedTransaction, + TransactionsSource, +}; + +pub struct WasmTxSource; + +impl WasmTxSource { + pub fn new() -> Self { + Self + } +} + +impl TransactionsSource for WasmTxSource { + fn next(&self, gas_limit: u64) -> Vec { + ext::next_transactions(gas_limit).expect("Failed to get next transactions") + } +} diff --git a/crates/services/upgradable-executor/wasm-executor/src/utils.rs b/crates/services/upgradable-executor/wasm-executor/src/utils.rs new file mode 100644 index 00000000..18c67386 --- /dev/null +++ b/crates/services/upgradable-executor/wasm-executor/src/utils.rs @@ -0,0 +1,73 @@ +use fuel_core_executor::executor::ExecutionBlockWithSource; +use fuel_core_storage::transactional::Changes; +use fuel_core_types::services::{ + executor::{ + ExecutionResult, + Result as ExecutorResult, + }, + Uncommitted, +}; + +/// Pack a pointer and length into an `u64`. +pub fn pack_ptr_and_len(ptr: u32, len: u32) -> u64 { + (u64::from(len) << 32) | u64::from(ptr) +} + +/// Unpacks an `u64` into the pointer and length. +pub fn unpack_ptr_and_len(val: u64) -> (u32, u32) { + let ptr = u32::try_from(val & (u32::MAX as u64)) + .expect("It ony contains first 32 bytes; qed"); + let len = u32::try_from(val >> 32).expect("It ony contains first 32 bytes; qed"); + + (ptr, len) +} + +/// Pack a `exists`, `size` and `result` into one `u64`. +pub fn pack_exists_size_result(exists: bool, size: u32, result: u16) -> u64 { + (u64::from(result) << 33 | u64::from(size) << 1) | u64::from(exists) +} + +/// Unpacks an `u64` into `exists`, `size` and `result`. +pub fn unpack_exists_size_result(val: u64) -> (bool, u32, u16) { + let exists = (val & 1u64) != 0; + let size = u32::try_from((val >> 1) & (u32::MAX as u64)) + .expect("It only contains first 32 bytes; qed"); + let result = u16::try_from(val >> 33 & (u16::MAX as u64)) + .expect("It only contains first 16 bytes; qed"); + + (exists, size, result) +} + +pub type InputType = ExecutionBlockWithSource<()>; + +pub type ReturnType = ExecutorResult>; + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::prop::*; + + proptest::proptest! { + #[test] + fn can_pack_any_values(exists: bool, size: u32, result: u16) { + pack_exists_size_result(exists, size, result); + } + + #[test] + fn can_unpack_any_values(value: u64) { + let _ = unpack_exists_size_result(value); + } + + + #[test] + fn unpacks_packed_values(exists: bool, size: u32, result: u16) { + let packed = pack_exists_size_result(exists, size, result); + let (unpacked_exists, unpacked_size, unpacked_result) = + unpack_exists_size_result(packed); + + proptest::prop_assert_eq!(exists, unpacked_exists); + proptest::prop_assert_eq!(size, unpacked_size); + proptest::prop_assert_eq!(result, unpacked_result); + } + } +} diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index df9ea8b0..655ab988 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -27,6 +27,7 @@ fuel-vm-private = { workspace = true, default-features = false } impl-tools = "0.10" itertools = { workspace = true, features = ["use_alloc"] } mockall = { workspace = true, optional = true } +num_enum = { workspace = true } paste = "1" postcard = { workspace = true, features = ["alloc"] } primitive-types = { workspace = true, default-features = false } @@ -45,4 +46,6 @@ fuel-core-types = { workspace = true, default-features = false, features = [ test-case = { workspace = true } [features] +default = ["std"] +std = ["fuel-core-types/std"] test-helpers = ["dep:mockall", "dep:rand"] diff --git a/crates/storage/src/column.rs b/crates/storage/src/column.rs index 3848a030..078f20fc 100644 --- a/crates/storage/src/column.rs +++ b/crates/storage/src/column.rs @@ -16,6 +16,7 @@ use crate::kv_store::StorageColumn; Eq, enum_iterator::Sequence, Hash, + num_enum::TryFromPrimitive, )] pub enum Column { /// The column id of metadata about the blockchain diff --git a/crates/storage/src/kv_store.rs b/crates/storage/src/kv_store.rs index ae60b9a0..9bdb0809 100644 --- a/crates/storage/src/kv_store.rs +++ b/crates/storage/src/kv_store.rs @@ -115,7 +115,7 @@ pub trait KeyValueMutate: KeyValueInspect { } /// The operation to write into the storage. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)] pub enum WriteOperation { /// Insert the value into the storage. Insert(Value), diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 4d25f4d4..50f3f1d8 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -80,7 +80,7 @@ impl From for Error { impl From for ExecutorError { fn from(e: Error) -> Self { - ExecutorError::StorageError(anyhow::anyhow!(e)) + ExecutorError::StorageError(e.to_string()) } } diff --git a/crates/types/src/blockchain/header.rs b/crates/types/src/blockchain/header.rs index d6a55cb7..32154fb3 100644 --- a/crates/types/src/blockchain/header.rs +++ b/crates/types/src/blockchain/header.rs @@ -147,6 +147,7 @@ impl BlockHeader { } #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(any(test, feature = "test-helpers"), derive(Default))] /// A partially complete fuel block header that doesn't not /// have any generated fields because it has not been executed yet. diff --git a/crates/types/src/blockchain/primitives.rs b/crates/types/src/blockchain/primitives.rs index bbf63ce5..bc3c9e04 100644 --- a/crates/types/src/blockchain/primitives.rs +++ b/crates/types/src/blockchain/primitives.rs @@ -20,13 +20,13 @@ use derive_more::{ UpperHex, }; use secrecy::{ - zeroize, CloneableSecret, DebugSecret, }; use zeroize::Zeroize; #[derive(Clone, Copy, Debug, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] /// Empty generated fields. pub struct Empty; diff --git a/crates/types/src/services.rs b/crates/types/src/services.rs index aa5bc8eb..3082ae3c 100644 --- a/crates/types/src/services.rs +++ b/crates/types/src/services.rs @@ -13,6 +13,7 @@ pub mod txpool; /// The uncommitted `Result` of some action with storage changes. /// The user should commit the result by itself. #[derive(Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[must_use] pub struct Uncommitted { /// The result of the action. diff --git a/crates/types/src/services/block_producer.rs b/crates/types/src/services/block_producer.rs index 586ea008..d1e03335 100644 --- a/crates/types/src/services/block_producer.rs +++ b/crates/types/src/services/block_producer.rs @@ -1,9 +1,13 @@ //! Types related to block producer service. -use crate::blockchain::header::PartialBlockHeader; +use crate::{ + blockchain::header::PartialBlockHeader, + fuel_tx::ContractId, +}; /// The components required to produce a block. #[derive(Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Components { /// The partial block header of the future block without transactions related information. pub header_to_produce: PartialBlockHeader, @@ -11,6 +15,8 @@ pub struct Components { /// It can be a predefined vector of transactions, a stream of transactions, /// or any other type that carries the transactions. pub transactions_source: Source, + /// The `ContractId` of the fee recipient. + pub coinbase_recipient: ContractId, /// The gas price for all transactions in the block. pub gas_price: u64, /// The gas limit of the block. diff --git a/crates/types/src/services/executor.rs b/crates/types/src/services/executor.rs index f1b82945..78ae5bdd 100644 --- a/crates/types/src/services/executor.rs +++ b/crates/types/src/services/executor.rs @@ -25,13 +25,10 @@ use crate::{ }, fuel_vm::{ checked_transaction::CheckError, - Backtrace, - InterpreterError, ProgramState, }, services::Uncommitted, }; -use std::error::Error as StdError; /// The alias for executor result. pub type Result = core::result::Result; @@ -40,6 +37,7 @@ pub type UncommittedResult = Uncommitted; /// The result of transactions execution. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug)] pub struct ExecutionResult { /// Created block during the execution of transactions. It contains only valid transactions. @@ -55,6 +53,7 @@ pub struct ExecutionResult { /// The event represents some internal state changes caused by the block execution. #[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Event { /// Imported a new spendable message from the relayer. MessageImported(Message), @@ -68,6 +67,7 @@ pub enum Event { /// The status of a transaction after it is executed. #[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TransactionExecutionStatus { /// The id of the transaction. pub id: Bytes32, @@ -77,6 +77,7 @@ pub struct TransactionExecutionStatus { /// The result of transaction execution. #[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum TransactionExecutionResult { /// Transaction was successfully executed. Success { @@ -121,6 +122,7 @@ impl TransactionExecutionResult { /// Execution wrapper where the types /// depend on the type of execution. #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ExecutionTypes { /// DryRun mode where P is being produced. DryRun(P), @@ -289,7 +291,8 @@ impl ExecutionKind { } #[allow(missing_docs)] -#[derive(Debug, derive_more::Display, derive_more::From)] +#[derive(Debug, PartialEq, derive_more::Display, derive_more::From)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[non_exhaustive] pub enum Error { #[display(fmt = "Transaction id was already used: {_0:#x}")] @@ -311,7 +314,7 @@ pub enum Error { #[display(fmt = "The `Mint` transaction mismatches expectations.")] MintMismatch, #[display(fmt = "Can't increase the balance of the coinbase contract: {_0}.")] - CoinbaseCannotIncreaseBalance(anyhow::Error), + CoinbaseCannotIncreaseBalance(String), #[display(fmt = "Coinbase amount mismatches with expected.")] CoinbaseAmountMismatch, #[display(fmt = "Coinbase gas price mismatches with expected.")] @@ -321,20 +324,17 @@ pub enum Error { // TODO: Replace with `fuel_core_storage::Error` when execution error will live in the // `fuel-core-executor`. #[display(fmt = "got error during work with storage {_0}")] - StorageError(anyhow::Error), + StorageError(String), #[display(fmt = "got error during work with relayer {_0}")] - RelayerError(Box), + RelayerError(String), #[display(fmt = "Transaction({transaction_id:#x}) execution error: {error:?}")] VmExecution { - // TODO: Replace with `fuel_core_storage::Error` when execution error will live in the - // `fuel-core-executor`. - error: InterpreterError, + // TODO: Use `InterpreterError` when `InterpreterError` implements serde + error: String, transaction_id: Bytes32, }, #[display(fmt = "{_0:?}")] InvalidTransaction(CheckError), - #[display(fmt = "Execution error with backtrace")] - Backtrace(Box), #[display(fmt = "Transaction doesn't match expected result: {transaction_id:#x}")] InvalidTransactionOutcome { transaction_id: Bytes32 }, #[display(fmt = "The amount of charged fees is invalid")] @@ -355,6 +355,9 @@ pub enum Error { PreviousBlockIsNotFound, #[display(fmt = "The relayer gives incorrect messages for the requested da height")] RelayerGivesIncorrectMessages, + /// It is possible to occur untyped errors in the case of the upgrade. + #[display(fmt = "Occurred untyped error: {_0}")] + Other(String), } impl From for anyhow::Error { @@ -363,12 +366,6 @@ impl From for anyhow::Error { } } -impl From for Error { - fn from(e: Backtrace) -> Self { - Error::Backtrace(Box::new(e)) - } -} - impl From for Error { fn from(e: CheckError) -> Self { Self::InvalidTransaction(e) @@ -382,7 +379,8 @@ impl From for Error { } #[allow(missing_docs)] -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[non_exhaustive] pub enum TransactionValidityError { #[error("Coin({0:#x}) input was already spent")] diff --git a/deployment/Dockerfile b/deployment/Dockerfile index bd5afe23..e83b75b9 100644 --- a/deployment/Dockerfile +++ b/deployment/Dockerfile @@ -3,7 +3,7 @@ FROM --platform=$BUILDPLATFORM tonistiigi/xx AS xx FROM --platform=$BUILDPLATFORM rust:1.75.0 AS chef ARG TARGETPLATFORM -RUN cargo install cargo-chef +RUN cargo install cargo-chef && rustup target add wasm32-unknown-unknown WORKDIR /build/ COPY --from=xx / / diff --git a/deployment/e2e-client.Dockerfile b/deployment/e2e-client.Dockerfile index bced2f74..7741734b 100644 --- a/deployment/e2e-client.Dockerfile +++ b/deployment/e2e-client.Dockerfile @@ -1,6 +1,6 @@ # Stage 1: Build FROM rust:1.75.0 AS chef -RUN cargo install cargo-chef +RUN cargo install cargo-chef && rustup target add wasm32-unknown-unknown WORKDIR /build/ # hadolint ignore=DL3008 RUN apt-get update && \ diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 399e7f49..0551b632 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -64,3 +64,4 @@ pretty_assertions = "1.4" default = ["fuel-core/default", "relayer"] p2p = ["fuel-core/p2p", "fuel-core-p2p"] relayer = ["fuel-core/relayer", "fuel-core-relayer"] +wasm-executor = ["fuel-core/wasm-executor"]