• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tari-project / tari / 17297453740

28 Aug 2025 01:33PM UTC coverage: 61.046% (+0.9%) from 60.14%
17297453740

push

github

web-flow
chore(ci): add a wasm build step in ci (#7448)

Description
Add a wasm build step in ci

Motivation and Context
Test the wasm builds

How Has This Been Tested?
Builds in local fork


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Added CI workflow to build WebAssembly targets with optimized caching
on both hosted and self-hosted runners, improving build consistency and
speed.
* **Tests**
* Expanded automated checks to include WebAssembly build verification
for multiple modules, increasing coverage and early detection of build
issues.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

72582 of 118897 relevant lines covered (61.05%)

301536.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.15
/base_layer/core/src/test_helpers/blockchain.rs
1
//  Copyright 2020, The Tari Project
2
//
3
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
//  following conditions are met:
5
//
6
//  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
//  disclaimer.
8
//
9
//  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
10
//  following disclaimer in the documentation and/or other materials provided with the distribution.
11
//
12
//  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13
//  products derived from this software without specific prior written permission.
14
//
15
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
//  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
//  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
//  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
21
//  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
use std::{
23
    collections::HashMap,
24
    fs,
25
    ops::Deref,
26
    path::{Path, PathBuf},
27
    sync::Arc,
28
};
29

30
use jmt::{
31
    mock::MockTreeStore,
32
    storage::{TreeReader, TreeUpdateBatch, TreeWriter},
33
    JellyfishMerkleTree,
34
    KeyHash,
35
};
36
use tari_common::configuration::Network;
37
use tari_common_types::{
38
    chain_metadata::ChainMetadata,
39
    epoch::VnEpoch,
40
    tari_address::TariAddress,
41
    types::{BadBlock, CompressedCommitment, CompressedPublicKey, CompressedSignature, FixedHash, HashOutput},
42
};
43
use tari_node_components::blocks::{Block, BlockHeader};
44
use tari_sidechain::ShardGroup;
45
use tari_storage::lmdb_store::LMDBConfig;
46
use tari_test_utils::paths::create_temporary_data_path;
47
use tari_transaction_components::{
48
    consensus::consensus_constants::ConsensusConstantsBuilder,
49
    crypto_factories::CryptoFactories,
50
    key_manager::TariKeyId,
51
    tari_proof_of_work::{Difficulty, PowAlgorithm},
52
    transaction_components::{RangeProofType, TransactionInput, TransactionKernel, TransactionOutput, WalletOutput},
53
};
54
use tari_transaction_key_manager::{create_memory_db_key_manager, MemoryDbKeyManager};
55
use tari_utilities::ByteArray;
56

57
use super::{create_block, mine_to_difficulty};
58
use crate::{
59
    blocks::{BlockAccumulatedData, BlockHeaderAccumulatedData, ChainBlock, ChainHeader},
60
    chain_storage::{
61
        create_lmdb_database,
62
        AccumulatedDataRebuildStatus,
63
        BlockAddResult,
64
        BlockchainBackend,
65
        BlockchainDatabase,
66
        BlockchainDatabaseConfig,
67
        ChainStorageError,
68
        DbBasicStats,
69
        DbKey,
70
        DbTotalSizeStats,
71
        DbTransaction,
72
        DbValue,
73
        HorizonData,
74
        InputMinedInfo,
75
        LMDBDatabase,
76
        MinedInfo,
77
        MmrTree,
78
        OutputMinedInfo,
79
        OwnedLmdbTreeReader,
80
        PayrefRebuildStatus,
81
        Reorg,
82
        SmtHasher,
83
        TemplateRegistrationEntry,
84
        ValidatorNodeRegistrationInfo,
85
        Validators,
86
    },
87
    consensus::{chain_strength_comparer::ChainStrengthComparerBuilder, BaseNodeConsensusManager},
88
    proof_of_work::AchievedTargetDifficulty,
89
    test_helpers::{block_spec::BlockSpecs, create_consensus_rules, default_coinbase_entities, BlockSpec},
90
    validation::{
91
        block_body::{BlockBodyFullValidator, BlockBodyInternalConsistencyValidator},
92
        mocks::MockValidator,
93
        DifficultyCalculator,
94
    },
95
};
96

97
/// Create a new blockchain database containing the genesis block
98
pub fn create_new_blockchain() -> BlockchainDatabase<TempDatabase> {
59✔
99
    create_new_blockchain_with_network(Network::LocalNet)
59✔
100
}
59✔
101

102
pub fn create_new_blockchain_with_network(network: Network) -> BlockchainDatabase<TempDatabase> {
65✔
103
    let consensus_constants = ConsensusConstantsBuilder::new(network).build();
65✔
104
    let consensus_manager = BaseNodeConsensusManager::builder(network)
65✔
105
        .add_consensus_constants(consensus_constants)
65✔
106
        .on_ties(ChainStrengthComparerBuilder::new().by_height().build())
65✔
107
        .build()
65✔
108
        .unwrap();
65✔
109
    create_custom_blockchain(consensus_manager)
65✔
110
}
65✔
111

112
/// Create a new custom blockchain database containing no blocks.
113
pub fn create_custom_blockchain(rules: BaseNodeConsensusManager) -> BlockchainDatabase<TempDatabase> {
85✔
114
    let validators = Validators::new(
85✔
115
        MockValidator::new(true),
85✔
116
        MockValidator::new(true),
85✔
117
        MockValidator::new(true),
85✔
118
    );
85✔
119
    create_store_with_consensus_and_validators(rules, validators)
85✔
120
}
85✔
121

122
pub fn create_store_with_consensus_and_validators(
93✔
123
    rules: BaseNodeConsensusManager,
93✔
124
    validators: Validators<TempDatabase>,
93✔
125
) -> BlockchainDatabase<TempDatabase> {
93✔
126
    create_store_with_consensus_and_validators_and_config(rules, validators, BlockchainDatabaseConfig::default())
93✔
127
}
93✔
128

129
pub fn create_store_with_consensus_and_validators_and_config(
93✔
130
    rules: BaseNodeConsensusManager,
93✔
131
    validators: Validators<TempDatabase>,
93✔
132
    config: BlockchainDatabaseConfig,
93✔
133
) -> BlockchainDatabase<TempDatabase> {
93✔
134
    let backend = create_test_db();
93✔
135
    BlockchainDatabase::start_new(
93✔
136
        backend,
93✔
137
        rules.clone(),
93✔
138
        validators,
93✔
139
        config,
93✔
140
        DifficultyCalculator::new(rules, Default::default()),
93✔
141
    )
93✔
142
    .unwrap()
93✔
143
}
93✔
144

145
pub fn create_store_with_consensus(rules: BaseNodeConsensusManager) -> BlockchainDatabase<TempDatabase> {
8✔
146
    let factories = CryptoFactories::default();
8✔
147
    let validators = Validators::new(
8✔
148
        BlockBodyFullValidator::new(rules.clone(), true),
8✔
149
        MockValidator::new(true),
8✔
150
        BlockBodyInternalConsistencyValidator::new(rules.clone(), false, factories),
8✔
151
    );
8✔
152
    create_store_with_consensus_and_validators(rules, validators)
8✔
153
}
8✔
154
pub fn create_test_blockchain_db() -> BlockchainDatabase<TempDatabase> {
1✔
155
    let rules = create_consensus_rules();
1✔
156
    create_store_with_consensus(rules)
1✔
157
}
1✔
158

159
pub fn create_test_db() -> TempDatabase {
93✔
160
    TempDatabase::new()
93✔
161
}
93✔
162

163
pub struct TempDatabase {
164
    path: PathBuf,
165
    db: Option<LMDBDatabase>,
166
    delete_on_drop: bool,
167
}
168

169
impl TempDatabase {
170
    pub fn new() -> Self {
102✔
171
        let temp_path = create_temporary_data_path();
102✔
172
        let rules = create_consensus_rules();
102✔
173

102✔
174
        Self {
102✔
175
            db: Some(create_lmdb_database(&temp_path, LMDBConfig::default(), rules).unwrap()),
102✔
176
            path: temp_path,
102✔
177
            delete_on_drop: true,
102✔
178
        }
102✔
179
    }
102✔
180

181
    pub fn from_path<P: AsRef<Path>>(temp_path: P) -> Self {
×
182
        let rules = create_consensus_rules();
×
183
        Self {
×
184
            db: Some(create_lmdb_database(&temp_path, LMDBConfig::default(), rules).unwrap()),
×
185
            path: temp_path.as_ref().to_path_buf(),
×
186
            delete_on_drop: true,
×
187
        }
×
188
    }
×
189

190
    pub fn disable_delete_on_drop(&mut self) -> &mut Self {
×
191
        self.delete_on_drop = false;
×
192
        self
×
193
    }
×
194

195
    pub fn db(&self) -> &LMDBDatabase {
27✔
196
        self.db.as_ref().unwrap()
27✔
197
    }
27✔
198
}
199

200
impl Default for TempDatabase {
201
    fn default() -> Self {
×
202
        Self::new()
×
203
    }
×
204
}
205

206
impl Deref for TempDatabase {
207
    type Target = LMDBDatabase;
208

209
    fn deref(&self) -> &Self::Target {
×
210
        self.db.as_ref().unwrap()
×
211
    }
×
212
}
213

214
impl Drop for TempDatabase {
215
    fn drop(&mut self) {
102✔
216
        // force a drop on the LMDB db
102✔
217
        self.db = None;
102✔
218
        if self.delete_on_drop && Path::new(&self.path).exists() {
102✔
219
            fs::remove_dir_all(&self.path).expect("Could not delete temporary file");
102✔
220
        }
102✔
221
    }
102✔
222
}
223

224
impl BlockchainBackend for TempDatabase {
225
    fn write(&mut self, tx: DbTransaction) -> Result<(), ChainStorageError> {
1,404✔
226
        self.db.as_mut().unwrap().write(tx)
1,404✔
227
    }
1,404✔
228

229
    fn fetch(&self, key: &DbKey) -> Result<Option<DbValue>, ChainStorageError> {
578✔
230
        self.db.as_ref().unwrap().fetch(key)
578✔
231
    }
578✔
232

233
    fn contains(&self, key: &DbKey) -> Result<bool, ChainStorageError> {
553✔
234
        self.db.as_ref().unwrap().contains(key)
553✔
235
    }
553✔
236

237
    fn fetch_chain_header_by_height(&self, height: u64) -> Result<ChainHeader, ChainStorageError> {
721✔
238
        self.db.as_ref().unwrap().fetch_chain_header_by_height(height)
721✔
239
    }
721✔
240

241
    fn fetch_header_accumulated_data(
3✔
242
        &self,
3✔
243
        hash: &HashOutput,
3✔
244
    ) -> Result<Option<BlockHeaderAccumulatedData>, ChainStorageError> {
3✔
245
        self.db.as_ref().unwrap().fetch_header_accumulated_data(hash)
3✔
246
    }
3✔
247

248
    fn fetch_chain_header_in_all_chains(&self, hash: &HashOutput) -> Result<ChainHeader, ChainStorageError> {
1,465✔
249
        self.db.as_ref().unwrap().fetch_chain_header_in_all_chains(hash)
1,465✔
250
    }
1,465✔
251

252
    fn fetch_header_containing_kernel_mmr(&self, mmr_position: u64) -> Result<ChainHeader, ChainStorageError> {
7✔
253
        self.db
7✔
254
            .as_ref()
7✔
255
            .unwrap()
7✔
256
            .fetch_header_containing_kernel_mmr(mmr_position)
7✔
257
    }
7✔
258

259
    fn is_empty(&self) -> Result<bool, ChainStorageError> {
93✔
260
        self.db.as_ref().unwrap().is_empty()
93✔
261
    }
93✔
262

263
    fn fetch_block_accumulated_data(
296✔
264
        &self,
296✔
265
        header_hash: &HashOutput,
296✔
266
    ) -> Result<Option<BlockAccumulatedData>, ChainStorageError> {
296✔
267
        self.db.as_ref().unwrap().fetch_block_accumulated_data(header_hash)
296✔
268
    }
296✔
269

270
    fn fetch_block_accumulated_data_by_height(
×
271
        &self,
×
272
        height: u64,
×
273
    ) -> Result<Option<BlockAccumulatedData>, ChainStorageError> {
×
274
        self.db.as_ref().unwrap().fetch_block_accumulated_data_by_height(height)
×
275
    }
×
276

277
    fn fetch_kernels_in_block(&self, header_hash: &HashOutput) -> Result<Vec<TransactionKernel>, ChainStorageError> {
143✔
278
        self.db.as_ref().unwrap().fetch_kernels_in_block(header_hash)
143✔
279
    }
143✔
280

281
    fn fetch_bad_blocks(&self) -> Result<Vec<BadBlock>, ChainStorageError> {
×
282
        self.db.as_ref().unwrap().fetch_bad_blocks()
×
283
    }
×
284

285
    fn fetch_kernel_by_excess_sig(
26✔
286
        &self,
26✔
287
        excess_sig: &CompressedSignature,
26✔
288
    ) -> Result<Option<(TransactionKernel, HashOutput)>, ChainStorageError> {
26✔
289
        self.db.as_ref().unwrap().fetch_kernel_by_excess_sig(excess_sig)
26✔
290
    }
26✔
291

292
    fn fetch_outputs_in_block_with_spend_state(
×
293
        &self,
×
294
        header_hash: &HashOutput,
×
295
        spend_status_at_header: Option<&HashOutput>,
×
296
    ) -> Result<Vec<(TransactionOutput, bool)>, ChainStorageError> {
×
297
        self.db
×
298
            .as_ref()
×
299
            .unwrap()
×
300
            .fetch_outputs_in_block_with_spend_state(header_hash, spend_status_at_header)
×
301
    }
×
302

303
    fn fetch_output(&self, output_hash: &HashOutput) -> Result<Option<OutputMinedInfo>, ChainStorageError> {
5✔
304
        self.db.as_ref().unwrap().fetch_output(output_hash)
5✔
305
    }
5✔
306

307
    fn fetch_input(&self, output_hash: &HashOutput) -> Result<Option<InputMinedInfo>, ChainStorageError> {
×
308
        self.db.as_ref().unwrap().fetch_input(output_hash)
×
309
    }
×
310

311
    fn fetch_unspent_output_hash_by_commitment(
527✔
312
        &self,
527✔
313
        commitment: &CompressedCommitment,
527✔
314
    ) -> Result<Option<HashOutput>, ChainStorageError> {
527✔
315
        self.db
527✔
316
            .as_ref()
527✔
317
            .unwrap()
527✔
318
            .fetch_unspent_output_hash_by_commitment(commitment)
527✔
319
    }
527✔
320

321
    fn fetch_mined_info_by_payref(&self, payref: &FixedHash) -> Result<MinedInfo, ChainStorageError> {
×
322
        self.db.as_ref().unwrap().fetch_mined_info_by_payref(payref)
×
323
    }
×
324

325
    fn fetch_mined_info_by_output_hash(&self, output_hash: &HashOutput) -> Result<MinedInfo, ChainStorageError> {
×
326
        self.db.as_ref().unwrap().fetch_mined_info_by_output_hash(output_hash)
×
327
    }
×
328

329
    fn fetch_outputs_in_block(&self, header_hash: &HashOutput) -> Result<Vec<TransactionOutput>, ChainStorageError> {
143✔
330
        self.db.as_ref().unwrap().fetch_outputs_in_block(header_hash)
143✔
331
    }
143✔
332

333
    fn fetch_inputs_in_block(&self, header_hash: &HashOutput) -> Result<Vec<TransactionInput>, ChainStorageError> {
143✔
334
        self.db.as_ref().unwrap().fetch_inputs_in_block(header_hash)
143✔
335
    }
143✔
336

337
    fn fetch_mmr_size(&self, tree: MmrTree) -> Result<u64, ChainStorageError> {
×
338
        self.db.as_ref().unwrap().fetch_mmr_size(tree)
×
339
    }
×
340

341
    fn orphan_count(&self) -> Result<usize, ChainStorageError> {
×
342
        self.db.as_ref().unwrap().orphan_count()
×
343
    }
×
344

345
    fn fetch_last_header(&self) -> Result<BlockHeader, ChainStorageError> {
409✔
346
        self.db.as_ref().unwrap().fetch_last_header()
409✔
347
    }
409✔
348

349
    fn clear_all_pending_headers(&self) -> Result<usize, ChainStorageError> {
4✔
350
        self.db.as_ref().unwrap().clear_all_pending_headers()
4✔
351
    }
4✔
352

353
    fn fetch_last_chain_header(&self) -> Result<ChainHeader, ChainStorageError> {
1✔
354
        self.db.as_ref().unwrap().fetch_last_chain_header()
1✔
355
    }
1✔
356

357
    fn fetch_tip_header(&self) -> Result<ChainHeader, ChainStorageError> {
188✔
358
        self.db.as_ref().unwrap().fetch_tip_header()
188✔
359
    }
188✔
360

361
    fn fetch_chain_metadata(&self) -> Result<ChainMetadata, ChainStorageError> {
1,654✔
362
        self.db.as_ref().unwrap().fetch_chain_metadata()
1,654✔
363
    }
1,654✔
364

365
    fn fetch_payref_rebuild_status(&self) -> Result<PayrefRebuildStatus, ChainStorageError> {
93✔
366
        self.db.as_ref().unwrap().fetch_payref_rebuild_status()
93✔
367
    }
93✔
368

369
    fn fetch_accumulated_data_rebuild_status(&self) -> Result<AccumulatedDataRebuildStatus, ChainStorageError> {
93✔
370
        self.db.as_ref().unwrap().fetch_accumulated_data_rebuild_status()
93✔
371
    }
93✔
372

373
    fn build_payref_indexes_for_height(
×
374
        &self,
×
375
        height: u64,
×
376
        metadata_at_start: ChainMetadata,
×
377
        initialize_stats: Option<u64>,
×
378
        finalize: bool,
×
379
    ) -> Result<PayrefRebuildStatus, ChainStorageError> {
×
380
        self.db
×
381
            .as_ref()
×
382
            .unwrap()
×
383
            .build_payref_indexes_for_height(height, metadata_at_start, initialize_stats, finalize)
×
384
    }
×
385

386
    fn update_accumulated_difficulty(
×
387
        &self,
×
388
        height: u64,
×
389
        header_accum_data: BlockHeaderAccumulatedData,
×
390
        last_chain_header: ChainHeader,
×
391
    ) -> Result<AccumulatedDataRebuildStatus, ChainStorageError> {
×
392
        self.db
×
393
            .as_ref()
×
394
            .unwrap()
×
395
            .update_accumulated_difficulty(height, header_accum_data, last_chain_header)
×
396
    }
×
397

398
    fn utxo_count(&self) -> Result<usize, ChainStorageError> {
×
399
        self.db.as_ref().unwrap().utxo_count()
×
400
    }
×
401

402
    fn kernel_count(&self) -> Result<usize, ChainStorageError> {
×
403
        self.db.as_ref().unwrap().kernel_count()
×
404
    }
×
405

406
    fn fetch_orphan_chain_tip_by_hash(&self, hash: &HashOutput) -> Result<Option<ChainHeader>, ChainStorageError> {
189✔
407
        self.db.as_ref().unwrap().fetch_orphan_chain_tip_by_hash(hash)
189✔
408
    }
189✔
409

410
    fn fetch_strongest_orphan_chain_tips(&self) -> Result<Vec<ChainHeader>, ChainStorageError> {
185✔
411
        self.db.as_ref().unwrap().fetch_strongest_orphan_chain_tips()
185✔
412
    }
185✔
413

414
    fn fetch_orphan_children_of(&self, hash: HashOutput) -> Result<Vec<Block>, ChainStorageError> {
184✔
415
        self.db.as_ref().unwrap().fetch_orphan_children_of(hash)
184✔
416
    }
184✔
417

418
    fn fetch_orphan_chain_block(&self, hash: HashOutput) -> Result<Option<ChainBlock>, ChainStorageError> {
189✔
419
        self.db.as_ref().unwrap().fetch_orphan_chain_block(hash)
189✔
420
    }
189✔
421

422
    fn delete_oldest_orphans(
167✔
423
        &mut self,
167✔
424
        horizon_height: u64,
167✔
425
        orphan_storage_capacity: usize,
167✔
426
    ) -> Result<(), ChainStorageError> {
167✔
427
        self.db
167✔
428
            .as_mut()
167✔
429
            .unwrap()
167✔
430
            .delete_oldest_orphans(horizon_height, orphan_storage_capacity)
167✔
431
    }
167✔
432

433
    fn fetch_monero_seed_first_seen_height(&self, seed: &[u8]) -> Result<u64, ChainStorageError> {
3✔
434
        self.db.as_ref().unwrap().fetch_monero_seed_first_seen_height(seed)
3✔
435
    }
3✔
436

437
    fn fetch_horizon_data(&self) -> Result<Option<HorizonData>, ChainStorageError> {
1✔
438
        self.db.as_ref().unwrap().fetch_horizon_data()
1✔
439
    }
1✔
440

441
    fn get_stats(&self) -> Result<DbBasicStats, ChainStorageError> {
2✔
442
        self.db.as_ref().unwrap().get_stats()
2✔
443
    }
2✔
444

445
    fn fetch_total_size_stats(&self) -> Result<DbTotalSizeStats, ChainStorageError> {
2✔
446
        self.db.as_ref().unwrap().fetch_total_size_stats()
2✔
447
    }
2✔
448

449
    fn bad_block_exists(&self, block_hash: HashOutput) -> Result<(bool, String), ChainStorageError> {
183✔
450
        self.db.as_ref().unwrap().bad_block_exists(block_hash)
183✔
451
    }
183✔
452

453
    fn fetch_all_reorgs(&self) -> Result<Vec<Reorg>, ChainStorageError> {
×
454
        self.db.as_ref().unwrap().fetch_all_reorgs()
×
455
    }
×
456

457
    fn fetch_all_active_validator_nodes(
4✔
458
        &self,
4✔
459
        height: u64,
4✔
460
    ) -> Result<Vec<ValidatorNodeRegistrationInfo>, ChainStorageError> {
4✔
461
        self.db.as_ref().unwrap().fetch_all_active_validator_nodes(height)
4✔
462
    }
4✔
463

464
    fn fetch_active_validator_nodes(
×
465
        &self,
×
466
        sidechain_pk: Option<&CompressedPublicKey>,
×
467
        height: u64,
×
468
    ) -> Result<Vec<ValidatorNodeRegistrationInfo>, ChainStorageError> {
×
469
        self.db
×
470
            .as_ref()
×
471
            .unwrap()
×
472
            .fetch_active_validator_nodes(sidechain_pk, height)
×
473
    }
×
474

475
    fn fetch_validators_activating_in_epoch(
×
476
        &self,
×
477
        sidechain_pk: Option<&CompressedPublicKey>,
×
478
        epoch: VnEpoch,
×
479
    ) -> Result<Vec<ValidatorNodeRegistrationInfo>, ChainStorageError> {
×
480
        self.db
×
481
            .as_ref()
×
482
            .unwrap()
×
483
            .fetch_validators_activating_in_epoch(sidechain_pk, epoch)
×
484
    }
×
485

486
    fn fetch_validators_exiting_in_epoch(
×
487
        &self,
×
488
        sidechain_pk: Option<&CompressedPublicKey>,
×
489
        epoch: VnEpoch,
×
490
    ) -> Result<Vec<ValidatorNodeRegistrationInfo>, ChainStorageError> {
×
491
        self.db
×
492
            .as_ref()
×
493
            .unwrap()
×
494
            .fetch_validators_exiting_in_epoch(sidechain_pk, epoch)
×
495
    }
×
496

497
    fn validator_node_exists(
×
498
        &self,
×
499
        sidechain_pk: Option<&CompressedPublicKey>,
×
500
        end_epoch: VnEpoch,
×
501
        validator_node_pk: &CompressedPublicKey,
×
502
    ) -> Result<bool, ChainStorageError> {
×
503
        self.db
×
504
            .as_ref()
×
505
            .unwrap()
×
506
            .validator_node_exists(sidechain_pk, end_epoch, validator_node_pk)
×
507
    }
×
508

509
    fn validator_node_is_active(
×
510
        &self,
×
511
        sidechain_pk: Option<&CompressedPublicKey>,
×
512
        end_epoch: VnEpoch,
×
513
        validator_node_pk: &CompressedPublicKey,
×
514
    ) -> Result<bool, ChainStorageError> {
×
515
        self.db
×
516
            .as_ref()
×
517
            .unwrap()
×
518
            .validator_node_is_active(sidechain_pk, end_epoch, validator_node_pk)
×
519
    }
×
520

521
    fn validator_node_is_active_for_shard_group(
×
522
        &self,
×
523
        sidechain_pk: Option<&CompressedPublicKey>,
×
524
        end_epoch: VnEpoch,
×
525
        validator_node_pk: &CompressedPublicKey,
×
526
        shard_group: ShardGroup,
×
527
    ) -> Result<bool, ChainStorageError> {
×
528
        self.db.as_ref().unwrap().validator_node_is_active_for_shard_group(
×
529
            sidechain_pk,
×
530
            end_epoch,
×
531
            validator_node_pk,
×
532
            shard_group,
×
533
        )
×
534
    }
×
535

536
    fn validator_nodes_count_for_shard_group(
×
537
        &self,
×
538
        sidechain_pk: Option<&CompressedPublicKey>,
×
539
        end_epoch: VnEpoch,
×
540
        shard_group: ShardGroup,
×
541
    ) -> Result<usize, ChainStorageError> {
×
542
        self.db
×
543
            .as_ref()
×
544
            .unwrap()
×
545
            .validator_nodes_count_for_shard_group(sidechain_pk, end_epoch, shard_group)
×
546
    }
×
547

548
    fn get_validator_node(
2✔
549
        &self,
2✔
550
        sidechain_pk: Option<&CompressedPublicKey>,
2✔
551
        public_key: CompressedPublicKey,
2✔
552
    ) -> Result<Option<ValidatorNodeRegistrationInfo>, ChainStorageError> {
2✔
553
        self.db.as_ref().unwrap().get_validator_node(sidechain_pk, public_key)
2✔
554
    }
2✔
555

556
    fn fetch_template_registrations(
×
557
        &self,
×
558
        start_height: u64,
×
559
        end_height: u64,
×
560
    ) -> Result<Vec<TemplateRegistrationEntry>, ChainStorageError> {
×
561
        self.db
×
562
            .as_ref()
×
563
            .unwrap()
×
564
            .fetch_template_registrations(start_height, end_height)
×
565
    }
×
566

567
    fn create_smt_reader(&self) -> Result<OwnedLmdbTreeReader<'_>, ChainStorageError> {
344✔
568
        self.db.as_ref().unwrap().create_smt_reader()
344✔
569
    }
344✔
570

571
    fn set_stats_total_height(&self, _total: u64) {}
3✔
572

573
    fn update_stats_progress(&self, _current: u64) {}
3✔
574
}
575

576
pub async fn create_chained_blocks<T: Into<BlockSpecs>, TDB: BlockchainBackend>(
23✔
577
    db: &BlockchainDatabase<TDB>,
23✔
578
    blocks: T,
23✔
579
    genesis_block: Arc<ChainBlock>,
23✔
580
) -> (Vec<String>, HashMap<String, Arc<ChainBlock>>) {
23✔
581
    let mut block_hashes = HashMap::new();
23✔
582
    let gb_height = genesis_block.header().height;
23✔
583
    block_hashes.insert("GB".to_string(), genesis_block);
23✔
584
    let rules = BaseNodeConsensusManager::builder(Network::LocalNet).build().unwrap();
23✔
585
    let km = create_memory_db_key_manager().await.unwrap();
23✔
586
    let blocks: BlockSpecs = blocks.into();
23✔
587
    let mut block_names = Vec::with_capacity(blocks.len());
23✔
588
    let (script_key_id, wallet_payment_address) = default_coinbase_entities(&km).await;
23✔
589
    let mock_store = MockTreeStore::new(true);
23✔
590
    let jmt = JellyfishMerkleTree::<_, SmtHasher>::new(&mock_store);
23✔
591

592
    for h in 0..=gb_height {
34✔
593
        let mut batch = vec![];
34✔
594
        let h_block = db.fetch_block(h, false).unwrap();
34✔
595

596
        for output in h_block.block().body.outputs() {
34✔
597
            if !output.is_burned() {
11✔
598
                let smt_key = KeyHash(output.commitment.as_bytes().try_into().expect("commitment is 32 bytes"));
11✔
599
                let smt_value = output.smt_hash(h_block.block().header.height);
11✔
600
                batch.push((smt_key, Some(smt_value.to_vec())));
11✔
601
            }
11✔
602
        }
603
        for input in h_block.block().body.inputs() {
34✔
604
            let smt_key = KeyHash(
×
605
                input
×
606
                    .commitment()
×
607
                    .unwrap()
×
608
                    .as_bytes()
×
609
                    .try_into()
×
610
                    .expect("Commitment is 32 bytes"),
×
611
            );
×
612
            batch.push((smt_key, None));
×
613
        }
×
614
        let (root, updates) = jmt.put_value_set(batch, h).unwrap();
34✔
615
        mock_store.write_node_batch(&updates.node_batch).unwrap();
34✔
616
        assert_eq!(root.0.as_slice(), h_block.block().header.output_mr.as_slice());
34✔
617
    }
618

619
    for block_spec in blocks {
83✔
620
        let prev_block = block_hashes
60✔
621
            .get(block_spec.parent)
60✔
622
            .unwrap_or_else(|| panic!("Could not find block {}", block_spec.parent));
60✔
623
        let name = block_spec.name;
60✔
624
        let difficulty = block_spec.difficulty;
60✔
625
        let (mut block, _) = create_block(
60✔
626
            db,
60✔
627
            &rules,
60✔
628
            prev_block.block(),
60✔
629
            block_spec,
60✔
630
            &km,
60✔
631
            &script_key_id,
60✔
632
            &wallet_payment_address,
60✔
633
            None,
60✔
634
        )
60✔
635
        .await;
60✔
636
        let updates = update_block_and_smt(&mut block, &jmt);
60✔
637

60✔
638
        mock_store.write_node_batch(&updates.node_batch).unwrap();
60✔
639

60✔
640
        let block = mine_block(block, prev_block.accumulated_data(), difficulty);
60✔
641
        block_names.push(name.to_string());
60✔
642
        block_hashes.insert(name.to_string(), block);
60✔
643
    }
644
    (block_names, block_hashes)
23✔
645
}
23✔
646

647
fn mine_block(block: Block, prev_block_accum: &BlockHeaderAccumulatedData, difficulty: Difficulty) -> Arc<ChainBlock> {
93✔
648
    let block = mine_to_difficulty(block, difficulty).unwrap();
93✔
649
    let accum = BlockHeaderAccumulatedData::builder(prev_block_accum)
93✔
650
        .with_hash(block.hash())
93✔
651
        .with_achieved_target_difficulty(
93✔
652
            AchievedTargetDifficulty::try_construct(PowAlgorithm::Sha3x, difficulty, difficulty).unwrap(),
93✔
653
        )
93✔
654
        .with_total_kernel_offset(block.header.total_kernel_offset.clone())
93✔
655
        .build()
93✔
656
        .unwrap();
93✔
657
    Arc::new(ChainBlock::try_construct(Arc::new(block), accum).unwrap())
93✔
658
}
93✔
659

660
pub async fn create_main_chain<T: Into<BlockSpecs>>(
11✔
661
    db: &BlockchainDatabase<TempDatabase>,
11✔
662
    blocks: T,
11✔
663
) -> (Vec<String>, HashMap<String, Arc<ChainBlock>>) {
11✔
664
    let genesis_block = db
11✔
665
        .fetch_block(0, true)
11✔
666
        .unwrap()
11✔
667
        .try_into_chain_block()
11✔
668
        .map(Arc::new)
11✔
669
        .unwrap();
11✔
670
    let (names, chain) = { create_chained_blocks(db, blocks, genesis_block).await };
11✔
671
    names.iter().for_each(|name| {
30✔
672
        let block = chain.get(name).unwrap();
30✔
673
        db.add_block(block.to_arc_block()).unwrap();
30✔
674
    });
30✔
675

11✔
676
    (names, chain)
11✔
677
}
11✔
678

679
pub async fn create_orphan_chain<T: Into<BlockSpecs>>(
3✔
680
    db: &BlockchainDatabase<TempDatabase>,
3✔
681
    blocks: T,
3✔
682
    root_block: Arc<ChainBlock>,
3✔
683
) -> (Vec<String>, HashMap<String, Arc<ChainBlock>>) {
3✔
684
    let (names, chain) = create_chained_blocks(db, blocks, root_block).await;
3✔
685
    let mut txn = DbTransaction::new();
3✔
686
    for name in &names {
11✔
687
        let block = chain.get(name).unwrap().clone();
8✔
688
        txn.insert_chained_orphan(block);
8✔
689
    }
8✔
690
    db.write(txn).unwrap();
3✔
691

3✔
692
    (names, chain)
3✔
693
}
3✔
694

695
pub fn update_block_and_smt<T: TreeReader>(
60✔
696
    block: &mut Block,
60✔
697
    jmt: &JellyfishMerkleTree<T, SmtHasher>,
60✔
698
) -> TreeUpdateBatch {
60✔
699
    // let jmt = JellyfishMerkleTree::<_, SmtHasher>::new(smt_reader);
60✔
700
    let mut batch = vec![];
60✔
701
    for output in block.body.outputs() {
60✔
702
        if !output.is_burned() {
60✔
703
            let smt_key = KeyHash(output.commitment.as_bytes().try_into().expect("commitment is 32 bytes"));
60✔
704
            let smt_value = output.smt_hash(block.header.height);
60✔
705

60✔
706
            batch.push((smt_key, Some(smt_value.to_vec())));
60✔
707
        }
60✔
708
    }
709
    for input in block.body.inputs() {
60✔
710
        let smt_key = KeyHash(
×
711
            input
×
712
                .commitment()
×
713
                .unwrap()
×
714
                .as_bytes()
×
715
                .try_into()
×
716
                .expect("Commitment is 32 bytes"),
×
717
        );
×
718
        batch.push((smt_key, None));
×
719
    }
×
720
    let (root, updates) = jmt.put_value_set(batch, block.header.height).unwrap();
60✔
721
    // let root = FixedHash::try_from(smt.hash().as_slice()).unwrap();
60✔
722
    block.header.output_mr = FixedHash::try_from(root.0.as_slice()).unwrap();
60✔
723
    updates
60✔
724
}
60✔
725

726
pub struct TestBlockchain {
727
    db: BlockchainDatabase<TempDatabase>,
728
    chain: Vec<(&'static str, Arc<ChainBlock>)>,
729
    rules: BaseNodeConsensusManager,
730
    pub km: MemoryDbKeyManager,
731
    script_key_id: TariKeyId,
732
    wallet_payment_address: TariAddress,
733
    range_proof_type: RangeProofType,
734
}
735

736
impl TestBlockchain {
737
    pub async fn new(db: BlockchainDatabase<TempDatabase>, rules: BaseNodeConsensusManager) -> Self {
20✔
738
        let genesis = db
20✔
739
            .fetch_block(0, true)
20✔
740
            .unwrap()
20✔
741
            .try_into_chain_block()
20✔
742
            .map(Arc::new)
20✔
743
            .unwrap();
20✔
744
        let km = create_memory_db_key_manager().await.unwrap();
20✔
745
        let (script_key_id, wallet_payment_address) = default_coinbase_entities(&km).await;
20✔
746
        let mut blockchain = Self {
20✔
747
            db,
20✔
748
            chain: Default::default(),
20✔
749
            rules,
20✔
750
            km,
20✔
751
            script_key_id,
20✔
752
            wallet_payment_address,
20✔
753
            range_proof_type: RangeProofType::BulletProofPlus,
20✔
754
        };
20✔
755

20✔
756
        blockchain.chain.push(("GB", genesis));
20✔
757
        blockchain
20✔
758
    }
20✔
759

760
    pub async fn create(rules: BaseNodeConsensusManager) -> Self {
20✔
761
        Self::new(create_custom_blockchain(rules.clone()), rules).await
20✔
762
    }
20✔
763

764
    pub async fn append_chain(
×
765
        &mut self,
×
766
        block_specs: BlockSpecs,
×
767
    ) -> Result<Vec<(Arc<ChainBlock>, WalletOutput)>, ChainStorageError> {
×
768
        let mut blocks = Vec::with_capacity(block_specs.len());
×
769
        for spec in block_specs {
×
770
            blocks.push(self.append(spec).await?);
×
771
        }
772
        Ok(blocks)
×
773
    }
×
774

775
    pub async fn create_chain(&self, block_specs: BlockSpecs) -> Vec<(Arc<ChainBlock>, WalletOutput)> {
×
776
        let mut result = Vec::new();
×
777
        for spec in block_specs {
×
778
            result.push(self.create_chained_block(spec).await);
×
779
        }
780
        result
×
781
    }
×
782

783
    pub fn add_blocks(&self, blocks: Vec<Arc<ChainBlock>>) -> Result<(), ChainStorageError> {
×
784
        for block in blocks {
×
785
            let result = self.db.add_block(block.to_arc_block())?;
×
786
            assert!(result.is_added());
×
787
        }
788
        Ok(())
×
789
    }
×
790

791
    pub async fn with_validators(validators: Validators<TempDatabase>) -> Self {
×
792
        let rules = BaseNodeConsensusManager::builder(Network::LocalNet).build().unwrap();
×
793
        let db = create_store_with_consensus_and_validators(rules.clone(), validators);
×
794
        Self::new(db, rules).await
×
795
    }
×
796

797
    pub fn rules(&self) -> &BaseNodeConsensusManager {
1✔
798
        &self.rules
1✔
799
    }
1✔
800

801
    pub fn db(&self) -> &BlockchainDatabase<TempDatabase> {
58✔
802
        &self.db
58✔
803
    }
58✔
804

805
    pub async fn add_block(
×
806
        &mut self,
×
807
        block_spec: BlockSpec,
×
808
    ) -> Result<(Arc<ChainBlock>, WalletOutput), ChainStorageError> {
×
809
        let name = block_spec.name;
×
810
        let (block, coinbase) = self.create_chained_block(block_spec).await;
×
811
        let result = self.append_block(name, block.clone())?;
×
812
        assert!(result.is_added());
×
813
        Ok((block, coinbase))
×
814
    }
×
815

816
    pub async fn add_next_tip(
13✔
817
        &mut self,
13✔
818
        spec: BlockSpec,
13✔
819
    ) -> Result<(Arc<ChainBlock>, WalletOutput), ChainStorageError> {
13✔
820
        let name = spec.name;
13✔
821
        let (block, coinbase) = self.create_next_tip(spec).await;
13✔
822
        let result = self.append_block(name, block.clone())?;
13✔
823
        assert!(result.is_added());
13✔
824
        Ok((block, coinbase))
13✔
825
    }
13✔
826

827
    pub fn append_block(
20✔
828
        &mut self,
20✔
829
        name: &'static str,
20✔
830
        block: Arc<ChainBlock>,
20✔
831
    ) -> Result<BlockAddResult, ChainStorageError> {
20✔
832
        let result = self.db.add_block(block.to_arc_block())?;
20✔
833
        // let smt = self.db.smt().read().unwrap().clone();
834
        self.chain.push((name, block));
19✔
835
        Ok(result)
19✔
836
    }
20✔
837

838
    pub fn get_block_and_smt_by_name(&self, name: &'static str) -> Option<Arc<ChainBlock>> {
42✔
839
        self.chain.iter().find(|(n, _)| *n == name).map(|(_, ch)| ch.clone())
64✔
840
    }
42✔
841

842
    pub fn get_tip_block(&self) -> (&'static str, Arc<ChainBlock>) {
23✔
843
        self.chain.last().cloned().unwrap()
23✔
844
    }
23✔
845

846
    pub async fn create_chained_block(&self, block_spec: BlockSpec) -> (Arc<ChainBlock>, WalletOutput) {
31✔
847
        let parent = self
31✔
848
            .get_block_and_smt_by_name(block_spec.parent)
31✔
849
            .ok_or_else(|| format!("Parent block not found with name '{}'", block_spec.parent))
31✔
850
            .unwrap();
31✔
851
        let difficulty = block_spec.difficulty;
31✔
852
        let (block, coinbase) = create_block(
31✔
853
            self.db(),
31✔
854
            &self.rules,
31✔
855
            parent.block(),
31✔
856
            block_spec,
31✔
857
            &self.km,
31✔
858
            &self.script_key_id,
31✔
859
            &self.wallet_payment_address,
31✔
860
            Some(self.range_proof_type),
31✔
861
        )
31✔
862
        .await;
31✔
863
        let block = mine_block(block, parent.accumulated_data(), difficulty);
31✔
864
        (block, coinbase)
31✔
865
    }
31✔
866

867
    pub async fn create_unmined_block(&self, block_spec: BlockSpec) -> (Block, WalletOutput) {
9✔
868
        let parent = self
9✔
869
            .get_block_and_smt_by_name(block_spec.parent)
9✔
870
            .ok_or_else(|| format!("Parent block not found with name '{}'", block_spec.parent))
9✔
871
            .unwrap();
9✔
872
        let (mut block, outputs) = create_block(
9✔
873
            self.db(),
9✔
874
            &self.rules,
9✔
875
            parent.block(),
9✔
876
            block_spec,
9✔
877
            &self.km,
9✔
878
            &self.script_key_id,
9✔
879
            &self.wallet_payment_address,
9✔
880
            Some(self.range_proof_type),
9✔
881
        )
9✔
882
        .await;
9✔
883
        block.body.sort();
9✔
884
        (block, outputs)
9✔
885
    }
9✔
886

887
    pub fn mine_block(&self, parent_name: &'static str, block: Block, difficulty: Difficulty) -> Arc<ChainBlock> {
2✔
888
        let parent = self.get_block_and_smt_by_name(parent_name).unwrap();
2✔
889
        mine_block(block, parent.accumulated_data(), difficulty)
2✔
890
    }
2✔
891

892
    pub async fn create_next_tip(&self, spec: BlockSpec) -> (Arc<ChainBlock>, WalletOutput) {
23✔
893
        let (name, _) = self.get_tip_block();
23✔
894
        self.create_chained_block(spec.with_parent_block(name)).await
23✔
895
    }
23✔
896

897
    pub async fn append_to_tip(
×
898
        &mut self,
×
899
        spec: BlockSpec,
×
900
    ) -> Result<(Arc<ChainBlock>, WalletOutput), ChainStorageError> {
×
901
        let (tip, _) = self.get_tip_block();
×
902
        self.append(spec.with_parent_block(tip)).await
×
903
    }
×
904

905
    pub async fn append(&mut self, spec: BlockSpec) -> Result<(Arc<ChainBlock>, WalletOutput), ChainStorageError> {
7✔
906
        let name = spec.name;
7✔
907
        let (block, outputs) = self.create_chained_block(spec).await;
7✔
908
        self.append_block(name, block.clone())?;
7✔
909
        Ok((block, outputs))
6✔
910
    }
7✔
911

912
    pub fn get_genesis_block(&self) -> Arc<ChainBlock> {
×
913
        self.chain.first().map(|(_, block)| block).unwrap().clone()
×
914
    }
×
915
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc