reth_transaction_pool/blobstore/
mod.rs1use alloy_eips::{
4 eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
5 eip7594::{BlobCellMask, BlobTransactionSidecarVariant, Cell},
6};
7use alloy_primitives::{TxHash, B128, B256};
8pub use converter::BlobSidecarConverter;
9pub use disk::{DiskFileBlobStore, DiskFileBlobStoreConfig, OpenDiskFileBlobStore};
10pub use mem::InMemoryBlobStore;
11pub use noop::NoopBlobStore;
12use std::{
13 fmt,
14 ops::Deref,
15 sync::{
16 atomic::{AtomicU64, AtomicUsize, Ordering},
17 Arc,
18 },
19};
20pub use tracker::{BlobStoreCanonTracker, BlobStoreUpdates};
21
22mod converter;
23pub mod disk;
24mod mem;
25mod noop;
26mod tracker;
27
28#[derive(Debug, Clone)]
33pub struct BlobCellAvailability(Arc<[AtomicU64; 2]>);
34
35impl BlobCellAvailability {
36 const LOW_WORD: usize = 0;
37 const HIGH_WORD: usize = 1;
38
39 pub fn full() -> Self {
41 Self(Arc::new([AtomicU64::new(u64::MAX), AtomicU64::new(u64::MAX)]))
42 }
43
44 pub fn get(&self) -> BlobCellMask {
49 let low = self.0[Self::LOW_WORD].load(Ordering::Relaxed) as u128;
50 let high = self.0[Self::HIGH_WORD].load(Ordering::Relaxed) as u128;
51 BlobCellMask::from_bits((high << 64) | low)
52 }
53
54 pub fn is_full(&self) -> bool {
56 self.get().bits() == u128::MAX
57 }
58}
59
60impl PartialEq for BlobCellAvailability {
61 fn eq(&self, other: &Self) -> bool {
62 self.get() == other.get()
63 }
64}
65
66impl Eq for BlobCellAvailability {}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct PooledBlobSidecar {
71 sidecar: BlobTransactionSidecarVariant,
72 availability: BlobCellAvailability,
73}
74
75impl PooledBlobSidecar {
76 pub const fn new(
78 sidecar: BlobTransactionSidecarVariant,
79 availability: BlobCellAvailability,
80 ) -> Self {
81 Self { sidecar, availability }
82 }
83
84 pub const fn sidecar(&self) -> &BlobTransactionSidecarVariant {
86 &self.sidecar
87 }
88
89 pub const fn is_eip7594(&self) -> bool {
91 self.sidecar.is_eip7594()
92 }
93
94 pub const fn availability(&self) -> &BlobCellAvailability {
96 &self.availability
97 }
98
99 pub fn into_sidecar(self) -> BlobTransactionSidecarVariant {
101 self.sidecar
102 }
103}
104
105impl Deref for PooledBlobSidecar {
106 type Target = BlobTransactionSidecarVariant;
107
108 fn deref(&self) -> &Self::Target {
109 &self.sidecar
110 }
111}
112
113impl From<BlobTransactionSidecarVariant> for PooledBlobSidecar {
114 fn from(sidecar: BlobTransactionSidecarVariant) -> Self {
115 Self::new(sidecar, BlobCellAvailability::full())
117 }
118}
119
120pub trait BlobStore: fmt::Debug + Send + Sync + 'static {
127 fn insert(&self, tx: B256, data: PooledBlobSidecar) -> Result<(), BlobStoreError>;
129
130 fn insert_all(&self, txs: Vec<(B256, PooledBlobSidecar)>) -> Result<(), BlobStoreError>;
132
133 fn delete(&self, tx: B256) -> Result<(), BlobStoreError>;
135
136 fn delete_all(&self, txs: Vec<B256>) -> Result<(), BlobStoreError>;
138
139 fn cleanup(&self) -> BlobStoreCleanupStat;
145
146 fn get(&self, tx: B256) -> Result<Option<Arc<BlobTransactionSidecarVariant>>, BlobStoreError>;
148
149 fn contains(&self, tx: B256) -> Result<bool, BlobStoreError>;
151
152 fn get_all(
159 &self,
160 txs: Vec<B256>,
161 ) -> Result<Vec<(B256, Arc<BlobTransactionSidecarVariant>)>, BlobStoreError>;
162
163 fn get_exact(
168 &self,
169 txs: Vec<B256>,
170 ) -> Result<Vec<Arc<BlobTransactionSidecarVariant>>, BlobStoreError>;
171
172 fn get_by_versioned_hashes_v1(
174 &self,
175 versioned_hashes: &[B256],
176 ) -> Result<Vec<Option<BlobAndProofV1>>, BlobStoreError>;
177
178 fn get_by_versioned_hashes_v2(
189 &self,
190 versioned_hashes: &[B256],
191 ) -> Result<Option<Vec<BlobAndProofV2>>, BlobStoreError>;
192
193 fn get_by_versioned_hashes_v3(
198 &self,
199 versioned_hashes: &[B256],
200 ) -> Result<Vec<Option<BlobAndProofV2>>, BlobStoreError>;
201
202 fn get_by_versioned_hashes_v4(
208 &self,
209 versioned_hashes: &[B256],
210 indices_bitarray: B128,
211 ) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError>;
212
213 fn has_versioned_hashes(&self, versioned_hashes: &[B256]) -> Result<Vec<bool>, BlobStoreError>;
217
218 fn get_cells(
239 &self,
240 tx_hash: TxHash,
241 indices_bitarray: B128,
242 ) -> Result<Option<Vec<Cell>>, BlobStoreError>;
243
244 fn data_size_hint(&self) -> Option<usize>;
246
247 fn blobs_len(&self) -> usize;
249}
250
251#[derive(Debug, thiserror::Error)]
253pub enum BlobStoreError {
254 #[error("blob sidecar not found for transaction {0:?}")]
256 MissingSidecar(B256),
257 #[error("failed to decode blob data: {0}")]
259 DecodeError(#[from] alloy_rlp::Error),
260 #[error(transparent)]
262 Other(Box<dyn core::error::Error + Send + Sync>),
263}
264
265#[derive(Debug, Default)]
267pub(crate) struct BlobStoreSize {
268 data_size: AtomicUsize,
269 num_blobs: AtomicUsize,
270}
271
272impl BlobStoreSize {
273 #[inline]
274 pub(crate) fn add_size(&self, add: usize) {
275 self.data_size.fetch_add(add, Ordering::Relaxed);
276 }
277
278 #[inline]
279 pub(crate) fn sub_size(&self, sub: usize) {
280 let _ = self.data_size.try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
281 Some(current.saturating_sub(sub))
282 });
283 }
284
285 #[inline]
286 pub(crate) fn update_len(&self, len: usize) {
287 self.num_blobs.store(len, Ordering::Relaxed);
288 }
289
290 #[inline]
291 pub(crate) fn inc_len(&self, add: usize) {
292 self.num_blobs.fetch_add(add, Ordering::Relaxed);
293 }
294
295 #[inline]
296 pub(crate) fn sub_len(&self, sub: usize) {
297 let _ = self.num_blobs.try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
298 Some(current.saturating_sub(sub))
299 });
300 }
301
302 #[inline]
303 pub(crate) fn data_size(&self) -> usize {
304 self.data_size.load(Ordering::Relaxed)
305 }
306
307 #[inline]
308 pub(crate) fn blobs_len(&self) -> usize {
309 self.num_blobs.load(Ordering::Relaxed)
310 }
311}
312
313impl PartialEq for BlobStoreSize {
314 fn eq(&self, other: &Self) -> bool {
315 self.data_size.load(Ordering::Relaxed) == other.data_size.load(Ordering::Relaxed) &&
316 self.num_blobs.load(Ordering::Relaxed) == other.num_blobs.load(Ordering::Relaxed)
317 }
318}
319
320#[derive(Debug, Clone, Default, PartialEq, Eq)]
322pub struct BlobStoreCleanupStat {
323 pub delete_succeed: usize,
325 pub delete_failed: usize,
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use alloy_eips::{eip4844::BlobTransactionSidecar, eip7594::BlobTransactionSidecarEip7594};
333
334 #[expect(dead_code)]
335 struct DynStore {
336 store: Box<dyn BlobStore>,
337 }
338
339 #[test]
340 fn pooled_blob_sidecar_defaults_to_full_availability() {
341 let sidecars = [
342 BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default()),
343 BlobTransactionSidecarVariant::Eip7594(BlobTransactionSidecarEip7594::default()),
344 ];
345
346 for sidecar in sidecars {
347 assert!(PooledBlobSidecar::from(sidecar).availability().is_full());
348 }
349 }
350
351 #[test]
352 fn blob_cell_availability_uses_cell_index_bit_order() {
353 let availability =
354 BlobCellAvailability(Arc::new([AtomicU64::new(1), AtomicU64::new(1 << 1)]));
355
356 let mask = availability.get();
357 assert!(mask.contains(0));
358 assert!(mask.contains(65));
359 assert_eq!(mask.count(), 2);
360 }
361}