From e77628a9abda1f30f5f18cfe3c64194656183618 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 8 Nov 2024 16:59:37 -0800 Subject: [PATCH] Add support for `maxfeerate` and `maxburnamount` to `sendrawtransaction` --- client/src/client.rs | 48 ++++++++++++++++++++++++++++++------ integration_test/src/main.rs | 4 +-- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index 2f809a79..fdc5877e 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -26,7 +26,7 @@ use crate::bitcoin::address::{NetworkUnchecked, NetworkChecked}; use crate::bitcoin::hashes::hex::FromHex; use crate::bitcoin::secp256k1::ecdsa::Signature; use crate::bitcoin::{ - Address, Amount, Block, OutPoint, PrivateKey, PublicKey, Script, Transaction, + Address, Amount, Block, OutPoint, PrivateKey, PublicKey, Script, Transaction, FeeRate, }; use log::Level::{Debug, Trace, Warn}; @@ -1076,8 +1076,42 @@ pub trait RpcApi: Sized { self.call("ping", &[]) } - fn send_raw_transaction(&self, tx: R) -> Result { - self.call("sendrawtransaction", &[tx.raw_hex().into()]) + // Submit a raw transaction to local node and network. + // + // # Arguments + // + // 1. `tx`: Transaction to submit + // 2. `maxfeerate`: Reject transaction whose fee rate is higher than the + // specified value Available in Bitcoin Core 0.19.0 and later. + // 3. `maxburnamount`: Reject transactions with provably unspendable + // outputs (e.g. 'datacarrier' outputs that use the OP_RETURN opcode) + // greater than the specified value, expressed in BTC. If burning funds + // through unspendable outputs is desired, increase this value. This + // check is based on heuristics and does not guarantee spendability of + // outputs. Available in Bitcoin Core 25.0.0 and later. + fn send_raw_transaction( + &self, + tx: R, + maxfeerate: Option, + maxburnamount: Option, + ) -> Result { + fn fee_rate_to_btc_per_kvb(fee_rate: FeeRate) -> f64 { + let sat_per_kwu = fee_rate.to_sat_per_kwu() as f64; + let sat_per_kvb = sat_per_kwu * 4.0; + let btc_per_kvb = sat_per_kvb / Amount::ONE_BTC.to_sat() as f64; + btc_per_kvb + } + + let mut args = [ + into_json(tx.raw_hex())?, + opt_into_json(maxfeerate.map(fee_rate_to_btc_per_kvb))?, + opt_into_json(maxburnamount.map(|amount| amount.to_btc()))?, + ]; + + self.call( + "sendrawtransaction", + handle_defaults(&mut args, &[null(), null(), null()]), + ) } fn estimate_smart_fee( @@ -1360,10 +1394,10 @@ mod tests { let client = Client::new("http://localhost/".into(), Auth::None).unwrap(); let tx: bitcoin::Transaction = encode::deserialize(&Vec::::from_hex("0200000001586bd02815cf5faabfec986a4e50d25dbee089bd2758621e61c5fab06c334af0000000006b483045022100e85425f6d7c589972ee061413bcf08dc8c8e589ce37b217535a42af924f0e4d602205c9ba9cb14ef15513c9d946fa1c4b797883e748e8c32171bdf6166583946e35c012103dae30a4d7870cd87b45dd53e6012f71318fdd059c1c2623b8cc73f8af287bb2dfeffffff021dc4260c010000001976a914f602e88b2b5901d8aab15ebe4a97cf92ec6e03b388ac00e1f505000000001976a914687ffeffe8cf4e4c038da46a9b1d37db385a472d88acfd211500").unwrap()).unwrap(); - assert!(client.send_raw_transaction(&tx).is_err()); - assert!(client.send_raw_transaction(&encode::serialize(&tx)).is_err()); - assert!(client.send_raw_transaction("deadbeef").is_err()); - assert!(client.send_raw_transaction("deadbeef".to_owned()).is_err()); + assert!(client.send_raw_transaction(&tx, None, None).is_err()); + assert!(client.send_raw_transaction(&encode::serialize(&tx), None, None).is_err()); + assert!(client.send_raw_transaction("deadbeef", None, None).is_err()); + assert!(client.send_raw_transaction("deadbeef".to_owned(), None, None).is_err()); } fn test_handle_defaults_inner() -> Result<()> { diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index c3236bb9..4f4b31db 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -613,7 +613,7 @@ fn test_sign_raw_transaction_with_send_raw_transaction(cl: &Client) { }; let res = cl.sign_raw_transaction_with_wallet(&tx, Some(&[input]), None).unwrap(); assert!(res.complete); - let txid = cl.send_raw_transaction(&res.transaction().unwrap()).unwrap(); + let txid = cl.send_raw_transaction(&res.transaction().unwrap(), None, None).unwrap(); let tx = Transaction { version: transaction::Version::ONE, @@ -637,7 +637,7 @@ fn test_sign_raw_transaction_with_send_raw_transaction(cl: &Client) { .sign_raw_transaction_with_key(&tx, &[sk], None, Some(sighash::EcdsaSighashType::All.into())) .unwrap(); assert!(res.complete); - let _ = cl.send_raw_transaction(&res.transaction().unwrap()).unwrap(); + let _ = cl.send_raw_transaction(&res.transaction().unwrap(), None, None).unwrap(); } fn test_invalidate_block_reconsider_block(cl: &Client) {