feat: introduce custom exex wal errors (#11789)

Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com>
This commit is contained in:
caglarkaya
2025-01-28 17:18:33 +03:00
committed by GitHub
parent 36eec984a0
commit 77568f8d3e
6 changed files with 64 additions and 31 deletions

View File

@ -47,6 +47,7 @@ itertools = { workspace = true, features = ["use_std"] }
metrics.workspace = true
parking_lot.workspace = true
rmp-serde = "1.3"
thiserror.workspace = true
tracing.workspace = true
[dev-dependencies]

View File

@ -657,6 +657,7 @@ impl<N: NodePrimitives> Clone for ExExManagerHandle<N> {
#[cfg(test)]
mod tests {
use super::*;
use crate::wal::WalResult;
use alloy_primitives::B256;
use futures::{StreamExt, TryStreamExt};
use rand::Rng;
@ -1356,7 +1357,7 @@ mod tests {
);
// WAL shouldn't contain the genesis notification, because it's finalized
assert_eq!(
exex_manager.wal.iter_notifications()?.collect::<eyre::Result<Vec<_>>>()?,
exex_manager.wal.iter_notifications()?.collect::<WalResult<Vec<_>>>()?,
[notification.clone()]
);
@ -1364,7 +1365,7 @@ mod tests {
assert!(exex_manager.as_mut().poll(&mut cx).is_pending());
// WAL isn't finalized because the ExEx didn't emit the `FinishedHeight` event
assert_eq!(
exex_manager.wal.iter_notifications()?.collect::<eyre::Result<Vec<_>>>()?,
exex_manager.wal.iter_notifications()?.collect::<WalResult<Vec<_>>>()?,
[notification.clone()]
);
@ -1378,7 +1379,7 @@ mod tests {
// WAL isn't finalized because the ExEx emitted a `FinishedHeight` event with a
// non-canonical block
assert_eq!(
exex_manager.wal.iter_notifications()?.collect::<eyre::Result<Vec<_>>>()?,
exex_manager.wal.iter_notifications()?.collect::<WalResult<Vec<_>>>()?,
[notification]
);

View File

@ -0,0 +1,29 @@
//! Wal Errors
use std::path::PathBuf;
/// Wal Result type.
pub type WalResult<T> = Result<T, WalError>;
/// Wal Error types
#[derive(Debug, thiserror::Error)]
pub enum WalError {
/// Filesystem error at the path
#[error(transparent)]
FsPathError(#[from] reth_fs_util::FsPathError),
/// Directory entry reading error
#[error("failed to get {0} directory entry: {1}")]
DirEntry(PathBuf, std::io::Error),
/// Error when reading file metadata
#[error("failed to get metadata for file {0}: {1}")]
FileMetadata(u32, std::io::Error),
/// Parse error
#[error("failed to parse file name: {0}")]
Parse(String),
/// Notification not found error
#[error("notification {0} not found")]
FileNotFound(u32),
/// Decode error
#[error("failed to decode notification {0} from {1}: {2}")]
Decode(u32, PathBuf, rmp_serde::decode::Error),
}

View File

@ -8,6 +8,8 @@ use reth_primitives::EthPrimitives;
pub use storage::Storage;
mod metrics;
use metrics::Metrics;
mod error;
pub use error::{WalError, WalResult};
use std::{
path::Path,
@ -43,7 +45,7 @@ where
N: NodePrimitives,
{
/// Creates a new instance of [`Wal`].
pub fn new(directory: impl AsRef<Path>) -> eyre::Result<Self> {
pub fn new(directory: impl AsRef<Path>) -> WalResult<Self> {
Ok(Self { inner: Arc::new(WalInner::new(directory)?) })
}
@ -53,7 +55,7 @@ where
}
/// Commits the notification to WAL.
pub fn commit(&self, notification: &ExExNotification<N>) -> eyre::Result<()> {
pub fn commit(&self, notification: &ExExNotification<N>) -> WalResult<()> {
self.inner.commit(notification)
}
@ -61,14 +63,14 @@ where
///
/// The caller should check that all ExExes are on the canonical chain and will not need any
/// blocks from the WAL below the provided block, inclusive.
pub fn finalize(&self, to_block: BlockNumHash) -> eyre::Result<()> {
pub fn finalize(&self, to_block: BlockNumHash) -> WalResult<()> {
self.inner.finalize(to_block)
}
/// Returns an iterator over all notifications in the WAL.
pub fn iter_notifications(
&self,
) -> eyre::Result<Box<dyn Iterator<Item = eyre::Result<ExExNotification<N>>> + '_>> {
) -> WalResult<Box<dyn Iterator<Item = WalResult<ExExNotification<N>>> + '_>> {
self.inner.iter_notifications()
}
@ -93,7 +95,7 @@ impl<N> WalInner<N>
where
N: NodePrimitives,
{
fn new(directory: impl AsRef<Path>) -> eyre::Result<Self> {
fn new(directory: impl AsRef<Path>) -> WalResult<Self> {
let mut wal = Self {
next_file_id: AtomicU32::new(0),
storage: Storage::new(directory)?,
@ -110,7 +112,7 @@ where
/// Fills the block cache with the notifications from the storage.
#[instrument(skip(self))]
fn fill_block_cache(&mut self) -> eyre::Result<()> {
fn fill_block_cache(&mut self) -> WalResult<()> {
let Some(files_range) = self.storage.files_range()? else { return Ok(()) };
self.next_file_id.store(files_range.end() + 1, Ordering::Relaxed);
@ -145,7 +147,7 @@ where
reverted_block_range = ?notification.reverted_chain().as_ref().map(|chain| chain.range()),
committed_block_range = ?notification.committed_chain().as_ref().map(|chain| chain.range())
))]
fn commit(&self, notification: &ExExNotification<N>) -> eyre::Result<()> {
fn commit(&self, notification: &ExExNotification<N>) -> WalResult<()> {
let mut block_cache = self.block_cache.write();
let file_id = self.next_file_id.fetch_add(1, Ordering::Relaxed);
@ -160,7 +162,7 @@ where
}
#[instrument(skip(self))]
fn finalize(&self, to_block: BlockNumHash) -> eyre::Result<()> {
fn finalize(&self, to_block: BlockNumHash) -> WalResult<()> {
let mut block_cache = self.block_cache.write();
let file_ids = block_cache.remove_before(to_block.number);
@ -195,7 +197,7 @@ where
/// Returns an iterator over all notifications in the WAL.
fn iter_notifications(
&self,
) -> eyre::Result<Box<dyn Iterator<Item = eyre::Result<ExExNotification<N>>> + '_>> {
) -> WalResult<Box<dyn Iterator<Item = WalResult<ExExNotification<N>>> + '_>> {
let Some(range) = self.storage.files_range()? else {
return Ok(Box::new(std::iter::empty()))
};
@ -218,7 +220,7 @@ where
pub fn get_committed_notification_by_block_hash(
&self,
block_hash: &B256,
) -> eyre::Result<Option<ExExNotification<N>>> {
) -> WalResult<Option<ExExNotification<N>>> {
let Some(file_id) = self.wal.block_cache().get_file_id_by_committed_block_hash(block_hash)
else {
return Ok(None)
@ -233,7 +235,7 @@ where
#[cfg(test)]
mod tests {
use crate::wal::{cache::CachedBlock, Wal};
use crate::wal::{cache::CachedBlock, error::WalResult, Wal};
use alloy_primitives::B256;
use itertools::Itertools;
use reth_exex_types::ExExNotification;
@ -243,7 +245,7 @@ mod tests {
};
use std::sync::Arc;
fn read_notifications(wal: &Wal) -> eyre::Result<Vec<ExExNotification>> {
fn read_notifications(wal: &Wal) -> WalResult<Vec<ExExNotification>> {
wal.inner.storage.files_range()?.map_or(Ok(Vec::new()), |range| {
wal.inner
.storage

View File

@ -4,7 +4,7 @@ use std::{
path::{Path, PathBuf},
};
use eyre::OptionExt;
use crate::wal::{WalError, WalResult};
use reth_exex_types::ExExNotification;
use reth_node_api::NodePrimitives;
use reth_primitives::EthPrimitives;
@ -30,7 +30,7 @@ where
{
/// Creates a new instance of [`Storage`] backed by the file at the given path and creates
/// it doesn't exist.
pub(super) fn new(path: impl AsRef<Path>) -> eyre::Result<Self> {
pub(super) fn new(path: impl AsRef<Path>) -> WalResult<Self> {
reth_fs_util::create_dir_all(&path)?;
Ok(Self { path: path.as_ref().to_path_buf(), _pd: std::marker::PhantomData })
@ -40,11 +40,11 @@ where
self.path.join(format!("{id}.{FILE_EXTENSION}"))
}
fn parse_filename(filename: &str) -> eyre::Result<u32> {
fn parse_filename(filename: &str) -> WalResult<u32> {
filename
.strip_suffix(".wal")
.and_then(|s| s.parse().ok())
.ok_or_eyre(format!("failed to parse file name: {filename}"))
.ok_or_else(|| WalError::Parse(filename.to_string()))
}
/// Removes notification for the given file ID from the storage.
@ -72,12 +72,12 @@ where
/// Returns the range of file IDs in the storage.
///
/// If there are no files in the storage, returns `None`.
pub(super) fn files_range(&self) -> eyre::Result<Option<RangeInclusive<u32>>> {
pub(super) fn files_range(&self) -> WalResult<Option<RangeInclusive<u32>>> {
let mut min_id = None;
let mut max_id = None;
for entry in reth_fs_util::read_dir(&self.path)? {
let entry = entry?;
let entry = entry.map_err(|err| WalError::DirEntry(self.path.clone(), err))?;
if entry.path().extension() == Some(FILE_EXTENSION.as_ref()) {
let file_name = entry.file_name();
@ -99,7 +99,7 @@ where
pub(super) fn remove_notifications(
&self,
file_ids: impl IntoIterator<Item = u32>,
) -> eyre::Result<(usize, u64)> {
) -> WalResult<(usize, u64)> {
let mut deleted_total = 0;
let mut deleted_size = 0;
@ -116,10 +116,10 @@ where
pub(super) fn iter_notifications(
&self,
range: RangeInclusive<u32>,
) -> impl Iterator<Item = eyre::Result<(u32, u64, ExExNotification<N>)>> + '_ {
) -> impl Iterator<Item = WalResult<(u32, u64, ExExNotification<N>)>> + '_ {
range.map(move |id| {
let (notification, size) =
self.read_notification(id)?.ok_or_eyre(format!("notification {id} not found"))?;
self.read_notification(id)?.ok_or(WalError::FileNotFound(id))?;
Ok((id, size, notification))
})
@ -130,7 +130,7 @@ where
pub(super) fn read_notification(
&self,
file_id: u32,
) -> eyre::Result<Option<(ExExNotification<N>, u64)>> {
) -> WalResult<Option<(ExExNotification<N>, u64)>> {
let file_path = self.file_path(file_id);
debug!(target: "exex::wal::storage", ?file_path, "Reading notification from WAL");
@ -139,13 +139,12 @@ where
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(err) => return Err(reth_fs_util::FsPathError::open(err, &file_path).into()),
};
let size = file.metadata()?.len();
let size = file.metadata().map_err(|err| WalError::FileMetadata(file_id, err))?.len();
// Deserialize using the bincode- and msgpack-compatible serde wrapper
let notification: reth_exex_types::serde_bincode_compat::ExExNotification<'_, N> =
rmp_serde::decode::from_read(&mut file).map_err(|err| {
eyre::eyre!("failed to decode notification from {file_path:?}: {err:?}")
})?;
rmp_serde::decode::from_read(&mut file)
.map_err(|err| WalError::Decode(file_id, file_path, err))?;
Ok(Some((notification.into(), size)))
}
@ -160,7 +159,7 @@ where
&self,
file_id: u32,
notification: &ExExNotification<N>,
) -> eyre::Result<u64> {
) -> WalResult<u64> {
let file_path = self.file_path(file_id);
debug!(target: "exex::wal::storage", ?file_path, "Writing notification to WAL");
@ -172,7 +171,7 @@ where
rmp_serde::encode::write(file, &notification)
})?;
Ok(file_path.metadata()?.len())
Ok(file_path.metadata().map_err(|err| WalError::FileMetadata(file_id, err))?.len())
}
}