From 45d5997134e9de548a99a46367023c1ea4625073 Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:10:18 +0530 Subject: [PATCH] fix(`anvil`): set `storage.best_number` correctly (#9215) * feat(`anvil`): persist accounts in `ForkedStorage` * load accounts * test * fix(`anvil`): override storage.best_number with fork_block_num if loading state on a fork url * Revert "load accounts" This reverts commit b650f56fe52f79be3eb7c8ab4d2ad1aaca08a32f. * Revert "feat(`anvil`): persist accounts in `ForkedStorage`" This reverts commit 456da156e07b1ede01c08c4f48ef36eed4094f17. * nit --------- Co-authored-by: grandizzy * nit --------- Co-authored-by: grandizzy --- crates/anvil/src/eth/backend/mem/mod.rs | 32 +++++---- crates/anvil/tests/it/state.rs | 94 ++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 14 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 73adaa97f3fd..65ab8c32952b 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -907,19 +907,27 @@ impl Backend { // Set the current best block number. // Defaults to block number for compatibility with existing state files. + let fork_num_and_hash = self.get_fork().map(|f| (f.block_number(), f.block_hash())); - let best_number = state.best_block_number.unwrap_or(block.number.to::()); - self.blockchain.storage.write().best_number = best_number; - - // Set the current best block hash; - let best_hash = - self.blockchain.storage.read().hash(best_number.into()).ok_or_else(|| { - BlockchainError::RpcError(RpcError::internal_error_with(format!( - "Best hash not found for best number {best_number}", - ))) - })?; - - self.blockchain.storage.write().best_hash = best_hash; + if let Some((number, hash)) = fork_num_and_hash { + // If loading state file on a fork, set best number to the fork block number. + // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838 + self.blockchain.storage.write().best_number = U64::from(number); + self.blockchain.storage.write().best_hash = hash; + } else { + let best_number = state.best_block_number.unwrap_or(block.number.to::()); + self.blockchain.storage.write().best_number = best_number; + + // Set the current best block hash; + let best_hash = + self.blockchain.storage.read().hash(best_number.into()).ok_or_else(|| { + BlockchainError::RpcError(RpcError::internal_error_with(format!( + "Best hash not found for best number {best_number}", + ))) + })?; + + self.blockchain.storage.write().best_hash = best_hash; + } } if !self.db.write().await.load_state(state.clone())? { diff --git a/crates/anvil/tests/it/state.rs b/crates/anvil/tests/it/state.rs index cb8f3b9ebbca..f7736de2f403 100644 --- a/crates/anvil/tests/it/state.rs +++ b/crates/anvil/tests/it/state.rs @@ -1,10 +1,13 @@ //! general eth api tests use crate::abi::Greeter; -use alloy_primitives::{Bytes, Uint, U256}; +use alloy_network::{ReceiptResponse, TransactionBuilder}; +use alloy_primitives::{address, utils::Unit, Bytes, Uint, U256}; use alloy_provider::Provider; -use alloy_rpc_types::BlockId; +use alloy_rpc_types::{BlockId, TransactionRequest}; +use alloy_serde::WithOtherFields; use anvil::{spawn, NodeConfig}; +use foundry_test_utils::rpc::next_http_rpc_endpoint; #[tokio::test(flavor = "multi_thread")] async fn can_load_state() { @@ -155,3 +158,90 @@ async fn can_preserve_historical_states_between_dump_and_load() { assert_eq!(greeting_after_change, "World!"); } + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_load_state() { + let (api, handle) = spawn( + NodeConfig::test() + .with_eth_rpc_url(Some(next_http_rpc_endpoint())) + .with_fork_block_number(Some(21070682u64)), + ) + .await; + + let bob = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let alice = address!("9276449EaC5b4f7Bc17cFC6700f7BeeB86F9bCd0"); + + let provider = handle.http_provider(); + + let init_nonce_bob = provider.get_transaction_count(bob).await.unwrap(); + + let init_balance_alice = provider.get_balance(alice).await.unwrap(); + + let value = Unit::ETHER.wei().saturating_mul(U256::from(1)); // 1 ether + let tx = TransactionRequest::default().with_to(alice).with_value(value).with_from(bob); + let tx = WithOtherFields::new(tx); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + + let serialized_state = api.serialized_state(false).await.unwrap(); + + let state_dump_block = api.block_number().unwrap(); + + let (api, handle) = spawn( + NodeConfig::test() + .with_eth_rpc_url(Some(next_http_rpc_endpoint())) + .with_fork_block_number(Some(21070686u64)) // Forked chain has moved forward + .with_init_state(Some(serialized_state)), + ) + .await; + + // Ensure the initial block number is the fork_block_number and not the state_dump_block + let block_number = api.block_number().unwrap(); + assert_eq!(block_number, U256::from(21070686u64)); + assert_ne!(block_number, state_dump_block); + + let provider = handle.http_provider(); + + let restart_nonce_bob = provider.get_transaction_count(bob).await.unwrap(); + + let restart_balance_alice = provider.get_balance(alice).await.unwrap(); + + assert_eq!(init_nonce_bob + 1, restart_nonce_bob); + + assert_eq!(init_balance_alice + value, restart_balance_alice); + + // Send another tx to check if the state is preserved + + let tx = TransactionRequest::default().with_to(alice).with_value(value).with_from(bob); + let tx = WithOtherFields::new(tx); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + + let nonce_bob = provider.get_transaction_count(bob).await.unwrap(); + + let balance_alice = provider.get_balance(alice).await.unwrap(); + + let tx = TransactionRequest::default() + .with_to(alice) + .with_value(value) + .with_from(bob) + .with_nonce(nonce_bob); + let tx = WithOtherFields::new(tx); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + + let latest_nonce_bob = provider.get_transaction_count(bob).await.unwrap(); + + let latest_balance_alice = provider.get_balance(alice).await.unwrap(); + + assert_eq!(nonce_bob + 1, latest_nonce_bob); + + assert_eq!(balance_alice + value, latest_balance_alice); +}