diff --git a/.github/scripts/install_test_binaries.sh b/.github/scripts/install_test_binaries.sh index 3e89dbaea22..53f7d513d00 100755 --- a/.github/scripts/install_test_binaries.sh +++ b/.github/scripts/install_test_binaries.sh @@ -3,7 +3,8 @@ # Note: intended for use only with CI (x86_64 Ubuntu, MacOS or Windows) set -e -GETH_BUILD=${GETH_BUILD:-"1.14.0-87246f3c"} +GETH_BUILD=${GETH_BUILD:-"1.14.8-a9523b64"} +RETH_BUILD=${RETH_BUILD:-"1.0.6"} BIN_DIR=${BIN_DIR:-"$HOME/bin"} @@ -17,32 +18,49 @@ main() { echo "$BIN_DIR" >> "$GITHUB_PATH" fi - install_geth + install_geth & + install_reth & - echo "" - echo "Installed Geth:" - geth version + wait } # Installs geth from https://geth.ethereum.org/downloads install_geth() { case "$PLATFORM" in - linux|darwin) - name="geth-$PLATFORM-amd64-$GETH_BUILD" - curl -s "https://gethstore.blob.core.windows.net/builds/$name.tar.gz" | tar -xzf - - mv -f "$name/geth" ./ - rm -rf "$name" + linux) + NAME="geth-$PLATFORM-amd64-$GETH_BUILD" + curl -sL "https://gethstore.blob.core.windows.net/builds/$NAME.tar.gz" | tar -xzf - + mv -f "$NAME/geth" ./ + rm -rf "$NAME" chmod +x geth ;; *) - name="geth-windows-amd64-$GETH_BUILD" - zip="$name.zip" - curl -so "$zip" "https://gethstore.blob.core.windows.net/builds/$zip" - unzip "$zip" - mv -f "$name/geth.exe" ./ - rm -rf "$name" "$zip" + NAME="geth-windows-amd64-$GETH_BUILD" + curl -so $NAME.zip "https://gethstore.blob.core.windows.net/builds/$NAME.zip" + unzip $NAME.zip + mv -f "$NAME/geth.exe" ./ + rm -rf "$NAME" "$NAME.zip" + ;; + esac + + echo "" + echo "Installed Geth:" + geth version +} + +# Install reth from https://github.com/paradigmxyz/reth/releases +install_reth() { + case "$PLATFORM" in + linux) + NAME="reth-v$RETH_BUILD-x86_64-unknown-linux-gnu" + curl -sL "https://github.com/paradigmxyz/reth/releases/download/v$RETH_BUILD/$NAME.tar.gz" | tar -xzf - + chmod +x reth + + echo "" + echo "Installed Reth:" + reth --version ;; esac } -main +main \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e4dee9142c..bf8b3a3ec63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: rust: - "stable" - "nightly" - - "1.76" # MSRV + - "1.79" # MSRV flags: # No features - "--no-default-features" @@ -34,7 +34,7 @@ jobs: - "--all-features" exclude: # All features on MSRV - - rust: "1.76" # MSRV + - rust: "1.79" # MSRV flags: "--all-features" steps: - uses: actions/checkout@v4 @@ -53,14 +53,14 @@ jobs: cache-on-failure: true # Only run tests on latest stable and above - name: Install cargo-nextest - if: ${{ matrix.rust != '1.76' }} # MSRV + if: ${{ matrix.rust != '1.79' }} # MSRV uses: taiki-e/install-action@nextest - name: build - if: ${{ matrix.rust == '1.76' }} # MSRV + if: ${{ matrix.rust == '1.79' }} # MSRV run: cargo build --workspace ${{ matrix.flags }} - name: test shell: bash - if: ${{ matrix.rust != '1.76' }} # MSRV + if: ${{ matrix.rust != '1.79' }} # MSRV run: cargo nextest run --workspace ${{ matrix.flags }} doctest: @@ -127,33 +127,33 @@ jobs: - name: build ledger run: cargo build -p alloy-signer-ledger --features browser --target wasm32-wasip1 - no-std: + feature-checks: runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - with: - target: riscv32imac-unknown-none-elf - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - name: check - run: ./scripts/check_no_std.sh + - name: cargo hack + run: cargo hack check --feature-powerset --depth 1 - feature-checks: + check-no-std: + name: check no_std ${{ matrix.features }} runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: + targets: riscv32imac-unknown-none-elf - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - name: cargo hack - run: cargo hack check --feature-powerset --depth 1 + - run: ./scripts/check_no_std.sh clippy: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 0492ace7812..03c481e6348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,166 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Bug Fixes + +- [types-eth] Optional Alloy Serde ([#1284](https://github.com/alloy-rs/alloy/issues/1284)) +- `eth_simulateV1` ([#1289](https://github.com/alloy-rs/alloy/issues/1289)) + +### Features + +- Add block num hash helper ([#1304](https://github.com/alloy-rs/alloy/issues/1304)) +- ProviderCall ([#788](https://github.com/alloy-rs/alloy/issues/788)) +- [rpc-types-beacon] `SignedBidSubmissionV4` ([#1303](https://github.com/alloy-rs/alloy/issues/1303)) +- [transport-http] Layer client ([#1227](https://github.com/alloy-rs/alloy/issues/1227)) +- Add blob and proof v1 ([#1300](https://github.com/alloy-rs/alloy/issues/1300)) +- Add types for flat call tracer ([#1292](https://github.com/alloy-rs/alloy/issues/1292)) +- [`node-bindings`] Support appending extra args ([#1299](https://github.com/alloy-rs/alloy/issues/1299)) + +### Miscellaneous Tasks + +- [rpc] Rename witness fields ([#1293](https://github.com/alloy-rs/alloy/issues/1293)) +- [engine] `no_std` Checks ([#1298](https://github.com/alloy-rs/alloy/issues/1298)) + +### Refactor + +- Separate transaction builders for tx types ([#1259](https://github.com/alloy-rs/alloy/issues/1259)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Bug Fixes + +- Add missing conversion ([#1287](https://github.com/alloy-rs/alloy/issues/1287)) + +### Miscellaneous Tasks + +- Release 0.3.5 +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Bug Fixes + +- `debug_traceCallMany` and `trace_callMany` ([#1278](https://github.com/alloy-rs/alloy/issues/1278)) +- Serde for `eth_simulateV1` ([#1273](https://github.com/alloy-rs/alloy/issues/1273)) + +### Features + +- [engine] Optional Serde ([#1283](https://github.com/alloy-rs/alloy/issues/1283)) +- [alloy-rpc-types-eth] Optional serde ([#1276](https://github.com/alloy-rs/alloy/issues/1276)) +- Improve node bindings ([#1279](https://github.com/alloy-rs/alloy/issues/1279)) +- Add serde for NumHash ([#1277](https://github.com/alloy-rs/alloy/issues/1277)) +- [engine] No_std engine types ([#1268](https://github.com/alloy-rs/alloy/issues/1268)) +- No_std eth rpc types ([#1252](https://github.com/alloy-rs/alloy/issues/1252)) + +### Miscellaneous Tasks + +- Release 0.3.4 +- Remove eth rpc types dep from engine types ([#1280](https://github.com/alloy-rs/alloy/issues/1280)) +- Swap `BlockHashOrNumber` alias and struct name ([#1270](https://github.com/alloy-rs/alloy/issues/1270)) +- [consensus] Remove Header Method ([#1271](https://github.com/alloy-rs/alloy/issues/1271)) +- [consensus] Alloc by Default ([#1272](https://github.com/alloy-rs/alloy/issues/1272)) +- [network-primitives] Remove alloc Vec Dep ([#1267](https://github.com/alloy-rs/alloy/issues/1267)) + +### Other + +- Add trait methods `cumulative_gas_used` and `state_root` to `ReceiptResponse` ([#1275](https://github.com/alloy-rs/alloy/issues/1275)) +- Implement `seal` helper for `Header` ([#1269](https://github.com/alloy-rs/alloy/issues/1269)) + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Bug Fixes + +- [rpc-types-trace] Use rpc-types Log in OtsReceipt ([#1261](https://github.com/alloy-rs/alloy/issues/1261)) + +### Features + +- [rpc-types-trace] Always serialize result if no error ([#1258](https://github.com/alloy-rs/alloy/issues/1258)) + +### Miscellaneous Tasks + +- Release 0.3.3 +- Require destination for 7702 ([#1262](https://github.com/alloy-rs/alloy/issues/1262)) +- Swap BlockNumHash alias and struct name ([#1265](https://github.com/alloy-rs/alloy/issues/1265)) + +### Other + +- Implement `AsRef` for `Header` ([#1260](https://github.com/alloy-rs/alloy/issues/1260)) + +### Testing + +- Dont use fork test ([#1263](https://github.com/alloy-rs/alloy/issues/1263)) + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Bug Fixes + +- [consensus] Remove Unused Alloc Vecs ([#1250](https://github.com/alloy-rs/alloy/issues/1250)) + +### Dependencies + +- Bump tower to 0.5 ([#1249](https://github.com/alloy-rs/alloy/issues/1249)) + +### Features + +- No_std network primitives ([#1248](https://github.com/alloy-rs/alloy/issues/1248)) +- [rpc-types-eth] AnyBlock ([#1243](https://github.com/alloy-rs/alloy/issues/1243)) +- Add Reth node bindings ([#1092](https://github.com/alloy-rs/alloy/issues/1092)) +- [rpc-types-engine] Add forkchoice state zero helpers ([#1231](https://github.com/alloy-rs/alloy/issues/1231)) +- [network-primitives] Expose more fields via block response traits ([#1229](https://github.com/alloy-rs/alloy/issues/1229)) + +### Miscellaneous Tasks + +- Release 0.3.2 +- Add aliases for Num Hash ([#1253](https://github.com/alloy-rs/alloy/issues/1253)) +- Add helpers for beacon blob bundle ([#1254](https://github.com/alloy-rs/alloy/issues/1254)) +- [eip1898] Display `RpcBlockHash` ([#1242](https://github.com/alloy-rs/alloy/issues/1242)) +- Optional derive more ([#1239](https://github.com/alloy-rs/alloy/issues/1239)) +- Derive more default features false ([#1230](https://github.com/alloy-rs/alloy/issues/1230)) + +### Other + +- Add getter trait methods to `ReceiptResponse` ([#1251](https://github.com/alloy-rs/alloy/issues/1251)) +- Impl `exceeds_allowed_future_timestamp` for `Header` ([#1237](https://github.com/alloy-rs/alloy/issues/1237)) +- Impl `is_zero_difficulty` for `Header` ([#1236](https://github.com/alloy-rs/alloy/issues/1236)) +- Impl parent_num_hash for Header ([#1238](https://github.com/alloy-rs/alloy/issues/1238)) +- Implement `Arbitrary` for `Header` ([#1235](https://github.com/alloy-rs/alloy/issues/1235)) + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Bug Fixes + +- Anvil builder default port ([#1213](https://github.com/alloy-rs/alloy/issues/1213)) +- [eips] No-std compat ([#1222](https://github.com/alloy-rs/alloy/issues/1222)) +- Value of TxEip1559.ty ([#1210](https://github.com/alloy-rs/alloy/issues/1210)) + +### Dependencies + +- Bump rust msrv to 1.78 ([#1219](https://github.com/alloy-rs/alloy/issues/1219)) + +### Documentation + +- Update version ([#1211](https://github.com/alloy-rs/alloy/issues/1211)) + +### Features + +- [`json-rpc`] Implement From U256 and String for SubId ([#1226](https://github.com/alloy-rs/alloy/issues/1226)) +- Workflow to validate no_std compatibility ([#1223](https://github.com/alloy-rs/alloy/issues/1223)) +- Derive `arbitrary::Arbitrary` for `TxEip7702` ([#1216](https://github.com/alloy-rs/alloy/issues/1216)) +- Implement `tx_type` for `TxEip7702` ([#1214](https://github.com/alloy-rs/alloy/issues/1214)) +- [alloy-provider] Add abstraction for `NonceFiller` behavior ([#1108](https://github.com/alloy-rs/alloy/issues/1108)) + +### Miscellaneous Tasks + +- Release 0.3.1 +- [README] Add a link to `rpc-types-debug` ([#1212](https://github.com/alloy-rs/alloy/issues/1212)) +- [features] Enable `consensus` and `network` along with `providers` ([#1207](https://github.com/alloy-rs/alloy/issues/1207)) + +### Other + +- Rm useless methods for `TxEip7702` ([#1221](https://github.com/alloy-rs/alloy/issues/1221)) + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -92,6 +252,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - [consensus] Add missing getter trait methods for `alloy_consensus::Transaction` ([#1197](https://github.com/alloy-rs/alloy/issues/1197)) - Rm Rich type ([#1195](https://github.com/alloy-rs/alloy/issues/1195)) - Clippy für docs ([#1194](https://github.com/alloy-rs/alloy/issues/1194)) diff --git a/Cargo.toml b/Cargo.toml index db96f817e62..b75fe19a851 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,9 @@ members = ["crates/*"] resolver = "2" [workspace.package] -version = "0.3.0" +version = "0.3.6" edition = "2021" -rust-version = "1.76" +rust-version = "1.79" authors = ["Alloy Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/alloy-rs/alloy" @@ -113,18 +113,22 @@ rustls = { version = "0.23", default-features = false, features = [ ] } tokio-test = "0.4" tokio-tungstenite = "0.23" -tower = { version = "0.4", features = ["util"] } +tower = { version = "0.5", features = ["util"] } # tracing tracing = "0.1" tracing-subscriber = "0.3" +# no_std +cfg-if = "1" +hashbrown = "0.14.5" + # misc auto_impl = "1.2" base64 = "0.22" bimap = "0.6" home = "0.5" -itertools = "0.13" +itertools = { version = "0.13", default-features = false } once_cell = { version = "1.19", default-features = false } pin-project = "1.1" rand = "0.8" @@ -133,7 +137,8 @@ semver = "1.0" thiserror = "1.0" thiserror-no-std = "2.0.2" url = "2.5" -derive_more = "1.0.0" +derive_more = { version = "1.0.0", default-features = false } +http = "1.1.0" ## serde serde = { version = "1.0", default-features = false, features = [ @@ -149,3 +154,4 @@ assert_matches = "1.5" serial_test = "3.0" similar-asserts = "1.5" tempfile = "3.10" +tower-http = "0.5.2" diff --git a/README.md b/README.md index e528389819e..c3eeecec195 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ cargo add alloy --features full Alternatively, you can add the following to your `Cargo.toml` file: ```toml -alloy = { version = "0.2", features = ["full"] } +alloy = { version = "0.3", features = ["full"] } ``` For a more fine-grained control over the features you wish to include, you can add the individual crates to your `Cargo.toml` file, or use the `alloy` crate with the features you need. @@ -88,6 +88,7 @@ This repository contains the following crates: [`alloy-rpc-types-admin`]: https://github.com/alloy-rs/alloy/tree/main/crates/rpc-types-admin [`alloy-rpc-types-anvil`]: https://github.com/alloy-rs/alloy/tree/main/crates/rpc-types-anvil [`alloy-rpc-types-beacon`]: https://github.com/alloy-rs/alloy/tree/main/crates/rpc-types-beacon +[`alloy-rpc-types-debug`]: https://github.com/alloy-rs/alloy/tree/main/crates/rpc-types-debug [`alloy-rpc-types-engine`]: https://github.com/alloy-rs/alloy/tree/main/crates/rpc-types-engine [`alloy-rpc-types-eth`]: https://github.com/alloy-rs/alloy/tree/main/crates/rpc-types-eth [`alloy-rpc-types-mev`]: https://github.com/alloy-rs/alloy/tree/main/crates/rpc-types-mev @@ -123,7 +124,7 @@ When updating this, also update: - .github/workflows/ci.yml --> -The current MSRV (minimum supported rust version) is 1.76. +The current MSRV (minimum supported rust version) is 1.79. Alloy will keep a rolling MSRV policy of **at least** two versions behind the latest stable release (so if the latest stable release is 1.58, we would diff --git a/clippy.toml b/clippy.toml index 472818efed9..f1acf4b1122 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.76" +msrv = "1.79" diff --git a/crates/alloy/CHANGELOG.md b/crates/alloy/CHANGELOG.md index fa1baa5a7cf..b1433b28383 100644 --- a/crates/alloy/CHANGELOG.md +++ b/crates/alloy/CHANGELOG.md @@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- [transport-http] Layer client ([#1227](https://github.com/alloy-rs/alloy/issues/1227)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 +- [features] Enable `consensus` and `network` along with `providers` ([#1207](https://github.com/alloy-rs/alloy/issues/1207)) + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Dependencies @@ -19,6 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/alloy/Cargo.toml b/crates/alloy/Cargo.toml index 542924c6895..ef4a91b1b79 100644 --- a/crates/alloy/Cargo.toml +++ b/crates/alloy/Cargo.toml @@ -131,7 +131,14 @@ network = ["dep:alloy-network"] node-bindings = ["dep:alloy-node-bindings", "alloy-provider?/anvil-node"] # providers -providers = ["dep:alloy-provider", "rpc-client", "transports", "eips"] +providers = [ + "dep:alloy-provider", + "rpc-client", + "transports", + "eips", + "consensus", + "network", +] provider-http = ["providers", "transport-http"] provider-ws = ["providers", "alloy-provider?/ws", "transport-ws"] provider-ipc = ["providers", "alloy-provider?/ipc", "transport-ipc"] diff --git a/crates/consensus/CHANGELOG.md b/crates/consensus/CHANGELOG.md index f01dac1bf4d..634cf5acf9e 100644 --- a/crates/consensus/CHANGELOG.md +++ b/crates/consensus/CHANGELOG.md @@ -5,6 +5,71 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 +- [consensus] Remove Header Method ([#1271](https://github.com/alloy-rs/alloy/issues/1271)) +- [consensus] Alloc by Default ([#1272](https://github.com/alloy-rs/alloy/issues/1272)) + +### Other + +- Implement `seal` helper for `Header` ([#1269](https://github.com/alloy-rs/alloy/issues/1269)) + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 +- Require destination for 7702 ([#1262](https://github.com/alloy-rs/alloy/issues/1262)) + +### Other + +- Implement `AsRef` for `Header` ([#1260](https://github.com/alloy-rs/alloy/issues/1260)) + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Bug Fixes + +- [consensus] Remove Unused Alloc Vecs ([#1250](https://github.com/alloy-rs/alloy/issues/1250)) + +### Miscellaneous Tasks + +- Release 0.3.2 + +### Other + +- Impl `exceeds_allowed_future_timestamp` for `Header` ([#1237](https://github.com/alloy-rs/alloy/issues/1237)) +- Impl `is_zero_difficulty` for `Header` ([#1236](https://github.com/alloy-rs/alloy/issues/1236)) +- Impl parent_num_hash for Header ([#1238](https://github.com/alloy-rs/alloy/issues/1238)) +- Implement `Arbitrary` for `Header` ([#1235](https://github.com/alloy-rs/alloy/issues/1235)) + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Bug Fixes + +- Value of TxEip1559.ty ([#1210](https://github.com/alloy-rs/alloy/issues/1210)) + +### Features + +- Derive `arbitrary::Arbitrary` for `TxEip7702` ([#1216](https://github.com/alloy-rs/alloy/issues/1216)) +- Implement `tx_type` for `TxEip7702` ([#1214](https://github.com/alloy-rs/alloy/issues/1214)) + +### Miscellaneous Tasks + +- Release 0.3.1 + +### Other + +- Rm useless methods for `TxEip7702` ([#1221](https://github.com/alloy-rs/alloy/issues/1221)) + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Dependencies @@ -23,6 +88,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - [consensus] Add missing getter trait methods for `alloy_consensus::Transaction` ([#1197](https://github.com/alloy-rs/alloy/issues/1197)) - Release 0.2.1 - Chore : fix typos ([#1087](https://github.com/alloy-rs/alloy/issues/1087)) diff --git a/crates/consensus/src/header.rs b/crates/consensus/src/header.rs index f593b873929..3109d9044e3 100644 --- a/crates/consensus/src/header.rs +++ b/crates/consensus/src/header.rs @@ -1,18 +1,18 @@ +use alloc::vec::Vec; use alloy_eips::{ eip1559::{calc_next_block_base_fee, BaseFeeParams}, eip4844::{calc_blob_gasprice, calc_excess_blob_gas}, + merge::ALLOWED_FUTURE_BLOCK_TIME_SECONDS, + BlockNumHash, }; use alloy_primitives::{ - b256, keccak256, Address, BlockNumber, Bloom, Bytes, Sealable, B256, B64, U256, + b256, keccak256, Address, BlockNumber, Bloom, Bytes, Sealable, Sealed, B256, B64, U256, }; use alloy_rlp::{ length_of_length, Buf, BufMut, Decodable, Encodable, EMPTY_LIST_CODE, EMPTY_STRING_CODE, }; use core::mem; -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - /// Ommer root of empty list. pub const EMPTY_OMMER_ROOT_HASH: B256 = b256!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"); @@ -133,6 +133,12 @@ pub struct Header { pub extra_data: Bytes, } +impl AsRef for Header { + fn as_ref(&self) -> &Self { + self + } +} + impl Default for Header { fn default() -> Self { Self { @@ -168,13 +174,6 @@ impl Sealable for Header { } impl Header { - // TODO: re-enable - - // /// Returns the parent block's number and hash - // pub fn parent_num_hash(&self) -> BlockNumHash { - // BlockNumHash { number: self.number.saturating_sub(1), hash: self.parent_hash } - // } - /// Heavy function that will calculate hash of data and will *not* save the change to metadata. /// /// Use [`Header::seal_slow`] and unlock if you need the hash to be persistent. @@ -344,6 +343,41 @@ impl Header { length } + + /// Returns the parent block's number and hash + pub const fn parent_num_hash(&self) -> BlockNumHash { + BlockNumHash { number: self.number.saturating_sub(1), hash: self.parent_hash } + } + + /// Checks if the block's difficulty is set to zero, indicating a Proof-of-Stake header. + /// + /// This function is linked to EIP-3675, proposing the consensus upgrade to Proof-of-Stake: + /// [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#replacing-difficulty-with-0) + /// + /// Verifies whether, as per the EIP, the block's difficulty is updated to zero, + /// signifying the transition to a Proof-of-Stake mechanism. + /// + /// Returns `true` if the block's difficulty matches the constant zero set by the EIP. + pub fn is_zero_difficulty(&self) -> bool { + self.difficulty.is_zero() + } + + /// Checks if the block's timestamp is in the future based on the present timestamp. + /// + /// Clock can drift but this can be consensus issue. + /// + /// Note: This check is relevant only pre-merge. + pub const fn exceeds_allowed_future_timestamp(&self, present_timestamp: u64) -> bool { + self.timestamp > present_timestamp + ALLOWED_FUTURE_BLOCK_TIME_SECONDS + } + + /// Seal the header with a known hash. + /// + /// WARNING: This method does not perform validation whether the hash is correct. + #[inline] + pub const fn seal(self, hash: B256) -> Sealed { + Sealed::new_unchecked(self, hash) + } } impl Encodable for Header { @@ -527,11 +561,87 @@ impl Decodable for Header { } } -#[cfg(test)] +/// Generates a header which is valid __with respect to past and future forks__. This means, for +/// example, that if the withdrawals root is present, the base fee per gas is also present. +/// +/// If blob gas used were present, then the excess blob gas and parent beacon block root are also +/// present. In this example, the withdrawals root would also be present. +/// +/// This __does not, and should not guarantee__ that the header is valid with respect to __anything +/// else__. +#[cfg(any(test, feature = "arbitrary"))] +pub(crate) const fn generate_valid_header( + mut header: Header, + eip_4844_active: bool, + blob_gas_used: u128, + excess_blob_gas: u128, + parent_beacon_block_root: B256, +) -> Header { + // Clear all related fields if EIP-1559 is inactive + if header.base_fee_per_gas.is_none() { + header.withdrawals_root = None; + } + + // Set fields based on EIP-4844 being active + if eip_4844_active { + header.blob_gas_used = Some(blob_gas_used); + header.excess_blob_gas = Some(excess_blob_gas); + header.parent_beacon_block_root = Some(parent_beacon_block_root); + } else { + header.blob_gas_used = None; + header.excess_blob_gas = None; + header.parent_beacon_block_root = None; + } + + // Placeholder for future EIP adjustments + header.requests_root = None; + + header +} + +#[cfg(any(test, feature = "arbitrary"))] +impl<'a> arbitrary::Arbitrary<'a> for Header { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + // Generate an arbitrary header, passing it to the generate_valid_header function to make + // sure it is valid _with respect to hardforks only_. + let base = Self { + parent_hash: u.arbitrary()?, + ommers_hash: u.arbitrary()?, + beneficiary: u.arbitrary()?, + state_root: u.arbitrary()?, + transactions_root: u.arbitrary()?, + receipts_root: u.arbitrary()?, + logs_bloom: u.arbitrary()?, + difficulty: u.arbitrary()?, + number: u.arbitrary()?, + gas_limit: u.arbitrary()?, + gas_used: u.arbitrary()?, + timestamp: u.arbitrary()?, + extra_data: u.arbitrary()?, + mix_hash: u.arbitrary()?, + nonce: u.arbitrary()?, + base_fee_per_gas: u.arbitrary()?, + blob_gas_used: u.arbitrary()?, + excess_blob_gas: u.arbitrary()?, + parent_beacon_block_root: u.arbitrary()?, + requests_root: u.arbitrary()?, + withdrawals_root: u.arbitrary()?, + }; + + Ok(generate_valid_header( + base, + u.arbitrary()?, + u.arbitrary()?, + u.arbitrary()?, + u.arbitrary()?, + )) + } +} + +#[cfg(all(test, feature = "serde"))] mod tests { use super::*; - #[cfg(feature = "serde")] #[test] fn header_serde() { let raw = r#"{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","ommersHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","beneficiary":"0x0000000000000000000000000000000000000000","stateRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x0","gasUsed":"0x0","timestamp":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1","extraData":"0x"}"#; diff --git a/crates/consensus/src/lib.rs b/crates/consensus/src/lib.rs index 5fd91a76559..65ec35a908f 100644 --- a/crates/consensus/src/lib.rs +++ b/crates/consensus/src/lib.rs @@ -7,7 +7,6 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(not(feature = "std"))] extern crate alloc; mod account; diff --git a/crates/consensus/src/receipt/receipts.rs b/crates/consensus/src/receipt/receipts.rs index e9476a81f72..63fa657e5d7 100644 --- a/crates/consensus/src/receipt/receipts.rs +++ b/crates/consensus/src/receipt/receipts.rs @@ -1,11 +1,9 @@ use crate::receipt::{Eip658Value, TxReceipt}; +use alloc::vec::Vec; use alloy_primitives::{Bloom, Log}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable}; use core::borrow::Borrow; -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - /// Receipt containing result of transaction execution. #[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] diff --git a/crates/consensus/src/transaction/eip1559.rs b/crates/consensus/src/transaction/eip1559.rs index c53d39b3160..108c7e8a4c6 100644 --- a/crates/consensus/src/transaction/eip1559.rs +++ b/crates/consensus/src/transaction/eip1559.rs @@ -1,12 +1,10 @@ use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType}; +use alloc::vec::Vec; use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, B256, U256}; use alloy_rlp::{BufMut, Decodable, Encodable, Header}; use core::mem; -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - /// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)). #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] @@ -307,7 +305,7 @@ impl Transaction for TxEip1559 { } fn ty(&self) -> u8 { - TxType::Eip2930 as u8 + TxType::Eip1559 as u8 } fn access_list(&self) -> Option<&AccessList> { diff --git a/crates/consensus/src/transaction/eip2930.rs b/crates/consensus/src/transaction/eip2930.rs index 5917f3d741c..5ed91a2b6f0 100644 --- a/crates/consensus/src/transaction/eip2930.rs +++ b/crates/consensus/src/transaction/eip2930.rs @@ -1,12 +1,10 @@ use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType}; +use alloc::vec::Vec; use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, B256, U256}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header}; use core::mem; -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - /// Transaction with an [`AccessList`] ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)). #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] diff --git a/crates/consensus/src/transaction/eip4844.rs b/crates/consensus/src/transaction/eip4844.rs index a4775200952..78bb4d28650 100644 --- a/crates/consensus/src/transaction/eip4844.rs +++ b/crates/consensus/src/transaction/eip4844.rs @@ -1,5 +1,6 @@ use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType}; +use alloc::vec::Vec; use alloy_eips::{eip2930::AccessList, eip4844::DATA_GAS_PER_BLOB, eip7702::SignedAuthorization}; use alloy_primitives::{keccak256, Address, Bytes, ChainId, Signature, TxKind, B256, U256}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header}; @@ -12,9 +13,6 @@ pub use alloy_eips::eip4844::BlobTransactionSidecar; #[doc(inline)] pub use alloy_eips::eip4844::BlobTransactionValidationError; -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - /// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction) /// /// A transaction with blob hashes and max blob fee. diff --git a/crates/consensus/src/transaction/eip7702.rs b/crates/consensus/src/transaction/eip7702.rs index c34a0471810..c13bda3bf98 100644 --- a/crates/consensus/src/transaction/eip7702.rs +++ b/crates/consensus/src/transaction/eip7702.rs @@ -1,15 +1,16 @@ use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType}; -use alloy_eips::eip2930::AccessList; -use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, B256, U256}; -use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header}; -use core::mem; - -#[cfg(not(feature = "std"))] use alloc::vec::Vec; -use alloy_eips::eip7702::{constants::EIP7702_TX_TYPE_ID, SignedAuthorization}; +use alloy_eips::{ + eip2930::AccessList, + eip7702::{constants::EIP7702_TX_TYPE_ID, SignedAuthorization}, +}; +use alloy_primitives::{keccak256, Address, Bytes, ChainId, Signature, TxKind, B256, U256}; +use alloy_rlp::{BufMut, Decodable, Encodable, Header}; +use core::mem; /// A transaction with a priority fee ([EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)). #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[doc(alias = "Eip7702Transaction", alias = "TransactionEip7702", alias = "Eip7702Tx")] @@ -49,10 +50,8 @@ pub struct TxEip7702 { /// This is also known as `GasTipCap` #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub max_priority_fee_per_gas: u128, - /// The 160-bit address of the message call’s recipient or, for a contract creation - /// transaction, ∅, used here to denote the only member of B0 ; formally Tt. - #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "TxKind::is_create"))] - pub to: TxKind, + /// The 160-bit address of the message call’s recipient. + pub to: Address, /// A scalar value equal to the number of Wei to /// be transferred to the message call’s recipient or, /// in the case of contract creation, as an endowment @@ -250,9 +249,8 @@ impl TxEip7702 { /// Get transaction type #[doc(alias = "transaction_type")] - #[allow(unused)] - pub(crate) fn tx_type(&self) -> TxType { - unimplemented!("not yet added to tx type enum") + pub const fn tx_type(&self) -> TxType { + TxType::Eip7702 } /// Calculates a heuristic for the in-memory size of the [TxEip7702] transaction. @@ -263,25 +261,12 @@ impl TxEip7702 { mem::size_of::() + // gas_limit mem::size_of::() + // max_fee_per_gas mem::size_of::() + // max_priority_fee_per_gas - self.to.size() + // to + mem::size_of::
() + // to mem::size_of::() + // value self.access_list.size() + // access_list self.input.len() + // input self.authorization_list.capacity() * mem::size_of::() // authorization_list } - - /// Output the length of the RLP signed transaction encoding, _without_ a RLP string header. - pub fn payload_len_with_signature_without_header(&self, signature: &Signature) -> usize { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - // 'transaction type byte length' + 'header length' + 'payload length' - 1 + length_of_length(payload_length) + payload_length - } - - /// Output the length of the RLP signed transaction encoding. This encodes with a RLP header. - pub fn payload_len_with_signature(&self, signature: &Signature) -> usize { - let len = self.payload_len_with_signature_without_header(signature); - length_of_length(len) + len - } } impl Transaction for TxEip7702 { @@ -318,7 +303,7 @@ impl Transaction for TxEip7702 { } fn to(&self) -> TxKind { - self.to + self.to.into() } fn value(&self) -> U256 { @@ -401,66 +386,10 @@ impl Decodable for TxEip7702 { #[cfg(all(test, feature = "k256"))] mod tests { - use core::str::FromStr; - use super::TxEip7702; use crate::SignableTransaction; use alloy_eips::eip2930::AccessList; - use alloy_primitives::{address, b256, hex, Address, Bytes, Signature, TxKind, U256}; - - #[test] - fn test_payload_len_with_signature_without_header() { - let tx = TxEip7702 { - chain_id: 1u64, - nonce: 0, - max_fee_per_gas: 0x4a817c800, - max_priority_fee_per_gas: 0x3b9aca00, - gas_limit: 2, - to: TxKind::Create, - value: U256::ZERO, - input: Bytes::from(vec![1, 2]), - access_list: Default::default(), - authorization_list: Default::default(), - }; - - let signature = Signature::from_rs_and_parity( - U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - 1, - ) - .unwrap(); - - assert_eq!(tx.payload_len_with_signature_without_header(&signature), 91); - } - - #[test] - fn test_payload_len_with_signature() { - let tx = TxEip7702 { - chain_id: 1u64, - nonce: 0, - max_fee_per_gas: 0x4a817c800, - max_priority_fee_per_gas: 0x3b9aca00, - gas_limit: 2, - to: TxKind::Create, - value: U256::ZERO, - input: Bytes::from(vec![1, 2]), - access_list: Default::default(), - authorization_list: Default::default(), - }; - - let signature = Signature::from_rs_and_parity( - U256::from_str("0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0") - .unwrap(), - U256::from_str("0x1a891b566d369e79b7a66eecab1e008831e22daa15f91a0a0cf4f9f28f47ee05") - .unwrap(), - 1, - ) - .unwrap(); - - assert_eq!(tx.payload_len_with_signature(&signature), 93); - } + use alloy_primitives::{address, b256, hex, Address, Signature, U256}; #[test] fn encode_decode_eip7702() { @@ -468,7 +397,7 @@ mod tests { chain_id: 1, nonce: 0x42, gas_limit: 44386, - to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(), + to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6"), value: U256::from(0_u64), input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(), max_fee_per_gas: 0x4a817c800, @@ -499,7 +428,7 @@ mod tests { max_fee_per_gas: 0x4a817c800, max_priority_fee_per_gas: 0x3b9aca00, gas_limit: 2, - to: TxKind::Create, + to: Address::default(), value: U256::ZERO, input: vec![1, 2].into(), access_list: Default::default(), @@ -525,7 +454,7 @@ mod tests { max_fee_per_gas: 0x4a817c800, max_priority_fee_per_gas: 0x3b9aca00, gas_limit: 2, - to: Address::default().into(), + to: Address::default(), value: U256::ZERO, input: vec![1, 2].into(), access_list: Default::default(), diff --git a/crates/consensus/src/transaction/envelope.rs b/crates/consensus/src/transaction/envelope.rs index 79ab90b9e15..232e88ca326 100644 --- a/crates/consensus/src/transaction/envelope.rs +++ b/crates/consensus/src/transaction/envelope.rs @@ -1,5 +1,3 @@ -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; use core::fmt; use crate::{Signed, Transaction, TxEip1559, TxEip2930, TxEip7702, TxLegacy}; @@ -825,7 +823,7 @@ mod tests { gas_limit: 3, max_fee_per_gas: 4, max_priority_fee_per_gas: 5, - to: Address::left_padding_from(&[5]).into(), + to: Address::left_padding_from(&[5]), value: U256::from(6_u64), input: vec![7].into(), access_list: AccessList(vec![AccessListItem { @@ -996,7 +994,7 @@ mod tests { gas_limit: u128::MAX, max_fee_per_gas: u128::MAX, max_priority_fee_per_gas: u128::MAX, - to: Address::random().into(), + to: Address::random(), value: U256::MAX, input: Bytes::new(), access_list: AccessList(vec![AccessListItem { diff --git a/crates/consensus/src/transaction/legacy.rs b/crates/consensus/src/transaction/legacy.rs index e212f93bc6d..c3ecdc5437c 100644 --- a/crates/consensus/src/transaction/legacy.rs +++ b/crates/consensus/src/transaction/legacy.rs @@ -1,14 +1,12 @@ use core::mem; +use alloc::vec::Vec; use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; use alloy_primitives::{keccak256, Bytes, ChainId, Signature, TxKind, B256, U256}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header, Result}; use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType}; -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - /// Legacy transaction. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] diff --git a/crates/consensus/src/transaction/mod.rs b/crates/consensus/src/transaction/mod.rs index a4bf0ebd401..64d6edd7935 100644 --- a/crates/consensus/src/transaction/mod.rs +++ b/crates/consensus/src/transaction/mod.rs @@ -1,13 +1,11 @@ //! Transaction types. use crate::Signed; +use alloc::vec::Vec; use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; use alloy_primitives::{keccak256, ChainId, TxKind, B256, U256}; use core::any; -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - mod eip1559; pub use eip1559::TxEip1559; diff --git a/crates/consensus/src/transaction/typed.rs b/crates/consensus/src/transaction/typed.rs index 3023fc7bdac..0dca9ebb2d5 100644 --- a/crates/consensus/src/transaction/typed.rs +++ b/crates/consensus/src/transaction/typed.rs @@ -1,6 +1,3 @@ -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; - use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; use alloy_primitives::{ChainId, TxKind, B256}; diff --git a/crates/contract/CHANGELOG.md b/crates/contract/CHANGELOG.md index 7ba37b6787a..6f4675e7a76 100644 --- a/crates/contract/CHANGELOG.md +++ b/crates/contract/CHANGELOG.md @@ -5,6 +5,50 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- ProviderCall ([#788](https://github.com/alloy-rs/alloy/issues/788)) + +### Refactor + +- Separate transaction builders for tx types ([#1259](https://github.com/alloy-rs/alloy/issues/1259)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Features + +- [alloy-rpc-types-eth] Optional serde ([#1276](https://github.com/alloy-rs/alloy/issues/1276)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -13,6 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 - Fix unnameable types ([#1029](https://github.com/alloy-rs/alloy/issues/1029)) diff --git a/crates/contract/Cargo.toml b/crates/contract/Cargo.toml index b1862a2b99e..4456055d5b2 100644 --- a/crates/contract/Cargo.toml +++ b/crates/contract/Cargo.toml @@ -22,7 +22,7 @@ workspace = true alloy-network.workspace = true alloy-network-primitives.workspace = true alloy-provider.workspace = true -alloy-rpc-types-eth.workspace = true +alloy-rpc-types-eth = { workspace = true, features = ["serde"] } alloy-transport.workspace = true alloy-dyn-abi = { workspace = true, features = ["std"] } diff --git a/crates/contract/src/call.rs b/crates/contract/src/call.rs index d9aaf5279ce..1d1f0a38171 100644 --- a/crates/contract/src/call.rs +++ b/crates/contract/src/call.rs @@ -1,7 +1,7 @@ use crate::{CallDecoder, Error, EthCall, Result}; use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; use alloy_json_abi::Function; -use alloy_network::{Ethereum, Network, TransactionBuilder}; +use alloy_network::{Ethereum, Network, TransactionBuilder, TransactionBuilder4844}; use alloy_network_primitives::ReceiptResponse; use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256}; use alloy_provider::{PendingTransactionBuilder, Provider}; @@ -334,7 +334,10 @@ impl, D: CallDecoder, N: Network> CallBu } /// Sets the `sidecar` field in the transaction to the provided value. - pub fn sidecar(mut self, blob_sidecar: BlobTransactionSidecar) -> Self { + pub fn sidecar(mut self, blob_sidecar: BlobTransactionSidecar) -> Self + where + N::TransactionRequest: TransactionBuilder4844, + { self.request.set_blob_sidecar(blob_sidecar); self } @@ -371,7 +374,10 @@ impl, D: CallDecoder, N: Network> CallBu } /// Sets the `max_fee_per_blob_gas` in the transaction to the provided value - pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self { + pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self + where + N::TransactionRequest: TransactionBuilder4844, + { self.request.set_max_fee_per_blob_gas(max_fee_per_blob_gas); self } @@ -441,7 +447,7 @@ impl, D: CallDecoder, N: Network> CallBu /// If this is not desired, use [`call_raw`](Self::call_raw) to get the raw output data. #[doc(alias = "eth_call")] #[doc(alias = "call_with_overrides")] - pub fn call(&self) -> EthCall<'_, '_, '_, D, T, N> { + pub fn call(&self) -> EthCall<'_, '_, D, T, N> { self.call_raw().with_decoder(&self.decoder) } @@ -451,7 +457,7 @@ impl, D: CallDecoder, N: Network> CallBu /// Does not decode the output of the call, returning the raw output data instead. /// /// See [`call`](Self::call) for more information. - pub fn call_raw(&self) -> EthCall<'_, '_, '_, (), T, N> { + pub fn call_raw(&self) -> EthCall<'_, '_, (), T, N> { let call = self.provider.call(&self.request).block(self.block); let call = match &self.state { Some(state) => call.overrides(state), diff --git a/crates/contract/src/eth_call.rs b/crates/contract/src/eth_call.rs index c4dd8c227bb..2f4e9db089c 100644 --- a/crates/contract/src/eth_call.rs +++ b/crates/contract/src/eth_call.rs @@ -24,18 +24,18 @@ mod private { /// An [`alloy_provider::EthCall`] with an abi decoder. #[must_use = "EthCall must be awaited to execute the call"] #[derive(Clone, Debug)] -pub struct EthCall<'req, 'state, 'coder, D, T, N> +pub struct EthCall<'req, 'coder, D, T, N> where T: Transport + Clone, N: Network, D: CallDecoder, { - inner: alloy_provider::EthCall<'req, 'state, T, N, Bytes>, + inner: alloy_provider::EthCall<'req, T, N, Bytes>, decoder: &'coder D, } -impl<'req, 'state, 'coder, D, T, N> EthCall<'req, 'state, 'coder, D, T, N> +impl<'req, 'coder, D, T, N> EthCall<'req, 'coder, D, T, N> where T: Transport + Clone, N: Network, @@ -43,25 +43,25 @@ where { /// Create a new [`EthCall`]. pub const fn new( - inner: alloy_provider::EthCall<'req, 'state, T, N, Bytes>, + inner: alloy_provider::EthCall<'req, T, N, Bytes>, decoder: &'coder D, ) -> Self { Self { inner, decoder } } } -impl<'req, 'state, T, N> EthCall<'req, 'state, 'static, (), T, N> +impl<'req, T, N> EthCall<'req, 'static, (), T, N> where T: Transport + Clone, N: Network, { /// Create a new [`EthCall`]. - pub const fn new_raw(inner: alloy_provider::EthCall<'req, 'state, T, N, Bytes>) -> Self { + pub const fn new_raw(inner: alloy_provider::EthCall<'req, T, N, Bytes>) -> Self { Self::new(inner, &RAW_CODER) } } -impl<'req, 'state, 'coder, D, T, N> EthCall<'req, 'state, 'coder, D, T, N> +impl<'req, 'coder, D, T, N> EthCall<'req, 'coder, D, T, N> where T: Transport + Clone, N: Network, @@ -71,7 +71,7 @@ where pub fn with_decoder<'new_coder, E>( self, decoder: &'new_coder E, - ) -> EthCall<'req, 'state, 'new_coder, E, T, N> + ) -> EthCall<'req, 'new_coder, E, T, N> where E: CallDecoder, { @@ -79,7 +79,7 @@ where } /// Set the state overrides for this call. - pub fn overrides(mut self, overrides: &'state StateOverride) -> Self { + pub fn overrides(mut self, overrides: &'req StateOverride) -> Self { self.inner = self.inner.overrides(overrides); self } @@ -91,19 +91,18 @@ where } } -impl<'req, 'state, T, N> From> - for EthCall<'req, 'state, 'static, (), T, N> +impl<'req, T, N> From> + for EthCall<'req, 'static, (), T, N> where T: Transport + Clone, N: Network, { - fn from(inner: alloy_provider::EthCall<'req, 'state, T, N, Bytes>) -> Self { + fn from(inner: alloy_provider::EthCall<'req, T, N, Bytes>) -> Self { Self { inner, decoder: &RAW_CODER } } } -impl<'req, 'state, 'coder, D, T, N> std::future::IntoFuture - for EthCall<'req, 'state, 'coder, D, T, N> +impl<'req, 'coder, D, T, N> std::future::IntoFuture for EthCall<'req, 'coder, D, T, N> where D: CallDecoder + Unpin, T: Transport + Clone, @@ -111,7 +110,7 @@ where { type Output = Result; - type IntoFuture = EthCallFut<'req, 'state, 'coder, D, T, N>; + type IntoFuture = EthCallFut<'req, 'coder, D, T, N>; fn into_future(self) -> Self::IntoFuture { EthCallFut { inner: self.inner.into_future(), decoder: self.decoder } @@ -121,20 +120,19 @@ where /// Future for the [`EthCall`] type. This future wraps an RPC call with an abi /// decoder. #[must_use = "futures do nothing unless you `.await` or poll them"] -#[derive(Clone, Debug)] +#[derive(Debug)] #[allow(unnameable_types)] -pub struct EthCallFut<'req, 'state, 'coder, D, T, N> +pub struct EthCallFut<'req, 'coder, D, T, N> where T: Transport + Clone, N: Network, D: CallDecoder, { - inner: as IntoFuture>::IntoFuture, + inner: as IntoFuture>::IntoFuture, decoder: &'coder D, } -impl<'req, 'state, 'coder, D, T, N> std::future::Future - for EthCallFut<'req, 'state, 'coder, D, T, N> +impl<'req, 'coder, D, T, N> std::future::Future for EthCallFut<'req, 'coder, D, T, N> where D: CallDecoder + Unpin, T: Transport + Clone, diff --git a/crates/eip7547/CHANGELOG.md b/crates/eip7547/CHANGELOG.md index 8470fd51c5b..9d8f8297bf6 100644 --- a/crates/eip7547/CHANGELOG.md +++ b/crates/eip7547/CHANGELOG.md @@ -5,10 +5,46 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Features + +- [engine] Optional Serde ([#1283](https://github.com/alloy-rs/alloy/issues/1283)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/eip7547/Cargo.toml b/crates/eip7547/Cargo.toml index 3a576ec249a..45682d7a3bd 100644 --- a/crates/eip7547/Cargo.toml +++ b/crates/eip7547/Cargo.toml @@ -20,7 +20,7 @@ workspace = true [dependencies] alloy-primitives = { workspace = true, features = ["rlp", "serde"] } -alloy-rpc-types-engine = { workspace = true } +alloy-rpc-types-engine = { workspace = true, features = ["serde"] } alloy-serde = { workspace = true } # serde diff --git a/crates/eips/CHANGELOG.md b/crates/eips/CHANGELOG.md index e4809481e77..7b61b20667a 100644 --- a/crates/eips/CHANGELOG.md +++ b/crates/eips/CHANGELOG.md @@ -5,6 +5,56 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- [rpc-types-beacon] `SignedBidSubmissionV4` ([#1303](https://github.com/alloy-rs/alloy/issues/1303)) +- Add blob and proof v1 ([#1300](https://github.com/alloy-rs/alloy/issues/1300)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Features + +- Add serde for NumHash ([#1277](https://github.com/alloy-rs/alloy/issues/1277)) + +### Miscellaneous Tasks + +- Release 0.3.4 +- Swap `BlockHashOrNumber` alias and struct name ([#1270](https://github.com/alloy-rs/alloy/issues/1270)) + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 +- Swap BlockNumHash alias and struct name ([#1265](https://github.com/alloy-rs/alloy/issues/1265)) + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 +- Add aliases for Num Hash ([#1253](https://github.com/alloy-rs/alloy/issues/1253)) +- [eip1898] Display `RpcBlockHash` ([#1242](https://github.com/alloy-rs/alloy/issues/1242)) +- Optional derive more ([#1239](https://github.com/alloy-rs/alloy/issues/1239)) + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Bug Fixes + +- [eips] No-std compat ([#1222](https://github.com/alloy-rs/alloy/issues/1222)) + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -32,6 +82,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Clippy für docs ([#1194](https://github.com/alloy-rs/alloy/issues/1194)) - [eip7702] Devnet3 changes ([#1056](https://github.com/alloy-rs/alloy/issues/1056)) - Release 0.2.1 diff --git a/crates/eips/Cargo.toml b/crates/eips/Cargo.toml index 1e327cd7de9..7e352aeb548 100644 --- a/crates/eips/Cargo.toml +++ b/crates/eips/Cargo.toml @@ -36,7 +36,7 @@ derive_more = { workspace = true, features = [ "as_ref", "deref", "deref_mut", -], optional = true } +], default-features = false, optional = true } once_cell = { workspace = true, features = ["race", "alloc"], optional = true } sha2 = { workspace = true, optional = true } @@ -59,7 +59,7 @@ serde_json.workspace = true [features] default = ["std", "kzg-sidecar"] -std = ["alloy-primitives/std", "alloy-rlp/std", +std = ["alloy-primitives/std", "alloy-rlp/std", "derive_more?/std", "serde?/std", "c-kzg?/std", "once_cell?/std"] serde = ["dep:alloy-serde", "dep:serde", "alloy-primitives/serde", "c-kzg?/serde", "alloy-eip2930/serde", "alloy-eip7702/serde"] diff --git a/crates/eips/src/eip1898.rs b/crates/eips/src/eip1898.rs index f7c02b16986..56be68bff2d 100644 --- a/crates/eips/src/eip1898.rs +++ b/crates/eips/src/eip1898.rs @@ -1,6 +1,6 @@ //! [EIP-1898]: https://eips.ethereum.org/EIPS/eip-1898 -use alloy_primitives::{hex::FromHexError, ruint::ParseError, BlockHash, BlockNumber, B256, U64}; +use alloy_primitives::{hex::FromHexError, ruint::ParseError, BlockHash, B256, U64}; use alloy_rlp::{bytes, Decodable, Encodable, Error as RlpError}; use core::{ fmt::{self, Debug, Display, Formatter}, @@ -56,6 +56,16 @@ impl AsRef for RpcBlockHash { } } +impl Display for RpcBlockHash { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self { block_hash, require_canonical } = self; + if *require_canonical == Some(true) { + write!(f, "canonical ")? + } + write!(f, "hash {}", block_hash) + } +} + /// A block Number (or tag - "latest", "earliest", "pending") #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub enum BlockNumberOrTag { @@ -383,13 +393,13 @@ impl From for BlockId { } } -impl From for BlockId { - fn from(block: BlockHashOrNumber) -> Self { +impl From for BlockId { + fn from(block: HashOrNumber) -> Self { match block { - BlockHashOrNumber::Hash(hash) => { + HashOrNumber::Hash(hash) => { Self::Hash(RpcBlockHash { block_hash: hash, require_canonical: None }) } - BlockHashOrNumber::Number(num) => Self::Number(BlockNumberOrTag::Number(num)), + HashOrNumber::Number(num) => Self::Number(BlockNumberOrTag::Number(num)), } } } @@ -515,6 +525,20 @@ impl<'de> Deserialize<'de> for BlockId { } } +impl Display for BlockId { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Hash(hash) => write!(f, "{}", hash), + Self::Number(num) => { + if num.is_number() { + return write!(f, "number {}", num); + } + write!(f, "{}", num) + } + } + } +} + /// Error thrown when parsing a [BlockId] from a string. #[derive(Debug)] pub enum ParseBlockIdError { @@ -576,65 +600,72 @@ impl FromStr for BlockId { } } -/// Block number and hash. +/// A number and a hash. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -pub struct BlockNumHash { - /// Block number - pub number: BlockNumber, - /// Block hash - pub hash: BlockHash, +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NumHash { + /// The number + pub number: u64, + /// The hash. + pub hash: B256, } /// Block number and hash of the forked block. -pub type ForkBlock = BlockNumHash; +pub type ForkBlock = NumHash; + +/// A block number and a hash +pub type BlockNumHash = NumHash; -impl BlockNumHash { - /// Creates a new `BlockNumHash` from a block number and hash. - pub const fn new(number: BlockNumber, hash: BlockHash) -> Self { +impl NumHash { + /// Creates a new `NumHash` from a number and hash. + pub const fn new(number: u64, hash: B256) -> Self { Self { number, hash } } - /// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`] - pub const fn into_components(self) -> (BlockNumber, BlockHash) { + /// Consumes `Self` and returns the number and hash + pub const fn into_components(self) -> (u64, B256) { (self.number, self.hash) } - /// Returns whether or not the block matches the given [BlockHashOrNumber]. - pub fn matches_block_or_num(&self, block: &BlockHashOrNumber) -> bool { + /// Returns whether or not the block matches the given [HashOrNumber]. + pub fn matches_block_or_num(&self, block: &HashOrNumber) -> bool { match block { - BlockHashOrNumber::Hash(hash) => self.hash == *hash, - BlockHashOrNumber::Number(number) => self.number == *number, + HashOrNumber::Hash(hash) => self.hash == *hash, + HashOrNumber::Number(number) => self.number == *number, } } } -impl From<(BlockNumber, BlockHash)> for BlockNumHash { - fn from(val: (BlockNumber, BlockHash)) -> Self { +impl From<(u64, B256)> for NumHash { + fn from(val: (u64, B256)) -> Self { Self { number: val.0, hash: val.1 } } } -impl From<(BlockHash, BlockNumber)> for BlockNumHash { - fn from(val: (BlockHash, BlockNumber)) -> Self { +impl From<(B256, u64)> for NumHash { + fn from(val: (B256, u64)) -> Self { Self { hash: val.0, number: val.1 } } } -/// Either a block hash _or_ a block number +/// Either a hash _or_ a block number #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -pub enum BlockHashOrNumber { - /// A block hash +pub enum HashOrNumber { + /// The hash Hash(B256), - /// A block number + /// The number Number(u64), } -// === impl BlockHashOrNumber === +/// A block hash _or_ a block number +pub type BlockHashOrNumber = HashOrNumber; -impl BlockHashOrNumber { - /// Returns the block number if it is a [`BlockHashOrNumber::Number`]. +// === impl HashOrNumber === + +impl HashOrNumber { + /// Returns the block number if it is a [`HashOrNumber::Number`]. #[inline] pub const fn as_number(self) -> Option { match self { @@ -644,32 +675,32 @@ impl BlockHashOrNumber { } } -impl From for BlockHashOrNumber { +impl From for HashOrNumber { fn from(value: B256) -> Self { Self::Hash(value) } } -impl From for BlockHashOrNumber { +impl From for HashOrNumber { fn from(value: u64) -> Self { Self::Number(value) } } -impl From for BlockHashOrNumber { +impl From for HashOrNumber { fn from(value: U64) -> Self { value.to::().into() } } -impl From for BlockHashOrNumber { +impl From for HashOrNumber { fn from(value: RpcBlockHash) -> Self { Self::Hash(value.into()) } } -/// Allows for RLP encoding of either a block hash or block number -impl Encodable for BlockHashOrNumber { +/// Allows for RLP encoding of either a hash or a number +impl Encodable for HashOrNumber { fn encode(&self, out: &mut dyn bytes::BufMut) { match self { Self::Hash(block_hash) => block_hash.encode(out), @@ -684,8 +715,8 @@ impl Encodable for BlockHashOrNumber { } } -/// Allows for RLP decoding of a block hash or block number -impl Decodable for BlockHashOrNumber { +/// Allows for RLP decoding of a hash or number +impl Decodable for HashOrNumber { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { let header: u8 = *buf.first().ok_or(RlpError::InputTooShort)?; // if the byte string is exactly 32 bytes, decode it into a Hash @@ -706,7 +737,7 @@ impl Decodable for BlockHashOrNumber { } } -impl fmt::Display for BlockHashOrNumber { +impl fmt::Display for HashOrNumber { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Hash(hash) => write!(f, "{}", hash), @@ -715,7 +746,7 @@ impl fmt::Display for BlockHashOrNumber { } } -/// Error thrown when parsing a [BlockHashOrNumber] from a string. +/// Error thrown when parsing a [HashOrNumber] from a string. #[derive(Debug)] pub struct ParseBlockHashOrNumberError { input: alloc::string::String, @@ -736,7 +767,7 @@ impl fmt::Display for ParseBlockHashOrNumberError { #[cfg(feature = "std")] impl std::error::Error for ParseBlockHashOrNumberError {} -impl FromStr for BlockHashOrNumber { +impl FromStr for HashOrNumber { type Err = ParseBlockHashOrNumberError; fn from_str(s: &str) -> Result { @@ -757,11 +788,16 @@ impl FromStr for BlockHashOrNumber { } } -#[cfg(all(test, feature = "serde"))] +#[cfg(test)] mod tests { + use alloy_primitives::b256; + use super::*; + const HASH: B256 = b256!("1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9"); + #[test] + #[cfg(feature = "serde")] fn compact_block_number_serde() { let num: BlockNumberOrTag = 1u64.into(); let serialized = serde_json::to_string(&num).unwrap(); @@ -781,6 +817,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn can_parse_eip1898_block_ids() { let num = serde_json::json!( { "blockNumber": "0x0" } @@ -856,4 +893,62 @@ mod tests { ) ); } + + #[test] + fn display_rpc_block_hash() { + let hash = RpcBlockHash::from_hash(HASH, Some(true)); + + assert_eq!( + hash.to_string(), + "canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9" + ); + + let hash = RpcBlockHash::from_hash(HASH, None); + + assert_eq!( + hash.to_string(), + "hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9" + ); + } + + #[test] + fn display_block_id() { + let id = BlockId::hash(HASH); + + assert_eq!( + id.to_string(), + "hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9" + ); + + let id = BlockId::hash_canonical(HASH); + + assert_eq!( + id.to_string(), + "canonical hash 0x1a15e3c30cf094a99826869517b16d185d45831d3a494f01030b0001a9d3ebb9" + ); + + let id = BlockId::number(100000); + + assert_eq!(id.to_string(), "number 0x186a0"); + + let id = BlockId::latest(); + + assert_eq!(id.to_string(), "latest"); + + let id = BlockId::safe(); + + assert_eq!(id.to_string(), "safe"); + + let id = BlockId::finalized(); + + assert_eq!(id.to_string(), "finalized"); + + let id = BlockId::earliest(); + + assert_eq!(id.to_string(), "earliest"); + + let id = BlockId::pending(); + + assert_eq!(id.to_string(), "pending"); + } } diff --git a/crates/eips/src/eip4844/engine.rs b/crates/eips/src/eip4844/engine.rs new file mode 100644 index 00000000000..b8bb0146238 --- /dev/null +++ b/crates/eips/src/eip4844/engine.rs @@ -0,0 +1,14 @@ +//! Misc types related to the 4844 + +use crate::eip4844::{Blob, Bytes48}; +use alloc::boxed::Box; + +/// Blob type returned in responses to `engine_getBlobsV1`: +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BlobAndProofV1 { + /// The blob data. + pub blob: Box, + /// The KZG proof for the blob. + pub proof: Bytes48, +} diff --git a/crates/eips/src/eip4844/mod.rs b/crates/eips/src/eip4844/mod.rs index dbba4352d44..99e315555ea 100644 --- a/crates/eips/src/eip4844/mod.rs +++ b/crates/eips/src/eip4844/mod.rs @@ -13,6 +13,9 @@ pub mod trusted_setup_points; pub mod builder; pub mod utils; +mod engine; +pub use engine::*; + /// Contains sidecar related types #[cfg(feature = "kzg-sidecar")] mod sidecar; diff --git a/crates/eips/src/eip7002.rs b/crates/eips/src/eip7002.rs index 43a11df5c06..fb3780337fe 100644 --- a/crates/eips/src/eip7002.rs +++ b/crates/eips/src/eip7002.rs @@ -25,6 +25,7 @@ pub const WITHDRAWAL_REQUEST_TYPE: u8 = 0x01; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] pub struct WithdrawalRequest { /// Address of the source of the exit. diff --git a/crates/eips/src/lib.rs b/crates/eips/src/lib.rs index 393ba4f343e..2bb4b07e0cf 100644 --- a/crates/eips/src/lib.rs +++ b/crates/eips/src/lib.rs @@ -16,7 +16,8 @@ pub use eip1559::calc_next_block_base_fee; pub mod eip1898; pub use eip1898::{ - BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, ForkBlock, RpcBlockHash, + BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, ForkBlock, HashOrNumber, NumHash, + RpcBlockHash, }; pub mod eip2718; diff --git a/crates/genesis/CHANGELOG.md b/crates/genesis/CHANGELOG.md index 386ed653940..d070777f633 100644 --- a/crates/genesis/CHANGELOG.md +++ b/crates/genesis/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Features @@ -13,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Clippy für docs ([#1194](https://github.com/alloy-rs/alloy/issues/1194)) - Release 0.2.1 - Release 0.2.0 diff --git a/crates/json-rpc/CHANGELOG.md b/crates/json-rpc/CHANGELOG.md index 4f4ab9ac606..fb16a616108 100644 --- a/crates/json-rpc/CHANGELOG.md +++ b/crates/json-rpc/CHANGELOG.md @@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- ProviderCall ([#788](https://github.com/alloy-rs/alloy/issues/788)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Features + +- [`json-rpc`] Implement From U256 and String for SubId ([#1226](https://github.com/alloy-rs/alloy/issues/1226)) + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -19,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Clippy für docs ([#1194](https://github.com/alloy-rs/alloy/issues/1194)) - JSON-RPC 2.0 spelling ([#1146](https://github.com/alloy-rs/alloy/issues/1146)) - Release 0.2.1 diff --git a/crates/json-rpc/src/notification.rs b/crates/json-rpc/src/notification.rs index e1af087127b..d6f6a9ab4be 100644 --- a/crates/json-rpc/src/notification.rs +++ b/crates/json-rpc/src/notification.rs @@ -15,6 +15,18 @@ pub enum SubId { String(String), } +impl From for SubId { + fn from(value: U256) -> Self { + Self::Number(value) + } +} + +impl From for SubId { + fn from(value: String) -> Self { + Self::String(value) + } +} + /// An ethereum-style notification, not to be confused with a JSON-RPC /// notification. #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/crates/json-rpc/src/request.rs b/crates/json-rpc/src/request.rs index bc8fb11b4e7..2dac9b67092 100644 --- a/crates/json-rpc/src/request.rs +++ b/crates/json-rpc/src/request.rs @@ -78,6 +78,14 @@ impl Request { pub fn set_subscription_status(&mut self, sub: bool) { self.meta.set_subscription_status(sub); } + + /// Change type of the request parameters. + pub fn map_params( + self, + map: impl FnOnce(Params) -> NewParams, + ) -> Request { + Request { meta: self.meta, params: map(self.params) } + } } /// A [`Request`] that has been partially serialized. @@ -113,11 +121,12 @@ where impl Request<&Params> where - Params: Clone, + Params: ToOwned, + Params::Owned: RpcParam, { /// Clone the request, including the request parameters. - pub fn into_owned_params(self) -> Request { - Request { meta: self.meta, params: self.params.clone() } + pub fn into_owned_params(self) -> Request { + Request { meta: self.meta, params: self.params.to_owned() } } } diff --git a/crates/network-primitives/CHANGELOG.md b/crates/network-primitives/CHANGELOG.md index 0d14783e8d8..5fbdc6869f4 100644 --- a/crates/network-primitives/CHANGELOG.md +++ b/crates/network-primitives/CHANGELOG.md @@ -5,6 +5,50 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 +- [network-primitives] Remove alloc Vec Dep ([#1267](https://github.com/alloy-rs/alloy/issues/1267)) + +### Other + +- Add trait methods `cumulative_gas_used` and `state_root` to `ReceiptResponse` ([#1275](https://github.com/alloy-rs/alloy/issues/1275)) + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Features + +- No_std network primitives ([#1248](https://github.com/alloy-rs/alloy/issues/1248)) +- [network-primitives] Expose more fields via block response traits ([#1229](https://github.com/alloy-rs/alloy/issues/1229)) + +### Miscellaneous Tasks + +- Release 0.3.2 + +### Other + +- Add getter trait methods to `ReceiptResponse` ([#1251](https://github.com/alloy-rs/alloy/issues/1251)) + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -17,6 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 ### Refactor diff --git a/crates/network-primitives/Cargo.toml b/crates/network-primitives/Cargo.toml index a56fb3e6239..32447e59121 100644 --- a/crates/network-primitives/Cargo.toml +++ b/crates/network-primitives/Cargo.toml @@ -21,8 +21,13 @@ workspace = true [dependencies] alloy-primitives.workspace = true alloy-serde.workspace = true +alloy-eips.workspace = true serde.workspace = true [dev-dependencies] rand.workspace = true + +[features] +default = ["std"] +std = ["alloy-primitives/std"] diff --git a/crates/network-primitives/src/block.rs b/crates/network-primitives/src/block.rs index 1ec52f338ba..f817d2988b9 100644 --- a/crates/network-primitives/src/block.rs +++ b/crates/network-primitives/src/block.rs @@ -1,6 +1,9 @@ use alloy_primitives::B256; use serde::{Deserialize, Serialize}; +use alloc::{vec, vec::Vec}; +use core::slice; + use crate::TransactionResponse; /// Block Transactions depending on the boolean attribute of `eth_getBlockBy*`, @@ -69,10 +72,10 @@ impl BlockTransactions { /// Returns an iterator over the transactions (if any). This will be empty if the block is not /// full. - pub fn into_transactions(self) -> std::vec::IntoIter { + pub fn into_transactions(self) -> vec::IntoIter { match self { Self::Full(txs) => txs.into_iter(), - _ => std::vec::IntoIter::default(), + _ => vec::IntoIter::default(), } } @@ -149,8 +152,8 @@ pub struct BlockTransactionHashes<'a, T>(BlockTransactionHashesInner<'a, T>); #[derive(Clone, Debug)] enum BlockTransactionHashesInner<'a, T> { - Hashes(std::slice::Iter<'a, B256>), - Full(std::slice::Iter<'a, T>), + Hashes(slice::Iter<'a, B256>), + Full(slice::Iter<'a, T>), Uncle, } @@ -209,6 +212,7 @@ impl DoubleEndedIterator for BlockTransactionHashes<'_, } } +#[cfg(feature = "std")] impl<'a, T: TransactionResponse> std::iter::FusedIterator for BlockTransactionHashes<'a, T> {} /// Determines how the `transactions` field of block should be filled. diff --git a/crates/network-primitives/src/lib.rs b/crates/network-primitives/src/lib.rs index d1534307e4a..6012668a3dd 100644 --- a/crates/network-primitives/src/lib.rs +++ b/crates/network-primitives/src/lib.rs @@ -5,6 +5,9 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; mod traits; pub use traits::{BlockResponse, HeaderResponse, ReceiptResponse, TransactionResponse}; diff --git a/crates/network-primitives/src/traits.rs b/crates/network-primitives/src/traits.rs index 64fc692cfa6..a8ec7e3f8b2 100644 --- a/crates/network-primitives/src/traits.rs +++ b/crates/network-primitives/src/traits.rs @@ -1,4 +1,5 @@ -use alloy_primitives::{Address, BlockHash, Bytes, TxHash, U256}; +use alloy_eips::eip7702::SignedAuthorization; +use alloy_primitives::{Address, BlockHash, Bytes, TxHash, B256, U256}; use alloy_serde::WithOtherFields; use crate::BlockTransactions; @@ -23,6 +24,41 @@ pub trait ReceiptResponse { /// Number of the block this transaction was included within. fn block_number(&self) -> Option; + + /// Transaction Hash. + fn transaction_hash(&self) -> TxHash; + + /// Index within the block. + fn transaction_index(&self) -> Option; + + /// Gas used by this transaction alone. + fn gas_used(&self) -> u128; + + /// Effective gas price. + fn effective_gas_price(&self) -> u128; + + /// Blob gas used by the eip-4844 transaction. + fn blob_gas_used(&self) -> Option; + + /// Blob gas price paid by the eip-4844 transaction. + fn blob_gas_price(&self) -> Option; + + /// Address of the sender. + fn from(&self) -> Address; + + /// Address of the receiver. + fn to(&self) -> Option
; + + /// EIP-7702 Authorization list. + fn authorization_list(&self) -> Option<&[SignedAuthorization]>; + + /// Returns the cumulative gas used at this receipt. + fn cumulative_gas_used(&self) -> u128; + + /// The post-transaction state root (pre Byzantium) + /// + /// EIP98 makes this field optional. + fn state_root(&self) -> Option; } /// Transaction JSON-RPC response. @@ -67,6 +103,30 @@ pub trait HeaderResponse { /// Blob fee for the next block (if EIP-4844 is supported) fn next_block_blob_fee(&self) -> Option; + + /// Coinbase/Miner of the block + fn coinbase(&self) -> Address; + + /// Gas limit of the block + fn gas_limit(&self) -> u128; + + /// Mix hash of the block + /// + /// Before the merge this proves, combined with the nonce, that a sufficient amount of + /// computation has been carried out on this block: the Proof-of-Work (PoW). + /// + /// After the merge this is `prevRandao`: Randomness value for the generated payload. + /// + /// This is an Option because it is not always set by non-ethereum networks. + /// + /// See also + /// And + fn mix_hash(&self) -> Option; + + /// Difficulty of the block + /// + /// Unused after the Paris (AKA the merge) upgrade, and replaced by `prevrandao`. + fn difficulty(&self) -> U256; } /// Block JSON-RPC response. @@ -84,6 +144,11 @@ pub trait BlockResponse { /// Mutable reference to block transactions fn transactions_mut(&mut self) -> &mut BlockTransactions; + + /// Returns the `other` field from `WithOtherFields` type. + fn other_fields(&self) -> Option<&alloy_serde::OtherFields> { + None + } } impl TransactionResponse for WithOtherFields { @@ -128,6 +193,50 @@ impl ReceiptResponse for WithOtherFields { fn block_number(&self) -> Option { self.inner.block_number() } + + fn transaction_hash(&self) -> TxHash { + self.inner.transaction_hash() + } + + fn transaction_index(&self) -> Option { + self.inner.transaction_index() + } + + fn gas_used(&self) -> u128 { + self.inner.gas_used() + } + + fn effective_gas_price(&self) -> u128 { + self.inner.effective_gas_price() + } + + fn blob_gas_used(&self) -> Option { + self.inner.blob_gas_used() + } + + fn blob_gas_price(&self) -> Option { + self.inner.blob_gas_price() + } + + fn from(&self) -> Address { + self.inner.from() + } + + fn to(&self) -> Option
{ + self.inner.to() + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.inner.authorization_list() + } + + fn cumulative_gas_used(&self) -> u128 { + self.inner.cumulative_gas_used() + } + + fn state_root(&self) -> Option { + self.inner.state_root() + } } impl BlockResponse for WithOtherFields { @@ -145,6 +254,10 @@ impl BlockResponse for WithOtherFields { fn transactions_mut(&mut self) -> &mut BlockTransactions { self.inner.transactions_mut() } + + fn other_fields(&self) -> Option<&alloy_serde::OtherFields> { + Some(&self.other) + } } impl HeaderResponse for WithOtherFields { @@ -171,4 +284,20 @@ impl HeaderResponse for WithOtherFields { fn next_block_blob_fee(&self) -> Option { self.inner.next_block_blob_fee() } + + fn coinbase(&self) -> Address { + self.inner.coinbase() + } + + fn gas_limit(&self) -> u128 { + self.inner.gas_limit() + } + + fn mix_hash(&self) -> Option { + self.inner.mix_hash() + } + + fn difficulty(&self) -> U256 { + self.inner.difficulty() + } } diff --git a/crates/network/CHANGELOG.md b/crates/network/CHANGELOG.md index 6b2f0a26b90..d444553b9ac 100644 --- a/crates/network/CHANGELOG.md +++ b/crates/network/CHANGELOG.md @@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Refactor + +- Separate transaction builders for tx types ([#1259](https://github.com/alloy-rs/alloy/issues/1259)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Features + +- [alloy-rpc-types-eth] Optional serde ([#1276](https://github.com/alloy-rs/alloy/issues/1276)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -20,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Remove async_trait from NetworkWallet ([#1160](https://github.com/alloy-rs/alloy/issues/1160)) - Add missing 7702 check ([#1137](https://github.com/alloy-rs/alloy/issues/1137)) - Release 0.2.1 diff --git a/crates/network/Cargo.toml b/crates/network/Cargo.toml index 9eda78c2426..8a647bef2e5 100644 --- a/crates/network/Cargo.toml +++ b/crates/network/Cargo.toml @@ -24,7 +24,7 @@ alloy-eips = { workspace = true, features = ["serde"] } alloy-json-rpc.workspace = true alloy-network-primitives.workspace = true alloy-primitives.workspace = true -alloy-rpc-types-eth.workspace = true +alloy-rpc-types-eth = { workspace = true, features = ["std", "serde"] } alloy-signer.workspace = true alloy-serde.workspace = true alloy-sol-types.workspace = true diff --git a/crates/network/src/any/builder.rs b/crates/network/src/any/builder.rs index 66618489e00..2d53702eab3 100644 --- a/crates/network/src/any/builder.rs +++ b/crates/network/src/any/builder.rs @@ -1,6 +1,6 @@ use crate::{ any::AnyNetwork, BuildResult, Network, NetworkWallet, TransactionBuilder, - TransactionBuilderError, + TransactionBuilder4844, TransactionBuilder7702, TransactionBuilderError, }; use alloy_consensus::BlobTransactionSidecar; use alloy_eips::eip7702::SignedAuthorization; @@ -86,14 +86,6 @@ impl TransactionBuilder for WithOtherFields { self.deref_mut().set_max_priority_fee_per_gas(max_priority_fee_per_gas); } - fn max_fee_per_blob_gas(&self) -> Option { - self.deref().max_fee_per_blob_gas() - } - - fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) { - self.deref_mut().set_max_fee_per_blob_gas(max_fee_per_blob_gas) - } - fn gas_limit(&self) -> Option { self.deref().gas_limit() } @@ -112,22 +104,6 @@ impl TransactionBuilder for WithOtherFields { self.deref_mut().set_access_list(access_list) } - fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { - self.deref().blob_sidecar() - } - - fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecar) { - self.deref_mut().set_blob_sidecar(sidecar) - } - - fn authorization_list(&self) -> Option<&Vec> { - self.deref().authorization_list() - } - - fn set_authorization_list(&mut self, authorization_list: Vec) { - self.deref_mut().set_authorization_list(authorization_list) - } - fn complete_type(&self, ty: ::TxType) -> Result<(), Vec<&'static str>> { self.deref().complete_type(ty.try_into().map_err(|_| vec!["supported tx type"])?) } @@ -172,3 +148,31 @@ impl TransactionBuilder for WithOtherFields { Ok(wallet.sign_request(self).await?) } } + +impl TransactionBuilder4844 for WithOtherFields { + fn max_fee_per_blob_gas(&self) -> Option { + self.deref().max_fee_per_blob_gas() + } + + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) { + self.deref_mut().set_max_fee_per_blob_gas(max_fee_per_blob_gas) + } + + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { + self.deref().blob_sidecar() + } + + fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecar) { + self.deref_mut().set_blob_sidecar(sidecar) + } +} + +impl TransactionBuilder7702 for WithOtherFields { + fn authorization_list(&self) -> Option<&Vec> { + self.deref().authorization_list() + } + + fn set_authorization_list(&mut self, authorization_list: Vec) { + self.deref_mut().set_authorization_list(authorization_list) + } +} diff --git a/crates/network/src/ethereum/builder.rs b/crates/network/src/ethereum/builder.rs index e2328cca4c5..f4b90972e62 100644 --- a/crates/network/src/ethereum/builder.rs +++ b/crates/network/src/ethereum/builder.rs @@ -1,5 +1,6 @@ use crate::{ - BuildResult, Ethereum, Network, NetworkWallet, TransactionBuilder, TransactionBuilderError, + BuildResult, Ethereum, Network, NetworkWallet, TransactionBuilder, TransactionBuilder4844, + TransactionBuilder7702, TransactionBuilderError, }; use alloy_consensus::{BlobTransactionSidecar, TxType, TypedTransaction}; use alloy_eips::eip7702::SignedAuthorization; @@ -83,14 +84,6 @@ impl TransactionBuilder for TransactionRequest { self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); } - fn max_fee_per_blob_gas(&self) -> Option { - self.max_fee_per_blob_gas - } - - fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) { - self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas) - } - fn gas_limit(&self) -> Option { self.gas } @@ -107,23 +100,6 @@ impl TransactionBuilder for TransactionRequest { self.access_list = Some(access_list); } - fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { - self.sidecar.as_ref() - } - - fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecar) { - self.sidecar = Some(sidecar); - self.populate_blob_hashes(); - } - - fn authorization_list(&self) -> Option<&Vec> { - self.authorization_list.as_ref() - } - - fn set_authorization_list(&mut self, authorization_list: Vec) { - self.authorization_list = Some(authorization_list); - } - fn complete_type(&self, ty: TxType) -> Result<(), Vec<&'static str>> { match ty { TxType::Legacy => self.complete_legacy(), @@ -191,9 +167,40 @@ impl TransactionBuilder for TransactionRequest { } } +impl TransactionBuilder4844 for TransactionRequest { + fn max_fee_per_blob_gas(&self) -> Option { + self.max_fee_per_blob_gas + } + + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128) { + self.max_fee_per_blob_gas = Some(max_fee_per_blob_gas) + } + + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { + self.sidecar.as_ref() + } + + fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecar) { + self.sidecar = Some(sidecar); + self.populate_blob_hashes(); + } +} + +impl TransactionBuilder7702 for TransactionRequest { + fn authorization_list(&self) -> Option<&Vec> { + self.authorization_list.as_ref() + } + + fn set_authorization_list(&mut self, authorization_list: Vec) { + self.authorization_list = Some(authorization_list); + } +} + #[cfg(test)] mod tests { - use crate::{TransactionBuilder, TransactionBuilderError}; + use crate::{ + TransactionBuilder, TransactionBuilder4844, TransactionBuilder7702, TransactionBuilderError, + }; use alloy_consensus::{BlobTransactionSidecar, TxEip1559, TxType, TypedTransaction}; use alloy_eips::eip7702::Authorization; use alloy_primitives::{Address, Signature, U256}; diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index f285150b045..1ae519fdef0 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -13,8 +13,8 @@ use core::fmt::{Debug, Display}; mod transaction; pub use transaction::{ - BuildResult, NetworkWallet, TransactionBuilder, TransactionBuilderError, TxSigner, - TxSignerSync, UnbuiltTransactionError, + BuildResult, NetworkWallet, TransactionBuilder, TransactionBuilder4844, TransactionBuilder7702, + TransactionBuilderError, TxSigner, TxSignerSync, UnbuiltTransactionError, }; mod ethereum; diff --git a/crates/network/src/transaction/builder.rs b/crates/network/src/transaction/builder.rs index f2ba6bea84a..a82efceaa12 100644 --- a/crates/network/src/transaction/builder.rs +++ b/crates/network/src/transaction/builder.rs @@ -248,19 +248,6 @@ pub trait TransactionBuilder: Default + Sized + Send + Sync + 'stati self.set_max_priority_fee_per_gas(max_priority_fee_per_gas); self } - - /// Get the max fee per blob gas for the transaction. - fn max_fee_per_blob_gas(&self) -> Option; - - /// Set the max fee per blob gas for the transaction. - fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128); - - /// Builder-pattern method for setting max fee per blob gas . - fn with_max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self { - self.set_max_fee_per_blob_gas(max_fee_per_blob_gas); - self - } - /// Get the gas limit for the transaction. fn gas_limit(&self) -> Option; @@ -285,33 +272,6 @@ pub trait TransactionBuilder: Default + Sized + Send + Sync + 'stati self } - /// Gets the EIP-4844 blob sidecar of the transaction. - fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar>; - - /// Sets the EIP-4844 blob sidecar of the transaction. - /// - /// Note: This will also set the versioned blob hashes accordingly: - /// [BlobTransactionSidecar::versioned_hashes] - fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecar); - - /// Builder-pattern method for setting the EIP-4844 blob sidecar of the transaction. - fn with_blob_sidecar(mut self, sidecar: BlobTransactionSidecar) -> Self { - self.set_blob_sidecar(sidecar); - self - } - - /// Get the EIP-7702 authorization list for the transaction. - fn authorization_list(&self) -> Option<&Vec>; - - /// Sets the EIP-7702 authorization list. - fn set_authorization_list(&mut self, authorization_list: Vec); - - /// Builder-pattern method for setting the authorization list. - fn with_authorization_list(mut self, authorization_list: Vec) -> Self { - self.set_authorization_list(authorization_list); - self - } - /// Check if all necessary keys are present to build the specified type, /// returning a list of missing keys. fn complete_type(&self, ty: N::TxType) -> Result<(), Vec<&'static str>>; @@ -383,3 +343,48 @@ pub trait TransactionBuilder: Default + Sized + Send + Sync + 'stati wallet: &W, ) -> impl_future!(>>); } + +/// Transaction builder type supporting EIP-4844 transaction fields. +pub trait TransactionBuilder4844: Default + Sized + Send + Sync + 'static { + /// Get the max fee per blob gas for the transaction. + fn max_fee_per_blob_gas(&self) -> Option; + + /// Set the max fee per blob gas for the transaction. + fn set_max_fee_per_blob_gas(&mut self, max_fee_per_blob_gas: u128); + + /// Builder-pattern method for setting max fee per blob gas . + fn with_max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self { + self.set_max_fee_per_blob_gas(max_fee_per_blob_gas); + self + } + + /// Gets the EIP-4844 blob sidecar of the transaction. + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar>; + + /// Sets the EIP-4844 blob sidecar of the transaction. + /// + /// Note: This will also set the versioned blob hashes accordingly: + /// [BlobTransactionSidecar::versioned_hashes] + fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecar); + + /// Builder-pattern method for setting the EIP-4844 blob sidecar of the transaction. + fn with_blob_sidecar(mut self, sidecar: BlobTransactionSidecar) -> Self { + self.set_blob_sidecar(sidecar); + self + } +} + +/// Transaction builder type supporting EIP-7702 transaction fields. +pub trait TransactionBuilder7702: Default + Sized + Send + Sync + 'static { + /// Get the EIP-7702 authorization list for the transaction. + fn authorization_list(&self) -> Option<&Vec>; + + /// Sets the EIP-7702 authorization list. + fn set_authorization_list(&mut self, authorization_list: Vec); + + /// Builder-pattern method for setting the authorization list. + fn with_authorization_list(mut self, authorization_list: Vec) -> Self { + self.set_authorization_list(authorization_list); + self + } +} diff --git a/crates/network/src/transaction/mod.rs b/crates/network/src/transaction/mod.rs index 3d78d84c0d2..f790970b2e9 100644 --- a/crates/network/src/transaction/mod.rs +++ b/crates/network/src/transaction/mod.rs @@ -1,6 +1,7 @@ mod builder; pub use builder::{ - BuildResult, TransactionBuilder, TransactionBuilderError, UnbuiltTransactionError, + BuildResult, TransactionBuilder, TransactionBuilder4844, TransactionBuilder7702, + TransactionBuilderError, UnbuiltTransactionError, }; mod signer; diff --git a/crates/node-bindings/CHANGELOG.md b/crates/node-bindings/CHANGELOG.md index af9777df71a..80a3196d0d3 100644 --- a/crates/node-bindings/CHANGELOG.md +++ b/crates/node-bindings/CHANGELOG.md @@ -5,6 +5,62 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- [`node-bindings`] Support appending extra args ([#1299](https://github.com/alloy-rs/alloy/issues/1299)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Bug Fixes + +- `debug_traceCallMany` and `trace_callMany` ([#1278](https://github.com/alloy-rs/alloy/issues/1278)) + +### Features + +- Improve node bindings ([#1279](https://github.com/alloy-rs/alloy/issues/1279)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +### Testing + +- Dont use fork test ([#1263](https://github.com/alloy-rs/alloy/issues/1263)) + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Features + +- Add Reth node bindings ([#1092](https://github.com/alloy-rs/alloy/issues/1092)) + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Bug Fixes + +- Anvil builder default port ([#1213](https://github.com/alloy-rs/alloy/issues/1213)) + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -15,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/node-bindings/Cargo.toml b/crates/node-bindings/Cargo.toml index bb55e3ab08f..67d34c7ffbd 100644 --- a/crates/node-bindings/Cargo.toml +++ b/crates/node-bindings/Cargo.toml @@ -22,11 +22,9 @@ workspace = true alloy-primitives = { workspace = true, features = ["std", "k256", "serde"] } alloy-genesis.workspace = true k256.workspace = true +rand.workspace = true serde_json = { workspace = true, features = ["std"] } tempfile.workspace = true thiserror.workspace = true tracing.workspace = true url.workspace = true - -[dev-dependencies] -rand.workspace = true diff --git a/crates/node-bindings/src/lib.rs b/crates/node-bindings/src/lib.rs index dc99b996839..2ed26210609 100644 --- a/crates/node-bindings/src/lib.rs +++ b/crates/node-bindings/src/lib.rs @@ -11,37 +11,34 @@ extern crate tracing; use alloy_primitives::U256; -pub mod anvil; -pub use anvil::{Anvil, AnvilInstance}; +pub mod nodes; +pub use nodes::{ + anvil::{self, Anvil, AnvilInstance}, + geth::{self, Geth, GethInstance}, + reth::{self, Reth, RethInstance}, +}; -pub mod geth; -pub use geth::{Geth, GethInstance}; +mod node; +pub use node::*; + +pub mod utils; /// 1 Ether = 1e18 Wei == 0x0de0b6b3a7640000 Wei pub const WEI_IN_ETHER: U256 = U256::from_limbs([0x0de0b6b3a7640000, 0x0, 0x0, 0x0]); /// The number of blocks from the past for which the fee rewards are fetched for fee estimation. pub const EIP1559_FEE_ESTIMATION_PAST_BLOCKS: u64 = 10; + /// The default percentile of gas premiums that are fetched for fee estimation. pub const EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE: f64 = 5.0; + /// The default max priority fee per gas, used in case the base fee is within a threshold. pub const EIP1559_FEE_ESTIMATION_DEFAULT_PRIORITY_FEE: u64 = 3_000_000_000; + /// The threshold for base fee below which we use the default priority fee, and beyond which we /// estimate an appropriate value for priority fee. pub const EIP1559_FEE_ESTIMATION_PRIORITY_FEE_TRIGGER: u64 = 100_000_000_000; + /// The threshold max change/difference (in %) at which we will ignore the fee history values /// under it. pub const EIP1559_FEE_ESTIMATION_THRESHOLD_MAX_CHANGE: i64 = 200; - -/// A bit of hack to find an unused TCP port. -/// -/// Does not guarantee that the given port is unused after the function exists, just that it was -/// unused before the function started (i.e., it does not reserve a port). -fn unused_port() -> u16 { - let listener = std::net::TcpListener::bind("127.0.0.1:0") - .expect("Failed to create TCP listener to find unused port"); - - let local_addr = - listener.local_addr().expect("Failed to read TCP listener local_addr to find unused port"); - local_addr.port() -} diff --git a/crates/node-bindings/src/node.rs b/crates/node-bindings/src/node.rs new file mode 100644 index 00000000000..8a8b94fac0c --- /dev/null +++ b/crates/node-bindings/src/node.rs @@ -0,0 +1,68 @@ +//! Node-related types and constants. + +use alloy_primitives::hex; +use std::time::Duration; +use thiserror::Error; + +/// How long we will wait for the node to indicate that it is ready. +pub const NODE_STARTUP_TIMEOUT: Duration = Duration::from_secs(10); + +/// Timeout for waiting for the node to add a peer. +pub const NODE_DIAL_LOOP_TIMEOUT: Duration = Duration::from_secs(20); + +/// Errors that can occur when working with the node instance. +#[derive(Debug, Error)] +pub enum NodeError { + /// No stderr was captured from the child process. + #[error("no stderr was captured from the process")] + NoStderr, + /// No stdout was captured from the child process. + #[error("no stdout was captured from the process")] + NoStdout, + /// Timed out waiting for the node to start. + #[error("timed out waiting for node to spawn; is the node binary installed?")] + Timeout, + /// Encountered a fatal error. + #[error("fatal error: {0}")] + Fatal(String), + /// A line could not be read from the node stderr. + #[error("could not read line from node stderr: {0}")] + ReadLineError(std::io::Error), + + /// The chain id was not set. + #[error("the chain ID was not set")] + ChainIdNotSet, + /// Could not create the data directory. + #[error("could not create directory: {0}")] + CreateDirError(std::io::Error), + + /// Genesis error + #[error("genesis error occurred: {0}")] + GenesisError(String), + /// Node init error + #[error("node init error occurred")] + InitError, + /// Spawn node error + #[error("could not spawn node: {0}")] + SpawnError(std::io::Error), + /// Wait error + #[error("could not wait for node to exit: {0}")] + WaitError(std::io::Error), + + /// Clique private key error + #[error("clique address error: {0}")] + CliqueAddressError(String), + + /// The private key could not be parsed. + #[error("could not parse private key")] + ParsePrivateKeyError, + /// An error occurred while deserializing a private key. + #[error("could not deserialize private key from bytes")] + DeserializePrivateKeyError, + /// An error occurred while parsing a hex string. + #[error(transparent)] + FromHexError(#[from] hex::FromHexError), + /// No keys available this node instance. + #[error("no keys available in this node instance")] + NoKeysAvailable, +} diff --git a/crates/node-bindings/src/anvil.rs b/crates/node-bindings/src/nodes/anvil.rs similarity index 84% rename from crates/node-bindings/src/anvil.rs rename to crates/node-bindings/src/nodes/anvil.rs index 21028610210..5c050f14f8d 100644 --- a/crates/node-bindings/src/anvil.rs +++ b/crates/node-bindings/src/nodes/anvil.rs @@ -3,6 +3,7 @@ use alloy_primitives::{hex, Address, ChainId}; use k256::{ecdsa::SigningKey, SecretKey as K256SecretKey}; use std::{ + ffi::OsString, io::{BufRead, BufReader}, net::SocketAddr, path::PathBuf, @@ -10,9 +11,10 @@ use std::{ str::FromStr, time::{Duration, Instant}, }; -use thiserror::Error; use url::Url; +use crate::NodeError; + /// How long we will wait for anvil to indicate that it is ready. const ANVIL_STARTUP_TIMEOUT_MILLIS: u64 = 10_000; @@ -89,42 +91,6 @@ impl Drop for AnvilInstance { } } -/// Errors that can occur when working with the [`Anvil`]. -#[derive(Debug, Error)] -pub enum AnvilError { - /// Spawning the anvil process failed. - #[error("could not start anvil: {0}")] - SpawnError(std::io::Error), - - /// Timed out waiting for a message from anvil's stderr. - #[error("timed out waiting for anvil to spawn; is anvil installed?")] - Timeout, - - /// A line could not be read from the geth stderr. - #[error("could not read line from anvil stderr: {0}")] - ReadLineError(std::io::Error), - - /// The child anvil process's stderr was not captured. - #[error("could not get stderr for anvil child process")] - NoStderr, - - /// The private key could not be parsed. - #[error("could not parse private key")] - ParsePrivateKeyError, - - /// An error occurred while deserializing a private key. - #[error("could not deserialize private key from bytes")] - DeserializePrivateKeyError, - - /// An error occurred while parsing a hex string. - #[error(transparent)] - FromHexError(#[from] hex::FromHexError), - - /// No keys available in anvil instance. - #[error("no keys available in anvil instance")] - NoKeysAvailable, -} - /// Builder for launching `anvil`. /// /// # Panics @@ -158,13 +124,13 @@ pub struct Anvil { mnemonic: Option, fork: Option, fork_block_number: Option, - args: Vec, + args: Vec, timeout: Option, } impl Anvil { /// Creates an empty Anvil builder. - /// The default port is 8545. The mnemonic is chosen randomly. + /// The default port and the mnemonic are chosen randomly. /// /// # Example /// @@ -254,7 +220,7 @@ impl Anvil { } /// Adds an argument to pass to the `anvil`. - pub fn arg>(mut self, arg: T) -> Self { + pub fn arg>(mut self, arg: T) -> Self { self.args.push(arg.into()); self } @@ -263,7 +229,7 @@ impl Anvil { pub fn args(mut self, args: I) -> Self where I: IntoIterator, - S: Into, + S: Into, { for arg in args { self = self.arg(arg); @@ -288,7 +254,7 @@ impl Anvil { } /// Consumes the builder and spawns `anvil`. If spawning fails, returns an error. - pub fn try_spawn(self) -> Result { + pub fn try_spawn(self) -> Result { let mut cmd = self.program.as_ref().map_or_else(|| Command::new("anvil"), Command::new); cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::inherit()); let mut port = self.port.unwrap_or_default(); @@ -316,9 +282,9 @@ impl Anvil { cmd.args(self.args); - let mut child = cmd.spawn().map_err(AnvilError::SpawnError)?; + let mut child = cmd.spawn().map_err(NodeError::SpawnError)?; - let stdout = child.stdout.take().ok_or(AnvilError::NoStderr)?; + let stdout = child.stdout.take().ok_or(NodeError::NoStderr)?; let start = Instant::now(); let mut reader = BufReader::new(stdout); @@ -331,11 +297,11 @@ impl Anvil { if start + Duration::from_millis(self.timeout.unwrap_or(ANVIL_STARTUP_TIMEOUT_MILLIS)) <= Instant::now() { - return Err(AnvilError::Timeout); + return Err(NodeError::Timeout); } let mut line = String::new(); - reader.read_line(&mut line).map_err(AnvilError::ReadLineError)?; + reader.read_line(&mut line).map_err(NodeError::ReadLineError)?; trace!(target: "anvil", line); if let Some(addr) = line.strip_prefix("Listening on") { // @@ -352,10 +318,10 @@ impl Anvil { if is_private_key && line.starts_with('(') { let key_str = - line.split("0x").last().ok_or(AnvilError::ParsePrivateKeyError)?.trim(); - let key_hex = hex::decode(key_str).map_err(AnvilError::FromHexError)?; + line.split("0x").last().ok_or(NodeError::ParsePrivateKeyError)?.trim(); + let key_hex = hex::decode(key_str).map_err(NodeError::FromHexError)?; let key = K256SecretKey::from_bytes((&key_hex[..]).into()) - .map_err(|_| AnvilError::DeserializePrivateKeyError)?; + .map_err(|_| NodeError::DeserializePrivateKeyError)?; addresses.push(Address::from_public_key(SigningKey::from(&key).verifying_key())); private_keys.push(key); } @@ -408,8 +374,9 @@ mod tests { #[test] fn assert_chain_id() { - let anvil = Anvil::new().fork("https://eth.llamarpc.com ").spawn(); - assert_eq!(anvil.chain_id(), 1); + let id = 99999; + let anvil = Anvil::new().chain_id(id).spawn(); + assert_eq!(anvil.chain_id(), id); } #[test] diff --git a/crates/node-bindings/src/geth.rs b/crates/node-bindings/src/nodes/geth.rs similarity index 74% rename from crates/node-bindings/src/geth.rs rename to crates/node-bindings/src/nodes/geth.rs index 4c61b153a86..3e36f096a67 100644 --- a/crates/node-bindings/src/geth.rs +++ b/crates/node-bindings/src/nodes/geth.rs @@ -1,45 +1,65 @@ -//! Utilities for launching a go-ethereum dev-mode instance. +//! Utilities for launching a Geth dev-mode instance. -use crate::unused_port; +use crate::{ + utils::{extract_endpoint, extract_value, unused_port}, + NodeError, NODE_DIAL_LOOP_TIMEOUT, NODE_STARTUP_TIMEOUT, +}; use alloy_genesis::{CliqueConfig, Genesis}; use alloy_primitives::Address; use k256::ecdsa::SigningKey; use std::{ - borrow::Cow, + ffi::OsString, fs::{create_dir, File}, io::{BufRead, BufReader}, - net::SocketAddr, path::PathBuf, process::{Child, ChildStderr, Command, Stdio}, - time::{Duration, Instant}, + time::Instant, }; use tempfile::tempdir; -use thiserror::Error; use url::Url; -/// How long we will wait for geth to indicate that it is ready. -const GETH_STARTUP_TIMEOUT: Duration = Duration::from_secs(10); - -/// Timeout for waiting for geth to add a peer. -const GETH_DIAL_LOOP_TIMEOUT: Duration = Duration::from_secs(20); - /// The exposed APIs const API: &str = "eth,net,web3,txpool,admin,personal,miner,debug"; /// The geth command const GETH: &str = "geth"; -/// Errors that can occur when working with the [`GethInstance`]. -#[derive(Debug)] -pub enum GethInstanceError { - /// Timed out waiting for a message from geth's stderr. - Timeout(String), +/// Whether or not node is in `dev` mode and configuration options that depend on the mode. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum NodeMode { + /// Options that can be set in dev mode + Dev(DevOptions), + /// Options that cannot be set in dev mode + NonDev(PrivateNetOptions), +} + +impl Default for NodeMode { + fn default() -> Self { + Self::Dev(Default::default()) + } +} - /// A line could not be read from the geth stderr. - ReadLineError(std::io::Error), +/// Configuration options that can be set in dev mode. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct DevOptions { + /// The interval at which the dev chain will mine new blocks. + pub block_time: Option, +} + +/// Configuration options that cannot be set in dev mode. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PrivateNetOptions { + /// The p2p port to use. + pub p2p_port: Option, + + /// Whether or not peer discovery is enabled. + pub discovery: bool, +} - /// The child geth process's stderr was not captured. - NoStderr, +impl Default for PrivateNetOptions { + fn default() -> Self { + Self { p2p_port: None, discovery: true } + } } /// A geth instance. Will close the instance when dropped. @@ -49,9 +69,10 @@ pub enum GethInstanceError { pub struct GethInstance { pid: Child, port: u16, + p2p_port: Option, + auth_port: Option, ipc: Option, data_dir: Option, - p2p_port: Option, genesis: Option, clique_private_key: Option, } @@ -67,6 +88,11 @@ impl GethInstance { self.p2p_port } + /// Returns the auth port of this instance + pub const fn auth_port(&self) -> Option { + self.auth_port + } + /// Returns the HTTP endpoint of this instance #[doc(alias = "http_endpoint")] pub fn endpoint(&self) -> String { @@ -114,22 +140,22 @@ impl GethInstance { /// /// This leaves a `None` in its place, so calling methods that require a stderr to be present /// will fail if called after this. - pub fn stderr(&mut self) -> Result { - self.pid.stderr.take().ok_or(GethInstanceError::NoStderr) + pub fn stderr(&mut self) -> Result { + self.pid.stderr.take().ok_or(NodeError::NoStderr) } /// Blocks until geth adds the specified peer, using 20s as the timeout. /// /// Requires the stderr to be present in the `GethInstance`. - pub fn wait_to_add_peer(&mut self, id: &str) -> Result<(), GethInstanceError> { - let mut stderr = self.pid.stderr.as_mut().ok_or(GethInstanceError::NoStderr)?; + pub fn wait_to_add_peer(&mut self, id: &str) -> Result<(), NodeError> { + let mut stderr = self.pid.stderr.as_mut().ok_or(NodeError::NoStderr)?; let mut err_reader = BufReader::new(&mut stderr); let mut line = String::new(); let start = Instant::now(); - while start.elapsed() < GETH_DIAL_LOOP_TIMEOUT { + while start.elapsed() < NODE_DIAL_LOOP_TIMEOUT { line.clear(); - err_reader.read_line(&mut line).map_err(GethInstanceError::ReadLineError)?; + err_reader.read_line(&mut line).map_err(NodeError::ReadLineError)?; // geth ids are truncated let truncated_id = if id.len() > 16 { &id[..16] } else { id }; @@ -137,7 +163,7 @@ impl GethInstance { return Ok(()); } } - Err(GethInstanceError::Timeout("Timed out waiting for geth to add a peer".into())) + Err(NodeError::Timeout) } } @@ -147,82 +173,6 @@ impl Drop for GethInstance { } } -/// Whether or not geth is in `dev` mode and configuration options that depend on the mode. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum GethMode { - /// Options that can be set in dev mode - Dev(DevOptions), - /// Options that cannot be set in dev mode - NonDev(PrivateNetOptions), -} - -impl Default for GethMode { - fn default() -> Self { - Self::Dev(Default::default()) - } -} - -/// Configuration options that can be set in dev mode. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct DevOptions { - /// The interval at which the dev chain will mine new blocks. - pub block_time: Option, -} - -/// Configuration options that cannot be set in dev mode. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PrivateNetOptions { - /// The p2p port to use. - pub p2p_port: Option, - - /// Whether or not peer discovery is enabled. - pub discovery: bool, -} - -impl Default for PrivateNetOptions { - fn default() -> Self { - Self { p2p_port: None, discovery: true } - } -} - -/// Errors that can occur when working with the [`Geth`]. -#[derive(Debug, Error)] -pub enum GethError { - /// Clique private key error - #[error("clique address error: {0}")] - CliqueAddressError(String), - /// The chain id was not set. - #[error("the chain ID was not set")] - ChainIdNotSet, - /// Could not create the data directory. - #[error("could not create directory: {0}")] - CreateDirError(std::io::Error), - /// No stderr was captured from the child process. - #[error("no stderr was captured from the process")] - NoStderr, - /// Timed out waiting for geth to start. - #[error("timed out waiting for geth to spawn; is geth installed?")] - Timeout, - /// Encountered a fatal error. - #[error("fatal error: {0}")] - Fatal(String), - /// A line could not be read from the geth stderr. - #[error("could not read line from geth stderr: {0}")] - ReadLineError(std::io::Error), - /// Genesis error - #[error("genesis error occurred: {0}")] - GenesisError(String), - /// Geth init error - #[error("geth init error occurred")] - InitError, - /// Spawn geth error - #[error("could not spawn geth: {0}")] - SpawnError(std::io::Error), - /// Wait error - #[error("could not wait for geth to exit: {0}")] - WaitError(std::io::Error), -} - /// Builder for launching `geth`. /// /// # Panics @@ -253,8 +203,9 @@ pub struct Geth { chain_id: Option, insecure_unlock: bool, genesis: Option, - mode: GethMode, + mode: NodeMode, clique_private_key: Option, + args: Vec, } impl Geth { @@ -281,6 +232,21 @@ impl Geth { Self::new().path(path) } + /// Sets the `path` to the `geth` executable + /// + /// By default, it's expected that `geth` is in `$PATH`, see also + /// [`std::process::Command::new()`] + pub fn path>(mut self, path: T) -> Self { + self.program = Some(path.into()); + self + } + + /// Puts the `geth` instance in `dev` mode. + pub fn dev(mut self) -> Self { + self.mode = NodeMode::Dev(Default::default()); + self + } + /// Returns whether the node is launched in Clique consensus mode. pub const fn is_clique(&self) -> bool { self.clique_private_key.is_some() @@ -291,15 +257,6 @@ impl Geth { self.clique_private_key.as_ref().map(|pk| Address::from_public_key(pk.verifying_key())) } - /// Sets the `path` to the `geth` executable - /// - /// By default, it's expected that `geth` is in `$PATH`, see also - /// [`std::process::Command::new()`] - pub fn path>(mut self, path: T) -> Self { - self.program = Some(path.into()); - self - } - /// Sets the Clique Private Key to the `geth` executable, which will be later /// loaded on the node. /// @@ -326,13 +283,13 @@ impl Geth { /// options. pub fn p2p_port(mut self, port: u16) -> Self { match &mut self.mode { - GethMode::Dev(_) => { - self.mode = GethMode::NonDev(PrivateNetOptions { + NodeMode::Dev(_) => { + self.mode = NodeMode::NonDev(PrivateNetOptions { p2p_port: Some(port), ..Default::default() }) } - GethMode::NonDev(opts) => opts.p2p_port = Some(port), + NodeMode::NonDev(opts) => opts.p2p_port = Some(port), } self } @@ -342,7 +299,7 @@ impl Geth { /// This will put the geth instance in `dev` mode, discarding any previously set options that /// cannot be used in dev mode. pub const fn block_time(mut self, block_time: u64) -> Self { - self.mode = GethMode::Dev(DevOptions { block_time: Some(block_time) }); + self.mode = NodeMode::Dev(DevOptions { block_time: Some(block_time) }); self } @@ -375,11 +332,11 @@ impl Geth { fn inner_disable_discovery(&mut self) { match &mut self.mode { - GethMode::Dev(_) => { + NodeMode::Dev(_) => { self.mode = - GethMode::NonDev(PrivateNetOptions { discovery: false, ..Default::default() }) + NodeMode::NonDev(PrivateNetOptions { discovery: false, ..Default::default() }) } - GethMode::NonDev(opts) => opts.discovery = false, + NodeMode::NonDev(opts) => opts.discovery = false, } } @@ -412,6 +369,28 @@ impl Geth { self } + /// Adds an argument to pass to `geth`. + /// + /// Pass any arg that is not supported by the builder. + pub fn arg>(mut self, arg: T) -> Self { + self.args.push(arg.into()); + self + } + + /// Adds multiple arguments to pass to `geth`. + /// + /// Pass any args that is not supported by the builder. + pub fn args(mut self, args: I) -> Self + where + I: IntoIterator, + S: Into, + { + for arg in args { + self = self.arg(arg); + } + self + } + /// Consumes the builder and spawns `geth`. /// /// # Panics @@ -423,14 +402,14 @@ impl Geth { } /// Consumes the builder and spawns `geth`. If spawning fails, returns an error. - pub fn try_spawn(mut self) -> Result { + pub fn try_spawn(mut self) -> Result { let bin_path = self .program .as_ref() .map_or_else(|| GETH.as_ref(), |bin| bin.as_os_str()) .to_os_string(); let mut cmd = Command::new(&bin_path); - // geth uses stderr for its logs + // `geth` uses stderr for its logs cmd.stderr(Stdio::piped()); // If no port provided, let the os chose it for us @@ -474,7 +453,7 @@ impl Geth { let clique_config = CliqueConfig { period: Some(0), epoch: Some(8) }; genesis.config.clique = Some(clique_config); - let clique_addr = clique_addr.ok_or(GethError::CliqueAddressError( + let clique_addr = clique_addr.ok_or(NodeError::CliqueAddressError( "could not calculates the address of the Clique consensus address.".to_string(), ))?; @@ -489,12 +468,12 @@ impl Geth { cmd.arg("--miner.etherbase").arg(format!("{clique_addr:?}")); } - let clique_addr = self.clique_address().ok_or(GethError::CliqueAddressError( + let clique_addr = self.clique_address().ok_or(NodeError::CliqueAddressError( "could not calculates the address of the Clique consensus address.".to_string(), ))?; self.genesis = Some(Genesis::clique_genesis( - self.chain_id.ok_or(GethError::ChainIdNotSet)?, + self.chain_id.ok_or(NodeError::ChainIdNotSet)?, clique_addr, )); @@ -506,19 +485,19 @@ impl Geth { if let Some(genesis) = &self.genesis { // create a temp dir to store the genesis file - let temp_genesis_dir_path = tempdir().map_err(GethError::CreateDirError)?.into_path(); + let temp_genesis_dir_path = tempdir().map_err(NodeError::CreateDirError)?.into_path(); // create a temp dir to store the genesis file let temp_genesis_path = temp_genesis_dir_path.join("genesis.json"); // create the genesis file let mut file = File::create(&temp_genesis_path).map_err(|_| { - GethError::GenesisError("could not create genesis file".to_string()) + NodeError::GenesisError("could not create genesis file".to_string()) })?; // serialize genesis and write to file serde_json::to_writer_pretty(&mut file, &genesis).map_err(|_| { - GethError::GenesisError("could not write genesis to file".to_string()) + NodeError::GenesisError("could not write genesis to file".to_string()) })?; let mut init_cmd = Command::new(bin_path); @@ -532,17 +511,17 @@ impl Geth { init_cmd.arg("init").arg(temp_genesis_path); let res = init_cmd .spawn() - .map_err(GethError::SpawnError)? + .map_err(NodeError::SpawnError)? .wait() - .map_err(GethError::WaitError)?; + .map_err(NodeError::WaitError)?; // .expect("failed to wait for geth init to exit"); if !res.success() { - return Err(GethError::InitError); + return Err(NodeError::InitError); } // clean up the temp dir which is now persisted std::fs::remove_dir_all(temp_genesis_dir_path).map_err(|_| { - GethError::GenesisError("could not remove genesis temp dir".to_string()) + NodeError::GenesisError("could not remove genesis temp dir".to_string()) })?; } @@ -551,20 +530,20 @@ impl Geth { // create the directory if it doesn't exist if !data_dir.exists() { - create_dir(data_dir).map_err(GethError::CreateDirError)?; + create_dir(data_dir).map_err(NodeError::CreateDirError)?; } } // Dev mode with custom block time let mut p2p_port = match self.mode { - GethMode::Dev(DevOptions { block_time }) => { + NodeMode::Dev(DevOptions { block_time }) => { cmd.arg("--dev"); if let Some(block_time) = block_time { cmd.arg("--dev.period").arg(block_time.to_string()); } None } - GethMode::NonDev(PrivateNetOptions { p2p_port, discovery }) => { + NodeMode::NonDev(PrivateNetOptions { p2p_port, discovery }) => { // if no port provided, let the os chose it for us let port = p2p_port.unwrap_or(0); cmd.arg("--port").arg(port.to_string()); @@ -588,31 +567,34 @@ impl Geth { cmd.arg("--ipcpath").arg(ipc); } - let mut child = cmd.spawn().map_err(GethError::SpawnError)?; + cmd.args(self.args); + + let mut child = cmd.spawn().map_err(NodeError::SpawnError)?; - let stderr = child.stderr.ok_or(GethError::NoStderr)?; + let stderr = child.stderr.take().ok_or(NodeError::NoStderr)?; let start = Instant::now(); let mut reader = BufReader::new(stderr); // we shouldn't need to wait for p2p to start if geth is in dev mode - p2p is disabled in // dev mode - let mut p2p_started = matches!(self.mode, GethMode::Dev(_)); - let mut http_started = false; + let mut p2p_started = matches!(self.mode, NodeMode::Dev(_)); + let mut ports_started = false; loop { - if start + GETH_STARTUP_TIMEOUT <= Instant::now() { - return Err(GethError::Timeout); + if start + NODE_STARTUP_TIMEOUT <= Instant::now() { + let _ = child.kill(); + return Err(NodeError::Timeout); } let mut line = String::with_capacity(120); - reader.read_line(&mut line).map_err(GethError::ReadLineError)?; + reader.read_line(&mut line).map_err(NodeError::ReadLineError)?; - if matches!(self.mode, GethMode::NonDev(_)) && line.contains("Started P2P networking") { + if matches!(self.mode, NodeMode::NonDev(_)) && line.contains("Started P2P networking") { p2p_started = true; } - if !matches!(self.mode, GethMode::Dev(_)) { + if !matches!(self.mode, NodeMode::Dev(_)) { // try to find the p2p port, if not in dev mode if line.contains("New local node record") { if let Some(port) = extract_value("tcp=", &line) { @@ -627,21 +609,23 @@ impl Geth { || (line.contains("HTTP server started") && !line.contains("auth=true")) { // Extracts the address from the output - if let Some(addr) = extract_endpoint(&line) { + if let Some(addr) = extract_endpoint("endpoint=", &line) { // use the actual http port port = addr.port(); } - http_started = true; + ports_started = true; } // Encountered an error such as Fatal: Error starting protocol stack: listen tcp // 127.0.0.1:8545: bind: address already in use if line.contains("Fatal:") { - return Err(GethError::Fatal(line)); + let _ = child.kill(); + return Err(NodeError::Fatal(line)); } - if p2p_started && http_started { + // If all ports have started we are ready to be queried. + if ports_started && p2p_started { break; } } @@ -654,67 +638,30 @@ impl Geth { ipc: self.ipc_path, data_dir: self.data_dir, p2p_port, + auth_port: self.authrpc_port, genesis: self.genesis, clique_private_key: self.clique_private_key, }) } } -// extracts the value for the given key and line -fn extract_value<'a>(key: &str, line: &'a str) -> Option<&'a str> { - let mut key = Cow::from(key); - if !key.ends_with('=') { - key = format!("{}=", key).into(); - } - line.find(key.as_ref()).map(|pos| { - let start = pos + key.len(); - let end = line[start..].find(' ').map(|i| start + i).unwrap_or(line.len()); - line[start..end].trim() - }) -} - -// extracts the value for the given key and line -fn extract_endpoint(line: &str) -> Option { - let val = extract_value("endpoint=", line)?; - val.parse::().ok() -} - -// These tests should use a different datadir for each `Geth` spawned +// These tests should use a different datadir for each `geth` spawned. #[cfg(test)] mod tests { - use super::*; - use std::path::Path; + use crate::utils::run_with_tempdir_sync; - #[test] - fn test_extract_address() { - let line = "INFO [07-01|13:20:42.774] HTTP server started endpoint=127.0.0.1:8545 auth=false prefix= cors= vhosts=localhost"; - assert_eq!(extract_endpoint(line), Some(SocketAddr::from(([127, 0, 0, 1], 8545)))); - } + use super::*; #[test] fn port_0() { - run_with_tempdir(|_| { + run_with_tempdir_sync("geth-test-", |_| { let _geth = Geth::new().disable_discovery().port(0u16).spawn(); }); } - /// Allows running tests with a temporary directory, which is cleaned up after the function is - /// called. - /// - /// Helps with tests that spawn a helper instance, which has to be dropped before the temporary - /// directory is cleaned up. - #[track_caller] - fn run_with_tempdir(f: impl Fn(&Path)) { - let temp_dir = tempfile::tempdir().unwrap(); - let temp_dir_path = temp_dir.path(); - f(temp_dir_path); - #[cfg(not(windows))] - temp_dir.close().unwrap(); - } - #[test] fn p2p_port() { - run_with_tempdir(|temp_dir_path| { + run_with_tempdir_sync("geth-test-", |temp_dir_path| { let geth = Geth::new().disable_discovery().data_dir(temp_dir_path).spawn(); let p2p_port = geth.p2p_port(); assert!(p2p_port.is_some()); @@ -723,7 +670,7 @@ mod tests { #[test] fn explicit_p2p_port() { - run_with_tempdir(|temp_dir_path| { + run_with_tempdir_sync("geth-test-", |temp_dir_path| { // if a p2p port is explicitly set, it should be used let geth = Geth::new().p2p_port(1234).data_dir(temp_dir_path).spawn(); let p2p_port = geth.p2p_port(); @@ -733,7 +680,7 @@ mod tests { #[test] fn dev_mode() { - run_with_tempdir(|temp_dir_path| { + run_with_tempdir_sync("geth-test-", |temp_dir_path| { // dev mode should not have a p2p port, and dev should be the default let geth = Geth::new().data_dir(temp_dir_path).spawn(); let p2p_port = geth.p2p_port(); @@ -745,7 +692,7 @@ mod tests { #[ignore = "fails on geth >=1.14"] #[allow(deprecated)] fn clique_correctly_configured() { - run_with_tempdir(|temp_dir_path| { + run_with_tempdir_sync("geth-test-", |temp_dir_path| { let private_key = SigningKey::random(&mut rand::thread_rng()); let geth = Geth::new() .set_clique_private_key(private_key) diff --git a/crates/node-bindings/src/nodes/mod.rs b/crates/node-bindings/src/nodes/mod.rs new file mode 100644 index 00000000000..91d59734c3a --- /dev/null +++ b/crates/node-bindings/src/nodes/mod.rs @@ -0,0 +1,5 @@ +//! Bindings for various nodes. + +pub mod anvil; +pub mod geth; +pub mod reth; diff --git a/crates/node-bindings/src/nodes/reth.rs b/crates/node-bindings/src/nodes/reth.rs new file mode 100644 index 00000000000..80d0632cc3c --- /dev/null +++ b/crates/node-bindings/src/nodes/reth.rs @@ -0,0 +1,681 @@ +//! Utilities for launching a Reth dev-mode instance. + +use crate::{utils::extract_endpoint, NodeError, NODE_STARTUP_TIMEOUT}; +use alloy_genesis::Genesis; +use rand::Rng; +use std::{ + ffi::OsString, + fs::create_dir, + io::{BufRead, BufReader}, + path::PathBuf, + process::{Child, ChildStdout, Command, Stdio}, + time::Instant, +}; +use url::Url; + +/// The exposed APIs +const API: &str = "eth,net,web3,txpool,trace,rpc,reth,ots,admin,debug"; + +/// The reth command +const RETH: &str = "reth"; + +/// The default HTTP port for Reth. +const DEFAULT_HTTP_PORT: u16 = 8545; + +/// The default WS port for Reth. +const DEFAULT_WS_PORT: u16 = 8546; + +/// The default auth port for Reth. +const DEFAULT_AUTH_PORT: u16 = 8551; + +/// The default P2P port for Reth. +const DEFAULT_P2P_PORT: u16 = 30303; + +/// A Reth instance. Will close the instance when dropped. +/// +/// Construct this using [`Reth`]. +#[derive(Debug)] +pub struct RethInstance { + pid: Child, + instance: u16, + http_port: u16, + ws_port: u16, + auth_port: Option, + p2p_port: Option, + ipc: Option, + data_dir: Option, + genesis: Option, +} + +impl RethInstance { + /// Returns the instance number of this instance. + pub const fn instance(&self) -> u16 { + self.instance + } + + /// Returns the HTTP port of this instance. + pub const fn http_port(&self) -> u16 { + self.http_port + } + + /// Returns the WS port of this instance. + pub const fn ws_port(&self) -> u16 { + self.ws_port + } + + /// Returns the auth port of this instance. + pub const fn auth_port(&self) -> Option { + self.auth_port + } + + /// Returns the p2p port of this instance. + /// If discovery is disabled, this will be `None`. + pub const fn p2p_port(&self) -> Option { + self.p2p_port + } + + /// Returns the HTTP endpoint of this instance. + #[doc(alias = "http_endpoint")] + pub fn endpoint(&self) -> String { + format!("http://localhost:{}", self.http_port) + } + + /// Returns the Websocket endpoint of this instance. + pub fn ws_endpoint(&self) -> String { + format!("ws://localhost:{}", self.ws_port) + } + + /// Returns the IPC endpoint of this instance. + pub fn ipc_endpoint(&self) -> String { + self.ipc.clone().map_or_else(|| "reth.ipc".to_string(), |ipc| ipc.display().to_string()) + } + + /// Returns the HTTP endpoint url of this instance. + #[doc(alias = "http_endpoint_url")] + pub fn endpoint_url(&self) -> Url { + Url::parse(&self.endpoint()).unwrap() + } + + /// Returns the Websocket endpoint url of this instance. + pub fn ws_endpoint_url(&self) -> Url { + Url::parse(&self.ws_endpoint()).unwrap() + } + + /// Returns the path to this instances' data directory. + pub const fn data_dir(&self) -> &Option { + &self.data_dir + } + + /// Returns the genesis configuration used to configure this instance + pub const fn genesis(&self) -> &Option { + &self.genesis + } + + /// Takes the stdout contained in the child process. + /// + /// This leaves a `None` in its place, so calling methods that require a stdout to be present + /// will fail if called after this. + pub fn stdout(&mut self) -> Result { + self.pid.stdout.take().ok_or(NodeError::NoStdout) + } +} + +impl Drop for RethInstance { + fn drop(&mut self) { + self.pid.kill().expect("could not kill reth"); + } +} + +/// Builder for launching `reth`. +/// +/// # Panics +/// +/// If `spawn` is called without `reth` being available in the user's $PATH +/// +/// # Example +/// +/// ```no_run +/// use alloy_node_bindings::Reth; +/// +/// let port = 8545u16; +/// let url = format!("http://localhost:{}", port).to_string(); +/// +/// let reth = Reth::new().instance(1).block_time("12sec").spawn(); +/// +/// drop(reth); // this will kill the instance +/// ``` +#[derive(Clone, Debug, Default)] +#[must_use = "This Builder struct does nothing unless it is `spawn`ed"] +pub struct Reth { + dev: bool, + http_port: u16, + ws_port: u16, + auth_port: u16, + p2p_port: u16, + block_time: Option, + instance: u16, + discovery_enabled: bool, + program: Option, + ipc_path: Option, + ipc_enabled: bool, + data_dir: Option, + chain_or_path: Option, + genesis: Option, + args: Vec, +} + +impl Reth { + /// Creates an empty Reth builder. + /// + /// The instance number is set to a random number between 1 and 200 by default to reduce the + /// odds of port conflicts. This can be changed with [`Reth::instance`]. Set to 0 to use the + /// default ports. 200 is the maximum number of instances that can be run set by Reth. + pub fn new() -> Self { + Self { + dev: false, + http_port: DEFAULT_HTTP_PORT, + ws_port: DEFAULT_WS_PORT, + auth_port: DEFAULT_AUTH_PORT, + p2p_port: DEFAULT_P2P_PORT, + block_time: None, + instance: rand::thread_rng().gen_range(1..200), + discovery_enabled: true, + program: None, + ipc_path: None, + ipc_enabled: false, + data_dir: None, + chain_or_path: None, + genesis: None, + args: Vec::new(), + } + } + + /// Creates a Reth builder which will execute `reth` at the given path. + /// + /// # Example + /// + /// ``` + /// use alloy_node_bindings::Reth; + /// # fn a() { + /// let reth = Reth::at("../reth/target/release/reth").spawn(); + /// + /// println!("Reth running at `{}`", reth.endpoint()); + /// # } + /// ``` + pub fn at(path: impl Into) -> Self { + Self::new().path(path) + } + + /// Sets the `path` to the `reth` executable + /// + /// By default, it's expected that `reth` is in `$PATH`, see also + /// [`std::process::Command::new()`] + pub fn path>(mut self, path: T) -> Self { + self.program = Some(path.into()); + self + } + + /// Enable `dev` mode for the Reth instance. + pub const fn dev(mut self) -> Self { + self.dev = true; + self + } + + /// Sets the HTTP port for the Reth instance. + /// Note: this resets the instance number to 0 to allow for custom ports. + pub const fn http_port(mut self, http_port: u16) -> Self { + self.http_port = http_port; + self.instance = 0; + self + } + + /// Sets the WS port for the Reth instance. + /// Note: this resets the instance number to 0 to allow for custom ports. + pub const fn ws_port(mut self, ws_port: u16) -> Self { + self.ws_port = ws_port; + self.instance = 0; + self + } + + /// Sets the auth port for the Reth instance. + /// Note: this resets the instance number to 0 to allow for custom ports. + pub const fn auth_port(mut self, auth_port: u16) -> Self { + self.auth_port = auth_port; + self.instance = 0; + self + } + + /// Sets the p2p port for the Reth instance. + /// Note: this resets the instance number to 0 to allow for custom ports. + pub const fn p2p_port(mut self, p2p_port: u16) -> Self { + self.p2p_port = p2p_port; + self.instance = 0; + self + } + + /// Sets the block time for the Reth instance. + /// Parses strings using + /// This is only used if `dev` mode is enabled. + pub fn block_time(mut self, block_time: &str) -> Self { + self.block_time = Some(block_time.to_string()); + self + } + + /// Disables discovery for the Reth instance. + pub const fn disable_discovery(mut self) -> Self { + self.discovery_enabled = false; + self + } + + /// Sets the chain id for the Reth instance. + pub fn chain_or_path(mut self, chain_or_path: &str) -> Self { + self.chain_or_path = Some(chain_or_path.to_string()); + self + } + + /// Enable IPC for the Reth instance. + pub const fn enable_ipc(mut self) -> Self { + self.ipc_enabled = true; + self + } + + /// Sets the instance number for the Reth instance. Set to 0 to use the default ports. + /// By default, a random number between 1 and 200 is used. + pub const fn instance(mut self, instance: u16) -> Self { + self.instance = instance; + self + } + + /// Sets the IPC path for the socket. + pub fn ipc_path>(mut self, path: T) -> Self { + self.ipc_path = Some(path.into()); + self + } + + /// Sets the data directory for reth. + pub fn data_dir>(mut self, path: T) -> Self { + self.data_dir = Some(path.into()); + self + } + + /// Sets the `genesis.json` for the Reth instance. + /// + /// If this is set, reth will be initialized with `reth init` and the `--datadir` option will be + /// set to the same value as `data_dir`. + /// + /// This is destructive and will overwrite any existing data in the data directory. + pub fn genesis(mut self, genesis: Genesis) -> Self { + self.genesis = Some(genesis); + self + } + + /// Adds an argument to pass to `reth`. + /// + /// Pass any arg that is not supported by the builder. + pub fn arg>(mut self, arg: T) -> Self { + self.args.push(arg.into()); + self + } + + /// Adds multiple arguments to pass to `reth`. + /// + /// Pass any args that is not supported by the builder. + pub fn args(mut self, args: I) -> Self + where + I: IntoIterator, + S: Into, + { + for arg in args { + self = self.arg(arg); + } + self + } + + /// Consumes the builder and spawns `reth`. + /// + /// # Panics + /// + /// If spawning the instance fails at any point. + #[track_caller] + pub fn spawn(self) -> RethInstance { + self.try_spawn().unwrap() + } + + /// Consumes the builder and spawns `reth`. If spawning fails, returns an error. + pub fn try_spawn(self) -> Result { + let bin_path = self + .program + .as_ref() + .map_or_else(|| RETH.as_ref(), |bin| bin.as_os_str()) + .to_os_string(); + let mut cmd = Command::new(&bin_path); + // `reth` uses stdout for its logs + cmd.stdout(Stdio::piped()); + + // Use Reth's `node` subcommand. + cmd.arg("node"); + + // Set the ports if they are not the default. + if self.http_port != DEFAULT_HTTP_PORT { + cmd.arg("--http.port").arg(self.http_port.to_string()); + } + + if self.ws_port != DEFAULT_WS_PORT { + cmd.arg("--ws.port").arg(self.ws_port.to_string()); + } + + if self.auth_port != DEFAULT_AUTH_PORT { + cmd.arg("--authrpc.port").arg(self.auth_port.to_string()); + } + + if self.p2p_port != DEFAULT_P2P_PORT { + cmd.arg("--discovery.port").arg(self.p2p_port.to_string()); + } + + // If the `dev` flag is set, enable it. + if self.dev { + // Enable the dev mode. + // This mode uses a local proof-of-authority consensus engine with either fixed block + // times or automatically mined blocks. + // Disables network discovery and enables local http server. + // Prefunds 20 accounts derived by mnemonic "test test test test test test test test + // test test test junk" with 10 000 ETH each. + cmd.arg("--dev"); + + // If the block time is set, use it. + if let Some(block_time) = self.block_time { + cmd.arg("--dev.block-time").arg(block_time); + } + } + + // If IPC is not enabled on the builder, disable it. + if !self.ipc_enabled { + cmd.arg("--ipcdisable"); + } + + // Open the HTTP API. + cmd.arg("--http"); + cmd.arg("--http.api").arg(API); + + // Open the WS API. + cmd.arg("--ws"); + cmd.arg("--ws.api").arg(API); + + // Configure the IPC path if it is set. + if let Some(ipc) = &self.ipc_path { + cmd.arg("--ipcpath").arg(ipc); + } + + // If the instance is set, use it. + // Set the `instance` to 0 to use the default ports. + // By defining a custom `http_port`, `ws_port`, `auth_port`, or `p2p_port`, the instance + // number will be set to 0 automatically. + if self.instance > 0 { + cmd.arg("--instance").arg(self.instance.to_string()); + } + + if let Some(data_dir) = &self.data_dir { + cmd.arg("--datadir").arg(data_dir); + + // create the directory if it doesn't exist + if !data_dir.exists() { + create_dir(data_dir).map_err(NodeError::CreateDirError)?; + } + } + + if !self.discovery_enabled { + cmd.arg("--disable-discovery"); + cmd.arg("--no-persist-peers"); + } else { + // Verbosity is required to read the P2P port from the logs. + cmd.arg("--verbosity").arg("-vvv"); + } + + if let Some(chain_or_path) = self.chain_or_path { + cmd.arg("--chain").arg(chain_or_path); + } + + // Disable color output to make parsing logs easier. + cmd.arg("--color").arg("never"); + + // Add any additional arguments. + cmd.args(self.args); + + let mut child = cmd.spawn().map_err(NodeError::SpawnError)?; + + let stdout = child.stdout.take().ok_or(NodeError::NoStdout)?; + + let start = Instant::now(); + let mut reader = BufReader::new(stdout); + + let mut http_port = 0; + let mut ws_port = 0; + let mut auth_port = 0; + let mut p2p_port = 0; + + let mut ports_started = false; + let mut p2p_started = !self.discovery_enabled; + + loop { + if start + NODE_STARTUP_TIMEOUT <= Instant::now() { + let _ = child.kill(); + return Err(NodeError::Timeout); + } + + let mut line = String::with_capacity(120); + reader.read_line(&mut line).map_err(NodeError::ReadLineError)?; + + if line.contains("RPC HTTP server started") { + if let Some(addr) = extract_endpoint("url=", &line) { + http_port = addr.port(); + } + } + + if line.contains("RPC WS server started") { + if let Some(addr) = extract_endpoint("url=", &line) { + ws_port = addr.port(); + } + } + + if line.contains("RPC auth server started") { + if let Some(addr) = extract_endpoint("url=", &line) { + auth_port = addr.port(); + } + } + + // Encountered a critical error, exit early. + if line.contains("ERROR") { + let _ = child.kill(); + return Err(NodeError::Fatal(line)); + } + + if http_port != 0 && ws_port != 0 && auth_port != 0 { + ports_started = true; + } + + if self.discovery_enabled { + if line.contains("Updated local ENR") { + if let Some(port) = extract_endpoint("IpV4 UDP Socket", &line) { + p2p_port = port.port(); + p2p_started = true; + } + } + } else { + p2p_started = true; + } + + // If all ports have started we are ready to be queried. + if ports_started && p2p_started { + break; + } + } + + child.stdout = Some(reader.into_inner()); + + Ok(RethInstance { + pid: child, + instance: self.instance, + http_port, + ws_port, + p2p_port: if p2p_port != 0 { Some(p2p_port) } else { None }, + ipc: self.ipc_path, + data_dir: self.data_dir, + auth_port: Some(auth_port), + genesis: self.genesis, + }) + } +} + +// These tests should use a different datadir for each `reth` instance spawned. +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::run_with_tempdir_sync; + + #[test] + #[cfg(not(windows))] + fn can_launch_reth() { + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new().data_dir(temp_dir_path).spawn(); + + assert_ports(&reth, false); + }); + } + + #[test] + #[cfg(not(windows))] + fn can_launch_reth_sepolia() { + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new().chain_or_path("sepolia").data_dir(temp_dir_path).spawn(); + + assert_ports(&reth, false); + }); + } + + #[test] + #[cfg(not(windows))] + fn can_launch_reth_dev() { + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir_path).spawn(); + + assert_ports(&reth, true); + }); + } + + #[test] + #[cfg(not(windows))] + fn can_launch_reth_dev_custom_genesis() { + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new() + .dev() + .disable_discovery() + .data_dir(temp_dir_path) + .genesis(Genesis::default()) + .spawn(); + + assert_ports(&reth, true); + }); + } + + #[test] + #[cfg(not(windows))] + fn can_launch_reth_dev_custom_blocktime() { + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new() + .dev() + .disable_discovery() + .block_time("1sec") + .data_dir(temp_dir_path) + .spawn(); + + assert_ports(&reth, true); + }); + } + + #[test] + #[cfg(not(windows))] + fn can_launch_reth_p2p_instances() { + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new().instance(100).data_dir(temp_dir_path).spawn(); + + assert_ports(&reth, false); + + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new().instance(101).data_dir(temp_dir_path).spawn(); + + assert_ports(&reth, false); + }); + }); + } + + // Tests that occupy the same port are combined so they are ran sequentially, to prevent + // flakiness. + #[test] + #[cfg(not(windows))] + fn can_launch_reth_custom_ports() { + // Assert that all ports are default if no custom ports are set + // and the instance is set to 0. + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new().instance(0).data_dir(temp_dir_path).spawn(); + + assert_eq!(reth.http_port(), DEFAULT_HTTP_PORT); + assert_eq!(reth.ws_port(), DEFAULT_WS_PORT); + assert_eq!(reth.auth_port(), Some(DEFAULT_AUTH_PORT)); + assert_eq!(reth.p2p_port(), Some(DEFAULT_P2P_PORT)); + }); + + // Assert that only the HTTP port is set and the rest are default. + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new().http_port(8577).data_dir(temp_dir_path).spawn(); + + assert_eq!(reth.http_port(), 8577); + assert_eq!(reth.ws_port(), DEFAULT_WS_PORT); + assert_eq!(reth.auth_port(), Some(DEFAULT_AUTH_PORT)); + assert_eq!(reth.p2p_port(), Some(DEFAULT_P2P_PORT)); + }); + + // Assert that all ports can be set. + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new() + .http_port(8577) + .ws_port(8578) + .auth_port(8579) + .p2p_port(30307) + .data_dir(temp_dir_path) + .spawn(); + + assert_eq!(reth.http_port(), 8577); + assert_eq!(reth.ws_port(), 8578); + assert_eq!(reth.auth_port(), Some(8579)); + assert_eq!(reth.p2p_port(), Some(30307)); + }); + + // Assert that the HTTP port is picked by the OS and the rest are default. + run_with_tempdir_sync("reth-test-", |temp_dir_path| { + let reth = Reth::new().http_port(0).data_dir(temp_dir_path).spawn(); + + // Assert that a random unused port is used picked by the OS. + assert_ne!(reth.http_port(), DEFAULT_HTTP_PORT); + + assert_eq!(reth.ws_port(), DEFAULT_WS_PORT); + assert_eq!(reth.auth_port(), Some(DEFAULT_AUTH_PORT)); + assert_eq!(reth.p2p_port(), Some(DEFAULT_P2P_PORT)); + }); + } + + // Asserts that the ports are set correctly for the given Reth instance. + fn assert_ports(reth: &RethInstance, dev: bool) { + // Changes to the following port numbers for each instance: + // - `HTTP_RPC_PORT`: default - `instance` + 1 + // - `WS_RPC_PORT`: default + `instance` * 2 - 2 + // - `AUTH_PORT`: default + `instance` * 100 - 100 + // - `DISCOVERY_PORT`: default + `instance` - 1 + assert_eq!(reth.http_port(), DEFAULT_HTTP_PORT - reth.instance + 1); + assert_eq!(reth.ws_port(), DEFAULT_WS_PORT + reth.instance * 2 - 2); + assert_eq!(reth.auth_port(), Some(DEFAULT_AUTH_PORT + reth.instance * 100 - 100)); + assert_eq!( + reth.p2p_port(), + if dev { None } else { Some(DEFAULT_P2P_PORT + reth.instance - 1) } + ); + } +} diff --git a/crates/node-bindings/src/utils.rs b/crates/node-bindings/src/utils.rs new file mode 100644 index 00000000000..e3ee8a4511e --- /dev/null +++ b/crates/node-bindings/src/utils.rs @@ -0,0 +1,112 @@ +//! Utility functions for the node bindings. + +use std::{ + borrow::Cow, + future::Future, + net::{SocketAddr, TcpListener}, + path::PathBuf, +}; +use tempfile::TempDir; + +/// A bit of hack to find an unused TCP port. +/// +/// Does not guarantee that the given port is unused after the function exists, just that it was +/// unused before the function started (i.e., it does not reserve a port). +pub(crate) fn unused_port() -> u16 { + let listener = TcpListener::bind("127.0.0.1:0") + .expect("Failed to create TCP listener to find unused port"); + + let local_addr = + listener.local_addr().expect("Failed to read TCP listener local_addr to find unused port"); + local_addr.port() +} + +/// Extracts the value for the given key from the line of text. +/// It supports keys that end with '=' or ': '. +pub(crate) fn extract_value<'a>(key: &str, line: &'a str) -> Option<&'a str> { + let mut key_equal = Cow::from(key); + let mut key_colon = Cow::from(key); + + // Prepare both key variants + if !key_equal.ends_with('=') { + key_equal = format!("{}=", key).into(); + } + if !key_colon.ends_with(": ") { + key_colon = format!("{}: ", key).into(); + } + + // Try to find the key with '=' + if let Some(pos) = line.find(key_equal.as_ref()) { + let start = pos + key_equal.len(); + let end = line[start..].find(' ').map(|i| start + i).unwrap_or(line.len()); + if start <= line.len() && end <= line.len() { + return Some(line[start..end].trim()); + } + } + + // If not found, try to find the key with ': ' + if let Some(pos) = line.find(key_colon.as_ref()) { + let start = pos + key_colon.len(); + let end = line[start..].find(',').unwrap_or(line.len()); // Assuming comma or end of line + if start <= line.len() && start + end <= line.len() { + return Some(line[start..start + end].trim()); + } + } + + // If neither variant matches, return None + None +} + +/// Extracts the endpoint from the given line. +pub(crate) fn extract_endpoint(key: &str, line: &str) -> Option { + let val = extract_value(key, line)?; + + // Remove the "Some( ... )" wrapper if it exists + let val = + if val.starts_with("Some(") && val.ends_with(')') { &val[5..val.len() - 1] } else { val }; + + val.parse::().ok() +} + +/// Runs the given closure with a temporary directory. +pub fn run_with_tempdir_sync(prefix: &str, f: impl FnOnce(PathBuf)) { + let temp_dir = TempDir::with_prefix(prefix).unwrap(); + let temp_dir_path = temp_dir.path().to_path_buf(); + f(temp_dir_path); + #[cfg(not(windows))] + temp_dir.close().unwrap(); +} + +/// Runs the given async closure with a temporary directory. +pub async fn run_with_tempdir(prefix: &str, f: F) +where + F: FnOnce(PathBuf) -> Fut, + Fut: Future, +{ + let temp_dir = TempDir::with_prefix(prefix).unwrap(); + let temp_dir_path = temp_dir.path().to_path_buf(); + f(temp_dir_path).await; + #[cfg(not(windows))] + temp_dir.close().unwrap(); +} + +#[test] +fn test_extract_http_address() { + let line = "INFO [07-01|13:20:42.774] HTTP server started endpoint=127.0.0.1:8545 auth=false prefix= cors= vhosts=localhost"; + assert_eq!(extract_endpoint("endpoint=", line), Some(SocketAddr::from(([127, 0, 0, 1], 8545)))); +} + +#[test] +fn test_extract_udp_address() { + let line = "Updated local ENR enr=Enr { id: Some(\"v4\"), seq: 2, NodeId: 0x04dad428038b4db230fc5298646e137564fc6861662f32bdbf220f31299bdde7, signature: \"416520d69bfd701d95f4b77778970a5c18fa86e4dd4dc0746e80779d986c68605f491c01ef39cd3739fdefc1e3558995ad2f5d325f9e1db795896799e8ee94a3\", IpV4 UDP Socket: Some(0.0.0.0:30303), IpV6 UDP Socket: None, IpV4 TCP Socket: Some(0.0.0.0:30303), IpV6 TCP Socket: None, Other Pairs: [(\"eth\", \"c984fc64ec0483118c30\"), (\"secp256k1\", \"a103aa181e8fd5df651716430f1d4b504b54d353b880256f56aa727beadd1b7a9766\")], .. }"; + assert_eq!( + extract_endpoint("IpV4 TCP Socket: ", line), + Some(SocketAddr::from(([0, 0, 0, 0], 30303))) + ); +} + +#[test] +fn test_unused_port() { + let port = unused_port(); + assert!(port > 0); +} diff --git a/crates/provider/CHANGELOG.md b/crates/provider/CHANGELOG.md index e04f483c42e..4c7e3625698 100644 --- a/crates/provider/CHANGELOG.md +++ b/crates/provider/CHANGELOG.md @@ -5,6 +5,62 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- ProviderCall ([#788](https://github.com/alloy-rs/alloy/issues/788)) +- [transport-http] Layer client ([#1227](https://github.com/alloy-rs/alloy/issues/1227)) + +### Refactor + +- Separate transaction builders for tx types ([#1259](https://github.com/alloy-rs/alloy/issues/1259)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Bug Fixes + +- `debug_traceCallMany` and `trace_callMany` ([#1278](https://github.com/alloy-rs/alloy/issues/1278)) +- Serde for `eth_simulateV1` ([#1273](https://github.com/alloy-rs/alloy/issues/1273)) + +### Features + +- [engine] Optional Serde ([#1283](https://github.com/alloy-rs/alloy/issues/1283)) +- [alloy-rpc-types-eth] Optional serde ([#1276](https://github.com/alloy-rs/alloy/issues/1276)) +- Improve node bindings ([#1279](https://github.com/alloy-rs/alloy/issues/1279)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Features + +- [alloy-provider] Add abstraction for `NonceFiller` behavior ([#1108](https://github.com/alloy-rs/alloy/issues/1108)) + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -34,6 +90,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Clippy für docs ([#1194](https://github.com/alloy-rs/alloy/issues/1194)) - Release 0.2.1 - Correctly cfg unused type ([#1117](https://github.com/alloy-rs/alloy/issues/1117)) diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index 164c442100f..12444152f42 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -29,10 +29,12 @@ alloy-signer-local = { workspace = true, optional = true } alloy-rpc-client.workspace = true alloy-rpc-types-admin = { workspace = true, optional = true } alloy-rpc-types-anvil = { workspace = true, optional = true } -alloy-rpc-types-eth.workspace = true +alloy-rpc-types-eth = { workspace = true, features = ["serde"] } alloy-rpc-types-trace = { workspace = true, optional = true } alloy-rpc-types-txpool = { workspace = true, optional = true } -alloy-rpc-types-engine = { workspace = true, optional = true } +alloy-rpc-types-engine = { workspace = true, optional = true, features = [ + "serde", +] } alloy-rpc-types = { workspace = true, optional = true } alloy-transport-http = { workspace = true, optional = true } alloy-transport-ipc = { workspace = true, optional = true } @@ -73,6 +75,13 @@ reqwest.workspace = true tokio = { workspace = true, features = ["macros"] } tracing-subscriber = { workspace = true, features = ["fmt"] } tempfile.workspace = true +tower.workspace = true +tower-http = { workspace = true, features = [ + "set-header", + "sensitive-headers", +] } +http-body-util.workspace = true +http.workspace = true [features] default = ["reqwest", "reqwest-default-tls"] diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 802daae0c84..fedf12dd269 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -1,7 +1,7 @@ use crate::{ fillers::{ - ChainIdFiller, FillerControlFlow, GasFiller, JoinFill, NonceFiller, RecommendedFiller, - TxFiller, WalletFiller, + CachedNonceManager, ChainIdFiller, FillerControlFlow, GasFiller, JoinFill, NonceFiller, + NonceManager, RecommendedFillers, SimpleNonceManager, TxFiller, WalletFiller, }, provider::SendableTx, Provider, RootProvider, @@ -130,8 +130,13 @@ impl Default for ProviderBuilder { impl ProviderBuilder { /// Add preconfigured set of layers handling gas estimation, nonce /// management, and chain-id fetching. - pub fn with_recommended_fillers(self) -> ProviderBuilder { - self.filler(GasFiller).filler(NonceFiller::default()).filler(ChainIdFiller::default()) + pub fn with_recommended_fillers( + self, + ) -> ProviderBuilder, N> + where + N: RecommendedFillers, + { + self.filler(N::recommended_fillers()) } /// Add gas estimation to the stack being built. @@ -144,8 +149,29 @@ impl ProviderBuilder { /// Add nonce management to the stack being built. /// /// See [`NonceFiller`] - pub fn with_nonce_management(self) -> ProviderBuilder, N> { - self.filler(NonceFiller::default()) + pub fn with_nonce_management( + self, + nonce_manager: M, + ) -> ProviderBuilder>, N> { + self.filler(NonceFiller::new(nonce_manager)) + } + + /// Add simple nonce management to the stack being built. + /// + /// See [`SimpleNonceManager`] + pub fn with_simple_nonce_management( + self, + ) -> ProviderBuilder, N> { + self.with_nonce_management(SimpleNonceManager::default()) + } + + /// Add cached nonce management to the stack being built. + /// + /// See [`CachedNonceManager`] + pub fn with_cached_nonce_management( + self, + ) -> ProviderBuilder>, N> { + self.with_nonce_management(CachedNonceManager::default()) } /// Add a chain ID filler to the stack being built. The filler will attempt @@ -328,17 +354,8 @@ impl ProviderBuilder { #[cfg(feature = "hyper")] pub fn on_hyper_http(self, url: url::Url) -> F::Provider where - L: ProviderLayer< - crate::HyperProvider, - alloy_transport_http::Http, - N, - >, - F: TxFiller - + ProviderLayer< - L::Provider, - alloy_transport_http::Http, - N, - >, + L: ProviderLayer, alloy_transport_http::HyperTransport, N>, + F: TxFiller + ProviderLayer, N: Network, { let client = ClientBuilder::default().hyper_http(url); @@ -350,7 +367,7 @@ impl ProviderBuilder { type JoinedEthereumWalletFiller = JoinFill>; #[cfg(any(test, feature = "anvil-node"))] -type AnvilProviderResult = Result; +type AnvilProviderResult = Result; // Enabled when the `anvil` feature is enabled, or when both in test and the // `reqwest` feature is enabled. @@ -469,9 +486,8 @@ impl ProviderBuilder { let url = anvil_layer.endpoint_url(); let default_keys = anvil_layer.instance().keys().to_vec(); - let (default_key, remaining_keys) = default_keys - .split_first() - .ok_or(alloy_node_bindings::anvil::AnvilError::NoKeysAvailable)?; + let (default_key, remaining_keys) = + default_keys.split_first().ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?; let default_signer = alloy_signer_local::LocalSigner::from(default_key.clone()); let mut wallet = alloy_network::EthereumWallet::from(default_signer); diff --git a/crates/provider/src/ext/admin.rs b/crates/provider/src/ext/admin.rs index c1872f8e9ec..87471c8064d 100644 --- a/crates/provider/src/ext/admin.rs +++ b/crates/provider/src/ext/admin.rs @@ -85,39 +85,43 @@ where #[cfg(test)] mod test { - use crate::ProviderBuilder; - use super::*; - use alloy_node_bindings::Geth; - use tempfile::TempDir; + use crate::ProviderBuilder; + use alloy_node_bindings::{utils::run_with_tempdir, Geth}; #[tokio::test] async fn node_info() { - let temp_dir = TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let node_info = provider.node_info().await.unwrap(); - assert!(node_info.enode.starts_with("enode://")); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + let node_info = provider.node_info().await.unwrap(); + assert!(node_info.enode.starts_with("enode://")); + }) + .await; } #[tokio::test] async fn admin_peers() { - let temp_dir = TempDir::with_prefix("geth-test-1").unwrap(); - let temp_dir_2 = TempDir::with_prefix("geth-test-2").unwrap(); - let geth1 = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let mut geth2 = - Geth::new().disable_discovery().port(0u16).data_dir(temp_dir_2.path()).spawn(); - - let provider1 = ProviderBuilder::new().on_http(geth1.endpoint_url()); - let provider2 = ProviderBuilder::new().on_http(geth2.endpoint_url()); - let node1_info = provider1.node_info().await.unwrap(); - let node1_id = node1_info.id; - let node1_enode = node1_info.enode; - - let added = provider2.add_peer(&node1_enode).await.unwrap(); - assert!(added); - geth2.wait_to_add_peer(&node1_id).unwrap(); - let peers = provider2.peers().await.unwrap(); - assert_eq!(peers[0].enode, node1_enode); + run_with_tempdir("geth-test-1", |temp_dir_1| async move { + run_with_tempdir("geth-test-2", |temp_dir_2| async move { + let geth1 = Geth::new().disable_discovery().data_dir(&temp_dir_1).spawn(); + let mut geth2 = + Geth::new().disable_discovery().port(0u16).data_dir(&temp_dir_2).spawn(); + + let provider1 = ProviderBuilder::new().on_http(geth1.endpoint_url()); + let provider2 = ProviderBuilder::new().on_http(geth2.endpoint_url()); + let node1_info = provider1.node_info().await.unwrap(); + let node1_id = node1_info.id; + let node1_enode = node1_info.enode; + + let added = provider2.add_peer(&node1_enode).await.unwrap(); + assert!(added); + geth2.wait_to_add_peer(&node1_id).unwrap(); + let peers = provider2.peers().await.unwrap(); + assert_eq!(peers[0].enode, node1_enode); + }) + .await; + }) + .await; } } diff --git a/crates/provider/src/ext/debug.rs b/crates/provider/src/ext/debug.rs index 07005ae5a20..8ad57b7228a 100644 --- a/crates/provider/src/ext/debug.rs +++ b/crates/provider/src/ext/debug.rs @@ -2,7 +2,9 @@ use crate::Provider; use alloy_network::Network; use alloy_primitives::{hex, Bytes, TxHash, B256}; -use alloy_rpc_types_eth::{Block, BlockId, BlockNumberOrTag, TransactionRequest}; +use alloy_rpc_types_eth::{ + Block, BlockId, BlockNumberOrTag, Bundle, StateContext, TransactionRequest, +}; use alloy_rpc_types_trace::geth::{ BlockTraceResult, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult, }; @@ -122,8 +124,8 @@ pub trait DebugApi: Send + Sync { /// Not all nodes support this call. async fn debug_trace_call_many( &self, - txs: Vec, - block: BlockId, + bundles: Vec, + state_context: StateContext, trace_options: GethDebugTracingCallOptions, ) -> TransportResult>; } @@ -208,11 +210,11 @@ where async fn debug_trace_call_many( &self, - txs: Vec, - block: BlockId, + bundles: Vec, + state_context: StateContext, trace_options: GethDebugTracingCallOptions, ) -> TransportResult> { - self.client().request("debug_traceCallMany", (txs, block, trace_options)).await + self.client().request("debug_traceCallMany", (bundles, state_context, trace_options)).await } } @@ -222,7 +224,7 @@ mod test { use super::*; use alloy_network::TransactionBuilder; - use alloy_node_bindings::Geth; + use alloy_node_bindings::{utils::run_with_tempdir, Geth, Reth}; use alloy_primitives::{address, U256}; fn init_tracing() { @@ -283,50 +285,108 @@ mod test { #[tokio::test] async fn call_debug_get_raw_header() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - - let rlp_header = provider - .debug_get_raw_header(BlockId::Number(BlockNumberOrTag::Latest)) - .await - .expect("debug_getRawHeader call should succeed"); - - assert!(!rlp_header.is_empty()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + + let rlp_header = provider + .debug_get_raw_header(BlockId::Number(BlockNumberOrTag::Latest)) + .await + .expect("debug_getRawHeader call should succeed"); + + assert!(!rlp_header.is_empty()); + }) + .await } #[tokio::test] async fn call_debug_get_raw_block() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - - let rlp_block = provider - .debug_get_raw_block(BlockId::Number(BlockNumberOrTag::Latest)) - .await - .expect("debug_getRawBlock call should succeed"); - - assert!(!rlp_block.is_empty()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + + let rlp_block = provider + .debug_get_raw_block(BlockId::Number(BlockNumberOrTag::Latest)) + .await + .expect("debug_getRawBlock call should succeed"); + + assert!(!rlp_block.is_empty()); + }) + .await } #[tokio::test] async fn call_debug_get_raw_receipts() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - - let result = - provider.debug_get_raw_receipts(BlockId::Number(BlockNumberOrTag::Latest)).await; - assert!(result.is_ok()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + + let result = + provider.debug_get_raw_receipts(BlockId::Number(BlockNumberOrTag::Latest)).await; + assert!(result.is_ok()); + }) + .await } #[tokio::test] async fn call_debug_get_bad_blocks() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + + let result = provider.debug_get_bad_blocks().await; + assert!(result.is_ok()); + }) + .await + } - let result = provider.debug_get_bad_blocks().await; - assert!(result.is_ok()); + #[tokio::test] + #[cfg(not(windows))] + async fn debug_trace_call_many() { + run_with_tempdir("reth-test-", |temp_dir| async move { + let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn(); + let provider = + ProviderBuilder::new().with_recommended_fillers().on_http(reth.endpoint_url()); + + let tx1 = TransactionRequest::default() + .with_from(address!("0000000000000000000000000000000000000123")) + .with_to(address!("0000000000000000000000000000000000000456")); + + let tx2 = TransactionRequest::default() + .with_from(address!("0000000000000000000000000000000000000456")) + .with_to(address!("0000000000000000000000000000000000000789")); + + let bundles = vec![Bundle { transactions: vec![tx1, tx2], block_override: None }]; + let state_context = StateContext::default(); + let trace_options = GethDebugTracingCallOptions::default(); + let result = + provider.debug_trace_call_many(bundles, state_context, trace_options).await; + assert!(result.is_ok()); + + let traces = result.unwrap(); + assert_eq!( + serde_json::to_string_pretty(&traces).unwrap().trim(), + r#" +[ + [ + { + "failed": false, + "gas": 21000, + "returnValue": "", + "structLogs": [] + }, + { + "failed": false, + "gas": 21000, + "returnValue": "", + "structLogs": [] + } + ] +] +"# + .trim(), + ); + }) + .await } } diff --git a/crates/provider/src/ext/net.rs b/crates/provider/src/ext/net.rs index f1d962629ae..633d58fd59e 100644 --- a/crates/provider/src/ext/net.rs +++ b/crates/provider/src/ext/net.rs @@ -41,35 +41,42 @@ mod test { use crate::ProviderBuilder; use super::*; - use alloy_node_bindings::Geth; + use alloy_node_bindings::{utils::run_with_tempdir, Geth}; #[tokio::test] async fn call_net_version() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let version = provider.net_version().await.expect("net_version call should succeed"); - assert_eq!(version, 1); + let version = provider.net_version().await.expect("net_version call should succeed"); + assert_eq!(version, 1); + }) + .await; } #[tokio::test] async fn call_net_peer_count() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let count = provider.net_peer_count().await.expect("net_peerCount call should succeed"); - assert_eq!(count, 0); + let count = provider.net_peer_count().await.expect("net_peerCount call should succeed"); + assert_eq!(count, 0); + }) + .await; } #[tokio::test] async fn call_net_listening() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let listening = provider.net_listening().await.expect("net_listening call should succeed"); - assert!(listening); + let listening = + provider.net_listening().await.expect("net_listening call should succeed"); + assert!(listening); + }) + .await; } } diff --git a/crates/provider/src/ext/trace.rs b/crates/provider/src/ext/trace.rs index b6a81dc2077..58944e8963d 100644 --- a/crates/provider/src/ext/trace.rs +++ b/crates/provider/src/ext/trace.rs @@ -11,7 +11,7 @@ use alloy_rpc_types_trace::{ use alloy_transport::{Transport, TransportResult}; /// List of trace calls for use with [`TraceApi::trace_call_many`] -pub type TraceCallList<'a, N> = &'a [(::TransactionRequest, Vec)]; +pub type TraceCallList<'a, N> = &'a [(::TransactionRequest, &'a [TraceType])]; /// Trace namespace rpc interface that gives access to several non-standard RPC methods. #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] @@ -43,7 +43,7 @@ where fn trace_call_many<'a>( &self, request: TraceCallList<'a, N>, - ) -> RpcWithBlock, TraceResults>; + ) -> RpcWithBlock,), Vec>; /// Parity trace transaction. async fn trace_transaction( @@ -112,14 +112,14 @@ where trace_types: &'b [TraceType], ) -> RpcWithBlock::TransactionRequest, &'b [TraceType]), TraceResults> { - RpcWithBlock::new(self.weak_client(), "trace_call", (request, trace_types)) + self.client().request("trace_call", (request, trace_types)).into() } fn trace_call_many<'a>( &self, request: TraceCallList<'a, N>, - ) -> RpcWithBlock, TraceResults> { - RpcWithBlock::new(self.weak_client(), "trace_callMany", request) + ) -> RpcWithBlock,), Vec> { + self.client().request("trace_callMany", (request,)).into() } async fn trace_transaction( @@ -178,6 +178,10 @@ where mod test { use crate::ProviderBuilder; use alloy_eips::BlockNumberOrTag; + use alloy_network::TransactionBuilder; + use alloy_node_bindings::{utils::run_with_tempdir, Reth}; + use alloy_primitives::address; + use alloy_rpc_types_eth::TransactionRequest; use super::*; @@ -186,10 +190,140 @@ mod test { } #[tokio::test] - async fn test_trace_block() { + async fn trace_block() { init_tracing(); let provider = ProviderBuilder::new().on_anvil(); let traces = provider.trace_block(BlockId::Number(BlockNumberOrTag::Latest)).await.unwrap(); assert_eq!(traces.len(), 0); } + + #[tokio::test] + #[cfg(not(windows))] + async fn trace_call() { + run_with_tempdir("reth-test-", |temp_dir| async move { + let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(reth.endpoint_url()); + + let tx = TransactionRequest::default() + .with_from(address!("0000000000000000000000000000000000000123")) + .with_to(address!("0000000000000000000000000000000000000456")); + + let result = provider.trace_call(&tx, &[TraceType::Trace]).await; + assert!(result.is_ok()); + + let traces = result.unwrap(); + assert_eq!( + serde_json::to_string_pretty(&traces).unwrap().trim(), + r#" +{ + "output": "0x", + "stateDiff": null, + "trace": [ + { + "type": "call", + "action": { + "from": "0x0000000000000000000000000000000000000123", + "callType": "call", + "gas": "0x2fa9e78", + "input": "0x", + "to": "0x0000000000000000000000000000000000000456", + "value": "0x0" + }, + "result": { + "gasUsed": "0x0", + "output": "0x" + }, + "subtraces": 0, + "traceAddress": [] + } + ], + "vmTrace": null +} +"# + .trim(), + ); + }) + .await; + } + + #[tokio::test] + #[cfg(not(windows))] + async fn trace_call_many() { + run_with_tempdir("reth-test-", |temp_dir| async move { + let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(reth.endpoint_url()); + + let tx1 = TransactionRequest::default() + .with_from(address!("0000000000000000000000000000000000000123")) + .with_to(address!("0000000000000000000000000000000000000456")); + + let tx2 = TransactionRequest::default() + .with_from(address!("0000000000000000000000000000000000000456")) + .with_to(address!("0000000000000000000000000000000000000789")); + + let result = provider + .trace_call_many(&[(tx1, &[TraceType::Trace]), (tx2, &[TraceType::Trace])]) + .await; + assert!(result.is_ok()); + + let traces = result.unwrap(); + assert_eq!( + serde_json::to_string_pretty(&traces).unwrap().trim(), + r#" +[ + { + "output": "0x", + "stateDiff": null, + "trace": [ + { + "type": "call", + "action": { + "from": "0x0000000000000000000000000000000000000123", + "callType": "call", + "gas": "0x2fa9e78", + "input": "0x", + "to": "0x0000000000000000000000000000000000000456", + "value": "0x0" + }, + "result": { + "gasUsed": "0x0", + "output": "0x" + }, + "subtraces": 0, + "traceAddress": [] + } + ], + "vmTrace": null + }, + { + "output": "0x", + "stateDiff": null, + "trace": [ + { + "type": "call", + "action": { + "from": "0x0000000000000000000000000000000000000456", + "callType": "call", + "gas": "0x2fa9e78", + "input": "0x", + "to": "0x0000000000000000000000000000000000000789", + "value": "0x0" + }, + "result": { + "gasUsed": "0x0", + "output": "0x" + }, + "subtraces": 0, + "traceAddress": [] + } + ], + "vmTrace": null + } +] +"# + .trim() + ); + }) + .await; + } } diff --git a/crates/provider/src/ext/txpool.rs b/crates/provider/src/ext/txpool.rs index 34e497c2190..5326ae5922c 100644 --- a/crates/provider/src/ext/txpool.rs +++ b/crates/provider/src/ext/txpool.rs @@ -78,41 +78,49 @@ mod tests { use crate::ProviderBuilder; use super::*; - use alloy_node_bindings::Geth; + use alloy_node_bindings::{utils::run_with_tempdir, Geth}; #[tokio::test] async fn test_txpool_content() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let content = provider.txpool_content().await.unwrap(); - assert_eq!(content, TxpoolContent::default()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + let content = provider.txpool_content().await.unwrap(); + assert_eq!(content, TxpoolContent::default()); + }) + .await; } #[tokio::test] async fn test_txpool_content_from() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let content = provider.txpool_content_from(Address::default()).await.unwrap(); - assert_eq!(content, TxpoolContentFrom::default()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + let content = provider.txpool_content_from(Address::default()).await.unwrap(); + assert_eq!(content, TxpoolContentFrom::default()); + }) + .await; } #[tokio::test] async fn test_txpool_inspect() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let content = provider.txpool_inspect().await.unwrap(); - assert_eq!(content, TxpoolInspect::default()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + let content = provider.txpool_inspect().await.unwrap(); + assert_eq!(content, TxpoolInspect::default()); + }) + .await; } #[tokio::test] async fn test_txpool_status() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new().disable_discovery().data_dir(temp_dir.path()).spawn(); - let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); - let content = provider.txpool_status().await.unwrap(); - assert_eq!(content, TxpoolStatus::default()); + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new().disable_discovery().data_dir(temp_dir).spawn(); + let provider = ProviderBuilder::new().on_http(geth.endpoint_url()); + let content = provider.txpool_status().await.unwrap(); + assert_eq!(content, TxpoolStatus::default()); + }) + .await; } } diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index 422c750ab0a..6c484329762 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -7,7 +7,7 @@ use crate::{ Provider, }; use alloy_json_rpc::RpcError; -use alloy_network::{Network, TransactionBuilder}; +use alloy_network::{Network, TransactionBuilder, TransactionBuilder4844}; use alloy_network_primitives::{BlockResponse, HeaderResponse}; use alloy_rpc_types_eth::BlockNumberOrTag; use alloy_transport::{Transport, TransportResult}; @@ -19,7 +19,6 @@ use futures::FutureExt; pub enum GasFillable { Legacy { gas_limit: u128, gas_price: u128 }, Eip1559 { gas_limit: u128, estimate: Eip1559Estimation }, - Eip4844 { gas_limit: u128, estimate: Eip1559Estimation, max_fee_per_blob_gas: u128 }, } /// A [`TxFiller`] that populates gas related fields in transaction requests if @@ -65,6 +64,10 @@ pub enum GasFillable { #[derive(Clone, Copy, Debug, Default)] pub struct GasFiller; +/// Filler for the `max_fee_per_blob_gas` field in EIP-4844 transactions. +#[derive(Clone, Copy, Debug, Default)] +pub struct BlobGasFiller; + impl GasFiller { async fn prepare_legacy( &self, @@ -119,52 +122,6 @@ impl GasFiller { Ok(GasFillable::Eip1559 { gas_limit, estimate }) } - - async fn prepare_4844( - &self, - provider: &P, - tx: &N::TransactionRequest, - ) -> TransportResult - where - P: Provider, - T: Transport + Clone, - N: Network, - { - let gas_limit_fut = tx.gas_limit().map_or_else( - || provider.estimate_gas(tx).into_future().right_future(), - |gas_limit| async move { Ok(gas_limit) }.left_future(), - ); - - let eip1559_fees_fut = if let (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) = - (tx.max_fee_per_gas(), tx.max_priority_fee_per_gas()) - { - async move { Ok(Eip1559Estimation { max_fee_per_gas, max_priority_fee_per_gas }) } - .left_future() - } else { - provider.estimate_eip1559_fees(None).right_future() - }; - - let max_fee_per_blob_gas_fut = tx.max_fee_per_blob_gas().map_or_else( - || { - async { - provider - .get_block_by_number(BlockNumberOrTag::Latest, false) - .await? - .ok_or(RpcError::NullResp)? - .header() - .next_block_blob_fee() - .ok_or(RpcError::UnsupportedFeature("eip4844")) - } - .right_future() - }, - |max_fee_per_blob_gas| async move { Ok(max_fee_per_blob_gas) }.left_future(), - ); - - let (gas_limit, estimate, max_fee_per_blob_gas) = - futures::try_join!(gas_limit_fut, eip1559_fees_fut, max_fee_per_blob_gas_fut)?; - - Ok(GasFillable::Eip4844 { gas_limit, estimate, max_fee_per_blob_gas }) - } } impl TxFiller for GasFiller { @@ -176,18 +133,8 @@ impl TxFiller for GasFiller { return FillerControlFlow::Finished; } - // 4844 - if tx.max_fee_per_blob_gas().is_some() - && tx.max_fee_per_gas().is_some() - && tx.max_priority_fee_per_gas().is_some() - && tx.gas_limit().is_some() - { - return FillerControlFlow::Finished; - } - // eip1559 - if tx.blob_sidecar().is_none() - && tx.max_fee_per_gas().is_some() + if tx.max_fee_per_gas().is_some() && tx.max_priority_fee_per_gas().is_some() && tx.gas_limit().is_some() { @@ -208,10 +155,8 @@ impl TxFiller for GasFiller { P: Provider, T: Transport + Clone, { - if tx.gas_price().is_some() || tx.access_list().is_some() { + if tx.gas_price().is_some() { self.prepare_legacy(provider, tx).await - } else if tx.blob_sidecar().is_some() { - self.prepare_4844(provider, tx).await } else { match self.prepare_1559(provider, tx).await { // fallback to legacy @@ -238,18 +183,62 @@ impl TxFiller for GasFiller { builder.set_max_fee_per_gas(estimate.max_fee_per_gas); builder.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); } - GasFillable::Eip4844 { gas_limit, estimate, max_fee_per_blob_gas } => { - builder.set_gas_limit(gas_limit); - builder.set_max_fee_per_gas(estimate.max_fee_per_gas); - builder.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); - builder.set_max_fee_per_blob_gas(max_fee_per_blob_gas); - } } }; Ok(tx) } } +impl TxFiller for BlobGasFiller +where + N::TransactionRequest: TransactionBuilder4844, +{ + type Fillable = u128; + + fn status(&self, tx: &::TransactionRequest) -> FillerControlFlow { + // nothing to fill if non-eip4844 tx or max_fee_per_blob_gas is already set + if tx.blob_sidecar().is_none() || tx.max_fee_per_blob_gas().is_some() { + return FillerControlFlow::Finished; + } + + FillerControlFlow::Ready + } + + fn fill_sync(&self, _tx: &mut SendableTx) {} + + async fn prepare( + &self, + provider: &P, + tx: &::TransactionRequest, + ) -> TransportResult + where + P: Provider, + T: Transport + Clone, + { + if let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() { + return Ok(max_fee_per_blob_gas); + } + provider + .get_block_by_number(BlockNumberOrTag::Latest, false) + .await? + .ok_or(RpcError::NullResp)? + .header() + .next_block_blob_fee() + .ok_or(RpcError::UnsupportedFeature("eip4844")) + } + + async fn fill( + &self, + fillable: Self::Fillable, + mut tx: SendableTx, + ) -> TransportResult> { + if let Some(builder) = tx.as_mut_builder() { + builder.set_max_fee_per_blob_gas(fillable); + } + Ok(tx) + } +} + #[cfg(feature = "reqwest")] #[cfg(test)] mod tests { @@ -300,28 +289,4 @@ mod tests { assert_eq!(receipt.gas_used, 0x5208); } - - #[tokio::test] - async fn non_eip1559_network() { - let provider = ProviderBuilder::new() - .filler(crate::fillers::GasFiller) - .filler(crate::fillers::NonceFiller::default()) - .filler(crate::fillers::ChainIdFiller::default()) - .on_anvil(); - - let tx = TransactionRequest { - from: Some(address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266")), - value: Some(U256::from(100)), - to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()), - // access list forces legacy gassing - access_list: Some(vec![Default::default()].into()), - ..Default::default() - }; - - let tx = provider.send_transaction(tx).await.unwrap(); - - let receipt = tx.get_receipt().await.unwrap(); - - assert_eq!(receipt.effective_gas_price, 2000000000); - } } diff --git a/crates/provider/src/fillers/mod.rs b/crates/provider/src/fillers/mod.rs index e2321f45ce0..fed8afeaddd 100644 --- a/crates/provider/src/fillers/mod.rs +++ b/crates/provider/src/fillers/mod.rs @@ -13,10 +13,10 @@ mod wallet; pub use wallet::WalletFiller; mod nonce; -pub use nonce::NonceFiller; +pub use nonce::{CachedNonceManager, NonceFiller, NonceManager, SimpleNonceManager}; mod gas; -pub use gas::{GasFillable, GasFiller}; +pub use gas::{BlobGasFiller, GasFillable, GasFiller}; mod join_fill; pub use join_fill::JoinFill; @@ -27,7 +27,7 @@ use crate::{ RootProvider, }; use alloy_json_rpc::RpcError; -use alloy_network::{Ethereum, Network}; +use alloy_network::{AnyNetwork, Ethereum, Network}; use alloy_transport::{Transport, TransportResult}; use async_trait::async_trait; use futures_utils_wasm::impl_future; @@ -308,3 +308,42 @@ where self.inner.send_transaction_internal(tx).await } } + +/// A trait which may be used to configure default fillers for [Network] implementations. +pub trait RecommendedFillers { + /// Recommended fillers for this network. + type RecomendedFillters: TxFiller; + + /// Returns the recommended filler for this provider. + fn recommended_fillers() -> Self::RecomendedFillters; +} + +impl RecommendedFillers for Ethereum { + type RecomendedFillters = + JoinFill>>; + + fn recommended_fillers() -> Self::RecomendedFillters { + JoinFill::new( + GasFiller, + JoinFill::new( + BlobGasFiller, + JoinFill::new(NonceFiller::default(), ChainIdFiller::default()), + ), + ) + } +} + +impl RecommendedFillers for AnyNetwork { + type RecomendedFillters = + JoinFill>>; + + fn recommended_fillers() -> Self::RecomendedFillters { + JoinFill::new( + GasFiller, + JoinFill::new( + BlobGasFiller, + JoinFill::new(NonceFiller::default(), ChainIdFiller::default()), + ), + ) + } +} diff --git a/crates/provider/src/fillers/nonce.rs b/crates/provider/src/fillers/nonce.rs index ad9f0aa60f8..ad81f83ac13 100644 --- a/crates/provider/src/fillers/nonce.rs +++ b/crates/provider/src/fillers/nonce.rs @@ -6,15 +6,92 @@ use crate::{ use alloy_network::{Network, TransactionBuilder}; use alloy_primitives::Address; use alloy_transport::{Transport, TransportResult}; +use async_trait::async_trait; use dashmap::DashMap; use futures::lock::Mutex; use std::sync::Arc; -/// A [`TxFiller`] that fills nonces on transactions. +/// A trait that determines the behavior of filling nonces. +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +pub trait NonceManager: Clone + Send + Sync + std::fmt::Debug { + /// Get the next nonce for the given account. + async fn get_next_nonce(&self, provider: &P, address: Address) -> TransportResult + where + P: Provider, + N: Network, + T: Transport + Clone; +} + +/// This [`NonceManager`] implementation will fetch the transaction count for any new account it +/// sees. +/// +/// Unlike [`CachedNonceManager`], this implementation does not store the transaction count locally, +/// which results in more frequent calls to the provider, but it is more resilient to chain +/// reorganizations. +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct SimpleNonceManager; + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl NonceManager for SimpleNonceManager { + async fn get_next_nonce(&self, provider: &P, address: Address) -> TransportResult + where + P: Provider, + N: Network, + T: Transport + Clone, + { + provider.get_transaction_count(address).await + } +} + +/// Cached nonce manager /// -/// The filler will fetch the transaction count for any new account it sees, -/// store it locally and increment the locally stored nonce as transactions are -/// sent via [`Provider::send_transaction`]. +/// This [`NonceManager`] implementation will fetch the transaction count for any new account it +/// sees, store it locally and increment the locally stored nonce as transactions are sent via +/// [`Provider::send_transaction`]. +/// +/// There is also an alternative implementation [`SimpleNonceManager`] that does not store the +/// transaction count locally. +#[derive(Clone, Debug, Default)] +pub struct CachedNonceManager { + nonces: DashMap>>, +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl NonceManager for CachedNonceManager { + async fn get_next_nonce(&self, provider: &P, address: Address) -> TransportResult + where + P: Provider, + N: Network, + T: Transport + Clone, + { + // Use `u64::MAX` as a sentinel value to indicate that the nonce has not been fetched yet. + const NONE: u64 = u64::MAX; + + // Locks dashmap internally for a short duration to clone the `Arc`. + // We also don't want to hold the dashmap lock through the await point below. + let nonce = { + let rm = self.nonces.entry(address).or_insert_with(|| Arc::new(Mutex::new(NONE))); + Arc::clone(rm.value()) + }; + + let mut nonce = nonce.lock().await; + let new_nonce = if *nonce == NONE { + // Initialize the nonce if we haven't seen this account before. + provider.get_transaction_count(address).await? + } else { + *nonce + 1 + }; + *nonce = new_nonce; + Ok(new_nonce) + } +} + +/// A [`TxFiller`] that fills nonces on transactions. The behavior of filling nonces is determined +/// by the [`NonceManager`]. /// /// # Note /// @@ -31,7 +108,7 @@ use std::sync::Arc; /// # use alloy_provider::{ProviderBuilder, RootProvider, Provider}; /// # async fn test + Clone>(url: url::Url, wallet: W) -> Result<(), Box> { /// let provider = ProviderBuilder::new() -/// .with_nonce_management() +/// .with_simple_nonce_management() /// .wallet(wallet) /// .on_http(url); /// @@ -40,11 +117,18 @@ use std::sync::Arc; /// # } /// ``` #[derive(Clone, Debug, Default)] -pub struct NonceFiller { - nonces: DashMap>>, +pub struct NonceFiller { + nonce_manager: M, +} + +impl NonceFiller { + /// Creates a new [`NonceFiller`] with the specified [`NonceManager`]. + pub const fn new(nonce_manager: M) -> Self { + Self { nonce_manager } + } } -impl TxFiller for NonceFiller { +impl TxFiller for NonceFiller { type Fillable = u64; fn status(&self, tx: &::TransactionRequest) -> FillerControlFlow { @@ -69,7 +153,7 @@ impl TxFiller for NonceFiller { T: Transport + Clone, { let from = tx.from().expect("checked by 'ready()'"); - self.get_next_nonce(provider, from).await + self.nonce_manager.get_next_nonce(provider, from).await } async fn fill( @@ -84,36 +168,6 @@ impl TxFiller for NonceFiller { } } -impl NonceFiller { - /// Get the next nonce for the given account. - async fn get_next_nonce(&self, provider: &P, address: Address) -> TransportResult - where - P: Provider, - N: Network, - T: Transport + Clone, - { - // Use `u64::MAX` as a sentinel value to indicate that the nonce has not been fetched yet. - const NONE: u64 = u64::MAX; - - // Locks dashmap internally for a short duration to clone the `Arc`. - // We also don't want to hold the dashmap lock through the await point below. - let nonce = { - let rm = self.nonces.entry(address).or_insert_with(|| Arc::new(Mutex::new(NONE))); - Arc::clone(rm.value()) - }; - - let mut nonce = nonce.lock().await; - let new_nonce = if *nonce == NONE { - // Initialize the nonce if we haven't seen this account before. - provider.get_transaction_count(address).await? - } else { - *nonce + 1 - }; - *nonce = new_nonce; - Ok(new_nonce) - } -} - #[cfg(test)] mod tests { use super::*; @@ -121,21 +175,26 @@ mod tests { use alloy_primitives::{address, U256}; use alloy_rpc_types_eth::TransactionRequest; - async fn check_nonces(filler: &NonceFiller, provider: &P, address: Address, start: u64) - where + async fn check_nonces( + filler: &NonceFiller, + provider: &P, + address: Address, + start: u64, + ) where P: Provider, N: Network, T: Transport + Clone, + M: NonceManager, { for i in start..start + 5 { - let nonce = filler.get_next_nonce(&provider, address).await.unwrap(); + let nonce = filler.nonce_manager.get_next_nonce(&provider, address).await.unwrap(); assert_eq!(nonce, i); } } #[tokio::test] async fn smoke_test() { - let filler = NonceFiller::default(); + let filler = NonceFiller::::default(); let provider = ProviderBuilder::new().on_anvil(); let address = Address::ZERO; check_nonces(&filler, &provider, address, 0).await; @@ -143,7 +202,7 @@ mod tests { #[cfg(feature = "anvil-api")] { use crate::ext::AnvilApi; - filler.nonces.clear(); + filler.nonce_manager.nonces.clear(); provider.anvil_set_nonce(address, U256::from(69)).await.unwrap(); check_nonces(&filler, &provider, address, 69).await; } @@ -151,14 +210,16 @@ mod tests { #[tokio::test] async fn concurrency() { - let filler = Arc::new(NonceFiller::default()); + let filler = Arc::new(NonceFiller::::default()); let provider = Arc::new(ProviderBuilder::new().on_anvil()); let address = Address::ZERO; let tasks = (0..5) .map(|_| { let filler = Arc::clone(&filler); let provider = Arc::clone(&provider); - tokio::spawn(async move { filler.get_next_nonce(&provider, address).await }) + tokio::spawn(async move { + filler.nonce_manager.get_next_nonce(&provider, address).await + }) }) .collect::>(); @@ -169,13 +230,13 @@ mod tests { ns.sort_unstable(); assert_eq!(ns, (0..5).collect::>()); - assert_eq!(filler.nonces.len(), 1); - assert_eq!(*filler.nonces.get(&address).unwrap().value().lock().await, 4); + assert_eq!(filler.nonce_manager.nonces.len(), 1); + assert_eq!(*filler.nonce_manager.nonces.get(&address).unwrap().value().lock().await, 4); } #[tokio::test] async fn no_nonce_if_sender_unset() { - let provider = ProviderBuilder::new().with_nonce_management().on_anvil(); + let provider = ProviderBuilder::new().with_cached_nonce_management().on_anvil(); let tx = TransactionRequest { value: Some(U256::from(100)), @@ -191,7 +252,7 @@ mod tests { #[tokio::test] async fn increments_nonce() { - let provider = ProviderBuilder::new().with_nonce_management().on_anvil_with_wallet(); + let provider = ProviderBuilder::new().with_cached_nonce_management().on_anvil_with_wallet(); let from = provider.default_signer_address(); let tx = TransactionRequest { diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs index 416c98da727..9105f58e530 100644 --- a/crates/provider/src/lib.rs +++ b/crates/provider/src/lib.rs @@ -20,7 +20,7 @@ pub type ReqwestProvider = /// [`Http`]: alloy_transport_http::Http #[cfg(feature = "hyper")] pub type HyperProvider = - crate::RootProvider, N>; + crate::RootProvider; #[macro_use] extern crate tracing; @@ -28,12 +28,11 @@ extern crate tracing; mod builder; pub use builder::{Identity, ProviderBuilder, ProviderLayer, Stack}; +mod chain; + pub mod ext; pub mod fillers; -pub mod layers; - -mod chain; mod heart; pub use heart::{ @@ -41,10 +40,12 @@ pub use heart::{ PendingTransactionError, WatchTxError, }; +pub mod layers; + mod provider; pub use provider::{ - builder, EthCall, FilterPollerBuilder, Provider, RootProvider, RpcWithBlock, SendableTx, - WalletProvider, + builder, Caller, EthCall, EthCallParams, FilterPollerBuilder, ParamsWithBlock, Provider, + ProviderCall, RootProvider, RpcWithBlock, SendableTx, WalletProvider, }; pub mod utils; diff --git a/crates/provider/src/provider/caller.rs b/crates/provider/src/provider/caller.rs new file mode 100644 index 00000000000..abd6db8c565 --- /dev/null +++ b/crates/provider/src/provider/caller.rs @@ -0,0 +1,47 @@ +use crate::ProviderCall; +use alloy_json_rpc::{RpcParam, RpcReturn}; +use alloy_rpc_client::WeakClient; +use alloy_transport::{RpcError, Transport, TransportErrorKind, TransportResult}; +use std::borrow::Cow; + +// TODO: Make `EthCall` specific. Ref: https://github.com/alloy-rs/alloy/pull/788#discussion_r1748862509. + +/// Trait that helpes convert `EthCall` into a `ProviderCall`. +pub trait Caller: Send + Sync +where + T: Transport + Clone, + Params: RpcParam, + Resp: RpcReturn, +{ + /// Method that needs to be implemented to convert to a `ProviderCall`. + /// + /// This method handles serialization of the params and sends the request to relevant data + /// source and returns a `ProviderCall`. + fn call( + &self, + method: Cow<'static, str>, + params: Params, + ) -> TransportResult>; +} + +impl Caller for WeakClient +where + T: Transport + Clone, + Params: RpcParam, + Resp: RpcReturn, +{ + fn call( + &self, + method: Cow<'static, str>, + params: Params, + ) -> TransportResult> { + let client = self.upgrade().ok_or_else(TransportErrorKind::backend_gone)?; + + // serialize the params + let ser = serde_json::to_value(params).map_err(RpcError::ser_err)?; + + let rpc_call = client.request(method, ser); + + Ok(ProviderCall::RpcCall(rpc_call)) + } +} diff --git a/crates/provider/src/provider/call.rs b/crates/provider/src/provider/eth_call.rs similarity index 61% rename from crates/provider/src/provider/call.rs rename to crates/provider/src/provider/eth_call.rs index c79d3944c36..83433490568 100644 --- a/crates/provider/src/provider/call.rs +++ b/crates/provider/src/provider/eth_call.rs @@ -1,24 +1,23 @@ use alloy_eips::BlockId; use alloy_json_rpc::RpcReturn; use alloy_network::Network; -use alloy_rpc_client::{RpcCall, WeakClient}; use alloy_rpc_types_eth::state::StateOverride; -use alloy_transport::{Transport, TransportErrorKind, TransportResult}; +use alloy_transport::{Transport, TransportResult}; use futures::FutureExt; use serde::ser::SerializeSeq; -use std::{future::Future, marker::PhantomData, task::Poll}; +use std::{future::Future, marker::PhantomData, sync::Arc, task::Poll}; -type RunningFut<'req, 'state, T, N, Resp, Output, Map> = - RpcCall, Resp, Output, Map>; +use crate::{Caller, ProviderCall}; +/// The parameters for an `"eth_call"` RPC request. #[derive(Clone, Debug)] -struct EthCallParams<'req, 'state, N: Network> { +pub struct EthCallParams<'req, N: Network> { data: &'req N::TransactionRequest, block: Option, - overrides: Option<&'state StateOverride>, + overrides: Option<&'req StateOverride>, } -impl serde::Serialize for EthCallParams<'_, '_, N> { +impl serde::Serialize for EthCallParams<'_, N> { fn serialize(&self, serializer: S) -> Result { let len = if self.overrides.is_some() { 3 } else { 2 }; @@ -37,21 +36,22 @@ impl serde::Serialize for EthCallParams<'_, '_, N> { } /// The [`EthCallFut`] future is the future type for an `eth_call` RPC request. -#[derive(Clone, Debug)] +#[derive(Debug)] #[doc(hidden)] // Not public API. #[allow(unnameable_types)] #[pin_project::pin_project] -pub struct EthCallFut<'req, 'state, T, N, Resp, Output, Map>( - EthCallFutInner<'req, 'state, T, N, Resp, Output, Map>, -) +pub struct EthCallFut<'req, T, N, Resp, Output, Map> where T: Transport + Clone, N: Network, Resp: RpcReturn, - Map: Fn(Resp) -> Output; + Output: 'static, + Map: Fn(Resp) -> Output, +{ + inner: EthCallFutInner<'req, T, N, Resp, Output, Map>, +} -#[derive(Clone, Debug)] -enum EthCallFutInner<'req, 'state, T, N, Resp, Output, Map> +enum EthCallFutInner<'req, T, N, Resp, Output, Map> where T: Transport + Clone, N: Network, @@ -59,18 +59,45 @@ where Map: Fn(Resp) -> Output, { Preparing { - client: WeakClient, + caller: Arc, Resp>>, data: &'req N::TransactionRequest, - overrides: Option<&'state StateOverride>, + overrides: Option<&'req StateOverride>, block: Option, method: &'static str, map: Map, }, - Running(RunningFut<'req, 'state, T, N, Resp, Output, Map>), + Running { + map: Map, + fut: ProviderCall, + }, Polling, } -impl<'req, 'state, T, N, Resp, Output, Map> EthCallFutInner<'req, 'state, T, N, Resp, Output, Map> +impl<'req, T, N, Resp, Output, Map> core::fmt::Debug + for EthCallFutInner<'req, T, N, Resp, Output, Map> +where + T: Transport + Clone, + N: Network, + Resp: RpcReturn, + Output: 'static, + Map: Fn(Resp) -> Output, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Preparing { caller: _, data, overrides, block, method, map: _ } => f + .debug_struct("Preparing") + .field("data", data) + .field("overrides", overrides) + .field("block", block) + .field("method", method) + .finish(), + Self::Running { .. } => f.debug_tuple("Running").finish(), + Self::Polling => f.debug_tuple("Polling").finish(), + } + } +} + +impl<'req, T, N, Resp, Output, Map> EthCallFut<'req, T, N, Resp, Output, Map> where T: Transport + Clone, N: Network, @@ -80,43 +107,40 @@ where { /// Returns `true` if the future is in the preparing state. const fn is_preparing(&self) -> bool { - matches!(self, Self::Preparing { .. }) + matches!(self.inner, EthCallFutInner::Preparing { .. }) } /// Returns `true` if the future is in the running state. const fn is_running(&self) -> bool { - matches!(self, Self::Running(..)) + matches!(self.inner, EthCallFutInner::Running { .. }) } fn poll_preparing(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - let Self::Preparing { client, data, overrides, block, method, map } = - std::mem::replace(self, Self::Polling) + let EthCallFutInner::Preparing { caller, data, overrides, block, method, map } = + std::mem::replace(&mut self.inner, EthCallFutInner::Polling) else { unreachable!("bad state") }; - let client = match client.upgrade().ok_or_else(TransportErrorKind::backend_gone) { - Ok(client) => client, - Err(e) => return Poll::Ready(Err(e)), - }; - let params = EthCallParams { data, block, overrides }; - let fut = client.request(method, params).map_resp(map); + let fut = caller.call(method.into(), params)?; + + self.inner = EthCallFutInner::Running { map, fut }; - *self = Self::Running(fut); self.poll_running(cx) } fn poll_running(&mut self, cx: &mut std::task::Context<'_>) -> Poll> { - let Self::Running(ref mut call) = self else { unreachable!("bad state") }; + let EthCallFutInner::Running { ref map, ref mut fut } = self.inner else { + unreachable!("bad state") + }; - call.poll_unpin(cx) + fut.poll_unpin(cx).map(|res| res.map(map)) } } -impl<'req, 'state, T, N, Resp, Output, Map> Future - for EthCallFut<'req, 'state, T, N, Resp, Output, Map> +impl<'req, T, N, Resp, Output, Map> Future for EthCallFut<'req, T, N, Resp, Output, Map> where T: Transport + Clone, N: Network, @@ -130,7 +154,7 @@ where self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll { - let this = &mut self.get_mut().0; + let this = self.get_mut(); if this.is_preparing() { this.poll_preparing(cx) } else if this.is_running() { @@ -146,34 +170,52 @@ where /// /// [`Provider::call`]: crate::Provider::call #[must_use = "EthCall must be awaited to execute the call"] -#[derive(Debug, Clone)] -pub struct EthCall<'req, 'state, T, N, Resp, Output = Resp, Map = fn(Resp) -> Output> +#[derive(Clone)] +pub struct EthCall<'req, T, N, Resp, Output = Resp, Map = fn(Resp) -> Output> where T: Transport + Clone, N: Network, Resp: RpcReturn, Map: Fn(Resp) -> Output, { - client: WeakClient, - + caller: Arc, Resp>>, data: &'req N::TransactionRequest, - overrides: Option<&'state StateOverride>, + overrides: Option<&'req StateOverride>, block: Option, method: &'static str, map: Map, _pd: PhantomData (Resp, Output)>, } -impl<'req, T, N, Resp> EthCall<'req, 'static, T, N, Resp> +impl<'req, T, N, Resp> core::fmt::Debug for EthCall<'req, T, N, Resp> +where + T: Transport + Clone, + N: Network, + Resp: RpcReturn, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("EthCall") + .field("method", &self.method) + .field("data", &self.data) + .field("block", &self.block) + .field("overrides", &self.overrides) + .finish() + } +} + +impl<'req, T, N, Resp> EthCall<'req, T, N, Resp> where T: Transport + Clone, N: Network, Resp: RpcReturn, { /// Create a new CallBuilder. - pub const fn new(client: WeakClient, data: &'req N::TransactionRequest) -> Self { + pub fn new( + caller: impl Caller, Resp> + 'static, + data: &'req N::TransactionRequest, + ) -> Self { Self { - client, + caller: Arc::new(caller), data, overrides: None, block: None, @@ -184,9 +226,12 @@ where } /// Create new EthCall for gas estimates. - pub const fn gas_estimate(client: WeakClient, data: &'req N::TransactionRequest) -> Self { + pub fn gas_estimate( + caller: impl Caller, Resp> + 'static, + data: &'req N::TransactionRequest, + ) -> Self { Self { - client, + caller: Arc::new(caller), data, overrides: None, block: None, @@ -197,23 +242,33 @@ where } } -impl<'req, 'state, T, N, Resp, Output, Map> EthCall<'req, 'state, T, N, Resp, Output, Map> +impl<'req, T, N, Resp, Output, Map> EthCall<'req, T, N, Resp, Output, Map> where T: Transport + Clone, N: Network, Resp: RpcReturn, Map: Fn(Resp) -> Output, { - /// Map the response to a different type. + /// Map the response to a different type. This is usable for converting + /// the response to a more usable type, e.g. changing `U64` to `u64`. + /// + /// ## Note + /// + /// Carefully review the rust documentation on [fn pointers] before passing + /// them to this function. Unless the pointer is specifically coerced to a + /// `fn(_) -> _`, the `NewMap` will be inferred as that function's unique + /// type. This can lead to confusing error messages. + /// + /// [fn pointers]: https://doc.rust-lang.org/std/primitive.fn.html#creating-function-pointers pub fn map_resp( self, map: NewMap, - ) -> EthCall<'req, 'state, T, N, Resp, NewOutput, NewMap> + ) -> EthCall<'req, T, N, Resp, NewOutput, NewMap> where NewMap: Fn(Resp) -> NewOutput, { EthCall { - client: self.client, + caller: self.caller, data: self.data, overrides: self.overrides, block: self.block, @@ -224,7 +279,7 @@ where } /// Set the state overrides for this call. - pub const fn overrides(mut self, overrides: &'state StateOverride) -> Self { + pub const fn overrides(mut self, overrides: &'req StateOverride) -> Self { self.overrides = Some(overrides); self } @@ -236,8 +291,8 @@ where } } -impl<'req, 'state, T, N, Resp, Output, Map> std::future::IntoFuture - for EthCall<'req, 'state, T, N, Resp, Output, Map> +impl<'req, T, N, Resp, Output, Map> std::future::IntoFuture + for EthCall<'req, T, N, Resp, Output, Map> where T: Transport + Clone, N: Network, @@ -247,17 +302,19 @@ where { type Output = TransportResult; - type IntoFuture = EthCallFut<'req, 'state, T, N, Resp, Output, Map>; + type IntoFuture = EthCallFut<'req, T, N, Resp, Output, Map>; fn into_future(self) -> Self::IntoFuture { - EthCallFut(EthCallFutInner::Preparing { - client: self.client, - data: self.data, - overrides: self.overrides, - block: self.block, - method: self.method, - map: self.map, - }) + EthCallFut { + inner: EthCallFutInner::Preparing { + caller: self.caller, + data: self.data, + overrides: self.overrides, + block: self.block, + method: self.method, + map: self.map, + }, + } } } @@ -288,7 +345,7 @@ mod test { let overrides = StateOverride::default(); // Expected: [data] - let params: EthCallParams<'_, '_, Ethereum> = + let params: EthCallParams<'_, Ethereum> = EthCallParams { data: &data, block: None, overrides: None }; assert_eq!(params.data, &data); @@ -300,7 +357,7 @@ mod test { ); // Expected: [data, block, overrides] - let params: EthCallParams<'_, '_, Ethereum> = + let params: EthCallParams<'_, Ethereum> = EthCallParams { data: &data, block: Some(block), overrides: Some(&overrides) }; assert_eq!(params.data, &data); @@ -312,7 +369,7 @@ mod test { ); // Expected: [data, (default), overrides] - let params: EthCallParams<'_, '_, Ethereum> = + let params: EthCallParams<'_, Ethereum> = EthCallParams { data: &data, block: None, overrides: Some(&overrides) }; assert_eq!(params.data, &data); @@ -324,7 +381,7 @@ mod test { ); // Expected: [data, block] - let params: EthCallParams<'_, '_, Ethereum> = + let params: EthCallParams<'_, Ethereum> = EthCallParams { data: &data, block: Some(block), overrides: None }; assert_eq!(params.data, &data); diff --git a/crates/provider/src/provider/mod.rs b/crates/provider/src/provider/mod.rs index 0d8e939ed5a..8cd167497e6 100644 --- a/crates/provider/src/provider/mod.rs +++ b/crates/provider/src/provider/mod.rs @@ -1,5 +1,8 @@ -mod call; -pub use call::EthCall; +mod eth_call; +pub use eth_call::{EthCall, EthCallParams}; + +mod prov_call; +pub use prov_call::ProviderCall; mod root; pub use root::{builder, RootProvider}; @@ -14,4 +17,7 @@ mod wallet; pub use wallet::WalletProvider; mod with_block; -pub use with_block::RpcWithBlock; +pub use with_block::{ParamsWithBlock, RpcWithBlock}; + +mod caller; +pub use caller::Caller; diff --git a/crates/provider/src/provider/prov_call.rs b/crates/provider/src/provider/prov_call.rs new file mode 100644 index 00000000000..8e6b9f6119d --- /dev/null +++ b/crates/provider/src/provider/prov_call.rs @@ -0,0 +1,268 @@ +use alloy_json_rpc::{RpcParam, RpcReturn}; +use alloy_rpc_client::{RpcCall, Waiter}; +use alloy_transport::{Transport, TransportResult}; +use futures::FutureExt; +use pin_project::pin_project; +use serde_json::value::RawValue; +use std::{ + future::Future, + pin::Pin, + task::{self, Poll}, +}; +use tokio::sync::oneshot; + +/// The primary future type for the [`Provider`]. +/// +/// This future abstracts over several potential data sources. It allows +/// providers to: +/// - produce data via an [`RpcCall`] +/// - produce data by waiting on a batched RPC [`Waiter`] +/// - proudce data via an arbitrary boxed future +/// - produce data in any synchronous way +/// +/// [`Provider`]: crate::Provider +#[pin_project(project = ProviderCallProj)] +pub enum ProviderCall Output> +where + Conn: Transport + Clone, + Params: RpcParam, + Resp: RpcReturn, + Map: Fn(Resp) -> Output, +{ + /// An underlying call to an RPC server. + RpcCall(RpcCall), + /// A waiter for a batched call to a remote RPC server. + Waiter(Waiter), + /// A boxed future. + BoxedFuture(Pin> + Send>>), + /// The output, produces synchronously. + Ready(Option>), +} + +impl ProviderCall +where + Conn: Transport + Clone, + Params: RpcParam, + Resp: RpcReturn, + Map: Fn(Resp) -> Output, +{ + /// Instantiate a new [`ProviderCall`] from the output. + pub const fn ready(output: TransportResult) -> Self { + Self::Ready(Some(output)) + } + + /// True if this is an RPC call. + pub const fn is_rpc_call(&self) -> bool { + matches!(self, Self::RpcCall(_)) + } + + /// Fallible cast to [`RpcCall`] + pub const fn as_rpc_call(&self) -> Option<&RpcCall> { + match self { + Self::RpcCall(call) => Some(call), + _ => None, + } + } + + /// Fallible cast to mutable [`RpcCall`] + pub fn as_mut_rpc_call(&mut self) -> Option<&mut RpcCall> { + match self { + Self::RpcCall(call) => Some(call), + _ => None, + } + } + + /// True if this is a waiter. + pub const fn is_waiter(&self) -> bool { + matches!(self, Self::Waiter(_)) + } + + /// Fallible cast to [`Waiter`] + pub const fn as_waiter(&self) -> Option<&Waiter> { + match self { + Self::Waiter(waiter) => Some(waiter), + _ => None, + } + } + + /// Fallible cast to mutable [`Waiter`] + pub fn as_mut_waiter(&mut self) -> Option<&mut Waiter> { + match self { + Self::Waiter(waiter) => Some(waiter), + _ => None, + } + } + + /// True if this is a boxed future. + pub const fn is_boxed_future(&self) -> bool { + matches!(self, Self::BoxedFuture(_)) + } + + /// Fallible cast to a boxed future. + pub const fn as_boxed_future( + &self, + ) -> Option<&Pin> + Send>>> { + match self { + Self::BoxedFuture(fut) => Some(fut), + _ => None, + } + } + + /// True if this is a ready value. + pub const fn is_ready(&self) -> bool { + matches!(self, Self::Ready(_)) + } + + /// Fallible cast to a ready value. + /// + /// # Panics + /// + /// Panics if the future is already complete + pub const fn as_ready(&self) -> Option<&TransportResult> { + match self { + Self::Ready(Some(output)) => Some(output), + Self::Ready(None) => panic!("tried to access ready value after taking"), + _ => None, + } + } + + /// Set a function to map the response into a different type. This is + /// useful for transforming the response into a more usable type, e.g. + /// changing `U64` to `u64`. + /// + /// This function fails if the inner future is not an [`RpcCall`] or + /// [`Waiter`]. + /// + /// ## Note + /// + /// Carefully review the rust documentation on [fn pointers] before passing + /// them to this function. Unless the pointer is specifically coerced to a + /// `fn(_) -> _`, the `NewMap` will be inferred as that function's unique + /// type. This can lead to confusing error messages. + /// + /// [fn pointers]: https://doc.rust-lang.org/std/primitive.fn.html#creating-function-pointers + pub fn map_resp( + self, + map: NewMap, + ) -> Result, Self> + where + NewMap: Fn(Resp) -> NewOutput + Clone, + { + match self { + Self::RpcCall(call) => Ok(ProviderCall::RpcCall(call.map_resp(map))), + Self::Waiter(waiter) => Ok(ProviderCall::Waiter(waiter.map_resp(map))), + _ => Err(self), + } + } +} + +impl ProviderCall +where + Conn: Transport + Clone, + Params: RpcParam, + Params: ToOwned, + Params::Owned: RpcParam, + Resp: RpcReturn, + Map: Fn(Resp) -> Output, +{ + /// Convert this call into one with owned params, by cloning the params. + /// + /// # Panics + /// + /// Panics if called after the request has been polled. + pub fn into_owned_params(self) -> ProviderCall { + match self { + Self::RpcCall(call) => ProviderCall::RpcCall(call.into_owned_params()), + _ => panic!(), + } + } +} + +impl std::fmt::Debug for ProviderCall +where + Conn: Transport + Clone, + Params: RpcParam, + Resp: RpcReturn, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::RpcCall(call) => f.debug_tuple("RpcCall").field(call).finish(), + Self::Waiter { .. } => f.debug_struct("Waiter").finish_non_exhaustive(), + Self::BoxedFuture(_) => f.debug_struct("BoxedFuture").finish_non_exhaustive(), + Self::Ready(_) => f.debug_struct("Ready").finish_non_exhaustive(), + } + } +} + +impl From> + for ProviderCall +where + Conn: Transport + Clone, + Params: RpcParam, + Resp: RpcReturn, + Map: Fn(Resp) -> Output, +{ + fn from(call: RpcCall) -> Self { + Self::RpcCall(call) + } +} + +impl From> + for ProviderCall Resp> +where + Conn: Transport + Clone, + Params: RpcParam, + Resp: RpcReturn, +{ + fn from(waiter: Waiter) -> Self { + Self::Waiter(waiter) + } +} + +impl + From> + Send>>> + for ProviderCall +where + Conn: Transport + Clone, + Params: RpcParam, + Resp: RpcReturn, + Map: Fn(Resp) -> Output, +{ + fn from(fut: Pin> + Send>>) -> Self { + Self::BoxedFuture(fut) + } +} + +impl From>>> + for ProviderCall +where + Conn: Transport + Clone, + Params: RpcParam, + Resp: RpcReturn, +{ + fn from(rx: oneshot::Receiver>>) -> Self { + Waiter::from(rx).into() + } +} + +impl Future for ProviderCall +where + Conn: Transport + Clone, + Params: RpcParam, + Resp: RpcReturn, + Output: 'static, + Map: Fn(Resp) -> Output, +{ + type Output = TransportResult; + + fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + match self.as_mut().project() { + ProviderCallProj::RpcCall(call) => call.poll_unpin(cx), + ProviderCallProj::Waiter(waiter) => waiter.poll_unpin(cx), + ProviderCallProj::BoxedFuture(fut) => fut.poll_unpin(cx), + ProviderCallProj::Ready(output) => { + Poll::Ready(output.take().expect("output taken twice")) + } + } + } +} diff --git a/crates/provider/src/provider/trait.rs b/crates/provider/src/provider/trait.rs index 17393c9c35e..d04fa821786 100644 --- a/crates/provider/src/provider/trait.rs +++ b/crates/provider/src/provider/trait.rs @@ -1,10 +1,9 @@ //! Ethereum JSON-RPC provider. - use crate::{ heart::PendingTransactionError, utils::{self, Eip1559Estimation, EstimatorFunction}, EthCall, Identity, PendingTransaction, PendingTransactionBuilder, PendingTransactionConfig, - ProviderBuilder, RootProvider, RpcWithBlock, SendableTx, + ProviderBuilder, ProviderCall, RootProvider, RpcWithBlock, SendableTx, }; use alloy_eips::eip2718::Encodable2718; use alloy_json_rpc::{RpcError, RpcParam, RpcReturn}; @@ -16,7 +15,7 @@ use alloy_primitives::{ hex, Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue, TxHash, B256, U128, U256, U64, }; -use alloy_rpc_client::{ClientRef, NoParams, PollerBuilder, RpcCall, WeakClient}; +use alloy_rpc_client::{ClientRef, NoParams, PollerBuilder, WeakClient}; use alloy_rpc_types_eth::{ AccessListResult, BlockId, BlockNumberOrTag, EIP1186AccountProofResponse, FeeHistory, Filter, FilterChanges, Log, SyncStatus, @@ -100,18 +99,24 @@ pub trait Provider: /// Gets the accounts in the remote node. This is usually empty unless you're using a local /// node. - async fn get_accounts(&self) -> TransportResult> { - self.client().request_noparams("eth_accounts").await + fn get_accounts(&self) -> ProviderCall> { + self.client().request_noparams("eth_accounts").into() } /// Returns the base fee per blob gas (blob gas price) in wei. - async fn get_blob_base_fee(&self) -> TransportResult { - self.client().request_noparams("eth_blobBaseFee").await.map(|fee: U128| fee.to::()) + fn get_blob_base_fee(&self) -> ProviderCall { + self.client() + .request_noparams("eth_blobBaseFee") + .map_resp(utils::convert_u128 as fn(U128) -> u128) + .into() } /// Get the last block number available. - fn get_block_number(&self) -> RpcCall { - self.client().request_noparams("eth_blockNumber").map_resp(crate::utils::convert_u64) + fn get_block_number(&self) -> ProviderCall { + self.client() + .request_noparams("eth_blockNumber") + .map_resp(crate::utils::convert_u64 as fn(U64) -> u64) + .into() } /// Execute a smart contract call with a transaction request and state @@ -138,29 +143,21 @@ pub trait Provider: /// # let tx = alloy_rpc_types_eth::transaction::TransactionRequest::default(); /// // Execute a call on the latest block, with no state overrides /// let output = provider.call(&tx).await?; - /// // Execute a call with a block ID. - /// let output = provider.call(&tx).block(1.into()).await?; - /// // Execute a call with state overrides. - /// let output = provider.call(&tx).overrides(&my_overrides).await?; /// # Ok(()) /// # } /// ``` - /// - /// # Note - /// - /// Not all client implementations support state overrides. #[doc(alias = "eth_call")] #[doc(alias = "call_with_overrides")] - fn call<'req, 'state>( - &self, - tx: &'req N::TransactionRequest, - ) -> EthCall<'req, 'state, T, N, Bytes> { + fn call<'req>(&self, tx: &'req N::TransactionRequest) -> EthCall<'req, T, N, Bytes> { EthCall::new(self.weak_client(), tx) } - /// Gets the chain ID. - fn get_chain_id(&self) -> RpcCall { - self.client().request_noparams("eth_chainId").map_resp(crate::utils::convert_u64) + /// Gets the chain ID. + fn get_chain_id(&self) -> ProviderCall { + self.client() + .request_noparams("eth_chainId") + .map_resp(crate::utils::convert_u64 as fn(U64) -> u64) + .into() } /// Create an [EIP-2930] access list. @@ -170,7 +167,7 @@ pub trait Provider: &self, request: &'a N::TransactionRequest, ) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_createAccessList", request) + self.client().request("eth_createAccessList", request).into() } /// This function returns an [`EthCall`] which can be used to get a gas estimate, @@ -186,7 +183,7 @@ pub trait Provider: fn estimate_gas<'req>( &self, tx: &'req N::TransactionRequest, - ) -> EthCall<'req, 'static, T, N, U128, u128> { + ) -> EthCall<'req, T, N, U128, u128> { EthCall::gas_estimate(self.weak_client(), tx).map_resp(crate::utils::convert_u128) } @@ -242,21 +239,24 @@ pub trait Provider: } /// Gets the current gas price in wei. - fn get_gas_price(&self) -> RpcCall { - self.client().request_noparams("eth_gasPrice").map_resp(crate::utils::convert_u128) + fn get_gas_price(&self) -> ProviderCall { + self.client() + .request_noparams("eth_gasPrice") + .map_resp(crate::utils::convert_u128 as fn(U128) -> u128) + .into() } /// Retrieves account information ([Account](alloy_consensus::Account)) for the given [Address] /// at the particular [BlockId]. fn get_account(&self, address: Address) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_getAccount", address) + self.client().request("eth_getAccount", address).into() } /// Gets the balance of the account. /// /// Defaults to the latest block. See also [`RpcWithBlock::block_id`]. - fn get_balance(&self, address: Address) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_getBalance", address) + fn get_balance(&self, address: Address) -> RpcWithBlock { + self.client().request("eth_getBalance", address).into() } /// Gets a block by either its hash, tag, or number, with full transactions or only hashes. @@ -324,16 +324,16 @@ pub trait Provider: } /// Gets the selected block [BlockId] receipts. - async fn get_block_receipts( + fn get_block_receipts( &self, block: BlockId, - ) -> TransportResult>> { - self.client().request("eth_getBlockReceipts", (block,)).await + ) -> ProviderCall>> { + self.client().request("eth_getBlockReceipts", (block,)).into() } /// Gets the bytecode located at the corresponding [Address]. fn get_code_at(&self, address: Address) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_getCode", address) + self.client().request("eth_getCode", address).into() } /// Watch for new blocks by polling the provider with @@ -501,7 +501,7 @@ pub trait Provider: address: Address, keys: Vec, ) -> RpcWithBlock), EIP1186AccountProofResponse> { - RpcWithBlock::new(self.weak_client(), "eth_getProof", (address, keys)) + self.client().request("eth_getProof", (address, keys)).into() } /// Gets the specified storage value from [Address]. @@ -510,15 +510,15 @@ pub trait Provider: address: Address, key: U256, ) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_getStorageAt", (address, key)) + self.client().request("eth_getStorageAt", (address, key)).into() } /// Gets a transaction by its [TxHash]. - async fn get_transaction_by_hash( + fn get_transaction_by_hash( &self, hash: TxHash, - ) -> TransportResult> { - self.client().request("eth_getTransactionByHash", (hash,)).await + ) -> ProviderCall> { + self.client().request("eth_getTransactionByHash", (hash,)).into() } /// Returns the EIP-2718 encoded transaction if it exists, see also @@ -529,24 +529,32 @@ pub trait Provider: /// [TxEip4844](alloy_consensus::transaction::eip4844::TxEip4844). /// /// This can be decoded into [TxEnvelope](alloy_consensus::transaction::TxEnvelope). - async fn get_raw_transaction_by_hash(&self, hash: TxHash) -> TransportResult> { - self.client().request("eth_getRawTransactionByHash", (hash,)).await + fn get_raw_transaction_by_hash( + &self, + hash: TxHash, + ) -> ProviderCall> { + self.client().request("eth_getRawTransactionByHash", (hash,)).into() } /// Gets the transaction count (AKA "nonce") of the corresponding address. #[doc(alias = "get_nonce")] #[doc(alias = "get_account_nonce")] - fn get_transaction_count(&self, address: Address) -> RpcWithBlock { - RpcWithBlock::new(self.weak_client(), "eth_getTransactionCount", address) - .map_resp(crate::utils::convert_u64) + fn get_transaction_count( + &self, + address: Address, + ) -> RpcWithBlock u64> { + self.client() + .request("eth_getTransactionCount", address) + .map_resp(crate::utils::convert_u64 as fn(U64) -> u64) + .into() } /// Gets a transaction receipt if it exists, by its [TxHash]. - async fn get_transaction_receipt( + fn get_transaction_receipt( &self, hash: TxHash, - ) -> TransportResult> { - self.client().request("eth_getTransactionReceipt", (hash,)).await + ) -> ProviderCall> { + self.client().request("eth_getTransactionReceipt", (hash,)).into() } /// Gets an uncle block through the tag [BlockId] and index [u64]. @@ -581,11 +589,11 @@ pub trait Provider: } /// Returns a suggestion for the current `maxPriorityFeePerGas` in wei. - async fn get_max_priority_fee_per_gas(&self) -> TransportResult { + fn get_max_priority_fee_per_gas(&self) -> ProviderCall { self.client() .request_noparams("eth_maxPriorityFeePerGas") - .await - .map(|fee: U128| fee.to::()) + .map_resp(utils::convert_u128 as fn(U128) -> u128) + .into() } /// Notify the provider that we are interested in new blocks. @@ -869,25 +877,28 @@ pub trait Provider: } /// Gets syncing info. - async fn syncing(&self) -> TransportResult { - self.client().request_noparams("eth_syncing").await + fn syncing(&self) -> ProviderCall { + self.client().request_noparams("eth_syncing").into() } /// Gets the client version. #[doc(alias = "web3_client_version")] - async fn get_client_version(&self) -> TransportResult { - self.client().request_noparams("web3_clientVersion").await + fn get_client_version(&self) -> ProviderCall { + self.client().request_noparams("web3_clientVersion").into() } /// Gets the `Keccak-256` hash of the given data. #[doc(alias = "web3_sha3")] - async fn get_sha3(&self, data: &[u8]) -> TransportResult { - self.client().request("web3_sha3", (hex::encode_prefixed(data),)).await + fn get_sha3(&self, data: &[u8]) -> ProviderCall { + self.client().request("web3_sha3", (hex::encode_prefixed(data),)).into() } /// Gets the network ID. Same as `eth_chainId`. - fn get_net_version(&self) -> RpcCall { - self.client().request_noparams("net_version").map_resp(crate::utils::convert_u64) + fn get_net_version(&self) -> ProviderCall { + self.client() + .request_noparams("net_version") + .map_resp(crate::utils::convert_u64 as fn(U64) -> u64) + .into() } /* ---------------------------------------- raw calls --------------------------------------- */ @@ -1013,6 +1024,21 @@ mod tests { use alloy_node_bindings::Anvil; use alloy_primitives::{address, b256, bytes, keccak256}; use alloy_rpc_types_eth::{request::TransactionRequest, Block}; + // For layer transport tests + #[cfg(feature = "hyper")] + use alloy_transport_http::{ + hyper, + hyper::body::Bytes as HyperBytes, + hyper_util::{ + client::legacy::{Client, Error}, + rt::TokioExecutor, + }, + HyperResponse, HyperResponseFut, + }; + #[cfg(feature = "hyper")] + use http_body_util::Full; + #[cfg(feature = "hyper")] + use tower::{Layer, Service}; fn init_tracing() { let _ = tracing_subscriber::fmt::try_init(); @@ -1035,6 +1061,115 @@ mod tests { assert_eq!(0, num); } + #[cfg(feature = "hyper")] + #[tokio::test] + async fn test_default_hyper_transport() { + init_tracing(); + let anvil = Anvil::new().spawn(); + let hyper_t = alloy_transport_http::HyperTransport::new_hyper(anvil.endpoint_url()); + + let rpc_client = alloy_rpc_client::RpcClient::new(hyper_t, true); + + let provider = RootProvider::<_, Ethereum>::new(rpc_client); + let num = provider.get_block_number().await.unwrap(); + assert_eq!(0, num); + } + + #[cfg(feature = "hyper")] + #[tokio::test] + async fn test_hyper_layer_transport() { + struct LoggingLayer; + + impl Layer for LoggingLayer { + type Service = LoggingService; + + fn layer(&self, inner: S) -> Self::Service { + LoggingService { inner } + } + } + + #[derive(Clone)] // required + struct LoggingService { + inner: S, + } + + impl Service> for LoggingService + where + S: Service, Response = HyperResponse, Error = Error> + + Clone + + Send + + Sync + + 'static, + S::Future: Send, + S::Error: std::error::Error + Send + Sync + 'static, + B: From> + Send + 'static + Clone + Sync + std::fmt::Debug, + { + type Response = HyperResponse; + type Error = Error; + type Future = HyperResponseFut; + + fn poll_ready( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: hyper::Request) -> Self::Future { + println!("Logging Layer - HyperRequest {req:?}"); + + let fut = self.inner.call(req); + + Box::pin(fut) + } + } + use http::header::{self, HeaderValue}; + use tower_http::{ + sensitive_headers::SetSensitiveRequestHeadersLayer, set_header::SetRequestHeaderLayer, + }; + init_tracing(); + let anvil = Anvil::new().spawn(); + let hyper_client = Client::builder(TokioExecutor::new()).build_http::>(); + + // Setup tower serive with multiple layers modifying request headers + let service = tower::ServiceBuilder::new() + .layer(SetRequestHeaderLayer::if_not_present( + header::USER_AGENT, + HeaderValue::from_static("alloy app"), + )) + .layer(SetRequestHeaderLayer::overriding( + header::AUTHORIZATION, + HeaderValue::from_static("some-jwt-token"), + )) + .layer(SetRequestHeaderLayer::appending( + header::SET_COOKIE, + HeaderValue::from_static("cookie-value"), + )) + .layer(SetSensitiveRequestHeadersLayer::new([header::AUTHORIZATION])) // Hides the jwt token as sensitive. + .layer(LoggingLayer) + .service(hyper_client); + + let layer_transport = alloy_transport_http::HyperClient::with_service(service); + + let http_hyper = + alloy_transport_http::Http::with_client(layer_transport, anvil.endpoint_url()); + + let rpc_client = alloy_rpc_client::RpcClient::new(http_hyper, true); + + let provider = RootProvider::<_, Ethereum>::new(rpc_client); + let num = provider.get_block_number().await.unwrap(); + assert_eq!(0, num); + + // Test Cloning with service + let cloned_t = provider.client().transport().clone(); + + let rpc_client = alloy_rpc_client::RpcClient::new(cloned_t, true); + + let provider = RootProvider::<_, Ethereum>::new(rpc_client); + let num = provider.get_block_number().await.unwrap(); + assert_eq!(0, num); + } + #[tokio::test] async fn test_builder_helper_fn_any_network() { init_tracing(); @@ -1259,14 +1394,35 @@ mod tests { assert_eq!(0, num.to::()) } + #[cfg(feature = "anvil-api")] #[tokio::test] async fn gets_transaction_count() { init_tracing(); let provider = ProviderBuilder::new().on_anvil(); - let count = provider - .get_transaction_count(address!("328375e18E7db8F1CA9d9bA8bF3E9C94ee34136A")) - .await - .unwrap(); + let accounts = provider.get_accounts().await.unwrap(); + let sender = accounts[0]; + + // Initial tx count should be 0 + let count = provider.get_transaction_count(sender).await.unwrap(); + assert_eq!(count, 0); + + // Send Tx + let tx = TransactionRequest { + value: Some(U256::from(100)), + from: Some(sender), + to: Some(address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into()), + gas_price: Some(20e9 as u128), + gas: Some(21000), + ..Default::default() + }; + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await; + + // Tx count should be 1 + let count = provider.get_transaction_count(sender).await.unwrap(); + assert_eq!(count, 1); + + // Tx count should be 0 at block 0 + let count = provider.get_transaction_count(sender).block_id(0.into()).await.unwrap(); assert_eq!(count, 0); } @@ -1528,6 +1684,9 @@ mod tests { .with_input(bytes!("06fdde03")); // `name()` let result = provider.call(&req).await.unwrap(); assert_eq!(String::abi_decode(&result, true).unwrap(), "Wrapped Ether"); + + let result = provider.call(&req).block(0.into()).await.unwrap(); + assert_eq!(result.to_string(), "0x"); } #[tokio::test] diff --git a/crates/provider/src/provider/with_block.rs b/crates/provider/src/provider/with_block.rs index 471e310c156..d3fcd300957 100644 --- a/crates/provider/src/provider/with_block.rs +++ b/crates/provider/src/provider/with_block.rs @@ -1,37 +1,60 @@ use alloy_eips::BlockId; -use alloy_json_rpc::{RpcError, RpcParam, RpcReturn}; +use alloy_json_rpc::{RpcParam, RpcReturn}; use alloy_primitives::B256; -use alloy_rpc_client::{RpcCall, WeakClient}; -use alloy_transport::{Transport, TransportErrorKind, TransportResult}; -use futures::FutureExt; -use std::{ - borrow::Cow, - future::{Future, IntoFuture}, - marker::PhantomData, - task::Poll, -}; +use alloy_rpc_client::RpcCall; +use alloy_transport::{Transport, TransportResult}; +use std::future::IntoFuture; -/// States of the [`RpcWithBlock`] future. -#[derive(Clone)] -enum States Output> +use crate::ProviderCall; + +/// Helper struct that houses the params along with the BlockId. +#[derive(Debug, Clone)] +pub struct ParamsWithBlock { + params: Params, + block_id: BlockId, +} + +impl serde::Serialize for ParamsWithBlock { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // Serialize params to a Value first + let mut ser = serde_json::to_value(&self.params).map_err(serde::ser::Error::custom)?; + + // serialize the block id + let block_id = serde_json::to_value(self.block_id).map_err(serde::ser::Error::custom)?; + + if let serde_json::Value::Array(ref mut arr) = ser { + arr.push(block_id); + } else if ser.is_null() { + ser = serde_json::Value::Array(vec![block_id]); + } else { + ser = serde_json::Value::Array(vec![ser, block_id]); + } + + ser.serialize(serializer) + } +} + +type ProviderCallProducer = + Box ProviderCall, Resp, Output, Map> + Send>; + +/// Container for varous types of calls dependent on a block id. +enum WithBlockInner Output> where T: Transport + Clone, Params: RpcParam, Resp: RpcReturn, Map: Fn(Resp) -> Output, { - Invalid, - Preparing { - client: WeakClient, - method: Cow<'static, str>, - params: Params, - block_id: BlockId, - map: Map, - }, - Running(RpcCall), + /// [RpcCall] which params are getting wrapped into [ParamsWithBlock] once the block id is set. + RpcCall(RpcCall), + /// Closure that produces a [ProviderCall] once the block id is set. + ProviderCall(ProviderCallProducer), } -impl core::fmt::Debug for States +impl core::fmt::Debug for WithBlockInner where T: Transport + Clone, Params: RpcParam, @@ -40,163 +63,77 @@ where { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Invalid => f.debug_tuple("Invalid").finish(), - Self::Preparing { client, method, params, block_id, .. } => f - .debug_struct("Preparing") - .field("client", client) - .field("method", method) - .field("params", params) - .field("block_id", block_id) - .finish(), - Self::Running(arg0) => f.debug_tuple("Running").field(arg0).finish(), + Self::RpcCall(call) => f.debug_tuple("RpcCall").field(call).finish(), + Self::ProviderCall(_) => f.debug_struct("ProviderCall").finish(), } } } -/// A future for [`RpcWithBlock`]. Simple wrapper around [`RpcCall`]. -#[derive(Debug, Clone)] +/// A struct that takes an optional [`BlockId`] parameter. +/// +/// This resolves to a [`ProviderCall`] that will execute the call on the specified block. +/// +/// By default this will use "latest". #[pin_project::pin_project] -#[allow(unnameable_types)] -pub struct RpcWithBlockFut +#[derive(Debug)] +pub struct RpcWithBlock Output> where T: Transport + Clone, Params: RpcParam, Resp: RpcReturn, - Map: Fn(Resp) -> Output, + Map: Fn(Resp) -> Output + Clone, { - state: States, + inner: WithBlockInner, + block_id: BlockId, } -impl RpcWithBlockFut +impl RpcWithBlock where T: Transport + Clone, Params: RpcParam, Resp: RpcReturn, - Output: 'static, - Map: Fn(Resp) -> Output, + Map: Fn(Resp) -> Output + Clone, { - fn poll_preparing( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - let this = self.project(); - let States::Preparing { client, method, params, block_id, map } = - std::mem::replace(this.state, States::Invalid) - else { - unreachable!("bad state") - }; - - let mut fut = { - // make sure the client still exists - let client = match client.upgrade().ok_or_else(TransportErrorKind::backend_gone) { - Ok(client) => client, - Err(e) => return Poll::Ready(Err(e)), - }; - - // serialize the params - let ser = serde_json::to_value(params).map_err(RpcError::ser_err); - let mut ser = match ser { - Ok(ser) => ser, - Err(e) => return Poll::Ready(Err(e)), - }; - - // serialize the block id - let block_id = serde_json::to_value(block_id).map_err(RpcError::ser_err); - let block_id = match block_id { - Ok(block_id) => block_id, - Err(e) => return Poll::Ready(Err(e)), - }; - - // append the block id to the params - if let serde_json::Value::Array(ref mut arr) = ser { - arr.push(block_id); - } else if ser.is_null() { - ser = serde_json::Value::Array(vec![block_id]); - } else { - ser = serde_json::Value::Array(vec![ser, block_id]); - } - - // create the call - client.request(method.clone(), ser).map_resp(map) - }; - // poll the call immediately - match fut.poll_unpin(cx) { - Poll::Ready(value) => Poll::Ready(value), - Poll::Pending => { - *this.state = States::Running(fut); - Poll::Pending - } - } + /// Create a new [`RpcWithBlock`] from a [`RpcCall`]. + pub fn new_rpc(inner: RpcCall) -> Self { + Self { inner: WithBlockInner::RpcCall(inner), block_id: Default::default() } } - fn poll_running( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> Poll> { - let States::Running(call) = self.project().state else { unreachable!("bad state") }; - call.poll_unpin(cx) + /// Create a new [`RpcWithBlock`] from a closure producing a [`ProviderCall`]. + pub fn new_provider(get_call: F) -> Self + where + F: Fn(BlockId) -> ProviderCall, Resp, Output, Map> + + Send + + 'static, + { + let get_call = Box::new(get_call); + Self { inner: WithBlockInner::ProviderCall(get_call), block_id: Default::default() } } } -impl Future for RpcWithBlockFut +impl From> + for RpcWithBlock where T: Transport + Clone, Params: RpcParam, Resp: RpcReturn, - Output: 'static, - Map: Fn(Resp) -> Output, + Map: Fn(Resp) -> Output + Clone, { - type Output = TransportResult; - - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - if matches!(self.state, States::Preparing { .. }) { - self.poll_preparing(cx) - } else if matches!(self.state, States::Running { .. }) { - self.poll_running(cx) - } else { - panic!("bad state") - } + fn from(inner: RpcCall) -> Self { + Self::new_rpc(inner) } } -/// An [`RpcCall`] that takes an optional [`BlockId`] parameter. By default -/// this will use "latest". -#[derive(Debug, Clone)] -pub struct RpcWithBlock Output> -where - T: Transport + Clone, - Params: RpcParam, - Resp: RpcReturn, - Map: Fn(Resp) -> Output, -{ - client: WeakClient, - method: Cow<'static, str>, - params: Params, - block_id: BlockId, - map: Map, - _pd: PhantomData (Resp, Output)>, -} - -impl RpcWithBlock +impl From for RpcWithBlock where T: Transport + Clone, Params: RpcParam, Resp: RpcReturn, + Map: Fn(Resp) -> Output + Clone, + F: Fn(BlockId) -> ProviderCall, Resp, Output, Map> + Send + 'static, { - /// Create a new [`RpcWithBlock`] instance. - pub fn new( - client: WeakClient, - method: impl Into>, - params: Params, - ) -> Self { - Self { - client, - method: method.into(), - params, - block_id: Default::default(), - map: std::convert::identity, - _pd: PhantomData, - } + fn from(inner: F) -> Self { + Self::new_provider(inner) } } @@ -205,26 +142,8 @@ where T: Transport + Clone, Params: RpcParam, Resp: RpcReturn, - Map: Fn(Resp) -> Output, + Map: Fn(Resp) -> Output + Clone, { - /// Map the response. - pub fn map_resp( - self, - map: NewMap, - ) -> RpcWithBlock - where - NewMap: Fn(Resp) -> NewOutput, - { - RpcWithBlock { - client: self.client, - method: self.method, - params: self.params, - block_id: self.block_id, - map, - _pd: PhantomData, - } - } - /// Set the block id. pub const fn block_id(mut self, block_id: BlockId) -> Self { self.block_id = block_id; @@ -280,21 +199,20 @@ where Params: RpcParam, Resp: RpcReturn, Output: 'static, - Map: Fn(Resp) -> Output, + Map: Fn(Resp) -> Output + Clone, { type Output = TransportResult; - type IntoFuture = RpcWithBlockFut; + type IntoFuture = ProviderCall, Resp, Output, Map>; fn into_future(self) -> Self::IntoFuture { - RpcWithBlockFut { - state: States::Preparing { - client: self.client, - method: self.method, - params: self.params, - block_id: self.block_id, - map: self.map, - }, + match self.inner { + WithBlockInner::RpcCall(rpc_call) => { + let block_id = self.block_id; + let rpc_call = rpc_call.map_params(|params| ParamsWithBlock { params, block_id }); + ProviderCall::RpcCall(rpc_call) + } + WithBlockInner::ProviderCall(get_call) => get_call(self.block_id), } } } diff --git a/crates/pubsub/CHANGELOG.md b/crates/pubsub/CHANGELOG.md index 03ca72542be..341f598414d 100644 --- a/crates/pubsub/CHANGELOG.md +++ b/crates/pubsub/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -14,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 - Fix unnameable types ([#1029](https://github.com/alloy-rs/alloy/issues/1029)) diff --git a/crates/rpc-client/CHANGELOG.md b/crates/rpc-client/CHANGELOG.md index 89b7a87a07f..306e53afd46 100644 --- a/crates/rpc-client/CHANGELOG.md +++ b/crates/rpc-client/CHANGELOG.md @@ -5,6 +5,51 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- ProviderCall ([#788](https://github.com/alloy-rs/alloy/issues/788)) +- [transport-http] Layer client ([#1227](https://github.com/alloy-rs/alloy/issues/1227)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Features + +- Improve node bindings ([#1279](https://github.com/alloy-rs/alloy/issues/1279)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Dependencies + +- Bump tower to 0.5 ([#1249](https://github.com/alloy-rs/alloy/issues/1249)) + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -19,6 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 - Fix unnameable types ([#1029](https://github.com/alloy-rs/alloy/issues/1029)) diff --git a/crates/rpc-client/Cargo.toml b/crates/rpc-client/Cargo.toml index 74f7e1c7dbf..ef694848afb 100644 --- a/crates/rpc-client/Cargo.toml +++ b/crates/rpc-client/Cargo.toml @@ -38,8 +38,6 @@ alloy-transport-ws = { workspace = true, optional = true } reqwest = { workspace = true, optional = true } -hyper-util = { workspace = true, optional = true } - url = { workspace = true, optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -57,7 +55,7 @@ futures-util.workspace = true [features] default = ["reqwest"] reqwest = ["dep:url", "dep:reqwest", "alloy-transport-http/reqwest"] -hyper = ["dep:url", "dep:hyper-util", "alloy-transport-http/hyper"] +hyper = ["dep:url", "alloy-transport-http/hyper"] pubsub = ["dep:alloy-pubsub", "dep:alloy-primitives"] ws = ["pubsub", "dep:alloy-transport-ws", "dep:url"] ipc = ["pubsub", "dep:alloy-transport-ipc"] diff --git a/crates/rpc-client/src/batch.rs b/crates/rpc-client/src/batch.rs index 7affdac2994..0993d22825f 100644 --- a/crates/rpc-client/src/batch.rs +++ b/crates/rpc-client/src/batch.rs @@ -4,7 +4,8 @@ use alloy_json_rpc::{ RpcReturn, SerializedRequest, }; use alloy_transport::{Transport, TransportError, TransportErrorKind, TransportResult}; -use futures::channel::oneshot; +use futures::FutureExt; +use pin_project::pin_project; use serde_json::value::RawValue; use std::{ borrow::Cow, @@ -12,8 +13,12 @@ use std::{ future::{Future, IntoFuture}, marker::PhantomData, pin::Pin, - task::{self, ready, Poll}, + task::{ + self, ready, + Poll::{self, Ready}, + }, }; +use tokio::sync::oneshot; pub(crate) type Channel = oneshot::Sender>>; pub(crate) type ChannelMap = HashMap; @@ -35,29 +40,58 @@ pub struct BatchRequest<'a, T> { /// Awaits a single response for a request that has been included in a batch. #[must_use = "A Waiter does nothing unless the corresponding BatchRequest is sent via `send_batch` and `.await`, AND the Waiter is awaited."] +#[pin_project] #[derive(Debug)] -pub struct Waiter { +pub struct Waiter Output> { + #[pin] rx: oneshot::Receiver>>, - _resp: PhantomData Resp>, + map: Option, + _resp: PhantomData (Output, Resp)>, +} + +impl Waiter { + /// Map the response to a different type. This is usable for converting + /// the response to a more usable type, e.g. changing `U64` to `u64`. + /// + /// ## Note + /// + /// Carefully review the rust documentation on [fn pointers] before passing + /// them to this function. Unless the pointer is specifically coerced to a + /// `fn(_) -> _`, the `NewMap` will be inferred as that function's unique + /// type. This can lead to confusing error messages. + /// + /// [fn pointers]: https://doc.rust-lang.org/std/primitive.fn.html#creating-function-pointers + pub fn map_resp(self, map: NewMap) -> Waiter + where + NewMap: FnOnce(Resp) -> NewOutput, + { + Waiter { rx: self.rx, map: Some(map), _resp: PhantomData } + } } impl From>>> for Waiter { fn from(rx: oneshot::Receiver>>) -> Self { - Self { rx, _resp: PhantomData } + Self { rx, map: Some(std::convert::identity), _resp: PhantomData } } } -impl std::future::Future for Waiter +impl std::future::Future for Waiter where Resp: RpcReturn, + Map: FnOnce(Resp) -> Output, { - type Output = TransportResult; + type Output = TransportResult; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + let this = self.get_mut(); - fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { - Pin::new(&mut self.rx).poll(cx).map(|resp| match resp { - Ok(resp) => try_deserialize_ok(resp), - Err(e) => Err(TransportErrorKind::custom(e)), - }) + match ready!(this.rx.poll_unpin(cx)) { + Ok(resp) => { + let resp: Result = try_deserialize_ok(resp); + Ready(resp.map(this.map.take().expect("polled after completion"))) + } + Err(e) => Poll::Ready(Err(TransportErrorKind::custom(e))), + } } } diff --git a/crates/rpc-client/src/builder.rs b/crates/rpc-client/src/builder.rs index 28be4963a76..759affd3608 100644 --- a/crates/rpc-client/src/builder.rs +++ b/crates/rpc-client/src/builder.rs @@ -64,12 +64,10 @@ impl ClientBuilder { #[cfg(all(not(target_arch = "wasm32"), feature = "hyper"))] pub fn hyper_http(self, url: url::Url) -> RpcClient where - L: Layer>, + L: Layer, L::Service: Transport, { - let executor = hyper_util::rt::TokioExecutor::new(); - let client = hyper_util::client::legacy::Client::builder(executor).build_http(); - let transport = alloy_transport_http::Http::with_client(client, url); + let transport = alloy_transport_http::HyperTransport::new_hyper(url); let is_local = transport.guess_local(); self.transport(transport, is_local) diff --git a/crates/rpc-client/src/call.rs b/crates/rpc-client/src/call.rs index a1efca47f50..8c9efc60fab 100644 --- a/crates/rpc-client/src/call.rs +++ b/crates/rpc-client/src/call.rs @@ -4,13 +4,14 @@ use alloy_json_rpc::{ }; use alloy_transport::{RpcFut, Transport, TransportError, TransportResult}; use core::panic; +use futures::FutureExt; use serde_json::value::RawValue; use std::{ fmt, future::Future, marker::PhantomData, pin::Pin, - task::{self, Poll::Ready}, + task::{self, ready, Poll::Ready}, }; use tower::Service; @@ -139,11 +140,11 @@ pub struct RpcCall Output> where Conn: Transport + Clone, Params: RpcParam, - Map: Fn(Resp) -> Output, + Map: FnOnce(Resp) -> Output, { #[pin] state: CallState, - map: Map, + map: Option, _pd: core::marker::PhantomData (Resp, Output)>, } @@ -151,7 +152,7 @@ impl core::fmt::Debug for RpcCall Output, + Map: FnOnce(Resp) -> Output, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("RpcCall").field("state", &self.state).finish() @@ -167,7 +168,7 @@ where pub fn new(req: Request, connection: Conn) -> Self { Self { state: CallState::Prepared { request: Some(req), connection }, - map: std::convert::identity, + map: Some(std::convert::identity), _pd: PhantomData, } } @@ -177,17 +178,27 @@ impl RpcCall where Conn: Transport + Clone, Params: RpcParam, - Map: Fn(Resp) -> Output, + Map: FnOnce(Resp) -> Output, { - /// Set a function to map the response into a different type. + /// Map the response to a different type. This is usable for converting + /// the response to a more usable type, e.g. changing `U64` to `u64`. + /// + /// ## Note + /// + /// Carefully review the rust documentation on [fn pointers] before passing + /// them to this function. Unless the pointer is specifically coerced to a + /// `fn(_) -> _`, the `NewMap` will be inferred as that function's unique + /// type. This can lead to confusing error messages. + /// + /// [fn pointers]: https://doc.rust-lang.org/std/primitive.fn.html#creating-function-pointers pub fn map_resp( self, map: NewMap, ) -> RpcCall where - NewMap: Fn(Resp) -> NewOutput, + NewMap: FnOnce(Resp) -> NewOutput, { - RpcCall { state: self.state, map, _pd: PhantomData } + RpcCall { state: self.state, map: Some(map), _pd: PhantomData } } /// Returns `true` if the request is a subscription. @@ -249,20 +260,37 @@ where }; request.as_mut().expect("no request in prepared") } + + /// Map the params of the request into a new type. + pub fn map_params( + self, + map: impl Fn(Params) -> NewParams, + ) -> RpcCall { + let CallState::Prepared { request, connection } = self.state else { + panic!("Cannot get request after request has been sent"); + }; + let request = request.expect("no request in prepared").map_params(map); + RpcCall { + state: CallState::Prepared { request: Some(request), connection }, + map: self.map, + _pd: PhantomData, + } + } } impl RpcCall where Conn: Transport + Clone, - Params: RpcParam + Clone, - Map: Fn(Resp) -> Output, + Params: RpcParam + ToOwned, + Params::Owned: RpcParam, + Map: FnOnce(Resp) -> Output, { /// Convert this call into one with owned params, by cloning the params. /// /// # Panics /// - /// Panics if called after the request has been sent. - pub fn into_owned_params(self) -> RpcCall { + /// Panics if called after the request has been polled. + pub fn into_owned_params(self) -> RpcCall { let CallState::Prepared { request, connection } = self.state else { panic!("Cannot get params after request has been sent"); }; @@ -282,7 +310,7 @@ where Params: RpcParam + 'a, Resp: RpcReturn, Output: 'static, - Map: Fn(Resp) -> Output + Send + 'a, + Map: FnOnce(Resp) -> Output + Send + 'a, { /// Convert this future into a boxed, pinned future, erasing its type. pub fn boxed(self) -> RpcFut<'a, Output> { @@ -296,13 +324,16 @@ where Params: RpcParam, Resp: RpcReturn, Output: 'static, - Map: Fn(Resp) -> Output, + Map: FnOnce(Resp) -> Output, { type Output = TransportResult; fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { trace!(?self.state, "polling RpcCall"); - let this = self.project(); - this.state.poll(cx).map(try_deserialize_ok).map(|r| r.map(this.map)) + + let this = self.get_mut(); + let resp = try_deserialize_ok(ready!(this.state.poll_unpin(cx))); + + Ready(resp.map(this.map.take().expect("polled after completion"))) } } diff --git a/crates/rpc-client/src/client.rs b/crates/rpc-client/src/client.rs index b960239e35e..370d18ccaa6 100644 --- a/crates/rpc-client/src/client.rs +++ b/crates/rpc-client/src/client.rs @@ -39,7 +39,7 @@ impl Clone for RpcClient { impl RpcClient { /// Create a new [`ClientBuilder`]. - pub fn builder() -> ClientBuilder { + pub const fn builder() -> ClientBuilder { ClientBuilder { builder: ServiceBuilder::new() } } } diff --git a/crates/rpc-client/tests/it/ipc.rs b/crates/rpc-client/tests/it/ipc.rs index a34484e7ea4..30f0781adb8 100644 --- a/crates/rpc-client/tests/it/ipc.rs +++ b/crates/rpc-client/tests/it/ipc.rs @@ -1,24 +1,26 @@ -use alloy_node_bindings::Geth; +use alloy_node_bindings::{utils::run_with_tempdir, Geth}; use alloy_primitives::U64; use alloy_rpc_client::{ClientBuilder, RpcCall}; use alloy_transport_ipc::IpcConnect; #[tokio::test] -async fn it_makes_a_request() { - let temp_dir = tempfile::TempDir::with_prefix("geth-test-").unwrap(); - let geth = Geth::new() - .disable_discovery() - .ipc_path(temp_dir.path().join("alloy.ipc")) - .enable_ipc() - .block_time(1u64) - .data_dir(temp_dir.path()) - .spawn(); +async fn can_make_a_request() { + run_with_tempdir("geth-test-", |temp_dir| async move { + let geth = Geth::new() + .disable_discovery() + .ipc_path(temp_dir.join("alloy.ipc")) + .enable_ipc() + .block_time(1u64) + .data_dir(temp_dir) + .spawn(); - let connect = IpcConnect::new(geth.ipc_endpoint()); - let client = ClientBuilder::default().pubsub(connect).await.unwrap(); + let connect = IpcConnect::new(geth.ipc_endpoint()); + let client = ClientBuilder::default().pubsub(connect).await.unwrap(); - let req: RpcCall<_, _, U64> = client.request_noparams("eth_blockNumber"); - let timeout = tokio::time::timeout(std::time::Duration::from_secs(2), req); - let res = timeout.await.unwrap().unwrap(); - assert!(res.to::() <= 3); + let req: RpcCall<_, _, U64> = client.request_noparams("eth_blockNumber"); + let timeout = tokio::time::timeout(std::time::Duration::from_secs(2), req); + let res = timeout.await.unwrap().unwrap(); + assert!(res.to::() <= 3); + }) + .await; } diff --git a/crates/rpc-types-admin/CHANGELOG.md b/crates/rpc-types-admin/CHANGELOG.md index 702b420443c..a34d80468f8 100644 --- a/crates/rpc-types-admin/CHANGELOG.md +++ b/crates/rpc-types-admin/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -13,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Chore : fix typos ([#1087](https://github.com/alloy-rs/alloy/issues/1087)) - Release 0.2.0 diff --git a/crates/rpc-types-anvil/CHANGELOG.md b/crates/rpc-types-anvil/CHANGELOG.md index 71d302cfd9f..e2de5158595 100644 --- a/crates/rpc-types-anvil/CHANGELOG.md +++ b/crates/rpc-types-anvil/CHANGELOG.md @@ -5,10 +5,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/rpc-types-beacon/CHANGELOG.md b/crates/rpc-types-beacon/CHANGELOG.md index 97b7d615fe0..a405f07bf04 100644 --- a/crates/rpc-types-beacon/CHANGELOG.md +++ b/crates/rpc-types-beacon/CHANGELOG.md @@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- [rpc-types-beacon] `SignedBidSubmissionV4` ([#1303](https://github.com/alloy-rs/alloy/issues/1303)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Features + +- [engine] Optional Serde ([#1283](https://github.com/alloy-rs/alloy/issues/1283)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 +- Add helpers for beacon blob bundle ([#1254](https://github.com/alloy-rs/alloy/issues/1254)) + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -17,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Clippy für docs ([#1194](https://github.com/alloy-rs/alloy/issues/1194)) - Release 0.2.1 - Release 0.2.0 diff --git a/crates/rpc-types-beacon/Cargo.toml b/crates/rpc-types-beacon/Cargo.toml index 092a533626a..9de616cbf8b 100644 --- a/crates/rpc-types-beacon/Cargo.toml +++ b/crates/rpc-types-beacon/Cargo.toml @@ -20,7 +20,7 @@ workspace = true [dependencies] # ethereum alloy-eips = { workspace = true, features = ["serde"] } -alloy-rpc-types-engine.workspace = true +alloy-rpc-types-engine = { workspace = true, features = ["serde"] } alloy-primitives.workspace = true # ssz diff --git a/crates/rpc-types-beacon/src/relay.rs b/crates/rpc-types-beacon/src/relay.rs index d15feb8e3a8..5b1b056843b 100644 --- a/crates/rpc-types-beacon/src/relay.rs +++ b/crates/rpc-types-beacon/src/relay.rs @@ -6,6 +6,7 @@ use crate::{BlsPublicKey, BlsSignature}; use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types_engine::{ BlobsBundleV1, ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, + ExecutionPayloadV4, }; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; @@ -139,6 +140,22 @@ pub struct SignedBidSubmissionV3 { pub signature: BlsSignature, } +/// Submission for the `/relay/v1/builder/blocks` endpoint (Electra). +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +#[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))] +pub struct SignedBidSubmissionV4 { + /// The [`BidTrace`] message associated with the submission. + pub message: BidTrace, + /// The execution payload for the submission. + #[serde(with = "crate::payload::beacon_payload_v4")] + pub execution_payload: ExecutionPayloadV4, + /// The Electra block bundle for this bid. + pub blobs_bundle: BlobsBundleV1, + /// The signature associated with the submission. + pub signature: BlsSignature, +} + /// SubmitBlockRequest is the request from the builder to submit a block. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct SubmitBlockRequest { diff --git a/crates/rpc-types-beacon/src/sidecar.rs b/crates/rpc-types-beacon/src/sidecar.rs index 7ec97f9ecb3..9c2866771a2 100644 --- a/crates/rpc-types-beacon/src/sidecar.rs +++ b/crates/rpc-types-beacon/src/sidecar.rs @@ -10,7 +10,29 @@ use std::vec::IntoIter; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BeaconBlobBundle { /// Vec of individual blob data - data: Vec, + pub data: Vec, +} + +impl BeaconBlobBundle { + /// Creates a new [`BeaconBlobBundle`] from a given vector of [`BlobData`]. + pub const fn new(data: Vec) -> Self { + Self { data } + } + + /// Returns the number of blobs in the bundle. + pub fn len(&self) -> usize { + self.data.len() + } + + /// Returns if the bundle is empty. + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + /// Returns the blob with the given index. + pub fn get_blob(&self, index: u64) -> Option<&BlobData> { + self.data.iter().find(|blob| blob.index == index) + } } /// Yields an iterator for BlobData diff --git a/crates/rpc-types-debug/CHANGELOG.md b/crates/rpc-types-debug/CHANGELOG.md index 38460702304..c98c9d154af 100644 --- a/crates/rpc-types-debug/CHANGELOG.md +++ b/crates/rpc-types-debug/CHANGELOG.md @@ -5,12 +5,52 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- [rpc] Rename witness fields ([#1293](https://github.com/alloy-rs/alloy/issues/1293)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Features - [rpc-types] `debug_executionWitness` ([#1178](https://github.com/alloy-rs/alloy/issues/1178)) +### Miscellaneous Tasks + +- Release 0.3.0 + [`alloy`]: https://crates.io/crates/alloy [alloy]: https://crates.io/crates/alloy [`alloy-core`]: https://crates.io/crates/alloy-core diff --git a/crates/rpc-types-debug/src/debug.rs b/crates/rpc-types-debug/src/debug.rs index f1cbf76f44b..ea3c1d770db 100644 --- a/crates/rpc-types-debug/src/debug.rs +++ b/crates/rpc-types-debug/src/debug.rs @@ -9,9 +9,12 @@ use std::collections::HashMap; pub struct ExecutionWitness { /// Map of all hashed trie nodes to their preimages that were required during the execution of /// the block, including during state root recomputation. - pub witness: HashMap, - /// Map of all hashed account addresses and storage slots to their preimages (unhashed account - /// addresses and storage slots, respectively) that were required during the execution of the - /// block. during the execution of the block. - pub state_preimages: Option>, + /// keccak(rlp(node)) => rlp(node) + pub state: HashMap, + /// Map of all hashed account and storage keys (addresses and slots) to their preimages + /// (unhashed account addresses and storage slots, respectively) that were required during + /// the execution of the block. during the execution of the block. + /// keccak(address|slot) => address|slot + #[serde(default)] + pub keys: Option>, } diff --git a/crates/rpc-types-engine/CHANGELOG.md b/crates/rpc-types-engine/CHANGELOG.md index 883cef7d143..d948ed5b068 100644 --- a/crates/rpc-types-engine/CHANGELOG.md +++ b/crates/rpc-types-engine/CHANGELOG.md @@ -5,6 +5,57 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- Add block num hash helper ([#1304](https://github.com/alloy-rs/alloy/issues/1304)) +- [rpc-types-beacon] `SignedBidSubmissionV4` ([#1303](https://github.com/alloy-rs/alloy/issues/1303)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Bug Fixes + +- Add missing conversion ([#1287](https://github.com/alloy-rs/alloy/issues/1287)) + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Features + +- [engine] Optional Serde ([#1283](https://github.com/alloy-rs/alloy/issues/1283)) +- [engine] No_std engine types ([#1268](https://github.com/alloy-rs/alloy/issues/1268)) + +### Miscellaneous Tasks + +- Release 0.3.4 +- Remove eth rpc types dep from engine types ([#1280](https://github.com/alloy-rs/alloy/issues/1280)) + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Features + +- [rpc-types-engine] Add forkchoice state zero helpers ([#1231](https://github.com/alloy-rs/alloy/issues/1231)) + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -23,6 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Clippy für docs ([#1194](https://github.com/alloy-rs/alloy/issues/1194)) - [dep] Feature gate jwt in engine types ([#1131](https://github.com/alloy-rs/alloy/issues/1131)) - Release 0.2.1 diff --git a/crates/rpc-types-engine/Cargo.toml b/crates/rpc-types-engine/Cargo.toml index 4d50ab3b6ff..d4b68d91764 100644 --- a/crates/rpc-types-engine/Cargo.toml +++ b/crates/rpc-types-engine/Cargo.toml @@ -22,30 +22,34 @@ workspace = true # ethereum alloy-rlp = { workspace = true, features = ["arrayvec", "derive"] } alloy-primitives = { workspace = true, features = ["rlp", "serde"] } -alloy-consensus = { workspace = true, features = ["std"] } -alloy-rpc-types-eth.workspace = true -alloy-serde.workspace = true +alloy-consensus = { workspace = true, features = ["serde"] } alloy-eips = { workspace = true, features = ["serde"] } +# misc +derive_more = { workspace = true, features = ["display"] } + +# serde +alloy-serde = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"], optional = true } + # ssz ethereum_ssz_derive = { workspace = true, optional = true } ethereum_ssz = { workspace = true, optional = true } -serde = { workspace = true, features = ["derive"] } -thiserror.workspace = true - # jsonrpsee jsonrpsee-types = { version = "0.24", optional = true } # jwt -jsonwebtoken = { version = "9.3.0", optional = true } rand = { workspace = true, optional = true } +jsonwebtoken = { version = "9.3.0", optional = true } [features] -default = ["jwt"] +default = ["jwt", "std", "serde"] +std = ["alloy-consensus/std", "derive_more/std"] +serde = ["dep:serde", "dep:alloy-serde"] jwt = ["dep:jsonwebtoken", "dep:rand"] jsonrpsee-types = ["dep:jsonrpsee-types"] -ssz = ["dep:ethereum_ssz", "dep:ethereum_ssz_derive", "alloy-eips/ssz"] +ssz = ["std", "dep:ethereum_ssz", "dep:ethereum_ssz_derive", "alloy-eips/ssz"] kzg = ["alloy-consensus/kzg"] [dev-dependencies] diff --git a/crates/rpc-types-engine/src/cancun.rs b/crates/rpc-types-engine/src/cancun.rs index e1809a1a049..28fc6ad0aa0 100644 --- a/crates/rpc-types-engine/src/cancun.rs +++ b/crates/rpc-types-engine/src/cancun.rs @@ -1,6 +1,8 @@ //! Contains types related to the Cancun hardfork that will be used by RPC to communicate with the //! beacon consensus engine. +use alloc::vec::Vec; + use alloy_primitives::B256; /// Fields introduced in `engine_newPayloadV3` that are not present in the `ExecutionPayload` RPC @@ -8,7 +10,8 @@ use alloy_primitives::B256; /// /// See also: /// -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct CancunPayloadFields { /// The parent beacon block root. pub parent_beacon_block_root: B256, diff --git a/crates/rpc-types-engine/src/forkchoice.rs b/crates/rpc-types-engine/src/forkchoice.rs index 781ae7b651b..a2af9b753f0 100644 --- a/crates/rpc-types-engine/src/forkchoice.rs +++ b/crates/rpc-types-engine/src/forkchoice.rs @@ -1,7 +1,6 @@ use super::{PayloadStatus, PayloadStatusEnum}; use crate::PayloadId; use alloy_primitives::B256; -use serde::{Deserialize, Serialize}; /// invalid forkchoice state error code. pub const INVALID_FORK_CHOICE_STATE_ERROR: i32 = -38002; @@ -19,8 +18,9 @@ pub const INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG: &str = "Invalid payload attribut pub type ForkChoiceUpdateResult = Result; /// This structure encapsulates the fork choice state -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ForkchoiceState { /// Hash of the head block. pub head_block_hash: B256, @@ -30,26 +30,64 @@ pub struct ForkchoiceState { pub finalized_block_hash: B256, } +impl ForkchoiceState { + /// Returns the `head_block_hash`, only if it is not [`B256::ZERO`], otherwise this returns + /// [`None`]. + #[inline] + pub fn state_head_hash(&self) -> Option { + if self.head_block_hash.is_zero() { + None + } else { + Some(self.head_block_hash) + } + } + + /// Returns the `safe_block_hash`, only if it is not [`B256::ZERO`], otherwise this returns + /// [`None`]. + #[inline] + pub fn state_safe_hash(&self) -> Option { + if self.safe_block_hash.is_zero() { + None + } else { + Some(self.safe_block_hash) + } + } + + /// Returns the `finalized_block_hash`, only if it is not [`B256::ZERO`], otherwise this + /// returns [`None`]. + #[inline] + pub fn state_finalized_hash(&self) -> Option { + if self.finalized_block_hash.is_zero() { + None + } else { + Some(self.finalized_block_hash) + } + } +} + /// A standalone forkchoice update errors for RPC. /// /// These are considered hard RPC errors and are _not_ returned as [PayloadStatus] or /// [PayloadStatusEnum::Invalid]. -#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, derive_more::Display)] pub enum ForkchoiceUpdateError { /// The forkchoice update has been processed, but the requested contained invalid /// [PayloadAttributes](crate::PayloadAttributes). /// /// This is returned as an error because the payload attributes are invalid and the payload is not valid, See - #[error("invalid payload attributes")] + #[display("invalid payload attributes")] UpdatedInvalidPayloadAttributes, /// The given [ForkchoiceState] is invalid or inconsistent. - #[error("invalid forkchoice state")] + #[display("invalid forkchoice state")] InvalidState, /// Thrown when a forkchoice final block does not exist in the database. - #[error("final block not available in database")] + #[display("final block not available in database")] UnknownFinalBlock, } +#[cfg(feature = "std")] +impl std::error::Error for ForkchoiceUpdateError {} + #[cfg(feature = "jsonrpsee-types")] impl From for jsonrpsee_types::error::ErrorObject<'static> { fn from(value: ForkchoiceUpdateError) -> Self { @@ -78,8 +116,9 @@ impl From for jsonrpsee_types::error::ErrorObject<'static /// Represents a successfully _processed_ forkchoice state update. /// /// Note: this can still be INVALID if the provided payload was invalid. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ForkchoiceUpdated { /// Represents the outcome of the validation of the payload, independently of the payload being /// valid or not. diff --git a/crates/rpc-types-engine/src/identification.rs b/crates/rpc-types-engine/src/identification.rs index 0dcdc04ab46..beefb5a4ed7 100644 --- a/crates/rpc-types-engine/src/identification.rs +++ b/crates/rpc-types-engine/src/identification.rs @@ -1,11 +1,12 @@ //! Client identification: -use serde::{Deserialize, Serialize}; -use std::str::FromStr; +use alloc::string::{String, ToString}; +use core::str::FromStr; /// This enum defines a standard for specifying a client with just two letters. Clients teams which /// have a code reserved in this list MUST use this code when identifying themselves. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ClientCode { /// Besu BU, @@ -93,15 +94,16 @@ impl FromStr for ClientCode { } } -impl std::fmt::Display for ClientCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for ClientCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", self.as_str()) } } /// Contains information which identifies a client implementation. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ClientVersionV1 { /// Client code, e.g. GE for Geth pub code: ClientCode, @@ -119,6 +121,7 @@ mod tests { use super::*; #[test] + #[cfg(feature = "serde")] fn client_id_serde() { let s = r#"{"code":"RH","name":"Reth","version":"v1.10.8","commit":"fa4ff922"}"#; let v: ClientVersionV1 = serde_json::from_str(s).unwrap(); diff --git a/crates/rpc-types-engine/src/jwt.rs b/crates/rpc-types-engine/src/jwt.rs index 4e29de7fca9..768ee132da1 100644 --- a/crates/rpc-types-engine/src/jwt.rs +++ b/crates/rpc-types-engine/src/jwt.rs @@ -1,53 +1,53 @@ //! JWT (JSON Web Token) utilities for the Engine API. +use alloc::{format, string::String}; use alloy_primitives::hex; +use core::{str::FromStr, time::Duration}; use jsonwebtoken::{ decode, errors::ErrorKind, get_current_timestamp, Algorithm, DecodingKey, Validation, }; use rand::Rng; -use serde::{Deserialize, Serialize}; +#[cfg(feature = "std")] use std::{ fs, io, path::{Path, PathBuf}, - str::FromStr, - time::Duration, }; -use thiserror::Error; /// Errors returned by the [`JwtSecret`] -#[derive(Error, Debug)] +#[derive(Debug, derive_more::Display)] pub enum JwtError { /// An error encountered while decoding the hexadecimal string for the JWT secret. - #[error(transparent)] - JwtSecretHexDecodeError(#[from] hex::FromHexError), + #[display("{_0}")] + JwtSecretHexDecodeError(hex::FromHexError), /// The JWT key length provided is invalid, expecting a specific length. - #[error("JWT key is expected to have a length of {0} digits. {1} digits key provided")] + #[display("JWT key is expected to have a length of {_0} digits. {_1} digits key provided")] InvalidLength(usize, usize), /// The signature algorithm used in the JWT is not supported. Only HS256 is supported. - #[error("unsupported signature algorithm. Only HS256 is supported")] + #[display("unsupported signature algorithm. Only HS256 is supported")] UnsupportedSignatureAlgorithm, /// The provided signature in the JWT is invalid. - #[error("provided signature is invalid")] + #[display("provided signature is invalid")] InvalidSignature, /// The "iat" (issued-at) claim in the JWT is not within the allowed ±60 seconds from the /// current time. - #[error("IAT (issued-at) claim is not within ±60 seconds from the current time")] + #[display("IAT (issued-at) claim is not within ±60 seconds from the current time")] InvalidIssuanceTimestamp, /// The Authorization header is missing or invalid in the context of JWT validation. - #[error("Authorization header is missing or invalid")] + #[display("Authorization header is missing or invalid")] MissingOrInvalidAuthorizationHeader, /// An error occurred during JWT decoding. - #[error("JWT decoding error: {0}")] + #[display("JWT decoding error: {_0}")] JwtDecodingError(String), /// An error occurred while creating a directory to store the JWT. - #[error("failed to create dir {path:?}: {source}")] + #[display("failed to create dir {path:?}: {source}")] + #[cfg(feature = "std")] CreateDir { /// The source `io::Error`. source: io::Error, @@ -56,7 +56,8 @@ pub enum JwtError { }, /// An error occurred while reading the JWT from a file. - #[error("failed to read from {path:?}: {source}")] + #[display("failed to read from {path:?}: {source}")] + #[cfg(feature = "std")] Read { /// The source `io::Error`. source: io::Error, @@ -65,7 +66,8 @@ pub enum JwtError { }, /// An error occurred while writing the JWT to a file. - #[error("failed to write to {path:?}: {source}")] + #[display("failed to write to {path:?}: {source}")] + #[cfg(feature = "std")] Write { /// The source `io::Error`. source: io::Error, @@ -74,6 +76,25 @@ pub enum JwtError { }, } +impl From for JwtError { + fn from(err: hex::FromHexError) -> Self { + Self::JwtSecretHexDecodeError(err) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for JwtError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::JwtSecretHexDecodeError(err) => Some(err), + Self::CreateDir { source, .. } => Some(source), + Self::Read { source, .. } => Some(source), + Self::Write { source, .. } => Some(source), + _ => None, + } + } +} + /// Length of the hex-encoded 256 bit secret key. /// A 256-bit encoded string in Rust has a length of 64 digits because each digit represents 4 bits /// of data. In hexadecimal representation, each digit can have 16 possible values (0-9 and A-F), so @@ -95,7 +116,8 @@ const JWT_SIGNATURE_ALGO: Algorithm = Algorithm::HS256; /// /// The Engine API spec requires that just the `iat` (issued-at) claim is provided. /// It ignores claims that are optional or additional for this specification. -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Claims { /// The "iat" value MUST be a number containing a NumericDate value. /// According to the RFC A NumericDate represents the number of seconds since @@ -161,6 +183,7 @@ impl JwtSecret { /// Tries to load a [`JwtSecret`] from the specified file path. /// I/O or secret validation errors might occur during read operations in the form of /// a [`JwtError`]. + #[cfg(feature = "std")] pub fn from_file(fpath: &Path) -> Result { let hex = fs::read_to_string(fpath) .map_err(|err| JwtError::Read { source: err, path: fpath.into() })?; @@ -170,6 +193,7 @@ impl JwtSecret { /// Creates a random [`JwtSecret`] and tries to store it at the specified path. I/O errors might /// occur during write operations in the form of a [`JwtError`] + #[cfg(feature = "std")] pub fn try_create_random(fpath: &Path) -> Result { if let Some(dir) = fpath.parent() { // Create parent directory @@ -191,6 +215,7 @@ impl JwtSecret { /// - The JWT `exp` (expiration time) claim is validated by default if defined. /// /// See also: [JWT Claims - Engine API specs](https://github.com/ethereum/execution-apis/blob/main/src/engine/authentication.md#jwt-claims) + #[cfg(feature = "serde")] pub fn validate(&self, jwt: &str) -> Result<(), JwtError> { // Create a new validation object with the required signature algorithm // and ensure that the `iat` claim is present. The `exp` claim is validated if defined. @@ -226,6 +251,7 @@ impl JwtSecret { /// Encode the header and claims given and sign the payload using the algorithm from the header /// and the key. + #[cfg(feature = "serde")] pub fn encode(&self, claims: &Claims) -> Result { let bytes = &self.0; let key = jsonwebtoken::EncodingKey::from_secret(bytes); @@ -234,8 +260,8 @@ impl JwtSecret { } } -impl std::fmt::Debug for JwtSecret { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for JwtSecret { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_tuple("JwtSecretHash").field(&"{{}}").finish() } } @@ -253,7 +279,8 @@ mod tests { use super::*; use assert_matches::assert_matches; use jsonwebtoken::{encode, EncodingKey, Header}; - use std::time::{SystemTime, UNIX_EPOCH}; + #[cfg(feature = "std")] + use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tempfile::tempdir; #[test] @@ -306,6 +333,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn validation_ok() { let secret = JwtSecret::random(); let claims = Claims { iat: get_current_timestamp(), exp: Some(10000000000) }; @@ -317,6 +345,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn validation_with_current_time_ok() { let secret = JwtSecret::random(); let claims = Claims::default(); @@ -328,6 +357,7 @@ mod tests { } #[test] + #[cfg(all(feature = "std", feature = "serde"))] fn validation_error_iat_out_of_window() { let secret = JwtSecret::random(); @@ -353,6 +383,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn validation_error_exp_expired() { let secret = JwtSecret::random(); let claims = Claims { iat: get_current_timestamp(), exp: Some(1) }; @@ -364,6 +395,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn validation_error_wrong_signature() { let secret_1 = JwtSecret::random(); let claims = Claims { iat: get_current_timestamp(), exp: Some(10000000000) }; @@ -376,6 +408,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn validation_error_unsupported_algorithm() { let secret = JwtSecret::random(); let bytes = &secret.0; @@ -391,6 +424,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn valid_without_exp_claim() { let secret = JwtSecret::random(); @@ -403,6 +437,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn ephemeral_secret_created() { let fpath: &Path = Path::new("secret0.hex"); assert!(fs::metadata(fpath).is_err()); @@ -412,6 +447,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn valid_secret_provided() { let fpath = Path::new("secret1.hex"); assert!(fs::metadata(fpath).is_err()); @@ -431,6 +467,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn invalid_hex_provided() { let fpath = Path::new("secret2.hex"); fs::write(fpath, "invalid hex").unwrap(); @@ -440,6 +477,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn provided_file_not_exists() { let fpath = Path::new("secret3.hex"); let result = JwtSecret::from_file(fpath); @@ -448,12 +486,14 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn provided_file_is_a_directory() { let dir = tempdir().unwrap(); let result = JwtSecret::from_file(dir.path()); assert_matches!(result, Err(JwtError::Read {source: _,path}) if path == dir.into_path()); } + #[cfg(feature = "std")] fn to_u64(time: SystemTime) -> u64 { time.duration_since(UNIX_EPOCH).unwrap().as_secs() } diff --git a/crates/rpc-types-engine/src/lib.rs b/crates/rpc-types-engine/src/lib.rs index ef9e6fd932d..8bdaced5719 100644 --- a/crates/rpc-types-engine/src/lib.rs +++ b/crates/rpc-types-engine/src/lib.rs @@ -5,19 +5,29 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; mod cancun; +pub use cancun::*; + mod forkchoice; +pub use forkchoice::*; + mod identification; +pub use identification::*; + #[cfg(feature = "jwt")] mod jwt; -pub mod payload; -mod transition; +#[cfg(feature = "jwt")] +pub use jwt::*; -pub use self::{cancun::*, forkchoice::*, identification::*, payload::*, transition::*}; +pub mod payload; +pub use payload::*; -#[cfg(feature = "jwt")] -pub use self::jwt::*; +mod transition; +pub use transition::*; #[doc(inline)] pub use alloy_eips::eip6110::DepositRequest as DepositRequestV1; diff --git a/crates/rpc-types-engine/src/payload.rs b/crates/rpc-types-engine/src/payload.rs index 338bd0572df..86c030f9503 100644 --- a/crates/rpc-types-engine/src/payload.rs +++ b/crates/rpc-types-engine/src/payload.rs @@ -1,12 +1,16 @@ //! Payload types. + +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; use alloy_consensus::{Blob, Bytes48}; use alloy_eips::{ - eip6110::DepositRequest, eip7002::WithdrawalRequest, eip7251::ConsolidationRequest, + eip4844::BlobTransactionSidecar, eip4895::Withdrawal, eip6110::DepositRequest, + eip7002::WithdrawalRequest, eip7251::ConsolidationRequest, BlockNumHash, }; use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256}; -use alloy_rpc_types_eth::{transaction::BlobTransactionSidecar, Withdrawal}; -use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt; +use core::iter::{FromIterator, IntoIterator}; /// The execution payload body response that allows for `null` values. pub type ExecutionPayloadBodiesV1 = Vec>; @@ -15,7 +19,8 @@ pub type ExecutionPayloadBodiesV1 = Vec>; pub type ExecutionPayloadBodiesV2 = Vec>; /// And 8-byte identifier for an execution payload. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PayloadId(pub B64); // === impl PayloadId === @@ -27,8 +32,8 @@ impl PayloadId { } } -impl fmt::Display for PayloadId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl core::fmt::Display for PayloadId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.0.fmt(f) } } @@ -44,8 +49,9 @@ impl fmt::Display for PayloadId { /// /// See: /// -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] pub enum ExecutionPayloadFieldV2 { /// V1 payload V1(ExecutionPayloadV1), @@ -64,14 +70,15 @@ impl ExecutionPayloadFieldV2 { } /// This is the input to `engine_newPayloadV2`, which may or may not have a withdrawals field. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase", deny_unknown_fields))] pub struct ExecutionPayloadInputV2 { /// The V1 execution payload - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] pub execution_payload: ExecutionPayloadV1, /// The payload withdrawals - #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub withdrawals: Option>, } @@ -80,8 +87,9 @@ pub struct ExecutionPayloadInputV2 { /// /// See also: /// -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ExecutionPayloadEnvelopeV2 { /// Execution payload, which could be either V1 or V2 /// @@ -107,8 +115,9 @@ impl ExecutionPayloadEnvelopeV2 { /// /// See also: /// -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ExecutionPayloadEnvelopeV3 { /// Execution payload V3 pub execution_payload: ExecutionPayloadV3, @@ -126,8 +135,9 @@ pub struct ExecutionPayloadEnvelopeV3 { /// /// See also: /// -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ExecutionPayloadEnvelopeV4 { /// Execution payload V4 pub execution_payload: ExecutionPayloadV4, @@ -143,9 +153,10 @@ pub struct ExecutionPayloadEnvelopeV4 { /// This structure maps on the ExecutionPayload structure of the beacon chain spec. /// /// See also: -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ExecutionPayloadV1 { /// The parent hash of the block. pub parent_hash: B256, @@ -160,16 +171,16 @@ pub struct ExecutionPayloadV1 { /// The previous randao of the block. pub prev_randao: B256, /// The block number. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub block_number: u64, /// The gas limit of the block. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub gas_limit: u64, /// The gas used of the block. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub gas_used: u64, /// The timestamp of the block. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub timestamp: u64, /// The extra data of the block. pub extra_data: Bytes, @@ -181,14 +192,22 @@ pub struct ExecutionPayloadV1 { pub transactions: Vec, } +impl ExecutionPayloadV1 { + /// Returns the block number and hash as a [`BlockNumHash`]. + pub const fn block_num_hash(&self) -> BlockNumHash { + BlockNumHash::new(self.block_number, self.block_hash) + } +} + /// This structure maps on the ExecutionPayloadV2 structure of the beacon chain spec. /// /// See also: -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase", deny_unknown_fields))] pub struct ExecutionPayloadV2 { /// Inner V1 payload - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] pub payload_inner: ExecutionPayloadV1, /// Array of [`Withdrawal`] enabled with V2 @@ -297,20 +316,21 @@ impl ssz::Encode for ExecutionPayloadV2 { /// This structure maps on the ExecutionPayloadV3 structure of the beacon chain spec. /// /// See also: -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ExecutionPayloadV3 { /// Inner V2 payload - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] pub payload_inner: ExecutionPayloadV2, /// Array of hex [`u64`] representing blob gas used, enabled with V3 /// See - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub blob_gas_used: u64, /// Array of hex[`u64`] representing excess blob gas, enabled with V3 /// See - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub excess_blob_gas: u64, } @@ -430,11 +450,12 @@ impl ssz::Encode for ExecutionPayloadV3 { /// /// This structure has the syntax of ExecutionPayloadV3 and appends the new fields: depositRequests /// and withdrawalRequests. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ExecutionPayloadV4 { /// Inner V3 payload - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] pub payload_inner: ExecutionPayloadV3, /// Array of deposit requests. /// @@ -462,8 +483,121 @@ impl ExecutionPayloadV4 { } } +#[cfg(feature = "ssz")] +impl ssz::Decode for ExecutionPayloadV4 { + fn is_ssz_fixed_len() -> bool { + false + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + let mut builder = ssz::SszDecoderBuilder::new(bytes); + + builder.register_type::()?; + builder.register_type::
()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::>()?; + builder.register_type::>()?; + builder.register_type::()?; + builder.register_type::()?; + builder.register_type::>()?; + builder.register_type::>()?; + builder.register_type::>()?; + + let mut decoder = builder.build()?; + + Ok(Self { + payload_inner: ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + parent_hash: decoder.decode_next()?, + fee_recipient: decoder.decode_next()?, + state_root: decoder.decode_next()?, + receipts_root: decoder.decode_next()?, + logs_bloom: decoder.decode_next()?, + prev_randao: decoder.decode_next()?, + block_number: decoder.decode_next()?, + gas_limit: decoder.decode_next()?, + gas_used: decoder.decode_next()?, + timestamp: decoder.decode_next()?, + extra_data: decoder.decode_next()?, + base_fee_per_gas: decoder.decode_next()?, + block_hash: decoder.decode_next()?, + transactions: decoder.decode_next()?, + }, + withdrawals: decoder.decode_next()?, + }, + blob_gas_used: decoder.decode_next()?, + excess_blob_gas: decoder.decode_next()?, + }, + deposit_requests: decoder.decode_next()?, + withdrawal_requests: decoder.decode_next()?, + consolidation_requests: decoder.decode_next()?, + }) + } +} + +#[cfg(feature = "ssz")] +impl ssz::Encode for ExecutionPayloadV4 { + fn is_ssz_fixed_len() -> bool { + false + } + + fn ssz_append(&self, buf: &mut Vec) { + let offset = ::ssz_fixed_len() * 5 + +
::ssz_fixed_len() + + ::ssz_fixed_len() + + ::ssz_fixed_len() * 6 + + ::ssz_fixed_len() + + ssz::BYTES_PER_LENGTH_OFFSET * 6; + + let mut encoder = ssz::SszEncoder::container(buf, offset); + + encoder.append(&self.payload_inner.payload_inner.payload_inner.parent_hash); + encoder.append(&self.payload_inner.payload_inner.payload_inner.fee_recipient); + encoder.append(&self.payload_inner.payload_inner.payload_inner.state_root); + encoder.append(&self.payload_inner.payload_inner.payload_inner.receipts_root); + encoder.append(&self.payload_inner.payload_inner.payload_inner.logs_bloom); + encoder.append(&self.payload_inner.payload_inner.payload_inner.prev_randao); + encoder.append(&self.payload_inner.payload_inner.payload_inner.block_number); + encoder.append(&self.payload_inner.payload_inner.payload_inner.gas_limit); + encoder.append(&self.payload_inner.payload_inner.payload_inner.gas_used); + encoder.append(&self.payload_inner.payload_inner.payload_inner.timestamp); + encoder.append(&self.payload_inner.payload_inner.payload_inner.extra_data); + encoder.append(&self.payload_inner.payload_inner.payload_inner.base_fee_per_gas); + encoder.append(&self.payload_inner.payload_inner.payload_inner.block_hash); + encoder.append(&self.payload_inner.payload_inner.payload_inner.transactions); + encoder.append(&self.payload_inner.payload_inner.withdrawals); + encoder.append(&self.payload_inner.blob_gas_used); + encoder.append(&self.payload_inner.excess_blob_gas); + encoder.append(&self.deposit_requests); + encoder.append(&self.withdrawal_requests); + encoder.append(&self.consolidation_requests); + + encoder.finalize(); + } + + fn ssz_bytes_len(&self) -> usize { + ::ssz_bytes_len(&self.payload_inner) + + ssz::BYTES_PER_LENGTH_OFFSET * 3 + + self.deposit_requests.ssz_bytes_len() + + self.withdrawal_requests.ssz_bytes_len() + + self.consolidation_requests.ssz_bytes_len() + } +} + /// This includes all bundled blob related data of an executed payload. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BlobsBundleV1 { /// All commitments in the bundle. pub commitments: Vec, @@ -589,8 +723,9 @@ impl FromIterator for BlobsBundleV1 { /// An execution payload, which can be either [ExecutionPayloadV1], [ExecutionPayloadV2], or /// [ExecutionPayloadV3]. -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] -#[serde(untagged)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] pub enum ExecutionPayload { /// V1 payload V1(ExecutionPayloadV1), @@ -715,6 +850,11 @@ impl ExecutionPayload { self.as_v1().block_number } + /// Returns the block number for this payload. + pub const fn block_num_hash(&self) -> BlockNumHash { + self.as_v1().block_num_hash() + } + /// Returns the prev randao for this payload. pub const fn prev_randao(&self) -> B256 { self.as_v1().prev_randao @@ -746,12 +886,13 @@ impl From for ExecutionPayload { } // Deserializes untagged ExecutionPayload by trying each variant in falling order -impl<'de> Deserialize<'de> for ExecutionPayload { +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for ExecutionPayload { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de>, + D: serde::Deserializer<'de>, { - #[derive(Deserialize)] + #[derive(serde::Deserialize)] #[serde(untagged)] enum ExecutionPayloadDesc { V4(ExecutionPayloadV4), @@ -769,58 +910,58 @@ impl<'de> Deserialize<'de> for ExecutionPayload { } /// Error that can occur when handling payloads. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, derive_more::Display)] pub enum PayloadError { /// Invalid payload extra data. - #[error("invalid payload extra data: {0}")] + #[display("invalid payload extra data: {_0}")] ExtraData(Bytes), /// Invalid payload base fee. - #[error("invalid payload base fee: {0}")] + #[display("invalid payload base fee: {_0}")] BaseFee(U256), /// Invalid payload blob gas used. - #[error("invalid payload blob gas used: {0}")] + #[display("invalid payload blob gas used: {_0}")] BlobGasUsed(U256), /// Invalid payload excess blob gas. - #[error("invalid payload excess blob gas: {0}")] + #[display("invalid payload excess blob gas: {_0}")] ExcessBlobGas(U256), /// withdrawals present in pre-shanghai payload. - #[error("withdrawals present in pre-shanghai payload")] + #[display("withdrawals present in pre-shanghai payload")] PreShanghaiBlockWithWitdrawals, /// withdrawals missing in post-shanghai payload. - #[error("withdrawals missing in post-shanghai payload")] + #[display("withdrawals missing in post-shanghai payload")] PostShanghaiBlockWithoutWitdrawals, /// blob transactions present in pre-cancun payload. - #[error("blob transactions present in pre-cancun payload")] + #[display("blob transactions present in pre-cancun payload")] PreCancunBlockWithBlobTransactions, /// blob gas used present in pre-cancun payload. - #[error("blob gas used present in pre-cancun payload")] + #[display("blob gas used present in pre-cancun payload")] PreCancunBlockWithBlobGasUsed, /// excess blob gas present in pre-cancun payload. - #[error("excess blob gas present in pre-cancun payload")] + #[display("excess blob gas present in pre-cancun payload")] PreCancunBlockWithExcessBlobGas, /// cancun fields present in pre-cancun payload. - #[error("cancun fields present in pre-cancun payload")] + #[display("cancun fields present in pre-cancun payload")] PreCancunWithCancunFields, /// blob transactions missing in post-cancun payload. - #[error("blob transactions missing in post-cancun payload")] + #[display("blob transactions missing in post-cancun payload")] PostCancunBlockWithoutBlobTransactions, /// blob gas used missing in post-cancun payload. - #[error("blob gas used missing in post-cancun payload")] + #[display("blob gas used missing in post-cancun payload")] PostCancunBlockWithoutBlobGasUsed, /// excess blob gas missing in post-cancun payload. - #[error("excess blob gas missing in post-cancun payload")] + #[display("excess blob gas missing in post-cancun payload")] PostCancunBlockWithoutExcessBlobGas, /// cancun fields missing in post-cancun payload. - #[error("cancun fields missing in post-cancun payload")] + #[display("cancun fields missing in post-cancun payload")] PostCancunWithoutCancunFields, /// blob transactions present in pre-prague payload. - #[error("eip 7702 transactions present in pre-prague payload")] + #[display("eip 7702 transactions present in pre-prague payload")] PrePragueBlockWithEip7702Transactions, /// requests present in pre-prague payload. - #[error("requests present in pre-prague payload")] + #[display("requests present in pre-prague payload")] PrePragueBlockRequests, /// Invalid payload block hash. - #[error("block hash mismatch: want {consensus}, got {execution}")] + #[display("block hash mismatch: want {consensus}, got {execution}")] BlockHash { /// The block hash computed from the payload. execution: B256, @@ -828,11 +969,27 @@ pub enum PayloadError { consensus: B256, }, /// Expected blob versioned hashes do not match the given transactions. - #[error("expected blob versioned hashes do not match the given transactions")] + #[display("expected blob versioned hashes do not match the given transactions")] InvalidVersionedHashes, /// Encountered decoding error. - #[error(transparent)] - Decode(#[from] alloy_rlp::Error), + #[display("{_0}")] + Decode(alloy_rlp::Error), +} + +#[cfg(feature = "std")] +impl std::error::Error for PayloadError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Decode(err) => Some(err), + _ => None, + } + } +} + +impl From for PayloadError { + fn from(value: alloy_rlp::Error) -> Self { + Self::Decode(value) + } } impl PayloadError { @@ -852,7 +1009,8 @@ impl PayloadError { /// This structure contains a body of an execution payload. /// /// See also: -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ExecutionPayloadBodyV1 { /// Enveloped encoded transactions. pub transactions: Vec, @@ -866,8 +1024,9 @@ pub struct ExecutionPayloadBodyV1 { /// depositRequests and withdrawalRequests. /// /// See also: -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ExecutionPayloadBodyV2 { /// Enveloped encoded transactions. pub transactions: Vec, @@ -891,11 +1050,12 @@ pub struct ExecutionPayloadBodyV2 { /// This structure contains the attributes required to initiate a payload build process in the /// context of an `engine_forkchoiceUpdated` call. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct PayloadAttributes { /// Value for the `timestamp` field of the new payload - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub timestamp: u64, /// Value for the `prevRandao` field of the new payload pub prev_randao: B256, @@ -903,21 +1063,22 @@ pub struct PayloadAttributes { pub suggested_fee_recipient: Address, /// Array of [`Withdrawal`] enabled with V2 /// See - #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub withdrawals: Option>, /// Root of the parent beacon block enabled with V3. /// /// See also - #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub parent_beacon_block_root: Option, } /// This structure contains the result of processing a payload or fork choice update. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct PayloadStatus { /// The status of the payload. - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] pub status: PayloadStatusEnum, /// Hash of the most recent valid block in the branch defined by payload and its ancestors pub latest_valid_hash: Option, @@ -962,8 +1123,8 @@ impl PayloadStatus { } } -impl fmt::Display for PayloadStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl core::fmt::Display for PayloadStatus { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "PayloadStatus {{ status: {}, latestValidHash: {:?} }}", @@ -972,11 +1133,13 @@ impl fmt::Display for PayloadStatus { } } -impl Serialize for PayloadStatus { +#[cfg(feature = "serde")] +impl serde::Serialize for PayloadStatus { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { + use serde::ser::SerializeMap; let mut map = serializer.serialize_map(Some(3))?; map.serialize_entry("status", self.status.as_str())?; map.serialize_entry("latestValidHash", &self.latest_valid_hash)?; @@ -992,8 +1155,9 @@ impl From for PayloadStatusEnum { } /// Represents the status response of a payload. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE"))] pub enum PayloadStatusEnum { /// VALID is returned by the engine API in the following calls: /// - newPayload: if the payload was already known or was just validated and executed @@ -1005,7 +1169,7 @@ pub enum PayloadStatusEnum { /// - forkchoiceUpdate: if the new head is unknown, pre-merge, or reorg to it fails Invalid { /// The error message for the invalid payload. - #[serde(rename = "validationError")] + #[cfg_attr(feature = "serde", serde(rename = "validationError"))] validation_error: String, }, @@ -1054,8 +1218,8 @@ impl PayloadStatusEnum { } } -impl fmt::Display for PayloadStatusEnum { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl core::fmt::Display for PayloadStatusEnum { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Invalid { validation_error } => { f.write_str(self.as_str())?; @@ -1070,16 +1234,16 @@ impl fmt::Display for PayloadStatusEnum { /// Various errors that can occur when validating a payload or forkchoice update. /// /// This is intended for the [PayloadStatusEnum::Invalid] variant. -#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, derive_more::Display)] pub enum PayloadValidationError { /// Thrown when a forkchoice update's head links to a previously rejected payload. - #[error("links to previously rejected block")] + #[display("links to previously rejected block")] LinksToRejectedPayload, /// Thrown when a new payload contains a wrong block number. - #[error("invalid block number")] + #[display("invalid block number")] InvalidBlockNumber, /// Thrown when a new payload contains a wrong state root - #[error("invalid merkle root: (remote: {remote:?} local: {local:?})")] + #[display("invalid merkle root: (remote: {remote:?} local: {local:?})")] InvalidStateRoot { /// The state root of the payload we received from remote (CL) remote: B256, @@ -1088,11 +1252,16 @@ pub enum PayloadValidationError { }, } +#[cfg(feature = "std")] +impl std::error::Error for PayloadValidationError {} + #[cfg(test)] mod tests { use super::*; + use alloc::vec; #[test] + #[cfg(feature = "serde")] fn serde_payload_status() { let s = r#"{"status":"SYNCING","latestValidHash":null,"validationError":null}"#; let status: PayloadStatus = serde_json::from_str(s).unwrap(); @@ -1111,6 +1280,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_payload_status_error_deserialize() { let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"Failed to decode block"}"#; let q = PayloadStatus { @@ -1159,6 +1329,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_roundtrip_legacy_txs_payload_v1() { // pulled from hive tests let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x44bb4b98c59dbb726f96ffceb5ee028dcbe35b9bba4f9ffd56aeebf8d1e4db62","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0x5655011482546f16b2312ef18e9fad03d6a52b1be95401aea884b222477f9e64","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"]}"#; @@ -1170,6 +1341,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_roundtrip_legacy_txs_payload_v3() { // pulled from hive tests - modified with 4844 fields let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x44bb4b98c59dbb726f96ffceb5ee028dcbe35b9bba4f9ffd56aeebf8d1e4db62","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0x5655011482546f16b2312ef18e9fad03d6a52b1be95401aea884b222477f9e64","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"],"withdrawals":[],"blobGasUsed":"0xb10b","excessBlobGas":"0xb10b"}"#; @@ -1181,6 +1353,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_roundtrip_enveloped_txs_payload_v1() { // pulled from hive tests let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x76a03cbcb7adce07fd284c61e4fa31e5e786175cefac54a29e46ec8efa28ea41","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x028111cb7d25918386a69656b3d17b2febe95fd0f11572c1a55c14f99fdfe3df","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0xa6f40ed042e61e88e76125dede8fff8026751ea14454b68fb534cea99f2b2a77","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"]}"#; @@ -1192,6 +1365,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_roundtrip_enveloped_txs_payload_v3() { // pulled from hive tests - modified with 4844 fields let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x76a03cbcb7adce07fd284c61e4fa31e5e786175cefac54a29e46ec8efa28ea41","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x028111cb7d25918386a69656b3d17b2febe95fd0f11572c1a55c14f99fdfe3df","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0xa6f40ed042e61e88e76125dede8fff8026751ea14454b68fb534cea99f2b2a77","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"],"withdrawals":[],"blobGasUsed":"0xb10b","excessBlobGas":"0xb10b"}"#; @@ -1203,6 +1377,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_roundtrip_execution_payload_envelope_v3() { // pulled from a geth response getPayloadV3 in hive tests let response = r#"{"executionPayload":{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[],"blobGasUsed":"0x0","excessBlobGas":"0x0"},"blockValue":"0x0","blobsBundle":{"commitments":[],"proofs":[],"blobs":[]},"shouldOverrideBuilder":false}"#; @@ -1211,6 +1386,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_deserialize_execution_payload_input_v2() { let response = r#" { @@ -1271,6 +1447,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_deserialize_v3_with_unknown_fields() { let input = r#" { @@ -1354,6 +1531,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_deserialize_v2_input_with_blob_fields() { let input = r#" { @@ -1385,6 +1563,7 @@ mod tests { // #[test] + #[cfg(feature = "serde")] fn deserialize_op_base_payload() { let payload = r#"{"parentHash":"0x24e8df372a61cdcdb1a163b52aaa1785e0c869d28c3b742ac09e826bbb524723","feeRecipient":"0x4200000000000000000000000000000000000011","stateRoot":"0x9a5db45897f1ff1e620a6c14b0a6f1b3bcdbed59f2adc516a34c9a9d6baafa71","receiptsRoot":"0x8af6f74835d47835deb5628ca941d00e0c9fd75585f26dabdcb280ec7122e6af","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xf37b24eeff594848072a05f74c8600001706c83e489a9132e55bf43a236e42ec","blockNumber":"0xe3d5d8","gasLimit":"0x17d7840","gasUsed":"0xb705","timestamp":"0x65a118c0","extraData":"0x","baseFeePerGas":"0x7a0ff32","blockHash":"0xf5c147b2d60a519b72434f0a8e082e18599021294dd9085d7597b0ffa638f1c0","withdrawals":[],"transactions":["0x7ef90159a05ba0034ffdcb246703298224564720b66964a6a69d0d7e9ffd970c546f7c048094deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b90104015d8eb900000000000000000000000000000000000000000000000000000000009e1c4a0000000000000000000000000000000000000000000000000000000065a11748000000000000000000000000000000000000000000000000000000000000000a4b479e5fa8d52dd20a8a66e468b56e993bdbffcccf729223aabff06299ab36db000000000000000000000000000000000000000000000000000000000000000400000000000000000000000073b4168cc87f35cc239200a20eb841cded23493b000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"]}"#; let _payload = serde_json::from_str::(payload).unwrap(); diff --git a/crates/rpc-types-engine/src/transition.rs b/crates/rpc-types-engine/src/transition.rs index a63cdf3f596..0e2aedfc121 100644 --- a/crates/rpc-types-engine/src/transition.rs +++ b/crates/rpc-types-engine/src/transition.rs @@ -1,9 +1,9 @@ use alloy_primitives::{B256, U256}; -use serde::{Deserialize, Serialize}; /// This structure contains configurable settings of the transition process. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[doc(alias = "TxConfiguration")] pub struct TransitionConfiguration { /// Maps on the TERMINAL_TOTAL_DIFFICULTY parameter of EIP-3675 @@ -11,6 +11,6 @@ pub struct TransitionConfiguration { /// Maps on TERMINAL_BLOCK_HASH parameter of EIP-3675 pub terminal_block_hash: B256, /// Maps on TERMINAL_BLOCK_NUMBER parameter of EIP-3675 - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub terminal_block_number: u64, } diff --git a/crates/rpc-types-eth/CHANGELOG.md b/crates/rpc-types-eth/CHANGELOG.md index 1075abc6343..6c9f56103b4 100644 --- a/crates/rpc-types-eth/CHANGELOG.md +++ b/crates/rpc-types-eth/CHANGELOG.md @@ -5,6 +5,72 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Bug Fixes + +- [types-eth] Optional Alloy Serde ([#1284](https://github.com/alloy-rs/alloy/issues/1284)) +- `eth_simulateV1` ([#1289](https://github.com/alloy-rs/alloy/issues/1289)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Bug Fixes + +- `debug_traceCallMany` and `trace_callMany` ([#1278](https://github.com/alloy-rs/alloy/issues/1278)) +- Serde for `eth_simulateV1` ([#1273](https://github.com/alloy-rs/alloy/issues/1273)) + +### Features + +- [alloy-rpc-types-eth] Optional serde ([#1276](https://github.com/alloy-rs/alloy/issues/1276)) +- No_std eth rpc types ([#1252](https://github.com/alloy-rs/alloy/issues/1252)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +### Other + +- Add trait methods `cumulative_gas_used` and `state_root` to `ReceiptResponse` ([#1275](https://github.com/alloy-rs/alloy/issues/1275)) + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 +- Require destination for 7702 ([#1262](https://github.com/alloy-rs/alloy/issues/1262)) + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Bug Fixes + +- [consensus] Remove Unused Alloc Vecs ([#1250](https://github.com/alloy-rs/alloy/issues/1250)) + +### Features + +- No_std network primitives ([#1248](https://github.com/alloy-rs/alloy/issues/1248)) +- [rpc-types-eth] AnyBlock ([#1243](https://github.com/alloy-rs/alloy/issues/1243)) +- [network-primitives] Expose more fields via block response traits ([#1229](https://github.com/alloy-rs/alloy/issues/1229)) + +### Miscellaneous Tasks + +- Release 0.3.2 + +### Other + +- Add getter trait methods to `ReceiptResponse` ([#1251](https://github.com/alloy-rs/alloy/issues/1251)) + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -40,6 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Rm Rich type ([#1195](https://github.com/alloy-rs/alloy/issues/1195)) - Clippy für docs ([#1194](https://github.com/alloy-rs/alloy/issues/1194)) - Remove RichBlock and RichHeader types ([#1185](https://github.com/alloy-rs/alloy/issues/1185)) diff --git a/crates/rpc-types-eth/Cargo.toml b/crates/rpc-types-eth/Cargo.toml index ed2a0c10c52..ff2fdabb53a 100644 --- a/crates/rpc-types-eth/Cargo.toml +++ b/crates/rpc-types-eth/Cargo.toml @@ -19,19 +19,23 @@ rustdoc-args = ["--cfg", "docsrs"] workspace = true [dependencies] +alloy-eips.workspace = true +alloy-consensus.workspace = true +alloy-network-primitives.workspace = true alloy-rlp = { workspace = true, features = ["arrayvec", "derive"] } -alloy-primitives = { workspace = true, features = ["rlp", "serde", "std"] } -alloy-serde.workspace = true +alloy-primitives = { workspace = true, features = ["rlp"] } -alloy-consensus = { workspace = true, features = ["std", "serde"] } -alloy-eips = { workspace = true, features = ["std", "serde"] } +itertools.workspace = true +derive_more = { workspace = true, features = ["display"] } -alloy-network-primitives.workspace = true +# serde +alloy-serde = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"], optional = true } +serde_json = { workspace = true, optional = true } -itertools.workspace = true -serde = { workspace = true, features = ["derive"] } -serde_json.workspace = true -thiserror.workspace = true +# `no_std` compatibility +cfg-if.workspace = true +hashbrown = { workspace = true, features = ["serde"] } # arbitrary arbitrary = { version = "1.3", features = ["derive"], optional = true } @@ -56,7 +60,11 @@ similar-asserts.workspace = true assert_matches.workspace = true [features] +default = ["std", "serde"] +std = ["alloy-primitives/std", "alloy-consensus/std", "alloy-eips/std"] +serde = ["dep:serde", "dep:serde_json", "dep:alloy-serde", "alloy-primitives/serde", "alloy-consensus/serde", "alloy-eips/serde"] arbitrary = [ + "std", "dep:arbitrary", "alloy-primitives/arbitrary", "alloy-serde/arbitrary", diff --git a/crates/rpc-types-eth/src/account.rs b/crates/rpc-types-eth/src/account.rs index 439e24dd5a3..54e8651a39d 100644 --- a/crates/rpc-types-eth/src/account.rs +++ b/crates/rpc-types-eth/src/account.rs @@ -1,23 +1,26 @@ use alloy_primitives::{Address, Bytes, B256, B512, U256}; -use alloy_serde::storage::JsonStorageKey; -use serde::{Deserialize, Serialize}; + +use alloc::{string::String, vec::Vec}; // re-export account type for `eth_getAccount` pub use alloy_consensus::Account; /// Account information. -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct AccountInfo { /// Account name pub name: String, } /// Data structure with proof for one single storage-entry -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg(feature = "serde")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct EIP1186StorageProof { /// Storage key. - pub key: JsonStorageKey, + pub key: alloy_serde::storage::JsonStorageKey, /// Value that the key holds pub value: U256, /// proof for the pair @@ -25,8 +28,10 @@ pub struct EIP1186StorageProof { } /// Response for EIP-1186 account proof `eth_getProof` -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg(feature = "serde")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct EIP1186AccountProofResponse { /// The account address. pub address: Address, @@ -35,7 +40,7 @@ pub struct EIP1186AccountProofResponse { /// The hash of the code of the account. pub code_hash: B256, /// The account nonce. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub nonce: u64, /// The hash of the storage of the account. pub storage_hash: B256, @@ -46,22 +51,24 @@ pub struct EIP1186AccountProofResponse { } /// Extended account information (used by `parity_allAccountInfo`). -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ExtAccountInfo { /// Account name pub name: String, /// Account meta JSON pub meta: String, /// Account UUID (`None` for address book entries) - #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub uuid: Option, } /// account derived from a signature /// as well as information that tells if it is valid for /// the current chain -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct RecoveredAccount { /// address of the recovered account pub address: Address, @@ -74,6 +81,7 @@ pub struct RecoveredAccount { } #[test] +#[cfg(feature = "serde")] fn test_eip_1186_account_without_storage_proof() { let response = r#"{ "address":"0xc36442b4a4522e871399cd717abdd847ab11fe88", diff --git a/crates/rpc-types-eth/src/block.rs b/crates/rpc-types-eth/src/block.rs index 42474cdb549..ef47e3f1a42 100644 --- a/crates/rpc-types-eth/src/block.rs +++ b/crates/rpc-types-eth/src/block.rs @@ -1,13 +1,13 @@ //! Block RPC types. use crate::{ConversionError, Transaction, Withdrawal}; +use alloc::collections::BTreeMap; use alloy_network_primitives::{ BlockResponse, BlockTransactions, HeaderResponse, TransactionResponse, }; use alloy_primitives::{Address, BlockHash, Bloom, Bytes, B256, B64, U256}; -use alloy_serde::WithOtherFields; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; + +use alloc::vec::Vec; pub use alloy_eips::{ calc_blob_gasprice, calc_excess_blob_gas, BlockHashOrNumber, BlockId, BlockNumHash, @@ -15,27 +15,31 @@ pub use alloy_eips::{ }; /// Block representation -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct Block { /// Header of the block. - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] pub header: H, /// Uncles' hashes. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub uncles: Vec, /// Block Transactions. In the case of an uncle block, this field is not included in RPC /// responses, and when deserialized, it will be set to [BlockTransactions::Uncle]. - #[serde( - default = "BlockTransactions::uncle", - skip_serializing_if = "BlockTransactions::is_uncle" + #[cfg_attr( + feature = "serde", + serde( + default = "BlockTransactions::uncle", + skip_serializing_if = "BlockTransactions::is_uncle" + ) )] pub transactions: BlockTransactions, /// Integer the size of this block in bytes. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub size: Option, /// Withdrawals in the block. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub withdrawals: Option>, } @@ -48,15 +52,16 @@ impl Block { /// Block header representation. #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct Header { /// Hash of the block pub hash: BlockHash, /// Hash of the parent pub parent_hash: B256, /// Hash of the uncles - #[serde(rename = "sha3Uncles")] + #[cfg_attr(feature = "serde", serde(rename = "sha3Uncles"))] pub uncles_hash: B256, /// Alias of `author` pub miner: Address, @@ -71,19 +76,19 @@ pub struct Header { /// Difficulty pub difficulty: U256, /// Block number - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub number: u64, /// Gas Limit - #[serde(default, with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))] pub gas_limit: u128, /// Gas Used - #[serde(default, with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))] pub gas_used: u128, /// Timestamp - #[serde(default, with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))] pub timestamp: u64, /// Total difficulty - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub total_difficulty: Option, /// Extra data pub extra_data: Bytes, @@ -98,28 +103,49 @@ pub struct Header { /// /// See also /// And - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub mix_hash: Option, /// Nonce - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub nonce: Option, /// Base fee per unit of gas (if past London) - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub base_fee_per_gas: Option, /// Withdrawals root hash added by EIP-4895 and is ignored in legacy headers. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub withdrawals_root: Option, /// Blob gas used - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub blob_gas_used: Option, /// Excess blob gas - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub excess_blob_gas: Option, /// EIP-4788 parent beacon block root - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub parent_beacon_block_root: Option, /// EIP-7685 requests root. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub requests_root: Option, } @@ -228,69 +254,120 @@ impl HeaderResponse for Header { fn next_block_blob_fee(&self) -> Option { self.next_block_blob_fee() } + + fn coinbase(&self) -> Address { + self.miner + } + + fn gas_limit(&self) -> u128 { + self.gas_limit + } + + fn mix_hash(&self) -> Option { + self.mix_hash + } + + fn difficulty(&self) -> U256 { + self.difficulty + } } /// Error that can occur when converting other types to blocks -#[derive(Clone, Copy, Debug, thiserror::Error)] +#[derive(Clone, Copy, Debug, derive_more::Display)] pub enum BlockError { /// A transaction failed sender recovery - #[error("transaction failed sender recovery")] + #[display("transaction failed sender recovery")] InvalidSignature, /// A raw block failed to decode - #[error("failed to decode raw block {0}")] + #[display("failed to decode raw block {_0}")] RlpDecodeRawBlock(alloy_rlp::Error), } -impl From for WithOtherFields { +#[cfg(feature = "std")] +impl std::error::Error for BlockError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::RlpDecodeRawBlock(err) => Some(err), + _ => None, + } + } +} + +#[cfg(feature = "serde")] +impl From for alloy_serde::WithOtherFields { fn from(inner: Block) -> Self { Self { inner, other: Default::default() } } } -impl From
for WithOtherFields
{ +#[cfg(feature = "serde")] +impl From
for alloy_serde::WithOtherFields
{ fn from(inner: Header) -> Self { Self { inner, other: Default::default() } } } /// BlockOverrides is a set of header fields to override. -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default, rename_all = "camelCase", deny_unknown_fields)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(default, rename_all = "camelCase", deny_unknown_fields))] pub struct BlockOverrides { /// Overrides the block number. /// /// For `eth_callMany` this will be the block number of the first simulated block. Each /// following block increments its block number by 1 // Note: geth uses `number`, erigon uses `blockNumber` - #[serde(default, skip_serializing_if = "Option::is_none", alias = "blockNumber")] + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none", alias = "blockNumber") + )] pub number: Option, /// Overrides the difficulty of the block. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub difficulty: Option, /// Overrides the timestamp of the block. // Note: geth uses `time`, erigon uses `timestamp` - #[serde( - default, - skip_serializing_if = "Option::is_none", - alias = "timestamp", - with = "alloy_serde::quantity::opt" + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + alias = "timestamp", + with = "alloy_serde::quantity::opt" + ) )] pub time: Option, /// Overrides the gas limit of the block. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub gas_limit: Option, /// Overrides the coinbase address of the block. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none", alias = "feeRecipient") + )] pub coinbase: Option
, /// Overrides the prevrandao of the block. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none", alias = "prevRandao") + )] pub random: Option, /// Overrides the basefee of the block. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none", alias = "baseFeePerGas") + )] pub base_fee: Option, /// A dictionary that maps blockNumber to a user-defined hash. It could be queried from the /// solidity opcode BLOCKHASH. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub block_hash: Option>, } @@ -327,7 +404,7 @@ mod tests { } #[test] - #[cfg(feature = "jsonrpsee-types")] + #[cfg(all(feature = "jsonrpsee-types", feature = "serde"))] fn serde_json_header() { use jsonrpsee_types::SubscriptionResponse; let resp = r#"{"jsonrpc":"2.0","method":"eth_subscribe","params":{"subscription":"0x7eef37ff35d471f8825b1c8f67a5d3c0","result":{"hash":"0x7a7ada12e140961a32395059597764416499f4178daf1917193fad7bd2cc6386","parentHash":"0xdedbd831f496e705e7f2ec3c8dcb79051040a360bf1455dbd7eb8ea6ad03b751","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":"0x8","gasUsed":"0x0","gasLimit":"0x1c9c380","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x642aa48f","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000"}}}"#; @@ -338,6 +415,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_block() { let block = Block { header: Header { @@ -380,6 +458,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_uncle_block() { let block = Block { header: Header { @@ -422,6 +501,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_block_with_withdrawals_set_as_none() { let block = Block { header: Header { @@ -464,12 +544,14 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn block_overrides() { let s = r#"{"blockNumber": "0xe39dd0"}"#; let _overrides = serde_json::from_str::(s).unwrap(); } #[test] + #[cfg(feature = "serde")] fn serde_rich_block() { let s = r#"{ "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4", @@ -496,13 +578,15 @@ mod tests { "size": "0xaeb6" }"#; - let block = serde_json::from_str::>(s).unwrap(); + let block = serde_json::from_str::>(s).unwrap(); let serialized = serde_json::to_string(&block).unwrap(); - let block2 = serde_json::from_str::>(&serialized).unwrap(); + let block2 = + serde_json::from_str::>(&serialized).unwrap(); assert_eq!(block, block2); } #[test] + #[cfg(feature = "serde")] fn serde_missing_uncles_block() { let s = r#"{ "baseFeePerGas":"0x886b221ad", @@ -544,6 +628,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_block_containing_uncles() { let s = r#"{ "baseFeePerGas":"0x886b221ad", @@ -587,6 +672,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_empty_block() { let s = r#"{ "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4", @@ -617,6 +703,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn recompute_block_hash() { let s = r#"{ "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4", diff --git a/crates/rpc-types-eth/src/call.rs b/crates/rpc-types-eth/src/call.rs index 4f4d65c3370..1020894da57 100644 --- a/crates/rpc-types-eth/src/call.rs +++ b/crates/rpc-types-eth/src/call.rs @@ -1,14 +1,21 @@ use crate::{request::TransactionRequest, BlockId, BlockOverrides}; use alloy_primitives::Bytes; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; /// Bundle of transactions -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default, rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct Bundle { /// All transactions to execute pub transactions: Vec, /// Block overrides to apply + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub block_override: Option, } @@ -20,27 +27,29 @@ impl From> for Bundle { } /// State context for callMany -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default, rename_all = "camelCase")] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct StateContext { /// Block Number - #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub block_number: Option, /// Inclusive number of tx to replay in block. -1 means replay all - #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] #[doc(alias = "tx_index")] pub transaction_index: Option, } /// CallResponse for eth_callMany -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default, rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct EthCallResponse { /// eth_call output (if no error) - #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub value: Option, /// eth_call output (if error) - #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub error: Option, } @@ -90,10 +99,11 @@ impl From for TransactionIndex { } } -impl Serialize for TransactionIndex { +#[cfg(feature = "serde")] +impl serde::Serialize for TransactionIndex { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { match self { Self::All => serializer.serialize_i8(-1), @@ -102,10 +112,11 @@ impl Serialize for TransactionIndex { } } -impl<'de> Deserialize<'de> for TransactionIndex { +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for TransactionIndex { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de>, + D: serde::Deserializer<'de>, { match isize::deserialize(deserializer)? { -1 => Ok(Self::All), @@ -124,6 +135,7 @@ mod tests { use crate::BlockNumberOrTag; #[test] + #[cfg(feature = "serde")] fn transaction_index() { let s = "-1"; let idx = serde_json::from_str::(s).unwrap(); @@ -139,6 +151,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_state_context() { let s = r#"{"blockNumber":"pending"}"#; let state_context = serde_json::from_str::(s).unwrap(); @@ -148,6 +161,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_bundle() { let s = r#"{"transactions":[{"data":"0x70a08231000000000000000000000000000000dbc80bf780c6dc0ca16ed071b1f00cc000","to":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"}],"blockOverride":{"timestamp":1711546233}}"#; let bundle = serde_json::from_str::(s).unwrap(); @@ -156,6 +170,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn full_bundle() { // let s = r#"{"transactions":[{"from":"0x0000000000000011110000000000000000000000","to":"0x1100000000000000000000000000000000000000","value":"0x1111111","maxFeePerGas":"0x3a35294400","maxPriorityFeePerGas":"0x3b9aca00"}]}"#; diff --git a/crates/rpc-types-eth/src/erc4337.rs b/crates/rpc-types-eth/src/erc4337.rs index a4a3ea201bf..73529dc943e 100644 --- a/crates/rpc-types-eth/src/erc4337.rs +++ b/crates/rpc-types-eth/src/erc4337.rs @@ -1,39 +1,41 @@ -use crate::{Log, TransactionReceipt}; +use crate::{collections::HashMap, Log, TransactionReceipt}; use alloy_primitives::{Address, BlockNumber, Bytes, B256, U256}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; + +use alloc::vec::Vec; /// Options for conditional raw transaction submissions. // reference for the implementation // See also -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ConditionalOptions { /// A map of account addresses to their expected storage states. /// Each account can have a specified storage root or explicit slot-value pairs. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub known_accounts: HashMap, /// The minimal block number at which the transaction can be included. /// `None` indicates no minimum block number constraint. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub block_number_min: Option, /// The maximal block number at which the transaction can be included. /// `None` indicates no maximum block number constraint. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub block_number_max: Option, /// The minimal timestamp at which the transaction can be included. /// `None` indicates no minimum timestamp constraint. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub timestamp_min: Option, /// The maximal timestamp at which the transaction can be included. /// `None` indicates no maximum timestamp constraint. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub timestamp_max: Option, } /// Represents the expected state of an account for a transaction to be conditionally accepted. -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(untagged)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] pub enum AccountStorage { /// Expected storage root hash of the account. RootHash(B256), @@ -42,8 +44,9 @@ pub enum AccountStorage { } /// [`UserOperation`] in the spec: Entry Point V0.6 -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct UserOperation { /// The address of the smart contract account pub sender: Address, @@ -71,8 +74,9 @@ pub struct UserOperation { } /// [`PackedUserOperation`] in the spec: Entry Point V0.7 -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct PackedUserOperation { /// The account making the operation. pub sender: Address, @@ -112,7 +116,8 @@ pub struct PackedUserOperation { } /// Send User Operation -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum SendUserOperation { /// User Operation EntryPointV06(UserOperation), @@ -121,16 +126,18 @@ pub enum SendUserOperation { } /// Response to sending a user operation. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct SendUserOperationResponse { /// The hash of the user operation. pub user_op_hash: Bytes, } /// Represents the receipt of a user operation. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct UserOperationReceipt { /// The hash of the user operation. pub user_op_hash: Bytes, @@ -157,8 +164,9 @@ pub struct UserOperationReceipt { } /// Represents the gas estimation for a user operation. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct UserOperationGasEstimation { /// The gas limit for the pre-verification. pub pre_verification_gas: U256, diff --git a/crates/rpc-types-eth/src/fee.rs b/crates/rpc-types-eth/src/fee.rs index fb01699c4f9..d6222da37f6 100644 --- a/crates/rpc-types-eth/src/fee.rs +++ b/crates/rpc-types-eth/src/fee.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Serialize}; +use alloc::vec::Vec; /// Internal struct to calculate reward percentiles #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -11,13 +11,13 @@ pub struct TxGasAndReward { } impl PartialOrd for TxGasAndReward { - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for TxGasAndReward { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { // compare only the reward // see: // @@ -26,8 +26,9 @@ impl Ord for TxGasAndReward { } /// Response type for `eth_feeHistory` -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct FeeHistory { /// An array of block base fees per gas. /// This includes the next block after the newest of the returned range, @@ -37,7 +38,10 @@ pub struct FeeHistory { /// # Note /// /// Empty list is skipped only for compatibility with Erigon and Geth. - #[serde(default, skip_serializing_if = "Vec::is_empty", with = "alloy_serde::quantity::vec")] + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Vec::is_empty", with = "alloy_serde::quantity::vec") + )] pub base_fee_per_gas: Vec, /// An array of block gas used ratios. These are calculated as the ratio /// of `gasUsed` and `gasLimit`. @@ -45,21 +49,27 @@ pub struct FeeHistory { /// An array of block base fees per blob gas. This includes the next block after the newest /// of the returned range, because this value can be derived from the newest block. Zeroes /// are returned for pre-EIP-4844 blocks. - #[serde(default, skip_serializing_if = "Vec::is_empty", with = "alloy_serde::quantity::vec")] + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Vec::is_empty", with = "alloy_serde::quantity::vec") + )] pub base_fee_per_blob_gas: Vec, /// An array of block blob gas used ratios. These are calculated as the ratio of gasUsed and /// gasLimit. - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty"))] pub blob_gas_used_ratio: Vec, /// Lowest number block of the returned range. - #[serde(default, with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))] pub oldest_block: u64, /// An (optional) array of effective priority fee per gas data points from a single /// block. All zeroes are returned if the block is empty. - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::u128_vec_vec_opt_via_ruint" + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::u128_vec_vec_opt_via_ruint" + ) )] pub reward: Option>>, } @@ -108,6 +118,7 @@ mod tests { use similar_asserts::assert_eq; #[test] + #[cfg(feature = "serde")] fn test_fee_history_serde() { let sample = r#"{"baseFeePerGas":["0x342770c0","0x2da282a8"],"gasUsedRatio":[0.0],"baseFeePerBlobGas":["0x0","0x0"],"blobGasUsedRatio":[0.0],"oldestBlock":"0x1"}"#; let fee_history: FeeHistory = serde_json::from_str(sample).unwrap(); @@ -125,12 +136,14 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn test_fee_history_serde_2() { let json = r#"{"baseFeePerBlobGas":["0xc0","0xb2","0xab","0x98","0x9e","0x92","0xa4","0xb9","0xd0","0xea","0xfd"],"baseFeePerGas":["0x4cb8cf181","0x53075988e","0x4fb92ee18","0x45c209055","0x4e790dca2","0x58462e84e","0x5b7659f4e","0x5d66ea3aa","0x6283c6e45","0x5ecf0e1e5","0x5da59cf89"],"blobGasUsedRatio":[0.16666666666666666,0.3333333333333333,0,0.6666666666666666,0.16666666666666666,1,1,1,1,0.8333333333333334],"gasUsedRatio":[0.8288135,0.3407616666666667,0,0.9997232,0.999601,0.6444664333333333,0.5848306333333333,0.7189564,0.34952733333333336,0.4509799666666667],"oldestBlock":"0x59f94f","reward":[["0x59682f00"],["0x59682f00"],["0x0"],["0x59682f00"],["0x59682f00"],["0x3b9aca00"],["0x59682f00"],["0x59682f00"],["0x3b9aca00"],["0x59682f00"]]}"#; let _actual = serde_json::from_str::(json).unwrap(); } #[test] + #[cfg(feature = "serde")] fn test_fee_history_serde_3() { let json = r#"{"oldestBlock":"0xdee807","baseFeePerGas":["0x4ccf46253","0x4457de658","0x4531c5aee","0x3cfa33972","0x3d33403eb","0x399457884","0x40bdf9772","0x48d55e7c4","0x51e9ebf14","0x55f460bf9","0x4e31607e4"],"gasUsedRatio":[0.05909575012589385,0.5498182666666667,0.0249864,0.5146185,0.2633512,0.997582061117319,0.999914966153302,0.9986873805040722,0.6973219148223686,0.13879896448917434],"baseFeePerBlobGas":["0x0","0x0","0x0","0x0","0x0","0x0","0x0","0x0","0x0","0x0","0x0"],"blobGasUsedRatio":[0,0,0,0,0,0,0,0,0,0]}"#; let _actual = serde_json::from_str::(json).unwrap(); diff --git a/crates/rpc-types-eth/src/filter.rs b/crates/rpc-types-eth/src/filter.rs index ef9ecf1e963..f8e5706f4c3 100644 --- a/crates/rpc-types-eth/src/filter.rs +++ b/crates/rpc-types-eth/src/filter.rs @@ -1,20 +1,17 @@ use crate::{BlockNumberOrTag, Log as RpcLog, Transaction}; +use alloc::{format, string::String, vec::Vec}; use alloy_primitives::{keccak256, Address, BlockHash, Bloom, BloomInput, B256, U256, U64}; use itertools::{EitherOrBoth::*, Itertools}; -use serde::{ - de::{DeserializeOwned, MapAccess, Visitor}, - ser::SerializeStruct, - Deserialize, Deserializer, Serialize, Serializer, + +use crate::collections::{ + hash_set::{IntoIter, Iter}, + HashSet, }; -use std::{ - collections::{ - hash_set::{IntoIter, Iter}, - HashSet, - }, +use core::{ hash::Hash, + iter::{FromIterator, IntoIterator}, ops::{RangeFrom, RangeInclusive, RangeToInclusive}, }; -use thiserror::Error; /// Helper type to represent a bloom filter used for matching logs. #[derive(Debug, Default)] @@ -35,7 +32,8 @@ impl BloomFilter { } } -#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] /// FilterSet is a set of values that will be used to filter logs pub struct FilterSet(HashSet); @@ -46,7 +44,7 @@ impl From for FilterSet { } impl Hash for FilterSet { - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut H) { for value in &self.0 { value.hash(state); } @@ -154,10 +152,10 @@ impl From for Topic { } /// Represents errors that can occur when setting block filters in `FilterBlockOption`. -#[derive(Debug, PartialEq, Eq, Error)] +#[derive(Debug, PartialEq, Eq, derive_more::Display)] pub enum FilterBlockError { /// Error indicating that the `from_block` is greater than the `to_block`. - #[error("`from_block` ({from}) is greater than `to_block` ({to})")] + #[display("`from_block` ({from}) is greater than `to_block` ({to})")] FromBlockGreaterThanToBlock { /// The starting block number, which is greater than `to`. from: u64, @@ -166,6 +164,9 @@ pub enum FilterBlockError { }, } +#[cfg(feature = "std")] +impl std::error::Error for FilterBlockError {} + /// Represents the target range of blocks for the filter #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum FilterBlockOption { @@ -541,11 +542,14 @@ impl Filter { } } -impl Serialize for Filter { +#[cfg(feature = "serde")] +impl serde::Serialize for Filter { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { + use serde::ser::SerializeStruct; + let mut s = serializer.serialize_struct("Filter", 5)?; match self.block_option { FilterBlockOption::Range { from_block, to_block } => { @@ -583,23 +587,24 @@ impl Serialize for Filter { type RawAddressFilter = ValueOrArray>; type RawTopicsFilter = Vec>>>; -impl<'de> Deserialize<'de> for Filter { +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Filter { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de>, + D: serde::Deserializer<'de>, { struct FilterVisitor; - impl<'de> Visitor<'de> for FilterVisitor { + impl<'de> serde::de::Visitor<'de> for FilterVisitor { type Value = Filter; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { formatter.write_str("Filter object") } fn visit_map(self, mut map: A) -> Result where - A: MapAccess<'de>, + A: serde::de::MapAccess<'de>, { let mut from_block: Option> = None; let mut to_block: Option> = None; @@ -754,13 +759,14 @@ impl From> for ValueOrArray { } } -impl Serialize for ValueOrArray +#[cfg(feature = "serde")] +impl serde::Serialize for ValueOrArray where - T: Serialize, + T: serde::Serialize, { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { match self { Self::Value(inner) => inner.serialize(serializer), @@ -769,13 +775,14 @@ where } } -impl<'a, T> Deserialize<'a> for ValueOrArray +#[cfg(feature = "serde")] +impl<'a, T> serde::Deserialize<'a> for ValueOrArray where - T: DeserializeOwned, + T: serde::de::DeserializeOwned, { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'a>, + D: serde::Deserializer<'a>, { let value = serde_json::Value::deserialize(deserializer)?; @@ -783,7 +790,7 @@ where return Ok(Self::Array(Vec::new())); } - #[derive(Deserialize)] + #[derive(serde::Deserialize)] #[serde(untagged)] enum Variadic { Value(T), @@ -929,11 +936,12 @@ impl FilteredParams { } /// Response of the `eth_getFilterChanges` RPC. -#[derive(Default, Clone, Debug, PartialEq, Eq, Serialize)] -#[serde(untagged)] +#[derive(Default, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(untagged))] pub enum FilterChanges { /// Empty result. - #[serde(with = "empty_array")] + #[cfg_attr(feature = "serde", serde(with = "empty_array"))] #[default] Empty, /// New logs. @@ -1011,6 +1019,7 @@ impl FilterChanges { } } +#[cfg(feature = "serde")] mod empty_array { use serde::{Serialize, Serializer}; @@ -1022,15 +1031,16 @@ mod empty_array { } } -impl<'de, T> Deserialize<'de> for FilterChanges +#[cfg(feature = "serde")] +impl<'de, T> serde::Deserialize<'de> for FilterChanges where - T: Deserialize<'de>, + T: serde::Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de>, + D: serde::Deserializer<'de>, { - #[derive(Deserialize)] + #[derive(serde::Deserialize)] #[serde(untagged)] enum Changes { Hashes(Vec), @@ -1067,9 +1077,10 @@ where } /// Owned equivalent of a `SubscriptionId` -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +#[cfg_attr(feature = "serde", serde(untagged))] pub enum FilterId { /// Numeric id Num(u64), @@ -1123,13 +1134,14 @@ pub enum PendingTransactionFilterKind { Full, } -impl Serialize for PendingTransactionFilterKind { +#[cfg(feature = "serde")] +impl serde::Serialize for PendingTransactionFilterKind { /// Serializes the `PendingTransactionFilterKind` into a boolean value: /// - `false` for `Hashes` /// - `true` for `Full` fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { match self { Self::Hashes => false.serialize(serializer), @@ -1138,13 +1150,14 @@ impl Serialize for PendingTransactionFilterKind { } } -impl<'a> Deserialize<'a> for PendingTransactionFilterKind { +#[cfg(feature = "serde")] +impl<'a> serde::Deserialize<'a> for PendingTransactionFilterKind { /// Deserializes a boolean value into `PendingTransactionFilterKind`: /// - `false` becomes `Hashes` /// - `true` becomes `Full` fn deserialize(deserializer: D) -> Result where - D: Deserializer<'a>, + D: serde::Deserializer<'a>, { let val = Option::::deserialize(deserializer)?; match val { @@ -1159,11 +1172,13 @@ mod tests { use super::*; use serde_json::json; + #[cfg(feature = "serde")] fn serialize(t: &T) -> serde_json::Value { serde_json::to_value(t).expect("Failed to serialize value") } #[test] + #[cfg(feature = "serde")] fn test_empty_filter_topics_list() { let s = r#"{"fromBlock": "0xfc359e", "toBlock": "0xfc359e", "topics": [["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"], [], ["0x0000000000000000000000000c17e776cd218252adfca8d4e761d3fe757e9778"]]}"#; let filter = serde_json::from_str::(s).unwrap(); @@ -1210,6 +1225,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn test_block_hash() { let s = r#"{"blockHash":"0x58dc57ab582b282c143424bd01e8d923cddfdcda9455bad02a29522f6274a948"}"#; @@ -1225,6 +1241,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn test_filter_topics_middle_wildcard() { let s = r#"{"fromBlock": "0xfc359e", "toBlock": "0xfc359e", "topics": [["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"], [], [null, "0x0000000000000000000000000c17e776cd218252adfca8d4e761d3fe757e9778"]]}"#; let filter = serde_json::from_str::(s).unwrap(); @@ -1243,8 +1260,9 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn can_serde_value_or_array() { - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] + #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] struct Item { value: ValueOrArray, } @@ -1261,6 +1279,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn filter_serialization_test() { let t1 = "0000000000000000000000009729a6fbefefc8f6005933898b13dc45c3a2c8b7" .parse::() @@ -1500,6 +1519,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn can_convert_to_ethers_filter() { let json = json!( { @@ -1546,6 +1566,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn can_convert_to_ethers_filter_with_null_fields() { let json = json!( { diff --git a/crates/rpc-types-eth/src/index.rs b/crates/rpc-types-eth/src/index.rs index b1183c9dc15..322591e08e7 100644 --- a/crates/rpc-types-eth/src/index.rs +++ b/crates/rpc-types-eth/src/index.rs @@ -1,9 +1,6 @@ +use alloc::{format, string::String}; use alloy_primitives::U256; -use serde::{ - de::{Error, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; -use std::fmt; +use core::fmt; /// A hex encoded or decimal index that's intended to be used as a rust index, hence it's /// deserialized into a `usize`. @@ -28,23 +25,25 @@ impl From for Index { } } -impl Serialize for Index { +#[cfg(feature = "serde")] +impl serde::Serialize for Index { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { serializer.serialize_str(&format!("0x{:x}", self.0)) } } -impl<'a> Deserialize<'a> for Index { +#[cfg(feature = "serde")] +impl<'a> serde::Deserialize<'a> for Index { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'a>, + D: serde::Deserializer<'a>, { struct IndexVisitor; - impl<'a> Visitor<'a> for IndexVisitor { + impl<'a> serde::de::Visitor<'a> for IndexVisitor { type Value = Index; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -53,24 +52,26 @@ impl<'a> Deserialize<'a> for Index { fn visit_u64(self, value: u64) -> Result where - E: Error, + E: serde::de::Error, { Ok(Index(value as usize)) } fn visit_str(self, value: &str) -> Result where - E: Error, + E: serde::de::Error, { value.strip_prefix("0x").map_or_else( || { value.parse::().map(Index).map_err(|e| { - Error::custom(format!("Failed to parse numeric index: {e}")) + serde::de::Error::custom(format!("Failed to parse numeric index: {e}")) }) }, |val| { usize::from_str_radix(val, 16).map(Index).map_err(|e| { - Error::custom(format!("Failed to parse hex encoded index value: {e}")) + serde::de::Error::custom(format!( + "Failed to parse hex encoded index value: {e}" + )) }) }, ) @@ -78,7 +79,7 @@ impl<'a> Deserialize<'a> for Index { fn visit_string(self, value: String) -> Result where - E: Error, + E: serde::de::Error, { self.visit_str(value.as_ref()) } @@ -95,6 +96,7 @@ mod tests { use serde_json::json; #[test] + #[cfg(feature = "serde")] fn test_serde_index_rand() { let mut rng = thread_rng(); for _ in 0..100 { @@ -106,6 +108,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn test_serde_index_deserialization() { // Test decimal index let json_data = json!(42); diff --git a/crates/rpc-types-eth/src/lib.rs b/crates/rpc-types-eth/src/lib.rs index 049c2b15392..e6ab4033b56 100644 --- a/crates/rpc-types-eth/src/lib.rs +++ b/crates/rpc-types-eth/src/lib.rs @@ -5,6 +5,21 @@ )] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(any(test, feature = "std")), no_std)] + +extern crate alloc; + +/// Standardized collections across `std` and `no_std` environments. +pub mod collections { + cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + pub use std::collections::{hash_set, HashMap, HashSet}; + use hashbrown as _; + } else { + pub use hashbrown::{hash_set, HashMap, HashSet}; + } + } +} pub use alloy_eips::eip4895::Withdrawal; @@ -14,6 +29,13 @@ pub use account::*; mod block; pub use block::*; +#[cfg(feature = "serde")] +use alloy_serde::WithOtherFields; + +/// A catch-all block type for handling blocks on multiple networks. +#[cfg(feature = "serde")] +pub type AnyNetworkBlock = WithOtherFields>>; + pub use alloy_network_primitives::{ BlockTransactionHashes, BlockTransactions, BlockTransactionsKind, }; @@ -35,6 +57,7 @@ pub use index::Index; mod log; pub use log::*; +#[cfg(feature = "serde")] pub mod pubsub; mod raw_log; diff --git a/crates/rpc-types-eth/src/log.rs b/crates/rpc-types-eth/src/log.rs index b1c4973cbab..9c15b67230b 100644 --- a/crates/rpc-types-eth/src/log.rs +++ b/crates/rpc-types-eth/src/log.rs @@ -1,36 +1,43 @@ use alloy_primitives::{Address, BlockHash, LogData, TxHash, B256}; -use serde::{Deserialize, Serialize}; /// Ethereum Log emitted by a transaction -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[serde(rename_all = "camelCase")] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct Log { - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] /// Consensus log object pub inner: alloy_primitives::Log, /// Hash of the block the transaction that emitted this log was mined in pub block_hash: Option, /// Number of the block the transaction that emitted this log was mined in - #[serde(with = "alloy_serde::quantity::opt")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))] pub block_number: Option, /// The timestamp of the block as proposed in: /// /// - #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt", default)] + #[cfg_attr( + feature = "serde", + serde( + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt", + default + ) + )] pub block_timestamp: Option, /// Transaction Hash #[doc(alias = "tx_hash")] pub transaction_hash: Option, /// Index of the Transaction in the block - #[serde(with = "alloy_serde::quantity::opt")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))] #[doc(alias = "tx_index")] pub transaction_index: Option, /// Log Index in Block - #[serde(with = "alloy_serde::quantity::opt")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))] pub log_index: Option, /// Geth Compatibility Field: whether this log was removed - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub removed: bool, } @@ -161,6 +168,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_log() { let mut log = Log { inner: alloy_primitives::Log { diff --git a/crates/rpc-types-eth/src/pubsub.rs b/crates/rpc-types-eth/src/pubsub.rs index b5e2dc512e7..26606225504 100644 --- a/crates/rpc-types-eth/src/pubsub.rs +++ b/crates/rpc-types-eth/src/pubsub.rs @@ -1,13 +1,14 @@ //! Ethereum types for pub-sub use crate::{Filter, Header, Log, Transaction}; +use alloc::{boxed::Box, format}; use alloy_primitives::B256; use alloy_serde::WithOtherFields; -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; /// Subscription result. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] -#[serde(untagged)] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] pub enum SubscriptionResult { /// New block header. Header(Box>), @@ -22,8 +23,9 @@ pub enum SubscriptionResult { } /// Response type for a SyncStatus subscription. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(untagged))] pub enum PubSubSyncStatus { /// If not currently syncing, this should always be `false`. Simple(bool), @@ -32,8 +34,9 @@ pub enum PubSubSyncStatus { } /// Sync status metadata. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct SyncStatusMetadata { /// Whether the node is currently syncing. pub syncing: bool, @@ -42,17 +45,18 @@ pub struct SyncStatusMetadata { /// The current block. pub current_block: u64, /// The highest block. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub highest_block: Option, } -impl Serialize for SubscriptionResult +#[cfg(feature = "serde")] +impl serde::Serialize for SubscriptionResult where - T: Serialize, + T: serde::Serialize, { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { match *self { Self::Header(ref header) => header.serialize(serializer), @@ -65,9 +69,10 @@ where } /// Subscription kind. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub enum SubscriptionKind { /// New block headers subscription. /// @@ -125,10 +130,11 @@ impl Params { } } -impl Serialize for Params { +#[cfg(feature = "serde")] +impl serde::Serialize for Params { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { match self { Self::None => (&[] as &[serde_json::Value]).serialize(serializer), @@ -138,11 +144,14 @@ impl Serialize for Params { } } -impl<'a> Deserialize<'a> for Params { +#[cfg(feature = "serde")] +impl<'a> serde::Deserialize<'a> for Params { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'a>, + D: serde::Deserializer<'a>, { + use serde::de::Error; + let v = serde_json::Value::deserialize(deserializer)?; if v.is_null() { @@ -164,6 +173,7 @@ mod tests { use super::*; #[test] + #[cfg(feature = "serde")] fn params_serde() { let s: Params = serde_json::from_str("true").unwrap(); assert_eq!(s, Params::Bool(true)); diff --git a/crates/rpc-types-eth/src/raw_log.rs b/crates/rpc-types-eth/src/raw_log.rs index c1b3df43c47..53a13081d7e 100644 --- a/crates/rpc-types-eth/src/raw_log.rs +++ b/crates/rpc-types-eth/src/raw_log.rs @@ -3,6 +3,8 @@ use alloy_primitives::{Address, Bloom, Bytes, B256}; use alloy_rlp::{RlpDecodable, RlpEncodable}; +use alloc::vec::Vec; + /// Ethereum Log #[derive(Clone, Debug, Default, PartialEq, Eq, RlpDecodable, RlpEncodable)] pub struct Log { diff --git a/crates/rpc-types-eth/src/sidecars.rs b/crates/rpc-types-eth/src/sidecars.rs index 040cd7372b4..97902840301 100644 --- a/crates/rpc-types-eth/src/sidecars.rs +++ b/crates/rpc-types-eth/src/sidecars.rs @@ -2,30 +2,31 @@ use alloy_eips::eip4844::BlobTransactionSidecar; use alloy_primitives::B256; -use serde::{Deserialize, Serialize}; /// Block sidecar representation -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct BlockSidecar { /// Transaction sidecar. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub blob_sidecar: BlobTransactionSidecar, /// Block hash. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub block_hash: B256, /// Block number. - #[serde(default, with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))] pub block_number: u64, /// Transaction hash. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub tx_hash: B256, /// Transaction index. - #[serde(default, with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))] pub tx_index: u64, } #[test] +#[cfg(feature = "serde")] fn test_block_sidecar() { let block_sidecar = BlockSidecar { blob_sidecar: BlobTransactionSidecar::default(), diff --git a/crates/rpc-types-eth/src/simulate.rs b/crates/rpc-types-eth/src/simulate.rs index be61122456c..798f2ac0870 100644 --- a/crates/rpc-types-eth/src/simulate.rs +++ b/crates/rpc-types-eth/src/simulate.rs @@ -1,9 +1,8 @@ //! 'eth_simulateV1' Request / Response types: -use alloy_primitives::{Address, Bytes, Log, B256}; -use serde::{Deserialize, Serialize}; - -use crate::{state::StateOverride, BlockOverrides, TransactionRequest}; +use crate::{state::StateOverride, Block, BlockOverrides, Log, TransactionRequest}; +use alloc::{string::String, vec::Vec}; +use alloy_primitives::Bytes; /// The maximum number of blocks that can be simulated in a single request, pub const MAX_SIMULATE_BLOCKS: u64 = 256; @@ -11,69 +10,51 @@ pub const MAX_SIMULATE_BLOCKS: u64 = 256; /// Represents a batch of calls to be simulated sequentially within a block. /// This struct includes block and state overrides as well as the transaction requests to be /// executed. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct SimBlock { /// Modifications to the default block characteristics. - pub block_overrides: BlockOverrides, + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] + pub block_overrides: Option, /// State modifications to apply before executing the transactions. - pub state_overrides: StateOverride, + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] + pub state_overrides: Option, /// A vector of transactions to be simulated. pub calls: Vec, } + /// Represents the result of simulating a block. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SimulatedBlock { - /// The number of the block. - #[serde(with = "alloy_serde::quantity")] - pub number: u64, - /// The hash of the block. - pub hash: B256, - /// The timestamp of the block. - #[serde(with = "alloy_serde::quantity")] - pub timestamp: u64, - /// The gas limit of the block. - #[serde(with = "alloy_serde::quantity")] - pub gas_limit: u64, - /// The amount of gas used in the block. - #[serde(with = "alloy_serde::quantity")] - pub gas_used: u64, - /// The recipient of the block's fees. - pub fee_recipient: Address, - /// The base fee per gas unit for the block. - #[serde(with = "alloy_serde::quantity")] - pub base_fee_per_gas: u64, - /// The previous RANDAO value of the block. - pub prev_randao: B256, +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct SimulatedBlock { + /// The simulated block. + #[cfg_attr(feature = "serde", serde(flatten))] + pub inner: B, /// A vector of results for each call in the block. pub calls: Vec, } -/// The response type for the eth_simulateV1 method. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SimulateV1Response { - /// Simulated blocks vector. - pub simulated_blocks: Vec, -} + /// Captures the outcome of a transaction simulation. /// It includes the return value, logs produced, gas used, and the status of the transaction. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct SimCallResult { /// The raw bytes returned by the transaction. pub return_value: Bytes, /// Logs generated during the execution of the transaction. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub logs: Vec, /// The amount of gas used by the transaction. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub gas_used: u64, /// The final status of the transaction, typically indicating success or failure. - #[serde(with = "alloy_serde::quantity")] - pub status: u64, + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] + pub status: bool, /// Error in case the call failed - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub error: Option, } @@ -81,24 +62,27 @@ pub struct SimCallResult { /// /// This struct configures how simulations are executed, including whether to trace token transfers, /// validate transaction sequences, and whether to return full transaction objects. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct SimulatePayload { /// Array of block state calls to be executed at specific, optional block/state. pub block_state_calls: Vec, /// Flag to determine whether to trace ERC20/ERC721 token transfers within transactions. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub trace_transfers: bool, /// Flag to enable or disable validation of the transaction sequence in the blocks. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub validation: bool, /// Flag to decide if full transactions should be returned instead of just their hashes. + #[cfg_attr(feature = "serde", serde(default))] pub return_full_transactions: bool, } /// The error response returned by the `eth_simulateV1` method. -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct SimulateError { /// Code error /// -3200: Execution reverted @@ -115,6 +99,7 @@ mod tests { use serde_json::json; #[test] + #[cfg(feature = "serde")] fn test_eth_simulate_v1_account_not_precompile() { let request_json = json!({ "jsonrpc": "2.0", @@ -168,11 +153,21 @@ mod tests { assert_eq!(sim_opts.block_state_calls.len(), 2); let block_state_call_1 = &sim_opts.block_state_calls[0]; - assert!(block_state_call_1.state_overrides.contains_key(&address_1)); - assert_eq!(block_state_call_1.state_overrides.get(&address_1).unwrap().nonce.unwrap(), 5); + assert!(block_state_call_1.state_overrides.as_ref().unwrap().contains_key(&address_1)); + assert_eq!( + block_state_call_1 + .state_overrides + .as_ref() + .unwrap() + .get(&address_1) + .unwrap() + .nonce + .unwrap(), + 5 + ); let block_state_call_2 = &sim_opts.block_state_calls[1]; - assert!(block_state_call_2.state_overrides.contains_key(&address_1)); + assert!(block_state_call_2.state_overrides.as_ref().unwrap().contains_key(&address_1)); assert_eq!(block_state_call_2.calls.len(), 2); assert_eq!(block_state_call_2.calls[0].from.unwrap(), address_1); diff --git a/crates/rpc-types-eth/src/state.rs b/crates/rpc-types-eth/src/state.rs index 4746e5368da..8843edb06cf 100644 --- a/crates/rpc-types-eth/src/state.rs +++ b/crates/rpc-types-eth/src/state.rs @@ -1,34 +1,53 @@ //! bindings for state overrides in eth_call -use crate::BlockOverrides; +use crate::{collections::HashMap, BlockOverrides}; +use alloc::boxed::Box; use alloy_primitives::{Address, Bytes, B256, U256}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; /// A set of account overrides pub type StateOverride = HashMap; /// Custom account override used in call -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default, rename_all = "camelCase", deny_unknown_fields)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(default, rename_all = "camelCase", deny_unknown_fields))] pub struct AccountOverride { /// Fake balance to set for the account before executing the call. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub balance: Option, /// Fake nonce to set for the account before executing the call. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub nonce: Option, /// Fake EVM bytecode to inject into the account before executing the call. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub code: Option, /// Fake key-value mapping to override all slots in the account storage before executing the /// call. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub state: Option>, /// Fake key-value mapping to override individual slots in the account storage before executing /// the call. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub state_diff: Option>, + /// Moves addresses precompile into the specified address. This move is done before the 'code' + /// override is set. When the specified address is not a precompile, the behaviour is undefined + /// and different clients might behave differently. + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + rename = "movePrecompileToAddress" + ) + )] + pub move_precompile_to: Option
, } /// Helper type that bundles various overrides for EVM Execution. @@ -89,6 +108,17 @@ mod tests { use alloy_primitives::address; #[test] + fn test_default_account_override() { + let acc_override = AccountOverride::default(); + assert!(acc_override.balance.is_none()); + assert!(acc_override.nonce.is_none()); + assert!(acc_override.code.is_none()); + assert!(acc_override.state.is_none()); + assert!(acc_override.state_diff.is_none()); + } + + #[test] + #[cfg(feature = "serde")] #[should_panic(expected = "invalid type")] fn test_invalid_json_structure() { let invalid_json = r#"{ @@ -101,6 +131,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn test_large_values_in_override() { let large_values_json = r#"{ "0x1234567890123456789012345678901234567890": { @@ -117,16 +148,7 @@ mod tests { } #[test] - fn test_default_account_override() { - let acc_override = AccountOverride::default(); - assert!(acc_override.balance.is_none()); - assert!(acc_override.nonce.is_none()); - assert!(acc_override.code.is_none()); - assert!(acc_override.state.is_none()); - assert!(acc_override.state_diff.is_none()); - } - - #[test] + #[cfg(feature = "serde")] fn test_state_override() { let s = r#"{ "0x0000000000000000000000000000000000000124": { @@ -140,6 +162,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn test_state_override_state_diff() { let s = r#"{ "0x1b5212AF6b76113afD94cD2B5a78a73B7d7A8222": { diff --git a/crates/rpc-types-eth/src/syncing.rs b/crates/rpc-types-eth/src/syncing.rs index f2fda6fdf17..c61cc58c5ab 100644 --- a/crates/rpc-types-eth/src/syncing.rs +++ b/crates/rpc-types-eth/src/syncing.rs @@ -1,10 +1,10 @@ +use alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec}; use alloy_primitives::{B512, U256}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::collections::BTreeMap; /// Syncing info -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct SyncInfo { /// Starting block pub starting_block: U256, @@ -18,24 +18,26 @@ pub struct SyncInfo { pub warp_chunks_processed: Option, /// The details of the sync stages as an hashmap /// where the key is the name of the stage and the value is the block number. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub stages: Option>, } /// The detail of the sync stages. -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct Stage { /// The name of the sync stage. - #[serde(alias = "stage_name")] + #[cfg_attr(feature = "serde", serde(alias = "stage_name"))] pub name: String, /// Indicates the progress of the sync stage. - #[serde(alias = "block_number", with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(alias = "block_number", with = "alloy_serde::quantity"))] pub block: u64, } /// Peers info -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Peers { /// Number of active peers pub active: usize, @@ -48,7 +50,8 @@ pub struct Peers { } /// Peer connection information -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PeerInfo { /// Public node id pub id: Option, @@ -63,8 +66,9 @@ pub struct PeerInfo { } /// Peer network information -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct PeerNetworkInfo { /// Remote endpoint address pub remote_address: String, @@ -73,17 +77,19 @@ pub struct PeerNetworkInfo { } /// Peer protocols information -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PeerProtocolsInfo { /// Ethereum protocol information pub eth: Option, /// PIP protocol information. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub pip: Option, } /// Peer Ethereum protocol information -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PeerEthProtocolInfo { /// Negotiated ethereum protocol version pub version: u32, @@ -94,7 +100,8 @@ pub struct PeerEthProtocolInfo { } /// Peer PIP protocol information -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PipProtocolInfo { /// Negotiated PIP protocol version pub version: u32, @@ -113,12 +120,13 @@ pub enum SyncStatus { None, } -impl<'de> Deserialize<'de> for SyncStatus { +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for SyncStatus { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de>, + D: serde::Deserializer<'de>, { - #[derive(Deserialize)] + #[derive(serde::Deserialize)] #[serde(untagged)] enum Syncing { /// When client is synced to the highest block, eth_syncing with return "false" @@ -136,10 +144,11 @@ impl<'de> Deserialize<'de> for SyncStatus { } } -impl Serialize for SyncStatus { +#[cfg(feature = "serde")] +impl serde::Serialize for SyncStatus { fn serialize(&self, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { match self { Self::Info(info) => info.serialize(serializer), @@ -149,8 +158,9 @@ impl Serialize for SyncStatus { } /// Propagation statistics for pending transaction. -#[derive(Clone, Debug, Default, Serialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[doc(alias = "TxStats")] pub struct TransactionStats { /// Block no this transaction was first seen. @@ -160,8 +170,9 @@ pub struct TransactionStats { } /// Chain status. -#[derive(Clone, Copy, Debug, Default, Serialize)] -#[serde(rename_all = "camelCase")] +#[derive(Clone, Copy, Debug, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ChainStatus { /// Describes the gap in the blockchain, if there is one: (first, last) pub block_gap: Option<(U256, U256)>, diff --git a/crates/rpc-types-eth/src/transaction/error.rs b/crates/rpc-types-eth/src/transaction/error.rs index b285cbb48a4..fdd90502512 100644 --- a/crates/rpc-types-eth/src/transaction/error.rs +++ b/crates/rpc-types-eth/src/transaction/error.rs @@ -1,73 +1,103 @@ -use std::num::TryFromIntError; +use core::num::TryFromIntError; + +use alloc::string::String; /// Error variants when converting from [crate::Transaction] to [alloy_consensus::Signed] /// transaction. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, derive_more::Display)] pub enum ConversionError { /// Error during EIP-2718 transaction coding. - #[error(transparent)] - Eip2718Error(#[from] alloy_eips::eip2718::Eip2718Error), + #[display("{_0}")] + Eip2718Error(alloy_eips::eip2718::Eip2718Error), /// [`alloy_primitives::SignatureError`]. - #[error(transparent)] - SignatureError(#[from] alloy_primitives::SignatureError), + #[display("{_0}")] + SignatureError(alloy_primitives::SignatureError), /// Missing signature for transaction. - #[error("missing signature for transaction")] + #[display("missing signature for transaction")] MissingSignature, /// Missing y parity in signature. - #[error("missing y parity in signature")] + #[display("missing y parity in signature")] MissingYParity, /// Invalid signature - #[error("invalid signature")] + #[display("invalid signature")] InvalidSignature, /// Missing `chainId` field for EIP-1559 transaction. - #[error("missing `chainId` field for EIP-155 transaction")] + #[display("missing `chainId` field for EIP-155 transaction")] MissingChainId, /// Missing `gasPrice` field for Legacy transaction. - #[error("missing `gasPrice` field for Legacy transaction")] + #[display("missing `gasPrice` field for Legacy transaction")] MissingGasPrice, /// Missing `accessList` field for EIP-2930 transaction. - #[error("missing `accessList` field for EIP-2930 transaction")] + #[display("missing `accessList` field for EIP-2930 transaction")] MissingAccessList, /// Missing `maxFeePerGas` field for EIP-1559 transaction. - #[error("missing `maxFeePerGas` field for EIP-1559 transaction")] + #[display("missing `maxFeePerGas` field for EIP-1559 transaction")] MissingMaxFeePerGas, /// Missing `maxPriorityFeePerGas` field for EIP-1559 transaction. - #[error("missing `maxPriorityFeePerGas` field for EIP-1559 transaction")] + #[display("missing `maxPriorityFeePerGas` field for EIP-1559 transaction")] MissingMaxPriorityFeePerGas, /// Missing `maxFeePerBlobGas` field for EIP-1559 transaction. - #[error("missing `maxFeePerBlobGas` field for EIP-1559 transaction")] + #[display("missing `maxFeePerBlobGas` field for EIP-1559 transaction")] MissingMaxFeePerBlobGas, /// Missing `to` field for EIP-4844 transaction. - #[error("missing `to` field for EIP-4844 transaction")] + #[display("missing `to` field for EIP-4844 transaction")] MissingTo, /// Missing `blobVersionedHashes` field for EIP-4844 transaction. - #[error("missing `blobVersionedHashes` field for EIP-4844 transaction")] + #[display("missing `blobVersionedHashes` field for EIP-4844 transaction")] MissingBlobVersionedHashes, /// Missing `authorizationList` field for EIP-7702 transaction. - #[error("missing `authorizationList` field for EIP-7702 transaction")] + #[display("missing `authorizationList` field for EIP-7702 transaction")] MissingAuthorizationList, /// Missing full transactions required for block decoding - #[error("missing full transactions required for block decoding")] + #[display("missing full transactions required for block decoding")] MissingFullTransactions, /// Base fee per gas integer conversion error - #[error("base fee per gas integer conversion error: {0}")] + #[display("base fee per gas integer conversion error: {_0}")] BaseFeePerGasConversion(TryFromIntError), /// Gas limit integer conversion error - #[error("gas limit integer conversion error: {0}")] + #[display("gas limit integer conversion error: {_0}")] GasLimitConversion(TryFromIntError), /// Gas used integer conversion error - #[error("gas used integer conversion error: {0}")] + #[display("gas used integer conversion error: {_0}")] GasUsedConversion(TryFromIntError), /// Missing block number - #[error("missing block number")] + #[display("missing block number")] MissingBlockNumber, /// Blob gas used integer conversion error - #[error("blob gas used integer conversion error: {0}")] + #[display("blob gas used integer conversion error: {_0}")] BlobGasUsedConversion(TryFromIntError), /// Excess blob gas integer conversion error - #[error("excess blob gas integer conversion error: {0}")] + #[display("excess blob gas integer conversion error: {_0}")] ExcessBlobGasConversion(TryFromIntError), /// A custom Conversion Error that doesn't fit other categories. - #[error("conversion error: {0}")] + #[display("conversion error: {_0}")] Custom(String), } + +#[cfg(feature = "std")] +impl std::error::Error for ConversionError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Eip2718Error(err) => Some(err), + Self::SignatureError(err) => Some(err), + Self::BaseFeePerGasConversion(err) => Some(err), + Self::GasLimitConversion(err) => Some(err), + Self::GasUsedConversion(err) => Some(err), + Self::BlobGasUsedConversion(err) => Some(err), + Self::ExcessBlobGasConversion(err) => Some(err), + _ => None, + } + } +} + +impl From for ConversionError { + fn from(err: alloy_eips::eip2718::Eip2718Error) -> Self { + Self::Eip2718Error(err) + } +} + +impl From for ConversionError { + fn from(err: alloy_primitives::SignatureError) -> Self { + Self::SignatureError(err) + } +} diff --git a/crates/rpc-types-eth/src/transaction/mod.rs b/crates/rpc-types-eth/src/transaction/mod.rs index 1624bd21656..824f942f143 100644 --- a/crates/rpc-types-eth/src/transaction/mod.rs +++ b/crates/rpc-types-eth/src/transaction/mod.rs @@ -7,7 +7,8 @@ use alloy_consensus::{ use alloy_eips::eip7702::SignedAuthorization; use alloy_network_primitives::TransactionResponse; use alloy_primitives::{Address, BlockHash, Bytes, ChainId, TxHash, TxKind, B256, U256}; -use serde::{Deserialize, Serialize}; + +use alloc::vec::Vec; pub use alloy_consensus::BlobTransactionSidecar; pub use alloy_eips::{ @@ -22,7 +23,10 @@ mod error; pub use error::ConversionError; mod receipt; -pub use receipt::{AnyTransactionReceipt, TransactionReceipt}; +pub use receipt::TransactionReceipt; + +#[cfg(feature = "serde")] +pub use receipt::AnyTransactionReceipt; pub mod request; pub use request::{TransactionInput, TransactionRequest}; @@ -33,24 +37,25 @@ pub use signature::{Parity, Signature}; pub use alloy_consensus::{AnyReceiptEnvelope, Receipt, ReceiptEnvelope, ReceiptWithBloom}; /// Transaction object used in RPC -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[serde(rename_all = "camelCase")] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[doc(alias = "Tx")] pub struct Transaction { /// Hash pub hash: TxHash, /// Nonce - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub nonce: u64, /// Block hash - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub block_hash: Option, /// Block number - #[serde(default, with = "alloy_serde::quantity::opt")] + #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity::opt"))] pub block_number: Option, /// Transaction Index - #[serde(default, with = "alloy_serde::quantity::opt")] + #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity::opt"))] pub transaction_index: Option, /// Sender pub from: Address, @@ -59,54 +64,92 @@ pub struct Transaction { /// Transferred value pub value: U256, /// Gas Price - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub gas_price: Option, /// Gas amount - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub gas: u128, /// Max BaseFeePerGas the user is willing to pay. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub max_fee_per_gas: Option, /// The miner's tip. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub max_priority_fee_per_gas: Option, /// Configured max fee per blob gas for eip-4844 transactions - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub max_fee_per_blob_gas: Option, /// Data pub input: Bytes, /// All _flattened_ fields of the transaction signature. /// /// Note: this is an option so special transaction types without a signature (e.g. ) can be supported. - #[serde(flatten, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(flatten, skip_serializing_if = "Option::is_none"))] pub signature: Option, /// The chain id of the transaction, if any. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub chain_id: Option, /// Contains the blob hashes for eip-4844 transactions. - #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub blob_versioned_hashes: Option>, /// EIP2930 /// /// Pre-pay to warm storage access. - #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub access_list: Option, /// EIP2718 /// /// Transaction type, /// Some(4) for EIP-7702 transaction, Some(3) for EIP-4844 transaction, Some(2) for EIP-1559 /// transaction, Some(1) for AccessList transaction, None or Some(0) for Legacy - #[serde( - default, - rename = "type", - skip_serializing_if = "Option::is_none", - with = "alloy_serde::quantity::opt" + #[cfg_attr( + feature = "serde", + serde( + default, + rename = "type", + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) )] #[doc(alias = "tx_type")] pub transaction_type: Option, /// The signed authorization list is a list of tuples that store the address to code which the /// signer desires to execute in the context of their EOA and their signature. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub authorization_list: Option>, } @@ -269,7 +312,7 @@ impl TryFrom for Signed { max_priority_fee_per_gas: tx .max_priority_fee_per_gas .ok_or(ConversionError::MissingMaxPriorityFeePerGas)?, - to: tx.to.into(), + to: tx.to.ok_or(ConversionError::MissingTo)?, value: tx.value, access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, authorization_list: tx @@ -325,8 +368,8 @@ mod tests { use super::*; use alloy_primitives::Signature as AlloySignature; use arbitrary::Arbitrary; + use core::str::FromStr; use rand::Rng; - use std::str::FromStr; #[test] fn arbitrary_transaction() { @@ -337,6 +380,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_transaction() { let transaction = Transaction { hash: B256::with_last_byte(1), @@ -373,13 +417,14 @@ mod tests { let serialized = serde_json::to_string(&transaction).unwrap(); assert_eq!( serialized, - r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","nonce":"0x2","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000003","blockNumber":"0x4","transactionIndex":"0x5","from":"0x0000000000000000000000000000000000000006","to":"0x0000000000000000000000000000000000000007","value":"0x8","gasPrice":"0x9","gas":"0xa","maxFeePerGas":"0x15","maxPriorityFeePerGas":"0x16","input":"0x0b0c0d","r":"0xe","s":"0xe","v":"0xe","chainId":"0x11","type":"0x14","authorizationList":[{"chainId":"0x1","address":"0x0000000000000000000000000000000000000006","nonce":"0x1","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","v":27}]}"# + r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","nonce":"0x2","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000003","blockNumber":"0x4","transactionIndex":"0x5","from":"0x0000000000000000000000000000000000000006","to":"0x0000000000000000000000000000000000000007","value":"0x8","gasPrice":"0x9","gas":"0xa","maxFeePerGas":"0x15","maxPriorityFeePerGas":"0x16","input":"0x0b0c0d","r":"0xe","s":"0xe","v":"0xe","chainId":"0x11","type":"0x14","authorizationList":[{"chainId":"0x1","address":"0x0000000000000000000000000000000000000006","nonce":"0x1","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","v":"0x1b"}]}"# ); let deserialized: Transaction = serde_json::from_str(&serialized).unwrap(); assert_eq!(transaction, deserialized); } #[test] + #[cfg(feature = "serde")] fn serde_transaction_with_parity_bit() { let transaction = Transaction { hash: B256::with_last_byte(1), @@ -416,13 +461,14 @@ mod tests { let serialized = serde_json::to_string(&transaction).unwrap(); assert_eq!( serialized, - r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","nonce":"0x2","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000003","blockNumber":"0x4","transactionIndex":"0x5","from":"0x0000000000000000000000000000000000000006","to":"0x0000000000000000000000000000000000000007","value":"0x8","gasPrice":"0x9","gas":"0xa","maxFeePerGas":"0x15","maxPriorityFeePerGas":"0x16","input":"0x0b0c0d","r":"0xe","s":"0xe","v":"0xe","yParity":"0x1","chainId":"0x11","type":"0x14","authorizationList":[{"chainId":"0x1","address":"0x0000000000000000000000000000000000000006","nonce":"0x1","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","v":27}]}"# + r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","nonce":"0x2","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000003","blockNumber":"0x4","transactionIndex":"0x5","from":"0x0000000000000000000000000000000000000006","to":"0x0000000000000000000000000000000000000007","value":"0x8","gasPrice":"0x9","gas":"0xa","maxFeePerGas":"0x15","maxPriorityFeePerGas":"0x16","input":"0x0b0c0d","r":"0xe","s":"0xe","v":"0xe","yParity":"0x1","chainId":"0x11","type":"0x14","authorizationList":[{"chainId":"0x1","address":"0x0000000000000000000000000000000000000006","nonce":"0x1","r":"0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353","s":"0xefffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","v":"0x1b"}]}"# ); let deserialized: Transaction = serde_json::from_str(&serialized).unwrap(); assert_eq!(transaction, deserialized); } #[test] + #[cfg(feature = "serde")] fn serde_minimal_transaction() { let transaction = Transaction { hash: B256::with_last_byte(1), @@ -443,6 +489,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn into_request_legacy() { // cast rpc eth_getTransactionByHash // 0xe9e91f1ee4b56c0df2e9f06c2b8c27c6076195a88a7b8537ba8313d80e6f124e --rpc-url mainnet @@ -455,6 +502,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn into_request_eip1559() { // cast rpc eth_getTransactionByHash // 0x0e07d8b53ed3d91314c80e53cf25bcde02084939395845cbb625b029d568135c --rpc-url mainnet diff --git a/crates/rpc-types-eth/src/transaction/receipt.rs b/crates/rpc-types-eth/src/transaction/receipt.rs index f9336f09c5c..48353e6fba3 100644 --- a/crates/rpc-types-eth/src/transaction/receipt.rs +++ b/crates/rpc-types-eth/src/transaction/receipt.rs @@ -3,49 +3,64 @@ use alloy_consensus::{AnyReceiptEnvelope, ReceiptEnvelope, TxReceipt, TxType}; use alloy_eips::eip7702::SignedAuthorization; use alloy_network_primitives::ReceiptResponse; use alloy_primitives::{Address, BlockHash, TxHash, B256}; -use alloy_serde::WithOtherFields; -use serde::{Deserialize, Serialize}; + +use alloc::vec::Vec; /// Transaction receipt /// /// This type is generic over an inner [`ReceiptEnvelope`] which contains /// consensus data and metadata. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[serde(rename_all = "camelCase")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[doc(alias = "TxReceipt")] pub struct TransactionReceipt> { /// The receipt envelope, which contains the consensus receipt data.. - #[serde(flatten)] + #[cfg_attr(feature = "serde", serde(flatten))] pub inner: T, /// Transaction Hash. #[doc(alias = "tx_hash")] pub transaction_hash: TxHash, /// Index within the block. - #[serde(default, with = "alloy_serde::quantity::opt")] + #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity::opt"))] #[doc(alias = "tx_index")] pub transaction_index: Option, /// Hash of the block this transaction was included within. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub block_hash: Option, /// Number of the block this transaction was included within. - #[serde(default, with = "alloy_serde::quantity::opt")] + #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity::opt"))] pub block_number: Option, /// Gas used by this transaction alone. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub gas_used: u128, /// The price paid post-execution by the transaction (i.e. base fee + priority fee). Both /// fields in 1559-style transactions are maximums (max fee + max priority fee), the amount /// that's actually paid by users can only be determined post-execution - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub effective_gas_price: u128, /// Blob gas used by the eip-4844 transaction /// /// This is None for non eip-4844 transactions - #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt", default)] + #[cfg_attr( + feature = "serde", + serde( + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt", + default + ) + )] pub blob_gas_used: Option, /// The price paid by the eip-4844 transaction per blob gas. - #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt", default)] + #[cfg_attr( + feature = "serde", + serde( + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt", + default + ) + )] pub blob_gas_price: Option, /// Address of the sender pub from: Address, @@ -56,11 +71,11 @@ pub struct TransactionReceipt> { /// The post-transaction stateroot (pre Byzantium) /// /// EIP98 makes this optional field, if it's missing then skip serializing it - #[serde(skip_serializing_if = "Option::is_none", rename = "root")] + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", rename = "root"))] pub state_root: Option, /// The authorization list is a list of tuples that store the address to code which the signer /// desires to execute in the context of their EOA. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub authorization_list: Option>, } @@ -128,10 +143,12 @@ impl TransactionReceipt { /// Alias for a catch-all receipt type. #[doc(alias = "AnyTxReceipt")] -pub type AnyTransactionReceipt = WithOtherFields>>; +#[cfg(feature = "serde")] +pub type AnyTransactionReceipt = + alloy_serde::WithOtherFields>>; impl> ReceiptResponse for TransactionReceipt { - fn contract_address(&self) -> Option { + fn contract_address(&self) -> Option
{ self.contract_address } @@ -139,13 +156,57 @@ impl> ReceiptResponse for TransactionReceipt { self.inner.status() } - fn block_hash(&self) -> Option { + fn block_hash(&self) -> Option { self.block_hash } fn block_number(&self) -> Option { self.block_number } + + fn transaction_hash(&self) -> TxHash { + self.transaction_hash + } + + fn transaction_index(&self) -> Option { + self.transaction_index + } + + fn gas_used(&self) -> u128 { + self.gas_used + } + + fn effective_gas_price(&self) -> u128 { + self.effective_gas_price + } + + fn blob_gas_used(&self) -> Option { + self.blob_gas_used + } + + fn blob_gas_price(&self) -> Option { + self.blob_gas_price + } + + fn from(&self) -> Address { + self.from + } + + fn to(&self) -> Option
{ + self.to + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.authorization_list.as_deref() + } + + fn cumulative_gas_used(&self) -> u128 { + self.inner.cumulative_gas_used() + } + + fn state_root(&self) -> Option { + self.state_root + } } #[cfg(test)] @@ -167,6 +228,7 @@ mod test { } #[test] + #[cfg(feature = "serde")] fn test_sanity() { let json_str = r#"{"transactionHash":"0x21f6554c28453a01e7276c1db2fc1695bb512b170818bfa98fa8136433100616","blockHash":"0x4acbdefb861ef4adedb135ca52865f6743451bfbfa35db78076f881a40401a5e","blockNumber":"0x129f4b9","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000200000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000800000000000000000000000000000000004000000000000000000800000000100000020000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000010000000000000000000000000000","gasUsed":"0xbde1","contractAddress":null,"cumulativeGasUsed":"0xa42aec","transactionIndex":"0x7f","from":"0x9a53bfba35269414f3b2d20b52ca01b15932c7b2","to":"0xdac17f958d2ee523a2206206994597c13d831ec7","type":"0x2","effectiveGasPrice":"0xfb0f6e8c9","logs":[{"blockHash":"0x4acbdefb861ef4adedb135ca52865f6743451bfbfa35db78076f881a40401a5e","address":"0xdac17f958d2ee523a2206206994597c13d831ec7","logIndex":"0x118","data":"0x00000000000000000000000000000000000000000052b7d2dcc80cd2e4000000","removed":false,"topics":["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925","0x0000000000000000000000009a53bfba35269414f3b2d20b52ca01b15932c7b2","0x00000000000000000000000039e5dbb9d2fead31234d7c647d6ce77d85826f76"],"blockNumber":"0x129f4b9","transactionIndex":"0x7f","transactionHash":"0x21f6554c28453a01e7276c1db2fc1695bb512b170818bfa98fa8136433100616"}],"status":"0x1"}"#; @@ -210,9 +272,10 @@ mod test { } #[test] + #[cfg(feature = "serde")] fn deserialize_tx_receipt_op() { // OtherFields for Optimism - #[derive(Debug, Deserialize)] + #[derive(Debug, serde::Deserialize)] struct OpOtherFields { #[serde(rename = "l1BaseFeeScalar")] l1_base_fee_scalar: String, @@ -285,9 +348,10 @@ mod test { } #[test] + #[cfg(feature = "serde")] fn deserialize_tx_receipt_arb() { // OtherFields for Arbitrum - #[derive(Debug, Deserialize)] + #[derive(Debug, serde::Deserialize)] struct ArbOtherFields { #[serde(rename = "gasUsedForL1")] gas_used_for_l1: String, diff --git a/crates/rpc-types-eth/src/transaction/request.rs b/crates/rpc-types-eth/src/transaction/request.rs index aacd3f3af70..424e7796934 100644 --- a/crates/rpc-types-eth/src/transaction/request.rs +++ b/crates/rpc-types-eth/src/transaction/request.rs @@ -7,68 +7,129 @@ use alloy_consensus::{ }; use alloy_eips::eip7702::SignedAuthorization; use alloy_primitives::{Address, Bytes, ChainId, TxKind, B256, U256}; -use serde::{Deserialize, Serialize}; -use std::hash::Hash; +use core::hash::Hash; + +use alloc::{ + string::{String, ToString}, + vec, + vec::Vec, +}; /// Represents _all_ transaction requests to/from RPC. +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[doc(alias = "TxRequest")] pub struct TransactionRequest { /// The address of the transaction author. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub from: Option
, /// The destination address of the transaction. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub to: Option, /// The legacy gas price. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub gas_price: Option, /// The max base fee per gas the sender is willing to pay. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub max_fee_per_gas: Option, /// The max priority fee per gas the sender is willing to pay, also called the miner tip. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub max_priority_fee_per_gas: Option, /// The max fee per blob gas for EIP-4844 blob transactions. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub max_fee_per_blob_gas: Option, /// The gas limit for the transaction. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub gas: Option, /// The value transferred in the transaction, in wei. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub value: Option, /// Transaction data. - #[serde(default, flatten)] + #[cfg_attr(feature = "serde", serde(default, flatten))] pub input: TransactionInput, /// The nonce of the transaction. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub nonce: Option, /// The chain ID for the transaction. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) + )] pub chain_id: Option, /// An EIP-2930 access list, which lowers cost for accessing accounts and storages in the list. See [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) for more information. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub access_list: Option, /// The EIP-2718 transaction type. See [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) for more information. - #[serde( - default, - rename = "type", - skip_serializing_if = "Option::is_none", - with = "alloy_serde::quantity::opt" + #[cfg_attr( + feature = "serde", + serde( + default, + rename = "type", + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) )] #[doc(alias = "tx_type")] pub transaction_type: Option, /// Blob versioned hashes for EIP-4844 transactions. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub blob_versioned_hashes: Option>, /// Blob sidecar for EIP-4844 transactions. - #[serde(default, flatten, skip_serializing_if = "Option::is_none")] + #[cfg_attr( + feature = "serde", + serde(default, flatten, skip_serializing_if = "Option::is_none") + )] pub sidecar: Option, /// Authorization list for for EIP-7702 transactions. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub authorization_list: Option>, } @@ -312,7 +373,7 @@ impl TransactionRequest { /// If required fields are missing. Use `complete_7702` to check if the /// request can be built. fn build_7702(self) -> Result { - let checked_to = self.to.ok_or("Missing 'to' field for Eip7702 transaction.")?; + let to_address = self.to.ok_or("Missing 'to' field for Eip7702 transaction.")?.to().copied().ok_or("The field `to` can only be of type TxKind::Call(Account). Please change it accordingly.")?; Ok(TxEip7702 { chain_id: self.chain_id.unwrap_or(1), @@ -324,7 +385,7 @@ impl TransactionRequest { max_priority_fee_per_gas: self .max_priority_fee_per_gas .ok_or("Missing 'max_priority_fee_per_gas' field for Eip7702 transaction.")?, - to: checked_to, + to: to_address, value: self.value.unwrap_or_default(), input: self.input.into_input().unwrap_or_default(), access_list: self.access_list.unwrap_or_default(), @@ -604,16 +665,17 @@ impl TransactionRequest { /// If both fields are set, it is expected that they contain the same value, otherwise an error is /// returned. #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[doc(alias = "TxInput")] pub struct TransactionInput { /// Transaction data - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub input: Option, /// Transaction data /// /// This is the same as `input` but is used for backwards compatibility: - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))] pub data: Option, } @@ -808,7 +870,7 @@ impl From for TransactionRequest { impl From for TransactionRequest { fn from(tx: TxEip7702) -> Self { Self { - to: if let TxKind::Call(to) = tx.to { Some(to.into()) } else { None }, + to: Some(tx.to.into()), gas: Some(tx.gas_limit), max_fee_per_gas: Some(tx.max_fee_per_gas), max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), @@ -930,12 +992,15 @@ impl From for TransactionRequest { } /// Error thrown when both `data` and `input` fields are set and not equal. -#[derive(Debug, Default, thiserror::Error)] -#[error("both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data")] +#[derive(Debug, Default, derive_more::Display)] +#[display("both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data")] #[doc(alias = "TxInputError")] #[non_exhaustive] pub struct TransactionInputError; +#[cfg(feature = "std")] +impl std::error::Error for TransactionInputError {} + /// Error thrown when a transaction request cannot be built into a transaction. #[derive(Debug)] pub struct BuildTransactionErr { @@ -954,6 +1019,7 @@ mod tests { // #[test] + #[cfg(feature = "serde")] fn serde_from_to() { let s = r#"{"from":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "to":"0x70997970C51812dc3A010C7d01b50e0d17dc79C8" }"#; let req = serde_json::from_str::(s).unwrap(); @@ -961,12 +1027,14 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_tx_request() { let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#; let _req = serde_json::from_str::(s).unwrap(); } #[test] + #[cfg(feature = "serde")] fn serde_unique_call_input() { let s = r#"{"accessList":[],"data":"0x0902f1ac", "input":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02"}"#; let req = serde_json::from_str::(s).unwrap(); @@ -986,6 +1054,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_tx_request_additional_fields() { let s = r#"{"accessList":[],"data":"0x0902f1ac","to":"0xa478c2975ab1ea89e8196811f51a7b7ade33eb11","type":"0x02","sourceHash":"0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"}"#; let req = serde_json::from_str::>(s).unwrap(); @@ -996,6 +1065,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_tx_chain_id_field() { let chain_id: ChainId = 12345678; @@ -1009,6 +1079,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_empty() { let tx = TransactionRequest::default(); let serialized = serde_json::to_string(&tx).unwrap(); @@ -1016,6 +1087,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serde_tx_with_sidecar() { // Create a sidecar with some data. let s = r#"{ diff --git a/crates/rpc-types-eth/src/transaction/signature.rs b/crates/rpc-types-eth/src/transaction/signature.rs index 5599fc9afc6..29cde635399 100644 --- a/crates/rpc-types-eth/src/transaction/signature.rs +++ b/crates/rpc-types-eth/src/transaction/signature.rs @@ -1,10 +1,12 @@ //! Signature related RPC values use alloy_primitives::U256; -use serde::{Deserialize, Serialize}; + +use alloc::{format, string::String}; /// Container type for all signature fields in RPC #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Signature { /// The R field of the signature; the point on the curve. pub r: U256, @@ -20,7 +22,10 @@ pub struct Signature { /// See also and pub v: U256, /// The y parity of the signature. This is only used for typed (non-legacy) transactions. - #[serde(default, rename = "yParity", skip_serializing_if = "Option::is_none")] + #[cfg_attr( + feature = "serde", + serde(default, rename = "yParity", skip_serializing_if = "Option::is_none") + )] pub y_parity: Option, } @@ -28,9 +33,14 @@ pub struct Signature { /// /// This will be serialized as "0x0" if false, and "0x1" if true. #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Parity( - #[serde(serialize_with = "serialize_parity", deserialize_with = "deserialize_parity")] pub bool, + #[cfg_attr( + feature = "serde", + serde(serialize_with = "serialize_parity", deserialize_with = "deserialize_parity") + )] + pub bool, ); impl From for Parity { @@ -39,6 +49,7 @@ impl From for Parity { } } +#[cfg(feature = "serde")] fn serialize_parity(parity: &bool, serializer: S) -> Result where S: serde::Serializer, @@ -47,10 +58,12 @@ where } /// This implementation disallows serialization of the y parity bit that are not `"0x0"` or `"0x1"`. +#[cfg(feature = "serde")] fn deserialize_parity<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { + use serde::Deserialize; let s = String::deserialize(deserializer)?; match s.as_str() { "0x0" => Ok(false), @@ -89,9 +102,10 @@ impl From for Signature { #[cfg(test)] mod tests { use super::*; - use std::str::FromStr; + use core::str::FromStr; #[test] + #[cfg(feature = "serde")] fn deserialize_without_parity() { let raw_signature_without_y_parity = r#"{ "r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0", @@ -113,6 +127,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn deserialize_with_parity() { let raw_signature_with_y_parity = r#"{ "r":"0xc569c92f176a3be1a6352dd5005bfc751dcb32f57623dd2a23693e64bf4447b0", @@ -135,6 +150,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serialize_both_parity() { // this test should be removed if the struct moves to an enum based on tx type let signature = Signature { @@ -154,6 +170,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serialize_v_only() { // this test should be removed if the struct moves to an enum based on tx type let signature = Signature { @@ -172,6 +189,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn serialize_parity() { let parity = Parity(true); let serialized = serde_json::to_string(&parity).unwrap(); @@ -183,6 +201,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn deserialize_parity() { let raw_parity = r#""0x1""#; let parity: Parity = serde_json::from_str(raw_parity).unwrap(); @@ -194,6 +213,7 @@ mod tests { } #[test] + #[cfg(feature = "serde")] fn deserialize_parity_invalid() { let raw_parity = r#""0x2""#; let parity: Result = serde_json::from_str(raw_parity); diff --git a/crates/rpc-types-eth/src/work.rs b/crates/rpc-types-eth/src/work.rs index b0d2a1fb7a3..1120b6bf9fb 100644 --- a/crates/rpc-types-eth/src/work.rs +++ b/crates/rpc-types-eth/src/work.rs @@ -1,9 +1,5 @@ use alloy_primitives::{B256, U256}; -use serde::{ - de::{Error, SeqAccess, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; -use std::fmt; +use core::fmt; /// The result of an `eth_getWork` request #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -18,10 +14,11 @@ pub struct Work { pub number: Option, } -impl Serialize for Work { +#[cfg(feature = "serde")] +impl serde::Serialize for Work { fn serialize(&self, s: S) -> Result where - S: Serializer, + S: serde::Serializer, { match self.number.as_ref() { Some(num) => { @@ -32,14 +29,15 @@ impl Serialize for Work { } } -impl<'a> Deserialize<'a> for Work { +#[cfg(feature = "serde")] +impl<'a> serde::Deserialize<'a> for Work { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'a>, + D: serde::Deserializer<'a>, { struct WorkVisitor; - impl<'a> Visitor<'a> for WorkVisitor { + impl<'a> serde::de::Visitor<'a> for WorkVisitor { type Value = Work; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -48,8 +46,9 @@ impl<'a> Deserialize<'a> for Work { fn visit_seq(self, mut seq: A) -> Result where - A: SeqAccess<'a>, + A: serde::de::SeqAccess<'a>, { + use serde::de::Error; let pow_hash = seq .next_element::()? .ok_or_else(|| A::Error::custom("missing pow hash"))?; diff --git a/crates/rpc-types-mev/CHANGELOG.md b/crates/rpc-types-mev/CHANGELOG.md index 5702f4ac04f..e10a2a16b5e 100644 --- a/crates/rpc-types-mev/CHANGELOG.md +++ b/crates/rpc-types-mev/CHANGELOG.md @@ -5,10 +5,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Miscellaneous Tasks +- Release 0.3.0 - Clippy für docs ([#1194](https://github.com/alloy-rs/alloy/issues/1194)) - Release 0.2.1 - Chore : fix typos ([#1087](https://github.com/alloy-rs/alloy/issues/1087)) diff --git a/crates/rpc-types-trace/CHANGELOG.md b/crates/rpc-types-trace/CHANGELOG.md index 64b3a719899..ee223341b04 100644 --- a/crates/rpc-types-trace/CHANGELOG.md +++ b/crates/rpc-types-trace/CHANGELOG.md @@ -5,6 +5,55 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- Add types for flat call tracer ([#1292](https://github.com/alloy-rs/alloy/issues/1292)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Features + +- [alloy-rpc-types-eth] Optional serde ([#1276](https://github.com/alloy-rs/alloy/issues/1276)) +- No_std eth rpc types ([#1252](https://github.com/alloy-rs/alloy/issues/1252)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Bug Fixes + +- [rpc-types-trace] Use rpc-types Log in OtsReceipt ([#1261](https://github.com/alloy-rs/alloy/issues/1261)) + +### Features + +- [rpc-types-trace] Always serialize result if no error ([#1258](https://github.com/alloy-rs/alloy/issues/1258)) + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Bug Fixes @@ -20,6 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Rm Rich type ([#1195](https://github.com/alloy-rs/alloy/issues/1195)) - Clippy für docs ([#1194](https://github.com/alloy-rs/alloy/issues/1194)) - Release 0.2.1 diff --git a/crates/rpc-types-trace/Cargo.toml b/crates/rpc-types-trace/Cargo.toml index 7181ff4755f..993e7a447b8 100644 --- a/crates/rpc-types-trace/Cargo.toml +++ b/crates/rpc-types-trace/Cargo.toml @@ -20,7 +20,7 @@ workspace = true [dependencies] alloy-primitives = { workspace = true, features = ["rlp", "serde"] } -alloy-rpc-types-eth.workspace = true +alloy-rpc-types-eth = { workspace = true, features = ["std", "serde"] } alloy-serde.workspace = true serde.workspace = true diff --git a/crates/rpc-types-trace/src/geth/call.rs b/crates/rpc-types-trace/src/geth/call.rs index 5555b9acba5..5a1cb126ea3 100644 --- a/crates/rpc-types-trace/src/geth/call.rs +++ b/crates/rpc-types-trace/src/geth/call.rs @@ -1,5 +1,6 @@ //! Geth call tracer types. +use crate::parity::LocalizedTransactionTrace; use alloy_primitives::{Address, Bytes, B256, U256}; use serde::{Deserialize, Serialize}; @@ -88,6 +89,38 @@ impl CallConfig { } } +/// The response object for `debug_traceTransaction` with `"tracer": "flatCallTracer"`. +/// +/// That is equivalent to parity's [`LocalizedTransactionTrace`] +/// +pub type FlatCallFrame = LocalizedTransactionTrace; + +/// The configuration for the flat call tracer. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FlatCallConfig { + /// If true, call tracer converts errors to parity format + #[serde(default, skip_serializing_if = "Option::is_none")] + pub convert_parity_errors: Option, + /// If true, call tracer includes calls to precompiled contracts + #[serde(default, skip_serializing_if = "Option::is_none")] + pub include_precompiles: Option, +} + +impl FlatCallConfig { + /// Converts errors to parity format. + pub const fn parity_errors(mut self) -> Self { + self.convert_parity_errors = Some(true); + self + } + + /// Include calls to precompiled contracts. + pub const fn with_precompiles(mut self) -> Self { + self.include_precompiles = Some(true); + self + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc-types-trace/src/geth/mod.rs b/crates/rpc-types-trace/src/geth/mod.rs index ea8c94026d5..9b64b642052 100644 --- a/crates/rpc-types-trace/src/geth/mod.rs +++ b/crates/rpc-types-trace/src/geth/mod.rs @@ -1,14 +1,16 @@ //! Geth tracing types. -use crate::geth::mux::{MuxConfig, MuxFrame}; +use crate::geth::{ + call::FlatCallFrame, + mux::{MuxConfig, MuxFrame}, +}; use alloy_primitives::{Bytes, B256, U256}; use alloy_rpc_types_eth::{state::StateOverride, BlockOverrides}; use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, time::Duration}; - // re-exports pub use self::{ - call::{CallConfig, CallFrame, CallLogFrame}, + call::{CallConfig, CallFrame, CallLogFrame, FlatCallConfig}, four_byte::FourByteFrame, noop::NoopFrame, pre_state::{ @@ -118,6 +120,8 @@ pub enum GethTrace { Default(DefaultFrame), /// The response for call tracer CallTracer(CallFrame), + /// The response for the flat call tracer + FlatCallTracer(FlatCallFrame), /// The response for four byte tracer FourByteTracer(FourByteFrame), /// The response for pre-state byte tracer @@ -147,6 +151,14 @@ impl GethTrace { } } + /// Try to convert the inner tracer to [FlatCallFrame] + pub fn try_into_flat_call_frame(self) -> Result { + match self { + Self::FlatCallTracer(inner) => Ok(inner), + _ => Err(UnexpectedTracerError(self)), + } + } + /// Try to convert the inner tracer to [FourByteFrame] pub fn try_into_four_byte_frame(self) -> Result { match self { @@ -212,6 +224,12 @@ impl From for GethTrace { } } +impl From for GethTrace { + fn from(value: FlatCallFrame) -> Self { + Self::FlatCallTracer(value) + } +} + impl From for GethTrace { fn from(value: PreStateFrame) -> Self { Self::PreStateTracer(value) @@ -246,6 +264,10 @@ pub enum GethDebugBuiltInTracerType { /// with the top-level call at root and sub-calls as children of the higher levels. #[serde(rename = "callTracer")] CallTracer, + /// Tracks all call frames of a transaction and returns them in a flat format, i.e. as opposed + /// to the nested format of `callTracer`. + #[serde(rename = "flatCallTracer")] + FlatCallTracer, /// The prestate tracer has two modes: prestate and diff. The prestate mode returns the /// accounts necessary to execute a given transaction. diff mode returns the differences /// between the transaction's pre and post-state (i.e. what changed because the transaction @@ -311,6 +333,14 @@ impl GethDebugTracerConfig { self.from_value() } + /// Returns the [FlatCallConfig] if it is a call config. + pub fn into_flat_call_config(self) -> Result { + if self.0.is_null() { + return Ok(Default::default()); + } + self.from_value() + } + /// Returns the raw json value pub fn into_json(self) -> serde_json::Value { self.0 diff --git a/crates/rpc-types-trace/src/otterscan.rs b/crates/rpc-types-trace/src/otterscan.rs index 8c93ca9c9d9..903edf5a594 100644 --- a/crates/rpc-types-trace/src/otterscan.rs +++ b/crates/rpc-types-trace/src/otterscan.rs @@ -4,7 +4,7 @@ //! use alloy_primitives::{Address, Bloom, Bytes, TxHash, B256, U256}; -use alloy_rpc_types_eth::{Block, Header, Transaction, TransactionReceipt, Withdrawal}; +use alloy_rpc_types_eth::{Block, Header, Log, Transaction, TransactionReceipt, Withdrawal}; use serde::{ de::{self, Unexpected}, Deserialize, Deserializer, Serialize, Serializer, @@ -204,7 +204,7 @@ pub struct OtsReceipt { /// The logs sent from contracts. /// /// Note: this is set to null. - pub logs: Option>, + pub logs: Option>, /// The bloom filter. /// /// Note: this is set to null. diff --git a/crates/rpc-types-trace/src/parity.rs b/crates/rpc-types-trace/src/parity.rs index b6794694eb7..a740bd14780 100644 --- a/crates/rpc-types-trace/src/parity.rs +++ b/crates/rpc-types-trace/src/parity.rs @@ -429,6 +429,7 @@ pub struct TransactionTrace { #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, /// Output of the trace, can be CALL or CREATE + #[serde(default)] pub result: Option, /// How many subtraces this trace has. pub subtraces: usize, @@ -498,15 +499,10 @@ impl Serialize for LocalizedTransactionTrace { if let Some(error) = error { s.serialize_field("error", error)?; } - match result { - Some(TraceOutput::Call(call)) => { - s.serialize_field("result", call)?; - } - Some(TraceOutput::Create(create)) => { - s.serialize_field("result", create)?; - } - None => {} + Some(TraceOutput::Call(call)) => s.serialize_field("result", call)?, + Some(TraceOutput::Create(create)) => s.serialize_field("result", create)?, + None => s.serialize_field("result", &None::<()>)?, } s.serialize_field("subtraces", &subtraces)?; @@ -822,6 +818,47 @@ mod tests { let serialized = serde_json::to_string_pretty(&trace).unwrap(); similar_asserts::assert_eq!(serialized, reference_data); } + + #[test] + fn test_transaction_trace_null_result() { + let trace = TransactionTrace { + action: Action::Call(CallAction { + from: Address::from_str("0x1234567890123456789012345678901234567890").unwrap(), + call_type: CallType::Call, + gas: 100000, + input: Bytes::from_str("0x1234").unwrap(), + to: Address::from_str("0x0987654321098765432109876543210987654321").unwrap(), + value: U256::from(0), + }), + ..Default::default() + }; + + let serialized = serde_json::to_string(&trace).unwrap(); + let deserialized: serde_json::Value = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(deserialized["result"], serde_json::Value::Null); + assert!(deserialized.as_object().unwrap().contains_key("result")); + assert!(!deserialized.as_object().unwrap().contains_key("error")); + + let deserialized_trace: TransactionTrace = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized_trace.result, None); + } + + #[test] + fn test_transaction_trace_error_result() { + let trace = TransactionTrace { error: Some("Reverted".to_string()), ..Default::default() }; + + let serialized = serde_json::to_string(&trace).unwrap(); + let deserialized: serde_json::Value = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(deserialized["result"], serde_json::Value::Null); + assert!(deserialized.as_object().unwrap().contains_key("result")); + assert!(deserialized.as_object().unwrap().contains_key("error")); + + let deserialized_trace: TransactionTrace = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized_trace.result, None); + } + #[test] fn test_nethermind_trace_result_null_output_value() { let reference_data = r#"{ diff --git a/crates/rpc-types-txpool/CHANGELOG.md b/crates/rpc-types-txpool/CHANGELOG.md index b0bec33dba3..421b6969c28 100644 --- a/crates/rpc-types-txpool/CHANGELOG.md +++ b/crates/rpc-types-txpool/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Features @@ -13,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/rpc-types/CHANGELOG.md b/crates/rpc-types/CHANGELOG.md index 8e3d2fb66f9..93ef094113e 100644 --- a/crates/rpc-types/CHANGELOG.md +++ b/crates/rpc-types/CHANGELOG.md @@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Features + +- No_std eth rpc types ([#1252](https://github.com/alloy-rs/alloy/issues/1252)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Dependencies @@ -18,6 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/rpc-types/Cargo.toml b/crates/rpc-types/Cargo.toml index bc5d58e1e27..0027917ddc5 100644 --- a/crates/rpc-types/Cargo.toml +++ b/crates/rpc-types/Cargo.toml @@ -35,7 +35,7 @@ serde = { workspace = true, features = ["derive", "std"] } serde_json.workspace = true [features] -default = ["eth", "alloy-rpc-types-engine?/default"] +default = ["eth", "alloy-rpc-types-engine?/default", "alloy-rpc-types-eth?/default"] admin = ["dep:alloy-rpc-types-admin"] anvil = ["dep:alloy-rpc-types-anvil"] beacon = ["dep:alloy-rpc-types-beacon"] diff --git a/crates/serde/CHANGELOG.md b/crates/serde/CHANGELOG.md index 675bfabadf8..8b9c09517ec 100644 --- a/crates/serde/CHANGELOG.md +++ b/crates/serde/CHANGELOG.md @@ -5,10 +5,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 - Fix unnameable types ([#1029](https://github.com/alloy-rs/alloy/issues/1029)) diff --git a/crates/signer-aws/CHANGELOG.md b/crates/signer-aws/CHANGELOG.md index 2dade179b6c..f87c8ddcd24 100644 --- a/crates/signer-aws/CHANGELOG.md +++ b/crates/signer-aws/CHANGELOG.md @@ -5,10 +5,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/signer-gcp/CHANGELOG.md b/crates/signer-gcp/CHANGELOG.md index ab10ea16025..bd9432708af 100644 --- a/crates/signer-gcp/CHANGELOG.md +++ b/crates/signer-gcp/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Dependencies @@ -15,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/signer-ledger/CHANGELOG.md b/crates/signer-ledger/CHANGELOG.md index 91bf2e9b6ae..11e5caa69db 100644 --- a/crates/signer-ledger/CHANGELOG.md +++ b/crates/signer-ledger/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Dependencies @@ -15,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/signer-local/CHANGELOG.md b/crates/signer-local/CHANGELOG.md index d6ef4068366..b1917212dfd 100644 --- a/crates/signer-local/CHANGELOG.md +++ b/crates/signer-local/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Dependencies @@ -15,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 - Fix unnameable types ([#1029](https://github.com/alloy-rs/alloy/issues/1029)) diff --git a/crates/signer-trezor/CHANGELOG.md b/crates/signer-trezor/CHANGELOG.md index d809cce6144..7afff243630 100644 --- a/crates/signer-trezor/CHANGELOG.md +++ b/crates/signer-trezor/CHANGELOG.md @@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Features + +- [alloy-rpc-types-eth] Optional serde ([#1276](https://github.com/alloy-rs/alloy/issues/1276)) + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Dependencies @@ -13,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/signer-trezor/Cargo.toml b/crates/signer-trezor/Cargo.toml index eb71768d570..2258a1b326b 100644 --- a/crates/signer-trezor/Cargo.toml +++ b/crates/signer-trezor/Cargo.toml @@ -34,5 +34,5 @@ thiserror.workspace = true tracing.workspace = true [dev-dependencies] -alloy-rpc-types-eth.workspace = true +alloy-rpc-types-eth = { workspace = true, features = ["std", "serde"] } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/crates/signer/CHANGELOG.md b/crates/signer/CHANGELOG.md index 9374ee9beba..7fc6ddf961e 100644 --- a/crates/signer/CHANGELOG.md +++ b/crates/signer/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Features @@ -13,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/transport-http/CHANGELOG.md b/crates/transport-http/CHANGELOG.md index efa8ea635c3..d95903293ca 100644 --- a/crates/transport-http/CHANGELOG.md +++ b/crates/transport-http/CHANGELOG.md @@ -5,10 +5,47 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Features + +- [transport-http] Layer client ([#1227](https://github.com/alloy-rs/alloy/issues/1227)) + +## [0.3.5](https://github.com/alloy-rs/alloy/releases/tag/v0.3.5) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/transport-http/src/hyper_transport.rs b/crates/transport-http/src/hyper_transport.rs index a92a941ee07..389d0289c03 100644 --- a/crates/transport-http/src/hyper_transport.rs +++ b/crates/transport-http/src/hyper_transport.rs @@ -1,63 +1,92 @@ -use crate::{Http, HttpConnect}; use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_transport::{ utils::guess_local_url, TransportConnect, TransportError, TransportErrorKind, TransportFut, }; use http_body_util::{BodyExt, Full}; use hyper::{ - body::{Buf, Bytes}, - header, + body::{Bytes, Incoming}, + header, Request, Response, }; -use hyper_util::client::legacy::{connect::Connect, Client}; -use std::task; +use hyper_util::client::legacy::Error; +use std::{future::Future, marker::PhantomData, pin::Pin, task}; use tower::Service; use tracing::{debug, debug_span, trace, Instrument}; -/// A [`hyper`] HTTP client. -pub type HyperClient = hyper_util::client::legacy::Client< +use crate::{Http, HttpConnect}; + +type Hyper = hyper_util::client::legacy::Client< hyper_util::client::legacy::connect::HttpConnector, http_body_util::Full<::hyper::body::Bytes>, >; -/// An [`Http`] transport using [`hyper`]. +/// A [`hyper`] based transport client. pub type HyperTransport = Http; -/// Connection details for a [`HyperTransport`]. -pub type HyperConnect = HttpConnect; +impl HyperTransport { + /// Create a new [`HyperTransport`] with the given URL and default hyper client. + pub fn new_hyper(url: url::Url) -> Self { + let client = HyperClient::new(); + Self::with_client(client, url) + } +} -impl TransportConnect for HyperConnect { - type Transport = HyperTransport; +/// A [hyper] based client that can be used with tower layers. +#[derive(Clone, Debug)] +pub struct HyperClient, S = Hyper> { + service: S, + _pd: PhantomData, +} - fn is_local(&self) -> bool { - guess_local_url(self.url.as_str()) - } +/// Alias for [`Response`] +pub type HyperResponse = Response; - fn get_transport<'a: 'b, 'b>( - &'a self, - ) -> alloy_transport::Pbf<'b, Self::Transport, TransportError> { +/// Alias for pinned box future that results in [`HyperResponse`] +pub type HyperResponseFut = + Pin> + Send + 'static>>; + +impl HyperClient { + /// Create a new [HyperClient] with the given URL and default hyper client. + pub fn new() -> Self { let executor = hyper_util::rt::TokioExecutor::new(); - let client = hyper_util::client::legacy::Client::builder(executor).build_http(); + let service = + hyper_util::client::legacy::Client::builder(executor).build_http::>(); - Box::pin(async move { Ok(Http::with_client(client, self.url.clone())) }) + Self { service, _pd: PhantomData } } } -impl Http>> +impl Default for HyperClient { + fn default() -> Self { + Self::new() + } +} + +impl HyperClient { + /// Create a new [HyperClient] with the given URL and service. + pub const fn with_service(service: S) -> Self { + Self { service, _pd: PhantomData } + } +} + +impl Http> where - C: Connect + Clone + Send + Sync + 'static, - B: From + Buf + Send + 'static, + S: Service, Response = HyperResponse> + Clone + Send + Sync + 'static, + S::Future: Send, + S::Error: std::error::Error + Send + Sync + 'static, + B: From> + Send + 'static + Clone, { - /// Make a request. + /// Make a request to the server using the given service. fn request_hyper(&self, req: RequestPacket) -> TransportFut<'static> { let this = self.clone(); - let span = debug_span!("HyperTransport", url = %self.url); + let span = debug_span!("HyperClient", url = %this.url); Box::pin( async move { debug!(count = req.len(), "sending request packet to server"); let ser = req.serialize().map_err(TransportError::ser_err)?; // convert the Box into a hyper request - let body = Full::from(Bytes::from(>::from(>::from(ser)))); + let body = ser.get().as_bytes().to_owned().into(); + let req = hyper::Request::builder() .method(hyper::Method::POST) .uri(this.url.as_str()) @@ -66,9 +95,11 @@ where header::HeaderValue::from_static("application/json"), ) .body(body) - .expect("request parts are valid"); + .expect("request parts are invalid"); + + let mut service = this.client.service.clone(); + let resp = service.call(req).await.map_err(TransportErrorKind::custom)?; - let resp = this.client.request(req).await.map_err(TransportErrorKind::custom)?; let status = resp.status(); debug!(%status, "received response from server"); @@ -105,43 +136,59 @@ where } } -impl Service for &Http>> +impl TransportConnect for HttpConnect { + type Transport = HyperTransport; + + fn is_local(&self) -> bool { + guess_local_url(self.url.as_str()) + } + + fn get_transport<'a: 'b, 'b>( + &'a self, + ) -> alloy_transport::Pbf<'b, Self::Transport, TransportError> { + Box::pin(async move { + let hyper_t = HyperClient::new(); + + Ok(Http::with_client(hyper_t, self.url.clone())) + }) + } +} + +impl Service for Http> where - C: Connect + Clone + Send + Sync + 'static, - B: From + Buf + Send + 'static, + S: Service, Response = HyperResponse> + Clone + Send + Sync + 'static, + S::Future: Send, + S::Error: std::error::Error + Send + Sync + 'static, + B: From> + Send + 'static + Clone + Sync, { type Response = ResponsePacket; type Error = TransportError; type Future = TransportFut<'static>; - #[inline] fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> task::Poll> { - // hyper always returns ok task::Poll::Ready(Ok(())) } - #[inline] fn call(&mut self, req: RequestPacket) -> Self::Future { self.request_hyper(req) } } -impl Service for Http>> +impl Service for &Http> where - C: Connect + Clone + Send + Sync + 'static, - B: From + Buf + Send + 'static, + S: Service, Response = HyperResponse> + Clone + Send + Sync + 'static, + S::Future: Send, + S::Error: std::error::Error + Send + Sync + 'static, + B: From> + Send + 'static + Clone + Sync, { type Response = ResponsePacket; type Error = TransportError; type Future = TransportFut<'static>; - #[inline] fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> task::Poll> { - // hyper always returns ok task::Poll::Ready(Ok(())) } - #[inline] fn call(&mut self, req: RequestPacket) -> Self::Future { self.request_hyper(req) } diff --git a/crates/transport-http/src/lib.rs b/crates/transport-http/src/lib.rs index b12a048ce61..d1881fefaae 100644 --- a/crates/transport-http/src/lib.rs +++ b/crates/transport-http/src/lib.rs @@ -6,6 +6,8 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#[cfg(feature = "reqwest")] +pub use reqwest; #[cfg(feature = "reqwest")] mod reqwest_transport; @@ -13,22 +15,20 @@ mod reqwest_transport; #[doc(inline)] pub use reqwest_transport::*; -#[cfg(feature = "reqwest")] -pub use reqwest; - #[cfg(all(not(target_arch = "wasm32"), feature = "hyper"))] -mod hyper_transport; +pub use hyper; #[cfg(all(not(target_arch = "wasm32"), feature = "hyper"))] -#[doc(inline)] -pub use hyper_transport::*; +pub use hyper_util; #[cfg(all(not(target_arch = "wasm32"), feature = "hyper"))] -pub use hyper; +mod hyper_transport; #[cfg(all(not(target_arch = "wasm32"), feature = "hyper"))] -pub use hyper_util; +#[doc(inline)] +pub use hyper_transport::{HyperClient, HyperResponse, HyperResponseFut, HyperTransport}; use alloy_transport::utils::guess_local_url; -use core::{marker::PhantomData, str::FromStr}; +use core::str::FromStr; +use std::marker::PhantomData; use url::Url; /// Connection details for an HTTP transport. @@ -37,6 +37,7 @@ use url::Url; pub struct HttpConnect { /// The URL to connect to. url: Url, + _pd: PhantomData, } diff --git a/crates/transport-ipc/CHANGELOG.md b/crates/transport-ipc/CHANGELOG.md index dc4d68fd3c2..fc2bd3b2f45 100644 --- a/crates/transport-ipc/CHANGELOG.md +++ b/crates/transport-ipc/CHANGELOG.md @@ -5,10 +5,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/transport-ws/CHANGELOG.md b/crates/transport-ws/CHANGELOG.md index 46c0b325c3d..b9251dbab5c 100644 --- a/crates/transport-ws/CHANGELOG.md +++ b/crates/transport-ws/CHANGELOG.md @@ -5,10 +5,41 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 diff --git a/crates/transport/CHANGELOG.md b/crates/transport/CHANGELOG.md index baf3c42ce26..f11200ece95 100644 --- a/crates/transport/CHANGELOG.md +++ b/crates/transport/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.6](https://github.com/alloy-rs/alloy/releases/tag/v0.3.6) - 2024-09-18 + +### Miscellaneous Tasks + +- Release 0.3.5 + +## [0.3.4](https://github.com/alloy-rs/alloy/releases/tag/v0.3.4) - 2024-09-13 + +### Miscellaneous Tasks + +- Release 0.3.4 + +## [0.3.3](https://github.com/alloy-rs/alloy/releases/tag/v0.3.3) - 2024-09-10 + +### Miscellaneous Tasks + +- Release 0.3.3 + +## [0.3.2](https://github.com/alloy-rs/alloy/releases/tag/v0.3.2) - 2024-09-09 + +### Miscellaneous Tasks + +- Release 0.3.2 + +## [0.3.1](https://github.com/alloy-rs/alloy/releases/tag/v0.3.1) - 2024-09-02 + +### Miscellaneous Tasks + +- Release 0.3.1 + ## [0.3.0](https://github.com/alloy-rs/alloy/releases/tag/v0.3.0) - 2024-08-28 ### Documentation @@ -18,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Miscellaneous Tasks +- Release 0.3.0 - Release 0.2.1 - Release 0.2.0 - Fix unnameable types ([#1029](https://github.com/alloy-rs/alloy/issues/1029)) diff --git a/scripts/check_no_std.sh b/scripts/check_no_std.sh index 1aa49ec6d64..62ebbd5d426 100755 --- a/scripts/check_no_std.sh +++ b/scripts/check_no_std.sh @@ -1,24 +1,21 @@ #!/usr/bin/env bash set -eo pipefail -no_std_packages=( +target=riscv32imac-unknown-none-elf +crates=( alloy-eips alloy-genesis alloy-serde alloy-consensus + alloy-network-primitives + alloy-rpc-types-eth + alloy-rpc-types-engine ) -for package in "${no_std_packages[@]}"; do - cmd="cargo +stable build -p $package --target riscv32imac-unknown-none-elf --no-default-features" - if [ -n "$CI" ]; then - echo "::group::$cmd" - else - printf "\n%s:\n %s\n" "$package" "$cmd" - fi - - $cmd - - if [ -n "$CI" ]; then - echo "::endgroup::" - fi +cmd=(cargo +stable hack check --no-default-features --target "$target") +for crate in "${crates[@]}"; do + cmd+=(-p "$crate") done + +echo "Running: ${cmd[*]}" +"${cmd[@]}"