Skip to content

Commit

Permalink
Fix BSC gas price issues by treating chains with a base fee of 0 as n…
Browse files Browse the repository at this point in the history
…on-1559 (#3462)

### Description

- Fixes #3396 
- See v2 backport here #3463 

So turns out this is because:
- BSC nodes tend to enforce a minimum gas price of 3 gwei when you
submit a tx
- it's possible for validators to include privileged txs at a lower gas
price, so in practice (probably some mev stuff ?) there are a bunch of
txs in blocks with transactions lower than 3 gwei
- BSC's 1559 situation is they have a base fee of zero, so it's
basically like they just use legacy txs
- when we try to figure out what to pay in prio fees, we are suggested a
1 gwei priority fee:
```
$ cast rpc eth_feeHistory 10 latest "[5.0]" --rpc-url https://rpc.ankr.com/bsc
{"oldestBlock":"0x23706f7","reward":[["0x3b9aca00"]],"baseFeePerGas":["0x0","0x0"],"gasUsedRatio":[0.1149265]}

$ cast td 0x3b9aca00
1000000000
```

So options are:
1. tx overrides for bsc, set it to 3 gwei

2. use a different eip 1559 estimator and set the percentile used in
eth_feeHistory to something higher. Atm the percentile is 5%, if you
change this to 50% we seem to get 3 gwei. This would have consequences
for other chains tho

3. another heuristic, to fall back to legacy txs (where we can trust the
eth_gasPrice to give us an accurate price) whenever the base fee is 0


I'm a tx override hater and we don't have this in the agents so far so
I'm gonna go with 3. It's the least intrusive change
  • Loading branch information
tkporter authored Mar 21, 2024
1 parent 59e89af commit 176e710
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 3 deletions.
63 changes: 61 additions & 2 deletions rust/chains/hyperlane-ethereum/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@ use std::time::Duration;
use ethers::{
abi::Detokenize,
prelude::{NameOrAddress, TransactionReceipt},
providers::ProviderError,
types::Eip1559TransactionRequest,
};
use ethers_contract::builders::ContractCall;
use ethers_core::types::BlockNumber;
use ethers_core::{
types::{BlockNumber, U256 as EthersU256},
utils::{
eip1559_default_estimator, EIP1559_FEE_ESTIMATION_PAST_BLOCKS,
EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE,
},
};
use hyperlane_core::{utils::bytes_to_hex, ChainCommunicationError, ChainResult, H256, U256};
use tracing::{error, info};

Expand Down Expand Up @@ -84,10 +91,22 @@ where
.saturating_add(U256::from(GAS_ESTIMATE_BUFFER).into())
.into()
};
let Ok((max_fee, max_priority_fee)) = provider.estimate_eip1559_fees(None).await else {

let Ok((base_fee, max_fee, max_priority_fee)) = estimate_eip1559_fees(provider, None).await
else {
// Is not EIP 1559 chain
return Ok(tx.gas(gas_limit));
};

// If the base fee is zero, just treat the chain as a non-EIP-1559 chain.
// This is useful for BSC, where the base fee is zero, there's a minimum gas price
// generally enforced by nodes of 3 gwei, but EIP 1559 estimation suggests a priority
// fee lower than 3 gwei because of privileged transactions being included by block
// producers that have a lower priority fee.
if base_fee.is_zero() {
return Ok(tx.gas(gas_limit));
}

// Is EIP 1559 chain
let mut request = Eip1559TransactionRequest::new();
if let Some(from) = tx.tx.from() {
Expand All @@ -109,6 +128,46 @@ where
Ok(eip_1559_tx.gas(gas_limit))
}

type FeeEstimator = fn(EthersU256, Vec<Vec<EthersU256>>) -> (EthersU256, EthersU256);

/// Pretty much a copy of the logic in ethers-rs (https://github.com/hyperlane-xyz/ethers-rs/blob/c9ced035628da59376c369be035facda1648577a/ethers-providers/src/provider.rs#L478)
/// but returns the base fee as well as the max fee and max priority fee.
/// Gets a heuristic recommendation of max fee per gas and max priority fee per gas for
/// EIP-1559 compatible transactions.
async fn estimate_eip1559_fees<M>(
provider: Arc<M>,
estimator: Option<FeeEstimator>,
) -> ChainResult<(EthersU256, EthersU256, EthersU256)>
where
M: Middleware + 'static,
{
let base_fee_per_gas = provider
.get_block(BlockNumber::Latest)
.await
.map_err(ChainCommunicationError::from_other)?
.ok_or_else(|| ProviderError::CustomError("Latest block not found".into()))?
.base_fee_per_gas
.ok_or_else(|| ProviderError::CustomError("EIP-1559 not activated".into()))?;

let fee_history = provider
.fee_history(
EIP1559_FEE_ESTIMATION_PAST_BLOCKS,
BlockNumber::Latest,
&[EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE],
)
.await
.map_err(ChainCommunicationError::from_other)?;

// use the provided fee estimator function, or fallback to the default implementation.
let (max_fee_per_gas, max_priority_fee_per_gas) = if let Some(es) = estimator {
es(base_fee_per_gas, fee_history.reward)
} else {
eip1559_default_estimator(base_fee_per_gas, fee_history.reward)
};

Ok((base_fee_per_gas, max_fee_per_gas, max_priority_fee_per_gas))
}

pub(crate) async fn call_with_lag<M, T>(
call: ethers::contract::builders::ContractCall<M, T>,
provider: &M,
Expand Down
2 changes: 1 addition & 1 deletion typescript/infra/config/environments/mainnet3/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ const hyperlane: RootAgentConfig = {
docker: {
repo,
// Includes Cosmos block-by-block indexing.
tag: 'a72c3cf-20240314-173418',
tag: '39df4ca-20240321-100543',
},
gasPaymentEnforcement: [
// Temporary measure to ensure all inEVM warp route messages are delivered -
Expand Down

0 comments on commit 176e710

Please sign in to comment.