diff --git a/.editorconfig b/.editorconfig index 3178a1e0..76a93c06 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,3 @@ -# see https://editorconfig.org for more options, and setup instructions for yours editor - [*] indent_style = space -indent_size = 4 +indent_size = 2 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cce86ea6..736e38fe 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -56,5 +56,5 @@ jobs: - name: Integration Tests env: - BITCOINVERSION: 0.21.0 + BITCOINVERSION: '22.0' run: ./contrib/integration_test.sh diff --git a/client/src/client.rs b/client/src/client.rs index b4d3f212..d6e82cda 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -280,6 +280,7 @@ pub trait RpcApi: Sized { blank: Option, passphrase: Option<&str>, avoid_reuse: Option, + descriptors: Option, ) -> Result { let mut args = [ wallet.into(), @@ -287,10 +288,14 @@ pub trait RpcApi: Sized { opt_into_json(blank)?, opt_into_json(passphrase)?, opt_into_json(avoid_reuse)?, + opt_into_json(descriptors)?, ]; self.call( "createwallet", - handle_defaults(&mut args, &[false.into(), false.into(), into_json("")?, false.into()]), + handle_defaults( + &mut args, + &[false.into(), false.into(), into_json("")?, false.into(), null()], + ), ) } @@ -1073,8 +1078,31 @@ 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. + // + // Note that `maxfeerate` is not supported. + // + // # Arguments + // + // 1. `tx`: Transaction to submit + // 2. `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, + maxburnamount: Option, + ) -> Result { + let mut args = [ + into_json(tx.raw_hex())?, + null(), + opt_into_json(maxburnamount.map(|amount| amount.to_btc()))?, + ]; + + self.call("sendrawtransaction", handle_defaults(&mut args, &[null(), null(), null()])) } fn estimate_smart_fee( @@ -1361,10 +1389,10 @@ mod tests { let client = Client::new("http://localhost/", 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).is_err()); + assert!(client.send_raw_transaction(&encode::serialize(&tx), None).is_err()); + assert!(client.send_raw_transaction("deadbeef", None).is_err()); + assert!(client.send_raw_transaction("deadbeef".to_owned(), None).is_err()); } fn test_handle_defaults_inner() -> Result<()> { diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index ecba8323..64b08f87 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -10,29 +10,31 @@ #![deny(unused)] -#[macro_use] -extern crate lazy_static; - -use std::collections::HashMap; -use std::str::FromStr; - -use bitcoin::absolute::LockTime; -use bitcoin::address::{NetworkChecked, NetworkUnchecked}; -use bitcoincore_rpc::json; -use bitcoincore_rpc::jsonrpc::error::Error as JsonRpcError; -use bitcoincore_rpc::{Auth, Client, Error, RpcApi}; - -use crate::json::BlockStatsFields as BsFields; -use bitcoin::consensus::encode::{deserialize, serialize_hex}; -use bitcoin::hashes::hex::FromHex; -use bitcoin::hashes::Hash; -use bitcoin::{secp256k1, sighash, ScriptBuf}; -use bitcoin::{ - transaction, Address, Amount, CompressedPublicKey, Network, OutPoint, PrivateKey, Sequence, - SignedAmount, Transaction, TxIn, TxOut, Txid, Witness, -}; -use bitcoincore_rpc::bitcoincore_rpc_json::{ - GetBlockTemplateModes, GetBlockTemplateRules, GetZmqNotificationsResult, ScanTxOutRequest, +use { + bitcoin::{ + absolute::LockTime, + address::{NetworkChecked, NetworkUnchecked}, + consensus::{ + encode::{deserialize, serialize_hex}, + Encodable, + }, + hashes::{hex::FromHex, Hash}, + opcodes, script, secp256k1, sighash, transaction, Address, Amount, CompressedPublicKey, + Network, OutPoint, PrivateKey, ScriptBuf, Sequence, SignedAmount, Transaction, TxIn, TxOut, + Txid, Witness, + }, + bitcoincore_rpc::{ + bitcoincore_rpc_json::{ + GetBlockTemplateModes, GetBlockTemplateRules, GetZmqNotificationsResult, + ScanTxOutRequest, + }, + json, + jsonrpc::error::Error as JsonRpcError, + Auth, Client, Error, RpcApi, + }, + json::BlockStatsFields as BsFields, + lazy_static::lazy_static, + std::{collections::HashMap, str::FromStr}, }; lazy_static! { @@ -140,7 +142,7 @@ fn main() { unsafe { VERSION = cl.version().unwrap() }; println!("Version: {}", version()); - cl.create_wallet("testwallet", None, None, None, None).unwrap(); + cl.create_wallet("testwallet", None, None, None, None, Some(false)).unwrap(); test_get_mining_info(&cl); test_get_blockchain_info(&cl); @@ -176,6 +178,7 @@ fn main() { test_lock_unspent_unlock_unspent(&cl); test_get_block_filter(&cl); test_sign_raw_transaction_with_send_raw_transaction(&cl); + test_send_raw_transaction(&cl); test_invalidate_block_reconsider_block(&cl); test_key_pool_refill(&cl); test_create_raw_transaction(&cl); @@ -632,7 +635,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).unwrap(); let tx = Transaction { version: transaction::Version::ONE, @@ -661,7 +664,44 @@ fn test_sign_raw_transaction_with_send_raw_transaction(cl: &Client) { ) .unwrap(); assert!(res.complete); - let _ = cl.send_raw_transaction(&res.transaction().unwrap()).unwrap(); + let _ = cl.send_raw_transaction(&res.transaction().unwrap(), None).unwrap(); +} + +fn test_send_raw_transaction(cl: &Client) { + if version() > 240000 { + let tx = Transaction { + input: Vec::new(), + lock_time: LockTime::ZERO, + output: vec![TxOut { + value: Amount::from_sat(10), + script_pubkey: script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .into_script(), + }], + version: transaction::Version::ONE, + }; + + let mut buffer = Vec::new(); + + { + tx.version.consensus_encode(&mut buffer).unwrap(); + tx.input.consensus_encode(&mut buffer).unwrap(); + tx.output.consensus_encode(&mut buffer).unwrap(); + tx.lock_time.consensus_encode(&mut buffer).unwrap(); + } + + let tx = cl.fund_raw_transaction(&buffer, None, None).unwrap(); + + let signed = cl + .sign_raw_transaction_with_wallet(&tx.hex, None, None) + .unwrap() + .transaction() + .unwrap(); + + cl.send_raw_transaction(&signed, None).unwrap_err(); + cl.send_raw_transaction(&signed, Some(Amount::from_sat(9))).unwrap_err(); + cl.send_raw_transaction(&signed, Some(Amount::from_sat(10))).unwrap(); + } } fn test_invalidate_block_reconsider_block(cl: &Client) { @@ -1152,6 +1192,7 @@ fn test_create_wallet(cl: &Client) { wallet_param.blank, wallet_param.passphrase, wallet_param.avoid_reuse, + None, ) .unwrap(); @@ -1305,7 +1346,7 @@ fn test_getblocktemplate(cl: &Client) { } fn test_unloadwallet(cl: &Client) { - cl.create_wallet("testunloadwallet", None, None, None, None).unwrap(); + cl.create_wallet("testunloadwallet", None, None, None, None, None).unwrap(); let res = new_wallet_client("testunloadwallet").unload_wallet(None).unwrap(); @@ -1321,7 +1362,7 @@ fn test_loadwallet(_: &Client) { let wallet_client = new_wallet_client(wallet_name); assert!(wallet_client.load_wallet(wallet_name).is_err()); - wallet_client.create_wallet(wallet_name, None, None, None, None).unwrap(); + wallet_client.create_wallet(wallet_name, None, None, None, None, None).unwrap(); assert!(wallet_client.load_wallet(wallet_name).is_err()); wallet_client.unload_wallet(None).unwrap(); @@ -1336,7 +1377,7 @@ fn test_backupwallet(_: &Client) { assert!(wallet_client.backup_wallet(None).is_err()); assert!(wallet_client.backup_wallet(Some(&backup_path)).is_err()); - wallet_client.create_wallet("testbackupwallet", None, None, None, None).unwrap(); + wallet_client.create_wallet("testbackupwallet", None, None, None, None, None).unwrap(); assert!(wallet_client.backup_wallet(None).is_err()); assert!(wallet_client.backup_wallet(Some(&backup_path)).is_ok()); } diff --git a/justfile b/justfile index 7e3b26d4..16b40fff 100644 --- a/justfile +++ b/justfile @@ -1,20 +1,8 @@ watch +args='test': cargo watch --clear --exec '{{args}}' -build: - cargo build --workspace --all-targets - -check: - cargo check --workspace --all-targets - -lint: - cargo clippy --workspace --all-targets - -fmt: - cargo fmt --all - -format: - cargo fmt --all --check - -test: - cargo test --workspace --all-targets +ci: + cargo build --all --all-targets + cargo clippy --all --all-targets + cargo fmt --all -- --check + cargo test --all