From ce915ded5ac0db081530b2c9253b4b94f7112732 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 1 Nov 2022 12:14:20 -0500 Subject: [PATCH 1/2] Add generate block command --- client/src/client.rs | 26 ++++++++- integration_test/src/main.rs | 102 +++++++++++++++++++++++++++++++++++ json/src/lib.rs | 7 +++ 3 files changed, 134 insertions(+), 1 deletion(-) diff --git a/client/src/client.rs b/client/src/client.rs index d12c4f4b..2978a75d 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -22,7 +22,7 @@ use serde_json; use crate::bitcoin::hashes::hex::{FromHex, ToHex}; use crate::bitcoin::secp256k1::ecdsa::Signature; use crate::bitcoin::{ - Address, Amount, Block, BlockHeader, OutPoint, PrivateKey, PublicKey, Script, Transaction, + Address, Amount, Block, BlockHeader, OutPoint, PrivateKey, PublicKey, Script, Transaction, Txid, }; use log::Level::{Debug, Trace, Warn}; @@ -186,6 +186,13 @@ impl RawTx for String { } } +/// The different ways to add a transaction to a block in generate block +#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum MineableTx { + RawTx(Transaction), + Txid(Txid), +} + /// The different authentication methods for the client. #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum Auth { @@ -866,6 +873,23 @@ pub trait RpcApi: Sized { self.call("generate", &[block_num.into(), opt_into_json(maxtries)?]) } + /// Mine a set of ordered transactions to a specified address + /// and return the block hash. + fn generate_block( + &self, + address: &Address, + txs: &[MineableTx], + ) -> Result { + let tx_strs: Vec<_> = txs + .iter() + .map(|t| match t.to_owned() { + MineableTx::RawTx(tx) => tx.raw_hex(), + MineableTx::Txid(txid) => txid.to_hex(), + }) + .collect(); + self.call("generateblock", &[address.to_string().into(), tx_strs.into()]) + } + /// Mark a block as invalid by `block_hash` fn invalidate_block(&self, block_hash: &bitcoin::BlockHash) -> Result<()> { self.call("invalidateblock", &[into_json(block_hash)?]) diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index c91cc33d..f83af82e 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -140,6 +140,8 @@ fn main() { test_generate(&cl); test_get_balance_generate_to_address(&cl); test_get_balances_generate_to_address(&cl); + test_generate_block_raw_tx(&cl); + test_generate_block_txid(&cl); test_get_best_block_hash(&cl); test_get_block_count(&cl); test_get_block_hash(&cl); @@ -277,6 +279,106 @@ fn test_get_balances_generate_to_address(cl: &Client) { } } +fn test_generate_block_raw_tx(cl: &Client) { + let sk = PrivateKey { + network: Network::Regtest, + inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), + compressed: true, + }; + let addr = Address::p2wpkh(&sk.public_key(&SECP), Network::Regtest).unwrap(); + + let options = json::ListUnspentQueryOptions { + minimum_amount: Some(btc(2)), + ..Default::default() + }; + let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); + let unspent = unspent.into_iter().nth(0).unwrap(); + + let tx = Transaction { + version: 1, + lock_time: PackedLockTime::ZERO, + input: vec![TxIn { + previous_output: OutPoint { + txid: unspent.txid, + vout: unspent.vout, + }, + sequence: Sequence::MAX, + script_sig: Script::new(), + witness: Witness::new(), + }], + output: vec![TxOut { + value: (unspent.amount - *FEE).to_sat(), + script_pubkey: addr.script_pubkey(), + }], + }; + + let input = json::SignRawTransactionInput { + txid: unspent.txid, + vout: unspent.vout, + script_pub_key: unspent.script_pub_key, + redeem_script: None, + amount: Some(unspent.amount), + }; + let res = cl.sign_raw_transaction_with_wallet(&tx, Some(&[input]), None).unwrap(); + assert!(res.complete); + + let raw_tx = bitcoincore_rpc::MineableTx::RawTx(res.transaction().unwrap()); + let result = cl.generate_block(&cl.get_new_address(None, None).unwrap(), &[raw_tx]).unwrap(); + let tip = cl.get_best_block_hash().unwrap(); + assert_eq!(result.hash, tip); +} + +fn test_generate_block_txid(cl: &Client) { + let sk = PrivateKey { + network: Network::Regtest, + inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), + compressed: true, + }; + let addr = Address::p2wpkh(&sk.public_key(&SECP), Network::Regtest).unwrap(); + + let options = json::ListUnspentQueryOptions { + minimum_amount: Some(btc(2)), + ..Default::default() + }; + let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); + let unspent = unspent.into_iter().nth(0).unwrap(); + + let tx = Transaction { + version: 1, + lock_time: PackedLockTime::ZERO, + input: vec![TxIn { + previous_output: OutPoint { + txid: unspent.txid, + vout: unspent.vout, + }, + sequence: Sequence::MAX, + script_sig: Script::new(), + witness: Witness::new(), + }], + output: vec![TxOut { + value: (unspent.amount - *FEE).to_sat(), + script_pubkey: addr.script_pubkey(), + }], + }; + + let input = json::SignRawTransactionInput { + txid: unspent.txid, + vout: unspent.vout, + script_pub_key: unspent.script_pub_key, + redeem_script: None, + amount: Some(unspent.amount), + }; + let res = cl.sign_raw_transaction_with_wallet(&tx, Some(&[input]), None).unwrap(); + assert!(res.complete); + + let tx = res.transaction().unwrap(); + let txid = bitcoincore_rpc::MineableTx::Txid(tx.txid()); + cl.send_raw_transaction(&tx).unwrap(); + let result = cl.generate_block(&cl.get_new_address(None, None).unwrap(), &[txid]).unwrap(); + let tip = cl.get_best_block_hash().unwrap(); + assert_eq!(result.hash, tip); +} + fn test_get_best_block_hash(cl: &Client) { let _ = cl.get_best_block_hash().unwrap(); } diff --git a/json/src/lib.rs b/json/src/lib.rs index 63f27327..80d1fab7 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -988,6 +988,13 @@ pub struct GetAddressInfoResult { pub label: Option, } +/// Models the result of "generateblock" +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct GenerateBlockResult { + /// Hash of the block generated + pub hash: bitcoin::BlockHash, +} + /// Models the result of "getblockchaininfo" #[derive(Clone, Debug, Deserialize, Serialize)] pub struct GetBlockchainInfoResult { From badf7fcc8a7d996b7465c231350dbe040a201743 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Tue, 14 Feb 2023 16:41:44 -0600 Subject: [PATCH 2/2] Fix formatting --- client/src/lib.rs | 6 ++++-- integration_test/src/main.rs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index e15d049b..4cc5f0d2 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -42,8 +42,10 @@ fn deserialize_hex(hex: &str) -> Result { let mut reader = HexIterator::new(&hex)?; let object = Decodable::consensus_decode(&mut reader)?; if reader.read_u8().is_ok() { - Err(Error::BitcoinSerialization(bitcoin::consensus::encode::Error::ParseFailed("data not consumed entirely when explicitly deserializing"))) + Err(Error::BitcoinSerialization(bitcoin::consensus::encode::Error::ParseFailed( + "data not consumed entirely when explicitly deserializing", + ))) } else { Ok(object) } -} \ No newline at end of file +} diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index f83af82e..7a4ddc0c 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -25,8 +25,8 @@ use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::hashes::Hash; use bitcoin::secp256k1; use bitcoin::{ - Address, Amount, PackedLockTime, Network, OutPoint, PrivateKey, Script, EcdsaSighashType, SignedAmount, - Sequence, Transaction, TxIn, TxOut, Txid, Witness, + Address, Amount, EcdsaSighashType, Network, OutPoint, PackedLockTime, PrivateKey, Script, + Sequence, SignedAmount, Transaction, TxIn, TxOut, Txid, Witness, }; use bitcoincore_rpc::bitcoincore_rpc_json::{ GetBlockTemplateModes, GetBlockTemplateRules, ScanTxOutRequest,