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: Prevent contiguous blockchain storage from bitrotting #654

Merged
merged 2 commits into from
Sep 10, 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
6 changes: 5 additions & 1 deletion crates/edr_evm/src/blockchain/storage.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
mod contiguous;
mod reservable;
mod sparse;

use edr_eth::B256;

pub use self::{reservable::ReservableSparseBlockchainStorage, sparse::SparseBlockchainStorage};
pub use self::{
contiguous::ContiguousBlockchainStorage, reservable::ReservableSparseBlockchainStorage,
sparse::SparseBlockchainStorage,
};

/// An error that occurs when trying to insert a block into storage.
#[derive(Debug, thiserror::Error)]
Expand Down
70 changes: 47 additions & 23 deletions crates/edr_evm/src/blockchain/storage/contiguous.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
use std::sync::Arc;
//! A more cache-friendly block storage implementation.
//!
//! While this does not support block reservations, which we require[^1], it
//! still may be useful for applications that do not.
//!
//! [^1]: for that, we internally use the sparse implementation via
//! [`SparseBlockchainStorage`](super::sparse::SparseBlockchainStorage).

use edr_eth::{receipt::BlockReceipt, B256, U256};
use revm::primitives::HashMap;
use std::{marker::PhantomData, sync::Arc};

use crate::{Block, LocalBlock};
use edr_eth::{receipt::BlockReceipt, transaction::Transaction, B256, U256};
use revm::primitives::HashMap;

use super::InsertError;
use crate::{chain_spec::ChainSpec, Block, LocalBlock};

/// A storage solution for storing a Blockchain's blocks contiguously in-memory.
#[derive(Clone, Default, Debug)]
pub struct ContiguousBlockchainStorage<BlockT: Block + Clone + ?Sized> {
pub struct ContiguousBlockchainStorage<BlockT, ChainSpecT>
where
BlockT: Block<ChainSpecT> + Clone + ?Sized,
ChainSpecT: ChainSpec,
{
blocks: Vec<BlockT>,
hash_to_block: HashMap<B256, BlockT>,
total_difficulties: Vec<U256>,
transaction_hash_to_block: HashMap<B256, BlockT>,
transaction_hash_to_receipt: HashMap<B256, Arc<BlockReceipt>>,
phantom: PhantomData<ChainSpecT>,
}

impl<BlockT: Block + Clone> ContiguousBlockchainStorage<BlockT> {
impl<BlockT, ChainSpecT> ContiguousBlockchainStorage<BlockT, ChainSpecT>
where
BlockT: Block<ChainSpecT> + Clone,
ChainSpecT: ChainSpec,
{
/// Retrieves the instance's blocks.
pub fn blocks(&self) -> &[BlockT] {
&self.blocks
Expand All @@ -28,20 +44,23 @@ impl<BlockT: Block + Clone> ContiguousBlockchainStorage<BlockT> {
self.hash_to_block.get(hash)
}

/// Retrieves the block that contains the transaction with the provided hash, if it exists.
/// Retrieves the block that contains the transaction with the provided
/// hash, if it exists.
pub fn block_by_transaction_hash(&self, transaction_hash: &B256) -> Option<&BlockT> {
self.transaction_hash_to_block.get(transaction_hash)
}

/// Retrieves the receipt of the transaction with the provided hash, if it exists.
/// Retrieves the receipt of the transaction with the provided hash, if it
/// exists.
pub fn receipt_by_transaction_hash(
&self,
transaction_hash: &B256,
) -> Option<&Arc<BlockReceipt>> {
self.transaction_hash_to_receipt.get(transaction_hash)
}

/// Reverts to the block with the provided number, deleting all later blocks.
/// Reverts to the block with the provided number, deleting all later
/// blocks.
pub fn revert_to_block(&mut self, block_number: &U256) -> bool {
let block_number = usize::try_from(block_number)
.expect("No blocks with a number larger than usize::MAX are inserted");
Expand Down Expand Up @@ -101,10 +120,14 @@ impl<BlockT: Block + Clone> ContiguousBlockchainStorage<BlockT> {
}
}

impl<BlockT: Block + Clone + From<LocalBlock>> ContiguousBlockchainStorage<BlockT> {
impl<BlockT, ChainSpecT> ContiguousBlockchainStorage<BlockT, ChainSpecT>
where
BlockT: Block<ChainSpecT> + Clone + From<LocalBlock<ChainSpecT>>,
ChainSpecT: ChainSpec,
{
/// Constructs a new instance with the provided block.
pub fn with_block(block: LocalBlock, total_difficulty: U256) -> Self {
let block_hash = block.hash();
pub fn with_block(block: LocalBlock<ChainSpecT>, total_difficulty: U256) -> Self {
let block_hash = *block.hash();

let transaction_hash_to_receipt = block
.transaction_receipts()
Expand All @@ -117,31 +140,32 @@ impl<BlockT: Block + Clone + From<LocalBlock>> ContiguousBlockchainStorage<Block
let transaction_hash_to_block = block
.transactions()
.iter()
.map(|transaction| (transaction.hash(), block.clone()))
.map(|transaction| (*transaction.transaction_hash(), block.clone()))
.collect();

let mut hash_to_block = HashMap::new();
hash_to_block.insert(*block_hash, block.clone());
hash_to_block.insert(block_hash, block.clone());

Self {
total_difficulties: vec![total_difficulty],
blocks: vec![block],
hash_to_block,
transaction_hash_to_block,
transaction_hash_to_receipt,
phantom: PhantomData,
}
}

/// Inserts a block, failing if a block with the same hash already exists.
pub fn insert_block(
&mut self,
block: LocalBlock,
block: LocalBlock<ChainSpecT>,
total_difficulty: U256,
) -> Result<&BlockT, InsertError> {
let block_hash = block.hash();

// As blocks are contiguous, we are guaranteed that the block number won't exist if its
// hash is not present.
// As blocks are contiguous, we are guaranteed that the block number won't exist
// if its hash is not present.
if self.hash_to_block.contains_key(block_hash) {
return Err(InsertError::DuplicateBlock {
block_hash: *block_hash,
Expand All @@ -151,10 +175,10 @@ impl<BlockT: Block + Clone + From<LocalBlock>> ContiguousBlockchainStorage<Block

if let Some(transaction) = block.transactions().iter().find(|transaction| {
self.transaction_hash_to_block
.contains_key(&transaction.hash())
.contains_key(transaction.transaction_hash())
}) {
return Err(InsertError::DuplicateTransaction {
hash: transaction.hash(),
hash: *transaction.transaction_hash(),
});
}

Expand All @@ -166,11 +190,11 @@ impl<BlockT: Block + Clone + From<LocalBlock>> ContiguousBlockchainStorage<Block
///
/// # Safety
///
/// Ensure that the instance does not contain a block with the same hash, nor
/// any transactions with the same hash.
/// Ensure that the instance does not contain a block with the same hash,
/// nor any transactions with the same hash.
pub unsafe fn insert_block_unchecked(
&mut self,
block: LocalBlock,
block: LocalBlock<ChainSpecT>,
total_difficulty: U256,
) -> &BlockT {
self.transaction_hash_to_receipt.extend(
Expand All @@ -186,7 +210,7 @@ impl<BlockT: Block + Clone + From<LocalBlock>> ContiguousBlockchainStorage<Block
block
.transactions()
.iter()
.map(|transaction| (transaction.hash(), block.clone())),
.map(|transaction| (*transaction.transaction_hash(), block.clone())),
);

self.blocks.push(block.clone());
Expand Down
Loading