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

tari-project / tari / 24709704177

21 Apr 2026 07:25AM UTC coverage: 60.325% (-0.7%) from 61.03%
24709704177

push

github

web-flow
fix: better sync (#7774)

Description
---
Makes sure we keep the sync peer initial state, and listens for a
termination signal for a closed connection.

24 of 36 new or added lines in 4 files covered. (66.67%)

896 existing lines in 29 files now uncovered.

69979 of 116003 relevant lines covered (60.33%)

224152.31 hits per line

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

91.63
/base_layer/core/src/validation/header/header_full_validator.rs
1
// Copyright 2022. 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
use std::cmp;
24

25
use log::warn;
26
use tari_common_types::types::FixedHash;
27
use tari_node_components::blocks::{BlockHeader, BlockHeaderValidationError};
28
use tari_transaction_components::{
29
    consensus::ConsensusConstants,
30
    tari_proof_of_work::{Difficulty, PowAlgorithm, PowError, ProofOfWork},
31
};
32
use tari_utilities::{epoch_time::EpochTime, hex::Hex};
33

34
use crate::{
35
    chain_storage::BlockchainBackend,
36
    consensus::BaseNodeConsensusManager,
37
    proof_of_work::AchievedTargetDifficulty,
38
    validation::{
39
        DifficultyCalculator,
40
        HeaderChainLinkedValidator,
41
        ValidationError,
42
        helpers::{check_header_timestamp_greater_than_median, check_target_difficulty},
43
    },
44
};
45
pub const LOG_TARGET: &str = "c::val::header_full_validator";
46

47
#[derive(Clone)]
48
pub struct HeaderFullValidator {
49
    rules: BaseNodeConsensusManager,
50
    difficulty_calculator: DifficultyCalculator,
51
    gen_hash: FixedHash,
52
}
53

54
impl HeaderFullValidator {
55
    pub fn new(rules: BaseNodeConsensusManager, difficulty_calculator: DifficultyCalculator) -> Self {
34✔
56
        let gen_hash = *rules.get_genesis_block().hash();
34✔
57
        Self {
34✔
58
            rules,
34✔
59
            difficulty_calculator,
34✔
60
            gen_hash,
34✔
61
        }
34✔
62
    }
34✔
63
}
64

65
impl<B: BlockchainBackend> HeaderChainLinkedValidator<B> for HeaderFullValidator {
66
    fn validate(
45✔
67
        &self,
45✔
68
        db: &B,
45✔
69
        header: &BlockHeader,
45✔
70
        prev_header: &BlockHeader,
45✔
71
        prev_timestamps: &[EpochTime],
45✔
72
        target_difficulty: Option<Difficulty>,
45✔
73
        vm_key: FixedHash,
45✔
74
    ) -> Result<AchievedTargetDifficulty, ValidationError> {
45✔
75
        let constants = self.rules.consensus_constants(header.height);
45✔
76

77
        check_not_bad_block(db, header.hash())?;
45✔
78
        check_blockchain_version(constants, header.version)?;
45✔
79
        check_height(header, prev_header)?;
44✔
80
        check_prev_hash(header, prev_header)?;
42✔
81

82
        sanity_check_timestamp_count(header, prev_timestamps, constants)?;
42✔
83
        check_header_timestamp_greater_than_median(header, prev_timestamps)?;
40✔
84

85
        check_timestamp_ftl(header, &self.rules)?;
40✔
86
        check_pow_data(header, constants)?;
40✔
87
        let achieved_target = if let Some(target) = target_difficulty {
39✔
88
            check_target_difficulty(
22✔
89
                header,
22✔
90
                target,
22✔
91
                &self.difficulty_calculator.randomx_factory,
22✔
92
                &self.gen_hash,
22✔
93
                &self.rules,
22✔
94
                vm_key,
22✔
95
            )?
×
96
        } else {
97
            self.difficulty_calculator
17✔
98
                .check_achieved_and_target_difficulty(db, header)?
17✔
99
        };
100

101
        Ok(achieved_target)
37✔
102
    }
45✔
103
}
104

105
/// This is a sanity check for the information provided by the caller, rather than a validation for the header itself.
106
fn sanity_check_timestamp_count(
42✔
107
    header: &BlockHeader,
42✔
108
    timestamps: &[EpochTime],
42✔
109
    consensus_constants: &ConsensusConstants,
42✔
110
) -> Result<(), ValidationError> {
42✔
111
    let expected_timestamp_count = cmp::min(consensus_constants.median_timestamp_count() as u64, header.height);
42✔
112
    // Empty `timestamps` is never valid
113
    if timestamps.is_empty() {
42✔
114
        return Err(ValidationError::IncorrectNumberOfTimestampsProvided {
2✔
115
            expected: expected_timestamp_count,
2✔
116
            actual: 0,
2✔
117
        });
2✔
118
    }
40✔
119

120
    if timestamps.len() as u64 != expected_timestamp_count {
40✔
UNCOV
121
        return Err(ValidationError::IncorrectNumberOfTimestampsProvided {
×
UNCOV
122
            actual: timestamps.len() as u64,
×
UNCOV
123
            expected: expected_timestamp_count,
×
UNCOV
124
        });
×
125
    }
40✔
126

127
    Ok(())
40✔
128
}
42✔
129

130
fn check_height(header: &BlockHeader, prev_header: &BlockHeader) -> Result<(), ValidationError> {
44✔
131
    if header.height != prev_header.height + 1 {
44✔
132
        return Err(ValidationError::BlockHeaderError(
2✔
133
            BlockHeaderValidationError::InvalidHeight {
2✔
134
                expected: prev_header.height + 1,
2✔
135
                actual: header.height,
2✔
136
            },
2✔
137
        ));
2✔
138
    }
42✔
139

140
    Ok(())
42✔
141
}
44✔
142

143
fn check_prev_hash(header: &BlockHeader, prev_header: &BlockHeader) -> Result<(), ValidationError> {
42✔
144
    if header.prev_hash != prev_header.hash() {
42✔
145
        return Err(ValidationError::BlockHeaderError(
×
146
            BlockHeaderValidationError::InvalidPreviousHash {
×
147
                expected: prev_header.hash(),
×
148
                actual: header.prev_hash,
×
149
            },
×
150
        ));
×
151
    }
42✔
152

153
    Ok(())
42✔
154
}
42✔
155

156
fn check_blockchain_version(constants: &ConsensusConstants, version: u16) -> Result<(), ValidationError> {
45✔
157
    if constants.valid_blockchain_version_range().contains(&version) {
45✔
158
        Ok(())
44✔
159
    } else {
160
        Err(ValidationError::InvalidBlockchainVersion { version })
1✔
161
    }
162
}
45✔
163

164
/// This function tests that the block timestamp is less than the FTL
165
pub fn check_timestamp_ftl(
40✔
166
    block_header: &BlockHeader,
40✔
167
    consensus_manager: &BaseNodeConsensusManager,
40✔
168
) -> Result<(), ValidationError> {
40✔
169
    if block_header.timestamp > consensus_manager.consensus_constants(block_header.height).ftl() {
40✔
170
        warn!(
×
171
            target: LOG_TARGET,
×
172
            "Invalid Future Time Limit on block:{}",
173
            block_header.hash().to_hex()
×
174
        );
175
        return Err(ValidationError::BlockHeaderError(
×
176
            BlockHeaderValidationError::InvalidTimestampFutureTimeLimit,
×
177
        ));
×
178
    }
40✔
179
    Ok(())
40✔
180
}
40✔
181

182
fn check_not_bad_block<B: BlockchainBackend>(db: &B, hash: FixedHash) -> Result<(), ValidationError> {
45✔
183
    let (is_bad_block, reason) = db.bad_block_exists(hash)?;
45✔
184
    if is_bad_block {
45✔
185
        return Err(ValidationError::BadBlockFound {
×
186
            hash: hash.to_hex(),
×
187
            reason,
×
188
        });
×
189
    }
45✔
190
    Ok(())
45✔
191
}
45✔
192

193
fn check_allowed_algos(pow: &ProofOfWork, allowed_algos: &[PowAlgorithm]) -> Result<(), ValidationError> {
42✔
194
    if !allowed_algos.contains(&pow.pow_algo) {
42✔
195
        return Err(ValidationError::BlockHeaderError(
1✔
196
            BlockHeaderValidationError::InvalidPowAlgorithm(pow.pow_algo.to_string()),
1✔
197
        ));
1✔
198
    }
41✔
199
    Ok(())
41✔
200
}
42✔
201

202
/// Check the PoW data in the BlockHeader. This currently only applies to blocks merged mined with Monero.
203
fn check_pow_data(block_header: &BlockHeader, consensus_constants: &ConsensusConstants) -> Result<(), ValidationError> {
40✔
204
    let allowed_algos = consensus_constants.current_permitted_pow_algos();
40✔
205
    check_allowed_algos(&block_header.pow, &allowed_algos)?;
40✔
206
    check_pow_data_inner(
40✔
207
        &block_header.pow,
40✔
208
        block_header.nonce,
40✔
209
        consensus_constants.cuckaroo_cycle_length(),
40✔
210
        consensus_constants.cuckaroo_edge_bits(),
40✔
211
    )
212
}
40✔
213

214
fn check_pow_data_inner(
52✔
215
    pow: &ProofOfWork,
52✔
216
    nonce: u64,
52✔
217
    cuckaroo_cycle_length: u8,
52✔
218
    cuckaroo_edge_bits: u8,
52✔
219
) -> Result<(), ValidationError> {
52✔
220
    match pow.pow_algo {
52✔
221
        PowAlgorithm::RandomXM => {
222
            if nonce != 0 {
8✔
223
                return Err(ValidationError::BlockHeaderError(
2✔
224
                    BlockHeaderValidationError::InvalidNonce,
2✔
225
                ));
2✔
226
            }
6✔
227
            Ok(())
6✔
228
        },
229
        PowAlgorithm::RandomXT => {
230
            if pow.pow_data.len() > 32 {
3✔
231
                return Err(PowError::RandomxTPowDataTooLong.into());
1✔
232
            }
2✔
233
            Ok(())
2✔
234
        },
235
        PowAlgorithm::Sha3x => {
236
            if !pow.pow_data.is_empty() {
36✔
237
                return Err(PowError::Sha3HeaderNonEmptyPowBytes.into());
1✔
238
            }
35✔
239
            Ok(())
35✔
240
        },
241
        PowAlgorithm::Cuckaroo => {
242
            let cycle_length = cuckaroo_cycle_length as usize;
5✔
243
            let edge_bits = cuckaroo_edge_bits as usize;
5✔
244
            let total_packed_size = cycle_length * edge_bits;
5✔
245
            let remainder = total_packed_size % 8;
5✔
246
            let total_bytes = if remainder != 0 {
5✔
247
                total_packed_size / 8 + 1
3✔
248
            } else {
249
                total_packed_size / 8
2✔
250
            };
251

252
            if pow.pow_data.len() != total_bytes {
5✔
253
                return Err(PowError::CuckarooPowDataSizeMismatch {
2✔
254
                    expected: total_bytes,
2✔
255
                    actual: pow.pow_data.len(),
2✔
256
                }
2✔
257
                .into());
2✔
258
            }
3✔
259

260
            if remainder != 0 {
3✔
261
                // Ensure that the last byte is not padded with zeros
262
                let last_byte = *pow.pow_data.get(total_bytes - 1).expect("Already checked");
2✔
263

264
                let padding_mask = (1 << remainder) - 1;
2✔
265
                let mask = 0xff ^ padding_mask; // Mask to check if the last byte is padded
2✔
266

267
                if last_byte & mask != 0 {
2✔
268
                    return Err(PowError::CuckarooPowDataNonZeroPadding {
1✔
269
                        padding: last_byte & mask,
1✔
270
                    }
1✔
271
                    .into());
1✔
272
                }
1✔
273
            }
1✔
274
            Ok(())
2✔
275
        },
276
    }
277
}
52✔
278

279
#[cfg(test)]
280
mod test {
281
    use super::*;
282

283
    #[test]
284
    fn test_check_pow_data_allowed_algos() {
1✔
285
        let allowed_algos = vec![PowAlgorithm::RandomXM];
1✔
286

287
        let pow = ProofOfWork::new(PowAlgorithm::RandomXM);
1✔
288
        let res = check_allowed_algos(&pow, &allowed_algos);
1✔
289
        assert!(res.is_ok());
1✔
290
        let pow = ProofOfWork::new(PowAlgorithm::RandomXT);
1✔
291

292
        let res = check_allowed_algos(&pow, &allowed_algos);
1✔
293
        assert!(res.is_err());
1✔
294
    }
1✔
295

296
    #[test]
297
    fn test_check_pow_data_randomxm() {
1✔
298
        let pow = ProofOfWork::new(PowAlgorithm::RandomXM);
1✔
299
        let res = check_pow_data_inner(&pow, 0, 0, 0);
1✔
300
        assert!(res.is_ok());
1✔
301

302
        let pow = ProofOfWork::new(PowAlgorithm::RandomXM);
1✔
303
        let res = check_pow_data_inner(&pow, 1, 0, 0);
1✔
304
        assert!(res.is_err());
1✔
305
    }
1✔
306

307
    #[test]
308
    fn test_check_pow_data_randomxt() {
1✔
309
        let pow = ProofOfWork::new(PowAlgorithm::RandomXT);
1✔
310
        let res = check_pow_data_inner(&pow, 0, 0, 0);
1✔
311
        assert!(res.is_ok());
1✔
312

313
        let pow = ProofOfWork::new(PowAlgorithm::RandomXT);
1✔
314
        let res = check_pow_data_inner(&pow, 0, 0, 0);
1✔
315
        assert!(res.is_ok());
1✔
316

317
        let pow = ProofOfWork {
1✔
318
            pow_algo: PowAlgorithm::RandomXT,
1✔
319
            pow_data: vec![0; 33].try_into().unwrap(),
1✔
320
        };
1✔
321
        let res = check_pow_data_inner(&pow, 0, 0, 0);
1✔
322
        assert!(res.is_err());
1✔
323
    }
1✔
324

325
    #[test]
326
    fn test_check_pow_data_sha3x() {
1✔
327
        let pow = ProofOfWork::new(PowAlgorithm::Sha3x);
1✔
328
        let res = check_pow_data_inner(&pow, 0, 0, 0);
1✔
329
        assert!(res.is_ok());
1✔
330

331
        let pow = ProofOfWork {
1✔
332
            pow_algo: PowAlgorithm::Sha3x,
1✔
333
            pow_data: vec![1].try_into().unwrap(),
1✔
334
        };
1✔
335
        let res = check_pow_data_inner(&pow, 0, 0, 0);
1✔
336
        assert!(res.is_err());
1✔
337
    }
1✔
338

339
    #[test]
340
    fn test_check_pow_data_cuckaroo_data_size_mismatch() {
1✔
341
        let pow = ProofOfWork {
1✔
342
            pow_algo: PowAlgorithm::Cuckaroo,
1✔
343
            pow_data: vec![0u8; 10].try_into().unwrap(),
1✔
344
        };
1✔
345
        // Check with multiple of 8
346
        let res = check_pow_data_inner(&pow, 0, 10, 8);
1✔
347
        assert!(res.is_ok());
1✔
348

349
        // Check with non-multiple of 8
350
        let pow = ProofOfWork {
1✔
351
            pow_algo: PowAlgorithm::Cuckaroo,
1✔
352
            pow_data: vec![0u8; 11].try_into().unwrap(),
1✔
353
        };
1✔
354
        let res = check_pow_data_inner(&pow, 0, 9, 9);
1✔
355
        assert!(res.is_ok());
1✔
356

357
        // Check now with invalid data.
358

359
        let pow = ProofOfWork {
1✔
360
            pow_algo: PowAlgorithm::Cuckaroo,
1✔
361
            pow_data: vec![0u8; 11].try_into().unwrap(),
1✔
362
        };
1✔
363
        let res = check_pow_data_inner(&pow, 0, 10, 8);
1✔
364
        assert!(res.is_err());
1✔
365

366
        let pow = ProofOfWork {
1✔
367
            pow_algo: PowAlgorithm::Cuckaroo,
1✔
368
            pow_data: vec![0u8; 12].try_into().unwrap(),
1✔
369
        };
1✔
370
        let res = check_pow_data_inner(&pow, 0, 9, 9);
1✔
371
        assert!(res.is_err());
1✔
372
    }
1✔
373

374
    #[test]
375
    fn test_check_pow_data_cuckaroo_non_zero_padding() {
1✔
376
        let pow = ProofOfWork {
1✔
377
            pow_algo: PowAlgorithm::Cuckaroo,
1✔
378
            pow_data: vec![128u8; 11].try_into().unwrap(),
1✔
379
        };
1✔
380
        let res = check_pow_data_inner(&pow, 0, 9, 9);
1✔
381
        assert!(res.is_err());
1✔
382
    }
1✔
383
}
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