signet_cold/traits.rs
1//! Core trait definition for cold storage backends.
2//!
3//! The [`ColdStorage`] trait defines the interface that all cold storage
4//! backends must implement. Backends are responsible for data organization,
5//! indexing, and keying - the trait is agnostic to these implementation details.
6
7use alloy::{consensus::Header, primitives::BlockNumber};
8use signet_storage_types::{
9 DbSignetEvent, DbZenithHeader, ExecutedBlock, Receipt, TransactionSigned,
10};
11use std::future::Future;
12
13use super::{
14 ColdResult, Confirmed, HeaderSpecifier, ReceiptSpecifier, SignetEventsSpecifier,
15 TransactionSpecifier, ZenithHeaderSpecifier,
16};
17
18/// Data for appending a complete block to cold storage.
19#[derive(Debug, Clone)]
20pub struct BlockData {
21 /// The block header.
22 pub header: Header,
23 /// The transactions in the block.
24 pub transactions: Vec<TransactionSigned>,
25 /// The receipts for the transactions.
26 pub receipts: Vec<Receipt>,
27 /// The signet events in the block.
28 pub signet_events: Vec<DbSignetEvent>,
29 /// The zenith header for the block, if present.
30 pub zenith_header: Option<DbZenithHeader>,
31}
32
33impl BlockData {
34 /// Create new block data.
35 pub const fn new(
36 header: Header,
37 transactions: Vec<TransactionSigned>,
38 receipts: Vec<Receipt>,
39 signet_events: Vec<DbSignetEvent>,
40 zenith_header: Option<DbZenithHeader>,
41 ) -> Self {
42 Self { header, transactions, receipts, signet_events, zenith_header }
43 }
44
45 /// Get the block number of the block.
46 pub const fn block_number(&self) -> BlockNumber {
47 self.header.number
48 }
49}
50
51/// All data needed to build a complete RPC receipt response.
52///
53/// Bundles a [`Confirmed`] receipt with its transaction, block header,
54/// and the prior cumulative gas (needed to compute per-tx `gas_used`).
55#[derive(Debug, Clone)]
56pub struct ReceiptContext {
57 /// The block header.
58 pub header: Header,
59 /// The transaction that produced this receipt.
60 pub transaction: TransactionSigned,
61 /// The receipt with block confirmation metadata.
62 pub receipt: Confirmed<Receipt>,
63 /// Cumulative gas used by all preceding transactions in the block.
64 /// Zero for the first transaction.
65 pub prior_cumulative_gas: u64,
66}
67
68impl ReceiptContext {
69 /// Create a new receipt context.
70 pub const fn new(
71 header: Header,
72 transaction: TransactionSigned,
73 receipt: Confirmed<Receipt>,
74 prior_cumulative_gas: u64,
75 ) -> Self {
76 Self { header, transaction, receipt, prior_cumulative_gas }
77 }
78}
79
80impl From<ExecutedBlock> for BlockData {
81 fn from(block: ExecutedBlock) -> Self {
82 Self::new(
83 block.header.into_inner(),
84 block.transactions,
85 block.receipts,
86 block.signet_events,
87 block.zenith_header,
88 )
89 }
90}
91
92/// Unified cold storage backend trait.
93///
94/// Backend is responsible for all data organization, indexing, and keying.
95/// The trait is agnostic to how the backend stores or indexes data.
96///
97/// All methods are async and return futures that are `Send`.
98///
99/// # Implementation Guide
100///
101/// Implementers must ensure:
102///
103/// - **Append-only ordering**: `append_block` must enforce monotonically
104/// increasing block numbers. Attempting to append a block with a number <=
105/// the current latest should return an error.
106///
107/// - **Atomic truncation**: `truncate_above` must remove all data for blocks
108/// N+1 and higher atomically. Partial truncation is not acceptable.
109///
110/// - **Index maintenance**: Hash-based lookups (e.g., header by hash,
111/// transaction by hash) require the implementation to maintain appropriate
112/// indexes. These indexes must be updated during `append_block` and cleaned
113/// during `truncate_above`.
114///
115/// - **Consistent reads**: Read operations should return consistent snapshots.
116/// A read started before a write completes should not see partial data from
117/// that write.
118///
119pub trait ColdStorage: Send + Sync + 'static {
120 // --- Headers ---
121
122 /// Get a header by specifier.
123 fn get_header(
124 &self,
125 spec: HeaderSpecifier,
126 ) -> impl Future<Output = ColdResult<Option<Header>>> + Send;
127
128 /// Get multiple headers by specifiers.
129 fn get_headers(
130 &self,
131 specs: Vec<HeaderSpecifier>,
132 ) -> impl Future<Output = ColdResult<Vec<Option<Header>>>> + Send;
133
134 // --- Transactions ---
135
136 /// Get a transaction by specifier, with block confirmation metadata.
137 fn get_transaction(
138 &self,
139 spec: TransactionSpecifier,
140 ) -> impl Future<Output = ColdResult<Option<Confirmed<TransactionSigned>>>> + Send;
141
142 /// Get all transactions in a block.
143 fn get_transactions_in_block(
144 &self,
145 block: BlockNumber,
146 ) -> impl Future<Output = ColdResult<Vec<TransactionSigned>>> + Send;
147
148 /// Get the number of transactions in a block.
149 fn get_transaction_count(
150 &self,
151 block: BlockNumber,
152 ) -> impl Future<Output = ColdResult<u64>> + Send;
153
154 // --- Receipts ---
155
156 /// Get a receipt by specifier, with block confirmation metadata.
157 fn get_receipt(
158 &self,
159 spec: ReceiptSpecifier,
160 ) -> impl Future<Output = ColdResult<Option<Confirmed<Receipt>>>> + Send;
161
162 /// Get all receipts in a block.
163 fn get_receipts_in_block(
164 &self,
165 block: BlockNumber,
166 ) -> impl Future<Output = ColdResult<Vec<Receipt>>> + Send;
167
168 // --- SignetEvents ---
169
170 /// Get signet events by specifier.
171 fn get_signet_events(
172 &self,
173 spec: SignetEventsSpecifier,
174 ) -> impl Future<Output = ColdResult<Vec<DbSignetEvent>>> + Send;
175
176 // --- ZenithHeaders ---
177
178 /// Get a zenith header by specifier.
179 fn get_zenith_header(
180 &self,
181 spec: ZenithHeaderSpecifier,
182 ) -> impl Future<Output = ColdResult<Option<DbZenithHeader>>> + Send;
183
184 /// Get multiple zenith headers by specifier.
185 fn get_zenith_headers(
186 &self,
187 spec: ZenithHeaderSpecifier,
188 ) -> impl Future<Output = ColdResult<Vec<DbZenithHeader>>> + Send;
189
190 // --- Metadata ---
191
192 /// Get the latest block number in storage.
193 fn get_latest_block(&self) -> impl Future<Output = ColdResult<Option<BlockNumber>>> + Send;
194
195 // --- Composite queries ---
196
197 /// Get a receipt with all context needed for RPC responses.
198 ///
199 /// Returns the receipt, its transaction, the block header, confirmation
200 /// metadata, and the cumulative gas used by preceding transactions.
201 /// Returns `None` if the receipt does not exist.
202 ///
203 /// The default implementation composes existing trait methods. Backends
204 /// that can serve this more efficiently (e.g., in a single transaction)
205 /// should override.
206 fn get_receipt_with_context(
207 &self,
208 spec: ReceiptSpecifier,
209 ) -> impl Future<Output = ColdResult<Option<ReceiptContext>>> + Send {
210 async move {
211 let Some(receipt) = self.get_receipt(spec).await? else {
212 return Ok(None);
213 };
214 let block = receipt.meta().block_number();
215 let index = receipt.meta().transaction_index();
216
217 let Some(header) = self.get_header(HeaderSpecifier::Number(block)).await? else {
218 return Ok(None);
219 };
220 let Some(tx) =
221 self.get_transaction(TransactionSpecifier::BlockAndIndex { block, index }).await?
222 else {
223 return Ok(None);
224 };
225
226 let prior_cumulative_gas = if index > 0 {
227 self.get_receipt(ReceiptSpecifier::BlockAndIndex { block, index: index - 1 })
228 .await?
229 .map(|r| r.into_inner().inner.cumulative_gas_used)
230 .unwrap_or(0)
231 } else {
232 0
233 };
234
235 Ok(Some(ReceiptContext::new(header, tx.into_inner(), receipt, prior_cumulative_gas)))
236 }
237 }
238
239 // --- Write operations ---
240
241 /// Append a single block to cold storage.
242 fn append_block(&self, data: BlockData) -> impl Future<Output = ColdResult<()>> + Send;
243
244 /// Append multiple blocks to cold storage.
245 fn append_blocks(&self, data: Vec<BlockData>) -> impl Future<Output = ColdResult<()>> + Send;
246
247 /// Truncate all data above the given block number (exclusive).
248 ///
249 /// This removes block N+1 and higher from all tables. Used for reorg handling.
250 fn truncate_above(&self, block: BlockNumber) -> impl Future<Output = ColdResult<()>> + Send;
251}