diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 9da9720d26..5d6efecbf7 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -749,6 +749,12 @@ impl ChainSpec { .unwrap_or_else(|| self.is_fork_active_at_timestamp(Hardfork::Cancun, timestamp)) } + /// Convenience method to check if [Hardfork::Homestead] is active at a given block number. + #[inline] + pub fn is_homestead_active_at_block(&self, block_number: u64) -> bool { + self.fork(Hardfork::Homestead).active_at_block(block_number) + } + /// Creates a [`ForkFilter`] for the block described by [Head]. pub fn fork_filter(&self, head: Head) -> ForkFilter { let forks = self.forks_iter().filter_map(|(_, condition)| { diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 8d86794db3..fce4629aaf 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1037,6 +1037,16 @@ impl TransactionSigned { Some(TransactionSignedEcRecovered { signed_transaction: self, signer }) } + /// Consumes the type, recover signer and return [`TransactionSignedEcRecovered`] _without + /// ensuring that the signature has a low `s` value_ (EIP-2). + /// + /// Returns `None` if the transaction's signature is invalid, see also + /// [Self::recover_signer_unchecked]. + pub fn into_ecrecovered_unchecked(self) -> Option { + let signer = self.recover_signer_unchecked()?; + Some(TransactionSignedEcRecovered { signed_transaction: self, signer }) + } + /// Tries to recover signer and return [`TransactionSignedEcRecovered`] by cloning the type. pub fn try_ecrecovered(&self) -> Option { let signer = self.recover_signer()?; @@ -1054,6 +1064,18 @@ impl TransactionSigned { } } + /// Tries to recover signer and return [`TransactionSignedEcRecovered`]. _without ensuring that + /// the signature has a low `s` value_ (EIP-2). + /// + /// Returns `Err(Self)` if the transaction's signature is invalid, see also + /// [Self::recover_signer_unchecked]. + pub fn try_into_ecrecovered_unchecked(self) -> Result { + match self.recover_signer_unchecked() { + None => Err(self), + Some(signer) => Ok(TransactionSignedEcRecovered { signed_transaction: self, signer }), + } + } + /// Returns the enveloped encoded transactions. /// /// See also [TransactionSigned::encode_enveloped] diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 5ce8df6cee..f520f42e78 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -19,9 +19,11 @@ use reth_primitives::{ db::{DatabaseCommit, DatabaseRef}, BlockEnv, CfgEnv, }, - Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSigned, B256, + Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, B256, +}; +use reth_provider::{ + BlockReaderIdExt, ChainSpecProvider, HeaderProvider, StateProviderBox, TransactionVariant, }; -use reth_provider::{BlockReaderIdExt, HeaderProvider, StateProviderBox, TransactionVariant}; use reth_revm::{ database::{StateProviderDatabase, SubState}, tracing::{ @@ -73,7 +75,7 @@ impl DebugApi { impl DebugApi where - Provider: BlockReaderIdExt + HeaderProvider + 'static, + Provider: BlockReaderIdExt + HeaderProvider + ChainSpecProvider + 'static, Eth: EthTransactions + 'static, { /// Acquires a permit to execute a tracing call. @@ -85,7 +87,7 @@ where async fn trace_block_with( &self, at: BlockId, - transactions: Vec, + transactions: Vec, cfg: CfgEnv, block_env: BlockEnv, opts: GethDebugTracingOptions, @@ -100,7 +102,6 @@ where let mut transactions = transactions.into_iter().peekable(); while let Some(tx) = transactions.next() { - let tx = tx.into_ecrecovered().ok_or(BlockError::InvalidSignature)?; let tx = tx_env_with_recovered(&tx); let env = Env { cfg: cfg.clone(), block: block_env.clone(), tx }; let (result, state_changes) = @@ -133,10 +134,32 @@ where Block::decode(&mut rlp_block.as_ref()).map_err(BlockError::RlpDecodeRawBlock)?; let (cfg, block_env) = self.inner.eth_api.evm_env_for_raw_block(&block.header).await?; - // we trace on top the block's parent block let parent = block.parent_hash; - self.trace_block_with(parent.into(), block.body, cfg, block_env, opts).await + + // Depending on EIP-2 we need to recover the transactions differently + let transactions = + if self.inner.provider.chain_spec().is_homestead_active_at_block(block.number) { + block + .body + .into_iter() + .map(|tx| { + tx.into_ecrecovered() + .ok_or_else(|| EthApiError::InvalidTransactionSignature) + }) + .collect::>>()? + } else { + block + .body + .into_iter() + .map(|tx| { + tx.into_ecrecovered_unchecked() + .ok_or_else(|| EthApiError::InvalidTransactionSignature) + }) + .collect::>>()? + }; + + self.trace_block_with(parent.into(), transactions, cfg, block_env, opts).await } /// Replays a block and returns the trace of each transaction. @@ -153,7 +176,7 @@ where let ((cfg, block_env, _), block) = futures::try_join!( self.inner.eth_api.evm_env_at(block_hash.into()), - self.inner.eth_api.block_by_id(block_id), + self.inner.eth_api.block_by_id_with_senders(block_id), )?; let block = block.ok_or_else(|| EthApiError::UnknownBlockNumber)?; @@ -161,7 +184,14 @@ where // its parent block's state let state_at = block.parent_hash; - self.trace_block_with(state_at.into(), block.body, cfg, block_env, opts).await + self.trace_block_with( + state_at.into(), + block.into_transactions_ecrecovered().collect(), + cfg, + block_env, + opts, + ) + .await } /// Trace the transaction according to the provided options. @@ -354,11 +384,10 @@ where let StateContext { transaction_index, block_number } = state_context.unwrap_or_default(); let transaction_index = transaction_index.unwrap_or_default(); - let target_block = block_number - .unwrap_or(reth_rpc_types::BlockId::Number(reth_rpc_types::BlockNumberOrTag::Latest)); + let target_block = block_number.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)); let ((cfg, block_env, _), block) = futures::try_join!( self.inner.eth_api.evm_env_at(target_block), - self.inner.eth_api.block_by_id(target_block), + self.inner.eth_api.block_by_id_with_senders(target_block), )?; let opts = opts.unwrap_or_default(); @@ -389,11 +418,10 @@ where if replay_block_txs { // only need to replay the transactions in the block if not all transactions are // to be replayed - let transactions = block.body.into_iter().take(num_txs); + let transactions = block.into_transactions_ecrecovered().take(num_txs); // Execute all transactions until index for tx in transactions { - let tx = tx.into_ecrecovered().ok_or(BlockError::InvalidSignature)?; let tx = tx_env_with_recovered(&tx); let env = Env { cfg: cfg.clone(), block: block_env.clone(), tx }; let (res, _) = transact(&mut db, env)?; @@ -632,7 +660,7 @@ where #[async_trait] impl DebugApiServer for DebugApi where - Provider: BlockReaderIdExt + HeaderProvider + 'static, + Provider: BlockReaderIdExt + HeaderProvider + ChainSpecProvider + 'static, Eth: EthApiSpec + 'static, { /// Handler for `debug_getRawHeader` @@ -658,6 +686,136 @@ where Ok(res.into()) } + /// Handler for `debug_getRawBlock` + async fn raw_block(&self, block_id: BlockId) -> RpcResult { + let block = self.inner.provider.block_by_id(block_id).to_rpc_result()?; + + let mut res = Vec::new(); + if let Some(mut block) = block { + // In RPC withdrawals are always present + if block.withdrawals.is_none() { + block.withdrawals = Some(vec![]); + } + block.encode(&mut res); + } + + Ok(res.into()) + } + + /// Handler for `debug_getRawTransaction` + /// Returns the bytes of the transaction for the given hash. + async fn raw_transaction(&self, hash: B256) -> RpcResult { + let tx = self.inner.eth_api.transaction_by_hash(hash).await?; + Ok(tx + .map(TransactionSource::into_recovered) + .map(|tx| tx.envelope_encoded()) + .unwrap_or_default()) + } + + /// Handler for `debug_getRawTransactions` + /// Returns the bytes of the transaction for the given hash. + async fn raw_transactions(&self, block_id: BlockId) -> RpcResult> { + let block = self + .inner + .provider + .block_with_senders_by_id(block_id, TransactionVariant::NoHash) + .to_rpc_result()? + .unwrap_or_default(); + Ok(block.into_transactions_ecrecovered().map(|tx| tx.envelope_encoded()).collect()) + } + + /// Handler for `debug_getRawReceipts` + async fn raw_receipts(&self, block_id: BlockId) -> RpcResult> { + let receipts = + self.inner.provider.receipts_by_block_id(block_id).to_rpc_result()?.unwrap_or_default(); + let mut all_receipts = Vec::with_capacity(receipts.len()); + + for receipt in receipts { + let mut buf = Vec::new(); + let receipt = receipt.with_bloom(); + receipt.encode(&mut buf); + all_receipts.push(buf.into()); + } + + Ok(all_receipts) + } + + /// Handler for `debug_getBadBlocks` + async fn bad_blocks(&self) -> RpcResult> { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `debug_traceChain` + async fn debug_trace_chain( + &self, + _start_exclusive: BlockNumberOrTag, + _end_inclusive: BlockNumberOrTag, + ) -> RpcResult> { + Err(internal_rpc_err("unimplemented")) + } + + /// Handler for `debug_traceBlock` + async fn debug_trace_block( + &self, + rlp_block: Bytes, + opts: Option, + ) -> RpcResult> { + let _permit = self.acquire_trace_permit().await; + Ok(DebugApi::debug_trace_raw_block(self, rlp_block, opts.unwrap_or_default()).await?) + } + + /// Handler for `debug_traceBlockByHash` + async fn debug_trace_block_by_hash( + &self, + block: B256, + opts: Option, + ) -> RpcResult> { + let _permit = self.acquire_trace_permit().await; + Ok(DebugApi::debug_trace_block(self, block.into(), opts.unwrap_or_default()).await?) + } + + /// Handler for `debug_traceBlockByNumber` + async fn debug_trace_block_by_number( + &self, + block: BlockNumberOrTag, + opts: Option, + ) -> RpcResult> { + let _permit = self.acquire_trace_permit().await; + Ok(DebugApi::debug_trace_block(self, block.into(), opts.unwrap_or_default()).await?) + } + + /// Handler for `debug_traceTransaction` + async fn debug_trace_transaction( + &self, + tx_hash: B256, + opts: Option, + ) -> RpcResult { + let _permit = self.acquire_trace_permit().await; + Ok(DebugApi::debug_trace_transaction(self, tx_hash, opts.unwrap_or_default()).await?) + } + + /// Handler for `debug_traceCall` + async fn debug_trace_call( + &self, + request: CallRequest, + block_number: Option, + opts: Option, + ) -> RpcResult { + let _permit = self.acquire_trace_permit().await; + Ok(DebugApi::debug_trace_call(self, request, block_number, opts.unwrap_or_default()) + .await?) + } + + async fn debug_trace_call_many( + &self, + bundles: Vec, + state_context: Option, + opts: Option, + ) -> RpcResult>> { + let _permit = self.acquire_trace_permit().await; + Ok(DebugApi::debug_trace_call_many(self, bundles, state_context, opts).await?) + } + async fn debug_backtrace_at(&self, _location: &str) -> RpcResult<()> { Ok(()) } @@ -868,136 +1026,6 @@ where async fn debug_write_mutex_profile(&self, _file: String) -> RpcResult<()> { Ok(()) } - - /// Handler for `debug_getRawBlock` - async fn raw_block(&self, block_id: BlockId) -> RpcResult { - let block = self.inner.provider.block_by_id(block_id).to_rpc_result()?; - - let mut res = Vec::new(); - if let Some(mut block) = block { - // In RPC withdrawals are always present - if block.withdrawals.is_none() { - block.withdrawals = Some(vec![]); - } - block.encode(&mut res); - } - - Ok(res.into()) - } - - /// Handler for `debug_getRawTransaction` - /// Returns the bytes of the transaction for the given hash. - async fn raw_transaction(&self, hash: B256) -> RpcResult { - let tx = self.inner.eth_api.transaction_by_hash(hash).await?; - Ok(tx - .map(TransactionSource::into_recovered) - .map(|tx| tx.envelope_encoded()) - .unwrap_or_default()) - } - - /// Handler for `debug_getRawTransactions` - /// Returns the bytes of the transaction for the given hash. - async fn raw_transactions(&self, block_id: BlockId) -> RpcResult> { - let block = self - .inner - .provider - .block_with_senders_by_id(block_id, TransactionVariant::NoHash) - .to_rpc_result()? - .unwrap_or_default(); - Ok(block.into_transactions_ecrecovered().map(|tx| tx.envelope_encoded()).collect()) - } - - /// Handler for `debug_getRawReceipts` - async fn raw_receipts(&self, block_id: BlockId) -> RpcResult> { - let receipts = - self.inner.provider.receipts_by_block_id(block_id).to_rpc_result()?.unwrap_or_default(); - let mut all_receipts = Vec::with_capacity(receipts.len()); - - for receipt in receipts { - let mut buf = Vec::new(); - let receipt = receipt.with_bloom(); - receipt.encode(&mut buf); - all_receipts.push(buf.into()); - } - - Ok(all_receipts) - } - - /// Handler for `debug_getBadBlocks` - async fn bad_blocks(&self) -> RpcResult> { - Err(internal_rpc_err("unimplemented")) - } - - /// Handler for `debug_traceChain` - async fn debug_trace_chain( - &self, - _start_exclusive: BlockNumberOrTag, - _end_inclusive: BlockNumberOrTag, - ) -> RpcResult> { - Err(internal_rpc_err("unimplemented")) - } - - /// Handler for `debug_traceBlock` - async fn debug_trace_block( - &self, - rlp_block: Bytes, - opts: Option, - ) -> RpcResult> { - let _permit = self.acquire_trace_permit().await; - Ok(DebugApi::debug_trace_raw_block(self, rlp_block, opts.unwrap_or_default()).await?) - } - - /// Handler for `debug_traceBlockByHash` - async fn debug_trace_block_by_hash( - &self, - block: B256, - opts: Option, - ) -> RpcResult> { - let _permit = self.acquire_trace_permit().await; - Ok(DebugApi::debug_trace_block(self, block.into(), opts.unwrap_or_default()).await?) - } - - /// Handler for `debug_traceBlockByNumber` - async fn debug_trace_block_by_number( - &self, - block: BlockNumberOrTag, - opts: Option, - ) -> RpcResult> { - let _permit = self.acquire_trace_permit().await; - Ok(DebugApi::debug_trace_block(self, block.into(), opts.unwrap_or_default()).await?) - } - - /// Handler for `debug_traceTransaction` - async fn debug_trace_transaction( - &self, - tx_hash: B256, - opts: Option, - ) -> RpcResult { - let _permit = self.acquire_trace_permit().await; - Ok(DebugApi::debug_trace_transaction(self, tx_hash, opts.unwrap_or_default()).await?) - } - - /// Handler for `debug_traceCall` - async fn debug_trace_call( - &self, - request: CallRequest, - block_number: Option, - opts: Option, - ) -> RpcResult { - let _permit = self.acquire_trace_permit().await; - Ok(DebugApi::debug_trace_call(self, request, block_number, opts.unwrap_or_default()) - .await?) - } - - async fn debug_trace_call_many( - &self, - bundles: Vec, - state_context: Option, - opts: Option, - ) -> RpcResult>> { - let _permit = self.acquire_trace_permit().await; - Ok(DebugApi::debug_trace_call_many(self, bundles, state_context, opts).await?) - } } impl std::fmt::Debug for DebugApi { diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index ec98b9cf8c..cbff163e52 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -18,7 +18,7 @@ use reth_primitives::{ revm::env::{fill_block_env_with_coinbase, tx_env_with_recovered}, revm_primitives::{db::DatabaseCommit, Env, ExecutionResult, ResultAndState, SpecId, State}, Address, BlockId, BlockNumberOrTag, Bytes, FromRecoveredPooledTransaction, Header, - IntoRecoveredTransaction, Receipt, SealedBlock, + IntoRecoveredTransaction, Receipt, SealedBlock, SealedBlockWithSenders, TransactionKind::{Call, Create}, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, B256, U128, U256, U64, }; @@ -100,6 +100,14 @@ pub trait EthTransactions: Send + Sync { /// Returns `None` if block does not exist. async fn block_by_id(&self, id: BlockId) -> EthResult>; + /// Get the entire block for the given id. + /// + /// Returns `None` if block does not exist. + async fn block_by_id_with_senders( + &self, + id: BlockId, + ) -> EthResult>; + /// Get all transactions in the block with the given hash. /// /// Returns `None` if block does not exist. @@ -365,6 +373,13 @@ where self.block(id).await } + async fn block_by_id_with_senders( + &self, + id: BlockId, + ) -> EthResult> { + self.block_with_senders(id).await + } + async fn transactions_by_block_id( &self, block: BlockId, @@ -379,8 +394,11 @@ where match this.provider().transaction_by_hash_with_meta(hash)? { None => Ok(None), Some((tx, meta)) => { + // Note: we assume this transaction is valid, because it's mined (or part of + // pending block) and already. We don't need to + // check for pre EIP-2 because this transaction could be pre-EIP-2. let transaction = tx - .into_ecrecovered() + .into_ecrecovered_unchecked() .ok_or(EthApiError::InvalidTransactionSignature)?; let tx = TransactionSource::Block {