From f0334d2779c7aee343bb472028b4ace896e71a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ceyhun=20=C5=9Een?= Date: Fri, 23 Aug 2024 14:29:25 +0300 Subject: [PATCH] Add fundrawtransaction and signrawtransactionwithwallet (#51) * transactions: Move input/output amount checkher to their own function. * rpc_api: Add fund_raw_transaction. * utils: Move hex serializers to utils from rpc_adapter. * rpc_api: Add initial sign_raw_transaction_with_wallet. * rpc_api: Add new test for sign_raw_transaction_with_wallet. * rpc_api: Return a fixed address. * rpc: Add unimplemented new RPC functions for the future. * rpc_api: Use the new get_constant_credential_from_witness. * rpc_api: Fix sign_raw_transaction_with_wallet test. * rpc_api: Assign script_sig to new input in fund_raw_transaction. * rpc_api: Compare output script pubkey instead of input in sign_raw_transaction_with_wallet. * tests: Add fund_sign_raw_transaction_with_wallet. --- src/client/mod.rs | 2 +- src/client/rpc_api.rs | 229 ++++++++++++++++++++++++++++- src/ledger/address.rs | 31 ++++ src/ledger/errors.rs | 2 + src/ledger/mod.rs | 2 +- src/ledger/transactions.rs | 25 ++-- src/rpc/adapter/blockchain.rs | 10 +- src/rpc/adapter/mod.rs | 44 ------ src/rpc/adapter/rawtransactions.rs | 30 +++- src/rpc/adapter/wallet.rs | 4 +- src/rpc/traits.rs | 41 ++++++ src/utils.rs | 43 +++++- tests/raw_transactions.rs | 39 ++++- 13 files changed, 426 insertions(+), 76 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index f412e9b..61f0cc2 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -32,7 +32,7 @@ impl RpcApiWrapper for bitcoincore_rpc::Client { } /// Mock Bitcoin RPC client. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Client { /// Bitcoin ledger. ledger: Ledger, diff --git a/src/client/rpc_api.rs b/src/client/rpc_api.rs index 3e8ac55..9cf0222 100644 --- a/src/client/rpc_api.rs +++ b/src/client/rpc_api.rs @@ -4,20 +4,23 @@ //! `Client`. use super::Client; -use crate::ledger::Ledger; +use crate::{ + ledger::{self, errors::LedgerError}, + utils::encode_to_hex, +}; use bitcoin::{ address::NetworkChecked, consensus::{encode, Encodable}, hashes::Hash, params::Params, - Address, Amount, BlockHash, SignedAmount, Transaction, Txid, + Address, Amount, BlockHash, OutPoint, SignedAmount, Transaction, TxIn, TxOut, Txid, }; use bitcoincore_rpc::{ json::{ self, GetRawTransactionResult, GetRawTransactionResultVin, GetRawTransactionResultVinScriptSig, GetRawTransactionResultVout, GetRawTransactionResultVoutScriptPubKey, GetTransactionResult, GetTransactionResultDetail, - GetTransactionResultDetailCategory, GetTxOutResult, WalletTxInfo, + GetTransactionResultDetailCategory, GetTxOutResult, SignRawTransactionResult, WalletTxInfo, }, Error, RpcApi, }; @@ -276,7 +279,7 @@ impl RpcApi for Client { _label: Option<&str>, _address_type: Option, ) -> bitcoincore_rpc::Result> { - let address = Ledger::generate_address_from_witness(); + let address = ledger::Ledger::get_constant_credential_from_witness().address; Ok(address.as_unchecked().to_owned()) } @@ -353,12 +356,144 @@ impl RpcApi for Client { fn get_block_count(&self) -> bitcoincore_rpc::Result { Ok(self.ledger.get_block_height()?.into()) } + + fn fund_raw_transaction( + &self, + tx: R, + _options: Option<&json::FundRawTransactionOptions>, + _is_witness: Option, + ) -> bitcoincore_rpc::Result { + let mut transaction: Transaction = encode::deserialize_hex(&tx.raw_hex())?; + tracing::debug!("Decoded input transaction: {transaction:?}"); + + let mut hex: Vec = Vec::new(); + let tx = encode_to_hex(&transaction); + tx.consensus_encode(&mut hex).unwrap(); + + let diff = match self.ledger.check_transaction_funds(&transaction) { + // If input amount is sufficient, no need to modify anything. + Ok(()) => { + return Ok(json::FundRawTransactionResult { + hex, + fee: Amount::from_sat(0), + change_position: -1, + }) + } + // Input funds are lower than the output funds, use the difference. + Err(LedgerError::InputFundsNotEnough(diff)) => diff, + // Other ledger errors. + Err(e) => return Err(e.into()), + }; + + tracing::debug!( + "Input funds are {diff} sats lower than the output sats, adding new input." + ); + + // Generate a new txout. + let address = self.get_new_address(None, None)?.assume_checked(); + let txid = self.send_to_address( + &address, + Amount::from_sat(diff * diff), + None, + None, + None, + None, + None, + None, + )?; + + let txin = TxIn { + previous_output: OutPoint { txid, vout: 0 }, + ..Default::default() + }; + + transaction.input.insert(0, txin); + tracing::debug!("New transaction: {transaction:?}"); + + let tx = encode_to_hex(&transaction); + let mut hex: Vec = Vec::new(); + tx.consensus_encode(&mut hex).unwrap(); + + Ok(json::FundRawTransactionResult { + hex, + fee: Amount::from_sat(0), + change_position: 0, + }) + } + + fn sign_raw_transaction_with_wallet( + &self, + tx: R, + _utxos: Option<&[json::SignRawTransactionInput]>, + _sighash_type: Option, + ) -> bitcoincore_rpc::Result { + let mut transaction: Transaction = encode::deserialize_hex(&tx.raw_hex())?; + tracing::debug!("Decoded input transaction: {transaction:?}"); + + let credentials = ledger::Ledger::get_constant_credential_from_witness(); + + let mut txouts: Vec = Vec::new(); + for input in transaction.input.clone() { + let tx = match self.get_raw_transaction(&input.previous_output.txid, None) { + Ok(tx) => tx, + Err(e) => return Err(e), + }; + + let txout = match tx.output.get(input.previous_output.vout as usize) { + Some(txout) => txout, + None => { + return Err(LedgerError::Transaction(format!( + "No txout for {:?}", + input.previous_output + )) + .into()) + } + }; + + txouts.push(txout.clone()); + } + + let inputs: Vec = transaction + .input + .iter() + .enumerate() + .map(|(idx, input)| { + let mut input = input.to_owned(); + tracing::trace!("Examining input {input:?}"); + + if input.witness.is_empty() + && txouts[idx].script_pubkey == credentials.address.script_pubkey() + { + tracing::debug!( + "Signing input {input:?} with witness {:?}", + credentials.witness.clone().unwrap() + ); + input.witness = credentials.witness.clone().unwrap(); + } + + input + }) + .collect(); + + transaction.input = inputs; + tracing::trace!("Final inputs {:?}", transaction.input); + + let mut hex: Vec = Vec::new(); + let tx = encode_to_hex(&transaction); + tx.consensus_encode(&mut hex).unwrap(); + + Ok(SignRawTransactionResult { + hex, + complete: true, + errors: None, + }) + } } #[cfg(test)] mod tests { - use crate::{ledger::Ledger, Client, RpcApiWrapper}; - use bitcoin::{Amount, Network, OutPoint, TxIn}; + use crate::{ledger::Ledger, utils::decode_from_hex, Client, RpcApiWrapper}; + use bitcoin::{consensus::Decodable, Amount, Network, OutPoint, Transaction, TxIn}; use bitcoincore_rpc::RpcApi; #[test] @@ -645,4 +780,86 @@ mod tests { assert_eq!(rpc.get_block_count().unwrap(), 1); } + + #[test] + fn fund_raw_transaction() { + let rpc = Client::new("fund_raw_transaction", bitcoincore_rpc::Auth::None).unwrap(); + + let address = Ledger::generate_credential_from_witness().address; + let txid = rpc + .send_to_address( + &address, + Amount::from_sat(0x1F), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + let txin = rpc.ledger.create_txin(txid, 0); + let txout = rpc + .ledger + .create_txout(Amount::from_sat(0x45), address.script_pubkey()); + let og_tx = rpc.ledger.create_transaction(vec![txin], vec![txout]); + + let res = rpc.fund_raw_transaction(&og_tx, None, None).unwrap(); + let tx = String::consensus_decode(&mut res.hex.as_slice()).unwrap(); + let tx = decode_from_hex::(tx).unwrap(); + + assert_ne!(og_tx, tx); + assert_eq!(res.change_position, 0); + + let res = rpc.fund_raw_transaction(&tx, None, None).unwrap(); + let new_tx = String::consensus_decode(&mut res.hex.as_slice()).unwrap(); + let new_tx = decode_from_hex::(new_tx).unwrap(); + + assert_eq!(tx, new_tx); + assert_eq!(res.change_position, -1); + } + + #[test] + fn sign_raw_transaction_with_wallet() { + let rpc = Client::new( + "sign_raw_transaction_with_wallet", + bitcoincore_rpc::Auth::None, + ) + .unwrap(); + + let address = Ledger::get_constant_credential_from_witness().address; + let txid = rpc + .send_to_address( + &address, + Amount::from_sat(0x1F), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + let txin = TxIn { + previous_output: OutPoint { txid, vout: 0 }, + script_sig: address.script_pubkey(), + ..Default::default() + }; + let txout = rpc + .ledger + .create_txout(Amount::from_sat(0x45), address.script_pubkey()); + let tx = rpc + .ledger + .create_transaction(vec![txin.clone()], vec![txout]); + + assert!(txin.witness.is_empty()); + + let res = rpc + .sign_raw_transaction_with_wallet(&tx, None, None) + .unwrap(); + let new_tx = String::consensus_decode(&mut res.hex.as_slice()).unwrap(); + let new_tx = decode_from_hex::(new_tx).unwrap(); + + assert!(!new_tx.input.first().unwrap().witness.is_empty()); + } } diff --git a/src/ledger/address.rs b/src/ledger/address.rs index 1b5d9c7..1ac58b2 100644 --- a/src/ledger/address.rs +++ b/src/ledger/address.rs @@ -81,6 +81,36 @@ impl Ledger { credential } + /// Generates the constant Bitcoin credentials from a witness program. + #[tracing::instrument] + pub fn get_constant_credential_from_witness() -> UserCredential { + let secp = Secp256k1::new(); + let secret_key = SecretKey::from_slice(&[0x45; 32]).unwrap(); + let public_key = PublicKey::from_secret_key(&secp, &secret_key); + let x_only_public_key = + XOnlyPublicKey::from_keypair(&Keypair::from_secret_key(&secp, &secret_key)).0; + let address = Address::p2tr(&secp, x_only_public_key, None, Network::Regtest); + + let mut credential = UserCredential { + secp, + secret_key, + public_key, + x_only_public_key, + address, + witness: None, + witness_program: None, + }; + tracing::trace!("Constant credentials: {credential:?}"); + + Ledger::create_witness(&mut credential); + + credential.address = Address::from_witness_program( + credential.witness_program.unwrap(), + bitcoin::Network::Regtest, + ); + + credential + } /// Generates a random Bicoin address. pub fn _generate_address() -> Address { @@ -137,6 +167,7 @@ mod tests { #[test] fn generate_credentials() { let credential = Ledger::generate_credential(); + println!("{:?}", credential.secret_key); assert_eq!( credential.address.address_type().unwrap(), diff --git a/src/ledger/errors.rs b/src/ledger/errors.rs index 3619e1e..95698a1 100644 --- a/src/ledger/errors.rs +++ b/src/ledger/errors.rs @@ -9,6 +9,8 @@ use thiserror::Error; pub enum LedgerError { #[error("Transaction error: {0}")] Transaction(String), + #[error("Transaction's input funds are {0} sats lower than the output funds")] + InputFundsNotEnough(u64), #[error("UTXO error: {0}")] Utxo(String), #[error("SpendingRequirements error: {0}")] diff --git a/src/ledger/mod.rs b/src/ledger/mod.rs index b20694f..f3d5ac0 100644 --- a/src/ledger/mod.rs +++ b/src/ledger/mod.rs @@ -12,7 +12,7 @@ use std::{ sync::{Arc, Mutex}, }; -mod address; +pub mod address; mod block; pub(crate) mod errors; mod script; diff --git a/src/ledger/transactions.rs b/src/ledger/transactions.rs index a3ebf60..d565ba7 100644 --- a/src/ledger/transactions.rs +++ b/src/ledger/transactions.rs @@ -162,15 +162,7 @@ impl Ledger { /// No checks for if that UTXO is spendable or not. #[tracing::instrument] pub fn check_transaction(&self, transaction: &Transaction) -> Result<(), LedgerError> { - let input_value = self.calculate_transaction_input_value(transaction)?; - let output_value = self.calculate_transaction_output_value(transaction); - - if input_value < output_value { - return Err(LedgerError::Transaction(format!( - "Input amount is smaller than output amount: {} < {}", - input_value, output_value - ))); - } + self.check_transaction_funds(transaction)?; let mut txouts = vec![]; for input in transaction.input.iter() { @@ -226,6 +218,21 @@ impl Ledger { Ok(()) } + /// Checks if transactions input amount is equal or bigger than the output + /// amount. + pub fn check_transaction_funds(&self, transaction: &Transaction) -> Result<(), LedgerError> { + let input_value = self.calculate_transaction_input_value(transaction)?; + let output_value = self.calculate_transaction_output_value(transaction); + + if input_value < output_value { + Err(LedgerError::InputFundsNotEnough( + output_value.to_sat() - input_value.to_sat(), + )) + } else { + Ok(()) + } + } + /// Calculates a transaction's total output value. /// /// # Panics diff --git a/src/rpc/adapter/blockchain.rs b/src/rpc/adapter/blockchain.rs index 46d526c..3c5598c 100644 --- a/src/rpc/adapter/blockchain.rs +++ b/src/rpc/adapter/blockchain.rs @@ -1,6 +1,6 @@ //! # Blockchain RPCs -use super::{decode_from_hex, encode_decode_to_rpc_error, encode_to_hex}; +use crate::utils::{decode_from_hex, encode_decode_to_rpc_error, encode_to_hex}; use crate::Client; use bitcoin::{consensus::Decodable, BlockHash, Txid}; use bitcoincore_rpc::{Error, RpcApi}; @@ -8,7 +8,7 @@ use bitcoincore_rpc::{Error, RpcApi}; pub fn getbestblockhash(client: &Client) -> Result { let res = client.get_best_block_hash()?; - Ok(encode_to_hex(res)) + Ok(encode_to_hex(&res)) } pub fn getblock( @@ -23,7 +23,7 @@ pub fn getblock( }; let res = client.get_block(&blockhash)?; - let encoded = encode_to_hex(res); + let encoded = encode_to_hex(&res); match verbosity { None | Some(1) => Ok(encoded), @@ -38,7 +38,7 @@ pub fn getblockcount(client: &Client) -> Result { pub fn getblockhash(client: &Client, height: usize) -> Result { let block_hash = client.get_block_hash(height as u64)?; - Ok(encode_to_hex(block_hash)) + Ok(encode_to_hex(&block_hash)) } pub fn getblockheader( @@ -51,7 +51,7 @@ pub fn getblockheader( match verbose { None | Some(true) => Ok(serde_json::to_string(&header).unwrap()), - Some(false) => Ok(encode_to_hex(header)), + Some(false) => Ok(encode_to_hex(&header)), } } diff --git a/src/rpc/adapter/mod.rs b/src/rpc/adapter/mod.rs index d937331..0956c8b 100644 --- a/src/rpc/adapter/mod.rs +++ b/src/rpc/adapter/mod.rs @@ -3,8 +3,6 @@ //! This crate provides an adapter interface that aims to mimic real Bitcoin //! RPC interface. -use bitcoin::consensus::encode::{deserialize_hex, serialize_hex}; - mod blockchain; mod generating; mod rawtransactions; @@ -14,45 +12,3 @@ pub use blockchain::*; pub use generating::*; pub use rawtransactions::*; pub use wallet::*; - -/// Encodes given Rust struct to hex string. -fn encode_to_hex(strct: T) -> String -where - T: bitcoin::consensus::Encodable, -{ - serialize_hex::(&strct) -} - -/// Decodes given hex string to a Rust struct. -fn decode_from_hex(hex: String) -> Result -where - T: bitcoin::consensus::Decodable, -{ - Ok(deserialize_hex::(&hex)?) -} - -fn encode_decode_to_rpc_error(error: bitcoin::consensus::encode::Error) -> bitcoincore_rpc::Error { - bitcoincore_rpc::Error::BitcoinSerialization(bitcoin::consensus::encode::FromHexError::Decode( - bitcoin::consensus::DecodeError::Consensus(error), - )) -} - -#[cfg(test)] -mod tests { - use super::{decode_from_hex, encode_to_hex}; - use bitcoin::{hashes::sha256d::Hash, Txid}; - use std::str::FromStr; - - #[test] - fn encode_decode_txid() { - let txid = Txid::from_raw_hash( - Hash::from_str("e6d467860551868fe599889ea9e622ae1ff08891049e934f83a783a3ea5fbc12") - .unwrap(), - ); - - let encoded_txid = encode_to_hex(txid); - let decoded_txid = decode_from_hex::(encoded_txid).unwrap(); - - assert_eq!(txid, decoded_txid); - } -} diff --git a/src/rpc/adapter/rawtransactions.rs b/src/rpc/adapter/rawtransactions.rs index 0cc729e..a9b0d2e 100644 --- a/src/rpc/adapter/rawtransactions.rs +++ b/src/rpc/adapter/rawtransactions.rs @@ -1,6 +1,6 @@ //! # Rawtransactions RPCs -use super::{decode_from_hex, encode_to_hex}; +use crate::utils::{decode_from_hex, encode_to_hex}; use crate::Client; use bitcoin::{BlockHash, Transaction, Txid}; use bitcoincore_rpc::{Error, RpcApi}; @@ -17,7 +17,7 @@ pub fn getrawtransaction( None | Some(false) => { let tx = client.get_raw_transaction(&txid, blockhash.as_ref())?; - encode_to_hex(tx) + encode_to_hex(&tx) } Some(true) => { let tx = client.get_raw_transaction_info(&txid, blockhash.as_ref())?; @@ -37,15 +37,33 @@ pub fn sendrawtransaction( let tx = decode_from_hex::(hexstring)?; let txid = client.send_raw_transaction(&tx)?; - let txid = encode_to_hex(txid); + let txid = encode_to_hex(&txid); Ok(txid) } +pub fn fundrawtransaction( + _client: &Client, + _hexstring: String, + _options: Option, + _iswitness: Option, +) -> Result { + todo!() +} + +pub fn signrawtransactionwithwallet( + _client: &Client, + _hexstring: String, + _prevtxs: Option, + _sighashtype: Option, +) -> Result { + todo!() +} + #[cfg(test)] mod tests { use crate::{ - rpc::adapter::{decode_from_hex, encode_to_hex}, + utils::{decode_from_hex, encode_to_hex}, Client, RpcApiWrapper, }; use bitcoin::{ @@ -74,7 +92,7 @@ mod tests { let tx = client.get_raw_transaction(&txid, None).unwrap(); let encoded_tx = - super::getrawtransaction(&client, encode_to_hex(txid), None, None).unwrap(); + super::getrawtransaction(&client, encode_to_hex(&txid), None, None).unwrap(); let encoded_tx = decode_from_hex(encoded_tx).unwrap(); assert_eq!(tx, encoded_tx); @@ -114,7 +132,7 @@ mod tests { lock_time: LockTime::ZERO, }; - let txid = super::sendrawtransaction(&client, encode_to_hex(tx.clone()), None).unwrap(); + let txid = super::sendrawtransaction(&client, encode_to_hex(&tx.clone()), None).unwrap(); let txid = decode_from_hex::(txid).unwrap(); let read_tx = client.get_raw_transaction(&txid, None).unwrap(); diff --git a/src/rpc/adapter/wallet.rs b/src/rpc/adapter/wallet.rs index 0a955e7..fd02f59 100644 --- a/src/rpc/adapter/wallet.rs +++ b/src/rpc/adapter/wallet.rs @@ -1,6 +1,6 @@ //! # Wallet RPCs -use super::{decode_from_hex, encode_to_hex}; +use crate::utils::{decode_from_hex, encode_to_hex}; use crate::Client; use bitcoin::{Address, Amount, Txid}; use bitcoincore_rpc::{json, Error, RpcApi}; @@ -81,7 +81,7 @@ pub fn sendtoaddress( None, )?; - Ok(encode_to_hex::(txid)) + Ok(encode_to_hex::(&txid)) } #[cfg(test)] diff --git a/src/rpc/traits.rs b/src/rpc/traits.rs index 50c4f60..6adfc35 100644 --- a/src/rpc/traits.rs +++ b/src/rpc/traits.rs @@ -94,6 +94,22 @@ pub trait Rpc { estimate_mode: Option<&str>, avoid_reuse: Option, ) -> Result; + + #[method(name = "fundrawtransaction")] + async fn fundrawtransaction( + &self, + hexstring: String, + options: Option, + iswitness: Option, + ) -> Result; + + #[method(name = "signrawtransactionwithwallet")] + async fn signrawtransactionwithwallet( + &self, + hexstring: String, + prevtxs: Option, + sighashtype: Option, + ) -> Result; } #[async_trait] @@ -208,6 +224,31 @@ impl RpcServer for Client { avoid_reuse, )) } + + async fn fundrawtransaction( + &self, + hexstring: String, + options: Option, + iswitness: Option, + ) -> Result { + to_jsonrpsee_error(adapter::fundrawtransaction( + self, hexstring, options, iswitness, + )) + } + + async fn signrawtransactionwithwallet( + &self, + hexstring: String, + prevtxs: Option, + sighashtype: Option, + ) -> Result { + to_jsonrpsee_error(adapter::signrawtransactionwithwallet( + self, + hexstring, + prevtxs, + sighashtype, + )) + } } /// Helper for converting ledger error to [`jsonrpsee`] error. diff --git a/src/utils.rs b/src/utils.rs index 708f579..bdc6e29 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,7 +4,10 @@ use crate::ledger::errors::LedgerError; use bitcoin::{ - consensus::Encodable, + consensus::{ + encode::{deserialize_hex, serialize_hex}, + Encodable, + }, hashes::{sha256, Hash}, TxMerkleNode, }; @@ -116,6 +119,30 @@ pub fn hex_to_array(hex: &str, output: &mut [u8]) { }); } +/// Encodes given Rust struct to hex string. +pub fn encode_to_hex(strct: &T) -> String +where + T: bitcoin::consensus::Encodable, +{ + serialize_hex::(strct) +} + +/// Decodes given hex string to a Rust struct. +pub fn decode_from_hex(hex: String) -> Result +where + T: bitcoin::consensus::Decodable, +{ + Ok(deserialize_hex::(&hex)?) +} + +pub fn encode_decode_to_rpc_error( + error: bitcoin::consensus::encode::Error, +) -> bitcoincore_rpc::Error { + bitcoincore_rpc::Error::BitcoinSerialization(bitcoin::consensus::encode::FromHexError::Decode( + bitcoin::consensus::DecodeError::Consensus(error), + )) +} + /// Initializes `tracing` as the logger. /// /// # Returns @@ -145,6 +172,7 @@ pub fn initialize_logger() -> Result<(), tracing_subscriber::util::TryInitError> #[cfg(test)] mod tests { + use super::{decode_from_hex, encode_to_hex}; use bitcoin::{hashes::sha256d::Hash, TxMerkleNode, Txid}; use std::str::FromStr; @@ -217,4 +245,17 @@ mod tests { merkle_root ); } + + #[test] + fn encode_decode_txid() { + let txid = Txid::from_raw_hash( + Hash::from_str("e6d467860551868fe599889ea9e622ae1ff08891049e934f83a783a3ea5fbc12") + .unwrap(), + ); + + let encoded_txid = encode_to_hex(&txid); + let decoded_txid = decode_from_hex::(encoded_txid).unwrap(); + + assert_eq!(txid, decoded_txid); + } } diff --git a/tests/raw_transactions.rs b/tests/raw_transactions.rs index dae2c38..1369e7c 100644 --- a/tests/raw_transactions.rs +++ b/tests/raw_transactions.rs @@ -1,6 +1,8 @@ //! Integration tests for `raw_transaction` calls. -use bitcoin::{hashes::Hash, Amount, OutPoint, TxIn, TxOut, Txid}; +use bitcoin::{ + consensus::Decodable, hashes::Hash, Amount, OutPoint, Transaction, TxIn, TxOut, Txid, +}; use bitcoin_mock_rpc::{Client, RpcApiWrapper}; use bitcoincore_rpc::{Auth, RpcApi}; use common::send_raw_transaction_async; @@ -259,3 +261,38 @@ fn send_raw_transaction_insufficient_funds() { Amount::from_sat(0x45 * 0x45 * 0x1F) ); } + +#[test] +fn fund_sign_raw_transaction_with_wallet() { + let rpc = Client::new("fund_sign_raw_transaction_with_wallet", Auth::None).unwrap(); + + let address = rpc.get_new_address(None, None).unwrap().assume_checked(); + + let txout = TxOut { + value: Amount::from_sat(0x45), + script_pubkey: address.script_pubkey(), + }; + let tx = common::create_transaction(vec![], vec![txout]); + + // Lower input funds should be a problem. + if rpc.send_raw_transaction(&tx).is_ok() { + assert!(false); + }; + + let res = rpc.fund_raw_transaction(&tx, None, None).unwrap(); + let tx = String::consensus_decode(&mut res.hex.as_slice()).unwrap(); + let tx = bitcoin::consensus::encode::deserialize_hex::(&tx).unwrap(); + + // Not signed inputs should be a problem. + if rpc.send_raw_transaction(&tx).is_ok() { + assert!(false); + }; + + let res = rpc + .sign_raw_transaction_with_wallet(&tx, None, None) + .unwrap(); + let tx = String::consensus_decode(&mut res.hex.as_slice()).unwrap(); + let tx = bitcoin::consensus::encode::deserialize_hex::(&tx).unwrap(); + + rpc.send_raw_transaction(&tx).unwrap(); +}