Merge rust-bitcoin/rust-bitcoin#6836: bitcoin: move tests reading `tests/data` out of `src`
What changed, and why it matters
This commit is a housekeeping change that moves test files from inside the library source code to a separate tests folder. It does not change any production code or fix a security bug. The change makes sure that tests which need data files still work after the crate is published, because the test data folder is excluded from published packages.
No security action required. Treat as normal maintenance/test refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit relocates unit tests that rely on include_str!/include_bytes! from src/ modules into integration-style tests under tests/ for the bitcoin and p2p crates. The production logic remains identical; only test code and paths to test data files changed. One BIP-341 sighash test was rewritten to use hard-coded vectors so it can remain in src/ without accessing private caches. This is a test infrastructure refactor, not a runtime security patch.
Changed components
bitcoin/src/bip158.rsbitcoin/src/blockdata/block.rsbitcoin/src/blockdata/transaction.rsbitcoin/src/crypto/sighash.rsbitcoin/src/taproot/mod.rsbitcoin/tests/bip158.rsbitcoin/tests/block.rsbitcoin/tests/sighash.rsbitcoin/tests/taproot.rsbitcoin/tests/transaction.rsp2p/src/merkle_tree.rsp2p/tests/merkle_tree.rsInspect captured patch +627 / −560
### bitcoin/src/bip158.rs
@@ -551,83 +551,9 @@ pub mod error {
#[cfg(test)]
mod test {
- #[cfg(feature = "std")]
- use std::collections::HashMap;
-
use hex::hex;
- #[cfg(feature = "std")]
- use serde_json::Value;
use super::*;
- #[cfg(feature = "std")]
- use crate::encoding::decode_from_slice;
- #[cfg(feature = "std")]
- use crate::ScriptPubKeyBuf;
-
- #[test]
- #[cfg(feature = "std")]
- fn blockfilters() {
- let hex = |b| crate::hex::decode_to_vec(b).unwrap();
-
- // test vectors from: https://github.com/jimpo/bitcoin/blob/c7efb652f3543b001b4dd22186a354605b14f47e/src/test/data/blockfilters.json
- let data = include_str!("../tests/data/blockfilters.json");
-
- let testdata = serde_json::from_str::<Value>(data).unwrap().as_array().unwrap().clone();
- for t in testdata.iter().skip(1) {
- let block_hash = t.get(1).unwrap().as_str().unwrap().parse::<BlockHash>().unwrap();
- let block: Block =
- decode_from_slice(&hex(t.get(2).unwrap().as_str().unwrap())).unwrap();
- let block = block.assume_checked(None);
- assert_eq!(block.block_hash(), block_hash);
- let scripts = t.get(3).unwrap().as_array().unwrap();
- let filter_content = hex(t.get(5).unwrap().as_str().unwrap());
-
- let mut txmap = HashMap::new();
- let mut si = scripts.iter();
- for tx in block.transactions().iter().skip(1) {
- for input in tx.inputs.iter() {
- txmap.insert(
- input.previous_output,
- ScriptPubKeyBuf::from(hex(si.next().unwrap().as_str().unwrap())),
- );
- }
- }
-
- let filter = BlockFilter::new_script_filter(&block, |o| {
- if let Some(s) = txmap.get(o) {
- Ok(s.clone())
- } else {
- Err(Error::UtxoMissing(*o))
- }
- })
- .unwrap();
-
- let test_filter = BlockFilter::new(filter_content.as_slice());
-
- assert_eq!(test_filter.content, filter.content);
-
- let block_hash = &block.block_hash();
- assert!(filter
- .match_all(
- *block_hash,
- &mut txmap.values().filter_map(|s| if !s.is_empty() {
- Some(s.as_bytes())
- } else {
- None
- })
- )
- .unwrap());
-
- for script in txmap.values() {
- let query = [script];
- if !script.is_empty() {
- assert!(filter
- .match_any(*block_hash, &mut query.iter().map(|s| s.as_bytes()))
- .unwrap());
- }
- }
- }
- }
#[test]
fn filter() {
### bitcoin/src/blockdata/block.rs
@@ -321,12 +321,10 @@ mod tests {
use super::*;
use crate::encoding::{decode_from_slice, encode_to_vec};
- use crate::pow::test_utils::{u128_to_work, u64_to_work};
+ use crate::pow::test_utils::u128_to_work;
use crate::script::{ScriptPubKeyBuf, ScriptSigBuf};
use crate::transaction::{OutPoint, Transaction, TxIn, TxOut, Txid};
- use crate::{
- block, Amount, BlockTime, CompactTarget, Network, Sequence, TestnetVersion, Witness, Wtxid,
- };
+ use crate::{block, Amount, BlockTime, CompactTarget, Network, Sequence, Witness, Wtxid};
#[test]
fn coinbase_and_bip34() {
@@ -429,53 +427,6 @@ mod tests {
assert_eq!(encode_to_vec(&real_decode), some_block);
}
- // Check testnet block 000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b
- #[test]
- fn segwit_block() {
- let params = Params::new(Network::Testnet(TestnetVersion::V3));
- let segwit_block = include_bytes!("../../tests/data/testnet_block_000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b.raw").to_vec();
-
- let decode: Result<Block, _> = decode_from_slice(&segwit_block);
-
- let prevhash = hex!("2aa2f2ca794ccbd40c16e2f3333f6b8b683f9e7179b2c4d74906000000000000");
- let merkle = hex!("10bc26e70a2f672ad420a6153dd0c28b40a6002c55531bfc99bf8994a8e8f67e");
- let work = u64_to_work(0x257c3becdacc64_u64);
-
- assert!(decode.is_ok());
-
- let block = decode.unwrap();
- let (witness_commitment_matches, witness_root) = block.check_witness_commitment();
- assert!(witness_commitment_matches);
-
- let (header, transactions) = block.into_parts();
- let real_decode =
- Block::new_unchecked(header, transactions.clone()).assume_checked(witness_root);
-
- assert_eq!(real_decode.header().version, Version::from_consensus(0x2000_0000)); // VERSIONBITS but no bits set
- assert_eq!(encode_to_vec(&real_decode.header().prev_blockhash), prevhash);
- assert_eq!(encode_to_vec(&real_decode.header().merkle_root), merkle);
- assert_eq!(
- real_decode.header().merkle_root,
- block::compute_merkle_root(&transactions).unwrap()
- );
- assert_eq!(real_decode.header().time, BlockTime::from_u32(1472004949));
- assert_eq!(real_decode.header().bits, CompactTarget::from_consensus(0x1a06d450));
- assert_eq!(real_decode.header().nonce, 1879759182);
- assert_eq!(real_decode.header().work(), work);
- assert_eq!(real_decode.header().difficulty(¶ms), 2456598);
- assert_eq!(real_decode.header().difficulty_float(¶ms), 2456598.4399242126);
-
- assert_eq!(
- real_decode.header().validate_pow(real_decode.header().target()).unwrap(),
- real_decode.block_hash()
- );
- assert_eq!(real_decode.total_size(), segwit_block.len());
- assert_eq!(block_base_size(real_decode.transactions()), 4283);
- assert_eq!(real_decode.weight(), Weight::from_wu(17168));
-
- assert_eq!(encode_to_vec(&real_decode), segwit_block);
- }
-
#[test]
fn validate_pow() {
let some_header = hex!("010000004ddccd549d28f385ab457e98d1b11ce80bfea2c5ab93015ade4973e400000000bf4473e53794beae34e64fccc471dace6ae544180816f89591894e0f417a914cd74d6e49ffff001d323b3a7b");
### bitcoin/src/blockdata/transaction.rs
@@ -1323,13 +1323,6 @@ mod tests {
);
}
- #[test]
- fn huge_witness() {
- let hex =
- hex::decode_to_vec(include_str!("../../tests/data/huge_witness.hex").trim()).unwrap();
- decode_from_slice::<Transaction>(&hex).unwrap();
- }
-
#[test]
#[cfg(feature = "bitcoinconsensus")]
#[cfg(feature = "std")]
### bitcoin/src/crypto/sighash.rs
@@ -1354,9 +1354,6 @@ where
#[cfg(test)]
mod tests {
- #[cfg(feature = "serde")]
- use alloc::string::String;
- #[cfg(feature = "serde")]
use alloc::vec::Vec;
use hashes::HashEngine;
@@ -1396,55 +1393,6 @@ mod tests {
assert_eq!(got, want);
}
- #[test]
- #[cfg(feature = "serde")]
- fn legacy_sighash() {
- use serde_json::Value;
-
- use crate::sighash::SighashCache;
-
- fn run_test_sighash(
- tx: &str,
- script: &str,
- input_index: usize,
- hash_type: i64,
- expected_result: &str,
- ) {
- let tx: Transaction = decode_from_slice(&hex::decode_to_vec(tx).unwrap()[..]).unwrap();
- let script = ScriptPubKeyBuf::from(hex::decode_to_vec(script).unwrap());
- let mut raw_expected = hex::decode_to_vec(expected_result).unwrap();
- raw_expected.reverse();
- let bytes = <[u8; 32]>::try_from(&raw_expected[..]).unwrap();
- let want = LegacySighash::from_byte_array(bytes);
-
- let cache = SighashCache::new(&tx);
- let got = cache
- .legacy_signature_hash(
- input_index,
- &script,
- EcdsaSighashType::from_consensus(hash_type as u32),
- )
- .unwrap();
-
- assert_eq!(got, want);
- }
-
- // These test vectors were stolen from libbtc, which is Copyright 2014 Jonas Schnelli MIT
- // They were transformed by replacing {...} with run_test_sighash(...), then the ones containing
- // OP_CODESEPARATOR in their pubkeys were removed
- let data = include_str!("../../tests/data/legacy_sighash.json");
-
- let testdata = serde_json::from_str::<Value>(data).unwrap().as_array().unwrap().clone();
- for t in testdata.iter().skip(1) {
- let tx = t.get(0).unwrap().as_str().unwrap();
- let script = t.get(1).unwrap().as_str().unwrap_or("");
- let input_index = t.get(2).unwrap().as_u64().unwrap();
- let hash_type = t.get(3).unwrap().as_i64().unwrap();
- let expected_sighash = t.get(4).unwrap().as_str().unwrap();
- run_test_sighash(tx, script, input_index as usize, hash_type, expected_sighash);
- }
- }
-
#[test]
fn tap_sighash_hash() {
let bytes = hex!("00011b96877db45ffa23b307e9f0ac87b80ef9a80b4c5f0db3fbe734422453e83cc5576f3d542c5d4898fb2b696c15d43332534a7c1d1255fda38993545882df92c3e353ff6d36fbfadc4d168452afd8467f02fe53d71714fcea5dfe2ea759bd00185c4cb02bc76d42620393ca358a1a713f4997f9fc222911890afb3fe56c6a19b202df7bffdcfad08003821294279043746631b00e2dc5e52a111e213bbfe6ef09a19428d418dab0d50000000000");
@@ -1693,194 +1641,67 @@ mod tests {
assert_eq!(expected, hash.to_byte_array());
}
- #[cfg(feature = "serde")]
#[test]
- fn bip_341_sighash_tests() {
- use hex::DisplayHex;
-
- fn sighash_deser_numeric<'de, D>(deserializer: D) -> Result<TapSighashType, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- use serde::de::{Deserialize, Error, Unexpected};
-
- let raw = u8::deserialize(deserializer)?;
- TapSighashType::from_consensus_u8(raw).map_err(|_| {
- D::Error::invalid_value(
- Unexpected::Unsigned(raw.into()),
- &"number in range 0-3 or 0x81-0x83",
- )
+ fn bip_341_sighash_intermediary_caches() {
+ use hashes::sha256;
+
+ const RAW_UNSIGNED_TX: &str =
+ "02000000097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c0100000000\
+ 00000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000ff\
+ fffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a418420000000000ffff\
+ fffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffff\
+ ffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff\
+ 956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e6\
+ 64b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa\
+ 6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb\
+ 6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a\
+ 3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a\
+ 87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0065cd1d";
+
+ const UTXOS_SPENT: [(&str, u32); 9] = [
+ ("512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343", 420_000_000),
+ ("5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3", 462_000_000),
+ ("76a914751e76e8199196d454941c45d1b3a323f1433bd688ac", 294_000_000),
+ ("5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e", 504_000_000),
+ ("512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605", 630_000_000),
+ ("00147dd65592d0ab2fe0d0257d571abf032cd9db93dc", 378_000_000),
+ ("512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831", 672_000_000),
+ ("5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5", 546_000_000),
+ ("512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220", 588_000_000),
+ ];
+
+ let tx: Transaction = crate::encoding::decode_from_hex(RAW_UNSIGNED_TX).unwrap();
+ let utxos = UTXOS_SPENT
+ .iter()
+ .map(|&(script_pubkey, amount)| TxOut {
+ amount: Amount::from_sat_u32(amount),
+ script_pubkey: ScriptPubKeyBuf::from(hex::decode_to_vec(script_pubkey).unwrap()),
})
- }
-
- fn tx_deser_hex<'de, D>(deserializer: D) -> Result<Transaction, D::Error>
- where
- D: serde::Deserializer<'de>,
- {
- use serde::de::{Deserialize, Error};
-
- let hex_str = String::deserialize(deserializer)?;
- crate::encoding::decode_from_hex(&hex_str).map_err(D::Error::custom)
- }
-
- use secp256k1::SecretKey;
-
- use crate::crypto::key::XOnlyPublicKey;
- use crate::key::{Keypair, PrivateKey, TapTweak};
- use crate::taproot::TapNodeHash;
-
- #[derive(serde::Deserialize)]
- struct UtxoSpent {
- #[serde(rename = "scriptPubKey")]
- script_pubkey: ScriptPubKeyBuf,
- #[serde(rename = "amountSats")]
- #[serde(with = "crate::amount::serde::as_sat")]
- amount: Amount,
- }
-
- #[derive(serde::Deserialize)]
- #[serde(rename_all = "camelCase")]
- struct KpsGiven {
- #[serde(deserialize_with = "tx_deser_hex")]
- raw_unsigned_tx: Transaction,
- utxos_spent: Vec<UtxoSpent>,
- }
-
- #[derive(serde::Deserialize)]
- #[serde(rename_all = "camelCase")]
- struct KpsIntermediary {
- hash_prevouts: sha256::Hash,
- hash_outputs: sha256::Hash,
- hash_sequences: sha256::Hash,
- hash_amounts: sha256::Hash,
- hash_script_pubkeys: sha256::Hash,
- }
-
- #[derive(serde::Deserialize)]
- #[serde(rename_all = "camelCase")]
- struct KpsInputSpendingGiven {
- txin_index: usize,
- internal_privkey: SecretKey,
- merkle_root: Option<TapNodeHash>,
- #[serde(deserialize_with = "sighash_deser_numeric")]
- hash_type: TapSighashType,
- }
-
- #[derive(serde::Deserialize)]
- #[serde(rename_all = "camelCase")]
- struct KpsInputSpendingIntermediary {
- internal_pubkey: XOnlyPublicKey,
- tweaked_privkey: SecretKey,
- sig_msg: String,
- //precomputed_used: Vec<String>, // unused
- sig_hash: TapSighash,
- }
-
- #[derive(serde::Deserialize)]
- #[serde(rename_all = "camelCase")]
- struct KpsInputSpendingExpected {
- witness: Vec<String>,
- }
-
- #[derive(serde::Deserialize)]
- #[serde(rename_all = "camelCase")]
- struct KpsInputSpending {
- given: KpsInputSpendingGiven,
- intermediary: KpsInputSpendingIntermediary,
- expected: KpsInputSpendingExpected,
- // auxiliary: KpsAuxiliary, //unused
- }
-
- #[derive(serde::Deserialize)]
- #[serde(rename_all = "camelCase")]
- struct KeyPathSpending {
- given: KpsGiven,
- intermediary: KpsIntermediary,
- input_spending: Vec<KpsInputSpending>,
- }
-
- #[derive(serde::Deserialize)]
- #[serde(rename_all = "camelCase")]
- struct TestData {
- version: u64,
- key_path_spending: Vec<KeyPathSpending>,
- //script_pubkey: Vec<ScriptPubKey>, // unused
- }
-
- let json_str = include_str!("../../tests/data/bip341_tests.json");
- let mut data =
- serde_json::from_str::<TestData>(json_str).expect("JSON was not well-formatted");
-
- assert_eq!(data.version, 1u64);
- let key_path = data.key_path_spending.remove(0);
-
- let raw_unsigned_tx = key_path.given.raw_unsigned_tx;
- let utxos = key_path
- .given
- .utxos_spent
- .into_iter()
- .map(|txo| TxOut { amount: txo.amount, script_pubkey: txo.script_pubkey })
.collect::<Vec<_>>();
- // Test intermediary
- let mut cache = SighashCache::new(&raw_unsigned_tx);
-
- let expected = key_path.intermediary;
- // Compute all caches
- assert_eq!(expected.hash_amounts, cache.taproot_cache(&utxos).amounts);
- assert_eq!(expected.hash_outputs, cache.common_cache().outputs);
- assert_eq!(expected.hash_prevouts, cache.common_cache().prevouts);
- assert_eq!(expected.hash_script_pubkeys, cache.taproot_cache(&utxos).script_pubkeys);
- assert_eq!(expected.hash_sequences, cache.common_cache().sequences);
-
- for mut inp in key_path.input_spending {
- let tx_ind = inp.given.txin_index;
- let internal_priv_key = PrivateKey::from_secp(inp.given.internal_privkey);
- let merkle_root = inp.given.merkle_root;
- let hash_ty = inp.given.hash_type;
-
- let expected = inp.intermediary;
- let sig_str = inp.expected.witness.remove(0);
- let (expected_key_spend_sig, expected_hash_ty) = if sig_str.len() == 128 {
- (sig_str.parse::<secp256k1::schnorr::Signature>().unwrap(), TapSighashType::Default)
- } else {
- let hash_ty = u8::from_str_radix(&sig_str[128..130], 16).unwrap();
- let hash_ty = TapSighashType::from_consensus_u8(hash_ty).unwrap();
- (sig_str[..128].parse::<secp256k1::schnorr::Signature>().unwrap(), hash_ty)
- };
-
- // tests
- let keypair = Keypair::from_private_key(&internal_priv_key);
- let internal_key = XOnlyPublicKey::from_keypair(&keypair);
- let tweaked_keypair = keypair.tap_tweak(merkle_root);
- let mut sig_msg = Vec::new();
- cache
- .taproot_encode_signing_data_to(
- &mut sig_msg,
- tx_ind,
- &Prevouts::All(&utxos),
- None,
- None,
- hash_ty,
- )
- .unwrap();
- let sighash = cache
- .taproot_signature_hash(tx_ind, &Prevouts::All(&utxos), None, None, hash_ty)
- .unwrap();
-
- let tweaked_keypair = tweaked_keypair.into_keypair();
- let key_spend_sig = tweaked_keypair
- .raw_bip340_sign_with_aux_randomness(&sighash.to_byte_array(), &[0u8; 32]);
-
- assert_eq!(expected.internal_pubkey.with_parity(internal_key.parity()), internal_key);
- assert_eq!(expected.sig_msg, sig_msg.to_lower_hex_string());
- assert_eq!(expected.sig_hash, sighash);
- assert_eq!(expected_hash_ty, hash_ty);
- assert_eq!(expected_key_spend_sig, key_spend_sig);
+ let mut cache = SighashCache::new(&tx);
- let tweaked_priv_key = tweaked_keypair.to_private_key();
- assert_eq!(PrivateKey::from_secp(expected.tweaked_privkey), tweaked_priv_key);
- }
+ let hash = |s: &str| s.parse::<sha256::Hash>().unwrap();
+ assert_eq!(
+ cache.taproot_cache(&utxos).amounts,
+ hash("58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde6")
+ );
+ assert_eq!(
+ cache.common_cache().outputs,
+ hash("a2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc5")
+ );
+ assert_eq!(
+ cache.common_cache().prevouts,
+ hash("e3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f")
+ );
+ assert_eq!(
+ cache.taproot_cache(&utxos).script_pubkeys,
+ hash("23ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e21")
+ );
+ assert_eq!(
+ cache.common_cache().sequences,
+ hash("18959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e")
+ );
}
#[test]
### bitcoin/src/taproot/mod.rs
@@ -1499,7 +1499,6 @@ mod test {
use super::*;
use crate::sighash::TapSighashTag;
- use crate::{Address, KnownHrp, ScriptPubKeyBuf};
extern crate serde_json;
#[cfg(feature = "serde")]
@@ -1805,105 +1804,4 @@ mod test {
],
);
}
-
- #[test]
- fn bip_341_tests() {
- fn process_script_trees(
- v: &serde_json::Value,
- mut builder: TaprootBuilder,
- leaves: &mut Vec<(TapScriptBuf, LeafVersion)>,
- depth: u8,
- ) -> TaprootBuilder {
- if v.is_null() {
- // nothing to push
- } else if v.is_array() {
- for leaf in v.as_array().unwrap() {
- builder = process_script_trees(leaf, builder, leaves, depth + 1);
- }
- } else {
- let script =
- TapScriptBuf::from_hex_no_length_prefix(v["script"].as_str().unwrap()).unwrap();
- let ver =
- LeafVersion::from_consensus(v["leafVersion"].as_u64().unwrap() as u8).unwrap();
- leaves.push((script.clone(), ver));
- builder = builder.add_leaf_with_ver(depth, script, ver).unwrap();
- }
- builder
- }
-
- let data = bip_341_read_json();
- // Check the version of data
- assert!(data["version"] == 1);
-
- for arr in data["scriptPubKey"].as_array().unwrap() {
- let internal_key =
- arr["given"]["internalPubkey"].as_str().unwrap().parse::<XOnlyPublicKey>().unwrap();
- // process the tree
- let script_tree = &arr["given"]["scriptTree"];
- let mut merkle_root = None;
- if script_tree.is_null() {
- assert!(arr["intermediary"]["merkleRoot"].is_null());
- } else {
- merkle_root = Some(
- arr["intermediary"]["merkleRoot"]
- .as_str()
- .unwrap()
- .parse::<TapNodeHash>()
- .unwrap(),
- );
- let leaf_hashes = arr["intermediary"]["leafHashes"].as_array().unwrap();
- let ctrl_blks = arr["expected"]["scriptPathControlBlocks"].as_array().unwrap();
- let mut builder = TaprootBuilder::new();
- let mut leaves = vec![];
- builder = process_script_trees(script_tree, builder, &mut leaves, 0);
- let spend_info = builder.finalize(internal_key).unwrap();
- for (i, script_ver) in leaves.iter().enumerate() {
- let expected_leaf_hash = leaf_hashes[i].as_str().unwrap();
- let expected_ctrl_blk =
- ControlBlock::from_hex(ctrl_blks[i].as_str().unwrap()).unwrap();
-
- let leaf_hash = TapLeafHash::from_script(&script_ver.0, script_ver.1);
- let ctrl_blk = spend_info.control_block(script_ver).unwrap();
- assert_eq!(leaf_hash.to_string(), expected_leaf_hash);
- assert_eq!(ctrl_blk, expected_ctrl_blk);
- }
- }
- let expected_output_key = arr["intermediary"]["tweakedPubkey"]
- .as_str()
- .unwrap()
- .parse::<XOnlyPublicKey>()
- .unwrap();
- let expected_tweak =
- arr["intermediary"]["tweak"].as_str().unwrap().parse::<TapTweakHash>().unwrap();
- let expected_spk = ScriptPubKeyBuf::from_hex_no_length_prefix(
- arr["expected"]["scriptPubKey"].as_str().unwrap(),
- )
- .unwrap();
- let expected_addr = arr["expected"]["bip350Address"]
- .as_str()
- .unwrap()
- .parse::<Address<_>>()
- .unwrap()
- .assume_checked();
-
- let tweak = TapTweakHash::from_key_and_merkle_root(internal_key, merkle_root);
- let output_key = internal_key.tap_tweak(merkle_root);
- let addr = Address::p2tr(internal_key, merkle_root, KnownHrp::Mainnet);
- let spk = addr.script_pubkey();
-
- // Compare just the key bytes, not the parity
- assert_eq!(
- expected_output_key.serialize().0,
- output_key.to_x_only_public_key().serialize().0
- );
- assert_eq!(expected_tweak, tweak);
- assert_eq!(expected_addr, addr);
- assert_eq!(expected_spk, spk);
- }
- }
-
- fn bip_341_read_json() -> serde_json::Value {
- let json_str = include_str!("../../tests/data/bip341_tests.json");
- serde_json::from_str(json_str).expect("JSON was not well-formatted")
- }
}
### bitcoin/tests/bip158.rs
@@ -0,0 +1,73 @@
+//! Tests for BIP158 block filters. Data lives in `tests/data`, which is excluded when publishing.
+
+#![cfg(feature = "std")]
+
+use std::collections::HashMap;
+
+use bitcoin::bip158::{BlockFilter, Error};
+use bitcoin::{Block, BlockHash, ScriptPubKeyBuf};
+use encoding::decode_from_slice;
+use serde_json::Value;
+
+#[test]
+fn blockfilters() {
+ let hex = |b| hex::decode_to_vec(b).unwrap();
+
+ // test vectors from: https://github.com/jimpo/bitcoin/blob/c7efb652f3543b001b4dd22186a354605b14f47e/src/test/data/blockfilters.json
+ let data = include_str!("data/blockfilters.json");
+
+ let testdata = serde_json::from_str::<Value>(data).unwrap().as_array().unwrap().clone();
+ for t in testdata.iter().skip(1) {
+ let block_hash = t.get(1).unwrap().as_str().unwrap().parse::<BlockHash>().unwrap();
+ let block: Block = decode_from_slice(&hex(t.get(2).unwrap().as_str().unwrap())).unwrap();
+ let block = block.assume_checked(None);
+ assert_eq!(block.block_hash(), block_hash);
+ let scripts = t.get(3).unwrap().as_array().unwrap();
+ let filter_content = hex(t.get(5).unwrap().as_str().unwrap());
+
+ let mut txmap = HashMap::new();
+ let mut si = scripts.iter();
+ for tx in block.transactions().iter().skip(1) {
+ for input in tx.inputs.iter() {
+ txmap.insert(
+ input.previous_output,
+ ScriptPubKeyBuf::from(hex(si.next().unwrap().as_str().unwrap())),
+ );
+ }
+ }
+
+ let filter = BlockFilter::new_script_filter(&block, |o| {
+ if let Some(s) = txmap.get(o) {
+ Ok(s.clone())
+ } else {
+ Err(Error::UtxoMissing(*o))
+ }
+ })
+ .unwrap();
+
+ let test_filter = BlockFilter::new(filter_content.as_slice());
+
+ assert_eq!(test_filter.content, filter.content);
+
+ let block_hash = &block.block_hash();
+ assert!(filter
+ .match_all(
+ *block_hash,
+ &mut txmap.values().filter_map(|s| if !s.is_empty() {
+ Some(s.as_bytes())
+ } else {
+ None
+ })
+ )
+ .unwrap());
+
+ for script in txmap.values() {
+ let query = [script];
+ if !script.is_empty() {
+ assert!(filter
+ .match_any(*block_hash, &mut query.iter().map(|s| s.as_bytes()))
+ .unwrap());
+ }
+ }
+ }
+}
### bitcoin/tests/block.rs
@@ -0,0 +1,68 @@
+//! Tests for `Block`. Data lives in `tests/data`, which is excluded when publishing.
+
+use bitcoin::block::{self, Block, Header, Version};
+use bitcoin::ext::*;
+use bitcoin::network::Params;
+use bitcoin::pow::Work;
+use bitcoin::{BlockTime, CompactTarget, Network, TestnetVersion, Transaction, Weight};
+use encoding::{decode_from_slice, encode_to_vec, CompactSizeEncoder};
+use hex::hex;
+
+fn block_base_size(transactions: &[Transaction]) -> usize {
+ let mut size = Header::SIZE;
+
+ size += CompactSizeEncoder::encoded_size(transactions.len());
+ size += transactions.iter().map(|tx| tx.base_size()).sum::<usize>();
+
+ size
+}
+
+// Check testnet block 000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b
+#[test]
+fn segwit_block() {
+ let params = Params::new(Network::Testnet(TestnetVersion::V3));
+ let segwit_block = include_bytes!(
+ "data/testnet_block_000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b.raw"
+ )
+ .to_vec();
+
+ let decode: Result<Block, _> = decode_from_slice(&segwit_block);
+
+ let prevhash = hex!("2aa2f2ca794ccbd40c16e2f3333f6b8b683f9e7179b2c4d74906000000000000");
+ let merkle = hex!("10bc26e70a2f672ad420a6153dd0c28b40a6002c55531bfc99bf8994a8e8f67e");
+ let work = Work::from_hex("0x257c3becdacc64").unwrap();
+
+ assert!(decode.is_ok());
+
+ let block = decode.unwrap();
+ let (witness_commitment_matches, witness_root) = block.check_witness_commitment();
+ assert!(witness_commitment_matches);
+
+ let (header, transactions) = block.into_parts();
+ let real_decode =
+ Block::new_unchecked(header, transactions.clone()).assume_checked(witness_root);
+
+ assert_eq!(real_decode.header().version, Version::from_consensus(0x2000_0000)); // VERSIONBITS but no bits set
+ assert_eq!(encode_to_vec(&real_decode.header().prev_blockhash), prevhash);
+ assert_eq!(encode_to_vec(&real_decode.header().merkle_root), merkle);
+ assert_eq!(
+ real_decode.header().merkle_root,
+ block::compute_merkle_root(&transactions).unwrap()
+ );
+ assert_eq!(real_decode.header().time, BlockTime::from_u32(1472004949));
+ assert_eq!(real_decode.header().bits, CompactTarget::from_consensus(0x1a06d450));
+ assert_eq!(real_decode.header().nonce, 1879759182);
+ assert_eq!(real_decode.header().work(), work);
+ assert_eq!(real_decode.header().difficulty(¶ms), 2456598);
+ assert_eq!(real_decode.header().difficulty_float(¶ms), 2456598.4399242126);
+
+ assert_eq!(
+ real_decode.header().validate_pow(real_decode.header().target()).unwrap(),
+ real_decode.block_hash()
+ );
+ assert_eq!(real_decode.total_size(), segwit_block.len());
+ assert_eq!(block_base_size(real_decode.transactions()), 4283);
+ assert_eq!(real_decode.weight(), Weight::from_wu(17168));
+
+ assert_eq!(encode_to_vec(&real_decode), segwit_block);
+}
### bitcoin/tests/sighash.rs
@@ -0,0 +1,221 @@
+//! Tests for sighash computation. Data lives in `tests/data`, which is excluded when publishing.
+
+#![cfg(feature = "serde")]
+
+use bitcoin::sighash::{
+ EcdsaSighashType, LegacySighash, Prevouts, SighashCache, TapSighash, TapSighashType,
+};
+use bitcoin::{Amount, ScriptPubKeyBuf, Transaction, TxOut};
+
+#[test]
+fn legacy_sighash() {
+ use serde_json::Value;
+
+ fn run_test_sighash(
+ tx: &str,
+ script: &str,
+ input_index: usize,
+ hash_type: i64,
+ expected_result: &str,
+ ) {
+ let tx: Transaction =
+ encoding::decode_from_slice(&hex::decode_to_vec(tx).unwrap()[..]).unwrap();
+ let script = ScriptPubKeyBuf::from(hex::decode_to_vec(script).unwrap());
+ let mut raw_expected = hex::decode_to_vec(expected_result).unwrap();
+ raw_expected.reverse();
+ let bytes = <[u8; 32]>::try_from(&raw_expected[..]).unwrap();
+ let want = LegacySighash::from_byte_array(bytes);
+
+ let cache = SighashCache::new(&tx);
+ let got = cache
+ .legacy_signature_hash(
+ input_index,
+ &script,
+ EcdsaSighashType::from_consensus(hash_type as u32),
+ )
+ .unwrap();
+
+ assert_eq!(got, want);
+ }
+
+ // These test vectors were stolen from libbtc, which is Copyright 2014 Jonas Schnelli MIT
+ // They were transformed by replacing {...} with run_test_sighash(...), then the ones containing
+ // OP_CODESEPARATOR in their pubkeys were removed
+ let data = include_str!("data/legacy_sighash.json");
+
+ let testdata = serde_json::from_str::<Value>(data).unwrap().as_array().unwrap().clone();
+ for t in testdata.iter().skip(1) {
+ let tx = t.get(0).unwrap().as_str().unwrap();
+ let script = t.get(1).unwrap().as_str().unwrap_or("");
+ let input_index = t.get(2).unwrap().as_u64().unwrap();
+ let hash_type = t.get(3).unwrap().as_i64().unwrap();
+ let expected_sighash = t.get(4).unwrap().as_str().unwrap();
+ run_test_sighash(tx, script, input_index as usize, hash_type, expected_sighash);
+ }
+}
+
+#[test]
+fn bip_341_sighash_tests() {
+ use hex::DisplayHex;
+
+ fn sighash_deser_numeric<'de, D>(deserializer: D) -> Result<TapSighashType, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ use serde::de::{Deserialize, Error, Unexpected};
+
+ let raw = u8::deserialize(deserializer)?;
+ TapSighashType::from_consensus_u8(raw).map_err(|_| {
+ D::Error::invalid_value(
+ Unexpected::Unsigned(raw.into()),
+ &"number in range 0-3 or 0x81-0x83",
+ )
+ })
+ }
+
+ fn tx_deser_hex<'de, D>(deserializer: D) -> Result<Transaction, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ use serde::de::{Deserialize, Error};
+
+ let hex_str = String::deserialize(deserializer)?;
+ encoding::decode_from_hex(&hex_str).map_err(D::Error::custom)
+ }
+
+ use bitcoin::key::{Keypair, PrivateKey, TapTweak, XOnlyPublicKey};
+ use bitcoin::taproot::TapNodeHash;
+ use secp256k1::SecretKey;
+
+ #[derive(serde::Deserialize)]
+ struct UtxoSpent {
+ #[serde(rename = "scriptPubKey")]
+ script_pubkey: ScriptPubKeyBuf,
+ #[serde(rename = "amountSats")]
+ #[serde(with = "bitcoin::amount::serde::as_sat")]
+ amount: Amount,
+ }
+
+ #[derive(serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct KpsGiven {
+ #[serde(deserialize_with = "tx_deser_hex")]
+ raw_unsigned_tx: Transaction,
+ utxos_spent: Vec<UtxoSpent>,
+ }
+
+ #[derive(serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct KpsInputSpendingGiven {
+ txin_index: usize,
+ internal_privkey: SecretKey,
+ merkle_root: Option<TapNodeHash>,
+ #[serde(deserialize_with = "sighash_deser_numeric")]
+ hash_type: TapSighashType,
+ }
+
+ #[derive(serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct KpsInputSpendingIntermediary {
+ internal_pubkey: XOnlyPublicKey,
+ tweaked_privkey: SecretKey,
+ sig_msg: String,
+ //precomputed_used: Vec<String>, // unused
+ sig_hash: TapSighash,
+ }
+
+ #[derive(serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct KpsInputSpendingExpected {
+ witness: Vec<String>,
+ }
+
+ #[derive(serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct KpsInputSpending {
+ given: KpsInputSpendingGiven,
+ intermediary: KpsInputSpendingIntermediary,
+ expected: KpsInputSpendingExpected,
+ // auxiliary: KpsAuxiliary, //unused
+ }
+
+ #[derive(serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct KeyPathSpending {
+ given: KpsGiven,
+ input_spending: Vec<KpsInputSpending>,
+ }
+
+ #[derive(serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct TestData {
+ version: u64,
+ key_path_spending: Vec<KeyPathSpending>,
+ //script_pubkey: Vec<ScriptPubKey>, // unused
+ }
+
+ let json_str = include_str!("data/bip341_tests.json");
+ let mut data = serde_json::from_str::<TestData>(json_str).expect("JSON was not well-formatted");
+
+ assert_eq!(data.version, 1u64);
+ let key_path = data.key_path_spending.remove(0);
+
+ let raw_unsigned_tx = key_path.given.raw_unsigned_tx;
+ let utxos = key_path
+ .given
+ .utxos_spent
+ .into_iter()
+ .map(|txo| TxOut { amount: txo.amount, script_pubkey: txo.script_pubkey })
+ .collect::<Vec<_>>();
+
+ let mut cache = SighashCache::new(&raw_unsigned_tx);
+
+ for mut inp in key_path.input_spending {
+ let tx_ind = inp.given.txin_index;
+ let internal_priv_key = PrivateKey::from_secp(inp.given.internal_privkey);
+ let merkle_root = inp.given.merkle_root;
+ let hash_ty = inp.given.hash_type;
+
+ let expected = inp.intermediary;
+ let sig_str = inp.expected.witness.remove(0);
+ let (expected_key_spend_sig, expected_hash_ty) = if sig_str.len() == 128 {
+ (sig_str.parse::<secp256k1::schnorr::Signature>().unwrap(), TapSighashType::Default)
+ } else {
+ let hash_ty = u8::from_str_radix(&sig_str[128..130], 16).unwrap();
+ let hash_ty = TapSighashType::from_consensus_u8(hash_ty).unwrap();
+ (sig_str[..128].parse::<secp256k1::schnorr::Signature>().unwrap(), hash_ty)
+ };
+
+ // tests
+ let keypair = Keypair::from_private_key(&internal_priv_key);
+ let internal_key = XOnlyPublicKey::from_keypair(&keypair);
+ let tweaked_keypair = keypair.tap_tweak(merkle_root);
+ let mut sig_msg = Vec::new();
+ cache
+ .taproot_encode_signing_data_to(
+ &mut sig_msg,
+ tx_ind,
+ &Prevouts::All(&utxos),
+ None,
+ None,
+ hash_ty,
+ )
+ .unwrap();
+ let sighash = cache
+ .taproot_signature_hash(tx_ind, &Prevouts::All(&utxos), None, None, hash_ty)
+ .unwrap();
+
+ let tweaked_keypair = tweaked_keypair.into_keypair();
+ let key_spend_sig = tweaked_keypair
+ .raw_bip340_sign_with_aux_randomness(&sighash.to_byte_array(), &[0u8; 32]);
+
+ assert_eq!(expected.internal_pubkey.with_parity(internal_key.parity()), internal_key);
+ assert_eq!(expected.sig_msg, sig_msg.to_lower_hex_string());
+ assert_eq!(expected.sig_hash, sighash);
+ assert_eq!(expected_hash_ty, hash_ty);
+ assert_eq!(expected_key_spend_sig, key_spend_sig);
+
+ let tweaked_priv_key = tweaked_keypair.to_private_key();
+ assert_eq!(PrivateKey::from_secp(expected.tweaked_privkey), tweaked_priv_key);
+ }
+}
### bitcoin/tests/taproot.rs
@@ -0,0 +1,105 @@
+//! Tests for taproot types. Data lives in `tests/data`, which is excluded when publishing.
+
+use bitcoin::ext::*;
+use bitcoin::key::TapTweak as _;
+use bitcoin::taproot::{
+ ControlBlock, LeafVersion, TapLeafHash, TapNodeHash, TapTweakHash, TaprootBuilder,
+};
+use bitcoin::{Address, KnownHrp, ScriptPubKeyBuf, TapScriptBuf, XOnlyPublicKey};
+
+#[test]
+fn bip_341_tests() {
+ fn process_script_trees(
+ v: &serde_json::Value,
+ mut builder: TaprootBuilder,
+ leaves: &mut Vec<(TapScriptBuf, LeafVersion)>,
+ depth: u8,
+ ) -> TaprootBuilder {
+ if v.is_null() {
+ // nothing to push
+ } else if v.is_array() {
+ for leaf in v.as_array().unwrap() {
+ builder = process_script_trees(leaf, builder, leaves, depth + 1);
+ }
+ } else {
+ let script =
+ TapScriptBuf::from_hex_no_length_prefix(v["script"].as_str().unwrap()).unwrap();
+ let ver =
+ LeafVersion::from_consensus(v["leafVersion"].as_u64().unwrap() as u8).unwrap();
+ leaves.push((script.clone(), ver));
+ builder = builder.add_leaf_with_ver(depth, script, ver).unwrap();
+ }
+ builder
+ }
+
+ let data = bip_341_read_json();
+ // Check the version of data
+ assert!(data["version"] == 1);
+
+ for arr in data["scriptPubKey"].as_array().unwrap() {
+ let internal_key =
+ arr["given"]["internalPubkey"].as_str().unwrap().parse::<XOnlyPublicKey>().unwrap();
+ // process the tree
+ let script_tree = &arr["given"]["scriptTree"];
+ let mut merkle_root = None;
+ if script_tree.is_null() {
+ assert!(arr["intermediary"]["merkleRoot"].is_null());
+ } else {
+ merkle_root = Some(
+ arr["intermediary"]["merkleRoot"].as_str().unwrap().parse::<TapNodeHash>().unwrap(),
+ );
+ let leaf_hashes = arr["intermediary"]["leafHashes"].as_array().unwrap();
+ let ctrl_blks = arr["expected"]["scriptPathControlBlocks"].as_array().unwrap();
+ let mut builder = TaprootBuilder::new();
+ let mut leaves = vec![];
+ builder = process_script_trees(script_tree, builder, &mut leaves, 0);
+ let spend_info = builder.finalize(internal_key).unwrap();
+ for (i, script_ver) in leaves.iter().enumerate() {
+ let expected_leaf_hash = leaf_hashes[i].as_str().unwrap();
+ let expected_ctrl_blk =
+ ControlBlock::from_hex(ctrl_blks[i].as_str().unwrap()).unwrap();
+
+ let leaf_hash = TapLeafHash::from_script(&script_ver.0, script_ver.1);
+ let ctrl_blk = spend_info.control_block(script_ver).unwrap();
+ assert_eq!(leaf_hash.to_string(), expected_leaf_hash);
+ assert_eq!(ctrl_blk, expected_ctrl_blk);
+ }
+ }
+ let expected_output_key = arr["intermediary"]["tweakedPubkey"]
+ .as_str()
+ .unwrap()
+ .parse::<XOnlyPublicKey>()
+ .unwrap();
+ let expected_tweak =
+ arr["intermediary"]["tweak"].as_str().unwrap().parse::<TapTweakHash>().unwrap();
+ let expected_spk = ScriptPubKeyBuf::from_hex_no_length_prefix(
+ arr["expected"]["scriptPubKey"].as_str().unwrap(),
+ )
+ .unwrap();
+ let expected_addr = arr["expected"]["bip350Address"]
+ .as_str()
+ .unwrap()
+ .parse::<Address<_>>()
+ .unwrap()
+ .assume_checked();
+
+ let tweak = TapTweakHash::from_key_and_merkle_root(internal_key, merkle_root);
+ let output_key = internal_key.tap_tweak(merkle_root);
+ let addr = Address::p2tr(internal_key, merkle_root, KnownHrp::Mainnet);
+ let spk = addr.script_pubkey();
+
+ // Compare just the key bytes, not the parity
+ assert_eq!(
+ expected_output_key.serialize().0,
+ output_key.to_x_only_public_key().serialize().0
+ );
+ assert_eq!(expected_tweak, tweak);
+ assert_eq!(expected_addr, addr);
+ assert_eq!(expected_spk, spk);
+ }
+}
+
+fn bip_341_read_json() -> serde_json::Value {
+ let json_str = include_str!("data/bip341_tests.json");
+ serde_json::from_str(json_str).expect("JSON was not well-formatted")
+}
### bitcoin/tests/transaction.rs
@@ -0,0 +1,9 @@
+//! Tests for `Transaction`. Data lives in `tests/data`, which is excluded when publishing.
+
+use bitcoin::Transaction;
+
+#[test]
+fn huge_witness() {
+ let hex = hex::decode_to_vec(include_str!("data/huge_witness.hex").trim()).unwrap();
+ encoding::decode_from_slice::<Transaction>(&hex).unwrap();
+}
### p2p/src/merkle_tree.rs
@@ -671,8 +671,7 @@ impl<'a> Arbitrary<'a> for MerkleBlock {
mod tests {
use core::cmp;
- use hex::{hex, DisplayHex};
- use primitives::block::Unchecked;
+ use hex::hex;
use super::*;
use crate::hex;
@@ -822,86 +821,6 @@ mod tests {
assert!(result.is_err());
}
- #[test]
- fn merkleblock_serialization() {
- // Got it by running the rpc call
- // `gettxoutproof '["220ebc64e21abece964927322cba69180ed853bb187fbc6923bac7d010b9d87a"]'`
- let mb_hex = include_str!("../tests/data/merkle_block.hex");
-
- let bytes = hex::decode_to_vec(mb_hex).unwrap();
- let mb: MerkleBlock = encoding::decode_from_slice(&bytes).unwrap();
- assert_eq!(get_block_13b8a().block_hash(), mb.header.block_hash());
- assert_eq!(
- mb.header.merkle_root,
- mb.txn.extract_matches(&mut vec![], &mut vec![]).unwrap()
- );
- // Serialize again and check that it matches the original bytes
- assert_eq!(mb_hex, encoding::encode_to_vec(&mb).to_lower_hex_string().as_str());
- }
-
- /// Constructs a new [`MerkleBlock`] using a list of txids which will be found in the
- /// given block.
- #[test]
- fn merkleblock_construct_from_txids_found() {
- let block = get_block_13b8a();
-
- let txids: Vec<Txid> = [
- "74d681e0e03bafa802c8aa084379aa98d9fcd632ddc2ed9782b586ec87451f20",
- "f9fc751cb7dc372406a9f8d738d5e6f8f63bab71986a39cf36ee70ee17036d07",
- ]
- .iter()
- .map(|hex| hex.parse::<Txid>().unwrap())
- .collect();
-
- let txid1 = txids[0];
- let txid2 = txids[1];
- let txids = [txid1, txid2];
-
- let merkle_block = MerkleBlock::from_block_with_predicate(&block, |t| txids.contains(t));
-
- assert_eq!(merkle_block.header.block_hash(), block.block_hash());
-
- let mut matches: Vec<Txid> = vec![];
- let mut index: Vec<u32> = vec![];
-
- assert_eq!(
- merkle_block.txn.extract_matches(&mut matches, &mut index).unwrap(),
- block.header().merkle_root
- );
- assert_eq!(matches.len(), 2);
-
- // Ordered by occurrence in depth-first tree traversal.
- assert_eq!(matches[0], txid2);
- assert_eq!(index[0], 1);
-
- assert_eq!(matches[1], txid1);
- assert_eq!(index[1], 8);
- }
-
- /// Constructs a new [`MerkleBlock`] using a list of txids which will not be found in the given block
- #[test]
- fn merkleblock_construct_from_txids_not_found() {
- let block = get_block_13b8a();
- let txids: Vec<Txid> = ["c0ffee00003bafa802c8aa084379aa98d9fcd632ddc2ed9782b586ec87451f20"]
- .iter()
- .map(|hex| hex.parse::<Txid>().unwrap())
- .collect();
-
- let merkle_block = MerkleBlock::from_block_with_predicate(&block, |t| txids.contains(t));
-
- assert_eq!(merkle_block.header.block_hash(), block.block_hash());
-
- let mut matches: Vec<Txid> = vec![];
- let mut index: Vec<u32> = vec![];
-
- assert_eq!(
- merkle_block.txn.extract_matches(&mut matches, &mut index).unwrap(),
- block.header().merkle_root
- );
- assert_eq!(matches.len(), 0);
- assert_eq!(index.len(), 0);
- }
-
impl PartialMerkleTree {
/// Flip one bit in one of the hashes - this should break the authentication
fn damage(&mut self, rng: &mut LcgPrng) {
@@ -914,15 +833,6 @@ mod tests {
}
}
- /// Returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af)
- /// with 9 txs.
- fn get_block_13b8a() -> Block<Checked> {
- let block_hex = include_str!("../tests/data/block_13b8a.hex");
- let block: Block<Unchecked> =
- encoding::decode_from_slice(&hex::decode_to_vec(block_hex).unwrap()).unwrap();
- block.validate().expect("block should be valid")
- }
-
macro_rules! check_calc_tree_width {
($($test_name:ident, $num_transactions:literal, $height:literal, $expected_width:literal);* $(;)?) => {
$(
### p2p/tests/merkle_tree.rs
@@ -0,0 +1,92 @@
+//! Tests for `MerkleBlock`. Data lives in `tests/data`, which is excluded when publishing.
+
+use bitcoin_p2p_messages::merkle_tree::MerkleBlock;
+use hex::DisplayHex;
+use primitives::block::{Block, Checked, Unchecked};
+use primitives::Txid;
+
+/// Returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af)
+/// with 9 txs.
+fn get_block_13b8a() -> Block<Checked> {
+ let block_hex = include_str!("data/block_13b8a.hex");
+ let block: Block<Unchecked> =
+ encoding::decode_from_slice(&hex::decode_to_vec(block_hex).unwrap()).unwrap();
+ block.validate().expect("block should be valid")
+}
+
+#[test]
+fn merkleblock_serialization() {
+ // Got it by running the rpc call
+ // `gettxoutproof '["220ebc64e21abece964927322cba69180ed853bb187fbc6923bac7d010b9d87a"]'`
+ let mb_hex = include_str!("data/merkle_block.hex");
+
+ let bytes = hex::decode_to_vec(mb_hex).unwrap();
+ let mb: MerkleBlock = encoding::decode_from_slice(&bytes).unwrap();
+ assert_eq!(get_block_13b8a().block_hash(), mb.header.block_hash());
+ assert_eq!(mb.header.merkle_root, mb.txn.extract_matches(&mut vec![], &mut vec![]).unwrap());
+ // Serialize again and check that it matches the original bytes
+ assert_eq!(mb_hex, encoding::encode_to_vec(&mb).to_lower_hex_string().as_str());
+}
+
+/// Constructs a new [`MerkleBlock`] using a list of txids which will be found in the
+/// given block.
+#[test]
+fn merkleblock_construct_from_txids_found() {
+ let block = get_block_13b8a();
+
+ let txids: Vec<Txid> = [
+ "74d681e0e03bafa802c8aa084379aa98d9fcd632ddc2ed9782b586ec87451f20",
+ "f9fc751cb7dc372406a9f8d738d5e6f8f63bab71986a39cf36ee70ee17036d07",
+ ]
+ .iter()
+ .map(|hex| hex.parse::<Txid>().unwrap())
+ .collect();
+
+ let txid1 = txids[0];
+ let txid2 = txids[1];
+ let txids = [txid1, txid2];
+
+ let merkle_block = MerkleBlock::from_block_with_predicate(&block, |t| txids.contains(t));
+
+ assert_eq!(merkle_block.header.block_hash(), block.block_hash());
+
+ let mut matches: Vec<Txid> = vec![];
+ let mut index: Vec<u32> = vec![];
+
+ assert_eq!(
+ merkle_block.txn.extract_matches(&mut matches, &mut index).unwrap(),
+ block.header().merkle_root
+ );
+ assert_eq!(matches.len(), 2);
+
+ // Ordered by occurrence in depth-first tree traversal.
+ assert_eq!(matches[0], txid2);
+ assert_eq!(index[0], 1);
+
+ assert_eq!(matches[1], txid1);
+ assert_eq!(index[1], 8);
+}
+
+/// Constructs a new [`MerkleBlock`] using a list of txids which will not be found in the given block
+#[test]
+fn merkleblock_construct_from_txids_not_found() {
+ let block = get_block_13b8a();
+ let txids: Vec<Txid> = ["c0ffee00003bafa802c8aa084379aa98d9fcd632ddc2ed9782b586ec87451f20"]
+ .iter()
+ .map(|hex| hex.parse::<Txid>().unwrap())
+ .collect();
+
+ let merkle_block = MerkleBlock::from_block_with_predicate(&block, |t| txids.contains(t));
+
+ assert_eq!(merkle_block.header.block_hash(), block.block_hash());
+
+ let mut matches: Vec<Txid> = vec![];
+ let mut index: Vec<u32> = vec![];
+
+ assert_eq!(
+ merkle_block.txn.extract_matches(&mut matches, &mut index).unwrap(),
+ block.header().merkle_root
+ );
+ assert_eq!(matches.len(), 0);
+ assert_eq!(index.len(), 0);
+}Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.