clementine_core/
errors.rsuse crate::{
actor::VerificationError,
builder::transaction::input::SpendableTxInError,
extended_bitcoin_rpc::BitcoinRPCError,
header_chain_prover::HeaderChainProverError,
rpc::{aggregator::AggregatorError, ParserError},
};
#[cfg(feature = "automation")]
use crate::{states::StateMachineError, tx_sender::SendTxError};
use bitcoin::{secp256k1::PublicKey, OutPoint, Txid, XOnlyPublicKey};
use clap::builder::StyledStr;
use core::fmt::Debug;
use hex::FromHexError;
use thiserror::Error;
use tonic::Status;
pub use crate::builder::transaction::TxError;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum BridgeError {
#[error("Header chain prover returned an error: {0}")]
Prover(#[from] HeaderChainProverError),
#[error("Failed to build transactions: {0}")]
Transaction(#[from] TxError),
#[cfg(feature = "automation")]
#[error("Failed to send transactions: {0}")]
SendTx(#[from] SendTxError),
#[error("Aggregator error: {0}")]
Aggregator(#[from] AggregatorError),
#[error("Failed to parse request: {0}")]
Parser(#[from] ParserError),
#[error("SpendableTxIn error: {0}")]
SpendableTxIn(#[from] SpendableTxInError),
#[error("Bitcoin RPC error: {0}")]
BitcoinRPC(#[from] BitcoinRPCError),
#[cfg(feature = "automation")]
#[error("State machine error: {0}")]
StateMachine(#[from] StateMachineError),
#[error("RPC authentication error: {0}")]
RPCAuthError(#[from] VerificationError),
#[error("Unsupported network")]
UnsupportedNetwork,
#[error("Invalid configuration: {0}")]
ConfigError(String),
#[error("Missing environment variable {1}: {0}")]
EnvVarNotSet(std::env::VarError, &'static str),
#[error("Environment variable {0} is malformed: {1}")]
EnvVarMalformed(&'static str, String),
#[error("Failed to convert between integer types")]
IntConversionError,
#[error("Failed to encode/decode data using borsh")]
BorshError,
#[error("Operator x-only public key {0} was not found in the DB")]
OperatorNotFound(XOnlyPublicKey),
#[error("Verifier with public key {0} was not found among the verifier clients")]
VerifierNotFound(PublicKey),
#[error("Deposit not found in DB: {0:?}")]
DepositNotFound(OutPoint),
#[error("Deposit is invalid due to {0}")]
InvalidDeposit(String),
#[error("Operator data mismatch. Data already stored in DB and received by set_operator doesn't match for xonly_pk: {0}")]
OperatorDataMismatch(XOnlyPublicKey),
#[error("Deposit data mismatch. Data already stored in DB doesn't match the new data for deposit {0:?}")]
DepositDataMismatch(OutPoint),
#[error("Operator winternitz public keys mismatch. Data already stored in DB doesn't match the new data for operator {0}")]
OperatorWinternitzPublicKeysMismatch(XOnlyPublicKey),
#[error("BitVM setup data mismatch. Data already stored in DB doesn't match the new data for operator {0} and deposit {1:?}")]
BitvmSetupDataMismatch(XOnlyPublicKey, OutPoint),
#[error("BitVM replacement data will exhaust memory. The maximum number of operations is {0}")]
BitvmReplacementResourceExhaustion(usize),
#[error("Operator challenge ack hashes mismatch. Data already stored in DB doesn't match the new data for operator {0} and deposit {1:?}")]
OperatorChallengeAckHashesMismatch(XOnlyPublicKey, OutPoint),
#[error("Invalid BitVM public keys")]
InvalidBitVMPublicKeys,
#[error("Invalid challenge ack hashes")]
InvalidChallengeAckHashes,
#[error("Invalid operator index")]
InvalidOperatorIndex,
#[error("Invalid protocol paramset")]
InvalidProtocolParamset,
#[error("Deposit already signed and move txid {0} is in chain")]
DepositAlreadySigned(Txid),
#[error("Invalid withdrawal ECDSA verification signature")]
InvalidECDSAVerificationSignature,
#[error("Withdrawal ECDSA verification signature missing")]
ECDSAVerificationSignatureMissing,
#[error("Failed to call database: {0}")]
DatabaseError(#[from] sqlx::Error),
#[error("Failed to convert hex string: {0}")]
FromHexError(#[from] FromHexError),
#[error("Failed to convert to hash from slice: {0}")]
FromSliceError(#[from] bitcoin::hashes::FromSliceError),
#[error("Error while calling EVM contract: {0}")]
AlloyContract(#[from] alloy::contract::Error),
#[error("Error while calling EVM RPC function: {0}")]
AlloyRpc(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
#[error("Error while encoding/decoding EVM type: {0}")]
AlloySolTypes(#[from] alloy::sol_types::Error),
#[error("{0}")]
CLIDisplayAndExit(StyledStr),
#[error(transparent)]
RPC(#[from] Status),
#[error("Arithmetic overflow occurred: {0}")]
ArithmeticOverflow(&'static str),
#[error("Insufficient funds: {0}")]
InsufficientFunds(&'static str),
#[error(transparent)]
Eyre(#[from] eyre::Report),
}
pub trait ErrorExt: Sized {
fn into_eyre(self) -> eyre::Report;
fn into_status(self) -> tonic::Status;
}
pub trait ResultExt: Sized {
type Output;
fn map_to_eyre(self) -> Result<Self::Output, eyre::Report>;
fn map_to_status(self) -> Result<Self::Output, tonic::Status>;
}
impl<T: Into<BridgeError>> ErrorExt for T {
fn into_eyre(self) -> eyre::Report {
match self.into() {
BridgeError::Eyre(report) => report,
other => eyre::eyre!(other),
}
}
fn into_status(self) -> tonic::Status {
self.into().into()
}
}
impl<U: Sized, T: Into<BridgeError>> ResultExt for Result<U, T> {
type Output = U;
fn map_to_eyre(self) -> Result<Self::Output, eyre::Report> {
self.map_err(ErrorExt::into_eyre)
}
fn map_to_status(self) -> Result<Self::Output, tonic::Status> {
self.map_err(ErrorExt::into_status)
}
}
impl From<BridgeError> for tonic::Status {
fn from(val: BridgeError) -> Self {
let eyre_report = val.into_eyre();
eyre_report.downcast::<Status>().unwrap_or_else(|report| {
tracing::error!(
"Returning internal error on RPC call, full error: {:?}",
report
);
let mut status = tonic::Status::internal(report.to_string());
status.set_source(Into::into(
Into::<Box<dyn std::error::Error + Send + Sync>>::into(report),
));
status
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_downcast() {
assert_eq!(
BridgeError::IntConversionError
.into_eyre()
.wrap_err("Some other error")
.into_eyre()
.wrap_err("some other")
.downcast_ref::<BridgeError>()
.unwrap()
.to_string(),
BridgeError::IntConversionError.to_string()
);
}
#[test]
fn test_status_in_chain_cast_properly() {
let err: BridgeError = eyre::eyre!("Some problem")
.wrap_err(tonic::Status::deadline_exceeded("Some timer expired"))
.wrap_err("Something else went wrong")
.into();
let status: Status = err.into_status();
assert_eq!(status.code(), tonic::Code::DeadlineExceeded);
assert_eq!(status.message(), "Some timer expired");
}
}