diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index bf9493720..40c36bfa5 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -284,7 +284,8 @@ mod tests { max_priority_fee_per_gas: 0x28f000fff, max_fee_per_blob_gas: 0x7, gas_limit: 10, - to: Address::default().into(), + placeholder: Some(()), + to: Address::default(), value: U256::from(3_u64), input: Bytes::from(vec![1, 2]), access_list: Default::default(), diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index 5e2bff817..be8144e90 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -182,7 +182,8 @@ impl TryFrom for Transaction { .gas .try_into() .map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?, - to: tx.to.map_or(TxKind::Create, TxKind::Call), + placeholder: tx.to.map(|_| ()), + to: tx.to.unwrap_or_default(), value: tx.value, access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, input: tx.input, diff --git a/crates/primitives/src/revm/env.rs b/crates/primitives/src/revm/env.rs index f519abc05..0c16f5482 100644 --- a/crates/primitives/src/revm/env.rs +++ b/crates/primitives/src/revm/env.rs @@ -272,10 +272,7 @@ where tx_env.gas_limit = tx.gas_limit; tx_env.gas_price = U256::from(tx.max_fee_per_gas); tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas)); - tx_env.transact_to = match tx.to { - TxKind::Call(to) => TransactTo::Call(to), - TxKind::Create => TransactTo::create(), - }; + tx_env.transact_to = TransactTo::Call(tx.to); tx_env.value = tx.value; tx_env.data = tx.input.clone(); tx_env.chain_id = Some(tx.chain_id); diff --git a/crates/primitives/src/transaction/eip4844.rs b/crates/primitives/src/transaction/eip4844.rs index f2130ce50..885e457b8 100644 --- a/crates/primitives/src/transaction/eip4844.rs +++ b/crates/primitives/src/transaction/eip4844.rs @@ -1,10 +1,10 @@ use super::access_list::AccessList; use crate::{ - constants::eip4844::DATA_GAS_PER_BLOB, keccak256, Bytes, ChainId, Signature, TxKind, TxType, + constants::eip4844::DATA_GAS_PER_BLOB, keccak256, Address, Bytes, ChainId, Signature, TxType, B256, U256, }; use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; -use reth_codecs::{main_codec, Compact}; +use reth_codecs::{main_codec, Compact, CompactPlaceholder}; use std::mem; #[cfg(feature = "c-kzg")] @@ -46,9 +46,12 @@ pub struct TxEip4844 { /// /// This is also known as `GasTipCap` pub max_priority_fee_per_gas: u128, - /// The 160-bit address of the message call’s recipient or, for a contract creation - /// transaction, ∅, used here to denote the only member of B0 ; formally Tt. - pub to: TxKind, + /// TODO(debt): this should be removed if we break the DB. + /// Makes sure that the Compact bitflag struct has one bit after the above field: + /// + pub placeholder: Option, + /// The 160-bit address of the message call’s recipient. + pub to: Address, /// A scalar value equal to the number of Wei to /// be transferred to the message call’s recipient or, /// in the case of contract creation, as an endowment @@ -148,6 +151,7 @@ impl TxEip4844 { max_priority_fee_per_gas: Decodable::decode(buf)?, max_fee_per_gas: Decodable::decode(buf)?, gas_limit: Decodable::decode(buf)?, + placeholder: Some(()), to: Decodable::decode(buf)?, value: Decodable::decode(buf)?, input: Decodable::decode(buf)?, @@ -195,7 +199,7 @@ impl TxEip4844 { mem::size_of::() + // gas_limit mem::size_of::() + // max_fee_per_gas mem::size_of::() + // max_priority_fee_per_gas - self.to.size() + // to + mem::size_of::
() + // to mem::size_of::() + // value self.access_list.size() + // access_list self.input.len() + // input @@ -272,3 +276,20 @@ impl TxEip4844 { keccak256(&buf) } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, bytes}; + + #[test] + fn backwards_compatible_txkind_test() { + // TxEip4844 encoded with TxKind on to field + // holesky tx hash: <0xa3b1668225bf0fbfdd6c19aa6fd071fa4ff5d09a607c67ccd458b97735f745ac> + let tx = bytes!("224348a100426844cb2dc6c0b2d05e003b9aca0079c9109b764609df928d16fc4a91e9081f7e87db09310001019101fb28118ceccaabca22a47e35b9c3f12eb2dcb25e5c543d5b75e6cd841f0a05328d26ef16e8450000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000007b399987d24fc5951f3e94a4cb16e87414bf22290000000000000000000000001670090000000000000000000000000000010001302e31382e302d64657600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000009e640a6aadf4f664cf467b795c31332f44acbe6c000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006614c2d1000000000000000000000000000000000000000000000000000000000014012c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000093100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000093100000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041f06fd78f4dcdf089263524731620941747b9b93fd8f631557e25b23845a78b685bd82f9d36bce2f4cc812b6e5191df52479d349089461ffe76e9f2fa2848a0fe1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410819f04aba17677807c61ae72afdddf7737f26931ecfa8af05b7c669808b36a2587e32c90bb0ed2100266dd7797c80121a109a2b0fe941ca5a580e438988cac81c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + let (tx, _) = TxEip4844::from_compact(&tx, tx.len()); + assert_eq!(tx.to, address!("79C9109b764609df928d16fC4a91e9081F7e87DB")); + assert_eq!(tx.placeholder, Some(())); + assert_eq!(tx.input, bytes!("ef16e8450000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000007b399987d24fc5951f3e94a4cb16e87414bf22290000000000000000000000001670090000000000000000000000000000010001302e31382e302d64657600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000009e640a6aadf4f664cf467b795c31332f44acbe6c000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006614c2d1000000000000000000000000000000000000000000000000000000000014012c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000093100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000093100000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041f06fd78f4dcdf089263524731620941747b9b93fd8f631557e25b23845a78b685bd82f9d36bce2f4cc812b6e5191df52479d349089461ffe76e9f2fa2848a0fe1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410819f04aba17677807c61ae72afdddf7737f26931ecfa8af05b7c669808b36a2587e32c90bb0ed2100266dd7797c80121a109a2b0fe941ca5a580e438988cac81c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); + } +} diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index c441a3277..fb5abf08c 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -77,7 +77,7 @@ pub const MIN_LENGTH_EIP2930_TX_ENCODED: usize = 14; /// Minimum length of a rlp-encoded eip1559 transaction. pub const MIN_LENGTH_EIP1559_TX_ENCODED: usize = 15; /// Minimum length of a rlp-encoded eip4844 transaction. -pub const MIN_LENGTH_EIP4844_TX_ENCODED: usize = 17; +pub const MIN_LENGTH_EIP4844_TX_ENCODED: usize = 37; /// Minimum length of a rlp-encoded deposit transaction. #[cfg(feature = "optimism")] pub const MIN_LENGTH_DEPOSIT_TX_ENCODED: usize = 65; @@ -174,14 +174,14 @@ impl Transaction { /// Gets the transaction's [`TxKind`], which is the address of the recipient or /// [`TxKind::Create`] if the transaction is a contract creation. - pub fn kind(&self) -> &TxKind { + pub fn kind(&self) -> TxKind { match self { Transaction::Legacy(TxLegacy { to, .. }) | Transaction::Eip2930(TxEip2930 { to, .. }) | - Transaction::Eip1559(TxEip1559 { to, .. }) | - Transaction::Eip4844(TxEip4844 { to, .. }) => to, + Transaction::Eip1559(TxEip1559 { to, .. }) => *to, + Transaction::Eip4844(TxEip4844 { to, .. }) => TxKind::Call(*to), #[cfg(feature = "optimism")] - Transaction::Deposit(TxDeposit { to, .. }) => to, + Transaction::Deposit(TxDeposit { to, .. }) => *to, } } @@ -1462,6 +1462,11 @@ impl proptest::arbitrary::Arbitrary for TransactionSigned { .then(Signature::optimism_deposit_tx_signature) .unwrap_or(sig); + if let Transaction::Eip4844(ref mut tx_eip_4844) = transaction { + tx_eip_4844.placeholder = + if tx_eip_4844.to != Address::default() { Some(()) } else { None }; + } + let mut tx = TransactionSigned { hash: Default::default(), signature: sig, transaction }; tx.hash = tx.recalculate_hash(); @@ -2035,7 +2040,10 @@ mod tests { ); let encoded = alloy_rlp::encode(signed_tx); - assert_eq!(hex!("9003ce8080808080808080c080c0808080"), encoded[..]); + assert_eq!( + hex!("a403e280808080809400000000000000000000000000000000000000008080c080c0808080"), + encoded[..] + ); assert_eq!(MIN_LENGTH_EIP4844_TX_ENCODED, encoded.len()); TransactionSigned::decode(&mut &encoded[..]).unwrap(); @@ -2150,4 +2158,13 @@ mod tests { let written_bytes = tx_signed_no_hash.to_compact(&mut buff); from_compact_zstd_unaware(&buff, written_bytes); } + + #[test] + fn create_txs_disallowed_for_eip4844() { + let data = + [3, 208, 128, 128, 123, 128, 120, 128, 129, 129, 128, 192, 129, 129, 192, 128, 128, 9]; + let res = TransactionSigned::decode_enveloped(&mut &data[..]); + + assert!(res.is_err()); + } } diff --git a/crates/rpc/rpc-types-compat/src/transaction/mod.rs b/crates/rpc/rpc-types-compat/src/transaction/mod.rs index 6a35429c5..bb945ce8f 100644 --- a/crates/rpc/rpc-types-compat/src/transaction/mod.rs +++ b/crates/rpc/rpc-types-compat/src/transaction/mod.rs @@ -1,7 +1,7 @@ //! Compatibility functions for rpc `Transaction` type. use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; -use reth_primitives::{BlockNumber, TransactionSignedEcRecovered, TxKind, TxType, B256}; +use reth_primitives::{Address, BlockNumber, TransactionSignedEcRecovered, TxKind, TxType, B256}; use reth_rpc_types::Transaction; use signature::from_primitive_signature; pub use typed::*; @@ -42,9 +42,9 @@ fn fill( let signer = tx.signer(); let signed_tx = tx.into_signed(); - let to = match signed_tx.kind() { + let to: Option
= match signed_tx.kind() { TxKind::Create => None, - TxKind::Call(to) => Some(*to), + TxKind::Call(to) => Some(Address(*to)), }; #[allow(unreachable_patterns)] diff --git a/crates/rpc/rpc-types-compat/src/transaction/typed.rs b/crates/rpc/rpc-types-compat/src/transaction/typed.rs index b119a0956..21e492218 100644 --- a/crates/rpc/rpc-types-compat/src/transaction/typed.rs +++ b/crates/rpc/rpc-types-compat/src/transaction/typed.rs @@ -47,7 +47,8 @@ pub fn to_primitive_transaction( gas_limit: tx.gas_limit.to(), max_fee_per_gas: tx.max_fee_per_gas.to(), max_priority_fee_per_gas: tx.max_priority_fee_per_gas.to(), - to: tx.kind, + placeholder: Some(()), + to: tx.to, value: tx.value, access_list: tx.access_list, blob_versioned_hashes: tx.blob_versioned_hashes, diff --git a/crates/rpc/rpc-types/src/eth/transaction/typed.rs b/crates/rpc/rpc-types/src/eth/transaction/typed.rs index 6526bc2b6..e3d70d354 100644 --- a/crates/rpc/rpc-types/src/eth/transaction/typed.rs +++ b/crates/rpc/rpc-types/src/eth/transaction/typed.rs @@ -2,7 +2,7 @@ //! transaction deserialized from the json input of an RPC call. Depending on what fields are set, //! it can be converted into the container type [`TypedTransactionRequest`]. -use alloy_primitives::{Bytes, TxKind, B256, U256}; +use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; use alloy_rpc_types::{AccessList, BlobTransactionSidecar}; /// Container type for various Ethereum transaction requests @@ -100,8 +100,8 @@ pub struct EIP4844TransactionRequest { pub max_fee_per_gas: U256, /// The gas limit for the transaction pub gas_limit: U256, - /// The kind of transaction (e.g., Call, Create) - pub kind: TxKind, + /// The recipient of the transaction + pub to: Address, /// The value of the transaction pub value: U256, /// The input data for the transaction diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 721cef3db..dc01dc12c 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -979,7 +979,11 @@ where gas_limit: U256::from(gas.unwrap_or_default()), value: value.unwrap_or_default(), input: data.into_input().unwrap_or_default(), - kind: to.unwrap_or(RpcTransactionKind::Create), + #[allow(clippy::manual_unwrap_or_default)] // clippy is suggesting here unwrap_or_default + to: match to { + Some(RpcTransactionKind::Call(to)) => to, + _ => Address::default(), + }, access_list: access_list.unwrap_or_default(), // eip-4844 specific. @@ -1802,7 +1806,7 @@ pub(crate) fn build_transaction_receipt_with_block_receipts( res_receipt.contract_address = Some(from.create(transaction.transaction.nonce())); } Call(addr) => { - res_receipt.to = Some(*addr); + res_receipt.to = Some(Address(*addr)); } } diff --git a/crates/storage/codecs/derive/src/compact/mod.rs b/crates/storage/codecs/derive/src/compact/mod.rs index e67adb6fd..d4732256e 100644 --- a/crates/storage/codecs/derive/src/compact/mod.rs +++ b/crates/storage/codecs/derive/src/compact/mod.rs @@ -145,8 +145,16 @@ fn should_use_alt_impl(ftype: &String, segment: &syn::PathSegment) -> bool { if let (Some(path), 1) = (arg_path.path.segments.first(), arg_path.path.segments.len()) { - if ["B256", "Address", "Address", "Bloom", "TxHash", "BlockHash"] - .contains(&path.ident.to_string().as_str()) + if [ + "B256", + "Address", + "Address", + "Bloom", + "TxHash", + "BlockHash", + "CompactPlaceholder", + ] + .contains(&path.ident.to_string().as_str()) { return true } diff --git a/crates/storage/codecs/src/lib.rs b/crates/storage/codecs/src/lib.rs index 907fee440..9dcef1273 100644 --- a/crates/storage/codecs/src/lib.rs +++ b/crates/storage/codecs/src/lib.rs @@ -70,6 +70,24 @@ pub trait Compact: Sized { } } +/// To be used with `Option` to place or replace one bit on the bitflag struct. +pub type CompactPlaceholder = (); + +impl Compact for CompactPlaceholder { + #[inline] + fn to_compact(self, _: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + 0 + } + + #[inline] + fn from_compact(buf: &[u8], _: usize) -> (Self, &[u8]) { + ((), buf) + } +} + macro_rules! impl_uint_compact { ($($name:tt),+) => { $( diff --git a/crates/transaction-pool/src/test_utils/gen.rs b/crates/transaction-pool/src/test_utils/gen.rs index 5c335e5d6..e5c99ec03 100644 --- a/crates/transaction-pool/src/test_utils/gen.rs +++ b/crates/transaction-pool/src/test_utils/gen.rs @@ -184,7 +184,11 @@ impl TransactionBuilder { gas_limit: self.gas_limit, max_fee_per_gas: self.max_fee_per_gas, max_priority_fee_per_gas: self.max_priority_fee_per_gas, - to: self.to, + placeholder: None, + to: match self.to { + TxKind::Call(to) => to, + TxKind::Create => Address::default(), + }, value: self.value, access_list: self.access_list, input: self.input, diff --git a/crates/transaction-pool/src/test_utils/mock.rs b/crates/transaction-pool/src/test_utils/mock.rs index 948c47109..dded89a0e 100644 --- a/crates/transaction-pool/src/test_utils/mock.rs +++ b/crates/transaction-pool/src/test_utils/mock.rs @@ -186,8 +186,10 @@ pub enum MockTransaction { max_fee_per_blob_gas: u128, /// The gas limit for the transaction. gas_limit: u64, + /// Placeholder for backwards compatibility. + placeholder: Option<()>, /// The transaction's destination. - to: TxKind, + to: Address, /// The value of the transaction. value: U256, /// The access list associated with the transaction. @@ -276,7 +278,8 @@ impl MockTransaction { max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128, max_fee_per_blob_gas: DATA_GAS_PER_BLOB as u128, gas_limit: 0, - to: Address::random().into(), + placeholder: Some(()), + to: Address::random(), value: Default::default(), input: Bytes::new(), access_list: Default::default(), @@ -684,12 +687,12 @@ impl PoolTransaction for MockTransaction { } /// Returns the transaction kind associated with the transaction. - fn kind(&self) -> &TxKind { + fn kind(&self) -> TxKind { match self { MockTransaction::Legacy { to, .. } | MockTransaction::Eip1559 { to, .. } | - MockTransaction::Eip4844 { to, .. } | - MockTransaction::Eip2930 { to, .. } => to, + MockTransaction::Eip2930 { to, .. } => *to, + MockTransaction::Eip4844 { to, .. } => TxKind::Call(*to), } } @@ -851,6 +854,7 @@ impl TryFromRecoveredTransaction for MockTransaction { gas_limit, max_fee_per_gas, max_priority_fee_per_gas, + placeholder, to, value, input, @@ -866,6 +870,7 @@ impl TryFromRecoveredTransaction for MockTransaction { max_priority_fee_per_gas, max_fee_per_blob_gas, gas_limit, + placeholder, to, value, input, @@ -977,12 +982,14 @@ impl From for Transaction { input, sidecar, size: _, + placeholder, } => Self::Eip4844(TxEip4844 { chain_id, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas, + placeholder, to, value, access_list, @@ -1081,6 +1088,7 @@ impl proptest::arbitrary::Arbitrary for MockTransaction { max_fee_per_blob_gas, access_list, blob_versioned_hashes: _, + placeholder, }) => MockTransaction::Eip4844 { chain_id: *chain_id, sender, @@ -1090,6 +1098,7 @@ impl proptest::arbitrary::Arbitrary for MockTransaction { max_priority_fee_per_gas: *max_priority_fee_per_gas, max_fee_per_blob_gas: *max_fee_per_blob_gas, gas_limit: *gas_limit, + placeholder: *placeholder, to: *to, value: *value, input: input.clone(), diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index ca91b00da..fba43e899 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -813,12 +813,12 @@ pub trait PoolTransaction: /// Returns the transaction's [`TxKind`], which is the address of the recipient or /// [`TxKind::Create`] if the transaction is a contract creation. - fn kind(&self) -> &TxKind; + fn kind(&self) -> TxKind; /// Returns the recipient of the transaction if it is not a [TxKind::Create] /// transaction. fn to(&self) -> Option
{ - (*self.kind()).to().copied() + self.kind().to().copied() } /// Returns the input data of this transaction. @@ -1063,7 +1063,7 @@ impl PoolTransaction for EthPooledTransaction { /// Returns the transaction's [`TxKind`], which is the address of the recipient or /// [`TxKind::Create`] if the transaction is a contract creation. - fn kind(&self) -> &TxKind { + fn kind(&self) -> TxKind { self.transaction.kind() } diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 5c0a2db5a..057154d2f 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -708,7 +708,7 @@ pub fn ensure_intrinsic_gas( if transaction.gas_limit() < calculate_intrinsic_gas_after_merge( transaction.input(), - transaction.kind(), + &transaction.kind(), &access_list, is_shanghai, )