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