Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: alpenlabs/strata
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 64ec34a0b5308f854741fc5b2c1375b3a00e6f31
Choose a base ref
..
head repository: alpenlabs/strata
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: ff23ed66a0aab64ebdbe9a91fbc28c47d6849245
Choose a head ref
Showing with 152 additions and 18 deletions.
  1. +152 −18 crates/btcio/src/rpc/client.rs
170 changes: 152 additions & 18 deletions crates/btcio/src/rpc/client.rs
Original file line number Diff line number Diff line change
@@ -445,7 +445,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::*;
@@ -454,6 +454,9 @@ mod test {
test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
};

/// 50 BTC in [`Network::Regtest`].
const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);

#[tokio::test()]
async fn client_works() {
logging::init(logging::LoggerConfig::with_base_name("btcio-tests"));
@@ -552,13 +555,6 @@ mod test {
let got = client.send_raw_transaction(&tx).await.unwrap();
assert!(got.as_byte_array().len() == 32);

// get_tx_out
let got = client
.get_tx_out(&tx.compute_txid(), 0, true)
.await
.unwrap();
assert_eq!(got.value, 1.0);

// list_transactions
let got = client.list_transactions(None).await.unwrap();
assert_eq!(got.len(), 10);
@@ -569,13 +565,6 @@ mod test {
let got = client.get_utxos().await.unwrap();
assert_eq!(got.len(), 3);

// get_tx_out not in the mempool anymore
let got = client
.get_tx_out(&tx.compute_txid(), 0, false)
.await
.unwrap();
assert_eq!(got.value, 1.0);

// listdescriptors
let got = client.get_xpriv().await.unwrap().unwrap().network;
let expected = NetworkKind::Test;
@@ -598,6 +587,56 @@ mod test {
assert_eq!(expected, got);
}

async fn get_tx_out() {
logging::init(logging::LoggerConfig::with_base_name("btcio-gettxout"));

let (bitcoind, client) = get_bitcoind_and_client();

// network sanity check
let got = client.network().await.unwrap();
let expected = Network::Regtest;
assert_eq!(expected, got);

let address = bitcoind
.client
.get_new_address()
.unwrap()
.address()
.unwrap()
.assume_checked();
let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
let coinbase_tx = last_block.coinbase().unwrap();

// gettxout should work with a non-spent UTXO.
let got = client
.get_tx_out(&coinbase_tx.compute_txid(), 0, true)
.await
.unwrap();
assert_eq!(got.value, COINBASE_AMOUNT.to_btc());

// gettxout should fail with a spent UTXO.
let new_address = bitcoind
.client
.get_new_address()
.unwrap()
.address()
.unwrap()
.assume_checked();
let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); // 2k sats as fees.
let _send_tx = bitcoind
.client
.send_to_address(&new_address, send_amount)
.unwrap()
.txid()
.unwrap();
let result = client
.get_tx_out(&coinbase_tx.compute_txid(), 0, true)
.await;
trace!(?result, "gettxout result");
assert!(result.is_err());
}

/// Create two transactions.
/// 1. Normal one: sends 1 BTC to an address that we control.
/// 2. CFFP: replaces the first transaction with a different one that we also control.
@@ -606,11 +645,11 @@ mod test {
/// that we don't control.
#[tokio::test()]
async fn submit_package() {
logging::init(logging::LoggerConfig::with_base_name("btcio-tests"));
logging::init(logging::LoggerConfig::with_base_name("btcio-submitpackage"));

let (bitcoind, client) = get_bitcoind_and_client();

// network
// network sanity check
let got = client.network().await.unwrap();
let expected = Network::Regtest;
assert_eq!(expected, got);
@@ -622,7 +661,8 @@ mod test {
let destination = client.get_new_address().await.unwrap();
let change_address = client.get_new_address().await.unwrap();
let amount = Amount::from_btc(1.0).unwrap();
let change_amount = Amount::from_btc(48.999).unwrap(); // 0.0001 fee
let fees = Amount::from_btc(0.0001).unwrap();
let change_amount = COINBASE_AMOUNT - amount - fees;
let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);

let send_back_address = client.get_new_address().await.unwrap();
@@ -690,4 +730,98 @@ 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 <https://bitcoinops.org/en/bitcoin-core-28-wallet-integration-guide/>
/// for more information.
#[tokio::test]
#[ignore = "Somehow this does not work..."]
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)
.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 signed_child: Transaction = consensus::encode::deserialize_hex(
client
.sign_raw_transaction_with_wallet(&child)
.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");
}
}