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