1
1
use bitcoin:: { BlockHash , Network , block:: Header as BlockHeader } ;
2
2
use criterion:: measurement:: Measurement ;
3
- use criterion:: { BenchmarkGroup , Criterion , criterion_group, criterion_main} ;
3
+ use criterion:: { BenchmarkGroup , BenchmarkId , Criterion , criterion_group, criterion_main} ;
4
4
use ic_btc_adapter:: {
5
- BlockchainNetwork , BlockchainState , Config , IncomingSource , MAX_HEADERS_SIZE , start_server,
5
+ BlockchainHeader , BlockchainNetwork , BlockchainState , Config , HeaderValidator , IncomingSource ,
6
+ MAX_HEADERS_SIZE , start_server,
6
7
} ;
7
8
use ic_btc_adapter_client:: setup_bitcoin_adapter_clients;
8
9
use ic_btc_adapter_test_utils:: generate_headers;
@@ -17,9 +18,9 @@ use ic_logger::replica_logger::no_op_logger;
17
18
use ic_metrics:: MetricsRegistry ;
18
19
use rand:: { CryptoRng , Rng } ;
19
20
use sha2:: Digest ;
21
+ use std:: fmt;
20
22
use std:: path:: { Path , PathBuf } ;
21
- use std:: sync:: LazyLock ;
22
- use tempfile:: Builder ;
23
+ use tempfile:: { Builder , tempdir} ;
23
24
24
25
type BitcoinAdapterClient = Box <
25
26
dyn RpcAdapterClient < BitcoinAdapterRequestWrapper , Response = BitcoinAdapterResponseWrapper > ,
@@ -65,6 +66,11 @@ fn prepare(
65
66
}
66
67
}
67
68
69
+ // This simulation constructs a blockchain comprising four forks, each of 2000 blocks.
70
+ // For an extended BFS execution, the initial 1975 blocks of every branch are marked in
71
+ // the request as being processed, with the aim to receive the last 25 blocks of each fork.
72
+ // Performance metrics are captured from the sending of the deserialised request through
73
+ // to receiving the response and its deserialisation.
68
74
fn e2e ( criterion : & mut Criterion ) {
69
75
let network = Network :: Regtest ;
70
76
let mut processed_block_hashes = vec ! [ ] ;
@@ -173,36 +179,105 @@ fn random_header<const N: usize, R: Rng + CryptoRng>(rng: &mut R) -> [u8; N] {
173
179
}
174
180
175
181
fn add_800k_block_headers ( criterion : & mut Criterion ) {
176
- static BITCOIN_HEADERS : LazyLock < Vec < bitcoin:: block:: Header > > = LazyLock :: new ( || {
177
- let headers_data_path = PathBuf :: from (
178
- std:: env:: var ( "BITCOIN_MAINNET_HEADERS_DATA_PATH" )
179
- . expect ( "Failed to get test data path env variable" ) ,
180
- ) ;
181
- retrieve_headers :: < bitcoin:: Network > ( & headers_data_path)
182
- } ) ;
183
- // Call BITCOIN_HEADERS once before benchmarking to avoid biasing the first sample (lazy instantiation).
182
+ add_block_headers_for (
183
+ criterion,
184
+ bitcoin:: Network :: Bitcoin ,
185
+ "BITCOIN_MAINNET_HEADERS_DATA_PATH" ,
186
+ 800_000 ,
187
+ ) ;
188
+ add_block_headers_for (
189
+ criterion,
190
+ bitcoin:: dogecoin:: Network :: Dogecoin ,
191
+ "DOGECOIN_MAINNET_HEADERS_DATA_PATH" ,
192
+ 800_000 ,
193
+ ) ;
194
+ }
195
+
196
+ fn add_block_headers_for < Network : BlockchainNetwork + fmt:: Display > (
197
+ criterion : & mut Criterion ,
198
+ network : Network ,
199
+ headers_data_env : & str ,
200
+ expected_num_headers_to_add : usize ,
201
+ ) where
202
+ Network :: Header : for < ' de > serde:: Deserialize < ' de > ,
203
+ BlockchainState < Network > : HeaderValidator < Network > ,
204
+ {
205
+ let headers_data_path = PathBuf :: from (
206
+ std:: env:: var ( headers_data_env) . expect ( "Failed to get test data path env variable" ) ,
207
+ ) ;
208
+ let headers = retrieve_headers :: < Network > ( & headers_data_path) ;
184
209
// Genesis block header is automatically added when instantiating BlockchainState
185
- let bitcoin_headers_to_add = & BITCOIN_HEADERS . as_slice ( ) [ 1 ..] ;
186
- assert_eq ! ( bitcoin_headers_to_add . len( ) , 800_000 ) ;
187
- let mut group = criterion. benchmark_group ( "bitcoin_800k" ) ;
210
+ let headers_to_add = & headers . as_slice ( ) [ 1 ..] ;
211
+ assert_eq ! ( headers_to_add . len( ) , expected_num_headers_to_add ) ;
212
+ let mut group = criterion. benchmark_group ( format ! ( "{network}_{expected_num_headers_to_add}" ) ) ;
188
213
group. sample_size ( 10 ) ;
189
214
190
- group. bench_function ( "add_headers" , |bench| {
191
- let rt = tokio:: runtime:: Runtime :: new ( ) . unwrap ( ) ;
215
+ bench_add_headers ( & mut group, network, headers_to_add) ;
216
+ }
217
+
218
+ fn bench_add_headers < M : Measurement , Network : BlockchainNetwork > (
219
+ group : & mut BenchmarkGroup < ' _ , M > ,
220
+ network : Network ,
221
+ headers : & [ Network :: Header ] ,
222
+ ) where
223
+ BlockchainState < Network > : HeaderValidator < Network > ,
224
+ {
225
+ fn add_headers < Network : BlockchainNetwork > (
226
+ blockchain_state : & mut BlockchainState < Network > ,
227
+ headers : & [ Network :: Header ] ,
228
+ expect_pruning : bool ,
229
+ runtime : & tokio:: runtime:: Runtime ,
230
+ ) where
231
+ BlockchainState < Network > : HeaderValidator < Network > ,
232
+ {
233
+ // Genesis block header is automatically added when instantiating BlockchainState
234
+ let mut num_added_headers = 1 ;
235
+ // Headers are processed in chunks of at most MAX_HEADERS_SIZE entries
236
+ for chunk in headers. chunks ( MAX_HEADERS_SIZE ) {
237
+ let ( added_headers, error) =
238
+ runtime. block_on ( async { blockchain_state. add_headers ( chunk) . await } ) ;
239
+ assert ! ( error. is_none( ) , "Failed to add headers: {}" , error. unwrap( ) ) ;
240
+ assert_eq ! ( added_headers. len( ) , chunk. len( ) ) ;
241
+ num_added_headers += added_headers. len ( ) ;
242
+
243
+ runtime
244
+ . block_on ( async {
245
+ blockchain_state
246
+ . persist_and_prune_headers_below_anchor ( chunk. last ( ) . unwrap ( ) . block_hash ( ) )
247
+ . await
248
+ } )
249
+ . unwrap ( ) ;
250
+ let ( num_headers_disk, num_headers_memory) = blockchain_state. num_headers ( ) . unwrap ( ) ;
251
+ if expect_pruning {
252
+ assert_eq ! ( num_headers_disk, num_added_headers) ;
253
+ assert_eq ! ( num_headers_memory, 1 ) ;
254
+ } else {
255
+ assert_eq ! ( num_headers_disk, 0 ) ;
256
+ assert_eq ! ( num_headers_memory, num_added_headers) ;
257
+ }
258
+ }
259
+ }
260
+
261
+ let rt = tokio:: runtime:: Runtime :: new ( ) . unwrap ( ) ;
262
+
263
+ group. bench_function ( BenchmarkId :: new ( "add_headers" , "in_memory" ) , |bench| {
264
+ bench. iter ( || {
265
+ let mut blockchain_state =
266
+ BlockchainState :: new ( network, None , & MetricsRegistry :: default ( ) , no_op_logger ( ) ) ;
267
+ add_headers ( & mut blockchain_state, headers, false , & rt) ;
268
+ } )
269
+ } ) ;
270
+
271
+ group. bench_function ( BenchmarkId :: new ( "add_headers" , "lmdb" ) , |bench| {
192
272
bench. iter ( || {
193
- let blockchain_state = BlockchainState :: new (
194
- Network :: Bitcoin ,
195
- None ,
273
+ let dir = tempdir ( ) . unwrap ( ) ;
274
+ let mut blockchain_state = BlockchainState :: new (
275
+ network,
276
+ Some ( dir. path ( ) . to_path_buf ( ) ) ,
196
277
& MetricsRegistry :: default ( ) ,
197
278
no_op_logger ( ) ,
198
279
) ;
199
- // Headers are processed in chunks of at most MAX_HEADERS_SIZE entries
200
- for chunk in bitcoin_headers_to_add. chunks ( MAX_HEADERS_SIZE ) {
201
- let ( added_headers, error) =
202
- rt. block_on ( async { blockchain_state. add_headers ( chunk) . await } ) ;
203
- assert ! ( error. is_none( ) , "Failed to add headers: {}" , error. unwrap( ) ) ;
204
- assert_eq ! ( added_headers. len( ) , chunk. len( ) )
205
- }
280
+ add_headers ( & mut blockchain_state, headers, true , & rt) ;
206
281
} )
207
282
} ) ;
208
283
}
@@ -232,11 +307,6 @@ fn decompress<P: AsRef<Path>>(location: P) -> Vec<u8> {
232
307
decompressed
233
308
}
234
309
235
- // This simulation constructs a blockchain comprising four forks, each of 2000 blocks.
236
- // For an extended BFS execution, the initial 1975 blocks of every branch are marked in
237
- // the request as being processed, with the aim to receive the last 25 blocks of each fork.
238
- // Performance metrics are captured from the sending of the deserialised request through
239
- // to receiving the response and its deserialisation.
240
310
criterion_group ! ( benches, e2e, hash_block_header, add_800k_block_headers) ;
241
311
242
312
// The benchmark can be run using:
0 commit comments