feat(error): add wrappers for std::fs methods to track path for errors (#3367)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Thomas Coratger
2023-07-01 13:49:26 +02:00
committed by GitHub
parent e468e15e9c
commit 2126c01a42
14 changed files with 155 additions and 54 deletions

View File

@ -1,8 +1,8 @@
use hex::encode as hex_encode;
use reth_network::config::rng_secret_key;
use reth_primitives::{fs, fs::FsPathError};
use secp256k1::{Error as SecretKeyBaseError, SecretKey};
use std::{
fs::read_to_string,
io,
path::{Path, PathBuf},
};
@ -14,12 +14,8 @@ use thiserror::Error;
pub enum SecretKeyError {
#[error(transparent)]
SecretKeyDecodeError(#[from] SecretKeyBaseError),
#[error("Failed to create parent directory {dir:?} for secret key: {error}")]
FailedToCreateSecretParentDir { error: io::Error, dir: PathBuf },
#[error("Failed to write secret key file {secret_file:?}: {error}")]
FailedToWriteSecretKeyFile { error: io::Error, secret_file: PathBuf },
#[error("Failed to read secret key file {secret_file:?}: {error}")]
FailedToReadSecretKeyFile { error: io::Error, secret_file: PathBuf },
#[error(transparent)]
SecretKeyFsPathError(#[from] FsPathError),
#[error("Failed to access key file {secret_file:?}: {error}")]
FailedToAccessKeyFile { error: io::Error, secret_file: PathBuf },
}
@ -32,30 +28,19 @@ pub fn get_secret_key(secret_key_path: &Path) -> Result<SecretKey, SecretKeyErro
match exists {
Ok(true) => {
let contents = read_to_string(secret_key_path).map_err(|error| {
SecretKeyError::FailedToReadSecretKeyFile {
error,
secret_file: secret_key_path.to_path_buf(),
}
})?;
(contents.as_str().parse::<SecretKey>()).map_err(SecretKeyError::SecretKeyDecodeError)
let contents = fs::read_to_string(secret_key_path)?;
Ok((contents.as_str().parse::<SecretKey>())
.map_err(SecretKeyError::SecretKeyDecodeError)?)
}
Ok(false) => {
if let Some(dir) = secret_key_path.parent() {
// Create parent directory
std::fs::create_dir_all(dir).map_err(|error| {
SecretKeyError::FailedToCreateSecretParentDir { error, dir: dir.to_path_buf() }
})?;
fs::create_dir_all(dir)?;
}
let secret = rng_secret_key();
let hex = hex_encode(secret.as_ref());
std::fs::write(secret_key_path, hex).map_err(|error| {
SecretKeyError::FailedToWriteSecretKeyFile {
error,
secret_file: secret_key_path.to_path_buf(),
}
})?;
fs::write(secret_key_path, hex)?;
Ok(secret)
}
Err(error) => Err(SecretKeyError::FailedToAccessKeyFile {

View File

@ -1,6 +1,8 @@
//! Clap parser utilities
use reth_primitives::{AllGenesisFormats, BlockHashOrNumber, ChainSpec, GOERLI, MAINNET, SEPOLIA};
use reth_primitives::{
fs, AllGenesisFormats, BlockHashOrNumber, ChainSpec, GOERLI, MAINNET, SEPOLIA,
};
use reth_revm::primitives::B256 as H256;
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs},
@ -24,7 +26,7 @@ pub fn chain_spec_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Er
"goerli" => GOERLI.clone(),
"sepolia" => SEPOLIA.clone(),
_ => {
let raw = std::fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?;
let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?;
serde_json::from_str(&raw)?
}
})
@ -38,7 +40,7 @@ pub fn genesis_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Error
"goerli" => GOERLI.clone(),
"sepolia" => SEPOLIA.clone(),
_ => {
let raw = std::fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?;
let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?;
let genesis: AllGenesisFormats = serde_json::from_str(&raw)?;
Arc::new(genesis.into())
}

View File

@ -22,7 +22,7 @@ use reth_interfaces::{
};
use reth_network::NetworkHandle;
use reth_network_api::NetworkInfo;
use reth_primitives::{stage::StageId, BlockHashOrNumber, BlockNumber, ChainSpec, H256};
use reth_primitives::{fs, stage::StageId, BlockHashOrNumber, BlockNumber, ChainSpec, H256};
use reth_provider::{BlockExecutionWriter, ProviderFactory, StageCheckpointReader};
use reth_staged_sync::utils::init::init_genesis;
use reth_stages::{
@ -200,7 +200,7 @@ impl Command {
let data_dir = self.datadir.unwrap_or_chain_default(self.chain.chain);
let db_path = data_dir.db_path();
std::fs::create_dir_all(&db_path)?;
fs::create_dir_all(&db_path)?;
let db = Arc::new(init_db(db_path)?);
debug!(target: "reth::cli", chain=%self.chain.chain, genesis=?self.chain.genesis_hash(), "Initializing genesis");

View File

@ -6,6 +6,7 @@ use crate::{
use clap::Parser;
use reth_db::{cursor::DbCursorRO, init_db, tables, transaction::DbTx};
use reth_primitives::{
fs,
stage::{StageCheckpoint, StageId},
ChainSpec,
};
@ -64,7 +65,7 @@ impl Command {
// add network name to data dir
let data_dir = self.datadir.unwrap_or_chain_default(self.chain.chain);
let db_path = data_dir.db_path();
std::fs::create_dir_all(&db_path)?;
fs::create_dir_all(&db_path)?;
let db = Arc::new(init_db(db_path)?);
let factory = ProviderFactory::new(&db, self.chain.clone());

View File

@ -6,7 +6,7 @@ use crate::{
};
use clap::Parser;
use reth_db::{database::Database, open_db, tables, transaction::DbTxMut, DatabaseEnv};
use reth_primitives::{stage::StageId, ChainSpec};
use reth_primitives::{fs, stage::StageId, ChainSpec};
use reth_staged_sync::utils::init::{insert_genesis_header, insert_genesis_state};
use std::sync::Arc;
use tracing::info;
@ -50,7 +50,7 @@ impl Command {
// add network name to data dir
let data_dir = self.datadir.unwrap_or_chain_default(self.chain.chain);
let db_path = data_dir.db_path();
std::fs::create_dir_all(&db_path)?;
fs::create_dir_all(&db_path)?;
let db = open_db(db_path.as_ref())?;

View File

@ -11,6 +11,7 @@ use reth_db::{
table::{DupSort, Table},
tables,
};
use reth_primitives::fs;
use tracing::error;
const VECTORS_FOLDER: &str = "testdata/micro/db";
@ -19,7 +20,7 @@ const PER_TABLE: usize = 1000;
/// Generates test vectors for specified `tables`. If list is empty, then generate for all tables.
pub(crate) fn generate_vectors(mut tables: Vec<String>) -> Result<()> {
let mut runner = TestRunner::new(ProptestConfig::default());
std::fs::create_dir_all(VECTORS_FOLDER)?;
fs::create_dir_all(VECTORS_FOLDER)?;
macro_rules! generate_vector {
($table_type:ident, $per_table:expr, TABLE) => {

View File

@ -1,6 +1,6 @@
//! Common CLI utility functions.
use eyre::{Result, WrapErr};
use eyre::Result;
use reth_db::{
cursor::DbCursorRO,
database::Database,
@ -11,7 +11,7 @@ use reth_interfaces::p2p::{
headers::client::{HeadersClient, HeadersRequest},
priority::Priority,
};
use reth_primitives::{BlockHashOrNumber, ChainSpec, HeadersDirection, SealedHeader};
use reth_primitives::{fs, BlockHashOrNumber, ChainSpec, HeadersDirection, SealedHeader};
use std::{
env::VarError,
path::{Path, PathBuf},
@ -98,7 +98,7 @@ impl<'a, DB: Database> DbTool<'a, DB> {
pub fn drop(&mut self, path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();
info!(target: "reth::cli", "Dropping database at {:?}", path);
std::fs::remove_dir_all(path).wrap_err("Dropping the database failed")?;
fs::remove_dir_all(path)?;
Ok(())
}

110
crates/primitives/src/fs.rs Normal file
View File

@ -0,0 +1,110 @@
//! Wrapper for `std::fs` methods
use std::{
fs, io,
path::{Path, PathBuf},
};
/// Various error variants for `std::fs` operations that serve as an addition to the io::Error which
/// does not provide any information about the path.
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum FsPathError {
/// Provides additional path context for [`std::fs::write`].
#[error("failed to write to {path:?}: {source}")]
Write { source: io::Error, path: PathBuf },
/// Provides additional path context for [`std::fs::read`].
#[error("failed to read from {path:?}: {source}")]
Read { source: io::Error, path: PathBuf },
/// Provides additional path context for [`std::fs::read_link`].
#[error("failed to read from {path:?}: {source}")]
ReadLink { source: io::Error, path: PathBuf },
/// Provides additional path context for [`std::fs::File::create`].
#[error("failed to create file {path:?}: {source}")]
CreateFile { source: io::Error, path: PathBuf },
/// Provides additional path context for [`std::fs::remove_file`].
#[error("failed to remove file {path:?}: {source}")]
RemoveFile { source: io::Error, path: PathBuf },
/// Provides additional path context for [`std::fs::create_dir`].
#[error("failed to create dir {path:?}: {source}")]
CreateDir { source: io::Error, path: PathBuf },
/// Provides additional path context for [`std::fs::remove_dir`].
#[error("failed to remove dir {path:?}: {source}")]
RemoveDir { source: io::Error, path: PathBuf },
/// Provides additional path context for [`std::fs::File::open`].
#[error("failed to open file {path:?}: {source}")]
Open { source: io::Error, path: PathBuf },
/// Provides additional path context for the file whose contents should be parsed as JSON.
#[error("failed to parse json file: {path:?}: {source}")]
ReadJson { source: serde_json::Error, path: PathBuf },
/// Provides additional path context for the new JSON file.
#[error("failed to write to json file: {path:?}: {source}")]
WriteJson { source: serde_json::Error, path: PathBuf },
}
impl FsPathError {
/// Returns the complementary error variant for [`std::fs::write`].
pub fn write(source: io::Error, path: impl Into<PathBuf>) -> Self {
FsPathError::Write { source, path: path.into() }
}
/// Returns the complementary error variant for [`std::fs::read`].
pub fn read(source: io::Error, path: impl Into<PathBuf>) -> Self {
FsPathError::Read { source, path: path.into() }
}
/// Returns the complementary error variant for [`std::fs::read_link`].
pub fn read_link(source: io::Error, path: impl Into<PathBuf>) -> Self {
FsPathError::ReadLink { source, path: path.into() }
}
/// Returns the complementary error variant for [`std::fs::File::create`].
pub fn create_file(source: io::Error, path: impl Into<PathBuf>) -> Self {
FsPathError::CreateFile { source, path: path.into() }
}
/// Returns the complementary error variant for [`std::fs::remove_file`].
pub fn remove_file(source: io::Error, path: impl Into<PathBuf>) -> Self {
FsPathError::RemoveFile { source, path: path.into() }
}
/// Returns the complementary error variant for [`std::fs::create_dir`].
pub fn create_dir(source: io::Error, path: impl Into<PathBuf>) -> Self {
FsPathError::CreateDir { source, path: path.into() }
}
/// Returns the complementary error variant for [`std::fs::remove_dir`].
pub fn remove_dir(source: io::Error, path: impl Into<PathBuf>) -> Self {
FsPathError::RemoveDir { source, path: path.into() }
}
/// Returns the complementary error variant for [`std::fs::File::open`].
pub fn open(source: io::Error, path: impl Into<PathBuf>) -> Self {
FsPathError::Open { source, path: path.into() }
}
}
type Result<T> = std::result::Result<T, FsPathError>;
/// Wrapper for `std::fs::read_to_string`
pub fn read_to_string(path: impl AsRef<Path>) -> Result<String> {
let path = path.as_ref();
fs::read_to_string(path).map_err(|err| FsPathError::read(err, path))
}
/// Wrapper for `std::fs::write`
pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> {
let path = path.as_ref();
fs::write(path, contents).map_err(|err| FsPathError::write(err, path))
}
/// Wrapper for `std::fs::remove_dir_all`
pub fn remove_dir_all(path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();
fs::remove_dir_all(path).map_err(|err| FsPathError::remove_dir(err, path))
}
/// Wrapper for `std::fs::create_dir_all`
pub fn create_dir_all(path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();
fs::create_dir_all(path).map_err(|err| FsPathError::create_dir(err, path))
}

View File

@ -30,6 +30,7 @@ mod compression;
pub mod constants;
pub mod contract;
mod forkid;
pub mod fs;
mod genesis;
mod hardfork;
mod header;

View File

@ -64,4 +64,4 @@ futures = { workspace = true }
jsonrpsee = { version = "0.18", features = ["client"] }
assert_matches = "1.5.0"
tempfile = "3.5.0"
reth-interfaces = { workspace = true, features = ["test-utils"] }
reth-interfaces = { workspace = true, features = ["test-utils"] }

View File

@ -1,9 +1,10 @@
use hex::encode as hex_encode;
use jsonwebtoken::{decode, errors::ErrorKind, Algorithm, DecodingKey, Validation};
use rand::Rng;
use reth_primitives::{fs, fs::FsPathError};
use serde::{Deserialize, Serialize};
use std::{
path::{Path, PathBuf},
path::Path,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use thiserror::Error;
@ -26,10 +27,8 @@ pub enum JwtError {
MissingOrInvalidAuthorizationHeader,
#[error("JWT decoding error {0}")]
JwtDecodingError(String),
#[error("IO error occurred while reading {path}: {err}")]
IORead { err: std::io::Error, path: PathBuf },
#[error("IO error occurred while writing {path}: {err}")]
IOWrite { err: std::io::Error, path: PathBuf },
#[error(transparent)]
JwtFsPathError(#[from] FsPathError),
#[error("An I/O error occurred: {0}")]
IOError(#[from] std::io::Error),
}
@ -80,8 +79,7 @@ impl JwtSecret {
/// I/O or secret validation errors might occur during read operations in the form of
/// a [`JwtError`].
pub fn from_file(fpath: &Path) -> Result<Self, JwtError> {
let hex = std::fs::read_to_string(fpath)
.map_err(|err| JwtError::IORead { err, path: fpath.to_path_buf() })?;
let hex = fs::read_to_string(fpath)?;
let secret = JwtSecret::from_hex(hex)?;
Ok(secret)
}
@ -91,14 +89,13 @@ impl JwtSecret {
pub fn try_create(fpath: &Path) -> Result<Self, JwtError> {
if let Some(dir) = fpath.parent() {
// Create parent directory
std::fs::create_dir_all(dir)?
fs::create_dir_all(dir)?
}
let secret = JwtSecret::random();
let bytes = &secret.0;
let hex = hex::encode(bytes);
std::fs::write(fpath, hex)
.map_err(|err| JwtError::IOWrite { err, path: fpath.to_path_buf() })?;
fs::write(fpath, hex)?;
Ok(secret)
}
}
@ -204,6 +201,7 @@ mod tests {
use assert_matches::assert_matches;
use hex::encode as hex_encode;
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
use reth_primitives::fs::FsPathError;
use std::{
path::Path,
time::{Duration, SystemTime, UNIX_EPOCH},
@ -375,7 +373,9 @@ mod tests {
fn provided_file_not_exists() {
let fpath = Path::new("secret3.hex");
let result = JwtSecret::from_file(fpath);
assert_matches!(result, Err(JwtError::IORead {err: _, path}) if path == fpath.to_path_buf());
assert_matches!(result,
Err(JwtError::JwtFsPathError(FsPathError::Read { source: _, path })) if path == fpath.to_path_buf()
);
assert!(!exists(fpath));
}
@ -383,7 +383,7 @@ mod tests {
fn provided_file_is_a_directory() {
let dir = tempdir().unwrap();
let result = JwtSecret::from_file(dir.path());
assert_matches!(result, Err(JwtError::IORead {err: _, path}) if path == dir.into_path());
assert_matches!(result, Err(JwtError::JwtFsPathError(FsPathError::Read { source: _, path })) if path == dir.into_path());
}
fn hex(secret: &JwtSecret) -> String {

View File

@ -130,7 +130,7 @@ where
b.iter_with_setup(
|| {
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
let _ = fs::remove_dir_all(bench_db_path);
(
input.clone(),
Arc::try_unwrap(create_test_rw_db_with_path(bench_db_path)).unwrap(),
@ -156,7 +156,7 @@ where
b.iter_with_setup(
|| {
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
let _ = fs::remove_dir_all(bench_db_path);
(input, Arc::try_unwrap(create_test_rw_db_with_path(bench_db_path)).unwrap())
},
|(input, db)| {
@ -227,7 +227,7 @@ where
b.iter_with_setup(
|| {
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
let _ = fs::remove_dir_all(bench_db_path);
(
input.clone(),
Arc::try_unwrap(create_test_rw_db_with_path(bench_db_path)).unwrap(),
@ -253,7 +253,7 @@ where
b.iter_with_setup(
|| {
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
let _ = fs::remove_dir_all(bench_db_path);
(input, Arc::try_unwrap(create_test_rw_db_with_path(bench_db_path)).unwrap())
},

View File

@ -84,7 +84,7 @@ where
// Setup phase before each benchmark iteration
let setup = || {
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
let _ = fs::remove_dir_all(bench_db_path);
let db = Arc::try_unwrap(create_test_rw_db_with_path(bench_db_path)).unwrap();
let mut unsorted_input = unsorted_input.clone();

View File

@ -6,6 +6,7 @@ use reth_db::{
transaction::{DbTx, DbTxMut},
DatabaseEnv,
};
use reth_primitives::fs;
use std::{path::Path, sync::Arc};
/// Path where the DB is initialized for benchmarks.
@ -59,7 +60,7 @@ where
T::Value: Default + Clone,
{
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
let _ = fs::remove_dir_all(bench_db_path);
let db = Arc::try_unwrap(create_test_rw_db_with_path(bench_db_path)).unwrap();
{