Skip to content

Commit eb775ca

Browse files
committed
Merge #41: Migrate to corepc for bitcoind integration tests
f8da0a4 Break out contributing docs (Nick Johnson) 8f88b0b Migrate to corepc for bitcoind integration tests (Nick Johnson) Pull request description: Added a new CI job to run the integration tests, based it on rust-miniscript. Closes #29 ACKs for top commit: tcharding: ACK f8da0a4 Tree-SHA512: 7c61c372edd76ece8007cee3cce2543321ca12ab1cb64c3bcbe766084042527da0970c3a9d8c4e18f4220cdfcb6b404196f2b6572c9b8f515a244177ecc770f9
2 parents 7c900c9 + f8da0a4 commit eb775ca

File tree

7 files changed

+111
-85
lines changed

7 files changed

+111
-85
lines changed

.github/workflows/rust.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,33 @@ jobs:
151151
run: rustup component add rustfmt
152152
- name: "Check formatting"
153153
run: cargo fmt --all -- --check
154+
155+
Integration: # 1 job for each bitcoind version we support.
156+
name: Integration tests against bitcoind
157+
runs-on: ubuntu-latest
158+
strategy:
159+
fail-fast: false
160+
matrix:
161+
feature:
162+
[
163+
"29_0",
164+
"28_2",
165+
"27_2",
166+
"26_2",
167+
"25_2",
168+
"24_2",
169+
"23_2",
170+
"22_1",
171+
"0_21_2",
172+
"0_20_2",
173+
"0_19_1",
174+
"0_18_1",
175+
"0_17_2",
176+
]
177+
steps:
178+
- name: "Checkout repo"
179+
uses: actions/checkout@v4
180+
- name: "Select toolchain"
181+
uses: dtolnay/rust-toolchain@stable
182+
- name: "Run integration tests"
183+
run: cd bitcoind-tests && cargo test --features=${{ matrix.feature }}

CONTRIBUTING.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Contributing
2+
3+
We generally follow the contribution guidelines of [rust-bitcoin](https://github.com/rust-bitcoin/rust-bitcoin/blob/master/CONTRIBUTING.md).
4+
5+
## Development Workflow
6+
7+
We use [`just`](https://just.systems/man/en/) for running development workflow commands. Run `just` from your shell to see the list of available commands.
8+
9+
### Git Hooks
10+
11+
To catch errors before running CI, we provide git hooks. To use them:
12+
13+
```bash
14+
git config --local core.hooksPath githooks/
15+
```
16+
17+
Alternatively, add symlinks in your `.git/hooks` directory to any of the githooks we provide.
18+
19+
## Integration Tests with Bitcoin Core
20+
21+
The `bitcoind-tests/` package contains integration tests that run against real Bitcoin Core instances. A separate package is used so that bitcoind version flags don't pollute the rust-psbt crate. The package is not a member of the workspace so that it doesn't effect dependency version resolution.
22+
23+
### NixOS Users
24+
25+
The auto-downloaded Bitcoin Core binaries don't work on NixOS due to dynamic linking requirements. If you're on NixOS you could manually configure the `BITCOIND_EXE` environment variable to use a Nix-provided `bitcoind` of the correct version.

README.md

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,16 @@
33
Implementation of the Partially Signed Bitcoin Transaction Format as defined in [BIP-174] and
44
PSBT version 2 as defined in [BIP-370].
55

6-
## Contributing
7-
8-
For now we more or less just follow the contribution guidelines of
9-
[rust-bitcoin](https://github.com/rust-bitcoin/rust-bitcoin/CONTRIBUTING.md).
10-
11-
### Minimum Supported Rust Version (MSRV)
6+
## Minimum Supported Rust Version (MSRV)
127

138
This library should always compile with any combination of features on **Rust 1.74.0**.
149

1510
To build with the MSRV you will likely need to pin a bunch of dependencies, see `./contrib/test.sh`
1611
for the current list.
1712

18-
### Just
19-
20-
We support [`just`](https://just.systems/man/en/) for running dev workflow commands. Run `just` from
21-
your shell to see list available sub-commands.
22-
23-
### Building the docs
24-
25-
We build docs with the nightly toolchain, you may wish to use the following shell alias to check
26-
your documentation changes build correctly.
27-
28-
```
29-
alias build-docs='RUSTDOCFLAGS="--cfg docsrs" cargo +nightly rustdoc --features="$FEATURES" -- -D rustdoc::broken-intra-doc-links'
30-
```
31-
32-
### Githooks
33-
34-
To assist devs in catching errors _before_ running CI we provide some githooks. If you do not
35-
already have locally configured githooks you can use the ones in this repository by running, in the
36-
root directory of the repository:
37-
```
38-
git config --local core.hooksPath githooks/
39-
```
40-
41-
Alternatively add symlinks in your `.git/hooks` directory to any of the githooks we provide.
42-
43-
### rustfmt
13+
## Contributing
4414

45-
We format with `cargo +nightly fmt`, see `./rusntfmt.toml` for the current configuration.
15+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
4616

4717
## License
4818

bitcoind-tests/Cargo.toml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,32 @@ version = "0.1.0"
44
authors = ["Tobin C. Harding <me@tobin.cc>"]
55
license = "CC0-1.0"
66
edition = "2021"
7+
publish = false
78

89
[dependencies]
910
anyhow = "1"
10-
bitcoin = { version = "0.31.0", features = ["rand-std"] }
11-
bitcoind = { version = "0.34.1", features = ["25_1"] }
12-
secp256k1 = { version = "0.28.2", features = ["global-context", "rand-std"] }
11+
bitcoin = { version = "0.32.6", features = ["rand-std"] }
12+
bitcoind = { package = "corepc-node", version = "0.10.1", features = ["download"] }
13+
secp256k1 = { version = "0.29.0", features = ["global-context", "rand-std"] }
1314
psbt-v2 = { path = "..", features = ["std", "miniscript"] }
1415

1516
[dev-dependencies]
17+
18+
[features]
19+
# Bitcoin Core version features for integration testing.
20+
# corepc-node provides one feature per major version (latest patch release).
21+
# Bitcoin Core does not support PSBT v2 (BIP-370) yet.
22+
"29_0" = ["bitcoind/29_0"]
23+
"28_2" = ["bitcoind/28_2"]
24+
"27_2" = ["bitcoind/27_2"]
25+
"26_2" = ["bitcoind/26_2"]
26+
"25_2" = ["bitcoind/25_2"]
27+
"24_2" = ["bitcoind/24_2"]
28+
"23_2" = ["bitcoind/23_2"]
29+
"22_1" = ["bitcoind/22_1"]
30+
"0_21_2" = ["bitcoind/0_21_2"]
31+
"0_20_2" = ["bitcoind/0_20_2"]
32+
"0_19_1" = ["bitcoind/0_19_1"]
33+
"0_18_1" = ["bitcoind/0_18_1"]
34+
# Starting from 0.17.0 because that's when PSBT v0 (BIP-174) was first introduced.
35+
"0_17_2" = ["bitcoind/0_17_2"]

bitcoind-tests/src/client.rs

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,15 @@
66
77
// We depend upon and import directly from bitcoin because this module is not concerned with PSBT
88
// i.e., it is lower down the stack than the psbt_v2 crate.
9-
use bitcoin::{consensus, Address, Amount, Network, Transaction, Txid};
10-
use bitcoind::bitcoincore_rpc::bitcoincore_rpc_json::{AddressType, GetBlockchainInfoResult};
11-
use bitcoind::bitcoincore_rpc::RpcApi;
12-
use bitcoind::BitcoinD;
9+
use bitcoin::{Address, Amount, Transaction, Txid};
10+
use bitcoind::{AddressType, Node, vtype::GetBlockchainInfo};
1311

14-
const NETWORK: Network = Network::Regtest;
1512
const FIFTY_BTC: Amount = Amount::from_int_btc(50);
1613

1714
/// A custom bitcoind client.
1815
pub struct Client {
1916
/// Handle for the regtest `bitcoind` instance.
20-
bitcoind: BitcoinD,
17+
bitcoind: Node,
2118
/// This is public so we don't have to handle the complexity of know if send/receives are
2219
/// to/from the Core controlled wallet or somewhere else. User is required to manage this.
2320
pub balance: BalanceTracker,
@@ -26,7 +23,8 @@ pub struct Client {
2623
impl Client {
2724
/// Creates a new [`Client`].
2825
pub fn new() -> anyhow::Result<Self> {
29-
let bitcoind = BitcoinD::from_downloaded()?;
26+
let exe_path = bitcoind::exe_path()?;
27+
let bitcoind = Node::new(exe_path)?;
3028
let balance = BalanceTracker::zero();
3129

3230
let client = Client { bitcoind, balance };
@@ -60,29 +58,28 @@ impl Client {
6058
}
6159

6260
/// Calls through to bitcoincore_rpc client.
63-
pub fn get_blockchain_info(&self) -> anyhow::Result<GetBlockchainInfoResult> {
61+
pub fn get_blockchain_info(&self) -> anyhow::Result<GetBlockchainInfo> {
6462
let client = &self.bitcoind.client;
6563
Ok(client.get_blockchain_info()?)
6664
}
6765

6866
/// Gets an address controlled by the currently loaded Bitcoin Core wallet (via `bitcoind`).
6967
pub fn core_wallet_controlled_address(&self) -> anyhow::Result<Address> {
7068
let client = &self.bitcoind.client;
71-
let label = None;
72-
let address_type = Some(AddressType::Bech32m);
73-
let address = client.get_new_address(label, address_type)?.require_network(NETWORK)?;
69+
// Use Bech32 (segwit v0) for compatibility with all Bitcoin Core versions.
70+
// Bech32m (taproot) is only available from Bitcoin Core 22.0+.
71+
let address = client.new_address_with_type(AddressType::Bech32)?;
7472
Ok(address)
7573
}
7674

7775
pub fn balance(&self) -> anyhow::Result<Amount> {
7876
let client = &self.bitcoind.client;
79-
let minconf = None; // What is this?
80-
let include_watchonly = None;
81-
Ok(client.get_balance(minconf, include_watchonly)?)
77+
let balance = client.get_balance()?.balance()?;
78+
Ok(balance)
8279
}
8380

8481
/// Mines `n` blocks to a new address controlled by the currently loaded Bitcoin Core wallet.
85-
fn mine_blocks(&self, n: u64) -> anyhow::Result<()> {
82+
fn mine_blocks(&self, n: usize) -> anyhow::Result<()> {
8683
let client = &self.bitcoind.client;
8784
// Generate to an address controlled by the bitcoind wallet and wait for funds to mature.
8885
let address = self.core_wallet_controlled_address()?;
@@ -91,45 +88,25 @@ impl Client {
9188
Ok(())
9289
}
9390

94-
/// Send `amount` to `address` setting all other `bitcoincore_prc::send_to_address` args to `None`.
91+
/// Send `amount` to `address`.
9592
///
9693
/// Caller required to update balance (ie, call self.balance.send()).
9794
pub fn send(&self, amount: Amount, address: &Address) -> anyhow::Result<Txid> {
9895
let client = &self.bitcoind.client;
99-
100-
let comment = None;
101-
let comment_to = None;
102-
let subtract_fee = None;
103-
let replacable = None;
104-
let confirmation_target = None;
105-
let estimate_mode = None;
106-
107-
let txid = client.send_to_address(
108-
address,
109-
amount,
110-
comment,
111-
comment_to,
112-
subtract_fee,
113-
replacable,
114-
confirmation_target,
115-
estimate_mode,
116-
)?;
117-
96+
let txid = client.send_to_address(address, amount)?.txid()?;
11897
Ok(txid)
11998
}
12099

121100
pub fn get_transaction(&self, txid: &Txid) -> anyhow::Result<Transaction> {
122101
let client = &self.bitcoind.client;
123-
let include_watchonly = None;
124-
let res = client.get_transaction(txid, include_watchonly)?;
125-
let tx: Transaction = consensus::encode::deserialize(&res.hex)?;
102+
let res = client.get_transaction(*txid)?;
103+
let tx = res.into_model()?.tx;
126104
Ok(tx)
127105
}
128106

129107
pub fn send_raw_transaction(&self, tx: &Transaction) -> anyhow::Result<Txid> {
130108
let client = &self.bitcoind.client;
131-
let hex = consensus::encode::serialize_hex(&tx);
132-
let txid = client.send_raw_transaction(hex)?;
109+
let txid = client.send_raw_transaction(tx)?.txid()?;
133110
Ok(txid)
134111
}
135112
}

bitcoind-tests/tests/basic.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use core::str::FromStr;
44

55
// Only depend on `psbt` (and `bitcoind_tests`) because we are explicitly testing the `psbt_v2` crate.
66
use bitcoind_tests::client::Client;
7-
use psbt::bitcoin::{Address, Amount, Network, OutPoint, PublicKey, Script, Transaction, TxOut};
7+
use psbt::bitcoin::{Address, Amount, CompressedPublicKey, Network, OutPoint, Script, Transaction, TxOut};
88
use psbt::v2::{Constructor, InputBuilder, Modifiable, OutputBuilder};
99
// The `psbt_v2` crate, as we expect downstream to use it
1010
// E.g., in manifest file `use psbt = { package = "psbt_v2" ... }`
@@ -74,7 +74,7 @@ impl TransactionExt for Transaction {
7474
let mut utxos = vec![];
7575
for (index, utxo) in self.output.iter().enumerate() {
7676
if &utxo.script_pubkey == script_pubkey {
77-
let out_point = OutPoint { txid: self.txid(), vout: index as u32 };
77+
let out_point = OutPoint { txid: self.compute_txid(), vout: index as u32 };
7878

7979
utxos.push((out_point, utxo));
8080
}
@@ -85,24 +85,24 @@ impl TransactionExt for Transaction {
8585

8686
/// A super basic entity with a single public key.
8787
pub struct Alice {
88-
/// The single public key.
89-
public_key: PublicKey,
88+
/// The single compressed public key.
89+
public_key: CompressedPublicKey,
9090
}
9191

9292
impl Alice {
9393
/// Creates a new Alice.
9494
pub fn new() -> Self {
9595
// An arbitrary public key, assume the secret key is held by another entity.
96-
let public_key = PublicKey::from_str(
96+
let public_key = CompressedPublicKey::from_str(
9797
"032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
9898
)
9999
.unwrap();
100100

101101
Alice { public_key }
102102
}
103103

104-
/// Returns a bech32m address from a key Alice controls.
104+
/// Returns a bech32 address from a key Alice controls.
105105
pub fn address(&self) -> Address {
106-
Address::p2wpkh(&self.public_key, NETWORK).expect("uncompressed key")
106+
Address::p2wpkh(&self.public_key, NETWORK)
107107
}
108108
}

justfile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ check:
1616
build:
1717
cargo build --all --all-targets --all-features
1818

19-
# Cargo test everything.
19+
# Test everything.
2020
test:
2121
cargo test --all-targets --all-features
22-
cd bitcoind-tests; cargo test
22+
just test-bitcoind
23+
24+
# Test bitcoind integration with a bitcoind version.
25+
test-bitcoind version="29_0":
26+
cd {{justfile_directory()}}/bitcoind-tests && cargo test --features={{version}}
2327

2428
# Lint everything.
2529
lint:

0 commit comments

Comments
 (0)