diff --git a/.ethexe.example.local.toml b/.ethexe.example.local.toml index 810e2351819..fa6be91b6f6 100644 --- a/.ethexe.example.local.toml +++ b/.ethexe.example.local.toml @@ -121,6 +121,10 @@ block-time = 1 # (optional, default: 15). # eip1559-fee-increase-percentage = 15 +# EIP-1559 max fee per gas in gwei for transaction fee estimation (for batch commitments). +# (optional, default: 0). +# eip1559-max-fee-per-gas-in-gwei = 0 + # Blob gas multiplier. # (optional, default: 3). # blob-gas-multiplier = 3 diff --git a/.ethexe.example.toml b/.ethexe.example.toml index 16d4ea76ffc..7233842ddec 100644 --- a/.ethexe.example.toml +++ b/.ethexe.example.toml @@ -121,6 +121,10 @@ # (optional, default: 15). # eip1559-fee-increase-percentage = 15 +# EIP-1559 max fee per gas in gwei for transaction fee estimation (for batch commitments). +# (optional, default: 0). +# eip1559-max-fee-per-gas-in-gwei = 0 + # Blob gas multiplier. # (optional, default: 3). # blob-gas-multiplier = 3 diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index 1daa62b5260..3cf2035e1b6 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -76,6 +76,7 @@ jobs: linux-aarch64: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'E5-forcelinuxaarch64') }} release: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'E3-forcerelease') }} production: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'E4-forceproduction') }} + docker: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'E0-forcedocker') }} validator: needs: gate diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 820967e0a1b..e563caf0b25 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,9 @@ on: production: type: boolean default: false + docker: + type: boolean + default: false run-name: ${{ inputs.title }} ( ${{ format('#{0}', inputs.number) }} ) @@ -595,7 +598,7 @@ jobs: name: Build Docker images for gear-cli, ethexe-cli (linux-x86_64) runs-on: ubuntu-latest needs: [dynamic-matrix, gear-cli, ethexe-cli] - if: ${{ contains(fromJSON(needs.dynamic-matrix.outputs.profiles).*.name, 'release') }} + if: ${{ contains(fromJSON(needs.dynamic-matrix.outputs.profiles).*.name, 'release') || inputs.docker }} steps: - name: "ACTIONS: Checkout" uses: actions/checkout@v6 diff --git a/ethexe/cli/README.md b/ethexe/cli/README.md index f97ce4b3bff..256ca62efac 100644 --- a/ethexe/cli/README.md +++ b/ethexe/cli/README.md @@ -51,6 +51,7 @@ beacon-rpc = "http://127.0.0.1:8545" router = "0x..." block-time = 12 eip1559-fee-increase-percentage = 20 +eip1559-max-fee-per-gas-in-gwei = 0 blob-gas-multiplier = 2 [network] diff --git a/ethexe/cli/src/commands/tx.rs b/ethexe/cli/src/commands/tx.rs index f395221189d..69b55cd8418 100644 --- a/ethexe/cli/src/commands/tx.rs +++ b/ethexe/cli/src/commands/tx.rs @@ -279,10 +279,12 @@ impl TxCommand { .and_then(|p| p.eip1559_fee_increase_percentage) }); - self.blob_gas_multiplier = self - .blob_gas_multiplier - .take() - .or_else(|| params.ethereum.as_ref().and_then(|p| p.blob_gas_multiplier)); + self.blob_gas_multiplier = self.blob_gas_multiplier.take().or_else(|| { + params + .ethereum + .as_ref() + .and_then(|p| p.blob_gas_multiplier.map(|v| v as u128)) + }); self } diff --git a/ethexe/cli/src/params/ethereum.rs b/ethexe/cli/src/params/ethereum.rs index be3a0ec1537..1cc8c2ba85f 100644 --- a/ethexe/cli/src/params/ethereum.rs +++ b/ethexe/cli/src/params/ethereum.rs @@ -56,10 +56,15 @@ pub struct EthereumParams { #[serde(rename = "eip1559-fee-increase-percentage")] pub eip1559_fee_increase_percentage: Option, + /// EIP-1559 max fee per gas in gwei for transaction fee estimation (for batch commitments). + #[arg(long, alias = "eip1559-max-fee-per-gas-in-gwei")] + #[serde(rename = "eip1559-max-fee-per-gas-in-gwei")] + pub eip1559_max_fee_per_gas_in_gwei: Option, + /// Blob gas multiplier. #[arg(long, alias = "blob-gas-multiplier")] #[serde(rename = "blob-gas-multiplier")] - pub blob_gas_multiplier: Option, + pub blob_gas_multiplier: Option, } impl EthereumParams { @@ -76,8 +81,12 @@ impl EthereumParams { pub const DEFAULT_EIP1559_FEE_INCREASE_PERCENTAGE: u64 = Ethereum::INCREASED_EIP1559_FEE_INCREASE_PERCENTAGE; + /// Default EIP-1559 max fee per gas in gwei for transaction fee estimation (for batch commitments). + pub const DEFAULT_EIP1559_MAX_FEE_PER_GAS_IN_GWEI: u64 = + Ethereum::NO_EIP1559_MAX_FEE_PER_GAS_IN_GWEI as u64; + /// Default blob gas multiplier. - pub const DEFAULT_BLOB_GAS_MULTIPLIER: u128 = Ethereum::INCREASED_BLOB_GAS_MULTIPLIER; + pub const DEFAULT_BLOB_GAS_MULTIPLIER: u64 = Ethereum::INCREASED_BLOB_GAS_MULTIPLIER as u64; /// Converts Ethereum-facing CLI/TOML parameters into [`EthereumConfig`]. /// @@ -98,9 +107,14 @@ impl EthereumParams { eip1559_fee_increase_percentage: self .eip1559_fee_increase_percentage .unwrap_or(Self::DEFAULT_EIP1559_FEE_INCREASE_PERCENTAGE), + eip1559_max_fee_per_gas_in_gwei: self + .eip1559_max_fee_per_gas_in_gwei + .unwrap_or(Self::DEFAULT_EIP1559_MAX_FEE_PER_GAS_IN_GWEI) + as u128, blob_gas_multiplier: self .blob_gas_multiplier - .unwrap_or(Self::DEFAULT_BLOB_GAS_MULTIPLIER), + .unwrap_or(Self::DEFAULT_BLOB_GAS_MULTIPLIER) + as u128, }) } } @@ -115,6 +129,9 @@ impl MergeParams for EthereumParams { eip1559_fee_increase_percentage: self .eip1559_fee_increase_percentage .or(with.eip1559_fee_increase_percentage), + eip1559_max_fee_per_gas_in_gwei: self + .eip1559_max_fee_per_gas_in_gwei + .or(with.eip1559_max_fee_per_gas_in_gwei), blob_gas_multiplier: self.blob_gas_multiplier.or(with.blob_gas_multiplier), } } diff --git a/ethexe/ethereum/src/lib.rs b/ethexe/ethereum/src/lib.rs index 0bfcba53328..11f2a8b26d9 100644 --- a/ethexe/ethereum/src/lib.rs +++ b/ethexe/ethereum/src/lib.rs @@ -89,6 +89,7 @@ pub struct EthereumBuilder { signer: Option, sender_address: Option
, eip1559_fee_increase_percentage: Option, + eip1559_max_fee_per_gas_in_gwei: Option, blob_gas_multiplier: Option, initialize_addresses: Option, } @@ -144,6 +145,16 @@ impl EthereumBuilder { self.eip1559_fee_increase_percentage(Ethereum::INCREASED_EIP1559_FEE_INCREASE_PERCENTAGE) } + /// Sets the EIP-1559 max fee per gas in gwei to use for transaction fee estimation + /// (for batch commitments). + pub fn eip1559_max_fee_per_gas_in_gwei( + mut self, + eip1559_max_fee_per_gas_in_gwei: u128, + ) -> Self { + self.eip1559_max_fee_per_gas_in_gwei = Some(eip1559_max_fee_per_gas_in_gwei); + self + } + /// Sets the blob gas multiplier to use for transaction fee estimation. pub fn blob_gas_multiplier_opt(mut self, blob_gas_multiplier: Option) -> Self { self.blob_gas_multiplier = blob_gas_multiplier; @@ -188,6 +199,9 @@ impl EthereumBuilder { let eip1559_fee_increase_percentage = self .eip1559_fee_increase_percentage .unwrap_or(Ethereum::NO_EIP1559_FEE_INCREASE_PERCENTAGE); + let eip1559_max_fee_per_gas_in_gwei = self + .eip1559_max_fee_per_gas_in_gwei + .unwrap_or(Ethereum::NO_EIP1559_MAX_FEE_PER_GAS_IN_GWEI); let blob_gas_multiplier = self .blob_gas_multiplier .unwrap_or(Ethereum::NO_BLOB_GAS_MULTIPLIER); @@ -199,6 +213,7 @@ impl EthereumBuilder { signer, sender_address, eip1559_fee_increase_percentage, + eip1559_max_fee_per_gas_in_gwei, blob_gas_multiplier, initialize_addresses, ) @@ -224,6 +239,8 @@ pub struct Ethereum { provider: AlloyProvider, signer: Signer, sender_address: Address, + eip1559_estimator: Eip1559Estimator, + eip1559_max_fee_per_gas_in_gwei: u128, } impl Ethereum { @@ -235,6 +252,8 @@ impl Ethereum { /// Default EIP-1559 fee increase percentage for transaction fee estimation. pub const NO_EIP1559_FEE_INCREASE_PERCENTAGE: u64 = 0; + /// Default EIP-1559 max fee per gas in gwei for transaction fee estimation (for batch commitments). + pub const NO_EIP1559_MAX_FEE_PER_GAS_IN_GWEI: u128 = 0; /// EIP-1559 fee increase percentage for transaction fee estimation that increases the estimated fee /// by 15% compared to "medium" estimation. /// @@ -249,18 +268,20 @@ impl Ethereum { pub const INCREASED_BLOB_GAS_MULTIPLIER: u128 = 3; /// Default offset for permit deadline from the current block timestamp. - pub const PERMIT_DEADLINE_OFFSET: u64 = 120; // 2 minutes + pub const PERMIT_DEADLINE_OFFSET: u64 = 300; // 5 minutes + #[allow(clippy::too_many_arguments)] pub(crate) async fn new( ethereum_rpc_url: &str, router_address: Address, signer: Signer, sender_address: Address, eip1559_fee_increase_percentage: u64, + eip1559_max_fee_per_gas_in_gwei: u128, blob_gas_multiplier: u128, initialize_addresses: bool, ) -> Result { - let provider = create_provider( + let (provider, eip1559_estimator) = create_provider( ethereum_rpc_url, signer.clone(), sender_address, @@ -285,6 +306,8 @@ impl Ethereum { provider, signer, sender_address, + eip1559_estimator, + eip1559_max_fee_per_gas_in_gwei, }) } @@ -402,7 +425,14 @@ impl Ethereum { } pub fn router(&self) -> Router { - Router::new(self.router, self.wvara, self.sender(), self.provider()) + Router::new( + self.router, + self.wvara, + self.eip1559_estimator.clone(), + self.eip1559_max_fee_per_gas_in_gwei, + self.sender(), + self.provider(), + ) } pub fn wrapped_vara(&self) -> WVara { @@ -425,18 +455,22 @@ pub(crate) async fn create_provider( sender_address: Address, eip1559_fee_increase_percentage: u64, blob_gas_multiplier: u128, -) -> Result { - Ok(ProviderBuilder::default() - .with_eip1559_estimator(Eip1559Estimator::new(move |base_fee_per_gas, rewards| { - utils::eip1559_default_estimator(base_fee_per_gas, rewards) - .scaled_by_pct(eip1559_fee_increase_percentage) - })) - .with_blob_gas_estimator(BlobGasEstimator::scaled(blob_gas_multiplier)) - .with_simple_nonce_management() - .fetch_chain_id() - .wallet(EthereumWallet::new(Sender::new(signer, sender_address)?)) - .connect(rpc_url) - .await?) +) -> Result<(AlloyProvider, Eip1559Estimator)> { + let eip1559_estimator = Eip1559Estimator::new(move |base_fee_per_gas, rewards| { + utils::eip1559_default_estimator(base_fee_per_gas, rewards) + .scaled_by_pct(eip1559_fee_increase_percentage) + }); + Ok(( + ProviderBuilder::default() + .with_eip1559_estimator(eip1559_estimator.clone()) + .with_blob_gas_estimator(BlobGasEstimator::scaled(blob_gas_multiplier)) + .with_simple_nonce_management() + .fetch_chain_id() + .wallet(EthereumWallet::new(Sender::new(signer, sender_address)?)) + .connect(rpc_url) + .await?, + eip1559_estimator, + )) } #[derive(Debug, Clone)] diff --git a/ethexe/ethereum/src/router/mod.rs b/ethexe/ethereum/src/router/mod.rs index b172994d287..174215dbdc8 100644 --- a/ethexe/ethereum/src/router/mod.rs +++ b/ethexe/ethereum/src/router/mod.rs @@ -26,11 +26,14 @@ use crate::{ wvara::WVara, }; use alloy::{ - consensus::{SidecarBuilder, SimpleCoder}, + consensus::{SidecarBuilder, SimpleCoder, constants::GWEI_TO_WEI}, eips::BlockId, hex, primitives::{Address as AlloyAddress, Bytes, fixed_bytes}, - providers::{PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider}, + providers::{ + PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider, + utils::{Eip1559Estimation, Eip1559Estimator}, + }, rpc::types::{TransactionReceipt, eth::state::AccountOverride}, }; use anyhow::{Result, anyhow}; @@ -64,6 +67,8 @@ type QueryInstance = IRouter::IRouterInstance; pub struct Router { instance: Instance, wvara_address: AlloyAddress, + eip1559_estimator: Eip1559Estimator, + eip1559_max_fee_per_gas_in_gwei: u128, sender: Sender, } @@ -71,17 +76,21 @@ impl Router { /// `Gear.blockIsPredecessor(hash)` can consume up to 30_000 gas const GEAR_BLOCK_IS_PREDECESSOR_GAS: u64 = 30_000; /// Transaction gas limit cap - const TX_GAS_LIMIT_CAP: u64 = 16_777_216; + const TX_GAS_LIMIT_CAP: u64 = 10_000_000; pub(crate) fn new( address: AlloyAddress, wvara_address: AlloyAddress, + eip1559_estimator: Eip1559Estimator, + eip1559_max_fee_per_gas_in_gwei: u128, sender: Sender, provider: AlloyProvider, ) -> Self { Self { instance: Instance::new(address, provider), wvara_address, + eip1559_estimator, + eip1559_max_fee_per_gas_in_gwei, sender, } } @@ -536,8 +545,38 @@ impl Router { )); } }; - let gas_limit = - Self::TX_GAS_LIMIT_CAP.max(estimated_gas_limit + Self::GEAR_BLOCK_IS_PREDECESSOR_GAS); + + let Eip1559Estimation { + max_fee_per_gas, .. + } = self + .instance + .provider() + .estimate_eip1559_fees_with(self.eip1559_estimator.clone()) + .await?; + + let eip1559_max_fee_per_gas_in_wei = self + .eip1559_max_fee_per_gas_in_gwei + .saturating_mul(GWEI_TO_WEI as _); + + if eip1559_max_fee_per_gas_in_wei > 0 && max_fee_per_gas >= eip1559_max_fee_per_gas_in_wei { + log::error!( + "Estimated max fee per gas {max_fee_per_gas} wei is higher than the configured maximum of {eip1559_max_fee_per_gas_in_wei} wei, refusing to commit batch (commitment: {commitment:?})" + ); + return Err(anyhow!( + "Estimated max fee per gas {max_fee_per_gas} wei is higher than the configured maximum of {eip1559_max_fee_per_gas_in_wei} wei, refusing to commit batch", + )); + } + + let gas_limit = estimated_gas_limit + Self::GEAR_BLOCK_IS_PREDECESSOR_GAS; + + if gas_limit > Self::TX_GAS_LIMIT_CAP { + log::error!( + "Estimated gas limit {gas_limit} is too high for batch commitment: {commitment:?}", + ); + return Err(anyhow!( + "Estimated gas limit {gas_limit} is too high for batch commitment", + )); + } builder.gas(gas_limit).send().await.map_err(Into::into) } diff --git a/ethexe/service/src/config.rs b/ethexe/service/src/config.rs index fc31ca7e168..004a78df1cd 100644 --- a/ethexe/service/src/config.rs +++ b/ethexe/service/src/config.rs @@ -113,5 +113,6 @@ pub struct EthereumConfig { pub router_address: Address, pub block_time: Duration, pub eip1559_fee_increase_percentage: u64, + pub eip1559_max_fee_per_gas_in_gwei: u128, pub blob_gas_multiplier: u128, } diff --git a/ethexe/service/src/lib.rs b/ethexe/service/src/lib.rs index fc3eaf17f67..e936466d56b 100644 --- a/ethexe/service/src/lib.rs +++ b/ethexe/service/src/lib.rs @@ -392,6 +392,9 @@ impl Service { .eip1559_fee_increase_percentage( config.ethereum.eip1559_fee_increase_percentage, ) + .eip1559_max_fee_per_gas_in_gwei( + config.ethereum.eip1559_max_fee_per_gas_in_gwei, + ) .blob_gas_multiplier(config.ethereum.blob_gas_multiplier) .build() .await?; diff --git a/ethexe/service/src/tests/utils/env.rs b/ethexe/service/src/tests/utils/env.rs index be193bc8262..7c56a1f95c1 100644 --- a/ethexe/service/src/tests/utils/env.rs +++ b/ethexe/service/src/tests/utils/env.rs @@ -254,6 +254,7 @@ impl TestEnv { router_address, block_time, eip1559_fee_increase_percentage: Ethereum::NO_EIP1559_FEE_INCREASE_PERCENTAGE, + eip1559_max_fee_per_gas_in_gwei: Ethereum::NO_EIP1559_MAX_FEE_PER_GAS_IN_GWEI, blob_gas_multiplier: Ethereum::NO_BLOB_GAS_MULTIPLIER, }; let mut observer = ObserverService::new( diff --git a/ethexe/service/tests/smoke.rs b/ethexe/service/tests/smoke.rs index 3bb73d6dba4..f4f14ae36cd 100644 --- a/ethexe/service/tests/smoke.rs +++ b/ethexe/service/tests/smoke.rs @@ -70,6 +70,7 @@ async fn constructor() { .expect("infallible"), block_time: Duration::from_secs(12), eip1559_fee_increase_percentage: Ethereum::NO_EIP1559_FEE_INCREASE_PERCENTAGE, + eip1559_max_fee_per_gas_in_gwei: Ethereum::NO_EIP1559_MAX_FEE_PER_GAS_IN_GWEI, blob_gas_multiplier: Ethereum::NO_BLOB_GAS_MULTIPLIER, };