1use bitcoin::{Amount, FeeRate, Txid};
13use log::{debug, trace};
14
15use ark::{Vtxo, VtxoId};
16
17use crate::exit::models::{ExitError, ExitState};
18use crate::exit::progress::{ExitStateProgress, ProgressContext, ProgressStep};
19use crate::exit::transaction_manager::ExitTransactionManager;
20use crate::onchain::ExitUnilaterally;
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, 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: &Vtxo) -> 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 onchain: &dyn ExitUnilaterally,
121 ) -> anyhow::Result<(), ExitError> {
122 trace!("Initializing VTXO for exit {}", self.vtxo_id);
123 let vtxo = self.get_vtxo(persister).await?;
124 self.txids = Some(tx_manager.track_vtxo_exits(&vtxo, onchain).await?);
125 Ok(())
126 }
127
128 pub async fn progress(
144 &mut self,
145 wallet: &Wallet,
146 tx_manager: &mut ExitTransactionManager,
147 onchain: &mut dyn ExitUnilaterally,
148 fee_rate_override: Option<FeeRate>,
149 continue_until_finished: bool,
150 ) -> anyhow::Result<(), ExitError> {
151 if self.txids.is_none() {
152 return Err(ExitError::InternalError {
153 error: String::from("Unilateral exit not yet initialized"),
154 });
155 }
156
157 let wallet_vtxo = self.get_vtxo(&*wallet.db).await?;
158 const MAX_ITERATIONS: usize = 100;
159 for _ in 0..MAX_ITERATIONS {
160 let mut context = ProgressContext {
161 vtxo: &wallet_vtxo.vtxo,
162 exit_txids: self.txids.as_ref().unwrap(),
163 wallet,
164 fee_rate: fee_rate_override.unwrap_or(wallet.chain.fee_rates().await.fast),
165 tx_manager,
166 };
167 trace!("Progressing VTXO {} at height {}", self.id(), wallet.chain.tip().await.unwrap());
169 match self.state.clone().progress(&mut context, onchain).await {
170 Ok(new_state) => {
171 self.update_state_if_newer(new_state, &*wallet.db).await?;
172 if !continue_until_finished {
173 return Ok(());
174 }
175 match ProgressStep::from_exit_state(&self.state) {
176 ProgressStep::Continue => debug!("VTXO {} can continue", self.id()),
177 ProgressStep::Done => return Ok(())
178 }
179 },
180 Err(e) => {
181 if let Some(new_state) = e.state {
183 self.update_state_if_newer(new_state, &*wallet.db).await?;
184 }
185 return Err(e.error);
186 }
187 }
188 }
189 debug_assert!(false, "Exceeded maximum iterations for progressing VTXO {}", self.id());
190 Ok(())
191 }
192
193 pub async fn get_vtxo(&self, persister: &dyn BarkPersister) -> anyhow::Result<WalletVtxo, ExitError> {
194 persister.get_wallet_vtxo(self.vtxo_id).await
195 .map_err(|e| ExitError::InvalidWalletState { error: e.to_string() })?
196 .ok_or_else(|| ExitError::InternalError {
197 error: format!("VTXO for exit couldn't be found: {}", self.vtxo_id)
198 })
199 }
200
201 async fn update_state_if_newer(
202 &mut self,
203 new: ExitState,
204 persister: &dyn BarkPersister,
205 ) -> anyhow::Result<(), ExitError> {
206 if new != self.state {
208 self.history.push(self.state.clone());
209 self.state = new;
210 self.persist(persister).await
211 } else {
212 Ok(())
213 }
214 }
215
216 async fn persist(&self, persister: &dyn BarkPersister) -> anyhow::Result<(), ExitError> {
217 persister.store_exit_vtxo_entry(&StoredExit::new(self)).await
218 .map_err(|e| ExitError::DatabaseVtxoStoreFailure {
219 vtxo_id: self.id(), error: e.to_string(),
220 })
221 }
222}