From 198ba41fdf3c4740c7998335b72832d58b88f24a Mon Sep 17 00:00:00 2001 From: Jose Storopoli Date: Wed, 22 Jan 2025 11:49:55 -0300 Subject: [PATCH] test(btcio): submit_package 1P1C --- crates/btcio/src/rpc/client.rs | 120 ++++++++++++++++++++++++++++-- crates/btcio/src/rpc/traits.rs | 5 +- crates/btcio/src/test_utils.rs | 6 +- crates/btcio/src/writer/signer.rs | 2 +- 4 files changed, 121 insertions(+), 12 deletions(-) diff --git a/crates/btcio/src/rpc/client.rs b/crates/btcio/src/rpc/client.rs index c1544030d..38e26fbb4 100644 --- a/crates/btcio/src/rpc/client.rs +++ b/crates/btcio/src/rpc/client.rs @@ -30,8 +30,8 @@ use crate::rpc::{ types::{ CreateRawTransaction, CreateWallet, GetBlockVerbosityZero, GetBlockchainInfo, GetNewAddress, GetTransaction, GetTxOut, ImportDescriptor, ImportDescriptorResult, - ListDescriptors, ListTransactions, ListUnspent, SignRawTransactionWithWallet, - SubmitPackage, TestMempoolAccept, + ListDescriptors, ListTransactions, ListUnspent, PreviousTransactionOutput, + SignRawTransactionWithWallet, SubmitPackage, TestMempoolAccept, }, }; @@ -371,12 +371,14 @@ impl SignerRpc for BitcoinClient { async fn sign_raw_transaction_with_wallet( &self, tx: &Transaction, + prev_outputs: Option>, ) -> ClientResult { let tx_hex = serialize_hex(tx); trace!(tx_hex = %tx_hex, "Signing transaction"); + trace!(?prev_outputs, "Signing transaction with previous outputs"); self.call::( "signrawtransactionwithwallet", - &[to_value(tx_hex)?], + &[to_value(tx_hex)?, to_value(prev_outputs)?], ) .await } @@ -445,7 +447,7 @@ impl SignerRpc for BitcoinClient { #[cfg(test)] mod test { - use bitcoin::{consensus, hashes::Hash, Amount, NetworkKind}; + use bitcoin::{consensus, hashes::Hash, transaction, Amount, NetworkKind}; use strata_common::logging; use super::*; @@ -535,7 +537,10 @@ mod test { assert_eq!(expected, got); // sign_raw_transaction_with_wallet - let got = client.sign_raw_transaction_with_wallet(&tx).await.unwrap(); + let got = client + .sign_raw_transaction_with_wallet(&tx, None) + .await + .unwrap(); assert!(got.complete); assert!(consensus::encode::deserialize_hex::(&got.hex).is_ok()); @@ -687,7 +692,7 @@ mod test { let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap(); let signed_parent: Transaction = consensus::encode::deserialize_hex( client - .sign_raw_transaction_with_wallet(&parent) + .sign_raw_transaction_with_wallet(&parent, None) .await .unwrap() .hex @@ -714,7 +719,7 @@ mod test { let child = client.create_raw_transaction(child_raw_tx).await.unwrap(); let signed_child: Transaction = consensus::encode::deserialize_hex( client - .sign_raw_transaction_with_wallet(&child) + .sign_raw_transaction_with_wallet(&child, None) .await .unwrap() .hex @@ -730,4 +735,105 @@ mod test { assert_eq!(result.tx_results.len(), 2); assert_eq!(result.package_msg, "success"); } + + /// Similar to [`submit_package`], but with where the parent does not pay fees, + /// and the child has to pay fees. + /// + /// This is called 1P1C because it has one parent and one child. + /// See + /// for more information. + #[tokio::test] + async fn submit_package_1p1c() { + logging::init(logging::LoggerConfig::with_base_name( + "btcio-submitpackage-1p1c", + )); + + let (bitcoind, client) = get_bitcoind_and_client(); + + // 1p1c sanity check + let server_version = bitcoind.client.server_version().unwrap(); + assert!(server_version > 28); + + let destination = client.get_new_address().await.unwrap(); + + let blocks = mine_blocks(&bitcoind, 101, None).unwrap(); + let last_block = client.get_block(blocks.first().unwrap()).await.unwrap(); + let coinbase_tx = last_block.coinbase().unwrap(); + + let parent_raw_tx = CreateRawTransaction { + inputs: vec![CreateRawTransactionInput { + txid: coinbase_tx.compute_txid().to_string(), + vout: 0, + }], + outputs: vec![CreateRawTransactionOutput::AddressAmount { + address: destination.to_string(), + amount: COINBASE_AMOUNT.to_btc(), + }], + }; + let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap(); + parent.version = transaction::Version(3); + assert_eq!(parent.version, transaction::Version(3)); + trace!(?parent, "parent:"); + let signed_parent: Transaction = consensus::encode::deserialize_hex( + client + .sign_raw_transaction_with_wallet(&parent, None) + .await + .unwrap() + .hex + .as_str(), + ) + .unwrap(); + assert_eq!(signed_parent.version, transaction::Version(3)); + + // Assert that the parent tx cannot be broadcasted. + let parent_broadcasted = client.send_raw_transaction(&signed_parent).await; + assert!(parent_broadcasted.is_err()); + + // 5k sats as fees. + let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000); + let child_raw_tx = CreateRawTransaction { + inputs: vec![CreateRawTransactionInput { + txid: signed_parent.compute_txid().to_string(), + vout: 0, + }], + outputs: vec![CreateRawTransactionOutput::AddressAmount { + address: destination.to_string(), + amount: amount_minus_fees.to_btc(), + }], + }; + let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap(); + child.version = transaction::Version(3); + assert_eq!(child.version, transaction::Version(3)); + trace!(?child, "child:"); + let prev_outputs = vec![PreviousTransactionOutput { + txid: parent.compute_txid(), + vout: 0, + script_pubkey: parent.output[0].script_pubkey.to_hex_string(), + redeem_script: None, + witness_script: None, + amount: Some(COINBASE_AMOUNT.to_btc()), + }]; + let signed_child: Transaction = consensus::encode::deserialize_hex( + client + .sign_raw_transaction_with_wallet(&child, Some(prev_outputs)) + .await + .unwrap() + .hex + .as_str(), + ) + .unwrap(); + assert_eq!(signed_child.version, transaction::Version(3)); + + // Assert that the child tx cannot be broadcasted. + let child_broadcasted = client.send_raw_transaction(&signed_child).await; + assert!(child_broadcasted.is_err()); + + // Let's send as a package 1C1P. + let result = client + .submit_package(&[signed_parent, signed_child]) + .await + .unwrap(); + assert_eq!(result.tx_results.len(), 2); + assert_eq!(result.package_msg, "success"); + } } diff --git a/crates/btcio/src/rpc/traits.rs b/crates/btcio/src/rpc/traits.rs index 37058bad7..6938e4895 100644 --- a/crates/btcio/src/rpc/traits.rs +++ b/crates/btcio/src/rpc/traits.rs @@ -5,8 +5,8 @@ use crate::rpc::{ client::ClientResult, types::{ CreateRawTransaction, GetBlockchainInfo, GetTransaction, GetTxOut, ImportDescriptor, - ImportDescriptorResult, ListTransactions, ListUnspent, SignRawTransactionWithWallet, - SubmitPackage, TestMempoolAccept, + ImportDescriptorResult, ListTransactions, ListUnspent, PreviousTransactionOutput, + SignRawTransactionWithWallet, SubmitPackage, TestMempoolAccept, }, }; @@ -190,6 +190,7 @@ pub trait SignerRpc { async fn sign_raw_transaction_with_wallet( &self, tx: &Transaction, + prev_outputs: Option>, ) -> ClientResult; /// Gets the underlying [`Xpriv`] from the wallet. diff --git a/crates/btcio/src/test_utils.rs b/crates/btcio/src/test_utils.rs index 23fcd2dad..b14cd33a3 100644 --- a/crates/btcio/src/test_utils.rs +++ b/crates/btcio/src/test_utils.rs @@ -16,8 +16,9 @@ use crate::{ traits::{BroadcasterRpc, ReaderRpc, SignerRpc, WalletRpc}, types::{ CreateRawTransaction, GetBlockchainInfo, GetTransaction, GetTxOut, ImportDescriptor, - ImportDescriptorResult, ListTransactions, ListUnspent, ScriptPubkey, - SignRawTransactionWithWallet, SubmitPackage, SubmitPackageTxResult, TestMempoolAccept, + ImportDescriptorResult, ListTransactions, ListUnspent, PreviousTransactionOutput, + ScriptPubkey, SignRawTransactionWithWallet, SubmitPackage, SubmitPackageTxResult, + TestMempoolAccept, }, ClientResult, }, @@ -254,6 +255,7 @@ impl SignerRpc for TestBitcoinClient { async fn sign_raw_transaction_with_wallet( &self, tx: &Transaction, + _prev_outputs: Option>, ) -> ClientResult { let tx_hex = consensus::encode::serialize_hex(tx); Ok(SignRawTransactionWithWallet { diff --git a/crates/btcio/src/writer/signer.rs b/crates/btcio/src/writer/signer.rs index e79d157cd..f8e9bec7b 100644 --- a/crates/btcio/src/writer/signer.rs +++ b/crates/btcio/src/writer/signer.rs @@ -31,7 +31,7 @@ pub async fn create_and_sign_payload_envelopes( debug!(commit_txid = ?ctxid, "Signing commit transaction"); let signed_commit = ctx .client - .sign_raw_transaction_with_wallet(&commit) + .sign_raw_transaction_with_wallet(&commit, None) .await .map_err(|e| EnvelopeError::SignRawTransaction(e.to_string()))? .hex;