11#![ cfg_attr( feature = "libfuzzer_fuzz" , no_main) ]
22
3- use std:: collections:: BTreeMap ;
4-
5- use arbitrary:: { Arbitrary , Unstructured } ;
6- use bdk_chain:: bitcoin:: block:: { Header , Version } ;
73use bdk_chain:: bitcoin:: hashes:: Hash ;
8- use bdk_chain:: bitcoin:: { BlockHash , CompactTarget , TxMerkleNode } ;
9- use bdk_chain:: local_chain:: { ChangeSet , LocalChain , MissingGenesisError } ;
10- use bdk_chain:: { BlockId , CheckPoint } ;
11-
12- fn arbitrary_hash ( u : & mut Unstructured ) -> arbitrary:: Result < BlockHash > {
13- Ok ( BlockHash :: from_byte_array ( <[ u8 ; 32 ] >:: arbitrary ( u) ?) )
14- }
15-
16- /// Builds a header for `apply_header(_connected_to)`. Its `prev_blockhash` is usually taken
17- /// from an existing checkpoint (so the header connects to the chain), sometimes arbitrary.
18- fn arbitrary_header ( u : & mut Unstructured , chain : & LocalChain ) -> arbitrary:: Result < ( Header , u32 ) > {
19- let ( prev_blockhash, height) = if u. ratio ( 3 , 4 ) ? {
20- let block_ids: Vec < BlockId > = chain. iter_checkpoints ( ) . map ( |cp| cp. block_id ( ) ) . collect ( ) ;
21- let connect_at = u. choose ( & block_ids) ?;
22- ( connect_at. hash , connect_at. height . saturating_add ( 1 ) )
23- } else {
24- ( arbitrary_hash ( u) ?, u32:: arbitrary ( u) ?)
25- } ;
26- let header = Header {
27- version : Version :: from_consensus ( i32:: arbitrary ( u) ?) ,
28- prev_blockhash,
29- merkle_root : TxMerkleNode :: from_byte_array ( <[ u8 ; 32 ] >:: arbitrary ( u) ?) ,
30- time : u32:: arbitrary ( u) ?,
31- bits : CompactTarget :: from_consensus ( u32:: arbitrary ( u) ?) ,
32- nonce : u32:: arbitrary ( u) ?,
33- } ;
34- Ok ( ( header, height) )
4+ use bdk_chain:: bitcoin:: BlockHash ;
5+ use bdk_chain:: local_chain:: { LocalChain , MissingGenesisError } ;
6+ use bdk_chain:: BlockId ;
7+ use bdk_chain_fuzz:: arbitrary:: { self , Arbitrary , Unstructured } ;
8+ use bdk_chain_fuzz:: checks:: { assert_changeset_against_chains, assert_checkpoint_order} ;
9+
10+ /// An operation to perform against the chain under test.
11+ #[ derive( Arbitrary , Debug , Clone , Copy ) ]
12+ enum Op {
13+ /// `apply_update` with an independently constructed chain as the update.
14+ ApplyUpdate ,
15+ /// `insert_block` with an arbitrary height and hash.
16+ InsertBlock ,
17+ /// `disconnect_from` an existing checkpoint or an arbitrary block id.
18+ DisconnectFrom ,
19+ /// `apply_header` with a header that usually connects to an existing checkpoint.
20+ ApplyHeader ,
21+ /// `apply_header_connected_to` with an arbitrarily picked connection point.
22+ ApplyHeaderConnectedTo ,
23+ /// `apply_update` with an update derived by mutating the chain's own tip, so the
24+ /// update shares `Arc` nodes with the original and exercises `merge_chains`'
25+ /// `eq_ptr` fast path.
26+ ApplyDerivedUpdate ,
3527}
3628
37- fn arbitrary_chain ( u : & mut Unstructured ) -> arbitrary:: Result < Option < LocalChain > > {
38- let raw_blocks: BTreeMap < u32 , [ u8 ; 32 ] > = BTreeMap :: arbitrary ( u) ?;
39- println ! ( "{:#?}" , raw_blocks) ;
40- let blocks: BTreeMap < u32 , BlockHash > = raw_blocks
41- . into_iter ( )
42- . map ( |( height, hash) | ( height, BlockHash :: from_byte_array ( hash) ) )
43- . collect ( ) ;
44-
45- let constructed = match u. int_in_range ( 0 ..=2 ) ? {
46- 0 => LocalChain :: from_blocks ( blocks. clone ( ) ) . ok ( ) ,
47- 1 => {
48- let changeset = ChangeSet {
49- blocks : blocks. iter ( ) . map ( |( & h, & hash) | ( h, Some ( hash) ) ) . collect ( ) ,
50- } ;
51- LocalChain :: from_changeset ( changeset) . ok ( )
52- }
53- _ => CheckPoint :: from_blocks ( blocks. clone ( ) )
54- . ok ( )
55- . and_then ( |tip| LocalChain :: from_tip ( tip) . ok ( ) ) ,
56- } ;
57- let chain = match constructed {
58- Some ( chain) => chain,
59- None => return Ok ( None ) ,
60- } ;
61-
62- let tip = chain. tip ( ) ;
63- let ( & tip_height, & tip_hash) = blocks. last_key_value ( ) . expect ( "chain is non-empty" ) ;
64- assert_eq ! ( tip. block_id( ) . height, tip_height) ;
65- assert_eq ! ( tip. block_id( ) . hash, tip_hash) ;
66- assert_eq ! ( chain. genesis_hash( ) , blocks[ & 0 ] ) ;
67-
68- Ok ( Some ( chain) )
69- }
70-
71- /// Picks a block id for an operation: either a checkpoint of `chain` or an arbitrary one.
72- fn arbitrary_block_id ( u : & mut Unstructured , chain : & LocalChain ) -> arbitrary:: Result < BlockId > {
73- if bool:: arbitrary ( u) ? {
74- let block_ids: Vec < BlockId > = chain. iter_checkpoints ( ) . map ( |cp| cp. block_id ( ) ) . collect ( ) ;
75- Ok ( * u. choose ( & block_ids) ?)
76- } else {
77- Ok ( BlockId {
78- height : u32:: arbitrary ( u) ?,
79- hash : arbitrary_hash ( u) ?,
80- } )
81- }
82- }
83-
84- /// On success, `pre`-state plus the returned changeset must reconstruct the post-state.
85- /// On failure, the chain must be left untouched.
86- fn check_op_result < E > ( pre : LocalChain , post : & LocalChain , result : & Result < ChangeSet , E > ) {
87- match result {
88- Ok ( changeset) => {
89- let mut reconstructed = pre;
90- reconstructed
91- . apply_changeset ( changeset)
92- . expect ( "applying an op's changeset to the pre-state must succeed" ) ;
93- assert_eq ! ( & reconstructed, post) ;
94- }
95- Err ( _) => assert_eq ! ( & pre, post, "a failed op must not modify the chain" ) ,
96- }
97- }
98-
99- fn check_chain ( chain : & LocalChain ) {
100- let heights: Vec < u32 > = chain. iter_checkpoints ( ) . map ( |cp| cp. height ( ) ) . collect ( ) ;
101- assert ! (
102- heights. windows( 2 ) . all( |w| w[ 0 ] > w[ 1 ] ) ,
103- "checkpoint heights must be strictly decreasing from tip"
104- ) ;
105- assert_eq ! ( heights. last( ) , Some ( & 0 ) , "genesis must be present" ) ;
29+ fn assert_chain ( chain : & LocalChain ) {
30+ assert_checkpoint_order ( chain) ;
10631
10732 let tip = chain. chain_tip ( ) ;
10833 assert_eq ! ( tip, chain. tip( ) . block_id( ) ) ;
@@ -137,7 +62,7 @@ fn do_test(data: &[u8]) {
13762 let mut chain: Option < LocalChain > = None ;
13863 for _ in 0 ..op_count {
13964 if chain. is_none ( ) {
140- match arbitrary_chain ( & mut u) {
65+ match arbitrary :: blockhash_chain ( & mut u) {
14166 Ok ( Some ( initial) ) => chain = Some ( initial) ,
14267 Ok ( None ) => continue ,
14368 Err ( _) => break ,
@@ -146,30 +71,30 @@ fn do_test(data: &[u8]) {
14671 }
14772 let chain = chain. as_mut ( ) . expect ( "initialized above" ) ;
14873
149- let op = match u . int_in_range :: < u8 > ( 0 ..= 5 ) {
74+ let op = match Op :: arbitrary ( & mut u ) {
15075 Ok ( op) => op,
15176 Err ( _) => break ,
15277 } ;
15378 let pre = chain. clone ( ) ;
15479 match op {
155- 0 => {
156- let update = match arbitrary_chain ( & mut u) {
80+ Op :: ApplyUpdate => {
81+ let update = match arbitrary :: blockhash_chain ( & mut u) {
15782 Ok ( Some ( update) ) => update,
15883 Ok ( None ) => continue ,
15984 Err ( _) => break ,
16085 } ;
16186 let result = chain. apply_update ( update. tip ( ) ) ;
162- check_op_result ( pre, chain, & result) ;
87+ assert_changeset_against_chains ( pre, chain, & result) ;
16388 }
164- 1 => {
89+ Op :: InsertBlock => {
16590 let ( height, hash) = match u32:: arbitrary ( & mut u)
166- . and_then ( |height| arbitrary_hash ( & mut u) . map ( |hash| ( height, hash) ) )
91+ . and_then ( |height| arbitrary :: hash ( & mut u) . map ( |hash| ( height, hash) ) )
16792 {
16893 Ok ( block) => block,
16994 Err ( _) => break ,
17095 } ;
17196 let result = chain. insert_block ( height, hash) ;
172- check_op_result ( pre, chain, & result) ;
97+ assert_changeset_against_chains ( pre, chain, & result) ;
17398 match & result {
17499 Ok ( _) => {
175100 assert_eq ! ( chain. get( height) . map( |cp| cp. hash( ) ) , Some ( hash) ) ;
@@ -183,13 +108,13 @@ fn do_test(data: &[u8]) {
183108 }
184109 }
185110 }
186- 2 => {
187- let block_id = match arbitrary_block_id ( & mut u, chain) {
111+ Op :: DisconnectFrom => {
112+ let block_id = match arbitrary :: block_id ( & mut u, chain, & [ ] ) {
188113 Ok ( block_id) => block_id,
189114 Err ( _) => break ,
190115 } ;
191116 let result = chain. disconnect_from ( block_id) ;
192- check_op_result ( pre, chain, & result) ;
117+ assert_changeset_against_chains ( pre, chain, & result) ;
193118 match & result {
194119 Ok ( changeset) if !changeset. blocks . is_empty ( ) => {
195120 assert ! ( chain. tip( ) . height( ) < block_id. height) ;
@@ -201,46 +126,44 @@ fn do_test(data: &[u8]) {
201126 }
202127 }
203128 }
204- 3 => {
205- let ( header, height) = match arbitrary_header ( & mut u, chain) {
129+ Op :: ApplyHeader => {
130+ let ( header, height) = match arbitrary :: connectable_header ( & mut u, chain) {
206131 Ok ( header) => header,
207132 Err ( _) => break ,
208133 } ;
209134 let result = chain. apply_header ( & header, height) ;
210- check_op_result ( pre, chain, & result) ;
135+ assert_changeset_against_chains ( pre, chain, & result) ;
211136 if result. is_ok ( ) {
212137 assert_eq ! (
213138 chain. get( height) . map( |cp| cp. hash( ) ) ,
214139 Some ( header. block_hash( ) )
215140 ) ;
216141 }
217142 }
218- 4 => {
143+ Op :: ApplyHeaderConnectedTo => {
219144 let params: arbitrary:: Result < _ > = ( || {
220- let ( header, height) = arbitrary_header ( & mut u, chain) ?;
221- let connected_to = arbitrary_block_id ( & mut u, chain) ?;
145+ let ( header, height) = arbitrary :: connectable_header ( & mut u, chain) ?;
146+ let connected_to = arbitrary :: block_id ( & mut u, chain, & [ ] ) ?;
222147 Ok ( ( header, height, connected_to) )
223148 } ) ( ) ;
224149 let ( header, height, connected_to) = match params {
225150 Ok ( params) => params,
226151 Err ( _) => break ,
227152 } ;
228153 let result = chain. apply_header_connected_to ( & header, height, connected_to) ;
229- check_op_result ( pre, chain, & result) ;
154+ assert_changeset_against_chains ( pre, chain, & result) ;
230155 if result. is_ok ( ) {
231156 assert_eq ! (
232157 chain. get( height) . map( |cp| cp. hash( ) ) ,
233158 Some ( header. block_hash( ) )
234159 ) ;
235160 }
236161 }
237- _ => {
238- // Derived update: mutate the chain's own tip so the update shares `Arc`
239- // nodes with the original, exercising `merge_chains`' `eq_ptr` fast path.
162+ Op :: ApplyDerivedUpdate => {
240163 let params: arbitrary:: Result < _ > = ( || {
241164 let insert = bool:: arbitrary ( & mut u) ?;
242165 let height = u32:: arbitrary ( & mut u) ?;
243- let hash = arbitrary_hash ( & mut u) ?;
166+ let hash = arbitrary :: hash ( & mut u) ?;
244167 Ok ( ( insert, height, hash) )
245168 } ) ( ) ;
246169 let ( insert, height, hash) = match params {
@@ -262,54 +185,18 @@ fn do_test(data: &[u8]) {
262185 }
263186 } ;
264187 let result = chain. apply_update ( update_tip) ;
265- check_op_result ( pre, chain, & result) ;
188+ assert_changeset_against_chains ( pre, chain, & result) ;
266189 if result. is_ok ( ) {
267190 assert_eq ! ( chain. get( height) . map( |cp| cp. hash( ) ) , Some ( hash) ) ;
268191 }
269192 }
270193 }
271- check_chain ( chain) ;
194+ assert_chain ( chain) ;
272195 }
273196
274197 if let Some ( chain) = chain {
275- check_chain ( & chain) ;
276- }
277- }
278-
279- #[ cfg( feature = "afl_fuzz" ) ]
280- #[ macro_use]
281- extern crate afl;
282- #[ cfg( feature = "afl_fuzz" ) ]
283- fn main ( ) {
284- fuzz ! ( |data| { do_test( data) } ) ;
285- }
286-
287- #[ cfg( feature = "honggfuzz_fuzz" ) ]
288- #[ macro_use]
289- extern crate honggfuzz;
290- #[ cfg( feature = "honggfuzz_fuzz" ) ]
291- fn main ( ) {
292- loop {
293- fuzz ! ( |data| { do_test( data) } ) ;
198+ assert_chain ( & chain) ;
294199 }
295200}
296201
297- #[ cfg( feature = "libfuzzer_fuzz" ) ]
298- #[ macro_use]
299- extern crate libfuzzer_sys;
300- #[ cfg( feature = "libfuzzer_fuzz" ) ]
301- fuzz_target ! ( |data: & [ u8 ] | do_test( data) ) ;
302-
303- /// Replays corpus files passed as arguments. Used for coverage reports and
304- /// reproducing crashes without a fuzzer attached.
305- #[ cfg( not( any(
306- feature = "afl_fuzz" ,
307- feature = "honggfuzz_fuzz" ,
308- feature = "libfuzzer_fuzz"
309- ) ) ) ]
310- fn main ( ) {
311- for path in std:: env:: args ( ) . skip ( 1 ) {
312- let data = std:: fs:: read ( & path) . unwrap_or_else ( |e| panic ! ( "failed to read {path}: {e}" ) ) ;
313- do_test ( & data) ;
314- }
315- }
202+ bdk_chain_fuzz:: fuzz_main!( do_test) ;
0 commit comments