@@ -18,6 +18,7 @@ use alloy_rpc_types::{
1818 anvil:: Forking ,
1919 request:: { TransactionInput , TransactionRequest } ,
2020 state:: EvmOverrides ,
21+ trace:: parity:: { Action , TraceResultsWithTransactionHash , TraceType } ,
2122} ;
2223use alloy_serde:: WithOtherFields ;
2324use alloy_signer_local:: PrivateKeySigner ;
@@ -228,6 +229,57 @@ async fn test_fork_debug_account_info_at() {
228229 assert_eq ! ( by_tag_after. unwrap( ) . balance, amount) ;
229230}
230231
232+ // Pre-fork `trace_replayBlockTransactions` must be forwarded to the upstream with the trace types
233+ // serialized as their camelCase JSON names (`trace`, `stateDiff`), not their Rust `Debug`
234+ // representation, otherwise the upstream rejects the request.
235+ #[ tokio:: test( flavor = "multi_thread" ) ]
236+ async fn test_fork_trace_replay_block_transactions_forwards_trace_types ( ) {
237+ // Use a local anvil node as the upstream so the request path is fully exercised in-process.
238+ let ( _origin_api, origin_handle) = spawn ( NodeConfig :: test ( ) ) . await ;
239+ let origin_provider = origin_handle. http_provider ( ) ;
240+
241+ let account = origin_handle. dev_wallets ( ) . next ( ) . unwrap ( ) . address ( ) ;
242+ let to = Address :: random ( ) ;
243+ let amount = U256 :: from ( 1_000u64 ) ;
244+
245+ // Mine two blocks on the upstream; the first strictly predates the fork head.
246+ let mut first_block = None ;
247+ for _ in 0 ..2 {
248+ let tx = TransactionRequest :: default ( ) . from ( account) . to ( to) . value ( amount) ;
249+ let tx = WithOtherFields :: new ( tx) ;
250+ let receipt =
251+ origin_provider. send_transaction ( tx) . await . unwrap ( ) . get_receipt ( ) . await . unwrap ( ) ;
252+ first_block. get_or_insert ( receipt. block_number . unwrap ( ) ) ;
253+ }
254+ let pre_fork_block = first_block. unwrap ( ) ;
255+
256+ // Fork from the upstream head so `pre_fork_block` is delegated upstream.
257+ let ( _fork_api, fork_handle) =
258+ spawn ( NodeConfig :: test ( ) . with_eth_rpc_url ( Some ( origin_handle. http_endpoint ( ) ) ) ) . await ;
259+ let fork_provider = fork_handle. http_provider ( ) ;
260+
261+ let results: Vec < TraceResultsWithTransactionHash > = fork_provider
262+ . client ( )
263+ . request (
264+ "trace_replayBlockTransactions" ,
265+ ( pre_fork_block, vec ! [ TraceType :: Trace , TraceType :: StateDiff ] ) ,
266+ )
267+ . await
268+ . unwrap ( ) ;
269+
270+ assert_eq ! ( results. len( ) , 1 ) ;
271+ let full_trace = & results[ 0 ] . full_trace ;
272+ match & full_trace. trace [ 0 ] . action {
273+ Action :: Call ( call) => {
274+ assert_eq ! ( call. from, account) ;
275+ assert_eq ! ( call. to, to) ;
276+ }
277+ other => panic ! ( "expected Call action, got {other:?}" ) ,
278+ }
279+ // `StateDiff` was also requested, so it must be honored, not just `Trace`.
280+ assert ! ( full_trace. state_diff. is_some( ) ) ;
281+ }
282+
231283#[ tokio:: test( flavor = "multi_thread" ) ]
232284async fn test_spawn_fork ( ) {
233285 let ( api, _handle) = spawn ( fork_config ( ) ) . await ;
0 commit comments