diff --git a/Cargo.lock b/Cargo.lock index e50551f2393..ad0e10bbe3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7262,6 +7262,7 @@ dependencies = [ "reth-db", "reth-db-api", "reth-db-common", + "reth-discv5", "reth-downloaders", "reth-engine-util", "reth-errors", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index bd8086e38d3..b2cd27aa323 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -123,6 +123,8 @@ revm-primitives.workspace = true reth-db-common.workspace = true reth-db = { workspace = true, features = ["mdbx", "test-utils"] } serial_test.workspace = true +reth-discv5.workspace = true +reth-transaction-pool = { workspace = true, features = ["test-utils"] } [features] default = ["jemalloc"] diff --git a/bin/reth/tests/commands/bitfinity_node_it.rs b/bin/reth/tests/commands/bitfinity_node_it.rs index d8377fc0ef0..3cf218c1549 100644 --- a/bin/reth/tests/commands/bitfinity_node_it.rs +++ b/bin/reth/tests/commands/bitfinity_node_it.rs @@ -16,6 +16,7 @@ use reth::{ }; use reth_consensus::FullConsensus; use reth_db::{init_db, test_utils::tempdir_path, DatabaseEnv}; +use reth_discv5::discv5::enr::secp256k1::{Keypair, Secp256k1}; use reth_network::NetworkHandle; use reth_node_api::{FullNodeTypesAdapter, NodeTypesWithDBAdapter}; use reth_node_builder::{ @@ -25,21 +26,24 @@ use reth_node_ethereum::{ node::EthereumEngineValidatorBuilder, BasicBlockExecutorProvider, EthEvmConfig, EthExecutionStrategyFactory, EthereumNode, }; +use reth_primitives::{Transaction, TransactionSigned}; use reth_provider::providers::BlockchainProvider; use reth_rpc::EthApi; use reth_tasks::TaskManager; +use reth_transaction_pool::blobstore::DiskFileBlobStore; +use reth_transaction_pool::test_utils::MockTransaction; use reth_transaction_pool::{ - blobstore::DiskFileBlobStore, CoinbaseTipOrdering, EthPooledTransaction, - EthTransactionValidator, Pool, TransactionValidationTaskExecutor, + CoinbaseTipOrdering, EthPooledTransaction, EthTransactionValidator, Pool, + TransactionValidationTaskExecutor, }; -use revm_primitives::{hex, Address, U256}; +use revm_primitives::{hex, Address, B256, U256}; use std::{net::SocketAddr, str::FromStr, sync::Arc}; #[tokio::test] async fn bitfinity_test_should_start_local_reth_node() { // Arrange let _log = init_logs(); - let (reth_client, _reth_node) = start_reth_node(None, None).await; + let (reth_client, _reth_node, _tasks) = start_reth_node(None, None).await; // Act & Assert assert!(reth_client.get_chain_id().await.is_ok()); @@ -53,7 +57,7 @@ async fn bitfinity_test_lb_lag_check() { let eth_server = EthImpl::new(); let (_server, eth_server_address) = mock_eth_server_start(EthServer::into_rpc(eth_server)).await; - let (reth_client, _reth_node) = + let (reth_client, _reth_node, _tasks) = start_reth_node(Some(format!("http://{}", eth_server_address)), None).await; // Try `eth_lbLagCheck` @@ -107,7 +111,7 @@ async fn bitfinity_test_lb_lag_check() { #[tokio::test] async fn bitfinity_test_lb_lag_check_fail_safe() { - let (reth_client, _reth_node) = + let (reth_client, _reth_node, _tasks) = start_reth_node(Some("http://local_host:11".to_string()), None).await; let message: String = reth_client @@ -132,7 +136,7 @@ async fn bitfinity_test_node_forward_ic_or_eth_get_last_certified_block() { let eth_server = EthImpl::new(); let (_server, eth_server_address) = mock_eth_server_start(EthServer::into_rpc(eth_server)).await; - let (reth_client, _reth_node) = + let (reth_client, _reth_node, _tasks) = start_reth_node(Some(format!("http://{}", eth_server_address)), None).await; // Act @@ -163,7 +167,7 @@ async fn bitfinity_test_node_forward_get_gas_price_requests() { let gas_price = eth_server.gas_price; let (_server, eth_server_address) = mock_eth_server_start(EthServer::into_rpc(eth_server)).await; - let (reth_client, _reth_node) = + let (reth_client, _reth_node, _tasks) = start_reth_node(Some(format!("http://{}", eth_server_address)), None).await; // Act @@ -182,7 +186,7 @@ async fn bitfinity_test_node_forward_max_priority_fee_per_gas_requests() { let max_priority_fee_per_gas = eth_server.max_priority_fee_per_gas; let (_server, eth_server_address) = mock_eth_server_start(EthServer::into_rpc(eth_server)).await; - let (reth_client, _reth_node) = + let (reth_client, _reth_node, _tasks) = start_reth_node(Some(format!("http://{}", eth_server_address)), None).await; // Act @@ -204,7 +208,7 @@ async fn bitfinity_test_node_forward_eth_get_genesis_balances() { ]); let (_server, eth_server_address) = mock_eth_server_start(EthServer::into_rpc(eth_server)).await; - let (reth_client, _reth_node) = + let (reth_client, _reth_node, _tasks) = start_reth_node(Some(format!("http://{}", eth_server_address)), None).await; // Act @@ -242,7 +246,7 @@ async fn bitfinity_test_node_forward_ic_get_genesis_balances() { ]); let (_server, eth_server_address) = mock_eth_server_start(EthServer::into_rpc(eth_server)).await; - let (reth_client, _reth_node) = + let (reth_client, _reth_node, _tasks) = start_reth_node(Some(format!("http://{}", eth_server_address)), None).await; // Act @@ -269,7 +273,7 @@ async fn bitfinity_test_node_forward_send_raw_transaction_requests() { let eth_server = EthImpl::new(); let (_server, eth_server_address) = mock_eth_server_start(EthServer::into_rpc(eth_server)).await; - let (reth_client, _reth_node) = + let (reth_client, _reth_node, _tasks) = start_reth_node(Some(format!("http://{}", eth_server_address)), None).await; // Create a random transaction @@ -284,6 +288,44 @@ async fn bitfinity_test_node_forward_send_raw_transaction_requests() { assert_eq!(result.unwrap(), expected_tx_hash); } +#[tokio::test] +async fn bitfinity_test_node_forward_get_transaction_by_hash() { + // Arrange + let _log = init_logs(); + + let eth_server = EthImpl::new(); + let last_get_tx_hash = eth_server.last_get_tx_by_hash.clone(); + + let (_server, eth_server_address) = + mock_eth_server_start(EthServer::into_rpc(eth_server)).await; + let (reth_client, _reth_node, _tasks) = + start_reth_node(Some(format!("http://{}", eth_server_address)), None).await; + + let hash = B256::random(); + + // Act + let tx_result = reth_client.get_transaction_by_hash(hash.into()).await; + + // Assert + assert!(matches!(tx_result, Ok(Some(_)))); + assert_eq!(last_get_tx_hash.read().await.unwrap(), hash); +} + +fn sign_tx_with_random_key_pair(tx: Transaction) -> TransactionSigned { + let secp = Secp256k1::new(); + let key_pair = Keypair::new(&secp, &mut rand::thread_rng()); + sign_tx_with_key_pair(key_pair, tx) +} + +fn sign_tx_with_key_pair(key_pair: Keypair, tx: Transaction) -> TransactionSigned { + let signature = reth_primitives::sign_message( + B256::from_slice(&key_pair.secret_bytes()[..]), + tx.signature_hash(), + ) + .unwrap(); + TransactionSigned::new(tx, signature, Default::default()) +} + /// Start a local reth node pub async fn start_reth_node( bitfinity_evm_url: Option, @@ -372,6 +414,7 @@ pub async fn start_reth_node( EthereumEngineValidatorBuilder, >, >, + TaskManager, ) { let tasks = TaskManager::current(); @@ -416,7 +459,7 @@ pub async fn start_reth_node( let client: EthJsonRpcClient = EthJsonRpcClient::new(ReqwestClient::new(format!("http://{}", reth_address))); - (client, node_handle) + (client, node_handle, tasks) } /// Start a local Eth server. @@ -454,9 +497,13 @@ pub mod eth_server { use did::{keccak, BlockConfirmationData, BlockConfirmationResult, BlockNumber, H256, U64}; use ethereum_json_rpc_client::CertifiedResult; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; + use reth_transaction_pool::test_utils::MockTransaction; use reth_trie::EMPTY_ROOT_HASH; use revm_primitives::{Address, B256, U256}; + use tokio::sync::RwLock; + + use super::sign_tx_with_random_key_pair; #[rpc(server, namespace = "eth")] pub trait Eth { @@ -472,6 +519,10 @@ pub mod eth_server { #[method(name = "sendRawTransaction")] async fn send_raw_transaction(&self, tx: Bytes) -> RpcResult; + /// Gets transaction by hash. + #[method(name = "getTransactionByHash")] + async fn transaction_by_hash(&self, hash: B256) -> RpcResult>; + /// Returns the genesis balances. #[method(name = "getGenesisBalances", aliases = ["ic_getGenesisBalances"])] async fn get_genesis_balances(&self) -> RpcResult>; @@ -519,6 +570,9 @@ pub mod eth_server { pub gas_price: u128, /// Current max priority fee per gas pub max_priority_fee_per_gas: u128, + + /// Hash of last + pub last_get_tx_by_hash: Arc>>, /// Current block number (atomic for thread safety) pub current_block: Arc, /// Chain ID @@ -564,6 +618,7 @@ pub mod eth_server { Self { gas_price: rand::random(), max_priority_fee_per_gas: rand::random(), + last_get_tx_by_hash: Default::default(), current_block, chain_id: 1, state: did::evm_state::EvmGlobalState::Staging { max_block_number: None }, @@ -684,6 +739,22 @@ pub mod eth_server { Ok(hash.into()) } + async fn transaction_by_hash(&self, hash: B256) -> RpcResult> { + *self.last_get_tx_by_hash.write().await = Some(hash); + + // return transaction, initialized enough to pass reth checks. + let tx = sign_tx_with_random_key_pair(MockTransaction::legacy().with_hash(hash).into()); + let did_tx = did::Transaction { + hash: hash.into(), + r: tx.signature.r().into(), + s: tx.signature.s().into(), + v: (tx.signature.recid().to_byte() as u64).into(), + ..Default::default() + }; + + Ok(Some(did_tx)) + } + async fn get_genesis_balances(&self) -> RpcResult> { Ok(self.genesis_balances.clone()) } diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index aa4f174f29c..a457b903bcc 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -551,10 +551,23 @@ where hash: B256, ) -> RpcResult>> { trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionByHash"); - Ok(EthTransactions::transaction_by_hash(self, hash) + // Ok(EthTransactions::transaction_by_hash(self, hash) + // .await? + // .map(|tx| tx.into_transaction(self.tx_resp_builder())) + // .transpose()?) + + let mut tx_opt = EthTransactions::transaction_by_hash(self, hash) .await? .map(|tx| tx.into_transaction(self.tx_resp_builder())) - .transpose()?) + .transpose()?; + if tx_opt.is_none() { + tx_opt = BitfinityEvmRpc::btf_transaction_by_hash(self, hash) + .await? + .map(|tx| tx.into_transaction(self.tx_resp_builder())) + .transpose()?; + } + + Ok(tx_opt) } /// Handler for: `eth_getRawTransactionByBlockHashAndIndex` @@ -743,7 +756,7 @@ where /// Handler for: `eth_gasPrice` async fn gas_price(&self) -> RpcResult { trace!(target: "rpc::eth", "Serving eth_gasPrice"); - Ok(BitfinityEvmRpc::gas_price(self).await?) + Ok(BitfinityEvmRpc::btf_gas_price(self).await?) } /// Handler for: `eth_getAccount` @@ -759,7 +772,7 @@ where /// Handler for: `eth_maxPriorityFeePerGas` async fn max_priority_fee_per_gas(&self) -> RpcResult { trace!(target: "rpc::eth", "Serving eth_maxPriorityFeePerGas"); - Ok(BitfinityEvmRpc::max_priority_fee_per_gas(self).await?) + Ok(BitfinityEvmRpc::btf_max_priority_fee_per_gas(self).await?) } /// Handler for: `eth_blobBaseFee` @@ -827,7 +840,7 @@ where async fn send_raw_transaction(&self, tx: Bytes) -> RpcResult { trace!(target: "rpc::eth", ?tx, "Serving eth_sendRawTransaction"); // Ok(EthTransactions::send_raw_transaction(self, tx).await?) - BitfinityEvmRpc::send_raw_transaction(self, tx).await + BitfinityEvmRpc::btf_send_raw_transaction(self, tx).await } /// Handler for: `eth_sign` diff --git a/crates/rpc/rpc-eth-api/src/helpers/bitfinity_evm_rpc.rs b/crates/rpc/rpc-eth-api/src/helpers/bitfinity_evm_rpc.rs index 123ec13ccd8..138698f82c0 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/bitfinity_evm_rpc.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/bitfinity_evm_rpc.rs @@ -2,17 +2,24 @@ use std::sync::Arc; +use alloy_network::TransactionResponse; use did::{Block, H256}; use ethereum_json_rpc_client::CertifiedResult; use ethereum_json_rpc_client::{reqwest::ReqwestClient, EthJsonRpcClient}; use futures::Future; use jsonrpsee::core::RpcResult; use reth_chainspec::ChainSpec; +use reth_primitives::RecoveredTx; +use reth_primitives_traits::SignedTransaction; +use reth_rpc_eth_types::TransactionSource; use reth_rpc_server_types::result::internal_rpc_err; use revm_primitives::{Address, Bytes, B256, U256}; /// Proxy to the Bitfinity EVM RPC. pub trait BitfinityEvmRpc { + /// Transaction type. + type Transaction: SignedTransaction; + /// Returns the `ChainSpec`. fn chain_spec(&self) -> Arc; @@ -36,7 +43,7 @@ pub trait BitfinityEvmRpc { } /// Forwards `eth_gasPrice` calls to the Bitfinity EVM. - fn gas_price(&self) -> impl Future> + Send { + fn btf_gas_price(&self) -> impl Future> + Send { let chain_spec = self.chain_spec(); async move { let (rpc_url, client) = get_client(&chain_spec)?; @@ -53,7 +60,7 @@ pub trait BitfinityEvmRpc { } /// Forwards `eth_maxPriorityFeePerGas` calls to the Bitfinity EVM - fn max_priority_fee_per_gas(&self) -> impl Future> + Send { + fn btf_max_priority_fee_per_gas(&self) -> impl Future> + Send { let chain_spec = self.chain_spec(); async move { let (rpc_url, client) = get_client(&chain_spec)?; @@ -69,8 +76,63 @@ pub trait BitfinityEvmRpc { } } + /// Returns transaction from forwarder or query it from EVM RPC. + fn btf_transaction_by_hash( + &self, + hash: B256, + ) -> impl Future>>> + Send { + let chain_spec = self.chain_spec(); + + async move { + let (rpc_url, client) = get_client(&chain_spec)?; + let Some(tx) = client.get_transaction_by_hash(hash.into()).await.map_err(|e| { + internal_rpc_err(format!( + "failed to forward eth_transactionByHash request to {}: {}", + rpc_url, e + )) + })? + else { + return Ok(None); + }; + + let alloy_tx: alloy_rpc_types_eth::Transaction = tx.try_into().map_err(|e| { + internal_rpc_err(format!( + "failed to convert did::Transaction into alloy_rpc_types::Transaction: {e}" + )) + })?; + let encoded = alloy_rlp::encode(&alloy_tx.inner); + let self_tx = + ::decode(&mut encoded.as_ref()) + .map_err(|e| internal_rpc_err(format!("failed to decode BitfinityEvmRpc::Transaction from received did::Transaction: {e}")))?; + + let signer = self_tx.recover_signer().ok_or_else(|| { + internal_rpc_err( + "failed to recover signer from decoded BitfinityEvmRpc::Transaction", + ) + })?; + let recovered_tx = RecoveredTx::new_unchecked(self_tx, signer); + + let block_params = alloy_tx + .block_number() + .zip(alloy_tx.transaction_index()) + .zip(alloy_tx.block_hash()); + let tx_source = match block_params { + Some(((block_number, index), block_hash)) => TransactionSource::Block { + transaction: recovered_tx, + index, + block_hash, + block_number, + base_fee: None, + }, + None => TransactionSource::Pool(recovered_tx), + }; + + Ok(Some(tx_source)) + } + } + /// Forwards `eth_sendRawTransaction` calls to the Bitfinity EVM - fn send_raw_transaction(&self, tx: Bytes) -> impl Future> + Send { + fn btf_send_raw_transaction(&self, tx: Bytes) -> impl Future> + Send { let chain_spec = self.chain_spec(); async move { let (rpc_url, client) = get_client(&chain_spec)?; diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index 6da59a98b24..c4aaa7c2cde 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -112,7 +112,7 @@ pub trait EthTransactions: LoadTransaction { if let Some(tx) = self.pool().get_pooled_transaction_element(hash).map(|tx| tx.encoded_2718().into()) { - return Ok(Some(tx)) + return Ok(Some(tx)); } self.spawn_blocking_io(move |ref this| { @@ -223,7 +223,7 @@ pub trait EthTransactions: LoadTransaction { return Ok(Some( self.tx_resp_builder().fill(tx.clone().with_signer(*signer), tx_info)?, - )) + )); } } @@ -321,7 +321,7 @@ pub trait EthTransactions: LoadTransaction { async move { if let Some(block) = self.block_with_senders(block_id).await? { if let Some(tx) = block.transactions().get(index) { - return Ok(Some(tx.encoded_2718().into())) + return Ok(Some(tx.encoded_2718().into())); } } @@ -345,7 +345,7 @@ pub trait EthTransactions: LoadTransaction { }; if self.find_signer(&from).is_err() { - return Err(SignError::NoAccount.into_eth_err()) + return Err(SignError::NoAccount.into_eth_err()); } // set nonce if not already set before diff --git a/crates/rpc/rpc-eth-api/src/types.rs b/crates/rpc/rpc-eth-api/src/types.rs index 2da1bdac281..6f570350019 100644 --- a/crates/rpc/rpc-eth-api/src/types.rs +++ b/crates/rpc/rpc-eth-api/src/types.rs @@ -11,7 +11,10 @@ use reth_provider::{ProviderTx, ReceiptProvider, TransactionsProvider}; use reth_rpc_types_compat::TransactionCompat; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -use crate::{AsEthApiError, FromEthApiError, FromEvmError, RpcNodeCore}; +use crate::{ + helpers::bitfinity_evm_rpc::BitfinityEvmRpc, AsEthApiError, FromEthApiError, FromEvmError, + RpcNodeCore, +}; /// Network specific `eth` API types. pub trait EthApiTypes: Send + Sync + Clone { @@ -61,7 +64,7 @@ where Transaction = RpcTransaction, Error = RpcError, >, - >, + > + BitfinityEvmRpc::Transaction>, { } @@ -77,6 +80,6 @@ impl FullEthApiTypes for T where Transaction = RpcTransaction, Error = RpcError, >, - > + > + BitfinityEvmRpc::Transaction> { } diff --git a/crates/rpc/rpc/src/eth/helpers/bitfinity_evm_rpc.rs b/crates/rpc/rpc/src/eth/helpers/bitfinity_evm_rpc.rs index c5a7151d1c5..8a87aac0ba8 100644 --- a/crates/rpc/rpc/src/eth/helpers/bitfinity_evm_rpc.rs +++ b/crates/rpc/rpc/src/eth/helpers/bitfinity_evm_rpc.rs @@ -12,6 +12,8 @@ impl BitfinityEvmRpc where Provider: BlockReader + ChainSpecProvider, { + type Transaction = Provider::Transaction; + fn chain_spec(&self) -> Arc { self.inner.provider().chain_spec() }