use bitcoin::{Amount, Txid};
use thiserror::Error;
use ark::VtxoId;
use bitcoin_ext::BlockHeight;
#[cfg(feature = "utoipa")]
use utoipa::ToSchema;
use crate::exit::ExitStateKind;
use crate::exit::states::ExitTxStatus;
#[derive(Clone, Debug, Error, PartialEq, Eq, Deserialize, Serialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ExitError {
#[error("Transaction Retrieval Failure: Unable to retrieve ancestral data for TX {txid}: {error}")]
AncestorRetrievalFailure {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
txid: Txid,
error: String
},
#[error("Block Retrieval Failure: Unable to retrieve a block at height {height}: {error}")]
BlockRetrievalFailure { height: BlockHeight, error: String },
#[error("Cannot Cancel Exit: The exit for VTXO {vtxo} can no longer be canceled (state: {state})")]
CannotCancelExit {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
vtxo: VtxoId,
state: ExitStateKind,
},
#[error("Claim Missing Inputs: No inputs given to claim")]
ClaimMissingInputs,
#[error("Claim Fee Exceeds Output: Cost to claim exits was {needed}, but the total output was {output}")]
ClaimFeeExceedsOutput {
#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
needed: Amount,
#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
output: Amount,
},
#[error("Claim Missing Signable Clause: Couldn't find a signable clause for VTXO {vtxo}")]
ClaimMissingSignableClause {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
vtxo: VtxoId,
},
#[error("Claim Signing Error: Unable to sign claim: {error}")]
ClaimSigningError { error: String },
#[error("Cyclic Exit Transactions Error: The exit transactions for VTXO {vtxo} are cyclic")]
CyclicExitTransactions {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
vtxo: VtxoId
},
#[error("Database Store Failure: Unable to update exit VTXO {vtxo_id} in the database: {error}")]
DatabaseVtxoStoreFailure {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
vtxo_id: VtxoId,
error: String
},
#[error("Database Retrieval Failure: Unable to get child tx: {error}")]
DatabaseChildRetrievalFailure { error: String },
#[error("Database Store Failure: Unable to store child tx: {error}")]
DatabaseChildStoreFailure { error: String },
#[error("Dust Limit Error: The dust limit for a VTXO is {dust} but vtxo {vtxo} is only {amount}")]
DustLimit {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
vtxo: VtxoId,
#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
amount: Amount,
#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
dust: Amount
},
#[error("Exit Package Broadcast Failure: Unable to broadcast exit transaction package {txid}: {error}")]
ExitPackageBroadcastFailure {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
txid: Txid,
error: String
},
#[error("Exit Package Finalize Failure: Unable to create exit transaction package: {error}")]
ExitPackageFinalizeFailure { error: String },
#[error("Exit Package Store Failure: Unable to store exit transaction package {txid}: {error}")]
ExitPackageStoreFailure {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
txid: Txid,
error: String
},
#[error("Exit Tx Already Broadcast: Cannot cancel the exit for VTXO {vtxo}, its final exit tx {txid} has already been broadcast")]
ExitTxAlreadyBroadcast {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
vtxo: VtxoId,
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
txid: Txid,
},
#[error("Insufficient Confirmed Funds: {needed} is needed but only {available} is available")]
InsufficientConfirmedFunds {
#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
needed: Amount,
#[cfg_attr(feature = "utoipa", schema(value_type = u64))]
available: Amount
},
#[error("Internal Error: An unexpected problem occurred, {error}")]
InternalError { error: String },
#[error("Invalid Exit Transaction Status: Exit tx {txid} has an invalid status ({status}): {error}")]
InvalidExitTransactionStatus {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
txid: Txid,
status: ExitTxStatus,
error: String
},
#[error("Invalid LockTime ({tip}): {error}")]
InvalidLocktime { tip: BlockHeight, error: String },
#[error("Invalid Wallet State: {error}")]
InvalidWalletState { error: String },
#[error("Missing Anchor Output: Malformed exit tx {txid}")]
MissingAnchorOutput { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid },
#[error("Missing VTXO Transaction: Couldn't find exit tx {txid}")]
MissingExitTransaction { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid },
#[error("Non-Standard VTXO {vtxo}: exit chain is not relayable: {error}")]
NonStandardVtxo {
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
vtxo: VtxoId,
error: String,
},
#[error("Movement Registration Failure: {error}")]
MovementRegistrationFailure { error: String },
#[error("Not Exiting: VTXO {vtxo} has no unilateral exit")]
NotExiting { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
#[error("Tip Retrieval Failure: Unable to retrieve the blockchain tip height: {error}")]
TipRetrievalFailure { error: String },
#[error("Transaction Retrieval Failure: Unable to check the status of TX {txid}: {error}")]
TransactionRetrievalFailure { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid, error: String },
#[error("VTXO Not Spendable Error: Attempted to claim a VTXO which is not in a spendable state: {vtxo}")]
VtxoNotClaimable { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
#[error("Unknown VTXO: {vtxo} is not known to this wallet")]
UnknownVtxo { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
#[error("VTXO Already Exited: {vtxo} has already completed its unilateral exit")]
VtxoAlreadyExited { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
#[error("VTXO Already Spent: {vtxo} has already been spent and can no longer be exited")]
VtxoAlreadySpent { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
#[error("VTXO ScriptPubKey Invalid: {error}")]
VtxoScriptPubKeyInvalid { error: String },
}
impl From<bark::exit::ExitError> for ExitError {
fn from(v: bark::exit::ExitError) -> Self {
match v {
bark::exit::ExitError::AncestorRetrievalFailure { txid, error } => {
ExitError::AncestorRetrievalFailure { txid, error }
},
bark::exit::ExitError::BlockRetrievalFailure { height, error } => {
ExitError::BlockRetrievalFailure { height, error }
},
bark::exit::ExitError::ClaimMissingInputs => {
ExitError::ClaimMissingInputs
},
bark::exit::ExitError::ClaimFeeExceedsOutput { needed, output } => {
ExitError::ClaimFeeExceedsOutput { needed, output }
},
bark::exit::ExitError::ClaimMissingSignableClause { vtxo } => {
ExitError::ClaimMissingSignableClause { vtxo }
},
bark::exit::ExitError::ClaimSigningError { error } => {
ExitError::ClaimSigningError { error }
},
bark::exit::ExitError::CyclicExitTransactions { vtxo } => {
ExitError::CyclicExitTransactions { vtxo }
},
bark::exit::ExitError::DatabaseVtxoStoreFailure { vtxo_id, error } => {
ExitError::DatabaseVtxoStoreFailure { vtxo_id, error }
},
bark::exit::ExitError::DatabaseChildRetrievalFailure { error } => {
ExitError::DatabaseChildRetrievalFailure { error }
},
bark::exit::ExitError::DatabaseChildStoreFailure { error } => {
ExitError::DatabaseChildStoreFailure { error }
},
bark::exit::ExitError::DustLimit { vtxo, amount, dust } => {
ExitError::DustLimit { vtxo, amount, dust }
},
bark::exit::ExitError::ExitPackageBroadcastFailure { txid, error } => {
ExitError::ExitPackageBroadcastFailure { txid, error: error.to_string() }
},
bark::exit::ExitError::ExitPackageFinalizeFailure { error } => {
ExitError::ExitPackageFinalizeFailure { error }
},
bark::exit::ExitError::ExitPackageStoreFailure { txid, error } => {
ExitError::ExitPackageStoreFailure { txid, error }
},
bark::exit::ExitError::ExitTxAlreadyBroadcast { vtxo, txid } => {
ExitError::ExitTxAlreadyBroadcast { vtxo, txid }
},
bark::exit::ExitError::InsufficientConfirmedFunds { needed, available } => {
ExitError::InsufficientConfirmedFunds { needed, available }
},
bark::exit::ExitError::InternalError { error } => {
ExitError::InternalError { error }
},
bark::exit::ExitError::InvalidExitTransactionStatus { txid, status, error } => {
ExitError::InvalidExitTransactionStatus { txid, status: status.into(), error }
},
bark::exit::ExitError::InvalidLocktime { tip, error } => {
ExitError::InvalidLocktime { tip, error }
},
bark::exit::ExitError::InvalidWalletState { error } => {
ExitError::InvalidWalletState { error }
},
bark::exit::ExitError::MissingAnchorOutput { txid } => {
ExitError::MissingAnchorOutput { txid }
},
bark::exit::ExitError::MissingExitTransaction { txid } => {
ExitError::MissingExitTransaction { txid }
},
bark::exit::ExitError::NonStandardVtxo { vtxo, error } => {
ExitError::NonStandardVtxo { vtxo, error: error.to_string() }
},
bark::exit::ExitError::MovementRegistrationFailure { error } => {
ExitError::MovementRegistrationFailure { error }
},
bark::exit::ExitError::CannotCancelExit { vtxo, state } => {
ExitError::CannotCancelExit { vtxo, state: state.into() }
},
bark::exit::ExitError::NotExiting { vtxo } => {
ExitError::NotExiting { vtxo }
},
bark::exit::ExitError::TipRetrievalFailure { error } => {
ExitError::TipRetrievalFailure { error }
},
bark::exit::ExitError::TransactionRetrievalFailure { txid, error } => {
ExitError::TransactionRetrievalFailure { txid, error }
},
bark::exit::ExitError::VtxoNotClaimable { vtxo } => {
ExitError::VtxoNotClaimable { vtxo }
},
bark::exit::ExitError::UnknownVtxo { vtxo } => {
ExitError::UnknownVtxo { vtxo }
},
bark::exit::ExitError::VtxoAlreadyExited { vtxo } => {
ExitError::VtxoAlreadyExited { vtxo }
},
bark::exit::ExitError::VtxoAlreadySpent { vtxo } => {
ExitError::VtxoAlreadySpent { vtxo }
},
bark::exit::ExitError::VtxoScriptPubKeyInvalid { error } => {
ExitError::VtxoScriptPubKeyInvalid { error }
},
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn json_roundtrip() {
let err = ExitError::InvalidWalletState { error: "none shall pass".into() };
let json = serde_json::to_string(&err).unwrap();
let err2 = serde_json::from_str::<ExitError>(&json).unwrap();
assert_eq!(err, err2);
}
}