use bitcoin::{Amount, FeeRate, Txid};
use log::{debug, trace};
use ark::{Vtxo, VtxoId};
use ark::vtxo::Full;
use crate::exit::models::{ExitError, ExitState};
use crate::exit::progress::{ExitStateProgress, ProgressContext, ProgressStep};
use crate::exit::transaction_manager::ExitTransactionManager;
use crate::onchain::ExitUnilaterally;
use crate::persist::BarkPersister;
use crate::persist::models::StoredExit;
use crate::{Wallet, WalletVtxo};
#[derive(Debug, Clone)]
pub struct ExitVtxo {
vtxo_id: VtxoId,
amount: Amount,
state: ExitState,
history: Vec<ExitState>,
txids: Option<Vec<Txid>>,
}
impl ExitVtxo {
pub fn new(vtxo: &Vtxo<Full>, tip: u32) -> Self {
Self {
vtxo_id: vtxo.id(),
amount: vtxo.amount(),
state: ExitState::new_start(tip),
history: vec![],
txids: None,
}
}
pub fn from_entry(entry: StoredExit, vtxo: &WalletVtxo) -> Self {
assert_eq!(entry.vtxo_id, vtxo.id());
ExitVtxo {
vtxo_id: entry.vtxo_id,
amount: vtxo.amount(),
state: entry.state,
history: entry.history,
txids: None,
}
}
pub fn id(&self) -> VtxoId {
self.vtxo_id
}
pub fn amount(&self) -> Amount {
self.amount
}
pub fn state(&self) -> &ExitState {
&self.state
}
pub fn history(&self) -> &Vec<ExitState> {
&self.history
}
pub fn txids(&self) -> Option<&Vec<Txid>> {
self.txids.as_ref()
}
pub fn is_claimable(&self) -> bool {
matches!(self.state, ExitState::Claimable(..))
}
pub fn is_initialized(&self) -> bool {
self.txids.is_some()
}
pub async fn initialize(
&mut self,
tx_manager: &mut ExitTransactionManager,
persister: &dyn BarkPersister,
onchain: &dyn ExitUnilaterally,
) -> anyhow::Result<(), ExitError> {
trace!("Initializing VTXO for exit {}", self.vtxo_id);
let vtxo = self.get_vtxo(persister).await?;
self.txids = Some(tx_manager.track_vtxo_exits(&vtxo, onchain).await?);
Ok(())
}
pub async fn progress(
&mut self,
wallet: &Wallet,
tx_manager: &mut ExitTransactionManager,
onchain: &mut dyn ExitUnilaterally,
fee_rate_override: Option<FeeRate>,
continue_until_finished: bool,
) -> anyhow::Result<(), ExitError> {
if self.txids.is_none() {
return Err(ExitError::InternalError {
error: String::from("Unilateral exit not yet initialized"),
});
}
let wallet_vtxo = self.get_vtxo(&*wallet.db).await?;
const MAX_ITERATIONS: usize = 100;
for _ in 0..MAX_ITERATIONS {
let mut context = ProgressContext {
vtxo: &wallet_vtxo.vtxo,
exit_txids: self.txids.as_ref().unwrap(),
wallet,
fee_rate: fee_rate_override.unwrap_or(wallet.chain.fee_rates().await.fast),
tx_manager,
};
trace!("Progressing VTXO {} at height {}", self.id(), wallet.chain.tip().await.unwrap());
match self.state.clone().progress(&mut context, onchain).await {
Ok(new_state) => {
self.update_state_if_newer(new_state, &*wallet.db).await?;
if !continue_until_finished {
return Ok(());
}
match ProgressStep::from_exit_state(&self.state) {
ProgressStep::Continue => debug!("VTXO {} can continue", self.id()),
ProgressStep::Done => return Ok(())
}
},
Err(e) => {
if let Some(new_state) = e.state {
self.update_state_if_newer(new_state, &*wallet.db).await?;
}
return Err(e.error);
}
}
}
debug_assert!(false, "Exceeded maximum iterations for progressing VTXO {}", self.id());
Ok(())
}
pub async fn get_vtxo(&self, persister: &dyn BarkPersister) -> anyhow::Result<WalletVtxo, ExitError> {
persister.get_wallet_vtxo(self.vtxo_id).await
.map_err(|e| ExitError::InvalidWalletState { error: e.to_string() })?
.ok_or_else(|| ExitError::InternalError {
error: format!("VTXO for exit couldn't be found: {}", self.vtxo_id)
})
}
async fn update_state_if_newer(
&mut self,
new: ExitState,
persister: &dyn BarkPersister,
) -> anyhow::Result<(), ExitError> {
if new != self.state {
self.history.push(self.state.clone());
self.state = new;
self.persist(persister).await
} else {
Ok(())
}
}
async fn persist(&self, persister: &dyn BarkPersister) -> anyhow::Result<(), ExitError> {
persister.store_exit_vtxo_entry(&StoredExit::new(self)).await
.map_err(|e| ExitError::DatabaseVtxoStoreFailure {
vtxo_id: self.id(), error: e.to_string(),
})
}
}