Skip to main content

bark/exit/models/
mod.rs

1
2mod error;
3mod package;
4mod states;
5
6pub use self::package::{
7	ChildTransactionInfo, ExitCpfpRequest, ExitTransactionPackage, FeeInfo, RbfRequirement,
8	TransactionInfo,
9};
10pub use self::error::ExitError;
11pub use self::states::{
12	ExitTx, ExitTxStatus, ExitTxOrigin, ExitStartState, ExitProcessingState, ExitAwaitingDeltaState,
13	ExitClaimableState, ExitClaimInProgressState, ExitClaimedState, ExitVtxoAlreadySpentState,
14};
15
16use ark::VtxoId;
17use bitcoin::Txid;
18
19use bitcoin_ext::{BlockDelta, BlockHeight, BlockRef, TxStatus};
20
21/// A utility type to wrap ExitState children so they can be easily serialized. This also helps with
22/// debugging a lot!
23#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(tag = "type", rename_all = "kebab-case")]
25pub enum ExitState {
26	Start(ExitStartState),
27	Processing(ExitProcessingState),
28	AwaitingDelta(ExitAwaitingDeltaState),
29	Claimable(ExitClaimableState),
30	ClaimInProgress(ExitClaimInProgressState),
31	/// Terminal state: the exit is fully complete, and the VTXO has been claimed by the user or
32	/// spent by another user who owns a VTXO that is deeper in the tree than this one.
33	///
34	/// Note: The circumstances in which the latter can occur are typically when the user has stale
35	/// data and is trying to exit an already-spent VTXO.
36	Claimed(ExitClaimedState),
37	/// Terminal state: the exit cannot proceed because the VTXO has already been spent offchain. A
38	/// user can start a unilateral exit for a VTXO but later spend it via a refresh, arkoor, etc,
39	/// in that situation an exit will enter this state.
40	VtxoAlreadySpent(ExitVtxoAlreadySpentState),
41}
42
43impl ExitState {
44	pub fn new_start(tip: BlockHeight) -> Self {
45		ExitState::Start(ExitStartState { tip_height: tip })
46	}
47
48	pub fn new_processing<T: IntoIterator<Item = Txid>>(tip: BlockHeight, txids: T) -> Self {
49		ExitState::Processing(ExitProcessingState {
50			tip_height: tip,
51			transactions: txids.into_iter()
52				.map(|id| ExitTx {
53					txid: id,
54					status: ExitTxStatus::VerifyInputs,
55				})
56				.collect::<Vec<_>>(),
57		})
58	}
59
60	pub fn new_processing_from_transactions(tip: BlockHeight, transactions: Vec<ExitTx>) -> Self {
61		ExitState::Processing(ExitProcessingState {
62			tip_height: tip,
63			transactions,
64		})
65	}
66
67	pub fn new_awaiting_delta(
68		tip: BlockHeight,
69		confirmed_block: BlockRef,
70		wait_delta: BlockDelta
71	) -> Self {
72		debug_assert_ne!(wait_delta, 0, "wait delta must be non-zero");
73		let claimable_height = confirmed_block.height + wait_delta as BlockHeight;
74		ExitState::AwaitingDelta(ExitAwaitingDeltaState {
75			tip_height: tip,
76			confirmed_block,
77			claimable_height,
78		})
79	}
80
81	pub fn new_claimable(
82		tip: BlockHeight,
83		claimable_since: BlockRef,
84		last_scanned_block: Option<BlockRef>
85	) -> Self {
86		ExitState::Claimable(ExitClaimableState {
87			tip_height: tip,
88			claimable_since,
89			last_scanned_block,
90		})
91	}
92
93	pub fn new_claim_in_progress(
94		tip: BlockHeight,
95		claimable_since: BlockRef,
96		claim_txid: Txid
97	) -> Self {
98		ExitState::ClaimInProgress(ExitClaimInProgressState {
99			tip_height: tip,
100			claimable_since,
101			claim_txid,
102		})
103	}
104
105	pub fn new_claimed(tip: BlockHeight, txid: Txid, block: BlockRef) -> Self {
106		ExitState::Claimed(ExitClaimedState {
107			tip_height: tip,
108			txid,
109			block,
110		})
111	}
112
113	pub fn new_vtxo_already_spent(tip: BlockHeight) -> Self {
114		ExitState::VtxoAlreadySpent(ExitVtxoAlreadySpentState { tip_height: tip })
115	}
116
117	/// Checks if the state is awaiting the confirmation of every exit transaction in the tree and
118	/// the exit delta required for the VTXO to become claimable.
119	///
120	/// Note: This excludes the claimable state, use [ExitState::is_claimable] for that.
121	pub fn is_pending(&self) -> bool {
122		match self {
123			ExitState::Start(_) => true,
124			ExitState::Processing(_) => true,
125			ExitState::AwaitingDelta(_) => true,
126			_ => false,
127		}
128	}
129
130	/// A simple helper for [ExitState::Claimable], at this point an exit can be spent on-chain
131	/// and redeemed into a UTXO controlled by the user.
132	pub fn is_claimable(&self) -> bool {
133		match self {
134			ExitState::Claimable(_) => true,
135			_ => false,
136		}
137	}
138
139	pub fn requires_confirmations(&self) -> bool {
140		match self {
141			ExitState::Processing(s) => {
142				s.transactions.iter().any(|s| match s.status {
143					ExitTxStatus::AwaitingInputConfirmation { .. } => true,
144					ExitTxStatus::AwaitingConfirmation { .. } => true,
145					_ => false,
146				})
147			},
148			ExitState::AwaitingDelta(_) => true,
149			ExitState::ClaimInProgress(_) => true,
150			_ => false,
151		}
152	}
153
154	pub fn claimable_height(&self) -> Option<BlockHeight> {
155		match self {
156			ExitState::AwaitingDelta(s) => Some(s.claimable_height),
157			ExitState::Claimable(s) => Some(s.claimable_since.height),
158			ExitState::ClaimInProgress(s) => Some(s.claimable_since.height),
159			_ => None,
160		}
161	}
162
163	/// True once every exit transaction has confirmed on-chain (i.e. the exit has reached
164	/// at least [`ExitState::AwaitingDelta`]). At that point the VTXO can be considered
165	/// [crate::vtxo::VtxoStateKind::Exited]: the underlying onchain outpoint is committed
166	/// to the exit chain, the server can see it and will refuse to service the VTXO for
167	/// any payment operations offchain, and there's nothing left to undo client-side.
168	///
169	/// Note: we deliberately don't flip the VTXO at `Processing` — even with every tx
170	/// broadcast, mempool eviction is still possible until confirmation, so we hold off
171	/// to keep `Exited` an accurate "this is gone" signal.
172	pub fn warrants_exited_vtxo(&self) -> bool {
173		match self {
174			ExitState::Start(_) => false,
175			ExitState::Processing(_) => false,
176			ExitState::AwaitingDelta(_) => true,
177			ExitState::Claimable(_) => true,
178			ExitState::ClaimInProgress(_) => true,
179			ExitState::Claimed(_) => true,
180			ExitState::VtxoAlreadySpent(_) => false,
181		}
182	}
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct ExitProgressStatus {
187	/// The ID of the VTXO that is being unilaterally exited
188	pub vtxo_id: VtxoId,
189	/// The current state of the exit transaction
190	pub state: ExitState,
191	/// Any error that occurred during the exit process
192	pub error: Option<ExitError>,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub struct ExitTransactionStatus {
197	/// The ID of the VTXO that is being unilaterally exited
198	pub vtxo_id: VtxoId,
199	/// The current state of the exit transaction
200	pub state: ExitState,
201	/// The history of each state the exit transaction has gone through
202	pub history: Option<Vec<ExitState>>,
203	/// Each exit transaction package required for the unilateral exit
204	pub transactions: Vec<ExitTransactionPackage>,
205}
206
207#[derive(Clone, Copy, Debug,  Eq, PartialEq)]
208pub struct ExitChildStatus {
209	pub txid: Txid,
210	pub status: TxStatus,
211	pub origin: ExitTxOrigin,
212	pub fee_info: Option<FeeInfo>,
213}