Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(chore): Fortuna refactor #2372

Merged
merged 7 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions apps/fortuna/src/chain.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
pub(crate) mod eth_gas_oracle;
pub(crate) mod ethereum;
mod nonce_manager;
pub(crate) mod reader;
pub(crate) mod traced_client;
99 changes: 10 additions & 89 deletions apps/fortuna/src/chain/ethereum.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
use {
crate::{
api::ChainId,
chain::{
chain::reader::{
self, BlockNumber, BlockStatus, EntropyReader, RequestedWithCallbackEvent,
},
config::EthereumConfig,
eth_utils::{
eth_gas_oracle::EthProviderOracle,
legacy_tx_middleware::LegacyTxMiddleware,
nonce_manager::NonceManagerMiddleware,
reader::{self, BlockNumber, BlockStatus, EntropyReader, RequestedWithCallbackEvent},
traced_client::{RpcMetrics, TracedClient},
},
config::EthereumConfig,
},
anyhow::{anyhow, Error, Result},
axum::async_trait,
ethers::{
abi::RawLog,
contract::{abigen, ContractCall, EthLogDecode},
contract::{abigen, EthLogDecode},
core::types::Address,
middleware::{gas_oracle::GasOracleMiddleware, MiddlewareError, SignerMiddleware},
prelude::{BlockId, JsonRpcClient, PendingTransaction, TransactionRequest},
middleware::{gas_oracle::GasOracleMiddleware, SignerMiddleware},
prelude::JsonRpcClient,
providers::{Http, Middleware, Provider},
signers::{LocalWallet, Signer},
types::{transaction::eip2718::TypedTransaction, BlockNumber as EthersBlockNumber, U256},
types::{BlockNumber as EthersBlockNumber, U256},
},
sha3::{Digest, Keccak256},
std::sync::Arc,
thiserror::Error,
};

// TODO: Programmatically generate this so we don't have to keep committed ABI in sync with the
Expand All @@ -43,90 +45,9 @@ pub type SignablePythContractInner<T> = PythRandom<MiddlewaresWrapper<T>>;
pub type SignablePythContract = SignablePythContractInner<Http>;
pub type InstrumentedSignablePythContract = SignablePythContractInner<TracedClient>;

pub type PythContractCall = ContractCall<MiddlewaresWrapper<TracedClient>, ()>;

pub type PythContract = PythRandom<Provider<Http>>;
pub type InstrumentedPythContract = PythRandom<Provider<TracedClient>>;

/// Middleware that converts a transaction into a legacy transaction if use_legacy_tx is true.
/// We can not use TransformerMiddleware because keeper calls fill_transaction first which bypasses
/// the transformer.
#[derive(Clone, Debug)]
pub struct LegacyTxMiddleware<M> {
use_legacy_tx: bool,
inner: M,
}

impl<M> LegacyTxMiddleware<M> {
pub fn new(use_legacy_tx: bool, inner: M) -> Self {
Self {
use_legacy_tx,
inner,
}
}
}

#[derive(Error, Debug)]
pub enum LegacyTxMiddlewareError<M: Middleware> {
#[error("{0}")]
MiddlewareError(M::Error),
}

impl<M: Middleware> MiddlewareError for LegacyTxMiddlewareError<M> {
type Inner = M::Error;

fn from_err(src: M::Error) -> Self {
LegacyTxMiddlewareError::MiddlewareError(src)
}

fn as_inner(&self) -> Option<&Self::Inner> {
match self {
LegacyTxMiddlewareError::MiddlewareError(e) => Some(e),
}
}
}

#[async_trait]
impl<M: Middleware> Middleware for LegacyTxMiddleware<M> {
type Error = LegacyTxMiddlewareError<M>;
type Provider = M::Provider;
type Inner = M;
fn inner(&self) -> &M {
&self.inner
}

async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self,
tx: T,
block: Option<BlockId>,
) -> std::result::Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
let mut tx = tx.into();
if self.use_legacy_tx {
let legacy_request: TransactionRequest = tx.into();
tx = legacy_request.into();
}
self.inner()
.send_transaction(tx, block)
.await
.map_err(MiddlewareError::from_err)
}

async fn fill_transaction(
&self,
tx: &mut TypedTransaction,
block: Option<BlockId>,
) -> std::result::Result<(), Self::Error> {
if self.use_legacy_tx {
let legacy_request: TransactionRequest = (*tx).clone().into();
*tx = legacy_request.into();
}
self.inner()
.fill_transaction(tx, block)
.await
.map_err(MiddlewareError::from_err)
}
}

impl<T: JsonRpcClient + 'static + Clone> SignablePythContractInner<T> {
/// Get the wallet that signs transactions sent to this contract.
pub fn wallet(&self) -> LocalWallet {
Expand Down
8 changes: 3 additions & 5 deletions apps/fortuna/src/command/run.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use {
crate::{
api::{self, BlockchainState, ChainId},
chain::{
ethereum::InstrumentedPythContract,
traced_client::{RpcMetrics, TracedClient},
},
chain::ethereum::InstrumentedPythContract,
command::register_provider::CommitmentMetadata,
config::{Commitment, Config, EthereumConfig, RunOptions},
keeper::{self, KeeperMetrics},
eth_utils::traced_client::{RpcMetrics, TracedClient},
keeper::{self, keeper_metrics::KeeperMetrics},
state::{HashChainState, PebbleHashChain},
},
anyhow::{anyhow, Error, Result},
Expand Down
5 changes: 5 additions & 0 deletions apps/fortuna/src/eth_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod eth_gas_oracle;
pub mod legacy_tx_middleware;
pub mod nonce_manager;
pub mod traced_client;
pub mod utils;
88 changes: 88 additions & 0 deletions apps/fortuna/src/eth_utils/legacy_tx_middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use {
axum::async_trait,
ethers::{
middleware::{Middleware, MiddlewareError},
prelude::{BlockId, PendingTransaction, TransactionRequest},
types::transaction::eip2718::TypedTransaction,
},
thiserror::Error,
};

/// Middleware that converts a transaction into a legacy transaction if use_legacy_tx is true.
/// We can not use TransformerMiddleware because keeper calls fill_transaction first which bypasses
/// the transformer.
#[derive(Clone, Debug)]
pub struct LegacyTxMiddleware<M> {
use_legacy_tx: bool,
inner: M,
}

impl<M> LegacyTxMiddleware<M> {
pub fn new(use_legacy_tx: bool, inner: M) -> Self {
Self {
use_legacy_tx,
inner,
}
}
}

#[derive(Error, Debug)]
pub enum LegacyTxMiddlewareError<M: Middleware> {
#[error("{0}")]
MiddlewareError(M::Error),
}

impl<M: Middleware> MiddlewareError for LegacyTxMiddlewareError<M> {
type Inner = M::Error;

fn from_err(src: M::Error) -> Self {
LegacyTxMiddlewareError::MiddlewareError(src)
}

fn as_inner(&self) -> Option<&Self::Inner> {
match self {
LegacyTxMiddlewareError::MiddlewareError(e) => Some(e),
}
}
}

#[async_trait]
impl<M: Middleware> Middleware for LegacyTxMiddleware<M> {
type Error = LegacyTxMiddlewareError<M>;
type Provider = M::Provider;
type Inner = M;
fn inner(&self) -> &M {
&self.inner
}

async fn send_transaction<T: Into<TypedTransaction> + Send + Sync>(
&self,
tx: T,
block: Option<BlockId>,
) -> std::result::Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
let mut tx = tx.into();
if self.use_legacy_tx {
let legacy_request: TransactionRequest = tx.into();
tx = legacy_request.into();
}
self.inner()
.send_transaction(tx, block)
.await
.map_err(MiddlewareError::from_err)
}

async fn fill_transaction(
&self,
tx: &mut TypedTransaction,
block: Option<BlockId>,
) -> std::result::Result<(), Self::Error> {
if self.use_legacy_tx {
let legacy_request: TransactionRequest = (*tx).clone().into();
*tx = legacy_request.into();
}
self.inner()
.fill_transaction(tx, block)
.await
.map_err(MiddlewareError::from_err)
}
}
66 changes: 66 additions & 0 deletions apps/fortuna/src/eth_utils/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use {
anyhow::{anyhow, Result},
ethers::{contract::ContractCall, middleware::Middleware},
std::sync::Arc,
tracing,
};

pub async fn send_and_confirm<A: Middleware>(contract_call: ContractCall<A, ()>) -> Result<()> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the function signatures of this function and the one below changed very slightly so that they don't depend on the pyth-specific type declarations in the chain/ module.

let call_name = contract_call.function.name.as_str();
let pending_tx = contract_call
.send()
.await
.map_err(|e| anyhow!("Error submitting transaction({}) {:?}", call_name, e))?;

let tx_result = pending_tx
.await
.map_err(|e| {
anyhow!(
"Error waiting for transaction({}) receipt: {:?}",
call_name,
e
)
})?
.ok_or_else(|| {
anyhow!(
"Can't verify the transaction({}), probably dropped from mempool",
call_name
)
})?;

tracing::info!(
transaction_hash = &tx_result.transaction_hash.to_string(),
"Confirmed transaction({}). Receipt: {:?}",
call_name,
tx_result,
);
Ok(())
}

/// Estimate the cost (in wei) of a transaction consuming gas_used gas.
pub async fn estimate_tx_cost<T: Middleware + 'static>(
middleware: Arc<T>,
use_legacy_tx: bool,
gas_used: u128,
) -> Result<u128> {
let gas_price: u128 = if use_legacy_tx {
middleware
.get_gas_price()
.await
.map_err(|e| anyhow!("Failed to fetch gas price. error: {:?}", e))?
.try_into()
.map_err(|e| anyhow!("gas price doesn't fit into 128 bits. error: {:?}", e))?
} else {
// This is not obvious but the implementation of estimate_eip1559_fees in ethers.rs
// for a middleware that has a GasOracleMiddleware inside is to ignore the passed-in callback
// and use whatever the gas oracle returns.
let (max_fee_per_gas, max_priority_fee_per_gas) =
middleware.estimate_eip1559_fees(None).await?;

(max_fee_per_gas + max_priority_fee_per_gas)
.try_into()
.map_err(|e| anyhow!("gas price doesn't fit into 128 bits. error: {:?}", e))?
};

Ok(gas_price * gas_used)
}
Loading
Loading