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