Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(anvil): block dumps #8160

Merged
merged 6 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)?;
Comment on lines +740 to +741
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is the best implementation. Reading the blocks and then passing them to the dump_state function in db to also include them in the dumped json.

Will want to also dump events soon, so the db dump_state function might become cluttered and coupled. An alternative design is to pass mut references to a serialized_state to db, storage, event services, and have them add whatever information they are dumping, so that we can partially construct the serialized_state?

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() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added this unit test for storage, but now that we are dumping and reloading both the db and storage, we might need an integration test in mod.rs for the dumping/reloading of the entire serialized_state?

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);
}
}
Loading