mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
729 lines
28 KiB
Rust
729 lines
28 KiB
Rust
//! Database access for `eth_` transaction RPC methods. Loads transaction and receipt data w.r.t.
|
|
//! network.
|
|
|
|
use alloy_dyn_abi::TypedData;
|
|
use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256};
|
|
use futures::Future;
|
|
use reth_primitives::{
|
|
BlockId, Receipt, SealedBlockWithSenders, TransactionMeta, TransactionSigned,
|
|
};
|
|
use reth_provider::{BlockNumReader, BlockReaderIdExt, ReceiptProvider, TransactionsProvider};
|
|
use reth_rpc_eth_types::{
|
|
utils::{binary_search, recover_raw_transaction},
|
|
EthApiError, EthStateCache, SignError, TransactionSource,
|
|
};
|
|
use reth_rpc_types::{
|
|
transaction::{
|
|
EIP1559TransactionRequest, EIP2930TransactionRequest, EIP4844TransactionRequest,
|
|
LegacyTransactionRequest,
|
|
},
|
|
BlockNumberOrTag, TransactionInfo, TransactionRequest, TypedTransactionRequest,
|
|
};
|
|
use reth_rpc_types_compat::transaction::{from_recovered, from_recovered_with_block_context};
|
|
use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool};
|
|
|
|
use crate::{FromEthApiError, FullEthApiTypes, IntoEthApiError, RpcReceipt, RpcTransaction};
|
|
|
|
use super::{
|
|
Call, EthApiSpec, EthSigner, LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, LoadState,
|
|
SpawnBlocking,
|
|
};
|
|
|
|
/// Transaction related functions for the [`EthApiServer`](crate::EthApiServer) trait in
|
|
/// the `eth_` namespace.
|
|
///
|
|
/// This includes utilities for transaction tracing, transacting and inspection.
|
|
///
|
|
/// Async functions that are spawned onto the
|
|
/// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool) begin with `spawn_`
|
|
///
|
|
/// ## Calls
|
|
///
|
|
/// There are subtle differences between when transacting [`TransactionRequest`]:
|
|
///
|
|
/// The endpoints `eth_call` and `eth_estimateGas` and `eth_createAccessList` should always
|
|
/// __disable__ the base fee check in the
|
|
/// [`EnvWithHandlerCfg`](revm_primitives::CfgEnvWithHandlerCfg).
|
|
///
|
|
/// The behaviour for tracing endpoints is not consistent across clients.
|
|
/// Geth also disables the basefee check for tracing: <https://github.com/ethereum/go-ethereum/blob/bc0b87ca196f92e5af49bd33cc190ef0ec32b197/eth/tracers/api.go#L955-L955>
|
|
/// Erigon does not: <https://github.com/ledgerwatch/erigon/blob/aefb97b07d1c4fd32a66097a24eddd8f6ccacae0/turbo/transactions/tracing.go#L209-L209>
|
|
///
|
|
/// See also <https://github.com/paradigmxyz/reth/issues/6240>
|
|
///
|
|
/// This implementation follows the behaviour of Geth and disables the basefee check for tracing.
|
|
pub trait EthTransactions: LoadTransaction {
|
|
/// Returns a handle for reading data from disk.
|
|
///
|
|
/// Data access in default (L1) trait method implementations.
|
|
fn provider(&self) -> impl BlockReaderIdExt;
|
|
|
|
/// Returns a handle for signing data.
|
|
///
|
|
/// Singer access in default (L1) trait method implementations.
|
|
fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner>>>;
|
|
|
|
/// Returns the transaction by hash.
|
|
///
|
|
/// Checks the pool and state.
|
|
///
|
|
/// Returns `Ok(None)` if no matching transaction was found.
|
|
fn transaction_by_hash(
|
|
&self,
|
|
hash: B256,
|
|
) -> impl Future<Output = Result<Option<TransactionSource>, Self::Error>> + Send {
|
|
LoadTransaction::transaction_by_hash(self, hash)
|
|
}
|
|
|
|
/// Get all transactions in the block with the given hash.
|
|
///
|
|
/// Returns `None` if block does not exist.
|
|
fn transactions_by_block(
|
|
&self,
|
|
block: B256,
|
|
) -> impl Future<Output = Result<Option<Vec<TransactionSigned>>, Self::Error>> + Send {
|
|
async move {
|
|
self.cache().get_block_transactions(block).await.map_err(Self::Error::from_eth_err)
|
|
}
|
|
}
|
|
|
|
/// Returns the EIP-2718 encoded transaction by hash.
|
|
///
|
|
/// If this is a pooled EIP-4844 transaction, the blob sidecar is included.
|
|
///
|
|
/// Checks the pool and state.
|
|
///
|
|
/// Returns `Ok(None)` if no matching transaction was found.
|
|
fn raw_transaction_by_hash(
|
|
&self,
|
|
hash: B256,
|
|
) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send {
|
|
async move {
|
|
// Note: this is mostly used to fetch pooled transactions so we check the pool first
|
|
if let Some(tx) =
|
|
self.pool().get_pooled_transaction_element(hash).map(|tx| tx.envelope_encoded())
|
|
{
|
|
return Ok(Some(tx))
|
|
}
|
|
|
|
self.spawn_blocking_io(move |ref this| {
|
|
Ok(LoadTransaction::provider(this)
|
|
.transaction_by_hash(hash)
|
|
.map_err(Self::Error::from_eth_err)?
|
|
.map(|tx| tx.envelope_encoded()))
|
|
})
|
|
.await
|
|
}
|
|
}
|
|
|
|
/// Returns the _historical_ transaction and the block it was mined in
|
|
fn historical_transaction_by_hash_at(
|
|
&self,
|
|
hash: B256,
|
|
) -> impl Future<Output = Result<Option<(TransactionSource, B256)>, Self::Error>> + Send {
|
|
async move {
|
|
match self.transaction_by_hash_at(hash).await? {
|
|
None => Ok(None),
|
|
Some((tx, at)) => Ok(at.as_block_hash().map(|hash| (tx, hash))),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the transaction receipt for the given hash.
|
|
///
|
|
/// Returns None if the transaction does not exist or is pending
|
|
/// Note: The tx receipt is not available for pending transactions.
|
|
fn transaction_receipt(
|
|
&self,
|
|
hash: B256,
|
|
) -> impl Future<Output = Result<Option<RpcReceipt<Self::NetworkTypes>>, Self::Error>> + Send
|
|
where
|
|
Self: LoadReceipt + 'static,
|
|
{
|
|
async move {
|
|
match self.load_transaction_and_receipt(hash).await? {
|
|
Some((tx, meta, receipt)) => {
|
|
self.build_transaction_receipt(tx, meta, receipt).await.map(Some)
|
|
}
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Helper method that loads a transaction and its receipt.
|
|
fn load_transaction_and_receipt(
|
|
&self,
|
|
hash: TxHash,
|
|
) -> impl Future<
|
|
Output = Result<Option<(TransactionSigned, TransactionMeta, Receipt)>, Self::Error>,
|
|
> + Send
|
|
where
|
|
Self: 'static,
|
|
{
|
|
let this = self.clone();
|
|
self.spawn_blocking_io(move |_| {
|
|
let (tx, meta) = match LoadTransaction::provider(&this)
|
|
.transaction_by_hash_with_meta(hash)
|
|
.map_err(Self::Error::from_eth_err)?
|
|
{
|
|
Some((tx, meta)) => (tx, meta),
|
|
None => return Ok(None),
|
|
};
|
|
|
|
let receipt = match EthTransactions::provider(&this)
|
|
.receipt_by_hash(hash)
|
|
.map_err(Self::Error::from_eth_err)?
|
|
{
|
|
Some(recpt) => recpt,
|
|
None => return Ok(None),
|
|
};
|
|
|
|
Ok(Some((tx, meta, receipt)))
|
|
})
|
|
}
|
|
|
|
/// Get transaction by [`BlockId`] and index of transaction within that block.
|
|
///
|
|
/// Returns `Ok(None)` if the block does not exist, or index is out of range.
|
|
fn transaction_by_block_and_tx_index(
|
|
&self,
|
|
block_id: BlockId,
|
|
index: usize,
|
|
) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
|
|
where
|
|
Self: LoadBlock,
|
|
{
|
|
async move {
|
|
if let Some(block) = self.block_with_senders(block_id).await? {
|
|
let block_hash = block.hash();
|
|
let block_number = block.number;
|
|
let base_fee_per_gas = block.base_fee_per_gas;
|
|
if let Some(tx) = block.into_transactions_ecrecovered().nth(index) {
|
|
let tx_info = TransactionInfo {
|
|
hash: Some(tx.hash()),
|
|
block_hash: Some(block_hash),
|
|
block_number: Some(block_number),
|
|
base_fee: base_fee_per_gas.map(u128::from),
|
|
index: Some(index as u64),
|
|
};
|
|
|
|
return Ok(Some(from_recovered_with_block_context::<Self::TransactionCompat>(
|
|
tx, tx_info,
|
|
)))
|
|
}
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
/// Find a transaction by sender's address and nonce.
|
|
fn get_transaction_by_sender_and_nonce(
|
|
&self,
|
|
sender: Address,
|
|
nonce: u64,
|
|
include_pending: bool,
|
|
) -> impl Future<Output = Result<Option<RpcTransaction<Self::NetworkTypes>>, Self::Error>> + Send
|
|
where
|
|
Self: LoadBlock + LoadState + FullEthApiTypes,
|
|
{
|
|
async move {
|
|
// Check the pool first
|
|
if include_pending {
|
|
if let Some(tx) =
|
|
LoadState::pool(self).get_transaction_by_sender_and_nonce(sender, nonce)
|
|
{
|
|
let transaction = tx.transaction.clone().into_consensus();
|
|
return Ok(Some(from_recovered::<Self::TransactionCompat>(transaction)));
|
|
}
|
|
}
|
|
|
|
// Check if the sender is a contract
|
|
if self.get_code(sender, None).await?.len() > 0 {
|
|
return Ok(None);
|
|
}
|
|
|
|
let highest = self.transaction_count(sender, None).await?.saturating_to::<u64>();
|
|
|
|
// If the nonce is higher or equal to the highest nonce, the transaction is pending or
|
|
// not exists.
|
|
if nonce >= highest {
|
|
return Ok(None);
|
|
}
|
|
|
|
let Ok(high) = LoadBlock::provider(self).best_block_number() else {
|
|
return Err(EthApiError::HeaderNotFound(BlockNumberOrTag::Latest.into()).into());
|
|
};
|
|
|
|
// Perform a binary search over the block range to find the block in which the sender's
|
|
// nonce reached the requested nonce.
|
|
let num = binary_search::<_, _, Self::Error>(1, high, |mid| async move {
|
|
let mid_nonce =
|
|
self.transaction_count(sender, Some(mid.into())).await?.saturating_to::<u64>();
|
|
|
|
Ok(mid_nonce > nonce)
|
|
})
|
|
.await?;
|
|
|
|
let block_id = num.into();
|
|
self.block_with_senders(block_id)
|
|
.await?
|
|
.and_then(|block| {
|
|
let block_hash = block.hash();
|
|
let block_number = block.number;
|
|
let base_fee_per_gas = block.base_fee_per_gas;
|
|
|
|
block
|
|
.into_transactions_ecrecovered()
|
|
.enumerate()
|
|
.find(|(_, tx)| tx.signer() == sender && tx.nonce() == nonce)
|
|
.map(|(index, tx)| {
|
|
let tx_info = TransactionInfo {
|
|
hash: Some(tx.hash()),
|
|
block_hash: Some(block_hash),
|
|
block_number: Some(block_number),
|
|
base_fee: base_fee_per_gas.map(u128::from),
|
|
index: Some(index as u64),
|
|
};
|
|
from_recovered_with_block_context::<Self::TransactionCompat>(
|
|
tx, tx_info,
|
|
)
|
|
})
|
|
})
|
|
.ok_or(EthApiError::HeaderNotFound(block_id).into())
|
|
.map(Some)
|
|
}
|
|
}
|
|
|
|
/// Get transaction, as raw bytes, by [`BlockId`] and index of transaction within that block.
|
|
///
|
|
/// Returns `Ok(None)` if the block does not exist, or index is out of range.
|
|
fn raw_transaction_by_block_and_tx_index(
|
|
&self,
|
|
block_id: BlockId,
|
|
index: usize,
|
|
) -> impl Future<Output = Result<Option<Bytes>, Self::Error>> + Send
|
|
where
|
|
Self: LoadBlock,
|
|
{
|
|
async move {
|
|
if let Some(block) = self.block_with_senders(block_id).await? {
|
|
if let Some(tx) = block.transactions().nth(index) {
|
|
return Ok(Some(tx.envelope_encoded()))
|
|
}
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
/// Decodes and recovers the transaction and submits it to the pool.
|
|
///
|
|
/// Returns the hash of the transaction.
|
|
fn send_raw_transaction(
|
|
&self,
|
|
tx: Bytes,
|
|
) -> impl Future<Output = Result<B256, Self::Error>> + Send {
|
|
async move {
|
|
let recovered = recover_raw_transaction(tx.clone())?;
|
|
let pool_transaction =
|
|
<Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
|
|
|
|
// submit the transaction to the pool with a `Local` origin
|
|
let hash = self
|
|
.pool()
|
|
.add_transaction(TransactionOrigin::Local, pool_transaction)
|
|
.await
|
|
.map_err(Self::Error::from_eth_err)?;
|
|
|
|
Ok(hash)
|
|
}
|
|
}
|
|
|
|
/// Signs transaction with a matching signer, if any and submits the transaction to the pool.
|
|
/// Returns the hash of the signed transaction.
|
|
fn send_transaction(
|
|
&self,
|
|
mut request: TransactionRequest,
|
|
) -> impl Future<Output = Result<B256, Self::Error>> + Send
|
|
where
|
|
Self: EthApiSpec + LoadBlock + LoadPendingBlock + LoadFee + Call,
|
|
{
|
|
async move {
|
|
let from = match request.from {
|
|
Some(from) => from,
|
|
None => return Err(SignError::NoAccount.into_eth_err()),
|
|
};
|
|
|
|
if self.find_signer(&from).is_err() {
|
|
return Err(SignError::NoAccount.into_eth_err())
|
|
}
|
|
|
|
// set nonce if not already set before
|
|
if request.nonce.is_none() {
|
|
let nonce = self.transaction_count(from, Some(BlockId::pending())).await?;
|
|
// note: `.to()` can't panic because the nonce is constructed from a `u64`
|
|
request.nonce = Some(nonce.to::<u64>());
|
|
}
|
|
|
|
let chain_id = self.chain_id();
|
|
|
|
let estimated_gas =
|
|
self.estimate_gas_at(request.clone(), BlockId::pending(), None).await?;
|
|
let gas_limit = estimated_gas;
|
|
|
|
let TransactionRequest {
|
|
to,
|
|
gas_price,
|
|
max_fee_per_gas,
|
|
max_priority_fee_per_gas,
|
|
gas,
|
|
value,
|
|
input: data,
|
|
nonce,
|
|
mut access_list,
|
|
max_fee_per_blob_gas,
|
|
blob_versioned_hashes,
|
|
sidecar,
|
|
..
|
|
} = request;
|
|
|
|
// todo: remove this inlining after https://github.com/alloy-rs/alloy/pull/183#issuecomment-1928161285
|
|
let transaction = match (
|
|
gas_price,
|
|
max_fee_per_gas,
|
|
access_list.take(),
|
|
max_fee_per_blob_gas,
|
|
blob_versioned_hashes,
|
|
sidecar,
|
|
) {
|
|
// legacy transaction
|
|
// gas price required
|
|
(Some(_), None, None, None, None, None) => {
|
|
Some(TypedTransactionRequest::Legacy(LegacyTransactionRequest {
|
|
nonce: nonce.unwrap_or_default(),
|
|
gas_price: U256::from(gas_price.unwrap_or_default()),
|
|
gas_limit: U256::from(gas.unwrap_or_default()),
|
|
value: value.unwrap_or_default(),
|
|
input: data.into_input().unwrap_or_default(),
|
|
kind: to.unwrap_or(TxKind::Create),
|
|
chain_id: None,
|
|
}))
|
|
}
|
|
// EIP2930
|
|
// if only accesslist is set, and no eip1599 fees
|
|
(_, None, Some(access_list), None, None, None) => {
|
|
Some(TypedTransactionRequest::EIP2930(EIP2930TransactionRequest {
|
|
nonce: nonce.unwrap_or_default(),
|
|
gas_price: U256::from(gas_price.unwrap_or_default()),
|
|
gas_limit: U256::from(gas.unwrap_or_default()),
|
|
value: value.unwrap_or_default(),
|
|
input: data.into_input().unwrap_or_default(),
|
|
kind: to.unwrap_or(TxKind::Create),
|
|
chain_id: 0,
|
|
access_list,
|
|
}))
|
|
}
|
|
// EIP1559
|
|
// if 4844 fields missing
|
|
// gas_price, max_fee_per_gas, access_list, max_fee_per_blob_gas,
|
|
// blob_versioned_hashes, sidecar,
|
|
(None, _, _, None, None, None) => {
|
|
// Empty fields fall back to the canonical transaction schema.
|
|
Some(TypedTransactionRequest::EIP1559(EIP1559TransactionRequest {
|
|
nonce: nonce.unwrap_or_default(),
|
|
max_fee_per_gas: U256::from(max_fee_per_gas.unwrap_or_default()),
|
|
max_priority_fee_per_gas: U256::from(
|
|
max_priority_fee_per_gas.unwrap_or_default(),
|
|
),
|
|
gas_limit: U256::from(gas.unwrap_or_default()),
|
|
value: value.unwrap_or_default(),
|
|
input: data.into_input().unwrap_or_default(),
|
|
kind: to.unwrap_or(TxKind::Create),
|
|
chain_id: 0,
|
|
access_list: access_list.unwrap_or_default(),
|
|
}))
|
|
}
|
|
// EIP4884
|
|
// all blob fields required
|
|
(
|
|
None,
|
|
_,
|
|
_,
|
|
Some(max_fee_per_blob_gas),
|
|
Some(blob_versioned_hashes),
|
|
Some(sidecar),
|
|
) => {
|
|
// As per the EIP, we follow the same semantics as EIP-1559.
|
|
Some(TypedTransactionRequest::EIP4844(EIP4844TransactionRequest {
|
|
chain_id: 0,
|
|
nonce: nonce.unwrap_or_default(),
|
|
max_priority_fee_per_gas: U256::from(
|
|
max_priority_fee_per_gas.unwrap_or_default(),
|
|
),
|
|
max_fee_per_gas: U256::from(max_fee_per_gas.unwrap_or_default()),
|
|
gas_limit: U256::from(gas.unwrap_or_default()),
|
|
value: value.unwrap_or_default(),
|
|
input: data.into_input().unwrap_or_default(),
|
|
to: match to {
|
|
Some(TxKind::Call(to)) => to,
|
|
_ => Address::default(),
|
|
},
|
|
access_list: access_list.unwrap_or_default(),
|
|
|
|
// eip-4844 specific.
|
|
max_fee_per_blob_gas: U256::from(max_fee_per_blob_gas),
|
|
blob_versioned_hashes,
|
|
sidecar,
|
|
}))
|
|
}
|
|
|
|
_ => None,
|
|
};
|
|
|
|
let transaction = match transaction {
|
|
Some(TypedTransactionRequest::Legacy(mut req)) => {
|
|
req.chain_id = Some(chain_id.to());
|
|
req.gas_limit = gas_limit.saturating_to();
|
|
req.gas_price = self.legacy_gas_price(gas_price.map(U256::from)).await?;
|
|
|
|
TypedTransactionRequest::Legacy(req)
|
|
}
|
|
Some(TypedTransactionRequest::EIP2930(mut req)) => {
|
|
req.chain_id = chain_id.to();
|
|
req.gas_limit = gas_limit.saturating_to();
|
|
req.gas_price = self.legacy_gas_price(gas_price.map(U256::from)).await?;
|
|
|
|
TypedTransactionRequest::EIP2930(req)
|
|
}
|
|
Some(TypedTransactionRequest::EIP1559(mut req)) => {
|
|
let (max_fee_per_gas, max_priority_fee_per_gas) = self
|
|
.eip1559_fees(
|
|
max_fee_per_gas.map(U256::from),
|
|
max_priority_fee_per_gas.map(U256::from),
|
|
)
|
|
.await?;
|
|
|
|
req.chain_id = chain_id.to();
|
|
req.gas_limit = gas_limit.saturating_to();
|
|
req.max_fee_per_gas = max_fee_per_gas.saturating_to();
|
|
req.max_priority_fee_per_gas = max_priority_fee_per_gas.saturating_to();
|
|
|
|
TypedTransactionRequest::EIP1559(req)
|
|
}
|
|
Some(TypedTransactionRequest::EIP4844(mut req)) => {
|
|
let (max_fee_per_gas, max_priority_fee_per_gas) = self
|
|
.eip1559_fees(
|
|
max_fee_per_gas.map(U256::from),
|
|
max_priority_fee_per_gas.map(U256::from),
|
|
)
|
|
.await?;
|
|
|
|
req.max_fee_per_gas = max_fee_per_gas;
|
|
req.max_priority_fee_per_gas = max_priority_fee_per_gas;
|
|
req.max_fee_per_blob_gas =
|
|
self.eip4844_blob_fee(max_fee_per_blob_gas.map(U256::from)).await?;
|
|
|
|
req.chain_id = chain_id.to();
|
|
req.gas_limit = gas_limit;
|
|
|
|
TypedTransactionRequest::EIP4844(req)
|
|
}
|
|
None => return Err(EthApiError::ConflictingFeeFieldsInRequest.into()),
|
|
};
|
|
|
|
let signed_tx = self.sign_request(&from, transaction)?;
|
|
|
|
let recovered =
|
|
signed_tx.into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?;
|
|
|
|
let pool_transaction = <<Self as LoadTransaction>::Pool as TransactionPool>::Transaction::try_from_consensus(recovered).map_err(|_| EthApiError::TransactionConversionError)?;
|
|
|
|
// submit the transaction to the pool with a `Local` origin
|
|
let hash = LoadTransaction::pool(self)
|
|
.add_transaction(TransactionOrigin::Local, pool_transaction)
|
|
.await
|
|
.map_err(Self::Error::from_eth_err)?;
|
|
|
|
Ok(hash)
|
|
}
|
|
}
|
|
|
|
/// Signs a transaction, with configured signers.
|
|
fn sign_request(
|
|
&self,
|
|
from: &Address,
|
|
request: TypedTransactionRequest,
|
|
) -> Result<TransactionSigned, Self::Error> {
|
|
for signer in self.signers().read().iter() {
|
|
if signer.is_signer_for(from) {
|
|
return match signer.sign_transaction(request, from) {
|
|
Ok(tx) => Ok(tx),
|
|
Err(e) => Err(e.into_eth_err()),
|
|
}
|
|
}
|
|
}
|
|
Err(EthApiError::InvalidTransactionSignature.into())
|
|
}
|
|
|
|
/// Signs given message. Returns the signature.
|
|
fn sign(
|
|
&self,
|
|
account: Address,
|
|
message: Bytes,
|
|
) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
|
|
async move {
|
|
Ok(self
|
|
.find_signer(&account)?
|
|
.sign(account, &message)
|
|
.await
|
|
.map_err(Self::Error::from_eth_err)?
|
|
.to_hex_bytes())
|
|
}
|
|
}
|
|
|
|
/// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait.
|
|
fn sign_typed_data(&self, data: &TypedData, account: Address) -> Result<Bytes, Self::Error> {
|
|
Ok(self
|
|
.find_signer(&account)?
|
|
.sign_typed_data(account, data)
|
|
.map_err(Self::Error::from_eth_err)?
|
|
.to_hex_bytes())
|
|
}
|
|
|
|
/// Returns the signer for the given account, if found in configured signers.
|
|
fn find_signer(
|
|
&self,
|
|
account: &Address,
|
|
) -> Result<Box<(dyn EthSigner + 'static)>, Self::Error> {
|
|
self.signers()
|
|
.read()
|
|
.iter()
|
|
.find(|signer| signer.is_signer_for(account))
|
|
.map(|signer| dyn_clone::clone_box(&**signer))
|
|
.ok_or_else(|| SignError::NoAccount.into_eth_err())
|
|
}
|
|
}
|
|
|
|
/// Loads a transaction from database.
|
|
///
|
|
/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` transactions RPC
|
|
/// methods.
|
|
pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes {
|
|
/// Transaction pool with pending transactions. [`TransactionPool::Transaction`] is the
|
|
/// supported transaction type.
|
|
type Pool: TransactionPool;
|
|
|
|
/// Returns a handle for reading data from disk.
|
|
///
|
|
/// Data access in default (L1) trait method implementations.
|
|
fn provider(&self) -> impl TransactionsProvider;
|
|
|
|
/// Returns a handle for reading data from memory.
|
|
///
|
|
/// Data access in default (L1) trait method implementations.
|
|
fn cache(&self) -> &EthStateCache;
|
|
|
|
/// Returns a handle for reading data from pool.
|
|
///
|
|
/// Data access in default (L1) trait method implementations.
|
|
fn pool(&self) -> &Self::Pool;
|
|
|
|
/// Returns the transaction by hash.
|
|
///
|
|
/// Checks the pool and state.
|
|
///
|
|
/// Returns `Ok(None)` if no matching transaction was found.
|
|
fn transaction_by_hash(
|
|
&self,
|
|
hash: B256,
|
|
) -> impl Future<Output = Result<Option<TransactionSource>, Self::Error>> + Send {
|
|
async move {
|
|
// Try to find the transaction on disk
|
|
let mut resp = self
|
|
.spawn_blocking_io(move |this| {
|
|
match this
|
|
.provider()
|
|
.transaction_by_hash_with_meta(hash)
|
|
.map_err(Self::Error::from_eth_err)?
|
|
{
|
|
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_unchecked()
|
|
.ok_or(EthApiError::InvalidTransactionSignature)?;
|
|
|
|
let tx = TransactionSource::Block {
|
|
transaction,
|
|
index: meta.index,
|
|
block_hash: meta.block_hash,
|
|
block_number: meta.block_number,
|
|
base_fee: meta.base_fee,
|
|
};
|
|
Ok(Some(tx))
|
|
}
|
|
}
|
|
})
|
|
.await?;
|
|
|
|
if resp.is_none() {
|
|
// tx not found on disk, check pool
|
|
if let Some(tx) =
|
|
self.pool().get(&hash).map(|tx| tx.transaction.clone().into_consensus())
|
|
{
|
|
resp = Some(TransactionSource::Pool(tx));
|
|
}
|
|
}
|
|
|
|
Ok(resp)
|
|
}
|
|
}
|
|
|
|
/// Returns the transaction by including its corresponding [`BlockId`].
|
|
///
|
|
/// Note: this supports pending transactions
|
|
fn transaction_by_hash_at(
|
|
&self,
|
|
transaction_hash: B256,
|
|
) -> impl Future<Output = Result<Option<(TransactionSource, BlockId)>, Self::Error>> + Send
|
|
{
|
|
async move {
|
|
Ok(self.transaction_by_hash(transaction_hash).await?.map(|tx| match tx {
|
|
tx @ TransactionSource::Pool(_) => (tx, BlockId::pending()),
|
|
tx @ TransactionSource::Block { block_hash, .. } => {
|
|
(tx, BlockId::Hash(block_hash.into()))
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
|
|
/// Fetches the transaction and the transaction's block
|
|
fn transaction_and_block(
|
|
&self,
|
|
hash: B256,
|
|
) -> impl Future<Output = Result<Option<(TransactionSource, SealedBlockWithSenders)>, Self::Error>>
|
|
+ Send {
|
|
async move {
|
|
let (transaction, at) = match self.transaction_by_hash_at(hash).await? {
|
|
None => return Ok(None),
|
|
Some(res) => res,
|
|
};
|
|
|
|
// Note: this is always either hash or pending
|
|
let block_hash = match at {
|
|
BlockId::Hash(hash) => hash.block_hash,
|
|
_ => return Ok(None),
|
|
};
|
|
let block = self
|
|
.cache()
|
|
.get_block_with_senders(block_hash)
|
|
.await
|
|
.map_err(Self::Error::from_eth_err)?;
|
|
Ok(block.map(|block| (transaction, block.seal(block_hash))))
|
|
}
|
|
}
|
|
}
|