1use bitcoin::{Amount, Txid};
2use thiserror::Error;
3
4use ark::VtxoId;
5use bitcoin_ext::BlockHeight;
6#[cfg(feature = "utoipa")]
7use utoipa::ToSchema;
8
9use crate::exit::ExitStateKind;
10use crate::exit::states::ExitTxStatus;
11
12#[derive(Clone, Debug, Error, PartialEq, Eq, Deserialize, Serialize)]
13#[cfg_attr(feature = "utoipa", derive(ToSchema))]
14#[serde(tag = "type", rename_all = "kebab-case")]
15pub enum ExitError {
16 #[error("Transaction Retrieval Failure: Unable to retrieve ancestral data for TX {txid}: {error}")]
17 AncestorRetrievalFailure {
18 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
19 txid: Txid,
20 error: String
21 },
22
23 #[error("Block Retrieval Failure: Unable to retrieve a block at height {height}: {error}")]
24 BlockRetrievalFailure {
25 #[cfg_attr(feature = "utoipa", schema(value_type = u32))]
26 height: BlockHeight,
27 error: String
28 },
29
30 #[error("Cannot Cancel Exit: The exit for VTXO {vtxo} can no longer be canceled (state: {state})")]
31 CannotCancelExit {
32 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
33 vtxo: VtxoId,
34 state: ExitStateKind,
35 },
36
37 #[error("Claim Missing Inputs: No inputs given to claim")]
38 ClaimMissingInputs,
39
40 #[error("Claim Fee Exceeds Output: Cost to claim exits was {needed}, but the total output was {output}")]
41 ClaimFeeExceedsOutput {
42 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
43 needed: Amount,
44 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
45 output: Amount,
46 },
47
48 #[error("Claim Missing Signable Clause: Couldn't find a signable clause for VTXO {vtxo}")]
49 ClaimMissingSignableClause {
50 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
51 vtxo: VtxoId,
52 },
53
54 #[error("Claim Signing Error: Unable to sign claim: {error}")]
55 ClaimSigningError { error: String },
56
57 #[error("Cyclic Exit Transactions Error: The exit transactions for VTXO {vtxo} are cyclic")]
58 CyclicExitTransactions {
59 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
60 vtxo: VtxoId
61 },
62
63 #[error("Database Store Failure: Unable to update exit VTXO {vtxo_id} in the database: {error}")]
64 DatabaseVtxoStoreFailure {
65 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
66 vtxo_id: VtxoId,
67 error: String
68 },
69
70 #[error("Database Retrieval Failure: Unable to get child tx: {error}")]
71 DatabaseChildRetrievalFailure { error: String },
72
73 #[error("Database Store Failure: Unable to store child tx: {error}")]
74 DatabaseChildStoreFailure { error: String },
75
76 #[error("Dust Limit Error: The dust limit for a VTXO is {dust} but vtxo {vtxo} is only {amount}")]
77 DustLimit {
78 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
79 vtxo: VtxoId,
80 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
81 amount: Amount,
82 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
83 dust: Amount
84 },
85
86 #[error("Exit Package Broadcast Failure: Unable to broadcast exit transaction package {txid}: {error}")]
87 ExitPackageBroadcastFailure {
88 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
89 txid: Txid,
90 error: String
91 },
92
93 #[error("Exit Package Finalize Failure: Unable to create exit transaction package: {error}")]
94 ExitPackageFinalizeFailure { error: String },
95
96 #[error("Exit Package Store Failure: Unable to store exit transaction package {txid}: {error}")]
97 ExitPackageStoreFailure {
98 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
99 txid: Txid,
100 error: String
101 },
102
103 #[error("Exit Tx Already Broadcast: Cannot cancel the exit for VTXO {vtxo}, its final exit tx {txid} has already been broadcast")]
104 ExitTxAlreadyBroadcast {
105 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
106 vtxo: VtxoId,
107 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
108 txid: Txid,
109 },
110
111 #[error("Insufficient Confirmed Funds: {needed} is needed but only {available} is available")]
112 InsufficientConfirmedFunds {
113 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
114 needed: Amount,
115 #[cfg_attr(feature = "utoipa", schema(value_type = u64))]
116 available: Amount
117 },
118
119 #[error("Internal Error: An unexpected problem occurred, {error}")]
120 InternalError { error: String },
121
122 #[error("Invalid Exit Transaction Status: Exit tx {txid} has an invalid status ({status}): {error}")]
123 InvalidExitTransactionStatus {
124 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
125 txid: Txid,
126 status: ExitTxStatus,
127 error: String
128 },
129
130 #[error("Invalid Fee Margin: {margin} must be finite and non-negative and keep the fee in range")]
131 InvalidFeeMargin { margin: String },
132
133 #[error("Invalid LockTime ({tip}): {error}")]
134 InvalidLocktime {
135 #[cfg_attr(feature = "utoipa", schema(value_type = u32))]
136 tip: BlockHeight,
137 error: String
138 },
139
140 #[error("Invalid Wallet State: {error}")]
141 InvalidWalletState { error: String },
142
143 #[error("Missing Anchor Output: Malformed exit tx {txid}")]
144 MissingAnchorOutput { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid },
145
146 #[error("Missing VTXO Transaction: Couldn't find exit tx {txid}")]
147 MissingExitTransaction { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid },
148
149 #[error("Non-Standard VTXO {vtxo}: exit chain is not relayable: {error}")]
150 NonStandardVtxo {
151 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
152 vtxo: VtxoId,
153 error: String,
154 },
155
156 #[error("Movement Registration Failure: {error}")]
157 MovementRegistrationFailure { error: String },
158
159 #[error("Not Exiting: VTXO {vtxo} has no unilateral exit")]
160 NotExiting { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
161
162 #[error("Tip Retrieval Failure: Unable to retrieve the blockchain tip height: {error}")]
163 TipRetrievalFailure { error: String },
164
165 #[error("Transaction Retrieval Failure: Unable to check the status of TX {txid}: {error}")]
166 TransactionRetrievalFailure { #[cfg_attr(feature = "utoipa", schema(value_type = String))] txid: Txid, error: String },
167
168 #[error("VTXO Not Spendable Error: Attempted to claim a VTXO which is not in a spendable state: {vtxo}")]
169 VtxoNotClaimable { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
170
171 #[error("Unknown VTXO: {vtxo} is not known to this wallet")]
172 UnknownVtxo { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
173
174 #[error("VTXO Already Exited: {vtxo} has already completed its unilateral exit")]
175 VtxoAlreadyExited { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
176
177 #[error("VTXO Already Spent: {vtxo} has already been spent and can no longer be exited")]
178 VtxoAlreadySpent { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
179
180 #[error("VTXO Swept: an output the exit chain of {vtxo} needs was spent on chain, so it can \
181 no longer be exited")]
182 VtxoSwept { #[cfg_attr(feature = "utoipa", schema(value_type = String))] vtxo: VtxoId },
183
184 #[error("VTXO ScriptPubKey Invalid: {error}")]
185 VtxoScriptPubKeyInvalid { error: String },
186}
187
188impl From<bark::exit::ExitError> for ExitError {
189 fn from(v: bark::exit::ExitError) -> Self {
190 match v {
191 bark::exit::ExitError::AncestorRetrievalFailure { txid, error } => {
192 ExitError::AncestorRetrievalFailure { txid, error }
193 },
194 bark::exit::ExitError::BlockRetrievalFailure { height, error } => {
195 ExitError::BlockRetrievalFailure { height, error }
196 },
197 bark::exit::ExitError::ClaimMissingInputs => {
198 ExitError::ClaimMissingInputs
199 },
200 bark::exit::ExitError::ClaimFeeExceedsOutput { needed, output } => {
201 ExitError::ClaimFeeExceedsOutput { needed, output }
202 },
203 bark::exit::ExitError::ClaimMissingSignableClause { vtxo } => {
204 ExitError::ClaimMissingSignableClause { vtxo }
205 },
206 bark::exit::ExitError::ClaimSigningError { error } => {
207 ExitError::ClaimSigningError { error }
208 },
209 bark::exit::ExitError::CyclicExitTransactions { vtxo } => {
210 ExitError::CyclicExitTransactions { vtxo }
211 },
212 bark::exit::ExitError::DatabaseVtxoStoreFailure { vtxo_id, error } => {
213 ExitError::DatabaseVtxoStoreFailure { vtxo_id, error }
214 },
215 bark::exit::ExitError::DatabaseChildRetrievalFailure { error } => {
216 ExitError::DatabaseChildRetrievalFailure { error }
217 },
218 bark::exit::ExitError::DatabaseChildStoreFailure { error } => {
219 ExitError::DatabaseChildStoreFailure { error }
220 },
221 bark::exit::ExitError::DustLimit { vtxo, amount, dust } => {
222 ExitError::DustLimit { vtxo, amount, dust }
223 },
224 bark::exit::ExitError::ExitPackageBroadcastFailure { txid, error } => {
225 ExitError::ExitPackageBroadcastFailure { txid, error: error.to_string() }
226 },
227 bark::exit::ExitError::ExitPackageFinalizeFailure { error } => {
228 ExitError::ExitPackageFinalizeFailure { error }
229 },
230 bark::exit::ExitError::ExitPackageStoreFailure { txid, error } => {
231 ExitError::ExitPackageStoreFailure { txid, error }
232 },
233 bark::exit::ExitError::ExitTxAlreadyBroadcast { vtxo, txid } => {
234 ExitError::ExitTxAlreadyBroadcast { vtxo, txid }
235 },
236 bark::exit::ExitError::InsufficientConfirmedFunds { needed, available } => {
237 ExitError::InsufficientConfirmedFunds { needed, available }
238 },
239 bark::exit::ExitError::InternalError { error } => {
240 ExitError::InternalError { error }
241 },
242 bark::exit::ExitError::InvalidExitTransactionStatus { txid, status, error } => {
243 ExitError::InvalidExitTransactionStatus { txid, status: status.into(), error }
244 },
245 bark::exit::ExitError::InvalidFeeMargin { margin } => {
246 ExitError::InvalidFeeMargin { margin }
247 },
248 bark::exit::ExitError::InvalidLocktime { tip, error } => {
249 ExitError::InvalidLocktime { tip, error }
250 },
251 bark::exit::ExitError::InvalidWalletState { error } => {
252 ExitError::InvalidWalletState { error }
253 },
254 bark::exit::ExitError::MissingAnchorOutput { txid } => {
255 ExitError::MissingAnchorOutput { txid }
256 },
257 bark::exit::ExitError::MissingExitTransaction { txid } => {
258 ExitError::MissingExitTransaction { txid }
259 },
260 bark::exit::ExitError::NonStandardVtxo { vtxo, error } => {
261 ExitError::NonStandardVtxo { vtxo, error: error.to_string() }
262 },
263 bark::exit::ExitError::MovementRegistrationFailure { error } => {
264 ExitError::MovementRegistrationFailure { error }
265 },
266 bark::exit::ExitError::CannotCancelExit { vtxo, state } => {
267 ExitError::CannotCancelExit { vtxo, state: state.into() }
268 },
269 bark::exit::ExitError::NotExiting { vtxo } => {
270 ExitError::NotExiting { vtxo }
271 },
272 bark::exit::ExitError::TipRetrievalFailure { error } => {
273 ExitError::TipRetrievalFailure { error }
274 },
275 bark::exit::ExitError::TransactionRetrievalFailure { txid, error } => {
276 ExitError::TransactionRetrievalFailure { txid, error }
277 },
278 bark::exit::ExitError::VtxoNotClaimable { vtxo } => {
279 ExitError::VtxoNotClaimable { vtxo }
280 },
281 bark::exit::ExitError::UnknownVtxo { vtxo } => {
282 ExitError::UnknownVtxo { vtxo }
283 },
284 bark::exit::ExitError::VtxoAlreadyExited { vtxo } => {
285 ExitError::VtxoAlreadyExited { vtxo }
286 },
287 bark::exit::ExitError::VtxoAlreadySpent { vtxo } => {
288 ExitError::VtxoAlreadySpent { vtxo }
289 },
290 bark::exit::ExitError::VtxoSwept { vtxo } => {
291 ExitError::VtxoSwept { vtxo }
292 },
293 bark::exit::ExitError::VtxoScriptPubKeyInvalid { error } => {
294 ExitError::VtxoScriptPubKeyInvalid { error }
295 },
296 }
297 }
298}
299
300#[cfg(test)]
301mod test {
302 use super::*;
303 #[test]
304 fn json_roundtrip() {
305 let err = ExitError::InvalidWalletState { error: "none shall pass".into() };
306 let json = serde_json::to_string(&err).unwrap();
307 let err2 = serde_json::from_str::<ExitError>(&json).unwrap();
308 assert_eq!(err, err2);
309 }
310}