diff --git a/daemon/src/wallet.rs b/daemon/src/wallet.rs index 20a96b8..fc23ea4 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,23 +328,27 @@ 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 { - /// Transaction or block was rejected by network rules. Error code -27. + /// General error during transaction or block submission Error code -25. + RpcVerifyError, + /// Transaction already in chain. Error code -27. RpcVerifyAlreadyInChain, } 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]