Skip to content
Open
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: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ alloy-contract = { version = "1.4.0", features = ["pubsub"] }
reth = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
reth-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
reth-codecs = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
reth-db-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
reth-db-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
reth-eth-wire-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
Expand Down
17 changes: 17 additions & 0 deletions crates/storage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "signet-storage"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true

[dependencies]
alloy.workspace = true
bytes = "1.11.0"
reth.workspace = true
reth-db.workspace = true
reth-db-api.workspace = true
thiserror.workspace = true
17 changes: 17 additions & 0 deletions crates/storage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Signet Storage

High-level API for Signet's storage layer

This library contains the following:

- Traits for serializing and deserializing Signet data structures as DB keys/
value.
- Traits for hot and cold storage operations.
- Relevant KV table definitions.

## Significant Traits

- `HotKv` - Encapsulates logic for reading and writing to hot storage.
- `ColdKv` - Encapsulates logic for reading and writing to cold storage.
- `KeySer` - Provides methods for serializing a type as a DB key.
- `ValueSer` - Provides methods for serializing a type as a DB value.
1 change: 1 addition & 0 deletions crates/storage/src/cold/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//! Placeholder module for cold storage implementation.
87 changes: 87 additions & 0 deletions crates/storage/src/hot/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use crate::hot::{HotKv, HotKvError, HotKvRead, HotKvWrite};
use std::{
borrow::Cow,
sync::atomic::{AtomicBool, Ordering},
};

/// Hot database wrapper around a key-value storage.
#[derive(Debug)]
pub struct HotDb<Inner> {
inner: Inner,

write_locked: AtomicBool,
}

impl<Inner> HotDb<Inner> {
/// Create a new HotDb wrapping the given inner KV storage.
pub const fn new(inner: Inner) -> Self {
Self { inner, write_locked: AtomicBool::new(false) }
}

/// Get a read-only handle.
pub fn reader(&self) -> Result<Inner::RoTx, HotKvError>
where
Inner: HotKv,
{
self.inner.reader()
}

/// Get a write handle, if available. If not available, returns
/// [`HotKvError::WriteLocked`].
pub fn writer(&self) -> Result<WriteGuard<'_, Inner>, HotKvError>
where
Inner: HotKv,
{
if self.write_locked.swap(true, Ordering::AcqRel) {
return Err(HotKvError::WriteLocked);
}
self.inner.writer().map(Some).map(|tx| WriteGuard { tx, db: self })
}
}

/// Write guard for a write transaction.
#[derive(Debug)]
pub struct WriteGuard<'a, Inner>
where
Inner: HotKv,
{
tx: Option<Inner::RwTx>,
db: &'a HotDb<Inner>,
}

impl<Inner> Drop for WriteGuard<'_, Inner>
where
Inner: HotKv,
{
fn drop(&mut self) {
self.db.write_locked.store(false, Ordering::Release);
}
}

impl<Inner> HotKvRead for WriteGuard<'_, Inner>
where
Inner: HotKv,
{
type Error = <<Inner as HotKv>::RwTx as HotKvRead>::Error;

fn get_raw<'a>(
&'a self,
table: &str,
key: &[u8],
) -> Result<Option<Cow<'a, [u8]>>, Self::Error> {
self.tx.as_ref().expect("present until drop").get_raw(table, key)
}
}

impl<Inner> HotKvWrite for WriteGuard<'_, Inner>
where
Inner: HotKv,
{
fn queue_raw_put(&mut self, table: &str, key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
self.tx.as_mut().expect("present until drop").queue_raw_put(table, key, value)
}

fn raw_commit(mut self) -> Result<(), Self::Error> {
self.tx.take().expect("present until drop").raw_commit()
}
}
167 changes: 167 additions & 0 deletions crates/storage/src/hot/db_traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use crate::{
hot::{HotKvRead, HotKvWrite},
tables::hot::{self as tables, AccountStorageKey},
};
use alloy::primitives::{Address, B256, U256};
use reth::primitives::{Account, Bytecode, Header, SealedHeader, StorageEntry};
use std::borrow::Cow;

/// Trait for database read operations.
pub trait HotDbReader: sealed::Sealed {
/// The error type for read operations
type Error: std::error::Error + Send + Sync + 'static + From<crate::ser::DeserError>;

/// Read a block header by its number.
fn get_header(&self, number: u64) -> Result<Option<Header>, Self::Error>;

/// Read a block number by its hash.
fn get_header_number(&self, hash: &B256) -> Result<Option<u64>, Self::Error>;

/// Read the canonical hash by block number.
fn get_canonical_hash(&self, number: u64) -> Result<Option<B256>, Self::Error>;

/// Read contract Bytecode by its hash.
fn get_bytecode(&self, code_hash: &B256) -> Result<Option<Bytecode>, Self::Error>;

/// Read an account by its address.
fn get_account(&self, address: &Address) -> Result<Option<Account>, Self::Error>;

/// Read a storage slot by its address and key.
fn get_storage(&self, address: &Address, key: &B256) -> Result<Option<U256>, Self::Error>;

/// Read a [`StorageEntry`] by its address and key.
fn get_storage_entry(
&self,
address: &Address,
key: &B256,
) -> Result<Option<StorageEntry>, Self::Error> {
let opt = self.get_storage(address, key)?;
Ok(opt.map(|value| StorageEntry { key: *key, value }))
}
}

impl<T> HotDbReader for T
where
T: HotKvRead,
{
type Error = <T as HotKvRead>::Error;

fn get_header(&self, number: u64) -> Result<Option<Header>, Self::Error> {
self.get::<tables::Headers>(&number)
}

fn get_header_number(&self, hash: &B256) -> Result<Option<u64>, Self::Error> {
self.get::<tables::HeaderNumbers>(hash)
}

fn get_canonical_hash(&self, number: u64) -> Result<Option<B256>, Self::Error> {
self.get::<tables::CanonicalHeaders>(&number)
}

fn get_bytecode(&self, code_hash: &B256) -> Result<Option<Bytecode>, Self::Error> {
self.get::<tables::Bytecodes>(code_hash)
}

fn get_account(&self, address: &Address) -> Result<Option<Account>, Self::Error> {
self.get::<tables::PlainAccountState>(address)
}

fn get_storage(&self, address: &Address, key: &B256) -> Result<Option<U256>, Self::Error> {
let storage_key = AccountStorageKey {
address: std::borrow::Cow::Borrowed(address),
key: std::borrow::Cow::Borrowed(key),
};
let key = storage_key.encode_key();
self.get::<tables::PlainStorageState>(&key)
}
}

/// Trait for database write operations.
pub trait HotDbWriter: sealed::Sealed {
/// The error type for write operations
type Error: std::error::Error + Send + Sync + 'static + From<crate::ser::DeserError>;

/// Read the latest block header.
fn put_header(&mut self, header: &Header) -> Result<(), Self::Error>;

/// Write a block number by its hash.
fn put_header_number(&mut self, hash: &B256, number: u64) -> Result<(), Self::Error>;

/// Write the canonical hash by block number.
fn put_canonical_hash(&mut self, number: u64, hash: &B256) -> Result<(), Self::Error>;

/// Write contract Bytecode by its hash.
fn put_bytecode(&mut self, code_hash: &B256, bytecode: &Bytecode) -> Result<(), Self::Error>;

/// Write an account by its address.
fn put_account(&mut self, address: &Address, account: &Account) -> Result<(), Self::Error>;

/// Write a storage entry by its address and key.
fn put_storage(
&mut self,
address: &Address,
key: &B256,
entry: &U256,
) -> Result<(), Self::Error>;

/// Commit the write transaction.
fn commit(self) -> Result<(), Self::Error>;

/// Write a canonical header (header, number mapping, and canonical hash).
fn put_canonical(&mut self, header: &SealedHeader) -> Result<(), Self::Error> {
self.put_header(header)?;
self.put_header_number(&header.hash(), header.number)?;
self.put_canonical_hash(header.number, &header.hash())
}
}

impl<T> HotDbWriter for T
where
T: HotKvWrite,
{
type Error = <T as HotKvRead>::Error;

fn put_header(&mut self, header: &Header) -> Result<(), Self::Error> {
self.queue_put::<tables::Headers>(&header.number, header)
}

fn put_header_number(&mut self, hash: &B256, number: u64) -> Result<(), Self::Error> {
self.queue_put::<tables::HeaderNumbers>(hash, &number)
}

fn put_canonical_hash(&mut self, number: u64, hash: &B256) -> Result<(), Self::Error> {
self.queue_put::<tables::CanonicalHeaders>(&number, hash)
}

fn put_bytecode(&mut self, code_hash: &B256, bytecode: &Bytecode) -> Result<(), Self::Error> {
self.queue_put::<tables::Bytecodes>(code_hash, bytecode)
}

fn put_account(&mut self, address: &Address, account: &Account) -> Result<(), Self::Error> {
self.queue_put::<tables::PlainAccountState>(address, account)
}

fn put_storage(
&mut self,
address: &Address,
key: &B256,
entry: &U256,
) -> Result<(), Self::Error> {
let storage_key =
AccountStorageKey { address: Cow::Borrowed(address), key: Cow::Borrowed(key) };
self.queue_put::<tables::PlainStorageState>(&storage_key.encode_key(), entry)
}

fn commit(self) -> Result<(), Self::Error> {
HotKvWrite::raw_commit(self)
}
}

mod sealed {
use crate::hot::HotKvRead;

/// Sealed trait to prevent external implementations of HotDbReader and HotDbWriter.
#[allow(dead_code, unreachable_pub)]
pub trait Sealed {}
impl<T> Sealed for T where T: HotKvRead {}
}
28 changes: 28 additions & 0 deletions crates/storage/src/hot/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/// Trait for hot storage read/write errors.
#[derive(thiserror::Error, Debug)]
pub enum HotKvError {
/// Boxed error. Indicates an issue with the DB backend.
#[error(transparent)]
Inner(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),

/// Deserialization error. Indicates an issue deserializing a key or value.
#[error("Deserialization error: {0}")]
DeserError(#[from] crate::ser::DeserError),

/// Indicates that a write transaction is already in progress.
#[error("A write transaction is already in progress")]
WriteLocked,
}

impl HotKvError {
/// Internal helper to create a `HotKvError::Inner` from any error.
pub fn from_err<E>(err: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
{
HotKvError::Inner(Box::new(err))
}
}

/// Result type for hot storage operations.
pub type HotKvResult<T> = Result<T, HotKvError>;
13 changes: 13 additions & 0 deletions crates/storage/src/hot/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mod db;
pub use db::{HotDb, WriteGuard};

mod db_traits;
pub use db_traits::{HotDbReader, HotDbWriter};

mod error;
pub use error::{HotKvError, HotKvResult};

mod reth_impl;

mod traits;
pub use traits::{HotKv, HotKvRead, HotKvWrite};
32 changes: 32 additions & 0 deletions crates/storage/src/hot/reth_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use std::borrow::Cow;

use crate::{hot::HotKvRead, ser::DeserError};
use reth_db::mdbx::{TransactionKind, tx::Tx};
use reth_db_api::DatabaseError;

impl From<DeserError> for DatabaseError {
fn from(value: DeserError) -> Self {
DatabaseError::Other(value.to_string())
}
}

impl<K> HotKvRead for Tx<K>
where
K: TransactionKind,
{
type Error = DatabaseError;

fn get_raw<'a>(
&'a self,
table: &str,
key: &[u8],
) -> Result<Option<Cow<'a, [u8]>>, Self::Error> {
let dbi = self
.inner
.open_db(Some(table))
.map(|db| db.dbi())
.map_err(|e| DatabaseError::Open(e.into()))?;

self.inner.get(dbi, key.as_ref()).map_err(|err| DatabaseError::Read(err.into()))
}
}
Loading
Loading