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