From 0582d5c396951dd8eed998a71ea9cb4dd3627996 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Wed, 10 Apr 2024 12:50:54 +0400 Subject: [PATCH 01/25] chore: add serde and alloy_primitives to the dependencies --- Cargo.toml | 7 +++++++ crates/op-rpc-types/Cargo.toml | 4 ++++ crates/op-rpc-types/src/lib.rs | 1 - 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3275ef83..87e1d436 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,13 @@ homepage = "https://github.com/alloy-rs/op-alloy" repository = "https://github.com/alloy-rs/op-alloy" exclude = ["benches/", "tests/"] +[workspace.dependencies] +# Alloy +alloy-primitives = { version = "0.7.0", default-features = false } + +# Serde +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } + [workspace.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] \ No newline at end of file diff --git a/crates/op-rpc-types/Cargo.toml b/crates/op-rpc-types/Cargo.toml index 3ee5ad6c..7fee2021 100644 --- a/crates/op-rpc-types/Cargo.toml +++ b/crates/op-rpc-types/Cargo.toml @@ -10,3 +10,7 @@ homepage.workspace = true authors.workspace = true repository.workspace = true exclude.workspace = true + +[dependencies] +serde = { workspace = true, features = ["derive"] } +alloy-primitives = { workspace = true, features = ["rlp", "serde", "std"] } diff --git a/crates/op-rpc-types/src/lib.rs b/crates/op-rpc-types/src/lib.rs index 8b137891..e69de29b 100644 --- a/crates/op-rpc-types/src/lib.rs +++ b/crates/op-rpc-types/src/lib.rs @@ -1 +0,0 @@ - From dbdcda9b6f1a6475abdc150c7080cb8fcc73209e Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Wed, 10 Apr 2024 14:06:21 +0400 Subject: [PATCH 02/25] feat: add transaction receipt type without tests + several dependencies. --- Cargo.toml | 10 ++ crates/op-rpc-types/Cargo.toml | 31 +++- crates/op-rpc-types/src/lib.rs | 1 + crates/op-rpc-types/src/op/mod.rs | 1 + crates/op-rpc-types/src/op/transaction/mod.rs | 1 + .../src/op/transaction/receipt.rs | 140 ++++++++++++++++++ 6 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 crates/op-rpc-types/src/op/mod.rs create mode 100644 crates/op-rpc-types/src/op/transaction/mod.rs create mode 100644 crates/op-rpc-types/src/op/transaction/receipt.rs diff --git a/Cargo.toml b/Cargo.toml index 87e1d436..af249155 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,19 @@ exclude = ["benches/", "tests/"] [workspace.dependencies] # Alloy alloy-primitives = { version = "0.7.0", default-features = false } +alloy = { git = "https://github.com/alloy-rs/alloy", rev = "89f14f9", features = [ + "serde", +] } # Serde serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +## misc-testing +arbitrary = { version = "1.3", features = ["derive"] } +rand = "0.8" +proptest = "1.4" +proptest-derive = "0.4" [workspace.metadata.docs.rs] all-features = true diff --git a/crates/op-rpc-types/Cargo.toml b/crates/op-rpc-types/Cargo.toml index 7fee2021..aef654e6 100644 --- a/crates/op-rpc-types/Cargo.toml +++ b/crates/op-rpc-types/Cargo.toml @@ -12,5 +12,34 @@ repository.workspace = true exclude.workspace = true [dependencies] -serde = { workspace = true, features = ["derive"] } +# alloy-rlp = { workspace = true, features = ["arrayvec", "derive"] } alloy-primitives = { workspace = true, features = ["rlp", "serde", "std"] } +# alloy-serde.workspace = true +# alloy-genesis.workspace = true + +# alloy-consensus = { workspace = true, features = ["std", "serde"] } +# alloy-eips = { workspace = true, features = ["std", "serde"] } + +serde = { workspace = true, features = ["derive"] } +alloy = { workspace = true , features = ["serde"]} +serde_json.workspace = true + + +# arbitrary +arbitrary = { version = "1.3", features = ["derive"], optional = true } +proptest = { version = "1.4", optional = true } +proptest-derive = { version = "0.4", optional = true } + +[features] +arbitrary = [ + "dep:arbitrary", + "dep:proptest-derive", + "dep:proptest", + "alloy-primitives/arbitrary", +] + +[dev-dependencies] +arbitrary = { workspace = true, features = ["derive"] } +proptest.workspace = true +proptest-derive.workspace = true +rand.workspace = true \ No newline at end of file diff --git a/crates/op-rpc-types/src/lib.rs b/crates/op-rpc-types/src/lib.rs index e69de29b..acabf786 100644 --- a/crates/op-rpc-types/src/lib.rs +++ b/crates/op-rpc-types/src/lib.rs @@ -0,0 +1 @@ +mod op; diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs new file mode 100644 index 00000000..8d760e2a --- /dev/null +++ b/crates/op-rpc-types/src/op/mod.rs @@ -0,0 +1 @@ +mod transaction; diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs new file mode 100644 index 00000000..4df93164 --- /dev/null +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -0,0 +1 @@ +mod receipt; diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs new file mode 100644 index 00000000..20b9e727 --- /dev/null +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -0,0 +1,140 @@ +use crate::{Log, WithOtherFields}; +use alloy_consensus::{AnyReceiptEnvelope, ReceiptEnvelope, TxType}; +use alloy_primitives::{Address, B256}; +use serde::{Deserialize, Serialize}; +use alloy::serde as alloy_serde; +/// Transaction receipt +/// +/// This type is generic over an inner [`ReceiptEnvelope`] which contains +/// consensus data and metadata. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr( + any(test, feature = "arbitrary"), + derive(proptest_derive::Arbitrary, arbitrary::Arbitrary) +)] +#[serde(rename_all = "camelCase")] +pub struct TransactionReceipt> { + /// The receipt envelope, which contains the consensus receipt data.. + #[serde(flatten)] + pub inner: T, + /// Transaction Hash. + pub transaction_hash: B256, + /// Index within the block. + #[serde(with = "alloy_serde::u64_hex")] + pub transaction_index: u64, + /// Hash of the block this transaction was included within. + pub block_hash: Option, + /// Number of the block this transaction was included within. + #[serde(with = "alloy_serde::u64_hex_opt")] + pub block_number: Option, + /// Gas used by this transaction alone. + #[serde(with = "alloy_serde::u128_hex_or_decimal")] + pub gas_used: u128, + /// The price paid post-execution by the transaction (i.e. base fee + priority fee). Both + /// fields in 1559-style transactions are maximums (max fee + max priority fee), the amount + /// that's actually paid by users can only be determined post-execution + #[serde(with = "alloy_serde::u128_hex_or_decimal")] + pub effective_gas_price: u128, + /// Blob gas used by the eip-4844 transaction + /// + /// This is None for non eip-4844 transactions + #[serde( + skip_serializing_if = "Option::is_none", + with = "alloy_serde::u128_hex_or_decimal_opt", + default + )] + pub blob_gas_used: Option, + /// The price paid by the eip-4844 transaction per blob gas. + #[serde( + skip_serializing_if = "Option::is_none", + with = "alloy_serde::u128_hex_or_decimal_opt", + default + )] + pub blob_gas_price: Option, + /// Address of the sender + pub from: Address, + /// Address of the receiver. None when its a contract creation transaction. + pub to: Option
, + /// Contract address created, or None if not a deployment. + pub contract_address: Option
, + /// The post-transaction stateroot (pre Byzantium) + /// + /// EIP98 makes this optional field, if it's missing then skip serializing it + #[serde(skip_serializing_if = "Option::is_none", rename = "root")] + pub state_root: Option, + /// The fee associated with a transaction on the Layer 1 + #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] + pub l1_fee: Option, + /// A multiplier applied to the actual gas usage on Layer 1 to calculate the dynamic costs. + #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] + pub l1_fee_scalar: Option, + /// The gas price for transactions on the Layer 1 + #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] + pub l1_gas_price: Option, + /// The amount of gas consumed by a transaction on the Layer 1 + #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] + pub l1_gas_used: Option, +} + +impl AsRef> for TransactionReceipt { + fn as_ref(&self) -> &ReceiptEnvelope { + &self.inner + } +} + +impl TransactionReceipt { + /// Returns the status of the transaction. + pub const fn status(&self) -> bool { + match &self.inner { + ReceiptEnvelope::Eip1559(receipt) + | ReceiptEnvelope::Eip2930(receipt) + | ReceiptEnvelope::Eip4844(receipt) + | ReceiptEnvelope::Legacy(receipt) => receipt.receipt.status, + _ => false, + } + } + + /// Returns the transaction type. + pub const fn transaction_type(&self) -> TxType { + self.inner.tx_type() + } + + /// Calculates the address that will be created by the transaction, if any. + /// + /// Returns `None` if the transaction is not a contract creation (the `to` field is set), or if + /// the `from` field is not set. + pub fn calculate_create_address(&self, nonce: u64) -> Option
{ + if self.to.is_some() { + return None; + } + Some(self.from.create(nonce)) + } +} + +impl TransactionReceipt { + /// Maps the inner receipt value of this receipt. + pub fn map_inner(self, f: F) -> TransactionReceipt + where + F: FnOnce(T) -> U, + { + TransactionReceipt { + inner: f(self.inner), + transaction_hash: self.transaction_hash, + transaction_index: self.transaction_index, + block_hash: self.block_hash, + block_number: self.block_number, + gas_used: self.gas_used, + effective_gas_price: self.effective_gas_price, + blob_gas_used: self.blob_gas_used, + blob_gas_price: self.blob_gas_price, + from: self.from, + to: self.to, + contract_address: self.contract_address, + state_root: self.state_root, + l1_fee: self.l1_fee, + l1_fee_scalar: self.l1_fee_scalar, + l1_gas_price: self.l1_gas_price, + l1_gas_used: self.l1_gas_used, + } + } +} \ No newline at end of file From 1ebd0e082295b5abe82a1851e22928e7404ab2f4 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Wed, 10 Apr 2024 14:18:58 +0400 Subject: [PATCH 03/25] feat: add log --- crates/op-rpc-types/src/op/log.rs | 179 ++++++++++++++++++ crates/op-rpc-types/src/op/mod.rs | 1 + .../src/op/transaction/receipt.rs | 2 +- 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 crates/op-rpc-types/src/op/log.rs diff --git a/crates/op-rpc-types/src/op/log.rs b/crates/op-rpc-types/src/op/log.rs new file mode 100644 index 00000000..ed0e1b9b --- /dev/null +++ b/crates/op-rpc-types/src/op/log.rs @@ -0,0 +1,179 @@ +use alloy_primitives::{LogData, B256}; +use serde::{Deserialize, Serialize}; +use alloy::serde as alloy_serde; + +/// Optimism Log emitted by a transaction +#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] +#[cfg_attr( + any(test, feature = "arbitrary"), + derive(proptest_derive::Arbitrary, arbitrary::Arbitrary) +)] +#[serde(rename_all = "camelCase")] +pub struct Log { + #[serde(flatten)] + /// Consensus log object + pub inner: alloy_primitives::Log, + /// Hash of the block the transaction that emitted this log was mined in + pub block_hash: Option, + /// Number of the block the transaction that emitted this log was mined in + #[serde(with = "alloy_serde::u64_hex_opt")] + pub block_number: Option, + /// The timestamp of the block as proposed in: + /// + /// + #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u64_hex_opt", default)] + pub block_timestamp: Option, + /// Transaction Hash + pub transaction_hash: Option, + /// Index of the Transaction in the block + #[serde(with = "alloy_serde::u64_hex_opt")] + pub transaction_index: Option, + /// Log Index in Block + #[serde(with = "alloy_serde::u64_hex_opt")] + pub log_index: Option, + /// Geth Compatibility Field: whether this log was removed + #[serde(default)] + pub removed: bool, +} + +impl Log { + /// Getter for the address field. Shortcut for `log.inner.address`. + pub const fn address(&self) -> alloy_primitives::Address { + self.inner.address + } + + /// Getter for the data field. Shortcut for `log.inner.data`. + pub const fn data(&self) -> &T { + &self.inner.data + } +} + +impl Log { + /// Getter for the topics field. Shortcut for `log.inner.topics()`. + pub fn topics(&self) -> &[B256] { + self.inner.topics() + } + + /// Get the topic list, mutably. This gives access to the internal + /// array, without allowing extension of that array. Shortcut for + /// [`LogData::topics_mut`] + pub fn topics_mut(&mut self) -> &mut [B256] { + self.inner.data.topics_mut() + } + + /// Decode the log data into a typed log. + pub fn log_decode(&self) -> alloy_sol_types::Result> { + let decoded = T::decode_log(&self.inner, false)?; + Ok(Log { + inner: decoded, + block_hash: self.block_hash, + block_number: self.block_number, + block_timestamp: self.block_timestamp, + transaction_hash: self.transaction_hash, + transaction_index: self.transaction_index, + log_index: self.log_index, + removed: self.removed, + }) + } +} + +impl Log +where + for<'a> &'a T: Into, +{ + /// Reserialize the data. + pub fn reserialize(&self) -> Log { + Log { + inner: alloy_primitives::Log { + address: self.inner.address, + data: (&self.inner.data).into(), + }, + block_hash: self.block_hash, + block_number: self.block_number, + block_timestamp: self.block_timestamp, + transaction_hash: self.transaction_hash, + transaction_index: self.transaction_index, + log_index: self.log_index, + removed: self.removed, + } + } +} + +impl AsRef> for Log { + fn as_ref(&self) -> &alloy_primitives::Log { + &self.inner + } +} + +impl AsMut> for Log { + fn as_mut(&mut self) -> &mut alloy_primitives::Log { + &mut self.inner + } +} + +impl AsRef for Log { + fn as_ref(&self) -> &T { + &self.inner.data + } +} + +impl AsMut for Log { + fn as_mut(&mut self) -> &mut T { + &mut self.inner.data + } +} + +#[cfg(test)] +mod tests { + use alloy_primitives::{Address, Bytes}; + + use super::*; + use arbitrary::Arbitrary; + use rand::Rng; + + #[test] + fn log_arbitrary() { + let mut bytes = [0u8; 1024]; + rand::thread_rng().fill(bytes.as_mut_slice()); + + let _: Log = Log::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(); + } + + #[test] + fn serde_log() { + let mut log = Log { + inner: alloy_primitives::Log { + address: Address::with_last_byte(0x69), + data: alloy_primitives::LogData::new_unchecked( + vec![B256::with_last_byte(0x69)], + Bytes::from_static(&[0x69]), + ), + }, + block_hash: Some(B256::with_last_byte(0x69)), + block_number: Some(0x69), + block_timestamp: None, + transaction_hash: Some(B256::with_last_byte(0x69)), + transaction_index: Some(0x69), + log_index: Some(0x69), + removed: false, + }; + let serialized = serde_json::to_string(&log).unwrap(); + assert_eq!( + serialized, + r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"# + ); + + let deserialized: Log = serde_json::from_str(&serialized).unwrap(); + assert_eq!(log, deserialized); + + log.block_timestamp = Some(0x69); + let serialized = serde_json::to_string(&log).unwrap(); + assert_eq!( + serialized, + r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","blockTimestamp":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"# + ); + + let deserialized: Log = serde_json::from_str(&serialized).unwrap(); + assert_eq!(log, deserialized); + } +} diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs index 8d760e2a..0536f312 100644 --- a/crates/op-rpc-types/src/op/mod.rs +++ b/crates/op-rpc-types/src/op/mod.rs @@ -1 +1,2 @@ mod transaction; +mod log; \ No newline at end of file diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index 20b9e727..cec0699e 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -1,4 +1,4 @@ -use crate::{Log, WithOtherFields}; +use crate::{op::log::Log, WithOtherFields}; use alloy_consensus::{AnyReceiptEnvelope, ReceiptEnvelope, TxType}; use alloy_primitives::{Address, B256}; use serde::{Deserialize, Serialize}; From 15eb43606a7668688aaffb99c202052bae0092d1 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Wed, 10 Apr 2024 15:36:52 +0400 Subject: [PATCH 04/25] feat: add txtype, deposit nonce, and receipt version. --- .../src/op/transaction/receipt.rs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index cec0699e..1e6a7882 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -3,6 +3,32 @@ use alloy_consensus::{AnyReceiptEnvelope, ReceiptEnvelope, TxType}; use alloy_primitives::{Address, B256}; use serde::{Deserialize, Serialize}; use alloy::serde as alloy_serde; + +/// Transaction Type +/// +/// Currently being used as 2-bit type when encoding it to [`Compact`] on +/// [`crate::TransactionSignedNoHash`]. Adding more transaction types will break the codec and +/// database format. +/// +/// Other required changes when adding a new type can be seen on [PR#3953](https://github.com/paradigmxyz/reth/pull/3953/files). +#[derive_arbitrary(compact)] +#[derive( + Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Hash, +)] +pub enum TxType { + /// Legacy transaction pre EIP-2929 + #[default] + Legacy = 0_isize, + /// AccessList transaction + Eip2930 = 1_isize, + /// Transaction with Priority fee + Eip1559 = 2_isize, + /// Shard Blob Transactions - EIP-4844 + Eip4844 = 3_isize, + /// Optimism Deposit transaction. + Deposit = 126_isize, +} + /// Transaction receipt /// /// This type is generic over an inner [`ReceiptEnvelope`] which contains @@ -74,6 +100,19 @@ pub struct TransactionReceipt> { /// The amount of gas consumed by a transaction on the Layer 1 #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] pub l1_gas_used: Option, + /// Deposit nonce for Optimism deposit transactions + #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u64_hex_opt")] + pub deposit_nonce: Option, + /// Deposit receipt version for Optimism deposit transactions + /// + /// + /// The deposit receipt version was introduced in Canyon to indicate an update to how + /// receipt hashes should be computed when set. The state transition process + /// ensures this is only set for post-Canyon deposit transactions. + /// The value is always equal to `1` when present. + #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u64_hex_opt")] + pub deposit_receipt_version: Option, + pub tx_type: TxType; } impl AsRef> for TransactionReceipt { @@ -89,6 +128,7 @@ impl TransactionReceipt { ReceiptEnvelope::Eip1559(receipt) | ReceiptEnvelope::Eip2930(receipt) | ReceiptEnvelope::Eip4844(receipt) + | ReceiptEnvelope::Deposit(receipt) | ReceiptEnvelope::Legacy(receipt) => receipt.receipt.status, _ => false, } @@ -135,6 +175,8 @@ impl TransactionReceipt { l1_fee_scalar: self.l1_fee_scalar, l1_gas_price: self.l1_gas_price, l1_gas_used: self.l1_gas_used, + deposit_nonce: self.deposit_nonce, + deposit_receipt_version: self.deposit_receipt_version, } } } \ No newline at end of file From 599c9a295cdebc0d1febe1418666aaae2c0eb3f5 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Wed, 10 Apr 2024 15:54:19 +0400 Subject: [PATCH 05/25] feat: add block. --- crates/op-rpc-types/src/op/block.rs | 1516 +++++++++++++++++++++++++++ 1 file changed, 1516 insertions(+) create mode 100644 crates/op-rpc-types/src/op/block.rs diff --git a/crates/op-rpc-types/src/op/block.rs b/crates/op-rpc-types/src/op/block.rs new file mode 100644 index 00000000..ec02f8e5 --- /dev/null +++ b/crates/op-rpc-types/src/op/block.rs @@ -0,0 +1,1516 @@ +//! Block RPC types. + +#![allow(unknown_lints, non_local_definitions)] + +use crate::{other::OtherFields, Transaction, Withdrawal}; +use alloy_eips::{calc_blob_gasprice, calc_excess_blob_gas}; +use alloy_primitives::{ + ruint::ParseError, Address, BlockHash, BlockNumber, Bloom, Bytes, B256, U256, U64, +}; +use alloy_rlp::{bytes, Decodable, Encodable, Error as RlpError}; +use serde::{ + de::{MapAccess, Visitor}, + ser::{Error, SerializeStruct}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{collections::BTreeMap, fmt, num::ParseIntError, ops::Deref, str::FromStr}; + +/// Block representation +#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Block { + /// Header of the block. + #[serde(flatten)] + pub header: Header, + /// Uncles' hashes. + #[serde(default)] + pub uncles: Vec, + /// Block Transactions. In the case of an uncle block, this field is not included in RPC + /// responses, and when deserialized, it will be set to [BlockTransactions::Uncle]. + #[serde( + default = "BlockTransactions::uncle", + skip_serializing_if = "BlockTransactions::is_uncle" + )] + pub transactions: BlockTransactions, + /// Integer the size of this block in bytes. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub size: Option, + /// Withdrawals in the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub withdrawals: Option>, + /// Support for arbitrary additional fields. + #[serde(flatten)] + pub other: OtherFields, +} + +impl Block { + /// Converts a block with Tx hashes into a full block. + pub fn into_full_block(self, txs: Vec) -> Self { + Self { transactions: BlockTransactions::Full(txs), ..self } + } +} + +/// Block header representation. +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[serde(rename_all = "camelCase")] +pub struct Header { + /// Hash of the block + pub hash: Option, + /// Hash of the parent + pub parent_hash: B256, + /// Hash of the uncles + #[serde(rename = "sha3Uncles")] + pub uncles_hash: B256, + /// Alias of `author` + pub miner: Address, + /// State root hash + pub state_root: B256, + /// Transactions root hash + pub transactions_root: B256, + /// Transactions receipts root hash + pub receipts_root: B256, + /// Logs bloom + pub logs_bloom: Bloom, + /// Difficulty + pub difficulty: U256, + /// Block number + #[serde(default, with = "alloy_serde::num::u64_hex_opt")] + pub number: Option, + /// Gas Limit + #[serde(default, with = "alloy_serde::num::u128_hex_or_decimal")] + pub gas_limit: u128, + /// Gas Used + #[serde(default, with = "alloy_serde::num::u128_hex_or_decimal")] + pub gas_used: u128, + /// Timestamp + #[serde(default, with = "alloy_serde::num::u64_hex")] + pub timestamp: u64, + /// Total difficulty + #[serde(skip_serializing_if = "Option::is_none")] + pub total_difficulty: Option, + /// Extra data + pub extra_data: Bytes, + /// Mix Hash + /// + /// Before the merge this proves, combined with the nonce, that a sufficient amount of + /// computation has been carried out on this block: the Proof-of-Work (PoF). + /// + /// After the merge this is `prevRandao`: Randomness value for the generated payload. + /// + /// This is an Option because it is not always set by non-ethereum networks. + /// + /// See also + /// And + #[serde(skip_serializing_if = "Option::is_none")] + pub mix_hash: Option, + /// Nonce + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u64_hex_opt" + )] + pub nonce: Option, + /// Base fee per unit of gas (if past London) + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u128_hex_or_decimal_opt" + )] + pub base_fee_per_gas: Option, + /// Withdrawals root hash added by EIP-4895 and is ignored in legacy headers. + #[serde(skip_serializing_if = "Option::is_none")] + pub withdrawals_root: Option, + /// Blob gas used + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u128_hex_or_decimal_opt" + )] + pub blob_gas_used: Option, + /// Excess blob gas + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u128_hex_or_decimal_opt" + )] + pub excess_blob_gas: Option, + /// Parent beacon block root + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_beacon_block_root: Option, +} + +impl Header { + /// Returns the blob fee for _this_ block according to the EIP-4844 spec. + /// + /// Returns `None` if `excess_blob_gas` is None + pub fn blob_fee(&self) -> Option { + self.excess_blob_gas.map(calc_blob_gasprice) + } + + /// Returns the blob fee for the next block according to the EIP-4844 spec. + /// + /// Returns `None` if `excess_blob_gas` is None. + /// + /// See also [Self::next_block_excess_blob_gas] + pub fn next_block_blob_fee(&self) -> Option { + self.next_block_excess_blob_gas().map(calc_blob_gasprice) + } + + /// Calculate excess blob gas for the next block according to the EIP-4844 + /// spec. + /// + /// Returns a `None` if no excess blob gas is set, no EIP-4844 support + pub fn next_block_excess_blob_gas(&self) -> Option { + Some(calc_excess_blob_gas(self.excess_blob_gas?, self.blob_gas_used?)) + } +} + +/// Block Transactions depending on the boolean attribute of `eth_getBlockBy*`, +/// or if used by `eth_getUncle*` +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum BlockTransactions { + /// Only hashes + Hashes(Vec), + /// Full transactions + Full(Vec), + /// Special case for uncle response. + Uncle, +} + +impl Default for BlockTransactions { + fn default() -> Self { + BlockTransactions::Hashes(Vec::default()) + } +} + +impl BlockTransactions { + /// Converts `self` into `Hashes`. + #[inline] + pub fn convert_to_hashes(&mut self) { + if !self.is_hashes() { + *self = Self::Hashes(self.hashes().copied().collect()); + } + } + + /// Converts `self` into `Hashes`. + #[inline] + pub fn into_hashes(mut self) -> Self { + self.convert_to_hashes(); + self + } + + /// Check if the enum variant is used for hashes. + #[inline] + pub const fn is_hashes(&self) -> bool { + matches!(self, Self::Hashes(_)) + } + + /// Returns true if the enum variant is used for full transactions. + #[inline] + pub const fn is_full(&self) -> bool { + matches!(self, Self::Full(_)) + } + + /// Returns true if the enum variant is used for an uncle response. + #[inline] + pub const fn is_uncle(&self) -> bool { + matches!(self, Self::Uncle) + } + + /// Returns an iterator over the transaction hashes. + #[deprecated = "use `hashes` instead"] + #[inline] + pub fn iter(&self) -> BlockTransactionHashes<'_> { + self.hashes() + } + + /// Returns an iterator over references to the transaction hashes. + #[inline] + pub fn hashes(&self) -> BlockTransactionHashes<'_> { + BlockTransactionHashes::new(self) + } + + /// Returns an iterator over mutable references to the transaction hashes. + #[inline] + pub fn hashes_mut(&mut self) -> BlockTransactionHashesMut<'_> { + BlockTransactionHashesMut::new(self) + } + + /// Returns an instance of BlockTransactions with the Uncle special case. + #[inline] + pub const fn uncle() -> Self { + Self::Uncle + } + + /// Returns the number of transactions. + #[inline] + pub fn len(&self) -> usize { + self.hashes().len() + } + + /// Whether the block has no transactions. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +/// An iterator over the transaction hashes of a block. +/// +/// See [`BlockTransactions::hashes`]. +#[derive(Clone, Debug)] +pub struct BlockTransactionHashes<'a>(BlockTransactionHashesInner<'a>); + +#[derive(Clone, Debug)] +enum BlockTransactionHashesInner<'a> { + Hashes(std::slice::Iter<'a, B256>), + Full(std::slice::Iter<'a, Transaction>), + Uncle, +} + +impl<'a> BlockTransactionHashes<'a> { + #[inline] + fn new(txs: &'a BlockTransactions) -> Self { + Self(match txs { + BlockTransactions::Hashes(txs) => BlockTransactionHashesInner::Hashes(txs.iter()), + BlockTransactions::Full(txs) => BlockTransactionHashesInner::Full(txs.iter()), + BlockTransactions::Uncle => BlockTransactionHashesInner::Uncle, + }) + } +} + +impl<'a> Iterator for BlockTransactionHashes<'a> { + type Item = &'a B256; + + #[inline] + fn next(&mut self) -> Option { + match &mut self.0 { + BlockTransactionHashesInner::Full(txs) => txs.next().map(|tx| &tx.hash), + BlockTransactionHashesInner::Hashes(txs) => txs.next(), + BlockTransactionHashesInner::Uncle => None, + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match &self.0 { + BlockTransactionHashesInner::Full(txs) => txs.size_hint(), + BlockTransactionHashesInner::Hashes(txs) => txs.size_hint(), + BlockTransactionHashesInner::Uncle => (0, Some(0)), + } + } +} + +impl ExactSizeIterator for BlockTransactionHashes<'_> { + #[inline] + fn len(&self) -> usize { + match &self.0 { + BlockTransactionHashesInner::Full(txs) => txs.len(), + BlockTransactionHashesInner::Hashes(txs) => txs.len(), + BlockTransactionHashesInner::Uncle => 0, + } + } +} + +impl DoubleEndedIterator for BlockTransactionHashes<'_> { + #[inline] + fn next_back(&mut self) -> Option { + match &mut self.0 { + BlockTransactionHashesInner::Full(txs) => txs.next_back().map(|tx| &tx.hash), + BlockTransactionHashesInner::Hashes(txs) => txs.next_back(), + BlockTransactionHashesInner::Uncle => None, + } + } +} + +impl<'a> std::iter::FusedIterator for BlockTransactionHashes<'a> {} + +/// An Iterator over the transaction hashes of a block. +/// +/// See [`BlockTransactions::hashes_mut`]. +#[derive(Debug)] +pub struct BlockTransactionHashesMut<'a>(BlockTransactionHashesInnerMut<'a>); + +#[derive(Debug)] +enum BlockTransactionHashesInnerMut<'a> { + Hashes(std::slice::IterMut<'a, B256>), + Full(std::slice::IterMut<'a, Transaction>), + Uncle, +} + +impl<'a> BlockTransactionHashesMut<'a> { + #[inline] + fn new(txs: &'a mut BlockTransactions) -> Self { + Self(match txs { + BlockTransactions::Hashes(txs) => { + BlockTransactionHashesInnerMut::Hashes(txs.iter_mut()) + } + BlockTransactions::Full(txs) => BlockTransactionHashesInnerMut::Full(txs.iter_mut()), + BlockTransactions::Uncle => BlockTransactionHashesInnerMut::Uncle, + }) + } +} + +impl<'a> Iterator for BlockTransactionHashesMut<'a> { + type Item = &'a mut B256; + + #[inline] + fn next(&mut self) -> Option { + match &mut self.0 { + BlockTransactionHashesInnerMut::Full(txs) => txs.next().map(|tx| &mut tx.hash), + BlockTransactionHashesInnerMut::Hashes(txs) => txs.next(), + BlockTransactionHashesInnerMut::Uncle => None, + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match &self.0 { + BlockTransactionHashesInnerMut::Full(txs) => txs.size_hint(), + BlockTransactionHashesInnerMut::Hashes(txs) => txs.size_hint(), + BlockTransactionHashesInnerMut::Uncle => (0, Some(0)), + } + } +} + +impl ExactSizeIterator for BlockTransactionHashesMut<'_> { + #[inline] + fn len(&self) -> usize { + match &self.0 { + BlockTransactionHashesInnerMut::Full(txs) => txs.len(), + BlockTransactionHashesInnerMut::Hashes(txs) => txs.len(), + BlockTransactionHashesInnerMut::Uncle => 0, + } + } +} + +impl DoubleEndedIterator for BlockTransactionHashesMut<'_> { + #[inline] + fn next_back(&mut self) -> Option { + match &mut self.0 { + BlockTransactionHashesInnerMut::Full(txs) => txs.next_back().map(|tx| &mut tx.hash), + BlockTransactionHashesInnerMut::Hashes(txs) => txs.next_back(), + BlockTransactionHashesInnerMut::Uncle => None, + } + } +} + +impl<'a> std::iter::FusedIterator for BlockTransactionHashesMut<'a> {} + +/// Determines how the `transactions` field of [Block] should be filled. +/// +/// This essentially represents the `full:bool` argument in RPC calls that determine whether the +/// response should include full transaction objects or just the hashes. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum BlockTransactionsKind { + /// Only include hashes: [BlockTransactions::Hashes] + Hashes, + /// Include full transaction objects: [BlockTransactions::Full] + Full, +} + +impl From for BlockTransactionsKind { + fn from(is_full: bool) -> Self { + if is_full { + BlockTransactionsKind::Full + } else { + BlockTransactionsKind::Hashes + } + } +} + +/// Error that can occur when converting other types to blocks +#[derive(Debug, Clone, Copy, thiserror::Error)] +pub enum BlockError { + /// A transaction failed sender recovery + #[error("transaction failed sender recovery")] + InvalidSignature, + /// A raw block failed to decode + #[error("failed to decode raw block {0}")] + RlpDecodeRawBlock(alloy_rlp::Error), +} + +/// A block hash which may have +/// a boolean requireCanonical field. +/// If false, an RPC call should raise if a block +/// matching the hash is not found. +/// If true, an RPC call should additionally raise if +/// the block is not in the canonical chain. +/// +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)] +pub struct RpcBlockHash { + /// A block hash + pub block_hash: B256, + /// Whether the block must be a canonical block + pub require_canonical: Option, +} + +impl RpcBlockHash { + /// Returns an [RpcBlockHash] from a [B256]. + pub const fn from_hash(block_hash: B256, require_canonical: Option) -> Self { + RpcBlockHash { block_hash, require_canonical } + } +} + +impl From for RpcBlockHash { + fn from(value: B256) -> Self { + Self::from_hash(value, None) + } +} + +impl From for B256 { + fn from(value: RpcBlockHash) -> Self { + value.block_hash + } +} + +impl AsRef for RpcBlockHash { + fn as_ref(&self) -> &B256 { + &self.block_hash + } +} + +/// A block Number (or tag - "latest", "earliest", "pending") +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] +pub enum BlockNumberOrTag { + /// Latest block + #[default] + Latest, + /// Finalized block accepted as canonical + Finalized, + /// Safe head block + Safe, + /// Earliest block (genesis) + Earliest, + /// Pending block (not yet part of the blockchain) + Pending, + /// Block by number from canon chain + Number(u64), +} + +impl BlockNumberOrTag { + /// Returns the numeric block number if explicitly set + pub const fn as_number(&self) -> Option { + match *self { + BlockNumberOrTag::Number(num) => Some(num), + _ => None, + } + } + + /// Returns `true` if a numeric block number is set + pub const fn is_number(&self) -> bool { + matches!(self, BlockNumberOrTag::Number(_)) + } + + /// Returns `true` if it's "latest" + pub const fn is_latest(&self) -> bool { + matches!(self, BlockNumberOrTag::Latest) + } + + /// Returns `true` if it's "finalized" + pub const fn is_finalized(&self) -> bool { + matches!(self, BlockNumberOrTag::Finalized) + } + + /// Returns `true` if it's "safe" + pub const fn is_safe(&self) -> bool { + matches!(self, BlockNumberOrTag::Safe) + } + + /// Returns `true` if it's "pending" + pub const fn is_pending(&self) -> bool { + matches!(self, BlockNumberOrTag::Pending) + } + + /// Returns `true` if it's "earliest" + pub const fn is_earliest(&self) -> bool { + matches!(self, BlockNumberOrTag::Earliest) + } +} + +impl From for BlockNumberOrTag { + fn from(num: u64) -> Self { + BlockNumberOrTag::Number(num) + } +} + +impl From for BlockNumberOrTag { + fn from(num: U64) -> Self { + num.to::().into() + } +} + +impl Serialize for BlockNumberOrTag { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + BlockNumberOrTag::Number(x) => serializer.serialize_str(&format!("0x{x:x}")), + BlockNumberOrTag::Latest => serializer.serialize_str("latest"), + BlockNumberOrTag::Finalized => serializer.serialize_str("finalized"), + BlockNumberOrTag::Safe => serializer.serialize_str("safe"), + BlockNumberOrTag::Earliest => serializer.serialize_str("earliest"), + BlockNumberOrTag::Pending => serializer.serialize_str("pending"), + } + } +} + +impl<'de> Deserialize<'de> for BlockNumberOrTag { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?.to_lowercase(); + s.parse().map_err(serde::de::Error::custom) + } +} + +impl FromStr for BlockNumberOrTag { + type Err = ParseBlockNumberError; + + fn from_str(s: &str) -> Result { + let block = match s { + "latest" => Self::Latest, + "finalized" => Self::Finalized, + "safe" => Self::Safe, + "earliest" => Self::Earliest, + "pending" => Self::Pending, + _number => { + if let Some(hex_val) = s.strip_prefix("0x") { + let number = u64::from_str_radix(hex_val, 16); + BlockNumberOrTag::Number(number?) + } else { + return Err(HexStringMissingPrefixError::default().into()); + } + } + }; + Ok(block) + } +} + +impl fmt::Display for BlockNumberOrTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BlockNumberOrTag::Number(x) => write!(f, "0x{x:x}"), + BlockNumberOrTag::Latest => f.write_str("latest"), + BlockNumberOrTag::Finalized => f.write_str("finalized"), + BlockNumberOrTag::Safe => f.write_str("safe"), + BlockNumberOrTag::Earliest => f.write_str("earliest"), + BlockNumberOrTag::Pending => f.write_str("pending"), + } + } +} + +/// Error variants when parsing a [BlockNumberOrTag] +#[derive(Debug, thiserror::Error)] +pub enum ParseBlockNumberError { + /// Failed to parse hex value + #[error(transparent)] + ParseIntErr(#[from] ParseIntError), + /// Failed to parse hex value + #[error(transparent)] + ParseErr(#[from] ParseError), + /// Block numbers should be 0x-prefixed + #[error(transparent)] + MissingPrefix(#[from] HexStringMissingPrefixError), +} + +/// Thrown when a 0x-prefixed hex string was expected +#[derive(Copy, Clone, Debug, Default, thiserror::Error)] +#[non_exhaustive] +#[error("hex string without 0x prefix")] +pub struct HexStringMissingPrefixError; + +/// A Block Identifier +/// +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum BlockId { + /// A block hash and an optional bool that defines if it's canonical + Hash(RpcBlockHash), + /// A block number + Number(BlockNumberOrTag), +} + +// === impl BlockId === + +impl BlockId { + /// Returns the block hash if it is [BlockId::Hash] + pub const fn as_block_hash(&self) -> Option { + match self { + BlockId::Hash(hash) => Some(hash.block_hash), + BlockId::Number(_) => None, + } + } + + /// Returns true if this is [BlockNumberOrTag::Latest] + pub const fn is_latest(&self) -> bool { + matches!(self, BlockId::Number(BlockNumberOrTag::Latest)) + } + + /// Returns true if this is [BlockNumberOrTag::Pending] + pub const fn is_pending(&self) -> bool { + matches!(self, BlockId::Number(BlockNumberOrTag::Pending)) + } + + /// Returns true if this is [BlockNumberOrTag::Safe] + pub const fn is_safe(&self) -> bool { + matches!(self, BlockId::Number(BlockNumberOrTag::Safe)) + } + + /// Returns true if this is [BlockNumberOrTag::Finalized] + pub const fn is_finalized(&self) -> bool { + matches!(self, BlockId::Number(BlockNumberOrTag::Finalized)) + } + + /// Returns true if this is [BlockNumberOrTag::Earliest] + pub const fn is_earliest(&self) -> bool { + matches!(self, BlockId::Number(BlockNumberOrTag::Earliest)) + } + + /// Returns true if this is [BlockNumberOrTag::Number] + pub const fn is_number(&self) -> bool { + matches!(self, BlockId::Number(BlockNumberOrTag::Number(_))) + } + /// Returns true if this is [BlockId::Hash] + pub const fn is_hash(&self) -> bool { + matches!(self, BlockId::Hash(_)) + } + + /// Creates a new "pending" tag instance. + pub const fn pending() -> Self { + BlockId::Number(BlockNumberOrTag::Pending) + } + + /// Creates a new "latest" tag instance. + pub const fn latest() -> Self { + BlockId::Number(BlockNumberOrTag::Latest) + } + + /// Creates a new "earliest" tag instance. + pub const fn earliest() -> Self { + BlockId::Number(BlockNumberOrTag::Earliest) + } + + /// Creates a new "finalized" tag instance. + pub const fn finalized() -> Self { + BlockId::Number(BlockNumberOrTag::Finalized) + } + + /// Creates a new "safe" tag instance. + pub const fn safe() -> Self { + BlockId::Number(BlockNumberOrTag::Safe) + } + + /// Creates a new block number instance. + pub const fn number(num: u64) -> Self { + BlockId::Number(BlockNumberOrTag::Number(num)) + } + + /// Create a new block hash instance. + pub const fn hash(block_hash: B256) -> Self { + BlockId::Hash(RpcBlockHash { block_hash, require_canonical: None }) + } + + /// Create a new block hash instance that requires the block to be canonical. + pub const fn hash_canonical(block_hash: B256) -> Self { + BlockId::Hash(RpcBlockHash { block_hash, require_canonical: Some(true) }) + } +} + +impl Default for BlockId { + fn default() -> Self { + BlockId::Number(BlockNumberOrTag::Latest) + } +} + +impl From for BlockId { + fn from(num: u64) -> Self { + BlockNumberOrTag::Number(num).into() + } +} + +impl From for BlockId { + fn from(value: U64) -> Self { + BlockNumberOrTag::Number(value.to()).into() + } +} + +impl From for BlockId { + fn from(num: BlockNumberOrTag) -> Self { + BlockId::Number(num) + } +} + +impl From for BlockId { + fn from(block_hash: B256) -> Self { + BlockId::Hash(RpcBlockHash { block_hash, require_canonical: None }) + } +} + +impl From<(B256, Option)> for BlockId { + fn from(hash_can: (B256, Option)) -> Self { + BlockId::Hash(RpcBlockHash { block_hash: hash_can.0, require_canonical: hash_can.1 }) + } +} + +impl Serialize for BlockId { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + BlockId::Hash(RpcBlockHash { block_hash, require_canonical }) => { + let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?; + s.serialize_field("blockHash", block_hash)?; + if let Some(require_canonical) = require_canonical { + s.serialize_field("requireCanonical", require_canonical)?; + } + s.end() + } + BlockId::Number(num) => num.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for BlockId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct BlockIdVisitor; + + impl<'de> Visitor<'de> for BlockIdVisitor { + type Value = BlockId; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("Block identifier following EIP-1898") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + // Since there is no way to clearly distinguish between a DATA parameter and a QUANTITY parameter. A str is therefor deserialized into a Block Number: + // However, since the hex string should be a QUANTITY, we can safely assume that if the len is 66 bytes, it is in fact a hash, ref + if v.len() == 66 { + Ok(BlockId::Hash(v.parse::().map_err(serde::de::Error::custom)?.into())) + } else { + // quantity hex string or tag + Ok(BlockId::Number(v.parse().map_err(serde::de::Error::custom)?)) + } + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut number = None; + let mut block_hash = None; + let mut require_canonical = None; + while let Some(key) = map.next_key::()? { + match key.as_str() { + "blockNumber" => { + if number.is_some() || block_hash.is_some() { + return Err(serde::de::Error::duplicate_field("blockNumber")); + } + if require_canonical.is_some() { + return Err(serde::de::Error::custom( + "Non-valid require_canonical field", + )); + } + number = Some(map.next_value::()?) + } + "blockHash" => { + if number.is_some() || block_hash.is_some() { + return Err(serde::de::Error::duplicate_field("blockHash")); + } + + block_hash = Some(map.next_value::()?); + } + "requireCanonical" => { + if number.is_some() || require_canonical.is_some() { + return Err(serde::de::Error::duplicate_field("requireCanonical")); + } + + require_canonical = Some(map.next_value::()?) + } + key => { + return Err(serde::de::Error::unknown_field( + key, + &["blockNumber", "blockHash", "requireCanonical"], + )) + } + } + } + + if let Some(number) = number { + Ok(BlockId::Number(number)) + } else if let Some(block_hash) = block_hash { + Ok(BlockId::Hash(RpcBlockHash { block_hash, require_canonical })) + } else { + Err(serde::de::Error::custom( + "Expected `blockNumber` or `blockHash` with `requireCanonical` optionally", + )) + } + } + } + + deserializer.deserialize_any(BlockIdVisitor) + } +} + +/// Error thrown when parsing a [BlockId] from a string. +#[derive(Debug, thiserror::Error)] +pub enum ParseBlockIdError { + /// Failed to parse a block id from a number. + #[error(transparent)] + ParseIntError(#[from] ParseIntError), + /// Failed to parse a block id as a hex string. + #[error(transparent)] + FromHexError(#[from] alloy_primitives::hex::FromHexError), +} + +impl FromStr for BlockId { + type Err = ParseBlockIdError; + fn from_str(s: &str) -> Result { + if s.starts_with("0x") { + return B256::from_str(s).map(Into::into).map_err(ParseBlockIdError::FromHexError); + } + + match s { + "latest" => Ok(BlockId::Number(BlockNumberOrTag::Latest)), + "finalized" => Ok(BlockId::Number(BlockNumberOrTag::Finalized)), + "safe" => Ok(BlockId::Number(BlockNumberOrTag::Safe)), + "earliest" => Ok(BlockId::Number(BlockNumberOrTag::Earliest)), + "pending" => Ok(BlockId::Number(BlockNumberOrTag::Pending)), + _ => s + .parse::() + .map_err(ParseBlockIdError::ParseIntError) + .map(|n| BlockId::Number(n.into())), + } + } +} + +/// Block number and hash. +#[derive(Clone, Copy, Hash, Default, PartialEq, Eq)] +pub struct BlockNumHash { + /// Block number + pub number: BlockNumber, + /// Block hash + pub hash: BlockHash, +} + +/// Block number and hash of the forked block. +pub type ForkBlock = BlockNumHash; + +impl fmt::Debug for BlockNumHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("").field(&self.number).field(&self.hash).finish() + } +} + +impl BlockNumHash { + /// Creates a new `BlockNumHash` from a block number and hash. + pub const fn new(number: BlockNumber, hash: BlockHash) -> Self { + Self { number, hash } + } + + /// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`] + pub const fn into_components(self) -> (BlockNumber, BlockHash) { + (self.number, self.hash) + } + + /// Returns whether or not the block matches the given [BlockHashOrNumber]. + pub fn matches_block_or_num(&self, block: &BlockHashOrNumber) -> bool { + match block { + BlockHashOrNumber::Hash(hash) => self.hash == *hash, + BlockHashOrNumber::Number(number) => self.number == *number, + } + } +} + +impl From<(BlockNumber, BlockHash)> for BlockNumHash { + fn from(val: (BlockNumber, BlockHash)) -> Self { + BlockNumHash { number: val.0, hash: val.1 } + } +} + +impl From<(BlockHash, BlockNumber)> for BlockNumHash { + fn from(val: (BlockHash, BlockNumber)) -> Self { + BlockNumHash { hash: val.0, number: val.1 } + } +} + +/// Either a block hash _or_ a block number +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[cfg_attr( + any(test, feature = "arbitrary"), + derive(proptest_derive::Arbitrary, arbitrary::Arbitrary) +)] +pub enum BlockHashOrNumber { + /// A block hash + Hash(B256), + /// A block number + Number(u64), +} + +// === impl BlockHashOrNumber === + +impl BlockHashOrNumber { + /// Returns the block number if it is a [`BlockHashOrNumber::Number`]. + #[inline] + pub const fn as_number(self) -> Option { + match self { + BlockHashOrNumber::Hash(_) => None, + BlockHashOrNumber::Number(num) => Some(num), + } + } +} + +impl From for BlockHashOrNumber { + fn from(value: B256) -> Self { + BlockHashOrNumber::Hash(value) + } +} + +impl From for BlockHashOrNumber { + fn from(value: u64) -> Self { + BlockHashOrNumber::Number(value) + } +} + +impl From for BlockHashOrNumber { + fn from(value: U64) -> Self { + value.to::().into() + } +} + +/// Allows for RLP encoding of either a block hash or block number +impl Encodable for BlockHashOrNumber { + fn encode(&self, out: &mut dyn bytes::BufMut) { + match self { + Self::Hash(block_hash) => block_hash.encode(out), + Self::Number(block_number) => block_number.encode(out), + } + } + fn length(&self) -> usize { + match self { + Self::Hash(block_hash) => block_hash.length(), + Self::Number(block_number) => block_number.length(), + } + } +} + +/// Allows for RLP decoding of a block hash or block number +impl Decodable for BlockHashOrNumber { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let header: u8 = *buf.first().ok_or(RlpError::InputTooShort)?; + // if the byte string is exactly 32 bytes, decode it into a Hash + // 0xa0 = 0x80 (start of string) + 0x20 (32, length of string) + if header == 0xa0 { + // strip the first byte, parsing the rest of the string. + // If the rest of the string fails to decode into 32 bytes, we'll bubble up the + // decoding error. + let hash = B256::decode(buf)?; + Ok(Self::Hash(hash)) + } else { + // a block number when encoded as bytes ranges from 0 to any number of bytes - we're + // going to accept numbers which fit in less than 64 bytes. + // Any data larger than this which is not caught by the Hash decoding should error and + // is considered an invalid block number. + Ok(Self::Number(u64::decode(buf)?)) + } + } +} + +/// Error thrown when parsing a [BlockHashOrNumber] from a string. +#[derive(Debug, thiserror::Error)] +#[error("failed to parse {input:?} as a number: {parse_int_error} or hash: {hex_error}")] +pub struct ParseBlockHashOrNumberError { + input: String, + parse_int_error: ParseIntError, + hex_error: alloy_primitives::hex::FromHexError, +} + +impl FromStr for BlockHashOrNumber { + type Err = ParseBlockHashOrNumberError; + + fn from_str(s: &str) -> Result { + match u64::from_str(s) { + Ok(val) => Ok(val.into()), + Err(pares_int_error) => match B256::from_str(s) { + Ok(val) => Ok(val.into()), + Err(hex_error) => Err(ParseBlockHashOrNumberError { + input: s.to_string(), + parse_int_error: pares_int_error, + hex_error, + }), + }, + } + } +} + +/// A Block representation that allows to include additional fields +pub type RichBlock = Rich; + +impl From for RichBlock { + fn from(block: Block) -> Self { + Rich { inner: block, extra_info: Default::default() } + } +} + +/// Header representation with additional info. +pub type RichHeader = Rich
; + +impl From
for RichHeader { + fn from(header: Header) -> Self { + Rich { inner: header, extra_info: Default::default() } + } +} + +/// Value representation with additional info +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Rich { + /// Standard value. + #[serde(flatten)] + pub inner: T, + /// Additional fields that should be serialized into the `Block` object + #[serde(flatten)] + pub extra_info: BTreeMap, +} + +impl Deref for Rich { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl Serialize for Rich { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.extra_info.is_empty() { + return self.inner.serialize(serializer); + } + + let inner = serde_json::to_value(&self.inner); + let extras = serde_json::to_value(&self.extra_info); + + if let (Ok(serde_json::Value::Object(mut value)), Ok(serde_json::Value::Object(extras))) = + (inner, extras) + { + value.extend(extras); + value.serialize(serializer) + } else { + Err(S::Error::custom("Unserializable structures: expected objects")) + } + } +} + +/// BlockOverrides is a set of header fields to override. +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase", deny_unknown_fields)] +pub struct BlockOverrides { + /// Overrides the block number. + /// + /// For `eth_callMany` this will be the block number of the first simulated block. Each + /// following block increments its block number by 1 + // Note: geth uses `number`, erigon uses `blockNumber` + #[serde(default, skip_serializing_if = "Option::is_none", alias = "blockNumber")] + pub number: Option, + /// Overrides the difficulty of the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub difficulty: Option, + /// Overrides the timestamp of the block. + // Note: geth uses `time`, erigon uses `timestamp` + #[serde(default, skip_serializing_if = "Option::is_none", alias = "timestamp")] + pub time: Option, + /// Overrides the gas limit of the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub gas_limit: Option, + /// Overrides the coinbase address of the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub coinbase: Option
, + /// Overrides the prevrandao of the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub random: Option, + /// Overrides the basefee of the block. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub base_fee: Option, + /// A dictionary that maps blockNumber to a user-defined hash. It could be queried from the + /// solidity opcode BLOCKHASH. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub block_hash: Option>, +} + +#[cfg(test)] +mod tests { + use super::*; + use arbitrary::Arbitrary; + use rand::Rng; + + #[test] + fn arbitrary_header() { + let mut bytes = [0u8; 1024]; + rand::thread_rng().fill(bytes.as_mut_slice()); + let _: Header = Header::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(); + } + + #[test] + fn test_full_conversion() { + let full = true; + assert_eq!(BlockTransactionsKind::Full, full.into()); + + let full = false; + assert_eq!(BlockTransactionsKind::Hashes, full.into()); + } + + #[test] + #[cfg(feature = "jsonrpsee-types")] + fn serde_json_header() { + use jsonrpsee_types::SubscriptionResponse; + let resp = r#"{"jsonrpc":"2.0","method":"eth_subscribe","params":{"subscription":"0x7eef37ff35d471f8825b1c8f67a5d3c0","result":{"hash":"0x7a7ada12e140961a32395059597764416499f4178daf1917193fad7bd2cc6386","parentHash":"0xdedbd831f496e705e7f2ec3c8dcb79051040a360bf1455dbd7eb8ea6ad03b751","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":"0x8","gasUsed":"0x0","gasLimit":"0x1c9c380","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x642aa48f","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000"}}}"#; + let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap(); + + let resp = r#"{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x1a14b6bdcf4542fabf71c4abee244e47","result":{"author":"0x000000568b9b5a365eaa767d42e74ed88915c204","difficulty":"0x1","extraData":"0x4e65746865726d696e6420312e392e32322d302d6463373666616366612d32308639ad8ff3d850a261f3b26bc2a55e0f3a718de0dd040a19a4ce37e7b473f2d7481448a1e1fd8fb69260825377c0478393e6055f471a5cf839467ce919a6ad2700","gasLimit":"0x7a1200","gasUsed":"0x0","hash":"0xa4856602944fdfd18c528ef93cc52a681b38d766a7e39c27a47488c8461adcb0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x434822","parentHash":"0x1a9bdc31fc785f8a95efeeb7ae58f40f6366b8e805f47447a52335c95f4ceb49","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x261","stateRoot":"0xf38c4bf2958e541ec6df148e54ce073dc6b610f8613147ede568cb7b5c2d81ee","totalDifficulty":"0x633ebd","timestamp":"0x604726b0","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}}}"#; + let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap(); + } + + #[test] + fn serde_block() { + let block = Block { + header: Header { + hash: Some(B256::with_last_byte(1)), + parent_hash: B256::with_last_byte(2), + uncles_hash: B256::with_last_byte(3), + miner: Address::with_last_byte(4), + state_root: B256::with_last_byte(5), + transactions_root: B256::with_last_byte(6), + receipts_root: B256::with_last_byte(7), + withdrawals_root: Some(B256::with_last_byte(8)), + number: Some(9), + gas_used: 10, + gas_limit: 11, + extra_data: Bytes::from(vec![1, 2, 3]), + logs_bloom: Bloom::default(), + timestamp: 12, + difficulty: U256::from(13), + total_difficulty: Some(U256::from(100000)), + mix_hash: Some(B256::with_last_byte(14)), + nonce: Some(15), + base_fee_per_gas: Some(20), + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + }, + uncles: vec![B256::with_last_byte(17)], + transactions: BlockTransactions::Hashes(vec![B256::with_last_byte(18)]), + size: Some(U256::from(19)), + withdrawals: Some(vec![]), + other: Default::default(), + }; + let serialized = serde_json::to_string(&block).unwrap(); + assert_eq!( + serialized, + r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","totalDifficulty":"0x186a0","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0xf","baseFeePerGas":"0x14","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"size":"0x13","withdrawals":[]}"# + ); + let deserialized: Block = serde_json::from_str(&serialized).unwrap(); + assert_eq!(block, deserialized); + } + + #[test] + fn serde_uncle_block() { + let block = Block { + header: Header { + hash: Some(B256::with_last_byte(1)), + parent_hash: B256::with_last_byte(2), + uncles_hash: B256::with_last_byte(3), + miner: Address::with_last_byte(4), + state_root: B256::with_last_byte(5), + transactions_root: B256::with_last_byte(6), + receipts_root: B256::with_last_byte(7), + withdrawals_root: Some(B256::with_last_byte(8)), + number: Some(9), + gas_used: 10, + gas_limit: 11, + extra_data: Bytes::from(vec![1, 2, 3]), + logs_bloom: Bloom::default(), + timestamp: 12, + difficulty: U256::from(13), + total_difficulty: Some(U256::from(100000)), + mix_hash: Some(B256::with_last_byte(14)), + nonce: Some(15), + base_fee_per_gas: Some(20), + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + }, + uncles: vec![], + transactions: BlockTransactions::Uncle, + size: Some(U256::from(19)), + withdrawals: None, + other: Default::default(), + }; + let serialized = serde_json::to_string(&block).unwrap(); + assert_eq!( + serialized, + r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","totalDifficulty":"0x186a0","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0xf","baseFeePerGas":"0x14","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","uncles":[],"size":"0x13"}"# + ); + let deserialized: Block = serde_json::from_str(&serialized).unwrap(); + assert_eq!(block, deserialized); + } + + #[test] + fn serde_block_with_withdrawals_set_as_none() { + let block = Block { + header: Header { + hash: Some(B256::with_last_byte(1)), + parent_hash: B256::with_last_byte(2), + uncles_hash: B256::with_last_byte(3), + miner: Address::with_last_byte(4), + state_root: B256::with_last_byte(5), + transactions_root: B256::with_last_byte(6), + receipts_root: B256::with_last_byte(7), + withdrawals_root: None, + number: Some(9), + gas_used: 10, + gas_limit: 11, + extra_data: Bytes::from(vec![1, 2, 3]), + logs_bloom: Bloom::default(), + timestamp: 12, + difficulty: U256::from(13), + total_difficulty: Some(U256::from(100000)), + mix_hash: Some(B256::with_last_byte(14)), + nonce: Some(15), + base_fee_per_gas: Some(20), + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + }, + uncles: vec![B256::with_last_byte(17)], + transactions: BlockTransactions::Hashes(vec![B256::with_last_byte(18)]), + size: Some(U256::from(19)), + withdrawals: None, + other: Default::default(), + }; + let serialized = serde_json::to_string(&block).unwrap(); + assert_eq!( + serialized, + r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","totalDifficulty":"0x186a0","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0xf","baseFeePerGas":"0x14","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"size":"0x13"}"# + ); + let deserialized: Block = serde_json::from_str(&serialized).unwrap(); + assert_eq!(block, deserialized); + } + + #[test] + fn block_overrides() { + let s = r#"{"blockNumber": "0xe39dd0"}"#; + let _overrides = serde_json::from_str::(s).unwrap(); + } + + #[test] + fn serde_rich_block() { + let s = r#"{ + "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4", + "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x829bd824b016326a401d083b33d092293333a830", + "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84", + "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe", + "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0", + "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794", + "difficulty": "0xc40faff9c737d", + "number": "0xa9a230", + "gasLimit": "0xbe5a66", + "gasUsed": "0xbe0fcc", + "timestamp": "0x5f93b749", + "totalDifficulty": "0x3dc957fd8167fb2684a", + "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103", + "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc", + "nonce": "0x4722f2acd35abe0f", + "uncles": [], + "transactions": [ + "0xf435a26acc2a9ef73ac0b73632e32e29bd0e28d5c4f46a7e18ed545c93315916" + ], + "size": "0xaeb6" +}"#; + + let block = serde_json::from_str::(s).unwrap(); + let serialized = serde_json::to_string(&block).unwrap(); + let block2 = serde_json::from_str::(&serialized).unwrap(); + assert_eq!(block, block2); + } + + #[test] + fn serde_missing_uncles_block() { + let s = r#"{ + "baseFeePerGas":"0x886b221ad", + "blobGasUsed":"0x0", + "difficulty":"0x0", + "excessBlobGas":"0x0", + "extraData":"0x6265617665726275696c642e6f7267", + "gasLimit":"0x1c9c380", + "gasUsed":"0xb0033c", + "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac", + "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427", + "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5", + "nonce":"0x0000000000000000", + "number":"0x128c6df", + "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc", + "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717", + "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90", + "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size":"0xdcc3", + "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404", + "timestamp":"0x65f5f4c3", + "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780", + "withdrawals":[ + { + "index":"0x24d80e6", + "validatorIndex":"0x8b2b6", + "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff", + "amount":"0x1161f10" + } + ], + "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7" + }"#; + + let block = serde_json::from_str::(s).unwrap(); + let serialized = serde_json::to_string(&block).unwrap(); + let block2 = serde_json::from_str::(&serialized).unwrap(); + assert_eq!(block, block2); + } + + #[test] + fn serde_block_containing_uncles() { + let s = r#"{ + "baseFeePerGas":"0x886b221ad", + "blobGasUsed":"0x0", + "difficulty":"0x0", + "excessBlobGas":"0x0", + "extraData":"0x6265617665726275696c642e6f7267", + "gasLimit":"0x1c9c380", + "gasUsed":"0xb0033c", + "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac", + "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427", + "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5", + "nonce":"0x0000000000000000", + "number":"0x128c6df", + "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc", + "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717", + "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90", + "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size":"0xdcc3", + "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404", + "timestamp":"0x65f5f4c3", + "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780", + "uncles": ["0x123a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780", "0x489a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780"], + "withdrawals":[ + { + "index":"0x24d80e6", + "validatorIndex":"0x8b2b6", + "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff", + "amount":"0x1161f10" + } + ], + "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7" + }"#; + + let block = serde_json::from_str::(s).unwrap(); + assert!(block.uncles.len() == 2); + let serialized = serde_json::to_string(&block).unwrap(); + let block2 = serde_json::from_str::(&serialized).unwrap(); + assert_eq!(block, block2); + } + + #[test] + fn compact_block_number_serde() { + let num: BlockNumberOrTag = 1u64.into(); + let serialized = serde_json::to_string(&num).unwrap(); + assert_eq!(serialized, "\"0x1\""); + } + + #[test] + fn can_parse_eip1898_block_ids() { + let num = serde_json::json!( + { "blockNumber": "0x0" } + ); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Number(0u64))); + + let num = serde_json::json!( + { "blockNumber": "pending" } + ); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Pending)); + + let num = serde_json::json!( + { "blockNumber": "latest" } + ); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Latest)); + + let num = serde_json::json!( + { "blockNumber": "finalized" } + ); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Finalized)); + + let num = serde_json::json!( + { "blockNumber": "safe" } + ); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Safe)); + + let num = serde_json::json!( + { "blockNumber": "earliest" } + ); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Earliest)); + + let num = serde_json::json!("0x0"); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Number(0u64))); + + let num = serde_json::json!("pending"); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Pending)); + + let num = serde_json::json!("latest"); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Latest)); + + let num = serde_json::json!("finalized"); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Finalized)); + + let num = serde_json::json!("safe"); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Safe)); + + let num = serde_json::json!("earliest"); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!(id, BlockId::Number(BlockNumberOrTag::Earliest)); + + let num = serde_json::json!( + { "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" } + ); + let id = serde_json::from_value::(num).unwrap(); + assert_eq!( + id, + BlockId::Hash( + "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + .parse::() + .unwrap() + .into() + ) + ); + } +} From 2def3b04847bf9f8c8c603dce24b01ccbc77a138 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Wed, 10 Apr 2024 16:02:45 +0400 Subject: [PATCH 06/25] feat: add txType as a separate file under transactions and update receipt.rs accordingly. --- crates/op-rpc-types/src/op/transaction/mod.rs | 1 + .../src/op/transaction/receipt.rs | 29 +--------- .../src/op/transaction/tx_type.rs | 54 +++++++++++++++++++ 3 files changed, 57 insertions(+), 27 deletions(-) create mode 100644 crates/op-rpc-types/src/op/transaction/tx_type.rs diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs index 4df93164..d2a67f66 100644 --- a/crates/op-rpc-types/src/op/transaction/mod.rs +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -1 +1,2 @@ mod receipt; +mod tx_type; \ No newline at end of file diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index 1e6a7882..cc6d2e35 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -1,34 +1,9 @@ -use crate::{op::log::Log, WithOtherFields}; +use crate::{op::log::Log, op::transaction::tx_type, WithOtherFields}; use alloy_consensus::{AnyReceiptEnvelope, ReceiptEnvelope, TxType}; use alloy_primitives::{Address, B256}; use serde::{Deserialize, Serialize}; use alloy::serde as alloy_serde; -/// Transaction Type -/// -/// Currently being used as 2-bit type when encoding it to [`Compact`] on -/// [`crate::TransactionSignedNoHash`]. Adding more transaction types will break the codec and -/// database format. -/// -/// Other required changes when adding a new type can be seen on [PR#3953](https://github.com/paradigmxyz/reth/pull/3953/files). -#[derive_arbitrary(compact)] -#[derive( - Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Hash, -)] -pub enum TxType { - /// Legacy transaction pre EIP-2929 - #[default] - Legacy = 0_isize, - /// AccessList transaction - Eip2930 = 1_isize, - /// Transaction with Priority fee - Eip1559 = 2_isize, - /// Shard Blob Transactions - EIP-4844 - Eip4844 = 3_isize, - /// Optimism Deposit transaction. - Deposit = 126_isize, -} - /// Transaction receipt /// /// This type is generic over an inner [`ReceiptEnvelope`] which contains @@ -112,7 +87,7 @@ pub struct TransactionReceipt> { /// The value is always equal to `1` when present. #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u64_hex_opt")] pub deposit_receipt_version: Option, - pub tx_type: TxType; + pub tx_type: tx_type::TxType, } impl AsRef> for TransactionReceipt { diff --git a/crates/op-rpc-types/src/op/transaction/tx_type.rs b/crates/op-rpc-types/src/op/transaction/tx_type.rs new file mode 100644 index 00000000..020af2d8 --- /dev/null +++ b/crates/op-rpc-types/src/op/transaction/tx_type.rs @@ -0,0 +1,54 @@ +use alloy_primitives::U8; +use serde::{Deserialize, Serialize}; + +/// Identifier for legacy transaction, however a legacy tx is technically not +/// typed. +pub const LEGACY_TX_TYPE_ID: u8 = 0; + +/// Identifier for an EIP2930 transaction. +pub const EIP2930_TX_TYPE_ID: u8 = 1; + +/// Identifier for an EIP1559 transaction. +pub const EIP1559_TX_TYPE_ID: u8 = 2; + +/// Identifier for an EIP4844 transaction. +pub const EIP4844_TX_TYPE_ID: u8 = 3; + +// Identifier for an Optimism deposit transaction +pub const DEPOSIT_TX_TYPE_ID: u8 = 126; + +/// Transaction Type +/// +/// Currently being used as 2-bit type when encoding it to [`Compact`] on +/// [`crate::TransactionSignedNoHash`]. Adding more transaction types will break the codec and +/// database format. +/// +/// Other required changes when adding a new type can be seen on [PR#3953](https://github.com/paradigmxyz/reth/pull/3953/files). +#[derive( + Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Hash, +)] +pub enum TxType { + /// Legacy transaction pre EIP-2929 + #[default] + Legacy = 0_isize, + /// AccessList transaction + Eip2930 = 1_isize, + /// Transaction with Priority fee + Eip1559 = 2_isize, + /// Shard Blob Transactions - EIP-4844 + Eip4844 = 3_isize, + /// Optimism Deposit transaction. + Deposit = 126_isize, +} + +impl From for u8 { + fn from(value: TxType) -> Self { + match value { + TxType::Legacy => LEGACY_TX_TYPE_ID, + TxType::EIP2930 => EIP2930_TX_TYPE_ID, + TxType::EIP1559 => EIP1559_TX_TYPE_ID, + TxType::EIP4844 => EIP4844_TX_TYPE_ID, + TxType::Deposit => DEPOSIT_TX_TYPE_ID + } + } +} From 6b0c42a2d60edf33de13bcad4dc40a9ae2dbd355 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Wed, 10 Apr 2024 16:30:45 +0400 Subject: [PATCH 07/25] refactor: update optimism specific fields and their (de)serialization methods in receipt.rs --- .../src/op/transaction/receipt.rs | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index cc6d2e35..6063d22f 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -1,4 +1,4 @@ -use crate::{op::log::Log, op::transaction::tx_type, WithOtherFields}; +use crate::{op::log::Log, op::transaction::tx_type}; use alloy_consensus::{AnyReceiptEnvelope, ReceiptEnvelope, TxType}; use alloy_primitives::{Address, B256}; use serde::{Deserialize, Serialize}; @@ -64,16 +64,16 @@ pub struct TransactionReceipt> { #[serde(skip_serializing_if = "Option::is_none", rename = "root")] pub state_root: Option, /// The fee associated with a transaction on the Layer 1 - #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] + #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] pub l1_fee: Option, /// A multiplier applied to the actual gas usage on Layer 1 to calculate the dynamic costs. - #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] - pub l1_fee_scalar: Option, + #[serde(default, skip_serializing_if = "Option::is_none", with = "l1_fee_scalar_serde")] + pub l1_fee_scalar: Option, /// The gas price for transactions on the Layer 1 - #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] + #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] pub l1_gas_price: Option, /// The amount of gas consumed by a transaction on the Layer 1 - #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] + #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] pub l1_gas_used: Option, /// Deposit nonce for Optimism deposit transactions #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u64_hex_opt")] @@ -152,6 +152,34 @@ impl TransactionReceipt { l1_gas_used: self.l1_gas_used, deposit_nonce: self.deposit_nonce, deposit_receipt_version: self.deposit_receipt_version, + tx_type: self.tx_type, } } -} \ No newline at end of file +} + +/// Serialize/Deserialize l1FeeScalar to/from string +mod l1_fee_scalar_serde { + use serde::{de, Deserialize}; + + pub(super) fn serialize(value: &Option, s: S) -> Result + where + S: serde::Serializer, + { + if let Some(v) = value { + return s.serialize_str(&v.to_string()); + } + s.serialize_none() + } + + pub(super) fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let s: Option = Option::deserialize(deserializer)?; + if let Some(s) = s { + return Ok(Some(s.parse::().map_err(de::Error::custom)?)); + } + + Ok(None) + } +} From 87742c37ae9e58734e5a4ff7e9b5ef6fa905ad67 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Wed, 10 Apr 2024 16:54:35 +0400 Subject: [PATCH 08/25] docs: remove outdated documentation. --- crates/op-rpc-types/src/op/transaction/tx_type.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/op-rpc-types/src/op/transaction/tx_type.rs b/crates/op-rpc-types/src/op/transaction/tx_type.rs index 020af2d8..f3f9f948 100644 --- a/crates/op-rpc-types/src/op/transaction/tx_type.rs +++ b/crates/op-rpc-types/src/op/transaction/tx_type.rs @@ -18,12 +18,6 @@ pub const EIP4844_TX_TYPE_ID: u8 = 3; pub const DEPOSIT_TX_TYPE_ID: u8 = 126; /// Transaction Type -/// -/// Currently being used as 2-bit type when encoding it to [`Compact`] on -/// [`crate::TransactionSignedNoHash`]. Adding more transaction types will break the codec and -/// database format. -/// -/// Other required changes when adding a new type can be seen on [PR#3953](https://github.com/paradigmxyz/reth/pull/3953/files). #[derive( Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Hash, )] From 516eb59fb7ca1742cc06b9edbedd6a4b299df352 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Wed, 10 Apr 2024 22:14:57 +0400 Subject: [PATCH 09/25] feat: add transaction, and request types. Adjust block to use the crate's transaction and alloy's header. --- Cargo.toml | 2 + crates/op-rpc-types/Cargo.toml | 3 +- crates/op-rpc-types/src/op/block.rs | 493 +----------------- crates/op-rpc-types/src/op/log.rs | 179 ------- crates/op-rpc-types/src/op/mod.rs | 3 +- crates/op-rpc-types/src/op/transaction/mod.rs | 244 ++++++++- .../src/op/transaction/receipt.rs | 23 +- .../src/op/transaction/request.rs | 444 ++++++++++++++++ .../src/op/transaction/tx_type.rs | 2 +- 9 files changed, 714 insertions(+), 679 deletions(-) delete mode 100644 crates/op-rpc-types/src/op/log.rs create mode 100644 crates/op-rpc-types/src/op/transaction/request.rs diff --git a/Cargo.toml b/Cargo.toml index af249155..bcbffe12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ exclude = ["benches/", "tests/"] alloy-primitives = { version = "0.7.0", default-features = false } alloy = { git = "https://github.com/alloy-rs/alloy", rev = "89f14f9", features = [ "serde", + "rpc-types-eth", + "rpc-types" ] } # Serde diff --git a/crates/op-rpc-types/Cargo.toml b/crates/op-rpc-types/Cargo.toml index aef654e6..faf8e835 100644 --- a/crates/op-rpc-types/Cargo.toml +++ b/crates/op-rpc-types/Cargo.toml @@ -21,10 +21,9 @@ alloy-primitives = { workspace = true, features = ["rlp", "serde", "std"] } # alloy-eips = { workspace = true, features = ["std", "serde"] } serde = { workspace = true, features = ["derive"] } -alloy = { workspace = true , features = ["serde"]} +alloy = { workspace = true , features = ["serde", "rpc-types", "rpc-types-eth"]} serde_json.workspace = true - # arbitrary arbitrary = { version = "1.3", features = ["derive"], optional = true } proptest = { version = "1.4", optional = true } diff --git a/crates/op-rpc-types/src/op/block.rs b/crates/op-rpc-types/src/op/block.rs index ec02f8e5..62a3152e 100644 --- a/crates/op-rpc-types/src/op/block.rs +++ b/crates/op-rpc-types/src/op/block.rs @@ -2,10 +2,11 @@ #![allow(unknown_lints, non_local_definitions)] -use crate::{other::OtherFields, Transaction, Withdrawal}; +use crate::op::transaction::Transaction; +use alloy::rpc::types::eth::Header; use alloy_eips::{calc_blob_gasprice, calc_excess_blob_gas}; use alloy_primitives::{ - ruint::ParseError, Address, BlockHash, BlockNumber, Bloom, Bytes, B256, U256, U64, + ruint::ParseError, Address, BlockHash, BlockNumber, Bloom, Bytes, B256, B64, U256, U64, }; use alloy_rlp::{bytes, Decodable, Encodable, Error as RlpError}; use serde::{ @@ -38,9 +39,6 @@ pub struct Block { /// Withdrawals in the block. #[serde(default, skip_serializing_if = "Option::is_none")] pub withdrawals: Option>, - /// Support for arbitrary additional fields. - #[serde(flatten)] - pub other: OtherFields, } impl Block { @@ -50,122 +48,6 @@ impl Block { } } -/// Block header representation. -#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] -#[serde(rename_all = "camelCase")] -pub struct Header { - /// Hash of the block - pub hash: Option, - /// Hash of the parent - pub parent_hash: B256, - /// Hash of the uncles - #[serde(rename = "sha3Uncles")] - pub uncles_hash: B256, - /// Alias of `author` - pub miner: Address, - /// State root hash - pub state_root: B256, - /// Transactions root hash - pub transactions_root: B256, - /// Transactions receipts root hash - pub receipts_root: B256, - /// Logs bloom - pub logs_bloom: Bloom, - /// Difficulty - pub difficulty: U256, - /// Block number - #[serde(default, with = "alloy_serde::num::u64_hex_opt")] - pub number: Option, - /// Gas Limit - #[serde(default, with = "alloy_serde::num::u128_hex_or_decimal")] - pub gas_limit: u128, - /// Gas Used - #[serde(default, with = "alloy_serde::num::u128_hex_or_decimal")] - pub gas_used: u128, - /// Timestamp - #[serde(default, with = "alloy_serde::num::u64_hex")] - pub timestamp: u64, - /// Total difficulty - #[serde(skip_serializing_if = "Option::is_none")] - pub total_difficulty: Option, - /// Extra data - pub extra_data: Bytes, - /// Mix Hash - /// - /// Before the merge this proves, combined with the nonce, that a sufficient amount of - /// computation has been carried out on this block: the Proof-of-Work (PoF). - /// - /// After the merge this is `prevRandao`: Randomness value for the generated payload. - /// - /// This is an Option because it is not always set by non-ethereum networks. - /// - /// See also - /// And - #[serde(skip_serializing_if = "Option::is_none")] - pub mix_hash: Option, - /// Nonce - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u64_hex_opt" - )] - pub nonce: Option, - /// Base fee per unit of gas (if past London) - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u128_hex_or_decimal_opt" - )] - pub base_fee_per_gas: Option, - /// Withdrawals root hash added by EIP-4895 and is ignored in legacy headers. - #[serde(skip_serializing_if = "Option::is_none")] - pub withdrawals_root: Option, - /// Blob gas used - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u128_hex_or_decimal_opt" - )] - pub blob_gas_used: Option, - /// Excess blob gas - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u128_hex_or_decimal_opt" - )] - pub excess_blob_gas: Option, - /// Parent beacon block root - #[serde(skip_serializing_if = "Option::is_none")] - pub parent_beacon_block_root: Option, -} - -impl Header { - /// Returns the blob fee for _this_ block according to the EIP-4844 spec. - /// - /// Returns `None` if `excess_blob_gas` is None - pub fn blob_fee(&self) -> Option { - self.excess_blob_gas.map(calc_blob_gasprice) - } - - /// Returns the blob fee for the next block according to the EIP-4844 spec. - /// - /// Returns `None` if `excess_blob_gas` is None. - /// - /// See also [Self::next_block_excess_blob_gas] - pub fn next_block_blob_fee(&self) -> Option { - self.next_block_excess_blob_gas().map(calc_blob_gasprice) - } - - /// Calculate excess blob gas for the next block according to the EIP-4844 - /// spec. - /// - /// Returns a `None` if no excess blob gas is set, no EIP-4844 support - pub fn next_block_excess_blob_gas(&self) -> Option { - Some(calc_excess_blob_gas(self.excess_blob_gas?, self.blob_gas_used?)) - } -} - /// Block Transactions depending on the boolean attribute of `eth_getBlockBy*`, /// or if used by `eth_getUncle*` #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] @@ -1146,371 +1028,4 @@ pub struct BlockOverrides { /// solidity opcode BLOCKHASH. #[serde(default, skip_serializing_if = "Option::is_none")] pub block_hash: Option>, -} - -#[cfg(test)] -mod tests { - use super::*; - use arbitrary::Arbitrary; - use rand::Rng; - - #[test] - fn arbitrary_header() { - let mut bytes = [0u8; 1024]; - rand::thread_rng().fill(bytes.as_mut_slice()); - let _: Header = Header::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(); - } - - #[test] - fn test_full_conversion() { - let full = true; - assert_eq!(BlockTransactionsKind::Full, full.into()); - - let full = false; - assert_eq!(BlockTransactionsKind::Hashes, full.into()); - } - - #[test] - #[cfg(feature = "jsonrpsee-types")] - fn serde_json_header() { - use jsonrpsee_types::SubscriptionResponse; - let resp = r#"{"jsonrpc":"2.0","method":"eth_subscribe","params":{"subscription":"0x7eef37ff35d471f8825b1c8f67a5d3c0","result":{"hash":"0x7a7ada12e140961a32395059597764416499f4178daf1917193fad7bd2cc6386","parentHash":"0xdedbd831f496e705e7f2ec3c8dcb79051040a360bf1455dbd7eb8ea6ad03b751","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":"0x8","gasUsed":"0x0","gasLimit":"0x1c9c380","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x642aa48f","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000"}}}"#; - let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap(); - - let resp = r#"{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x1a14b6bdcf4542fabf71c4abee244e47","result":{"author":"0x000000568b9b5a365eaa767d42e74ed88915c204","difficulty":"0x1","extraData":"0x4e65746865726d696e6420312e392e32322d302d6463373666616366612d32308639ad8ff3d850a261f3b26bc2a55e0f3a718de0dd040a19a4ce37e7b473f2d7481448a1e1fd8fb69260825377c0478393e6055f471a5cf839467ce919a6ad2700","gasLimit":"0x7a1200","gasUsed":"0x0","hash":"0xa4856602944fdfd18c528ef93cc52a681b38d766a7e39c27a47488c8461adcb0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x434822","parentHash":"0x1a9bdc31fc785f8a95efeeb7ae58f40f6366b8e805f47447a52335c95f4ceb49","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x261","stateRoot":"0xf38c4bf2958e541ec6df148e54ce073dc6b610f8613147ede568cb7b5c2d81ee","totalDifficulty":"0x633ebd","timestamp":"0x604726b0","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}}}"#; - let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap(); - } - - #[test] - fn serde_block() { - let block = Block { - header: Header { - hash: Some(B256::with_last_byte(1)), - parent_hash: B256::with_last_byte(2), - uncles_hash: B256::with_last_byte(3), - miner: Address::with_last_byte(4), - state_root: B256::with_last_byte(5), - transactions_root: B256::with_last_byte(6), - receipts_root: B256::with_last_byte(7), - withdrawals_root: Some(B256::with_last_byte(8)), - number: Some(9), - gas_used: 10, - gas_limit: 11, - extra_data: Bytes::from(vec![1, 2, 3]), - logs_bloom: Bloom::default(), - timestamp: 12, - difficulty: U256::from(13), - total_difficulty: Some(U256::from(100000)), - mix_hash: Some(B256::with_last_byte(14)), - nonce: Some(15), - base_fee_per_gas: Some(20), - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, - }, - uncles: vec![B256::with_last_byte(17)], - transactions: BlockTransactions::Hashes(vec![B256::with_last_byte(18)]), - size: Some(U256::from(19)), - withdrawals: Some(vec![]), - other: Default::default(), - }; - let serialized = serde_json::to_string(&block).unwrap(); - assert_eq!( - serialized, - r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","totalDifficulty":"0x186a0","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0xf","baseFeePerGas":"0x14","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"size":"0x13","withdrawals":[]}"# - ); - let deserialized: Block = serde_json::from_str(&serialized).unwrap(); - assert_eq!(block, deserialized); - } - - #[test] - fn serde_uncle_block() { - let block = Block { - header: Header { - hash: Some(B256::with_last_byte(1)), - parent_hash: B256::with_last_byte(2), - uncles_hash: B256::with_last_byte(3), - miner: Address::with_last_byte(4), - state_root: B256::with_last_byte(5), - transactions_root: B256::with_last_byte(6), - receipts_root: B256::with_last_byte(7), - withdrawals_root: Some(B256::with_last_byte(8)), - number: Some(9), - gas_used: 10, - gas_limit: 11, - extra_data: Bytes::from(vec![1, 2, 3]), - logs_bloom: Bloom::default(), - timestamp: 12, - difficulty: U256::from(13), - total_difficulty: Some(U256::from(100000)), - mix_hash: Some(B256::with_last_byte(14)), - nonce: Some(15), - base_fee_per_gas: Some(20), - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, - }, - uncles: vec![], - transactions: BlockTransactions::Uncle, - size: Some(U256::from(19)), - withdrawals: None, - other: Default::default(), - }; - let serialized = serde_json::to_string(&block).unwrap(); - assert_eq!( - serialized, - r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","totalDifficulty":"0x186a0","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0xf","baseFeePerGas":"0x14","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","uncles":[],"size":"0x13"}"# - ); - let deserialized: Block = serde_json::from_str(&serialized).unwrap(); - assert_eq!(block, deserialized); - } - - #[test] - fn serde_block_with_withdrawals_set_as_none() { - let block = Block { - header: Header { - hash: Some(B256::with_last_byte(1)), - parent_hash: B256::with_last_byte(2), - uncles_hash: B256::with_last_byte(3), - miner: Address::with_last_byte(4), - state_root: B256::with_last_byte(5), - transactions_root: B256::with_last_byte(6), - receipts_root: B256::with_last_byte(7), - withdrawals_root: None, - number: Some(9), - gas_used: 10, - gas_limit: 11, - extra_data: Bytes::from(vec![1, 2, 3]), - logs_bloom: Bloom::default(), - timestamp: 12, - difficulty: U256::from(13), - total_difficulty: Some(U256::from(100000)), - mix_hash: Some(B256::with_last_byte(14)), - nonce: Some(15), - base_fee_per_gas: Some(20), - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, - }, - uncles: vec![B256::with_last_byte(17)], - transactions: BlockTransactions::Hashes(vec![B256::with_last_byte(18)]), - size: Some(U256::from(19)), - withdrawals: None, - other: Default::default(), - }; - let serialized = serde_json::to_string(&block).unwrap(); - assert_eq!( - serialized, - r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","totalDifficulty":"0x186a0","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0xf","baseFeePerGas":"0x14","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"size":"0x13"}"# - ); - let deserialized: Block = serde_json::from_str(&serialized).unwrap(); - assert_eq!(block, deserialized); - } - - #[test] - fn block_overrides() { - let s = r#"{"blockNumber": "0xe39dd0"}"#; - let _overrides = serde_json::from_str::(s).unwrap(); - } - - #[test] - fn serde_rich_block() { - let s = r#"{ - "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4", - "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d", - "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "miner": "0x829bd824b016326a401d083b33d092293333a830", - "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84", - "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe", - "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0", - "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794", - "difficulty": "0xc40faff9c737d", - "number": "0xa9a230", - "gasLimit": "0xbe5a66", - "gasUsed": "0xbe0fcc", - "timestamp": "0x5f93b749", - "totalDifficulty": "0x3dc957fd8167fb2684a", - "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103", - "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc", - "nonce": "0x4722f2acd35abe0f", - "uncles": [], - "transactions": [ - "0xf435a26acc2a9ef73ac0b73632e32e29bd0e28d5c4f46a7e18ed545c93315916" - ], - "size": "0xaeb6" -}"#; - - let block = serde_json::from_str::(s).unwrap(); - let serialized = serde_json::to_string(&block).unwrap(); - let block2 = serde_json::from_str::(&serialized).unwrap(); - assert_eq!(block, block2); - } - - #[test] - fn serde_missing_uncles_block() { - let s = r#"{ - "baseFeePerGas":"0x886b221ad", - "blobGasUsed":"0x0", - "difficulty":"0x0", - "excessBlobGas":"0x0", - "extraData":"0x6265617665726275696c642e6f7267", - "gasLimit":"0x1c9c380", - "gasUsed":"0xb0033c", - "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac", - "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427", - "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5", - "nonce":"0x0000000000000000", - "number":"0x128c6df", - "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc", - "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717", - "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90", - "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size":"0xdcc3", - "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404", - "timestamp":"0x65f5f4c3", - "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780", - "withdrawals":[ - { - "index":"0x24d80e6", - "validatorIndex":"0x8b2b6", - "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff", - "amount":"0x1161f10" - } - ], - "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7" - }"#; - - let block = serde_json::from_str::(s).unwrap(); - let serialized = serde_json::to_string(&block).unwrap(); - let block2 = serde_json::from_str::(&serialized).unwrap(); - assert_eq!(block, block2); - } - - #[test] - fn serde_block_containing_uncles() { - let s = r#"{ - "baseFeePerGas":"0x886b221ad", - "blobGasUsed":"0x0", - "difficulty":"0x0", - "excessBlobGas":"0x0", - "extraData":"0x6265617665726275696c642e6f7267", - "gasLimit":"0x1c9c380", - "gasUsed":"0xb0033c", - "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac", - "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427", - "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", - "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5", - "nonce":"0x0000000000000000", - "number":"0x128c6df", - "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc", - "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717", - "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90", - "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size":"0xdcc3", - "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404", - "timestamp":"0x65f5f4c3", - "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780", - "uncles": ["0x123a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780", "0x489a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780"], - "withdrawals":[ - { - "index":"0x24d80e6", - "validatorIndex":"0x8b2b6", - "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff", - "amount":"0x1161f10" - } - ], - "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7" - }"#; - - let block = serde_json::from_str::(s).unwrap(); - assert!(block.uncles.len() == 2); - let serialized = serde_json::to_string(&block).unwrap(); - let block2 = serde_json::from_str::(&serialized).unwrap(); - assert_eq!(block, block2); - } - - #[test] - fn compact_block_number_serde() { - let num: BlockNumberOrTag = 1u64.into(); - let serialized = serde_json::to_string(&num).unwrap(); - assert_eq!(serialized, "\"0x1\""); - } - - #[test] - fn can_parse_eip1898_block_ids() { - let num = serde_json::json!( - { "blockNumber": "0x0" } - ); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Number(0u64))); - - let num = serde_json::json!( - { "blockNumber": "pending" } - ); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Pending)); - - let num = serde_json::json!( - { "blockNumber": "latest" } - ); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Latest)); - - let num = serde_json::json!( - { "blockNumber": "finalized" } - ); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Finalized)); - - let num = serde_json::json!( - { "blockNumber": "safe" } - ); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Safe)); - - let num = serde_json::json!( - { "blockNumber": "earliest" } - ); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Earliest)); - - let num = serde_json::json!("0x0"); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Number(0u64))); - - let num = serde_json::json!("pending"); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Pending)); - - let num = serde_json::json!("latest"); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Latest)); - - let num = serde_json::json!("finalized"); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Finalized)); - - let num = serde_json::json!("safe"); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Safe)); - - let num = serde_json::json!("earliest"); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!(id, BlockId::Number(BlockNumberOrTag::Earliest)); - - let num = serde_json::json!( - { "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" } - ); - let id = serde_json::from_value::(num).unwrap(); - assert_eq!( - id, - BlockId::Hash( - "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" - .parse::() - .unwrap() - .into() - ) - ); - } -} +} \ No newline at end of file diff --git a/crates/op-rpc-types/src/op/log.rs b/crates/op-rpc-types/src/op/log.rs deleted file mode 100644 index ed0e1b9b..00000000 --- a/crates/op-rpc-types/src/op/log.rs +++ /dev/null @@ -1,179 +0,0 @@ -use alloy_primitives::{LogData, B256}; -use serde::{Deserialize, Serialize}; -use alloy::serde as alloy_serde; - -/// Optimism Log emitted by a transaction -#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] -#[cfg_attr( - any(test, feature = "arbitrary"), - derive(proptest_derive::Arbitrary, arbitrary::Arbitrary) -)] -#[serde(rename_all = "camelCase")] -pub struct Log { - #[serde(flatten)] - /// Consensus log object - pub inner: alloy_primitives::Log, - /// Hash of the block the transaction that emitted this log was mined in - pub block_hash: Option, - /// Number of the block the transaction that emitted this log was mined in - #[serde(with = "alloy_serde::u64_hex_opt")] - pub block_number: Option, - /// The timestamp of the block as proposed in: - /// - /// - #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u64_hex_opt", default)] - pub block_timestamp: Option, - /// Transaction Hash - pub transaction_hash: Option, - /// Index of the Transaction in the block - #[serde(with = "alloy_serde::u64_hex_opt")] - pub transaction_index: Option, - /// Log Index in Block - #[serde(with = "alloy_serde::u64_hex_opt")] - pub log_index: Option, - /// Geth Compatibility Field: whether this log was removed - #[serde(default)] - pub removed: bool, -} - -impl Log { - /// Getter for the address field. Shortcut for `log.inner.address`. - pub const fn address(&self) -> alloy_primitives::Address { - self.inner.address - } - - /// Getter for the data field. Shortcut for `log.inner.data`. - pub const fn data(&self) -> &T { - &self.inner.data - } -} - -impl Log { - /// Getter for the topics field. Shortcut for `log.inner.topics()`. - pub fn topics(&self) -> &[B256] { - self.inner.topics() - } - - /// Get the topic list, mutably. This gives access to the internal - /// array, without allowing extension of that array. Shortcut for - /// [`LogData::topics_mut`] - pub fn topics_mut(&mut self) -> &mut [B256] { - self.inner.data.topics_mut() - } - - /// Decode the log data into a typed log. - pub fn log_decode(&self) -> alloy_sol_types::Result> { - let decoded = T::decode_log(&self.inner, false)?; - Ok(Log { - inner: decoded, - block_hash: self.block_hash, - block_number: self.block_number, - block_timestamp: self.block_timestamp, - transaction_hash: self.transaction_hash, - transaction_index: self.transaction_index, - log_index: self.log_index, - removed: self.removed, - }) - } -} - -impl Log -where - for<'a> &'a T: Into, -{ - /// Reserialize the data. - pub fn reserialize(&self) -> Log { - Log { - inner: alloy_primitives::Log { - address: self.inner.address, - data: (&self.inner.data).into(), - }, - block_hash: self.block_hash, - block_number: self.block_number, - block_timestamp: self.block_timestamp, - transaction_hash: self.transaction_hash, - transaction_index: self.transaction_index, - log_index: self.log_index, - removed: self.removed, - } - } -} - -impl AsRef> for Log { - fn as_ref(&self) -> &alloy_primitives::Log { - &self.inner - } -} - -impl AsMut> for Log { - fn as_mut(&mut self) -> &mut alloy_primitives::Log { - &mut self.inner - } -} - -impl AsRef for Log { - fn as_ref(&self) -> &T { - &self.inner.data - } -} - -impl AsMut for Log { - fn as_mut(&mut self) -> &mut T { - &mut self.inner.data - } -} - -#[cfg(test)] -mod tests { - use alloy_primitives::{Address, Bytes}; - - use super::*; - use arbitrary::Arbitrary; - use rand::Rng; - - #[test] - fn log_arbitrary() { - let mut bytes = [0u8; 1024]; - rand::thread_rng().fill(bytes.as_mut_slice()); - - let _: Log = Log::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(); - } - - #[test] - fn serde_log() { - let mut log = Log { - inner: alloy_primitives::Log { - address: Address::with_last_byte(0x69), - data: alloy_primitives::LogData::new_unchecked( - vec![B256::with_last_byte(0x69)], - Bytes::from_static(&[0x69]), - ), - }, - block_hash: Some(B256::with_last_byte(0x69)), - block_number: Some(0x69), - block_timestamp: None, - transaction_hash: Some(B256::with_last_byte(0x69)), - transaction_index: Some(0x69), - log_index: Some(0x69), - removed: false, - }; - let serialized = serde_json::to_string(&log).unwrap(); - assert_eq!( - serialized, - r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"# - ); - - let deserialized: Log = serde_json::from_str(&serialized).unwrap(); - assert_eq!(log, deserialized); - - log.block_timestamp = Some(0x69); - let serialized = serde_json::to_string(&log).unwrap(); - assert_eq!( - serialized, - r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","blockTimestamp":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"# - ); - - let deserialized: Log = serde_json::from_str(&serialized).unwrap(); - assert_eq!(log, deserialized); - } -} diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs index 0536f312..772da96b 100644 --- a/crates/op-rpc-types/src/op/mod.rs +++ b/crates/op-rpc-types/src/op/mod.rs @@ -1,2 +1,3 @@ mod transaction; -mod log; \ No newline at end of file +mod block; +pub use alloy::rpc::types::eth::{Header, Log}; diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs index d2a67f66..92dac0ec 100644 --- a/crates/op-rpc-types/src/op/transaction/mod.rs +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -1,2 +1,244 @@ +use alloy::serde as alloy_serde; +use alloy::rpc::types::eth::{AccessList, Signature}; +use alloy_primitives::{Address, Bytes, B256, U128, U256, U64}; +use serde::{Deserialize, Serialize}; + +use self::{request::TransactionRequest, tx_type::TxType}; + mod receipt; -mod tx_type; \ No newline at end of file +mod request; +mod tx_type; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +#[serde(rename_all = "camelCase")] +pub struct Transaction { + /// Hash + pub hash: B256, + /// Nonce + #[serde(with = "alloy_serde::num::u64_hex")] + pub nonce: u64, + /// Block hash + pub block_hash: Option, + /// Block number + #[serde(with = "alloy_serde::num::u64_hex_opt")] + pub block_number: Option, + /// Transaction Index + #[serde(with = "alloy_serde::num::u64_hex_opt")] + pub transaction_index: Option, + /// Sender + pub from: Address, + /// Recipient + pub to: Option
, + /// Transferred value + pub value: U256, + /// Gas Price + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u128_hex_or_decimal_opt" + )] + pub gas_price: Option, + /// Gas amount + #[serde(with = "alloy_serde::num::u128_hex_or_decimal")] + pub gas: u128, + /// Max BaseFeePerGas the user is willing to pay. + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u128_hex_or_decimal_opt" + )] + pub max_fee_per_gas: Option, + /// The miner's tip. + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u128_hex_or_decimal_opt" + )] + pub max_priority_fee_per_gas: Option, + /// Data + pub input: Bytes, + /// All _flattened_ fields of the transaction signature. + /// + /// Note: this is an option so special transaction types without a signature (e.g. ) can be supported. + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub signature: Option, + /// The chain id of the transaction, if any. + #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::u64_hex_opt")] + pub chain_id: Option, + /// EIP2930 + /// + /// Pre-pay to warm storage access. + #[serde(skip_serializing_if = "Option::is_none")] + pub access_list: Option, + /// EIP2718 + /// + /// Transaction type, Some(2) for EIP-1559 transaction, + /// Some(1) for AccessList transaction, None for Legacy + #[serde( + default, + rename = "type", + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u8_hex_opt" + )] + pub transaction_type: Option, + /// The ETH value to mint on L2 + #[serde(rename = "mint", skip_serializing_if = "Option::is_none")] + pub mint: Option, + /// Hash that uniquely identifies the source of the deposit. + #[serde(rename = "sourceHash", skip_serializing_if = "Option::is_none")] + pub source_hash: Option, + /// Field indicating whether the transaction is a system transaction, and therefore + /// exempt from the L2 gas limit. + #[serde(rename = "isSystemTx", skip_serializing_if = "Option::is_none")] + pub is_system_tx: Option, + /// Deposit receipt version for deposit transactions post-canyon + #[serde(skip_serializing_if = "Option::is_none")] + pub deposit_receipt_version: Option, +} + +impl Transaction { + /// Converts [Transaction] into [TransactionRequest]. + /// + /// During this conversion data for [TransactionRequest::sidecar] is not populated as it is not + /// part of [Transaction]. + pub fn into_request(self) -> TransactionRequest { + let gas_price = match (self.gas_price, self.max_fee_per_gas) { + (Some(gas_price), None) => Some(gas_price), + // EIP-1559 transactions include deprecated `gasPrice` field displaying gas used by + // transaction. + // Setting this field for resulted tx request will result in it being invalid + (_, Some(_)) => None, + // unreachable + (None, None) => None, + }; + TransactionRequest { + from: Some(self.from), + to: self.to, + gas: Some(self.gas), + gas_price, + value: Some(self.value), + input: self.input.into(), + nonce: Some(self.nonce), + chain_id: self.chain_id, + access_list: self.access_list, + transaction_type: self.transaction_type, + max_fee_per_gas: self.max_fee_per_gas, + max_priority_fee_per_gas: self.max_priority_fee_per_gas, + } + } +} + +impl TryFrom for Signed { + type Error = ConversionError; + + fn try_from(tx: Transaction) -> Result { + let signature = tx.signature.ok_or(ConversionError::MissingSignature)?.try_into()?; + + let tx = TxLegacy { + chain_id: tx.chain_id, + nonce: tx.nonce, + gas_price: tx.gas_price.ok_or(ConversionError::MissingGasPrice)?, + gas_limit: tx.gas, + to: tx.to.into(), + value: tx.value, + input: tx.input, + }; + Ok(tx.into_signed(signature)) + } +} + +impl TryFrom for Signed { + type Error = ConversionError; + + fn try_from(tx: Transaction) -> Result { + let signature = tx.signature.ok_or(ConversionError::MissingSignature)?.try_into()?; + + let tx = TxEip1559 { + chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?, + nonce: tx.nonce, + max_fee_per_gas: tx.max_fee_per_gas.ok_or(ConversionError::MissingMaxFeePerGas)?, + max_priority_fee_per_gas: tx + .max_priority_fee_per_gas + .ok_or(ConversionError::MissingMaxPriorityFeePerGas)?, + gas_limit: tx.gas, + to: tx.to.into(), + value: tx.value, + input: tx.input, + access_list: tx.access_list.unwrap_or_default(), + }; + Ok(tx.into_signed(signature)) + } +} + +impl TryFrom for Signed { + type Error = ConversionError; + + fn try_from(tx: Transaction) -> Result { + let signature = tx.signature.ok_or(ConversionError::MissingSignature)?.try_into()?; + + let tx = TxEip2930 { + chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?, + nonce: tx.nonce, + gas_price: tx.gas_price.ok_or(ConversionError::MissingGasPrice)?, + gas_limit: tx.gas, + to: tx.to.into(), + value: tx.value, + input: tx.input, + access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, + }; + Ok(tx.into_signed(signature)) + } +} + +impl TryFrom for Signed { + type Error = ConversionError; + + fn try_from(tx: Transaction) -> Result { + let signature = tx.signature.ok_or(ConversionError::MissingSignature)?.try_into()?; + let tx = TxEip4844 { + chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?, + nonce: tx.nonce, + max_fee_per_gas: tx.max_fee_per_gas.ok_or(ConversionError::MissingMaxFeePerGas)?, + max_priority_fee_per_gas: tx + .max_priority_fee_per_gas + .ok_or(ConversionError::MissingMaxPriorityFeePerGas)?, + gas_limit: tx.gas, + to: tx.to.ok_or(ConversionError::MissingTo)?, + value: tx.value, + input: tx.input, + access_list: tx.access_list.unwrap_or_default(), + blob_versioned_hashes: tx.blob_versioned_hashes.unwrap_or_default(), + max_fee_per_blob_gas: tx + .max_fee_per_blob_gas + .ok_or(ConversionError::MissingMaxFeePerBlobGas)?, + }; + Ok(tx.into_signed(signature)) + } +} + +impl TryFrom for Signed { + type Error = ConversionError; + + fn try_from(tx: Transaction) -> Result { + let tx: Signed = tx.try_into()?; + let (inner, signature, _) = tx.into_parts(); + let tx = TxEip4844Variant::TxEip4844(inner); + + Ok(tx.into_signed(signature)) + } +} + +impl TryFrom for TxEnvelope { + type Error = ConversionError; + + fn try_from(tx: Transaction) -> Result { + match tx.transaction_type.unwrap_or_default().try_into()? { + TxType::Legacy => Ok(Self::Legacy(tx.try_into()?)), + TxType::Eip1559 => Ok(Self::Eip1559(tx.try_into()?)), + TxType::Eip2930 => Ok(Self::Eip2930(tx.try_into()?)), + TxType::Eip4844 => Ok(Self::Eip4844(tx.try_into()?)), + TxType::Deposit => Ok(Self::Deposit(tx.try_into()?)), + } + } +} diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index 6063d22f..2fd171c1 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -1,9 +1,8 @@ -use crate::{op::log::Log, op::transaction::tx_type}; +use crate::op::transaction::tx_type; +use alloy::{rpc::types::eth::Log, serde as alloy_serde}; use alloy_consensus::{AnyReceiptEnvelope, ReceiptEnvelope, TxType}; use alloy_primitives::{Address, B256}; use serde::{Deserialize, Serialize}; -use alloy::serde as alloy_serde; - /// Transaction receipt /// /// This type is generic over an inner [`ReceiptEnvelope`] which contains @@ -64,16 +63,28 @@ pub struct TransactionReceipt> { #[serde(skip_serializing_if = "Option::is_none", rename = "root")] pub state_root: Option, /// The fee associated with a transaction on the Layer 1 - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::u128_hex_or_decimal_opt" + )] pub l1_fee: Option, /// A multiplier applied to the actual gas usage on Layer 1 to calculate the dynamic costs. #[serde(default, skip_serializing_if = "Option::is_none", with = "l1_fee_scalar_serde")] pub l1_fee_scalar: Option, /// The gas price for transactions on the Layer 1 - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::u128_hex_or_decimal_opt" + )] pub l1_gas_price: Option, /// The amount of gas consumed by a transaction on the Layer 1 - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::u128_hex_or_decimal_opt")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::u128_hex_or_decimal_opt" + )] pub l1_gas_used: Option, /// Deposit nonce for Optimism deposit transactions #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u64_hex_opt")] diff --git a/crates/op-rpc-types/src/op/transaction/request.rs b/crates/op-rpc-types/src/op/transaction/request.rs new file mode 100644 index 00000000..ccebdd7e --- /dev/null +++ b/crates/op-rpc-types/src/op/transaction/request.rs @@ -0,0 +1,444 @@ +//! Alloy basic Transaction Request type. + +use crate::op::transaction::Transaction; +use alloy::{rpc::types::eth::transaction::AccessList, serde as alloy_serde}; +use alloy_primitives::{Address, Bytes, ChainId, TxHash, TxKind, U256}; +use serde::{Deserialize, Serialize}; +use std::hash::Hash; + +/// Represents _all_ transaction requests to/from RPC. +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionRequest { + /// The address of the transaction author. + pub from: Option
, + /// The destination address of the transaction. + pub to: Option
, + /// The legacy gas price. + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u128_hex_or_decimal_opt" + )] + pub gas_price: Option, + /// The max base fee per gas the sender is willing to pay. + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u128_hex_or_decimal_opt" + )] + pub max_fee_per_gas: Option, + /// The max priority fee per gas the sender is willing to pay, also called the miner tip. + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u128_hex_or_decimal_opt" + )] + pub max_priority_fee_per_gas: Option, + /// The gas limit for the transaction. + #[serde(default, with = "alloy_serde::num::u128_hex_or_decimal_opt")] + pub gas: Option, + /// The value transferred in the transaction, in wei. + pub value: Option, + /// Transaction data. + #[serde(default, flatten)] + pub input: TransactionInput, + /// The nonce of the transaction. + #[serde(default, with = "alloy_serde::num::u64_hex_opt")] + pub nonce: Option, + /// The chain ID for the transaction. + #[serde(default, with = "alloy_serde::num::u64_hex_opt")] + pub chain_id: Option, + /// An EIP-2930 access list, which lowers cost for accessing accounts and storages in the list. See [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) for more information. + #[serde(default)] + pub access_list: Option, + /// The EIP-2718 transaction type. See [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) for more information. + #[serde(default, rename = "type", with = "alloy_serde::num::u8_hex_opt")] + pub transaction_type: Option, +} + +impl Hash for TransactionRequest { + fn hash(&self, state: &mut H) { + self.from.hash(state); + self.to.hash(state); + self.gas_price.hash(state); + self.max_fee_per_gas.hash(state); + self.max_priority_fee_per_gas.hash(state); + self.gas.hash(state); + self.value.hash(state); + self.input.hash(state); + self.nonce.hash(state); + self.chain_id.hash(state); + self.access_list.hash(state); + self.transaction_type.hash(state); + } +} + +// == impl TransactionRequest == + +impl TransactionRequest { + /// Returns the configured fee cap, if any. + /// + /// The returns `gas_price` (legacy) if set or `max_fee_per_gas` (EIP1559) + #[inline] + pub fn fee_cap(&self) -> Option { + self.gas_price.or(self.max_fee_per_gas) + } + + /// Returns true if the request has a `blobVersionedHashes` field but it is empty. + #[inline] + pub fn has_empty_blob_hashes(&self) -> bool { + self.blob_versioned_hashes.as_ref().map(|blobs| blobs.is_empty()).unwrap_or(false) + } + + /// Sets the `from` field in the call to the provided address + #[inline] + pub const fn from(mut self, from: Address) -> Self { + self.from = Some(from); + self + } + + /// Sets the gas limit for the transaction. + pub const fn gas_limit(mut self, gas_limit: u128) -> Self { + self.gas = Some(gas_limit); + self + } + + /// Sets the nonce for the transaction. + pub const fn nonce(mut self, nonce: u64) -> Self { + self.nonce = Some(nonce); + self + } + + /// Sets the maximum fee per gas for the transaction. + pub const fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self { + self.max_fee_per_gas = Some(max_fee_per_gas); + self + } + + /// Sets the maximum priority fee per gas for the transaction. + pub const fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self { + self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); + self + } + + /// Sets the recipient address for the transaction. + #[inline] + pub const fn to(mut self, to: Option
) -> Self { + self.to = to; + self + } + + /// Sets the value (amount) for the transaction. + pub const fn value(mut self, value: U256) -> Self { + self.value = Some(value); + self + } + + /// Sets the access list for the transaction. + pub fn access_list(mut self, access_list: AccessList) -> Self { + self.access_list = Some(access_list); + self + } + + /// Sets the input data for the transaction. + pub fn input(mut self, input: TransactionInput) -> Self { + self.input = input; + self + } + + /// Sets the transactions type for the transactions. + pub const fn transaction_type(mut self, transaction_type: u8) -> Self { + self.transaction_type = Some(transaction_type); + self + } +} + +/// Helper type that supports both `data` and `input` fields that map to transaction input data. +/// +/// This is done for compatibility reasons where older implementations used `data` instead of the +/// newer, recommended `input` field. +/// +/// If both fields are set, it is expected that they contain the same value, otherwise an error is +/// returned. +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] +pub struct TransactionInput { + /// Transaction data + #[serde(skip_serializing_if = "Option::is_none")] + pub input: Option, + /// Transaction data + /// + /// This is the same as `input` but is used for backwards compatibility: + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +impl TransactionInput { + /// Creates a new instance with the given input data. + pub const fn new(data: Bytes) -> Self { + Self::maybe_input(Some(data)) + } + + /// Creates a new instance with the given input data. + pub const fn maybe_input(input: Option) -> Self { + Self { input, data: None } + } + + /// Consumes the type and returns the optional input data. + #[inline] + pub fn into_input(self) -> Option { + self.input.or(self.data) + } + + /// Consumes the type and returns the optional input data. + /// + /// Returns an error if both `data` and `input` fields are set and not equal. + #[inline] + pub fn try_into_unique_input(self) -> Result, TransactionInputError> { + self.check_unique_input().map(|()| self.into_input()) + } + + /// Returns the optional input data. + #[inline] + pub fn input(&self) -> Option<&Bytes> { + self.input.as_ref().or(self.data.as_ref()) + } + + /// Returns the optional input data. + /// + /// Returns an error if both `data` and `input` fields are set and not equal. + #[inline] + pub fn unique_input(&self) -> Result, TransactionInputError> { + self.check_unique_input().map(|()| self.input()) + } + + fn check_unique_input(&self) -> Result<(), TransactionInputError> { + if let (Some(input), Some(data)) = (&self.input, &self.data) { + if input != data { + return Err(TransactionInputError::default()); + } + } + Ok(()) + } +} + +impl From> for TransactionInput { + fn from(input: Vec) -> Self { + Self { input: Some(input.into()), data: None } + } +} + +impl From for TransactionInput { + fn from(input: Bytes) -> Self { + Self { input: Some(input), data: None } + } +} + +impl From> for TransactionInput { + fn from(input: Option) -> Self { + Self { input, data: None } + } +} + +impl From for TransactionRequest { + fn from(tx: Transaction) -> TransactionRequest { + tx.into_request() + } +} + +impl From for TransactionRequest { + fn from(tx: TxLegacy) -> TransactionRequest { + TransactionRequest { + from: None, + to: if let TxKind::Call(to) = tx.to { Some(to) } else { None }, + gas_price: Some(tx.gas_price), + gas: Some(tx.gas_limit), + value: Some(tx.value), + input: TransactionInput::from(tx.input), + nonce: Some(tx.nonce), + chain_id: tx.chain_id, + transaction_type: Some(0), + ..Default::default() + } + } +} + +impl From for TransactionRequest { + fn from(tx: TxEip2930) -> TransactionRequest { + TransactionRequest { + from: None, + to: if let TxKind::Call(to) = tx.to { Some(to) } else { None }, + gas_price: Some(tx.gas_price), + gas: Some(tx.gas_limit), + value: Some(tx.value), + input: TransactionInput::from(tx.input), + nonce: Some(tx.nonce), + chain_id: Some(tx.chain_id), + transaction_type: Some(1), + access_list: Some(tx.access_list), + ..Default::default() + } + } +} + +impl From for TransactionRequest { + fn from(tx: TxEip1559) -> TransactionRequest { + TransactionRequest { + from: None, + to: if let TxKind::Call(to) = tx.to { Some(to) } else { None }, + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + gas: Some(tx.gas_limit), + value: Some(tx.value), + input: TransactionInput::from(tx.input), + nonce: Some(tx.nonce), + chain_id: Some(tx.chain_id), + transaction_type: Some(2), + access_list: Some(tx.access_list), + ..Default::default() + } + } +} + +impl From for TransactionRequest { + fn from(tx: TxEip4844) -> TransactionRequest { + TransactionRequest { + from: None, + to: Some(tx.to), + gas: Some(tx.gas_limit), + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + value: Some(tx.value), + input: TransactionInput::from(tx.input), + nonce: Some(tx.nonce), + chain_id: Some(tx.chain_id), + transaction_type: Some(3), + access_list: Some(tx.access_list), + ..Default::default() + } + } +} + +impl From for TransactionRequest { + fn from(tx: TxEip4844WithSidecar) -> TransactionRequest { + let sidecar = tx.sidecar; + let tx = tx.tx; + TransactionRequest { + from: None, + to: Some(tx.to), + gas: Some(tx.gas_limit), + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + value: Some(tx.value), + input: TransactionInput::from(tx.input), + nonce: Some(tx.nonce), + chain_id: Some(tx.chain_id), + transaction_type: Some(3), + access_list: Some(tx.access_list), + sidecar: Some(sidecar), + ..Default::default() + } + } +} + +impl From for TransactionRequest { + fn from(tx: TxEip4844Variant) -> TransactionRequest { + match tx { + TxEip4844Variant::TxEip4844(tx) => tx.into(), + TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.into(), + } + } +} + +impl From for TransactionRequest { + fn from(tx: TypedTransaction) -> TransactionRequest { + match tx { + TypedTransaction::Legacy(tx) => tx.into(), + TypedTransaction::Eip2930(tx) => tx.into(), + TypedTransaction::Eip1559(tx) => tx.into(), + TypedTransaction::Eip4844(tx) => tx.into(), + } + } +} + +impl From for TransactionRequest { + fn from(envelope: TxEnvelope) -> TransactionRequest { + match envelope { + TxEnvelope::Legacy(tx) => { + #[cfg(feature = "k256")] + { + let from = tx.recover_signer().ok(); + let tx: TransactionRequest = tx.strip_signature().into(); + if let Some(from) = from { + tx.from(from) + } else { + tx + } + } + + #[cfg(not(feature = "k256"))] + { + tx.strip_signature().into() + } + } + TxEnvelope::Eip2930(tx) => { + #[cfg(feature = "k256")] + { + let from = tx.recover_signer().ok(); + let tx: TransactionRequest = tx.strip_signature().into(); + if let Some(from) = from { + tx.from(from) + } else { + tx + } + } + + #[cfg(not(feature = "k256"))] + { + tx.strip_signature().into() + } + } + TxEnvelope::Eip1559(tx) => { + #[cfg(feature = "k256")] + { + let from = tx.recover_signer().ok(); + let tx: TransactionRequest = tx.strip_signature().into(); + if let Some(from) = from { + tx.from(from) + } else { + tx + } + } + + #[cfg(not(feature = "k256"))] + { + tx.strip_signature().into() + } + } + TxEnvelope::Eip4844(tx) => { + #[cfg(feature = "k256")] + { + let from = tx.recover_signer().ok(); + let tx: TransactionRequest = tx.strip_signature().into(); + if let Some(from) = from { + tx.from(from) + } else { + tx + } + } + + #[cfg(not(feature = "k256"))] + { + tx.strip_signature().into() + } + } + _ => Default::default(), + } + } +} + +/// Error thrown when both `data` and `input` fields are set and not equal. +#[derive(Debug, Default, thiserror::Error)] +#[error("both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data")] +#[non_exhaustive] +pub struct TransactionInputError; diff --git a/crates/op-rpc-types/src/op/transaction/tx_type.rs b/crates/op-rpc-types/src/op/transaction/tx_type.rs index f3f9f948..ca4172a9 100644 --- a/crates/op-rpc-types/src/op/transaction/tx_type.rs +++ b/crates/op-rpc-types/src/op/transaction/tx_type.rs @@ -42,7 +42,7 @@ impl From for u8 { TxType::EIP2930 => EIP2930_TX_TYPE_ID, TxType::EIP1559 => EIP1559_TX_TYPE_ID, TxType::EIP4844 => EIP4844_TX_TYPE_ID, - TxType::Deposit => DEPOSIT_TX_TYPE_ID + TxType::Deposit => DEPOSIT_TX_TYPE_ID, } } } From fafe2aa0a3e8b044bb309c1684a57c953125ae7f Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Thu, 11 Apr 2024 23:44:16 +0400 Subject: [PATCH 10/25] feat: add op-consensus and receiptEnvelope --- Cargo.toml | 1 + crates/op-consensus/Cargo.toml | 34 +++++++++++++++++ crates/op-consensus/src/lib.rs | 1 + crates/op-consensus/src/receipt/envelope.rs | 37 +++++++++++++++++++ crates/op-consensus/src/receipt/mod.rs | 1 + crates/op-rpc-types/Cargo.toml | 1 + .../src/op/transaction/receipt.rs | 1 + .../src/op/transaction/request.rs | 4 +- 8 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 crates/op-consensus/Cargo.toml create mode 100644 crates/op-consensus/src/lib.rs create mode 100644 crates/op-consensus/src/receipt/envelope.rs create mode 100644 crates/op-consensus/src/receipt/mod.rs diff --git a/Cargo.toml b/Cargo.toml index bcbffe12..dd7eed57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ alloy = { git = "https://github.com/alloy-rs/alloy", rev = "89f14f9", features = "rpc-types-eth", "rpc-types" ] } +op-consensus = { version = "0.1.0", default-features = false, path = "crates/op-consensus" } # Serde serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } diff --git a/crates/op-consensus/Cargo.toml b/crates/op-consensus/Cargo.toml new file mode 100644 index 00000000..e81f5dd8 --- /dev/null +++ b/crates/op-consensus/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "op-consensus" +description = "Optimism Consensus" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +authors.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +alloy-primitives = { workspace = true, features = ["rlp"] } +alloy = { workspace = true , features = ["serde", "rpc-types", "rpc-types-eth"]} + +sha2 = { version = "0.10", default-features = false } + +# arbitrary +arbitrary = { workspace = true, features = ["derive"], optional = true } + +# serde +serde = { workspace = true, features = ["derive"], optional = true } +serde_json.workspace = true + +[features] +serde = ["dep:serde"] + +[dev-dependencies] +arbitrary = { workspace = true, features = ["derive"] } +proptest.workspace = true +proptest-derive.workspace = true +rand.workspace = true \ No newline at end of file diff --git a/crates/op-consensus/src/lib.rs b/crates/op-consensus/src/lib.rs new file mode 100644 index 00000000..80498889 --- /dev/null +++ b/crates/op-consensus/src/lib.rs @@ -0,0 +1 @@ +mod receipt; \ No newline at end of file diff --git a/crates/op-consensus/src/receipt/envelope.rs b/crates/op-consensus/src/receipt/envelope.rs new file mode 100644 index 00000000..0270e584 --- /dev/null +++ b/crates/op-consensus/src/receipt/envelope.rs @@ -0,0 +1,37 @@ +/// Receipt envelope, as defined in [EIP-2718]. +/// +/// This enum distinguishes between tagged and untagged legacy receipts, as the +/// in-protocol merkle tree may commit to EITHER 0-prefixed or raw. Therefore +/// we must ensure that encoding returns the precise byte-array that was +/// decoded, preserving the presence or absence of the `TransactionType` flag. +/// +/// Transaction receipt payloads are specified in their respective EIPs. +/// +/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(tag = "type"))] +#[non_exhaustive] +pub enum ReceiptEnvelope { + /// Receipt envelope with no type flag. + #[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))] + Legacy(ReceiptWithBloom), + /// Receipt envelope with type flag 1, containing a [EIP-2930] receipt. + /// + /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + #[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))] + Eip2930(ReceiptWithBloom), + /// Receipt envelope with type flag 2, containing a [EIP-1559] receipt. + /// + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + #[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))] + Eip1559(ReceiptWithBloom), + /// Receipt envelope with type flag 2, containing a [EIP-4844] receipt. + /// + /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 + #[cfg_attr(feature = "serde", serde(rename = "0x3", alias = "0x03"))] + Eip4844(ReceiptWithBloom), + /// Receipt envelope for Optimism's deposit transactions + #[cfg_attr(feature = "serde", serde(rename = "0x7E", alias = "0x7E"))] + Deposit(ReceiptWithBloom), +} diff --git a/crates/op-consensus/src/receipt/mod.rs b/crates/op-consensus/src/receipt/mod.rs new file mode 100644 index 00000000..4363ad53 --- /dev/null +++ b/crates/op-consensus/src/receipt/mod.rs @@ -0,0 +1 @@ +mod envelope; \ No newline at end of file diff --git a/crates/op-rpc-types/Cargo.toml b/crates/op-rpc-types/Cargo.toml index faf8e835..7deae62d 100644 --- a/crates/op-rpc-types/Cargo.toml +++ b/crates/op-rpc-types/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true exclude.workspace = true [dependencies] +op-consensus = { workspace = true, features = ["serde"]} # alloy-rlp = { workspace = true, features = ["arrayvec", "derive"] } alloy-primitives = { workspace = true, features = ["rlp", "serde", "std"] } # alloy-serde.workspace = true diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index 2fd171c1..c844640e 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -1,5 +1,6 @@ use crate::op::transaction::tx_type; use alloy::{rpc::types::eth::Log, serde as alloy_serde}; +use op_consensus::receipt::ReceiptEnvelope; use alloy_consensus::{AnyReceiptEnvelope, ReceiptEnvelope, TxType}; use alloy_primitives::{Address, B256}; use serde::{Deserialize, Serialize}; diff --git a/crates/op-rpc-types/src/op/transaction/request.rs b/crates/op-rpc-types/src/op/transaction/request.rs index ccebdd7e..b2baf4f1 100644 --- a/crates/op-rpc-types/src/op/transaction/request.rs +++ b/crates/op-rpc-types/src/op/transaction/request.rs @@ -6,6 +6,8 @@ use alloy_primitives::{Address, Bytes, ChainId, TxHash, TxKind, U256}; use serde::{Deserialize, Serialize}; use std::hash::Hash; +use super::tx_type::TxType; + /// Represents _all_ transaction requests to/from RPC. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -54,7 +56,7 @@ pub struct TransactionRequest { pub access_list: Option, /// The EIP-2718 transaction type. See [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) for more information. #[serde(default, rename = "type", with = "alloy_serde::num::u8_hex_opt")] - pub transaction_type: Option, + pub transaction_type: Option, } impl Hash for TransactionRequest { From 259906043bf65c6a2e67fbee697f89d13709a11f Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Fri, 12 Apr 2024 01:19:54 +0400 Subject: [PATCH 11/25] feat: add call.rs and update visibility of transaction requests, types, and receipts. --- crates/op-consensus/src/receipt/envelope.rs | 14 ++++++++------ crates/op-rpc-types/src/op/call.rs | 13 +++++++++++++ crates/op-rpc-types/src/op/mod.rs | 1 + crates/op-rpc-types/src/op/transaction/mod.rs | 6 +++--- 4 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 crates/op-rpc-types/src/op/call.rs diff --git a/crates/op-consensus/src/receipt/envelope.rs b/crates/op-consensus/src/receipt/envelope.rs index 0270e584..f2048bf0 100644 --- a/crates/op-consensus/src/receipt/envelope.rs +++ b/crates/op-consensus/src/receipt/envelope.rs @@ -1,3 +1,5 @@ +use alloy::rpc::types::eth::Log; + /// Receipt envelope, as defined in [EIP-2718]. /// /// This enum distinguishes between tagged and untagged legacy receipts, as the @@ -12,26 +14,26 @@ #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "type"))] #[non_exhaustive] -pub enum ReceiptEnvelope { +pub enum ReceiptEnvelope { // TODO: Add T and receiptbloom /// Receipt envelope with no type flag. #[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))] - Legacy(ReceiptWithBloom), + Legacy, /// Receipt envelope with type flag 1, containing a [EIP-2930] receipt. /// /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 #[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))] - Eip2930(ReceiptWithBloom), + Eip2930, /// Receipt envelope with type flag 2, containing a [EIP-1559] receipt. /// /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 #[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))] - Eip1559(ReceiptWithBloom), + Eip1559, /// Receipt envelope with type flag 2, containing a [EIP-4844] receipt. /// /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 #[cfg_attr(feature = "serde", serde(rename = "0x3", alias = "0x03"))] - Eip4844(ReceiptWithBloom), + Eip4844, /// Receipt envelope for Optimism's deposit transactions #[cfg_attr(feature = "serde", serde(rename = "0x7E", alias = "0x7E"))] - Deposit(ReceiptWithBloom), + Deposit, } diff --git a/crates/op-rpc-types/src/op/call.rs b/crates/op-rpc-types/src/op/call.rs new file mode 100644 index 00000000..d6b8b398 --- /dev/null +++ b/crates/op-rpc-types/src/op/call.rs @@ -0,0 +1,13 @@ +use crate::op::transaction::request::TransactionRequest; +use alloy::rpc::types::eth::BlockOverrides; +use serde::{Deserialize, Serialize}; + +/// Bundle of transactions +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(default, rename_all = "camelCase")] +pub struct Bundle { + /// All transactions to execute + pub transactions: Vec, + /// Block overrides to apply + pub block_override: Option, +} diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs index 772da96b..bb377f1a 100644 --- a/crates/op-rpc-types/src/op/mod.rs +++ b/crates/op-rpc-types/src/op/mod.rs @@ -1,3 +1,4 @@ mod transaction; mod block; +mod call; pub use alloy::rpc::types::eth::{Header, Log}; diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs index 92dac0ec..8b9c3f92 100644 --- a/crates/op-rpc-types/src/op/transaction/mod.rs +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -5,9 +5,9 @@ use serde::{Deserialize, Serialize}; use self::{request::TransactionRequest, tx_type::TxType}; -mod receipt; -mod request; -mod tx_type; +pub mod receipt; +pub mod request; +pub mod tx_type; #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] From d5c9b787be470c624f2c430a0a8b2726373a9e9f Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Fri, 12 Apr 2024 01:20:16 +0400 Subject: [PATCH 12/25] lint: cargo fmt. --- crates/op-consensus/src/lib.rs | 2 +- crates/op-consensus/src/receipt/envelope.rs | 3 ++- crates/op-consensus/src/receipt/mod.rs | 2 +- crates/op-rpc-types/src/op/block.rs | 2 +- crates/op-rpc-types/src/op/mod.rs | 2 +- crates/op-rpc-types/src/op/transaction/mod.rs | 6 ++++-- crates/op-rpc-types/src/op/transaction/receipt.rs | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/op-consensus/src/lib.rs b/crates/op-consensus/src/lib.rs index 80498889..4df93164 100644 --- a/crates/op-consensus/src/lib.rs +++ b/crates/op-consensus/src/lib.rs @@ -1 +1 @@ -mod receipt; \ No newline at end of file +mod receipt; diff --git a/crates/op-consensus/src/receipt/envelope.rs b/crates/op-consensus/src/receipt/envelope.rs index f2048bf0..94584917 100644 --- a/crates/op-consensus/src/receipt/envelope.rs +++ b/crates/op-consensus/src/receipt/envelope.rs @@ -14,7 +14,8 @@ use alloy::rpc::types::eth::Log; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "type"))] #[non_exhaustive] -pub enum ReceiptEnvelope { // TODO: Add T and receiptbloom +pub enum ReceiptEnvelope { + // TODO: Add T and receiptbloom /// Receipt envelope with no type flag. #[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))] Legacy, diff --git a/crates/op-consensus/src/receipt/mod.rs b/crates/op-consensus/src/receipt/mod.rs index 4363ad53..172ee72b 100644 --- a/crates/op-consensus/src/receipt/mod.rs +++ b/crates/op-consensus/src/receipt/mod.rs @@ -1 +1 @@ -mod envelope; \ No newline at end of file +mod envelope; diff --git a/crates/op-rpc-types/src/op/block.rs b/crates/op-rpc-types/src/op/block.rs index 62a3152e..bbe7ee64 100644 --- a/crates/op-rpc-types/src/op/block.rs +++ b/crates/op-rpc-types/src/op/block.rs @@ -1028,4 +1028,4 @@ pub struct BlockOverrides { /// solidity opcode BLOCKHASH. #[serde(default, skip_serializing_if = "Option::is_none")] pub block_hash: Option>, -} \ No newline at end of file +} diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs index bb377f1a..7a54ffd1 100644 --- a/crates/op-rpc-types/src/op/mod.rs +++ b/crates/op-rpc-types/src/op/mod.rs @@ -1,4 +1,4 @@ -mod transaction; mod block; mod call; +mod transaction; pub use alloy::rpc::types::eth::{Header, Log}; diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs index 8b9c3f92..2940d7ba 100644 --- a/crates/op-rpc-types/src/op/transaction/mod.rs +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -1,5 +1,7 @@ -use alloy::serde as alloy_serde; -use alloy::rpc::types::eth::{AccessList, Signature}; +use alloy::{ + rpc::types::eth::{AccessList, Signature}, + serde as alloy_serde, +}; use alloy_primitives::{Address, Bytes, B256, U128, U256, U64}; use serde::{Deserialize, Serialize}; diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index c844640e..70c8147b 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -1,8 +1,8 @@ use crate::op::transaction::tx_type; use alloy::{rpc::types::eth::Log, serde as alloy_serde}; -use op_consensus::receipt::ReceiptEnvelope; use alloy_consensus::{AnyReceiptEnvelope, ReceiptEnvelope, TxType}; use alloy_primitives::{Address, B256}; +use op_consensus::receipt::ReceiptEnvelope; use serde::{Deserialize, Serialize}; /// Transaction receipt /// From b9169e9e3c9fca1b22a2e4daef905b53f95faf77 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Fri, 12 Apr 2024 13:02:28 +0400 Subject: [PATCH 13/25] feat: add pubsub.rs --- crates/op-rpc-types/src/op/pubsub.rs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 crates/op-rpc-types/src/op/pubsub.rs diff --git a/crates/op-rpc-types/src/op/pubsub.rs b/crates/op-rpc-types/src/op/pubsub.rs new file mode 100644 index 00000000..c6199846 --- /dev/null +++ b/crates/op-rpc-types/src/op/pubsub.rs @@ -0,0 +1,37 @@ +//! Optimism types for pub-sub + +use crate::op::transaction::Transaction; +use alloy::rpc::types::eth::{pubsub::PubSubSyncStatus, Log, RichHeader}; +use alloy_primitives::B256; +use serde::{Deserialize, Serialize, Serializer}; + +/// Subscription result. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[serde(untagged)] +pub enum SubscriptionResult { + /// New block header. + Header(Box), + /// Log + Log(Box), + /// Transaction hash + TransactionHash(B256), + /// Full Transaction + FullTransaction(Box), + /// SyncStatus + SyncState(PubSubSyncStatus), +} + +impl Serialize for SubscriptionResult { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + SubscriptionResult::Header(ref header) => header.serialize(serializer), + SubscriptionResult::Log(ref log) => log.serialize(serializer), + SubscriptionResult::TransactionHash(ref hash) => hash.serialize(serializer), + SubscriptionResult::FullTransaction(ref tx) => tx.serialize(serializer), + SubscriptionResult::SyncState(ref sync) => sync.serialize(serializer), + } + } +} \ No newline at end of file From f1675b9b75fdba80465d1c79c3abe7943590a094 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Fri, 12 Apr 2024 13:02:47 +0400 Subject: [PATCH 14/25] feat: fix imports, add TODO comments, organize the code. --- Cargo.toml | 2 +- crates/op-consensus/src/receipt/mod.rs | 2 +- crates/op-rpc-types/Cargo.toml | 2 +- crates/op-rpc-types/src/op/block.rs | 898 +----------------- crates/op-rpc-types/src/op/mod.rs | 1 + crates/op-rpc-types/src/op/transaction/mod.rs | 8 +- .../src/op/transaction/receipt.rs | 5 +- .../src/op/transaction/request.rs | 115 +-- 8 files changed, 36 insertions(+), 997 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd7eed57..b7ed3fa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ alloy-primitives = { version = "0.7.0", default-features = false } alloy = { git = "https://github.com/alloy-rs/alloy", rev = "89f14f9", features = [ "serde", "rpc-types-eth", - "rpc-types" + "rpc-types","rlp","consensus" ] } op-consensus = { version = "0.1.0", default-features = false, path = "crates/op-consensus" } diff --git a/crates/op-consensus/src/receipt/mod.rs b/crates/op-consensus/src/receipt/mod.rs index 172ee72b..788ab129 100644 --- a/crates/op-consensus/src/receipt/mod.rs +++ b/crates/op-consensus/src/receipt/mod.rs @@ -1 +1 @@ -mod envelope; +pub mod envelope; diff --git a/crates/op-rpc-types/Cargo.toml b/crates/op-rpc-types/Cargo.toml index 7deae62d..0f627039 100644 --- a/crates/op-rpc-types/Cargo.toml +++ b/crates/op-rpc-types/Cargo.toml @@ -22,7 +22,7 @@ alloy-primitives = { workspace = true, features = ["rlp", "serde", "std"] } # alloy-eips = { workspace = true, features = ["std", "serde"] } serde = { workspace = true, features = ["derive"] } -alloy = { workspace = true , features = ["serde", "rpc-types", "rpc-types-eth"]} +alloy = { workspace = true , features = ["serde", "rpc-types", "rpc-types-eth", "rlp", "consensus"]} serde_json.workspace = true # arbitrary diff --git a/crates/op-rpc-types/src/op/block.rs b/crates/op-rpc-types/src/op/block.rs index bbe7ee64..58eed263 100644 --- a/crates/op-rpc-types/src/op/block.rs +++ b/crates/op-rpc-types/src/op/block.rs @@ -3,18 +3,13 @@ #![allow(unknown_lints, non_local_definitions)] use crate::op::transaction::Transaction; -use alloy::rpc::types::eth::Header; -use alloy_eips::{calc_blob_gasprice, calc_excess_blob_gas}; +use alloy::rpc::types::eth::{BlockTransactionHashes, BlockTransactionHashesMut, Header, Rich, Withdrawal}; use alloy_primitives::{ - ruint::ParseError, Address, BlockHash, BlockNumber, Bloom, Bytes, B256, B64, U256, U64, +B256, U256, }; -use alloy_rlp::{bytes, Decodable, Encodable, Error as RlpError}; use serde::{ - de::{MapAccess, Visitor}, - ser::{Error, SerializeStruct}, - Deserialize, Deserializer, Serialize, Serializer, + Deserialize, Serialize, }; -use std::{collections::BTreeMap, fmt, num::ParseIntError, ops::Deref, str::FromStr}; /// Block representation #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] @@ -111,13 +106,13 @@ impl BlockTransactions { /// Returns an iterator over references to the transaction hashes. #[inline] pub fn hashes(&self) -> BlockTransactionHashes<'_> { - BlockTransactionHashes::new(self) + BlockTransactionHashes::new(self) // TODO: The `new` method is not public, so cannot be used here. Make a PR to alloy to make this public } /// Returns an iterator over mutable references to the transaction hashes. #[inline] pub fn hashes_mut(&mut self) -> BlockTransactionHashesMut<'_> { - BlockTransactionHashesMut::new(self) + BlockTransactionHashesMut::new(self) // TODO: The `new` method is not public, so cannot be used here. Make a PR to alloy to make this public } /// Returns an instance of BlockTransactions with the Uncle special case. @@ -139,801 +134,6 @@ impl BlockTransactions { } } -/// An iterator over the transaction hashes of a block. -/// -/// See [`BlockTransactions::hashes`]. -#[derive(Clone, Debug)] -pub struct BlockTransactionHashes<'a>(BlockTransactionHashesInner<'a>); - -#[derive(Clone, Debug)] -enum BlockTransactionHashesInner<'a> { - Hashes(std::slice::Iter<'a, B256>), - Full(std::slice::Iter<'a, Transaction>), - Uncle, -} - -impl<'a> BlockTransactionHashes<'a> { - #[inline] - fn new(txs: &'a BlockTransactions) -> Self { - Self(match txs { - BlockTransactions::Hashes(txs) => BlockTransactionHashesInner::Hashes(txs.iter()), - BlockTransactions::Full(txs) => BlockTransactionHashesInner::Full(txs.iter()), - BlockTransactions::Uncle => BlockTransactionHashesInner::Uncle, - }) - } -} - -impl<'a> Iterator for BlockTransactionHashes<'a> { - type Item = &'a B256; - - #[inline] - fn next(&mut self) -> Option { - match &mut self.0 { - BlockTransactionHashesInner::Full(txs) => txs.next().map(|tx| &tx.hash), - BlockTransactionHashesInner::Hashes(txs) => txs.next(), - BlockTransactionHashesInner::Uncle => None, - } - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - match &self.0 { - BlockTransactionHashesInner::Full(txs) => txs.size_hint(), - BlockTransactionHashesInner::Hashes(txs) => txs.size_hint(), - BlockTransactionHashesInner::Uncle => (0, Some(0)), - } - } -} - -impl ExactSizeIterator for BlockTransactionHashes<'_> { - #[inline] - fn len(&self) -> usize { - match &self.0 { - BlockTransactionHashesInner::Full(txs) => txs.len(), - BlockTransactionHashesInner::Hashes(txs) => txs.len(), - BlockTransactionHashesInner::Uncle => 0, - } - } -} - -impl DoubleEndedIterator for BlockTransactionHashes<'_> { - #[inline] - fn next_back(&mut self) -> Option { - match &mut self.0 { - BlockTransactionHashesInner::Full(txs) => txs.next_back().map(|tx| &tx.hash), - BlockTransactionHashesInner::Hashes(txs) => txs.next_back(), - BlockTransactionHashesInner::Uncle => None, - } - } -} - -impl<'a> std::iter::FusedIterator for BlockTransactionHashes<'a> {} - -/// An Iterator over the transaction hashes of a block. -/// -/// See [`BlockTransactions::hashes_mut`]. -#[derive(Debug)] -pub struct BlockTransactionHashesMut<'a>(BlockTransactionHashesInnerMut<'a>); - -#[derive(Debug)] -enum BlockTransactionHashesInnerMut<'a> { - Hashes(std::slice::IterMut<'a, B256>), - Full(std::slice::IterMut<'a, Transaction>), - Uncle, -} - -impl<'a> BlockTransactionHashesMut<'a> { - #[inline] - fn new(txs: &'a mut BlockTransactions) -> Self { - Self(match txs { - BlockTransactions::Hashes(txs) => { - BlockTransactionHashesInnerMut::Hashes(txs.iter_mut()) - } - BlockTransactions::Full(txs) => BlockTransactionHashesInnerMut::Full(txs.iter_mut()), - BlockTransactions::Uncle => BlockTransactionHashesInnerMut::Uncle, - }) - } -} - -impl<'a> Iterator for BlockTransactionHashesMut<'a> { - type Item = &'a mut B256; - - #[inline] - fn next(&mut self) -> Option { - match &mut self.0 { - BlockTransactionHashesInnerMut::Full(txs) => txs.next().map(|tx| &mut tx.hash), - BlockTransactionHashesInnerMut::Hashes(txs) => txs.next(), - BlockTransactionHashesInnerMut::Uncle => None, - } - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - match &self.0 { - BlockTransactionHashesInnerMut::Full(txs) => txs.size_hint(), - BlockTransactionHashesInnerMut::Hashes(txs) => txs.size_hint(), - BlockTransactionHashesInnerMut::Uncle => (0, Some(0)), - } - } -} - -impl ExactSizeIterator for BlockTransactionHashesMut<'_> { - #[inline] - fn len(&self) -> usize { - match &self.0 { - BlockTransactionHashesInnerMut::Full(txs) => txs.len(), - BlockTransactionHashesInnerMut::Hashes(txs) => txs.len(), - BlockTransactionHashesInnerMut::Uncle => 0, - } - } -} - -impl DoubleEndedIterator for BlockTransactionHashesMut<'_> { - #[inline] - fn next_back(&mut self) -> Option { - match &mut self.0 { - BlockTransactionHashesInnerMut::Full(txs) => txs.next_back().map(|tx| &mut tx.hash), - BlockTransactionHashesInnerMut::Hashes(txs) => txs.next_back(), - BlockTransactionHashesInnerMut::Uncle => None, - } - } -} - -impl<'a> std::iter::FusedIterator for BlockTransactionHashesMut<'a> {} - -/// Determines how the `transactions` field of [Block] should be filled. -/// -/// This essentially represents the `full:bool` argument in RPC calls that determine whether the -/// response should include full transaction objects or just the hashes. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum BlockTransactionsKind { - /// Only include hashes: [BlockTransactions::Hashes] - Hashes, - /// Include full transaction objects: [BlockTransactions::Full] - Full, -} - -impl From for BlockTransactionsKind { - fn from(is_full: bool) -> Self { - if is_full { - BlockTransactionsKind::Full - } else { - BlockTransactionsKind::Hashes - } - } -} - -/// Error that can occur when converting other types to blocks -#[derive(Debug, Clone, Copy, thiserror::Error)] -pub enum BlockError { - /// A transaction failed sender recovery - #[error("transaction failed sender recovery")] - InvalidSignature, - /// A raw block failed to decode - #[error("failed to decode raw block {0}")] - RlpDecodeRawBlock(alloy_rlp::Error), -} - -/// A block hash which may have -/// a boolean requireCanonical field. -/// If false, an RPC call should raise if a block -/// matching the hash is not found. -/// If true, an RPC call should additionally raise if -/// the block is not in the canonical chain. -/// -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)] -pub struct RpcBlockHash { - /// A block hash - pub block_hash: B256, - /// Whether the block must be a canonical block - pub require_canonical: Option, -} - -impl RpcBlockHash { - /// Returns an [RpcBlockHash] from a [B256]. - pub const fn from_hash(block_hash: B256, require_canonical: Option) -> Self { - RpcBlockHash { block_hash, require_canonical } - } -} - -impl From for RpcBlockHash { - fn from(value: B256) -> Self { - Self::from_hash(value, None) - } -} - -impl From for B256 { - fn from(value: RpcBlockHash) -> Self { - value.block_hash - } -} - -impl AsRef for RpcBlockHash { - fn as_ref(&self) -> &B256 { - &self.block_hash - } -} - -/// A block Number (or tag - "latest", "earliest", "pending") -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] -pub enum BlockNumberOrTag { - /// Latest block - #[default] - Latest, - /// Finalized block accepted as canonical - Finalized, - /// Safe head block - Safe, - /// Earliest block (genesis) - Earliest, - /// Pending block (not yet part of the blockchain) - Pending, - /// Block by number from canon chain - Number(u64), -} - -impl BlockNumberOrTag { - /// Returns the numeric block number if explicitly set - pub const fn as_number(&self) -> Option { - match *self { - BlockNumberOrTag::Number(num) => Some(num), - _ => None, - } - } - - /// Returns `true` if a numeric block number is set - pub const fn is_number(&self) -> bool { - matches!(self, BlockNumberOrTag::Number(_)) - } - - /// Returns `true` if it's "latest" - pub const fn is_latest(&self) -> bool { - matches!(self, BlockNumberOrTag::Latest) - } - - /// Returns `true` if it's "finalized" - pub const fn is_finalized(&self) -> bool { - matches!(self, BlockNumberOrTag::Finalized) - } - - /// Returns `true` if it's "safe" - pub const fn is_safe(&self) -> bool { - matches!(self, BlockNumberOrTag::Safe) - } - - /// Returns `true` if it's "pending" - pub const fn is_pending(&self) -> bool { - matches!(self, BlockNumberOrTag::Pending) - } - - /// Returns `true` if it's "earliest" - pub const fn is_earliest(&self) -> bool { - matches!(self, BlockNumberOrTag::Earliest) - } -} - -impl From for BlockNumberOrTag { - fn from(num: u64) -> Self { - BlockNumberOrTag::Number(num) - } -} - -impl From for BlockNumberOrTag { - fn from(num: U64) -> Self { - num.to::().into() - } -} - -impl Serialize for BlockNumberOrTag { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match *self { - BlockNumberOrTag::Number(x) => serializer.serialize_str(&format!("0x{x:x}")), - BlockNumberOrTag::Latest => serializer.serialize_str("latest"), - BlockNumberOrTag::Finalized => serializer.serialize_str("finalized"), - BlockNumberOrTag::Safe => serializer.serialize_str("safe"), - BlockNumberOrTag::Earliest => serializer.serialize_str("earliest"), - BlockNumberOrTag::Pending => serializer.serialize_str("pending"), - } - } -} - -impl<'de> Deserialize<'de> for BlockNumberOrTag { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?.to_lowercase(); - s.parse().map_err(serde::de::Error::custom) - } -} - -impl FromStr for BlockNumberOrTag { - type Err = ParseBlockNumberError; - - fn from_str(s: &str) -> Result { - let block = match s { - "latest" => Self::Latest, - "finalized" => Self::Finalized, - "safe" => Self::Safe, - "earliest" => Self::Earliest, - "pending" => Self::Pending, - _number => { - if let Some(hex_val) = s.strip_prefix("0x") { - let number = u64::from_str_radix(hex_val, 16); - BlockNumberOrTag::Number(number?) - } else { - return Err(HexStringMissingPrefixError::default().into()); - } - } - }; - Ok(block) - } -} - -impl fmt::Display for BlockNumberOrTag { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - BlockNumberOrTag::Number(x) => write!(f, "0x{x:x}"), - BlockNumberOrTag::Latest => f.write_str("latest"), - BlockNumberOrTag::Finalized => f.write_str("finalized"), - BlockNumberOrTag::Safe => f.write_str("safe"), - BlockNumberOrTag::Earliest => f.write_str("earliest"), - BlockNumberOrTag::Pending => f.write_str("pending"), - } - } -} - -/// Error variants when parsing a [BlockNumberOrTag] -#[derive(Debug, thiserror::Error)] -pub enum ParseBlockNumberError { - /// Failed to parse hex value - #[error(transparent)] - ParseIntErr(#[from] ParseIntError), - /// Failed to parse hex value - #[error(transparent)] - ParseErr(#[from] ParseError), - /// Block numbers should be 0x-prefixed - #[error(transparent)] - MissingPrefix(#[from] HexStringMissingPrefixError), -} - -/// Thrown when a 0x-prefixed hex string was expected -#[derive(Copy, Clone, Debug, Default, thiserror::Error)] -#[non_exhaustive] -#[error("hex string without 0x prefix")] -pub struct HexStringMissingPrefixError; - -/// A Block Identifier -/// -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum BlockId { - /// A block hash and an optional bool that defines if it's canonical - Hash(RpcBlockHash), - /// A block number - Number(BlockNumberOrTag), -} - -// === impl BlockId === - -impl BlockId { - /// Returns the block hash if it is [BlockId::Hash] - pub const fn as_block_hash(&self) -> Option { - match self { - BlockId::Hash(hash) => Some(hash.block_hash), - BlockId::Number(_) => None, - } - } - - /// Returns true if this is [BlockNumberOrTag::Latest] - pub const fn is_latest(&self) -> bool { - matches!(self, BlockId::Number(BlockNumberOrTag::Latest)) - } - - /// Returns true if this is [BlockNumberOrTag::Pending] - pub const fn is_pending(&self) -> bool { - matches!(self, BlockId::Number(BlockNumberOrTag::Pending)) - } - - /// Returns true if this is [BlockNumberOrTag::Safe] - pub const fn is_safe(&self) -> bool { - matches!(self, BlockId::Number(BlockNumberOrTag::Safe)) - } - - /// Returns true if this is [BlockNumberOrTag::Finalized] - pub const fn is_finalized(&self) -> bool { - matches!(self, BlockId::Number(BlockNumberOrTag::Finalized)) - } - - /// Returns true if this is [BlockNumberOrTag::Earliest] - pub const fn is_earliest(&self) -> bool { - matches!(self, BlockId::Number(BlockNumberOrTag::Earliest)) - } - - /// Returns true if this is [BlockNumberOrTag::Number] - pub const fn is_number(&self) -> bool { - matches!(self, BlockId::Number(BlockNumberOrTag::Number(_))) - } - /// Returns true if this is [BlockId::Hash] - pub const fn is_hash(&self) -> bool { - matches!(self, BlockId::Hash(_)) - } - - /// Creates a new "pending" tag instance. - pub const fn pending() -> Self { - BlockId::Number(BlockNumberOrTag::Pending) - } - - /// Creates a new "latest" tag instance. - pub const fn latest() -> Self { - BlockId::Number(BlockNumberOrTag::Latest) - } - - /// Creates a new "earliest" tag instance. - pub const fn earliest() -> Self { - BlockId::Number(BlockNumberOrTag::Earliest) - } - - /// Creates a new "finalized" tag instance. - pub const fn finalized() -> Self { - BlockId::Number(BlockNumberOrTag::Finalized) - } - - /// Creates a new "safe" tag instance. - pub const fn safe() -> Self { - BlockId::Number(BlockNumberOrTag::Safe) - } - - /// Creates a new block number instance. - pub const fn number(num: u64) -> Self { - BlockId::Number(BlockNumberOrTag::Number(num)) - } - - /// Create a new block hash instance. - pub const fn hash(block_hash: B256) -> Self { - BlockId::Hash(RpcBlockHash { block_hash, require_canonical: None }) - } - - /// Create a new block hash instance that requires the block to be canonical. - pub const fn hash_canonical(block_hash: B256) -> Self { - BlockId::Hash(RpcBlockHash { block_hash, require_canonical: Some(true) }) - } -} - -impl Default for BlockId { - fn default() -> Self { - BlockId::Number(BlockNumberOrTag::Latest) - } -} - -impl From for BlockId { - fn from(num: u64) -> Self { - BlockNumberOrTag::Number(num).into() - } -} - -impl From for BlockId { - fn from(value: U64) -> Self { - BlockNumberOrTag::Number(value.to()).into() - } -} - -impl From for BlockId { - fn from(num: BlockNumberOrTag) -> Self { - BlockId::Number(num) - } -} - -impl From for BlockId { - fn from(block_hash: B256) -> Self { - BlockId::Hash(RpcBlockHash { block_hash, require_canonical: None }) - } -} - -impl From<(B256, Option)> for BlockId { - fn from(hash_can: (B256, Option)) -> Self { - BlockId::Hash(RpcBlockHash { block_hash: hash_can.0, require_canonical: hash_can.1 }) - } -} - -impl Serialize for BlockId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - BlockId::Hash(RpcBlockHash { block_hash, require_canonical }) => { - let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?; - s.serialize_field("blockHash", block_hash)?; - if let Some(require_canonical) = require_canonical { - s.serialize_field("requireCanonical", require_canonical)?; - } - s.end() - } - BlockId::Number(num) => num.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for BlockId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct BlockIdVisitor; - - impl<'de> Visitor<'de> for BlockIdVisitor { - type Value = BlockId; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("Block identifier following EIP-1898") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - // Since there is no way to clearly distinguish between a DATA parameter and a QUANTITY parameter. A str is therefor deserialized into a Block Number: - // However, since the hex string should be a QUANTITY, we can safely assume that if the len is 66 bytes, it is in fact a hash, ref - if v.len() == 66 { - Ok(BlockId::Hash(v.parse::().map_err(serde::de::Error::custom)?.into())) - } else { - // quantity hex string or tag - Ok(BlockId::Number(v.parse().map_err(serde::de::Error::custom)?)) - } - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut number = None; - let mut block_hash = None; - let mut require_canonical = None; - while let Some(key) = map.next_key::()? { - match key.as_str() { - "blockNumber" => { - if number.is_some() || block_hash.is_some() { - return Err(serde::de::Error::duplicate_field("blockNumber")); - } - if require_canonical.is_some() { - return Err(serde::de::Error::custom( - "Non-valid require_canonical field", - )); - } - number = Some(map.next_value::()?) - } - "blockHash" => { - if number.is_some() || block_hash.is_some() { - return Err(serde::de::Error::duplicate_field("blockHash")); - } - - block_hash = Some(map.next_value::()?); - } - "requireCanonical" => { - if number.is_some() || require_canonical.is_some() { - return Err(serde::de::Error::duplicate_field("requireCanonical")); - } - - require_canonical = Some(map.next_value::()?) - } - key => { - return Err(serde::de::Error::unknown_field( - key, - &["blockNumber", "blockHash", "requireCanonical"], - )) - } - } - } - - if let Some(number) = number { - Ok(BlockId::Number(number)) - } else if let Some(block_hash) = block_hash { - Ok(BlockId::Hash(RpcBlockHash { block_hash, require_canonical })) - } else { - Err(serde::de::Error::custom( - "Expected `blockNumber` or `blockHash` with `requireCanonical` optionally", - )) - } - } - } - - deserializer.deserialize_any(BlockIdVisitor) - } -} - -/// Error thrown when parsing a [BlockId] from a string. -#[derive(Debug, thiserror::Error)] -pub enum ParseBlockIdError { - /// Failed to parse a block id from a number. - #[error(transparent)] - ParseIntError(#[from] ParseIntError), - /// Failed to parse a block id as a hex string. - #[error(transparent)] - FromHexError(#[from] alloy_primitives::hex::FromHexError), -} - -impl FromStr for BlockId { - type Err = ParseBlockIdError; - fn from_str(s: &str) -> Result { - if s.starts_with("0x") { - return B256::from_str(s).map(Into::into).map_err(ParseBlockIdError::FromHexError); - } - - match s { - "latest" => Ok(BlockId::Number(BlockNumberOrTag::Latest)), - "finalized" => Ok(BlockId::Number(BlockNumberOrTag::Finalized)), - "safe" => Ok(BlockId::Number(BlockNumberOrTag::Safe)), - "earliest" => Ok(BlockId::Number(BlockNumberOrTag::Earliest)), - "pending" => Ok(BlockId::Number(BlockNumberOrTag::Pending)), - _ => s - .parse::() - .map_err(ParseBlockIdError::ParseIntError) - .map(|n| BlockId::Number(n.into())), - } - } -} - -/// Block number and hash. -#[derive(Clone, Copy, Hash, Default, PartialEq, Eq)] -pub struct BlockNumHash { - /// Block number - pub number: BlockNumber, - /// Block hash - pub hash: BlockHash, -} - -/// Block number and hash of the forked block. -pub type ForkBlock = BlockNumHash; - -impl fmt::Debug for BlockNumHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("").field(&self.number).field(&self.hash).finish() - } -} - -impl BlockNumHash { - /// Creates a new `BlockNumHash` from a block number and hash. - pub const fn new(number: BlockNumber, hash: BlockHash) -> Self { - Self { number, hash } - } - - /// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`] - pub const fn into_components(self) -> (BlockNumber, BlockHash) { - (self.number, self.hash) - } - - /// Returns whether or not the block matches the given [BlockHashOrNumber]. - pub fn matches_block_or_num(&self, block: &BlockHashOrNumber) -> bool { - match block { - BlockHashOrNumber::Hash(hash) => self.hash == *hash, - BlockHashOrNumber::Number(number) => self.number == *number, - } - } -} - -impl From<(BlockNumber, BlockHash)> for BlockNumHash { - fn from(val: (BlockNumber, BlockHash)) -> Self { - BlockNumHash { number: val.0, hash: val.1 } - } -} - -impl From<(BlockHash, BlockNumber)> for BlockNumHash { - fn from(val: (BlockHash, BlockNumber)) -> Self { - BlockNumHash { hash: val.0, number: val.1 } - } -} - -/// Either a block hash _or_ a block number -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[cfg_attr( - any(test, feature = "arbitrary"), - derive(proptest_derive::Arbitrary, arbitrary::Arbitrary) -)] -pub enum BlockHashOrNumber { - /// A block hash - Hash(B256), - /// A block number - Number(u64), -} - -// === impl BlockHashOrNumber === - -impl BlockHashOrNumber { - /// Returns the block number if it is a [`BlockHashOrNumber::Number`]. - #[inline] - pub const fn as_number(self) -> Option { - match self { - BlockHashOrNumber::Hash(_) => None, - BlockHashOrNumber::Number(num) => Some(num), - } - } -} - -impl From for BlockHashOrNumber { - fn from(value: B256) -> Self { - BlockHashOrNumber::Hash(value) - } -} - -impl From for BlockHashOrNumber { - fn from(value: u64) -> Self { - BlockHashOrNumber::Number(value) - } -} - -impl From for BlockHashOrNumber { - fn from(value: U64) -> Self { - value.to::().into() - } -} - -/// Allows for RLP encoding of either a block hash or block number -impl Encodable for BlockHashOrNumber { - fn encode(&self, out: &mut dyn bytes::BufMut) { - match self { - Self::Hash(block_hash) => block_hash.encode(out), - Self::Number(block_number) => block_number.encode(out), - } - } - fn length(&self) -> usize { - match self { - Self::Hash(block_hash) => block_hash.length(), - Self::Number(block_number) => block_number.length(), - } - } -} - -/// Allows for RLP decoding of a block hash or block number -impl Decodable for BlockHashOrNumber { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let header: u8 = *buf.first().ok_or(RlpError::InputTooShort)?; - // if the byte string is exactly 32 bytes, decode it into a Hash - // 0xa0 = 0x80 (start of string) + 0x20 (32, length of string) - if header == 0xa0 { - // strip the first byte, parsing the rest of the string. - // If the rest of the string fails to decode into 32 bytes, we'll bubble up the - // decoding error. - let hash = B256::decode(buf)?; - Ok(Self::Hash(hash)) - } else { - // a block number when encoded as bytes ranges from 0 to any number of bytes - we're - // going to accept numbers which fit in less than 64 bytes. - // Any data larger than this which is not caught by the Hash decoding should error and - // is considered an invalid block number. - Ok(Self::Number(u64::decode(buf)?)) - } - } -} - -/// Error thrown when parsing a [BlockHashOrNumber] from a string. -#[derive(Debug, thiserror::Error)] -#[error("failed to parse {input:?} as a number: {parse_int_error} or hash: {hex_error}")] -pub struct ParseBlockHashOrNumberError { - input: String, - parse_int_error: ParseIntError, - hex_error: alloy_primitives::hex::FromHexError, -} - -impl FromStr for BlockHashOrNumber { - type Err = ParseBlockHashOrNumberError; - - fn from_str(s: &str) -> Result { - match u64::from_str(s) { - Ok(val) => Ok(val.into()), - Err(pares_int_error) => match B256::from_str(s) { - Ok(val) => Ok(val.into()), - Err(hex_error) => Err(ParseBlockHashOrNumberError { - input: s.to_string(), - parse_int_error: pares_int_error, - hex_error, - }), - }, - } - } -} /// A Block representation that allows to include additional fields pub type RichBlock = Rich; @@ -942,90 +142,4 @@ impl From for RichBlock { fn from(block: Block) -> Self { Rich { inner: block, extra_info: Default::default() } } -} - -/// Header representation with additional info. -pub type RichHeader = Rich
; - -impl From
for RichHeader { - fn from(header: Header) -> Self { - Rich { inner: header, extra_info: Default::default() } - } -} - -/// Value representation with additional info -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Rich { - /// Standard value. - #[serde(flatten)] - pub inner: T, - /// Additional fields that should be serialized into the `Block` object - #[serde(flatten)] - pub extra_info: BTreeMap, -} - -impl Deref for Rich { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl Serialize for Rich { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if self.extra_info.is_empty() { - return self.inner.serialize(serializer); - } - - let inner = serde_json::to_value(&self.inner); - let extras = serde_json::to_value(&self.extra_info); - - if let (Ok(serde_json::Value::Object(mut value)), Ok(serde_json::Value::Object(extras))) = - (inner, extras) - { - value.extend(extras); - value.serialize(serializer) - } else { - Err(S::Error::custom("Unserializable structures: expected objects")) - } - } -} - -/// BlockOverrides is a set of header fields to override. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -#[serde(default, rename_all = "camelCase", deny_unknown_fields)] -pub struct BlockOverrides { - /// Overrides the block number. - /// - /// For `eth_callMany` this will be the block number of the first simulated block. Each - /// following block increments its block number by 1 - // Note: geth uses `number`, erigon uses `blockNumber` - #[serde(default, skip_serializing_if = "Option::is_none", alias = "blockNumber")] - pub number: Option, - /// Overrides the difficulty of the block. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub difficulty: Option, - /// Overrides the timestamp of the block. - // Note: geth uses `time`, erigon uses `timestamp` - #[serde(default, skip_serializing_if = "Option::is_none", alias = "timestamp")] - pub time: Option, - /// Overrides the gas limit of the block. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub gas_limit: Option, - /// Overrides the coinbase address of the block. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub coinbase: Option
, - /// Overrides the prevrandao of the block. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub random: Option, - /// Overrides the basefee of the block. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub base_fee: Option, - /// A dictionary that maps blockNumber to a user-defined hash. It could be queried from the - /// solidity opcode BLOCKHASH. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub block_hash: Option>, -} +} \ No newline at end of file diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs index 7a54ffd1..b71f64ba 100644 --- a/crates/op-rpc-types/src/op/mod.rs +++ b/crates/op-rpc-types/src/op/mod.rs @@ -1,4 +1,5 @@ mod block; mod call; mod transaction; +mod pubsub; pub use alloy::rpc::types::eth::{Header, Log}; diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs index 2940d7ba..3e7ad8ba 100644 --- a/crates/op-rpc-types/src/op/transaction/mod.rs +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -1,6 +1,5 @@ use alloy::{ - rpc::types::eth::{AccessList, Signature}, - serde as alloy_serde, + consensus::{Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEnvelope, TxLegacy}, rpc::types::eth::{AccessList, ConversionError, Signature}, serde as alloy_serde }; use alloy_primitives::{Address, Bytes, B256, U128, U256, U64}; use serde::{Deserialize, Serialize}; @@ -127,6 +126,9 @@ impl Transaction { transaction_type: self.transaction_type, max_fee_per_gas: self.max_fee_per_gas, max_priority_fee_per_gas: self.max_priority_fee_per_gas, + max_fee_per_blob_gas: self.max_fee_per_blob_gas, + blob_versioned_hashes: self.blob_versioned_hashes, + sidecar: self.sidecar, } } } @@ -231,7 +233,7 @@ impl TryFrom for Signed { } } -impl TryFrom for TxEnvelope { +impl TryFrom for TxEnvelope { // TODO: When the TxEnvelope is implemented for op-consensus, import it from there. This envelope doesn't handle DEPOSIT type Error = ConversionError; fn try_from(tx: Transaction) -> Result { diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index 70c8147b..960401cf 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -1,8 +1,7 @@ -use crate::op::transaction::tx_type; +use crate::op::transaction::tx_type::TxType; use alloy::{rpc::types::eth::Log, serde as alloy_serde}; -use alloy_consensus::{AnyReceiptEnvelope, ReceiptEnvelope, TxType}; -use alloy_primitives::{Address, B256}; use op_consensus::receipt::ReceiptEnvelope; +use alloy_primitives::{Address, B256}; use serde::{Deserialize, Serialize}; /// Transaction receipt /// diff --git a/crates/op-rpc-types/src/op/transaction/request.rs b/crates/op-rpc-types/src/op/transaction/request.rs index b2baf4f1..8810cdee 100644 --- a/crates/op-rpc-types/src/op/transaction/request.rs +++ b/crates/op-rpc-types/src/op/transaction/request.rs @@ -1,8 +1,8 @@ //! Alloy basic Transaction Request type. use crate::op::transaction::Transaction; -use alloy::{rpc::types::eth::transaction::AccessList, serde as alloy_serde}; -use alloy_primitives::{Address, Bytes, ChainId, TxHash, TxKind, U256}; +use alloy::{consensus::{BlobTransactionSidecar, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip4844WithSidecar, TxEnvelope, TxLegacy, TypedTransaction}, rpc::types::eth::{transaction::AccessList, TransactionInput}, serde as alloy_serde}; +use alloy_primitives::{Address, ChainId, TxKind, B256, U256}; use serde::{Deserialize, Serialize}; use std::hash::Hash; @@ -37,6 +37,13 @@ pub struct TransactionRequest { with = "alloy_serde::num::u128_hex_or_decimal_opt" )] pub max_priority_fee_per_gas: Option, + /// The max fee per blob gas for EIP-4844 blob transactions. + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::num::u128_hex_or_decimal_opt" + )] + pub max_fee_per_blob_gas: Option, /// The gas limit for the transaction. #[serde(default, with = "alloy_serde::num::u128_hex_or_decimal_opt")] pub gas: Option, @@ -57,6 +64,12 @@ pub struct TransactionRequest { /// The EIP-2718 transaction type. See [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) for more information. #[serde(default, rename = "type", with = "alloy_serde::num::u8_hex_opt")] pub transaction_type: Option, + /// Blob versioned hashes for EIP-4844 transactions. + #[serde(skip_serializing_if = "Option::is_none")] + pub blob_versioned_hashes: Option>, + /// Blob sidecar for EIP-4844 transactions. + #[serde(skip_serializing_if = "Option::is_none")] + pub sidecar: Option, } impl Hash for TransactionRequest { @@ -156,92 +169,6 @@ impl TransactionRequest { } } -/// Helper type that supports both `data` and `input` fields that map to transaction input data. -/// -/// This is done for compatibility reasons where older implementations used `data` instead of the -/// newer, recommended `input` field. -/// -/// If both fields are set, it is expected that they contain the same value, otherwise an error is -/// returned. -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Hash)] -pub struct TransactionInput { - /// Transaction data - #[serde(skip_serializing_if = "Option::is_none")] - pub input: Option, - /// Transaction data - /// - /// This is the same as `input` but is used for backwards compatibility: - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, -} - -impl TransactionInput { - /// Creates a new instance with the given input data. - pub const fn new(data: Bytes) -> Self { - Self::maybe_input(Some(data)) - } - - /// Creates a new instance with the given input data. - pub const fn maybe_input(input: Option) -> Self { - Self { input, data: None } - } - - /// Consumes the type and returns the optional input data. - #[inline] - pub fn into_input(self) -> Option { - self.input.or(self.data) - } - - /// Consumes the type and returns the optional input data. - /// - /// Returns an error if both `data` and `input` fields are set and not equal. - #[inline] - pub fn try_into_unique_input(self) -> Result, TransactionInputError> { - self.check_unique_input().map(|()| self.into_input()) - } - - /// Returns the optional input data. - #[inline] - pub fn input(&self) -> Option<&Bytes> { - self.input.as_ref().or(self.data.as_ref()) - } - - /// Returns the optional input data. - /// - /// Returns an error if both `data` and `input` fields are set and not equal. - #[inline] - pub fn unique_input(&self) -> Result, TransactionInputError> { - self.check_unique_input().map(|()| self.input()) - } - - fn check_unique_input(&self) -> Result<(), TransactionInputError> { - if let (Some(input), Some(data)) = (&self.input, &self.data) { - if input != data { - return Err(TransactionInputError::default()); - } - } - Ok(()) - } -} - -impl From> for TransactionInput { - fn from(input: Vec) -> Self { - Self { input: Some(input.into()), data: None } - } -} - -impl From for TransactionInput { - fn from(input: Bytes) -> Self { - Self { input: Some(input), data: None } - } -} - -impl From> for TransactionInput { - fn from(input: Option) -> Self { - Self { input, data: None } - } -} - impl From for TransactionRequest { fn from(tx: Transaction) -> TransactionRequest { tx.into_request() @@ -352,18 +279,19 @@ impl From for TransactionRequest { } } -impl From for TransactionRequest { +impl From for TransactionRequest { // TODO: Replace the import from alloy-consensus with the op-consensus, the eth TypedTransaction does not have the DEPOSIT type fn from(tx: TypedTransaction) -> TransactionRequest { match tx { TypedTransaction::Legacy(tx) => tx.into(), TypedTransaction::Eip2930(tx) => tx.into(), TypedTransaction::Eip1559(tx) => tx.into(), TypedTransaction::Eip4844(tx) => tx.into(), + // TODO: After changing the import to op-consensus, handle DEPOSIT here. } } } -impl From for TransactionRequest { +impl From for TransactionRequest { // TODO: Replace the import from alloy-consensus with the op-consensus, the eth TxEnvelope does not have the DEPOSIT type fn from(envelope: TxEnvelope) -> TransactionRequest { match envelope { TxEnvelope::Legacy(tx) => { @@ -434,13 +362,8 @@ impl From for TransactionRequest { tx.strip_signature().into() } } + // TODO: After changing the import to op-consensus, handle DEPOSIT here. _ => Default::default(), } } } - -/// Error thrown when both `data` and `input` fields are set and not equal. -#[derive(Debug, Default, thiserror::Error)] -#[error("both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data")] -#[non_exhaustive] -pub struct TransactionInputError; From d939344b2f735834fbb7f5ee151b95a341dc5628 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Fri, 12 Apr 2024 13:03:01 +0400 Subject: [PATCH 15/25] lint: cargo fmt --- crates/op-rpc-types/src/op/block.rs | 19 ++++++------ crates/op-rpc-types/src/op/mod.rs | 2 +- crates/op-rpc-types/src/op/pubsub.rs | 2 +- crates/op-rpc-types/src/op/transaction/mod.rs | 8 +++-- .../src/op/transaction/receipt.rs | 2 +- .../src/op/transaction/request.rs | 29 +++++++++++++------ 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/crates/op-rpc-types/src/op/block.rs b/crates/op-rpc-types/src/op/block.rs index 58eed263..c8705510 100644 --- a/crates/op-rpc-types/src/op/block.rs +++ b/crates/op-rpc-types/src/op/block.rs @@ -3,13 +3,11 @@ #![allow(unknown_lints, non_local_definitions)] use crate::op::transaction::Transaction; -use alloy::rpc::types::eth::{BlockTransactionHashes, BlockTransactionHashesMut, Header, Rich, Withdrawal}; -use alloy_primitives::{ -B256, U256, -}; -use serde::{ - Deserialize, Serialize, +use alloy::rpc::types::eth::{ + BlockTransactionHashes, BlockTransactionHashesMut, Header, Rich, Withdrawal, }; +use alloy_primitives::{B256, U256}; +use serde::{Deserialize, Serialize}; /// Block representation #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] @@ -106,13 +104,15 @@ impl BlockTransactions { /// Returns an iterator over references to the transaction hashes. #[inline] pub fn hashes(&self) -> BlockTransactionHashes<'_> { - BlockTransactionHashes::new(self) // TODO: The `new` method is not public, so cannot be used here. Make a PR to alloy to make this public + BlockTransactionHashes::new(self) // TODO: The `new` method is not public, so cannot be used + // here. Make a PR to alloy to make this public } /// Returns an iterator over mutable references to the transaction hashes. #[inline] pub fn hashes_mut(&mut self) -> BlockTransactionHashesMut<'_> { - BlockTransactionHashesMut::new(self) // TODO: The `new` method is not public, so cannot be used here. Make a PR to alloy to make this public + BlockTransactionHashesMut::new(self) // TODO: The `new` method is not public, so cannot be + // used here. Make a PR to alloy to make this public } /// Returns an instance of BlockTransactions with the Uncle special case. @@ -134,7 +134,6 @@ impl BlockTransactions { } } - /// A Block representation that allows to include additional fields pub type RichBlock = Rich; @@ -142,4 +141,4 @@ impl From for RichBlock { fn from(block: Block) -> Self { Rich { inner: block, extra_info: Default::default() } } -} \ No newline at end of file +} diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs index b71f64ba..5439e338 100644 --- a/crates/op-rpc-types/src/op/mod.rs +++ b/crates/op-rpc-types/src/op/mod.rs @@ -1,5 +1,5 @@ mod block; mod call; -mod transaction; mod pubsub; +mod transaction; pub use alloy::rpc::types::eth::{Header, Log}; diff --git a/crates/op-rpc-types/src/op/pubsub.rs b/crates/op-rpc-types/src/op/pubsub.rs index c6199846..0cb00bbc 100644 --- a/crates/op-rpc-types/src/op/pubsub.rs +++ b/crates/op-rpc-types/src/op/pubsub.rs @@ -34,4 +34,4 @@ impl Serialize for SubscriptionResult { SubscriptionResult::SyncState(ref sync) => sync.serialize(serializer), } } -} \ No newline at end of file +} diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs index 3e7ad8ba..0537d56c 100644 --- a/crates/op-rpc-types/src/op/transaction/mod.rs +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -1,5 +1,7 @@ use alloy::{ - consensus::{Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEnvelope, TxLegacy}, rpc::types::eth::{AccessList, ConversionError, Signature}, serde as alloy_serde + consensus::{Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEnvelope, TxLegacy}, + rpc::types::eth::{AccessList, ConversionError, Signature}, + serde as alloy_serde, }; use alloy_primitives::{Address, Bytes, B256, U128, U256, U64}; use serde::{Deserialize, Serialize}; @@ -233,7 +235,9 @@ impl TryFrom for Signed { } } -impl TryFrom for TxEnvelope { // TODO: When the TxEnvelope is implemented for op-consensus, import it from there. This envelope doesn't handle DEPOSIT +impl TryFrom for TxEnvelope { + // TODO: When the TxEnvelope is implemented for op-consensus, import it from there. This + // envelope doesn't handle DEPOSIT type Error = ConversionError; fn try_from(tx: Transaction) -> Result { diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index 960401cf..3c4a8a89 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -1,7 +1,7 @@ use crate::op::transaction::tx_type::TxType; use alloy::{rpc::types::eth::Log, serde as alloy_serde}; -use op_consensus::receipt::ReceiptEnvelope; use alloy_primitives::{Address, B256}; +use op_consensus::receipt::ReceiptEnvelope; use serde::{Deserialize, Serialize}; /// Transaction receipt /// diff --git a/crates/op-rpc-types/src/op/transaction/request.rs b/crates/op-rpc-types/src/op/transaction/request.rs index 8810cdee..4e296e9e 100644 --- a/crates/op-rpc-types/src/op/transaction/request.rs +++ b/crates/op-rpc-types/src/op/transaction/request.rs @@ -1,7 +1,14 @@ //! Alloy basic Transaction Request type. use crate::op::transaction::Transaction; -use alloy::{consensus::{BlobTransactionSidecar, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip4844WithSidecar, TxEnvelope, TxLegacy, TypedTransaction}, rpc::types::eth::{transaction::AccessList, TransactionInput}, serde as alloy_serde}; +use alloy::{ + consensus::{ + BlobTransactionSidecar, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, + TxEip4844WithSidecar, TxEnvelope, TxLegacy, TypedTransaction, + }, + rpc::types::eth::{transaction::AccessList, TransactionInput}, + serde as alloy_serde, +}; use alloy_primitives::{Address, ChainId, TxKind, B256, U256}; use serde::{Deserialize, Serialize}; use std::hash::Hash; @@ -64,12 +71,12 @@ pub struct TransactionRequest { /// The EIP-2718 transaction type. See [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) for more information. #[serde(default, rename = "type", with = "alloy_serde::num::u8_hex_opt")] pub transaction_type: Option, - /// Blob versioned hashes for EIP-4844 transactions. - #[serde(skip_serializing_if = "Option::is_none")] - pub blob_versioned_hashes: Option>, - /// Blob sidecar for EIP-4844 transactions. - #[serde(skip_serializing_if = "Option::is_none")] - pub sidecar: Option, + /// Blob versioned hashes for EIP-4844 transactions. + #[serde(skip_serializing_if = "Option::is_none")] + pub blob_versioned_hashes: Option>, + /// Blob sidecar for EIP-4844 transactions. + #[serde(skip_serializing_if = "Option::is_none")] + pub sidecar: Option, } impl Hash for TransactionRequest { @@ -279,7 +286,9 @@ impl From for TransactionRequest { } } -impl From for TransactionRequest { // TODO: Replace the import from alloy-consensus with the op-consensus, the eth TypedTransaction does not have the DEPOSIT type +impl From for TransactionRequest { + // TODO: Replace the import from alloy-consensus with the op-consensus, the eth TypedTransaction + // does not have the DEPOSIT type fn from(tx: TypedTransaction) -> TransactionRequest { match tx { TypedTransaction::Legacy(tx) => tx.into(), @@ -291,7 +300,9 @@ impl From for TransactionRequest { // TODO: Replace the import } } -impl From for TransactionRequest { // TODO: Replace the import from alloy-consensus with the op-consensus, the eth TxEnvelope does not have the DEPOSIT type +impl From for TransactionRequest { + // TODO: Replace the import from alloy-consensus with the op-consensus, the eth TxEnvelope does + // not have the DEPOSIT type fn from(envelope: TxEnvelope) -> TransactionRequest { match envelope { TxEnvelope::Legacy(tx) => { From 5dbd2f58f2bf22d62f2b2679a56bf5f5fa85b577 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Fri, 12 Apr 2024 13:12:47 +0400 Subject: [PATCH 16/25] fix: receipt.rs imports are fixed. --- Cargo.toml | 1 + crates/op-consensus/src/lib.rs | 2 +- crates/op-consensus/src/receipt/envelope.rs | 172 +++++++++++++++++- .../src/op/transaction/receipt.rs | 8 +- .../src/op/transaction/tx_type.rs | 1 - 5 files changed, 168 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b7ed3fa7..4c1212f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ alloy = { git = "https://github.com/alloy-rs/alloy", rev = "89f14f9", features = "rpc-types","rlp","consensus" ] } op-consensus = { version = "0.1.0", default-features = false, path = "crates/op-consensus" } +op-rpc-types = { version = "0.1.0", default-features = false, path = "crates/op-rpc-types" } # Serde serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } diff --git a/crates/op-consensus/src/lib.rs b/crates/op-consensus/src/lib.rs index 4df93164..fadaadf4 100644 --- a/crates/op-consensus/src/lib.rs +++ b/crates/op-consensus/src/lib.rs @@ -1 +1 @@ -mod receipt; +pub mod receipt; diff --git a/crates/op-consensus/src/receipt/envelope.rs b/crates/op-consensus/src/receipt/envelope.rs index 94584917..6cb4e123 100644 --- a/crates/op-consensus/src/receipt/envelope.rs +++ b/crates/op-consensus/src/receipt/envelope.rs @@ -1,4 +1,8 @@ -use alloy::rpc::types::eth::Log; +use alloy::{ + consensus::{Receipt, ReceiptWithBloom}, + rpc::types::eth::Log, +}; +use alloy_primitives::Bloom; /// Receipt envelope, as defined in [EIP-2718]. /// @@ -14,27 +18,179 @@ use alloy::rpc::types::eth::Log; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "type"))] #[non_exhaustive] -pub enum ReceiptEnvelope { - // TODO: Add T and receiptbloom +pub enum ReceiptEnvelope { /// Receipt envelope with no type flag. #[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))] - Legacy, + Legacy(ReceiptWithBloom), /// Receipt envelope with type flag 1, containing a [EIP-2930] receipt. /// /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 #[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))] - Eip2930, + Eip2930(ReceiptWithBloom), /// Receipt envelope with type flag 2, containing a [EIP-1559] receipt. /// /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 #[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))] - Eip1559, + Eip1559(ReceiptWithBloom), /// Receipt envelope with type flag 2, containing a [EIP-4844] receipt. /// /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 #[cfg_attr(feature = "serde", serde(rename = "0x3", alias = "0x03"))] - Eip4844, + Eip4844(ReceiptWithBloom), /// Receipt envelope for Optimism's deposit transactions #[cfg_attr(feature = "serde", serde(rename = "0x7E", alias = "0x7E"))] - Deposit, + Deposit(ReceiptWithBloom), +} + +impl ReceiptEnvelope { + /// Return the [`TxType`] of the inner receipt. + pub const fn tx_type(&self) -> TxType { + match self { + Self::Legacy(_) => TxType::Legacy, + Self::Eip2930(_) => TxType::Eip2930, + Self::Eip1559(_) => TxType::Eip1559, + Self::Eip4844(_) => TxType::Eip4844, + } + } + + /// Return true if the transaction was successful. + pub fn is_success(&self) -> bool { + self.status() + } + + /// Returns the success status of the receipt's transaction. + pub fn status(&self) -> bool { + self.as_receipt().unwrap().status + } + + /// Returns the cumulative gas used at this receipt. + pub fn cumulative_gas_used(&self) -> u128 { + self.as_receipt().unwrap().cumulative_gas_used + } + + /// Return the receipt logs. + pub fn logs(&self) -> &[T] { + &self.as_receipt().unwrap().logs + } + + /// Return the receipt's bloom. + pub fn logs_bloom(&self) -> &Bloom { + &self.as_receipt_with_bloom().unwrap().logs_bloom + } + + /// Return the inner receipt with bloom. Currently this is infallible, + /// however, future receipt types may be added. + pub const fn as_receipt_with_bloom(&self) -> Option<&ReceiptWithBloom> { + match self { + Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip4844(t) => Some(t), + } + } + + /// Return the inner receipt. Currently this is infallible, however, future + /// receipt types may be added. + pub const fn as_receipt(&self) -> Option<&Receipt> { + match self { + Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip4844(t) => { + Some(&t.receipt) + } + } + } +} + +impl ReceiptEnvelope { + /// Get the length of the inner receipt in the 2718 encoding. + pub fn inner_length(&self) -> usize { + self.as_receipt_with_bloom().unwrap().length() + } + + /// Calculate the length of the rlp payload of the network encoded receipt. + pub fn rlp_payload_length(&self) -> usize { + let length = self.as_receipt_with_bloom().unwrap().length(); + match self { + Self::Legacy(_) => length, + _ => length + 1, + } + } +} + +impl Encodable for ReceiptEnvelope { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + self.network_encode(out) + } + + fn length(&self) -> usize { + let mut payload_length = self.rlp_payload_length(); + if !self.is_legacy() { + payload_length += length_of_length(payload_length); + } + payload_length + } +} + +impl Decodable for ReceiptEnvelope { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + match Self::network_decode(buf) { + Ok(t) => Ok(t), + Err(_) => Err(alloy_rlp::Error::Custom("Unexpected type")), + } + } +} + +impl Encodable2718 for ReceiptEnvelope { + fn type_flag(&self) -> Option { + match self { + Self::Legacy(_) => None, + Self::Eip2930(_) => Some(TxType::Eip2930 as u8), + Self::Eip1559(_) => Some(TxType::Eip1559 as u8), + Self::Eip4844(_) => Some(TxType::Eip4844 as u8), + } + } + + fn encode_2718_len(&self) -> usize { + self.inner_length() + !self.is_legacy() as usize + } + + fn encode_2718(&self, out: &mut dyn BufMut) { + match self.type_flag() { + None => {} + Some(ty) => out.put_u8(ty), + } + self.as_receipt_with_bloom().unwrap().encode(out); + } +} + +impl Decodable2718 for ReceiptEnvelope { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_rlp::Result { + let receipt = Decodable::decode(buf)?; + match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("Unexpected type"))? { + TxType::Eip2930 => Ok(Self::Eip2930(receipt)), + TxType::Eip1559 => Ok(Self::Eip1559(receipt)), + TxType::Eip4844 => Ok(Self::Eip4844(receipt)), + TxType::Legacy => { + Err(alloy_rlp::Error::Custom("type-0 eip2718 transactions are not supported")) + } + } + } + + fn fallback_decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Ok(Self::Legacy(Decodable::decode(buf)?)) + } +} + +#[cfg(any(test, feature = "arbitrary"))] +impl<'a, T> arbitrary::Arbitrary<'a> for ReceiptEnvelope +where + T: arbitrary::Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let receipt = ReceiptWithBloom::::arbitrary(u)?; + + match u.int_in_range(0..=3)? { + 0 => Ok(Self::Legacy(receipt)), + 1 => Ok(Self::Eip2930(receipt)), + 2 => Ok(Self::Eip1559(receipt)), + 3 => Ok(Self::Eip4844(receipt)), + _ => unreachable!(), + } + } } diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index 3c4a8a89..041ed80b 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -1,17 +1,13 @@ use crate::op::transaction::tx_type::TxType; use alloy::{rpc::types::eth::Log, serde as alloy_serde}; use alloy_primitives::{Address, B256}; -use op_consensus::receipt::ReceiptEnvelope; +use op_consensus::receipt::envelope::ReceiptEnvelope; use serde::{Deserialize, Serialize}; /// Transaction receipt /// /// This type is generic over an inner [`ReceiptEnvelope`] which contains /// consensus data and metadata. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[cfg_attr( - any(test, feature = "arbitrary"), - derive(proptest_derive::Arbitrary, arbitrary::Arbitrary) -)] #[serde(rename_all = "camelCase")] pub struct TransactionReceipt> { /// The receipt envelope, which contains the consensus receipt data.. @@ -98,7 +94,7 @@ pub struct TransactionReceipt> { /// The value is always equal to `1` when present. #[serde(skip_serializing_if = "Option::is_none", with = "alloy_serde::u64_hex_opt")] pub deposit_receipt_version: Option, - pub tx_type: tx_type::TxType, + pub tx_type: TxType, } impl AsRef> for TransactionReceipt { diff --git a/crates/op-rpc-types/src/op/transaction/tx_type.rs b/crates/op-rpc-types/src/op/transaction/tx_type.rs index ca4172a9..b970b763 100644 --- a/crates/op-rpc-types/src/op/transaction/tx_type.rs +++ b/crates/op-rpc-types/src/op/transaction/tx_type.rs @@ -1,4 +1,3 @@ -use alloy_primitives::U8; use serde::{Deserialize, Serialize}; /// Identifier for legacy transaction, however a legacy tx is technically not From 2304918209b2f5ad0cd29a2c36da1a9656d43784 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Fri, 12 Apr 2024 13:18:53 +0400 Subject: [PATCH 17/25] feat: add filters.rs --- crates/op-rpc-types/src/op/filters.rs | 69 +++++++++++++++++++++++++++ crates/op-rpc-types/src/op/mod.rs | 1 + 2 files changed, 70 insertions(+) create mode 100644 crates/op-rpc-types/src/op/filters.rs diff --git a/crates/op-rpc-types/src/op/filters.rs b/crates/op-rpc-types/src/op/filters.rs new file mode 100644 index 00000000..3534fe60 --- /dev/null +++ b/crates/op-rpc-types/src/op/filters.rs @@ -0,0 +1,69 @@ +use super::transaction::Transaction; +use alloy::rpc::types::eth::Log as RpcLog; +use alloy_primitives::B256; +use serde::{Deserialize, Deserializer, Serialize}; + +/// Response of the `eth_getFilterChanges` RPC. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +#[serde(untagged)] +pub enum FilterChanges { + /// Empty result. + #[serde(with = "empty_array")] + Empty, + /// New logs. + Logs(Vec), + /// New hashes (block or transactions). + Hashes(Vec), + /// New transactions. + Transactions(Vec), +} +mod empty_array { + use serde::{Serialize, Serializer}; + + pub(super) fn serialize(s: S) -> Result + where + S: Serializer, + { + (&[] as &[()]).serialize(s) + } +} +impl<'de> Deserialize<'de> for FilterChanges { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum Changes { + Hashes(Vec), + Logs(Vec), + Transactions(Vec), + } + + let changes = Changes::deserialize(deserializer)?; + let changes = match changes { + Changes::Logs(vals) => { + if vals.is_empty() { + FilterChanges::Empty + } else { + FilterChanges::Logs(vals) + } + } + Changes::Hashes(vals) => { + if vals.is_empty() { + FilterChanges::Empty + } else { + FilterChanges::Hashes(vals) + } + } + Changes::Transactions(vals) => { + if vals.is_empty() { + FilterChanges::Empty + } else { + FilterChanges::Transactions(vals) + } + } + }; + Ok(changes) + } +} diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs index 5439e338..f3bca027 100644 --- a/crates/op-rpc-types/src/op/mod.rs +++ b/crates/op-rpc-types/src/op/mod.rs @@ -1,5 +1,6 @@ mod block; mod call; +mod filters; mod pubsub; mod transaction; pub use alloy::rpc::types::eth::{Header, Log}; From 2ad343823f0f9299a29973754d921d6e3a6a62c1 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Fri, 12 Apr 2024 13:22:20 +0400 Subject: [PATCH 18/25] feat: re-export all eth types. --- crates/op-rpc-types/src/op/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs index f3bca027..b4ce1c9a 100644 --- a/crates/op-rpc-types/src/op/mod.rs +++ b/crates/op-rpc-types/src/op/mod.rs @@ -3,4 +3,4 @@ mod call; mod filters; mod pubsub; mod transaction; -pub use alloy::rpc::types::eth::{Header, Log}; +pub use alloy::rpc::types::eth::*; From 6a72e801474a698207dca8b1d30961bd76a6fcfa Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Mon, 15 Apr 2024 11:25:31 +0400 Subject: [PATCH 19/25] feat: review changes. --- crates/op-consensus/Cargo.toml | 34 ---- crates/op-consensus/src/lib.rs | 1 - crates/op-consensus/src/receipt/envelope.rs | 196 -------------------- crates/op-consensus/src/receipt/mod.rs | 1 - crates/op-rpc-types/src/op/block.rs | 99 +--------- crates/op-rpc-types/src/op/filters.rs | 2 +- 6 files changed, 4 insertions(+), 329 deletions(-) delete mode 100644 crates/op-consensus/Cargo.toml delete mode 100644 crates/op-consensus/src/lib.rs delete mode 100644 crates/op-consensus/src/receipt/envelope.rs delete mode 100644 crates/op-consensus/src/receipt/mod.rs diff --git a/crates/op-consensus/Cargo.toml b/crates/op-consensus/Cargo.toml deleted file mode 100644 index e81f5dd8..00000000 --- a/crates/op-consensus/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "op-consensus" -description = "Optimism Consensus" - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -homepage.workspace = true -authors.workspace = true -repository.workspace = true -exclude.workspace = true - -[dependencies] -alloy-primitives = { workspace = true, features = ["rlp"] } -alloy = { workspace = true , features = ["serde", "rpc-types", "rpc-types-eth"]} - -sha2 = { version = "0.10", default-features = false } - -# arbitrary -arbitrary = { workspace = true, features = ["derive"], optional = true } - -# serde -serde = { workspace = true, features = ["derive"], optional = true } -serde_json.workspace = true - -[features] -serde = ["dep:serde"] - -[dev-dependencies] -arbitrary = { workspace = true, features = ["derive"] } -proptest.workspace = true -proptest-derive.workspace = true -rand.workspace = true \ No newline at end of file diff --git a/crates/op-consensus/src/lib.rs b/crates/op-consensus/src/lib.rs deleted file mode 100644 index fadaadf4..00000000 --- a/crates/op-consensus/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod receipt; diff --git a/crates/op-consensus/src/receipt/envelope.rs b/crates/op-consensus/src/receipt/envelope.rs deleted file mode 100644 index 6cb4e123..00000000 --- a/crates/op-consensus/src/receipt/envelope.rs +++ /dev/null @@ -1,196 +0,0 @@ -use alloy::{ - consensus::{Receipt, ReceiptWithBloom}, - rpc::types::eth::Log, -}; -use alloy_primitives::Bloom; - -/// Receipt envelope, as defined in [EIP-2718]. -/// -/// This enum distinguishes between tagged and untagged legacy receipts, as the -/// in-protocol merkle tree may commit to EITHER 0-prefixed or raw. Therefore -/// we must ensure that encoding returns the precise byte-array that was -/// decoded, preserving the presence or absence of the `TransactionType` flag. -/// -/// Transaction receipt payloads are specified in their respective EIPs. -/// -/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718 -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(tag = "type"))] -#[non_exhaustive] -pub enum ReceiptEnvelope { - /// Receipt envelope with no type flag. - #[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))] - Legacy(ReceiptWithBloom), - /// Receipt envelope with type flag 1, containing a [EIP-2930] receipt. - /// - /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 - #[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))] - Eip2930(ReceiptWithBloom), - /// Receipt envelope with type flag 2, containing a [EIP-1559] receipt. - /// - /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 - #[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))] - Eip1559(ReceiptWithBloom), - /// Receipt envelope with type flag 2, containing a [EIP-4844] receipt. - /// - /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844 - #[cfg_attr(feature = "serde", serde(rename = "0x3", alias = "0x03"))] - Eip4844(ReceiptWithBloom), - /// Receipt envelope for Optimism's deposit transactions - #[cfg_attr(feature = "serde", serde(rename = "0x7E", alias = "0x7E"))] - Deposit(ReceiptWithBloom), -} - -impl ReceiptEnvelope { - /// Return the [`TxType`] of the inner receipt. - pub const fn tx_type(&self) -> TxType { - match self { - Self::Legacy(_) => TxType::Legacy, - Self::Eip2930(_) => TxType::Eip2930, - Self::Eip1559(_) => TxType::Eip1559, - Self::Eip4844(_) => TxType::Eip4844, - } - } - - /// Return true if the transaction was successful. - pub fn is_success(&self) -> bool { - self.status() - } - - /// Returns the success status of the receipt's transaction. - pub fn status(&self) -> bool { - self.as_receipt().unwrap().status - } - - /// Returns the cumulative gas used at this receipt. - pub fn cumulative_gas_used(&self) -> u128 { - self.as_receipt().unwrap().cumulative_gas_used - } - - /// Return the receipt logs. - pub fn logs(&self) -> &[T] { - &self.as_receipt().unwrap().logs - } - - /// Return the receipt's bloom. - pub fn logs_bloom(&self) -> &Bloom { - &self.as_receipt_with_bloom().unwrap().logs_bloom - } - - /// Return the inner receipt with bloom. Currently this is infallible, - /// however, future receipt types may be added. - pub const fn as_receipt_with_bloom(&self) -> Option<&ReceiptWithBloom> { - match self { - Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip4844(t) => Some(t), - } - } - - /// Return the inner receipt. Currently this is infallible, however, future - /// receipt types may be added. - pub const fn as_receipt(&self) -> Option<&Receipt> { - match self { - Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip4844(t) => { - Some(&t.receipt) - } - } - } -} - -impl ReceiptEnvelope { - /// Get the length of the inner receipt in the 2718 encoding. - pub fn inner_length(&self) -> usize { - self.as_receipt_with_bloom().unwrap().length() - } - - /// Calculate the length of the rlp payload of the network encoded receipt. - pub fn rlp_payload_length(&self) -> usize { - let length = self.as_receipt_with_bloom().unwrap().length(); - match self { - Self::Legacy(_) => length, - _ => length + 1, - } - } -} - -impl Encodable for ReceiptEnvelope { - fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - self.network_encode(out) - } - - fn length(&self) -> usize { - let mut payload_length = self.rlp_payload_length(); - if !self.is_legacy() { - payload_length += length_of_length(payload_length); - } - payload_length - } -} - -impl Decodable for ReceiptEnvelope { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - match Self::network_decode(buf) { - Ok(t) => Ok(t), - Err(_) => Err(alloy_rlp::Error::Custom("Unexpected type")), - } - } -} - -impl Encodable2718 for ReceiptEnvelope { - fn type_flag(&self) -> Option { - match self { - Self::Legacy(_) => None, - Self::Eip2930(_) => Some(TxType::Eip2930 as u8), - Self::Eip1559(_) => Some(TxType::Eip1559 as u8), - Self::Eip4844(_) => Some(TxType::Eip4844 as u8), - } - } - - fn encode_2718_len(&self) -> usize { - self.inner_length() + !self.is_legacy() as usize - } - - fn encode_2718(&self, out: &mut dyn BufMut) { - match self.type_flag() { - None => {} - Some(ty) => out.put_u8(ty), - } - self.as_receipt_with_bloom().unwrap().encode(out); - } -} - -impl Decodable2718 for ReceiptEnvelope { - fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_rlp::Result { - let receipt = Decodable::decode(buf)?; - match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("Unexpected type"))? { - TxType::Eip2930 => Ok(Self::Eip2930(receipt)), - TxType::Eip1559 => Ok(Self::Eip1559(receipt)), - TxType::Eip4844 => Ok(Self::Eip4844(receipt)), - TxType::Legacy => { - Err(alloy_rlp::Error::Custom("type-0 eip2718 transactions are not supported")) - } - } - } - - fn fallback_decode(buf: &mut &[u8]) -> alloy_rlp::Result { - Ok(Self::Legacy(Decodable::decode(buf)?)) - } -} - -#[cfg(any(test, feature = "arbitrary"))] -impl<'a, T> arbitrary::Arbitrary<'a> for ReceiptEnvelope -where - T: arbitrary::Arbitrary<'a>, -{ - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let receipt = ReceiptWithBloom::::arbitrary(u)?; - - match u.int_in_range(0..=3)? { - 0 => Ok(Self::Legacy(receipt)), - 1 => Ok(Self::Eip2930(receipt)), - 2 => Ok(Self::Eip1559(receipt)), - 3 => Ok(Self::Eip4844(receipt)), - _ => unreachable!(), - } - } -} diff --git a/crates/op-consensus/src/receipt/mod.rs b/crates/op-consensus/src/receipt/mod.rs deleted file mode 100644 index 788ab129..00000000 --- a/crates/op-consensus/src/receipt/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod envelope; diff --git a/crates/op-rpc-types/src/op/block.rs b/crates/op-rpc-types/src/op/block.rs index c8705510..53b4b199 100644 --- a/crates/op-rpc-types/src/op/block.rs +++ b/crates/op-rpc-types/src/op/block.rs @@ -4,7 +4,7 @@ use crate::op::transaction::Transaction; use alloy::rpc::types::eth::{ - BlockTransactionHashes, BlockTransactionHashesMut, Header, Rich, Withdrawal, + BlockTransactions, Header, Rich, Withdrawal }; use alloy_primitives::{B256, U256}; use serde::{Deserialize, Serialize}; @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; /// Block representation #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Block { +pub struct Block{ /// Header of the block. #[serde(flatten)] pub header: Header, @@ -25,7 +25,7 @@ pub struct Block { default = "BlockTransactions::uncle", skip_serializing_if = "BlockTransactions::is_uncle" )] - pub transactions: BlockTransactions, + pub transactions: BlockTransactions, /// Integer the size of this block in bytes. #[serde(default, skip_serializing_if = "Option::is_none")] pub size: Option, @@ -41,99 +41,6 @@ impl Block { } } -/// Block Transactions depending on the boolean attribute of `eth_getBlockBy*`, -/// or if used by `eth_getUncle*` -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum BlockTransactions { - /// Only hashes - Hashes(Vec), - /// Full transactions - Full(Vec), - /// Special case for uncle response. - Uncle, -} - -impl Default for BlockTransactions { - fn default() -> Self { - BlockTransactions::Hashes(Vec::default()) - } -} - -impl BlockTransactions { - /// Converts `self` into `Hashes`. - #[inline] - pub fn convert_to_hashes(&mut self) { - if !self.is_hashes() { - *self = Self::Hashes(self.hashes().copied().collect()); - } - } - - /// Converts `self` into `Hashes`. - #[inline] - pub fn into_hashes(mut self) -> Self { - self.convert_to_hashes(); - self - } - - /// Check if the enum variant is used for hashes. - #[inline] - pub const fn is_hashes(&self) -> bool { - matches!(self, Self::Hashes(_)) - } - - /// Returns true if the enum variant is used for full transactions. - #[inline] - pub const fn is_full(&self) -> bool { - matches!(self, Self::Full(_)) - } - - /// Returns true if the enum variant is used for an uncle response. - #[inline] - pub const fn is_uncle(&self) -> bool { - matches!(self, Self::Uncle) - } - - /// Returns an iterator over the transaction hashes. - #[deprecated = "use `hashes` instead"] - #[inline] - pub fn iter(&self) -> BlockTransactionHashes<'_> { - self.hashes() - } - - /// Returns an iterator over references to the transaction hashes. - #[inline] - pub fn hashes(&self) -> BlockTransactionHashes<'_> { - BlockTransactionHashes::new(self) // TODO: The `new` method is not public, so cannot be used - // here. Make a PR to alloy to make this public - } - - /// Returns an iterator over mutable references to the transaction hashes. - #[inline] - pub fn hashes_mut(&mut self) -> BlockTransactionHashesMut<'_> { - BlockTransactionHashesMut::new(self) // TODO: The `new` method is not public, so cannot be - // used here. Make a PR to alloy to make this public - } - - /// Returns an instance of BlockTransactions with the Uncle special case. - #[inline] - pub const fn uncle() -> Self { - Self::Uncle - } - - /// Returns the number of transactions. - #[inline] - pub fn len(&self) -> usize { - self.hashes().len() - } - - /// Whether the block has no transactions. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - /// A Block representation that allows to include additional fields pub type RichBlock = Rich; diff --git a/crates/op-rpc-types/src/op/filters.rs b/crates/op-rpc-types/src/op/filters.rs index 3534fe60..b481d176 100644 --- a/crates/op-rpc-types/src/op/filters.rs +++ b/crates/op-rpc-types/src/op/filters.rs @@ -1,4 +1,4 @@ -use super::transaction::Transaction; +use crate::op::Transaction; use alloy::rpc::types::eth::Log as RpcLog; use alloy_primitives::B256; use serde::{Deserialize, Deserializer, Serialize}; From 93141f03b00e0ea051b83b4292fcb32da3b0cf00 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Thu, 18 Apr 2024 23:11:34 +0400 Subject: [PATCH 20/25] refactor: re-import instead of redefining. --- Cargo.toml | 1 - crates/op-rpc-types/Cargo.toml | 1 - crates/op-rpc-types/src/op/block.rs | 9 ++- crates/op-rpc-types/src/op/transaction/mod.rs | 65 ++---------------- .../src/op/transaction/receipt.rs | 67 ++----------------- 5 files changed, 15 insertions(+), 128 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c1212f5..4de4e2e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ alloy = { git = "https://github.com/alloy-rs/alloy", rev = "89f14f9", features = "rpc-types-eth", "rpc-types","rlp","consensus" ] } -op-consensus = { version = "0.1.0", default-features = false, path = "crates/op-consensus" } op-rpc-types = { version = "0.1.0", default-features = false, path = "crates/op-rpc-types" } # Serde diff --git a/crates/op-rpc-types/Cargo.toml b/crates/op-rpc-types/Cargo.toml index 0f627039..c8bf3b22 100644 --- a/crates/op-rpc-types/Cargo.toml +++ b/crates/op-rpc-types/Cargo.toml @@ -12,7 +12,6 @@ repository.workspace = true exclude.workspace = true [dependencies] -op-consensus = { workspace = true, features = ["serde"]} # alloy-rlp = { workspace = true, features = ["arrayvec", "derive"] } alloy-primitives = { workspace = true, features = ["rlp", "serde", "std"] } # alloy-serde.workspace = true diff --git a/crates/op-rpc-types/src/op/block.rs b/crates/op-rpc-types/src/op/block.rs index 53b4b199..f178975d 100644 --- a/crates/op-rpc-types/src/op/block.rs +++ b/crates/op-rpc-types/src/op/block.rs @@ -3,16 +3,14 @@ #![allow(unknown_lints, non_local_definitions)] use crate::op::transaction::Transaction; -use alloy::rpc::types::eth::{ - BlockTransactions, Header, Rich, Withdrawal -}; +use alloy::rpc::types::eth::{BlockTransactions, Header, Rich, WithOtherFields, Withdrawal}; use alloy_primitives::{B256, U256}; use serde::{Deserialize, Serialize}; /// Block representation #[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Block{ +pub struct Block { /// Header of the block. #[serde(flatten)] pub header: Header, @@ -46,6 +44,7 @@ pub type RichBlock = Rich; impl From for RichBlock { fn from(block: Block) -> Self { - Rich { inner: block, extra_info: Default::default() } + let a = WithOtherFields::new(block); + a.other = Rich { inner: block, extra_info: Default::default() } } } diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs index 0537d56c..850e75a7 100644 --- a/crates/op-rpc-types/src/op/transaction/mod.rs +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -1,9 +1,9 @@ use alloy::{ consensus::{Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEnvelope, TxLegacy}, - rpc::types::eth::{AccessList, ConversionError, Signature}, + rpc::types::eth::{ConversionError, Signature}, serde as alloy_serde, }; -use alloy_primitives::{Address, Bytes, B256, U128, U256, U64}; +use alloy_primitives::{B256, U128, U64}; use serde::{Deserialize, Serialize}; use self::{request::TransactionRequest, tx_type::TxType}; @@ -16,64 +16,9 @@ pub mod tx_type; #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] #[serde(rename_all = "camelCase")] pub struct Transaction { - /// Hash - pub hash: B256, - /// Nonce - #[serde(with = "alloy_serde::num::u64_hex")] - pub nonce: u64, - /// Block hash - pub block_hash: Option, - /// Block number - #[serde(with = "alloy_serde::num::u64_hex_opt")] - pub block_number: Option, - /// Transaction Index - #[serde(with = "alloy_serde::num::u64_hex_opt")] - pub transaction_index: Option, - /// Sender - pub from: Address, - /// Recipient - pub to: Option
, - /// Transferred value - pub value: U256, - /// Gas Price - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u128_hex_or_decimal_opt" - )] - pub gas_price: Option, - /// Gas amount - #[serde(with = "alloy_serde::num::u128_hex_or_decimal")] - pub gas: u128, - /// Max BaseFeePerGas the user is willing to pay. - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u128_hex_or_decimal_opt" - )] - pub max_fee_per_gas: Option, - /// The miner's tip. - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u128_hex_or_decimal_opt" - )] - pub max_priority_fee_per_gas: Option, - /// Data - pub input: Bytes, - /// All _flattened_ fields of the transaction signature. - /// - /// Note: this is an option so special transaction types without a signature (e.g. ) can be supported. - #[serde(flatten, skip_serializing_if = "Option::is_none")] - pub signature: Option, - /// The chain id of the transaction, if any. - #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::u64_hex_opt")] - pub chain_id: Option, - /// EIP2930 - /// - /// Pre-pay to warm storage access. - #[serde(skip_serializing_if = "Option::is_none")] - pub access_list: Option, + /// Ethereum Transaction Types + #[serde(flatten)] + pub inner: alloy::rpc::types::eth::Transaction, /// EIP2718 /// /// Transaction type, Some(2) for EIP-1559 transaction, diff --git a/crates/op-rpc-types/src/op/transaction/receipt.rs b/crates/op-rpc-types/src/op/transaction/receipt.rs index 041ed80b..2ff081c4 100644 --- a/crates/op-rpc-types/src/op/transaction/receipt.rs +++ b/crates/op-rpc-types/src/op/transaction/receipt.rs @@ -1,7 +1,9 @@ use crate::op::transaction::tx_type::TxType; -use alloy::{rpc::types::eth::Log, serde as alloy_serde}; +use alloy::{ + rpc::types::eth::{Log, TransactionReceipt as EthTransactionReceipt}, + serde as alloy_serde, +}; use alloy_primitives::{Address, B256}; -use op_consensus::receipt::envelope::ReceiptEnvelope; use serde::{Deserialize, Serialize}; /// Transaction receipt /// @@ -9,55 +11,10 @@ use serde::{Deserialize, Serialize}; /// consensus data and metadata. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TransactionReceipt> { - /// The receipt envelope, which contains the consensus receipt data.. +pub struct TransactionReceipt>> { + /// The Ethereum transaction receipt with Optimism Log #[serde(flatten)] pub inner: T, - /// Transaction Hash. - pub transaction_hash: B256, - /// Index within the block. - #[serde(with = "alloy_serde::u64_hex")] - pub transaction_index: u64, - /// Hash of the block this transaction was included within. - pub block_hash: Option, - /// Number of the block this transaction was included within. - #[serde(with = "alloy_serde::u64_hex_opt")] - pub block_number: Option, - /// Gas used by this transaction alone. - #[serde(with = "alloy_serde::u128_hex_or_decimal")] - pub gas_used: u128, - /// The price paid post-execution by the transaction (i.e. base fee + priority fee). Both - /// fields in 1559-style transactions are maximums (max fee + max priority fee), the amount - /// that's actually paid by users can only be determined post-execution - #[serde(with = "alloy_serde::u128_hex_or_decimal")] - pub effective_gas_price: u128, - /// Blob gas used by the eip-4844 transaction - /// - /// This is None for non eip-4844 transactions - #[serde( - skip_serializing_if = "Option::is_none", - with = "alloy_serde::u128_hex_or_decimal_opt", - default - )] - pub blob_gas_used: Option, - /// The price paid by the eip-4844 transaction per blob gas. - #[serde( - skip_serializing_if = "Option::is_none", - with = "alloy_serde::u128_hex_or_decimal_opt", - default - )] - pub blob_gas_price: Option, - /// Address of the sender - pub from: Address, - /// Address of the receiver. None when its a contract creation transaction. - pub to: Option
, - /// Contract address created, or None if not a deployment. - pub contract_address: Option
, - /// The post-transaction stateroot (pre Byzantium) - /// - /// EIP98 makes this optional field, if it's missing then skip serializing it - #[serde(skip_serializing_if = "Option::is_none", rename = "root")] - pub state_root: Option, /// The fee associated with a transaction on the Layer 1 #[serde( default, @@ -141,18 +98,6 @@ impl TransactionReceipt { { TransactionReceipt { inner: f(self.inner), - transaction_hash: self.transaction_hash, - transaction_index: self.transaction_index, - block_hash: self.block_hash, - block_number: self.block_number, - gas_used: self.gas_used, - effective_gas_price: self.effective_gas_price, - blob_gas_used: self.blob_gas_used, - blob_gas_price: self.blob_gas_price, - from: self.from, - to: self.to, - contract_address: self.contract_address, - state_root: self.state_root, l1_fee: self.l1_fee, l1_fee_scalar: self.l1_fee_scalar, l1_gas_price: self.l1_gas_price, From b5005c9a149cd3758d1a53ed8a245c81a90e30f9 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Sat, 20 Apr 2024 21:32:28 +0400 Subject: [PATCH 21/25] chore: bump alloy version. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4de4e2e8..a83851ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ exclude = ["benches/", "tests/"] [workspace.dependencies] # Alloy alloy-primitives = { version = "0.7.0", default-features = false } -alloy = { git = "https://github.com/alloy-rs/alloy", rev = "89f14f9", features = [ +alloy = { git = "https://github.com/alloy-rs/alloy", rev = "39b8695", features = [ "serde", "rpc-types-eth", "rpc-types","rlp","consensus" From 577749e658286c5387b9fd2e4efcd68ce52c71fd Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Sat, 20 Apr 2024 21:54:55 +0400 Subject: [PATCH 22/25] feat: use generics, remove unnecessary types. --- crates/op-rpc-types/src/op/block.rs | 50 --- crates/op-rpc-types/src/op/call.rs | 13 - crates/op-rpc-types/src/op/filters.rs | 69 ---- crates/op-rpc-types/src/op/mod.rs | 9 +- crates/op-rpc-types/src/op/pubsub.rs | 37 -- crates/op-rpc-types/src/op/transaction/mod.rs | 20 +- .../src/op/transaction/request.rs | 380 ------------------ 7 files changed, 9 insertions(+), 569 deletions(-) delete mode 100644 crates/op-rpc-types/src/op/block.rs delete mode 100644 crates/op-rpc-types/src/op/call.rs delete mode 100644 crates/op-rpc-types/src/op/filters.rs delete mode 100644 crates/op-rpc-types/src/op/pubsub.rs delete mode 100644 crates/op-rpc-types/src/op/transaction/request.rs diff --git a/crates/op-rpc-types/src/op/block.rs b/crates/op-rpc-types/src/op/block.rs deleted file mode 100644 index f178975d..00000000 --- a/crates/op-rpc-types/src/op/block.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Block RPC types. - -#![allow(unknown_lints, non_local_definitions)] - -use crate::op::transaction::Transaction; -use alloy::rpc::types::eth::{BlockTransactions, Header, Rich, WithOtherFields, Withdrawal}; -use alloy_primitives::{B256, U256}; -use serde::{Deserialize, Serialize}; - -/// Block representation -#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Block { - /// Header of the block. - #[serde(flatten)] - pub header: Header, - /// Uncles' hashes. - #[serde(default)] - pub uncles: Vec, - /// Block Transactions. In the case of an uncle block, this field is not included in RPC - /// responses, and when deserialized, it will be set to [BlockTransactions::Uncle]. - #[serde( - default = "BlockTransactions::uncle", - skip_serializing_if = "BlockTransactions::is_uncle" - )] - pub transactions: BlockTransactions, - /// Integer the size of this block in bytes. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub size: Option, - /// Withdrawals in the block. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub withdrawals: Option>, -} - -impl Block { - /// Converts a block with Tx hashes into a full block. - pub fn into_full_block(self, txs: Vec) -> Self { - Self { transactions: BlockTransactions::Full(txs), ..self } - } -} - -/// A Block representation that allows to include additional fields -pub type RichBlock = Rich; - -impl From for RichBlock { - fn from(block: Block) -> Self { - let a = WithOtherFields::new(block); - a.other = Rich { inner: block, extra_info: Default::default() } - } -} diff --git a/crates/op-rpc-types/src/op/call.rs b/crates/op-rpc-types/src/op/call.rs deleted file mode 100644 index d6b8b398..00000000 --- a/crates/op-rpc-types/src/op/call.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::op::transaction::request::TransactionRequest; -use alloy::rpc::types::eth::BlockOverrides; -use serde::{Deserialize, Serialize}; - -/// Bundle of transactions -#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] -#[serde(default, rename_all = "camelCase")] -pub struct Bundle { - /// All transactions to execute - pub transactions: Vec, - /// Block overrides to apply - pub block_override: Option, -} diff --git a/crates/op-rpc-types/src/op/filters.rs b/crates/op-rpc-types/src/op/filters.rs deleted file mode 100644 index b481d176..00000000 --- a/crates/op-rpc-types/src/op/filters.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::op::Transaction; -use alloy::rpc::types::eth::Log as RpcLog; -use alloy_primitives::B256; -use serde::{Deserialize, Deserializer, Serialize}; - -/// Response of the `eth_getFilterChanges` RPC. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -#[serde(untagged)] -pub enum FilterChanges { - /// Empty result. - #[serde(with = "empty_array")] - Empty, - /// New logs. - Logs(Vec), - /// New hashes (block or transactions). - Hashes(Vec), - /// New transactions. - Transactions(Vec), -} -mod empty_array { - use serde::{Serialize, Serializer}; - - pub(super) fn serialize(s: S) -> Result - where - S: Serializer, - { - (&[] as &[()]).serialize(s) - } -} -impl<'de> Deserialize<'de> for FilterChanges { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(untagged)] - enum Changes { - Hashes(Vec), - Logs(Vec), - Transactions(Vec), - } - - let changes = Changes::deserialize(deserializer)?; - let changes = match changes { - Changes::Logs(vals) => { - if vals.is_empty() { - FilterChanges::Empty - } else { - FilterChanges::Logs(vals) - } - } - Changes::Hashes(vals) => { - if vals.is_empty() { - FilterChanges::Empty - } else { - FilterChanges::Hashes(vals) - } - } - Changes::Transactions(vals) => { - if vals.is_empty() { - FilterChanges::Empty - } else { - FilterChanges::Transactions(vals) - } - } - }; - Ok(changes) - } -} diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs index b4ce1c9a..42b3ee77 100644 --- a/crates/op-rpc-types/src/op/mod.rs +++ b/crates/op-rpc-types/src/op/mod.rs @@ -1,6 +1,5 @@ -mod block; -mod call; -mod filters; -mod pubsub; mod transaction; -pub use alloy::rpc::types::eth::*; +use alloy::rpc::types::eth::FilterChanges as EthFilterChanges; +use crate::op::transaction::Transaction; + +pub type FilterChanges = EthFilterChanges; \ No newline at end of file diff --git a/crates/op-rpc-types/src/op/pubsub.rs b/crates/op-rpc-types/src/op/pubsub.rs deleted file mode 100644 index 0cb00bbc..00000000 --- a/crates/op-rpc-types/src/op/pubsub.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! Optimism types for pub-sub - -use crate::op::transaction::Transaction; -use alloy::rpc::types::eth::{pubsub::PubSubSyncStatus, Log, RichHeader}; -use alloy_primitives::B256; -use serde::{Deserialize, Serialize, Serializer}; - -/// Subscription result. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -#[serde(untagged)] -pub enum SubscriptionResult { - /// New block header. - Header(Box), - /// Log - Log(Box), - /// Transaction hash - TransactionHash(B256), - /// Full Transaction - FullTransaction(Box), - /// SyncStatus - SyncState(PubSubSyncStatus), -} - -impl Serialize for SubscriptionResult { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match *self { - SubscriptionResult::Header(ref header) => header.serialize(serializer), - SubscriptionResult::Log(ref log) => log.serialize(serializer), - SubscriptionResult::TransactionHash(ref hash) => hash.serialize(serializer), - SubscriptionResult::FullTransaction(ref tx) => tx.serialize(serializer), - SubscriptionResult::SyncState(ref sync) => sync.serialize(serializer), - } - } -} diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs index 850e75a7..b0bd9203 100644 --- a/crates/op-rpc-types/src/op/transaction/mod.rs +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -1,15 +1,13 @@ use alloy::{ consensus::{Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEnvelope, TxLegacy}, - rpc::types::eth::{ConversionError, Signature}, - serde as alloy_serde, + rpc::types::eth::{ConversionError, TransactionRequest}, }; use alloy_primitives::{B256, U128, U64}; use serde::{Deserialize, Serialize}; -use self::{request::TransactionRequest, tx_type::TxType}; +use self::tx_type::TxType; pub mod receipt; -pub mod request; pub mod tx_type; #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -19,17 +17,6 @@ pub struct Transaction { /// Ethereum Transaction Types #[serde(flatten)] pub inner: alloy::rpc::types::eth::Transaction, - /// EIP2718 - /// - /// Transaction type, Some(2) for EIP-1559 transaction, - /// Some(1) for AccessList transaction, None for Legacy - #[serde( - default, - rename = "type", - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u8_hex_opt" - )] - pub transaction_type: Option, /// The ETH value to mint on L2 #[serde(rename = "mint", skip_serializing_if = "Option::is_none")] pub mint: Option, @@ -76,6 +63,7 @@ impl Transaction { max_fee_per_blob_gas: self.max_fee_per_blob_gas, blob_versioned_hashes: self.blob_versioned_hashes, sidecar: self.sidecar, + transaction_type: self.transaction_type, } } } @@ -180,6 +168,8 @@ impl TryFrom for Signed { } } +// TODO: Implement a impl TryFrom for Signed when the consensus types are ready + impl TryFrom for TxEnvelope { // TODO: When the TxEnvelope is implemented for op-consensus, import it from there. This // envelope doesn't handle DEPOSIT diff --git a/crates/op-rpc-types/src/op/transaction/request.rs b/crates/op-rpc-types/src/op/transaction/request.rs deleted file mode 100644 index 4e296e9e..00000000 --- a/crates/op-rpc-types/src/op/transaction/request.rs +++ /dev/null @@ -1,380 +0,0 @@ -//! Alloy basic Transaction Request type. - -use crate::op::transaction::Transaction; -use alloy::{ - consensus::{ - BlobTransactionSidecar, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, - TxEip4844WithSidecar, TxEnvelope, TxLegacy, TypedTransaction, - }, - rpc::types::eth::{transaction::AccessList, TransactionInput}, - serde as alloy_serde, -}; -use alloy_primitives::{Address, ChainId, TxKind, B256, U256}; -use serde::{Deserialize, Serialize}; -use std::hash::Hash; - -use super::tx_type::TxType; - -/// Represents _all_ transaction requests to/from RPC. -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TransactionRequest { - /// The address of the transaction author. - pub from: Option
, - /// The destination address of the transaction. - pub to: Option
, - /// The legacy gas price. - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u128_hex_or_decimal_opt" - )] - pub gas_price: Option, - /// The max base fee per gas the sender is willing to pay. - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u128_hex_or_decimal_opt" - )] - pub max_fee_per_gas: Option, - /// The max priority fee per gas the sender is willing to pay, also called the miner tip. - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u128_hex_or_decimal_opt" - )] - pub max_priority_fee_per_gas: Option, - /// The max fee per blob gas for EIP-4844 blob transactions. - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::num::u128_hex_or_decimal_opt" - )] - pub max_fee_per_blob_gas: Option, - /// The gas limit for the transaction. - #[serde(default, with = "alloy_serde::num::u128_hex_or_decimal_opt")] - pub gas: Option, - /// The value transferred in the transaction, in wei. - pub value: Option, - /// Transaction data. - #[serde(default, flatten)] - pub input: TransactionInput, - /// The nonce of the transaction. - #[serde(default, with = "alloy_serde::num::u64_hex_opt")] - pub nonce: Option, - /// The chain ID for the transaction. - #[serde(default, with = "alloy_serde::num::u64_hex_opt")] - pub chain_id: Option, - /// An EIP-2930 access list, which lowers cost for accessing accounts and storages in the list. See [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) for more information. - #[serde(default)] - pub access_list: Option, - /// The EIP-2718 transaction type. See [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) for more information. - #[serde(default, rename = "type", with = "alloy_serde::num::u8_hex_opt")] - pub transaction_type: Option, - /// Blob versioned hashes for EIP-4844 transactions. - #[serde(skip_serializing_if = "Option::is_none")] - pub blob_versioned_hashes: Option>, - /// Blob sidecar for EIP-4844 transactions. - #[serde(skip_serializing_if = "Option::is_none")] - pub sidecar: Option, -} - -impl Hash for TransactionRequest { - fn hash(&self, state: &mut H) { - self.from.hash(state); - self.to.hash(state); - self.gas_price.hash(state); - self.max_fee_per_gas.hash(state); - self.max_priority_fee_per_gas.hash(state); - self.gas.hash(state); - self.value.hash(state); - self.input.hash(state); - self.nonce.hash(state); - self.chain_id.hash(state); - self.access_list.hash(state); - self.transaction_type.hash(state); - } -} - -// == impl TransactionRequest == - -impl TransactionRequest { - /// Returns the configured fee cap, if any. - /// - /// The returns `gas_price` (legacy) if set or `max_fee_per_gas` (EIP1559) - #[inline] - pub fn fee_cap(&self) -> Option { - self.gas_price.or(self.max_fee_per_gas) - } - - /// Returns true if the request has a `blobVersionedHashes` field but it is empty. - #[inline] - pub fn has_empty_blob_hashes(&self) -> bool { - self.blob_versioned_hashes.as_ref().map(|blobs| blobs.is_empty()).unwrap_or(false) - } - - /// Sets the `from` field in the call to the provided address - #[inline] - pub const fn from(mut self, from: Address) -> Self { - self.from = Some(from); - self - } - - /// Sets the gas limit for the transaction. - pub const fn gas_limit(mut self, gas_limit: u128) -> Self { - self.gas = Some(gas_limit); - self - } - - /// Sets the nonce for the transaction. - pub const fn nonce(mut self, nonce: u64) -> Self { - self.nonce = Some(nonce); - self - } - - /// Sets the maximum fee per gas for the transaction. - pub const fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self { - self.max_fee_per_gas = Some(max_fee_per_gas); - self - } - - /// Sets the maximum priority fee per gas for the transaction. - pub const fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self { - self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); - self - } - - /// Sets the recipient address for the transaction. - #[inline] - pub const fn to(mut self, to: Option
) -> Self { - self.to = to; - self - } - - /// Sets the value (amount) for the transaction. - pub const fn value(mut self, value: U256) -> Self { - self.value = Some(value); - self - } - - /// Sets the access list for the transaction. - pub fn access_list(mut self, access_list: AccessList) -> Self { - self.access_list = Some(access_list); - self - } - - /// Sets the input data for the transaction. - pub fn input(mut self, input: TransactionInput) -> Self { - self.input = input; - self - } - - /// Sets the transactions type for the transactions. - pub const fn transaction_type(mut self, transaction_type: u8) -> Self { - self.transaction_type = Some(transaction_type); - self - } -} - -impl From for TransactionRequest { - fn from(tx: Transaction) -> TransactionRequest { - tx.into_request() - } -} - -impl From for TransactionRequest { - fn from(tx: TxLegacy) -> TransactionRequest { - TransactionRequest { - from: None, - to: if let TxKind::Call(to) = tx.to { Some(to) } else { None }, - gas_price: Some(tx.gas_price), - gas: Some(tx.gas_limit), - value: Some(tx.value), - input: TransactionInput::from(tx.input), - nonce: Some(tx.nonce), - chain_id: tx.chain_id, - transaction_type: Some(0), - ..Default::default() - } - } -} - -impl From for TransactionRequest { - fn from(tx: TxEip2930) -> TransactionRequest { - TransactionRequest { - from: None, - to: if let TxKind::Call(to) = tx.to { Some(to) } else { None }, - gas_price: Some(tx.gas_price), - gas: Some(tx.gas_limit), - value: Some(tx.value), - input: TransactionInput::from(tx.input), - nonce: Some(tx.nonce), - chain_id: Some(tx.chain_id), - transaction_type: Some(1), - access_list: Some(tx.access_list), - ..Default::default() - } - } -} - -impl From for TransactionRequest { - fn from(tx: TxEip1559) -> TransactionRequest { - TransactionRequest { - from: None, - to: if let TxKind::Call(to) = tx.to { Some(to) } else { None }, - max_fee_per_gas: Some(tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), - gas: Some(tx.gas_limit), - value: Some(tx.value), - input: TransactionInput::from(tx.input), - nonce: Some(tx.nonce), - chain_id: Some(tx.chain_id), - transaction_type: Some(2), - access_list: Some(tx.access_list), - ..Default::default() - } - } -} - -impl From for TransactionRequest { - fn from(tx: TxEip4844) -> TransactionRequest { - TransactionRequest { - from: None, - to: Some(tx.to), - gas: Some(tx.gas_limit), - max_fee_per_gas: Some(tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), - value: Some(tx.value), - input: TransactionInput::from(tx.input), - nonce: Some(tx.nonce), - chain_id: Some(tx.chain_id), - transaction_type: Some(3), - access_list: Some(tx.access_list), - ..Default::default() - } - } -} - -impl From for TransactionRequest { - fn from(tx: TxEip4844WithSidecar) -> TransactionRequest { - let sidecar = tx.sidecar; - let tx = tx.tx; - TransactionRequest { - from: None, - to: Some(tx.to), - gas: Some(tx.gas_limit), - max_fee_per_gas: Some(tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), - value: Some(tx.value), - input: TransactionInput::from(tx.input), - nonce: Some(tx.nonce), - chain_id: Some(tx.chain_id), - transaction_type: Some(3), - access_list: Some(tx.access_list), - sidecar: Some(sidecar), - ..Default::default() - } - } -} - -impl From for TransactionRequest { - fn from(tx: TxEip4844Variant) -> TransactionRequest { - match tx { - TxEip4844Variant::TxEip4844(tx) => tx.into(), - TxEip4844Variant::TxEip4844WithSidecar(tx) => tx.into(), - } - } -} - -impl From for TransactionRequest { - // TODO: Replace the import from alloy-consensus with the op-consensus, the eth TypedTransaction - // does not have the DEPOSIT type - fn from(tx: TypedTransaction) -> TransactionRequest { - match tx { - TypedTransaction::Legacy(tx) => tx.into(), - TypedTransaction::Eip2930(tx) => tx.into(), - TypedTransaction::Eip1559(tx) => tx.into(), - TypedTransaction::Eip4844(tx) => tx.into(), - // TODO: After changing the import to op-consensus, handle DEPOSIT here. - } - } -} - -impl From for TransactionRequest { - // TODO: Replace the import from alloy-consensus with the op-consensus, the eth TxEnvelope does - // not have the DEPOSIT type - fn from(envelope: TxEnvelope) -> TransactionRequest { - match envelope { - TxEnvelope::Legacy(tx) => { - #[cfg(feature = "k256")] - { - let from = tx.recover_signer().ok(); - let tx: TransactionRequest = tx.strip_signature().into(); - if let Some(from) = from { - tx.from(from) - } else { - tx - } - } - - #[cfg(not(feature = "k256"))] - { - tx.strip_signature().into() - } - } - TxEnvelope::Eip2930(tx) => { - #[cfg(feature = "k256")] - { - let from = tx.recover_signer().ok(); - let tx: TransactionRequest = tx.strip_signature().into(); - if let Some(from) = from { - tx.from(from) - } else { - tx - } - } - - #[cfg(not(feature = "k256"))] - { - tx.strip_signature().into() - } - } - TxEnvelope::Eip1559(tx) => { - #[cfg(feature = "k256")] - { - let from = tx.recover_signer().ok(); - let tx: TransactionRequest = tx.strip_signature().into(); - if let Some(from) = from { - tx.from(from) - } else { - tx - } - } - - #[cfg(not(feature = "k256"))] - { - tx.strip_signature().into() - } - } - TxEnvelope::Eip4844(tx) => { - #[cfg(feature = "k256")] - { - let from = tx.recover_signer().ok(); - let tx: TransactionRequest = tx.strip_signature().into(); - if let Some(from) = from { - tx.from(from) - } else { - tx - } - } - - #[cfg(not(feature = "k256"))] - { - tx.strip_signature().into() - } - } - // TODO: After changing the import to op-consensus, handle DEPOSIT here. - _ => Default::default(), - } - } -} From 074488466a25af9654c2a5ff2633438499499a6b Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Sat, 20 Apr 2024 21:55:06 +0400 Subject: [PATCH 23/25] lint: fmt --- crates/op-rpc-types/src/op/mod.rs | 4 ++-- crates/op-rpc-types/src/op/transaction/mod.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/op-rpc-types/src/op/mod.rs b/crates/op-rpc-types/src/op/mod.rs index 42b3ee77..e0d942d4 100644 --- a/crates/op-rpc-types/src/op/mod.rs +++ b/crates/op-rpc-types/src/op/mod.rs @@ -1,5 +1,5 @@ mod transaction; -use alloy::rpc::types::eth::FilterChanges as EthFilterChanges; use crate::op::transaction::Transaction; +use alloy::rpc::types::eth::FilterChanges as EthFilterChanges; -pub type FilterChanges = EthFilterChanges; \ No newline at end of file +pub type FilterChanges = EthFilterChanges; diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs index b0bd9203..d9a6a197 100644 --- a/crates/op-rpc-types/src/op/transaction/mod.rs +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -168,7 +168,8 @@ impl TryFrom for Signed { } } -// TODO: Implement a impl TryFrom for Signed when the consensus types are ready +// TODO: Implement a impl TryFrom for Signed when the consensus types are +// ready impl TryFrom for TxEnvelope { // TODO: When the TxEnvelope is implemented for op-consensus, import it from there. This From 12195e1b8c32edd88513dc80d0c4a87552fe4227 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Thu, 25 Apr 2024 21:43:27 +0400 Subject: [PATCH 24/25] refactor: use native types --- crates/op-rpc-types/src/op/transaction/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/op-rpc-types/src/op/transaction/mod.rs b/crates/op-rpc-types/src/op/transaction/mod.rs index d9a6a197..7dd4e59e 100644 --- a/crates/op-rpc-types/src/op/transaction/mod.rs +++ b/crates/op-rpc-types/src/op/transaction/mod.rs @@ -19,7 +19,7 @@ pub struct Transaction { pub inner: alloy::rpc::types::eth::Transaction, /// The ETH value to mint on L2 #[serde(rename = "mint", skip_serializing_if = "Option::is_none")] - pub mint: Option, + pub mint: Option, /// Hash that uniquely identifies the source of the deposit. #[serde(rename = "sourceHash", skip_serializing_if = "Option::is_none")] pub source_hash: Option, @@ -29,7 +29,7 @@ pub struct Transaction { pub is_system_tx: Option, /// Deposit receipt version for deposit transactions post-canyon #[serde(skip_serializing_if = "Option::is_none")] - pub deposit_receipt_version: Option, + pub deposit_receipt_version: Option, } impl Transaction { From cf248d40dfec5955697a5c1f816a41e26b315bb8 Mon Sep 17 00:00:00 2001 From: EmperorOrokuSaki Date: Thu, 25 Apr 2024 21:44:45 +0400 Subject: [PATCH 25/25] chore: bump alloy version --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a83851ca..80f7a99f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ exclude = ["benches/", "tests/"] [workspace.dependencies] # Alloy -alloy-primitives = { version = "0.7.0", default-features = false } -alloy = { git = "https://github.com/alloy-rs/alloy", rev = "39b8695", features = [ +alloy-primitives = { version = "0.7.1", default-features = false } +alloy = { git = "https://github.com/alloy-rs/alloy", rev = "004aa98", features = [ "serde", "rpc-types-eth", "rpc-types","rlp","consensus"