• 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

79.51
/base_layer/core/src/base_node/sync/header_sync/validator.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::cmp::Ordering;
23

24
use log::*;
25
use primitive_types::U512;
26
use tari_common_types::types::{FixedHash, HashOutput};
27
use tari_node_components::blocks::{BlockHeader, BlockHeaderAccumulatedData, BlockHeaderValidationError, ChainHeader};
28
use tari_transaction_components::tari_proof_of_work::PowAlgorithm;
29
use tari_utilities::{epoch_time::EpochTime, hex::Hex};
30

31
use crate::{
32
    base_node::sync::{header_sync::HEADER_SYNC_INITIAL_MAX_HEADERS, BlockHeaderSyncError},
33
    blocks::BlockHeaderAccumulatedDataBuilder,
34
    chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend, ChainStorageError, TargetDifficulties},
35
    common::rolling_vec::RollingVec,
36
    consensus::BaseNodeConsensusManager,
37
    proof_of_work::randomx_factory::RandomXFactory,
38
    validation::{
39
        header::HeaderFullValidator,
40
        tari_rx_vm_key_height,
41
        DifficultyCalculator,
42
        HeaderChainLinkedValidator,
43
        ValidationError,
44
        TARI_RX_VM_KEY_BLOCK_SWAP,
45
    },
46
};
47

48
const LOG_TARGET: &str = "c::bn::header_sync";
49

50
#[derive(Clone)]
51
pub struct BlockHeaderSyncValidator<B> {
52
    db: AsyncBlockchainDb<B>,
53
    state: Option<State>,
54
    consensus_rules: BaseNodeConsensusManager,
55
    validator: HeaderFullValidator,
56
}
57

58
#[derive(Debug, Clone)]
59
struct State {
60
    current_height: u64,
61
    timestamps: RollingVec<EpochTime>,
62
    target_difficulties: TargetDifficulties,
63
    previous_accum: BlockHeaderAccumulatedData,
64
    previous_header: BlockHeader,
65
    valid_headers: Vec<ChainHeader>,
66
    vm_key: Vec<(u64, FixedHash)>,
67
}
68

69
impl<B: BlockchainBackend + 'static> BlockHeaderSyncValidator<B> {
70
    pub fn new(
4✔
71
        db: AsyncBlockchainDb<B>,
4✔
72
        consensus_rules: BaseNodeConsensusManager,
4✔
73
        randomx_factory: RandomXFactory,
4✔
74
    ) -> Self {
4✔
75
        let difficulty_calculator = DifficultyCalculator::new(consensus_rules.clone(), randomx_factory);
4✔
76
        let validator = HeaderFullValidator::new(consensus_rules.clone(), difficulty_calculator);
4✔
77
        Self {
4✔
78
            db,
4✔
79
            state: None,
4✔
80
            consensus_rules,
4✔
81
            validator,
4✔
82
        }
4✔
83
    }
4✔
84

85
    #[allow(clippy::ptr_arg)]
86
    pub async fn initialize_state(&mut self, start_hash: &HashOutput) -> Result<(), BlockHeaderSyncError> {
4✔
87
        let start_header = self
4✔
88
            .db
4✔
89
            .fetch_header_by_block_hash(*start_hash)
4✔
90
            .await?
4✔
91
            .ok_or_else(|| BlockHeaderSyncError::StartHashNotFound(start_hash.to_hex()))?;
4✔
92
        let timestamps = self.db.fetch_block_timestamps(*start_hash).await?;
3✔
93
        let target_difficulties = self.db.fetch_target_difficulties_for_next_block(*start_hash).await?;
3✔
94
        let previous_accum = self
3✔
95
            .db
3✔
96
            .fetch_header_accumulated_data(*start_hash)
3✔
97
            .await?
3✔
98
            .ok_or_else(|| ChainStorageError::ValueNotFound {
3✔
99
                entity: "BlockHeaderAccumulatedData",
100
                field: "hash",
101
                value: start_hash.to_hex(),
×
102
            })?;
×
103
        debug!(
3✔
104
            target: LOG_TARGET,
×
105
            "Setting header validator state ({} timestamp(s), target difficulties: {} SHA3, {} Monero RandomX, {} Tari RandomX)",
×
106
            timestamps.len(),
×
107
            target_difficulties.get(PowAlgorithm::Sha3x).map(|t| t.len()).unwrap_or(0),
×
108
            target_difficulties.get(PowAlgorithm::RandomXM).map(|t| t.len()).unwrap_or(0),
×
109
            target_difficulties.get(PowAlgorithm::RandomXT).map(|t| t.len()).unwrap_or(0),
×
110
        );
111

112
        let gen_hash = *self.consensus_rules.get_genesis_block().hash();
3✔
113
        self.state = Some(State {
3✔
114
            current_height: start_header.height,
3✔
115
            timestamps,
3✔
116
            target_difficulties,
3✔
117
            previous_accum,
3✔
118
            previous_header: start_header,
3✔
119
            // One large allocation is usually better even if it is not always used.
3✔
120
            valid_headers: Vec::with_capacity(HEADER_SYNC_INITIAL_MAX_HEADERS),
3✔
121
            vm_key: vec![(0, gen_hash)],
3✔
122
        });
3✔
123

124
        Ok(())
3✔
125
    }
4✔
126

127
    pub fn current_valid_chain_tip_header(&self) -> Option<&ChainHeader> {
×
128
        self.valid_headers().last()
×
129
    }
×
130

131
    pub async fn validate(&mut self, header: BlockHeader) -> Result<U512, BlockHeaderSyncError> {
3✔
132
        let constants = self.consensus_rules.consensus_constants(header.height).clone();
3✔
133
        if constants.effective_from_height() == header.height {
3✔
134
            if let Some(&mut ref mut mut_state) = self.state.as_mut() {
×
135
                // We need to update the target difficulties for the new algorithm
136
                mut_state
×
137
                    .target_difficulties
×
138
                    .update_algos(&self.consensus_rules, header.height)
×
139
                    .map_err(BlockHeaderSyncError::TargetDifficultiesError)?;
×
140
            }
×
141
        }
3✔
142

143
        let state = self.state();
3✔
144

145
        let target_difficulty = state
3✔
146
            .target_difficulties
3✔
147
            .get(header.pow_algo())
3✔
148
            .map_err(BlockHeaderSyncError::TargetDifficultiesError)?
3✔
149
            .calculate(
3✔
150
                constants.min_pow_difficulty(header.pow_algo()),
3✔
151
                constants.max_pow_difficulty(header.pow_algo()),
3✔
152
            );
153

154
        let result = {
3✔
155
            let txn = self.db.inner().db_read_access()?;
3✔
156
            let vm_key_height = tari_rx_vm_key_height(header.height);
3✔
157
            let vm_key = match txn.fetch_chain_header_by_height(vm_key_height) {
3✔
158
                Ok(header) => *header.hash(),
3✔
159
                Err(_) => {
160
                    // header not found, lets search our cached headers
161
                    let mut vm_key = None;
×
162
                    for (height, hash) in &state.vm_key {
×
163
                        if *height == vm_key_height {
×
164
                            vm_key = Some(*hash);
×
165
                            break;
×
166
                        }
×
167
                    }
168
                    vm_key.ok_or(ChainStorageError::UnexpectedResult(
×
169
                        "Could not find header in database or cache".to_string(),
×
170
                    ))?
×
171
                },
172
            };
173
            self.validator.validate(
3✔
174
                &*txn,
3✔
175
                &header,
3✔
176
                &state.previous_header,
3✔
177
                &state.timestamps,
3✔
178
                Some(target_difficulty),
3✔
179
                vm_key,
3✔
180
            )
181
        };
182
        let achieved_target = match result {
2✔
183
            Ok(achieved_target) => achieved_target,
2✔
184
            // future timelimit validation can succeed at a later time. As the block is not yet valid, we discard it
185
            // for now and ban the peer, but wont blacklist the block.
186
            Err(e @ ValidationError::BlockHeaderError(BlockHeaderValidationError::InvalidTimestampFutureTimeLimit)) => {
×
187
                return Err(e.into())
×
188
            },
189
            // We dont want to mark a block as bad for internal failures
190
            Err(
191
                e @ ValidationError::FatalStorageError(_) |
×
192
                e @ ValidationError::IncorrectNumberOfTimestampsProvided { .. },
×
193
            ) => return Err(e.into()),
×
194
            // We dont have to mark the block twice
195
            Err(e @ ValidationError::BadBlockFound { .. }) => return Err(e.into()),
×
196

197
            Err(e) => {
1✔
198
                let mut txn = self.db.write_transaction();
1✔
199
                txn.insert_bad_block(header.hash(), header.height, e.to_string());
1✔
200
                txn.commit().await?;
1✔
201
                return Err(e.into());
1✔
202
            },
203
        };
204

205
        // Header is valid, add this header onto the validation state for the next round
206
        // Mutable borrow done later in the function to allow multiple immutable borrows before this line. This has
207
        // nothing to do with locking or concurrency.
208
        let state = self.state_mut();
2✔
209
        state.previous_header = header.clone();
2✔
210

211
        // Ensure that timestamps are inserted in sorted order
212
        let maybe_index = state.timestamps.iter().position(|ts| *ts >= header.timestamp());
5✔
213
        match maybe_index {
2✔
214
            Some(pos) => {
×
215
                state.timestamps.insert(pos, header.timestamp());
×
216
            },
×
217
            None => {
2✔
218
                state.timestamps.push(header.timestamp());
2✔
219
            },
2✔
220
        }
221

222
        state.current_height = header.height;
2✔
223
        // Add a "more recent" datapoint onto the target difficulty
224
        state
2✔
225
            .target_difficulties
2✔
226
            .add_back(&header, target_difficulty)
2✔
227
            .map_err(ChainStorageError::UnexpectedResult)?;
2✔
228

229
        let accumulated_data = BlockHeaderAccumulatedDataBuilder::from_previous(&state.previous_accum)
2✔
230
            .with_hash(header.hash())
2✔
231
            .with_achieved_target_difficulty(achieved_target)
2✔
232
            .with_total_kernel_offset(header.total_kernel_offset.clone())
2✔
233
            .build(&constants)?;
2✔
234

235
        let total_accumulated_difficulty = accumulated_data.total_accumulated_difficulty;
2✔
236
        // NOTE: accumulated_data constructed from header so they are guaranteed to correspond
237
        let chain_header = ChainHeader::try_construct(header, accumulated_data).unwrap();
2✔
238

239
        state.previous_accum = chain_header.accumulated_data().clone();
2✔
240
        if chain_header.header().height.is_multiple_of(TARI_RX_VM_KEY_BLOCK_SWAP) {
2✔
241
            // we need to save the hash of this header and height
×
242
            state.vm_key.push((chain_header.header().height, *chain_header.hash()));
×
243
        }
2✔
244
        state.valid_headers.push(chain_header);
2✔
245

246
        Ok(total_accumulated_difficulty)
2✔
247
    }
3✔
248

249
    /// Drains and returns all the headers that were validated.
250
    ///
251
    /// ## Panics
252
    ///
253
    /// Panics if initialize_state was not called prior to calling this function
254
    pub fn take_valid_headers(&mut self) -> Vec<ChainHeader> {
×
255
        self.state_mut().valid_headers.drain(..).collect::<Vec<_>>()
×
256
    }
×
257

258
    /// Returns a slice containing the current valid headers
259
    ///
260
    /// ## Panics
261
    ///
262
    /// Panics if initialize_state was not called prior to calling this function
263
    pub fn valid_headers(&self) -> &[ChainHeader] {
4✔
264
        &self.state().valid_headers
4✔
265
    }
4✔
266

267
    pub fn compare_chains(&self, our_header: &ChainHeader, their_header: &ChainHeader) -> Ordering {
×
268
        debug!(
×
269
            target: LOG_TARGET,
×
270
            "Comparing PoW on remote header #{} and local header #{}",
×
271
            their_header.height(),
×
272
            our_header.height()
×
273
        );
274

275
        self.consensus_rules
×
276
            .chain_strength_comparer()
×
277
            .compare(our_header, their_header)
×
278
    }
×
279

280
    fn state_mut(&mut self) -> &mut State {
2✔
281
        self.state
2✔
282
            .as_mut()
2✔
283
            .expect("state_mut() called before state was initialized (using the `begin` method)")
2✔
284
    }
2✔
285

286
    fn state(&self) -> &State {
8✔
287
        self.state
8✔
288
            .as_ref()
8✔
289
            .expect("state() called before state was initialized (using the `begin` method)")
8✔
290
    }
8✔
291
}
292

293
#[cfg(test)]
294
mod test {
295
    use tari_common::configuration::Network;
296
    use tari_test_utils::unpack_enum;
297
    use tari_transaction_components::tari_proof_of_work::PowAlgorithm;
298

299
    use super::*;
300
    use crate::test_helpers::blockchain::{create_new_blockchain, TempDatabase};
301

302
    fn setup() -> (
4✔
303
        BlockHeaderSyncValidator<TempDatabase>,
4✔
304
        AsyncBlockchainDb<TempDatabase>,
4✔
305
        BaseNodeConsensusManager,
4✔
306
    ) {
4✔
307
        let rules = BaseNodeConsensusManager::builder(Network::LocalNet).build().unwrap();
4✔
308
        let randomx_factory = RandomXFactory::default();
4✔
309
        let db = create_new_blockchain();
4✔
310
        (
4✔
311
            BlockHeaderSyncValidator::new(db.clone().into(), rules.clone(), randomx_factory),
4✔
312
            db.into(),
4✔
313
            rules,
4✔
314
        )
4✔
315
    }
4✔
316

317
    async fn setup_with_headers(
3✔
318
        n: usize,
3✔
319
    ) -> (
3✔
320
        BlockHeaderSyncValidator<TempDatabase>,
3✔
321
        AsyncBlockchainDb<TempDatabase>,
3✔
322
        ChainHeader,
3✔
323
    ) {
3✔
324
        let (validator, db, cm) = setup();
3✔
325
        let mut tip = db.fetch_tip_header().await.unwrap();
3✔
326
        for _ in 0..n {
3✔
327
            let mut header = BlockHeader::from_previous(tip.header());
14✔
328
            header.version = cm.consensus_constants(header.height).blockchain_version().into();
14✔
329
            // Needed to have unique keys for the blockchain db mmr count indexes (MDB_KEY_EXIST error)
330
            header.kernel_mmr_size += 1;
14✔
331
            header.output_smt_size += 1;
14✔
332
            let acc_data = BlockHeaderAccumulatedData::genesis(header.hash(), header.total_kernel_offset.clone());
14✔
333

334
            let chain_header = ChainHeader::try_construct(header.clone(), acc_data.clone()).unwrap();
14✔
335
            db.insert_valid_headers(vec![chain_header.clone()]).await.unwrap();
14✔
336
            tip = chain_header;
14✔
337
        }
338

339
        (validator, db, tip)
3✔
340
    }
3✔
341

342
    mod initialize_state {
343
        use std::convert::TryInto;
344

345
        use super::*;
346

347
        #[tokio::test]
348
        async fn it_initializes_state_to_given_header() {
1✔
349
            let (mut validator, _, tip) = setup_with_headers(1).await;
1✔
350
            validator.initialize_state(&tip.header().hash()).await.unwrap();
1✔
351
            let state = validator.state();
1✔
352
            assert!(state.valid_headers.is_empty());
1✔
353
            assert_eq!(state.target_difficulties.get(PowAlgorithm::Sha3x).unwrap().len(), 2);
1✔
354
            assert!(state
1✔
355
                .target_difficulties
1✔
356
                .get(PowAlgorithm::RandomXM)
1✔
357
                .unwrap()
1✔
358
                .is_empty());
1✔
359
            assert_eq!(state.timestamps.len(), 2);
1✔
360
            assert_eq!(state.current_height, 1);
1✔
361
        }
1✔
362

363
        #[tokio::test]
364
        async fn it_errors_if_hash_does_not_exist() {
1✔
365
            let (mut validator, _, _cm) = setup();
1✔
366
            let start_hash = vec![0; 32];
1✔
367
            let err = validator
1✔
368
                .initialize_state(&start_hash.clone().try_into().unwrap())
1✔
369
                .await
1✔
370
                .unwrap_err();
1✔
371
            unpack_enum!(BlockHeaderSyncError::StartHashNotFound(hash) = err);
1✔
372
            assert_eq!(hash, start_hash.to_hex());
1✔
373
        }
1✔
374
    }
375

376
    mod validate {
377
        use super::*;
378

379
        #[tokio::test]
380
        async fn it_passes_if_headers_are_valid() {
1✔
381
            let (mut validator, _, tip) = setup_with_headers(1).await;
1✔
382
            validator.initialize_state(tip.hash()).await.unwrap();
1✔
383
            assert!(validator.valid_headers().is_empty());
1✔
384
            let mut next = BlockHeader::from_previous(tip.header());
1✔
385
            next.timestamp = tip.header().timestamp.checked_add(EpochTime::from(1)).unwrap();
1✔
386
            validator.validate(next).await.unwrap();
1✔
387
            assert_eq!(validator.valid_headers().len(), 1);
1✔
388
            let tip = validator.valid_headers().last().cloned().unwrap();
1✔
389
            let mut next = BlockHeader::from_previous(tip.header());
1✔
390
            next.timestamp = tip.header().timestamp.checked_add(EpochTime::from(1)).unwrap();
1✔
391
            validator.validate(next).await.unwrap();
1✔
392
            assert_eq!(validator.valid_headers().len(), 2);
1✔
393
        }
1✔
394

395
        #[tokio::test]
396
        async fn it_fails_if_height_is_not_serial() {
1✔
397
            let (mut validator, _, tip) = setup_with_headers(12).await;
1✔
398
            validator.initialize_state(tip.hash()).await.unwrap();
1✔
399
            let mut next = BlockHeader::from_previous(tip.header());
1✔
400
            next.height = 14;
1✔
401
            let err = validator.validate(next).await.unwrap_err();
1✔
402
            unpack_enum!(BlockHeaderSyncError::ValidationFailed(val_err) = err);
1✔
403
            unpack_enum!(ValidationError::BlockHeaderError(header_err) = val_err);
1✔
404
            unpack_enum!(BlockHeaderValidationError::InvalidHeight { actual, expected } = header_err);
1✔
405
            assert_eq!(actual, 14);
1✔
406
            assert_eq!(expected, 13);
1✔
407
        }
1✔
408
    }
409
}
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