From 0646e49b676dd09f972f7e737956daae4b083540 Mon Sep 17 00:00:00 2001 From: f3kilo Date: Fri, 24 Jan 2025 12:44:20 +0300 Subject: [PATCH 1/2] query txs from evmc --- crates/rpc/rpc-eth-api/src/core.rs | 23 +++++-- .../src/helpers/bitfinity_evm_rpc.rs | 68 ++++++++++++++++++- crates/rpc/rpc-eth-api/src/types.rs | 9 ++- .../rpc/src/eth/helpers/bitfinity_evm_rpc.rs | 2 + 4 files changed, 91 insertions(+), 11 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index 2120db192ef..07cc570dc65 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -514,10 +514,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` @@ -706,7 +719,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` @@ -722,7 +735,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` @@ -790,7 +803,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 dd18583d5bf..112462f29ab 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,22 +2,29 @@ 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; /// 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)?; @@ -34,7 +41,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)?; @@ -50,8 +57,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/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() } From ae3dcfb7b2ffbc11838629d904675079bee3d3ce Mon Sep 17 00:00:00 2001 From: f3kilo Date: Tue, 28 Jan 2025 13:21:29 +0300 Subject: [PATCH 2/2] tests --- Cargo.lock | 1 + bin/reth/Cargo.toml | 2 + bin/reth/tests/commands/bitfinity_node_it.rs | 178 ++++++++++++++++-- .../rpc-eth-api/src/helpers/transaction.rs | 8 +- 4 files changed, 174 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b570cf2cd3..362a2ad5c32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7235,6 +7235,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 ca31910c019..f903b957ba1 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -120,6 +120,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 8c9de89f861..fe96b1b17bc 100644 --- a/bin/reth/tests/commands/bitfinity_node_it.rs +++ b/bin/reth/tests/commands/bitfinity_node_it.rs @@ -19,6 +19,7 @@ use reth::{ use reth_consensus::FullConsensus; use reth_db::DatabaseEnv; use reth_db::{init_db, test_utils::tempdir_path}; +use reth_discv5::discv5::enr::secp256k1::{Keypair, Secp256k1}; use reth_network::NetworkHandle; use reth_node_api::{FullNodeTypesAdapter, NodeTypesWithDBAdapter}; use reth_node_builder::components::Components; @@ -28,22 +29,24 @@ use reth_node_ethereum::node::EthereumEngineValidatorBuilder; use reth_node_ethereum::{ 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::{ 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()); @@ -57,7 +60,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 @@ -88,7 +91,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 @@ -107,7 +110,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 @@ -125,7 +128,7 @@ async fn bitfinity_test_node_forward_eth_get_genesis_balances() { 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 @@ -159,7 +162,7 @@ async fn bitfinity_test_node_forward_ic_get_genesis_balances() { 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 @@ -186,7 +189,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 @@ -201,13 +204,133 @@ 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 async fn start_reth_node( bitfinity_evm_url: Option, import_data: Option, ) -> ( EthJsonRpcClient, - NodeHandle, BlockchainProvider>>>, Components, BlockchainProvider>>>, reth_network::EthNetworkPrimitives, Pool>>, EthPooledTransaction>>, CoinbaseTipOrdering, DiskFileBlobStore>, EthEvmConfig, BasicBlockExecutorProvider, Arc>>, RpcAddOns, BlockchainProvider>>>, Components, BlockchainProvider>>>, reth_network::EthNetworkPrimitives, Pool>>, EthPooledTransaction>>, CoinbaseTipOrdering, DiskFileBlobStore>, EthEvmConfig, BasicBlockExecutorProvider, Arc>>, EthApi>>, Pool>>, EthPooledTransaction>>, CoinbaseTipOrdering, DiskFileBlobStore>, NetworkHandle, EthEvmConfig>, EthereumEngineValidatorBuilder>>, + NodeHandle< + NodeAdapter< + FullNodeTypesAdapter< + EthereumNode, + Arc, + BlockchainProvider>>, + >, + Components< + FullNodeTypesAdapter< + EthereumNode, + Arc, + BlockchainProvider>>, + >, + reth_network::EthNetworkPrimitives, + Pool< + TransactionValidationTaskExecutor< + EthTransactionValidator< + BlockchainProvider< + NodeTypesWithDBAdapter>, + >, + EthPooledTransaction, + >, + >, + CoinbaseTipOrdering, + DiskFileBlobStore, + >, + EthEvmConfig, + BasicBlockExecutorProvider, + Arc, + >, + >, + RpcAddOns< + NodeAdapter< + FullNodeTypesAdapter< + EthereumNode, + Arc, + BlockchainProvider>>, + >, + Components< + FullNodeTypesAdapter< + EthereumNode, + Arc, + BlockchainProvider>>, + >, + reth_network::EthNetworkPrimitives, + Pool< + TransactionValidationTaskExecutor< + EthTransactionValidator< + BlockchainProvider< + NodeTypesWithDBAdapter>, + >, + EthPooledTransaction, + >, + >, + CoinbaseTipOrdering, + DiskFileBlobStore, + >, + EthEvmConfig, + BasicBlockExecutorProvider, + Arc, + >, + >, + EthApi< + BlockchainProvider>>, + Pool< + TransactionValidationTaskExecutor< + EthTransactionValidator< + BlockchainProvider< + NodeTypesWithDBAdapter>, + >, + EthPooledTransaction, + >, + >, + CoinbaseTipOrdering, + DiskFileBlobStore, + >, + NetworkHandle, + EthEvmConfig, + >, + EthereumEngineValidatorBuilder, + >, + >, + TaskManager, ) { let tasks = TaskManager::current(); @@ -252,7 +375,7 @@ 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. @@ -273,11 +396,17 @@ async fn mock_eth_server_start(methods: impl Into) -> (ServerHandle, So /// Eth server mock for local testing pub mod eth_server { + use std::sync::Arc; + use alloy_rlp::Bytes; use did::keccak; use ethereum_json_rpc_client::CertifiedResult; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; + use reth_transaction_pool::test_utils::MockTransaction; 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 { @@ -293,6 +422,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>; @@ -311,12 +444,19 @@ 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>>, } impl EthImpl { /// Create a new Eth server implementation pub fn new() -> Self { - Self { gas_price: rand::random(), max_priority_fee_per_gas: rand::random() } + Self { + gas_price: rand::random(), + max_priority_fee_per_gas: rand::random(), + last_get_tx_by_hash: Default::default(), + } } } @@ -341,6 +481,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(vec![ (Address::from_slice(&[1u8; 20]), U256::from(10)), 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