Skip to content

Commit f5d2a68

Browse files
committed
feat(fuzz): add initial fuzz testing
- creates a new `fuzz` crate, to run fuzz testing with `cargo fuzz` (libFuzzer). - creates an initial `fuzz` target for wallet. - creates initial utilities methods, following the standard of a fuzz data provider.
1 parent 9dff4e8 commit f5d2a68

File tree

7 files changed

+444
-0
lines changed

7 files changed

+444
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,9 @@ Cargo.lock
88
# Example persisted files.
99
*.db
1010
*.sqlite*
11+
12+
# fuzz testing related
13+
fuzz/target
14+
fuzz/corpus
15+
fuzz/artifacts
16+
fuzz/coverage

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
resolver = "2"
33
members = [
44
"wallet",
5+
"fuzz",
56
"examples/example_wallet_electrum",
67
"examples/example_wallet_esplora_blocking",
78
"examples/example_wallet_esplora_async",

fuzz/Cargo.toml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[package]
2+
name = "bdk_wallet_fuzz"
3+
homepage = "https://bitcoindevkit.org"
4+
version = "0.0.1-alpha.0"
5+
repository = "https://github.com/bitcoindevkit/bdk_wallet"
6+
description = "A fuzz testing library for the Bitcoin Development Kit Wallet"
7+
keywords = ["fuzz", "testing", "fuzzing", "bitcoin", "wallet"]
8+
publish = false
9+
readme = "README.md"
10+
authors = ["Bitcoin Dev Kit Developers"]
11+
edition = "2021"
12+
license = "MIT OR Apache-2.0"
13+
14+
[package.metadata]
15+
cargo-fuzz = true
16+
17+
[dependencies]
18+
libfuzzer-sys = "0.4"
19+
bdk_wallet = { path = "../wallet" }
20+
21+
[[bin]]
22+
name = "wallet"
23+
path = "fuzz_targets/wallet.rs"
24+
test = false
25+
doc = false
26+
bench = false
27+
28+
[[bin]]
29+
name = "test"
30+
path = "fuzz_targets/test.rs"
31+
test = false
32+
doc = false
33+
bench = false

fuzz/fuzz_targets/test.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![no_main]
2+
3+
use libfuzzer_sys::fuzz_target;
4+
5+
fuzz_target!(|data: &[u8]| {
6+
panic!();
7+
// fuzzed code goes here
8+
});

fuzz/fuzz_targets/wallet.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#![no_main]
2+
3+
use libfuzzer_sys::fuzz_target;
4+
use std::collections::{BTreeMap, VecDeque};
5+
6+
use bdk_wallet::{bitcoin::Network, chain::TxUpdate, CreateParams, KeychainKind, Update, Wallet};
7+
use bdk_wallet_fuzz::utils::*;
8+
9+
// testnet descriptors
10+
const INTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
11+
const EXTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
12+
13+
// mainnet descriptors
14+
// const INTERNAL_DESCRIPTOR: &str = "wpkh(xprv9y5m1SxNcjAY8DJPHqXM67ETRFwpjsacG9xGBiTBMj5A2KupsjuNJuFuFJAzoQJb7fjp3jz78TsmDmqpaTtCBzAKEuqE1NMC3Net5Ma2hY6/84'/1'/0'/0/*)";
15+
// const EXTERNAL_DESCRIPTOR: &str = "wpkh(xprv9y5m1SxNcjAY8DJPHqXM67ETRFwpjsacG9xGBiTBMj5A2KupsjuNJuFuFJAzoQJb7fjp3jz78TsmDmqpaTtCBzAKEuqE1NMC3Net5Ma2hY6/84'/1'/0'/1/*)";
16+
17+
// testnet
18+
const NETWORK: Network = Network::Testnet;
19+
20+
// mainnet
21+
// const NETWORK = Network::Testnet;
22+
23+
fuzz_target!(|data: &[u8]| {
24+
// let data_iter = data.iter();
25+
let params = CreateParams::new(INTERNAL_DESCRIPTOR, EXTERNAL_DESCRIPTOR).network(NETWORK);
26+
let mut wallet = match Wallet::create_with_params(params) {
27+
Ok(wallet) => wallet,
28+
Err(_) => panic!(),
29+
};
30+
31+
let mut unconfirmed_txids = VecDeque::new();
32+
33+
// fuzzed code goes here
34+
35+
// fuzz test wallet updates
36+
37+
// start with active indices.
38+
let mut last_active_indices = BTreeMap::new();
39+
last_active_indices.extend(get_last_active_indices(data, KeychainKind::Internal));
40+
last_active_indices.extend(get_last_active_indices(data, KeychainKind::External));
41+
42+
// generate the txs for the tx graph
43+
let txs = get_txs(data, &mut wallet);
44+
let _ = txs
45+
.iter()
46+
.map(|tx| unconfirmed_txids.push_back(tx.compute_txid()));
47+
48+
let txouts = get_txouts(data);
49+
50+
let anchors = get_anchors(data, unconfirmed_txids.clone());
51+
// if let Some(txid) = unconfirmed_txids.pop_front() {
52+
// anchors.insert((anchor, txid));
53+
// };
54+
55+
let seen_ats = get_seen_ats(data, unconfirmed_txids.clone());
56+
let evicted_ats = get_evicted_ats(data, unconfirmed_txids);
57+
58+
// build the tx update with fuzzed data
59+
let mut tx_update = TxUpdate::default();
60+
tx_update.txs = txs;
61+
tx_update.txouts = txouts;
62+
tx_update.anchors = anchors;
63+
tx_update.seen_ats = seen_ats;
64+
tx_update.evicted_ats = evicted_ats;
65+
66+
// generate the chain/checkpoints
67+
68+
let chain = get_checkpoint(data);
69+
70+
let update = Update {
71+
last_active_indices,
72+
tx_update: tx_update,
73+
chain: chain,
74+
};
75+
76+
match wallet.apply_update(update.clone()) {
77+
Ok(_result) => {
78+
// println!("{:#?}", update);
79+
// println!("successfully updated wallet")
80+
}
81+
Err(e) => {
82+
// println!("{:#?}", update)
83+
}
84+
};
85+
});
86+
87+
// let txouts_count = *next_or_return!(data_iter) as usize;
88+
// let mut txouts = BTreeMap::new();
89+
// for _ in 0..txouts_count {
90+
// let outpoint = bitcoin::OutPoint::new(
91+
// unique_hash.get_txid(),
92+
// *next_or_return!(data_iter) as u32,
93+
// );
94+
// let amount = *next_or_return!(data_iter) as u64 * 1_000;
95+
// let value = bitcoin::Amount::from_sat(amount);
96+
// txouts.insert(
97+
// outpoint,
98+
// bitcoin::TxOut {
99+
// value,
100+
// script_pubkey: Default::default(),
101+
// },
102+
// );
103+
// }
104+
105+
// let mut anchors = BTreeSet::new();
106+
// while next_or_return!(data_iter) & 0x01 == 0x01 {
107+
// let height = scale(*next_or_return!(data_iter));
108+
// let hash = unique_hash.get_block_hash();
109+
// let block_id = BlockId { height, hash };
110+
// let confirmation_time = scale_u64(*next_or_return!(data_iter));
111+
// let anchor = ConfirmationBlockTime {
112+
// block_id,
113+
// confirmation_time,
114+
// };
115+
// // FIXME: inserting anchors for transactions not in the tx graph will fail the
116+
// // SQLite persistence.
117+
// //let txid = unconfirmed_txids
118+
// //.pop_front()
119+
// //.unwrap_or(unique_hash.get_txid());
120+
// if let Some(txid) = unconfirmed_txids.pop_front() {
121+
// anchors.insert((anchor, txid));
122+
// } else {
123+
// break;
124+
// }
125+
// }
126+
127+
// let mut seen_ats = HashMap::new();
128+
// while next_or_return!(data_iter) & 0x01 == 0x01 {
129+
// let time = cmp::min(scale_u64(*next_or_return!(data_iter)), i64::MAX as u64 - 1);
130+
// let txid = unconfirmed_txids
131+
// .pop_front()
132+
// .unwrap_or(unique_hash.get_txid());
133+
// seen_ats.insert(txid, time);
134+
// }
135+
136+
// let tx_update = TxUpdate {
137+
// txs,
138+
// txouts,
139+
// anchors,
140+
// seen_ats,
141+
// };
142+
143+
// // Finally, do the chain update.
144+
// // TODO: sometimes generate invalid updates, reorgs, etc.
145+
// let chain = if next_or_return!(data_iter) & 0x01 == 0x01 {
146+
// let mut tip = wallet.latest_checkpoint();
147+
// let tip_height = tip.height();
148+
// let blocks_count = *next_or_return!(data_iter) as u32;
149+
// for i in 1..blocks_count + 1 {
150+
// tip = tip
151+
// .push(BlockId {
152+
// height: tip_height + i,
153+
// hash: unique_hash.get_block_hash(),
154+
// })
155+
// .unwrap();
156+
// }
157+
// Some(tip)
158+
// } else {
159+
// None
160+
// };
161+
162+
// // The Wallet update should never fail as we only ever create a consistent chain.
163+
// let update = WalletUpdate {
164+
// last_active_indices,
165+
// tx_update,
166+
// chain,
167+
// };
168+
// wallet.apply_update(update).unwrap();
169+
// }
170+
// // Assert the wallet roundtrips to persistence and check some invariants.

fuzz/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod utils;

0 commit comments

Comments
 (0)