Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into bump_revm
Browse files Browse the repository at this point in the history
  • Loading branch information
rakita committed Oct 20, 2023
2 parents 3128d58 + fafcabf commit 3e48a91
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 14 deletions.
13 changes: 6 additions & 7 deletions crates/primitives/src/transaction/pooled.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
//! Defines the types for blob transactions, legacy, and other EIP-2718 transactions included in a
//! response to `GetPooledTransactions`.
use crate::{
Address, BlobTransaction, BlobTransactionSidecar, Bytes, Signature, Transaction,
TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxHash, TxLegacy, B256,
EIP4844_TX_TYPE_ID,
Address, BlobTransaction, Bytes, Signature, Transaction, TransactionSigned,
TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxHash, TxLegacy, B256, EIP4844_TX_TYPE_ID,
};
use alloy_rlp::{Decodable, Encodable, Error as RlpError, Header, EMPTY_LIST_CODE};
use bytes::Buf;
Expand Down Expand Up @@ -430,7 +429,7 @@ impl<'a> arbitrary::Arbitrary<'a> for PooledTransactionsElement {

// generate a sidecar for blob txs
if let PooledTransactionsElement::BlobTransaction(mut tx) = pooled_txs_element {
tx.sidecar = BlobTransactionSidecar::arbitrary(u)?;
tx.sidecar = crate::BlobTransactionSidecar::arbitrary(u)?;
Ok(PooledTransactionsElement::BlobTransaction(tx))
} else {
Ok(pooled_txs_element)
Expand All @@ -441,12 +440,10 @@ impl<'a> arbitrary::Arbitrary<'a> for PooledTransactionsElement {
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for PooledTransactionsElement {
type Parameters = ();
type Strategy = proptest::strategy::BoxedStrategy<PooledTransactionsElement>;

fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
use proptest::prelude::{any, Strategy};

any::<(TransactionSigned, BlobTransactionSidecar)>()
any::<(TransactionSigned, crate::BlobTransactionSidecar)>()
.prop_map(move |(transaction, sidecar)| {
// this will have an empty sidecar
let pooled_txs_element = PooledTransactionsElement::from(transaction);
Expand All @@ -461,6 +458,8 @@ impl proptest::arbitrary::Arbitrary for PooledTransactionsElement {
})
.boxed()
}

type Strategy = proptest::strategy::BoxedStrategy<PooledTransactionsElement>;
}

/// A signed pooled transaction with recovered signer.
Expand Down
22 changes: 15 additions & 7 deletions crates/rpc/rpc-types/src/mev.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! MEV-share bundle type bindings
//! MEV bundle type bindings
#![allow(missing_docs)]
use alloy_primitives::{Address, BlockNumber, Bytes, TxHash, B256, U256, U64};
use reth_primitives::{BlockId, Log};
use alloy_primitives::{Address, Bytes, TxHash, B256, U256, U64};
use reth_primitives::{BlockId, BlockNumberOrTag, Log};
use serde::{
ser::{SerializeSeq, Serializer},
Deserialize, Deserializer, Serialize,
Expand Down Expand Up @@ -603,7 +604,7 @@ pub struct EthCallBundle {
/// hex encoded block number for which this bundle is valid on
pub block_number: U64,
/// Either a hex encoded number or a block tag for which state to base this simulation on
pub state_block_number: BlockNumber,
pub state_block_number: BlockNumberOrTag,
/// the timestamp to use for this bundle simulation, in seconds since the unix epoch
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<u64>,
Expand All @@ -613,9 +614,9 @@ pub struct EthCallBundle {
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct EthCallBundleResponse {
pub bundle_hash: B256,
#[serde(with = "u256_numeric_string")]
pub bundle_gas_price: U256,
pub bundle_hash: String,
#[serde(with = "u256_numeric_string")]
pub coinbase_diff: U256,
#[serde(with = "u256_numeric_string")]
Expand All @@ -641,9 +642,16 @@ pub struct EthCallBundleTransactionResult {
#[serde(with = "u256_numeric_string")]
pub gas_price: U256,
pub gas_used: u64,
pub to_address: Address,
pub to_address: Option<Address>,
pub tx_hash: B256,
pub value: Bytes,
/// Contains the return data if the transaction succeeded
///
/// Note: this is mutually exclusive with `revert`
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<Bytes>,
/// Contains the return data if the transaction reverted
#[serde(skip_serializing_if = "Option::is_none")]
pub revert: Option<Bytes>,
}

mod u256_numeric_string {
Expand Down
207 changes: 207 additions & 0 deletions crates/rpc/rpc/src/eth/bundle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
//! `Eth` bundle implementation and helpers.
use crate::{
eth::{
error::{EthApiError, EthResult, RpcInvalidTransactionError},
revm_utils::FillableTransaction,
utils::recover_raw_transaction,
EthTransactions,
},
BlockingTaskGuard,
};
use reth_primitives::{keccak256, U256};
use reth_revm::database::StateProviderDatabase;
use reth_rpc_types::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult};
use revm::{
db::CacheDB,
primitives::{Env, ResultAndState, TxEnv},
};
use revm_primitives::db::{DatabaseCommit, DatabaseRef};
use std::sync::Arc;

/// `Eth` bundle implementation.
pub struct EthBundle<Eth> {
/// All nested fields bundled together.
inner: Arc<EthBundleInner<Eth>>,
}

impl<Eth> EthBundle<Eth> {
/// Create a new `EthBundle` instance.
pub fn new(eth_api: Eth, blocking_task_guard: BlockingTaskGuard) -> Self {
Self { inner: Arc::new(EthBundleInner { eth_api, blocking_task_guard }) }
}
}

impl<Eth> EthBundle<Eth>
where
Eth: EthTransactions + 'static,
{
/// Simulates a bundle of transactions at the top of a given block number with the state of
/// another (or the same) block. This can be used to simulate future blocks with the current
/// state, or it can be used to simulate a past block. The sender is responsible for signing the
/// transactions and using the correct nonce and ensuring validity
pub async fn call_bundle(&self, bundle: EthCallBundle) -> EthResult<EthCallBundleResponse> {
let EthCallBundle { txs, block_number, state_block_number, timestamp } = bundle;
if txs.is_empty() {
return Err(EthApiError::InvalidParams(
EthBundleError::EmptyBundleTransactions.to_string(),
))
}
if block_number.to::<u64>() == 0 {
return Err(EthApiError::InvalidParams(
EthBundleError::BundleMissingBlockNumber.to_string(),
))
}

let transactions =
txs.into_iter().map(recover_raw_transaction).collect::<Result<Vec<_>, _>>()?;

let (cfg, mut block_env, at) =
self.inner.eth_api.evm_env_at(state_block_number.into()).await?;

// need to adjust the timestamp for the next block
if let Some(timestamp) = timestamp {
block_env.timestamp = U256::from(timestamp);
} else {
block_env.timestamp += U256::from(12);
}

let state_block_number = block_env.number;
// use the block number of the request
block_env.number = U256::from(block_number);

self.inner
.eth_api
.spawn_with_state_at_block(at, move |state| {
let coinbase = block_env.coinbase;
let basefee = Some(block_env.basefee.to::<u64>());
let env = Env { cfg, block: block_env, tx: TxEnv::default() };
let db = CacheDB::new(StateProviderDatabase::new(state));

let initial_coinbase = DatabaseRef::basic_ref(&db, coinbase)?
.map(|acc| acc.balance)
.unwrap_or_default();
let mut coinbase_balance_before_tx = initial_coinbase;
let mut coinbase_balance_after_tx = initial_coinbase;
let mut total_gas_used = 0u64;
let mut total_gas_fess = U256::ZERO;
let mut hash_bytes = Vec::with_capacity(32 * transactions.len());

let mut evm = revm::EVM::with_env(env);
evm.database(db);

let mut results = Vec::with_capacity(transactions.len());
let mut transactions = transactions.into_iter().peekable();

while let Some(tx) = transactions.next() {
let tx = tx.into_ecrecovered_transaction();
hash_bytes.extend_from_slice(tx.hash().as_slice());
let gas_price = tx
.effective_gas_tip(basefee)
.ok_or_else(|| RpcInvalidTransactionError::FeeCapTooLow)?;
tx.try_fill_tx_env(&mut evm.env.tx)?;
let ResultAndState { result, state } = evm.transact()?;

let gas_used = result.gas_used();
total_gas_used += gas_used;

let gas_fees = U256::from(gas_used) * U256::from(gas_price);
total_gas_fess += gas_fees;

// coinbase is always present in the result state
coinbase_balance_after_tx =
state.get(&coinbase).map(|acc| acc.info.balance).unwrap_or_default();
let coinbase_diff =
coinbase_balance_after_tx.saturating_sub(coinbase_balance_before_tx);
let eth_sent_to_coinbase = coinbase_diff.saturating_sub(gas_fees);

// update the coinbase balance
coinbase_balance_before_tx = coinbase_balance_after_tx;

// set the return data for the response
let (value, revert) = if result.is_success() {
let value = result.into_output().unwrap_or_default();
(Some(value), None)
} else {
let revert = result.into_output().unwrap_or_default();
(None, Some(revert))
};

let tx_res = EthCallBundleTransactionResult {
coinbase_diff,
eth_sent_to_coinbase,
from_address: tx.signer(),
gas_fees,
gas_price: U256::from(gas_price),
gas_used,
to_address: tx.to(),
tx_hash: tx.hash(),
value,
revert,
};
results.push(tx_res);

// need to apply the state changes of this call before executing the
// next call
if transactions.peek().is_some() {
// need to apply the state changes of this call before executing
// the next call
evm.db.as_mut().expect("is set").commit(state)
}
}

// populate the response

let coinbase_diff = coinbase_balance_after_tx.saturating_sub(initial_coinbase);
let eth_sent_to_coinbase = coinbase_diff.saturating_sub(total_gas_fess);
let bundle_gas_price =
coinbase_diff.checked_div(U256::from(total_gas_used)).unwrap_or_default();
let res = EthCallBundleResponse {
bundle_gas_price,
bundle_hash: keccak256(&hash_bytes),
coinbase_diff,
eth_sent_to_coinbase,
gas_fees: total_gas_fess,
results,
state_block_number: state_block_number.to(),
total_gas_used,
};

Ok(res)
})
.await
}
}

/// Container type for `EthBundle` internals
#[derive(Debug)]
struct EthBundleInner<Eth> {
/// Access to commonly used code of the `eth` namespace
eth_api: Eth,
// restrict the number of concurrent tracing calls.
#[allow(unused)]
blocking_task_guard: BlockingTaskGuard,
}

impl<Eth> std::fmt::Debug for EthBundle<Eth> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EthBundle").finish_non_exhaustive()
}
}

impl<Eth> Clone for EthBundle<Eth> {
fn clone(&self) -> Self {
Self { inner: Arc::clone(&self.inner) }
}
}

/// [EthBundle] specific errors.
#[derive(Debug, thiserror::Error)]
pub enum EthBundleError {
/// Thrown if the bundle does not contain any transactions.
#[error("bundle missing txs")]
EmptyBundleTransactions,
/// Thrown if the bundle does not contain a block number, or block number is 0.
#[error("bundle missing blockNumber")]
BundleMissingBlockNumber,
}
2 changes: 2 additions & 0 deletions crates/rpc/rpc/src/eth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! `eth` namespace handler implementation.
mod api;
pub mod bundle;
pub mod cache;
pub mod error;
mod filter;
Expand All @@ -13,6 +14,7 @@ mod signer;
pub(crate) mod utils;

pub use api::{EthApi, EthApiSpec, EthTransactions, TransactionSource, RPC_DEFAULT_GAS_CAP};
pub use bundle::EthBundle;
pub use filter::EthFilter;
pub use id_provider::EthSubscriptionIdProvider;
pub use pubsub::EthPubSub;

0 comments on commit 3e48a91

Please sign in to comment.