diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d8a11bc6c..329fb721d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -122,6 +122,12 @@ jobs: name: reth-rbuilder-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}${{ matrix.features && '-' }}${{ matrix.features }} path: target/${{ matrix.configs.target }}/release/reth-rbuilder + - name: Upload rbuilder-rebalancer artifact + uses: actions/upload-artifact@v4 + with: + name: rbuilder-rebalancer-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}${{ matrix.features && '-' }}${{ matrix.features }} + path: target/${{ matrix.configs.target }}/release/rbuilder-rebalancer + - name: Upload bid-scraper artifact uses: actions/upload-artifact@v4 with: diff --git a/Cargo.lock b/Cargo.lock index 197d3654c..4d00a3417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2020,7 +2020,7 @@ dependencies = [ "alloy-rpc-types-beacon", "async-trait", "chrono", - "clap 3.2.25", + "clap 4.5.36", "derivative", "ethereum_ssz 0.9.0", "ethereum_ssz_derive", @@ -2700,14 +2700,9 @@ version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ - "atty", "bitflags 1.3.2", - "clap_derive 3.2.25", "clap_lex 0.2.4", "indexmap 1.9.3", - "once_cell", - "strsim 0.10.0", - "termcolor", "textwrap", ] @@ -2718,7 +2713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" dependencies = [ "clap_builder", - "clap_derive 4.5.32", + "clap_derive", ] [[package]] @@ -2730,20 +2725,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex 0.7.4", - "strsim 0.11.1", -] - -[[package]] -name = "clap_derive" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" -dependencies = [ - "heck 0.4.1", - "proc-macro-error", - "proc-macro2 1.0.95", - "quote 1.0.40", - "syn 1.0.109", + "strsim", ] [[package]] @@ -3343,7 +3325,7 @@ dependencies = [ "ident_case", "proc-macro2 1.0.95", "quote 1.0.40", - "strsim 0.11.1", + "strsim", "syn 2.0.100", ] @@ -9352,6 +9334,29 @@ dependencies = [ "uuid", ] +[[package]] +name = "rbuilder-rebalancer" +version = "0.1.0" +dependencies = [ + "alloy-consensus 1.0.27", + "alloy-eips 1.0.27", + "alloy-primitives 1.3.1", + "alloy-provider", + "alloy-rpc-types-eth 1.0.27", + "alloy-signer", + "alloy-signer-local", + "clap 4.5.36", + "eyre", + "futures", + "rbuilder-config", + "reqwest 0.12.15", + "serde", + "serde_json", + "tokio", + "toml 0.8.20", + "tracing", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -13960,12 +13965,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -14273,15 +14272,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "termtree" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 150ceee7b..e1c373ea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "crates/rbuilder-config", "crates/rbuilder", "crates/rbuilder-operator", + "crates/rbuilder-rebalancer", "crates/reth-rbuilder", "crates/rbuilder/src/test_utils", "crates/rbuilder/src/telemetry/metrics_macros", diff --git a/crates/bid-scraper/Cargo.toml b/crates/bid-scraper/Cargo.toml index 7d4f58fda..def219e60 100644 --- a/crates/bid-scraper/Cargo.toml +++ b/crates/bid-scraper/Cargo.toml @@ -20,7 +20,7 @@ tokio-util.workspace = true tracing = "0.1.37" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } log = "^0.4" -clap = { version = "^3.2", features = ["derive"] } +clap.workspace = true serde_json = { workspace = true } serde = { workspace = true } serde_with = { version = "3.9.0", features = ["time_0_3"] } @@ -41,7 +41,7 @@ ssz_types = "^0.5" hex = "^0.4" derivative.workspace = true toml.workspace = true -eyre = "0.6.12" +eyre.workspace = true thiserror.workspace = true parking_lot.workspace = true strum = { version = "0.25", features = ["derive"] } diff --git a/crates/eth-sparse-mpt/Cargo.toml b/crates/eth-sparse-mpt/Cargo.toml index b25275e86..26de9cb85 100644 --- a/crates/eth-sparse-mpt/Cargo.toml +++ b/crates/eth-sparse-mpt/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true [dependencies] thiserror = "1.0.61" -serde = { version = "1.0.203", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } serde_json = "1.0.117" serde_with = "3.9.0" rustc-hash = "2.0.0" diff --git a/crates/rbuilder-config/src/logger.rs b/crates/rbuilder-config/src/logger.rs index 867ace4c3..bfbfeb377 100644 --- a/crates/rbuilder-config/src/logger.rs +++ b/crates/rbuilder-config/src/logger.rs @@ -1,13 +1,26 @@ use tracing_subscriber::EnvFilter; /// Logger configuration. -#[derive(Debug, Clone)] +#[derive(PartialEq, Eq, Clone, Debug, serde::Deserialize)] pub struct LoggerConfig { pub env_filter: String, + #[serde(default)] pub log_json: bool, + #[serde(default)] pub log_color: bool, } +impl LoggerConfig { + /// Default logger configuration for development. + pub fn dev() -> Self { + Self { + env_filter: String::from("info"), + log_color: true, + log_json: false, + } + } +} + impl LoggerConfig { /// Initialize tracing subscriber based on the configuration. pub fn init_tracing(self) -> eyre::Result<()> { diff --git a/crates/rbuilder-rebalancer/Cargo.toml b/crates/rbuilder-rebalancer/Cargo.toml new file mode 100644 index 000000000..ba6c8d76d --- /dev/null +++ b/crates/rbuilder-rebalancer/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "rbuilder-rebalancer" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +rbuilder-config.workspace = true + +alloy-primitives = { workspace = true, features = ["serde"] } +alloy-eips.workspace = true +alloy-consensus.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-provider.workspace = true +alloy-signer.workspace = true +alloy-signer-local.workspace = true + +# rt +tokio = { workspace = true, default-features = false, features = ["macros", "rt-multi-thread"] } +futures.workspace = true + +# misc +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +clap.workspace = true +eyre.workspace = true +toml.workspace = true +tracing.workspace = true +reqwest.workspace = true diff --git a/crates/rbuilder-rebalancer/README.md b/crates/rbuilder-rebalancer/README.md new file mode 100644 index 000000000..0f6d81907 --- /dev/null +++ b/crates/rbuilder-rebalancer/README.md @@ -0,0 +1,36 @@ +# BuilderNet Rebalancer + +A small, standalone service that moves funds between BuilderNet accounts to keep operational wallets topped up and tidy. It is designed to initiate rebalancing transfers from a builder’s EOA to a set of allow-listed targets without those transfers being counted as builder profit in coinbase accounting. + +## Why this exists? + +Operating a BuilderNet builder requires several wallets to be funded. This is especially true in the case of bid adjustments when the fee payer account constantly has decreasing balance and the block value is retained in the builder EOA. The rebalancer automates this by: +- Watching balances for a configured set of tracked accounts. +- Sending EOA-originated transfers when thresholds are crossed. + +## Configuration + +Sample configuration file: +```toml +rpc_url = "" # reth RPC URL for subscribing to new blocks and requesting state updates +builder_url = "" # builder URL for submitting bundles +transfer_max_priority_fee_per_gas = "" # the `max_priority_fee_per_gas` to set on the rebalancing transaction + +# Logging configuration +env_filter = "info" +log_color = false +log_json = true + +# Rebalancing account +[[account]] +id = "" # account ID +secret = "" # Private key or env variable name containing the private key +min_balance = "" + +[[rule]] +description = "" # information description of the rebalancing rule +source_id = "" # rebalancing account ID referencing an account entry in `accounts` +destination = "
" # destination target address +destination_min_balance = "2500000000000000000" # minimum balance. after going below it, the account will be topped up +destination_target_balance = "5000000000000000000" # the target balance to top up to +``` \ No newline at end of file diff --git a/crates/rbuilder-rebalancer/src/bin/main.rs b/crates/rbuilder-rebalancer/src/bin/main.rs new file mode 100644 index 000000000..c4c29656d --- /dev/null +++ b/crates/rbuilder-rebalancer/src/bin/main.rs @@ -0,0 +1,68 @@ +use alloy_provider::ProviderBuilder; +use alloy_signer_local::PrivateKeySigner; +use clap::Parser; +use rbuilder_rebalancer::{config::RebalancerConfig, rebalancer::Rebalancer}; +use std::{path::PathBuf, str::FromStr, time::Duration}; +use tracing::*; + +#[tokio::main] +async fn main() { + if let Err(error) = Cli::parse().run().await { + eprintln!("Error: {error:?}"); + std::process::exit(1); + } +} + +#[derive(Parser)] +struct Cli { + #[clap(env = "REBALANCER_CONFIG", help = "Config file path")] + config: PathBuf, +} + +impl Cli { + async fn run(self) -> eyre::Result<()> { + let config = RebalancerConfig::parse_toml_file(&self.config)?; + + config.logger.init_tracing()?; + + if config.rules.is_empty() { + warn!("No rebalancing rules have been configured, rebalancer will be idling"); + } + + for rule in &config.rules { + if rule.destination_min_balance >= rule.destination_target_balance { + eyre::bail!("Invalid configuration for rule `{}`: minimum balance must be lower than the target", rule.description); + } + + if !config.accounts.iter().any(|acc| acc.id == rule.source_id) { + eyre::bail!("Invalid configuration for rule `{}`: account entry is missing for source account {}", rule.description, rule.source_id); + } + } + + let rpc_provider = ProviderBuilder::new().connect(&config.rpc_url).await?; + let transfer_max_priority_fee_per_gas = + config.transfer_max_priority_fee_per_gas.try_into().unwrap(); + let accounts = config + .accounts + .into_iter() + .map(|account| { + let account = account.map_secret(|secret| { + let secret = secret.value().expect("invalid env"); + PrivateKeySigner::from_str(&secret).expect("invalid private key") + }); + (account.id.clone(), account) + }) + .collect(); + Rebalancer::new( + rpc_provider, + config.builder_url, + transfer_max_priority_fee_per_gas, + accounts, + config.rules, + Duration::from_secs(2), + Duration::from_secs(2), + ) + .run() + .await + } +} diff --git a/crates/rbuilder-rebalancer/src/config.rs b/crates/rbuilder-rebalancer/src/config.rs new file mode 100644 index 000000000..7edb6a85e --- /dev/null +++ b/crates/rbuilder-rebalancer/src/config.rs @@ -0,0 +1,118 @@ +use alloy_primitives::{Address, U256}; +use rbuilder_config::{EnvOrValue, LoggerConfig}; +use serde::Deserialize; +use std::{fs, path::Path}; + +#[derive(PartialEq, Eq, Debug, Deserialize)] +pub struct RebalancerConfig { + /// Node RPC URL for block subscription and state fetch. + pub rpc_url: String, + /// Builder RPC URL for bundle submissions. + pub builder_url: String, + /// Max priority fee per to set on the transfer. + pub transfer_max_priority_fee_per_gas: U256, + /// Logger configuration. + #[serde(flatten)] + pub logger: LoggerConfig, + /// Source accounts for funding. + #[serde(default, rename = "account")] + pub accounts: Vec>>, + /// Collection of rebelancer rules. + #[serde(default, rename = "rule")] + pub rules: Vec, +} + +impl RebalancerConfig { + /// Parse toml file. + pub fn parse_toml_file(path: &Path) -> eyre::Result { + let content = fs::read_to_string(path)?; + Ok(Self::parse_toml(&content)?) + } + + /// Parse relay configurations from toml string. + pub fn parse_toml(s: &str) -> Result { + toml::from_str(s) + } +} + +#[derive(PartialEq, Eq, Debug, Deserialize)] +pub struct RebalancerAccount { + /// Account ID. + pub id: String, + /// Account secret. + pub secret: S, + /// Minimum balance for source account. + pub min_balance: U256, +} + +impl RebalancerAccount { + /// Map account secret. + pub fn map_secret(self, map: F) -> RebalancerAccount + where + F: FnOnce(S) -> T, + { + RebalancerAccount { + id: self.id, + secret: map(self.secret), + min_balance: self.min_balance, + } + } +} + +#[derive(PartialEq, Eq, Debug, Deserialize)] +pub struct RebalancerRule { + /// Rule description. + pub description: String, + /// The source of funds referenced by id. + pub source_id: String, + /// Destination address. + pub destination: Address, + /// Destination target balance. + pub destination_target_balance: U256, + /// Destination minimum threshold balance after which rebalancing will be triggered. + pub destination_min_balance: U256, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_config() { + let config = RebalancerConfig::parse_toml( + r#" + rpc_url = "" + builder_url = "" + transfer_max_priority_fee_per_gas = "123" + + env_filter = "info" + log_color = true + + [[rule]] + description = "" + source_id = "" + destination = "0x0000000000000000000000000000000000000000" + destination_target_balance = "1" + destination_min_balance = "1" + "#, + ) + .unwrap(); + assert_eq!( + config, + RebalancerConfig { + rpc_url: String::new(), + builder_url: String::new(), + transfer_max_priority_fee_per_gas: U256::from(123), + logger: LoggerConfig::dev(), + accounts: Vec::new(), + rules: Vec::from([RebalancerRule { + description: String::new(), + source_id: String::new(), + destination: Address::ZERO, + destination_min_balance: U256::from(1), + destination_target_balance: U256::from(1), + }]) + } + ); + } +} diff --git a/crates/rbuilder-rebalancer/src/lib.rs b/crates/rbuilder-rebalancer/src/lib.rs new file mode 100644 index 000000000..46170bea8 --- /dev/null +++ b/crates/rbuilder-rebalancer/src/lib.rs @@ -0,0 +1,2 @@ +pub mod config; +pub mod rebalancer; diff --git a/crates/rbuilder-rebalancer/src/rebalancer.rs b/crates/rbuilder-rebalancer/src/rebalancer.rs new file mode 100644 index 000000000..f4bd82c64 --- /dev/null +++ b/crates/rbuilder-rebalancer/src/rebalancer.rs @@ -0,0 +1,279 @@ +use alloy_consensus::{ + EthereumTxEnvelope, EthereumTypedTransaction, SignableTransaction as _, TxEip1559, TxEip4844, +}; +use alloy_eips::{eip1559::BaseFeeParams, BlockId, Encodable2718}; +use alloy_primitives::{ + hex, + map::{AddressMap, AddressSet, HashMap}, + Address, Bytes, TxKind, B256, U256, +}; +use alloy_provider::Provider; +use alloy_rpc_types_eth::{AccountInfo, Header}; +use alloy_signer::SignerSync; +use alloy_signer_local::PrivateKeySigner; +use futures::{stream::FuturesUnordered, FutureExt, StreamExt as _}; +use reqwest::Client; +use serde_json::json; +use std::{cell::OnceCell, time::Duration}; +use tokio::time; +use tracing::*; + +use crate::config::{RebalancerAccount, RebalancerRule}; + +pub struct Rebalancer

{ + provider: P, + builder_client: reqwest::Client, + builder_url: String, + transfer_max_priority_fee_per_gas: u128, + accounts: HashMap>, + rules: Vec, + tracked_accounts: OnceCell, + timeout: Duration, + retry_delay: Duration, +} + +impl Rebalancer

{ + pub fn new( + provider: P, + builder_url: String, + transfer_max_priority_fee_per_gas: u128, + accounts: HashMap>, + rules: Vec, + timeout: Duration, + retry_delay: Duration, + ) -> Self { + let builder_client = reqwest::Client::builder().timeout(timeout).build().unwrap(); + Self { + provider, + builder_client, + builder_url, + transfer_max_priority_fee_per_gas, + accounts, + rules, + timeout, + retry_delay, + tracked_accounts: OnceCell::new(), + } + } + + fn tracked_accounts(&self) -> &AddressSet { + self.tracked_accounts.get_or_init(|| { + AddressSet::from_iter( + self.accounts + .values() + .map(|acc| acc.secret.address()) + .chain(self.rules.iter().map(|rule| rule.destination)), + ) + }) + } + + async fn fetch_accounts(&self, block_hash: B256) -> eyre::Result> { + let block_id = BlockId::hash(block_hash); + + let futs = FuturesUnordered::default(); + for target in self.tracked_accounts() { + futs.push(async move { + ( + *target, + time::timeout( + self.timeout, + self.provider.get_account_info(*target).block_id(block_id), + ) + .await, + ) + }); + } + + let mut infos = AddressMap::default(); + for (target, result) in futs.collect::>().await { + match result { + Ok(Ok(info)) => { + debug!(target: "rebalancer", %target, ?info, %block_hash, "Fetched account info for account"); + infos.insert(target, info); + } + // We don't want to rebalance if any of the account info fetches failed. + Ok(Err(error)) => { + return Err(eyre::eyre!( + "error fetching account info for {target}: {error}" + )); + } + Err(_) => return Err(eyre::eyre!("timed out fetching account info for {target}")), + } + } + + Ok(infos) + } + + async fn on_new_block(&self, header: Header) -> eyre::Result<()> { + info!(target: "rebalancer", number = header.number, hash = %header.hash, "Received new block"); + let accounts = self.fetch_accounts(header.hash).await?; + debug!(target: "rebalancer", number = header.number, hash = %header.hash, accounts = accounts.len(), "Updated account infos for tracked accounts"); + let mut transfers_by_source = HashMap::>::default(); + for rule in &self.rules { + let destination_balance = accounts + .get(&rule.destination) + .ok_or(eyre::eyre!("missing account for {}", rule.destination))? + .balance; + + if destination_balance > rule.destination_min_balance { + trace!(target: "rebalancer", number = header.number, hash = %header.hash, %rule.description, %rule.destination, %rule.destination_min_balance, %destination_balance, "Rebalancing destination balance above minimum"); + continue; + } + + let destination_target_delta = rule + .destination_target_balance + .checked_sub(destination_balance) + .expect("misconfiguration"); + + let transfer = Transfer { + destination: rule.destination, + amount: destination_target_delta, + description: rule.description.clone(), + }; + transfers_by_source + .entry(rule.source_id.clone()) + .or_default() + .push(transfer); + } + + for (source_id, transfers) in transfers_by_source { + let total_amount_out = transfers.iter().map(|t| t.amount).sum(); + + let source = self + .accounts + .get(&source_id) + .ok_or(eyre::eyre!("missing source {source_id}"))?; + let source_address = source.secret.address(); + let source_account = accounts + .get(&source_address) + .ok_or(eyre::eyre!("missing account {source_address}"))?; + let source_balance = source_account.balance; + + if source_balance + .checked_sub(total_amount_out) + .is_none_or(|final_balance| final_balance < source.min_balance) + { + let rules = transfers + .into_iter() + .map(|t| t.description) + .collect::>(); + warn!(target: "rebalancer", number = header.number, hash = %header.hash, %source_id, %source_address, %source_balance, %total_amount_out, ?rules, "Source account balance too low"); + continue; + } + + let client = self.builder_client.clone(); + let builder_url = self.builder_url.clone(); + let head = header.clone(); + let signer = source.secret.clone(); + let nonce = source_account.nonce; + let max_priority_fee_per_gas = self.transfer_max_priority_fee_per_gas; + tokio::spawn(async move { + if let Err(error) = send_system_transactions( + client, + &builder_url, + head.clone(), + signer, + nonce, + max_priority_fee_per_gas, + transfers, + ) + .await + { + error!(target: "rebalancer", number = head.number, hash = %head.hash, %source_id, %source_address, ?error, "Error sending system bundle"); + } + }); + } + + Ok(()) + } + + pub async fn run(self) -> eyre::Result<()> { + loop { + let mut subscription = self.provider.subscribe_blocks().await?.into_stream(); + while let Some(header) = subscription.next().await { + if let Err(error) = self.on_new_block(header.clone()).await { + error!(target: "rebalancer", number = header.number, hash = %header.hash, ?error, "Error handling block"); + } + } + warn!(target: "rebalancer", delay = ?self.retry_delay, "New block subscription has been terminated. Retrying..."); + tokio::time::sleep(self.retry_delay).await; + } + } +} + +async fn send_system_transactions( + client: Client, + builder_url: &str, + head: Header, + signer: PrivateKeySigner, + nonce: u64, + max_priority_fee_per_gas: u128, + transfers: Vec, +) -> eyre::Result<()> { + let signer_address = signer.address(); + let base_fee = head + .next_block_base_fee(BaseFeeParams::ethereum()) + .unwrap_or_default(); + let mut futs = FuturesUnordered::default(); + let mut next_nonce = nonce; + for transfer in transfers { + // Prepare transaction + let max_fee_per_gas = base_fee as u128 + max_priority_fee_per_gas; + let transaction = EthereumTypedTransaction::::Eip1559(TxEip1559 { + chain_id: 1, + nonce: next_nonce, + to: TxKind::Call(transfer.destination), + value: U256::from(transfer.amount), + gas_limit: 21_000, + max_fee_per_gas, + max_priority_fee_per_gas, + access_list: Default::default(), + input: Bytes::default(), + }); + let signature = signer.sign_hash_sync(&transaction.signature_hash())?; + let signed = EthereumTxEnvelope::::new_unhashed(transaction, signature); + + // Send the request to the builder + let client = client.clone(); + let tx_hash = *signed.hash(); + info!(target: "rebalancer", head_number = head.number, %head.hash, %signer_address, rule = %transfer.description, "Sending system transaction"); + futs.push( + async move { + let encoded = hex::encode_prefixed(signed.encoded_2718()); + let request = json!({ + "id": 1, + "jsonrpc": "2.0", + "method": "eth_sendRawTransaction", + "params": [encoded], + }); + let response = client.post(builder_url).json(&request).send().await?; + Ok::<_, eyre::Error>(response) + } + .map(move |result| (tx_hash, transfer, result)), + ); + + next_nonce += 1; + } + + while let Some((tx_hash, transfer, result)) = futs.next().await { + match result { + Ok(response) => { + let success = response.status().is_success(); + let text = response.text().await.ok(); + info!(target: "rebalancer", head_number = head.number, %head.hash, %signer_address, %tx_hash, rule = %transfer.description, success, response = ?text, "System transaction submitted"); + } + Err(error) => { + error!(target: "rebalancer", head_number = head.number, %head.hash, %signer_address, %tx_hash, rule = %transfer.description, ?error, "Error submitting system transaction") + } + } + } + + Ok(()) +} + +#[derive(Debug)] +struct Transfer { + destination: Address, + amount: U256, + description: String, +} diff --git a/crates/rbuilder/src/utils/tx_signer.rs b/crates/rbuilder/src/utils/tx_signer.rs index 1ef3210ef..891ca5544 100644 --- a/crates/rbuilder/src/utils/tx_signer.rs +++ b/crates/rbuilder/src/utils/tx_signer.rs @@ -53,6 +53,7 @@ mod test { use alloy_consensus::TxEip1559; use alloy_primitives::{address, fixed_bytes, TxKind as TransactionKind}; use reth_primitives_traits::SignerRecoverable; + #[test] fn test_sign_transaction() { let secret = @@ -64,8 +65,8 @@ mod test { let tx = Transaction::Eip1559(TxEip1559 { chain_id: 1, nonce: 2, - gas_limit: 21000, - max_fee_per_gas: 1000, + gas_limit: 21_000, + max_fee_per_gas: 0, max_priority_fee_per_gas: 20000, to: TransactionKind::Call(address), value: U256::from(3000u128),