1use bitcoin::{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::{ChainSource, ExitUnilaterally};
21use crate::persist::BarkPersister;
22use crate::persist::models::StoredExit;
23
24#[derive(Debug, Clone)]
35pub struct ExitVtxo {
36 vtxo: Vtxo,
37 txids: Vec<Txid>,
38 state: ExitState,
39 history: Vec<ExitState>,
40}
41
42impl ExitVtxo {
43 pub fn new(vtxo: Vtxo, txids: Vec<Txid>, tip: u32) -> Self {
50 Self {
51 vtxo,
52 txids,
53 state: ExitState::new_start(tip),
54 history: vec![],
55 }
56 }
57
58 pub fn from_parts(
62 vtxo: Vtxo,
63 txids: Vec<Txid>,
64 state: ExitState,
65 history: Vec<ExitState>,
66 ) -> Self {
67 ExitVtxo {
68 vtxo,
69 txids,
70 state,
71 history,
72 }
73 }
74
75 pub fn id(&self) -> VtxoId {
77 self.vtxo.id()
78 }
79
80 pub fn vtxo(&self) -> &Vtxo {
82 &self.vtxo
83 }
84
85 pub fn state(&self) -> &ExitState {
87 &self.state
88 }
89
90 pub fn history(&self) -> &Vec<ExitState> {
92 &self.history
93 }
94
95 pub fn txids(&self) -> &Vec<Txid> {
97 &self.txids
98 }
99
100 pub fn is_claimable(&self) -> bool {
102 matches!(self.state, ExitState::Claimable(..))
103 }
104
105 pub async fn progress(
121 &mut self,
122 chain_source: &ChainSource,
123 tx_manager: &mut ExitTransactionManager,
124 persister: &dyn BarkPersister,
125 onchain: &mut dyn ExitUnilaterally,
126 fee_rate_override: Option<FeeRate>,
127 ) -> anyhow::Result<(), ExitError> {
128 const MAX_ITERATIONS: usize = 100;
129 for _ in 0..MAX_ITERATIONS {
130 let mut context = ProgressContext {
131 vtxo: &self.vtxo,
132 exit_txids: &self.txids,
133 chain_source: &chain_source,
134 fee_rate: fee_rate_override.unwrap_or(chain_source.fee_rates().await.fast),
135 tx_manager,
136 };
137 trace!("Progressing VTXO {} at height {}", self.id(), chain_source.tip().await.unwrap());
139 match self.state.clone().progress(&mut context, onchain).await {
140 Ok(new_state) => {
141 self.update_state_if_newer(new_state, persister)?;
142 match ProgressStep::from_exit_state(&self.state) {
143 ProgressStep::Continue => debug!("VTXO {} can continue", self.id()),
144 ProgressStep::Done => return Ok(())
145 }
146 },
147 Err(e) => {
148 if let Some(new_state) = e.state {
150 self.update_state_if_newer(new_state, persister)?;
151 }
152 return Err(e.error);
153 }
154 }
155 }
156 debug_assert!(false, "Exceeded maximum iterations for progressing VTXO {}", self.id());
157 Ok(())
158 }
159
160 fn update_state_if_newer(
161 &mut self,
162 new: ExitState,
163 persister: &dyn BarkPersister,
164 ) -> anyhow::Result<(), ExitError> {
165 if new != self.state {
167 self.history.push(self.state.clone());
168 self.state = new;
169 self.persist(persister)
170 } else {
171 Ok(())
172 }
173 }
174
175 fn persist(&self, persister: &dyn BarkPersister) -> anyhow::Result<(), ExitError> {
176 persister.store_exit_vtxo_entry(&StoredExit::new(self))
177 .map_err(|e| ExitError::DatabaseVtxoStoreFailure {
178 vtxo_id: self.id(), error: e.to_string(),
179 })
180 }
181}