1use bitcoin::{Amount, FeeRate, Txid};
2use thiserror::Error;
3
4use ark::VtxoId;
5use bitcoin_ext::BlockHeight;
6#[cfg(feature = "utoipa")]
7use utoipa::ToSchema;
8
9use crate::exit::states::ExitTxStatus;
10
11#[derive(Clone, Debug, Error, PartialEq, Eq, Deserialize, Serialize)]
12#[cfg_attr(feature = "utoipa", derive(ToSchema))]
13#[serde(tag = "type", rename_all = "kebab-case")]
14pub enum ExitError {
15 #[error("Transaction Retrieval Failure: Unable to retrieve ancestral data for TX {txid}: {error}")]
16 AncestorRetrievalFailure {
17 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
18 txid: Txid,
19 error: String
20 },
21
22 #[error("Block Retrieval Failure: Unable to retrieve a block at height {height}: {error}")]
23 BlockRetrievalFailure { height: BlockHeight, error: String },
24
25 #[error("Claim Missing Inputs: No inputs given to claim")]
26 ClaimMissingInputs,
27
28 #[error("Claim Fee Exceeds Output: Cost to claim exits was {needed}, but the total output was {output}")]
29 ClaimFeeExceedsOutput {
30 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
31 needed: Amount,
32 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
33 output: Amount,
34 },
35
36 #[error("Claim Signing Error: Unable to sign claim: {error}")]
37 ClaimSigningError { error: String },
38
39 #[error("Cyclic Exit Transactions Error: The exit transactions for VTXO {vtxo} are cyclic")]
40 CyclicExitTransactions {
41 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
42 vtxo: VtxoId
43 },
44
45 #[error("Database Store Failure: Unable to update exit VTXO {vtxo_id} in the database: {error}")]
46 DatabaseVtxoStoreFailure {
47 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
48 vtxo_id: VtxoId,
49 error: String
50 },
51
52 #[error("Database Retrieval Failure: Unable to get child tx: {error}")]
53 DatabaseChildRetrievalFailure { error: String },
54
55 #[error("Dust Limit Error: The dust limit for a VTXO is {dust} but the balance is only {vtxo}")]
56 DustLimit {
57 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
58 vtxo: Amount,
59 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
60 dust: Amount
61 },
62
63 #[error("Exit Package Broadcast Failure: Unable to broadcast exit transaction package {txid}: {error}")]
64 ExitPackageBroadcastFailure {
65 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
66 txid: Txid,
67 error: String
68 },
69
70 #[error("Exit Package Finalize Failure: Unable to create exit transaction package: {error}")]
71 ExitPackageFinalizeFailure { error: String },
72
73 #[error("Exit Package Store Failure: Unable to store exit transaction package {txid}: {error}")]
74 ExitPackageStoreFailure {
75 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
76 txid: Txid,
77 error: String
78 },
79
80 #[error("Insufficient Confirmed Funds: {needed} is needed but only {available} is available")]
81 InsufficientConfirmedFunds {
82 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
83 needed: Amount,
84 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
85 available: Amount
86 },
87
88 #[error("Insufficient Fee Error: Your balance is {balance} but an estimated {total_fee} (fee rate of {fee_rate}) is required to exit the VTXO")]
89 InsufficientFeeToStart {
90 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
91 balance: Amount,
92 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
93 total_fee: Amount,
94 #[serde(rename = "fee_rate_kwu")]
95 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
96 fee_rate: FeeRate,
97 },
98
99 #[error("Internal Error: An unexpected problem occurred, {error}")]
100 InternalError { error: String },
101
102 #[error("Invalid Exit Transaction Status: Exit tx {txid} has an invalid status ({status}): {error}")]
103 InvalidExitTransactionStatus {
104 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
105 txid: Txid,
106 status: ExitTxStatus,
107 error: String
108 },
109
110 #[error("Invalid Wallet State: {error}")]
111 InvalidWalletState { error: String },
112
113 #[error("Missing Anchor Output: Malformed exit tx {txid}")]
114 MissingAnchorOutput { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid },
115
116 #[error("Missing VTXO Transaction: Couldn't find exit tx {txid}")]
117 MissingExitTransaction { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid },
118
119 #[error("Movement Registration Failure: {error}")]
120 MovementRegistrationFailure { error: String },
121
122 #[error("Tip Retrieval Failure: Unable to retrieve the blockchain tip height: {error}")]
123 TipRetrievalFailure { error: String },
124
125 #[error("Transaction Retrieval Failure: Unable to check the status of TX {txid}: {error}")]
126 TransactionRetrievalFailure { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid, error: String },
127
128 #[error("VTXO Not Spendable Error: Attempted to claim a VTXO which is not in a spendable state: {vtxo}")]
129 VtxoNotClaimable { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
130
131 #[error("VTXO ScriptPubKey Invalid: {error}")]
132 VtxoScriptPubKeyInvalid { error: String },
133}
134
135impl From<bark::exit::models::ExitError> for ExitError {
136 fn from(v: bark::exit::models::ExitError) -> Self {
137 match v {
138 bark::exit::models::ExitError::AncestorRetrievalFailure { txid, error } => {
139 ExitError::AncestorRetrievalFailure { txid, error }
140 },
141 bark::exit::models::ExitError::BlockRetrievalFailure { height, error } => {
142 ExitError::BlockRetrievalFailure { height, error }
143 },
144 bark::exit::models::ExitError::ClaimMissingInputs => {
145 ExitError::ClaimMissingInputs
146 },
147 bark::exit::models::ExitError::ClaimFeeExceedsOutput { needed, output } => {
148 ExitError::ClaimFeeExceedsOutput { needed, output }
149 },
150 bark::exit::models::ExitError::ClaimSigningError { error } => {
151 ExitError::ClaimSigningError { error }
152 },
153 bark::exit::models::ExitError::CyclicExitTransactions { vtxo } => {
154 ExitError::CyclicExitTransactions { vtxo }
155 },
156 bark::exit::models::ExitError::DatabaseVtxoStoreFailure { vtxo_id, error } => {
157 ExitError::DatabaseVtxoStoreFailure { vtxo_id, error }
158 },
159 bark::exit::models::ExitError::DatabaseChildRetrievalFailure { error } => {
160 ExitError::DatabaseChildRetrievalFailure { error }
161 },
162 bark::exit::models::ExitError::DustLimit { vtxo, dust } => {
163 ExitError::DustLimit { vtxo, dust }
164 },
165 bark::exit::models::ExitError::ExitPackageBroadcastFailure { txid, error } => {
166 ExitError::ExitPackageBroadcastFailure { txid, error }
167 },
168 bark::exit::models::ExitError::ExitPackageFinalizeFailure { error } => {
169 ExitError::ExitPackageFinalizeFailure { error }
170 },
171 bark::exit::models::ExitError::ExitPackageStoreFailure { txid, error } => {
172 ExitError::ExitPackageStoreFailure { txid, error }
173 },
174 bark::exit::models::ExitError::InsufficientConfirmedFunds { needed, available } => {
175 ExitError::InsufficientConfirmedFunds { needed, available }
176 },
177 bark::exit::models::ExitError::InsufficientFeeToStart { balance, total_fee, fee_rate } => {
178 ExitError::InsufficientFeeToStart { balance, total_fee, fee_rate }
179 },
180 bark::exit::models::ExitError::InternalError { error } => {
181 ExitError::InternalError { error }
182 },
183 bark::exit::models::ExitError::InvalidExitTransactionStatus { txid, status, error } => {
184 ExitError::InvalidExitTransactionStatus { txid, status: status.into(), error }
185 },
186 bark::exit::models::ExitError::InvalidWalletState { error } => {
187 ExitError::InvalidWalletState { error }
188 },
189 bark::exit::models::ExitError::MissingAnchorOutput { txid } => {
190 ExitError::MissingAnchorOutput { txid }
191 },
192 bark::exit::models::ExitError::MissingExitTransaction { txid } => {
193 ExitError::MissingExitTransaction { txid }
194 },
195 bark::exit::models::ExitError::MovementRegistrationFailure { error } => {
196 ExitError::MovementRegistrationFailure { error }
197 },
198 bark::exit::models::ExitError::TipRetrievalFailure { error } => {
199 ExitError::TipRetrievalFailure { error }
200 },
201 bark::exit::models::ExitError::TransactionRetrievalFailure { txid, error } => {
202 ExitError::TransactionRetrievalFailure { txid, error }
203 },
204 bark::exit::models::ExitError::VtxoNotClaimable { vtxo } => {
205 ExitError::VtxoNotClaimable { vtxo }
206 },
207 bark::exit::models::ExitError::VtxoScriptPubKeyInvalid { error } => {
208 ExitError::VtxoScriptPubKeyInvalid { error }
209 },
210 }
211 }
212}
213
214#[cfg(test)]
215mod test {
216 use super::*;
217 #[test]
218 fn json_roundtrip() {
219 let err = ExitError::InvalidWalletState { error: "none shall pass".into() };
220 let json = serde_json::to_string(&err).unwrap();
221 let err2 = serde_json::from_str::<ExitError>(&json).unwrap();
222 assert_eq!(err, err2);
223 }
224}