Skip to content

Commit cb1f244

Browse files
committed
Merge #309: Add integration tests
13c4746 Update CI to test on 22.0 instead of 0.21 (sanket1729) 51126d3 Add taproot tests (sanket1729) c25fc05 Add descriptor integration tests (sanket1729) ae3c98e Move only: Rework Integration Test and makes things more modular (sanket1729) Pull request description: This is the final PR testing of many of the new features in the taproot miniscript release. This defines a new template language for checking all the Psbt APIs (and thereby all the miniscript ones). ```rust // K : Compressed key available // K!: Compressed key with corresponding secret key unknown // X: X-only key available // X!: X-only key with corresponding secret key unknown // Test 1: Simple spend with internal key let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X)"); assert!(wit.len() == 1); ``` Right now, it does not test error cases, but I plan to update this framework so that we can also test those. Based on #308 and #310 ACKs for top commit: apoelstra: ACK 13c4746 Tree-SHA512: ce39c9b0242460e70f79c57843c69be36de045da6bcc686306a324c7fe58b70a5fe45e858c266a1f816f4e7f8aaf570fb42f64e543de86e2d786e1fa3fa0beb4
2 parents fb1b467 + 13c4746 commit cb1f244

File tree

8 files changed

+948
-362
lines changed

8 files changed

+948
-362
lines changed

.github/workflows/rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,5 @@ jobs:
9393
override: true
9494
- name: Running cargo
9595
env:
96-
BITCOINVERSION: 0.21.0
96+
BITCOINVERSION: '22.0'
9797
run: ./contrib/test.sh

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ fuzz/hfuzz_workspace
1717

1818
#Vscode project files
1919
.vscode
20+
21+
#Intergration test files
22+
integration_test/bitcoin-*

integration_test/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ miniscript = {path = "../"}
88

99
# Until 0.26 support is released on rust-bitcoincore-rpc
1010
bitcoincore-rpc = {git = "https://github.com/sanket1729/rust-bitcoincore-rpc",rev = "bcc35944b3dd636cdff9710f90f8e0cfcab28f27"}
11-
bitcoin = "0.28.0-rc.1"
11+
bitcoin = {ver = "0.28.0-rc.1", features = ["rand"]}
1212
log = "0.4"
13+
rand = "0.8.4"

integration_test/src/main.rs

Lines changed: 63 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,12 @@ extern crate log;
88
extern crate bitcoin;
99
extern crate miniscript;
1010

11-
use bitcoincore_rpc::{json, Auth, Client, RpcApi};
11+
use bitcoincore_rpc::{Auth, Client, RpcApi};
1212

13-
use bitcoin::secp256k1;
14-
use bitcoin::util::psbt;
15-
use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
16-
use bitcoin::{Amount, OutPoint, Transaction, TxIn, TxOut, Txid};
17-
mod read_file;
18-
use miniscript::miniscript::iter;
19-
use miniscript::psbt::PsbtExt;
20-
use miniscript::DescriptorTrait;
21-
use miniscript::MiniscriptKey;
22-
use miniscript::{Miniscript, Segwitv0};
23-
use std::collections::BTreeMap;
13+
mod test_cpp;
14+
mod test_desc;
15+
mod test_util;
16+
use test_util::TestData;
2417

2518
struct StdLogger;
2619

@@ -45,11 +38,6 @@ impl log::Log for StdLogger {
4538

4639
static LOGGER: StdLogger = StdLogger;
4740

48-
/// Quickly create a BTC amount.
49-
fn btc<F: Into<f64>>(btc: F) -> Amount {
50-
Amount::from_btc(btc.into()).unwrap()
51-
}
52-
5341
fn get_rpc_url() -> String {
5442
return std::env::var("RPC_URL").expect("RPC_URL must be set");
5543
}
@@ -64,23 +52,6 @@ fn get_auth() -> bitcoincore_rpc::Auth {
6452
};
6553
}
6654

67-
// Find the Outpoint by value.
68-
// Ideally, we should find by scriptPubkey, but this
69-
// works for temp test case
70-
fn get_vout(cl: &Client, txid: Txid, value: u64) -> (OutPoint, TxOut) {
71-
let tx = cl
72-
.get_transaction(&txid, None)
73-
.unwrap()
74-
.transaction()
75-
.unwrap();
76-
for (i, txout) in tx.output.into_iter().enumerate() {
77-
if txout.value == value {
78-
return (OutPoint::new(txid, i as u32), txout);
79-
}
80-
}
81-
unreachable!("Only call get vout on functions which have the expected outpoint");
82-
}
83-
8455
fn main() {
8556
log::set_logger(&LOGGER)
8657
.map(|()| log::set_max_level(log::LevelFilter::max()))
@@ -95,173 +66,64 @@ fn main() {
9566
cl.create_wallet("testwallet", None, None, None, None)
9667
.unwrap();
9768

98-
let testdata = read_file::TestData::new_fixed_data(50);
99-
let ms_vec = read_file::parse_miniscripts(&testdata.pubdata);
100-
let sks = testdata.secretdata.sks;
101-
let pks = testdata.pubdata.pks;
102-
// Generate some blocks
103-
let blocks = cl
104-
.generate_to_address(500, &cl.get_new_address(None, None).unwrap())
105-
.unwrap();
106-
assert_eq!(blocks.len(), 500);
107-
108-
// Next send some btc to each address corresponding to the miniscript
109-
let mut txids = vec![];
110-
for ms in ms_vec.iter() {
111-
let wsh = miniscript::Descriptor::new_wsh(ms.clone()).unwrap();
112-
let txid = cl
113-
.send_to_address(
114-
&wsh.address(bitcoin::Network::Regtest).unwrap(),
115-
btc(1),
116-
None,
117-
None,
118-
None,
119-
None,
120-
None,
121-
None,
122-
)
123-
.unwrap();
124-
txids.push(txid);
125-
}
126-
// Wait for the funds to mature.
127-
let blocks = cl
128-
.generate_to_address(50, &cl.get_new_address(None, None).unwrap())
129-
.unwrap();
130-
assert_eq!(blocks.len(), 50);
131-
// Create a PSBT for each transaction.
132-
// Spend one input and spend one output for simplicity.
133-
let mut psbts = vec![];
134-
for (ms, txid) in ms_vec.iter().zip(txids) {
135-
let mut psbt = Psbt {
136-
unsigned_tx: Transaction {
137-
version: 2,
138-
lock_time: 1_603_866_330, // time at 10/28/2020 @ 6:25am (UTC)
139-
input: vec![],
140-
output: vec![],
141-
},
142-
unknown: BTreeMap::new(),
143-
proprietary: BTreeMap::new(),
144-
xpub: BTreeMap::new(),
145-
version: 0,
146-
inputs: vec![],
147-
outputs: vec![],
148-
};
149-
// figure out the outpoint from the txid
150-
let (outpoint, witness_utxo) = get_vout(&cl, txid, btc(1.0).as_sat());
151-
let mut txin = TxIn::default();
152-
txin.previous_output = outpoint;
153-
// set the sequence to a non-final number for the locktime transactions to be
154-
// processed correctly.
155-
// We waited 50 blocks, keep 49 for safety
156-
txin.sequence = 49;
157-
psbt.unsigned_tx.input.push(txin);
158-
// Get a new script pubkey from the node so that
159-
// the node wallet tracks the receiving transaction
160-
// and we can check it by gettransaction RPC.
161-
let addr = cl
162-
.get_new_address(None, Some(json::AddressType::Bech32))
163-
.unwrap();
164-
psbt.unsigned_tx.output.push(TxOut {
165-
value: 99_999_000,
166-
script_pubkey: addr.script_pubkey(),
167-
});
168-
let mut input = psbt::Input::default();
169-
input.witness_utxo = Some(witness_utxo);
170-
input.witness_script = Some(ms.encode());
171-
psbt.inputs.push(input);
172-
psbt.outputs.push(psbt::Output::default());
173-
psbts.push(psbt);
174-
}
175-
176-
let mut spend_txids = vec![];
177-
// Sign the transactions with all keys
178-
// AKA the signer role of psbt
179-
for i in 0..psbts.len() {
180-
// Get all the pubkeys and the corresponding secret keys
181-
let ms: Miniscript<miniscript::bitcoin::PublicKey, Segwitv0> =
182-
Miniscript::parse_insane(psbts[i].inputs[0].witness_script.as_ref().unwrap()).unwrap();
183-
184-
let sks_reqd: Vec<_> = ms
185-
.iter_pk_pkh()
186-
.map(|pk_pkh| match pk_pkh {
187-
iter::PkPkh::PlainPubkey(pk) => sks[pks.iter().position(|&x| x == pk).unwrap()],
188-
iter::PkPkh::HashedPubkey(hash) => {
189-
sks[pks
190-
.iter()
191-
.position(|&pk| pk.to_pubkeyhash() == hash)
192-
.unwrap()]
193-
}
194-
})
195-
.collect();
196-
// Get the required sighash message
197-
let amt = btc(1).as_sat();
198-
let mut sighash_cache = bitcoin::util::sighash::SigHashCache::new(&psbts[i].unsigned_tx);
199-
let sighash_ty = bitcoin::EcdsaSigHashType::All;
200-
let sighash = sighash_cache
201-
.segwit_signature_hash(0, &ms.encode(), amt, sighash_ty)
202-
.unwrap();
69+
let testdata = TestData::new_fixed_data(50);
70+
test_cpp::test_from_cpp_ms(&cl, &testdata);
20371

204-
// requires both signing and verification because we check the tx
205-
// after we psbt extract it
206-
let secp = secp256k1::Secp256k1::new();
207-
let msg = secp256k1::Message::from_slice(&sighash[..]).unwrap();
208-
209-
// Finally construct the signature and add to psbt
210-
for sk in sks_reqd {
211-
let sig = secp.sign_ecdsa(&msg, &sk);
212-
let pk = pks[sks.iter().position(|&x| x == sk).unwrap()];
213-
psbts[i].inputs[0].partial_sigs.insert(
214-
pk.inner,
215-
bitcoin::EcdsaSig {
216-
sig,
217-
hash_ty: sighash_ty,
218-
},
219-
);
220-
}
221-
// Add the hash preimages to the psbt
222-
psbts[i].inputs[0].sha256_preimages.insert(
223-
testdata.pubdata.sha256,
224-
testdata.secretdata.sha256_pre.to_vec(),
225-
);
226-
psbts[i].inputs[0].hash256_preimages.insert(
227-
testdata.pubdata.hash256,
228-
testdata.secretdata.hash256_pre.to_vec(),
229-
);
230-
println!("{}", ms);
231-
psbts[i].inputs[0].hash160_preimages.insert(
232-
testdata.pubdata.hash160,
233-
testdata.secretdata.hash160_pre.to_vec(),
234-
);
235-
psbts[i].inputs[0].ripemd160_preimages.insert(
236-
testdata.pubdata.ripemd160,
237-
testdata.secretdata.ripemd160_pre.to_vec(),
238-
);
239-
// Finalize the transaction using psbt
240-
// Let miniscript do it's magic!
241-
if let Err(e) = psbts[i].finalize_mall_mut(&secp) {
242-
// All miniscripts should satisfy
243-
panic!("Could not satisfy: error{} ms:{} at ind:{}", e[0], ms, i);
244-
} else {
245-
let tx = psbts[i].extract(&secp).unwrap();
72+
test_descs(&cl, &testdata);
73+
}
24674

247-
// Send the transactions to bitcoin node for mining.
248-
// Regtest mode has standardness checks
249-
// Check whether the node accepts the transactions
250-
let txid = cl
251-
.send_raw_transaction(&tx)
252-
.expect(&format!("{} send tx failed for ms {}", i, ms));
253-
spend_txids.push(txid);
254-
}
255-
}
256-
// Finally mine the blocks and await confirmations
257-
let _blocks = cl
258-
.generate_to_address(10, &cl.get_new_address(None, None).unwrap())
259-
.unwrap();
260-
// Get the required transactions from the node mined in the blocks.
261-
for txid in spend_txids {
262-
// Check whether the transaction is mined in blocks
263-
// Assert that the confirmations are > 0.
264-
let num_conf = cl.get_transaction(&txid, None).unwrap().info.confirmations;
265-
assert!(num_conf > 0);
266-
}
75+
fn test_descs(cl: &Client, testdata: &TestData) {
76+
// K : Compressed key available
77+
// K!: Compressed key with corresponding secret key unknown
78+
// X: X-only key available
79+
// X!: X-only key with corresponding secret key unknown
80+
81+
// Test 1: Simple spend with internal key
82+
let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X)");
83+
assert!(wit.len() == 1);
84+
85+
// Test 2: Same as above, but with leaves
86+
let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X,{pk(X1!),pk(X2!)})");
87+
assert!(wit.len() == 1);
88+
89+
// Test 3: Force to spend with script spend. Unknown internal key and only one known script path
90+
// X! -> Internal key unknown
91+
// Leaf 1 -> pk(X1) with X1 known
92+
// Leaf 2-> and_v(v:pk(X2),pk(X3!)) with partial witness only to X2 known
93+
let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1),and_v(v:pk(X2),pk(X3!))})");
94+
assert!(wit.len() == 3); // control block, script and signature
95+
96+
// Test 4: Force to spend with script spend. Unknown internal key and multiple script paths
97+
// Should select the one with minimum weight
98+
// X! -> Internal key unknown
99+
// Leaf 1 -> pk(X1!) with X1 unknown
100+
// Leaf 2-> and_v(v:pk(X2),pk(X3)) X2 and X3 known
101+
let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1),and_v(v:pk(X2),pk(X3))})");
102+
assert!(wit.len() == 3); // control block, script and one signatures
103+
104+
// Test 5: When everything is available, we should select the key spend path
105+
let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X,{pk(X1),and_v(v:pk(X2),pk(X3!))})");
106+
assert!(wit.len() == 1); // control block, script and signature
107+
108+
// Test 6: Test the new multi_a opcodes
109+
test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(1,X2,X3!,X4!,X5!)})");
110+
test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(2,X2,X3,X4!,X5!)})");
111+
test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(3,X2,X3,X4,X5!)})");
112+
test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(4,X2,X3,X4,X5)})");
113+
114+
// Misc tests for other descriptors that we support
115+
// Keys
116+
test_desc::test_desc_satisfy(cl, testdata, "wpkh(K)");
117+
test_desc::test_desc_satisfy(cl, testdata, "pkh(K)");
118+
test_desc::test_desc_satisfy(cl, testdata, "sh(wpkh(K))");
119+
120+
// sorted multi
121+
test_desc::test_desc_satisfy(cl, testdata, "sh(sortedmulti(2,K1,K2,K3))");
122+
test_desc::test_desc_satisfy(cl, testdata, "wsh(sortedmulti(2,K1,K2,K3))");
123+
test_desc::test_desc_satisfy(cl, testdata, "sh(wsh(sortedmulti(2,K1,K2,K3)))");
124+
125+
// Miniscripts
126+
test_desc::test_desc_satisfy(cl, testdata, "sh(and_v(v:pk(K1),pk(K2)))");
127+
test_desc::test_desc_satisfy(cl, testdata, "wsh(and_v(v:pk(K1),pk(K2)))");
128+
test_desc::test_desc_satisfy(cl, testdata, "sh(wsh(and_v(v:pk(K1),pk(K2))))");
267129
}

0 commit comments

Comments
 (0)