Skip to content

Commit

Permalink
fix(anvil): block dumps (foundry-rs#8160)
Browse files Browse the repository at this point in the history
* implemented latest_block dump/load

* update to dump/load all blocks instead of only latest

* refactored state loading into storage.rs, and added load-dump cycle test

* fix clippy errors for anvil

* remove SerializableHeader and use Header (now serializable)

* clippy happy

---------

Co-authored-by: Matthias Seitz <[email protected]>
  • Loading branch information
2 people authored and klkvr committed Jun 19, 2024
1 parent 3d3f104 commit 5424ea1
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 18 deletions.
2 changes: 1 addition & 1 deletion crates/anvil/core/src/eth/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ impl PendingTransaction {
}

/// Container type for signed, typed transactions.
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TypedTransaction {
/// Legacy transaction type
Legacy(Signed<TxLegacy>),
Expand Down
3 changes: 2 additions & 1 deletion crates/anvil/core/src/eth/transaction/optimism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use alloy_primitives::{keccak256, Address, Bytes, ChainId, Signature, TxKind, B2
use alloy_rlp::{
length_of_length, Decodable, Encodable, Error as DecodeError, Header as RlpHeader,
};
use serde::{Deserialize, Serialize};
use std::mem;

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -228,7 +229,7 @@ impl Encodable for DepositTransactionRequest {

/// An op-stack deposit transaction.
/// See <https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#the-deposited-transaction-type>
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DepositTransaction {
pub nonce: u64,
pub source_hash: B256,
Expand Down
33 changes: 33 additions & 0 deletions crates/anvil/src/eth/backend/db.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Helper types for working with [revm](foundry_evm::revm)
use crate::revm::primitives::AccountInfo;
use alloy_consensus::Header;
use alloy_primitives::{keccak256, Address, Bytes, B256, U256, U64};
use alloy_rpc_types::BlockId;
use anvil_core::eth::{block::Block, transaction::TypedTransaction};
use foundry_common::errors::FsPathError;
use foundry_evm::{
backend::{DatabaseError, DatabaseResult, MemDb, RevertSnapshotAction, StateSnapshot},
Expand Down Expand Up @@ -116,6 +118,7 @@ pub trait Db:
&self,
at: BlockEnv,
best_number: U64,
blocks: Vec<SerializableBlock>,
) -> DatabaseResult<Option<SerializableState>>;

/// Deserialize and add all chain data to the backend storage
Expand Down Expand Up @@ -188,6 +191,7 @@ impl<T: DatabaseRef<Error = DatabaseError> + Send + Sync + Clone + fmt::Debug> D
&self,
_at: BlockEnv,
_best_number: U64,
_blocks: Vec<SerializableBlock>,
) -> DatabaseResult<Option<SerializableState>> {
Ok(None)
}
Expand Down Expand Up @@ -318,6 +322,8 @@ pub struct SerializableState {
pub accounts: BTreeMap<Address, SerializableAccountRecord>,
/// The best block number of the state, can be different from block number (Arbitrum chain).
pub best_block_number: Option<U64>,
#[serde(default)]
pub blocks: Vec<SerializableBlock>,
}

impl SerializableState {
Expand All @@ -344,3 +350,30 @@ pub struct SerializableAccountRecord {
pub code: Bytes,
pub storage: BTreeMap<U256, U256>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SerializableBlock {
pub header: Header,
pub transactions: Vec<TypedTransaction>,
pub ommers: Vec<Header>,
}

impl From<Block> for SerializableBlock {
fn from(block: Block) -> Self {
Self {
header: block.header,
transactions: block.transactions.into_iter().map(Into::into).collect(),
ommers: block.ommers.into_iter().map(Into::into).collect(),
}
}
}

impl From<SerializableBlock> for Block {
fn from(block: SerializableBlock) -> Self {
Self {
header: block.header,
transactions: block.transactions.into_iter().map(Into::into).collect(),
ommers: block.ommers.into_iter().map(Into::into).collect(),
}
}
}
6 changes: 4 additions & 2 deletions crates/anvil/src/eth/backend/mem/fork_db.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
eth::backend::db::{
Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableState,
StateDb,
Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock,
SerializableState, StateDb,
},
revm::primitives::AccountInfo,
};
Expand Down Expand Up @@ -36,6 +36,7 @@ impl Db for ForkedDatabase {
&self,
at: BlockEnv,
best_number: U64,
blocks: Vec<SerializableBlock>,
) -> DatabaseResult<Option<SerializableState>> {
let mut db = self.database().clone();
let accounts = self
Expand Down Expand Up @@ -64,6 +65,7 @@ impl Db for ForkedDatabase {
block: Some(at),
accounts,
best_block_number: Some(best_number),
blocks,
}))
}

Expand Down
13 changes: 7 additions & 6 deletions crates/anvil/src/eth/backend/mem/in_memory_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
use crate::{
eth::backend::db::{
Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableState,
StateDb,
Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock,
SerializableState, StateDb,
},
mem::state::state_root,
revm::{db::DbAccount, primitives::AccountInfo},
Expand Down Expand Up @@ -37,6 +37,7 @@ impl Db for MemDb {
&self,
at: BlockEnv,
best_number: U64,
blocks: Vec<SerializableBlock>,
) -> DatabaseResult<Option<SerializableState>> {
let accounts = self
.inner
Expand Down Expand Up @@ -65,6 +66,7 @@ impl Db for MemDb {
block: Some(at),
accounts,
best_block_number: Some(best_number),
blocks,
}))
}

Expand Down Expand Up @@ -137,7 +139,7 @@ mod tests {
use foundry_evm::revm::primitives::{Bytecode, KECCAK_EMPTY};
use std::{collections::BTreeMap, str::FromStr};

// verifies that all substantial aspects of a loaded account remain the state after an account
// verifies that all substantial aspects of a loaded account remain the same after an account
// is dumped and reloaded
#[test]
fn test_dump_reload_cycle() {
Expand All @@ -147,7 +149,6 @@ mod tests {
let mut dump_db = MemDb::default();

let contract_code = Bytecode::new_raw(Bytes::from("fake contract code"));

dump_db.insert_account(
test_addr,
AccountInfo {
Expand All @@ -157,10 +158,10 @@ mod tests {
nonce: 1234,
},
);

dump_db.set_storage_at(test_addr, U256::from(1234567), U256::from(1)).unwrap();

let state = dump_db.dump_state(Default::default(), U64::ZERO).unwrap().unwrap();
// blocks dumping/loading tested in storage.rs
let state = dump_db.dump_state(Default::default(), U64::ZERO, Vec::new()).unwrap().unwrap();

let mut load_db = MemDb::default();

Expand Down
15 changes: 9 additions & 6 deletions crates/anvil/src/eth/backend/mem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,8 @@ impl Backend {
pub async fn serialized_state(&self) -> Result<SerializableState, BlockchainError> {
let at = self.env.read().block.clone();
let best_number = self.blockchain.storage.read().best_number;
let state = self.db.read().await.dump_state(at, best_number)?;
let blocks = self.blockchain.storage.read().serialized_blocks();
let state = self.db.read().await.dump_state(at, best_number, blocks)?;
state.ok_or_else(|| {
RpcError::invalid_params("Dumping state not supported with the current configuration")
.into()
Expand Down Expand Up @@ -766,14 +767,16 @@ impl Backend {
state.best_block_number.unwrap_or(block.number.to::<U64>());
}

if !self.db.write().await.load_state(state)? {
Err(RpcError::invalid_params(
if !self.db.write().await.load_state(state.clone())? {
return Err(RpcError::invalid_params(
"Loading state not supported with the current configuration",
)
.into())
} else {
Ok(true)
.into());
}

self.blockchain.storage.write().load_blocks(state.blocks.clone());

Ok(true)
}

/// Deserialize and add all chain data to the backend storage
Expand Down
50 changes: 48 additions & 2 deletions crates/anvil/src/eth/backend/mem/storage.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! In-memory blockchain storage
use crate::eth::{
backend::{
db::{MaybeFullDatabase, StateDb},
db::{MaybeFullDatabase, SerializableBlock, StateDb},
mem::cache::DiskStateCache,
},
pool::transactions::PoolTransaction,
Expand Down Expand Up @@ -319,6 +319,21 @@ impl BlockchainStorage {
}
}
}

pub fn serialized_blocks(&self) -> Vec<SerializableBlock> {
self.blocks.values().map(|block| block.clone().into()).collect()
}

/// Deserialize and add all blocks data to the backend storage
pub fn load_blocks(&mut self, serializable_blocks: Vec<SerializableBlock>) {
for serializable_block in serializable_blocks.iter() {
let block: Block = serializable_block.clone().into();
let block_hash = block.header.hash_slow();
let block_number = block.header.number;
self.blocks.insert(block_hash, block);
self.hashes.insert(U64::from(block_number), block_hash);
}
}
}

/// A simple in-memory blockchain
Expand Down Expand Up @@ -427,7 +442,9 @@ pub struct MinedTransactionReceipt {
mod tests {
use super::*;
use crate::eth::backend::db::Db;
use alloy_primitives::Address;
use alloy_primitives::{hex, Address};
use alloy_rlp::Decodable;
use anvil_core::eth::transaction::TypedTransaction;
use foundry_evm::{
backend::MemDb,
revm::{
Expand Down Expand Up @@ -499,4 +516,33 @@ mod tests {
assert_eq!(acc.balance, rU256::from(balance));
}
}

// verifies that blocks in BlockchainStorage remain the same when dumped and reloaded
#[test]
fn test_storage_dump_reload_cycle() {
let mut dump_storage = BlockchainStorage::empty();

let partial_header = PartialHeader { gas_limit: 123456, ..Default::default() };
let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
let tx: MaybeImpersonatedTransaction =
TypedTransaction::decode(&mut &bytes_first[..]).unwrap().into();
let block = Block::new::<MaybeImpersonatedTransaction>(
partial_header.clone(),
vec![tx.clone()],
vec![],
);
let block_hash = block.header.hash_slow();
dump_storage.blocks.insert(block_hash, block);

let serialized_blocks = dump_storage.serialized_blocks();

let mut load_storage = BlockchainStorage::empty();

load_storage.load_blocks(serialized_blocks);

let loaded_block = load_storage.blocks.get(&block_hash).unwrap();
assert_eq!(loaded_block.header.gas_limit, partial_header.gas_limit);
let loaded_tx = loaded_block.transactions.first().unwrap();
assert_eq!(loaded_tx, &tx);
}
}

0 comments on commit 5424ea1

Please sign in to comment.