diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a52e85f4..040382d0 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -5,7 +5,7 @@ mod utils; use crate::{ - bitcoin::BitcoinInterface, + bitcoin::{BitcoinError, BitcoinInterface}, database::{Coin, DatabaseInterface}, descriptors, DaemonControl, VERSION, }; @@ -50,6 +50,10 @@ pub enum CommandError { /* target feerate */ u64, ), SanityCheckFailure(Psbt), + UnknownSpend(bitcoin::Txid), + // FIXME: when upgrading Miniscript put the actual error there + SpendFinalization(String), + TxBroadcast(String), } impl fmt::Display for CommandError { @@ -71,6 +75,11 @@ impl fmt::Display for CommandError { "BUG! Please report this. Failed sanity checks for PSBT '{:?}'.", psbt ), + Self::UnknownSpend(txid) => write!(f, "Unknown spend transaction '{}'.", txid), + Self::SpendFinalization(e) => { + write!(f, "Failed to finalize the spend transaction PSBT: '{}'.", e) + } + Self::TxBroadcast(e) => write!(f, "Failed to broadcast transaction: '{}'.", e), } } } @@ -446,6 +455,28 @@ impl DaemonControl { let mut db_conn = self.db.connection(); db_conn.delete_spend(txid); } + + /// Finalize and broadcast this stored Spend transaction. + pub fn broadcast_spend(&self, txid: &bitcoin::Txid) -> Result<(), CommandError> { + let mut db_conn = self.db.connection(); + + // First, try to finalize the spending transaction with the elements contained + // in the PSBT. + let mut spend_psbt = db_conn + .spend_tx(txid) + .ok_or(CommandError::UnknownSpend(*txid))?; + log::debug!("B"); + miniscript::psbt::finalize(&mut spend_psbt, &self.secp) + .map_err(|e| CommandError::SpendFinalization(e.to_string()))?; + + // Then, broadcast it (or try to, we never know if we are not going to hit an + // error at broadcast time). + let final_tx = spend_psbt.extract_tx(); + match self.bitcoin.broadcast_tx(&final_tx) { + Ok(()) => Ok(()), + Err(BitcoinError::Broadcast(e)) => Err(CommandError::TxBroadcast(e)), + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/jsonrpc/mod.rs b/src/jsonrpc/mod.rs index 7bde56f0..db721891 100644 --- a/src/jsonrpc/mod.rs +++ b/src/jsonrpc/mod.rs @@ -51,6 +51,9 @@ pub struct Request { pub id: ReqId, } +/// A failure to broadcast a transaction to the P2P network. +const BROADCAST_ERROR: i64 = 1_000; + /// JSONRPC2 error codes. See https://www.jsonrpc.org/specification#error_object. #[derive(Debug, PartialEq, Eq, Clone)] pub enum ErrorCode { @@ -80,6 +83,7 @@ impl From for ErrorCode { match code { -32601 => ErrorCode::MethodNotFound, -32602 => ErrorCode::InvalidParams, + -32603 => ErrorCode::InternalError, code => ErrorCode::ServerError(code), } } @@ -153,12 +157,17 @@ impl From for Error { | commands::CommandError::InvalidFeerate(..) | commands::CommandError::AlreadySpent(..) | commands::CommandError::InvalidOutputValue(..) - | commands::CommandError::InsufficientFunds(..) => { + | commands::CommandError::InsufficientFunds(..) + | commands::CommandError::UnknownSpend(..) + | commands::CommandError::SpendFinalization(..) => { Error::new(ErrorCode::InvalidParams, e.to_string()) } commands::CommandError::SanityCheckFailure(_) => { Error::new(ErrorCode::InternalError, e.to_string()) } + commands::CommandError::TxBroadcast(_) => { + Error::new(ErrorCode::ServerError(BROADCAST_ERROR), e.to_string()) + } } } }