Skip to content

Commit 88b7748

Browse files
refactor(fuzz): extract chainstate setup into shared library
Refactor chainstate manager initialization into fuzz_common library for reuse across mutliple fuzz targets. - Add ChainstateSetupConfig and FuzzChainType to common/lib.rs - Extract create_chainstate_manager() and cleanup helpers - Update fizz_target_chainman to use shared utilities
1 parent c2d18f6 commit 88b7748

File tree

3 files changed

+127
-71
lines changed

3 files changed

+127
-71
lines changed

fuzz/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ arbitrary = { version = "1.1", features = ["derive"] }
1414
[dependencies.bitcoinkernel]
1515
path = ".."
1616

17+
[lib]
18+
name = "fuzz_common"
19+
path = "common/lib.rs"
20+
1721
[[bin]]
1822
name = "fuzz_target_chainman"
1923
path = "fuzz_targets/fuzz_target_chainman.rs"

fuzz/common/lib.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use arbitrary::Arbitrary;
2+
use bitcoinkernel::{ChainType, ChainstateManager, ChainstateManagerOptions, Context, KernelError};
3+
use std::sync::{Arc, Once};
4+
5+
#[derive(Debug, Clone, Arbitrary)]
6+
pub enum FuzzChainType {
7+
MAINNET,
8+
TESTNET,
9+
REGTEST,
10+
SIGNET,
11+
}
12+
13+
impl From<FuzzChainType> for ChainType {
14+
fn from(val: FuzzChainType) -> ChainType {
15+
match val {
16+
FuzzChainType::MAINNET => ChainType::Mainnet,
17+
FuzzChainType::TESTNET => ChainType::Testnet,
18+
FuzzChainType::REGTEST => ChainType::Regtest,
19+
FuzzChainType::SIGNET => ChainType::Signet,
20+
}
21+
}
22+
}
23+
24+
#[derive(Debug, Clone, Arbitrary)]
25+
pub struct ChainstateSetupConfig {
26+
pub data_dir: String,
27+
pub chain_type: FuzzChainType,
28+
pub wipe_block_index: bool,
29+
pub wipe_chainstate_index: bool,
30+
pub block_tree_db_in_memory: bool,
31+
pub chainstate_db_in_memory: bool,
32+
pub worker_threads: i32,
33+
}
34+
35+
impl ChainstateSetupConfig {
36+
/// Sanitize the data directory string for filesystem use
37+
pub fn sanitize_data_dir(&self) -> String {
38+
let sanitized: String = self
39+
.data_dir
40+
.chars()
41+
.filter(|c| c.is_alphanumeric() || *c == '_' || *c == '-')
42+
.take(60)
43+
.collect();
44+
45+
if sanitized.is_empty() {
46+
"default".to_string()
47+
} else {
48+
sanitized
49+
}
50+
}
51+
52+
pub fn create_fuzz_data_dir(&self, prefix: &str) -> String {
53+
format!(
54+
"/tmp/rust_kernel_fuzz_{}/{}",
55+
prefix,
56+
self.sanitize_data_dir()
57+
)
58+
}
59+
60+
pub fn create_blocks_dir(&self, data_dir: &str) -> String {
61+
format!("{}/blocks", data_dir)
62+
}
63+
}
64+
65+
static INIT: Once = Once::new();
66+
67+
pub fn init_logging() {
68+
INIT.call_once(|| {
69+
bitcoinkernel::disable_logging();
70+
});
71+
}
72+
73+
pub fn create_chainstate_manager(
74+
context: &Arc<Context>,
75+
config: &ChainstateSetupConfig,
76+
data_dir: &str,
77+
) -> Option<ChainstateManager> {
78+
let blocks_dir = config.create_blocks_dir(data_dir);
79+
80+
let chainman_opts = match ChainstateManagerOptions::new(context, data_dir, &blocks_dir) {
81+
Ok(opts) => opts,
82+
Err(KernelError::CStringCreationFailed(_)) => return None,
83+
Err(err) => panic!("this should never happen: {}", err),
84+
}
85+
.wipe_db(config.wipe_block_index, config.wipe_chainstate_index)
86+
.block_tree_db_in_memory(config.block_tree_db_in_memory)
87+
.chainstate_db_in_memory(config.chainstate_db_in_memory)
88+
.worker_threads(config.worker_threads);
89+
90+
match ChainstateManager::new(chainman_opts) {
91+
Err(KernelError::Internal(_)) => None,
92+
Err(err) => {
93+
cleanup_dir(data_dir);
94+
panic!("this should never happen: {}", err);
95+
}
96+
Ok(chainman) => Some(chainman),
97+
}
98+
}
99+
100+
pub fn cleanup_dir(dir: &str) {
101+
if let Err(e) = std::fs::remove_dir_all(dir) {
102+
if e.kind() != std::io::ErrorKind::NotFound {
103+
eprintln!("Cleanup failed for {}: {}", dir, e);
104+
}
105+
}
106+
}
Lines changed: 17 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
#![no_main]
22

3-
use std::sync::{Arc, Once};
3+
use std::sync::Arc;
44

55
use libfuzzer_sys::fuzz_target;
66

77
use arbitrary::Arbitrary;
88

99
use bitcoinkernel::{
10-
disable_logging,
1110
notifications::types::{BlockValidationStateExt, BlockValidationStateRef},
12-
Block, ChainType, ChainstateManager, ChainstateManagerOptions, Context, ContextBuilder,
13-
KernelError, ValidationMode,
11+
Block, ChainType, Context, ContextBuilder, ValidationMode,
1412
};
13+
use fuzz_common::{cleanup_dir, create_chainstate_manager, init_logging, ChainstateSetupConfig};
1514

16-
fn create_context(chain_type: ChainType) -> Arc<Context> {
15+
pub fn create_context_with_notifications(chain_type: ChainType) -> std::sync::Arc<Context> {
1716
Arc::new(
1817
ContextBuilder::new()
1918
.chain_type(chain_type)
@@ -32,85 +31,32 @@ fn create_context(chain_type: ChainType) -> Arc<Context> {
3231
)
3332
}
3433

35-
#[derive(Debug, Arbitrary)]
36-
pub enum FuzzChainType {
37-
MAINNET,
38-
TESTNET,
39-
REGTEST,
40-
SIGNET,
41-
}
42-
43-
impl Into<ChainType> for FuzzChainType {
44-
fn into(self) -> ChainType {
45-
match self {
46-
FuzzChainType::MAINNET => ChainType::Mainnet,
47-
FuzzChainType::TESTNET => ChainType::Testnet,
48-
FuzzChainType::REGTEST => ChainType::Regtest,
49-
FuzzChainType::SIGNET => ChainType::Signet,
50-
}
51-
}
52-
}
53-
5434
#[derive(Debug, Arbitrary)]
5535
pub struct ChainstateManagerInput {
56-
pub data_dir: String,
57-
pub chain_type: FuzzChainType,
36+
pub setup: ChainstateSetupConfig,
5837
pub blocks: Vec<Vec<u8>>,
59-
pub wipe_block_index: bool,
60-
pub wipe_chainstate_index: bool,
61-
pub block_tree_db_in_memory: bool,
62-
pub chainstate_db_in_memory: bool,
63-
pub worker_threads: i32,
6438
}
6539

66-
static INIT: Once = Once::new();
67-
6840
fuzz_target!(|data: ChainstateManagerInput| {
69-
INIT.call_once(|| {
70-
disable_logging();
71-
});
72-
73-
let context = create_context(data.chain_type.into());
74-
// Sanitize the input string by removing dots and slashes
75-
let sanitized_string: String = data
76-
.data_dir
77-
.chars()
78-
.filter(|c| *c != '.' && *c != '/')
79-
.take(60)
80-
.collect();
41+
init_logging();
42+
let context = create_context_with_notifications(data.setup.chain_type.clone().into());
43+
let data_dir = data.setup.create_fuzz_data_dir("chainstate");
8144

82-
let data_dir = format!("/tmp/rust_kernel_fuzz/{}", sanitized_string);
83-
let blocks_dir = format!("{}/blocks", data_dir);
84-
let chainman_opts = match ChainstateManagerOptions::new(&context, &data_dir, &blocks_dir) {
85-
Ok(opts) => opts,
86-
Err(KernelError::CStringCreationFailed(_)) => return,
87-
Err(err) => panic!("this should never happen: {}", err),
88-
}
89-
.wipe_db(data.wipe_block_index, data.wipe_chainstate_index)
90-
.block_tree_db_in_memory(data.block_tree_db_in_memory)
91-
.chainstate_db_in_memory(data.chainstate_db_in_memory)
92-
.worker_threads(data.worker_threads);
93-
let chainman = match ChainstateManager::new(chainman_opts) {
94-
Err(KernelError::Internal(_)) => {
95-
return;
96-
}
97-
Err(err) => {
98-
let _ = std::fs::remove_dir_all(data_dir);
99-
panic!("this should never happen: {}", err);
100-
}
101-
Ok(chainman) => chainman,
45+
let Some(chainman) = create_chainstate_manager(&context, &data.setup, &data_dir) else {
46+
return;
10247
};
10348

104-
if let Err(err) = chainman.import_blocks() {
105-
let _ = std::fs::remove_dir_all(data_dir);
106-
panic!("this should never happen: {}", err);
49+
if chainman.import_blocks().is_err() {
50+
cleanup_dir(&data_dir);
51+
return;
10752
}
10853

109-
for block in data.blocks {
110-
if let Ok(block) = Block::try_from(block.as_slice()) {
54+
for block_data in blocks {
55+
if let Ok(block) = Block::try_from(block_data.as_slice()) {
11156
let _ = chainman.process_block(&block);
11257
}
11358
}
59+
11460
drop(chainman);
115-
let _ = std::fs::remove_dir_all(data_dir);
61+
cleanup_dir(&data_dir);
11662
});

0 commit comments

Comments
 (0)