Skip to main content

reth_storage_api/
bal.rs

1use alloc::{sync::Arc, vec::Vec};
2use alloy_primitives::{BlockHash, BlockNumber, Bytes};
3use reth_storage_errors::provider::ProviderResult;
4
5/// Store for Block Access Lists (BALs).
6///
7/// This abstraction intentionally does not prescribe where BALs live. Implementations may keep
8/// recent BALs in memory, read canonical BALs from static files, or compose multiple tiers behind
9/// a single interface.
10#[auto_impl::auto_impl(&, Arc, Box)]
11pub trait BalStore: Send + Sync + 'static {
12    /// Insert the BAL for the given block.
13    fn insert(
14        &self,
15        block_hash: BlockHash,
16        block_number: BlockNumber,
17        bal: Bytes,
18    ) -> ProviderResult<()>;
19
20    /// Fetch BALs for the given block hashes.
21    ///
22    /// The returned vector must align with `block_hashes`.
23    fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult<Vec<Option<Bytes>>>;
24
25    /// Fetch BAL response entries for the given block hashes, stopping after the soft limit is
26    /// exceeded.
27    ///
28    /// Entries are returned in request order. Unavailable BALs are represented as an RLP-encoded
29    /// empty list (`0xc0`). The limit is soft: the entry that exceeds the limit is included.
30    fn get_by_hashes_with_limit(
31        &self,
32        block_hashes: &[BlockHash],
33        limit: GetBlockAccessListLimit,
34    ) -> ProviderResult<Vec<Bytes>> {
35        let mut out = Vec::new();
36        self.append_by_hashes_with_limit(block_hashes, limit, &mut out)?;
37        out.shrink_to_fit();
38        Ok(out)
39    }
40
41    /// Extends the given vector with BAL response entries for the given hashes.
42    ///
43    /// This adheres to the expected behavior of [`Self::get_by_hashes_with_limit`].
44    fn append_by_hashes_with_limit(
45        &self,
46        block_hashes: &[BlockHash],
47        limit: GetBlockAccessListLimit,
48        out: &mut Vec<Bytes>,
49    ) -> ProviderResult<()> {
50        let mut size = 0;
51        for bal in self.get_by_hashes(block_hashes)? {
52            let bal = bal.unwrap_or_else(|| Bytes::from_static(&[0xc0]));
53            size += bal.len();
54            out.push(bal);
55
56            if limit.exceeds(size) {
57                break
58            }
59        }
60        Ok(())
61    }
62
63    /// Fetch BALs for the requested range.
64    ///
65    /// Implementations may stop at the first gap and return the contiguous prefix.
66    fn get_by_range(&self, start: BlockNumber, count: u64) -> ProviderResult<Vec<Bytes>>;
67}
68
69/// The limit to enforce for [`BalStore::get_by_hashes_with_limit`].
70#[derive(Debug, Clone, Copy, Eq, PartialEq)]
71pub enum GetBlockAccessListLimit {
72    /// No limit, return all BALs.
73    None,
74    /// Enforce a size limit on the returned BALs, for example 2MB.
75    ResponseSizeSoftLimit(usize),
76}
77
78impl GetBlockAccessListLimit {
79    /// Returns true if the given size exceeds the limit.
80    #[inline]
81    pub const fn exceeds(&self, size: usize) -> bool {
82        match self {
83            Self::None => false,
84            Self::ResponseSizeSoftLimit(limit) => size > *limit,
85        }
86    }
87}
88
89/// Clone-friendly façade around a BAL store implementation.
90#[derive(Clone)]
91pub struct BalStoreHandle {
92    inner: Arc<dyn BalStore>,
93}
94
95impl BalStoreHandle {
96    /// Creates a new [`BalStoreHandle`] from the given implementation.
97    pub fn new(inner: impl BalStore) -> Self {
98        Self { inner: Arc::new(inner) }
99    }
100
101    /// Creates a [`BalStoreHandle`] backed by [`NoopBalStore`].
102    pub fn noop() -> Self {
103        Self::new(NoopBalStore)
104    }
105
106    /// Insert the BAL for the given block.
107    #[inline]
108    pub fn insert(
109        &self,
110        block_hash: BlockHash,
111        block_number: BlockNumber,
112        bal: Bytes,
113    ) -> ProviderResult<()> {
114        self.inner.insert(block_hash, block_number, bal)
115    }
116
117    /// Fetch BALs for the given block hashes.
118    #[inline]
119    pub fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult<Vec<Option<Bytes>>> {
120        self.inner.get_by_hashes(block_hashes)
121    }
122
123    /// Fetch BAL response entries for the given block hashes, stopping after the soft limit is
124    /// exceeded.
125    #[inline]
126    pub fn get_by_hashes_with_limit(
127        &self,
128        block_hashes: &[BlockHash],
129        limit: GetBlockAccessListLimit,
130    ) -> ProviderResult<Vec<Bytes>> {
131        self.inner.get_by_hashes_with_limit(block_hashes, limit)
132    }
133
134    /// Extends the given vector with BAL response entries for the given hashes.
135    #[inline]
136    pub fn append_by_hashes_with_limit(
137        &self,
138        block_hashes: &[BlockHash],
139        limit: GetBlockAccessListLimit,
140        out: &mut Vec<Bytes>,
141    ) -> ProviderResult<()> {
142        self.inner.append_by_hashes_with_limit(block_hashes, limit, out)
143    }
144
145    /// Fetch BALs for the requested range.
146    #[inline]
147    pub fn get_by_range(&self, start: BlockNumber, count: u64) -> ProviderResult<Vec<Bytes>> {
148        self.inner.get_by_range(start, count)
149    }
150}
151
152impl Default for BalStoreHandle {
153    fn default() -> Self {
154        Self::noop()
155    }
156}
157
158impl core::fmt::Debug for BalStoreHandle {
159    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
160        f.debug_struct("BalStoreHandle").finish_non_exhaustive()
161    }
162}
163
164/// Provider-side access to BAL storage.
165#[auto_impl::auto_impl(&, Arc)]
166pub trait BalProvider {
167    /// Returns the configured BAL store handle.
168    fn bal_store(&self) -> &BalStoreHandle;
169}
170
171/// No-op BAL store used as the default wiring target until a concrete implementation is injected.
172#[derive(Debug, Default, Clone, Copy)]
173pub struct NoopBalStore;
174
175impl BalStore for NoopBalStore {
176    fn insert(
177        &self,
178        _block_hash: BlockHash,
179        _block_number: BlockNumber,
180        _bal: Bytes,
181    ) -> ProviderResult<()> {
182        Ok(())
183    }
184
185    fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult<Vec<Option<Bytes>>> {
186        Ok(block_hashes.iter().map(|_| None).collect())
187    }
188
189    fn append_by_hashes_with_limit(
190        &self,
191        block_hashes: &[BlockHash],
192        limit: GetBlockAccessListLimit,
193        out: &mut Vec<Bytes>,
194    ) -> ProviderResult<()> {
195        let mut size = 0;
196        for _ in block_hashes {
197            let bal = Bytes::from_static(&[0xc0]);
198            size += bal.len();
199            out.push(bal);
200
201            if limit.exceeds(size) {
202                break
203            }
204        }
205        Ok(())
206    }
207
208    fn get_by_range(&self, _start: BlockNumber, _count: u64) -> ProviderResult<Vec<Bytes>> {
209        Ok(Vec::new())
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use alloy_primitives::B256;
217
218    #[test]
219    fn noop_store_returns_empty_results() {
220        let store = BalStoreHandle::default();
221        let hashes = [B256::random(), B256::random()];
222
223        let by_hash = store.get_by_hashes(&hashes).unwrap();
224        let by_range = store.get_by_range(1, 10).unwrap();
225
226        assert_eq!(by_hash, vec![None, None]);
227        assert!(by_range.is_empty());
228    }
229
230    #[test]
231    fn noop_store_limited_lookup_returns_prefix() {
232        let store = BalStoreHandle::default();
233        let hashes = [B256::random(), B256::random(), B256::random()];
234
235        let limited = store
236            .get_by_hashes_with_limit(&hashes, GetBlockAccessListLimit::ResponseSizeSoftLimit(1))
237            .unwrap();
238
239        assert_eq!(limited, vec![Bytes::from_static(&[0xc0]), Bytes::from_static(&[0xc0])]);
240    }
241
242    #[test]
243    fn block_access_list_limit() {
244        let limit_none = GetBlockAccessListLimit::None;
245        assert!(!limit_none.exceeds(usize::MAX));
246
247        let size_limit_2mb = GetBlockAccessListLimit::ResponseSizeSoftLimit(2 * 1024 * 1024);
248        assert!(!size_limit_2mb.exceeds(1024 * 1024));
249        assert!(!size_limit_2mb.exceeds(2 * 1024 * 1024));
250        assert!(size_limit_2mb.exceeds(3 * 1024 * 1024));
251    }
252}