use anyhow::Context;
use bitcoin::{Amount, Txid};
use log::{info, warn};
use ark::vtxo::VtxoRef;
use crate::Wallet;
use crate::actions::{DriveMode, WalletActionId};
use crate::actions::offboard::{Offboard, Progress, StartOffboardSpec, start_offboard};
impl Wallet {
pub async fn pending_offboards(&self) -> anyhow::Result<Vec<Offboard>> {
let mut result = Vec::new();
for cp in self.inner.db.get_all_wallet_action_checkpoints().await? {
if let Some(o) = cp.into_offboard() {
result.push(o);
}
}
Ok(result)
}
pub async fn sync_pending_offboards(&self) -> anyhow::Result<()> {
let pending = self.pending_offboards().await?;
if pending.is_empty() {
return Ok(());
}
info!("Syncing {} pending offboard(s)", pending.len());
for action in pending {
let id = action.id();
if let Err(e) = self.drive_action(action, DriveMode::UntilParkOrDone).await {
warn!("Failed to sync offboard {}: {:#}", id, e);
}
}
Ok(())
}
pub async fn offboard_checkpoint(&self, id: &WalletActionId)
-> anyhow::Result<Option<Offboard>>
{
Ok(self.inner.db.get_wallet_action_checkpoint(id).await?
.and_then(|cp| cp.into_offboard()))
}
pub async fn send_onchain(
&self,
destination: bitcoin::Address,
amount: Amount,
) -> anyhow::Result<Txid> {
let action = start_offboard(
self, destination, StartOffboardSpec::SendOnchain { amount },
).await?;
self.run_offboard(action).await
}
pub async fn offboard_all(&self, address: bitcoin::Address) -> anyhow::Result<Txid> {
let input_vtxos = self.spendable_vtxos().await?;
let action = start_offboard(
self, address, StartOffboardSpec::OffboardWhole { vtxos: input_vtxos },
).await?;
self.run_offboard(action).await
}
pub async fn offboard_vtxos<V: VtxoRef>(
&self,
vtxos: impl IntoIterator<Item = V>,
address: bitcoin::Address,
) -> anyhow::Result<Txid> {
let mut input_vtxos = vec![];
for v in vtxos {
let id = v.vtxo_id();
let vtxo = match self.inner.db.get_wallet_vtxo(id).await? {
Some(vtxo) => vtxo,
_ => bail!("cannot find requested vtxo: {}", id),
};
input_vtxos.push(vtxo);
}
let action = start_offboard(
self, address, StartOffboardSpec::OffboardWhole { vtxos: input_vtxos },
).await?;
self.run_offboard(action).await
}
async fn run_offboard(&self, action: Offboard) -> anyhow::Result<Txid> {
let offboard_id = action.id();
let guard = self.inner.lock_manager.try_lock(&offboard_id).await
.context("offboard action already in progress")?;
self.inner.db.upsert_wallet_action_checkpoint(&offboard_id, &action.clone().into()).await
.context("failed to persist initial offboard checkpoint")?;
self.drive_action_with_guard(action, DriveMode::UntilParkOrDone, guard).await?;
match self.offboard_checkpoint(&offboard_id).await? {
Some(o) => match o.progress {
Progress::AwaitingConfirmations { offboard_txid, .. } => Ok(offboard_txid),
other => bail!(
"offboard {} could not complete yet (parked in {:?}); \
it remains pending and will be retried on wallet sync",
offboard_id, other,
),
},
None => bail!("offboard {} finished without producing a txid", offboard_id),
}
}
}