From efb0953750db4388abb0f7ab2236626b17995973 Mon Sep 17 00:00:00 2001 From: dancoombs Date: Wed, 9 Oct 2024 11:01:20 -0500 Subject: [PATCH] feat(provider): add a local bedrock DA provider --- .gitmodules | 3 + Cargo.lock | 217 +++++++++++++++--- Cargo.toml | 16 +- crates/bindings/fastlz/Cargo.toml | 12 + crates/bindings/fastlz/build.rs | 42 ++++ crates/bindings/fastlz/fastlz | 1 + crates/bindings/fastlz/src/lib.rs | 51 ++++ crates/provider/Cargo.toml | 2 + crates/provider/src/alloy/da/local/bedrock.rs | 172 ++++++++++++++ crates/provider/src/alloy/da/local/mod.rs | 15 ++ crates/provider/src/alloy/da/mod.rs | 78 +++++++ crates/types/src/chain.rs | 2 + 12 files changed, 577 insertions(+), 34 deletions(-) create mode 100644 crates/bindings/fastlz/Cargo.toml create mode 100644 crates/bindings/fastlz/build.rs create mode 160000 crates/bindings/fastlz/fastlz create mode 100644 crates/bindings/fastlz/src/lib.rs create mode 100644 crates/provider/src/alloy/da/local/bedrock.rs create mode 100644 crates/provider/src/alloy/da/local/mod.rs diff --git a/.gitmodules b/.gitmodules index a6acc126d..aaa528b9f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -26,3 +26,6 @@ path = test/spec-tests/v0_7/bundler-spec-tests url = git@github.com:alchemyplatform/bundler-spec-tests.git ignore = dirty +[submodule "crates/bindings/fastlz/fastlz"] + path = crates/bindings/fastlz/fastlz + url = https://github.com/ariya/FastLZ diff --git a/Cargo.lock b/Cargo.lock index 78fe8c62f..a986d2fde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1009,7 +1009,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0e249228c6ad2d240c2dc94b714d711629d52bad946075d8e9b2f5391f0703" dependencies = [ - "bindgen", + "bindgen 0.69.4", "cc", "cmake", "dunce", @@ -1243,13 +1243,17 @@ dependencies = [ "aws-smithy-types", "bytes", "fastrand 2.1.1", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "http-body 1.0.1", "httparse", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", + "rustls 0.21.12", "tokio", "tracing", ] @@ -1436,6 +1440,26 @@ dependencies = [ "which", ] +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.77", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -2486,6 +2510,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.5.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.6" @@ -2647,6 +2690,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hyper" +version = "0.14.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.4.1" @@ -2656,7 +2723,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.1", "httparse", @@ -2668,6 +2735,22 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.30", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.2" @@ -2676,13 +2759,13 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", "http 1.1.0", - "hyper", + "hyper 1.4.1", "hyper-util", "log", - "rustls", + "rustls 0.23.12", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tower-service", "webpki-roots", ] @@ -2693,7 +2776,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" dependencies = [ - "hyper", + "hyper 1.4.1", "hyper-util", "pin-project-lite", "tokio", @@ -2711,7 +2794,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper", + "hyper 1.4.1", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -2946,13 +3029,13 @@ dependencies = [ "http 1.1.0", "jsonrpsee-core", "pin-project", - "rustls", + "rustls 0.23.12", "rustls-pki-types", "rustls-platform-verifier", "soketto", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tokio-util", "tracing", "url", @@ -2994,12 +3077,12 @@ dependencies = [ "async-trait", "base64 0.22.1", "http-body 1.0.1", - "hyper", - "hyper-rustls", + "hyper 1.4.1", + "hyper-rustls 0.27.2", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", - "rustls", + "rustls 0.23.12", "rustls-platform-verifier", "serde", "serde_json", @@ -3033,7 +3116,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.4.1", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -3167,7 +3250,7 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae9ea4b75e1a81675429dafe43441df1caea70081e82246a8cccf514884a88bb" dependencies = [ - "bindgen", + "bindgen 0.69.4", "errno", "libc", ] @@ -3276,7 +3359,7 @@ checksum = "b4f0c8427b39666bf970460908b213ec09b3b350f20c0c2eabcbba51704a08e6" dependencies = [ "base64 0.22.1", "http-body-util", - "hyper", + "hyper 1.4.1", "hyper-util", "indexmap 2.5.0", "ipnet", @@ -4013,7 +4096,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls", + "rustls 0.23.12", "socket2 0.5.7", "thiserror", "tokio", @@ -4030,7 +4113,7 @@ dependencies = [ "rand", "ring", "rustc-hash 2.0.0", - "rustls", + "rustls 0.23.12", "slab", "thiserror", "tinyvec", @@ -4218,8 +4301,8 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.4.1", + "hyper-rustls 0.27.2", "hyper-util", "ipnet", "js-sys", @@ -4229,15 +4312,15 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls", - "rustls-pemfile", + "rustls 0.23.12", + "rustls-pemfile 2.1.3", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tower-service", "url", "wasm-bindgen", @@ -4420,7 +4503,7 @@ dependencies = [ "sscanf", "tokio", "tokio-metrics", - "tokio-rustls", + "tokio-rustls 0.26.0", "tokio-util", "tracing", "tracing-appender", @@ -4428,6 +4511,14 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "rundler-bindings-fastlz" +version = "0.3.0" +dependencies = [ + "bindgen 0.70.1", + "cc", +] + [[package]] name = "rundler-builder" version = "0.3.0" @@ -4548,6 +4639,7 @@ dependencies = [ "futures-util", "mockall", "reqwest", + "rundler-bindings-fastlz", "rundler-contracts", "rundler-provider", "rundler-types", @@ -4768,6 +4860,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.12" @@ -4779,11 +4883,23 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.7", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + [[package]] name = "rustls-native-certs" version = "0.7.3" @@ -4791,12 +4907,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 2.1.3", "rustls-pki-types", "schannel", "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pemfile" version = "2.1.3" @@ -4824,10 +4949,10 @@ dependencies = [ "jni", "log", "once_cell", - "rustls", - "rustls-native-certs", + "rustls 0.23.12", + "rustls-native-certs 0.7.3", "rustls-platform-verifier-android", - "rustls-webpki", + "rustls-webpki 0.102.7", "security-framework", "security-framework-sys", "webpki-roots", @@ -4840,6 +4965,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.102.7" @@ -4911,6 +5046,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sec1" version = "0.7.3" @@ -5521,13 +5666,23 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls", + "rustls 0.23.12", "rustls-pki-types", "tokio", ] @@ -5603,11 +5758,11 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", - "h2", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.4.1", "hyper-timeout", "hyper-util", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index 0436ebc7e..4b4d08d5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,16 @@ [workspace] members = [ - "bin/*", - "crates/*", + "bin/rundler/", + "crates/bindings/fastlz/", + "crates/builder/", + "crates/contracts/", + "crates/pool/", + "crates/provider/", + "crates/rpc/", + "crates/sim/", + "crates/task/", + "crates/types/", + "crates/utils/", ] default-members = ["bin/rundler"] resolver = "2" @@ -21,6 +30,7 @@ rundler-sim = { path = "crates/sim" } rundler-task = { path = "crates/task" } rundler-types = { path = "crates/types" } rundler-utils = { path = "crates/utils" } +rundler-bindings-fastlz = { path = "crates/bindings/fastlz" } # alloy core alloy-primitives = "0.8.5" @@ -51,7 +61,7 @@ reth-tasks = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.0.7" } anyhow = "1.0.89" async-trait = "0.1.83" auto_impl = "1.2.0" -aws-config = { version = "1.5.6", default-features = false } +aws-config = { version = "1.5.6", default-features = false, features = ["rt-tokio", "rustls"] } cargo-husky = { version = "1", default-features = false, features = ["user-hooks"] } futures = "0.3.30" futures-util = "0.3.30" diff --git a/crates/bindings/fastlz/Cargo.toml b/crates/bindings/fastlz/Cargo.toml new file mode 100644 index 000000000..6b3113508 --- /dev/null +++ b/crates/bindings/fastlz/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rundler-bindings-fastlz" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +publish = false + +[build-dependencies] +bindgen = "0.70.1" +cc = { version = "1", features = ["parallel"] } diff --git a/crates/bindings/fastlz/build.rs b/crates/bindings/fastlz/build.rs new file mode 100644 index 000000000..81b035bc4 --- /dev/null +++ b/crates/bindings/fastlz/build.rs @@ -0,0 +1,42 @@ +// This file is part of Rundler. +// +// Rundler is free software: you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later version. +// +// Rundler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with Rundler. +// If not, see https://www.gnu.org/licenses/. + +// Credit to https://github.com/mvertescher/fastlz-rs/blob/master/fastlz-sys/build.rs + +extern crate cc; + +use std::{env, path::PathBuf}; + +fn main() { + let mut build = cc::Build::new(); + build.include("fastlz"); + + #[cfg(target_os = "linux")] + build.flag("-Wno-unused-parameter"); + + let files = ["fastlz/fastlz.c"]; + + build.files(files.iter()).compile("fastlz"); + println!("cargo:rustc-link-lib=static=fastlz"); + + // Generate bindings + let bindings = bindgen::Builder::default() + .header("fastlz/fastlz.h") + .generate() + .expect("Unable to generate bindings"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!") +} diff --git a/crates/bindings/fastlz/fastlz b/crates/bindings/fastlz/fastlz new file mode 160000 index 000000000..344eb4025 --- /dev/null +++ b/crates/bindings/fastlz/fastlz @@ -0,0 +1 @@ +Subproject commit 344eb4025f9ae866ebf7a2ec48850f7113a97a42 diff --git a/crates/bindings/fastlz/src/lib.rs b/crates/bindings/fastlz/src/lib.rs new file mode 100644 index 000000000..0de4539ed --- /dev/null +++ b/crates/bindings/fastlz/src/lib.rs @@ -0,0 +1,51 @@ +// This file is part of Rundler. +// +// Rundler is free software: you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later version. +// +// Rundler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with Rundler. +// If not, see https://www.gnu.org/licenses/. + +//! Raw FastLZ FFI bindings + +// Credit to https://github.com/mvertescher/fastlz-rs/blob/master/src/lib.rs + +use core::ffi::c_void; + +// This is a generated binding of the fastlz C library at commit +// 344eb4025f9ae866ebf7a2ec48850f7113a97a42 as required by the fastlz implementation by +// solady's LibZip.sol here: https://github.com/Vectorized/solady/blob/8b0601e1573ed17a583fdab2b2ebfb895507ec15/src/utils/LibZip.sol#L19 +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +/// Compress a block of data in the input buffer and returns the size of +/// compressed block. The size of input buffer is specified by length. The +/// minimum input buffer size is 16. +/// +/// The output buffer must be at least 5% larger than the input buffer +/// and can not be smaller than 66 bytes. +/// +/// If the input is not compressible, the return value might be larger than +/// length (input buffer size). +/// +/// The input buffer and the output buffer can not overlap. +/// +/// MODIFICATION: Always use level 1 compression to match LibZip.sol +/// +/// Original credit to https://github.com/mvertescher/fastlz-rs/blob/master/src/lib.rs +pub fn compress<'a>(input: &[u8], output: &'a mut [u8]) -> &'a mut [u8] { + let in_ptr: *const c_void = input as *const _ as *const c_void; + let out_ptr: *mut c_void = output as *mut _ as *mut c_void; + let size = unsafe { fastlz_compress_level(1, in_ptr, input.len() as i32, out_ptr) }; + if size as usize > output.len() { + panic!("Output buffer overflow!"); + } + + let ret: &mut [u8] = + unsafe { core::slice::from_raw_parts_mut(out_ptr as *mut _, size as usize) }; + ret +} diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index 30a6b390b..af852450c 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -8,6 +8,7 @@ repository.workspace = true publish = false [dependencies] +rundler-bindings-fastlz.workspace = true rundler-contracts.workspace = true rundler-types.workspace = true rundler-utils.workspace = true @@ -31,6 +32,7 @@ async-trait.workspace = true auto_impl.workspace = true thiserror.workspace = true futures-util.workspace = true +tokio.workspace = true tower.workspace = true tracing.workspace = true url.workspace = true diff --git a/crates/provider/src/alloy/da/local/bedrock.rs b/crates/provider/src/alloy/da/local/bedrock.rs new file mode 100644 index 000000000..42a60db1f --- /dev/null +++ b/crates/provider/src/alloy/da/local/bedrock.rs @@ -0,0 +1,172 @@ +// This file is part of Rundler. +// +// Rundler is free software: you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later version. +// +// Rundler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with Rundler. +// If not, see https://www.gnu.org/licenses/. + +use alloy_primitives::{Address, Bytes}; +use alloy_provider::Provider as AlloyProvider; +use alloy_sol_types::sol; +use alloy_transport::Transport; +use rundler_utils::cache::LruMap; +use tokio::sync::Mutex; +use GasPriceOracle::GasPriceOracleInstance; + +use crate::{alloy::da::DAGasOracle, BlockHashOrNumber, ProviderResult}; + +// From https://github.com/ethereum-optimism/optimism/blob/f93f9f40adcd448168c6ea27820aeee5da65fcbd/packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L54 +sol! { + #[sol(rpc)] + interface GasPriceOracle { + bool public isFjord; + + function baseFeeScalar() public view returns (uint32); + function l1BaseFee() public view returns (uint32); + function blobBaseFeeScalar() public view returns (uint32); + function blobBaseFee() public view returns (uint32); + } +} + +const DECIMAL_SCALAR: u128 = 1_000_000_000_000; +const COST_INTERCEPT: i128 = -42_585_600; +const COST_FASTLZ_COEF: i128 = 836_500; +const MIN_TRANSACTION_SIZE: i128 = 100_000_000; + +/// Local Bedrock DA gas oracle +/// +/// Details: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/fjord/exec-engine.md#fjord-l1-cost-fee-changes-fastlz-estimator +pub(crate) struct LocalBedrockDAGasOracle { + oracle: GasPriceOracleInstance, + block_da_data_cache: Mutex>, +} + +#[derive(Debug, Clone, Copy)] +struct BlockDAData { + base_fee_scalar: u64, + l1_base_fee: u64, + blob_base_fee_scalar: u64, + blob_base_fee: u64, +} + +impl LocalBedrockDAGasOracle +where + AP: AlloyProvider, + T: Transport + Clone, +{ + pub(crate) fn new(oracle_address: Address, provider: AP) -> Self { + let oracle = GasPriceOracleInstance::new(oracle_address, provider); + Self { + oracle, + block_da_data_cache: Mutex::new(LruMap::new(100)), + } + } +} + +#[async_trait::async_trait] +impl DAGasOracle for LocalBedrockDAGasOracle +where + AP: AlloyProvider, + T: Transport + Clone, +{ + async fn estimate_da_gas( + &self, + _to: Address, + data: Bytes, + block: BlockHashOrNumber, + gas_price: u128, + ) -> ProviderResult { + let block_da_data = { + let mut cache = self.block_da_data_cache.lock().await; + + match cache.get(&block) { + Some(block_da_data) => *block_da_data, + None => { + let block_da_data = self.get_block_da_data(block).await?; + cache.insert(block, block_da_data); + block_da_data + } + } + }; + + let l1_fee = Self::fjord_l1_fee(data, &block_da_data); + Ok(l1_fee.checked_div(gas_price).unwrap_or(u128::MAX)) + } +} + +impl LocalBedrockDAGasOracle +where + AP: AlloyProvider, + T: Transport + Clone, +{ + async fn is_fjord(&self) -> bool { + self.oracle + .isFjord() + .call() + .await + .map(|r| r.isFjord) + .unwrap_or(false) + } + + async fn get_block_da_data(&self, block: BlockHashOrNumber) -> ProviderResult { + assert!(self.is_fjord().await); + + let base_fee_scalar = self + .oracle + .baseFeeScalar() + .block(block.into()) + .call() + .await? + ._0 as u64; + let l1_base_fee = self.oracle.l1BaseFee().block(block.into()).call().await?._0 as u64; + let blob_base_fee_scalar = self + .oracle + .blobBaseFeeScalar() + .block(block.into()) + .call() + .await? + ._0 as u64; + let blob_base_fee = self + .oracle + .blobBaseFee() + .block(block.into()) + .call() + .await? + ._0 as u64; + + Ok(BlockDAData { + base_fee_scalar, + l1_base_fee, + blob_base_fee_scalar, + blob_base_fee, + }) + } + + fn fjord_l1_fee(data: Bytes, block_da_data: &BlockDAData) -> u128 { + let mut buf = vec![0; data.len() * 2]; + let compressed = rundler_bindings_fastlz::compress(&data, &mut buf); + let compressed_length = compressed.len() as u64; + + Self::fjord_l1_cost(compressed_length + 68, block_da_data) + } + + fn fjord_l1_cost(fast_lz_size: u64, block_da_data: &BlockDAData) -> u128 { + let estimated_size = Self::fjord_linear_regression(fast_lz_size) as u128; + let fee_scaled = (block_da_data.base_fee_scalar * 16 * block_da_data.l1_base_fee + + block_da_data.blob_base_fee_scalar * block_da_data.blob_base_fee) + as u128; + (estimated_size * fee_scaled) / DECIMAL_SCALAR + } + + fn fjord_linear_regression(fast_lz_size: u64) -> u64 { + let estimated_size = COST_INTERCEPT + COST_FASTLZ_COEF * fast_lz_size as i128; + let ret = estimated_size.clamp(MIN_TRANSACTION_SIZE, u64::MAX as i128); + ret as u64 + } +} diff --git a/crates/provider/src/alloy/da/local/mod.rs b/crates/provider/src/alloy/da/local/mod.rs new file mode 100644 index 000000000..24d3fc8d2 --- /dev/null +++ b/crates/provider/src/alloy/da/local/mod.rs @@ -0,0 +1,15 @@ +// This file is part of Rundler. +// +// Rundler is free software: you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later version. +// +// Rundler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with Rundler. +// If not, see https://www.gnu.org/licenses/. + +mod bedrock; +pub(crate) use bedrock::LocalBedrockDAGasOracle; diff --git a/crates/provider/src/alloy/da/mod.rs b/crates/provider/src/alloy/da/mod.rs index 115a45ff3..9a7311825 100644 --- a/crates/provider/src/alloy/da/mod.rs +++ b/crates/provider/src/alloy/da/mod.rs @@ -22,6 +22,8 @@ mod arbitrum; use arbitrum::ArbitrumNitroDAGasOracle; mod optimism; use optimism::OptimismBedrockDAGasOracle; +mod local; +use local::LocalBedrockDAGasOracle; /// Trait for a DA gas oracle #[async_trait::async_trait] @@ -68,6 +70,82 @@ where chain_spec.da_gas_oracle_contract_address, provider, )), + DAGasOracleContractType::LocalBedrock => Box::new(LocalBedrockDAGasOracle::new( + chain_spec.da_gas_oracle_contract_address, + provider, + )), DAGasOracleContractType::None => Box::new(ZeroDAGasOracle), } } + +#[cfg(test)] +mod tests { + use alloy_primitives::{address, b256, bytes, uint, U256}; + use alloy_provider::ProviderBuilder; + use alloy_sol_types::SolValue; + use rundler_contracts::v0_7::PackedUserOperation; + + use super::*; + + // This test may begin to fail if an optimism sepolia fork changes how the L1 gas oracle works. + // If that happens, we should update the local bedrock oracle to match the new fork logic in + // a backwards compatible way based on the fork booleans in the contract. + #[tokio::test] + async fn compare_opt_latest() { + let provider = opt_provider(); + let block = provider.get_block_number().await.unwrap(); + + compare_opt_and_local_bedrock(provider, block.into()).await; + } + + // This test should never fail for a block on the Fjord fork of optimism sepolia. + #[tokio::test] + async fn compare_opt_fixed() { + let provider = opt_provider(); + + compare_opt_and_local_bedrock(provider, 18343127.into()).await; + } + + async fn compare_opt_and_local_bedrock( + provider: impl AlloyProvider + Clone, + block: BlockHashOrNumber, + ) { + let oracle_addr = address!("420000000000000000000000000000000000000F"); + + let opt_contract_oracle = OptimismBedrockDAGasOracle::new(oracle_addr, provider.clone()); + let local_contract_oracle = LocalBedrockDAGasOracle::new(oracle_addr, provider.clone()); + + let gas_price = 1; + let to = Address::random(); + + let puo = PackedUserOperation { + sender: address!("f497A8026717FbbA3944c3dd2533c0716b7685e2"), + nonce: uint!(0x23_U256), + initCode: Bytes::default(), + callData: bytes!("b61d27f6000000000000000000000000f497a8026717fbba3944c3dd2533c0716b7685e2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004d087d28800000000000000000000000000000000000000000000000000000000"), + accountGasLimits: b256!("000000000000000000000000000114fc0000000000000000000000000012c9b5"), + preVerificationGas: U256::from(48916), + gasFees: b256!("000000000000000000000000524121000000000000000000000000109a4a441a"), + paymasterAndData: Bytes::default(), + signature: bytes!("0b83faeeac250d4c4a2459c1d6e1f8427f96af246d7fb3027b10bb05d934912f23a9491c16ab97ab32ac88179f279e871387c23547aa2e27b83fc358058e71fa1c"), + }; + let uo_data: Bytes = puo.abi_encode().into(); + + let opt_gas = opt_contract_oracle + .estimate_da_gas(to, uo_data.clone(), block, gas_price) + .await + .unwrap(); + let local_gas = local_contract_oracle + .estimate_da_gas(to, uo_data, block, gas_price) + .await + .unwrap(); + + assert_eq!(opt_gas, local_gas); + } + + fn opt_provider() -> impl AlloyProvider + Clone { + ProviderBuilder::new() + .on_http("https://sepolia.optimism.io".parse().unwrap()) + .boxed() + } +} diff --git a/crates/types/src/chain.rs b/crates/types/src/chain.rs index 885296182..784efa86c 100644 --- a/crates/types/src/chain.rs +++ b/crates/types/src/chain.rs @@ -129,6 +129,8 @@ pub enum DAGasOracleContractType { ArbitrumNitro, /// Optimism Bedrock type gas oracle contract OptimismBedrock, + /// Local Bedrock type gas oracle contract + LocalBedrock, } /// Type of oracle for estimating priority fees