1use bitcoin::{Amount, Txid};
13use log::{debug, trace};
14
15use ark::{Vtxo, VtxoId};
16use ark::vtxo::{Bare, Full};
17
18use crate::exit::models::{ExitError, ExitState};
19use crate::exit::progress::{ExitStateProgress, ProgressContext, ProgressStep};
20use crate::exit::transaction_manager::ExitTransactionManager;
21use crate::persist::BarkPersister;
22use crate::persist::models::StoredExit;
23use crate::{Wallet, WalletVtxo};
24
25#[derive(Debug, Clone)]
36pub struct ExitVtxo {
37 vtxo_id: VtxoId,
38 amount: Amount,
39 state: ExitState,
40 history: Vec<ExitState>,
41 txids: Option<Vec<Txid>>,
42}
43
44impl ExitVtxo {
45 pub fn new(vtxo: &Vtxo<Bare>, tip: u32) -> Self {
52 Self {
53 vtxo_id: vtxo.id(),
54 amount: vtxo.amount(),
55 state: ExitState::new_start(tip),
56 history: vec![],
57 txids: None,
58 }
59 }
60
61 pub fn from_entry(entry: StoredExit, vtxo: &WalletVtxo) -> Self {
68 assert_eq!(entry.vtxo_id, vtxo.id());
69 ExitVtxo {
70 vtxo_id: entry.vtxo_id,
71 amount: vtxo.amount(),
72 state: entry.state,
73 history: entry.history,
74 txids: None,
75 }
76 }
77
78 pub fn id(&self) -> VtxoId {
80 self.vtxo_id
81 }
82
83 pub fn amount(&self) -> Amount {
85 self.amount
86 }
87
88 pub fn state(&self) -> &ExitState {
90 &self.state
91 }
92
93 pub fn history(&self) -> &Vec<ExitState> {
95 &self.history
96 }
97
98 pub fn txids(&self) -> Option<&Vec<Txid>> {
101 self.txids.as_ref()
102 }
103
104 pub fn is_claimable(&self) -> bool {
106 matches!(self.state, ExitState::Claimable(..))
107 }
108
109 pub fn is_initialized(&self) -> bool {
111 self.txids.is_some()
112 }
113
114 pub async fn initialize(
117 &mut self,
118 tx_manager: &mut ExitTransactionManager,
119 persister: &dyn BarkPersister,
120 ) -> anyhow::Result<(), ExitError> {
121 trace!("Initializing VTXO for exit {}", self.vtxo_id);
122 let vtxo = self.get_full_vtxo(persister).await?;
123 self.txids = Some(tx_manager.track_vtxo_exits(&vtxo).await?);
124 Ok(())
125 }
126
127 pub async fn progress(
141 &mut self,
142 wallet: &Wallet,
143 tx_manager: &mut ExitTransactionManager,
144 continue_until_finished: bool,
145 ) -> anyhow::Result<(), ExitError> {
146 if self.txids.is_none() {
147 return Err(ExitError::InternalError {
148 error: String::from("Unilateral exit not yet initialized"),
149 });
150 }
151
152 let vtxo = self.get_vtxo(&*wallet.inner.db).await?;
153 const MAX_ITERATIONS: usize = 100;
154 for _ in 0..MAX_ITERATIONS {
155 let mut context = ProgressContext {
156 vtxo: &vtxo,
157 exit_txids: self.txids.as_ref().unwrap(),
158 wallet,
159 tx_manager,
160 };
161 trace!("Progressing VTXO {} at height {}", self.id(), wallet.inner.chain.tip().await.unwrap());
163 match self.state.clone().progress(&mut context).await {
164 Ok(new_state) => {
165 self.update_state_if_newer(new_state, &*wallet.inner.db).await?;
166 if !continue_until_finished {
167 return Ok(());
168 }
169 match ProgressStep::from_exit_state(&self.state) {
170 ProgressStep::Continue => debug!("VTXO {} can continue", self.id()),
171 ProgressStep::Done => return Ok(())
172 }
173 },
174 Err(e) => {
175 if let Some(new_state) = e.state {
177 self.update_state_if_newer(new_state, &*wallet.inner.db).await?;
178 }
179 return Err(e.error);
180 }
181 }
182 }
183 debug_assert!(false, "Exceeded maximum iterations for progressing VTXO {}", self.id());
184 Ok(())
185 }
186
187 pub async fn get_vtxo(&self, persister: &dyn BarkPersister) -> anyhow::Result<WalletVtxo, ExitError> {
188 persister.get_wallet_vtxo(self.vtxo_id).await
189 .map_err(|e| ExitError::InvalidWalletState { error: e.to_string() })?
190 .ok_or_else(|| ExitError::InternalError {
191 error: format!("VTXO for exit couldn't be found: {}", self.vtxo_id)
192 })
193 }
194
195 pub async fn get_full_vtxo(
199 &self,
200 persister: &dyn BarkPersister,
201 ) -> anyhow::Result<Vtxo<Full>, ExitError> {
202 persister.get_full_vtxo(self.vtxo_id).await
203 .map_err(|e| ExitError::InvalidWalletState { error: e.to_string() })?
204 .ok_or_else(|| ExitError::InternalError {
205 error: format!("VTXO for exit couldn't be found: {}", self.vtxo_id)
206 })
207 }
208
209 async fn update_state_if_newer(
210 &mut self,
211 new: ExitState,
212 persister: &dyn BarkPersister,
213 ) -> anyhow::Result<(), ExitError> {
214 if new != self.state {
216 self.history.push(self.state.clone());
217 self.state = new;
218 self.persist(persister).await
219 } else {
220 Ok(())
221 }
222 }
223
224 async fn persist(&self, persister: &dyn BarkPersister) -> anyhow::Result<(), ExitError> {
225 persister.store_exit_vtxo_entry(&StoredExit::new(self)).await
226 .map_err(|e| ExitError::DatabaseVtxoStoreFailure {
227 vtxo_id: self.id(), error: e.to_string(),
228 })
229 }
230}