Skip to main content

signet_cold/
dyn_backend.rs

1//! Object-safe mirror of [`ColdStorageBackend`].
2//!
3//! [`DynColdStorageBackend`] re-declares every method on
4//! [`ColdStorageRead`], [`ColdStorageWrite`], and [`ColdStorageBackend`]
5//! with an explicit [`StorageFuture`] return type so the trait is
6//! object-safe. A blanket impl auto-implements it for every
7//! `B: ColdStorageBackend`, and [`ErasedBackend`] re-implements the
8//! strong traits by delegating to the boxed methods.
9//!
10//! # Plumbing, Not API
11//!
12//! This trait exists so [`ColdStorage`]'s default type parameter
13//! ([`ErasedBackend`]) is nameable in error messages and downstream
14//! signatures. Backends should implement [`ColdStorageBackend`] — the
15//! blanket impl handles this trait.
16//!
17//! # Why a Newtype, Not a Type Alias
18//!
19//! [`ErasedBackend`] is a newtype wrapping `Arc<dyn DynColdStorageBackend>`
20//! rather than a plain alias. A type alias exposes the dyn trait-object
21//! lifetime to trait resolution; when an `ErasedBackend` is captured
22//! into a spawned future, rustc invents a fresh `'0` lifetime for the
23//! dyn object and asks `for<'0> Arc<dyn DynColdStorageBackend + '0>:
24//! ColdStorageRead`. The `'static`-bounded impl does not satisfy this
25//! HRTB and downstream `Send` checks fail. A concrete newtype has no
26//! dyn lifetime in its surface type, so resolution is trivial.
27//!
28//! # Filter Cloning on the Erased Path
29//!
30//! The [`ColdStorageRead`] impl for [`ErasedBackend`] clones the
31//! [`Filter`] inside `get_logs` and `produce_log_stream`. The dyn
32//! methods unify `&self` and `&Filter` into a single lifetime, which
33//! cannot be expressed by the independent-lifetime trait signatures
34//! without an owned bridge. The concrete `ColdStorage<B>` path is
35//! unaffected.
36//!
37//! # Maintainer Note: Recursion Hazard for Borrowed Arguments
38//!
39//! Any method on the [`ErasedBackend`] impls that cannot use the
40//! direct `self.0.dyn_<name>(...)` form (because a borrowed argument
41//! forces it through a `self.clone()` + `async move` bridge) MUST
42//! dispatch via qualified path on the inner trait object, e.g.
43//! `DynColdStorageBackend::dyn_<name>(this.0.as_ref(), ...)`.
44//!
45//! Writing `this.dyn_<name>(...)` on a cloned [`ErasedBackend`]
46//! resolves to the blanket impl (`ErasedBackend: ColdStorageBackend`
47//! ⇒ `ErasedBackend: DynColdStorageBackend`), which calls back into
48//! the strong-trait impl and recurses infinitely. See `get_logs` and
49//! `produce_log_stream` for the canonical pattern.
50//!
51//! [`ColdStorage`]: crate::ColdStorage
52//! [`ColdStorageBackend`]: crate::ColdStorageBackend
53//! [`ColdStorageRead`]: crate::ColdStorageRead
54//! [`ColdStorageWrite`]: crate::ColdStorageWrite
55
56use crate::{
57    BlockData, ColdReceipt, ColdResult, ColdStorageBackend, ColdStorageRead, ColdStorageWrite,
58    Confirmed, Filter, HeaderSpecifier, ReceiptSpecifier, RpcLog, SignetEventsSpecifier,
59    StreamParams, TransactionSpecifier, ZenithHeaderSpecifier,
60};
61use alloy::primitives::BlockNumber;
62use signet_storage_types::{DbSignetEvent, DbZenithHeader, RecoveredTx, SealedHeader};
63use std::{future::Future, pin::Pin, sync::Arc, time::Duration};
64
65/// Boxed, pinned, `Send`-able future returned from object-safe
66/// [`DynColdStorageBackend`] methods.
67pub type StorageFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
68
69/// Type-erased cold storage backend, shareable across tasks.
70///
71/// This is the default `B` for [`ColdStorage`](crate::ColdStorage): a
72/// handle written as plain `ColdStorage` uses this backend. Construct
73/// one with [`ErasedBackend::new`] or
74/// [`ColdStorage::new_erased`](crate::ColdStorage::new_erased).
75///
76/// # Why a Newtype
77///
78/// Wrapping the `Arc<dyn ...>` in a struct keeps the trait-object
79/// lifetime out of the public type signature. See the module-level
80/// docs for the HRTB resolution problem this avoids.
81pub struct ErasedBackend(Arc<dyn DynColdStorageBackend>);
82
83impl ErasedBackend {
84    /// Erase a concrete backend behind `Arc<dyn DynColdStorageBackend>`.
85    pub fn new<B: ColdStorageBackend>(backend: B) -> Self {
86        Self(Arc::new(backend))
87    }
88
89    /// Wrap an existing trait object.
90    ///
91    /// Prefer [`ErasedBackend::new`] for concrete backends. Use this
92    /// only when you already hold an `Arc<dyn DynColdStorageBackend>`,
93    /// e.g. when bridging from another type-erased channel.
94    pub const fn from_arc(arc: Arc<dyn DynColdStorageBackend>) -> Self {
95        Self(arc)
96    }
97
98    /// Borrow the inner trait object.
99    pub fn as_dyn(&self) -> &(dyn DynColdStorageBackend + 'static) {
100        &*self.0
101    }
102
103    /// Consume the newtype and return the inner `Arc<dyn ...>`.
104    pub fn into_arc(self) -> Arc<dyn DynColdStorageBackend> {
105        self.0
106    }
107}
108
109impl Clone for ErasedBackend {
110    fn clone(&self) -> Self {
111        Self(Arc::clone(&self.0))
112    }
113}
114
115impl std::fmt::Debug for ErasedBackend {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        f.debug_tuple("ErasedBackend").finish()
118    }
119}
120
121/// Object-safe mirror of [`ColdStorageBackend`]. Auto-implemented by a
122/// blanket impl over every `B: ColdStorageBackend`; do not implement
123/// directly.
124///
125/// [`ColdStorageBackend`]: crate::ColdStorageBackend
126pub trait DynColdStorageBackend: Send + Sync + 'static {
127    /// Get a header by specifier.
128    fn dyn_get_header<'a>(
129        &'a self,
130        spec: HeaderSpecifier,
131    ) -> StorageFuture<'a, ColdResult<Option<SealedHeader>>>;
132
133    /// Get multiple headers by specifiers.
134    fn dyn_get_headers<'a>(
135        &'a self,
136        specs: Vec<HeaderSpecifier>,
137    ) -> StorageFuture<'a, ColdResult<Vec<Option<SealedHeader>>>>;
138
139    /// Get a transaction by specifier, with block confirmation metadata.
140    fn dyn_get_transaction<'a>(
141        &'a self,
142        spec: TransactionSpecifier,
143    ) -> StorageFuture<'a, ColdResult<Option<Confirmed<RecoveredTx>>>>;
144
145    /// Get all transactions in a block.
146    fn dyn_get_transactions_in_block<'a>(
147        &'a self,
148        block: BlockNumber,
149    ) -> StorageFuture<'a, ColdResult<Vec<RecoveredTx>>>;
150
151    /// Get the number of transactions in a block.
152    fn dyn_get_transaction_count<'a>(
153        &'a self,
154        block: BlockNumber,
155    ) -> StorageFuture<'a, ColdResult<u64>>;
156
157    /// Get a receipt by specifier.
158    fn dyn_get_receipt<'a>(
159        &'a self,
160        spec: ReceiptSpecifier,
161    ) -> StorageFuture<'a, ColdResult<Option<ColdReceipt>>>;
162
163    /// Get all receipts in a block.
164    fn dyn_get_receipts_in_block<'a>(
165        &'a self,
166        block: BlockNumber,
167    ) -> StorageFuture<'a, ColdResult<Vec<ColdReceipt>>>;
168
169    /// Get signet events by specifier.
170    fn dyn_get_signet_events<'a>(
171        &'a self,
172        spec: SignetEventsSpecifier,
173    ) -> StorageFuture<'a, ColdResult<Vec<DbSignetEvent>>>;
174
175    /// Get a zenith header by specifier.
176    fn dyn_get_zenith_header<'a>(
177        &'a self,
178        spec: ZenithHeaderSpecifier,
179    ) -> StorageFuture<'a, ColdResult<Option<DbZenithHeader>>>;
180
181    /// Get multiple zenith headers by specifier.
182    fn dyn_get_zenith_headers<'a>(
183        &'a self,
184        spec: ZenithHeaderSpecifier,
185    ) -> StorageFuture<'a, ColdResult<Vec<DbZenithHeader>>>;
186
187    /// Get the latest block number in storage.
188    fn dyn_get_latest_block<'a>(&'a self) -> StorageFuture<'a, ColdResult<Option<BlockNumber>>>;
189
190    /// Filter logs by block range, address, and topics.
191    fn dyn_get_logs<'a>(
192        &'a self,
193        filter: &'a Filter,
194        max_logs: usize,
195    ) -> StorageFuture<'a, ColdResult<Vec<RpcLog>>>;
196
197    /// Produce a log stream by iterating blocks and sending matching logs.
198    fn dyn_produce_log_stream<'a>(
199        &'a self,
200        filter: &'a Filter,
201        params: StreamParams,
202    ) -> StorageFuture<'a, ()>;
203
204    /// Append a single block to cold storage.
205    fn dyn_append_block<'a>(&'a self, data: BlockData) -> StorageFuture<'a, ColdResult<()>>;
206
207    /// Append multiple blocks to cold storage.
208    fn dyn_append_blocks<'a>(&'a self, data: Vec<BlockData>) -> StorageFuture<'a, ColdResult<()>>;
209
210    /// Truncate all data above the given block number (exclusive).
211    fn dyn_truncate_above<'a>(&'a self, block: BlockNumber) -> StorageFuture<'a, ColdResult<()>>;
212
213    /// Read and remove all blocks above the given block number.
214    fn dyn_drain_above<'a>(
215        &'a self,
216        block: BlockNumber,
217    ) -> StorageFuture<'a, ColdResult<Vec<Vec<ColdReceipt>>>>;
218
219    /// Configured read deadline, if any.
220    fn dyn_read_timeout(&self) -> Option<Duration>;
221
222    /// Configured write deadline, if any.
223    fn dyn_write_timeout(&self) -> Option<Duration>;
224}
225
226impl<B: ColdStorageBackend> DynColdStorageBackend for B {
227    fn dyn_get_header<'a>(
228        &'a self,
229        spec: HeaderSpecifier,
230    ) -> StorageFuture<'a, ColdResult<Option<SealedHeader>>> {
231        Box::pin(<B as ColdStorageRead>::get_header(self, spec))
232    }
233
234    fn dyn_get_headers<'a>(
235        &'a self,
236        specs: Vec<HeaderSpecifier>,
237    ) -> StorageFuture<'a, ColdResult<Vec<Option<SealedHeader>>>> {
238        Box::pin(<B as ColdStorageRead>::get_headers(self, specs))
239    }
240
241    fn dyn_get_transaction<'a>(
242        &'a self,
243        spec: TransactionSpecifier,
244    ) -> StorageFuture<'a, ColdResult<Option<Confirmed<RecoveredTx>>>> {
245        Box::pin(<B as ColdStorageRead>::get_transaction(self, spec))
246    }
247
248    fn dyn_get_transactions_in_block<'a>(
249        &'a self,
250        block: BlockNumber,
251    ) -> StorageFuture<'a, ColdResult<Vec<RecoveredTx>>> {
252        Box::pin(<B as ColdStorageRead>::get_transactions_in_block(self, block))
253    }
254
255    fn dyn_get_transaction_count<'a>(
256        &'a self,
257        block: BlockNumber,
258    ) -> StorageFuture<'a, ColdResult<u64>> {
259        Box::pin(<B as ColdStorageRead>::get_transaction_count(self, block))
260    }
261
262    fn dyn_get_receipt<'a>(
263        &'a self,
264        spec: ReceiptSpecifier,
265    ) -> StorageFuture<'a, ColdResult<Option<ColdReceipt>>> {
266        Box::pin(<B as ColdStorageRead>::get_receipt(self, spec))
267    }
268
269    fn dyn_get_receipts_in_block<'a>(
270        &'a self,
271        block: BlockNumber,
272    ) -> StorageFuture<'a, ColdResult<Vec<ColdReceipt>>> {
273        Box::pin(<B as ColdStorageRead>::get_receipts_in_block(self, block))
274    }
275
276    fn dyn_get_signet_events<'a>(
277        &'a self,
278        spec: SignetEventsSpecifier,
279    ) -> StorageFuture<'a, ColdResult<Vec<DbSignetEvent>>> {
280        Box::pin(<B as ColdStorageRead>::get_signet_events(self, spec))
281    }
282
283    fn dyn_get_zenith_header<'a>(
284        &'a self,
285        spec: ZenithHeaderSpecifier,
286    ) -> StorageFuture<'a, ColdResult<Option<DbZenithHeader>>> {
287        Box::pin(<B as ColdStorageRead>::get_zenith_header(self, spec))
288    }
289
290    fn dyn_get_zenith_headers<'a>(
291        &'a self,
292        spec: ZenithHeaderSpecifier,
293    ) -> StorageFuture<'a, ColdResult<Vec<DbZenithHeader>>> {
294        Box::pin(<B as ColdStorageRead>::get_zenith_headers(self, spec))
295    }
296
297    fn dyn_get_latest_block<'a>(&'a self) -> StorageFuture<'a, ColdResult<Option<BlockNumber>>> {
298        Box::pin(<B as ColdStorageRead>::get_latest_block(self))
299    }
300
301    fn dyn_get_logs<'a>(
302        &'a self,
303        filter: &'a Filter,
304        max_logs: usize,
305    ) -> StorageFuture<'a, ColdResult<Vec<RpcLog>>> {
306        Box::pin(<B as ColdStorageRead>::get_logs(self, filter, max_logs))
307    }
308
309    fn dyn_produce_log_stream<'a>(
310        &'a self,
311        filter: &'a Filter,
312        params: StreamParams,
313    ) -> StorageFuture<'a, ()> {
314        Box::pin(<B as ColdStorageRead>::produce_log_stream(self, filter, params))
315    }
316
317    fn dyn_append_block<'a>(&'a self, data: BlockData) -> StorageFuture<'a, ColdResult<()>> {
318        Box::pin(<B as ColdStorageWrite>::append_block(self, data))
319    }
320
321    fn dyn_append_blocks<'a>(&'a self, data: Vec<BlockData>) -> StorageFuture<'a, ColdResult<()>> {
322        Box::pin(<B as ColdStorageWrite>::append_blocks(self, data))
323    }
324
325    fn dyn_truncate_above<'a>(&'a self, block: BlockNumber) -> StorageFuture<'a, ColdResult<()>> {
326        Box::pin(<B as ColdStorageWrite>::truncate_above(self, block))
327    }
328
329    fn dyn_drain_above<'a>(
330        &'a self,
331        block: BlockNumber,
332    ) -> StorageFuture<'a, ColdResult<Vec<Vec<ColdReceipt>>>> {
333        Box::pin(<B as ColdStorageBackend>::drain_above(self, block))
334    }
335
336    fn dyn_read_timeout(&self) -> Option<Duration> {
337        <B as ColdStorageBackend>::read_timeout(self)
338    }
339
340    fn dyn_write_timeout(&self) -> Option<Duration> {
341        <B as ColdStorageBackend>::write_timeout(self)
342    }
343}
344
345// Compile-time check that the trait is object-safe.
346const _: fn() = || {
347    fn _assert_object_safe(_: &dyn DynColdStorageBackend) {}
348};
349
350impl ColdStorageRead for ErasedBackend {
351    fn get_header(
352        &self,
353        spec: HeaderSpecifier,
354    ) -> impl Future<Output = ColdResult<Option<SealedHeader>>> + Send {
355        self.0.dyn_get_header(spec)
356    }
357
358    fn get_headers(
359        &self,
360        specs: Vec<HeaderSpecifier>,
361    ) -> impl Future<Output = ColdResult<Vec<Option<SealedHeader>>>> + Send {
362        self.0.dyn_get_headers(specs)
363    }
364
365    fn get_transaction(
366        &self,
367        spec: TransactionSpecifier,
368    ) -> impl Future<Output = ColdResult<Option<Confirmed<RecoveredTx>>>> + Send {
369        self.0.dyn_get_transaction(spec)
370    }
371
372    fn get_transactions_in_block(
373        &self,
374        block: BlockNumber,
375    ) -> impl Future<Output = ColdResult<Vec<RecoveredTx>>> + Send {
376        self.0.dyn_get_transactions_in_block(block)
377    }
378
379    fn get_transaction_count(
380        &self,
381        block: BlockNumber,
382    ) -> impl Future<Output = ColdResult<u64>> + Send {
383        self.0.dyn_get_transaction_count(block)
384    }
385
386    fn get_receipt(
387        &self,
388        spec: ReceiptSpecifier,
389    ) -> impl Future<Output = ColdResult<Option<ColdReceipt>>> + Send {
390        self.0.dyn_get_receipt(spec)
391    }
392
393    fn get_receipts_in_block(
394        &self,
395        block: BlockNumber,
396    ) -> impl Future<Output = ColdResult<Vec<ColdReceipt>>> + Send {
397        self.0.dyn_get_receipts_in_block(block)
398    }
399
400    fn get_signet_events(
401        &self,
402        spec: SignetEventsSpecifier,
403    ) -> impl Future<Output = ColdResult<Vec<DbSignetEvent>>> + Send {
404        self.0.dyn_get_signet_events(spec)
405    }
406
407    fn get_zenith_header(
408        &self,
409        spec: ZenithHeaderSpecifier,
410    ) -> impl Future<Output = ColdResult<Option<DbZenithHeader>>> + Send {
411        self.0.dyn_get_zenith_header(spec)
412    }
413
414    fn get_zenith_headers(
415        &self,
416        spec: ZenithHeaderSpecifier,
417    ) -> impl Future<Output = ColdResult<Vec<DbZenithHeader>>> + Send {
418        self.0.dyn_get_zenith_headers(spec)
419    }
420
421    fn get_latest_block(&self) -> impl Future<Output = ColdResult<Option<BlockNumber>>> + Send {
422        self.0.dyn_get_latest_block()
423    }
424
425    fn get_logs(
426        &self,
427        filter: &Filter,
428        max_logs: usize,
429    ) -> impl Future<Output = ColdResult<Vec<RpcLog>>> + Send {
430        let this = self.clone();
431        let filter = filter.clone();
432        // Call dyn_get_logs via the inner trait object directly (not through
433        // the newtype's blanket DynColdStorageBackend impl), which would
434        // re-enter ColdStorageRead::get_logs on ErasedBackend and recurse
435        // infinitely.
436        async move { DynColdStorageBackend::dyn_get_logs(this.0.as_ref(), &filter, max_logs).await }
437    }
438
439    fn produce_log_stream(
440        &self,
441        filter: &Filter,
442        params: StreamParams,
443    ) -> impl Future<Output = ()> + Send {
444        let this = self.clone();
445        let filter = filter.clone();
446        // Same recursion hazard as `get_logs` above — call through the
447        // inner Arc + qualified path so dispatch lands on the trait
448        // object's vtable, not the newtype's blanket impl.
449        async move {
450            DynColdStorageBackend::dyn_produce_log_stream(this.0.as_ref(), &filter, params).await
451        }
452    }
453}
454
455impl ColdStorageWrite for ErasedBackend {
456    fn append_block(&self, data: BlockData) -> impl Future<Output = ColdResult<()>> + Send {
457        self.0.dyn_append_block(data)
458    }
459
460    fn append_blocks(&self, data: Vec<BlockData>) -> impl Future<Output = ColdResult<()>> + Send {
461        self.0.dyn_append_blocks(data)
462    }
463
464    fn truncate_above(&self, block: BlockNumber) -> impl Future<Output = ColdResult<()>> + Send {
465        self.0.dyn_truncate_above(block)
466    }
467}
468
469impl ColdStorageBackend for ErasedBackend {
470    fn read_timeout(&self) -> Option<Duration> {
471        self.0.dyn_read_timeout()
472    }
473
474    fn write_timeout(&self) -> Option<Duration> {
475        self.0.dyn_write_timeout()
476    }
477
478    fn drain_above(
479        &self,
480        block: BlockNumber,
481    ) -> impl Future<Output = ColdResult<Vec<Vec<ColdReceipt>>>> + Send {
482        self.0.dyn_drain_above(block)
483    }
484}
485
486// Compile-time check that `ErasedBackend` satisfies the bound
487// `ColdStorage` will require.
488const _: fn() = || {
489    const fn _assert_bound<B: ColdStorageBackend>() {}
490    _assert_bound::<ErasedBackend>();
491};