From 2c2098c05c2e695d96049c2552ff855a9801e6a0 Mon Sep 17 00:00:00 2001 From: jn Date: Mon, 8 Jul 2024 08:59:17 -0700 Subject: [PATCH] feat: move mev rpc types to alloy (#9108) Co-authored-by: Matthias Seitz --- Cargo.lock | 14 + Cargo.toml | 1 + crates/rpc/rpc-api/src/mev.rs | 2 +- crates/rpc/rpc-eth-api/src/bundle.rs | 2 +- crates/rpc/rpc-types/Cargo.toml | 7 +- crates/rpc/rpc-types/src/lib.rs | 11 +- crates/rpc/rpc-types/src/mev.rs | 1047 -------------------------- crates/rpc/rpc-types/src/rpc.rs | 1 + crates/rpc/rpc/src/eth/bundle.rs | 4 +- 9 files changed, 30 insertions(+), 1059 deletions(-) delete mode 100644 crates/rpc/rpc-types/src/mev.rs diff --git a/Cargo.lock b/Cargo.lock index 9d0406630e1f..5515adabba80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -465,6 +465,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "alloy-rpc-types-mev" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd8624e01721deacad6bc9af75abdf2e99d248df0e1ad5f3f0bda0b3c1d50fd" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", + "serde_json", +] + [[package]] name = "alloy-rpc-types-trace" version = "0.1.4" @@ -8474,6 +8487,7 @@ dependencies = [ "alloy-rpc-types-anvil", "alloy-rpc-types-beacon", "alloy-rpc-types-engine", + "alloy-rpc-types-mev", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", "alloy-serde", diff --git a/Cargo.toml b/Cargo.toml index 84b4bfb81afb..ae22ca07874a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -402,6 +402,7 @@ alloy-rpc-types-txpool = { version = "0.1", default-features = false } alloy-serde = { version = "0.1", default-features = false } alloy-rpc-types-engine = { version = "0.1", default-features = false } alloy-rpc-types-eth = { version = "0.1", default-features = false } +alloy-rpc-types-mev = { version = "0.1", default-features = false } alloy-rpc-types-trace = { version = "0.1", default-features = false } alloy-genesis = { version = "0.1", default-features = false } alloy-node-bindings = { version = "0.1", default-features = false } diff --git a/crates/rpc/rpc-api/src/mev.rs b/crates/rpc/rpc-api/src/mev.rs index 008535276328..ebe6f5ee8708 100644 --- a/crates/rpc/rpc-api/src/mev.rs +++ b/crates/rpc/rpc-api/src/mev.rs @@ -1,5 +1,5 @@ use jsonrpsee::proc_macros::rpc; -use reth_rpc_types::{ +use reth_rpc_types::mev::{ SendBundleRequest, SendBundleResponse, SimBundleOverrides, SimBundleResponse, }; diff --git a/crates/rpc/rpc-eth-api/src/bundle.rs b/crates/rpc/rpc-eth-api/src/bundle.rs index f657e30c430c..bf3a623df2f1 100644 --- a/crates/rpc/rpc-eth-api/src/bundle.rs +++ b/crates/rpc/rpc-eth-api/src/bundle.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; use reth_primitives::{Bytes, B256}; -use reth_rpc_types::{ +use reth_rpc_types::mev::{ CancelBundleRequest, CancelPrivateTransactionRequest, EthBundleHash, EthCallBundle, EthCallBundleResponse, EthSendBundle, PrivateTransactionRequest, }; diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index 7a6e081a24ff..fa5c8b79c9ae 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -16,17 +16,17 @@ workspace = true # ethereum alloy-primitives = { workspace = true, features = ["rand", "rlp", "serde"] } alloy-rpc-types = { workspace = true, features = ["jsonrpsee-types"] } +alloy-rpc-types-admin.workspace = true alloy-rpc-types-anvil.workspace = true -alloy-rpc-types-trace.workspace = true alloy-rpc-types-beacon.workspace = true -alloy-rpc-types-admin.workspace = true +alloy-rpc-types-mev.workspace = true +alloy-rpc-types-trace.workspace = true alloy-rpc-types-txpool.workspace = true alloy-serde.workspace = true alloy-rpc-types-engine = { workspace = true, features = ["jsonrpsee-types"] } # misc serde = { workspace = true, features = ["derive"] } -serde_json.workspace = true jsonrpsee-types = { workspace = true, optional = true } [dev-dependencies] @@ -38,6 +38,7 @@ proptest-derive.workspace = true rand.workspace = true similar-asserts.workspace = true bytes.workspace = true +serde_json.workspace = true [features] default = ["jsonrpsee-types"] diff --git a/crates/rpc/rpc-types/src/lib.rs b/crates/rpc/rpc-types/src/lib.rs index 5df802da09fa..7f578ab29400 100644 --- a/crates/rpc/rpc-types/src/lib.rs +++ b/crates/rpc/rpc-types/src/lib.rs @@ -11,7 +11,6 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #[allow(hidden_glob_reexports)] mod eth; -mod mev; mod peer; mod rpc; @@ -29,15 +28,18 @@ pub mod trace { pub use alloy_rpc_types_trace::*; } +// re-export admin +pub use alloy_rpc_types_admin as admin; + // Anvil specific rpc types coming from alloy. pub use alloy_rpc_types_anvil as anvil; +// re-export mev +pub use alloy_rpc_types_mev as mev; + // re-export beacon pub use alloy_rpc_types_beacon as beacon; -// re-export admin -pub use alloy_rpc_types_admin as admin; - // re-export txpool pub use alloy_rpc_types_txpool as txpool; @@ -51,6 +53,5 @@ pub use eth::{ transaction::{self, TransactionRequest, TypedTransactionRequest}, }; -pub use mev::*; pub use peer::*; pub use rpc::*; diff --git a/crates/rpc/rpc-types/src/mev.rs b/crates/rpc/rpc-types/src/mev.rs deleted file mode 100644 index 20c92f1a6cd8..000000000000 --- a/crates/rpc/rpc-types/src/mev.rs +++ /dev/null @@ -1,1047 +0,0 @@ -//! MEV bundle type bindings - -use crate::{BlockId, BlockNumberOrTag, Log}; -use alloy_primitives::{Address, Bytes, TxHash, B256, U256}; -use serde::{ - ser::{SerializeSeq, Serializer}, - Deserialize, Deserializer, Serialize, -}; -/// A bundle of transactions to send to the matchmaker. -/// -/// Note: this is for `mev_sendBundle` and not `eth_sendBundle`. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SendBundleRequest { - /// The version of the MEV-share API to use. - #[serde(rename = "version")] - pub protocol_version: ProtocolVersion, - /// Data used by block builders to check if the bundle should be considered for inclusion. - #[serde(rename = "inclusion")] - pub inclusion: Inclusion, - /// The transactions to include in the bundle. - #[serde(rename = "body")] - pub bundle_body: Vec, - /// Requirements for the bundle to be included in the block. - #[serde(rename = "validity", skip_serializing_if = "Option::is_none")] - pub validity: Option, - /// Preferences on what data should be shared about the bundle and its transactions - #[serde(rename = "privacy", skip_serializing_if = "Option::is_none")] - pub privacy: Option, -} - -/// Data used by block builders to check if the bundle should be considered for inclusion. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Inclusion { - /// The first block the bundle is valid for. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub block: u64, - /// The last block the bundle is valid for. - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub max_block: Option, -} - -impl Inclusion { - /// Creates a new inclusion with the given min block.. - pub const fn at_block(block: u64) -> Self { - Self { block, max_block: None } - } - - /// Returns the block number of the first block the bundle is valid for. - #[inline] - pub const fn block_number(&self) -> u64 { - self.block - } - - /// Returns the block number of the last block the bundle is valid for. - #[inline] - pub fn max_block_number(&self) -> Option { - self.max_block.as_ref().map(|b| *b) - } -} - -/// A bundle tx, which can either be a transaction hash, or a full tx. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(untagged)] -#[serde(rename_all = "camelCase")] -pub enum BundleItem { - /// The hash of either a transaction or bundle we are trying to backrun. - Hash { - /// Tx hash. - hash: TxHash, - }, - /// A new signed transaction. - #[serde(rename_all = "camelCase")] - Tx { - /// Bytes of the signed transaction. - tx: Bytes, - /// If true, the transaction can revert without the bundle being considered invalid. - can_revert: bool, - }, -} - -/// Requirements for the bundle to be included in the block. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Validity { - /// Specifies the minimum percent of a given bundle's earnings to redistribute - /// for it to be included in a builder's block. - #[serde(skip_serializing_if = "Option::is_none")] - pub refund: Option>, - /// Specifies what addresses should receive what percent of the overall refund for this bundle, - /// if it is enveloped by another bundle (eg. a searcher backrun). - #[serde(skip_serializing_if = "Option::is_none")] - pub refund_config: Option>, -} - -/// Specifies the minimum percent of a given bundle's earnings to redistribute -/// for it to be included in a builder's block. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Refund { - /// The index of the transaction in the bundle. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub body_idx: u64, - /// The minimum percent of the bundle's earnings to redistribute. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub percent: u64, -} - -/// Specifies what addresses should receive what percent of the overall refund for this bundle, -/// if it is enveloped by another bundle (eg. a searcher backrun). -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct RefundConfig { - /// The address to refund. - pub address: Address, - /// The minimum percent of the bundle's earnings to redistribute. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub percent: u64, -} - -/// Preferences on what data should be shared about the bundle and its transactions -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct Privacy { - /// Hints on what data should be shared about the bundle and its transactions - #[serde(skip_serializing_if = "Option::is_none")] - pub hints: Option, - /// The addresses of the builders that should be allowed to see the bundle/transaction. - #[serde(skip_serializing_if = "Option::is_none")] - pub builders: Option>, -} - -/// Hints on what data should be shared about the bundle and its transactions -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct PrivacyHint { - /// The calldata of the bundle's transactions should be shared. - pub calldata: bool, - /// The address of the bundle's transactions should be shared. - pub contract_address: bool, - /// The logs of the bundle's transactions should be shared. - pub logs: bool, - /// The function selector of the bundle's transactions should be shared. - pub function_selector: bool, - /// The hash of the bundle's transactions should be shared. - pub hash: bool, - /// The hash of the bundle should be shared. - pub tx_hash: bool, -} - -impl PrivacyHint { - /// Sets the flag indicating inclusion of calldata and returns the modified `PrivacyHint` - /// instance. - pub const fn with_calldata(mut self) -> Self { - self.calldata = true; - self - } - - /// Sets the flag indicating inclusion of contract address and returns the modified - /// `PrivacyHint` instance. - pub const fn with_contract_address(mut self) -> Self { - self.contract_address = true; - self - } - - /// Sets the flag indicating inclusion of logs and returns the modified `PrivacyHint` instance. - pub const fn with_logs(mut self) -> Self { - self.logs = true; - self - } - - /// Sets the flag indicating inclusion of function selector and returns the modified - /// `PrivacyHint` instance. - pub const fn with_function_selector(mut self) -> Self { - self.function_selector = true; - self - } - - /// Sets the flag indicating inclusion of hash and returns the modified `PrivacyHint` instance. - pub const fn with_hash(mut self) -> Self { - self.hash = true; - self - } - - /// Sets the flag indicating inclusion of transaction hash and returns the modified - /// `PrivacyHint` instance. - pub const fn with_tx_hash(mut self) -> Self { - self.tx_hash = true; - self - } - - /// Checks if calldata inclusion flag is set. - pub const fn has_calldata(&self) -> bool { - self.calldata - } - - /// Checks if contract address inclusion flag is set. - pub const fn has_contract_address(&self) -> bool { - self.contract_address - } - - /// Checks if logs inclusion flag is set. - pub const fn has_logs(&self) -> bool { - self.logs - } - - /// Checks if function selector inclusion flag is set. - pub const fn has_function_selector(&self) -> bool { - self.function_selector - } - - /// Checks if hash inclusion flag is set. - pub const fn has_hash(&self) -> bool { - self.hash - } - - /// Checks if transaction hash inclusion flag is set. - pub const fn has_tx_hash(&self) -> bool { - self.tx_hash - } - - /// Calculates the number of hints set within the `PrivacyHint` instance. - const fn num_hints(&self) -> usize { - let mut num_hints = 0; - if self.calldata { - num_hints += 1; - } - if self.contract_address { - num_hints += 1; - } - if self.logs { - num_hints += 1; - } - if self.function_selector { - num_hints += 1; - } - if self.hash { - num_hints += 1; - } - if self.tx_hash { - num_hints += 1; - } - num_hints - } -} - -impl Serialize for PrivacyHint { - fn serialize(&self, serializer: S) -> Result { - let mut seq = serializer.serialize_seq(Some(self.num_hints()))?; - if self.calldata { - seq.serialize_element("calldata")?; - } - if self.contract_address { - seq.serialize_element("contract_address")?; - } - if self.logs { - seq.serialize_element("logs")?; - } - if self.function_selector { - seq.serialize_element("function_selector")?; - } - if self.hash { - seq.serialize_element("hash")?; - } - if self.tx_hash { - seq.serialize_element("tx_hash")?; - } - seq.end() - } -} - -impl<'de> Deserialize<'de> for PrivacyHint { - fn deserialize>(deserializer: D) -> Result { - let hints = Vec::::deserialize(deserializer)?; - let mut privacy_hint = Self::default(); - for hint in hints { - match hint.as_str() { - "calldata" => privacy_hint.calldata = true, - "contract_address" => privacy_hint.contract_address = true, - "logs" => privacy_hint.logs = true, - "function_selector" => privacy_hint.function_selector = true, - "hash" => privacy_hint.hash = true, - "tx_hash" => privacy_hint.tx_hash = true, - _ => return Err(serde::de::Error::custom("invalid privacy hint")), - } - } - Ok(privacy_hint) - } -} - -/// Response from the matchmaker after sending a bundle. -#[derive(Deserialize, Debug, Serialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SendBundleResponse { - /// Hash of the bundle bodies. - pub bundle_hash: B256, -} - -/// The version of the MEV-share API to use. -#[derive(Deserialize, Debug, Serialize, Clone, Default, PartialEq, Eq)] -pub enum ProtocolVersion { - #[default] - #[serde(rename = "beta-1")] - /// The beta-1 version of the API. - Beta1, - /// The 0.1 version of the API. - #[serde(rename = "v0.1")] - V0_1, -} - -/// Optional fields to override simulation state. -#[derive(Deserialize, Debug, Serialize, Clone, Default, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SimBundleOverrides { - /// Block used for simulation state. Defaults to latest block. - /// Block header data will be derived from parent block by default. - /// Specify other params to override the default values. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub parent_block: Option, - /// Block number used for simulation, defaults to parentBlock.number + 1 - #[serde(default, with = "alloy_rpc_types::serde_helpers::quantity::opt")] - pub block_number: Option, - /// Coinbase used for simulation, defaults to parentBlock.coinbase - #[serde(default, skip_serializing_if = "Option::is_none")] - pub coinbase: Option
, - /// Timestamp used for simulation, defaults to parentBlock.timestamp + 12 - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub timestamp: Option, - /// Gas limit used for simulation, defaults to parentBlock.gasLimit - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub gas_limit: Option, - /// Base fee used for simulation, defaults to parentBlock.baseFeePerGas - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub base_fee: Option, - /// Timeout in seconds, defaults to 5 - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub timeout: Option, -} - -/// Response from the matchmaker after sending a simulation request. -#[derive(Deserialize, Debug, Serialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SimBundleResponse { - /// Whether the simulation was successful. - pub success: bool, - /// Error message if the simulation failed. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub error: Option, - /// The block number of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub state_block: u64, - /// The gas price of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub mev_gas_price: u64, - /// The profit of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub profit: u64, - /// The refundable value of the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub refundable_value: u64, - /// The gas used by the simulated block. - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub gas_used: u64, - /// Logs returned by `mev_simBundle`. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub logs: Option>, -} - -/// Logs returned by `mev_simBundle`. -#[derive(Deserialize, Debug, Serialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SimBundleLogs { - /// Logs for transactions in bundle. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub tx_logs: Option>, - /// Logs for bundles in bundle. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub bundle_logs: Option>, -} - -impl SendBundleRequest { - /// Create a new bundle request. - pub const fn new( - block_num: u64, - max_block: Option, - protocol_version: ProtocolVersion, - bundle_body: Vec, - ) -> Self { - Self { - protocol_version, - inclusion: Inclusion { block: block_num, max_block }, - bundle_body, - validity: None, - privacy: None, - } - } -} - -/// Request for `eth_cancelBundle` -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct CancelBundleRequest { - /// Bundle hash of the bundle to be canceled - pub bundle_hash: String, -} - -/// Request for `eth_sendPrivateTransaction` -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct PrivateTransactionRequest { - /// raw signed transaction - pub tx: Bytes, - /// Hex-encoded number string, optional. Highest block number in which the transaction should - /// be included. - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub max_block_number: Option, - /// Preferences for private transaction. - #[serde(default, skip_serializing_if = "PrivateTransactionPreferences::is_empty")] - pub preferences: PrivateTransactionPreferences, -} - -/// Additional preferences for `eth_sendPrivateTransaction` -#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] -pub struct PrivateTransactionPreferences { - /// Requirements for the bundle to be included in the block. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub validity: Option, - /// Preferences on what data should be shared about the bundle and its transactions - #[serde(default, skip_serializing_if = "Option::is_none")] - pub privacy: Option, -} - -impl PrivateTransactionPreferences { - /// Returns true if the preferences are empty. - pub const fn is_empty(&self) -> bool { - self.validity.is_none() && self.privacy.is_none() - } -} - -/// Request for `eth_cancelPrivateTransaction` -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct CancelPrivateTransactionRequest { - /// Transaction hash of the transaction to be canceled - pub tx_hash: B256, -} - -// TODO(@optimiz-r): Revisit after is closed. -/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle -/// -/// Note: this is V2: -/// -/// Timestamp format: "2022-10-06T21:36:06.322Z" -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub enum BundleStats { - /// The relayer has not yet seen the bundle. - #[default] - Unknown, - /// The relayer has seen the bundle, but has not simulated it yet. - Seen(StatsSeen), - /// The relayer has seen the bundle and has simulated it. - Simulated(StatsSimulated), -} - -impl Serialize for BundleStats { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Unknown => serde_json::json!({"isSimulated": false}).serialize(serializer), - Self::Seen(stats) => stats.serialize(serializer), - Self::Simulated(stats) => stats.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for BundleStats { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let map = serde_json::Map::deserialize(deserializer)?; - - if map.get("receivedAt").is_none() { - Ok(Self::Unknown) - } else if map["isSimulated"] == false { - StatsSeen::deserialize(serde_json::Value::Object(map)) - .map(BundleStats::Seen) - .map_err(serde::de::Error::custom) - } else { - StatsSimulated::deserialize(serde_json::Value::Object(map)) - .map(BundleStats::Simulated) - .map_err(serde::de::Error::custom) - } - } -} - -/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle -/// -/// Note: this is V2: -/// -/// Timestamp format: "2022-10-06T21:36:06.322Z -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct StatsSeen { - /// boolean representing if this searcher has a high enough reputation to be in the high - /// priority queue - pub is_high_priority: bool, - /// representing whether the bundle gets simulated. All other fields will be omitted except - /// simulated field if API didn't receive bundle - pub is_simulated: bool, - /// time at which the bundle API received the bundle - pub received_at: String, -} - -/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle -/// -/// Note: this is V2: -/// -/// Timestamp format: "2022-10-06T21:36:06.322Z -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct StatsSimulated { - /// boolean representing if this searcher has a high enough reputation to be in the high - /// priority queue - pub is_high_priority: bool, - /// representing whether the bundle gets simulated. All other fields will be omitted except - /// simulated field if API didn't receive bundle - pub is_simulated: bool, - /// time at which the bundle gets simulated - pub simulated_at: String, - /// time at which the bundle API received the bundle - pub received_at: String, - /// indicates time at which each builder selected the bundle to be included in the target - /// block - #[serde(default = "Vec::new")] - pub considered_by_builders_at: Vec, - /// indicates time at which each builder sealed a block containing the bundle - #[serde(default = "Vec::new")] - pub sealed_by_builders_at: Vec, -} - -/// Represents information about when a bundle was considered by a builder. -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ConsideredByBuildersAt { - /// The public key of the builder. - pub pubkey: String, - /// The timestamp indicating when the bundle was considered by the builder. - pub timestamp: String, -} - -/// Represents information about when a bundle was sealed by a builder. -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SealedByBuildersAt { - /// The public key of the builder. - pub pubkey: String, - /// The timestamp indicating when the bundle was sealed by the builder. - pub timestamp: String, -} - -/// Response for `flashbots_getUserStatsV2` represents stats for a searcher. -/// -/// Note: this is V2: -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UserStats { - /// Represents whether this searcher has a high enough reputation to be in the high priority - /// queue. - pub is_high_priority: bool, - /// The total amount paid to validators over all time. - #[serde(with = "u256_numeric_string")] - pub all_time_validator_payments: U256, - /// The total amount of gas simulated across all bundles submitted to Flashbots. - /// This is the actual gas used in simulations, not gas limit. - #[serde(with = "u256_numeric_string")] - pub all_time_gas_simulated: U256, - /// The total amount paid to validators the last 7 days. - #[serde(with = "u256_numeric_string")] - pub last_7d_validator_payments: U256, - /// The total amount of gas simulated across all bundles submitted to Flashbots in the last 7 - /// days. This is the actual gas used in simulations, not gas limit. - #[serde(with = "u256_numeric_string")] - pub last_7d_gas_simulated: U256, - /// The total amount paid to validators the last day. - #[serde(with = "u256_numeric_string")] - pub last_1d_validator_payments: U256, - /// The total amount of gas simulated across all bundles submitted to Flashbots in the last - /// day. This is the actual gas used in simulations, not gas limit. - #[serde(with = "u256_numeric_string")] - pub last_1d_gas_simulated: U256, -} - -/// Bundle of transactions for `eth_sendBundle` -/// -/// Note: this is for `eth_sendBundle` and not `mev_sendBundle` -/// -/// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EthSendBundle { - /// A list of hex-encoded signed transactions - pub txs: Vec, - /// hex-encoded block number for which this bundle is valid - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub block_number: u64, - /// unix timestamp when this bundle becomes active - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub min_timestamp: Option, - /// unix timestamp how long this bundle stays valid - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub max_timestamp: Option, - /// list of hashes of possibly reverting txs - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub reverting_tx_hashes: Vec, - /// UUID that can be used to cancel/replace this bundle - #[serde(default, rename = "replacementUuid", skip_serializing_if = "Option::is_none")] - pub replacement_uuid: Option, -} - -/// Response from the matchmaker after sending a bundle. -#[derive(Deserialize, Debug, Serialize, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct EthBundleHash { - /// Hash of the bundle bodies. - pub bundle_hash: B256, -} - -/// Bundle of transactions for `eth_callBundle` -/// -/// -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EthCallBundle { - /// A list of hex-encoded signed transactions - pub txs: Vec, - /// hex encoded block number for which this bundle is valid on - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - 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: BlockNumberOrTag, - /// the timestamp to use for this bundle simulation, in seconds since the unix epoch - #[serde( - default, - with = "alloy_rpc_types::serde_helpers::quantity::opt", - skip_serializing_if = "Option::is_none" - )] - pub timestamp: Option, -} - -/// Response for `eth_callBundle` -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct EthCallBundleResponse { - /// The hash of the bundle bodies. - pub bundle_hash: B256, - /// The gas price of the entire bundle - #[serde(with = "u256_numeric_string")] - pub bundle_gas_price: U256, - /// The difference in Ether sent to the coinbase after all transactions in the bundle - #[serde(with = "u256_numeric_string")] - pub coinbase_diff: U256, - /// The total amount of Ether sent to the coinbase after all transactions in the bundle - #[serde(with = "u256_numeric_string")] - pub eth_sent_to_coinbase: U256, - /// The total gas fees paid for all transactions in the bundle - #[serde(with = "u256_numeric_string")] - pub gas_fees: U256, - /// Results of individual transactions within the bundle - pub results: Vec, - /// The block number used as a base for this simulation - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub state_block_number: u64, - /// The total gas used by all transactions in the bundle - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub total_gas_used: u64, -} - -/// Result of a single transaction in a bundle for `eth_callBundle` -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EthCallBundleTransactionResult { - /// The difference in Ether sent to the coinbase after the transaction - #[serde(with = "u256_numeric_string")] - pub coinbase_diff: U256, - /// The amount of Ether sent to the coinbase after the transaction - #[serde(with = "u256_numeric_string")] - pub eth_sent_to_coinbase: U256, - /// The address from which the transaction originated - pub from_address: Address, - /// The gas fees paid for the transaction - #[serde(with = "u256_numeric_string")] - pub gas_fees: U256, - /// The gas price used for the transaction - #[serde(with = "u256_numeric_string")] - pub gas_price: U256, - /// The amount of gas used by the transaction - #[serde(with = "alloy_rpc_types::serde_helpers::quantity")] - pub gas_used: u64, - /// The address to which the transaction is sent (optional) - pub to_address: Option
, - /// The transaction hash - pub tx_hash: B256, - /// 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, - /// Contains the return data if the transaction reverted - #[serde(skip_serializing_if = "Option::is_none")] - pub revert: Option, -} - -mod u256_numeric_string { - use alloy_primitives::U256; - use serde::{de, Deserialize, Serializer}; - use std::str::FromStr; - - pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - let val = serde_json::Value::deserialize(deserializer)?; - match val { - serde_json::Value::String(s) => { - if let Ok(val) = s.parse::() { - return Ok(U256::from(val)) - } - U256::from_str(&s).map_err(de::Error::custom) - } - serde_json::Value::Number(num) => { - num.as_u64().map(U256::from).ok_or_else(|| de::Error::custom("invalid u256")) - } - _ => Err(de::Error::custom("invalid u256")), - } - } - - pub(crate) fn serialize(val: &U256, serializer: S) -> Result - where - S: Serializer, - { - let val: u128 = (*val).try_into().map_err(serde::ser::Error::custom)?; - serializer.serialize_str(&val.to_string()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::str::FromStr; - - #[test] - fn can_deserialize_simple() { - let str = r#" - [{ - "version": "v0.1", - "inclusion": { - "block": "0x1" - }, - "body": [{ - "tx": "0x02f86b0180843b9aca00852ecc889a0082520894c87037874aed04e51c29f582394217a0a2b89d808080c080a0a463985c616dd8ee17d7ef9112af4e6e06a27b071525b42182fe7b0b5c8b4925a00af5ca177ffef2ff28449292505d41be578bebb77110dfc09361d2fb56998260", - "canRevert": false - }] - }] - "#; - let res: Result, _> = serde_json::from_str(str); - assert!(res.is_ok()); - } - - #[test] - fn can_deserialize_complex() { - let str = r#" - [{ - "version": "v0.1", - "inclusion": { - "block": "0x1" - }, - "body": [{ - "tx": "0x02f86b0180843b9aca00852ecc889a0082520894c87037874aed04e51c29f582394217a0a2b89d808080c080a0a463985c616dd8ee17d7ef9112af4e6e06a27b071525b42182fe7b0b5c8b4925a00af5ca177ffef2ff28449292505d41be578bebb77110dfc09361d2fb56998260", - "canRevert": false - }], - "privacy": { - "hints": [ - "calldata" - ] - }, - "validity": { - "refundConfig": [ - { - "address": "0x8EC1237b1E80A6adf191F40D4b7D095E21cdb18f", - "percent": 100 - } - ] - } - }] - "#; - let res: Result, _> = serde_json::from_str(str); - assert!(res.is_ok()); - } - - #[test] - fn can_serialize_complex() { - let str = r#" - [{ - "version": "v0.1", - "inclusion": { - "block": "0x1" - }, - "body": [{ - "tx": "0x02f86b0180843b9aca00852ecc889a0082520894c87037874aed04e51c29f582394217a0a2b89d808080c080a0a463985c616dd8ee17d7ef9112af4e6e06a27b071525b42182fe7b0b5c8b4925a00af5ca177ffef2ff28449292505d41be578bebb77110dfc09361d2fb56998260", - "canRevert": false - }], - "privacy": { - "hints": [ - "calldata" - ] - }, - "validity": { - "refundConfig": [ - { - "address": "0x8EC1237b1E80A6adf191F40D4b7D095E21cdb18f", - "percent": 100 - } - ] - } - }] - "#; - let bundle_body = vec![BundleItem::Tx { - tx: Bytes::from_str("0x02f86b0180843b9aca00852ecc889a0082520894c87037874aed04e51c29f582394217a0a2b89d808080c080a0a463985c616dd8ee17d7ef9112af4e6e06a27b071525b42182fe7b0b5c8b4925a00af5ca177ffef2ff28449292505d41be578bebb77110dfc09361d2fb56998260").unwrap(), - can_revert: false, - }]; - - let validity = Some(Validity { - refund_config: Some(vec![RefundConfig { - address: "0x8EC1237b1E80A6adf191F40D4b7D095E21cdb18f".parse().unwrap(), - percent: 100, - }]), - ..Default::default() - }); - - let privacy = Some(Privacy { - hints: Some(PrivacyHint { calldata: true, ..Default::default() }), - ..Default::default() - }); - - let bundle = SendBundleRequest { - protocol_version: ProtocolVersion::V0_1, - inclusion: Inclusion { block: 1, max_block: None }, - bundle_body, - validity, - privacy, - }; - let expected = serde_json::from_str::>(str).unwrap(); - assert_eq!(bundle, expected[0]); - } - - #[test] - fn can_serialize_privacy_hint() { - let hint = PrivacyHint { - calldata: true, - contract_address: true, - logs: true, - function_selector: true, - hash: true, - tx_hash: true, - }; - let expected = - r#"["calldata","contract_address","logs","function_selector","hash","tx_hash"]"#; - let actual = serde_json::to_string(&hint).unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn can_deserialize_privacy_hint() { - let hint = PrivacyHint { - calldata: true, - contract_address: false, - logs: true, - function_selector: false, - hash: true, - tx_hash: false, - }; - let expected = r#"["calldata","logs","hash"]"#; - let actual: PrivacyHint = serde_json::from_str(expected).unwrap(); - assert_eq!(actual, hint); - } - - #[test] - fn can_dererialize_sim_response() { - let expected = r#" - { - "success": true, - "stateBlock": "0x8b8da8", - "mevGasPrice": "0x74c7906005", - "profit": "0x4bc800904fc000", - "refundableValue": "0x4bc800904fc000", - "gasUsed": "0xa620", - "logs": [{},{}] - } - "#; - let actual: SimBundleResponse = serde_json::from_str(expected).unwrap(); - assert!(actual.success); - } - - #[test] - fn can_deserialize_eth_call_resp() { - let s = r#"{ - "bundleGasPrice": "476190476193", - "bundleHash": "0x73b1e258c7a42fd0230b2fd05529c5d4b6fcb66c227783f8bece8aeacdd1db2e", - "coinbaseDiff": "20000000000126000", - "ethSentToCoinbase": "20000000000000000", - "gasFees": "126000", - "results": [ - { - "coinbaseDiff": "10000000000063000", - "ethSentToCoinbase": "10000000000000000", - "fromAddress": "0x02A727155aeF8609c9f7F2179b2a1f560B39F5A0", - "gasFees": "63000", - "gasPrice": "476190476193", - "gasUsed": 21000, - "toAddress": "0x73625f59CAdc5009Cb458B751b3E7b6b48C06f2C", - "txHash": "0x669b4704a7d993a946cdd6e2f95233f308ce0c4649d2e04944e8299efcaa098a", - "value": "0x" - }, - { - "coinbaseDiff": "10000000000063000", - "ethSentToCoinbase": "10000000000000000", - "fromAddress": "0x02A727155aeF8609c9f7F2179b2a1f560B39F5A0", - "gasFees": "63000", - "gasPrice": "476190476193", - "gasUsed": 21000, - "toAddress": "0x73625f59CAdc5009Cb458B751b3E7b6b48C06f2C", - "txHash": "0xa839ee83465657cac01adc1d50d96c1b586ed498120a84a64749c0034b4f19fa", - "value": "0x" - } - ], - "stateBlockNumber": 5221585, - "totalGasUsed": 42000 - }"#; - - let _call = serde_json::from_str::(s).unwrap(); - } - - #[test] - fn can_serialize_deserialize_bundle_stats() { - let fixtures = [ - ( - r#"{ - "isSimulated": false - }"#, - BundleStats::Unknown, - ), - ( - r#"{ - "isHighPriority": false, - "isSimulated": false, - "receivedAt": "476190476193" - }"#, - BundleStats::Seen(StatsSeen { - is_high_priority: false, - is_simulated: false, - received_at: "476190476193".to_string(), - }), - ), - ( - r#"{ - "isHighPriority": true, - "isSimulated": true, - "simulatedAt": "111", - "receivedAt": "222", - "consideredByBuildersAt":[], - "sealedByBuildersAt": [ - { - "pubkey": "333", - "timestamp": "444" - }, - { - "pubkey": "555", - "timestamp": "666" - } - ] - }"#, - BundleStats::Simulated(StatsSimulated { - is_high_priority: true, - is_simulated: true, - simulated_at: String::from("111"), - received_at: String::from("222"), - considered_by_builders_at: vec![], - sealed_by_builders_at: vec![ - SealedByBuildersAt { - pubkey: String::from("333"), - timestamp: String::from("444"), - }, - SealedByBuildersAt { - pubkey: String::from("555"), - timestamp: String::from("666"), - }, - ], - }), - ), - ]; - - let strip_whitespaces = - |input: &str| input.chars().filter(|&c| !c.is_whitespace()).collect::(); - - for (serialized, deserialized) in fixtures { - // Check de-serialization - let deserialized_expected = serde_json::from_str::(serialized).unwrap(); - assert_eq!(deserialized, deserialized_expected); - - // Check serialization - let serialized_expected = &serde_json::to_string(&deserialized).unwrap(); - assert_eq!(strip_whitespaces(serialized), strip_whitespaces(serialized_expected)); - } - } -} diff --git a/crates/rpc/rpc-types/src/rpc.rs b/crates/rpc/rpc-types/src/rpc.rs index bb5ae5d77fbb..0b9afeb79a67 100644 --- a/crates/rpc/rpc-types/src/rpc.rs +++ b/crates/rpc/rpc-types/src/rpc.rs @@ -24,6 +24,7 @@ impl RpcModules { #[cfg(test)] mod tests { use super::*; + #[test] fn test_parse_module_versions_roundtrip() { let s = r#"{"txpool":"1.0","trace":"1.0","eth":"1.0","web3":"1.0","net":"1.0"}"#; diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index 894f2f80404b..aab706b28b23 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -10,7 +10,7 @@ use reth_primitives::{ PooledTransactionsElement, U256, }; use reth_revm::database::StateProviderDatabase; -use reth_rpc_types::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult}; +use reth_rpc_types::mev::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult}; use reth_tasks::pool::BlockingTaskGuard; use revm::{ db::CacheDB, @@ -48,7 +48,7 @@ where /// 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 { - let EthCallBundle { txs, block_number, state_block_number, timestamp } = bundle; + let EthCallBundle { txs, block_number, state_block_number, timestamp, .. } = bundle; if txs.is_empty() { return Err(EthApiError::InvalidParams( EthBundleError::EmptyBundleTransactions.to_string(),