Skip to content

Commit

Permalink
fix: lower priority fee (#2148)
Browse files Browse the repository at this point in the history
* use (updated) blocknative oracle from ethers-rs

* log err if price oracle failed

(cherry picked from commit 711ab84)
  • Loading branch information
alxiong committed Oct 22, 2024
1 parent 79c478e commit 54e33bf
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 19 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ rand_distr = "0.4"
reqwest = "0.12"
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "^1.0.113"
tempfile = "3.10"
toml = "0.8"
url = "2.3"
vbs = "0.1"
Expand Down
63 changes: 45 additions & 18 deletions hotshot-state-prover/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ use async_std::{
};
use contract_bindings::light_client::{LightClient, LightClientErrors};
use displaydoc::Display;
use ethers::middleware::{
gas_oracle::{GasCategory, GasOracle},
signer::SignerMiddlewareError,
};
use ethers::{
core::k256::ecdsa::SigningKey,
middleware::SignerMiddleware,
providers::{Http, Middleware, Provider, ProviderError},
signers::{LocalWallet, Signer, Wallet},
types::{Address, U256},
types::{transaction::eip2718::TypedTransaction, Address, U256},
};
use futures::FutureExt;
use hotshot_contract_adapter::{
Expand All @@ -42,6 +46,7 @@ use jf_pcs::prelude::UnivariateUniversalParams;
use jf_plonk::errors::PlonkError;
use jf_relation::Circuit as _;
use jf_signature::constants::CS_ID_SCHNORR;
use sequencer_utils::blocknative::BlockNative;
use sequencer_utils::deployer::is_proxy_contract;
use serde::Deserialize;
use surf_disco::Client;
Expand Down Expand Up @@ -276,11 +281,8 @@ async fn prepare_contract(

/// get the `finalizedState` from the LightClient contract storage on L1
pub async fn read_contract_state(
provider: Url,
key: SigningKey,
light_client_address: Address,
contract: &LightClient<SignerWallet>,
) -> Result<(LightClientState, StakeTableState), ProverError> {
let contract = prepare_contract(provider, key, light_client_address).await?;
let state: ParsedLightClientState = match contract.finalized_state().call().await {
Ok(s) => s.into(),
Err(e) => {
Expand All @@ -306,16 +308,34 @@ pub async fn read_contract_state(
pub async fn submit_state_and_proof(
proof: Proof,
public_input: PublicInput,
provider: Url,
key: SigningKey,
light_client_address: Address,
contract: &LightClient<SignerWallet>,
) -> Result<(), ProverError> {
let contract = prepare_contract(provider, key, light_client_address).await?;

// prepare the input the contract call and the tx itself
let proof: ParsedPlonkProof = proof.into();
let new_state: ParsedLightClientState = public_input.into();
let tx = contract.new_finalized_state(new_state.into(), proof.into());

let mut tx = contract.new_finalized_state(new_state.into(), proof.into());

// only use gas oracle for mainnet
if contract.client_ref().get_chainid().await?.as_u64() == 1 {
let gas_oracle = BlockNative::new(None).category(GasCategory::SafeLow);
match gas_oracle.estimate_eip1559_fees().await {
Ok((max_fee, priority_fee)) => {
if let TypedTransaction::Eip1559(inner) = &mut tx.tx {
inner.max_fee_per_gas = Some(max_fee);
inner.max_priority_fee_per_gas = Some(priority_fee);
tracing::info!(
"Setting maxFeePerGas: {}; maxPriorityFeePerGas to: {}",
max_fee,
priority_fee
);
}
}
Err(e) => {
tracing::warn!("!! BlockNative Price Oracle failed: {}", e);
}
}
}

// send the tx
let (receipt, included_block) = sequencer_utils::contract_send::<_, _, LightClientErrors>(&tx)
Expand Down Expand Up @@ -349,8 +369,8 @@ pub async fn sync_state<ApiVer: StaticVersionType>(
let bundle = fetch_latest_state(relay_server_client).await?;
tracing::info!("Bundle accumulated weight: {}", bundle.accumulated_weight);
tracing::info!("Latest HotShot block height: {}", bundle.state.block_height);
let (old_state, st_state) =
read_contract_state(provider.clone(), key.clone(), light_client_address).await?;
let contract = prepare_contract(provider.clone(), key.clone(), light_client_address).await?;
let (old_state, st_state) = read_contract_state(&contract).await?;
tracing::info!(
"Current HotShot block height on contract: {}",
old_state.block_height
Expand Down Expand Up @@ -408,7 +428,7 @@ pub async fn sync_state<ApiVer: StaticVersionType>(
let proof_gen_elapsed = Instant::now().signed_duration_since(proof_gen_start);
tracing::info!("Proof generation completed. Elapsed: {proof_gen_elapsed:.3}");

submit_state_and_proof(proof, public_input, provider, key, light_client_address).await?;
submit_state_and_proof(proof, public_input, &contract).await?;

tracing::info!("Successfully synced light client state.");
Ok(())
Expand Down Expand Up @@ -521,6 +541,8 @@ pub enum ProverError {
PlonkError(PlonkError),
/// Internal error
Internal(String),
/// General network issue: {0}
NetworkError(anyhow::Error),
}

impl From<ServerError> for ProverError {
Expand All @@ -546,6 +568,11 @@ impl From<ProviderError> for ProverError {
Self::ContractError(anyhow!("{}", err))
}
}
impl From<SignerMiddlewareError<Provider<Http>, LocalWallet>> for ProverError {
fn from(err: SignerMiddlewareError<Provider<Http>, LocalWallet>) -> Self {
Self::ContractError(anyhow!("{}", err))
}
}

impl std::error::Error for ProverError {}

Expand Down Expand Up @@ -782,12 +809,13 @@ mod test {

let mut config = StateProverConfig::default();
config.update_l1_info(&anvil, contract.address());
let (state, st_state) = super::read_contract_state(
let contract = super::prepare_contract(
config.provider,
config.signing_key,
config.light_client_address,
)
.await?;
let (state, st_state) = super::read_contract_state(&contract).await?;

assert_eq!(state, genesis.into());
assert_eq!(st_state, stake_genesis.into());
Expand Down Expand Up @@ -817,14 +845,13 @@ mod test {
let (pi, proof) = gen_state_proof(new_state.clone(), &stake_genesis, &state_keys, &st);
tracing::info!("Successfully generated proof for new state.");

super::submit_state_and_proof(
proof,
pi,
let contract = super::prepare_contract(
config.provider,
config.signing_key,
config.light_client_address,
)
.await?;
super::submit_state_and_proof(proof, pi, &contract).await?;
tracing::info!("Successfully submitted new finalized state to L1.");
// test if new state is updated in l1
let finalized_l1: ParsedLightClientState = contract.finalized_state().await?.into();
Expand Down
2 changes: 1 addition & 1 deletion marketplace-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ vbs = { workspace = true }
hotshot-query-service = { workspace = true }
sequencer = { path = "../sequencer", features = ["testing"] }
sequencer-utils = { path = "../utils" }
tempfile = "3.10.1"
tempfile = { workspace = true }
3 changes: 3 additions & 0 deletions utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ anyhow = { workspace = true }
ark-serialize = { workspace = true, features = ["derive"] }
async-compatibility-layer = { workspace = true }
async-std = { workspace = true }
async-trait = { workspace = true }
clap = { workspace = true }
committable = "0.2"
contract-bindings = { path = "../contract-bindings" }
Expand All @@ -19,6 +20,8 @@ futures = { workspace = true }
hotshot-contract-adapter = { workspace = true }
log-panics = { workspace = true }
portpicker = { workspace = true }
# for price oracle and align with ethers-rs dep
reqwest = { version = "0.11.14", default-features = false, features = ["json", "rustls-tls"] }
serde = { workspace = true }
serde_json = "^1.0.113"
surf = "2.3.2"
Expand Down
150 changes: 150 additions & 0 deletions utils/src/blocknative.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//! Copy from <https://github.com/gakonst/ethers-rs/blob/master/ethers-middleware/src/gas_oracle/blocknative.rs>
//! which is unmaintained and out-of-sync with the latest blocknative feed
//!
//! TDOO: revisit this or remove this when switching to `alloy-rs`
use async_trait::async_trait;
use ethers::{
middleware::gas_oracle::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result},
types::U256,
};
use reqwest::{header::AUTHORIZATION, Client};
use serde::Deserialize;
use std::collections::HashMap;
use url::Url;

const URL: &str = "https://api.blocknative.com/gasprices/blockprices";

/// A client over HTTP for the [BlockNative](https://www.blocknative.com/gas-estimator) gas tracker API
/// that implements the `GasOracle` trait.
#[derive(Clone, Debug)]
#[must_use]
pub struct BlockNative {
client: Client,
url: Url,
api_key: Option<String>,
gas_category: GasCategory,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Response {
pub system: String,
pub network: String,
pub unit: String,
pub max_price: u64,
pub block_prices: Vec<BlockPrice>,
pub estimated_base_fees: Option<Vec<HashMap<String, Vec<BaseFeeEstimate>>>>,
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BlockPrice {
pub block_number: u64,
pub estimated_transaction_count: u64,
pub base_fee_per_gas: f64,
pub estimated_prices: Vec<GasEstimate>,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GasEstimate {
pub confidence: u64,
pub price: f64,
pub max_priority_fee_per_gas: f64,
pub max_fee_per_gas: f64,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BaseFeeEstimate {
pub confidence: u64,
pub base_fee: f64,
}

impl Response {
#[inline]
pub fn estimate_from_category(&self, gas_category: &GasCategory) -> Result<GasEstimate> {
let confidence = gas_category_to_confidence(gas_category);
let price = self
.block_prices
.first()
.ok_or(GasOracleError::InvalidResponse)?
.estimated_prices
.iter()
.find(|p| p.confidence == confidence)
.ok_or(GasOracleError::GasCategoryNotSupported)?;
Ok(*price)
}
}

impl Default for BlockNative {
fn default() -> Self {
Self::new(None)
}
}

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GasOracle for BlockNative {
async fn fetch(&self) -> Result<U256> {
let estimate = self
.query()
.await?
.estimate_from_category(&self.gas_category)?;
Ok(from_gwei_f64(estimate.price))
}

async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
let estimate = self
.query()
.await?
.estimate_from_category(&self.gas_category)?;
let max = from_gwei_f64(estimate.max_fee_per_gas);
let prio = from_gwei_f64(estimate.max_priority_fee_per_gas);
Ok((max, prio))
}
}

impl BlockNative {
/// Creates a new [BlockNative](https://www.blocknative.com/gas-estimator) gas oracle.
pub fn new(api_key: Option<String>) -> Self {
Self::with_client(Client::new(), api_key)
}

/// Same as [`Self::new`] but with a custom [`Client`].
pub fn with_client(client: Client, api_key: Option<String>) -> Self {
let url = Url::parse(URL).unwrap();
Self {
client,
api_key,
url,
gas_category: GasCategory::Standard,
}
}

/// Sets the gas price category to be used when fetching the gas price.
pub fn category(mut self, gas_category: GasCategory) -> Self {
self.gas_category = gas_category;
self
}

/// Perform a request to the gas price API and deserialize the response.
pub async fn query(&self) -> Result<Response, GasOracleError> {
let mut request = self.client.get(self.url.clone());
if let Some(api_key) = self.api_key.as_ref() {
request = request.header(AUTHORIZATION, api_key);
}
let response = request.send().await?.error_for_status()?.json().await?;
Ok(response)
}
}

#[inline]
fn gas_category_to_confidence(gas_category: &GasCategory) -> u64 {
match gas_category {
GasCategory::SafeLow => 80,
GasCategory::Standard => 90,
GasCategory::Fast => 95,
GasCategory::Fastest => 99,
}
}
1 change: 1 addition & 0 deletions utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use ethers::{
use tempfile::TempDir;
use url::Url;

pub mod blocknative;
pub mod deployer;
pub mod logging;
pub mod ser;
Expand Down

0 comments on commit 54e33bf

Please sign in to comment.