Skip to content

Commit

Permalink
fix: Prevent contiguous blockchain storage from bitrotting (#654)
Browse files Browse the repository at this point in the history
* fix: Prevent contiguous blockchain storage from bitrotting

* fixup: Reformat
  • Loading branch information
Xanewok authored Sep 10, 2024
1 parent d57ab75 commit a0601cb
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 24 deletions.
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

0 comments on commit a0601cb

Please sign in to comment.