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

tari-project / tari / 18097567115

29 Sep 2025 12:50PM UTC coverage: 58.554% (-2.3%) from 60.88%
18097567115

push

github

web-flow
chore(ci): switch rust toolchain to stable (#7524)

Description
switch rust toolchain to stable

Motivation and Context
use stable rust toolchain


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

## Summary by CodeRabbit

* **Chores**
* Standardized Rust toolchain on stable across CI workflows for more
predictable builds.
* Streamlined setup by removing unnecessary components and aligning
toolchain configuration with environment variables.
  * Enabled an environment flag to improve rustup behavior during CI.
* Improved coverage workflow consistency with dynamic toolchain
selection.

* **Tests**
* Removed nightly-only requirements, simplifying test commands and
improving compatibility.
* Expanded CI triggers to include ci-* branches for better pre-merge
validation.
* Maintained existing job logic while improving reliability and
maintainability.

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

66336 of 113291 relevant lines covered (58.55%)

551641.45 hits per line

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

98.65
/base_layer/core/src/validation/test.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

23
#![allow(clippy::indexing_slicing)]
24
use std::{cmp, sync::Arc};
25

26
use jmt::{mock::MockTreeStore, JellyfishMerkleTree, KeyHash};
27
use tari_common::configuration::Network;
28
use tari_common_types::types::{CompressedCommitment, UncompressedCommitment};
29
use tari_node_components::blocks::{BlockHeader, ChainBlock, ChainHeader};
30
use tari_script::TariScript;
31
use tari_test_utils::unpack_enum;
32
use tari_transaction_components::{
33
    consensus::ConsensusConstantsBuilder,
34
    crypto_factories::CryptoFactories,
35
    key_manager::TxoStage,
36
    tari_amount::{uT, MicroMinotari},
37
    test_helpers::{create_random_signature_from_secret_key, create_utxo},
38
    transaction_components::{
39
        covenants::Covenant,
40
        KernelBuilder,
41
        KernelFeatures,
42
        OutputFeatures,
43
        RangeProofType,
44
        TransactionKernel,
45
    },
46
    tx,
47
};
48
use tari_transaction_key_manager::create_memory_db_key_manager;
49
use tari_utilities::ByteArray;
50

51
use crate::{
52
    blocks::BlockHeaderAccumulatedDataBuilder,
53
    chain_storage::{BlockchainBackend, BlockchainDatabase, ChainStorageError, DbTransaction, SmtHasher},
54
    proof_of_work::AchievedTargetDifficulty,
55
    test_helpers::{blockchain::create_store_with_consensus, create_chain_header},
56
    validation::{ChainBalanceValidator, DifficultyCalculator, FinalHorizonStateValidation, ValidationError},
57
};
58

59
mod header_validators {
60
    use tari_common_types::types::FixedHash;
61
    use tari_utilities::epoch_time::EpochTime;
62

63
    use super::*;
64
    use crate::{
65
        block_specs,
66
        consensus::{BaseNodeConsensusManager, BaseNodeConsensusManagerBuilder},
67
        test_helpers::blockchain::{create_main_chain, create_new_blockchain},
68
        validation::{header::HeaderFullValidator, HeaderChainLinkedValidator},
69
    };
70

71
    #[test]
72
    fn header_iter_empty_and_invalid_height() {
1✔
73
        let consensus_manager = BaseNodeConsensusManager::builder(Network::LocalNet).build().unwrap();
1✔
74
        let genesis = consensus_manager.get_genesis_block();
1✔
75
        let db = create_store_with_consensus(consensus_manager);
1✔
76

77
        let iter = HeaderIter::new(&db, 0, 10);
1✔
78
        let headers = iter.map(Result::unwrap).collect::<Vec<_>>();
1✔
79
        assert_eq!(headers.len(), 1);
1✔
80

81
        assert_eq!(genesis.header(), &headers[0]);
1✔
82

83
        // Invalid header height
84
        let iter = HeaderIter::new(&db, 1, 10);
1✔
85
        let headers = iter.collect::<Result<Vec<_>, _>>().unwrap();
1✔
86
        assert_eq!(headers.len(), 1);
1✔
87
    }
1✔
88

89
    #[test]
90
    fn header_iter_fetch_in_chunks() {
1✔
91
        let consensus_manager = BaseNodeConsensusManagerBuilder::new(Network::LocalNet).build().unwrap();
1✔
92
        let db = create_store_with_consensus(consensus_manager.clone());
1✔
93
        let headers = (1..=15).fold(vec![db.fetch_chain_header(0).unwrap()], |mut acc, i| {
15✔
94
            let prev = acc.last().unwrap();
15✔
95
            let mut header = BlockHeader::new(0);
15✔
96
            header.height = i;
15✔
97
            header.prev_hash = *prev.hash();
15✔
98
            // These have to be unique
99
            header.kernel_mmr_size = 2 + i;
15✔
100
            header.output_smt_size = 4001 + i;
15✔
101

102
            let chain_header = create_chain_header(header, prev.accumulated_data());
15✔
103
            acc.push(chain_header);
15✔
104
            acc
15✔
105
        });
15✔
106
        db.insert_valid_headers(headers.into_iter().skip(1).collect()).unwrap();
1✔
107

108
        let iter = HeaderIter::new(&db, 11, 3);
1✔
109
        let headers = iter.map(Result::unwrap).collect::<Vec<_>>();
1✔
110
        assert_eq!(headers.len(), 12);
1✔
111
        let genesis = consensus_manager.get_genesis_block();
1✔
112
        assert_eq!(genesis.header(), &headers[0]);
1✔
113

114
        (1..=11).for_each(|i| {
11✔
115
            assert_eq!(headers[i].height, i as u64);
11✔
116
        })
11✔
117
    }
1✔
118

119
    #[test]
120
    fn it_validates_that_version_is_in_range() {
1✔
121
        let consensus_manager = BaseNodeConsensusManagerBuilder::new(Network::LocalNet).build().unwrap();
1✔
122
        let db = create_store_with_consensus(consensus_manager.clone());
1✔
123

124
        let genesis = db.fetch_chain_header(0).unwrap();
1✔
125

126
        let mut header = BlockHeader::from_previous(genesis.header());
1✔
127
        header.version = u16::MAX;
1✔
128
        let difficulty_calculator = DifficultyCalculator::new(consensus_manager.clone(), Default::default());
1✔
129
        let validator = HeaderFullValidator::new(consensus_manager, difficulty_calculator);
1✔
130

131
        let err = validator
1✔
132
            .validate(
1✔
133
                &*db.db_read_access().unwrap(),
1✔
134
                &header,
1✔
135
                genesis.header(),
1✔
136
                &[],
1✔
137
                None,
1✔
138
                FixedHash::zero(),
1✔
139
            )
140
            .unwrap_err();
1✔
141
        assert!(matches!(err, ValidationError::InvalidBlockchainVersion {
1✔
142
            version: u16::MAX
143
        }));
144
    }
1✔
145

146
    #[tokio::test]
147
    async fn it_does_a_sanity_check_on_the_number_of_timestamps_provided() {
1✔
148
        let consensus_manager = BaseNodeConsensusManagerBuilder::new(Network::LocalNet).build().unwrap();
1✔
149
        let db = create_new_blockchain();
1✔
150

151
        let (_, blocks) = create_main_chain(&db, block_specs!(["1->GB"], ["2->1"], ["3->2"])).await;
1✔
152
        let last_block = blocks.get("3").unwrap();
1✔
153

154
        let candidate_header = BlockHeader::from_previous(last_block.header());
1✔
155
        let difficulty_calculator = DifficultyCalculator::new(consensus_manager.clone(), Default::default());
1✔
156
        let validator = HeaderFullValidator::new(consensus_manager, difficulty_calculator);
1✔
157
        let mut timestamps = db.fetch_block_timestamps(*blocks.get("3").unwrap().hash()).unwrap();
1✔
158

159
        // First, lets check that everything else is valid
160
        validator
1✔
161
            .validate(
1✔
162
                &*db.db_read_access().unwrap(),
1✔
163
                &candidate_header,
1✔
164
                last_block.header(),
1✔
165
                &timestamps,
1✔
166
                None,
1✔
167
                FixedHash::zero(),
1✔
168
            )
169
            .unwrap();
1✔
170

171
        // Add an extra timestamp
172
        timestamps.push(EpochTime::now());
1✔
173
        let err = validator
1✔
174
            .validate(
1✔
175
                &*db.db_read_access().unwrap(),
1✔
176
                &candidate_header,
1✔
177
                last_block.header(),
1✔
178
                &timestamps,
1✔
179
                None,
1✔
180
                FixedHash::zero(),
1✔
181
            )
182
            .unwrap_err();
1✔
183
        assert!(matches!(err, ValidationError::IncorrectNumberOfTimestampsProvided {
1✔
184
            actual: 5,
1✔
185
            expected: 4
1✔
186
        }));
1✔
187
    }
1✔
188
}
189
use crate::consensus::BaseNodeConsensusManagerBuilder;
190
#[tokio::test]
191
#[allow(clippy::too_many_lines)]
192
async fn chain_balance_validation() {
1✔
193
    let factories = CryptoFactories::default();
1✔
194
    let consensus_manager = BaseNodeConsensusManagerBuilder::new(Network::Esmeralda)
1✔
195
        .build()
1✔
196
        .unwrap();
1✔
197
    let genesis = consensus_manager.get_genesis_block();
1✔
198
    let pre_mine_value = 5000 * uT;
1✔
199
    let key_manager = create_memory_db_key_manager().await.unwrap();
1✔
200
    let (pre_mine_utxo, pre_mine_key_id, _) = create_utxo(
1✔
201
        pre_mine_value,
1✔
202
        &key_manager,
1✔
203
        &OutputFeatures::default(),
1✔
204
        &TariScript::default(),
1✔
205
        &Covenant::default(),
1✔
206
        MicroMinotari::zero(),
1✔
207
    )
208
    .await;
1✔
209
    let (pk, sig) = create_random_signature_from_secret_key(
1✔
210
        &key_manager,
1✔
211
        pre_mine_key_id,
1✔
212
        0.into(),
1✔
213
        0,
214
        KernelFeatures::empty(),
1✔
215
        TxoStage::Output,
1✔
216
    )
217
    .await;
1✔
218
    let excess = CompressedCommitment::from_public_key(pk.to_public_key().unwrap());
1✔
219
    let kernel =
1✔
220
        TransactionKernel::new_current_version(KernelFeatures::empty(), MicroMinotari::from(0), 0, excess, sig, None);
1✔
221
    let mut gen_block = genesis.block().clone();
1✔
222
    gen_block.body.add_output(pre_mine_utxo);
1✔
223
    gen_block.body.add_kernels([kernel]);
1✔
224
    let mut utxo_sum = UncompressedCommitment::default();
1✔
225
    let mut kernel_sum = UncompressedCommitment::default();
1✔
226
    let burned_sum = UncompressedCommitment::default();
1✔
227
    let mock_store = MockTreeStore::new(true);
1✔
228
    let smt = JellyfishMerkleTree::<_, SmtHasher>::new(&mock_store);
1✔
229
    let mut batch = vec![];
1✔
230
    for output in gen_block.body.outputs() {
795✔
231
        utxo_sum = &output.commitment.to_commitment().unwrap() + &utxo_sum;
795✔
232
        let smt_key = KeyHash(output.commitment.as_bytes().try_into().expect("commitment is 32 bytes"));
795✔
233
        let smt_value = output.smt_hash(0);
795✔
234

795✔
235
        batch.push((smt_key, Some(smt_value.to_vec())));
795✔
236
    }
795✔
237
    for input in gen_block.body.inputs() {
313✔
238
        utxo_sum = &utxo_sum - &input.commitment().unwrap().to_commitment().unwrap();
313✔
239
        let smt_key = KeyHash(
313✔
240
            input
313✔
241
                .commitment()
313✔
242
                .unwrap()
313✔
243
                .as_bytes()
313✔
244
                .try_into()
313✔
245
                .expect("Commitment is 32 bytes"),
313✔
246
        );
313✔
247
        batch.push((smt_key, None));
313✔
248
    }
313✔
249
    for kernel in gen_block.body.kernels() {
315✔
250
        kernel_sum = &kernel.excess.to_commitment().unwrap() + &kernel_sum;
315✔
251
    }
315✔
252
    let root = smt.put_value_set(batch, 0).unwrap();
1✔
253

254
    gen_block.header.output_mr = root.0 .0.into();
1✔
255
    let mut accum = genesis.accumulated_data().clone();
1✔
256
    accum.hash = gen_block.header.hash();
1✔
257

258
    let genesis = ChainBlock::try_construct(Arc::new(gen_block), accum).unwrap();
1✔
259

260
    let total_pre_mine = pre_mine_value + consensus_manager.consensus_constants(0).pre_mine_value();
1✔
261
    let constants = ConsensusConstantsBuilder::new(Network::LocalNet)
1✔
262
        .with_consensus_constants(consensus_manager.consensus_constants(0).clone())
1✔
263
        .with_pre_mine_value(total_pre_mine)
1✔
264
        .build();
1✔
265
    // Create a LocalNet consensus manager that uses custom genesis block that contains an extra pre_mine utxo
266
    let consensus_manager = BaseNodeConsensusManagerBuilder::new(Network::LocalNet)
1✔
267
        .with_block(genesis.clone())
1✔
268
        .add_consensus_constants(constants)
1✔
269
        .build()
1✔
270
        .unwrap();
1✔
271

272
    let db = create_store_with_consensus(consensus_manager.clone());
1✔
273

274
    let validator = ChainBalanceValidator::new(consensus_manager.clone(), factories.clone());
1✔
275
    // Validate the genesis state
276
    validator
1✔
277
        .validate(
1✔
278
            &*db.db_read_access().unwrap(),
1✔
279
            0,
280
            &CompressedCommitment::from_commitment(utxo_sum.clone()),
1✔
281
            &CompressedCommitment::from_commitment(kernel_sum.clone()),
1✔
282
            &CompressedCommitment::from_commitment(burned_sum.clone()),
1✔
283
        )
284
        .unwrap();
1✔
285

286
    //---------------------------------- Add a new coinbase and header --------------------------------------------//
287
    let mut txn = DbTransaction::new();
1✔
288
    let coinbase_value = consensus_manager.get_block_reward_at(1);
1✔
289
    let (coinbase, coinbase_key_id, _) = create_utxo(
1✔
290
        coinbase_value,
1✔
291
        &key_manager,
1✔
292
        &OutputFeatures::create_coinbase(1, None, RangeProofType::BulletProofPlus),
1✔
293
        &TariScript::default(),
1✔
294
        &Covenant::default(),
1✔
295
        MicroMinotari::zero(),
1✔
296
    )
297
    .await;
1✔
298

299
    let (pk, sig) = create_random_signature_from_secret_key(
1✔
300
        &key_manager,
1✔
301
        coinbase_key_id,
1✔
302
        0.into(),
1✔
303
        0,
304
        KernelFeatures::create_coinbase(),
1✔
305
        TxoStage::Output,
1✔
306
    )
307
    .await;
1✔
308
    let excess = CompressedCommitment::from_compressed_key(pk);
1✔
309
    let kernel = KernelBuilder::new()
1✔
310
        .with_signature(sig)
1✔
311
        .with_excess(&excess)
1✔
312
        .with_features(KernelFeatures::COINBASE_KERNEL)
1✔
313
        .build()
1✔
314
        .unwrap();
1✔
315

316
    let mut header1 = BlockHeader::from_previous(genesis.header());
1✔
317
    header1.kernel_mmr_size += 1;
1✔
318
    header1.output_smt_size += 1;
1✔
319
    let achieved_difficulty = AchievedTargetDifficulty::try_construct(
1✔
320
        genesis.header().pow_algo(),
1✔
321
        genesis.accumulated_data().target_difficulty,
1✔
322
        genesis.accumulated_data().achieved_difficulty,
1✔
323
    )
324
    .unwrap();
1✔
325
    let accumulated_data = BlockHeaderAccumulatedDataBuilder::from_previous(genesis.accumulated_data())
1✔
326
        .with_hash(header1.hash())
1✔
327
        .with_achieved_target_difficulty(achieved_difficulty)
1✔
328
        .with_total_kernel_offset(header1.total_kernel_offset.clone())
1✔
329
        .build(consensus_manager.consensus_constants(header1.height))
1✔
330
        .unwrap();
1✔
331
    let header1 = ChainHeader::try_construct(header1, accumulated_data).unwrap();
1✔
332
    txn.insert_chain_header(header1.clone());
1✔
333

334
    let mut mmr_position = 4;
1✔
335

336
    txn.insert_kernel(kernel.clone(), *header1.hash(), mmr_position);
1✔
337
    txn.insert_utxo(coinbase.clone(), *header1.hash(), 1, 0);
1✔
338

339
    db.commit(txn).unwrap();
1✔
340
    utxo_sum = &coinbase.commitment.to_commitment().unwrap() + &utxo_sum;
1✔
341
    kernel_sum = &kernel.excess.to_commitment().unwrap() + &kernel_sum;
1✔
342
    validator
1✔
343
        .validate(
1✔
344
            &*db.db_read_access().unwrap(),
1✔
345
            1,
346
            &CompressedCommitment::from_commitment(utxo_sum.clone()),
1✔
347
            &CompressedCommitment::from_commitment(kernel_sum.clone()),
1✔
348
            &CompressedCommitment::from_commitment(burned_sum.clone()),
1✔
349
        )
350
        .unwrap();
1✔
351

352
    //---------------------------------- Try to inflate --------------------------------------------//
353
    let mut txn = DbTransaction::new();
1✔
354

355
    let v = consensus_manager.get_block_reward_at(2) + uT;
1✔
356
    let (coinbase, spending_key_id, _) = create_utxo(
1✔
357
        v,
1✔
358
        &key_manager,
1✔
359
        &OutputFeatures::create_coinbase(1, None, RangeProofType::BulletProofPlus),
1✔
360
        &TariScript::default(),
1✔
361
        &Covenant::default(),
1✔
362
        MicroMinotari::zero(),
1✔
363
    )
364
    .await;
1✔
365
    let (pk, sig) = create_random_signature_from_secret_key(
1✔
366
        &key_manager,
1✔
367
        spending_key_id,
1✔
368
        0.into(),
1✔
369
        0,
370
        KernelFeatures::create_coinbase(),
1✔
371
        TxoStage::Output,
1✔
372
    )
373
    .await;
1✔
374
    let excess = CompressedCommitment::from_compressed_key(pk);
1✔
375
    let kernel = KernelBuilder::new()
1✔
376
        .with_signature(sig)
1✔
377
        .with_excess(&excess)
1✔
378
        .with_features(KernelFeatures::COINBASE_KERNEL)
1✔
379
        .build()
1✔
380
        .unwrap();
1✔
381

382
    let mut header2 = BlockHeader::from_previous(header1.header());
1✔
383
    header2.kernel_mmr_size += 1;
1✔
384
    header2.output_smt_size += 1;
1✔
385
    let achieved_difficulty = AchievedTargetDifficulty::try_construct(
1✔
386
        genesis.header().pow_algo(),
1✔
387
        genesis.accumulated_data().target_difficulty,
1✔
388
        genesis.accumulated_data().achieved_difficulty,
1✔
389
    )
390
    .unwrap();
1✔
391
    let accumulated_data = BlockHeaderAccumulatedDataBuilder::from_previous(genesis.accumulated_data())
1✔
392
        .with_hash(header2.hash())
1✔
393
        .with_achieved_target_difficulty(achieved_difficulty)
1✔
394
        .with_total_kernel_offset(header2.total_kernel_offset.clone())
1✔
395
        .build(consensus_manager.consensus_constants(header2.height))
1✔
396
        .unwrap();
1✔
397
    let header2 = ChainHeader::try_construct(header2, accumulated_data).unwrap();
1✔
398
    txn.insert_chain_header(header2.clone());
1✔
399
    utxo_sum = &coinbase.commitment.to_commitment().unwrap() + &utxo_sum;
1✔
400
    kernel_sum = &kernel.excess.to_commitment().unwrap() + &kernel_sum;
1✔
401
    txn.insert_utxo(coinbase, *header2.hash(), 2, 0);
1✔
402
    mmr_position += 1;
1✔
403
    txn.insert_kernel(kernel, *header2.hash(), mmr_position);
1✔
404

405
    db.commit(txn).unwrap();
1✔
406

407
    validator
1✔
408
        .validate(
1✔
409
            &*db.db_read_access().unwrap(),
1✔
410
            2,
1✔
411
            &CompressedCommitment::from_commitment(utxo_sum),
1✔
412
            &CompressedCommitment::from_commitment(kernel_sum),
1✔
413
            &CompressedCommitment::from_commitment(burned_sum),
1✔
414
        )
1✔
415
        .unwrap_err();
1✔
416
}
1✔
417

418
#[tokio::test]
419
#[allow(clippy::too_many_lines)]
420
async fn chain_balance_validation_burned() {
1✔
421
    let factories = CryptoFactories::default();
1✔
422
    let consensus_manager = BaseNodeConsensusManagerBuilder::new(Network::Esmeralda)
1✔
423
        .build()
1✔
424
        .unwrap();
1✔
425
    let genesis = consensus_manager.get_genesis_block();
1✔
426
    let pre_mine_value = 5000 * uT;
1✔
427
    let key_manager = create_memory_db_key_manager().await.unwrap();
1✔
428
    let (pre_mine_utxo, pre_mine_key_id, _) = create_utxo(
1✔
429
        pre_mine_value,
1✔
430
        &key_manager,
1✔
431
        &OutputFeatures::default(),
1✔
432
        &TariScript::default(),
1✔
433
        &Covenant::default(),
1✔
434
        MicroMinotari::zero(),
1✔
435
    )
436
    .await;
1✔
437
    let (pk, sig) = create_random_signature_from_secret_key(
1✔
438
        &key_manager,
1✔
439
        pre_mine_key_id,
1✔
440
        0.into(),
1✔
441
        0,
442
        KernelFeatures::empty(),
1✔
443
        TxoStage::Output,
1✔
444
    )
445
    .await;
1✔
446
    let excess = CompressedCommitment::from_compressed_key(pk);
1✔
447
    let kernel =
1✔
448
        TransactionKernel::new_current_version(KernelFeatures::empty(), MicroMinotari::from(0), 0, excess, sig, None);
1✔
449
    let mut gen_block = genesis.block().clone();
1✔
450
    gen_block.body.add_output(pre_mine_utxo);
1✔
451
    gen_block.body.add_kernels([kernel]);
1✔
452
    let mut utxo_sum = UncompressedCommitment::default();
1✔
453
    let mut kernel_sum = UncompressedCommitment::default();
1✔
454
    let mut burned_sum = UncompressedCommitment::default();
1✔
455
    let mock_store = MockTreeStore::new(true);
1✔
456
    let smt = JellyfishMerkleTree::<_, SmtHasher>::new(&mock_store);
1✔
457
    let mut batch = vec![];
1✔
458
    for output in gen_block.body.outputs() {
795✔
459
        utxo_sum = &output.commitment.to_commitment().unwrap() + &utxo_sum;
795✔
460
        if !output.is_burned() {
795✔
461
            let smt_key = KeyHash(output.commitment.as_bytes().try_into().expect("commitment is 32 bytes"));
795✔
462
            let smt_value = output.smt_hash(0);
795✔
463

795✔
464
            batch.push((smt_key, Some(smt_value.to_vec())));
795✔
465
        }
795✔
466
    }
467
    for input in gen_block.body.inputs() {
313✔
468
        utxo_sum = &utxo_sum - &input.commitment().unwrap().to_commitment().unwrap();
313✔
469
        let smt_key = KeyHash(
313✔
470
            input
313✔
471
                .commitment()
313✔
472
                .unwrap()
313✔
473
                .as_bytes()
313✔
474
                .try_into()
313✔
475
                .expect("Commitment is 32 bytes"),
313✔
476
        );
313✔
477
        batch.push((smt_key, None));
313✔
478
    }
313✔
479
    for kernel in gen_block.body.kernels() {
315✔
480
        kernel_sum = &kernel.excess.to_commitment().unwrap() + &kernel_sum;
315✔
481
    }
315✔
482
    let root = smt.put_value_set(batch, 0).unwrap();
1✔
483

484
    gen_block.header.output_mr = root.0 .0.into();
1✔
485
    let mut accum = genesis.accumulated_data().clone();
1✔
486
    accum.hash = gen_block.header.hash();
1✔
487
    let genesis = ChainBlock::try_construct(Arc::new(gen_block), accum).unwrap();
1✔
488
    let total_pre_mine = pre_mine_value + consensus_manager.consensus_constants(0).pre_mine_value();
1✔
489
    let constants = ConsensusConstantsBuilder::new(Network::LocalNet)
1✔
490
        .with_consensus_constants(consensus_manager.consensus_constants(0).clone())
1✔
491
        .with_pre_mine_value(total_pre_mine)
1✔
492
        .build();
1✔
493
    // Create a LocalNet consensus manager that uses rincewind consensus constants and has a custom rincewind genesis
494
    // block that contains an extra pre-mine utxo
495
    let consensus_manager = BaseNodeConsensusManagerBuilder::new(Network::LocalNet)
1✔
496
        .with_block(genesis.clone())
1✔
497
        .add_consensus_constants(constants)
1✔
498
        .build()
1✔
499
        .unwrap();
1✔
500

501
    let db = create_store_with_consensus(consensus_manager.clone());
1✔
502

503
    let validator = ChainBalanceValidator::new(consensus_manager.clone(), factories.clone());
1✔
504
    // Validate the genesis state
505
    validator
1✔
506
        .validate(
1✔
507
            &*db.db_read_access().unwrap(),
1✔
508
            0,
509
            &CompressedCommitment::from_commitment(utxo_sum.clone()),
1✔
510
            &CompressedCommitment::from_commitment(kernel_sum.clone()),
1✔
511
            &CompressedCommitment::from_commitment(burned_sum.clone()),
1✔
512
        )
513
        .unwrap();
1✔
514

515
    //---------------------------------- Add block (coinbase + burned) --------------------------------------------//
516
    let mut txn = DbTransaction::new();
1✔
517
    let coinbase_value = consensus_manager.get_block_reward_at(1) - MicroMinotari::from(100);
1✔
518
    let (coinbase, coinbase_key_id, _) = create_utxo(
1✔
519
        coinbase_value,
1✔
520
        &key_manager,
1✔
521
        &OutputFeatures::create_coinbase(1, None, RangeProofType::RevealedValue),
1✔
522
        &TariScript::default(),
1✔
523
        &Covenant::default(),
1✔
524
        coinbase_value,
1✔
525
    )
526
    .await;
1✔
527
    let (pk, sig) = create_random_signature_from_secret_key(
1✔
528
        &key_manager,
1✔
529
        coinbase_key_id,
1✔
530
        0.into(),
1✔
531
        0,
532
        KernelFeatures::create_coinbase(),
1✔
533
        TxoStage::Output,
1✔
534
    )
535
    .await;
1✔
536
    let excess = CompressedCommitment::from_compressed_key(pk);
1✔
537
    let kernel = KernelBuilder::new()
1✔
538
        .with_signature(sig)
1✔
539
        .with_excess(&excess)
1✔
540
        .with_features(KernelFeatures::COINBASE_KERNEL)
1✔
541
        .build()
1✔
542
        .unwrap();
1✔
543

544
    let (burned, burned_key_id, _) = create_utxo(
1✔
545
        100.into(),
1✔
546
        &key_manager,
1✔
547
        &OutputFeatures::create_burn_output(),
1✔
548
        &TariScript::default(),
1✔
549
        &Covenant::default(),
1✔
550
        MicroMinotari::zero(),
1✔
551
    )
552
    .await;
1✔
553

554
    let (pk2, sig2) = create_random_signature_from_secret_key(
1✔
555
        &key_manager,
1✔
556
        burned_key_id,
1✔
557
        0.into(),
1✔
558
        0,
559
        KernelFeatures::create_burn(),
1✔
560
        TxoStage::Output,
1✔
561
    )
562
    .await;
1✔
563
    let excess2 = CompressedCommitment::from_compressed_key(pk2);
1✔
564
    let kernel2 = KernelBuilder::new()
1✔
565
        .with_signature(sig2)
1✔
566
        .with_excess(&excess2)
1✔
567
        .with_features(KernelFeatures::create_burn())
1✔
568
        .with_burn_commitment(Some(burned.commitment.clone()))
1✔
569
        .build()
1✔
570
        .unwrap();
1✔
571
    burned_sum = &burned_sum + &kernel2.get_burn_commitment().unwrap().to_commitment().unwrap();
1✔
572
    let mut header1 = BlockHeader::from_previous(genesis.header());
1✔
573
    header1.kernel_mmr_size += 2;
1✔
574
    header1.output_smt_size += 2;
1✔
575
    let achieved_difficulty = AchievedTargetDifficulty::try_construct(
1✔
576
        genesis.header().pow_algo(),
1✔
577
        genesis.accumulated_data().target_difficulty,
1✔
578
        genesis.accumulated_data().achieved_difficulty,
1✔
579
    )
580
    .unwrap();
1✔
581
    let accumulated_data = BlockHeaderAccumulatedDataBuilder::from_previous(genesis.accumulated_data())
1✔
582
        .with_hash(header1.hash())
1✔
583
        .with_achieved_target_difficulty(achieved_difficulty)
1✔
584
        .with_total_kernel_offset(header1.total_kernel_offset.clone())
1✔
585
        .build(consensus_manager.consensus_constants(header1.height))
1✔
586
        .unwrap();
1✔
587
    let header1 = ChainHeader::try_construct(header1, accumulated_data).unwrap();
1✔
588
    txn.insert_chain_header(header1.clone());
1✔
589

590
    let mut mmr_position = 4;
1✔
591

592
    txn.insert_kernel(kernel.clone(), *header1.hash(), mmr_position);
1✔
593
    txn.insert_utxo(coinbase.clone(), *header1.hash(), 1, 0);
1✔
594

595
    mmr_position = 5;
1✔
596

597
    txn.insert_kernel(kernel2.clone(), *header1.hash(), mmr_position);
1✔
598
    // txn.insert_pruned_utxo(burned.hash(), *header1.hash(), header1.height(),  0);
599

600
    db.commit(txn).unwrap();
1✔
601
    utxo_sum = &coinbase.commitment.to_commitment().unwrap() + &utxo_sum;
1✔
602
    kernel_sum = &(&kernel.excess.to_commitment().unwrap() + &kernel_sum) + &kernel2.excess.to_commitment().unwrap();
1✔
603
    validator
1✔
604
        .validate(
1✔
605
            &*db.db_read_access().unwrap(),
1✔
606
            1,
1✔
607
            &CompressedCommitment::from_commitment(utxo_sum),
1✔
608
            &CompressedCommitment::from_commitment(kernel_sum),
1✔
609
            &CompressedCommitment::from_commitment(burned_sum),
1✔
610
        )
1✔
611
        .unwrap();
1✔
612
}
1✔
613

614
mod transaction_validator {
615
    use std::convert::TryFrom;
616

617
    use tari_transaction_components::{
618
        transaction_components::{CoinBaseExtra, OutputType, TransactionError},
619
        validation::{transaction::TransactionInternalConsistencyValidator, AggregatedBodyValidationError},
620
    };
621

622
    use super::*;
623
    use crate::consensus::BaseNodeConsensusManagerBuilder;
624

625
    #[tokio::test]
626
    async fn it_rejects_coinbase_outputs() {
1✔
627
        let key_manager = create_memory_db_key_manager().await.unwrap();
1✔
628
        let consensus_manager = BaseNodeConsensusManagerBuilder::new(Network::LocalNet).build().unwrap();
1✔
629
        let db = create_store_with_consensus(consensus_manager.clone());
1✔
630
        let factories = CryptoFactories::default();
1✔
631
        let validator =
1✔
632
            TransactionInternalConsistencyValidator::new(true, consensus_manager.consensus_manager(), factories);
1✔
633
        let features = OutputFeatures::create_coinbase(0, None, RangeProofType::BulletProofPlus);
1✔
634
        let tx = match tx!(MicroMinotari(100_000), fee: MicroMinotari(5), inputs: 1, outputs: 1, features: features, &key_manager)
1✔
635
        {
636
            Ok((tx, _, _)) => tx,
1✔
637
            Err(e) => panic!("Error found: {e}"),
×
638
        };
639
        let tip = db.get_chain_metadata().unwrap();
1✔
640
        let err = validator.validate_with_current_tip(&tx, tip).unwrap_err();
1✔
641
        unpack_enum!(
×
642
            AggregatedBodyValidationError::OutputTypeNotPermitted {
1✔
643
                output_type: OutputType::Coinbase
1✔
644
            } = err
1✔
645
        );
1✔
646
    }
1✔
647

648
    #[tokio::test]
649
    async fn coinbase_extra_must_be_empty() {
1✔
650
        let key_manager = create_memory_db_key_manager().await.unwrap();
1✔
651
        let consensus_manager = BaseNodeConsensusManagerBuilder::new(Network::LocalNet).build().unwrap();
1✔
652
        let db = create_store_with_consensus(consensus_manager.clone());
1✔
653
        let factories = CryptoFactories::default();
1✔
654
        let validator =
1✔
655
            TransactionInternalConsistencyValidator::new(true, consensus_manager.consensus_manager(), factories);
1✔
656
        let mut features = OutputFeatures { ..Default::default() };
1✔
657
        features.coinbase_extra = CoinBaseExtra::try_from(b"deadbeef".to_vec()).unwrap();
1✔
658
        let tx = match tx!(MicroMinotari(100_000), fee: MicroMinotari(5), inputs: 1, outputs: 1, features: features, &key_manager)
1✔
659
        {
660
            Ok((tx, _, _)) => tx,
1✔
661
            Err(e) => panic!("Error found: {e}"),
×
662
        };
663
        let tip = db.get_chain_metadata().unwrap();
1✔
664
        let err = validator.validate_with_current_tip(&tx, tip).unwrap_err();
1✔
665
        assert!(matches!(
1✔
666
            err,
1✔
667
            AggregatedBodyValidationError::TransactionError(
1✔
668
                TransactionError::NonCoinbaseHasOutputFeaturesCoinbaseExtra
1✔
669
            )
1✔
670
        ));
1✔
671
    }
1✔
672
}
673

674
/// Iterator that emits BlockHeaders until a given height. This iterator loads headers in chunks of size `chunk_size`
675
/// for a low memory footprint. The chunk buffer is allocated once and reused.
676
pub struct HeaderIter<'a, B> {
677
    chunk: Vec<BlockHeader>,
678
    chunk_size: usize,
679
    cursor: usize,
680
    is_error: bool,
681
    height: u64,
682
    db: &'a BlockchainDatabase<B>,
683
}
684

685
impl<'a, B> HeaderIter<'a, B> {
686
    #[allow(dead_code)]
687
    pub fn new(db: &'a BlockchainDatabase<B>, height: u64, chunk_size: usize) -> Self {
3✔
688
        Self {
3✔
689
            db,
3✔
690
            chunk_size,
3✔
691
            cursor: 0,
3✔
692
            is_error: false,
3✔
693
            height,
3✔
694
            chunk: Vec::with_capacity(chunk_size),
3✔
695
        }
3✔
696
    }
3✔
697

698
    fn get_next_chunk(&self) -> (u64, u64) {
8✔
699
        #[allow(clippy::cast_possible_truncation)]
700
        let upper_bound = cmp::min(self.cursor + self.chunk_size, self.height as usize);
8✔
701
        (self.cursor as u64, upper_bound as u64)
8✔
702
    }
8✔
703
}
704

705
impl<B: BlockchainBackend> Iterator for HeaderIter<'_, B> {
706
    type Item = Result<BlockHeader, ChainStorageError>;
707

708
    fn next(&mut self) -> Option<Self::Item> {
17✔
709
        if self.is_error {
17✔
710
            return None;
×
711
        }
17✔
712

713
        if self.chunk.is_empty() {
17✔
714
            let (start, end) = self.get_next_chunk();
8✔
715
            // We're done: No more block headers to fetch
716
            if start > end {
8✔
717
                return None;
2✔
718
            }
6✔
719

720
            match self.db.fetch_headers(start..=end) {
6✔
721
                Ok(headers) => {
6✔
722
                    if headers.is_empty() {
6✔
723
                        return None;
1✔
724
                    }
5✔
725
                    self.cursor += headers.len();
5✔
726
                    self.chunk.extend(headers);
5✔
727
                },
728
                Err(err) => {
×
729
                    // On the next call, the iterator will end
730
                    self.is_error = true;
×
731
                    return Some(Err(err));
×
732
                },
733
            }
734
        }
9✔
735

736
        Some(Ok(self.chunk.remove(0)))
14✔
737
    }
17✔
738
}
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

© 2026 Coveralls, Inc