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

tari-project / tari / 15120110241

19 May 2025 06:08PM UTC coverage: 73.213% (-0.06%) from 73.269%
15120110241

push

github

web-flow
feat!: add second tari only randomx mining (#7057)

Description
---

Adds in a second randomx algo mining option, only mining tari.

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

- **New Features**
- Introduced distinct support for Monero RandomX and Tari RandomX
proof-of-work algorithms with separate difficulty tracking, hash rate
reporting, and block template caching.
- Added a new VM key field in block results to enhance mining and
validation processes.
- Extended miner configuration and mining logic to support multiple
proof-of-work algorithms including Tari RandomX.

- **Bug Fixes**
- Improved difficulty and hash rate accuracy by separating Monero and
Tari RandomX calculations and metrics.

- **Refactor**
- Renamed and split data structures, enums, protobuf messages, and
methods to differentiate between Monero and Tari RandomX.
- Updated consensus, validation, and chain strength comparison to handle
multiple RandomX variants.
- Migrated accumulated difficulty representations from 256-bit to
512-bit integers for enhanced precision.
- Generalized difficulty window handling to support multiple
proof-of-work algorithms dynamically.

- **Documentation**
- Clarified comments and field descriptions to reflect the distinction
between Monero and Tari RandomX algorithms.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

170 of 371 new or added lines in 26 files covered. (45.82%)

40 existing lines in 12 files now uncovered.

82064 of 112089 relevant lines covered (73.21%)

274996.0 hits per line

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

90.39
/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::HashOutput;
27
use tari_utilities::{epoch_time::EpochTime, hex::Hex};
28

29
use crate::{
30
    base_node::sync::{header_sync::HEADER_SYNC_INITIAL_MAX_HEADERS, BlockHeaderSyncError},
31
    blocks::{BlockHeader, BlockHeaderAccumulatedData, BlockHeaderValidationError, ChainHeader},
32
    chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend, ChainStorageError, TargetDifficulties},
33
    common::rolling_vec::RollingVec,
34
    consensus::ConsensusManager,
35
    proof_of_work::{randomx_factory::RandomXFactory, PowAlgorithm},
36
    validation::{header::HeaderFullValidator, DifficultyCalculator, HeaderChainLinkedValidator, ValidationError},
37
};
38

39
const LOG_TARGET: &str = "c::bn::header_sync";
40

41
#[derive(Clone)]
42
pub struct BlockHeaderSyncValidator<B> {
43
    db: AsyncBlockchainDb<B>,
44
    state: Option<State>,
45
    consensus_rules: ConsensusManager,
46
    validator: HeaderFullValidator,
47
}
48

49
#[derive(Debug, Clone)]
50
struct State {
51
    current_height: u64,
52
    timestamps: RollingVec<EpochTime>,
53
    target_difficulties: TargetDifficulties,
54
    previous_accum: BlockHeaderAccumulatedData,
55
    previous_header: BlockHeader,
56
    valid_headers: Vec<ChainHeader>,
57
}
58

59
impl<B: BlockchainBackend + 'static> BlockHeaderSyncValidator<B> {
60
    pub fn new(db: AsyncBlockchainDb<B>, consensus_rules: ConsensusManager, randomx_factory: RandomXFactory) -> Self {
20✔
61
        let difficulty_calculator = DifficultyCalculator::new(consensus_rules.clone(), randomx_factory);
20✔
62
        let validator = HeaderFullValidator::new(consensus_rules.clone(), difficulty_calculator);
20✔
63
        Self {
20✔
64
            db,
20✔
65
            state: None,
20✔
66
            consensus_rules,
20✔
67
            validator,
20✔
68
        }
20✔
69
    }
20✔
70

71
    #[allow(clippy::ptr_arg)]
72
    pub async fn initialize_state(&mut self, start_hash: &HashOutput) -> Result<(), BlockHeaderSyncError> {
16✔
73
        let start_header = self
16✔
74
            .db
16✔
75
            .fetch_header_by_block_hash(*start_hash)
16✔
76
            .await?
16✔
77
            .ok_or_else(|| BlockHeaderSyncError::StartHashNotFound(start_hash.to_hex()))?;
16✔
78
        let timestamps = self.db.fetch_block_timestamps(*start_hash).await?;
15✔
79
        let target_difficulties = self.db.fetch_target_difficulties_for_next_block(*start_hash).await?;
15✔
80
        let previous_accum = self
15✔
81
            .db
15✔
82
            .fetch_header_accumulated_data(*start_hash)
15✔
83
            .await?
15✔
84
            .ok_or_else(|| ChainStorageError::ValueNotFound {
15✔
85
                entity: "BlockHeaderAccumulatedData",
×
86
                field: "hash",
×
87
                value: start_hash.to_hex(),
×
88
            })?;
15✔
89
        debug!(
15✔
90
            target: LOG_TARGET,
×
NEW
91
            "Setting header validator state ({} timestamp(s), target difficulties: {} SHA3, {} Monero RandomX, {} Tari RandomX)",
×
92
            timestamps.len(),
×
NEW
93
            target_difficulties.get(PowAlgorithm::Sha3x).map(|t| t.len()).unwrap_or(0),
×
NEW
94
            target_difficulties.get(PowAlgorithm::RandomXM).map(|t| t.len()).unwrap_or(0),
×
NEW
95
            target_difficulties.get(PowAlgorithm::RandomXT).map(|t| t.len()).unwrap_or(0),
×
96
        );
97
        self.state = Some(State {
15✔
98
            current_height: start_header.height,
15✔
99
            timestamps,
15✔
100
            target_difficulties,
15✔
101
            previous_accum,
15✔
102
            previous_header: start_header,
15✔
103
            // One large allocation is usually better even if it is not always used.
15✔
104
            valid_headers: Vec::with_capacity(HEADER_SYNC_INITIAL_MAX_HEADERS),
15✔
105
        });
15✔
106

15✔
107
        Ok(())
15✔
108
    }
16✔
109

110
    pub fn current_valid_chain_tip_header(&self) -> Option<&ChainHeader> {
12✔
111
        self.valid_headers().last()
12✔
112
    }
12✔
113

114
    pub async fn validate(&mut self, header: BlockHeader) -> Result<U512, BlockHeaderSyncError> {
81✔
115
        let state = self.state();
81✔
116
        let constants = self.consensus_rules.consensus_constants(header.height);
81✔
117

118
        let target_difficulty = state
81✔
119
            .target_difficulties
81✔
120
            .get(header.pow_algo())
81✔
121
            .map_err(BlockHeaderSyncError::TargetDifficultiesError)?
81✔
122
            .calculate(
81✔
123
                constants.min_pow_difficulty(header.pow_algo()),
81✔
124
                constants.max_pow_difficulty(header.pow_algo()),
81✔
125
            );
81✔
126

127
        let result = {
81✔
128
            let txn = self.db.inner().db_read_access()?;
81✔
129
            self.validator.validate(
81✔
130
                &*txn,
81✔
131
                &header,
81✔
132
                &state.previous_header,
81✔
133
                &state.timestamps,
81✔
134
                Some(target_difficulty),
81✔
135
            )
81✔
136
        };
137
        let achieved_target = match result {
80✔
138
            Ok(achieved_target) => achieved_target,
80✔
139
            // future timelimit validation can succeed at a later time. As the block is not yet valid, we discard it
140
            // for now and ban the peer, but wont blacklist the block.
141
            Err(e @ ValidationError::BlockHeaderError(BlockHeaderValidationError::InvalidTimestampFutureTimeLimit)) => {
×
142
                return Err(e.into())
×
143
            },
144
            // We dont want to mark a block as bad for internal failures
145
            Err(
146
                e @ ValidationError::FatalStorageError(_) |
×
147
                e @ ValidationError::IncorrectNumberOfTimestampsProvided { .. },
×
148
            ) => return Err(e.into()),
×
149
            // We dont have to mark the block twice
150
            Err(e @ ValidationError::BadBlockFound { .. }) => return Err(e.into()),
×
151

152
            Err(e) => {
1✔
153
                let mut txn = self.db.write_transaction();
1✔
154
                txn.insert_bad_block(header.hash(), header.height, e.to_string());
1✔
155
                txn.commit().await?;
1✔
156
                return Err(e.into());
1✔
157
            },
158
        };
159

160
        // Header is valid, add this header onto the validation state for the next round
161
        // Mutable borrow done later in the function to allow multiple immutable borrows before this line. This has
162
        // nothing to do with locking or concurrency.
163
        let state = self.state_mut();
80✔
164
        state.previous_header = header.clone();
80✔
165

80✔
166
        // Ensure that timestamps are inserted in sorted order
80✔
167
        let maybe_index = state.timestamps.iter().position(|ts| *ts >= header.timestamp());
523✔
168
        match maybe_index {
80✔
169
            Some(pos) => {
×
170
                state.timestamps.insert(pos, header.timestamp());
×
171
            },
×
172
            None => {
80✔
173
                state.timestamps.push(header.timestamp());
80✔
174
            },
80✔
175
        }
176

177
        state.current_height = header.height;
80✔
178
        // Add a "more recent" datapoint onto the target difficulty
80✔
179
        state
80✔
180
            .target_difficulties
80✔
181
            .add_back(&header, target_difficulty)
80✔
182
            .map_err(ChainStorageError::UnexpectedResult)?;
80✔
183

184
        let accumulated_data = BlockHeaderAccumulatedData::builder(&state.previous_accum)
80✔
185
            .with_hash(header.hash())
80✔
186
            .with_achieved_target_difficulty(achieved_target)
80✔
187
            .with_total_kernel_offset(header.total_kernel_offset.clone())
80✔
188
            .build()?;
80✔
189

190
        let total_accumulated_difficulty = accumulated_data.total_accumulated_difficulty;
80✔
191
        // NOTE: accumulated_data constructed from header so they are guaranteed to correspond
80✔
192
        let chain_header = ChainHeader::try_construct(header, accumulated_data).unwrap();
80✔
193

80✔
194
        state.previous_accum = chain_header.accumulated_data().clone();
80✔
195
        state.valid_headers.push(chain_header);
80✔
196

80✔
197
        Ok(total_accumulated_difficulty)
80✔
198
    }
81✔
199

200
    /// Drains and returns all the headers that were validated.
201
    ///
202
    /// ## Panics
203
    ///
204
    /// Panics if initialize_state was not called prior to calling this function
205
    pub fn take_valid_headers(&mut self) -> Vec<ChainHeader> {
12✔
206
        self.state_mut().valid_headers.drain(..).collect::<Vec<_>>()
12✔
207
    }
12✔
208

209
    /// Returns a slice containing the current valid headers
210
    ///
211
    /// ## Panics
212
    ///
213
    /// Panics if initialize_state was not called prior to calling this function
214
    pub fn valid_headers(&self) -> &[ChainHeader] {
40✔
215
        &self.state().valid_headers
40✔
216
    }
40✔
217

218
    pub fn compare_chains(&self, our_header: &ChainHeader, their_header: &ChainHeader) -> Ordering {
12✔
219
        debug!(
12✔
220
            target: LOG_TARGET,
×
221
            "Comparing PoW on remote header #{} and local header #{}",
×
222
            their_header.height(),
×
223
            our_header.height()
×
224
        );
225

226
        self.consensus_rules
12✔
227
            .chain_strength_comparer()
12✔
228
            .compare(our_header, their_header)
12✔
229
    }
12✔
230

231
    fn state_mut(&mut self) -> &mut State {
92✔
232
        self.state
92✔
233
            .as_mut()
92✔
234
            .expect("state_mut() called before state was initialized (using the `begin` method)")
92✔
235
    }
92✔
236

237
    fn state(&self) -> &State {
122✔
238
        self.state
122✔
239
            .as_ref()
122✔
240
            .expect("state() called before state was initialized (using the `begin` method)")
122✔
241
    }
122✔
242
}
243

244
#[cfg(test)]
245
mod test {
246
    use tari_common::configuration::Network;
247
    use tari_test_utils::unpack_enum;
248

249
    use super::*;
250
    use crate::{
251
        blocks::BlockHeader,
252
        proof_of_work::PowAlgorithm,
253
        test_helpers::blockchain::{create_new_blockchain, TempDatabase},
254
    };
255

256
    fn setup() -> (
4✔
257
        BlockHeaderSyncValidator<TempDatabase>,
4✔
258
        AsyncBlockchainDb<TempDatabase>,
4✔
259
        ConsensusManager,
4✔
260
    ) {
4✔
261
        let rules = ConsensusManager::builder(Network::LocalNet).build().unwrap();
4✔
262
        let randomx_factory = RandomXFactory::default();
4✔
263
        let db = create_new_blockchain();
4✔
264
        (
4✔
265
            BlockHeaderSyncValidator::new(db.clone().into(), rules.clone(), randomx_factory),
4✔
266
            db.into(),
4✔
267
            rules,
4✔
268
        )
4✔
269
    }
4✔
270

271
    async fn setup_with_headers(
3✔
272
        n: usize,
3✔
273
    ) -> (
3✔
274
        BlockHeaderSyncValidator<TempDatabase>,
3✔
275
        AsyncBlockchainDb<TempDatabase>,
3✔
276
        ChainHeader,
3✔
277
    ) {
3✔
278
        let (validator, db, cm) = setup();
3✔
279
        let mut tip = db.fetch_tip_header().await.unwrap();
3✔
280
        for _ in 0..n {
3✔
281
            let mut header = BlockHeader::from_previous(tip.header());
14✔
282
            header.version = cm.consensus_constants(header.height).blockchain_version();
14✔
283
            // Needed to have unique keys for the blockchain db mmr count indexes (MDB_KEY_EXIST error)
14✔
284
            header.kernel_mmr_size += 1;
14✔
285
            header.output_smt_size += 1;
14✔
286
            let acc_data = BlockHeaderAccumulatedData {
14✔
287
                hash: header.hash(),
14✔
288
                ..Default::default()
14✔
289
            };
14✔
290

14✔
291
            let chain_header = ChainHeader::try_construct(header.clone(), acc_data.clone()).unwrap();
14✔
292
            db.insert_valid_headers(vec![chain_header.clone()]).await.unwrap();
14✔
293
            tip = chain_header;
14✔
294
        }
295

296
        (validator, db, tip)
3✔
297
    }
3✔
298

299
    mod initialize_state {
300
        use std::convert::TryInto;
301

302
        use super::*;
303

304
        #[tokio::test]
305
        async fn it_initializes_state_to_given_header() {
1✔
306
            let (mut validator, _, tip) = setup_with_headers(1).await;
1✔
307
            validator.initialize_state(&tip.header().hash()).await.unwrap();
1✔
308
            let state = validator.state();
1✔
309
            assert!(state.valid_headers.is_empty());
1✔
310
            assert_eq!(state.target_difficulties.get(PowAlgorithm::Sha3x).unwrap().len(), 2);
1✔
311
            assert!(state
1✔
312
                .target_difficulties
1✔
313
                .get(PowAlgorithm::RandomXM)
1✔
314
                .unwrap()
1✔
315
                .is_empty());
1✔
316
            assert_eq!(state.timestamps.len(), 2);
1✔
317
            assert_eq!(state.current_height, 1);
1✔
318
        }
1✔
319

320
        #[tokio::test]
321
        async fn it_errors_if_hash_does_not_exist() {
1✔
322
            let (mut validator, _, _cm) = setup();
1✔
323
            let start_hash = vec![0; 32];
1✔
324
            let err = validator
1✔
325
                .initialize_state(&start_hash.clone().try_into().unwrap())
1✔
326
                .await
1✔
327
                .unwrap_err();
1✔
328
            unpack_enum!(BlockHeaderSyncError::StartHashNotFound(hash) = err);
1✔
329
            assert_eq!(hash, start_hash.to_hex());
1✔
330
        }
1✔
331
    }
332

333
    mod validate {
334
        use super::*;
335

336
        #[tokio::test]
337
        async fn it_passes_if_headers_are_valid() {
1✔
338
            let (mut validator, _, tip) = setup_with_headers(1).await;
1✔
339
            validator.initialize_state(tip.hash()).await.unwrap();
1✔
340
            assert!(validator.valid_headers().is_empty());
1✔
341
            let mut next = BlockHeader::from_previous(tip.header());
1✔
342
            next.timestamp = tip.header().timestamp.checked_add(EpochTime::from(1)).unwrap();
1✔
343
            validator.validate(next).await.unwrap();
1✔
344
            assert_eq!(validator.valid_headers().len(), 1);
1✔
345
            let tip = validator.valid_headers().last().cloned().unwrap();
1✔
346
            let mut next = BlockHeader::from_previous(tip.header());
1✔
347
            next.timestamp = tip.header().timestamp.checked_add(EpochTime::from(1)).unwrap();
1✔
348
            validator.validate(next).await.unwrap();
1✔
349
            assert_eq!(validator.valid_headers().len(), 2);
1✔
350
        }
1✔
351

352
        #[tokio::test]
353
        async fn it_fails_if_height_is_not_serial() {
1✔
354
            let (mut validator, _, tip) = setup_with_headers(12).await;
1✔
355
            validator.initialize_state(tip.hash()).await.unwrap();
1✔
356
            let mut next = BlockHeader::from_previous(tip.header());
1✔
357
            next.height = 14;
1✔
358
            let err = validator.validate(next).await.unwrap_err();
1✔
359
            unpack_enum!(BlockHeaderSyncError::ValidationFailed(val_err) = err);
1✔
360
            unpack_enum!(ValidationError::BlockHeaderError(header_err) = val_err);
1✔
361
            unpack_enum!(BlockHeaderValidationError::InvalidHeight { actual, expected } = header_err);
1✔
362
            assert_eq!(actual, 14);
1✔
363
            assert_eq!(expected, 13);
1✔
364
        }
1✔
365
    }
366
}
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