From 192a38235c39c94f76f68aee7a6b2b4afbbe083d Mon Sep 17 00:00:00 2001 From: rishflab Date: Tue, 14 Dec 2021 13:00:24 +1100 Subject: [PATCH] Check if transaction on chain if input missing or spent Electrum sometimes returns error code -27 bad-txns-inputs-missingorspent. This can occur because the transaction has been published. Electrum should return error code -25 transaction already on chain. In the case of error code -27 we now check if the transaction is on chain and return ok if it is. --- daemon/src/wallet.rs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/daemon/src/wallet.rs b/daemon/src/wallet.rs index 20a96b8..3c496cd 100644 --- a/daemon/src/wallet.rs +++ b/daemon/src/wallet.rs @@ -15,6 +15,7 @@ use bdk::bitcoin::PublicKey; use bdk::bitcoin::Script; use bdk::bitcoin::Transaction; use bdk::bitcoin::Txid; +use bdk::blockchain::Blockchain; use bdk::blockchain::ElectrumBlockchain; use bdk::blockchain::NoopProgress; use bdk::database::BatchDatabase; @@ -195,17 +196,30 @@ impl Actor { if let Err(&bdk::Error::Electrum(electrum_client::Error::Protocol(ref value))) = result.as_ref() { - let error_code = parse_rpc_protocol_error_code(value).with_context(|| { + let rpc_error = parse_rpc_protocol_error(value).with_context(|| { format!("Failed to parse electrum error response '{:?}'", value) })?; - if error_code == i64::from(RpcErrorCode::RpcVerifyAlreadyInChain) { + if rpc_error.code == i64::from(RpcErrorCode::RpcVerifyAlreadyInChain) { tracing::trace!( %txid, "Attempted to broadcast transaction that was already on-chain", ); return Ok(txid); } + + // We do this check because electrum sometimes returns an RpcVerifyError when it should + // be returning a RpcVerifyAlreadyInChain error, + if rpc_error.code == i64::from(RpcErrorCode::RpcVerifyError) + && rpc_error.message == "bad-txns-inputs-missingorspent" + { + if let Ok(Some(_)) = self.wallet.client().get_tx(&txid) { + tracing::trace!( + %txid, "Attempted to broadcast transaction that was already on-chain", + ); + return Ok(txid); + } + } } let txid = result.with_context(|| { @@ -304,7 +318,7 @@ pub struct Withdraw { pub address: Address, } -fn parse_rpc_protocol_error_code(error_value: &Value) -> Result { +fn parse_rpc_protocol_error(error_value: &Value) -> Result { let json = error_value .as_str() .context("Not a string")? @@ -314,16 +328,19 @@ fn parse_rpc_protocol_error_code(error_value: &Value) -> Result { let error = serde_json::from_str::(json).context("Error has unexpected format")?; - Ok(error.code) + Ok(error) } #[derive(serde::Deserialize)] struct RpcError { code: i64, + message: String, } /// Bitcoin error codes: pub enum RpcErrorCode { + /// General error during transaction or block submission Error code -25. + RpcVerifyError, /// Transaction or block was rejected by network rules. Error code -27. RpcVerifyAlreadyInChain, } @@ -331,6 +348,7 @@ pub enum RpcErrorCode { impl From for i64 { fn from(code: RpcErrorCode) -> Self { match code { + RpcErrorCode::RpcVerifyError => -25, RpcErrorCode::RpcVerifyAlreadyInChain => -27, } } @@ -392,9 +410,10 @@ mod tests { fn parse_error_response() { let response = serde_json::Value::String(r#"sendrawtransaction RPC error: {"code":-27,"message":"Transaction already in block chain"}"#.to_owned()); - let code = parse_rpc_protocol_error_code(&response).unwrap(); + let rpc_error = parse_rpc_protocol_error(&response).unwrap(); - assert_eq!(code, -27); + assert_eq!(rpc_error.code, -27); + assert_eq!(rpc_error.message, "Transaction already in block chain"); } #[test]