From 1cebc39182f822329684050a2b54281f682f506e Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Thu, 23 Nov 2023 15:20:07 +0000 Subject: [PATCH] Use Polygon gas oracle (#2965) ### Description After investigating #2959, I found the following this is a known problem with Polygon, explanation here https://github.com/ethers-io/ethers.js/issues/2828#issuecomment-1283014250 Fun fact Asa looked into this once https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/771 Here's a discussion it in Foundry, which I found hoping that ethers-rs folks had ran into this before https://github.com/foundry-rs/foundry/issues/1703 Foundry fixed this by using the Polygon gas station oracle which seems to be recommended path https://github.com/foundry-rs/foundry/pull/3368/files#diff-c89a4bbf7a90da118dcf00c5fe70eba78f8e5d95662bb5f039a353113e95042bR205 There's actually a polygon ethers middleware for this https://docs.rs/ethers/latest/ethers/middleware/gas_oracle/polygon/struct.Polygon.html So I (originally) borrowed this code from Foundry https://github.com/foundry-rs/foundry/blob/master/crates/common/src/provider.rs#L254-L290 Changed to use Middlewares This also means we can remove our existing janky Polygon logic ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- rust/chains/hyperlane-ethereum/src/mailbox.rs | 2 +- .../hyperlane-ethereum/src/trait_builder.rs | 70 ++++++++++++++----- rust/chains/hyperlane-ethereum/src/tx.rs | 15 +--- .../src/validator_announce.rs | 2 +- 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/rust/chains/hyperlane-ethereum/src/mailbox.rs b/rust/chains/hyperlane-ethereum/src/mailbox.rs index 42fad5d843..60bf35c9d8 100644 --- a/rust/chains/hyperlane-ethereum/src/mailbox.rs +++ b/rust/chains/hyperlane-ethereum/src/mailbox.rs @@ -264,7 +264,7 @@ where metadata.to_vec().into(), RawHyperlaneMessage::from(message).to_vec().into(), ); - fill_tx_gas_params(tx, tx_gas_limit, self.provider.clone(), message.destination).await + fill_tx_gas_params(tx, tx_gas_limit, self.provider.clone()).await } } diff --git a/rust/chains/hyperlane-ethereum/src/trait_builder.rs b/rust/chains/hyperlane-ethereum/src/trait_builder.rs index f2242cb162..03a33c2fdc 100644 --- a/rust/chains/hyperlane-ethereum/src/trait_builder.rs +++ b/rust/chains/hyperlane-ethereum/src/trait_builder.rs @@ -3,6 +3,9 @@ use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; +use ethers::middleware::gas_oracle::{ + GasCategory, GasOracle, GasOracleMiddleware, Polygon, ProviderOracle, +}; use ethers::prelude::{ Http, JsonRpcClient, Middleware, NonceManagerMiddleware, Provider, Quorum, QuorumProvider, SignerMiddleware, WeightedProvider, Ws, WsClientError, @@ -17,7 +20,9 @@ use ethers_prometheus::json_rpc_client::{ use ethers_prometheus::middleware::{ MiddlewareMetrics, PrometheusMiddleware, PrometheusMiddlewareConf, }; -use hyperlane_core::{ChainCommunicationError, ChainResult, ContractLocator}; +use hyperlane_core::{ + ChainCommunicationError, ChainResult, ContractLocator, HyperlaneDomain, KnownHyperlaneDomain, +}; use crate::{signers::Signers, ConnectionConf, FallbackProvider, RetryingProvider}; @@ -89,7 +94,7 @@ pub trait BuildableWithProvider { builder = builder.add_provider(weighted_provider); } let quorum_provider = builder.build(); - self.wrap_with_metrics(quorum_provider, locator, signer, middleware_metrics) + self.build(quorum_provider, locator, signer, middleware_metrics) .await? } ConnectionConf::HttpFallback { urls } => { @@ -109,7 +114,7 @@ pub trait BuildableWithProvider { builder = builder.add_provider(metrics_provider); } let fallback_provider = builder.build(); - self.wrap_with_metrics(fallback_provider, locator, signer, middleware_metrics) + self.build(fallback_provider, locator, signer, middleware_metrics) .await? } ConnectionConf::Http { url } => { @@ -125,15 +130,14 @@ pub trait BuildableWithProvider { &middleware_metrics, ); let retrying_http_provider = RetryingProvider::new(metrics_provider, None, None); - self.wrap_with_metrics(retrying_http_provider, locator, signer, middleware_metrics) + self.build(retrying_http_provider, locator, signer, middleware_metrics) .await? } ConnectionConf::Ws { url } => { let ws = Ws::connect(url) .await .map_err(EthereumProviderConnectionError::from)?; - self.wrap_with_metrics(ws, locator, signer, middleware_metrics) - .await? + self.build(ws, locator, signer, middleware_metrics).await? } }) } @@ -174,9 +178,9 @@ pub trait BuildableWithProvider { ) } - /// Wrap the provider creation with metrics if provided; this is the second - /// step - async fn wrap_with_metrics

( + /// Create the provider, applying any middlewares (e.g. gas oracle, signer, metrics) as needed, + /// and then create the associated trait. + async fn build

( &self, client: P, locator: &ContractLocator, @@ -186,19 +190,20 @@ pub trait BuildableWithProvider { where P: JsonRpcClient + 'static, { - let provider = Provider::new(client); + let provider = wrap_with_gas_oracle(Provider::new(client), locator.domain)?; + Ok(if let Some(metrics) = metrics { let provider = Arc::new(PrometheusMiddleware::new(provider, metrics.0, metrics.1)); tokio::spawn(provider.start_updating_on_interval(METRICS_SCRAPE_INTERVAL)); - self.wrap_with_signer(provider, locator, signer).await? + self.build_with_signer(provider, locator, signer).await? } else { - self.wrap_with_signer(provider, locator, signer).await? + self.build_with_signer(provider, locator, signer).await? }) } /// Wrap the provider creation with a signing provider if signers were - /// provided; this is the third step. - async fn wrap_with_signer( + /// provided, and then create the associated trait. + async fn build_with_signer( &self, provider: M, locator: &ContractLocator, @@ -208,7 +213,7 @@ pub trait BuildableWithProvider { M: Middleware + 'static, { Ok(if let Some(signer) = signer { - let signing_provider = build_signing_provider(provider, signer) + let signing_provider = wrap_with_signer(provider, signer) .await .map_err(ChainCommunicationError::from_other)?; self.build_with_provider(signing_provider, locator) @@ -224,7 +229,7 @@ pub trait BuildableWithProvider { M: Middleware + 'static; } -async fn build_signing_provider( +async fn wrap_with_signer( provider: M, signer: Signers, ) -> Result, Signers>, M::Error> { @@ -237,3 +242,36 @@ async fn build_signing_provider( let signing_provider = SignerMiddleware::new(provider, signer); Ok(signing_provider) } + +fn build_polygon_gas_oracle(chain: ethers_core::types::Chain) -> ChainResult> { + let gas_oracle = Polygon::new(chain) + .map_err(ChainCommunicationError::from_other)? + .category(GasCategory::Standard); + Ok(Box::new(gas_oracle) as Box) +} + +/// Wrap the provider with a gas oracle middleware. +/// Polygon and Mumbai require using the Polygon gas oracle, see discussion here +/// https://github.com/foundry-rs/foundry/issues/1703. +/// Defaults to using the provider's gas oracle. +fn wrap_with_gas_oracle( + provider: M, + domain: &HyperlaneDomain, +) -> ChainResult, Box>> +where + M: Middleware + 'static, +{ + let provider = Arc::new(provider); + let gas_oracle: Box = { + match domain { + HyperlaneDomain::Known(KnownHyperlaneDomain::Polygon) => { + build_polygon_gas_oracle(ethers_core::types::Chain::Polygon)? + } + HyperlaneDomain::Known(KnownHyperlaneDomain::Mumbai) => { + build_polygon_gas_oracle(ethers_core::types::Chain::PolygonMumbai)? + } + _ => Box::new(ProviderOracle::new(provider.clone())), + } + }; + Ok(GasOracleMiddleware::new(provider, gas_oracle)) +} diff --git a/rust/chains/hyperlane-ethereum/src/tx.rs b/rust/chains/hyperlane-ethereum/src/tx.rs index 2850bf93cc..6919b4a03e 100644 --- a/rust/chains/hyperlane-ethereum/src/tx.rs +++ b/rust/chains/hyperlane-ethereum/src/tx.rs @@ -9,9 +9,7 @@ use ethers::{ }; use ethers_contract::builders::ContractCall; use ethers_core::types::BlockNumber; -use hyperlane_core::{ - utils::fmt_bytes, ChainCommunicationError, ChainResult, KnownHyperlaneDomain, H256, U256, -}; +use hyperlane_core::{utils::fmt_bytes, ChainCommunicationError, ChainResult, H256, U256}; use tracing::{error, info}; use crate::Middleware; @@ -73,7 +71,6 @@ pub(crate) async fn fill_tx_gas_params( tx: ContractCall, tx_gas_limit: Option, provider: Arc, - domain: u32, ) -> ChainResult> where M: Middleware + 'static, @@ -91,16 +88,6 @@ where // Is not EIP 1559 chain return Ok(tx.gas(gas_limit)); }; - let max_priority_fee = if matches!( - KnownHyperlaneDomain::try_from(domain), - Ok(KnownHyperlaneDomain::Polygon) - ) { - // Polygon needs a max priority fee >= 30 gwei - let min_polygon_fee = U256::from(30_000_000_000u64); - max_priority_fee.max(min_polygon_fee.into()) - } else { - max_priority_fee - }; // Is EIP 1559 chain let mut request = Eip1559TransactionRequest::new(); if let Some(from) = tx.tx.from() { diff --git a/rust/chains/hyperlane-ethereum/src/validator_announce.rs b/rust/chains/hyperlane-ethereum/src/validator_announce.rs index 8bce341c2e..d21348be2f 100644 --- a/rust/chains/hyperlane-ethereum/src/validator_announce.rs +++ b/rust/chains/hyperlane-ethereum/src/validator_announce.rs @@ -86,7 +86,7 @@ where announcement.value.storage_location, serialized_signature.into(), ); - fill_tx_gas_params(tx, tx_gas_limit, self.provider.clone(), self.domain.id()).await + fill_tx_gas_params(tx, tx_gas_limit, self.provider.clone()).await } }