From 2290e8b464946243aef0466f8de82a67d8f86536 Mon Sep 17 00:00:00 2001 From: Wolfgang Welz Date: Sat, 18 Nov 2023 10:24:53 +0100 Subject: [PATCH] cleanup transaction code (#53) --- primitives/src/ethers.rs | 2 +- primitives/src/lib.rs | 1 - primitives/src/transactions/ethereum.rs | 321 ++++++++++----- primitives/src/transactions/mod.rs | 365 +++++------------- primitives/src/transactions/optimism.rs | 178 ++++++--- .../src/{ => transactions}/signature.rs | 4 +- testing/ef-tests/src/lib.rs | 3 +- 7 files changed, 448 insertions(+), 426 deletions(-) rename primitives/src/{ => transactions}/signature.rs (92%) diff --git a/primitives/src/ethers.rs b/primitives/src/ethers.rs index 648170d5..298ee0ae 100644 --- a/primitives/src/ethers.rs +++ b/primitives/src/ethers.rs @@ -27,12 +27,12 @@ use ethers_core::types::{ use crate::{ access_list::{AccessList, AccessListItem}, block::Header, - signature::TxSignature, transactions::{ ethereum::{ EthereumTxEssence, TransactionKind, TxEssenceEip1559, TxEssenceEip2930, TxEssenceLegacy, }, optimism::{OptimismTxEssence, TxEssenceOptimismDeposited}, + signature::TxSignature, Transaction, TxEssence, }, withdrawal::Withdrawal, diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 0675a590..49ad2de3 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -18,7 +18,6 @@ pub mod access_list; pub mod block; pub mod keccak; pub mod receipt; -pub mod signature; pub mod transactions; pub mod trie; pub mod withdrawal; diff --git a/primitives/src/transactions/ethereum.rs b/primitives/src/transactions/ethereum.rs index 73bc7efa..49cbf994 100644 --- a/primitives/src/transactions/ethereum.rs +++ b/primitives/src/transactions/ethereum.rs @@ -16,7 +16,6 @@ use alloy_primitives::{Address, Bytes, ChainId, TxNumber, B256, U256}; use alloy_rlp::{Encodable, EMPTY_STRING_CODE}; use alloy_rlp_derive::RlpEncodable; use anyhow::Context; -use bytes::BufMut; use k256::{ ecdsa::{RecoveryId, Signature as K256Signature, VerifyingKey as K256VerifyingKey}, elliptic_curve::sec1::ToEncodedPoint, @@ -24,12 +23,8 @@ use k256::{ }; use serde::{Deserialize, Serialize}; -use crate::{ - access_list::AccessList, - keccak::keccak, - signature::TxSignature, - transactions::{Transaction, TxEssence}, -}; +use super::signature::TxSignature; +use crate::{access_list::AccessList, keccak::keccak, transactions::TxEssence}; /// Represents a legacy Ethereum transaction as detailed in [EIP-155](https://eips.ethereum.org/EIPS/eip-155). /// @@ -49,8 +44,8 @@ pub struct TxEssenceLegacy { pub gas_price: U256, /// The maximum amount of gas allocated for the transaction's execution. pub gas_limit: U256, - /// The 160-bit address of the intended recipient for a message call. For contract - /// creation transactions, this is null. + /// The 160-bit address of the intended recipient for a message call or + /// [TransactionKind::Create] for contract creation. pub to: TransactionKind, /// The amount, in Wei, to be transferred to the recipient of the message call. pub value: U256, @@ -75,11 +70,12 @@ impl TxEssenceLegacy { /// Encodes the transaction essence into the provided `out` buffer for the purpose of /// signing. /// - /// The method follows the RLP encoding scheme. If a `chain_id` is present, - /// the encoding adheres to the specifications set out in [EIP-155](https://eips.ethereum.org/EIPS/eip-155). + /// According to EIP-155, if `chain_id` is present, `(chain_id, 0, 0)` must be + /// appended to the regular RLP encoding when computing the hash of a transaction for + /// the purposes of signing. pub fn signing_encode(&self, out: &mut dyn alloy_rlp::BufMut) { let mut payload_length = self.payload_length(); - // Append chain ID according to EIP-155 if present + // append chain ID according to EIP-155 if present if let Some(chain_id) = self.chain_id { payload_length += chain_id.length() + 1 + 1; } @@ -108,11 +104,11 @@ impl TxEssenceLegacy { /// including any additional bytes required for the encoding format. pub fn signing_length(&self) -> usize { let mut payload_length = self.payload_length(); - // Append chain ID according to EIP-155 if present + // append chain ID according to EIP-155 if present if let Some(chain_id) = self.chain_id { payload_length += chain_id.length() + 1 + 1; } - alloy_rlp::length_of_length(payload_length) + payload_length + payload_length + alloy_rlp::length_of_length(payload_length) } } @@ -123,6 +119,7 @@ impl Encodable for TxEssenceLegacy { /// /// This method follows the RLP encoding scheme, but intentionally omits the /// `chain_id` to ensure compatibility with legacy transactions. + #[inline] fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { alloy_rlp::Header { list: true, @@ -141,9 +138,10 @@ impl Encodable for TxEssenceLegacy { /// /// This method calculates the total length of the transaction when it is RLP-encoded, /// excluding the `chain_id`. + #[inline] fn length(&self) -> usize { let payload_length = self.payload_length(); - alloy_rlp::length_of_length(payload_length) + payload_length + payload_length + alloy_rlp::length_of_length(payload_length) } } @@ -282,9 +280,8 @@ impl Encodable for TransactionKind { /// Represents the core essence of an Ethereum transaction, specifically the portion that /// gets signed. /// -/// The `TxEssence` enum provides a way to handle different types of Ethereum -/// transactions, from legacy transactions to more recent types introduced by various -/// Ethereum Improvement Proposals (EIPs). +/// The [EthereumTxEssence] enum provides a way to handle different types of Ethereum +/// transactions. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum EthereumTxEssence { /// Represents a legacy Ethereum transaction, which follows the original transaction @@ -300,14 +297,13 @@ pub enum EthereumTxEssence { Eip1559(TxEssenceEip1559), } -// Implement the Encodable trait for the TxEssence enum. -// Ensures that each variant of the `TxEssence` enum can be RLP-encoded. impl Encodable for EthereumTxEssence { /// Encodes the [EthereumTxEssence] enum variant into the provided `out` buffer. /// /// Depending on the variant of the [EthereumTxEssence] enum, this method will /// delegate the encoding process to the appropriate transaction type's encoding /// method. + #[inline] fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { match self { EthereumTxEssence::Legacy(tx) => tx.encode(out), @@ -321,6 +317,7 @@ impl Encodable for EthereumTxEssence { /// Depending on the variant of the [EthereumTxEssence] enum, this method will /// delegate the length computation to the appropriate transaction type's length /// method. + #[inline] fn length(&self) -> usize { match self { EthereumTxEssence::Legacy(tx) => tx.length(), @@ -366,11 +363,10 @@ impl EthereumTxEssence { } } - /// Determines whether the y-coordinate of the ECDSA signature's associated public key - /// is odd. + /// Returns the parity of the y-value of the curve point for which `signature.r` is + /// the x-value. This is encoded in the `v` field of the signature. /// - /// This information is derived from the `v` component of the signature and is used - /// during public key recovery. + /// It returns `None` if the parity cannot be determined. fn is_y_odd(&self, signature: &TxSignature) -> Option { match self { EthereumTxEssence::Legacy(TxEssenceLegacy { chain_id: None, .. }) => { @@ -386,13 +382,7 @@ impl EthereumTxEssence { } /// Converts a given value into a boolean based on its parity. -/// -/// Returns: -/// - `Some(true)` if the value is 1. -/// - `Some(false)` if the value is 0. -/// - `None` otherwise. -#[inline] -pub fn checked_bool(v: u64) -> Option { +fn checked_bool(v: u64) -> Option { match v { 0 => Some(false), 1 => Some(true), @@ -401,12 +391,7 @@ pub fn checked_bool(v: u64) -> Option { } impl TxEssence for EthereumTxEssence { - /// Determines the type of the transaction based on its essence. - /// - /// Returns a byte representing the transaction type: - /// - `0x00` for Legacy transactions. - /// - `0x01` for EIP-2930 transactions. - /// - `0x02` for EIP-1559 transactions. + /// Returns the EIP-2718 transaction type or `0x00` for Legacy transactions. fn tx_type(&self) -> u8 { match self { EthereumTxEssence::Legacy(_) => 0x00, @@ -414,10 +399,7 @@ impl TxEssence for EthereumTxEssence { EthereumTxEssence::Eip1559(_) => 0x02, } } - /// Retrieves the gas limit set for the transaction. - /// - /// The gas limit represents the maximum amount of gas units that the transaction - /// is allowed to consume. It ensures that transactions don't run indefinitely. + /// Returns the gas limit set for the transaction. fn gas_limit(&self) -> U256 { match self { EthereumTxEssence::Legacy(tx) => tx.gas_limit, @@ -425,10 +407,7 @@ impl TxEssence for EthereumTxEssence { EthereumTxEssence::Eip1559(tx) => tx.gas_limit, } } - /// Retrieves the recipient address of the transaction, if available. - /// - /// For contract creation transactions, this method returns `None` as there's no - /// recipient address. + /// Returns the recipient address of the transaction, if available. fn to(&self) -> Option
{ match self { EthereumTxEssence::Legacy(tx) => tx.to.into(), @@ -437,10 +416,6 @@ impl TxEssence for EthereumTxEssence { } } /// Recovers the Ethereum address of the sender from the transaction's signature. - /// - /// This method uses the ECDSA recovery mechanism to derive the sender's public key - /// and subsequently their Ethereum address. If the recovery is unsuccessful, an - /// error is returned. fn recover_from(&self, signature: &TxSignature) -> anyhow::Result
{ let is_y_odd = self.is_y_odd(signature).context("v invalid")?; let signature = @@ -462,11 +437,7 @@ impl TxEssence for EthereumTxEssence { Ok(Address::from_slice(&hash[12..])) } - /// Computes the length of the RLP-encoded payload in bytes for the transaction - /// essence. - /// - /// This method calculates the length of the transaction data when it is RLP-encoded, - /// which is used for serialization and deserialization in the Ethereum network. + /// Returns the length of the RLP-encoding payload in bytes. fn payload_length(&self) -> usize { match self { EthereumTxEssence::Legacy(tx) => tx.payload_length(), @@ -474,61 +445,207 @@ impl TxEssence for EthereumTxEssence { EthereumTxEssence::Eip1559(tx) => tx._alloy_rlp_payload_length(), } } +} - fn encode_with_signature(&self, signature: &TxSignature, out: &mut dyn BufMut) { - // join the essence lists and the signature list into one - rlp_join_lists(self, signature, out); - } +#[cfg(test)] +mod tests { + use alloy_primitives::{address, b256}; + use serde_json::json; - #[inline] - fn length(transaction: &Transaction) -> usize { - let payload_length = - transaction.essence.payload_length() + transaction.signature.payload_length(); - let mut length = payload_length + alloy_rlp::length_of_length(payload_length); - if transaction.essence.tx_type() != 0 { - length += 1; - } - length + use super::*; + use crate::transactions::EthereumTransaction; + + #[test] + fn legacy() { + // Tx: 0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060 + let tx = json!({ + "Legacy": { + "nonce": 0, + "gas_price": "0x2d79883d2000", + "gas_limit": "0x5208", + "to": { "Call": "0x5df9b87991262f6ba471f09758cde1c0fc1de734" }, + "value": "0x7a69", + "data": "0x" + } + }); + let essence: EthereumTxEssence = serde_json::from_value(tx).unwrap(); + + let signature: TxSignature = serde_json::from_value(json!({ + "v": 28, + "r": "0x88ff6cf0fefd94db46111149ae4bfc179e9b94721fffd821d38d16464b3f71d0", + "s": "0x45e0aff800961cfce805daef7016b9b675c137a6a41a548f7b60a3484c06a33a" + })) + .unwrap(); + let transaction = EthereumTransaction { essence, signature }; + + // verify that bincode serialization works + let _: EthereumTransaction = + bincode::deserialize(&bincode::serialize(&transaction).unwrap()).unwrap(); + + assert_eq!( + transaction.hash(), + b256!("5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060") + ); + let recovered = transaction.recover_from().unwrap(); + assert_eq!( + recovered, + address!("a1e4380a3b1f749673e270229993ee55f35663b4") + ); } -} -/// Joins two RLP-encoded lists into a single RLP-encoded list. -/// -/// This function takes two RLP-encoded lists, decodes their headers to ensure they are -/// valid lists, and then combines their payloads into a single RLP-encoded list. The -/// resulting list is written to the provided `out` buffer. -/// -/// # Arguments -/// -/// * `a` - The first RLP-encoded list to be joined. -/// * `b` - The second RLP-encoded list to be joined. -/// * `out` - The buffer where the resulting RLP-encoded list will be written. -/// -/// # Panics -/// -/// This function will panic if either `a` or `b` are not valid RLP-encoded lists. -fn rlp_join_lists(a: impl Encodable, b: impl Encodable, out: &mut dyn alloy_rlp::BufMut) { - let a_buf = alloy_rlp::encode(a); - let header = alloy_rlp::Header::decode(&mut &a_buf[..]).unwrap(); - if !header.list { - panic!("`a` not a list"); + #[test] + fn eip155() { + // Tx: 0x4540eb9c46b1654c26353ac3c65e56451f711926982ce1b02f15c50e7459caf7 + let tx = json!({ + "Legacy": { + "nonce": 537760, + "gas_price": "0x03c49bfa04", + "gas_limit": "0x019a28", + "to": { "Call": "0xf0ee707731d1be239f9f482e1b2ea5384c0c426f" }, + "value": "0x06df842eaa9fb800", + "data": "0x", + "chain_id": 1 + } + }); + let essence: EthereumTxEssence = serde_json::from_value(tx).unwrap(); + + let signature: TxSignature = serde_json::from_value(json!({ + "v": 38, + "r": "0xcadd790a37b78e5613c8cf44dc3002e3d7f06a5325d045963c708efe3f9fdf7a", + "s": "0x1f63adb9a2d5e020c6aa0ff64695e25d7d9a780ed8471abe716d2dc0bf7d4259" + })) + .unwrap(); + let transaction = EthereumTransaction { essence, signature }; + + // verify that bincode serialization works + let _: EthereumTransaction = + bincode::deserialize(&bincode::serialize(&transaction).unwrap()).unwrap(); + + assert_eq!( + transaction.hash(), + b256!("4540eb9c46b1654c26353ac3c65e56451f711926982ce1b02f15c50e7459caf7") + ); + let recovered = transaction.recover_from().unwrap(); + assert_eq!( + recovered, + address!("974caa59e49682cda0ad2bbe82983419a2ecc400") + ); } - let a_head_length = header.length(); - let a_payload_length = a_buf.len() - a_head_length; - let b_buf = alloy_rlp::encode(b); - let header = alloy_rlp::Header::decode(&mut &b_buf[..]).unwrap(); - if !header.list { - panic!("`b` not a list"); + #[test] + fn eip2930() { + // Tx: 0xbe4ef1a2244e99b1ef518aec10763b61360be22e3b649dcdf804103719b1faef + let tx = json!({ + "Eip2930": { + "chain_id": 1, + "nonce": 93847, + "gas_price": "0xf46a5a9d8", + "gas_limit": "0x21670", + "to": { "Call": "0xc11ce44147c9f6149fbe54adb0588523c38718d7" }, + "value": "0x10d1471", + "data": "0x050000000002b8809aef26206090eafd7d5688615d48197d1c5ce09be6c30a33be4c861dee44d13f6dd33c2e8c5cad7e2725f88a8f0000000002d67ca5eb0e5fb6", + "access_list": [ + { + "address": "0xd6e64961ba13ba42858ad8a74ed9a9b051a4957d", + "storage_keys": [ + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x0b4b38935f88a7bddbe6be76893de2a04640a55799d6160729a82349aff1ffae", + "0xc59ee2ee2ba599569b2b1f06989dadbec5ee157c8facfe64f36a3e33c2b9d1bf" + ] + }, + { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "storage_keys": [ + "0x7635825e4f8dfeb20367f8742c8aac958a66caa001d982b3a864dcc84167be80", + "0x42555691810bdf8f236c31de88d2cc9407a8ff86cd230ba3b7029254168df92a", + "0x29ece5a5f4f3e7751868475502ab752b5f5fa09010960779bf7204deb72f5dde" + ] + }, + { + "address": "0x4c861dee44d13f6dd33c2e8c5cad7e2725f88a8f", + "storage_keys": [ + "0x000000000000000000000000000000000000000000000000000000000000000c", + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007" + ] + }, + { + "address": "0x90eafd7d5688615d48197d1c5ce09be6c30a33be", + "storage_keys": [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x9c04773acff4c5c42718bd0120c72761f458e43068a3961eb935577d1ed4effb", + "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000004" + ] + } + ] + } + }); + let essence: EthereumTxEssence = serde_json::from_value(tx).unwrap(); + + let signature: TxSignature = serde_json::from_value(json!({ + "v": 1, + "r": "0xf86aa2dfde99b0d6a41741e96cfcdee0c6271febd63be4056911db19ae347e66", + "s": "0x601deefbc4835cb15aa1af84af6436fc692dea3428d53e7ff3d34a314cefe7fc" + })) + .unwrap(); + let transaction = EthereumTransaction { essence, signature }; + + // verify that bincode serialization works + let _: EthereumTransaction = + bincode::deserialize(&bincode::serialize(&transaction).unwrap()).unwrap(); + + assert_eq!( + transaction.hash(), + b256!("be4ef1a2244e99b1ef518aec10763b61360be22e3b649dcdf804103719b1faef") + ); + let recovered = transaction.recover_from().unwrap(); + assert_eq!( + recovered, + address!("79b7a69d90c82e014bf0315e164208119b510fa0") + ); } - let b_head_length = header.length(); - let b_payload_length = b_buf.len() - b_head_length; - alloy_rlp::Header { - list: true, - payload_length: a_payload_length + b_payload_length, + #[test] + fn eip1559() { + // Tx: 0x2bcdc03343ca9c050f8dfd3c87f32db718c762ae889f56762d8d8bdb7c5d69ff + let tx = json!({ + "Eip1559": { + "chain_id": 1, + "nonce": 32, + "max_priority_fee_per_gas": "0x3b9aca00", + "max_fee_per_gas": "0x89d5f3200", + "gas_limit": "0x5b04", + "to": { "Call": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43" }, + "value": "0x1dd1f234f68cde2", + "data": "0x", + "access_list": [] + } + }); + let essence: EthereumTxEssence = serde_json::from_value(tx).unwrap(); + + let signature: TxSignature = serde_json::from_value(json!({ + "v": 0, + "r": "0x2bdf47562da5f2a09f09cce70aed35ec9ac62f5377512b6a04cc427e0fda1f4d", + "s": "0x28f9311b515a5f17aa3ad5ea8bafaecfb0958801f01ca11fd593097b5087121b" + })) + .unwrap(); + let transaction = EthereumTransaction { essence, signature }; + + // verify that bincode serialization works + let _: EthereumTransaction = + bincode::deserialize(&bincode::serialize(&transaction).unwrap()).unwrap(); + + assert_eq!( + transaction.hash(), + b256!("2bcdc03343ca9c050f8dfd3c87f32db718c762ae889f56762d8d8bdb7c5d69ff") + ); + let recovered = transaction.recover_from().unwrap(); + assert_eq!( + recovered, + address!("4b9f4114d50e7907bff87728a060ce8d53bf4cf7") + ); } - .encode(out); - out.put_slice(&a_buf[a_head_length..]); // skip the header - out.put_slice(&b_buf[b_head_length..]); // skip the header } diff --git a/primitives/src/transactions/mod.rs b/primitives/src/transactions/mod.rs index 88fc4aa2..18c5565b 100644 --- a/primitives/src/transactions/mod.rs +++ b/primitives/src/transactions/mod.rs @@ -16,46 +16,45 @@ use alloy_primitives::{Address, TxHash}; use alloy_rlp::Encodable; use serde::{Deserialize, Serialize}; -use crate::{ - keccak::keccak, signature::TxSignature, transactions::ethereum::EthereumTxEssence, U256, +use self::{ + optimism::{OptimismTxEssence, OPTIMISM_DEPOSITED_TX_TYPE}, + signature::TxSignature, }; +use crate::{keccak::keccak, transactions::ethereum::EthereumTxEssence, U256}; pub mod ethereum; pub mod optimism; +pub mod signature; pub type EthereumTransaction = Transaction; +pub type OptimismTransaction = Transaction; -/// Represents a complete Ethereum transaction, encompassing its core essence and the -/// associated signature. +/// Represents a complete transaction, encompassing its core essence and the associated +/// signature. /// /// The `Transaction` struct encapsulates both the core details of the transaction (the /// essence) and its cryptographic signature. The signature ensures the authenticity and /// integrity of the transaction, confirming it was issued by the rightful sender. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Transaction { - /// The core details of the transaction, which include its type (e.g., legacy, - /// EIP-2930, EIP-1559) and associated data (e.g., recipient address, value, gas - /// details). + /// The core details of the transaction, which includes the data that is signed. pub essence: E, /// The cryptographic signature associated with the transaction, generated by signing /// the transaction essence. pub signature: TxSignature, } +/// Represents the core details of a [Transaction], specifically the portion that gets +/// signed. pub trait TxEssence: Encodable + Clone { - /// Determines the type of the transaction based on its essence. - /// - /// Returns a byte representing the transaction type: - /// - `0x00` for Legacy transactions. - /// - `0x01` for EIP-2930 transactions. - /// - `0x02` for EIP-1559 transactions. + /// Returns the EIP-2718 transaction type or `0x00` for Legacy transactions. fn tx_type(&self) -> u8; - /// Retrieves the gas limit set for the transaction. + /// Returns the gas limit set for the transaction. /// /// The gas limit represents the maximum amount of gas units that the transaction /// is allowed to consume. It ensures that transactions don't run indefinitely. fn gas_limit(&self) -> U256; - /// Retrieves the recipient address of the transaction, if available. + /// Returns the recipient address of the transaction, if available. /// /// For contract creation transactions, this method returns `None` as there's no /// recipient address. @@ -66,26 +65,14 @@ pub trait TxEssence: Encodable + Clone { /// and subsequently their Ethereum address. If the recovery is unsuccessful, an /// error is returned. fn recover_from(&self, signature: &TxSignature) -> anyhow::Result
; - /// Computes the length of the RLP-encoded payload in bytes. + /// Returns the length of the RLP-encoding payload in bytes. /// /// This method calculates the combined length of all the individual fields /// of the transaction when they are RLP-encoded. fn payload_length(&self) -> usize; - /// RLP encodes the transaction essence and signature into the provided `out` buffer. - fn encode_with_signature(&self, signature: &TxSignature, out: &mut dyn alloy_rlp::BufMut); - /// Computes the length of an encompassing RLP-encoded [Transaction] struct in bytes. - /// - /// The computed length includes the lengths of the encoded transaction essence and - /// signature. If the transaction type (as per EIP-2718) is not zero, an - /// additional byte is added to the length. - fn length(transaction: &Transaction) -> usize; } -/// Provides RLP encoding functionality for the [Transaction] struct. -/// -/// This implementation ensures that the entire transaction, including its essence and -/// signature, can be RLP-encoded. The encoding process also considers the EIP-2718 -/// transaction type. +/// Provides RLP encoding functionality for [Transaction]. impl Encodable for Transaction { /// Encodes the [Transaction] struct into the provided `out` buffer. /// @@ -95,13 +82,20 @@ impl Encodable for Transaction { /// reusing as much of the generated RLP code as possible. #[inline] fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - // prepend the EIP-2718 transaction type - match self.essence.tx_type() { - 0 => {} - tx_type => out.put_u8(tx_type), + let tx_type = self.essence.tx_type(); + // prepend the EIP-2718 transaction type for non-legacy transactions + if tx_type != 0 { + out.put_u8(tx_type); } - // encode according to essence type - self.essence.encode_with_signature(&self.signature, out); + if tx_type == OPTIMISM_DEPOSITED_TX_TYPE { + // optimism deposited transactions have no signature + self.essence.encode(out); + return; + } + + // join the essence lists and the signature list into one + // this allows to reuse as much of the generated RLP code as possible + rlp_join_lists(&self.essence, &self.signature, out); } /// Computes the length of the RLP-encoded [Transaction] struct in bytes. @@ -111,7 +105,20 @@ impl Encodable for Transaction { /// additional byte is added to the length. #[inline] fn length(&self) -> usize { - ::length(self) + let tx_type = self.essence.tx_type(); + let payload_length = if tx_type == OPTIMISM_DEPOSITED_TX_TYPE { + // optimism deposited transactions have no signature + self.essence.payload_length() + } else { + self.essence.payload_length() + self.signature.payload_length() + }; + + let mut length = payload_length + alloy_rlp::length_of_length(payload_length); + // add the EIP-2718 transaction type for non-legacy transactions + if tx_type != 0 { + length += 1; + } + length } } @@ -119,6 +126,7 @@ impl Transaction { /// Calculates the Keccak hash of the RLP-encoded transaction. /// /// This hash uniquely identifies the transaction on the Ethereum network. + #[inline] pub fn hash(&self) -> TxHash { keccak(alloy_rlp::encode(self)).into() } @@ -128,258 +136,83 @@ impl Transaction { /// This method uses the ECDSA recovery mechanism to derive the sender's public key /// and subsequently their Ethereum address. If the recovery is unsuccessful, an /// error is returned. + #[inline] pub fn recover_from(&self) -> anyhow::Result
{ self.essence.recover_from(&self.signature) } } +/// Joins two RLP-encoded lists into a single RLP-encoded list. +/// +/// This function takes two RLP-encoded lists, decodes their headers to ensure they are +/// valid lists, and then combines their payloads into a single RLP-encoded list. The +/// resulting list is written to the provided `out` buffer. +/// +/// # Panics +/// +/// This function will panic if either `a` or `b` are not valid RLP-encoded lists. +fn rlp_join_lists(a: impl Encodable, b: impl Encodable, out: &mut dyn alloy_rlp::BufMut) { + let a_buf = alloy_rlp::encode(a); + let header = alloy_rlp::Header::decode(&mut &a_buf[..]).unwrap(); + if !header.list { + panic!("`a` not a list"); + } + let a_head_length = header.length(); + let a_payload_length = a_buf.len() - a_head_length; + + let b_buf = alloy_rlp::encode(b); + let header = alloy_rlp::Header::decode(&mut &b_buf[..]).unwrap(); + if !header.list { + panic!("`b` not a list"); + } + let b_head_length = header.length(); + let b_payload_length = b_buf.len() - b_head_length; + + alloy_rlp::Header { + list: true, + payload_length: a_payload_length + b_payload_length, + } + .encode(out); + out.put_slice(&a_buf[a_head_length..]); // skip the header + out.put_slice(&b_buf[b_head_length..]); // skip the header +} + #[cfg(test)] mod tests { use serde_json::json; use super::*; + use crate::transactions::EthereumTransaction; #[test] - fn legacy() { - // Tx: 0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060 - let tx = json!({ - "Legacy": { - "nonce": 0, - "gas_price": "0x2d79883d2000", - "gas_limit": "0x5208", - "to": { "Call": "0x5df9b87991262f6ba471f09758cde1c0fc1de734" }, - "value": "0x7a69", - "data": "0x" - } - }); - let essence: EthereumTxEssence = serde_json::from_value(tx).unwrap(); - - let signature: TxSignature = serde_json::from_value(json!({ - "v": 28, - "r": "0x88ff6cf0fefd94db46111149ae4bfc179e9b94721fffd821d38d16464b3f71d0", - "s": "0x45e0aff800961cfce805daef7016b9b675c137a6a41a548f7b60a3484c06a33a" - })) - .unwrap(); - let transaction = EthereumTransaction { essence, signature }; - - // verify that bincode serialization works - let _: EthereumTransaction = - bincode::deserialize(&bincode::serialize(&transaction).unwrap()).unwrap(); - - assert_eq!( - "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060", - transaction.hash().to_string() - ); - let recovered = transaction.recover_from().unwrap(); - assert_eq!( - "0xa1e4380a3b1f749673e270229993ee55f35663b4".to_lowercase(), - recovered.to_string().to_lowercase() - ); - } - - #[test] - fn eip155() { - // Tx: 0x4540eb9c46b1654c26353ac3c65e56451f711926982ce1b02f15c50e7459caf7 + fn rlp_length() { let tx = json!({ - "Legacy": { - "nonce": 537760, - "gas_price": "0x03c49bfa04", - "gas_limit": "0x019a28", - "to": { "Call": "0xf0ee707731d1be239f9f482e1b2ea5384c0c426f" }, - "value": "0x06df842eaa9fb800", - "data": "0x", - "chain_id": 1 - } - }); - let essence: EthereumTxEssence = serde_json::from_value(tx).unwrap(); - - let signature: TxSignature = serde_json::from_value(json!({ + "essence": { + "Legacy": { + "nonce": 537760, + "gas_price": "0x03c49bfa04", + "gas_limit": "0x019a28", + "to": { "Call": "0xf0ee707731d1be239f9f482e1b2ea5384c0c426f" }, + "value": "0x06df842eaa9fb800", + "data": "0x", + "chain_id": 1 + } + }, + "signature": { "v": 38, "r": "0xcadd790a37b78e5613c8cf44dc3002e3d7f06a5325d045963c708efe3f9fdf7a", "s": "0x1f63adb9a2d5e020c6aa0ff64695e25d7d9a780ed8471abe716d2dc0bf7d4259" - })) - .unwrap(); - let transaction = EthereumTransaction { essence, signature }; - - // verify that bincode serialization works - let _: EthereumTransaction = - bincode::deserialize(&bincode::serialize(&transaction).unwrap()).unwrap(); - - assert_eq!( - "0x4540eb9c46b1654c26353ac3c65e56451f711926982ce1b02f15c50e7459caf7", - transaction.hash().to_string() - ); - let recovered = transaction.recover_from().unwrap(); - assert_eq!( - "0x974caa59e49682cda0ad2bbe82983419a2ecc400".to_lowercase(), - recovered.to_string().to_lowercase() - ); - } - - #[test] - fn eip2930() { - // Tx: 0xbe4ef1a2244e99b1ef518aec10763b61360be22e3b649dcdf804103719b1faef - let tx = json!({ - "Eip2930": { - "chain_id": 1, - "nonce": 93847, - "gas_price": "0xf46a5a9d8", - "gas_limit": "0x21670", - "to": { "Call": "0xc11ce44147c9f6149fbe54adb0588523c38718d7" }, - "value": "0x10d1471", - "data": "0x050000000002b8809aef26206090eafd7d5688615d48197d1c5ce09be6c30a33be4c861dee44d13f6dd33c2e8c5cad7e2725f88a8f0000000002d67ca5eb0e5fb6", - "access_list": [ - { - "address": "0xd6e64961ba13ba42858ad8a74ed9a9b051a4957d", - "storage_keys": [ - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x0b4b38935f88a7bddbe6be76893de2a04640a55799d6160729a82349aff1ffae", - "0xc59ee2ee2ba599569b2b1f06989dadbec5ee157c8facfe64f36a3e33c2b9d1bf" - ] - }, - { - "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "storage_keys": [ - "0x7635825e4f8dfeb20367f8742c8aac958a66caa001d982b3a864dcc84167be80", - "0x42555691810bdf8f236c31de88d2cc9407a8ff86cd230ba3b7029254168df92a", - "0x29ece5a5f4f3e7751868475502ab752b5f5fa09010960779bf7204deb72f5dde" - ] - }, - { - "address": "0x4c861dee44d13f6dd33c2e8c5cad7e2725f88a8f", - "storage_keys": [ - "0x000000000000000000000000000000000000000000000000000000000000000c", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x0000000000000000000000000000000000000000000000000000000000000006", - "0x0000000000000000000000000000000000000000000000000000000000000007" - ] - }, - { - "address": "0x90eafd7d5688615d48197d1c5ce09be6c30a33be", - "storage_keys": [ - "0x0000000000000000000000000000000000000000000000000000000000000001", - "0x9c04773acff4c5c42718bd0120c72761f458e43068a3961eb935577d1ed4effb", - "0x0000000000000000000000000000000000000000000000000000000000000008", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000000000000000000000000000000000000000000004" - ] - } - ] } }); - let essence: EthereumTxEssence = serde_json::from_value(tx).unwrap(); + let transaction: EthereumTransaction = serde_json::from_value(tx).unwrap(); - let signature: TxSignature = serde_json::from_value(json!({ - "v": 1, - "r": "0xf86aa2dfde99b0d6a41741e96cfcdee0c6271febd63be4056911db19ae347e66", - "s": "0x601deefbc4835cb15aa1af84af6436fc692dea3428d53e7ff3d34a314cefe7fc" - })) - .unwrap(); - let transaction = EthereumTransaction { essence, signature }; + let encoded = alloy_rlp::encode(&transaction.essence); + assert_eq!(encoded.len(), transaction.essence.length()); - // verify that bincode serialization works - let _: EthereumTransaction = - bincode::deserialize(&bincode::serialize(&transaction).unwrap()).unwrap(); - - assert_eq!( - "0xbe4ef1a2244e99b1ef518aec10763b61360be22e3b649dcdf804103719b1faef", - transaction.hash().to_string() - ); - let recovered = transaction.recover_from().unwrap(); - assert_eq!( - "0x79b7a69d90c82e014bf0315e164208119b510fa0".to_lowercase(), - recovered.to_string().to_lowercase() - ); - } - - #[test] - fn eip1559() { - // Tx: 0x2bcdc03343ca9c050f8dfd3c87f32db718c762ae889f56762d8d8bdb7c5d69ff - let tx = json!({ - "Eip1559": { - "chain_id": 1, - "nonce": 32, - "max_priority_fee_per_gas": "0x3b9aca00", - "max_fee_per_gas": "0x89d5f3200", - "gas_limit": "0x5b04", - "to": { "Call": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43" }, - "value": "0x1dd1f234f68cde2", - "data": "0x", - "access_list": [] - } - }); - let essence: EthereumTxEssence = serde_json::from_value(tx).unwrap(); - - let signature: TxSignature = serde_json::from_value(json!({ - "v": 0, - "r": "0x2bdf47562da5f2a09f09cce70aed35ec9ac62f5377512b6a04cc427e0fda1f4d", - "s": "0x28f9311b515a5f17aa3ad5ea8bafaecfb0958801f01ca11fd593097b5087121b" - })) - .unwrap(); - let transaction = EthereumTransaction { essence, signature }; - - // verify that bincode serialization works - let _: EthereumTransaction = - bincode::deserialize(&bincode::serialize(&transaction).unwrap()).unwrap(); - - assert_eq!( - "0x2bcdc03343ca9c050f8dfd3c87f32db718c762ae889f56762d8d8bdb7c5d69ff", - transaction.hash().to_string() - ); - let recovered = transaction.recover_from().unwrap(); - assert_eq!( - "0x4b9f4114d50e7907bff87728a060ce8d53bf4cf7".to_lowercase(), - recovered.to_string().to_lowercase() - ); - } - - #[test] - fn rlp() { - // Tx: 0x275631a3549307b2e8c93b18dfcc0fe8aedf0276bb650c28eaa0a8a011d18867 - let tx = json!({ - "Eip1559": { - "chain_id": 1, - "nonce": 267, - "max_priority_fee_per_gas": "0x05f5e100", - "max_fee_per_gas": "0x0cb2bf61c2", - "gas_limit": "0x0278be", - "to": { "Call": "0x00005ea00ac477b1030ce78506496e8c2de24bf5" }, - "value": "0x01351609ff758000", - "data": "0x161ac21f0000000000000000000000007de6f03b8b50b835f706e51a40b3224465802ddc0000000000000000000000000000a26b00c1f0df003000390027140000faa71900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003360c6ebe", - "access_list": [] - } - }); - let essence: EthereumTxEssence = serde_json::from_value(tx).unwrap(); - - let encoded = alloy_rlp::encode(&essence); - assert_eq!(encoded.len(), essence.length()); - assert_eq!( - essence.payload_length() + alloy_rlp::length_of_length(essence.payload_length()), - encoded.len() - ); - - let signature: TxSignature = serde_json::from_value(json!({ - "v": 0, - "r": "0x5fc1441d3469a16715c862240794ef76656c284930e08820b79fd703a98b380a", - "s": "0x37488b0ceef613dc68116ed44b8e63769dbcf039222e25acc1cb9e85e777ade2" - })) - .unwrap(); - - let encoded = alloy_rlp::encode(&signature); - assert_eq!(encoded.len(), signature.length()); - assert_eq!( - signature.payload_length() + alloy_rlp::length_of_length(signature.payload_length()), - encoded.len() - ); - - let transaction = EthereumTransaction { essence, signature }; + let encoded = alloy_rlp::encode(&transaction.signature); + assert_eq!(encoded.len(), transaction.signature.length()); let encoded = alloy_rlp::encode(&transaction); assert_eq!(encoded.len(), transaction.length()); - - assert_eq!( - "0x275631a3549307b2e8c93b18dfcc0fe8aedf0276bb650c28eaa0a8a011d18867", - transaction.hash().to_string() - ); } } diff --git a/primitives/src/transactions/optimism.rs b/primitives/src/transactions/optimism.rs index 2af234e9..b1ba4bbd 100644 --- a/primitives/src/transactions/optimism.rs +++ b/primitives/src/transactions/optimism.rs @@ -18,59 +18,54 @@ use alloy_rlp_derive::RlpEncodable; use bytes::BufMut; use serde::{Deserialize, Serialize}; -use crate::{ - signature::TxSignature, - transactions::{ - ethereum::{EthereumTxEssence, TransactionKind}, - Transaction, TxEssence, - }, +use super::signature::TxSignature; +use crate::transactions::{ + ethereum::{EthereumTxEssence, TransactionKind}, + TxEssence, }; +/// The EIP-2718 transaction type for an Optimism deposited transaction. +pub const OPTIMISM_DEPOSITED_TX_TYPE: u8 = 0x7E; + +/// Represents an Optimism depositing transaction that is a L2 transaction that was +/// derived from L1 and included in a L2 block. #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, RlpEncodable)] pub struct TxEssenceOptimismDeposited { - /// The source hash which uniquely identifies the origin of the deposit + /// The source hash which uniquely identifies the origin of the deposit. pub source_hash: B256, /// The 160-bit address of the sender. pub from: Address, - /// The 160-bit address of the message call's recipient or, for a contract creation - /// transaction, ∅. + /// The 160-bit address of the intended recipient for a message call or + /// [TransactionKind::Create] for contract creation. pub to: TransactionKind, - /// The ETH value to mint on L2 + /// The ETH value to mint on L2. pub mint: U256, - /// A scalar value equal to the number of Wei to be transferred to the message call's - /// recipient. + /// The amount, in Wei, to be transferred to the recipient of the message call. pub value: U256, - /// A scalar value equal to the maximum amount of gas that should be used in executing - /// this transaction. + /// The maximum amount of gas allocated for the execution of the L2 transaction. pub gas_limit: U256, /// If true, the transaction does not interact with the L2 block gas pool. - /// Note: boolean is disabled (enforced to be false) starting from the Regolith - /// upgrade. pub is_system_tx: bool, - /// An unlimited size byte array specifying the transaction data. + /// The transaction's payload, represented as a variable-length byte array. pub data: Bytes, } -impl TxEssenceOptimismDeposited { - pub fn payload_length(&self) -> usize { - self.source_hash.length() - + self.from.length() - + self.to.length() - + self.mint.length() - + self.value.length() - + self.gas_limit.length() - + self.is_system_tx.length() - + self.data.length() - } -} - +/// Represents the core essence of an Optimism transaction, specifically the portion that +/// gets signed. +/// +/// The [OptimismTxEssence] enum provides a way to handle different types of Optimism +/// transactions. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum OptimismTxEssence { + /// Represents an Ethereum-compatible L2 transaction. Ethereum(EthereumTxEssence), + /// Represents an Optimism depositing transaction. OptimismDeposited(TxEssenceOptimismDeposited), } impl Encodable for OptimismTxEssence { + /// Encodes the [OptimismTxEssence] enum variant into the provided `out` buffer. + #[inline] fn encode(&self, out: &mut dyn BufMut) { match self { OptimismTxEssence::Ethereum(eth) => eth.encode(out), @@ -78,6 +73,8 @@ impl Encodable for OptimismTxEssence { } } + /// Computes the length of the RLP-encoded [OptimismTxEssence] enum variant in bytes. + #[inline] fn length(&self) -> usize { match self { OptimismTxEssence::Ethereum(eth) => eth.length(), @@ -87,60 +84,135 @@ impl Encodable for OptimismTxEssence { } impl TxEssence for OptimismTxEssence { + /// Returns the EIP-2718 transaction type. fn tx_type(&self) -> u8 { match self { OptimismTxEssence::Ethereum(eth) => eth.tx_type(), - OptimismTxEssence::OptimismDeposited(_) => 0x7E, + OptimismTxEssence::OptimismDeposited(_) => OPTIMISM_DEPOSITED_TX_TYPE, } } - + /// Returns the gas limit set for the transaction. fn gas_limit(&self) -> U256 { match self { OptimismTxEssence::Ethereum(eth) => eth.gas_limit(), OptimismTxEssence::OptimismDeposited(op) => op.gas_limit, } } - + /// Returns the recipient address of the transaction, if available. fn to(&self) -> Option
{ match self { OptimismTxEssence::Ethereum(eth) => eth.to(), OptimismTxEssence::OptimismDeposited(op) => op.to.into(), } } - + /// Recovers the Ethereum address of the sender from the transaction's signature. fn recover_from(&self, signature: &TxSignature) -> anyhow::Result
{ match self { OptimismTxEssence::Ethereum(eth) => eth.recover_from(signature), OptimismTxEssence::OptimismDeposited(op) => Ok(op.from), } } - + /// Returns the length of the RLP-encoding payload in bytes. fn payload_length(&self) -> usize { match self { OptimismTxEssence::Ethereum(eth) => eth.payload_length(), - OptimismTxEssence::OptimismDeposited(op) => op.payload_length(), + OptimismTxEssence::OptimismDeposited(op) => op._alloy_rlp_payload_length(), } } +} - fn encode_with_signature(&self, signature: &TxSignature, out: &mut dyn BufMut) { - match self { - OptimismTxEssence::Ethereum(eth) => eth.encode_with_signature(signature, out), - OptimismTxEssence::OptimismDeposited(op) => op.encode(out), - } - } +#[cfg(test)] +mod tests { + use alloy_primitives::{address, b256}; + use serde_json::json; - #[inline] - fn length(transaction: &Transaction) -> usize { - let payload_length = match &transaction.essence { - OptimismTxEssence::Ethereum(eth) => { - eth.payload_length() + transaction.signature.payload_length() + use super::*; + use crate::transactions::OptimismTransaction; + + #[test] + fn ethereum() { + // Tx: 0x9125dcdf2a82f349bbcd8c1201cc601b7b4f98975c76d1f8ee3ce9270334fb8a + let tx = json!({ + "Ethereum": { + "Eip1559": { + "chain_id": 10, + "nonce": 17, + "max_priority_fee_per_gas": "0x3b9cdd02", + "max_fee_per_gas": "0x3b9cdd02", + "gas_limit": "0x01a1a7", + "to": { "Call": "0x7f5c764cbc14f9669b88837ca1490cca17c31607" }, + "value": "0x0", + "data": "0x095ea7b30000000000000000000000004c5d5234f232bd2d76b96aa33f5ae4fcf0e4bfabffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "access_list": [] + } } - OptimismTxEssence::OptimismDeposited(op) => op.payload_length(), + }); + let essence: OptimismTxEssence = serde_json::from_value(tx).unwrap(); + + let signature: TxSignature = serde_json::from_value(json!({ + "v": 0, + "r": "0x044e091fe419b233ddc76c616f60f33f5c68a5d6ea315b0b22afdbe5af66b9e6", + "s": "0x7f00ccbff42777c6c2e5d5e85579a7984783be446cc1b9a7b8e080d167d56fa8" + })) + .unwrap(); + + let transaction = OptimismTransaction { essence, signature }; + + // verify that bincode serialization works + let _: OptimismTransaction = + bincode::deserialize(&bincode::serialize(&transaction).unwrap()).unwrap(); + + let encoded = alloy_rlp::encode(&transaction); + assert_eq!(encoded.len(), transaction.length()); + + assert_eq!( + transaction.hash(), + b256!("9125dcdf2a82f349bbcd8c1201cc601b7b4f98975c76d1f8ee3ce9270334fb8a") + ); + let recovered = transaction.recover_from().unwrap(); + assert_eq!( + recovered, + address!("96dd9c6f1fd5b3fbaa70898f09bedff903237d6d") + ); + } + + #[test] + fn optimism_deposited() { + // Tx: 0x2bf9119d4faa19593ca1b3cda4b4ac03c0ced487454a50fbdcd09aebe21210e3 + let tx = json!({ + "OptimismDeposited": { + "source_hash": "0x20b925f36904e1e62099920d902925817c4357e9f674b8b14d13363196139010", + "from": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2", + "to": { "Call": "0x4200000000000000000000000000000000000007" }, + "mint": "0x030d98d59a960000", + "value": "0x030d98d59a960000", + "gas_limit": "0x077d2e", + "is_system_tx": false, + "data": "0xd764ad0b000100000000000000000000000000000000000000000000000000000000af8600000000000000000000000099c9fc46f92e8a1c0dec1b1747d010903e884be10000000000000000000000004200000000000000000000000000000000000010000000000000000000000000000000000000000000000000030d98d59a9600000000000000000000000000000000000000000000000000000000000000030d4000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000a41635f5fd000000000000000000000000ab12275f2d91f87b301a4f01c9af4e83b3f45baa000000000000000000000000ab12275f2d91f87b301a4f01c9af4e83b3f45baa000000000000000000000000000000000000000000000000030d98d59a9600000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }); + let essence: OptimismTxEssence = serde_json::from_value(tx).unwrap(); + + let transaction = OptimismTransaction { + essence, + signature: TxSignature::default(), }; - let mut length = payload_length + alloy_rlp::length_of_length(payload_length); - if transaction.essence.tx_type() != 0 { - length += 1; - } - length + + // verify that bincode serialization works + let _: OptimismTransaction = + bincode::deserialize(&bincode::serialize(&transaction).unwrap()).unwrap(); + + let encoded = alloy_rlp::encode(&transaction); + assert_eq!(encoded.len(), transaction.length()); + + assert_eq!( + transaction.hash(), + b256!("2bf9119d4faa19593ca1b3cda4b4ac03c0ced487454a50fbdcd09aebe21210e3") + ); + let recovered = transaction.recover_from().unwrap(); + assert_eq!( + recovered, + address!("36bde71c97b33cc4729cf772ae268934f7ab70b2") + ); } } diff --git a/primitives/src/signature.rs b/primitives/src/transactions/signature.rs similarity index 92% rename from primitives/src/signature.rs rename to primitives/src/transactions/signature.rs index 23a29b61..e19c0641 100644 --- a/primitives/src/signature.rs +++ b/primitives/src/transactions/signature.rs @@ -21,7 +21,9 @@ use serde::{Deserialize, Serialize}; /// The `TxSignature` struct encapsulates the components of an ECDSA signature: `v`, `r`, /// and `s`. This signature can be used to recover the public key of the signer, ensuring /// the authenticity of the transaction. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RlpEncodable, RlpMaxEncodedLen)] +#[derive( + Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, RlpEncodable, RlpMaxEncodedLen, +)] pub struct TxSignature { pub v: u64, pub r: U256, diff --git a/testing/ef-tests/src/lib.rs b/testing/ef-tests/src/lib.rs index b701d93c..3978de71 100644 --- a/testing/ef-tests/src/lib.rs +++ b/testing/ef-tests/src/lib.rs @@ -36,11 +36,11 @@ use zeth_primitives::{ block::Header, ethers::from_ethers_h160, keccak::keccak, - signature::TxSignature, transactions::{ ethereum::{ EthereumTxEssence, TransactionKind, TxEssenceEip1559, TxEssenceEip2930, TxEssenceLegacy, }, + signature::TxSignature, EthereumTransaction, }, trie::{self, MptNode, MptNodeData, StateAccount}, @@ -51,7 +51,6 @@ use zeth_primitives::{ use crate::ethers::{get_state_update_proofs, TestProvider}; pub mod ethers; - pub mod ethtests; pub mod guests {