Skip to main content

bark_json/exit/
error.rs

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}