diff --git a/crates/cli/commands/src/test_vectors/compact.rs b/crates/cli/commands/src/test_vectors/compact.rs index 5490f568d3a8..c321e35be731 100644 --- a/crates/cli/commands/src/test_vectors/compact.rs +++ b/crates/cli/commands/src/test_vectors/compact.rs @@ -22,7 +22,7 @@ use reth_db::{ }; use reth_fs_util as fs; use reth_primitives::{ - Account, Log, LogData, Receipt, StorageEntry, Transaction, TransactionSignedNoHash, TxType, + Account, Log, LogData, Receipt, StorageEntry, Transaction, TransactionSigned, TxType, }; use reth_prune_types::{PruneCheckpoint, PruneMode}; use reth_stages_types::{ @@ -111,7 +111,7 @@ compact_types!( StoredBlockBodyIndices, StoredBlockWithdrawals, // Manual implementations - TransactionSignedNoHash, + TransactionSigned, // Bytecode, // todo revm arbitrary StorageEntry, // MerkleCheckpoint, // todo storedsubnode -> branchnodecompact arbitrary diff --git a/crates/cli/commands/src/test_vectors/tables.rs b/crates/cli/commands/src/test_vectors/tables.rs index acb811b75dfc..f845d2a66130 100644 --- a/crates/cli/commands/src/test_vectors/tables.rs +++ b/crates/cli/commands/src/test_vectors/tables.rs @@ -11,7 +11,7 @@ use proptest_arbitrary_interop::arb; use reth_db::tables; use reth_db_api::table::{DupSort, Table, TableRow}; use reth_fs_util as fs; -use reth_primitives::TransactionSignedNoHash; +use reth_primitives::TransactionSigned; use std::collections::HashSet; use tracing::error; @@ -74,7 +74,7 @@ pub fn generate_vectors(mut tables: Vec) -> Result<()> { (BlockBodyIndices, PER_TABLE, TABLE), (BlockOmmers
, 100, TABLE), (TransactionHashNumbers, PER_TABLE, TABLE), - (Transactions, 100, TABLE), + (Transactions, 100, TABLE), (PlainStorageState, PER_TABLE, DUPSORT), (PlainAccountState, PER_TABLE, TABLE) ]); diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 97407ba610cf..2844c9397b83 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -53,7 +53,7 @@ pub use transaction::{ util::secp256k1::{public_key_to_address, recover_signer_unchecked, sign_message}, BlobTransaction, InvalidTransactionError, PooledTransactionsElement, PooledTransactionsElementEcRecovered, RecoveredTx, Transaction, TransactionMeta, - TransactionSigned, TransactionSignedEcRecovered, TransactionSignedNoHash, TxType, + TransactionSigned, TransactionSignedEcRecovered, TxType, }; // Re-exports diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index aaa6b82dc4ef..d0b88c4b179d 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -780,230 +780,6 @@ impl From for Transaction { } } -/// Signed transaction without its Hash. Used type for inserting into the DB. -/// -/// This can by converted to [`TransactionSigned`] by calling [`TransactionSignedNoHash::hash`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Serialize, Deserialize)] -#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(compact))] -pub struct TransactionSignedNoHash { - /// The transaction signature values - pub signature: Signature, - /// Raw transaction info - #[deref] - #[as_ref] - pub transaction: Transaction, -} - -impl TransactionSignedNoHash { - /// Calculates the transaction hash. If used more than once, it's better to convert it to - /// [`TransactionSigned`] first. - pub fn hash(&self) -> B256 { - // pre-allocate buffer for the transaction - let mut buf = Vec::with_capacity(128 + self.transaction.input().len()); - self.transaction.eip2718_encode(&self.signature, &mut buf); - keccak256(&buf) - } - - /// Recover signer from signature and hash. - /// - /// Returns `None` if the transaction's signature is invalid, see also [`Self::recover_signer`]. - pub fn recover_signer(&self) -> Option
{ - // Optimism's Deposit transaction does not have a signature. Directly return the - // `from` address. - #[cfg(feature = "optimism")] - if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction { - return Some(from) - } - - let signature_hash = self.signature_hash(); - recover_signer(&self.signature, signature_hash) - } - - /// Recover signer from signature and hash _without ensuring that the signature has a low `s` - /// value_. - /// - /// Reuses a given buffer to avoid numerous reallocations when recovering batches. **Clears the - /// buffer before use.** - /// - /// Returns `None` if the transaction's signature is invalid, see also - /// [`recover_signer_unchecked`]. - /// - /// # Optimism - /// - /// For optimism this will return [`Address::ZERO`] if the Signature is empty, this is because pre bedrock (on OP mainnet), relay messages to the L2 Cross Domain Messenger were sent as legacy transactions from the zero address with an empty signature, e.g.: - /// This makes it possible to import pre bedrock transactions via the sender recovery stage. - pub fn encode_and_recover_unchecked(&self, buffer: &mut Vec) -> Option
{ - buffer.clear(); - self.transaction.encode_for_signing(buffer); - - // Optimism's Deposit transaction does not have a signature. Directly return the - // `from` address. - #[cfg(feature = "optimism")] - { - if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction { - return Some(from) - } - - // pre bedrock system transactions were sent from the zero address as legacy - // transactions with an empty signature - // - // NOTE: this is very hacky and only relevant for op-mainnet pre bedrock - if self.is_legacy() && self.signature == TxDeposit::signature() { - return Some(Address::ZERO) - } - } - - recover_signer_unchecked(&self.signature, keccak256(buffer)) - } - - /// Converts into a transaction type with its hash: [`TransactionSigned`]. - /// - /// Note: This will recalculate the hash of the transaction. - #[inline] - pub fn with_hash(self) -> TransactionSigned { - let Self { signature, transaction } = self; - TransactionSigned::new_unhashed(transaction, signature) - } - - /// Recovers a list of signers from a transaction list iterator - /// - /// Returns `None`, if some transaction's signature is invalid, see also - /// [`Self::recover_signer`]. - pub fn recover_signers<'a, T>(txes: T, num_txes: usize) -> Option> - where - T: IntoParallelIterator + IntoIterator + Send, - { - if num_txes < *PARALLEL_SENDER_RECOVERY_THRESHOLD { - txes.into_iter().map(|tx| tx.recover_signer()).collect() - } else { - txes.into_par_iter().map(|tx| tx.recover_signer()).collect() - } - } -} - -impl Default for TransactionSignedNoHash { - fn default() -> Self { - Self { signature: Signature::test_signature(), transaction: Default::default() } - } -} - -#[cfg(any(test, feature = "arbitrary"))] -impl<'a> arbitrary::Arbitrary<'a> for TransactionSignedNoHash { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let tx_signed = TransactionSigned::arbitrary(u)?; - - Ok(Self { signature: tx_signed.signature, transaction: tx_signed.transaction }) - } -} - -#[cfg(any(test, feature = "reth-codec"))] -impl reth_codecs::Compact for TransactionSignedNoHash { - fn to_compact(&self, buf: &mut B) -> usize - where - B: bytes::BufMut + AsMut<[u8]>, - { - let start = buf.as_mut().len(); - - // Placeholder for bitflags. - // The first byte uses 4 bits as flags: IsCompressed[1bit], TxType[2bits], Signature[1bit] - buf.put_u8(0); - - let sig_bit = self.signature.to_compact(buf) as u8; - let zstd_bit = self.transaction.input().len() >= 32; - - let tx_bits = if zstd_bit { - let mut tmp = Vec::with_capacity(256); - if cfg!(feature = "std") { - crate::compression::TRANSACTION_COMPRESSOR.with(|compressor| { - let mut compressor = compressor.borrow_mut(); - let tx_bits = self.transaction.to_compact(&mut tmp); - buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress")); - tx_bits as u8 - }) - } else { - let mut compressor = crate::compression::create_tx_compressor(); - let tx_bits = self.transaction.to_compact(&mut tmp); - buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress")); - tx_bits as u8 - } - } else { - self.transaction.to_compact(buf) as u8 - }; - - // Replace bitflags with the actual values - buf.as_mut()[start] = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3); - - buf.as_mut().len() - start - } - - fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) { - use bytes::Buf; - - // The first byte uses 4 bits as flags: IsCompressed[1], TxType[2], Signature[1] - let bitflags = buf.get_u8() as usize; - - let sig_bit = bitflags & 1; - let (signature, buf) = Signature::from_compact(buf, sig_bit); - - let zstd_bit = bitflags >> 3; - let (transaction, buf) = if zstd_bit != 0 { - if cfg!(feature = "std") { - crate::compression::TRANSACTION_DECOMPRESSOR.with(|decompressor| { - let mut decompressor = decompressor.borrow_mut(); - - // TODO: enforce that zstd is only present at a "top" level type - - let transaction_type = (bitflags & 0b110) >> 1; - let (transaction, _) = - Transaction::from_compact(decompressor.decompress(buf), transaction_type); - - (transaction, buf) - }) - } else { - let mut decompressor = crate::compression::create_tx_decompressor(); - let transaction_type = (bitflags & 0b110) >> 1; - let (transaction, _) = - Transaction::from_compact(decompressor.decompress(buf), transaction_type); - - (transaction, buf) - } - } else { - let transaction_type = bitflags >> 1; - Transaction::from_compact(buf, transaction_type) - }; - - (Self { signature, transaction }, buf) - } -} - -#[cfg(any(test, feature = "reth-codec"))] -impl reth_codecs::Compact for TransactionSigned { - fn to_compact(&self, buf: &mut B) -> usize - where - B: bytes::BufMut + AsMut<[u8]>, - { - let tx: TransactionSignedNoHash = self.clone().into(); - tx.to_compact(buf) - } - - fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { - let (tx, buf) = TransactionSignedNoHash::from_compact(buf, len); - (tx.into(), buf) - } -} - -impl From for TransactionSigned { - fn from(tx: TransactionSignedNoHash) -> Self { - tx.with_hash() - } -} - -impl From for TransactionSignedNoHash { - fn from(tx: TransactionSigned) -> Self { - Self { signature: tx.signature, transaction: tx.transaction } - } -} - /// Signed transaction. #[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))] #[derive(Debug, Clone, Eq, AsRef, Deref, Serialize, Deserialize)] @@ -1543,6 +1319,86 @@ impl Decodable2718 for TransactionSigned { } } +#[cfg(any(test, feature = "reth-codec"))] +impl reth_codecs::Compact for TransactionSigned { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + let start = buf.as_mut().len(); + + // Placeholder for bitflags. + // The first byte uses 4 bits as flags: IsCompressed[1bit], TxType[2bits], Signature[1bit] + buf.put_u8(0); + + let sig_bit = self.signature.to_compact(buf) as u8; + let zstd_bit = self.transaction.input().len() >= 32; + + let tx_bits = if zstd_bit { + let mut tmp = Vec::with_capacity(256); + if cfg!(feature = "std") { + crate::compression::TRANSACTION_COMPRESSOR.with(|compressor| { + let mut compressor = compressor.borrow_mut(); + let tx_bits = self.transaction.to_compact(&mut tmp); + buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress")); + tx_bits as u8 + }) + } else { + let mut compressor = crate::compression::create_tx_compressor(); + let tx_bits = self.transaction.to_compact(&mut tmp); + buf.put_slice(&compressor.compress(&tmp).expect("Failed to compress")); + tx_bits as u8 + } + } else { + self.transaction.to_compact(buf) as u8 + }; + + // Replace bitflags with the actual values + buf.as_mut()[start] = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3); + + buf.as_mut().len() - start + } + + fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) { + use bytes::Buf; + + // The first byte uses 4 bits as flags: IsCompressed[1], TxType[2], Signature[1] + let bitflags = buf.get_u8() as usize; + + let sig_bit = bitflags & 1; + let (signature, buf) = Signature::from_compact(buf, sig_bit); + + let zstd_bit = bitflags >> 3; + let (transaction, buf) = if zstd_bit != 0 { + if cfg!(feature = "std") { + crate::compression::TRANSACTION_DECOMPRESSOR.with(|decompressor| { + let mut decompressor = decompressor.borrow_mut(); + + // TODO: enforce that zstd is only present at a "top" level type + + let transaction_type = (bitflags & 0b110) >> 1; + let (transaction, _) = + Transaction::from_compact(decompressor.decompress(buf), transaction_type); + + (transaction, buf) + }) + } else { + let mut decompressor = crate::compression::create_tx_decompressor(); + let transaction_type = (bitflags & 0b110) >> 1; + let (transaction, _) = + Transaction::from_compact(decompressor.decompress(buf), transaction_type); + + (transaction, buf) + } + } else { + let transaction_type = bitflags >> 1; + Transaction::from_compact(buf, transaction_type) + }; + + (Self { signature, transaction, hash: Default::default() }, buf) + } +} + macro_rules! impl_from_signed { ($($tx:ident),*) => { $( diff --git a/crates/primitives/src/transaction/tx_type.rs b/crates/primitives/src/transaction/tx_type.rs index 784a976ab792..1d709b902b53 100644 --- a/crates/primitives/src/transaction/tx_type.rs +++ b/crates/primitives/src/transaction/tx_type.rs @@ -29,7 +29,7 @@ pub const COMPACT_EXTENDED_IDENTIFIER_FLAG: usize = 3; /// Transaction Type /// /// Currently being used as 2-bit type when encoding it to `reth_codecs::Compact` on -/// [`crate::TransactionSignedNoHash`]. Adding more transaction types will break the codec and +/// [`crate::TransactionSigned`]. 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). diff --git a/crates/stages/stages/src/stages/hashing_storage.rs b/crates/stages/stages/src/stages/hashing_storage.rs index dcabbe83ee64..0be84665bee1 100644 --- a/crates/stages/stages/src/stages/hashing_storage.rs +++ b/crates/stages/stages/src/stages/hashing_storage.rs @@ -359,10 +359,7 @@ mod tests { transaction.hash(), next_tx_num, )?; - tx.put::( - next_tx_num, - transaction.clone().into(), - )?; + tx.put::(next_tx_num, transaction.clone())?; let (addr, _) = accounts.get_mut(rng.gen::() % n_accounts as usize).unwrap(); diff --git a/crates/stages/stages/src/test_utils/test_db.rs b/crates/stages/stages/src/test_utils/test_db.rs index 2f9712f84364..5a6c12d8e00f 100644 --- a/crates/stages/stages/src/test_utils/test_db.rs +++ b/crates/stages/stages/src/test_utils/test_db.rs @@ -267,7 +267,7 @@ impl TestStageDB { if let Some(txs_writer) = &mut txs_writer { txs_writer.append_transaction(next_tx_num, body_tx)?; } else { - tx.put::(next_tx_num, body_tx.clone().into())? + tx.put::(next_tx_num, body_tx.clone())? } next_tx_num += 1; Ok::<(), ProviderError>(()) diff --git a/crates/storage/db-api/src/models/mod.rs b/crates/storage/db-api/src/models/mod.rs index 0a008bb88a5f..7ded84e17208 100644 --- a/crates/storage/db-api/src/models/mod.rs +++ b/crates/storage/db-api/src/models/mod.rs @@ -8,7 +8,7 @@ use alloy_consensus::Header; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, Bytes, Log, B256, U256}; use reth_codecs::{add_arbitrary_tests, Compact}; -use reth_primitives::{Receipt, StorageEntry, TransactionSigned, TransactionSignedNoHash, TxType}; +use reth_primitives::{Receipt, StorageEntry, TransactionSigned, TxType}; use reth_primitives_traits::{Account, Bytecode}; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::StageCheckpoint; @@ -226,7 +226,6 @@ impl_compression_for_compact!( StoredBlockWithdrawals, Bytecode, AccountBeforeTx, - TransactionSignedNoHash, TransactionSigned, CompactU256, StageCheckpoint, diff --git a/crates/storage/db/src/tables/mod.rs b/crates/storage/db/src/tables/mod.rs index 8a11c4ac055d..88cfdde44aaf 100644 --- a/crates/storage/db/src/tables/mod.rs +++ b/crates/storage/db/src/tables/mod.rs @@ -31,7 +31,7 @@ use reth_db_api::{ }, table::{Decode, DupSort, Encode, Table}, }; -use reth_primitives::{Receipt, StorageEntry, TransactionSignedNoHash}; +use reth_primitives::{Receipt, StorageEntry, TransactionSigned}; use reth_primitives_traits::{Account, Bytecode}; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::StageCheckpoint; @@ -348,7 +348,7 @@ tables! { } /// Canonical only Stores the transaction body for canonical transactions. - table Transactions { + table Transactions { type Key = TxNumber; type Value = T; } diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index 8330ef3a66e8..521e1d959b3c 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -29,7 +29,7 @@ use reth_node_types::{BlockTy, HeaderTy, NodeTypesWithDB, ReceiptTy, TxTy}; use reth_primitives::{ Account, Block, BlockWithSenders, EthPrimitives, NodePrimitives, Receipt, SealedBlock, SealedBlockFor, SealedBlockWithSenders, SealedHeader, StorageEntry, TransactionMeta, - TransactionSigned, TransactionSignedNoHash, + TransactionSigned, }; use reth_primitives_traits::BlockBody as _; use reth_prune_types::{PruneCheckpoint, PruneSegment}; @@ -850,9 +850,7 @@ mod tests { use reth_db_api::{cursor::DbCursorRO, transaction::DbTx}; use reth_errors::ProviderError; use reth_execution_types::{Chain, ExecutionOutcome}; - use reth_primitives::{ - BlockExt, Receipt, SealedBlock, StaticFileSegment, TransactionSignedNoHash, - }; + use reth_primitives::{BlockExt, Receipt, SealedBlock, StaticFileSegment}; use reth_primitives_traits::{BlockBody as _, SignedTransaction}; use reth_storage_api::{ BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, BlockSource, diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index eca382af76c7..7af071299cdf 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -39,7 +39,7 @@ use reth_primitives::{ }, transaction::recover_signers, BlockWithSenders, Receipt, SealedBlockFor, SealedBlockWithSenders, SealedHeader, - StaticFileSegment, TransactionMeta, TransactionSignedNoHash, + StaticFileSegment, TransactionMeta, TransactionSigned, }; use reth_primitives_traits::SignedTransaction; use reth_stages_types::{PipelineTarget, StageId}; @@ -1706,7 +1706,7 @@ impl StatsReader for StaticFileProvider { .get_highest_static_file_tx(StaticFileSegment::Receipts) .map(|receipts| receipts + 1) .unwrap_or_default() as usize), - tables::Transactions::::NAME => Ok(self + tables::Transactions::::NAME => Ok(self .get_highest_static_file_tx(StaticFileSegment::Transactions) .map(|txs| txs + 1) .unwrap_or_default() diff --git a/docs/design/database.md b/docs/design/database.md index cf2a6c8fcc10..48fc8612cbaa 100644 --- a/docs/design/database.md +++ b/docs/design/database.md @@ -56,7 +56,7 @@ BlockWithdrawals { } Transactions { u64 TxNumber "PK" - TransactionSignedNoHash Data + TransactionSigned Data } TransactionHashNumbers { B256 TxHash "PK"