1use alloc::{sync::Arc, vec::Vec};
2use alloy_eip7928::bal::DecodedBal;
3pub use alloy_eip7928::bal::RawBal;
4use alloy_eips::NumHash;
5use alloy_primitives::{BlockHash, BlockNumber, Bytes};
6use reth_storage_errors::provider::ProviderResult;
7
8#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct BalNotification {
11 pub num_hash: NumHash,
13 pub bal: RawBal,
15}
16
17impl BalNotification {
18 pub const fn new(num_hash: NumHash, bal: RawBal) -> Self {
20 Self { num_hash, bal }
21 }
22}
23
24#[cfg(feature = "std")]
25pub use self::subscriptions::BalNotificationStream;
26
27#[cfg(feature = "std")]
28mod subscriptions {
29 use super::BalNotification;
30
31 pub type BalNotificationStream = reth_tokio_util::EventStream<BalNotification>;
33}
34
35#[auto_impl::auto_impl(&, Arc, Box)]
41pub trait BalStore: Send + Sync + 'static {
42 fn insert(&self, num_hash: NumHash, bal: RawBal) -> ProviderResult<()>;
47
48 fn insert_many(&self, entries: Vec<(NumHash, RawBal)>) -> ProviderResult<()> {
52 for (num_hash, bal) in entries {
53 self.insert(num_hash, bal)?;
54 }
55 Ok(())
56 }
57
58 fn flush(&self, _blocks: &[NumHash]) -> ProviderResult<()> {
62 Ok(())
63 }
64
65 fn prune(&self, tip: BlockNumber) -> ProviderResult<usize>;
69
70 fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult<Vec<Option<Bytes>>>;
74
75 fn get_by_hash(&self, block_hash: BlockHash) -> ProviderResult<Option<Bytes>> {
77 Ok(self.get_by_hashes(&[block_hash])?.into_iter().next().flatten())
78 }
79
80 fn get_decoded_by_hash(&self, block_hash: BlockHash) -> ProviderResult<Option<DecodedBal>> {
82 self.get_by_hash(block_hash)?
83 .map(DecodedBal::from_rlp_bytes)
84 .transpose()
85 .map_err(Into::into)
86 }
87
88 fn get_by_hashes_with_limit(
94 &self,
95 block_hashes: &[BlockHash],
96 limit: GetBlockAccessListLimit,
97 ) -> ProviderResult<Vec<Option<Bytes>>> {
98 let mut out = Vec::new();
99 self.append_by_hashes_with_limit(block_hashes, limit, &mut out)?;
100 out.shrink_to_fit();
101 Ok(out)
102 }
103
104 fn append_by_hashes_with_limit(
108 &self,
109 block_hashes: &[BlockHash],
110 limit: GetBlockAccessListLimit,
111 out: &mut Vec<Option<Bytes>>,
112 ) -> ProviderResult<()> {
113 let mut size = 0;
114 for bal in self.get_by_hashes(block_hashes)? {
115 size += bal.as_ref().map_or(1, |bytes| bytes.len());
116 out.push(bal);
117
118 if limit.exceeds(size) {
119 break
120 }
121 }
122 Ok(())
123 }
124
125 #[cfg(feature = "std")]
130 fn bal_stream(&self) -> BalNotificationStream;
131}
132
133#[derive(Debug, Clone, Copy, Eq, PartialEq)]
135pub enum GetBlockAccessListLimit {
136 None,
138 ResponseSizeSoftLimit(usize),
140}
141
142impl GetBlockAccessListLimit {
143 #[inline]
145 pub const fn exceeds(&self, size: usize) -> bool {
146 match self {
147 Self::None => false,
148 Self::ResponseSizeSoftLimit(limit) => size > *limit,
149 }
150 }
151}
152
153#[derive(Clone)]
155pub struct BalStoreHandle {
156 inner: Arc<dyn BalStore>,
157}
158
159impl BalStoreHandle {
160 pub fn new(inner: impl BalStore) -> Self {
162 Self { inner: Arc::new(inner) }
163 }
164
165 pub fn noop() -> Self {
167 Self::new(NoopBalStore)
168 }
169
170 #[inline]
172 pub fn insert(&self, num_hash: NumHash, bal: RawBal) -> ProviderResult<()> {
173 self.inner.insert(num_hash, bal)
174 }
175
176 #[inline]
178 pub fn insert_many(&self, entries: Vec<(NumHash, RawBal)>) -> ProviderResult<()> {
179 self.inner.insert_many(entries)
180 }
181
182 #[inline]
184 pub fn flush(&self, blocks: &[NumHash]) -> ProviderResult<()> {
185 self.inner.flush(blocks)
186 }
187
188 #[inline]
190 pub fn prune(&self, tip: BlockNumber) -> ProviderResult<usize> {
191 self.inner.prune(tip)
192 }
193
194 #[inline]
196 pub fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult<Vec<Option<Bytes>>> {
197 self.inner.get_by_hashes(block_hashes)
198 }
199
200 #[inline]
202 pub fn get_by_hash(&self, block_hash: BlockHash) -> ProviderResult<Option<Bytes>> {
203 self.inner.get_by_hash(block_hash)
204 }
205
206 #[inline]
208 pub fn get_decoded_by_hash(&self, block_hash: BlockHash) -> ProviderResult<Option<DecodedBal>> {
209 self.inner.get_decoded_by_hash(block_hash)
210 }
211
212 #[inline]
215 pub fn get_by_hashes_with_limit(
216 &self,
217 block_hashes: &[BlockHash],
218 limit: GetBlockAccessListLimit,
219 ) -> ProviderResult<Vec<Option<Bytes>>> {
220 self.inner.get_by_hashes_with_limit(block_hashes, limit)
221 }
222
223 #[inline]
225 pub fn append_by_hashes_with_limit(
226 &self,
227 block_hashes: &[BlockHash],
228 limit: GetBlockAccessListLimit,
229 out: &mut Vec<Option<Bytes>>,
230 ) -> ProviderResult<()> {
231 self.inner.append_by_hashes_with_limit(block_hashes, limit, out)
232 }
233
234 #[cfg(feature = "std")]
236 #[inline]
237 pub fn bal_stream(&self) -> BalNotificationStream {
238 self.inner.bal_stream()
239 }
240}
241
242impl Default for BalStoreHandle {
243 fn default() -> Self {
244 Self::noop()
245 }
246}
247
248impl core::fmt::Debug for BalStoreHandle {
249 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
250 f.debug_struct("BalStoreHandle").finish_non_exhaustive()
251 }
252}
253
254#[auto_impl::auto_impl(&, Arc)]
256pub trait BalProvider {
257 fn bal_store(&self) -> &BalStoreHandle;
259
260 fn get_bal_by_hash(&self, block_hash: BlockHash) -> ProviderResult<Option<Bytes>> {
262 self.bal_store().get_by_hash(block_hash)
263 }
264
265 fn get_bals_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult<Vec<Option<Bytes>>> {
267 self.bal_store().get_by_hashes(block_hashes)
268 }
269
270 fn get_bals_by_hashes_with_limit(
273 &self,
274 block_hashes: &[BlockHash],
275 limit: GetBlockAccessListLimit,
276 ) -> ProviderResult<Vec<Option<Bytes>>> {
277 self.bal_store().get_by_hashes_with_limit(block_hashes, limit)
278 }
279}
280
281#[derive(Debug, Default, Clone, Copy)]
283pub struct NoopBalStore;
284
285impl BalStore for NoopBalStore {
286 fn insert(&self, _num_hash: NumHash, _bal: RawBal) -> ProviderResult<()> {
287 Ok(())
288 }
289
290 fn insert_many(&self, _entries: Vec<(NumHash, RawBal)>) -> ProviderResult<()> {
291 Ok(())
292 }
293
294 fn prune(&self, _tip: BlockNumber) -> ProviderResult<usize> {
295 Ok(0)
296 }
297
298 fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult<Vec<Option<Bytes>>> {
299 Ok(block_hashes.iter().map(|_| None).collect())
300 }
301
302 fn append_by_hashes_with_limit(
303 &self,
304 block_hashes: &[BlockHash],
305 limit: GetBlockAccessListLimit,
306 out: &mut Vec<Option<Bytes>>,
307 ) -> ProviderResult<()> {
308 let mut size = 0;
309 for _ in block_hashes {
310 size += 1;
311 out.push(None);
312
313 if limit.exceeds(size) {
314 break
315 }
316 }
317 Ok(())
318 }
319
320 #[cfg(feature = "std")]
321 fn bal_stream(&self) -> BalNotificationStream {
322 reth_tokio_util::EventSender::new(1).new_listener()
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329 use alloy_primitives::B256;
330 #[cfg(feature = "std")]
331 use tokio_stream::StreamExt;
332
333 const EMPTY_LIST_CODE: u8 = 0xc0;
334
335 #[test]
336 fn noop_store_returns_empty_results() {
337 let store = BalStoreHandle::default();
338 let hashes = [B256::random(), B256::random()];
339
340 let by_hash = store.get_by_hashes(&hashes).unwrap();
341
342 assert_eq!(by_hash, vec![None, None]);
343 assert!(store.get_by_hash(B256::random()).unwrap().is_none());
344 assert_eq!(store.prune(10).unwrap(), 0);
345 }
346
347 #[test]
348 fn noop_provider_returns_empty_results() {
349 let provider = crate::noop::NoopProvider::default();
350 let hashes = [B256::random(), B256::random()];
351
352 assert_eq!(provider.get_bals_by_hashes(&hashes).unwrap(), vec![None, None]);
353 assert_eq!(
354 provider
355 .get_bals_by_hashes_with_limit(
356 &hashes,
357 GetBlockAccessListLimit::ResponseSizeSoftLimit(0),
358 )
359 .unwrap(),
360 vec![None]
361 );
362 assert!(provider.get_bal_by_hash(B256::random()).unwrap().is_none());
363 }
364
365 #[test]
366 fn noop_store_flush_is_noop() {
367 let store = BalStoreHandle::default();
368
369 store.flush(&[]).unwrap();
370 }
371
372 #[test]
373 fn noop_store_decoded_lookup_returns_none() {
374 let store = BalStoreHandle::default();
375
376 assert!(store.get_decoded_by_hash(B256::random()).unwrap().is_none());
377 }
378
379 #[test]
380 fn decoded_lookup_decodes_raw_bal() {
381 let hash = B256::random();
382 let raw_bal = Bytes::from_static(&[EMPTY_LIST_CODE]);
383 let store = BalStoreHandle::new(TestBalStore { hash, raw_bal: raw_bal.clone() });
384
385 assert_eq!(store.get_by_hash(hash).unwrap(), Some(raw_bal.clone()));
386
387 let decoded = store.get_decoded_by_hash(hash).unwrap().unwrap();
388
389 assert_eq!(decoded.as_raw(), &raw_bal);
390 }
391
392 #[test]
393 fn noop_store_limited_lookup_returns_prefix() {
394 let store = BalStoreHandle::default();
395 let hashes = [B256::random(), B256::random(), B256::random()];
396
397 let limited = store
398 .get_by_hashes_with_limit(&hashes, GetBlockAccessListLimit::ResponseSizeSoftLimit(1))
399 .unwrap();
400
401 assert_eq!(limited, vec![None, None]);
402 }
403
404 #[test]
405 fn block_access_list_limit() {
406 let limit_none = GetBlockAccessListLimit::None;
407 assert!(!limit_none.exceeds(usize::MAX));
408
409 let size_limit_2mb = GetBlockAccessListLimit::ResponseSizeSoftLimit(2 * 1024 * 1024);
410 assert!(!size_limit_2mb.exceeds(1024 * 1024));
411 assert!(!size_limit_2mb.exceeds(2 * 1024 * 1024));
412 assert!(size_limit_2mb.exceeds(3 * 1024 * 1024));
413 }
414
415 #[cfg(feature = "std")]
416 #[tokio::test]
417 async fn noop_store_stream_is_empty() {
418 let store = BalStoreHandle::default();
419 let mut stream = store.bal_stream();
420
421 assert!(stream.next().await.is_none());
422 }
423
424 #[derive(Debug)]
425 struct TestBalStore {
426 hash: B256,
427 raw_bal: Bytes,
428 }
429
430 impl BalStore for TestBalStore {
431 fn insert(&self, _num_hash: NumHash, _bal: RawBal) -> ProviderResult<()> {
432 Ok(())
433 }
434
435 fn prune(&self, _tip: BlockNumber) -> ProviderResult<usize> {
436 Ok(0)
437 }
438
439 fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> ProviderResult<Vec<Option<Bytes>>> {
440 Ok(block_hashes
441 .iter()
442 .map(|hash| (*hash == self.hash).then(|| self.raw_bal.clone()))
443 .collect())
444 }
445
446 #[cfg(feature = "std")]
447 fn bal_stream(&self) -> BalNotificationStream {
448 reth_tokio_util::EventSender::new(1).new_listener()
449 }
450 }
451}