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 Missing Signable Clause: Couldn't find a signable clause for VTXO {vtxo}")]
37 ClaimMissingSignableClause {
38 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
39 vtxo: VtxoId,
40 },
41
42 #[error("Claim Signing Error: Unable to sign claim: {error}")]
43 ClaimSigningError { error: String },
44
45 #[error("Cyclic Exit Transactions Error: The exit transactions for VTXO {vtxo} are cyclic")]
46 CyclicExitTransactions {
47 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
48 vtxo: VtxoId
49 },
50
51 #[error("Database Store Failure: Unable to update exit VTXO {vtxo_id} in the database: {error}")]
52 DatabaseVtxoStoreFailure {
53 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
54 vtxo_id: VtxoId,
55 error: String
56 },
57
58 #[error("Database Retrieval Failure: Unable to get child tx: {error}")]
59 DatabaseChildRetrievalFailure { error: String },
60
61 #[error("Dust Limit Error: The dust limit for a VTXO is {dust} but the balance is only {vtxo}")]
62 DustLimit {
63 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
64 vtxo: Amount,
65 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
66 dust: Amount
67 },
68
69 #[error("Exit Package Broadcast Failure: Unable to broadcast exit transaction package {txid}: {error}")]
70 ExitPackageBroadcastFailure {
71 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
72 txid: Txid,
73 error: String
74 },
75
76 #[error("Exit Package Finalize Failure: Unable to create exit transaction package: {error}")]
77 ExitPackageFinalizeFailure { error: String },
78
79 #[error("Exit Package Store Failure: Unable to store exit transaction package {txid}: {error}")]
80 ExitPackageStoreFailure {
81 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
82 txid: Txid,
83 error: String
84 },
85
86 #[error("Insufficient Confirmed Funds: {needed} is needed but only {available} is available")]
87 InsufficientConfirmedFunds {
88 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
89 needed: Amount,
90 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
91 available: Amount
92 },
93
94 #[error("Insufficient Fee Error: Your balance is {balance} but an estimated {total_fee} (fee rate of {fee_rate}) is required to exit the VTXO")]
95 InsufficientFeeToStart {
96 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
97 balance: Amount,
98 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
99 total_fee: Amount,
100 #[serde(rename = "fee_rate_kwu")]
101 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
102 fee_rate: FeeRate,
103 },
104
105 #[error("Internal Error: An unexpected problem occurred, {error}")]
106 InternalError { error: String },
107
108 #[error("Invalid Exit Transaction Status: Exit tx {txid} has an invalid status ({status}): {error}")]
109 InvalidExitTransactionStatus {
110 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
111 txid: Txid,
112 status: ExitTxStatus,
113 error: String
114 },
115
116 #[error("Invalid LockTime ({tip}): {error}")]
117 InvalidLocktime { tip: BlockHeight, error: String },
118
119 #[error("Invalid Wallet State: {error}")]
120 InvalidWalletState { error: String },
121
122 #[error("Missing Anchor Output: Malformed exit tx {txid}")]
123 MissingAnchorOutput { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid },
124
125 #[error("Missing VTXO Transaction: Couldn't find exit tx {txid}")]
126 MissingExitTransaction { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid },
127
128 #[error("Movement Registration Failure: {error}")]
129 MovementRegistrationFailure { error: String },
130
131 #[error("Tip Retrieval Failure: Unable to retrieve the blockchain tip height: {error}")]
132 TipRetrievalFailure { error: String },
133
134 #[error("Transaction Retrieval Failure: Unable to check the status of TX {txid}: {error}")]
135 TransactionRetrievalFailure { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid, error: String },
136
137 #[error("VTXO Not Spendable Error: Attempted to claim a VTXO which is not in a spendable state: {vtxo}")]
138 VtxoNotClaimable { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
139
140 #[error("VTXO ScriptPubKey Invalid: {error}")]
141 VtxoScriptPubKeyInvalid { error: String },
142}
143
144impl From<bark::exit::ExitError> for ExitError {
145 fn from(v: bark::exit::ExitError) -> Self {
146 match v {
147 bark::exit::ExitError::AncestorRetrievalFailure { txid, error } => {
148 ExitError::AncestorRetrievalFailure { txid, error }
149 },
150 bark::exit::ExitError::BlockRetrievalFailure { height, error } => {
151 ExitError::BlockRetrievalFailure { height, error }
152 },
153 bark::exit::ExitError::ClaimMissingInputs => {
154 ExitError::ClaimMissingInputs
155 },
156 bark::exit::ExitError::ClaimFeeExceedsOutput { needed, output } => {
157 ExitError::ClaimFeeExceedsOutput { needed, output }
158 },
159 bark::exit::ExitError::ClaimMissingSignableClause { vtxo } => {
160 ExitError::ClaimMissingSignableClause { vtxo }
161 },
162 bark::exit::ExitError::ClaimSigningError { error } => {
163 ExitError::ClaimSigningError { error }
164 },
165 bark::exit::ExitError::CyclicExitTransactions { vtxo } => {
166 ExitError::CyclicExitTransactions { vtxo }
167 },
168 bark::exit::ExitError::DatabaseVtxoStoreFailure { vtxo_id, error } => {
169 ExitError::DatabaseVtxoStoreFailure { vtxo_id, error }
170 },
171 bark::exit::ExitError::DatabaseChildRetrievalFailure { error } => {
172 ExitError::DatabaseChildRetrievalFailure { error }
173 },
174 bark::exit::ExitError::DustLimit { vtxo, dust } => {
175 ExitError::DustLimit { vtxo, dust }
176 },
177 bark::exit::ExitError::ExitPackageBroadcastFailure { txid, error } => {
178 ExitError::ExitPackageBroadcastFailure { txid, error }
179 },
180 bark::exit::ExitError::ExitPackageFinalizeFailure { error } => {
181 ExitError::ExitPackageFinalizeFailure { error }
182 },
183 bark::exit::ExitError::ExitPackageStoreFailure { txid, error } => {
184 ExitError::ExitPackageStoreFailure { txid, error }
185 },
186 bark::exit::ExitError::InsufficientConfirmedFunds { needed, available } => {
187 ExitError::InsufficientConfirmedFunds { needed, available }
188 },
189 bark::exit::ExitError::InsufficientFeeToStart { balance, total_fee, fee_rate } => {
190 ExitError::InsufficientFeeToStart { balance, total_fee, fee_rate }
191 },
192 bark::exit::ExitError::InternalError { error } => {
193 ExitError::InternalError { error }
194 },
195 bark::exit::ExitError::InvalidExitTransactionStatus { txid, status, error } => {
196 ExitError::InvalidExitTransactionStatus { txid, status: status.into(), error }
197 },
198 bark::exit::ExitError::InvalidLocktime { tip, error } => {
199 ExitError::InvalidLocktime { tip, error }
200 },
201 bark::exit::ExitError::InvalidWalletState { error } => {
202 ExitError::InvalidWalletState { error }
203 },
204 bark::exit::ExitError::MissingAnchorOutput { txid } => {
205 ExitError::MissingAnchorOutput { txid }
206 },
207 bark::exit::ExitError::MissingExitTransaction { txid } => {
208 ExitError::MissingExitTransaction { txid }
209 },
210 bark::exit::ExitError::MovementRegistrationFailure { error } => {
211 ExitError::MovementRegistrationFailure { error }
212 },
213 bark::exit::ExitError::TipRetrievalFailure { error } => {
214 ExitError::TipRetrievalFailure { error }
215 },
216 bark::exit::ExitError::TransactionRetrievalFailure { txid, error } => {
217 ExitError::TransactionRetrievalFailure { txid, error }
218 },
219 bark::exit::ExitError::VtxoNotClaimable { vtxo } => {
220 ExitError::VtxoNotClaimable { vtxo }
221 },
222 bark::exit::ExitError::VtxoScriptPubKeyInvalid { error } => {
223 ExitError::VtxoScriptPubKeyInvalid { error }
224 },
225 }
226 }
227}
228
229#[cfg(test)]
230mod test {
231 use super::*;
232 #[test]
233 fn json_roundtrip() {
234 let err = ExitError::InvalidWalletState { error: "none shall pass".into() };
235 let json = serde_json::to_string(&err).unwrap();
236 let err2 = serde_json::from_str::<ExitError>(&json).unwrap();
237 assert_eq!(err, err2);
238 }
239}