reth_transaction_pool/blobstore/
mod.rs

1//! Storage for blob data of EIP4844 transactions.
2
3use alloy_eips::{
4    eip4844::{BlobAndProofV1, BlobAndProofV2},
5    eip7594::BlobTransactionSidecarVariant,
6};
7use alloy_primitives::B256;
8pub use disk::{DiskFileBlobStore, DiskFileBlobStoreConfig, OpenDiskFileBlobStore};
9pub use mem::InMemoryBlobStore;
10pub use noop::NoopBlobStore;
11use std::{
12    fmt,
13    sync::{
14        atomic::{AtomicUsize, Ordering},
15        Arc,
16    },
17};
18pub use tracker::{BlobStoreCanonTracker, BlobStoreUpdates};
19
20pub mod disk;
21mod mem;
22mod noop;
23mod tracker;
24
25/// A blob store that can be used to store blob data of EIP4844 transactions.
26///
27/// This type is responsible for keeping track of blob data until it is no longer needed (after
28/// finalization).
29///
30/// Note: this is Clone because it is expected to be wrapped in an Arc.
31pub trait BlobStore: fmt::Debug + Send + Sync + 'static {
32    /// Inserts the blob sidecar into the store
33    fn insert(&self, tx: B256, data: BlobTransactionSidecarVariant) -> Result<(), BlobStoreError>;
34
35    /// Inserts multiple blob sidecars into the store
36    fn insert_all(
37        &self,
38        txs: Vec<(B256, BlobTransactionSidecarVariant)>,
39    ) -> Result<(), BlobStoreError>;
40
41    /// Deletes the blob sidecar from the store
42    fn delete(&self, tx: B256) -> Result<(), BlobStoreError>;
43
44    /// Deletes multiple blob sidecars from the store
45    fn delete_all(&self, txs: Vec<B256>) -> Result<(), BlobStoreError>;
46
47    /// A maintenance function that can be called periodically to clean up the blob store, returns
48    /// the number of successfully deleted blobs and the number of failed deletions.
49    ///
50    /// This is intended to be called in the background to clean up any old or unused data, in case
51    /// the store uses deferred cleanup: [`DiskFileBlobStore`]
52    fn cleanup(&self) -> BlobStoreCleanupStat;
53
54    /// Retrieves the decoded blob data for the given transaction hash.
55    fn get(&self, tx: B256) -> Result<Option<Arc<BlobTransactionSidecarVariant>>, BlobStoreError>;
56
57    /// Checks if the given transaction hash is in the blob store.
58    fn contains(&self, tx: B256) -> Result<bool, BlobStoreError>;
59
60    /// Retrieves all decoded blob data for the given transaction hashes.
61    ///
62    /// This only returns the blobs that were found in the store.
63    /// If there's no blob it will not be returned.
64    ///
65    /// Note: this is not guaranteed to return the blobs in the same order as the input.
66    fn get_all(
67        &self,
68        txs: Vec<B256>,
69    ) -> Result<Vec<(B256, Arc<BlobTransactionSidecarVariant>)>, BlobStoreError>;
70
71    /// Returns the exact [`BlobTransactionSidecarVariant`] for the given transaction hashes in the
72    /// exact order they were requested.
73    ///
74    /// Returns an error if any of the blobs are not found in the blob store.
75    fn get_exact(
76        &self,
77        txs: Vec<B256>,
78    ) -> Result<Vec<Arc<BlobTransactionSidecarVariant>>, BlobStoreError>;
79
80    /// Return the [`BlobAndProofV1`]s for a list of blob versioned hashes.
81    fn get_by_versioned_hashes_v1(
82        &self,
83        versioned_hashes: &[B256],
84    ) -> Result<Vec<Option<BlobAndProofV1>>, BlobStoreError>;
85
86    /// Return the [`BlobAndProofV2`]s for a list of blob versioned hashes.
87    /// Blobs and proofs are returned only if they are present for _all_ requested
88    /// versioned hashes.
89    ///
90    /// This differs from [`BlobStore::get_by_versioned_hashes_v1`] in that it also returns all the
91    /// cell proofs in [`BlobAndProofV2`] supported by the EIP-7594 blob sidecar variant.
92    ///
93    /// The response also differs from [`BlobStore::get_by_versioned_hashes_v1`] in that this
94    /// returns `None` if any of the requested versioned hashes are not present in the blob store:
95    /// e.g. where v1 would return `[A, None, C]` v2 would return `None`. See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/osaka.md#engine_getblobsv2>
96    fn get_by_versioned_hashes_v2(
97        &self,
98        versioned_hashes: &[B256],
99    ) -> Result<Option<Vec<BlobAndProofV2>>, BlobStoreError>;
100
101    /// Data size of all transactions in the blob store.
102    fn data_size_hint(&self) -> Option<usize>;
103
104    /// How many blobs are in the blob store.
105    fn blobs_len(&self) -> usize;
106}
107
108/// Error variants that can occur when interacting with a blob store.
109#[derive(Debug, thiserror::Error)]
110pub enum BlobStoreError {
111    /// Thrown if the blob sidecar is not found for a given transaction hash but was required.
112    #[error("blob sidecar not found for transaction {0:?}")]
113    MissingSidecar(B256),
114    /// Failed to decode the stored blob data.
115    #[error("failed to decode blob data: {0}")]
116    DecodeError(#[from] alloy_rlp::Error),
117    /// Other implementation specific error.
118    #[error(transparent)]
119    Other(Box<dyn core::error::Error + Send + Sync>),
120}
121
122/// Keeps track of the size of the blob store.
123#[derive(Debug, Default)]
124pub(crate) struct BlobStoreSize {
125    data_size: AtomicUsize,
126    num_blobs: AtomicUsize,
127}
128
129impl BlobStoreSize {
130    #[inline]
131    pub(crate) fn add_size(&self, add: usize) {
132        self.data_size.fetch_add(add, Ordering::Relaxed);
133    }
134
135    #[inline]
136    pub(crate) fn sub_size(&self, sub: usize) {
137        let _ = self.data_size.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
138            Some(current.saturating_sub(sub))
139        });
140    }
141
142    #[inline]
143    pub(crate) fn update_len(&self, len: usize) {
144        self.num_blobs.store(len, Ordering::Relaxed);
145    }
146
147    #[inline]
148    pub(crate) fn inc_len(&self, add: usize) {
149        self.num_blobs.fetch_add(add, Ordering::Relaxed);
150    }
151
152    #[inline]
153    pub(crate) fn sub_len(&self, sub: usize) {
154        let _ = self.num_blobs.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
155            Some(current.saturating_sub(sub))
156        });
157    }
158
159    #[inline]
160    pub(crate) fn data_size(&self) -> usize {
161        self.data_size.load(Ordering::Relaxed)
162    }
163
164    #[inline]
165    pub(crate) fn blobs_len(&self) -> usize {
166        self.num_blobs.load(Ordering::Relaxed)
167    }
168}
169
170impl PartialEq for BlobStoreSize {
171    fn eq(&self, other: &Self) -> bool {
172        self.data_size.load(Ordering::Relaxed) == other.data_size.load(Ordering::Relaxed) &&
173            self.num_blobs.load(Ordering::Relaxed) == other.num_blobs.load(Ordering::Relaxed)
174    }
175}
176
177/// Statistics for the cleanup operation.
178#[derive(Debug, Clone, Default, PartialEq, Eq)]
179pub struct BlobStoreCleanupStat {
180    /// the number of successfully deleted blobs
181    pub delete_succeed: usize,
182    /// the number of failed deletions
183    pub delete_failed: usize,
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[expect(dead_code)]
191    struct DynStore {
192        store: Box<dyn BlobStore>,
193    }
194}