diff --git a/src/subcommand/wallet/resume.rs b/src/subcommand/wallet/resume.rs index 3cea3f0fa4..64c3fcff0b 100644 --- a/src/subcommand/wallet/resume.rs +++ b/src/subcommand/wallet/resume.rs @@ -1,4 +1,4 @@ -use super::*; +use {super::*, crate::wallet::Maturity}; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct ResumeOutput { @@ -27,8 +27,15 @@ impl Resume { continue; }; - if wallet.is_mature(rune, &entry.commit)? { - etchings.push(wallet.send_etching(rune, &entry)?); + match wallet.check_maturity(rune, &entry.commit)? { + Maturity::Mature => etchings.push(wallet.send_etching(rune, &entry)?), + Maturity::CommitSpent(txid) => { + eprintln!("Commitment for rune etching {rune} spent in {txid}"); + wallet.clear_etching(rune)?; + } + Maturity::CommitNotFound => {} + Maturity::BelowMinimumHeight(_) => {} + Maturity::ConfirmationsPending(_) => {} } } diff --git a/src/wallet.rs b/src/wallet.rs index 7633d17587..cbca88c7fc 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -47,6 +47,15 @@ impl From for u64 { } } +#[derive(Debug, PartialEq)] +pub(crate) enum Maturity { + BelowMinimumHeight(u64), + CommitNotFound, + CommitSpent(Txid), + ConfirmationsPending(u32), + Mature, +} + pub(crate) struct Wallet { bitcoin_client: Client, database: Database, @@ -280,37 +289,43 @@ impl Wallet { self.settings.integration_test() } - pub(crate) fn is_mature(&self, rune: Rune, commit: &Transaction) -> Result { - let transaction = self - .bitcoin_client() - .get_transaction(&commit.txid(), Some(true)) - .into_option()?; + fn is_above_minimum_at_height(&self, rune: Rune) -> Result { + Ok( + rune + >= Rune::minimum_at_height( + self.chain().network(), + Height(u32::try_from(self.bitcoin_client().get_block_count()? + 1).unwrap()), + ), + ) + } - if let Some(transaction) = transaction { - if u32::try_from(transaction.info.confirmations).unwrap() + 1 - >= Runestone::COMMIT_CONFIRMATIONS.into() - && rune - >= Rune::minimum_at_height( - self.chain().network(), - Height(u32::try_from(self.bitcoin_client().get_block_count()? + 1).unwrap()), - ) + pub(crate) fn check_maturity(&self, rune: Rune, commit: &Transaction) -> Result { + Ok( + if let Some(commit_tx) = self + .bitcoin_client() + .get_transaction(&commit.txid(), Some(true)) + .into_option()? { - let tx_out = self + let current_confirmations = u32::try_from(commit_tx.info.confirmations)?; + if self .bitcoin_client() - .get_tx_out(&commit.txid(), 0, Some(true))?; - - if let Some(tx_out) = tx_out { - if tx_out.confirmations + 1 >= Runestone::COMMIT_CONFIRMATIONS.into() { - return Ok(true); - } + .get_tx_out(&commit.txid(), 0, Some(true))? + .is_none() + { + Maturity::CommitSpent(commit_tx.info.txid) + } else if !self.is_above_minimum_at_height(rune)? { + Maturity::BelowMinimumHeight(self.bitcoin_client().get_block_count()? + 1) + } else if current_confirmations + 1 < Runestone::COMMIT_CONFIRMATIONS.into() { + Maturity::ConfirmationsPending( + u32::from(Runestone::COMMIT_CONFIRMATIONS) - current_confirmations - 1, + ) } else { - self.clear_etching(rune)?; - bail!("rune commitment spent, can't send reveal tx"); + Maturity::Mature } - } - } - - Ok(false) + } else { + Maturity::CommitNotFound + }, + ) } pub(crate) fn wait_for_maturation(&self, rune: Rune) -> Result { @@ -324,14 +339,37 @@ impl Wallet { entry.commit.txid() ); + let mut pending_confirmations: u32 = Runestone::COMMIT_CONFIRMATIONS.into(); + + let progress = ProgressBar::new(pending_confirmations.into()).with_style( + ProgressStyle::default_bar() + .template("Maturing in...[{eta}] {spinner:.green} [{bar:40.cyan/blue}] {pos}/{len}") + .unwrap() + .progress_chars("█▓▒░ "), + ); + loop { if SHUTTING_DOWN.load(atomic::Ordering::Relaxed) { eprintln!("Suspending batch. Run `ord wallet resume` to continue."); return Ok(entry.output); } - if self.is_mature(rune, &entry.commit)? { - break; + match self.check_maturity(rune, &entry.commit)? { + Maturity::Mature => { + progress.finish_with_message("Rune matured, submitting..."); + break; + } + Maturity::ConfirmationsPending(remaining) => { + if remaining < pending_confirmations { + pending_confirmations = remaining; + progress.inc(1); + } + } + Maturity::CommitSpent(txid) => { + self.clear_etching(rune)?; + bail!("rune commitment {} spent, can't send reveal tx", txid); + } + _ => {} } if !self.integration_test() {