From e87a9deb3af27bbd65b83f8e4e3da2a7f1a4e2bb Mon Sep 17 00:00:00 2001 From: dancoombs Date: Tue, 1 Oct 2024 10:36:28 -0500 Subject: [PATCH] feat(builder): update builder crate to alloy types --- Cargo.lock | 400 ++++++++++++++++-- Cargo.toml | 3 +- crates/builder/Cargo.toml | 17 +- crates/builder/src/bundle_proposer.rs | 276 ++++++------ crates/builder/src/bundle_sender.rs | 95 ++--- crates/builder/src/emit.rs | 33 +- crates/builder/src/sender/bloxroute.rs | 85 ++-- crates/builder/src/sender/flashbots.rs | 128 +++--- crates/builder/src/sender/mod.rs | 124 ++---- crates/builder/src/sender/raw.rs | 83 ++-- crates/builder/src/server/local.rs | 6 +- crates/builder/src/server/remote/client.rs | 12 +- crates/builder/src/server/remote/server.rs | 7 +- crates/builder/src/signer/aws.rs | 21 +- crates/builder/src/signer/local.rs | 46 ++ crates/builder/src/signer/mod.rs | 160 +++---- crates/builder/src/task.rs | 97 ++--- crates/builder/src/transaction_tracker.rs | 253 +++++------ crates/provider/Cargo.toml | 7 +- crates/provider/src/alloy/entry_point/mod.rs | 2 +- crates/provider/src/alloy/entry_point/v0_6.rs | 14 +- crates/provider/src/alloy/entry_point/v0_7.rs | 10 +- crates/provider/src/alloy/evm.rs | 12 + crates/provider/src/alloy/mod.rs | 23 + crates/provider/src/lib.rs | 1 + crates/sim/src/lib.rs | 2 + 26 files changed, 1131 insertions(+), 786 deletions(-) create mode 100644 crates/builder/src/signer/local.rs diff --git a/Cargo.lock b/Cargo.lock index db11b7f7b..cc7b2af4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,6 +281,7 @@ dependencies = [ "futures-utils-wasm", "lru", "pin-project", + "reqwest 0.12.7", "serde", "serde_json", "thiserror", @@ -321,8 +322,8 @@ dependencies = [ "alloy-transport", "alloy-transport-http", "futures", - "hyper-util", "pin-project", + "reqwest 0.12.7", "serde", "serde_json", "tokio", @@ -390,6 +391,40 @@ dependencies = [ "thiserror", ] +[[package]] +name = "alloy-signer-aws" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076be69aa26a4c500919f1ad3847662aa6d1e9bc2995e263ed826b1546d1b990" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "aws-sdk-kms", + "k256", + "spki", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-signer-local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fabe917ab1778e760b4701628d1cae8e028ee9d52ac6307de4e1e9286ab6b5f" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "k256", + "rand", + "thiserror", +] + [[package]] name = "alloy-sol-macro" version = "0.8.3" @@ -490,9 +525,7 @@ checksum = "1742b94bb814f1ca6b322a6f9dd38a0252ff45a3119e40e888fb7029afa500ce" dependencies = [ "alloy-json-rpc", "alloy-transport", - "http-body-util", - "hyper 1.4.1", - "hyper-util", + "reqwest 0.12.7", "serde_json", "tower 0.5.1", "tracing", @@ -917,6 +950,43 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "aws-config" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8191fb3091fa0561d1379ef80333c3c7191c6f0435d986e85821bcf7acbd1126" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand 2.1.1", + "http 0.2.12", + "time", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + [[package]] name = "aws-lc-rs" version = "1.8.1" @@ -944,6 +1014,235 @@ dependencies = [ "paste", ] +[[package]] +name = "aws-runtime" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a10d5c055aa540164d9561a0e2e74ad30f0dcf7393c3a92f6733ddf9c5762468" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand 2.1.1", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid 1.10.0", +] + +[[package]] +name = "aws-sdk-kms" +version = "1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0caf20b8855dbeb458552e6c8f8f9eb92b95e4a131725b93540ec73d60c38eb3" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb5f98188ec1435b68097daa2a37d74b9d17c9caa799466338a8d1544e71b9d" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8db6904450bafe7473c6ca9123f88cc11089e41a025408f992db4e22d3be68" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac 0.12.1", + "http 0.2.12", + "http 1.1.0", + "once_cell", + "percent-encoding", + "sha2 0.10.8", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce695746394772e7000b39fe073095db6d45a862d0767dd5ad0ac0d7f8eb87" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand 2.1.1", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.1", + "httparse", + "once_cell", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e086682a53d3aa241192aa110fa8dfce98f2f5ac2ead0de84d41582c7e8fdb96" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.1.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147100a7bea70fa20ef224a6bad700358305f5dc0f84649c53769761395b355b" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "http 0.2.12", + "http 1.1.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version 0.4.1", + "tracing", +] + [[package]] name = "axum" version = "0.7.5" @@ -1030,6 +1329,16 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.6.0" @@ -1187,6 +1496,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "bzip2" version = "0.4.4" @@ -2012,7 +2331,7 @@ dependencies = [ "sha2 0.10.8", "sha3", "thiserror", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -2274,10 +2593,7 @@ dependencies = [ "eth-keystore", "ethers-core", "rand", - "rusoto_core", - "rusoto_kms", "sha2 0.10.8", - "spki", "thiserror", "tracing", ] @@ -3999,6 +4315,12 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + [[package]] name = "overload" version = "0.1.1" @@ -4806,6 +5128,12 @@ dependencies = [ "regex-syntax 0.8.4", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -5085,11 +5413,17 @@ dependencies = [ name = "rundler-builder" version = "0.3.0" dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-signer", + "alloy-signer-aws", + "alloy-signer-local", "anyhow", "async-trait", + "aws-config", + "aws-sdk-kms", "enum_dispatch", - "ethers", - "ethers-signers", "futures", "futures-timer", "futures-util", @@ -5097,18 +5431,18 @@ dependencies = [ "linked-hash-map", "metrics", "mockall", + "num-traits", "parse-display", "pin-project", "prost", "reqwest 0.12.7", "rslock", + "ruint", "rundler-provider", "rundler-sim", "rundler-task", "rundler-types", "rundler-utils", - "rusoto_core", - "rusoto_kms", "serde", "serde_json", "strum", @@ -5180,20 +5514,24 @@ dependencies = [ "alloy-primitives", "alloy-provider", "alloy-rlp", + "alloy-rpc-client", "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-sol-types", "alloy-transport", + "alloy-transport-http", "anyhow", "async-trait", "auto_impl", "mockall", + "reqwest 0.12.7", "rundler-contracts", "rundler-provider", "rundler-types", "rundler-utils", "thiserror", "tracing", + "url", ] [[package]] @@ -5362,20 +5700,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rusoto_kms" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1fc19cfcfd9f6b2f96e36d5b0dddda9004d2cbfc2d17543e3b9f10cc38fce8" -dependencies = [ - "async-trait", - "bytes", - "futures", - "rusoto_core", - "serde", - "serde_json", -] - [[package]] name = "rusoto_s3" version = "0.48.0" @@ -6919,6 +7243,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -6941,6 +7271,12 @@ dependencies = [ "serde", ] +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" + [[package]] name = "valuable" version = "0.1.0" @@ -6959,6 +7295,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -7437,6 +7779,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 8303b4b50..853618da0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,11 +23,12 @@ alloy-sol-types = "0.8.3" alloy-consensus = "0.3.3" alloy-contract = "0.3.3" alloy-json-rpc = "0.3.3" -alloy-provider = { version = "0.3.3", default-features = false } +alloy-provider = { version = "0.3.3", default-features = false, features = ["reqwest", "reqwest-rustls-tls"] } alloy-rpc-client = "0.3.3" alloy-rpc-types-eth = "0.3.3" alloy-rpc-types-trace = "0.3.3" alloy-transport = "0.3.3" +alloy-transport-http = { version = "0.3.3", default-features = false, features = ["reqwest", "reqwest-rustls-tls"] } # alloy other alloy-rlp = "0.3.8" diff --git a/crates/builder/Cargo.toml b/crates/builder/Cargo.toml index f7450ffde..1a0474383 100644 --- a/crates/builder/Cargo.toml +++ b/crates/builder/Cargo.toml @@ -14,24 +14,31 @@ rundler-task = { path = "../task" } rundler-types = { path = "../types" } rundler-utils = { path = "../utils" } +alloy-consensus.workspace = true +alloy-eips = "0.3.3" +alloy-primitives.workspace = true +alloy-signer = "0.3.3" +alloy-signer-aws = "0.3.3" +alloy-signer-local = "0.3.3" + anyhow.workspace = true async-trait.workspace = true +aws-config = { version = "1.5.6", default-features = false } +aws-sdk-kms = { version = "1.44", default-features = false } enum_dispatch = "0.3.13" -ethers.workspace = true -ethers-signers = {version = "2.0.14", features = ["aws"] } futures.workspace = true futures-timer = "3.0.3" futures-util.workspace = true jsonrpsee = { workspace = true, features = [ "http-client" ]} linked-hash-map = "0.5.6" metrics.workspace = true +num-traits = "0.2.19" pin-project.workspace = true prost.workspace = true parse-display.workspace = true -reqwest.workspace = true +reqwest = { workspace = true, default-features = false, features = ["json"] } rslock = "0.4.0" -rusoto_core = { version = "0.48.0", default-features = false, features = ["rustls"] } -rusoto_kms = { version = "0.48.0", default-features = false, features = ["rustls"] } +ruint = { version = "1.12.3", features = ["num-traits"]} thiserror.workspace = true tokio.workspace = true tokio-util.workspace = true diff --git a/crates/builder/src/bundle_proposer.rs b/crates/builder/src/bundle_proposer.rs index 656f8be92..fe4f65003 100644 --- a/crates/builder/src/bundle_proposer.rs +++ b/crates/builder/src/bundle_proposer.rs @@ -20,16 +20,16 @@ use std::{ sync::Arc, }; +use alloy_primitives::{Address, Bytes, B256, U256}; use anyhow::Context; use async_trait::async_trait; -use ethers::types::{Address, BlockId, Bytes, H256, U256}; use futures::future; use futures_util::TryFutureExt; use linked_hash_map::LinkedHashMap; #[cfg(test)] use mockall::automock; use rundler_provider::{ - BundleHandler, EntryPoint, HandleOpsOut, L1GasProvider, Provider, SignatureAggregator, + BundleHandler, EntryPoint, EvmProvider, HandleOpsOut, L1GasProvider, SignatureAggregator, }; use rundler_sim::{ gas, ExpectedStorage, FeeEstimator, PriorityFeeMode, SimulationError, SimulationResult, @@ -49,12 +49,12 @@ use tracing::{error, info, warn}; use crate::emit::{BuilderEvent, ConditionNotMetReason, OpRejectionReason, SkipReason}; /// Extra buffer percent to add on the bundle transaction gas estimate to be sure it will be enough -const BUNDLE_TRANSACTION_GAS_OVERHEAD_PERCENT: u64 = 5; +const BUNDLE_TRANSACTION_GAS_OVERHEAD_PERCENT: u32 = 5; #[derive(Debug)] pub(crate) struct Bundle { pub(crate) ops_per_aggregator: Vec>, - pub(crate) gas_estimate: U256, + pub(crate) gas_estimate: u128, pub(crate) gas_fees: GasFees, pub(crate) expected_storage: ExpectedStorage, pub(crate) rejected_ops: Vec, @@ -65,7 +65,7 @@ impl Default for Bundle { fn default() -> Self { Self { ops_per_aggregator: Vec::new(), - gas_estimate: U256::zero(), + gas_estimate: 0, gas_fees: GasFees::default(), expected_storage: ExpectedStorage::default(), rejected_ops: Vec::new(), @@ -93,7 +93,7 @@ impl Bundle { #[async_trait] #[cfg_attr(test, automock(type UO = rundler_types::v0_6::UserOperation;))] -pub(crate) trait BundleProposer: Send + Sync + 'static { +pub(crate) trait BundleProposer: Send + Sync { type UO: UserOperation; /// Constructs the next bundle @@ -112,7 +112,7 @@ pub(crate) trait BundleProposer: Send + Sync + 'static { async fn estimate_gas_fees( &self, min_fees: Option, - ) -> BundleProposerResult<(GasFees, U256)>; + ) -> BundleProposerResult<(GasFees, u128)>; /// Notifies the proposer that a condition was not met during the last bundle proposal fn notify_condition_not_met(&mut self); @@ -134,14 +134,14 @@ pub(crate) enum BundleProposerError { } #[derive(Debug)] -pub(crate) struct BundleProposerImpl { +pub(crate) struct BundleProposerImpl { builder_index: u64, pool: M, simulator: S, entry_point: E, - provider: Arc

, + provider: P, settings: Settings, - fee_estimator: FeeEstimator

, + fee_estimator: F, event_sender: broadcast::Sender>, condition_not_met_notified: bool, _uo_type: PhantomData, @@ -151,28 +151,29 @@ pub(crate) struct BundleProposerImpl { pub(crate) struct Settings { pub(crate) chain_spec: ChainSpec, pub(crate) max_bundle_size: u64, - pub(crate) max_bundle_gas: u64, + pub(crate) max_bundle_gas: u128, pub(crate) beneficiary: Address, - pub(crate) bundle_priority_fee_overhead_percent: u64, + pub(crate) bundle_priority_fee_overhead_percent: u32, pub(crate) priority_fee_mode: PriorityFeeMode, } #[async_trait] -impl BundleProposer for BundleProposerImpl +impl BundleProposer for BundleProposerImpl where UO: UserOperation + From, UserOperationVariant: AsRef, S: Simulator, E: EntryPoint + SignatureAggregator + BundleHandler + L1GasProvider, - P: Provider, + P: EvmProvider, M: Pool, + F: FeeEstimator, { type UO = UO; async fn estimate_gas_fees( &self, required_fees: Option, - ) -> BundleProposerResult<(GasFees, U256)> { + ) -> BundleProposerResult<(GasFees, u128)> { Ok(self .fee_estimator .required_bundle_fees(required_fees) @@ -301,21 +302,24 @@ where } } -impl BundleProposerImpl +impl BundleProposerImpl where UO: UserOperation + From, UserOperationVariant: AsRef, S: Simulator, E: EntryPoint + SignatureAggregator + BundleHandler + L1GasProvider, - P: Provider, + P: EvmProvider, M: Pool, + F: FeeEstimator, { + #[allow(clippy::too_many_arguments)] pub(crate) fn new( builder_index: u64, pool: M, simulator: S, entry_point: E, - provider: Arc

, + provider: P, + fee_estimator: F, settings: Settings, event_sender: broadcast::Sender>, ) -> Self { @@ -324,13 +328,8 @@ where pool, simulator, entry_point, - provider: provider.clone(), - fee_estimator: FeeEstimator::new( - &settings.chain_spec, - provider, - settings.priority_fee_mode, - settings.bundle_priority_fee_overhead_percent, - ), + provider, + fee_estimator, settings, event_sender, condition_not_met_notified: false, @@ -346,7 +345,7 @@ where async fn check_fees( &self, op: PoolOperation, - base_fee: U256, + base_fee: u128, required_op_fees: GasFees, ) -> Option { let op_hash = self.op_hash(&op.uo); @@ -420,7 +419,7 @@ where async fn simulate_op( &self, op: PoolOperation, - block_hash: H256, + block_hash: B256, ) -> Option<(PoolOperation, Result)> { let op_hash = self.op_hash(&op.uo); @@ -528,7 +527,7 @@ where // Skip this op if the bundle does not have enough remaining gas to execute it. let required_gas = gas_spent + gas::user_operation_execution_gas_limit(&self.settings.chain_spec, &op, false); - if required_gas > self.settings.max_bundle_gas.into() { + if required_gas > self.settings.max_bundle_gas { continue; } @@ -705,7 +704,7 @@ where async fn estimate_gas_rejecting_failed_ops( &self, context: &mut ProposalContext, - ) -> BundleProposerResult> { + ) -> BundleProposerResult> { // sum up the gas needed for all the ops in the bundle // and apply an overhead multiplier let gas = math::increase_by_percent( @@ -759,7 +758,7 @@ where Ok(self .pool .get_ops( - self.entry_point.address(), + *self.entry_point.address(), self.settings.max_bundle_size, self.builder_index, ) @@ -772,12 +771,12 @@ where async fn get_balances_by_paymaster( &self, addresses: impl IntoIterator, - block_hash: H256, + block_hash: B256, ) -> BundleProposerResult> { let futures = addresses.into_iter().map(|address| async move { let deposit = self .entry_point - .balance_of(address, Some(BlockId::Hash(block_hash))) + .balance_of(address, Some(block_hash.into())) .await?; Ok::<_, anyhow::Error>((address, deposit)) }); @@ -868,7 +867,7 @@ where async fn process_post_op_revert( &self, context: &mut ProposalContext, - gas: U256, + gas: u128, ) -> anyhow::Result<()> { let agg_groups = context.to_ops_per_aggregator(); let mut op_index = 0; @@ -921,12 +920,12 @@ where async fn check_for_post_op_revert_single_op( &self, op: UO, - gas: U256, + gas: u128, op_index: usize, ) -> Vec { let op_hash = self.op_hash(&op); let bundle = vec![UserOpsPerAggregator { - aggregator: Address::zero(), + aggregator: Address::ZERO, signature: Bytes::new(), user_ops: vec![op], }]; @@ -957,7 +956,7 @@ where async fn check_for_post_op_revert_agg_ops( &self, group: UserOpsPerAggregator, - gas: U256, + gas: u128, start_index: usize, ) -> Vec { let len = group.user_ops.len(); @@ -990,9 +989,9 @@ where fn limit_user_operations_for_simulation( &self, ops: Vec, - ) -> (Vec, u64) { + ) -> (Vec, u128) { // Make the bundle gas limit 10% higher here so that we simulate more UOs than we need in case that we end up dropping some UOs later so we can still pack a full bundle - let mut gas_left = math::increase_by_percent(U256::from(self.settings.max_bundle_gas), 10); + let mut gas_left = math::increase_by_percent(self.settings.max_bundle_gas, 10); let mut ops_in_bundle = Vec::new(); for op in ops { // Here we use optimistic gas limits for the UOs by assuming none of the paymaster UOs use postOp calls. @@ -1012,24 +1011,22 @@ where } ( ops_in_bundle, - self.settings - .max_bundle_gas - .saturating_sub(gas_left.as_u64()), + self.settings.max_bundle_gas.saturating_sub(gas_left), ) } fn emit(&self, event: BuilderEvent) { let _ = self.event_sender.send(WithEntryPoint { - entry_point: self.entry_point.address(), + entry_point: *self.entry_point.address(), event, }); } - fn op_hash(&self, op: &T) -> H256 + fn op_hash(&self, op: &T) -> B256 where T: UserOperation, { - op.hash(self.entry_point.address(), self.settings.chain_spec.id) + op.hash(*self.entry_point.address(), self.settings.chain_spec.id) } } @@ -1230,7 +1227,7 @@ impl ProposalContext { .collect() } - fn get_bundle_gas_limit(&self, chain_spec: &ChainSpec) -> U256 { + fn get_bundle_gas_limit(&self, chain_spec: &ChainSpec) -> u128 { // TODO(danc): in the 0.7 entrypoint we could optimize this by removing the need for // the 10K gas and 63/64 gas overheads for each op in the bundle and instead calculate exactly // the limit needed to include that overhead for each op. @@ -1241,7 +1238,7 @@ impl ProposalContext { self.iter_ops_with_simulations() .map(|sim_op| gas::user_operation_gas_limit(chain_spec, &sim_op.op, false)) - .fold(U256::zero(), |acc, i| acc + i) + .sum::() + chain_spec.transaction_intrinsic_gas } @@ -1413,13 +1410,10 @@ impl ProposalContext { mod tests { use std::time::Duration; + use alloy_primitives::{utils::parse_units, Address, B256}; use anyhow::anyhow; - use ethers::{ - types::{H160, U64}, - utils::parse_units, - }; - use rundler_provider::{AggregatorSimOut, MockEntryPointV0_6, MockProvider}; - use rundler_sim::MockSimulator; + use rundler_provider::{AggregatorSimOut, MockEntryPointV0_6, MockEvmProvider}; + use rundler_sim::{MockFeeEstimator, MockSimulator}; use rundler_types::{ pool::{MockPool, SimulationViolation}, v0_6::{UserOperation, ENTRY_POINT_INNER_GAS_OVERHEAD}, @@ -1431,9 +1425,9 @@ mod tests { #[tokio::test] async fn test_singleton_valid_bundle() { let op = UserOperation { - pre_verification_gas: DEFAULT_PVG.into(), - verification_gas_limit: 10000.into(), - call_gas_limit: 100000.into(), + pre_verification_gas: DEFAULT_PVG, + verification_gas_limit: 10000, + call_gas_limit: 100000, ..Default::default() }; @@ -1580,10 +1574,10 @@ mod tests { async fn test_drops_but_not_rejects_op_with_too_low_max_priority_fee() { // With 10% required overhead on priority fee, op1 should be excluded // but op2 accepted. - let base_fee = U256::from(1000); - let max_priority_fee_per_gas = U256::from(50); - let op1 = op_with_sender_and_fees(address(1), 2054.into(), 54.into()); - let op2 = op_with_sender_and_fees(address(2), 2055.into(), 55.into()); + let base_fee = 1000; + let max_priority_fee_per_gas = 50; + let op1 = op_with_sender_and_fees(address(1), 2049, 49); + let op2 = op_with_sender_and_fees(address(2), 2050, 50); let bundle = mock_make_bundle( vec![ MockOp { @@ -1616,10 +1610,10 @@ mod tests { #[tokio::test] async fn test_drops_but_not_rejects_op_with_too_low_max_fee_per_gas() { - let base_fee = U256::from(1000); - let max_priority_fee_per_gas = U256::from(50); - let op1 = op_with_sender_and_fees(address(1), 1054.into(), 55.into()); - let op2 = op_with_sender_and_fees(address(2), 1055.into(), 55.into()); + let base_fee = 1000; + let max_priority_fee_per_gas = 50; + let op1 = op_with_sender_and_fees(address(1), 1049, 49); + let op2 = op_with_sender_and_fees(address(2), 1050, 50); let bundle = mock_make_bundle( vec![ MockOp { @@ -1643,8 +1637,8 @@ mod tests { assert_eq!( bundle.gas_fees, GasFees { - max_fee_per_gas: 1050.into(), - max_priority_fee_per_gas: 50.into(), + max_fee_per_gas: 1050, + max_priority_fee_per_gas: 50, } ); assert_eq!( @@ -1659,11 +1653,11 @@ mod tests { #[tokio::test] async fn test_drops_but_not_rejects_op_with_too_low_pvg() { - let base_fee = U256::from(1000); - let max_priority_fee_per_gas = U256::from(50); - let mut op1 = op_with_sender_and_fees(address(1), 1054.into(), 55.into()); - op1.pre_verification_gas = 0.into(); // Should be dropped but not rejected - let op2 = op_with_sender_and_fees(address(2), 1055.into(), 55.into()); + let base_fee = 1000; + let max_priority_fee_per_gas = 50; + let mut op1 = op_with_sender_and_fees(address(1), 1054, 55); + op1.pre_verification_gas = 0; // Should be dropped but not rejected + let op2 = op_with_sender_and_fees(address(2), 1055, 55); let bundle = mock_make_bundle( vec![ MockOp { @@ -1687,8 +1681,8 @@ mod tests { assert_eq!( bundle.gas_fees, GasFees { - max_fee_per_gas: 1050.into(), - max_priority_fee_per_gas: 50.into(), + max_fee_per_gas: 1050, + max_priority_fee_per_gas: 50, } ); assert_eq!( @@ -1771,8 +1765,8 @@ mod tests { ], vec![HandleOpsOut::Success], vec![], - U256::zero(), - U256::zero(), + 0, + 0, false, ExpectedStorage::default(), ) @@ -1861,8 +1855,8 @@ mod tests { HandleOpsOut::Success, ], vec![deposit, deposit, deposit], - U256::zero(), - U256::zero(), + 0, + 0, false, ExpectedStorage::default(), ) @@ -1895,11 +1889,11 @@ mod tests { async fn test_bundle_gas_limit_simple() { // Limit is 10M // Each OP has 1M pre_verification_gas, 0 verification_gas_limit, call_gas_limit is passed in - let op1 = op_with_sender_call_gas_limit(address(1), U256::from(4_000_000)); - let op2 = op_with_sender_call_gas_limit(address(2), U256::from(3_000_000)); + let op1 = op_with_sender_call_gas_limit(address(1), 4_000_000); + let op2 = op_with_sender_call_gas_limit(address(2), 3_000_000); // these two shouldn't be included in the bundle - let op3 = op_with_sender_call_gas_limit(address(3), U256::from(10_000_000)); - let op4 = op_with_sender_call_gas_limit(address(4), U256::from(10_000_000)); + let op3 = op_with_sender_call_gas_limit(address(3), 10_000_000); + let op4 = op_with_sender_call_gas_limit(address(4), 10_000_000); let deposit = parse_units("1", "ether").unwrap().into(); let bundle = mock_make_bundle( @@ -1924,8 +1918,8 @@ mod tests { vec![], vec![HandleOpsOut::Success], vec![deposit, deposit, deposit], - U256::zero(), - U256::zero(), + 0, + 0, false, ExpectedStorage::default(), ) @@ -1942,18 +1936,18 @@ mod tests { ); assert_eq!( bundle.gas_estimate, - U256::from(math::increase_by_percent( + math::increase_by_percent( 9_000_000 + 2 * 5_000 + 21_000, BUNDLE_TRANSACTION_GAS_OVERHEAD_PERCENT - )) + ) ); } #[tokio::test] async fn test_bundle_gas_limit() { let cs = ChainSpec::default(); - let op1 = op_with_gas(100_000.into(), 100_000.into(), 1_000_000.into(), false); - let op2 = op_with_gas(100_000.into(), 100_000.into(), 200_000.into(), false); + let op1 = op_with_gas(100_000, 100_000, 1_000_000, false); + let op2 = op_with_gas(100_000, 100_000, 200_000, false); let mut groups_by_aggregator = LinkedHashMap::new(); groups_by_aggregator.insert( None, @@ -1999,8 +1993,8 @@ mod tests { #[tokio::test] async fn test_bundle_gas_limit_with_paymaster_op() { let cs = ChainSpec::default(); - let op1 = op_with_gas(100_000.into(), 100_000.into(), 1_000_000.into(), true); // has paymaster - let op2 = op_with_gas(100_000.into(), 100_000.into(), 200_000.into(), false); + let op1 = op_with_gas(100_000, 100_000, 1_000_000, true); // has paymaster + let op2 = op_with_gas(100_000, 100_000, 200_000, false); let mut groups_by_aggregator = LinkedHashMap::new(); groups_by_aggregator.insert( None, @@ -2055,8 +2049,8 @@ mod tests { vec![], vec![HandleOpsOut::PostOpRevert, HandleOpsOut::PostOpRevert], vec![], - U256::zero(), - U256::zero(), + 0, + 0, false, ExpectedStorage::default(), ) @@ -2089,8 +2083,8 @@ mod tests { HandleOpsOut::Success, ], vec![], - U256::zero(), - U256::zero(), + 0, + 0, false, ExpectedStorage::default(), ) @@ -2157,8 +2151,8 @@ mod tests { HandleOpsOut::Success, // after remove ], vec![], - U256::zero(), - U256::zero(), + 0, + 0, false, ExpectedStorage::default(), ) @@ -2182,7 +2176,7 @@ mod tests { let op = default_op(); let mut expected_storage = ExpectedStorage::default(); - expected_storage.insert(address(1), U256::zero(), U256::zero()); + expected_storage.insert(address(1), U256::ZERO, U256::ZERO); let actual_storage = expected_storage.clone(); let bundle = mock_make_bundle( @@ -2198,8 +2192,8 @@ mod tests { vec![], vec![HandleOpsOut::Success], vec![], - U256::zero(), - U256::zero(), + 0, + 0, true, actual_storage, ) @@ -2219,9 +2213,9 @@ mod tests { let op = default_op(); let mut expected_storage = ExpectedStorage::default(); - expected_storage.insert(address(1), U256::zero(), U256::zero()); + expected_storage.insert(address(1), U256::ZERO, U256::ZERO); let mut actual_storage = ExpectedStorage::default(); - actual_storage.insert(address(1), U256::zero(), U256::from(1)); + actual_storage.insert(address(1), U256::ZERO, U256::from(1)); let bundle = mock_make_bundle( vec![MockOp { @@ -2236,8 +2230,8 @@ mod tests { vec![], vec![HandleOpsOut::Success], vec![], - U256::zero(), - U256::zero(), + 0, + 0, true, actual_storage, ) @@ -2263,8 +2257,8 @@ mod tests { vec![], vec![HandleOpsOut::Success], vec![], - U256::zero(), - U256::zero(), + 0, + 0, false, ExpectedStorage::default(), ) @@ -2277,8 +2271,8 @@ mod tests { mock_aggregators: Vec, mock_handle_ops_call_results: Vec, mock_paymaster_deposits: Vec, - base_fee: U256, - max_priority_fee_per_gas: U256, + base_fee: u128, + max_priority_fee_per_gas: u128, notify_condition_not_met: bool, actual_storage: ExpectedStorage, ) -> Bundle { @@ -2341,16 +2335,30 @@ mod tests { .map(|agg| (agg.address, agg.signature)) .collect(); - let mut provider = MockProvider::new(); + let mut provider = MockEvmProvider::new(); provider .expect_get_latest_block_hash_and_number() - .returning(move || Ok((current_block_hash, U64::zero()))); - provider - .expect_get_base_fee() - .returning(move || Ok(base_fee)); - provider - .expect_get_max_priority_fee() - .returning(move || Ok(max_priority_fee_per_gas)); + .returning(move || Ok((current_block_hash, 0))); + + let mut fee_estimator = MockFeeEstimator::new(); + fee_estimator + .expect_required_bundle_fees() + .returning(move |_| { + Ok(( + GasFees { + max_fee_per_gas: base_fee + max_priority_fee_per_gas, + max_priority_fee_per_gas, + }, + base_fee, + )) + }); + fee_estimator + .expect_required_op_fees() + .returning(move |_| GasFees { + max_fee_per_gas: base_fee + max_priority_fee_per_gas, + max_priority_fee_per_gas, + }); + if notify_condition_not_met { for (addr, slots) in actual_storage.0.into_iter() { let values = slots.values().cloned().collect::>(); @@ -2364,19 +2372,21 @@ mod tests { entry_point .expect_aggregate_signatures() .returning(move |address, _| Ok(signatures_by_aggregator[&address]().unwrap())); + let (event_sender, _) = broadcast::channel(16); let mut proposer = BundleProposerImpl::new( 0, pool_client, simulator, entry_point, - Arc::new(provider), + provider, + fee_estimator, Settings { chain_spec: ChainSpec::default(), max_bundle_size, max_bundle_gas: 10_000_000, beneficiary, - priority_fee_mode: PriorityFeeMode::PriorityFeeIncreasePercent(10), + priority_fee_mode: PriorityFeeMode::PriorityFeeIncreasePercent(0), bundle_priority_fee_overhead_percent: 0, }, event_sender, @@ -2395,13 +2405,13 @@ mod tests { fn address(n: u8) -> Address { let mut bytes = [0_u8; 20]; bytes[0] = n; - H160(bytes) + Address::from_slice(&bytes) } - fn hash(n: u8) -> H256 { + fn hash(n: u8) -> B256 { let mut bytes = [0_u8; 32]; bytes[0] = n; - H256(bytes) + B256::from_slice(&bytes) } fn bytes(n: u8) -> Bytes { @@ -2409,12 +2419,12 @@ mod tests { } // UOs require PVG to pass the PVG check even when fees are 0 - const DEFAULT_PVG: u64 = 1_000_000; + const DEFAULT_PVG: u128 = 1_000_000; fn op_with_sender(sender: Address) -> UserOperation { UserOperation { sender, - pre_verification_gas: U256::from(DEFAULT_PVG), + pre_verification_gas: DEFAULT_PVG, ..Default::default() } } @@ -2422,8 +2432,8 @@ mod tests { fn op_with_sender_paymaster(sender: Address, paymaster: Address) -> UserOperation { UserOperation { sender, - paymaster_and_data: paymaster.as_bytes().to_vec().into(), - pre_verification_gas: U256::from(DEFAULT_PVG), + paymaster_and_data: paymaster.to_vec().into(), + pre_verification_gas: DEFAULT_PVG, ..Default::default() } } @@ -2431,46 +2441,46 @@ mod tests { fn op_with_sender_factory(sender: Address, factory: Address) -> UserOperation { UserOperation { sender, - init_code: factory.as_bytes().to_vec().into(), - pre_verification_gas: U256::from(DEFAULT_PVG), + init_code: factory.to_vec().into(), + pre_verification_gas: DEFAULT_PVG, ..Default::default() } } fn op_with_sender_and_fees( sender: Address, - max_fee_per_gas: U256, - max_priority_fee_per_gas: U256, + max_fee_per_gas: u128, + max_priority_fee_per_gas: u128, ) -> UserOperation { UserOperation { sender, max_fee_per_gas, max_priority_fee_per_gas, - pre_verification_gas: U256::from(DEFAULT_PVG), + pre_verification_gas: DEFAULT_PVG, ..Default::default() } } fn default_op() -> UserOperation { UserOperation { - pre_verification_gas: U256::from(DEFAULT_PVG), + pre_verification_gas: DEFAULT_PVG, ..Default::default() } } - fn op_with_sender_call_gas_limit(sender: Address, call_gas_limit: U256) -> UserOperation { + fn op_with_sender_call_gas_limit(sender: Address, call_gas_limit: u128) -> UserOperation { UserOperation { sender, call_gas_limit, - pre_verification_gas: U256::from(DEFAULT_PVG), + pre_verification_gas: DEFAULT_PVG, ..Default::default() } } fn op_with_gas( - pre_verification_gas: U256, - call_gas_limit: U256, - verification_gas_limit: U256, + pre_verification_gas: u128, + call_gas_limit: u128, + verification_gas_limit: u128, with_paymaster: bool, ) -> UserOperation { UserOperation { diff --git a/crates/builder/src/bundle_sender.rs b/crates/builder/src/bundle_sender.rs index fe283fca4..4df45051e 100644 --- a/crates/builder/src/bundle_sender.rs +++ b/crates/builder/src/bundle_sender.rs @@ -13,13 +13,13 @@ use std::{marker::PhantomData, sync::Arc, time::Duration}; +use alloy_primitives::{Address, B256}; use anyhow::{bail, Context}; use async_trait::async_trait; -use ethers::types::{transaction::eip2718::TypedTransaction, Address, H256, U256}; use futures_util::StreamExt; #[cfg(test)] use mockall::automock; -use rundler_provider::{BundleHandler, EntryPoint}; +use rundler_provider::{BundleHandler, EntryPoint, TransactionRequest}; use rundler_sim::ExpectedStorage; use rundler_types::{ builder::BundlingMode, @@ -41,7 +41,7 @@ use crate::{ }; #[async_trait] -pub(crate) trait BundleSender: Send + Sync + 'static { +pub(crate) trait BundleSender: Send + Sync { async fn send_bundles_in_loop(self) -> anyhow::Result<()>; } @@ -70,9 +70,9 @@ pub(crate) struct BundleSenderImpl { #[derive(Debug)] struct BundleTx { - tx: TypedTransaction, + tx: TransactionRequest, expected_storage: ExpectedStorage, - op_hashes: Vec, + op_hashes: Vec, } pub enum BundleSenderAction { @@ -92,7 +92,7 @@ pub enum SendBundleResult { Success { block_number: u64, attempt_number: u64, - tx_hash: H256, + tx_hash: B256, }, NoOperationsInitially, StalledAtMaxFeeIncreases, @@ -186,7 +186,7 @@ where event_sender, metrics: BuilderMetrics { builder_index, - entry_point: entry_point.address(), + entry_point: *entry_point.address(), }, entry_point, _uo_type: PhantomData, @@ -328,7 +328,7 @@ where self.emit(BuilderEvent::transaction_mined( self.builder_index, tx_hash, - nonce.low_u64(), + nonce, block_number, )); let send_bundle_result = Some(SendBundleResult::Success { @@ -342,7 +342,7 @@ where info!("Latest transaction dropped, starting new bundle attempt"); self.emit(BuilderEvent::latest_transaction_dropped( self.builder_index, - nonce.low_u64(), + nonce, )); self.metrics.increment_bundle_txns_dropped(); // try again, increasing fees @@ -352,7 +352,7 @@ where info!("Nonce used externally, starting new bundle attempt"); self.emit(BuilderEvent::nonce_used_for_other_transaction( self.builder_index, - nonce.low_u64(), + nonce, )); self.metrics.increment_bundle_txns_nonce_used(); state.reset(); @@ -392,7 +392,7 @@ where let cancel_res = state .transaction_tracker - .cancel_transaction(self.entry_point.address(), estimated_fees) + .cancel_transaction(*self.entry_point.address(), estimated_fees) .await; match cancel_res { @@ -459,8 +459,7 @@ where info!("Cancellation transaction mined. Price (wei) {fee:?}"); self.metrics.increment_cancellation_txns_mined(); if let Some(fee) = fee { - self.metrics - .increment_cancellation_txns_total_fee(fee.as_u64()); + self.metrics.increment_cancellation_txns_total_fee(fee); }; } TrackerUpdate::LatestTxDropped { .. } => { @@ -527,7 +526,7 @@ where self.emit(BuilderEvent::formed_bundle( self.builder_index, None, - nonce.low_u64(), + nonce, fee_increase_count, required_fees, )); @@ -555,7 +554,7 @@ where tx, op_hashes: Arc::new(op_hashes), }), - nonce.low_u64(), + nonce, fee_increase_count, required_fees, )); @@ -588,7 +587,7 @@ where /// it, or `None` if there are no valid operations available. async fn get_bundle_tx( &mut self, - nonce: U256, + nonce: u64, bundle: Bundle, ) -> anyhow::Result> { let remove_ops_future = async { @@ -638,7 +637,7 @@ where bundle.gas_estimate, bundle.gas_fees, ); - tx.set_nonce(nonce); + tx = tx.nonce(nonce); Ok(Some(BundleTx { tx, expected_storage: bundle.expected_storage, @@ -649,9 +648,9 @@ where async fn remove_ops_from_pool(&self, ops: &[UO]) -> anyhow::Result<()> { self.pool .remove_ops( - self.entry_point.address(), + *self.entry_point.address(), ops.iter() - .map(|op| op.hash(self.entry_point.address(), self.chain_spec.id)) + .map(|op| op.hash(*self.entry_point.address(), self.chain_spec.id)) .collect(), ) .await @@ -660,20 +659,20 @@ where async fn update_entities_in_pool(&self, entity_updates: &[EntityUpdate]) -> anyhow::Result<()> { self.pool - .update_entities(self.entry_point.address(), entity_updates.to_vec()) + .update_entities(*self.entry_point.address(), entity_updates.to_vec()) .await .context("builder should remove update entities in the pool") } fn emit(&self, event: BuilderEvent) { let _ = self.event_sender.send(WithEntryPoint { - entry_point: self.entry_point.address(), + entry_point: *self.entry_point.address(), event, }); } - fn op_hash(&self, op: &UO) -> H256 { - op.hash(self.entry_point.address(), self.chain_spec.id) + fn op_hash(&self, op: &UO) -> B256 { + op.hash(*self.entry_point.address(), self.chain_spec.id) } } @@ -1036,7 +1035,7 @@ impl BundleSenderTrigger { bundle_action_receiver, timer: tokio::time::interval(timer_interval), last_block: NewHead { - block_hash: H256::zero(), + block_hash: B256::ZERO, block_number: 0, }, }) @@ -1102,14 +1101,14 @@ impl BuilderMetrics { .increment(1); } - fn process_bundle_txn_success(&self, gas_limit: Option, gas_used: Option) { + fn process_bundle_txn_success(&self, gas_limit: Option, gas_used: Option) { metrics::counter!("builder_bundle_txns_success", "entry_point" => self.entry_point.to_string(), "builder_index" => self.builder_index.to_string()).increment(1); if let Some(limit) = gas_limit { - metrics::counter!("builder_bundle_gas_limit", "entry_point" => self.entry_point.to_string(), "builder_index" => self.builder_index.to_string()).increment(limit.as_u64()); + metrics::counter!("builder_bundle_gas_limit", "entry_point" => self.entry_point.to_string(), "builder_index" => self.builder_index.to_string()).increment(limit.try_into().unwrap_or(u64::MAX)); } if let Some(used) = gas_used { - metrics::counter!("builder_bundle_gas_used", "entry_point" => self.entry_point.to_string(), "builder_index" => self.builder_index.to_string()).increment(used.as_u64()); + metrics::counter!("builder_bundle_gas_used", "entry_point" => self.entry_point.to_string(), "builder_index" => self.builder_index.to_string()).increment(used.try_into().unwrap_or(u64::MAX)); } } @@ -1155,8 +1154,8 @@ impl BuilderMetrics { metrics::counter!("builder_cancellation_txns_mined", "entry_point" => self.entry_point.to_string(), "builder_index" => self.builder_index.to_string()).increment(1); } - fn increment_cancellation_txns_total_fee(&self, fee: u64) { - metrics::counter!("builder_cancellation_txns_total_fee", "entry_point" => self.entry_point.to_string(), "builder_index" => self.builder_index.to_string()).increment(fee); + fn increment_cancellation_txns_total_fee(&self, fee: u128) { + metrics::counter!("builder_cancellation_txns_total_fee", "entry_point" => self.entry_point.to_string(), "builder_index" => self.builder_index.to_string()).increment(fee.try_into().unwrap_or(u64::MAX)); } fn increment_cancellations_abandoned(&self) { @@ -1178,7 +1177,7 @@ impl BuilderMetrics { #[cfg(test)] mod tests { - use ethers::types::Bytes; + use alloy_primitives::Bytes; use mockall::Sequence; use rundler_provider::MockEntryPointV0_6; use rundler_types::{ @@ -1213,7 +1212,7 @@ mod tests { // zero nonce mock_tracker .expect_get_nonce_and_required_fees() - .returning(|| Ok((U256::zero(), None))); + .returning(|| Ok((0, None))); // empty bundle mock_proposer @@ -1258,7 +1257,7 @@ mod tests { // zero nonce mock_tracker .expect_get_nonce_and_required_fees() - .returning(|| Ok((U256::zero(), None))); + .returning(|| Ok((0, None))); // bundle with one op mock_proposer @@ -1269,12 +1268,12 @@ mod tests { // should create the bundle txn mock_entry_point .expect_get_send_bundle_transaction() - .returning(|_, _, _, _| TypedTransaction::default()); + .returning(|_, _, _, _| TransactionRequest::default()); // should send the bundle txn mock_tracker .expect_send_transaction() - .returning(|_, _| Box::pin(async { Ok(H256::zero()) })); + .returning(|_, _| Box::pin(async { Ok(B256::ZERO) })); let mut sender = new_sender(mock_proposer, mock_entry_point); @@ -1312,7 +1311,7 @@ mod tests { Box::pin(async { Ok(NewHead { block_number: 2, - block_hash: H256::zero(), + block_hash: B256::ZERO, }) }) }); @@ -1332,11 +1331,11 @@ mod tests { Box::pin(async { Ok(Some(TrackerUpdate::Mined { block_number: 2, - nonce: U256::zero(), + nonce: 0, gas_limit: None, gas_used: None, gas_price: None, - tx_hash: H256::zero(), + tx_hash: B256::ZERO, attempt_number: 0, })) }) @@ -1444,7 +1443,7 @@ mod tests { // zero nonce mock_tracker .expect_get_nonce_and_required_fees() - .returning(|| Ok((U256::zero(), None))); + .returning(|| Ok((0, None))); // fee filter error mock_proposer @@ -1494,16 +1493,16 @@ mod tests { mock_proposer .expect_estimate_gas_fees() .once() - .returning(|_| Box::pin(async { Ok((GasFees::default(), U256::zero())) })); + .returning(|_| Box::pin(async { Ok((GasFees::default(), 0)) })); mock_tracker .expect_cancel_transaction() .once() - .returning(|_, _| Box::pin(async { Ok(Some(H256::zero())) })); + .returning(|_, _| Box::pin(async { Ok(Some(B256::ZERO)) })); mock_trigger.expect_last_block().return_const(NewHead { block_number: 0, - block_hash: H256::zero(), + block_hash: B256::ZERO, }); let mut state = SenderMachineState { @@ -1595,7 +1594,7 @@ mod tests { // zero nonce mock_tracker .expect_get_nonce_and_required_fees() - .returning(|| Ok((U256::zero(), None))); + .returning(|| Ok((0, None))); // bundle with one op mock_proposer @@ -1606,7 +1605,7 @@ mod tests { // should create the bundle txn mock_entry_point .expect_get_send_bundle_transaction() - .returning(|_, _, _, _| TypedTransaction::default()); + .returning(|_, _, _, _| TransactionRequest::default()); // should send the bundle txn, returns condition not met mock_tracker @@ -1715,7 +1714,7 @@ mod tests { .in_sequence(seq) .return_const(NewHead { block_number, - block_hash: H256::zero(), + block_hash: B256::ZERO, }); } @@ -1732,7 +1731,7 @@ mod tests { Box::pin(async move { Ok(NewHead { block_number, - block_hash: H256::zero(), + block_hash: B256::ZERO, }) }) }); @@ -1742,19 +1741,19 @@ mod tests { .in_sequence(seq) .return_const(NewHead { block_number, - block_hash: H256::zero(), + block_hash: B256::ZERO, }); } fn bundle() -> Bundle { Bundle { - gas_estimate: U256::from(100_000), + gas_estimate: 100_000, gas_fees: GasFees::default(), expected_storage: Default::default(), rejected_ops: vec![], entity_updates: vec![], ops_per_aggregator: vec![UserOpsPerAggregator { - aggregator: Address::zero(), + aggregator: Address::ZERO, signature: Bytes::new(), user_ops: vec![UserOperation::default()], }], diff --git a/crates/builder/src/emit.rs b/crates/builder/src/emit.rs index 66de76f01..4b2cee467 100644 --- a/crates/builder/src/emit.rs +++ b/crates/builder/src/emit.rs @@ -13,7 +13,8 @@ use std::{fmt::Display, sync::Arc}; -use ethers::types::{transaction::eip2718::TypedTransaction, Address, H256, U256}; +use alloy_primitives::{Address, B256}; +use rundler_provider::TransactionRequest; use rundler_sim::SimulationError; use rundler_types::{GasFees, ValidTimeRange}; use rundler_utils::strs; @@ -55,7 +56,7 @@ impl BuilderEvent { pub(crate) fn transaction_mined( builder_index: u64, - tx_hash: H256, + tx_hash: B256, nonce: u64, block_number: u64, ) -> Self { @@ -83,7 +84,7 @@ impl BuilderEvent { ) } - pub(crate) fn skipped_op(builder_index: u64, op_hash: H256, reason: SkipReason) -> Self { + pub(crate) fn skipped_op(builder_index: u64, op_hash: B256, reason: SkipReason) -> Self { Self::new( builder_index, BuilderEventKind::SkippedOp { op_hash, reason }, @@ -92,7 +93,7 @@ impl BuilderEvent { pub(crate) fn rejected_op( builder_index: u64, - op_hash: H256, + op_hash: B256, reason: OpRejectionReason, ) -> Self { Self::new( @@ -121,7 +122,7 @@ pub enum BuilderEventKind { /// A bundle transaction was mined TransactionMined { /// Transaction hash - tx_hash: H256, + tx_hash: B256, /// Transaction nonce nonce: u64, /// Block number containing the transaction @@ -140,14 +141,14 @@ pub enum BuilderEventKind { /// An operation was skipped in the bundle SkippedOp { /// Operation hash - op_hash: H256, + op_hash: B256, /// Reason for skipping reason: SkipReason, }, /// An operation was rejected from the bundle and requested to be removed from the pool RejectedOp { /// Operation hash - op_hash: H256, + op_hash: B256, /// Reason for rejection reason: OpRejectionReason, }, @@ -157,11 +158,11 @@ pub enum BuilderEventKind { #[derive(Clone, Debug)] pub struct BundleTxDetails { /// Transaction hash - pub tx_hash: H256, + pub tx_hash: B256, /// The transaction - pub tx: TypedTransaction, + pub tx: TransactionRequest, /// Operation hashes included in the bundle - pub op_hashes: Arc>, + pub op_hashes: Arc>, } /// Reason for skipping an operation in a bundle @@ -178,10 +179,10 @@ pub enum SkipReason { }, /// Insufficient pre-verification gas for the operation at the given base fee InsufficientPreVerificationGas { - base_fee: U256, + base_fee: u128, op_fees: GasFees, - required_pvg: U256, - actual_pvg: U256, + required_pvg: u128, + actual_pvg: u128, }, /// Bundle ran out of space by gas limit to include the operation GasLimit, @@ -204,9 +205,9 @@ pub enum OpRejectionReason { #[derive(Clone, Debug)] pub struct ConditionNotMetReason { pub address: Address, - pub slot: H256, - pub expected: H256, - pub actual: H256, + pub slot: B256, + pub expected: B256, + pub actual: B256, } impl Display for BuilderEvent { diff --git a/crates/builder/src/sender/bloxroute.rs b/crates/builder/src/sender/bloxroute.rs index 4f79a10c2..b605fbd65 100644 --- a/crates/builder/src/sender/bloxroute.rs +++ b/crates/builder/src/sender/bloxroute.rs @@ -11,20 +11,13 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::sync::Arc; - +use alloy_primitives::{hex, Address, Bytes, B256}; use anyhow::Context; -use ethers::{ - middleware::SignerMiddleware, - providers::{JsonRpcClient, Middleware, Provider}, - types::{transaction::eip2718::TypedTransaction, Address, Bytes, TxHash, H256, U256}, - utils::hex, -}; -use ethers_signers::Signer; use jsonrpsee::{ core::{client::ClientT, traits::ToRpcParams}, http_client::{transport::HttpBackend, HeaderMap, HeaderValue, HttpClient, HttpClientBuilder}, }; +use rundler_provider::{EvmProvider, TransactionRequest}; use rundler_sim::ExpectedStorage; use rundler_types::GasFees; use serde::{Deserialize, Serialize}; @@ -32,49 +25,48 @@ use serde_json::value::RawValue; use tonic::async_trait; use super::{ - create_hard_cancel_tx, fill_and_sign, CancelTxInfo, Result, SentTxInfo, TransactionSender, + create_hard_cancel_tx, CancelTxInfo, Result, SentTxInfo, TransactionSender, TxSenderError, TxStatus, }; +use crate::signer::Signer; -pub(crate) struct PolygonBloxrouteTransactionSender -where - C: JsonRpcClient + 'static, - S: Signer + 'static, -{ - provider: SignerMiddleware>, S>, +pub(crate) struct PolygonBloxrouteTransactionSender { + provider: P, + signer: S, client: PolygonBloxrouteClient, } #[async_trait] -impl TransactionSender for PolygonBloxrouteTransactionSender +impl TransactionSender for PolygonBloxrouteTransactionSender where - C: JsonRpcClient + 'static, - S: Signer + 'static, + P: EvmProvider, + S: Signer, { async fn send_transaction( &self, - tx: TypedTransaction, + tx: TransactionRequest, _expected_storage: &ExpectedStorage, ) -> Result { - let (raw_tx, nonce) = fill_and_sign(&self.provider, tx).await?; + let (raw_tx, nonce) = self.signer.fill_and_sign(tx).await?; let tx_hash = self.client.send_transaction(raw_tx).await?; Ok(SentTxInfo { nonce, tx_hash }) } async fn cancel_transaction( &self, - _tx_hash: H256, - nonce: U256, + _tx_hash: B256, + nonce: u64, to: Address, gas_fees: GasFees, ) -> Result { - let tx = create_hard_cancel_tx(self.provider.address(), to, nonce, gas_fees); + // TODO: does this make sense? - let (raw_tx, _) = fill_and_sign(&self.provider, tx).await?; + let tx = create_hard_cancel_tx(to, nonce, gas_fees); + + let (raw_tx, _) = self.signer.fill_and_sign(tx).await?; let tx_hash = self .provider - .provider() .request("eth_sendRawTransaction", (raw_tx,)) .await?; @@ -84,10 +76,10 @@ where }) } - async fn get_transaction_status(&self, tx_hash: H256) -> Result { + async fn get_transaction_status(&self, tx_hash: B256) -> Result { let tx = self .provider - .get_transaction(tx_hash) + .get_transaction_by_hash(tx_hash) .await .context("provider should return transaction status")?; // BDN transactions will not always show up in the node's transaction pool @@ -95,25 +87,24 @@ where // Thus, always return pending. Ok(tx .and_then(|tx| tx.block_number) - .map(|block_number| TxStatus::Mined { - block_number: block_number.as_u64(), - }) + .map(|block_number| TxStatus::Mined { block_number }) .unwrap_or(TxStatus::Pending)) } fn address(&self) -> Address { - self.provider.address() + self.signer.address() } } -impl PolygonBloxrouteTransactionSender +impl PolygonBloxrouteTransactionSender where - C: JsonRpcClient + 'static, - S: Signer + 'static, + P: EvmProvider, + S: Signer, { - pub(crate) fn new(provider: Arc>, signer: S, auth_header: &str) -> Result { + pub(crate) fn new(provider: P, signer: S, auth_header: &str) -> Result { Ok(Self { - provider: SignerMiddleware::new(Arc::clone(&provider), signer), + provider, + signer, client: PolygonBloxrouteClient::new(auth_header)?, }) } @@ -133,7 +124,7 @@ impl PolygonBloxrouteClient { Ok(Self { client }) } - async fn send_transaction(&self, raw_tx: Bytes) -> Result { + async fn send_transaction(&self, raw_tx: Bytes) -> Result { let request = BloxrouteRequest { transaction: hex::encode(raw_tx), }; @@ -159,5 +150,21 @@ impl ToRpcParams for BloxrouteRequest { #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct BloxrouteResponse { - tx_hash: TxHash, + tx_hash: B256, +} + +impl From for TxSenderError { + fn from(value: jsonrpsee::core::ClientError) -> Self { + match &value { + jsonrpsee::core::ClientError::Call(e) => { + // TODO add more strings + if e.message().contains("replacement transaction underpriced") { + TxSenderError::ReplacementUnderpriced + } else { + TxSenderError::Other(value.into()) + } + } + _ => TxSenderError::Other(value.into()), + } + } } diff --git a/crates/builder/src/sender/flashbots.rs b/crates/builder/src/sender/flashbots.rs index d1e44272f..9ebf966e5 100644 --- a/crates/builder/src/sender/flashbots.rs +++ b/crates/builder/src/sender/flashbots.rs @@ -13,49 +13,43 @@ // Adapted from https://github.com/onbjerg/ethers-flashbots and // https://github.com/gakonst/ethers-rs/blob/master/ethers-providers/src/toolbox/pending_transaction.rs -use std::{str::FromStr, sync::Arc}; +use std::str::FromStr; +use alloy_primitives::{hex, utils, Address, Bytes, B256, U256, U64}; +use alloy_signer::SignerSync; +use alloy_signer_local::PrivateKeySigner; use anyhow::{anyhow, Context}; -use ethers::{ - middleware::SignerMiddleware, - providers::{JsonRpcClient, Middleware, Provider}, - types::{transaction::eip2718::TypedTransaction, Address, Bytes, H256, U256, U64}, - utils, -}; -use ethers_signers::Signer; use reqwest::{ header::{HeaderMap, HeaderValue, CONTENT_TYPE}, Client, Response, }; +use rundler_provider::{EvmProvider, TransactionRequest}; use rundler_types::GasFees; use serde::{de, Deserialize, Serialize}; use serde_json::{json, Value}; -use tonic::async_trait; -use super::{ - fill_and_sign, ExpectedStorage, Result, SentTxInfo, TransactionSender, TxSenderError, TxStatus, -}; -use crate::sender::CancelTxInfo; +use super::{ExpectedStorage, Result, SentTxInfo, TransactionSender, TxSenderError, TxStatus}; +use crate::{sender::CancelTxInfo, signer::Signer}; #[derive(Debug)] -pub(crate) struct FlashbotsTransactionSender { - provider: SignerMiddleware>, S>, - flashbots_client: FlashbotsClient, +pub(crate) struct FlashbotsTransactionSender { + provider: P, + signer: S, + flashbots_client: FlashbotsClient, } -#[async_trait] -impl TransactionSender for FlashbotsTransactionSender +#[async_trait::async_trait] +impl TransactionSender for FlashbotsTransactionSender where - C: JsonRpcClient + 'static, - S: Signer + 'static, - FS: Signer + 'static, + P: EvmProvider, + S: Signer, { async fn send_transaction( &self, - tx: TypedTransaction, + tx: TransactionRequest, _expected_storage: &ExpectedStorage, ) -> Result { - let (raw_tx, nonce) = fill_and_sign(&self.provider, tx).await?; + let (raw_tx, nonce) = self.signer.fill_and_sign(tx).await?; let tx_hash = self .flashbots_client @@ -67,8 +61,8 @@ where async fn cancel_transaction( &self, - tx_hash: H256, - _nonce: U256, + tx_hash: B256, + _nonce: u64, _to: Address, _gas_fees: GasFees, ) -> Result { @@ -82,12 +76,12 @@ where } Ok(CancelTxInfo { - tx_hash: H256::zero(), + tx_hash: B256::ZERO, soft_cancelled: true, }) } - async fn get_transaction_status(&self, tx_hash: H256) -> Result { + async fn get_transaction_status(&self, tx_hash: B256) -> Result { let status = self.flashbots_client.status(tx_hash).await?; Ok(match status.status { FlashbotsAPITransactionStatus::Pending => TxStatus::Pending, @@ -97,14 +91,12 @@ where // still pending. let tx = self .provider - .get_transaction(tx_hash) + .get_transaction_by_hash(tx_hash) .await .context("provider should look up transaction included by Flashbots")?; if let Some(tx) = tx { if let Some(block_number) = tx.block_number { - return Ok(TxStatus::Mined { - block_number: block_number.as_u64(), - }); + return Ok(TxStatus::Mined { block_number }); } } TxStatus::Pending @@ -121,28 +113,28 @@ where } fn address(&self) -> Address { - self.provider.address() + self.signer.address() } } -impl FlashbotsTransactionSender +impl FlashbotsTransactionSender where - C: JsonRpcClient + 'static, - S: Signer + 'static, - FS: Signer + 'static, + P: EvmProvider, + S: Signer, { pub(crate) fn new( - provider: Arc>, - tx_signer: S, - flashbots_signer: FS, + provider: P, + signer: S, + flashbots_auth_key: String, builders: Vec, relay_url: String, status_url: String, ) -> Result { Ok(Self { - provider: SignerMiddleware::new(provider, tx_signer), + provider, + signer, flashbots_client: FlashbotsClient::new( - flashbots_signer, + flashbots_auth_key, builders, relay_url, status_url, @@ -192,13 +184,13 @@ struct FlashbotsSendPrivateTransactionRequest { #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct FlashbotsSendPrivateTransactionResponse { - result: H256, + result: B256, } #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] struct FlashbotsCancelPrivateTransactionRequest { - tx_hash: H256, + tx_hash: B256, } #[derive(Deserialize, Debug)] @@ -242,7 +234,7 @@ enum FlashbotsAPITransactionStatus { #[allow(dead_code)] struct FlashbotsAPIResponse { status: FlashbotsAPITransactionStatus, - hash: H256, + hash: B256, #[serde(deserialize_with = "deserialize_u64")] max_block_number: U64, transaction: FlashbotsAPITransaction, @@ -250,26 +242,26 @@ struct FlashbotsAPIResponse { } #[derive(Debug)] -struct FlashbotsClient { +struct FlashbotsClient { http_client: Client, - signer: S, + signer: PrivateKeySigner, builders: Vec, relay_url: String, status_url: String, } -impl FlashbotsClient { - fn new(signer: S, builders: Vec, relay_url: String, status_url: String) -> Self { +impl FlashbotsClient { + fn new(auth_key: String, builders: Vec, relay_url: String, status_url: String) -> Self { Self { http_client: Client::new(), - signer, + signer: auth_key.parse().expect("should parse auth key"), builders, relay_url, status_url, } } - async fn status(&self, tx_hash: H256) -> anyhow::Result { + async fn status(&self, tx_hash: B256) -> anyhow::Result { let url = format!("{}{:?}", self.status_url, tx_hash); let resp = self.http_client.get(&url).send().await?; resp.json::() @@ -278,11 +270,8 @@ impl FlashbotsClient { } } -impl FlashbotsClient -where - S: Signer, -{ - async fn send_private_transaction(&self, raw_tx: Bytes) -> anyhow::Result { +impl FlashbotsClient { + async fn send_private_transaction(&self, raw_tx: Bytes) -> anyhow::Result { let preferences = Preferences { fast: false, privacy: Some(Privacy { @@ -314,7 +303,7 @@ where Ok(parsed_response.result) } - async fn cancel_private_transaction(&self, tx_hash: H256) -> anyhow::Result { + async fn cancel_private_transaction(&self, tx_hash: B256) -> anyhow::Result { let body = json!({ "jsonrpc": "2.0", "method": "eth_cancelPrivateTransaction", @@ -337,15 +326,14 @@ where async fn sign_send_request(&self, body: Value) -> anyhow::Result { let signature = self .signer - .sign_message(format!( - "0x{:x}", - H256::from(utils::keccak256(body.to_string())) - )) - .await + .sign_hash_sync(&utils::keccak256(body.to_string())) .expect("Signature failed"); - let header_val = - HeaderValue::from_str(&format!("{:?}:0x{}", self.signer.address(), signature)) - .expect("Header contains invalid characters"); + let header_val = HeaderValue::from_str(&format!( + "{:?}:0x{}", + self.signer.address(), + hex::encode(signature.as_bytes()) + )) + .expect("Header contains invalid characters"); let mut headers = HeaderMap::new(); headers.insert(CONTENT_TYPE, "application/json".parse().unwrap()); @@ -369,11 +357,11 @@ where Ok(match Value::deserialize(deserializer)? { Value::String(s) => { if s.as_str() == "0x" { - U64::zero() + U64::ZERO } else if s.as_str().starts_with("0x") { - U64::from_str_radix(s.as_str(), 16).map_err(de::Error::custom)? + U64::from_str_radix(&s[2..], 16).map_err(de::Error::custom)? } else { - U64::from_dec_str(s.as_str()).map_err(de::Error::custom)? + U64::from_str(s.as_str()).map_err(de::Error::custom)? } } Value::Number(num) => U64::from( @@ -393,11 +381,11 @@ where if s.is_empty() { None } else if s.as_str() == "0x" { - Some(U256::zero()) + Some(U256::ZERO) } else if s.as_str().starts_with("0x") { - Some(U256::from_str_radix(s.as_str(), 16).map_err(de::Error::custom)?) + Some(U256::from_str_radix(&s[2..], 16).map_err(de::Error::custom)?) } else { - Some(U256::from_dec_str(s.as_str()).map_err(de::Error::custom)?) + Some(U256::from_str(s.as_str()).map_err(de::Error::custom)?) } } Value::Number(num) => Some(U256::from( diff --git a/crates/builder/src/sender/mod.rs b/crates/builder/src/sender/mod.rs index db7cb026c..70a913374 100644 --- a/crates/builder/src/sender/mod.rs +++ b/crates/builder/src/sender/mod.rs @@ -14,37 +14,31 @@ mod bloxroute; mod flashbots; mod raw; + use std::sync::Arc; -use anyhow::Context; -use async_trait::async_trait; +use alloy_primitives::{Address, B256}; pub(crate) use bloxroute::PolygonBloxrouteTransactionSender; use enum_dispatch::enum_dispatch; -use ethers::{ - prelude::SignerMiddleware, - providers::{JsonRpcClient, Middleware, Provider, ProviderError}, - types::{ - transaction::eip2718::TypedTransaction, Address, Bytes, Eip1559TransactionRequest, H256, - U256, - }, -}; -use ethers_signers::{LocalWallet, Signer}; pub(crate) use flashbots::FlashbotsTransactionSender; #[cfg(test)] use mockall::automock; pub(crate) use raw::RawTransactionSender; +use rundler_provider::{EvmProvider, ProviderError, TransactionRequest}; use rundler_sim::ExpectedStorage; use rundler_types::GasFees; +use crate::signer::Signer; + #[derive(Debug)] pub(crate) struct SentTxInfo { - pub(crate) nonce: U256, - pub(crate) tx_hash: H256, + pub(crate) nonce: u64, + pub(crate) tx_hash: B256, } #[derive(Debug)] pub(crate) struct CancelTxInfo { - pub(crate) tx_hash: H256, + pub(crate) tx_hash: B256, // True if the transaction was soft-cancelled. Soft-cancellation is when the RPC endpoint // accepts the cancel without an onchain transaction. pub(crate) soft_cancelled: bool, @@ -79,39 +73,34 @@ pub(crate) enum TxSenderError { pub(crate) type Result = std::result::Result; -#[async_trait] -#[enum_dispatch(TransactionSenderEnum<_C,_S,_FS>)] +#[async_trait::async_trait] +#[enum_dispatch(TransactionSenderEnum<_P,_S>)] #[cfg_attr(test, automock)] -pub(crate) trait TransactionSender: Send + Sync + 'static { +pub(crate) trait TransactionSender: Send + Sync { async fn send_transaction( &self, - tx: TypedTransaction, + tx: TransactionRequest, expected_storage: &ExpectedStorage, ) -> Result; async fn cancel_transaction( &self, - tx_hash: H256, - nonce: U256, + tx_hash: B256, + nonce: u64, to: Address, gas_fees: GasFees, ) -> Result; - async fn get_transaction_status(&self, tx_hash: H256) -> Result; + async fn get_transaction_status(&self, tx_hash: B256) -> Result; fn address(&self) -> Address; } #[enum_dispatch] -pub(crate) enum TransactionSenderEnum -where - C: JsonRpcClient + 'static, - S: Signer + 'static, - FS: Signer + 'static, -{ - Raw(RawTransactionSender), - Flashbots(FlashbotsTransactionSender), - PolygonBloxroute(PolygonBloxrouteTransactionSender), +pub(crate) enum TransactionSenderEnum { + Raw(RawTransactionSender), + Flashbots(FlashbotsTransactionSender), + PolygonBloxroute(PolygonBloxrouteTransactionSender), } /// Transaction sender types @@ -171,19 +160,18 @@ pub struct FlashbotsSenderArgs { } impl TransactionSenderArgs { - pub(crate) fn into_sender( + pub(crate) fn into_sender( self, rpc_url: &str, signer: S, - ) -> std::result::Result< - TransactionSenderEnum, - SenderConstructorErrors, - > { - let provider = rundler_provider::new_provider(rpc_url, None)?; + ) -> std::result::Result, SenderConstructorErrors> + { + let provider = Arc::new(rundler_provider::new_alloy_evm_provider(rpc_url)?); let sender = match self { Self::Raw(args) => { if args.use_submit_for_status { - let submitter = rundler_provider::new_provider(&args.submit_url, None)?; + let submitter = + Arc::new(rundler_provider::new_alloy_evm_provider(&args.submit_url)?); TransactionSenderEnum::Raw(RawTransactionSender::new( provider, submitter, @@ -193,7 +181,7 @@ impl TransactionSenderArgs { )) } else { TransactionSenderEnum::Raw(RawTransactionSender::new( - Arc::clone(&provider), + provider.clone(), provider, signer, args.dropped_status_supported, @@ -202,12 +190,10 @@ impl TransactionSenderArgs { } } Self::Flashbots(args) => { - let flashbots_signer = args.auth_key.parse().context("should parse auth key")?; - TransactionSenderEnum::Flashbots(FlashbotsTransactionSender::new( provider, signer, - flashbots_signer, + args.auth_key, args.builders, args.relay_url, args.status_url, @@ -232,51 +218,20 @@ pub(crate) enum SenderConstructorErrors { Other(#[from] anyhow::Error), } -async fn fill_and_sign( - provider: &SignerMiddleware>, S>, - mut tx: TypedTransaction, -) -> anyhow::Result<(Bytes, U256)> -where - C: JsonRpcClient + 'static, - S: Signer + 'static, -{ - provider - .fill_transaction(&mut tx, None) - .await - .context("should fill transaction before signing it")?; - let nonce = *tx - .nonce() - .context("nonce should be set when transaction is filled")?; - let signature = provider - .signer() - .sign_transaction(&tx) - .await - .context("should sign transaction before sending")?; - Ok((tx.rlp_signed(&signature), nonce)) -} - -fn create_hard_cancel_tx( - from: Address, - to: Address, - nonce: U256, - gas_fees: GasFees, -) -> TypedTransaction { - Eip1559TransactionRequest::new() - .from(from) +fn create_hard_cancel_tx(to: Address, nonce: u64, gas_fees: GasFees) -> TransactionRequest { + TransactionRequest::default() .to(to) .nonce(nonce) - .gas(U256::from(30_000)) + .gas_limit(30_000) .max_fee_per_gas(gas_fees.max_fee_per_gas) .max_priority_fee_per_gas(gas_fees.max_priority_fee_per_gas) - .data(Bytes::new()) - .into() } impl From for TxSenderError { fn from(value: ProviderError) -> Self { match &value { - ProviderError::JsonRpcClientError(e) => { - if let Some(e) = e.as_error_response() { + ProviderError::RPC(e) => { + if let Some(e) = e.as_error_resp() { if e.message.contains("replacement transaction underpriced") { return TxSenderError::ReplacementUnderpriced; } else if e.message.contains("nonce too low") { @@ -297,18 +252,3 @@ impl From for TxSenderError { } } } - -impl From for TxSenderError { - fn from(value: jsonrpsee::core::ClientError) -> Self { - match &value { - jsonrpsee::core::ClientError::Call(e) => { - if e.message().contains("replacement transaction underpriced") { - TxSenderError::ReplacementUnderpriced - } else { - TxSenderError::Other(value.into()) - } - } - _ => TxSenderError::Other(value.into()), - } - } -} diff --git a/crates/builder/src/sender/raw.rs b/crates/builder/src/sender/raw.rs index 429b4f570..d04596481 100644 --- a/crates/builder/src/sender/raw.rs +++ b/crates/builder/src/sender/raw.rs @@ -11,64 +11,51 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::sync::Arc; - +use alloy_primitives::{Address, B256}; use anyhow::Context; use async_trait::async_trait; -use ethers::{ - middleware::SignerMiddleware, - providers::{JsonRpcClient, Middleware, Provider}, - types::{transaction::eip2718::TypedTransaction, Address, H256, U256}, -}; -use ethers_signers::Signer; +use rundler_provider::{EvmProvider, TransactionRequest}; use rundler_sim::ExpectedStorage; use rundler_types::GasFees; use serde_json::json; use super::{CancelTxInfo, Result}; -use crate::sender::{ - create_hard_cancel_tx, fill_and_sign, SentTxInfo, TransactionSender, TxStatus, +use crate::{ + sender::{create_hard_cancel_tx, SentTxInfo, TransactionSender, TxStatus}, + signer::Signer, }; #[derive(Debug)] -pub(crate) struct RawTransactionSender -where - C: JsonRpcClient + 'static, - S: Signer + 'static, -{ - provider: Arc>, - // The `SignerMiddleware` specifically needs to wrap a `Provider`, and not - // just any `Middleware`, because `.request()` is only on `Provider` and not - // on `Middleware`. - submitter: SignerMiddleware>, S>, +pub(crate) struct RawTransactionSender { + submit_provider: P, + status_provider: P, + signer: S, dropped_status_supported: bool, use_conditional_rpc: bool, } #[async_trait] -impl TransactionSender for RawTransactionSender +impl TransactionSender for RawTransactionSender where - C: JsonRpcClient + 'static, - S: Signer + 'static, + P: EvmProvider, + S: Signer, { async fn send_transaction( &self, - tx: TypedTransaction, + tx: TransactionRequest, expected_storage: &ExpectedStorage, ) -> Result { - let (raw_tx, nonce) = fill_and_sign(&self.submitter, tx).await?; + let (raw_tx, nonce) = self.signer.fill_and_sign(tx).await?; let tx_hash = if self.use_conditional_rpc { - self.submitter - .provider() + self.submit_provider .request( "eth_sendRawTransactionConditional", (raw_tx, json!({ "knownAccounts": expected_storage })), ) .await? } else { - self.submitter - .provider() + self.submit_provider .request("eth_sendRawTransaction", (raw_tx,)) .await? }; @@ -78,18 +65,17 @@ where async fn cancel_transaction( &self, - _tx_hash: H256, - nonce: U256, + _tx_hash: B256, + nonce: u64, to: Address, gas_fees: GasFees, ) -> Result { - let tx = create_hard_cancel_tx(self.submitter.address(), to, nonce, gas_fees); + let tx = create_hard_cancel_tx(to, nonce, gas_fees); - let (raw_tx, _) = fill_and_sign(&self.submitter, tx).await?; + let (raw_tx, _) = self.signer.fill_and_sign(tx).await?; let tx_hash = self - .submitter - .provider() + .submit_provider .request("eth_sendRawTransaction", (raw_tx,)) .await?; @@ -99,10 +85,10 @@ where }) } - async fn get_transaction_status(&self, tx_hash: H256) -> Result { + async fn get_transaction_status(&self, tx_hash: B256) -> Result { let tx = self - .provider - .get_transaction(tx_hash) + .status_provider + .get_transaction_by_hash(tx_hash) .await .context("provider should return transaction status")?; Ok(match tx { @@ -115,33 +101,28 @@ where } Some(tx) => match tx.block_number { None => TxStatus::Pending, - Some(block_number) => TxStatus::Mined { - block_number: block_number.as_u64(), - }, + Some(block_number) => TxStatus::Mined { block_number }, }, }) } fn address(&self) -> Address { - self.submitter.address() + self.signer.address() } } -impl RawTransactionSender -where - C: JsonRpcClient + 'static, - S: Signer + 'static, -{ +impl RawTransactionSender { pub(crate) fn new( - provider: Arc>, - submitter: Arc>, + submit_provider: P, + status_provider: P, signer: S, dropped_status_supported: bool, use_conditional_rpc: bool, ) -> Self { Self { - provider, - submitter: SignerMiddleware::new(submitter, signer), + submit_provider, + status_provider, + signer, dropped_status_supported, use_conditional_rpc, } diff --git a/crates/builder/src/server/local.rs b/crates/builder/src/server/local.rs index fa6758ab3..c15bb5d2c 100644 --- a/crates/builder/src/server/local.rs +++ b/crates/builder/src/server/local.rs @@ -11,8 +11,8 @@ // 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, B256}; use async_trait::async_trait; -use ethers::types::{Address, H256}; use rundler_task::server::{HealthCheck, ServerStatus}; use rundler_types::builder::{Builder, BuilderError, BuilderResult, BundlingMode}; use tokio::{ @@ -100,7 +100,7 @@ impl Builder for LocalBuilderHandle { } } - async fn debug_send_bundle_now(&self) -> BuilderResult<(H256, u64)> { + async fn debug_send_bundle_now(&self) -> BuilderResult<(B256, u64)> { let req = ServerRequestKind::DebugSendBundleNow; let resp = self.send(req).await?; match resp { @@ -230,6 +230,6 @@ struct ServerRequest { #[derive(Clone, Debug)] enum ServerResponse { GetSupportedEntryPoints { entry_points: Vec

}, - DebugSendBundleNow { hash: H256, block_number: u64 }, + DebugSendBundleNow { hash: B256, block_number: u64 }, DebugSetBundlingMode, } diff --git a/crates/builder/src/server/remote/client.rs b/crates/builder/src/server/remote/client.rs index 494ae0a78..7266f195d 100644 --- a/crates/builder/src/server/remote/client.rs +++ b/crates/builder/src/server/remote/client.rs @@ -13,16 +13,14 @@ use std::str::FromStr; -use ethers::types::{Address, H256}; +use alloy_primitives::{Address, B256}; +use async_trait::async_trait; use rundler_task::{ grpc::protos::{from_bytes, ConversionError}, server::{HealthCheck, ServerStatus}, }; use rundler_types::builder::{Builder, BuilderError, BuilderResult, BundlingMode}; -use tonic::{ - async_trait, - transport::{Channel, Uri}, -}; +use tonic::transport::{Channel, Uri}; use tonic_health::{ pb::{health_client::HealthClient, HealthCheckRequest}, ServingStatus, @@ -71,7 +69,7 @@ impl Builder for RemoteBuilderClient { .map_err(anyhow::Error::from)?) } - async fn debug_send_bundle_now(&self) -> BuilderResult<(H256, u64)> { + async fn debug_send_bundle_now(&self) -> BuilderResult<(B256, u64)> { let res = self .grpc_client .clone() @@ -83,7 +81,7 @@ impl Builder for RemoteBuilderClient { match res { Some(debug_send_bundle_now_response::Result::Success(s)) => { - Ok((H256::from_slice(&s.transaction_hash), s.block_number)) + Ok((B256::from_slice(&s.transaction_hash), s.block_number)) } Some(debug_send_bundle_now_response::Result::Failure(f)) => Err(f.try_into()?), None => Err(BuilderError::Other(anyhow::anyhow!( diff --git a/crates/builder/src/server/remote/server.rs b/crates/builder/src/server/remote/server.rs index dad28db46..791809fc0 100644 --- a/crates/builder/src/server/remote/server.rs +++ b/crates/builder/src/server/remote/server.rs @@ -83,10 +83,7 @@ impl GrpcBuilder for GrpcBuilderServerImpl { let resp = match self.local_builder.get_supported_entry_points().await { Ok(entry_points) => GetSupportedEntryPointsResponse { chain_id: self.chain_id, - entry_points: entry_points - .into_iter() - .map(|ep| ep.as_bytes().to_vec()) - .collect(), + entry_points: entry_points.into_iter().map(|ep| ep.to_vec()).collect(), }, Err(e) => { return Err(Status::internal(format!("Failed to get entry points: {e}"))); @@ -104,7 +101,7 @@ impl GrpcBuilder for GrpcBuilderServerImpl { Ok((hash, block_number)) => DebugSendBundleNowResponse { result: Some(debug_send_bundle_now_response::Result::Success( DebugSendBundleNowSuccess { - transaction_hash: hash.as_bytes().to_vec(), + transaction_hash: hash.to_vec(), block_number, }, )), diff --git a/crates/builder/src/signer/aws.rs b/crates/builder/src/signer/aws.rs index a9982765f..11c5f9f73 100644 --- a/crates/builder/src/signer/aws.rs +++ b/crates/builder/src/signer/aws.rs @@ -11,15 +11,15 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{sync::Arc, time::Duration}; +use std::time::Duration; +use alloy_signer::Signer as _; +use alloy_signer_aws::AwsSigner; use anyhow::Context; -use ethers_signers::{AwsSigner, Signer}; +use aws_config::BehaviorVersion; use rslock::{Lock, LockGuard, LockManager}; -use rundler_provider::Provider; +use rundler_provider::EvmProvider; use rundler_utils::handle::SpawnGuard; -use rusoto_core::Region; -use rusoto_kms::KmsClient; use tokio::{sync::oneshot, time::sleep}; use super::monitor_account_balance; @@ -33,15 +33,16 @@ pub(crate) struct KmsSigner { } impl KmsSigner { - pub(crate) async fn connect( - provider: Arc

, + pub(crate) async fn connect( + provider: P, chain_id: u64, - region: Region, key_ids: Vec, redis_uri: String, ttl_millis: u64, ) -> anyhow::Result { - let client = KmsClient::new(region); + let config = aws_config::load_defaults(BehaviorVersion::v2024_03_28()).await; + let client = aws_sdk_kms::Client::new(&config); + let mut kms_guard = None; let key_id; @@ -58,7 +59,7 @@ impl KmsSigner { .to_owned(); }; - let signer = AwsSigner::new(client, key_id, chain_id) + let signer = AwsSigner::new(client, key_id, Some(chain_id)) .await .context("should create signer")?; diff --git a/crates/builder/src/signer/local.rs b/crates/builder/src/signer/local.rs new file mode 100644 index 000000000..2743504fb --- /dev/null +++ b/crates/builder/src/signer/local.rs @@ -0,0 +1,46 @@ +// 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_signer::Signer as _; +use alloy_signer_local::PrivateKeySigner; +use anyhow::Context; +use rundler_provider::EvmProvider; +use rundler_utils::handle::SpawnGuard; + +/// A local signer handle +#[derive(Debug)] +pub(crate) struct LocalSigner { + pub(crate) signer: PrivateKeySigner, + _monitor_abort_handle: SpawnGuard, +} + +impl LocalSigner { + pub(crate) async fn connect( + provider: P, + chain_id: u64, + private_key: String, + ) -> anyhow::Result { + let signer = private_key + .parse::() + .context("should create signer")?; + let _monitor_abort_handle = SpawnGuard::spawn_with_guard(super::monitor_account_balance( + signer.address(), + provider, + )); + + Ok(Self { + signer: signer.with_chain_id(Some(chain_id)), + _monitor_abort_handle, + }) + } +} diff --git a/crates/builder/src/signer/mod.rs b/crates/builder/src/signer/mod.rs index bf7d8bc3b..ebe3be0a1 100644 --- a/crates/builder/src/signer/mod.rs +++ b/crates/builder/src/signer/mod.rs @@ -12,56 +12,62 @@ // If not, see https://www.gnu.org/licenses/. mod aws; -use std::sync::Arc; +mod local; -use anyhow::Context; -use async_trait::async_trait; +use alloy_consensus::{SignableTransaction, TxEnvelope, TypedTransaction}; +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{Address, Bytes, B256}; +use alloy_signer::{Signature, Signer as _}; +use anyhow::{bail, Context}; pub(crate) use aws::*; -use ethers::{ - abi::Address, - types::{ - transaction::{eip2718::TypedTransaction, eip712::Eip712}, - Signature, - }, -}; -use ethers_signers::{AwsSignerError, LocalWallet, Signer, WalletError}; -use rundler_provider::Provider; -use rundler_utils::handle::SpawnGuard; - -/// A local signer handle -#[derive(Debug)] -pub(crate) struct LocalSigner { - signer: LocalWallet, - _monitor_abort_handle: SpawnGuard, -} +pub(crate) use local::*; +use num_traits::cast::ToPrimitive; +use rundler_provider::{EvmProvider, TransactionRequest}; + +#[async_trait::async_trait] +pub(crate) trait Signer: Send + Sync { + fn address(&self) -> Address; + + fn chain_id(&self) -> u64; + + async fn sign_hash(&self, hash: &B256) -> anyhow::Result; + + async fn fill_and_sign(&self, mut tx: TransactionRequest) -> anyhow::Result<(Bytes, u64)> { + tx = tx.from(self.address()); + + let nonce = tx + .nonce + .context("nonce should be set when transaction is filled")?; + + let TypedTransaction::Eip1559(mut tx_1559) = tx + .build_typed_tx() + .expect("should build eip1559 transaction") + else { + bail!("transaction is not eip1559"); + }; + + tx_1559.set_chain_id(self.chain_id()); + let tx_hash = tx_1559.signature_hash(); + + let signature = self + .sign_hash(&tx_hash) + .await + .context("should sign transaction before sending")?; + + let signed: TxEnvelope = tx_1559.into_signed(signature).into(); + + let mut encoded = vec![]; + signed.encode_2718(&mut encoded); -impl LocalSigner { - pub(crate) async fn connect( - provider: Arc

, - chain_id: u64, - private_key: String, - ) -> anyhow::Result { - let signer = private_key - .parse::() - .context("should create signer")?; - let _monitor_abort_handle = SpawnGuard::spawn_with_guard( - super::signer::monitor_account_balance(signer.address(), Arc::clone(&provider)), - ); - - Ok(Self { - signer: signer.with_chain_id(chain_id), - _monitor_abort_handle, - }) + Ok((encoded.into(), nonce)) } } -pub(crate) async fn monitor_account_balance(addr: Address, provider: Arc

) { +pub(crate) async fn monitor_account_balance(addr: Address, provider: P) { loop { match provider.get_balance(addr, None).await { Ok(balance) => { - // Divide balance by a large number first to prevent overflow when - // converting to u64. This keeps six decimal places. - let eth_balance = (balance / 10_u64.pow(12)).as_u64() as f64 / 1e6; + let eth_balance = balance.to_f64().unwrap_or_default() / 1e18; tracing::info!("account {addr:?} balance: {}", eth_balance); metrics::gauge!("bundle_builder_account_balance", "addr" => format!("{addr:?}")) .set(eth_balance); @@ -81,72 +87,36 @@ pub(crate) enum BundlerSigner { Kms(KmsSigner), } -#[derive(Debug, thiserror::Error)] -pub(crate) enum BundlerSignerError { - #[error(transparent)] - Local(#[from] WalletError), - #[error(transparent)] - Kms(#[from] AwsSignerError), -} - -#[async_trait] +#[async_trait::async_trait] impl Signer for BundlerSigner { - type Error = BundlerSignerError; - - async fn sign_message>( - &self, - message: S, - ) -> Result { - let out = match self { - BundlerSigner::Local(s) => s.signer.sign_message(message).await?, - BundlerSigner::Kms(s) => s.signer.sign_message(message).await?, - }; - Ok(out) - } - - async fn sign_transaction(&self, message: &TypedTransaction) -> Result { - let out = match self { - BundlerSigner::Local(s) => s.signer.sign_transaction(message).await?, - BundlerSigner::Kms(s) => s.signer.sign_transaction(message).await?, - }; - Ok(out) - } - - async fn sign_typed_data( - &self, - payload: &T, - ) -> Result { - let out = match self { - BundlerSigner::Local(s) => s.signer.sign_typed_data(payload).await?, - BundlerSigner::Kms(s) => s.signer.sign_typed_data(payload).await?, - }; - Ok(out) - } - fn address(&self) -> Address { match self { - BundlerSigner::Local(s) => s.signer.address(), - BundlerSigner::Kms(s) => s.signer.address(), + Self::Local(l) => l.signer.address(), + Self::Kms(k) => k.signer.address(), } } fn chain_id(&self) -> u64 { match self { - BundlerSigner::Local(s) => s.signer.chain_id(), - BundlerSigner::Kms(s) => s.signer.chain_id(), + Self::Local(l) => l + .signer + .chain_id() + .expect("local signer should have chain id"), + Self::Kms(k) => k + .signer + .chain_id() + .expect("kms signer should have chain id"), } } - fn with_chain_id>(self, chain_id: T) -> Self { + async fn sign_hash(&self, hash: &B256) -> anyhow::Result { match self { - BundlerSigner::Local(mut s) => { - s.signer = s.signer.with_chain_id(chain_id); - BundlerSigner::Local(s) - } - BundlerSigner::Kms(mut s) => { - s.signer = s.signer.with_chain_id(chain_id); - BundlerSigner::Kms(s) - } + Self::Local(l) => l + .signer + .sign_hash(hash) + .await + .context("local signer failed"), + Self::Kms(k) => k.signer.sign_hash(hash).await.context("kms signer failed"), } } } diff --git a/crates/builder/src/task.rs b/crates/builder/src/task.rs index a86e46cf3..14385eb28 100644 --- a/crates/builder/src/task.rs +++ b/crates/builder/src/task.rs @@ -11,16 +11,16 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration}; +use std::{collections::HashMap, net::SocketAddr, time::Duration}; +use alloy_primitives::{Address, B256}; use anyhow::{bail, Context}; use async_trait::async_trait; -use ethers::types::{Address, H256}; -use ethers_signers::Signer; use futures::future; use futures_util::TryFutureExt; -use rundler_provider::{EntryPointProvider, Provider}; +use rundler_provider::{EntryPointProvider, EvmProvider}; use rundler_sim::{ + gas::{self, FeeEstimatorImpl}, simulation::{self, UnsafeSimulator}, MempoolConfig, PriorityFeeMode, SimulationSettings, Simulator, }; @@ -31,7 +31,6 @@ use rundler_types::{ UserOperationVariant, }; use rundler_utils::{emit::WithEntryPoint, handle}; -use rusoto_core::Region; use tokio::{ sync::{broadcast, mpsc}, task::JoinHandle, @@ -46,7 +45,7 @@ use crate::{ emit::BuilderEvent, sender::TransactionSenderArgs, server::{spawn_remote_builder_server, LocalBuilderBuilder}, - signer::{BundlerSigner, KmsSigner, LocalSigner}, + signer::{BundlerSigner, KmsSigner, LocalSigner, Signer}, transaction_tracker::{self, TransactionTrackerImpl}, }; @@ -65,8 +64,6 @@ pub struct Args { /// AWS KMS key ids to use for signing transactions /// Only used if private_key is not provided pub aws_kms_key_ids: Vec, - /// AWS KMS region - pub aws_kms_region: Region, /// Redis URI for key leasing pub redis_uri: String, /// Redis lease TTL in milliseconds @@ -74,9 +71,9 @@ pub struct Args { /// Maximum bundle size in number of operations pub max_bundle_size: u64, /// Maximum bundle size in gas limit - pub max_bundle_gas: u64, + pub max_bundle_gas: u128, /// Percentage to add to the network priority fee for the bundle priority fee - pub bundle_priority_fee_overhead_percent: u64, + pub bundle_priority_fee_overhead_percent: u32, /// Priority fee mode to use for operation priority fee minimums pub priority_fee_mode: PriorityFeeMode, /// Sender to be used by the builder @@ -86,7 +83,7 @@ pub struct Args { /// Maximum number of blocks to wait for a transaction to be mined pub max_blocks_to_wait_for_mine: u64, /// Percentage to increase the fees by when replacing a bundle transaction - pub replacement_fee_percent_increase: u64, + pub replacement_fee_percent_increase: u32, /// Maximum number of times to increase the fee when cancelling a transaction pub max_cancellation_fee_increases: u64, /// Maximum amount of blocks to spend in a replacement underpriced state before moving to cancel @@ -109,7 +106,7 @@ pub struct EntryPointBuilderSettings { /// Index offset for bundle builders pub bundle_builder_index_offset: u64, /// Mempool configs - pub mempool_configs: HashMap, + pub mempool_configs: HashMap, } /// Builder task @@ -119,7 +116,7 @@ pub struct BuilderTask { event_sender: broadcast::Sender>, builder_builder: LocalBuilderBuilder, pool: P, - provider: Arc, + provider: PR, ep_06: Option, ep_07: Option, } @@ -127,11 +124,15 @@ pub struct BuilderTask { #[async_trait] impl Task for BuilderTask where - P: Pool + Clone, - PR: Provider, - E06: EntryPointProvider, - E07: EntryPointProvider, + P: Pool + Clone + 'static, + PR: EvmProvider + Clone + 'static, + E06: EntryPointProvider + Clone + 'static, + E07: EntryPointProvider + Clone + 'static, { + fn boxed(self) -> Box { + Box::new(self) + } + async fn run(mut self: Box, shutdown_token: CancellationToken) -> anyhow::Result<()> { let mut sender_handles = vec![]; let mut bundle_sender_actions = vec![]; @@ -208,7 +209,7 @@ impl BuilderTask { event_sender: broadcast::Sender>, builder_builder: LocalBuilderBuilder, pool: P, - provider: Arc, + provider: PR, ep_06: Option, ep_07: Option, ) -> Self { @@ -226,16 +227,11 @@ impl BuilderTask { impl BuilderTask where - P: Pool + Clone, - PR: Provider, - E06: EntryPointProvider, - E07: EntryPointProvider, + P: Pool + Clone + 'static, + PR: EvmProvider + Clone + 'static, + E06: EntryPointProvider + Clone + 'static, + E07: EntryPointProvider + Clone + 'static, { - /// Convert this task into a boxed task - pub fn boxed(self) -> Box { - Box::new(self) - } - async fn create_builders_v0_6( &self, ep: &EntryPointBuilderSettings, @@ -258,10 +254,10 @@ where let (spawn_guard, bundle_sender_action) = if self.args.unsafe_mode { self.create_bundle_builder( i + ep.bundle_builder_index_offset, - Arc::clone(&self.provider), + self.provider.clone(), ep_v0_6.clone(), UnsafeSimulator::new( - Arc::clone(&self.provider), + self.provider.clone(), ep_v0_6.clone(), self.args.sim_settings.clone(), ), @@ -271,10 +267,10 @@ where } else { self.create_bundle_builder( i + ep.bundle_builder_index_offset, - Arc::clone(&self.provider), + self.provider.clone(), ep_v0_6.clone(), simulation::new_v0_6_simulator( - Arc::clone(&self.provider), + self.provider.clone(), ep_v0_6.clone(), self.args.sim_settings.clone(), ep.mempool_configs.clone(), @@ -311,10 +307,10 @@ where let (spawn_guard, bundle_sender_action) = if self.args.unsafe_mode { self.create_bundle_builder( i + ep.bundle_builder_index_offset, - Arc::clone(&self.provider), + self.provider.clone(), ep_v0_7.clone(), UnsafeSimulator::new( - Arc::clone(&self.provider), + self.provider.clone(), ep_v0_7.clone(), self.args.sim_settings.clone(), ), @@ -324,10 +320,10 @@ where } else { self.create_bundle_builder( i + ep.bundle_builder_index_offset, - Arc::clone(&self.provider), + self.provider.clone(), ep_v0_7.clone(), simulation::new_v0_7_simulator( - Arc::clone(&self.provider), + self.provider.clone(), ep_v0_7.clone(), self.args.sim_settings.clone(), ep.mempool_configs.clone(), @@ -345,7 +341,7 @@ where async fn create_bundle_builder( &self, index: u64, - provider: Arc, + provider: PR, entry_point: E, simulator: S, pk_iter: &mut I, @@ -356,8 +352,8 @@ where where UO: UserOperation + From, UserOperationVariant: AsRef, - E: EntryPointProvider + Clone, - S: Simulator, + E: EntryPointProvider + Clone + 'static, + S: Simulator + 'static, I: Iterator, { let (send_bundle_tx, send_bundle_rx) = mpsc::channel(1); @@ -365,12 +361,8 @@ where let signer = if let Some(pk) = pk_iter.next() { info!("Using local signer"); BundlerSigner::Local( - LocalSigner::connect( - Arc::clone(&provider), - self.args.chain_spec.id, - pk.to_owned(), - ) - .await?, + LocalSigner::connect(provider.clone(), self.args.chain_spec.id, pk.to_owned()) + .await?, ) } else { info!("Using AWS KMS signer"); @@ -381,9 +373,8 @@ where // so this should give ample time for the connection to establish. Duration::from_millis(self.args.redis_lock_ttl_millis / 4), KmsSigner::connect( - Arc::clone(&provider), + provider.clone(), self.args.chain_spec.id, - self.args.aws_kms_region.clone(), self.args.aws_kms_key_ids.clone(), self.args.redis_uri.clone(), self.args.redis_lock_ttl_millis, @@ -417,7 +408,7 @@ where }; let transaction_tracker = TransactionTrackerImpl::new( - Arc::clone(&provider), + provider.clone(), transaction_sender, tracker_settings, index, @@ -430,15 +421,25 @@ where max_blocks_to_wait_for_mine: self.args.max_blocks_to_wait_for_mine, }; + let fee_oracle = gas::get_fee_oracle(&self.args.chain_spec, provider.clone()); + let fee_estimator = FeeEstimatorImpl::new( + provider.clone(), + fee_oracle, + proposer_settings.priority_fee_mode, + proposer_settings.bundle_priority_fee_overhead_percent, + ); + let proposer = BundleProposerImpl::new( index, self.pool.clone(), simulator, entry_point.clone(), - Arc::clone(&provider), + provider.clone(), + fee_estimator, proposer_settings, self.event_sender.clone(), ); + let builder = BundleSenderImpl::new( index, send_bundle_rx, diff --git a/crates/builder/src/transaction_tracker.rs b/crates/builder/src/transaction_tracker.rs index 3055afbbb..923dcc74e 100644 --- a/crates/builder/src/transaction_tracker.rs +++ b/crates/builder/src/transaction_tracker.rs @@ -11,14 +11,12 @@ // You should have received a copy of the GNU General Public License along with Rundler. // If not, see https://www.gnu.org/licenses/. -use std::sync::Arc; - +use alloy_primitives::{Address, B256}; use anyhow::{bail, Context}; use async_trait::async_trait; -use ethers::types::{transaction::eip2718::TypedTransaction, Address, H256, U256}; #[cfg(test)] use mockall::automock; -use rundler_provider::Provider; +use rundler_provider::{EvmProvider, TransactionRequest}; use rundler_sim::ExpectedStorage; use rundler_types::GasFees; use tracing::{debug, info, warn}; @@ -36,9 +34,9 @@ use crate::sender::{TransactionSender, TxSenderError, TxStatus}; /// have changed so that it is worth making another attempt. #[async_trait] #[cfg_attr(test, automock)] -pub(crate) trait TransactionTracker: Send + Sync + 'static { +pub(crate) trait TransactionTracker: Send + Sync { /// Returns the current nonce and the required fees for the next transaction. - fn get_nonce_and_required_fees(&self) -> TransactionTrackerResult<(U256, Option)>; + fn get_nonce_and_required_fees(&self) -> TransactionTrackerResult<(u64, Option)>; /// Sends the provided transaction and typically returns its transaction /// hash, but if the transaction failed to send because another transaction @@ -46,9 +44,9 @@ pub(crate) trait TransactionTracker: Send + Sync + 'static { /// transaction instead. async fn send_transaction( &mut self, - tx: TypedTransaction, + tx: TransactionRequest, expected_stroage: &ExpectedStorage, - ) -> TransactionTrackerResult; + ) -> TransactionTrackerResult; /// Cancel the abandoned transaction in the tracker. /// @@ -58,7 +56,7 @@ pub(crate) trait TransactionTracker: Send + Sync + 'static { &mut self, to: Address, estimated_fees: GasFees, - ) -> TransactionTrackerResult>; + ) -> TransactionTrackerResult>; /// Checks: /// 1. One of our transactions mines (not necessarily the one just sent). @@ -100,33 +98,29 @@ pub(crate) type TransactionTrackerResult = std::result::Result, - gas_used: Option, - gas_price: Option, + gas_limit: Option, + gas_used: Option, + gas_price: Option, }, LatestTxDropped { - nonce: U256, + nonce: u64, }, NonceUsedForOtherTx { - nonce: U256, + nonce: u64, }, } #[derive(Debug)] -pub(crate) struct TransactionTrackerImpl -where - P: Provider, - T: TransactionSender, -{ - provider: Arc

, +pub(crate) struct TransactionTrackerImpl { + provider: P, sender: T, settings: Settings, builder_index: u64, - nonce: U256, + nonce: u64, transactions: Vec, has_abandoned: bool, attempt_count: u64, @@ -134,23 +128,23 @@ where #[derive(Clone, Copy, Debug)] pub(crate) struct Settings { - pub(crate) replacement_fee_percent_increase: u64, + pub(crate) replacement_fee_percent_increase: u32, } #[derive(Clone, Copy, Debug)] struct PendingTransaction { - tx_hash: H256, + tx_hash: B256, gas_fees: GasFees, attempt_number: u64, } impl TransactionTrackerImpl where - P: Provider, + P: EvmProvider, T: TransactionSender, { pub(crate) async fn new( - provider: Arc

, + provider: P, sender: T, settings: Settings, builder_index: u64, @@ -158,7 +152,7 @@ where let nonce = provider .get_transaction_count(sender.address()) .await - .unwrap_or(U256::zero()); + .unwrap_or(0); Ok(Self { provider, sender, @@ -171,7 +165,7 @@ where }) } - fn set_nonce_and_clear_state(&mut self, nonce: U256) { + fn set_nonce_and_clear_state(&mut self, nonce: u64) { self.nonce = nonce; self.transactions.clear(); self.attempt_count = 0; @@ -179,18 +173,21 @@ where self.update_metrics(); } - async fn get_external_nonce(&self) -> anyhow::Result { + async fn get_external_nonce(&self) -> anyhow::Result { self.provider .get_transaction_count(self.sender.address()) .await .context("tracker should load current nonce from provider") } - fn validate_transaction(&self, tx: &TypedTransaction) -> anyhow::Result<()> { - let Some(&nonce) = tx.nonce() else { + fn validate_transaction(&self, tx: &TransactionRequest) -> anyhow::Result<()> { + let Some(nonce) = tx.nonce else { bail!("transaction given to tracker should have nonce set"); }; - let gas_fees = GasFees::from(tx); + let gas_fees = GasFees { + max_fee_per_gas: tx.max_fee_per_gas.unwrap_or(0), + max_priority_fee_per_gas: tx.max_priority_fee_per_gas.unwrap_or(0), + }; let (required_nonce, required_gas_fees) = self.get_nonce_and_required_fees()?; if nonce != required_nonce { bail!("tried to send transaction with nonce {nonce}, but should match tracker's nonce of {required_nonce}"); @@ -221,10 +218,10 @@ where async fn get_mined_tx_gas_info( &self, - tx_hash: H256, - ) -> anyhow::Result<(Option, Option, Option)> { + tx_hash: B256, + ) -> anyhow::Result<(Option, Option, Option)> { let (tx, tx_receipt) = tokio::try_join!( - self.provider.get_transaction(tx_hash), + self.provider.get_transaction_by_hash(tx_hash), self.provider.get_transaction_receipt(tx_hash), )?; let gas_limit = tx.map(|t| t.gas).or_else(|| { @@ -232,7 +229,7 @@ where None }); let (gas_used, gas_price) = match tx_receipt { - Some(r) => (r.gas_used, r.effective_gas_price), + Some(r) => (Some(r.gas_used), Some(r.effective_gas_price)), None => { warn!("failed to fetch transaction receipt for tx: {}", tx_hash); (None, None) @@ -245,10 +242,10 @@ where #[async_trait] impl TransactionTracker for TransactionTrackerImpl where - P: Provider, + P: EvmProvider, T: TransactionSender, { - fn get_nonce_and_required_fees(&self) -> TransactionTrackerResult<(U256, Option)> { + fn get_nonce_and_required_fees(&self) -> TransactionTrackerResult<(u64, Option)> { let gas_fees = if self.has_abandoned { None } else { @@ -262,16 +259,19 @@ where async fn send_transaction( &mut self, - tx: TypedTransaction, + tx: TransactionRequest, expected_storage: &ExpectedStorage, - ) -> TransactionTrackerResult { + ) -> TransactionTrackerResult { self.validate_transaction(&tx)?; - let gas_fees = GasFees::from(&tx); + let gas_fees = GasFees { + max_fee_per_gas: tx.max_fee_per_gas.unwrap_or(0), + max_priority_fee_per_gas: tx.max_priority_fee_per_gas.unwrap_or(0), + }; info!( "Sending transaction with nonce: {:?} gas fees: {:?} gas limit: {:?}", self.nonce, gas_fees, - tx.gas() + tx.gas.unwrap_or(0), ); let sent_tx = self.sender.send_transaction(tx, expected_storage).await; @@ -303,7 +303,7 @@ where && gas_fees.max_priority_fee_per_gas > t.gas_fees.max_priority_fee_per_gas }) { self.transactions.push(PendingTransaction { - tx_hash: H256::zero(), + tx_hash: B256::ZERO, gas_fees, attempt_number: self.attempt_count, }); @@ -322,7 +322,7 @@ where &mut self, to: Address, estimated_fees: GasFees, - ) -> TransactionTrackerResult> { + ) -> TransactionTrackerResult> { let (tx_hash, gas_fees) = match self.transactions.last() { Some(tx) => { let increased_fees = tx @@ -338,7 +338,7 @@ where }; (tx.tx_hash, gas_fees) } - None => (H256::zero(), estimated_fees), + None => (B256::ZERO, estimated_fees), }; let cancel_info = self @@ -410,7 +410,7 @@ where return Ok(None); }; - if last_tx.tx_hash == H256::zero() { + if last_tx.tx_hash == B256::ZERO { // If the last transaction was a replacement that failed to send, we // don't need to check for updates. return Ok(None); @@ -482,9 +482,9 @@ impl TransactionTrackerMetrics { .set(num_pending_transactions as f64); } - fn set_nonce(builder_index: u64, nonce: U256) { + fn set_nonce(builder_index: u64, nonce: u64) { metrics::gauge!("builder_tracker_nonce", "builder_index" => builder_index.to_string()) - .set(nonce.as_u64() as f64); + .set(nonce as f64); } fn set_attempt_count(builder_index: u64, attempt_count: u64) { @@ -495,40 +495,41 @@ impl TransactionTrackerMetrics { let fees = current_fees.unwrap_or_default(); metrics::gauge!("builder_tracker_current_max_fee_per_gas", "builder_index" => builder_index.to_string()) - .set(fees.max_fee_per_gas.as_u64() as f64); + .set(fees.max_fee_per_gas as f64); metrics::gauge!("builder_tracker_current_max_priority_fee_per_gas", "builder_index" => builder_index.to_string()) - .set(fees.max_priority_fee_per_gas.as_u64() as f64); + .set(fees.max_priority_fee_per_gas as f64); } } #[cfg(test)] mod tests { - use std::sync::Arc; - - use ethers::types::{Address, Eip1559TransactionRequest, Transaction, TransactionReceipt}; + use alloy_primitives::Address; use mockall::Sequence; - use rundler_provider::MockProvider; + use rundler_provider::{ + MockEvmProvider, Transaction, TransactionReceipt, TransactionReceiptEnvelope, + TransactionReceiptWithBloom, + }; use super::*; use crate::sender::{MockTransactionSender, SentTxInfo}; - fn create_base_config() -> (MockTransactionSender, MockProvider) { + fn create_base_config() -> (MockTransactionSender, MockEvmProvider) { let sender = MockTransactionSender::new(); - let provider = MockProvider::new(); + let provider = MockEvmProvider::new(); (sender, provider) } async fn create_tracker( sender: MockTransactionSender, - provider: MockProvider, - ) -> TransactionTrackerImpl { + provider: MockEvmProvider, + ) -> TransactionTrackerImpl { let settings = Settings { replacement_fee_percent_increase: 5, }; - let tracker: TransactionTrackerImpl = - TransactionTrackerImpl::new(Arc::new(provider), sender, settings, 0) + let tracker: TransactionTrackerImpl = + TransactionTrackerImpl::new(provider, sender, settings, 0) .await .unwrap(); @@ -538,38 +539,38 @@ mod tests { #[tokio::test] async fn test_nonce_and_fees() { let (mut sender, mut provider) = create_base_config(); - sender.expect_address().return_const(Address::zero()); + sender.expect_address().return_const(Address::ZERO); sender.expect_send_transaction().returning(move |_a, _b| { Box::pin(async { Ok(SentTxInfo { - nonce: U256::from(0), - tx_hash: H256::zero(), + nonce: 0, + tx_hash: B256::ZERO, }) }) }); provider .expect_get_transaction_count() - .returning(move |_a| Ok(U256::from(0))); + .returning(move |_a| Ok(0)); let mut tracker = create_tracker(sender, provider).await; - let tx = Eip1559TransactionRequest::new() + let tx = TransactionRequest::default() .nonce(0) - .gas(10000) + .gas_limit(10000) .max_fee_per_gas(10000); let exp = ExpectedStorage::default(); // send dummy transaction - let _sent = tracker.send_transaction(tx.into(), &exp).await; + let _sent = tracker.send_transaction(tx, &exp).await; let nonce_and_fees = tracker.get_nonce_and_required_fees().unwrap(); assert_eq!( ( - U256::from(0), + 0, Some(GasFees { - max_fee_per_gas: U256::from(10500), - max_priority_fee_per_gas: U256::zero(), + max_fee_per_gas: 10500, + max_priority_fee_per_gas: 0, }) ), nonce_and_fees @@ -579,7 +580,7 @@ mod tests { #[tokio::test] async fn test_nonce_and_fees_abandoned() { let (mut sender, mut provider) = create_base_config(); - sender.expect_address().return_const(Address::zero()); + sender.expect_address().return_const(Address::ZERO); sender .expect_get_transaction_status() @@ -588,57 +589,57 @@ mod tests { sender.expect_send_transaction().returning(move |_a, _b| { Box::pin(async { Ok(SentTxInfo { - nonce: U256::from(0), - tx_hash: H256::zero(), + nonce: 0, + tx_hash: B256::ZERO, }) }) }); provider .expect_get_transaction_count() - .returning(move |_a| Ok(U256::from(0))); + .returning(move |_a| Ok(0)); let mut tracker = create_tracker(sender, provider).await; - let tx = Eip1559TransactionRequest::new() + let tx = TransactionRequest::default() .nonce(0) - .gas(10000) + .gas_limit(10000) .max_fee_per_gas(10000); let exp = ExpectedStorage::default(); // send dummy transaction - let _sent = tracker.send_transaction(tx.into(), &exp).await; + let _sent = tracker.send_transaction(tx, &exp).await; let _tracker_update = tracker.check_for_update().await.unwrap(); tracker.abandon(); let nonce_and_fees = tracker.get_nonce_and_required_fees().unwrap(); - assert_eq!((U256::from(0), None), nonce_and_fees); + assert_eq!((0, None), nonce_and_fees); } #[tokio::test] async fn test_send_transaction_without_nonce() { let (mut sender, mut provider) = create_base_config(); - sender.expect_address().return_const(Address::zero()); + sender.expect_address().return_const(Address::ZERO); sender.expect_send_transaction().returning(move |_a, _b| { Box::pin(async { Ok(SentTxInfo { - nonce: U256::from(0), - tx_hash: H256::zero(), + nonce: 0, + tx_hash: B256::ZERO, }) }) }); provider .expect_get_transaction_count() - .returning(move |_a| Ok(U256::from(2))); + .returning(move |_a| Ok(2)); let mut tracker = create_tracker(sender, provider).await; - let tx = Eip1559TransactionRequest::new(); + let tx = TransactionRequest::default(); let exp = ExpectedStorage::default(); - let sent_transaction = tracker.send_transaction(tx.into(), &exp).await; + let sent_transaction = tracker.send_transaction(tx, &exp).await; assert!(sent_transaction.is_err()); } @@ -647,25 +648,25 @@ mod tests { async fn test_send_transaction_with_invalid_nonce() { let (mut sender, mut provider) = create_base_config(); - sender.expect_address().return_const(Address::zero()); + sender.expect_address().return_const(Address::ZERO); sender.expect_send_transaction().returning(move |_a, _b| { Box::pin(async { Ok(SentTxInfo { - nonce: U256::from(0), - tx_hash: H256::zero(), + nonce: 0, + tx_hash: B256::ZERO, }) }) }); provider .expect_get_transaction_count() - .returning(move |_a| Ok(U256::from(2))); + .returning(move |_a| Ok(2)); let mut tracker = create_tracker(sender, provider).await; - let tx = Eip1559TransactionRequest::new().nonce(0); + let tx = TransactionRequest::default().nonce(0); let exp = ExpectedStorage::default(); - let sent_transaction = tracker.send_transaction(tx.into(), &exp).await; + let sent_transaction = tracker.send_transaction(tx, &exp).await; assert!(sent_transaction.is_err()); } @@ -673,32 +674,32 @@ mod tests { #[tokio::test] async fn test_send_transaction() { let (mut sender, mut provider) = create_base_config(); - sender.expect_address().return_const(Address::zero()); + sender.expect_address().return_const(Address::ZERO); sender.expect_send_transaction().returning(move |_a, _b| { Box::pin(async { Ok(SentTxInfo { - nonce: U256::from(0), - tx_hash: H256::zero(), + nonce: 0, + tx_hash: B256::ZERO, }) }) }); provider .expect_get_transaction_count() - .returning(move |_a| Ok(U256::from(0))); + .returning(move |_a| Ok(0)); let mut tracker = create_tracker(sender, provider).await; - let tx = Eip1559TransactionRequest::new().nonce(0); + let tx = TransactionRequest::default().nonce(0); let exp = ExpectedStorage::default(); - tracker.send_transaction(tx.into(), &exp).await.unwrap(); + tracker.send_transaction(tx, &exp).await.unwrap(); } // TODO(#295): fix dropped status // #[tokio::test] // async fn test_wait_for_update_dropped() { // let (mut sender, mut provider) = create_base_config(); - // sender.expect_address().return_const(Address::zero()); + // sender.expect_address().return_const(Address::ZERO); // sender // .expect_get_transaction_status() @@ -707,15 +708,15 @@ mod tests { // sender.expect_send_transaction().returning(move |_a, _b| { // Box::pin(async { // Ok(SentTxInfo { - // nonce: U256::from(0), - // tx_hash: H256::zero(), + // nonce: U256::ZERO, + // tx_hash: B256::zero(), // }) // }) // }); // provider // .expect_get_transaction_count() - // .returning(move |_a| Ok(U256::from(0))); + // .returning(move |_a| Ok(U256::ZERO)); // provider.expect_get_block_number().returning(move || Ok(1)); @@ -735,13 +736,13 @@ mod tests { #[tokio::test] async fn test_check_for_update_nonce_used() { let (mut sender, mut provider) = create_base_config(); - sender.expect_address().return_const(Address::zero()); + sender.expect_address().return_const(Address::ZERO); let mut provider_seq = Sequence::new(); for transaction_count in 0..=1 { provider .expect_get_transaction_count() - .returning(move |_a| Ok(U256::from(transaction_count))) + .returning(move |_a| Ok(transaction_count)) .times(1) .in_sequence(&mut provider_seq); } @@ -759,7 +760,7 @@ mod tests { #[tokio::test] async fn test_check_for_update_mined() { let (mut sender, mut provider) = create_base_config(); - sender.expect_address().return_const(Address::zero()); + sender.expect_address().return_const(Address::ZERO); sender .expect_get_transaction_status() .returning(move |_a| Box::pin(async { Ok(TxStatus::Mined { block_number: 1 }) })); @@ -767,39 +768,55 @@ mod tests { sender.expect_send_transaction().returning(move |_a, _b| { Box::pin(async { Ok(SentTxInfo { - nonce: U256::from(0), - tx_hash: H256::random(), + nonce: 0, + tx_hash: B256::random(), }) }) }); provider .expect_get_transaction_count() - .returning(move |_a| Ok(U256::from(0))); + .returning(move |_a| Ok(0)); - provider.expect_get_transaction().returning(|_: H256| { - Ok(Some(Transaction { - gas: U256::from(0), - ..Default::default() - })) - }); + provider + .expect_get_transaction_by_hash() + .returning(|_: B256| { + Ok(Some(Transaction { + gas: 0, + ..Default::default() + })) + }); provider .expect_get_transaction_receipt() - .returning(|_: H256| { + .returning(|_: B256| { Ok(Some(TransactionReceipt { - gas_used: Some(U256::from(0)), - ..Default::default() + inner: TransactionReceiptEnvelope::Legacy( + TransactionReceiptWithBloom::default(), + ), + transaction_hash: B256::ZERO, + transaction_index: None, + block_hash: None, + block_number: None, + gas_used: 0, + effective_gas_price: 0, + blob_gas_used: None, + blob_gas_price: None, + from: Address::ZERO, + to: None, + contract_address: None, + state_root: None, + authorization_list: None, })) }); let mut tracker = create_tracker(sender, provider).await; - let tx = Eip1559TransactionRequest::new().nonce(0); + let tx = TransactionRequest::default().nonce(0); let exp = ExpectedStorage::default(); // send dummy transaction - let _sent = tracker.send_transaction(tx.into(), &exp).await; + let _sent = tracker.send_transaction(tx, &exp).await; let tracker_update = tracker.check_for_update().await.unwrap().unwrap(); assert!(matches!(tracker_update, TrackerUpdate::Mined { .. })); diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index 5e3332d6a..a70016f5f 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -16,19 +16,22 @@ alloy-consensus.workspace = true alloy-contract.workspace = true alloy-json-rpc.workspace = true alloy-primitives = { workspace = true, features = ["rand"] } -alloy-provider = { workspace = true, features = ["debug-api", "hyper"] } +alloy-provider = { workspace = true, features = ["debug-api"] } alloy-rlp.workspace = true +alloy-rpc-client.workspace = true alloy-rpc-types-eth.workspace = true alloy-rpc-types-trace.workspace = true alloy-sol-types.workspace = true alloy-transport.workspace = true +alloy-transport-http.workspace = true +reqwest.workspace = true anyhow.workspace = true async-trait.workspace = true auto_impl.workspace = true thiserror.workspace = true tracing.workspace = true - +url.workspace = true mockall = {workspace = true, optional = true } [features] diff --git a/crates/provider/src/alloy/entry_point/mod.rs b/crates/provider/src/alloy/entry_point/mod.rs index 121f75e67..ee8af0190 100644 --- a/crates/provider/src/alloy/entry_point/mod.rs +++ b/crates/provider/src/alloy/entry_point/mod.rs @@ -27,7 +27,7 @@ pub(crate) mod v0_7; mod arbitrum; mod optimism; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] enum L1GasOracle { ArbitrumNitro(Address), OptimismBedrock(Address), diff --git a/crates/provider/src/alloy/entry_point/v0_6.rs b/crates/provider/src/alloy/entry_point/v0_6.rs index 1832a438b..0e7b93c01 100644 --- a/crates/provider/src/alloy/entry_point/v0_6.rs +++ b/crates/provider/src/alloy/entry_point/v0_6.rs @@ -41,6 +41,7 @@ use crate::{ }; /// Entry point provider for v0.6 +#[derive(Clone)] pub struct EntryPointProvider { i_entry_point: IEntryPointInstance, l1_gas_oracle: L1GasOracle, @@ -53,14 +54,9 @@ where AP: AlloyProvider, { /// Create a new `EntryPoint` instance for v0.6 - pub fn new( - entry_point_address: Address, - chain_spec: &ChainSpec, - max_aggregation_gas: u128, - provider: AP, - ) -> Self { + pub fn new(chain_spec: &ChainSpec, max_aggregation_gas: u128, provider: AP) -> Self { Self { - i_entry_point: IEntryPointInstance::new(entry_point_address, provider), + i_entry_point: IEntryPointInstance::new(chain_spec.entry_point_address_v0_6, provider), l1_gas_oracle: L1GasOracle::new(chain_spec), max_aggregation_gas, } @@ -261,8 +257,8 @@ where gas: u128, gas_fees: GasFees, ) -> TransactionRequest { - let tx = get_handle_ops_call(&self.i_entry_point, ops_per_aggregator, beneficiary, gas); - tx.max_fee_per_gas(gas_fees.max_fee_per_gas) + get_handle_ops_call(&self.i_entry_point, ops_per_aggregator, beneficiary, gas) + .max_fee_per_gas(gas_fees.max_fee_per_gas) .max_priority_fee_per_gas(gas_fees.max_priority_fee_per_gas) } } diff --git a/crates/provider/src/alloy/entry_point/v0_7.rs b/crates/provider/src/alloy/entry_point/v0_7.rs index afc39f598..7dc8a05a7 100644 --- a/crates/provider/src/alloy/entry_point/v0_7.rs +++ b/crates/provider/src/alloy/entry_point/v0_7.rs @@ -48,6 +48,7 @@ use crate::{ }; /// Entry point provider for v0.7 +#[derive(Clone)] pub struct EntryPointProvider { i_entry_point: IEntryPointInstance, l1_gas_oracle: L1GasOracle, @@ -60,14 +61,9 @@ where AP: AlloyProvider, { /// Create a new `EntryPoint` instance for v0.7 - pub fn new( - entry_point_address: Address, - chain_spec: &ChainSpec, - max_aggregation_gas: u128, - provider: AP, - ) -> Self { + pub fn new(chain_spec: &ChainSpec, max_aggregation_gas: u128, provider: AP) -> Self { Self { - i_entry_point: IEntryPointInstance::new(entry_point_address, provider), + i_entry_point: IEntryPointInstance::new(chain_spec.entry_point_address_v0_7, provider), l1_gas_oracle: L1GasOracle::new(chain_spec), max_aggregation_gas, } diff --git a/crates/provider/src/alloy/evm.rs b/crates/provider/src/alloy/evm.rs index bc2db9b98..03179fbb3 100644 --- a/crates/provider/src/alloy/evm.rs +++ b/crates/provider/src/alloy/evm.rs @@ -50,6 +50,18 @@ impl AlloyEvmProvider { } } +impl Clone for AlloyEvmProvider +where + AP: Clone, +{ + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + _marker: PhantomData, + } + } +} + impl From for AlloyEvmProvider where T: Transport + Clone, diff --git a/crates/provider/src/alloy/mod.rs b/crates/provider/src/alloy/mod.rs index 2a7b1b800..a0e61a89a 100644 --- a/crates/provider/src/alloy/mod.rs +++ b/crates/provider/src/alloy/mod.rs @@ -11,6 +11,29 @@ // 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_provider::{Provider as AlloyProvider, ProviderBuilder}; +use alloy_rpc_client::ClientBuilder; +use alloy_transport_http::Http; +use anyhow::Context; +use evm::AlloyEvmProvider; +use reqwest::Client; +use url::Url; + +use crate::EvmProvider; + pub(crate) mod entry_point; pub(crate) mod evm; pub(crate) mod metrics; + +/// Create a new alloy evm provider from a given RPC URL +pub fn new_alloy_evm_provider(rpc_url: &str) -> anyhow::Result { + let provider = new_alloy_provider(rpc_url)?; + Ok(AlloyEvmProvider::new(provider)) +} + +/// Create a new alloy provider from a given RPC URL +pub fn new_alloy_provider(rpc_url: &str) -> anyhow::Result>> { + let url = Url::parse(rpc_url).context("invalid rpc url")?; + let client = ClientBuilder::default().http(url); + Ok(ProviderBuilder::new().on_client(client)) +} diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs index 6b429a8d5..0b39c210b 100644 --- a/crates/provider/src/lib.rs +++ b/crates/provider/src/lib.rs @@ -32,6 +32,7 @@ pub use alloy::{ }, evm::AlloyEvmProvider, metrics::AlloyMethodExtractor, + new_alloy_evm_provider, new_alloy_provider, }; mod traits; diff --git a/crates/sim/src/lib.rs b/crates/sim/src/lib.rs index 1e3d35059..22249a8c3 100644 --- a/crates/sim/src/lib.rs +++ b/crates/sim/src/lib.rs @@ -41,6 +41,8 @@ pub use estimation::{ }; pub mod gas; +#[cfg(feature = "test-utils")] +pub use gas::MockFeeEstimator; pub use gas::{FeeEstimator, PriorityFeeMode}; mod precheck;