feat(db): propagate text error messages from mdbx (#6353)

This commit is contained in:
Seva Zhidkov
2024-02-02 23:01:01 +00:00
committed by GitHub
parent e86b2c175f
commit c7a4bc9f93
5 changed files with 56 additions and 35 deletions

View File

@@ -1,43 +1,64 @@
use std::fmt::Display;
use thiserror::Error;
/// Database error type.
#[derive(Clone, Debug, PartialEq, Eq, Error)]
pub enum DatabaseError {
/// Failed to open the database.
#[error("failed to open the database ({0})")]
Open(i32),
#[error("failed to open the database: {0}")]
Open(DatabaseErrorInfo),
/// Failed to create a table in the database.
#[error("failed to create a table ({0})")]
CreateTable(i32),
#[error("failed to create a table: {0}")]
CreateTable(DatabaseErrorInfo),
/// Failed to write a value into a table.
#[error(transparent)]
Write(#[from] Box<DatabaseWriteError>),
/// Failed to read a value from a table.
#[error("failed to read a value from a database table ({0})")]
Read(i32),
#[error("failed to read a value from a database table: {0}")]
Read(DatabaseErrorInfo),
/// Failed to delete a `(key, value)` pair from a table.
#[error("database delete error code ({0})")]
Delete(i32),
#[error("database delete error code: {0}")]
Delete(DatabaseErrorInfo),
/// Failed to commit transaction changes into the database.
#[error("failed to commit transaction changes ({0})")]
Commit(i32),
#[error("failed to commit transaction changes: {0}")]
Commit(DatabaseErrorInfo),
/// Failed to initiate a transaction.
#[error("failed to initialize a transaction ({0})")]
InitTx(i32),
#[error("failed to initialize a transaction: {0}")]
InitTx(DatabaseErrorInfo),
/// Failed to initialize a cursor.
#[error("failed to initialize a cursor ({0})")]
InitCursor(i32),
#[error("failed to initialize a cursor: {0}")]
InitCursor(DatabaseErrorInfo),
/// Failed to decode a key from a table.
#[error("failed to decode a key from a table")]
Decode,
/// Failed to get database stats.
#[error("failed to get stats ({0})")]
Stats(i32),
#[error("failed to get stats: {0}")]
Stats(DatabaseErrorInfo),
/// Failed to use the specified log level, as it's not available.
#[error("log level {0:?} is not available")]
LogLevelUnavailable(LogLevel),
}
/// Common error struct to propagate implementation-specific error information.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[error("{message} ({code})")]
pub struct DatabaseErrorInfo {
/// Human-readable error message.
pub message: String,
/// Error code.
pub code: i32,
}
impl<E> From<E> for DatabaseErrorInfo
where
E: Display + Into<i32>,
{
#[inline]
fn from(value: E) -> Self {
Self { message: value.to_string(), code: value.into() }
}
}
impl From<DatabaseWriteError> for DatabaseError {
#[inline]
fn from(value: DatabaseWriteError) -> Self {
@@ -48,12 +69,12 @@ impl From<DatabaseWriteError> for DatabaseError {
/// Database write error.
#[derive(Clone, Debug, PartialEq, Eq, Error)]
#[error(
"write operation {operation:?} failed for key \"{key}\" in table {table_name:?} ({code})",
"write operation {operation:?} failed for key \"{key}\" in table {table_name:?}: {info}",
key = reth_primitives::hex::encode(key),
)]
pub struct DatabaseWriteError {
/// The error code.
pub code: i32,
/// The error code and message.
pub info: DatabaseErrorInfo,
/// The write operation type.
pub operation: DatabaseWriteOperation,
/// The table name.

View File

@@ -73,7 +73,7 @@ mod size_asserts {
static_assert_size!(RethError, 56);
static_assert_size!(BlockExecutionError, 48);
static_assert_size!(ConsensusError, 48);
static_assert_size!(DatabaseError, 16);
static_assert_size!(DatabaseError, 40);
static_assert_size!(ProviderError, 48);
static_assert_size!(NetworkError, 0);
static_assert_size!(CanonicalError, 48);

View File

@@ -11,7 +11,7 @@ use crate::{
tables::utils::*,
DatabaseError,
};
use reth_interfaces::db::{DatabaseWriteError, DatabaseWriteOperation};
use reth_interfaces::db::{DatabaseErrorInfo, DatabaseWriteError, DatabaseWriteOperation};
use reth_libmdbx::{self, Error as MDBXError, TransactionKind, WriteFlags, RO, RW};
use std::{borrow::Cow, collections::Bound, marker::PhantomData, ops::RangeBounds};
@@ -58,7 +58,7 @@ impl<K: TransactionKind, T: Table> Cursor<K, T> {
/// Decodes a `(key, value)` pair from the database.
#[allow(clippy::type_complexity)]
pub fn decode<T>(
res: Result<Option<(Cow<'_, [u8]>, Cow<'_, [u8]>)>, impl Into<i32>>,
res: Result<Option<(Cow<'_, [u8]>, Cow<'_, [u8]>)>, impl Into<DatabaseErrorInfo>>,
) -> PairResult<T>
where
T: Table,
@@ -218,8 +218,7 @@ impl<K: TransactionKind, T: DupSort> DbDupCursorRO<T> for Cursor<K, T> {
.map_err(|e| DatabaseError::Read(e.into()))?
.map(|val| decoder::<T>((Cow::Owned(key), val)))
} else {
let err_code = MDBXError::to_err_code(&MDBXError::NotFound);
Some(Err(DatabaseError::Read(err_code)))
Some(Err(DatabaseError::Read(MDBXError::NotFound.into())))
}
}
(None, None) => self.first().transpose(),
@@ -248,7 +247,7 @@ impl<T: Table> DbCursorRW<T> for Cursor<RW, T> {
.put(key.as_ref(), value.unwrap_or(&this.buf), WriteFlags::UPSERT)
.map_err(|e| {
DatabaseWriteError {
code: e.into(),
info: e.into(),
operation: DatabaseWriteOperation::CursorUpsert,
table_name: T::NAME,
key: key.into(),
@@ -270,7 +269,7 @@ impl<T: Table> DbCursorRW<T> for Cursor<RW, T> {
.put(key.as_ref(), value.unwrap_or(&this.buf), WriteFlags::NO_OVERWRITE)
.map_err(|e| {
DatabaseWriteError {
code: e.into(),
info: e.into(),
operation: DatabaseWriteOperation::CursorInsert,
table_name: T::NAME,
key: key.into(),
@@ -294,7 +293,7 @@ impl<T: Table> DbCursorRW<T> for Cursor<RW, T> {
.put(key.as_ref(), value.unwrap_or(&this.buf), WriteFlags::APPEND)
.map_err(|e| {
DatabaseWriteError {
code: e.into(),
info: e.into(),
operation: DatabaseWriteOperation::CursorAppend,
table_name: T::NAME,
key: key.into(),
@@ -330,7 +329,7 @@ impl<T: DupSort> DbDupCursorRW<T> for Cursor<RW, T> {
.put(key.as_ref(), value.unwrap_or(&this.buf), WriteFlags::APPEND_DUP)
.map_err(|e| {
DatabaseWriteError {
code: e.into(),
info: e.into(),
operation: DatabaseWriteOperation::CursorAppendDup,
table_name: T::NAME,
key: key.into(),

View File

@@ -363,6 +363,7 @@ mod tests {
AccountChangeSet,
};
use reth_interfaces::db::{DatabaseWriteError, DatabaseWriteOperation};
use reth_libmdbx::Error;
use reth_primitives::{Account, Address, Header, IntegerList, StorageEntry, B256, U256};
use std::{path::Path, str::FromStr, sync::Arc};
use tempfile::TempDir;
@@ -725,7 +726,7 @@ mod tests {
assert_eq!(
cursor.insert(key_to_insert, B256::ZERO),
Err(DatabaseWriteError {
code: -30799,
info: Error::KeyExist.into(),
operation: DatabaseWriteOperation::CursorInsert,
table_name: CanonicalHeaders::NAME,
key: key_to_insert.encode().into(),
@@ -869,7 +870,7 @@ mod tests {
assert_eq!(
cursor.append(key_to_append, B256::ZERO),
Err(DatabaseWriteError {
code: -30418,
info: Error::KeyMismatch.into(),
operation: DatabaseWriteOperation::CursorAppend,
table_name: CanonicalHeaders::NAME,
key: key_to_append.encode().into(),
@@ -951,7 +952,7 @@ mod tests {
AccountBeforeTx { address: Address::with_last_byte(subkey_to_append), info: None }
),
Err(DatabaseWriteError {
code: -30418,
info: Error::KeyMismatch.into(),
operation: DatabaseWriteOperation::CursorAppendDup,
table_name: AccountChangeSet::NAME,
key: transition_id.encode().into(),
@@ -964,7 +965,7 @@ mod tests {
AccountBeforeTx { address: Address::with_last_byte(subkey_to_append), info: None }
),
Err(DatabaseWriteError {
code: -30418,
info: Error::KeyMismatch.into(),
operation: DatabaseWriteOperation::CursorAppend,
table_name: AccountChangeSet::NAME,
key: (transition_id - 1).encode().into(),

View File

@@ -310,7 +310,7 @@ impl DbTxMut for Tx<RW> {
|tx| {
tx.put(self.get_dbi::<T>()?, key.as_ref(), value, WriteFlags::UPSERT).map_err(|e| {
DatabaseWriteError {
code: e.into(),
info: e.into(),
operation: DatabaseWriteOperation::Put,
table_name: T::NAME,
key: key.into(),
@@ -383,7 +383,7 @@ mod tests {
assert_eq!(
tx.get::<tables::Transactions>(0).err(),
Some(DatabaseError::Open(reth_libmdbx::Error::NotFound.to_err_code()))
Some(DatabaseError::Open(reth_libmdbx::Error::NotFound.into()))
); // Transaction is not timeout-ed
assert!(!tx.metrics_handler.unwrap().backtrace_recorded.load(Ordering::Relaxed)); // Backtrace is not recorded
}
@@ -402,7 +402,7 @@ mod tests {
assert_eq!(
tx.get::<tables::Transactions>(0).err(),
Some(DatabaseError::Open(reth_libmdbx::Error::ReadTransactionAborted.to_err_code()))
Some(DatabaseError::Open(reth_libmdbx::Error::ReadTransactionAborted.into()))
); // Transaction is timeout-ed
assert!(tx.metrics_handler.unwrap().backtrace_recorded.load(Ordering::Relaxed)); // Backtrace is recorded
}