diff --git a/Cargo.lock b/Cargo.lock index b0901a87..25897807 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.13" @@ -310,6 +316,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.92" @@ -362,6 +374,33 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.5.4" @@ -467,6 +506,42 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + [[package]] name = "crossbeam" version = "0.8.4" @@ -523,6 +598,12 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -694,6 +775,7 @@ version = "0.2.0" dependencies = [ "bitcoin 0.31.0", "core2 0.4.0", + "criterion", "floresta-common", "hashbrown", "hex", @@ -1049,6 +1131,16 @@ dependencies = [ "regex-syntax 0.8.3", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if 1.0.0", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -1222,6 +1314,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1609,6 +1710,12 @@ dependencies = [ "loom", ] +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + [[package]] name = "overload" version = "0.1.1" @@ -1697,6 +1804,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2340,6 +2475,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" diff --git a/crates/floresta-chain/Cargo.toml b/crates/floresta-chain/Cargo.toml index a0bab3f3..0098ce1f 100644 --- a/crates/floresta-chain/Cargo.toml +++ b/crates/floresta-chain/Cargo.toml @@ -33,6 +33,7 @@ secp256k1 = { version = "*", features = ["alloc"], optional = true } floresta-common = { path = "../floresta-common", default-features = false, features = ["std"] } [dev-dependencies] +criterion = "0.5.1" rand = "0.8.5" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -41,3 +42,7 @@ hex = "0.4.3" [features] bitcoinconsensus = ["bitcoin/bitcoinconsensus"] + +[[bench]] +name = "chain_state_bench" +harness = false diff --git a/crates/floresta-chain/benches/chain_state_bench.rs b/crates/floresta-chain/benches/chain_state_bench.rs new file mode 100644 index 00000000..74e6d40a --- /dev/null +++ b/crates/floresta-chain/benches/chain_state_bench.rs @@ -0,0 +1,100 @@ +use std::collections::HashMap; +use std::io::Cursor; + +use bitcoin::block::Header as BlockHeader; +use bitcoin::consensus::deserialize; +use bitcoin::consensus::Decodable; +use bitcoin::Block; +use criterion::criterion_group; +use criterion::criterion_main; +use criterion::Criterion; +use floresta_chain::pruned_utreexo::UpdatableChainstate; +use floresta_chain::AssumeValidArg; +use floresta_chain::ChainState; +use floresta_chain::KvChainStore; +use floresta_chain::Network; +use rustreexo::accumulator::proof::Proof; + +// Reads the first 151 blocks (or 150 blocks on top of genesis) from blocks.txt, which are regtest +fn read_blocks_txt() -> Vec { + let blocks: Vec<_> = include_str!("../testdata/blocks.txt") + .lines() + .take(151) + .map(|b| deserialize(&hex::decode(b).unwrap()).unwrap()) + .collect(); + + assert_eq!(blocks.len(), 151, "Expected 151 blocks in blocks.txt"); + blocks +} + +pub fn setup_test_chain<'a>( + network: Network, + assume_valid_arg: AssumeValidArg, +) -> ChainState> { + let test_id = rand::random::(); + let chainstore = KvChainStore::new(format!("./data/{test_id}/")).unwrap(); + ChainState::new(chainstore, network, assume_valid_arg) +} + +fn accept_mainnet_headers_benchmark(c: &mut Criterion) { + // Accepts the first 10235 mainnet headers + let file = include_bytes!("../testdata/headers.zst"); + let uncompressed: Vec = zstd::decode_all(Cursor::new(file)).unwrap(); + let mut cursor = Cursor::new(uncompressed); + + // Read all headers into a vector + let mut headers = Vec::new(); + while let Ok(header) = BlockHeader::consensus_decode(&mut cursor) { + headers.push(header); + } + + let chain = setup_test_chain(Network::Bitcoin, AssumeValidArg::Hardcoded); + + c.bench_function("accept_10k_mainnet_headers", |b| { + b.iter(|| { + headers + .iter() + .for_each(|header| chain.accept_header(*header).unwrap()) + }) + }); +} + +fn accept_headers_benchmark(c: &mut Criterion) { + let chain = setup_test_chain(Network::Regtest, AssumeValidArg::Disabled); + let blocks = read_blocks_txt(); + + c.bench_function("accept_150_headers", |b| { + b.iter(|| { + blocks + .iter() + .for_each(|block| chain.accept_header(block.header).unwrap()); + }) + }); +} + +fn connect_blocks_benchmark(c: &mut Criterion) { + let chain = setup_test_chain(Network::Regtest, AssumeValidArg::Disabled); + let blocks = read_blocks_txt(); + + blocks + .iter() + .for_each(|block| chain.accept_header(block.header).unwrap()); + + c.bench_function("connect_150_blocks", |b| { + b.iter(|| { + blocks.iter().for_each(|block| { + chain + .connect_block(block, Proof::default(), HashMap::new(), Vec::new()) + .unwrap(); + }) + }) + }); +} + +criterion_group!( + benches, + accept_mainnet_headers_benchmark, + accept_headers_benchmark, + connect_blocks_benchmark +); +criterion_main!(benches); diff --git a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs index 84abd108..c00b42b0 100644 --- a/crates/floresta-chain/src/pruned_utreexo/chain_state.rs +++ b/crates/floresta-chain/src/pruned_utreexo/chain_state.rs @@ -594,15 +594,15 @@ impl ChainState { } fn check_chain_integrity(&self) { - let best_height = self.get_best_block().expect("should have this loaded").0; + let (best_height, best_hash) = self.get_best_block().expect("should have this loaded"); // make sure our index is right for the latest block - let best_block_heigh = self - .get_disk_block_header(&self.get_best_block().expect("should have this loaded").1) + let best_disk_height = self + .get_disk_block_header(&best_hash) .expect("should have this loaded") .height() .expect("should have this loaded"); - if best_height != best_block_heigh { + if best_height != best_disk_height { self.reindex_chain(); } @@ -1302,20 +1302,23 @@ mod test { use crate::KvChainStore; use crate::Network; + pub fn setup_test_chain<'a>( + network: Network, + assume_valid_arg: AssumeValidArg, + ) -> ChainState> { + let test_id = rand::random::(); + let chainstore = KvChainStore::new(format!("./data/{test_id}/")).unwrap(); + ChainState::new(chainstore, network, assume_valid_arg) + } + #[test] fn accept_mainnet_headers() { // Accepts the first 10235 mainnet headers - let file = include_bytes!("./testdata/headers.zst"); - let uncompressed: Vec = zstd::decode_all(std::io::Cursor::new(file)).unwrap(); + let file = include_bytes!("../../testdata/headers.zst"); + let uncompressed: Vec = zstd::decode_all(Cursor::new(file)).unwrap(); let mut cursor = Cursor::new(uncompressed); - let test_id = rand::random::(); - let chainstore = KvChainStore::new(format!("./data/{test_id}/")).unwrap(); - let chain = ChainState::::new( - chainstore, - Network::Bitcoin, - AssumeValidArg::Hardcoded, - ); + let chain = setup_test_chain(Network::Bitcoin, AssumeValidArg::Hardcoded); while let Ok(header) = BlockHeader::consensus_decode(&mut cursor) { chain.accept_header(header).unwrap(); } @@ -1323,14 +1326,11 @@ mod test { #[test] fn accept_first_signet_headers() { // Accepts the first 2016 signet headers - let file = include_bytes!("./testdata/signet_headers.zst"); - let uncompressed: Vec = zstd::decode_all(std::io::Cursor::new(file)).unwrap(); + let file = include_bytes!("../../testdata/signet_headers.zst"); + let uncompressed: Vec = zstd::decode_all(Cursor::new(file)).unwrap(); let mut cursor = Cursor::new(uncompressed); - let test_id = rand::random::(); - let chainstore = KvChainStore::new(format!("./data/{test_id}/")).unwrap(); - let chain = - ChainState::::new(chainstore, Network::Signet, AssumeValidArg::Hardcoded); + let chain = setup_test_chain(Network::Signet, AssumeValidArg::Hardcoded); while let Ok(header) = BlockHeader::consensus_decode(&mut cursor) { chain.accept_header(header).unwrap(); } @@ -1353,14 +1353,8 @@ mod test { } #[test] fn test_reorg() { - let test_id = rand::random::(); - let chainstore = KvChainStore::new(format!("./data/{test_id}/")).unwrap(); - let chain = ChainState::::new( - chainstore, - Network::Regtest, - AssumeValidArg::Hardcoded, - ); - let blocks = include_str!("./testdata/test_reorg.json"); + let chain = setup_test_chain(Network::Regtest, AssumeValidArg::Hardcoded); + let blocks = include_str!("../../testdata/test_reorg.json"); let blocks: Vec> = serde_json::from_str(blocks).unwrap(); for block in blocks[0].iter() { @@ -1409,14 +1403,11 @@ mod test { } #[test] fn test_chainstate_functions() { - let file = include_bytes!("./testdata/signet_headers.zst"); - let uncompressed: Vec = zstd::decode_all(std::io::Cursor::new(file)).unwrap(); + let file = include_bytes!("../../testdata/signet_headers.zst"); + let uncompressed: Vec = zstd::decode_all(Cursor::new(file)).unwrap(); let mut cursor = Cursor::new(uncompressed); - let test_id = rand::random::(); - let chainstore = KvChainStore::new(format!("./data/{test_id}/")).unwrap(); - let chain = - ChainState::::new(chainstore, Network::Signet, AssumeValidArg::Hardcoded); + let chain = setup_test_chain(Network::Signet, AssumeValidArg::Hardcoded); let mut headers: Vec = Vec::new(); while let Ok(header) = BlockHeader::consensus_decode(&mut cursor) { headers.push(header); diff --git a/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs b/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs index 429b1c73..f41826cd 100644 --- a/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs +++ b/crates/floresta-chain/src/pruned_utreexo/partial_chain.rs @@ -496,7 +496,6 @@ impl From for PartialChainState { #[cfg(test)] mod tests { - use core::str::FromStr; use std::collections::HashMap; @@ -555,7 +554,7 @@ mod tests { #[test] fn test_updating_single_chain() { - let blocks = include_str!("./testdata/blocks.txt"); + let blocks = include_str!("../../testdata/blocks.txt"); let mut parsed_blocks = vec![]; for (i, block) in blocks.lines().enumerate() { if i > 100 { @@ -581,7 +580,7 @@ mod tests { for block in parsed_blocks { let proof = Proof::default(); let inputs = HashMap::new(); - let del_hashes = vec![]; + let del_hashes = Vec::new(); chainstate .connect_block(&block, proof, inputs, del_hashes) .unwrap(); @@ -593,7 +592,7 @@ mod tests { fn test_updating_multiple_chains() { // We have two chains, one with 100 blocks, one with 50 blocks. We expect the // accumulator to be what we expect after 100 blocks and after 150 blocks. - let blocks = include_str!("./testdata/blocks.txt"); + let blocks = include_str!("../../testdata/blocks.txt"); let mut parsed_blocks = vec![]; for block in blocks.lines() { let block: Block = deserialize(&hex::decode(block).unwrap()).unwrap(); diff --git a/crates/floresta-chain/src/pruned_utreexo/testdata/blocks.txt b/crates/floresta-chain/testdata/blocks.txt similarity index 100% rename from crates/floresta-chain/src/pruned_utreexo/testdata/blocks.txt rename to crates/floresta-chain/testdata/blocks.txt diff --git a/crates/floresta-chain/src/pruned_utreexo/testdata/headers.zst b/crates/floresta-chain/testdata/headers.zst similarity index 100% rename from crates/floresta-chain/src/pruned_utreexo/testdata/headers.zst rename to crates/floresta-chain/testdata/headers.zst diff --git a/crates/floresta-chain/src/pruned_utreexo/testdata/signet_headers b/crates/floresta-chain/testdata/signet_headers similarity index 100% rename from crates/floresta-chain/src/pruned_utreexo/testdata/signet_headers rename to crates/floresta-chain/testdata/signet_headers diff --git a/crates/floresta-chain/src/pruned_utreexo/testdata/signet_headers.zst b/crates/floresta-chain/testdata/signet_headers.zst similarity index 100% rename from crates/floresta-chain/src/pruned_utreexo/testdata/signet_headers.zst rename to crates/floresta-chain/testdata/signet_headers.zst diff --git a/crates/floresta-chain/src/pruned_utreexo/testdata/test_reorg.json b/crates/floresta-chain/testdata/test_reorg.json similarity index 100% rename from crates/floresta-chain/src/pruned_utreexo/testdata/test_reorg.json rename to crates/floresta-chain/testdata/test_reorg.json diff --git a/crates/floresta-electrum/src/electrum_protocol.rs b/crates/floresta-electrum/src/electrum_protocol.rs index 7b7a24a8..b4df3a1c 100644 --- a/crates/floresta-electrum/src/electrum_protocol.rs +++ b/crates/floresta-electrum/src/electrum_protocol.rs @@ -954,8 +954,7 @@ mod test { } fn get_test_signet_headers() -> Vec { - let file = - include_bytes!("../../floresta-chain/src/pruned_utreexo/testdata/signet_headers.zst"); + let file = include_bytes!("../../floresta-chain/testdata/signet_headers.zst"); let uncompressed: Vec = zstd::decode_all(std::io::Cursor::new(file)).unwrap(); let mut cursor = Cursor::new(uncompressed); let mut headers: Vec = Vec::new(); diff --git a/crates/floresta-wire/src/p2p_wire/tests/utils.rs b/crates/floresta-wire/src/p2p_wire/tests/utils.rs index 7c100ab7..4b7bae8a 100644 --- a/crates/floresta-wire/src/p2p_wire/tests/utils.rs +++ b/crates/floresta-wire/src/p2p_wire/tests/utils.rs @@ -203,8 +203,7 @@ pub fn create_false_acc(tip: usize) -> Vec { } pub fn get_test_headers() -> Vec
{ - let file = - include_bytes!("../../../../floresta-chain/src/pruned_utreexo/testdata/signet_headers.zst"); + let file = include_bytes!("../../../../floresta-chain/testdata/signet_headers.zst"); let uncompressed: Vec = zstd::decode_all(std::io::Cursor::new(file)).unwrap(); let mut cursor = Cursor::new(uncompressed);