diff --git a/cmd/ethrex_l2/src/commands/test.rs b/cmd/ethrex_l2/src/commands/test.rs index ae8e90ef0..5ac6d8c66 100644 --- a/cmd/ethrex_l2/src/commands/test.rs +++ b/cmd/ethrex_l2/src/commands/test.rs @@ -89,6 +89,7 @@ async fn transfer_from( let tx = client .build_eip1559_transaction( to_address, + address, Bytes::new(), Overrides { chain_id: Some(cfg.network.l2_chain_id), @@ -99,14 +100,12 @@ async fn transfer_from( gas_limit: Some(TX_GAS_COST), ..Default::default() }, + 10, ) .await .unwrap(); - while let Err(e) = client - .send_eip1559_transaction(tx.clone(), &private_key) - .await - { + while let Err(e) = client.send_eip1559_transaction(&tx, &private_key).await { println!("Transaction failed (PK: {pk} - Nonce: {}): {e}", tx.nonce); retries += 1; sleep(std::time::Duration::from_secs(2)); diff --git a/cmd/ethrex_l2/src/commands/wallet.rs b/cmd/ethrex_l2/src/commands/wallet.rs index fdd6f0c6d..e9ea8de70 100644 --- a/cmd/ethrex_l2/src/commands/wallet.rs +++ b/cmd/ethrex_l2/src/commands/wallet.rs @@ -341,16 +341,18 @@ impl Command { let tx = eth_client .build_eip1559_transaction( cfg.contracts.common_bridge, + cfg.wallet.address, claim_withdrawal_data.into(), Overrides { chain_id: Some(cfg.network.l1_chain_id), from: Some(cfg.wallet.address), ..Default::default() }, + 10, ) .await?; let tx_hash = eth_client - .send_eip1559_transaction(tx, &cfg.wallet.private_key) + .send_eip1559_transaction(&tx, &cfg.wallet.private_key) .await?; println!("Withdrawal claim sent: {tx_hash:#x}"); @@ -377,6 +379,7 @@ impl Command { let transfer_tx = client .build_eip1559_transaction( to, + cfg.wallet.address, Bytes::new(), Overrides { value: Some(amount), @@ -390,11 +393,12 @@ impl Command { gas_limit: Some(21000 * 100), ..Default::default() }, + 10, ) .await?; let tx_hash = client - .send_eip1559_transaction(transfer_tx, &cfg.wallet.private_key) + .send_eip1559_transaction(&transfer_tx, &cfg.wallet.private_key) .await?; println!( @@ -418,6 +422,7 @@ impl Command { .build_privileged_transaction( PrivilegedTxType::Withdrawal, to.unwrap_or(cfg.wallet.address), + to.unwrap_or(cfg.wallet.address), Bytes::new(), Overrides { nonce, @@ -427,11 +432,12 @@ impl Command { gas_price: Some(800000000), ..Default::default() }, + 10, ) .await?; let tx_hash = rollup_client - .send_privileged_l2_transaction(withdraw_transaction, &cfg.wallet.private_key) + .send_privileged_l2_transaction(&withdraw_transaction, &cfg.wallet.private_key) .await?; println!("Withdrawal sent: {tx_hash:#x}"); @@ -470,6 +476,7 @@ impl Command { let tx = client .build_eip1559_transaction( to, + cfg.wallet.address, calldata, Overrides { value: Some(value), @@ -487,10 +494,11 @@ impl Command { from: Some(cfg.wallet.address), ..Default::default() }, + 10, ) .await?; let tx_hash = client - .send_eip1559_transaction(tx, &cfg.wallet.private_key) + .send_eip1559_transaction(&tx, &cfg.wallet.private_key) .await?; println!( diff --git a/crates/l2/Makefile b/crates/l2/Makefile index 54f011bd1..665d13392 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -98,7 +98,7 @@ rm_dev_libmdbx_l2: ## 🛑 Removes the Libmdbx DB used by the L2 # CI Testing -test: +test: ## 🚧 Runs the L2's integration test docker compose -f ${ethrex_L2_DOCKER_COMPOSE_PATH} down docker compose -f ${ethrex_L2_DOCKER_COMPOSE_PATH} up -d --build BRIDGE_ADDRESS=$$(grep 'L1_WATCHER_BRIDGE_ADDRESS' .env | cut -d= -f2) ON_CHAIN_PROPOSER_ADDRESS=$$(grep 'COMMITTER_ON_CHAIN_PROPOSER_ADDRESS' .env | cut -d= -f2) cargo test --release testito -- --nocapture diff --git a/crates/l2/contracts/deployer.rs b/crates/l2/contracts/deployer.rs index 39d464225..775419f25 100644 --- a/crates/l2/contracts/deployer.rs +++ b/crates/l2/contracts/deployer.rs @@ -1,10 +1,7 @@ use bytes::Bytes; use colored::Colorize; use ethereum_types::{Address, H160, H256}; -use ethrex_core::{ - types::{GAS_LIMIT_ADJUSTMENT_FACTOR, GAS_LIMIT_MINIMUM}, - U256, -}; +use ethrex_core::U256; use ethrex_l2::utils::{ config::{read_env_as_lines, read_env_file, write_env}, eth_client::{eth_sender::Overrides, EthClient}, @@ -220,18 +217,6 @@ async fn deploy_contracts( eth_client: &EthClient, contracts_path: &Path, ) -> (Address, Address) { - let gas_price = if eth_client.url.contains("localhost:8545") { - Some(1_000_000_000) - } else { - Some(eth_client.get_gas_price().await.unwrap().as_u64() * 2) - }; - - let overrides = Overrides { - gas_limit: Some(GAS_LIMIT_MINIMUM * GAS_LIMIT_ADJUSTMENT_FACTOR), - gas_price, - ..Default::default() - }; - let deploy_frames = spinner!(["📭❱❱", "❱📬❱", "❱❱📫"], 220); let mut spinner = Spinner::new( @@ -241,14 +226,7 @@ async fn deploy_contracts( ); let (on_chain_proposer_deployment_tx_hash, on_chain_proposer_address) = - deploy_on_chain_proposer( - deployer, - deployer_private_key, - overrides.clone(), - eth_client, - contracts_path, - ) - .await; + deploy_on_chain_proposer(deployer, deployer_private_key, eth_client, contracts_path).await; let msg = format!( "OnChainProposer:\n\tDeployed at address {} with tx hash {}", @@ -258,14 +236,8 @@ async fn deploy_contracts( spinner.success(&msg); let mut spinner = Spinner::new(deploy_frames, "Deploying CommonBridge", Color::Cyan); - let (bridge_deployment_tx_hash, bridge_address) = deploy_bridge( - deployer, - deployer_private_key, - overrides, - eth_client, - contracts_path, - ) - .await; + let (bridge_deployment_tx_hash, bridge_address) = + deploy_bridge(deployer, deployer_private_key, eth_client, contracts_path).await; let msg = format!( "CommonBridge:\n\tDeployed at address {} with tx hash {}", @@ -280,7 +252,6 @@ async fn deploy_contracts( async fn deploy_on_chain_proposer( deployer: Address, deployer_private_key: SecretKey, - overrides: Overrides, eth_client: &EthClient, contracts_path: &Path, ) -> (H256, Address) { @@ -295,7 +266,6 @@ async fn deploy_on_chain_proposer( deployer, deployer_private_key, &on_chain_proposer_init_code, - overrides, eth_client, ) .await; @@ -306,7 +276,6 @@ async fn deploy_on_chain_proposer( async fn deploy_bridge( deployer: Address, deployer_private_key: SecretKey, - overrides: Overrides, eth_client: &EthClient, contracts_path: &Path, ) -> (H256, Address) { @@ -329,7 +298,6 @@ async fn deploy_bridge( deployer, deployer_private_key, &bridge_init_code.into(), - overrides, eth_client, ) .await; @@ -341,24 +309,22 @@ async fn create2_deploy( deployer: Address, deployer_private_key: SecretKey, init_code: &Bytes, - overrides: Overrides, eth_client: &EthClient, ) -> (H256, Address) { let calldata = [SALT.lock().unwrap().as_bytes(), init_code].concat(); let deploy_tx = eth_client .build_eip1559_transaction( DETERMINISTIC_CREATE2_ADDRESS, + deployer, calldata.into(), - Overrides { - from: Some(deployer), - ..overrides - }, + Overrides::default(), + 10, ) .await .expect("Failed to build create2 deploy tx"); let deploy_tx_hash = eth_client - .send_eip1559_transaction(deploy_tx, &deployer_private_key) + .send_eip1559_transaction(&deploy_tx, &deployer_private_key) .await .expect("Failed to send create2 deploy tx"); @@ -493,16 +459,15 @@ async fn initialize_on_chain_proposer( let initialize_tx = eth_client .build_eip1559_transaction( on_chain_proposer, + deployer, on_chain_proposer_initialization_calldata.into(), - Overrides { - from: Some(deployer), - ..Default::default() - }, + Overrides::default(), + 10, ) .await .expect("Failed to build initialize transaction"); let initialize_tx_hash = eth_client - .send_eip1559_transaction(initialize_tx, &deployer_private_key) + .send_eip1559_transaction(&initialize_tx, &deployer_private_key) .await .expect("Failed to send initialize transaction"); @@ -537,16 +502,15 @@ async fn initialize_bridge( let initialize_tx = eth_client .build_eip1559_transaction( bridge, + deployer, bridge_initialization_calldata.into(), - Overrides { - from: Some(deployer), - ..Default::default() - }, + Overrides::default(), + 10, ) .await .expect("Failed to build initialize transaction"); let initialize_tx_hash = eth_client - .send_eip1559_transaction(initialize_tx, &deployer_private_key) + .send_eip1559_transaction(&initialize_tx, &deployer_private_key) .await .expect("Failed to send initialize transaction"); diff --git a/crates/l2/contracts/src/l1/OnChainProposer.sol b/crates/l2/contracts/src/l1/OnChainProposer.sol index 93643dda6..f4f204072 100644 --- a/crates/l2/contracts/src/l1/OnChainProposer.sol +++ b/crates/l2/contracts/src/l1/OnChainProposer.sol @@ -38,7 +38,8 @@ contract OnChainProposer is IOnChainProposer, ReentrancyGuard { uint256 public lastCommittedBlock; /// @dev The sequencer addresses that are authorized to commit and verify blocks. - mapping(address _authorizedAddress => bool) public authorizedSequencerAddresses; + mapping(address _authorizedAddress => bool) + public authorizedSequencerAddresses; address public BRIDGE; address public R0VERIFIER; @@ -138,21 +139,27 @@ contract OnChainProposer is IOnChainProposer, ReentrancyGuard { } /// @inheritdoc IOnChainProposer + /// @notice The first `require` checks that the block number is the subsequent block. + /// @notice The second `require` checks if the block has been committed. + /// @notice The order of these `require` statements is important. + /// Ordering Reason: After the verification process, we delete the `blockCommitments` for `blockNumber - 1`. This means that when checking the block, + /// we might get an error indicating that the block hasn’t been committed, even though it was committed but deleted. Therefore, it has already been verified. function verify( uint256 blockNumber, bytes calldata blockProof, bytes32 imageId, bytes32 journalDigest ) external override onlySequencer { - require( - blockCommitments[blockNumber].commitmentHash != bytes32(0), - "OnChainProposer: block not committed" - ); require( blockNumber == lastVerifiedBlock + 1, "OnChainProposer: block already verified" ); + require( + blockCommitments[blockNumber].commitmentHash != bytes32(0), + "OnChainProposer: block not committed" + ); + if (R0VERIFIER != DEV_MODE) { // If the verification fails, it will revert. IRiscZeroVerifier(R0VERIFIER).verify( diff --git a/crates/l2/proposer/errors.rs b/crates/l2/proposer/errors.rs index bf469f701..e8d3e6dcb 100644 --- a/crates/l2/proposer/errors.rs +++ b/crates/l2/proposer/errors.rs @@ -1,9 +1,12 @@ +use std::sync::mpsc::SendError; + use crate::utils::{config::errors::ConfigError, eth_client::errors::EthClientError}; use ethereum_types::FromStrRadixErr; use ethrex_core::types::BlobsBundleError; use ethrex_dev::utils::engine_client::errors::EngineClientError; use ethrex_storage::error::StoreError; use ethrex_vm::EvmError; +use tokio::task::JoinError; #[derive(Debug, thiserror::Error)] pub enum L1WatcherError { @@ -29,6 +32,28 @@ pub enum ProverServerError { EthClientError(#[from] EthClientError), #[error("ProverServer failed to send transaction: {0}")] FailedToVerifyProofOnChain(String), + #[error("ProverServer failed retrieve block from storage: {0}")] + FailedToRetrieveBlockFromStorage(#[from] StoreError), + #[error("ProverServer failed retrieve block from storaga, data is None.")] + StorageDataIsNone, + #[error("ProverServer failed to create ProverInputs: {0}")] + FailedToCreateProverInputs(#[from] EvmError), + #[error("ProverServer SigIntError: {0}")] + SigIntError(#[from] SigIntError), + #[error("ProverServer JoinError: {0}")] + JoinError(#[from] JoinError), + #[error("ProverServer failed: {0}")] + Custom(String), +} + +#[derive(Debug, thiserror::Error)] +pub enum SigIntError { + #[error("SigInt sigint.recv() failed")] + Recv, + #[error("SigInt tx.send(()) failed: {0}")] + Send(#[from] SendError<()>), + #[error("SigInt shutdown(Shutdown::Both) failed: {0}")] + Shutdown(#[from] std::io::Error), } #[derive(Debug, thiserror::Error)] @@ -39,6 +64,10 @@ pub enum ProposerError { FailedToProduceBlock(String), #[error("Proposer failed to prepare PayloadAttributes timestamp: {0}")] FailedToGetSystemTime(#[from] std::time::SystemTimeError), + #[error("Proposer failed retrieve block from storage: {0}")] + FailedToRetrieveBlockFromStorage(#[from] StoreError), + #[error("Proposer failed retrieve block from storaga, data is None.")] + StorageDataIsNone, } #[derive(Debug, thiserror::Error)] @@ -61,6 +90,8 @@ pub enum CommitterError { FailedToReExecuteBlock(#[from] EvmError), #[error("Committer failed to send transaction: {0}")] FailedToSendCommitment(String), + #[error("Withdrawal transaction was invalid")] + InvalidWithdrawalTransaction, } #[derive(Debug, thiserror::Error)] diff --git a/crates/l2/proposer/l1_committer.rs b/crates/l2/proposer/l1_committer.rs index 384cecda9..bd792f250 100644 --- a/crates/l2/proposer/l1_committer.rs +++ b/crates/l2/proposer/l1_committer.rs @@ -5,28 +5,25 @@ use crate::{ }, utils::{ config::{committer::CommitterConfig, eth::EthConfig}, - eth_client::{errors::EthClientError, eth_sender::Overrides, EthClient}, + eth_client::{eth_sender::Overrides, EthClient, WrappedTransaction}, merkle_tree::merkelize, }, }; use bytes::Bytes; -use ethrex_blockchain::constants::TX_GAS_COST; use ethrex_core::{ types::{ - blobs_bundle, BlobsBundle, Block, EIP1559Transaction, GenericTransaction, - PrivilegedL2Transaction, PrivilegedTxType, Transaction, TxKind, + blobs_bundle, BlobsBundle, Block, PrivilegedL2Transaction, PrivilegedTxType, Transaction, + TxKind, }, Address, H256, U256, }; -use ethrex_rpc::types::transaction::WrappedEIP4844Transaction; use ethrex_storage::Store; use ethrex_vm::{evm_state, execute_block, get_state_transitions}; use keccak_hash::keccak; use secp256k1::SecretKey; -use std::ops::Div; use std::{collections::HashMap, time::Duration}; use tokio::time::sleep; -use tracing::{error, info, warn}; +use tracing::{error, info}; const COMMIT_FUNCTION_SELECTOR: [u8; 4] = [132, 97, 12, 179]; @@ -43,7 +40,7 @@ pub async fn start_l1_commiter(store: Store) { let eth_config = EthConfig::from_env().expect("EthConfig::from_env()"); let committer_config = CommitterConfig::from_env().expect("CommitterConfig::from_env"); let committer = Committer::new_from_config(&committer_config, eth_config, store); - committer.start().await.expect("committer.start()"); + committer.run().await; } impl Committer { @@ -62,87 +59,93 @@ impl Committer { } } - pub async fn start(&self) -> Result<(), CommitterError> { + pub async fn run(&self) { loop { - let last_committed_block = EthClient::get_last_committed_block( - &self.eth_client, - self.on_chain_proposer_address, - ) - .await?; + if let Err(err) = self.main_logic().await { + error!("L1 Committer Error: {}", err); + } - let block_number_to_fetch = if last_committed_block == u64::MAX { - 0 - } else { - last_committed_block + 1 - }; + sleep(Duration::from_millis(self.interval_ms)).await; + } + } - if let Some(block_to_commit_body) = self + async fn main_logic(&self) -> Result<(), CommitterError> { + let last_committed_block = + EthClient::get_last_committed_block(&self.eth_client, self.on_chain_proposer_address) + .await?; + + let block_number_to_fetch = if last_committed_block == u64::MAX { + 0 + } else { + last_committed_block + 1 + }; + + if let Some(block_to_commit_body) = self + .store + .get_block_body(block_number_to_fetch) + .map_err(CommitterError::from)? + { + let block_to_commit_header = self .store - .get_block_body(block_number_to_fetch) + .get_block_header(block_number_to_fetch) .map_err(CommitterError::from)? + .ok_or(CommitterError::FailedToGetInformationFromStorage( + "Failed to get_block_header() after get_block_body()".to_owned(), + ))?; + + let block_to_commit = Block::new(block_to_commit_header, block_to_commit_body); + + let withdrawals = self.get_block_withdrawals(&block_to_commit)?; + let deposits = self.get_block_deposits(&block_to_commit); + + let mut withdrawal_hashes = vec![]; + + for (_, tx) in &withdrawals { + let hash = tx + .get_withdrawal_hash() + .ok_or(CommitterError::InvalidWithdrawalTransaction)?; + withdrawal_hashes.push(hash); + } + + let withdrawal_logs_merkle_root = self.get_withdrawals_merkle_root(withdrawal_hashes); + let deposit_logs_hash = self.get_deposit_hash( + deposits + .iter() + .filter_map(|tx| tx.get_deposit_hash()) + .collect(), + ); + + let state_diff = self.prepare_state_diff( + &block_to_commit, + self.store.clone(), + withdrawals, + deposits, + )?; + + let blobs_bundle = self.generate_blobs_bundle(state_diff.clone())?; + + let head_block_hash = block_to_commit.hash(); + match self + .send_commitment( + block_to_commit.header.number, + withdrawal_logs_merkle_root, + deposit_logs_hash, + blobs_bundle, + ) + .await { - let block_to_commit_header = self - .store - .get_block_header(block_number_to_fetch) - .map_err(CommitterError::from)? - .ok_or(CommitterError::FailedToGetInformationFromStorage( - "Failed to get_block_header() after get_block_body()".to_owned(), - ))?; - - let block_to_commit = Block::new(block_to_commit_header, block_to_commit_body); - - let withdrawals = self.get_block_withdrawals(&block_to_commit)?; - let deposits = self.get_block_deposits(&block_to_commit)?; - - let withdrawal_logs_merkle_root = self.get_withdrawals_merkle_root( - withdrawals - .iter() - .map(|(_hash, tx)| { - tx.get_withdrawal_hash() - .expect("Not a withdrawal transaction") - }) - .collect(), - ); - let deposit_logs_hash = self.get_deposit_hash( - deposits - .iter() - .filter_map(|tx| tx.get_deposit_hash()) - .collect(), - ); - - let state_diff = self.prepare_state_diff( - &block_to_commit, - self.store.clone(), - withdrawals, - deposits, - )?; - - let blobs_bundle = self.generate_blobs_bundle(state_diff.clone())?; - - let head_block_hash = block_to_commit.hash(); - match self - .send_commitment( - block_to_commit.header.number, - withdrawal_logs_merkle_root, - deposit_logs_hash, - blobs_bundle, - ) - .await - { - Ok(commit_tx_hash) => { - info!( - "Sent commitment to block {head_block_hash:#x}, with transaction hash {commit_tx_hash:#x}" - ); - } - Err(error) => { - error!("Failed to send commitment to block {head_block_hash:#x}. Manual intervention required: {error}"); - panic!("Failed to send commitment to block {head_block_hash:#x}. Manual intervention required: {error}"); - } + Ok(commit_tx_hash) => { + info!("Sent commitment to block {head_block_hash:#x}, with transaction hash {commit_tx_hash:#x}"); + } + Err(error) => { + return Err(CommitterError::FailedToSendCommitment(format!( + "Failed to send commitment to block {head_block_hash:#x}: {error}" + ))); } } - - sleep(Duration::from_millis(self.interval_ms)).await; } + + Ok(()) } pub fn get_block_withdrawals( @@ -174,10 +177,7 @@ impl Committer { } } - pub fn get_block_deposits( - &self, - block: &Block, - ) -> Result, CommitterError> { + pub fn get_block_deposits(&self, block: &Block) -> Vec { let deposits = block .body .transactions @@ -192,7 +192,7 @@ impl Committer { }) .collect(); - Ok(deposits) + deposits } pub fn get_deposit_hash(&self, deposit_hashes: Vec) -> H256 { @@ -314,6 +314,7 @@ impl Committer { .eth_client .build_eip4844_transaction( self.on_chain_proposer_address, + self.l1_address, Bytes::from(calldata), Overrides { from: Some(self.l1_address), @@ -321,117 +322,24 @@ impl Committer { ..Default::default() }, blobs_bundle, + 10, ) .await .map_err(CommitterError::from)?; let commit_tx_hash = self .eth_client - .send_eip4844_transaction(wrapped_tx.clone(), &self.l1_private_key) + .send_wrapped_transaction_with_retry( + &WrappedTransaction::EIP4844(wrapped_tx), + &self.l1_private_key, + 3 * 60, // 3 minutes + 10, // 180[secs]/20[retries] -> 18 seconds per retry + ) .await .map_err(CommitterError::from)?; - let commit_tx_hash = wrapped_eip4844_transaction_handler( - &self.eth_client, - &wrapped_tx, - &self.l1_private_key, - commit_tx_hash, - 10, - ) - .await?; - info!("Commitment sent: {commit_tx_hash:#x}"); Ok(commit_tx_hash) } } - -pub async fn send_transaction_with_calldata( - eth_client: &EthClient, - l1_address: Address, - l1_private_key: SecretKey, - to: Address, - nonce: Option, - calldata: Bytes, -) -> Result { - let mut tx = EIP1559Transaction { - to: TxKind::Call(to), - data: calldata, - max_fee_per_gas: eth_client.get_gas_price().await?.as_u64() * 2, - nonce: nonce.unwrap_or(eth_client.get_nonce(l1_address).await?), - chain_id: eth_client.get_chain_id().await?.as_u64(), - // Should the max_priority_fee_per_gas be dynamic? - max_priority_fee_per_gas: 10u64, - ..Default::default() - }; - - let mut generic_tx = GenericTransaction::from(tx.clone()); - generic_tx.from = l1_address; - - tx.gas_limit = eth_client - .estimate_gas(generic_tx) - .await? - .saturating_add(TX_GAS_COST); - - eth_client - .send_eip1559_transaction(tx, &l1_private_key) - .await -} - -async fn wrapped_eip4844_transaction_handler( - eth_client: &EthClient, - wrapped_eip4844: &WrappedEIP4844Transaction, - l1_private_key: &SecretKey, - commit_tx_hash: H256, - max_retries: u32, -) -> Result { - let mut retries = 0; - let max_receipt_retries: u32 = 60 * 2; // 2 minutes - let mut commit_tx_hash = commit_tx_hash; - let mut wrapped_tx = wrapped_eip4844.clone(); - - while retries < max_retries { - if (eth_client.get_transaction_receipt(commit_tx_hash).await?).is_some() { - // If the tx_receipt was found, return the tx_hash. - return Ok(commit_tx_hash); - } else { - // Else, wait for receipt and send again if necessary. - let mut receipt_retries = 0; - - // Try for 2 minutes with an interval of 1 second to get the tx_receipt. - while receipt_retries < max_receipt_retries { - match eth_client.get_transaction_receipt(commit_tx_hash).await? { - Some(_) => return Ok(commit_tx_hash), - None => { - receipt_retries += 1; - sleep(Duration::from_secs(1)).await; - } - } - } - - // If receipt was not found, send the same tx(same nonce) but with more gas. - // Sometimes the penalty is a 100% - warn!("Transaction not confirmed, resending with 110% more gas..."); - // Increase max fee per gas by 110% (set it to 210% of the original) - wrapped_tx.tx.max_fee_per_gas = - (wrapped_tx.tx.max_fee_per_gas as f64 * 2.1).round() as u64; - wrapped_tx.tx.max_priority_fee_per_gas = - (wrapped_tx.tx.max_priority_fee_per_gas as f64 * 2.1).round() as u64; - wrapped_tx.tx.max_fee_per_blob_gas = wrapped_tx - .tx - .max_fee_per_blob_gas - .saturating_mul(U256::from(20)) - .div(10); - - commit_tx_hash = eth_client - .send_eip4844_transaction(wrapped_tx.clone(), l1_private_key) - .await - .map_err(CommitterError::from)?; - - retries += 1; - } - } - Err(CommitterError::FailedToSendCommitment( - "Error handling eip4844".to_owned(), - )) -} diff --git a/crates/l2/proposer/l1_watcher.rs b/crates/l2/proposer/l1_watcher.rs index 73d7290e8..1c86026c6 100644 --- a/crates/l2/proposer/l1_watcher.rs +++ b/crates/l2/proposer/l1_watcher.rs @@ -16,39 +16,13 @@ use keccak_hash::keccak; use secp256k1::SecretKey; use std::{cmp::min, ops::Mul, time::Duration}; use tokio::time::sleep; -use tracing::{debug, info, warn}; +use tracing::{debug, error, info, warn}; pub async fn start_l1_watcher(store: Store) { let eth_config = EthConfig::from_env().expect("EthConfig::from_env()"); let watcher_config = L1WatcherConfig::from_env().expect("L1WatcherConfig::from_env()"); - let sleep_duration = Duration::from_millis(watcher_config.check_interval_ms); let mut l1_watcher = L1Watcher::new_from_config(watcher_config, eth_config); - loop { - sleep(sleep_duration).await; - - let logs = match l1_watcher.get_logs().await { - Ok(logs) => logs, - Err(error) => { - warn!("Error when getting logs from L1: {}", error); - continue; - } - }; - if logs.is_empty() { - continue; - } - - let pending_deposits_logs = match l1_watcher.get_pending_deposit_logs().await { - Ok(logs) => logs, - Err(error) => { - warn!("Error when getting L1 pending deposit logs: {}", error); - continue; - } - }; - let _deposit_txs = l1_watcher - .process_logs(logs, &pending_deposits_logs, &store) - .await - .expect("l1_watcher.process_logs()"); - } + l1_watcher.run(&store).await; } pub struct L1Watcher { @@ -58,6 +32,7 @@ pub struct L1Watcher { max_block_step: U256, last_block_fetched: U256, l2_proposer_pk: SecretKey, + check_interval: Duration, } impl L1Watcher { @@ -69,6 +44,35 @@ impl L1Watcher { max_block_step: watcher_config.max_block_step, last_block_fetched: U256::zero(), l2_proposer_pk: watcher_config.l2_proposer_private_key, + check_interval: Duration::from_millis(watcher_config.check_interval_ms), + } + } + + pub async fn run(&mut self, store: &Store) { + loop { + if let Err(err) = self.main_logic(store.clone()).await { + error!("L1 Watcher Error: {}", err); + } + + sleep(self.check_interval).await; + } + } + + async fn main_logic(&mut self, store: Store) -> Result<(), L1WatcherError> { + loop { + sleep(self.check_interval).await; + + let logs = self.get_logs().await?; + + // We may not have a deposit nor a withdrawal, that means no events -> no logs. + if logs.is_empty() { + continue; + } + + let pending_deposits_logs = self.get_pending_deposit_logs().await?; + let _deposit_txs = self + .process_logs(logs, &pending_deposits_logs, &store) + .await?; } } @@ -182,6 +186,7 @@ impl L1Watcher { .build_privileged_transaction( PrivilegedTxType::Deposit, beneficiary, + beneficiary, Bytes::new(), Overrides { chain_id: Some( @@ -201,6 +206,7 @@ impl L1Watcher { gas_limit: Some(TX_GAS_COST.mul(2)), ..Default::default() }, + 10, ) .await?; mint_transaction.sign_inplace(&self.l2_proposer_pk); diff --git a/crates/l2/proposer/mod.rs b/crates/l2/proposer/mod.rs index 5c6c435c9..c37b1298e 100644 --- a/crates/l2/proposer/mod.rs +++ b/crates/l2/proposer/mod.rs @@ -1,9 +1,12 @@ +use std::time::Duration; + use crate::utils::config::{proposer::ProposerConfig, read_env_file}; use errors::ProposerError; -use ethereum_types::{Address, H256}; -use ethrex_dev::utils::engine_client::{config::EngineApiConfig, errors::EngineClientError}; +use ethereum_types::Address; +use ethrex_dev::utils::engine_client::config::EngineApiConfig; use ethrex_storage::Store; -use tracing::{info, warn}; +use tokio::time::sleep; +use tracing::{error, info}; pub mod l1_committer; pub mod l1_watcher; @@ -22,7 +25,7 @@ pub async fn start_proposer(store: Store) { info!("Starting Proposer"); if let Err(e) = read_env_file() { - warn!("Failed to read .env file: {e}"); + panic!("Failed to read .env file: {e}"); } let l1_watcher = tokio::spawn(l1_watcher::start_l1_watcher(store.clone())); @@ -33,20 +36,8 @@ pub async fn start_proposer(store: Store) { let engine_config = EngineApiConfig::from_env().expect("EngineApiConfig::from_env"); let proposer = Proposer::new_from_config(&proposer_config, engine_config) .expect("Proposer::new_from_config"); - let head_block_hash = { - let current_block_number = store - .get_latest_block_number() - .expect("store.get_latest_block_number") - .expect("store.get_latest_block_number returned None"); - store - .get_canonical_block_hash(current_block_number) - .expect("store.get_canonical_block_hash") - .expect("store.get_canonical_block_hash returned None") - }; - proposer - .start(head_block_hash) - .await - .expect("Proposer::start"); + + proposer.run(store.clone()).await; }); tokio::try_join!(l1_watcher, l1_committer, prover_server, proposer).expect("tokio::try_join"); } @@ -63,7 +54,26 @@ impl Proposer { }) } - pub async fn start(&self, head_block_hash: H256) -> Result<(), ProposerError> { + pub async fn run(&self, store: Store) { + loop { + if let Err(err) = self.main_logic(store.clone()).await { + error!("Block Producer Error: {}", err); + } + + sleep(Duration::from_millis(200)).await; + } + } + + pub async fn main_logic(&self, store: Store) -> Result<(), ProposerError> { + let head_block_hash = { + let current_block_number = store + .get_latest_block_number()? + .ok_or(ProposerError::StorageDataIsNone)?; + store + .get_canonical_block_hash(current_block_number)? + .ok_or(ProposerError::StorageDataIsNone)? + }; + ethrex_dev::block_producer::start_block_producer( self.engine_config.rpc_url.clone(), std::fs::read(&self.engine_config.jwt_path).unwrap().into(), @@ -72,7 +82,8 @@ impl Proposer { self.block_production_interval, self.coinbase_address, ) - .await - .map_err(EngineClientError::into) + .await?; + + Ok(()) } } diff --git a/crates/l2/proposer/prover_server.rs b/crates/l2/proposer/prover_server.rs index 7ab1c0253..0846e2c29 100644 --- a/crates/l2/proposer/prover_server.rs +++ b/crates/l2/proposer/prover_server.rs @@ -1,5 +1,14 @@ +use super::errors::{ProverServerError, SigIntError}; +use crate::utils::{ + config::{committer::CommitterConfig, eth::EthConfig, prover_server::ProverServerConfig}, + eth_client::{errors::EthClientError, eth_sender::Overrides, EthClient, WrappedTransaction}, +}; +use ethrex_core::{ + types::{Block, BlockHeader}, + Address, H256, +}; use ethrex_storage::Store; -use ethrex_vm::execution_db::ExecutionDB; +use ethrex_vm::{execution_db::ExecutionDB, EvmError}; use keccak_hash::keccak; use secp256k1::SecretKey; use serde::{Deserialize, Serialize}; @@ -16,11 +25,6 @@ use tokio::{ }; use tracing::{debug, error, info, warn}; -use ethrex_core::{ - types::{Block, BlockHeader, EIP1559Transaction}, - Address, H256, -}; - use risc0_zkvm::sha::{Digest, Digestible}; #[derive(Debug, Serialize, Deserialize, Default)] @@ -30,126 +34,15 @@ pub struct ProverInputData { pub db: ExecutionDB, } -use crate::utils::{ - config::{committer::CommitterConfig, eth::EthConfig, prover_server::ProverServerConfig}, - eth_client::{errors::EthClientError, eth_sender::Overrides, EthClient}, -}; - -use super::errors::ProverServerError; - -pub async fn start_prover_server(store: Store) { - let server_config = ProverServerConfig::from_env().expect("ProverServerConfig::from_env()"); - let eth_config = EthConfig::from_env().expect("EthConfig::from_env()"); - let proposer_config = CommitterConfig::from_env().expect("CommitterConfig::from_env()"); - - if server_config.dev_mode { - let eth_client = EthClient::new_from_config(eth_config); - loop { - thread::sleep(Duration::from_millis(proposer_config.interval_ms)); - - let last_committed_block = EthClient::get_last_committed_block( - ð_client, - proposer_config.on_chain_proposer_address, - ) - .await - .expect("dev_mode::get_last_committed_block()"); - - let last_verified_block = EthClient::get_last_verified_block( - ð_client, - proposer_config.on_chain_proposer_address, - ) - .await - .expect("dev_mode::get_last_verified_block()"); - - if last_committed_block == u64::MAX { - debug!("No blocks commited yet"); - continue; - } - - if last_committed_block == last_verified_block { - debug!("No new blocks to prove"); - continue; - } - - info!("Last committed: {last_committed_block} - Last verified: {last_verified_block}"); - - // IOnChainProposer - // function verify(uint256,bytes,bytes32,bytes32) - // blockNumber, seal, imageId, journalDigest - // From crates/l2/contracts/l1/interfaces/IOnChainProposer.sol - let mut calldata = keccak(b"verify(uint256,bytes,bytes32,bytes32)") - .as_bytes() - .get(..4) - .expect("Failed to get initialize selector") - .to_vec(); - calldata.extend(H256::from_low_u64_be(last_verified_block + 1).as_bytes()); - calldata.extend(H256::from_low_u64_be(128).as_bytes()); - calldata.extend(H256::zero().as_bytes()); - calldata.extend(H256::zero().as_bytes()); - calldata.extend(H256::zero().as_bytes()); - calldata.extend(H256::zero().as_bytes()); - let verify_tx = eth_client - .build_eip1559_transaction( - proposer_config.on_chain_proposer_address, - calldata.into(), - Overrides { - from: Some(server_config.verifier_address), - ..Default::default() - }, - ) - .await - .unwrap(); - - let tx_hash = eth_client - .send_eip1559_transaction(verify_tx, &server_config.verifier_private_key) - .await - .unwrap(); - - info!("Sending verify transaction with tx hash: {tx_hash:#x}"); - - let mut retries = 1; - while eth_client - .get_transaction_receipt(tx_hash) - .await - .unwrap() - .is_none() - { - thread::sleep(Duration::from_secs(1)); - retries += 1; - if retries > 10 { - error!("Couldn't find receipt for transaction {tx_hash:#x}"); - panic!("Couldn't find receipt for transaction {tx_hash:#x}"); - } - } - - info!( - "Mocked verify transaction sent for block {}", - last_verified_block + 1 - ); - } - } else { - let mut prover_server = ProverServer::new_from_config( - server_config.clone(), - &proposer_config, - eth_config, - store, - ) - .await - .expect("ProverServer::new_from_config"); - - let (tx, rx) = mpsc::channel(); - - let server = tokio::spawn(async move { - prover_server - .start(rx) - .await - .expect("prover_server.start()") - }); - - ProverServer::handle_sigint(tx, server_config).await; - - tokio::try_join!(server).expect("tokio::try_join!()"); - } +#[derive(Debug, Clone)] +struct ProverServer { + ip: IpAddr, + port: u16, + store: Store, + eth_client: EthClient, + on_chain_proposer_address: Address, + verifier_address: Address, + verifier_private_key: SecretKey, } /// Enum for the ProverServer <--> ProverClient Communication Protocol. @@ -182,15 +75,15 @@ pub enum ProofData { SubmitAck { block_number: u64 }, } -struct ProverServer { - ip: IpAddr, - port: u16, - store: Store, - eth_client: EthClient, - on_chain_proposer_address: Address, - verifier_address: Address, - verifier_private_key: SecretKey, - last_verified_block: u64, +pub async fn start_prover_server(store: Store) { + let server_config = ProverServerConfig::from_env().expect("ProverServerConfig::from_env()"); + let eth_config = EthConfig::from_env().expect("EthConfig::from_env()"); + let proposer_config = CommitterConfig::from_env().expect("CommitterConfig::from_env()"); + let mut prover_server = + ProverServer::new_from_config(server_config.clone(), &proposer_config, eth_config, store) + .await + .expect("ProverServer::new_from_config"); + prover_server.run(&server_config).await; } impl ProverServer { @@ -203,15 +96,6 @@ impl ProverServer { let eth_client = EthClient::new(ð_config.rpc_url); let on_chain_proposer_address = committer_config.on_chain_proposer_address; - let last_verified_block = - EthClient::get_last_verified_block(ð_client, on_chain_proposer_address).await?; - - let last_verified_block = if last_verified_block == u64::MAX { - 0 - } else { - last_verified_block - }; - Ok(Self { ip: config.listen_ip, port: config.listen_port, @@ -220,32 +104,97 @@ impl ProverServer { on_chain_proposer_address, verifier_address: config.verifier_address, verifier_private_key: config.verifier_private_key, - last_verified_block, }) } - async fn handle_sigint(tx: mpsc::Sender<()>, config: ProverServerConfig) { - let mut sigint = signal(SignalKind::interrupt()).expect("Failed to create SIGINT stream"); - sigint.recv().await.expect("signal.recv()"); - tx.send(()).expect("Failed to send shutdown signal"); - TcpStream::connect(format!("{}:{}", config.listen_ip, config.listen_port)) - .expect("TcpStream::connect()") + pub async fn run(&mut self, server_config: &ProverServerConfig) { + loop { + let result = if server_config.dev_mode { + self.main_logic_dev().await + } else { + self.clone().main_logic(server_config).await + }; + + match result { + Ok(_) => { + if !server_config.dev_mode { + warn!("Prover Server shutting down"); + break; + } + } + Err(e) => { + let error_message = if !server_config.dev_mode { + format!("Prover Server, severe Error, trying to restart the main_logic function: {e}") + } else { + format!("Prover Server Dev Error: {e}") + }; + error!(error_message); + } + } + + sleep(Duration::from_millis(200)).await; + } + } + + async fn main_logic( + mut self, + server_config: &ProverServerConfig, + ) -> Result<(), ProverServerError> { + let (tx, rx) = mpsc::channel(); + + // It should never exit the start() fn, handling errors inside the for loop of the function. + let server_handle = tokio::spawn(async move { self.start(rx).await }); + + ProverServer::handle_sigint(tx, server_config).await?; + + match server_handle.await { + Ok(result) => match result { + Ok(_) => (), + Err(e) => return Err(e), + }, + Err(e) => return Err(e.into()), + }; + + Ok(()) + } + + async fn handle_sigint( + tx: mpsc::Sender<()>, + config: &ProverServerConfig, + ) -> Result<(), ProverServerError> { + let mut sigint = signal(SignalKind::interrupt())?; + sigint.recv().await.ok_or(SigIntError::Recv)?; + tx.send(()).map_err(SigIntError::Send)?; + TcpStream::connect(format!("{}:{}", config.listen_ip, config.listen_port))? .shutdown(Shutdown::Both) - .expect("TcpStream::shutdown()"); + .map_err(SigIntError::Shutdown)?; + + Ok(()) } pub async fn start(&mut self, rx: Receiver<()>) -> Result<(), ProverServerError> { let listener = TcpListener::bind(format!("{}:{}", self.ip, self.port))?; info!("Starting TCP server at {}:{}", self.ip, self.port); + for stream in listener.incoming() { - if let Ok(()) = rx.try_recv() { - info!("Shutting down Prover Server"); - break; - } + match stream { + Ok(stream) => { + debug!("Connection established!"); + + if let Ok(()) = rx.try_recv() { + info!("Shutting down Prover Server"); + break; + } - debug!("Connection established!"); - self.handle_connection(stream?).await?; + if let Err(e) = self.handle_connection(stream).await { + error!("Error handling connection: {}", e); + } + } + Err(e) => { + error!("Failed to accept connection: {}", e); + } + } } Ok(()) } @@ -253,11 +202,21 @@ impl ProverServer { async fn handle_connection(&mut self, mut stream: TcpStream) -> Result<(), ProverServerError> { let buf_reader = BufReader::new(&stream); + let last_verified_block = + EthClient::get_last_verified_block(&self.eth_client, self.on_chain_proposer_address) + .await?; + + let last_verified_block = if last_verified_block == u64::MAX { + 0 + } else { + last_verified_block + }; + let data: Result = serde_json::de::from_reader(buf_reader); match data { Ok(ProofData::Request) => { if let Err(e) = self - .handle_request(&mut stream, self.last_verified_block + 1) + .handle_request(&mut stream, last_verified_block + 1) .await { warn!("Failed to handle request: {e}"); @@ -267,15 +226,13 @@ impl ProverServer { block_number, receipt, }) => { - if let Err(e) = self.handle_submit(&mut stream, block_number) { - error!("Failed to handle submit_ack: {e}"); - panic!("Failed to handle submit_ack: {e}"); - } - // Seems to be stopping the prover_server <--> prover_client + self.handle_submit(&mut stream, block_number)?; + self.handle_proof_submission(block_number, receipt).await?; - assert!(block_number == (self.last_verified_block + 1), "Prover Client submitted an invalid block_number: {block_number}. The last_proved_block is: {}", self.last_verified_block); - self.last_verified_block = block_number; + if block_number != (last_verified_block + 1) { + return Err(ProverServerError::Custom(format!("Prover Client submitted an invalid block_number: {block_number}. The last_proved_block is: {}", last_verified_block))); + } } Err(e) => { warn!("Failed to parse request: {e}"); @@ -293,14 +250,13 @@ impl ProverServer { &self, stream: &mut TcpStream, block_number: u64, - ) -> Result<(), String> { + ) -> Result<(), ProverServerError> { debug!("Request received"); let latest_block_number = self .store - .get_latest_block_number() - .map_err(|e| e.to_string())? - .unwrap(); + .get_latest_block_number()? + .ok_or(ProverServerError::StorageDataIsNone)?; let response = if block_number > latest_block_number { let response = ProofData::Response { @@ -320,15 +276,21 @@ impl ProverServer { }; let writer = BufWriter::new(stream); - serde_json::to_writer(writer, &response).map_err(|e| e.to_string()) + serde_json::to_writer(writer, &response) + .map_err(|e| ProverServerError::ConnectionError(e.into())) } - fn handle_submit(&self, stream: &mut TcpStream, block_number: u64) -> Result<(), String> { + fn handle_submit( + &self, + stream: &mut TcpStream, + block_number: u64, + ) -> Result<(), ProverServerError> { debug!("Submit received for BlockNumber: {block_number}"); let response = ProofData::SubmitAck { block_number }; let writer = BufWriter::new(stream); - serde_json::to_writer(writer, &response).map_err(|e| e.to_string()) + serde_json::to_writer(writer, &response) + .map_err(|e| ProverServerError::ConnectionError(e.into())) } async fn handle_proof_submission( @@ -345,85 +307,55 @@ impl ProverServer { Ok(inner) => { // The SELECTOR is used to perform an extra check inside the groth16 verifier contract. let mut selector = - hex::encode(inner.verifier_parameters.as_bytes().get(..4).unwrap()); + hex::encode(inner.verifier_parameters.as_bytes().get(..4).ok_or( + ProverServerError::Custom( + "Failed to get verify_proof_selector in send_proof()".to_owned(), + ), + )?); let seal = hex::encode(inner.clone().seal); selector.push_str(&seal); - hex::decode(selector).unwrap() + hex::decode(selector).map_err(|e| { + ProverServerError::Custom(format!("Failed to hex::decode(selector): {e}")) + })? } Err(_) => vec![32; 0], }; let mut image_id: [u32; 8] = [0; 8]; for (i, b) in image_id.iter_mut().enumerate() { - *b = *receipt.1.get(i).unwrap(); + *b = *receipt.1.get(i).ok_or(ProverServerError::Custom( + "Failed to get image_id in handle_proof_submission()".to_owned(), + ))?; } let image_id: risc0_zkvm::sha::Digest = image_id.into(); let journal_digest = Digestible::digest(&receipt.0.journal); - // Retry proof verification, the transaction will fail if the block wasn't committed. - // It's being caused by the prover_server advancing faster than the block_generation_time + commitment_tx_approval_time - // The error message is `block not committed`. Retrying 100 times, if there is another error it panics. - let mut attempts = 0; - let max_retries = 100; - let retry_secs = std::time::Duration::from_secs(5); - while attempts < max_retries { - match self - .send_proof(block_number, &seal, image_id, journal_digest) - .await - { - Ok(tx_hash) => { - info!( - "Sent proof for block {block_number}, with transaction hash {tx_hash:#x}" - ); - break; // Exit the while loop - } - - Err(e) => { - warn!("Failed to send proof to block {block_number:#x}. Error: {e}"); - let eth_client_error = format!("{e}"); - if eth_client_error.contains("block not committed") { - attempts += 1; - if attempts < max_retries { - warn!("Retrying... Attempt {}/{}", attempts, max_retries); - sleep(retry_secs).await; // Wait before retrying - } else { - error!("Max retries reached. Giving up on sending proof for block {block_number:#x}."); - panic!("Failed to send proof after {} attempts.", max_retries); - } - } else { - error!("Failed to send proof to block {block_number:#x}. Manual intervention required: {e}"); - panic!("Failed to send proof to block {block_number:#x}. Manual intervention required: {e}"); - } - } - } - } + self.send_proof(block_number, &seal, image_id, journal_digest) + .await?; Ok(()) } - fn create_prover_input(&self, block_number: u64) -> Result { + fn create_prover_input(&self, block_number: u64) -> Result { let header = self .store - .get_block_header(block_number) - .map_err(|err| err.to_string())? - .ok_or("block header not found")?; + .get_block_header(block_number)? + .ok_or(ProverServerError::StorageDataIsNone)?; let body = self .store - .get_block_body(block_number) - .map_err(|err| err.to_string())? - .ok_or("block body not found")?; + .get_block_body(block_number)? + .ok_or(ProverServerError::StorageDataIsNone)?; let block = Block::new(header, body); - let db = ExecutionDB::from_exec(&block, &self.store).map_err(|err| err.to_string())?; + let db = ExecutionDB::from_exec(&block, &self.store).map_err(EvmError::ExecutionDB)?; let parent_block_header = self .store - .get_block_header_by_hash(block.header.parent_hash) - .map_err(|err| err.to_string())? - .ok_or("missing parent header".to_string())?; + .get_block_header_by_hash(block.header.parent_hash)? + .ok_or(ProverServerError::StorageDataIsNone)?; debug!("Created prover input for block {block_number}"); @@ -441,7 +373,7 @@ impl ProverServer { image_id: Digest, journal_digest: Digest, ) -> Result { - info!("Sending proof"); + debug!("Sending proof for {block_number}"); let mut calldata = Vec::new(); // IOnChainProposer @@ -453,7 +385,9 @@ impl ProverServer { let verify_proof_selector = keccak(b"verify(uint256,bytes,bytes32,bytes32)") .as_bytes() .get(..4) - .expect("Failed to get initialize selector") + .ok_or(ProverServerError::Custom( + "Failed to get verify_proof_selector in send_proof()".to_owned(), + ))? .to_vec(); calldata.extend(verify_proof_selector); @@ -490,79 +424,104 @@ impl ProverServer { .eth_client .build_eip1559_transaction( self.on_chain_proposer_address, + self.verifier_address, calldata.into(), - Overrides { - from: Some(self.verifier_address), - ..Default::default() - }, + Overrides::default(), + 10, ) .await?; + let verify_tx_hash = self .eth_client - .send_eip1559_transaction(verify_tx.clone(), &self.verifier_private_key) + .send_wrapped_transaction_with_retry( + &WrappedTransaction::EIP1559(verify_tx), + &self.verifier_private_key, + 3 * 60, + 10, + ) .await?; - eip1559_transaction_handler( - &self.eth_client, - &verify_tx, - &self.verifier_private_key, - verify_tx_hash, - 20, - ) - .await?; + info!("Sent proof for block {block_number}, with transaction hash {verify_tx_hash:#x}"); Ok(verify_tx_hash) } -} -async fn eip1559_transaction_handler( - eth_client: &EthClient, - eip1559: &EIP1559Transaction, - l1_private_key: &SecretKey, - verify_tx_hash: H256, - max_retries: u32, -) -> Result { - let mut retries = 0; - let max_receipt_retries: u32 = 60 * 2; // 2 minutes - let mut verify_tx_hash = verify_tx_hash; - let mut tx = eip1559.clone(); - - while retries < max_retries { - if (eth_client.get_transaction_receipt(verify_tx_hash).await?).is_some() { - // If the tx_receipt was found, return the tx_hash. - return Ok(verify_tx_hash); - } else { - // Else, wait for receipt and send again if necessary. - let mut receipt_retries = 0; - - // Try for 2 minutes with an interval of 1 second to get the tx_receipt. - while receipt_retries < max_receipt_retries { - match eth_client.get_transaction_receipt(verify_tx_hash).await? { - Some(_) => return Ok(verify_tx_hash), - None => { - receipt_retries += 1; - sleep(Duration::from_secs(1)).await; - } - } + pub async fn main_logic_dev(&self) -> Result<(), ProverServerError> { + loop { + thread::sleep(Duration::from_millis(200)); + + let last_committed_block = EthClient::get_last_committed_block( + &self.eth_client, + self.on_chain_proposer_address, + ) + .await?; + + let last_verified_block = EthClient::get_last_verified_block( + &self.eth_client, + self.on_chain_proposer_address, + ) + .await?; + + if last_committed_block == u64::MAX { + debug!("No blocks commited yet"); + continue; } - // If receipt was not found, send the same tx(same nonce) but with more gas. - // Sometimes the penalty is a 100% - warn!("Transaction not confirmed, resending with 110% more gas..."); - // Increase max fee per gas by 110% (set it to 210% of the original) - tx.max_fee_per_gas = (tx.max_fee_per_gas as f64 * 2.1).round() as u64; - tx.max_priority_fee_per_gas += - (tx.max_priority_fee_per_gas as f64 * 2.1).round() as u64; + if last_committed_block == last_verified_block { + debug!("No new blocks to prove"); + continue; + } - verify_tx_hash = eth_client - .send_eip1559_transaction(tx.clone(), l1_private_key) - .await - .map_err(ProverServerError::from)?; + info!("Last committed: {last_committed_block} - Last verified: {last_verified_block}"); - retries += 1; + // IOnChainProposer + // function verify(uint256,bytes,bytes32,bytes32) + // blockNumber, seal, imageId, journalDigest + // From crates/l2/contracts/l1/interfaces/IOnChainProposer.sol + let mut calldata = keccak(b"verify(uint256,bytes,bytes32,bytes32)") + .as_bytes() + .get(..4) + .ok_or(ProverServerError::Custom( + "Failed to get verify_proof_selector in send_proof()".to_owned(), + ))? + .to_vec(); + calldata.extend(H256::from_low_u64_be(last_verified_block + 1).as_bytes()); + calldata.extend(H256::from_low_u64_be(128).as_bytes()); + calldata.extend(H256::zero().as_bytes()); + calldata.extend(H256::zero().as_bytes()); + calldata.extend(H256::zero().as_bytes()); + calldata.extend(H256::zero().as_bytes()); + let verify_tx = self + .eth_client + .build_eip1559_transaction( + self.on_chain_proposer_address, + self.verifier_address, + calldata.into(), + Overrides { + ..Default::default() + }, + 10, + ) + .await?; + + info!("Sending verify transaction."); + + let verify_tx_hash = self + .eth_client + .send_wrapped_transaction_with_retry( + &WrappedTransaction::EIP1559(verify_tx), + &self.verifier_private_key, + 3 * 60, + 10, + ) + .await?; + + info!("Sent proof for block {last_verified_block}, with transaction hash {verify_tx_hash:#x}"); + + info!( + "Mocked verify transaction sent for block {}", + last_verified_block + 1 + ); } } - Err(ProverServerError::FailedToVerifyProofOnChain( - "Error handling eip1559".to_owned(), - )) } diff --git a/crates/l2/sdk/src/sdk.rs b/crates/l2/sdk/src/sdk.rs index ee7d472a5..a7d4042ff 100644 --- a/crates/l2/sdk/src/sdk.rs +++ b/crates/l2/sdk/src/sdk.rs @@ -71,15 +71,16 @@ pub async fn transfer( let tx = client .build_eip1559_transaction( to, + from, Default::default(), Overrides { value: Some(amount), - from: Some(from), ..Default::default() }, + 10, ) .await?; - client.send_eip1559_transaction(tx, &private_key).await + client.send_eip1559_transaction(&tx, &private_key).await } pub async fn deposit( @@ -102,19 +103,20 @@ pub async fn withdraw( .build_privileged_transaction( PrivilegedTxType::Withdrawal, from, + from, Default::default(), Overrides { value: Some(amount), - from: Some(from), gas_price: Some(800000000), gas_limit: Some(21000 * 2), ..Default::default() }, + 10, ) .await?; proposer_client - .send_privileged_l2_transaction(withdraw_transaction, &from_pk) + .send_privileged_l2_transaction(&withdraw_transaction, &from_pk) .await } @@ -194,16 +196,18 @@ pub async fn claim_withdraw( let claim_tx = eth_client .build_eip1559_transaction( bridge_address(), + from, claim_withdrawal_data.into(), Overrides { from: Some(from), ..Default::default() }, + 10, ) .await?; eth_client - .send_eip1559_transaction(claim_tx, &from_pk) + .send_eip1559_transaction(&claim_tx, &from_pk) .await } diff --git a/crates/l2/utils/eth_client/errors.rs b/crates/l2/utils/eth_client/errors.rs index 99287d03f..bc6ec3e6a 100644 --- a/crates/l2/utils/eth_client/errors.rs +++ b/crates/l2/utils/eth_client/errors.rs @@ -58,6 +58,8 @@ pub enum EstimateGasPriceError { RPCError(String), #[error("{0}")] ParseIntError(#[from] std::num::ParseIntError), + #[error("{0}")] + Custom(String), } #[derive(Debug, thiserror::Error)] diff --git a/crates/l2/utils/eth_client/eth_sender.rs b/crates/l2/utils/eth_client/eth_sender.rs index e8c38e5d7..87bccd3a6 100644 --- a/crates/l2/utils/eth_client/eth_sender.rs +++ b/crates/l2/utils/eth_client/eth_sender.rs @@ -80,11 +80,11 @@ impl EthClient { overrides: Overrides, ) -> Result<(H256, Address), EthClientError> { let mut deploy_tx = self - .build_eip1559_transaction(Address::zero(), init_code, overrides) + .build_eip1559_transaction(Address::zero(), deployer, init_code, overrides, 10) .await?; deploy_tx.to = TxKind::Create; let deploy_tx_hash = self - .send_eip1559_transaction(deploy_tx, &deployer_private_key) + .send_eip1559_transaction(&deploy_tx, &deployer_private_key) .await?; let encoded_from = deployer.encode_to_vec(); diff --git a/crates/l2/utils/eth_client/mod.rs b/crates/l2/utils/eth_client/mod.rs index 6d504f3e0..653a72379 100644 --- a/crates/l2/utils/eth_client/mod.rs +++ b/crates/l2/utils/eth_client/mod.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use crate::utils::config::eth::EthConfig; use bytes::Bytes; use errors::{ @@ -7,9 +9,12 @@ use errors::{ }; use eth_sender::Overrides; use ethereum_types::{Address, H256, U256}; -use ethrex_core::types::{ - BlobsBundle, EIP1559Transaction, EIP4844Transaction, GenericTransaction, - PrivilegedL2Transaction, PrivilegedTxType, Signable, TxKind, TxType, +use ethrex_core::{ + types::{ + BlobsBundle, EIP1559Transaction, EIP4844Transaction, GenericTransaction, + PrivilegedL2Transaction, PrivilegedTxType, Signable, TxKind, TxType, + }, + H160, }; use ethrex_rlp::encode::RLPEncode; use ethrex_rpc::{ @@ -25,6 +30,11 @@ use reqwest::Client; use secp256k1::SecretKey; use serde::{Deserialize, Serialize}; use serde_json::json; +use std::ops::Div; +use tokio::time::{sleep, Instant}; +use tracing::warn; + +use super::get_address_from_secret_key; pub mod errors; pub mod eth_sender; @@ -36,11 +46,19 @@ pub enum RpcResponse { Error(RpcErrorResponse), } +#[derive(Debug, Clone)] pub struct EthClient { client: Client, pub url: String, } +#[derive(Debug, Clone)] +pub enum WrappedTransaction { + EIP4844(WrappedEIP4844Transaction), + EIP1559(EIP1559Transaction), + L2(PrivilegedL2Transaction), +} + // 0x08c379a0 == Error(String) pub const ERROR_FUNCTION_SELECTOR: [u8; 4] = [0x08, 0xc3, 0x79, 0xa0]; @@ -94,7 +112,7 @@ impl EthClient { pub async fn send_eip1559_transaction( &self, - tx: EIP1559Transaction, + tx: &EIP1559Transaction, private_key: &SecretKey, ) -> Result { let signed_tx = tx.sign(private_key); @@ -107,9 +125,10 @@ impl EthClient { pub async fn send_eip4844_transaction( &self, - mut wrapped_tx: WrappedEIP4844Transaction, + wrapped_tx: &WrappedEIP4844Transaction, private_key: &SecretKey, ) -> Result { + let mut wrapped_tx = wrapped_tx.clone(); wrapped_tx.tx.sign_inplace(private_key); let mut encoded_tx = wrapped_tx.encode_to_vec(); @@ -118,9 +137,200 @@ impl EthClient { self.send_raw_transaction(encoded_tx.as_slice()).await } + /// Sends a [WrappedTransaction] with retries and gas bumping. + /// + /// The total wait time for each retry is determined by dividing the `max_seconds_to_wait` + /// by the `retries` parameter. The transaction is sent again with a gas bump if the receipt + /// is not confirmed within each retry period. + /// + /// seconds_per_retry = max_seconds_to_wait / retries; + pub async fn send_wrapped_transaction_with_retry( + &self, + wrapped_tx: &WrappedTransaction, + private_key: &SecretKey, + max_seconds_to_wait: u64, + retries: u64, + ) -> Result { + let tx_hash_res = match wrapped_tx { + WrappedTransaction::EIP4844(wrapped_eip4844_transaction) => { + self.send_eip4844_transaction(wrapped_eip4844_transaction, private_key) + .await + } + WrappedTransaction::EIP1559(eip1559_transaction) => { + self.send_eip1559_transaction(eip1559_transaction, private_key) + .await + } + WrappedTransaction::L2(privileged_l2_transaction) => { + self.send_privileged_l2_transaction(privileged_l2_transaction, private_key) + .await + } + }; + + // Check if the tx is `already known`, bump gas and resend it. + let mut tx_hash = match tx_hash_res { + Ok(hash) => hash, + Err(e) => { + let error = format!("{e}"); + if error.contains("already known") + || error.contains("replacement transaction underpriced") + { + H256::zero() + } else { + return Err(e); + } + } + }; + + let mut wrapped_tx = wrapped_tx.clone(); + + let seconds_per_retry = max_seconds_to_wait / retries; + let timer_total = Instant::now(); + + for r in 0..retries { + // Check if we are not waiting more than needed. + if timer_total.elapsed().as_secs() > max_seconds_to_wait { + return Err(EthClientError::Custom( + "TimeOut: Failed to send_wrapped_transaction_with_retry".to_owned(), + )); + } + + // Wait for the receipt with some time between retries. + let timer_per_retry = Instant::now(); + while timer_per_retry.elapsed().as_secs() < seconds_per_retry { + match self.get_transaction_receipt(tx_hash).await? { + Some(_) => return Ok(tx_hash), + None => sleep(Duration::from_secs(1)).await, + } + } + + // If receipt is not found after the time period, increase gas and resend the transaction. + tx_hash = match &mut wrapped_tx { + WrappedTransaction::EIP4844(wrapped_eip4844_transaction) => { + warn!("Resending EIP4844Transaction, attempts [{r}/{retries}]"); + self.bump_and_resend_eip4844(wrapped_eip4844_transaction, private_key) + .await? + } + WrappedTransaction::EIP1559(eip1559_transaction) => { + warn!("Resending EIP1559Transaction, attempts [{r}/{retries}]"); + self.bump_and_resend_eip1559(eip1559_transaction, private_key) + .await? + } + WrappedTransaction::L2(privileged_l2_transaction) => { + warn!("Resending PrivilegedL2Transaction, attempts [{r}/{retries}]"); + self.bump_and_resend_privileged_l2(privileged_l2_transaction, private_key) + .await? + } + }; + } + + // If the loop ends without success, return a timeout error + Err(EthClientError::Custom( + "Max retries exceeded while waiting for transaction receipt".to_owned(), + )) + } + + pub async fn bump_and_resend_eip1559( + &self, + tx: &mut EIP1559Transaction, + private_key: &SecretKey, + ) -> Result { + let from = get_address_from_secret_key(private_key).map_err(|e| { + EthClientError::Custom(format!("Failed to get_address_from_secret_key: {e}")) + })?; + // Sometimes the penalty is a 100% + // Increase max fee per gas by 110% (set it to 210% of the original) + self.bump_eip1559(tx, 1.1); + let wrapped_tx = &mut WrappedTransaction::EIP1559(tx.clone()); + self.estimate_gas_for_wrapped_tx(wrapped_tx, from).await?; + + if let WrappedTransaction::EIP1559(eip1559) = wrapped_tx { + tx.max_fee_per_gas = eip1559.max_fee_per_gas; + tx.max_priority_fee_per_gas = eip1559.max_fee_per_gas; + tx.gas_limit = eip1559.gas_limit; + } + self.send_eip1559_transaction(tx, private_key).await + } + + /// Increase max fee per gas by percentage% (set it to (100+percentage)% of the original) + pub fn bump_eip1559(&self, tx: &mut EIP1559Transaction, percentage: f64) { + // TODO: handle as conversions + tx.max_fee_per_gas = (tx.max_fee_per_gas as f64 * (1.0 + percentage)).round() as u64; + tx.max_priority_fee_per_gas += + (tx.max_priority_fee_per_gas as f64 * (1.0 + percentage)).round() as u64; + } + + pub async fn bump_and_resend_eip4844( + &self, + wrapped_tx: &mut WrappedEIP4844Transaction, + private_key: &SecretKey, + ) -> Result { + let from = get_address_from_secret_key(private_key).map_err(|e| { + EthClientError::Custom(format!("Failed to get_address_from_secret_key: {e}")) + })?; + // Sometimes the penalty is a 100% + // Increase max fee per gas by 110% (set it to 210% of the original) + self.bump_eip4844(wrapped_tx, 1.1); + let wrapped_eip4844 = &mut WrappedTransaction::EIP4844(wrapped_tx.clone()); + self.estimate_gas_for_wrapped_tx(wrapped_eip4844, from) + .await?; + + if let WrappedTransaction::EIP4844(eip4844) = wrapped_eip4844 { + wrapped_tx.tx.max_fee_per_gas = eip4844.tx.max_fee_per_gas; + wrapped_tx.tx.max_priority_fee_per_gas = eip4844.tx.max_fee_per_gas; + wrapped_tx.tx.gas = eip4844.tx.gas; + wrapped_tx.tx.max_fee_per_blob_gas = eip4844.tx.max_fee_per_blob_gas; + } + self.send_eip4844_transaction(wrapped_tx, private_key).await + } + + /// Increase max fee per gas by percentage% (set it to (100+percentage)% of the original) + pub fn bump_eip4844(&self, wrapped_tx: &mut WrappedEIP4844Transaction, percentage: f64) { + // TODO: handle as conversions + wrapped_tx.tx.max_fee_per_gas = + (wrapped_tx.tx.max_fee_per_gas as f64 * (1.0 + percentage)).round() as u64; + wrapped_tx.tx.max_priority_fee_per_gas = + (wrapped_tx.tx.max_priority_fee_per_gas as f64 * (1.0 + percentage)).round() as u64; + + let factor = ((1.0 + percentage) * 10.0).ceil() as u64; + wrapped_tx.tx.max_fee_per_blob_gas = wrapped_tx + .tx + .max_fee_per_blob_gas + .saturating_mul(U256::from(factor)) + .div(10); + } + + pub async fn bump_and_resend_privileged_l2( + &self, + tx: &mut PrivilegedL2Transaction, + private_key: &SecretKey, + ) -> Result { + let from = get_address_from_secret_key(private_key).map_err(|e| { + EthClientError::Custom(format!("Failed to get_address_from_secret_key: {e}")) + })?; + // Sometimes the penalty is a 100% + // Increase max fee per gas by 110% (set it to 210% of the original) + self.bump_privileged_l2(tx, 1.1); + let wrapped_tx = &mut WrappedTransaction::L2(tx.clone()); + self.estimate_gas_for_wrapped_tx(wrapped_tx, from).await?; + if let WrappedTransaction::L2(l2_tx) = wrapped_tx { + tx.max_fee_per_gas = l2_tx.max_fee_per_gas; + tx.max_priority_fee_per_gas = l2_tx.max_fee_per_gas; + tx.gas_limit = l2_tx.gas_limit; + } + self.send_privileged_l2_transaction(tx, private_key).await + } + + /// Increase max fee per gas by percentage% (set it to (100+percentage)% of the original) + pub fn bump_privileged_l2(&self, tx: &mut PrivilegedL2Transaction, percentage: f64) { + // TODO: handle as conversions + tx.max_fee_per_gas = (tx.max_fee_per_gas as f64 * (1.0 + percentage)).round() as u64; + tx.max_priority_fee_per_gas += + (tx.max_priority_fee_per_gas as f64 * (1.0 + percentage)).round() as u64; + } + pub async fn send_privileged_l2_transaction( &self, - tx: PrivilegedL2Transaction, + tx: &PrivilegedL2Transaction, private_key: &SecretKey, ) -> Result { let signed_tx = tx.sign(private_key); @@ -171,12 +381,24 @@ impl EthClient { if &error_data == "0x" { "unknown error".to_owned() } else { - let abi_decoded_error_data = - hex::decode(error_data.strip_prefix("0x").unwrap()).unwrap(); + let abi_decoded_error_data = hex::decode( + error_data.strip_prefix("0x").ok_or(EthClientError::Custom( + "Failed to strip_prefix in estimate_gas".to_owned(), + ))?, + ) + .map_err(|_| { + EthClientError::Custom( + "Failed to hex::decode in estimate_gas".to_owned(), + ) + })?; let string_length = U256::from_big_endian(&abi_decoded_error_data[36..68]); let string_data = &abi_decoded_error_data[68..68 + string_length.as_usize()]; - String::from_utf8(string_data.to_vec()).unwrap() + String::from_utf8(string_data.to_vec()).map_err(|_| { + EthClientError::Custom( + "Failed to String::from_utf8 in estimate_gas".to_owned(), + ) + })? } } else { "unknown error".to_owned() @@ -385,6 +607,50 @@ impl EthClient { } } + pub async fn estimate_gas_for_wrapped_tx( + &self, + wrapped_tx: &mut WrappedTransaction, + from: H160, + ) -> Result { + loop { + let mut transaction = match wrapped_tx { + WrappedTransaction::EIP4844(wrapped_eip4844_transaction) => { + GenericTransaction::from(wrapped_eip4844_transaction.clone().tx) + } + WrappedTransaction::EIP1559(eip1559_transaction) => { + GenericTransaction::from(eip1559_transaction.clone()) + } + WrappedTransaction::L2(privileged_l2_transaction) => { + GenericTransaction::from(privileged_l2_transaction.clone()) + } + }; + + transaction.from = from; + + match self.estimate_gas(transaction).await { + Ok(gas_limit) => return Ok(gas_limit), + Err(e) => { + let error = format!("{e}").to_owned(); + if error.contains("transaction underpriced") { + match wrapped_tx { + WrappedTransaction::EIP4844(wrapped_eip4844_transaction) => { + self.bump_eip4844(wrapped_eip4844_transaction, 1.1); + } + WrappedTransaction::EIP1559(eip1559_transaction) => { + self.bump_eip1559(eip1559_transaction, 1.1); + } + WrappedTransaction::L2(privileged_l2_transaction) => { + self.bump_privileged_l2(privileged_l2_transaction, 1.1); + } + }; + continue; + } + return Err(e); + } + }; + } + } + /// Build an EIP1559 transaction with the given parameters. /// Either `overrides.nonce` or `overrides.from` must be provided. /// If `overrides.gas_price`, `overrides.chain_id` or `overrides.gas_price` @@ -393,9 +659,12 @@ impl EthClient { pub async fn build_eip1559_transaction( &self, to: Address, + from: Address, calldata: Bytes, overrides: Overrides, + bump_retries: u64, ) -> Result { + let get_gas_price; let mut tx = EIP1559Transaction { to: TxKind::Call(to), chain_id: if let Some(chain_id) = overrides.chain_id { @@ -403,16 +672,20 @@ impl EthClient { } else { self.get_chain_id().await?.as_u64() }, - nonce: self.get_nonce_from_overrides(&overrides).await?, + nonce: self + .get_nonce_from_overrides_or_rpc(&overrides, from) + .await?, max_priority_fee_per_gas: if let Some(gas_price) = overrides.gas_price { + get_gas_price = gas_price; gas_price } else { - self.get_gas_price().await?.as_u64() + get_gas_price = self.get_gas_price().await?.as_u64(); + get_gas_price }, max_fee_per_gas: if let Some(gas_price) = overrides.gas_price { gas_price } else { - self.get_gas_price().await?.as_u64() + get_gas_price }, value: overrides.value.unwrap_or_default(), data: calldata, @@ -420,17 +693,42 @@ impl EthClient { ..Default::default() }; - tx.gas_limit = if let Some(gas_limit) = overrides.gas_limit { - gas_limit + let mut wrapped_tx; + + if let Some(overrides_gas_limit) = overrides.gas_limit { + tx.gas_limit = overrides_gas_limit; + Ok(tx) } else { - let mut generic_tx = GenericTransaction::from(tx.clone()); - if let Some(from) = overrides.from { - generic_tx.from = from; + let mut retry = 0_u64; + while retry < bump_retries { + wrapped_tx = WrappedTransaction::EIP1559(tx.clone()); + match self + .estimate_gas_for_wrapped_tx(&mut wrapped_tx, from) + .await + { + Ok(gas_limit) => { + // Estimation succeeded. + tx.gas_limit = gas_limit; + return Ok(tx); + } + Err(e) => { + let error = format!("{e}"); + if error.contains("replacement transaction underpriced") { + warn!("Bumping gas while building: already known"); + retry += 1; + self.bump_eip1559(&mut tx, 1.1); + continue; + } + return Err(e); + } + } } - self.estimate_gas(generic_tx).await? - }; - - Ok(tx) + Err(EthClientError::EstimateGasPriceError( + EstimateGasPriceError::Custom( + "Exceeded maximum retries while estimating gas.".to_string(), + ), + )) + } } /// Build an EIP4844 transaction with the given parameters. @@ -441,29 +739,36 @@ impl EthClient { pub async fn build_eip4844_transaction( &self, to: Address, + from: Address, calldata: Bytes, overrides: Overrides, blobs_bundle: BlobsBundle, + bump_retries: u64, ) -> Result { let blob_versioned_hashes = blobs_bundle.generate_versioned_hashes(); - let mut tx = EIP4844Transaction { + let get_gas_price; + let tx = EIP4844Transaction { to, chain_id: if let Some(chain_id) = overrides.chain_id { chain_id } else { self.get_chain_id().await?.as_u64() }, - nonce: self.get_nonce_from_overrides(&overrides).await?, + nonce: self + .get_nonce_from_overrides_or_rpc(&overrides, from) + .await?, max_priority_fee_per_gas: if let Some(gas_price) = overrides.gas_price { + get_gas_price = gas_price; gas_price } else { - self.get_gas_price().await?.as_u64() + get_gas_price = self.get_gas_price().await?.as_u64(); + get_gas_price }, max_fee_per_gas: if let Some(gas_price) = overrides.gas_price { gas_price } else { - self.get_gas_price().await?.as_u64() + get_gas_price }, value: overrides.value.unwrap_or_default(), data: calldata, @@ -473,17 +778,43 @@ impl EthClient { ..Default::default() }; - tx.gas = if let Some(gas_limit) = overrides.gas_limit { - gas_limit + let mut wrapped_eip4844 = WrappedEIP4844Transaction { tx, blobs_bundle }; + let mut wrapped_tx; + if let Some(overrides_gas_limit) = overrides.gas_limit { + wrapped_eip4844.tx.gas = overrides_gas_limit; + Ok(wrapped_eip4844) } else { - let mut generic_tx = GenericTransaction::from(tx.clone()); - if let Some(from) = overrides.from { - generic_tx.from = from; - } - self.estimate_gas(generic_tx).await? - }; + let mut retry = 0_u64; + while retry < bump_retries { + wrapped_tx = WrappedTransaction::EIP4844(wrapped_eip4844.clone()); - Ok(WrappedEIP4844Transaction { tx, blobs_bundle }) + match self + .estimate_gas_for_wrapped_tx(&mut wrapped_tx, from) + .await + { + Ok(gas_limit) => { + // Estimation succeeded. + wrapped_eip4844.tx.gas = gas_limit; + return Ok(wrapped_eip4844); + } + Err(e) => { + let error = format!("{e}"); + if error.contains("already known") { + warn!("Bumping gas while building: already known"); + retry += 1; + self.bump_eip4844(&mut wrapped_eip4844, 1.1); + continue; + } + return Err(e); + } + } + } + Err(EthClientError::EstimateGasPriceError( + EstimateGasPriceError::Custom( + "Exceeded maximum retries while estimating gas.".to_string(), + ), + )) + } } /// Build a PrivilegedL2 transaction with the given parameters. @@ -495,9 +826,12 @@ impl EthClient { &self, tx_type: PrivilegedTxType, to: Address, + from: Address, calldata: Bytes, overrides: Overrides, + bump_retries: u64, ) -> Result { + let get_gas_price; let mut tx = PrivilegedL2Transaction { tx_type, to: TxKind::Call(to), @@ -506,16 +840,20 @@ impl EthClient { } else { self.get_chain_id().await?.as_u64() }, - nonce: self.get_nonce_from_overrides(&overrides).await?, + nonce: self + .get_nonce_from_overrides_or_rpc(&overrides, from) + .await?, max_priority_fee_per_gas: if let Some(gas_price) = overrides.gas_price { + get_gas_price = gas_price; gas_price } else { - self.get_gas_price().await?.as_u64() + get_gas_price = self.get_gas_price().await?.as_u64(); + get_gas_price }, max_fee_per_gas: if let Some(gas_price) = overrides.gas_price { gas_price } else { - self.get_gas_price().await?.as_u64() + get_gas_price }, value: overrides.value.unwrap_or_default(), data: calldata, @@ -523,25 +861,52 @@ impl EthClient { ..Default::default() }; - tx.gas_limit = if let Some(gas_limit) = overrides.gas_limit { - gas_limit + let mut wrapped_tx; + + if let Some(overrides_gas_limit) = overrides.gas_limit { + tx.gas_limit = overrides_gas_limit; + Ok(tx) } else { - let mut generic_tx = GenericTransaction::from(tx.clone()); - if let Some(from) = overrides.from { - generic_tx.from = from; + let mut retry = 0_u64; + while retry < bump_retries { + wrapped_tx = WrappedTransaction::L2(tx.clone()); + match self + .estimate_gas_for_wrapped_tx(&mut wrapped_tx, from) + .await + { + Ok(gas_limit) => { + // Estimation succeeded. + tx.gas_limit = gas_limit; + return Ok(tx); + } + Err(e) => { + let error = format!("{e}"); + if error.contains("already known") { + warn!("Bumping gas while building: already known"); + retry += 1; + self.bump_privileged_l2(&mut tx, 1.1); + continue; + } + return Err(e); + } + } } - self.estimate_gas(generic_tx).await? - }; - - Ok(tx) + Err(EthClientError::EstimateGasPriceError( + EstimateGasPriceError::Custom( + "Exceeded maximum retries while estimating gas.".to_string(), + ), + )) + } } - async fn get_nonce_from_overrides(&self, overrides: &Overrides) -> Result { + async fn get_nonce_from_overrides_or_rpc( + &self, + overrides: &Overrides, + address: Address, + ) -> Result { if let Some(nonce) = overrides.nonce { return Ok(nonce); } - - let address = overrides.from.ok_or(EthClientError::UnrecheableNonce)?; self.get_nonce(address).await } @@ -577,7 +942,7 @@ impl EthClient { let selector = keccak(selector) .as_bytes() .get(..4) - .expect("Failed to get initialize selector") + .ok_or(EthClientError::Custom("Failed to get selector.".to_owned()))? .to_vec(); let mut calldata = Vec::new(); @@ -594,13 +959,14 @@ impl EthClient { ) .await?; - let hex_string = hex_string - .strip_prefix("0x") - .expect("Couldn't strip prefix from last_committed_block."); + let hex_string = hex_string.strip_prefix("0x").ok_or(EthClientError::Custom( + "Couldn't strip prefix from last_committed_block.".to_owned(), + ))?; - // TODO return error if hex_string.is_empty() { - panic!("Failed to fetch last_committed_block. Manual intervention required"); + return Err(EthClientError::Custom( + "Failed to fetch last_committed_block. Manual intervention required.".to_owned(), + )); } let value = U256::from_str_radix(hex_string, 16) diff --git a/crates/l2/utils/mod.rs b/crates/l2/utils/mod.rs index c1de728c3..293effdd5 100644 --- a/crates/l2/utils/mod.rs +++ b/crates/l2/utils/mod.rs @@ -1,4 +1,7 @@ -use keccak_hash::H256; +use std::array::TryFromSliceError; + +use ethrex_core::Address; +use keccak_hash::{keccak, H256}; use secp256k1::SecretKey; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -22,3 +25,15 @@ where let hex = H256::from_slice(&secret_key.secret_bytes()); hex.serialize(serializer) } + +pub fn get_address_from_secret_key(secret_key: &SecretKey) -> Result { + let public_key = secret_key + .public_key(secp256k1::SECP256K1) + .serialize_uncompressed(); + let hash = keccak(&public_key[1..]); + + // Get the lat 20 bytes of the hash + let address_bytes: [u8; 20] = hash[12..32].try_into()?; + + Ok(Address::from(address_bytes)) +}