Skip to content

Commit f03f120

Browse files
committed
feat: add storage api
1 parent ae6a415 commit f03f120

20 files changed

Lines changed: 1065 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ alloy-contract = { version = "1.4.0", features = ["pubsub"] }
7777
reth = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
7878
reth-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
7979
reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
80+
reth-codecs = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
8081
reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
82+
reth-db-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
8183
reth-db-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
8284
reth-eth-wire-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }
8385
reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.1" }

crates/storage/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "signet-storage"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
authors.workspace = true
7+
license.workspace = true
8+
homepage.workspace = true
9+
repository.workspace = true
10+
11+
[dependencies]
12+
alloy.workspace = true
13+
bytes = "1.11.0"
14+
reth.workspace = true
15+
reth-db.workspace = true
16+
reth-db-api.workspace = true
17+
thiserror.workspace = true

crates/storage/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Signet Storage
2+
3+
High-level API for Signet's storage layer
4+
5+
This library contains the following:
6+
7+
- Traits for serializing and deserializing Signet data structures as DB keys/
8+
value.
9+
- Traits for hot and cold storage operations.
10+
- Relevant KV table definitions.
11+
12+
## Significant Traits
13+
14+
- `HotKv` - Encapsulates logic for reading and writing to hot storage.
15+
- `ColdKv` - Encapsulates logic for reading and writing to cold storage.
16+
- `KeySer` - Provides methods for serializing a type as a DB key.
17+
- `ValueSer` - Provides methods for serializing a type as a DB value.

crates/storage/src/cold/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//! Placeholder module for cold storage implementation.

crates/storage/src/hot/db.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use crate::hot::{HotKv, HotKvError, HotKvRead, HotKvWrite};
2+
use std::{
3+
borrow::Cow,
4+
sync::atomic::{AtomicBool, Ordering},
5+
};
6+
7+
/// Hot database wrapper around a key-value storage.
8+
#[derive(Debug)]
9+
pub struct HotDb<Inner> {
10+
inner: Inner,
11+
12+
write_locked: AtomicBool,
13+
}
14+
15+
impl<Inner> HotDb<Inner> {
16+
/// Create a new HotDb wrapping the given inner KV storage.
17+
pub const fn new(inner: Inner) -> Self {
18+
Self { inner, write_locked: AtomicBool::new(false) }
19+
}
20+
21+
/// Get a read-only handle.
22+
pub fn reader(&self) -> Result<Inner::RoTx, HotKvError>
23+
where
24+
Inner: HotKv,
25+
{
26+
self.inner.reader()
27+
}
28+
29+
/// Get a write handle, if available. If not available, returns
30+
/// [`HotKvError::WriteLocked`].
31+
pub fn writer(&self) -> Result<WriteGuard<'_, Inner>, HotKvError>
32+
where
33+
Inner: HotKv,
34+
{
35+
if self.write_locked.swap(true, Ordering::AcqRel) {
36+
return Err(HotKvError::WriteLocked);
37+
}
38+
self.inner.writer().map(Some).map(|tx| WriteGuard { tx, db: self })
39+
}
40+
}
41+
42+
/// Write guard for a write transaction.
43+
#[derive(Debug)]
44+
pub struct WriteGuard<'a, Inner>
45+
where
46+
Inner: HotKv,
47+
{
48+
tx: Option<Inner::RwTx>,
49+
db: &'a HotDb<Inner>,
50+
}
51+
52+
impl<Inner> Drop for WriteGuard<'_, Inner>
53+
where
54+
Inner: HotKv,
55+
{
56+
fn drop(&mut self) {
57+
self.db.write_locked.store(false, Ordering::Release);
58+
}
59+
}
60+
61+
impl<Inner> HotKvRead for WriteGuard<'_, Inner>
62+
where
63+
Inner: HotKv,
64+
{
65+
type Error = <<Inner as HotKv>::RwTx as HotKvRead>::Error;
66+
67+
fn get_raw<'a>(
68+
&'a self,
69+
table: &str,
70+
key: &[u8],
71+
) -> Result<Option<Cow<'a, [u8]>>, Self::Error> {
72+
self.tx.as_ref().expect("present until drop").get_raw(table, key)
73+
}
74+
}
75+
76+
impl<Inner> HotKvWrite for WriteGuard<'_, Inner>
77+
where
78+
Inner: HotKv,
79+
{
80+
fn queue_raw_put(&mut self, table: &str, key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
81+
self.tx.as_mut().expect("present until drop").queue_raw_put(table, key, value)
82+
}
83+
84+
fn raw_commit(mut self) -> Result<(), Self::Error> {
85+
self.tx.take().expect("present until drop").raw_commit()
86+
}
87+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
use crate::{
2+
hot::{HotKvRead, HotKvWrite},
3+
tables::hot::{self as tables, AccountStorageKey},
4+
};
5+
use alloy::primitives::{Address, B256, U256};
6+
use reth::primitives::{Account, Bytecode, Header, SealedHeader, StorageEntry};
7+
use std::borrow::Cow;
8+
9+
/// Trait for database read operations.
10+
pub trait HotDbReader: sealed::Sealed {
11+
/// The error type for read operations
12+
type Error: std::error::Error + Send + Sync + 'static + From<crate::ser::DeserError>;
13+
14+
/// Read a block header by its number.
15+
fn get_header(&self, number: u64) -> Result<Option<Header>, Self::Error>;
16+
17+
/// Read a block number by its hash.
18+
fn get_header_number(&self, hash: &B256) -> Result<Option<u64>, Self::Error>;
19+
20+
/// Read the canonical hash by block number.
21+
fn get_canonical_hash(&self, number: u64) -> Result<Option<B256>, Self::Error>;
22+
23+
/// Read contract Bytecode by its hash.
24+
fn get_bytecode(&self, code_hash: &B256) -> Result<Option<Bytecode>, Self::Error>;
25+
26+
/// Read an account by its address.
27+
fn get_account(&self, address: &Address) -> Result<Option<Account>, Self::Error>;
28+
29+
/// Read a storage slot by its address and key.
30+
fn get_storage(&self, address: &Address, key: &B256) -> Result<Option<U256>, Self::Error>;
31+
32+
/// Read a [`StorageEntry`] by its address and key.
33+
fn get_storage_entry(
34+
&self,
35+
address: &Address,
36+
key: &B256,
37+
) -> Result<Option<StorageEntry>, Self::Error> {
38+
let opt = self.get_storage(address, key)?;
39+
Ok(opt.map(|value| StorageEntry { key: *key, value }))
40+
}
41+
}
42+
43+
impl<T> HotDbReader for T
44+
where
45+
T: HotKvRead,
46+
{
47+
type Error = <T as HotKvRead>::Error;
48+
49+
fn get_header(&self, number: u64) -> Result<Option<Header>, Self::Error> {
50+
self.get::<tables::Headers>(&number)
51+
}
52+
53+
fn get_header_number(&self, hash: &B256) -> Result<Option<u64>, Self::Error> {
54+
self.get::<tables::HeaderNumbers>(hash)
55+
}
56+
57+
fn get_canonical_hash(&self, number: u64) -> Result<Option<B256>, Self::Error> {
58+
self.get::<tables::CanonicalHeaders>(&number)
59+
}
60+
61+
fn get_bytecode(&self, code_hash: &B256) -> Result<Option<Bytecode>, Self::Error> {
62+
self.get::<tables::Bytecodes>(code_hash)
63+
}
64+
65+
fn get_account(&self, address: &Address) -> Result<Option<Account>, Self::Error> {
66+
self.get::<tables::PlainAccountState>(address)
67+
}
68+
69+
fn get_storage(&self, address: &Address, key: &B256) -> Result<Option<U256>, Self::Error> {
70+
let storage_key = AccountStorageKey {
71+
address: std::borrow::Cow::Borrowed(address),
72+
key: std::borrow::Cow::Borrowed(key),
73+
};
74+
let key = storage_key.encode_key();
75+
self.get::<tables::PlainStorageState>(&key)
76+
}
77+
}
78+
79+
/// Trait for database write operations.
80+
pub trait HotDbWriter: sealed::Sealed {
81+
/// The error type for write operations
82+
type Error: std::error::Error + Send + Sync + 'static + From<crate::ser::DeserError>;
83+
84+
/// Read the latest block header.
85+
fn put_header(&mut self, header: &Header) -> Result<(), Self::Error>;
86+
87+
/// Write a block number by its hash.
88+
fn put_header_number(&mut self, hash: &B256, number: u64) -> Result<(), Self::Error>;
89+
90+
/// Write the canonical hash by block number.
91+
fn put_canonical_hash(&mut self, number: u64, hash: &B256) -> Result<(), Self::Error>;
92+
93+
/// Write contract Bytecode by its hash.
94+
fn put_bytecode(&mut self, code_hash: &B256, bytecode: &Bytecode) -> Result<(), Self::Error>;
95+
96+
/// Write an account by its address.
97+
fn put_account(&mut self, address: &Address, account: &Account) -> Result<(), Self::Error>;
98+
99+
/// Write a storage entry by its address and key.
100+
fn put_storage(
101+
&mut self,
102+
address: &Address,
103+
key: &B256,
104+
entry: &U256,
105+
) -> Result<(), Self::Error>;
106+
107+
/// Commit the write transaction.
108+
fn commit(self) -> Result<(), Self::Error>;
109+
110+
/// Write a canonical header (header, number mapping, and canonical hash).
111+
fn put_canonical(&mut self, header: &SealedHeader) -> Result<(), Self::Error> {
112+
self.put_header(header)?;
113+
self.put_header_number(&header.hash(), header.number)?;
114+
self.put_canonical_hash(header.number, &header.hash())
115+
}
116+
}
117+
118+
impl<T> HotDbWriter for T
119+
where
120+
T: HotKvWrite,
121+
{
122+
type Error = <T as HotKvRead>::Error;
123+
124+
fn put_header(&mut self, header: &Header) -> Result<(), Self::Error> {
125+
self.queue_put::<tables::Headers>(&header.number, header)
126+
}
127+
128+
fn put_header_number(&mut self, hash: &B256, number: u64) -> Result<(), Self::Error> {
129+
self.queue_put::<tables::HeaderNumbers>(hash, &number)
130+
}
131+
132+
fn put_canonical_hash(&mut self, number: u64, hash: &B256) -> Result<(), Self::Error> {
133+
self.queue_put::<tables::CanonicalHeaders>(&number, hash)
134+
}
135+
136+
fn put_bytecode(&mut self, code_hash: &B256, bytecode: &Bytecode) -> Result<(), Self::Error> {
137+
self.queue_put::<tables::Bytecodes>(code_hash, bytecode)
138+
}
139+
140+
fn put_account(&mut self, address: &Address, account: &Account) -> Result<(), Self::Error> {
141+
self.queue_put::<tables::PlainAccountState>(address, account)
142+
}
143+
144+
fn put_storage(
145+
&mut self,
146+
address: &Address,
147+
key: &B256,
148+
entry: &U256,
149+
) -> Result<(), Self::Error> {
150+
let storage_key =
151+
AccountStorageKey { address: Cow::Borrowed(address), key: Cow::Borrowed(key) };
152+
self.queue_put::<tables::PlainStorageState>(&storage_key.encode_key(), entry)
153+
}
154+
155+
fn commit(self) -> Result<(), Self::Error> {
156+
HotKvWrite::raw_commit(self)
157+
}
158+
}
159+
160+
mod sealed {
161+
use crate::hot::HotKvRead;
162+
163+
/// Sealed trait to prevent external implementations of HotDbReader and HotDbWriter.
164+
#[allow(dead_code, unreachable_pub)]
165+
pub trait Sealed {}
166+
impl<T> Sealed for T where T: HotKvRead {}
167+
}

crates/storage/src/hot/error.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// Trait for hot storage read/write errors.
2+
#[derive(thiserror::Error, Debug)]
3+
pub enum HotKvError {
4+
/// Boxed error. Indicates an issue with the DB backend.
5+
#[error(transparent)]
6+
Inner(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
7+
8+
/// Deserialization error. Indicates an issue deserializing a key or value.
9+
#[error("Deserialization error: {0}")]
10+
DeserError(#[from] crate::ser::DeserError),
11+
12+
/// Indicates that a write transaction is already in progress.
13+
#[error("A write transaction is already in progress")]
14+
WriteLocked,
15+
}
16+
17+
impl HotKvError {
18+
/// Internal helper to create a `HotKvError::Inner` from any error.
19+
pub fn from_err<E>(err: E) -> Self
20+
where
21+
E: std::error::Error + Send + Sync + 'static,
22+
{
23+
HotKvError::Inner(Box::new(err))
24+
}
25+
}
26+
27+
/// Result type for hot storage operations.
28+
pub type HotKvResult<T> = Result<T, HotKvError>;

crates/storage/src/hot/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
mod db;
2+
pub use db::{HotDb, WriteGuard};
3+
4+
mod db_traits;
5+
pub use db_traits::{HotDbReader, HotDbWriter};
6+
7+
mod error;
8+
pub use error::{HotKvError, HotKvResult};
9+
10+
mod reth_impl;
11+
12+
mod traits;
13+
pub use traits::{HotKv, HotKvRead, HotKvWrite};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use std::borrow::Cow;
2+
3+
use crate::{hot::HotKvRead, ser::DeserError};
4+
use reth_db::mdbx::{TransactionKind, tx::Tx};
5+
use reth_db_api::DatabaseError;
6+
7+
impl From<DeserError> for DatabaseError {
8+
fn from(value: DeserError) -> Self {
9+
DatabaseError::Other(value.to_string())
10+
}
11+
}
12+
13+
impl<K> HotKvRead for Tx<K>
14+
where
15+
K: TransactionKind,
16+
{
17+
type Error = DatabaseError;
18+
19+
fn get_raw<'a>(
20+
&'a self,
21+
table: &str,
22+
key: &[u8],
23+
) -> Result<Option<Cow<'a, [u8]>>, Self::Error> {
24+
let dbi = self
25+
.inner
26+
.open_db(Some(table))
27+
.map(|db| db.dbi())
28+
.map_err(|e| DatabaseError::Open(e.into()))?;
29+
30+
self.inner.get(dbi, key.as_ref()).map_err(|err| DatabaseError::Read(err.into()))
31+
}
32+
}

0 commit comments

Comments
 (0)