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

tari-project / tari / 14729461626

29 Apr 2025 10:51AM UTC coverage: 69.15% (-4.3%) from 73.439%
14729461626

push

github

SWvheerden
new release

4 of 4 new or added lines in 2 files covered. (100.0%)

7663 existing lines in 132 files now uncovered.

76816 of 111086 relevant lines covered (69.15%)

237945.71 hits per line

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

13.25
/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs
1
// Copyright 2019. 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
#[cfg(feature = "metrics")]
24
use std::convert::{TryFrom, TryInto};
25
use std::{cmp::max, collections::HashSet, sync::Arc, time::Instant};
26

27
use log::*;
28
use strum_macros::Display;
29
use tari_common_types::types::{BlockHash, FixedHash, HashOutput};
30
use tari_comms::{connectivity::ConnectivityRequester, peer_manager::NodeId};
31
use tari_utilities::hex::Hex;
32
use tokio::sync::RwLock;
33

34
#[cfg(feature = "metrics")]
35
use crate::base_node::metrics;
36
use crate::{
37
    base_node::comms_interface::{
38
        error::CommsInterfaceError,
39
        local_interface::BlockEventSender,
40
        FetchMempoolTransactionsResponse,
41
        NodeCommsRequest,
42
        NodeCommsResponse,
43
        OutboundNodeCommsInterface,
44
    },
45
    blocks::{Block, BlockBuilder, BlockHeader, BlockHeaderValidationError, ChainBlock, NewBlock, NewBlockTemplate},
46
    chain_storage::{async_db::AsyncBlockchainDb, BlockAddResult, BlockchainBackend, ChainStorageError},
47
    consensus::{ConsensusConstants, ConsensusManager},
48
    mempool::Mempool,
49
    proof_of_work::{
50
        randomx_difficulty,
51
        randomx_factory::RandomXFactory,
52
        sha3x_difficulty,
53
        Difficulty,
54
        PowAlgorithm,
55
        PowError,
56
    },
57
    transactions::aggregated_body::AggregateBody,
58
    validation::{helpers, ValidationError},
59
};
60

61
const LOG_TARGET: &str = "c::bn::comms_interface::inbound_handler";
62
const MAX_REQUEST_BY_BLOCK_HASHES: usize = 100;
63
const MAX_REQUEST_BY_KERNEL_EXCESS_SIGS: usize = 100;
64
const MAX_REQUEST_BY_UTXO_HASHES: usize = 100;
65
const MAX_MEMPOOL_TIMEOUT: u64 = 150;
66

67
/// Events that can be published on the Validated Block Event Stream
68
/// Broadcast is to notify subscribers if this is a valid propagated block event
69
#[derive(Debug, Clone, Display)]
70
pub enum BlockEvent {
71
    ValidBlockAdded(Arc<Block>, BlockAddResult),
72
    AddBlockValidationFailed {
73
        block: Arc<Block>,
74
        source_peer: Option<NodeId>,
75
    },
76
    AddBlockErrored {
77
        block: Arc<Block>,
78
    },
79
    BlockSyncComplete(Arc<ChainBlock>, u64),
80
    BlockSyncRewind(Vec<Arc<ChainBlock>>),
81
}
82

83
/// The InboundNodeCommsInterface is used to handle all received inbound requests from remote nodes.
84
pub struct InboundNodeCommsHandlers<B> {
85
    block_event_sender: BlockEventSender,
86
    blockchain_db: AsyncBlockchainDb<B>,
87
    mempool: Mempool,
88
    consensus_manager: ConsensusManager,
89
    list_of_reconciling_blocks: Arc<RwLock<HashSet<HashOutput>>>,
90
    outbound_nci: OutboundNodeCommsInterface,
91
    connectivity: ConnectivityRequester,
92
    randomx_factory: RandomXFactory,
93
    cached_block_template: Arc<RwLock<Option<(FixedHash, NewBlockTemplate)>>>,
94
}
95

96
impl<B> InboundNodeCommsHandlers<B>
97
where B: BlockchainBackend + 'static
98
{
99
    /// Construct a new InboundNodeCommsInterface.
100
    pub fn new(
7✔
101
        block_event_sender: BlockEventSender,
7✔
102
        blockchain_db: AsyncBlockchainDb<B>,
7✔
103
        mempool: Mempool,
7✔
104
        consensus_manager: ConsensusManager,
7✔
105
        outbound_nci: OutboundNodeCommsInterface,
7✔
106
        connectivity: ConnectivityRequester,
7✔
107
        randomx_factory: RandomXFactory,
7✔
108
    ) -> Self {
7✔
109
        Self {
7✔
110
            block_event_sender,
7✔
111
            blockchain_db,
7✔
112
            mempool,
7✔
113
            consensus_manager,
7✔
114
            list_of_reconciling_blocks: Arc::new(RwLock::new(HashSet::new())),
7✔
115
            outbound_nci,
7✔
116
            connectivity,
7✔
117
            randomx_factory,
7✔
118
            cached_block_template: Arc::new(RwLock::new(None)),
7✔
119
        }
7✔
120
    }
7✔
121

122
    /// Handle inbound node comms requests from remote nodes and local services.
123
    #[allow(clippy::too_many_lines)]
124
    pub async fn handle_request(&self, request: NodeCommsRequest) -> Result<NodeCommsResponse, CommsInterfaceError> {
10✔
125
        trace!(target: LOG_TARGET, "Handling remote request {}", request);
10✔
126
        match request {
10✔
127
            NodeCommsRequest::GetChainMetadata => Ok(NodeCommsResponse::ChainMetadata(
128
                self.blockchain_db.get_chain_metadata().await?,
6✔
129
            )),
130
            NodeCommsRequest::GetTargetDifficultyNextBlock(algo) => {
×
131
                let header = self.blockchain_db.fetch_tip_header().await?;
×
132
                let constants = self.consensus_manager.consensus_constants(header.header().height);
×
133
                let target_difficulty = self
×
134
                    .get_target_difficulty_for_next_block(algo, constants, *header.hash())
×
135
                    .await?;
×
136
                Ok(NodeCommsResponse::TargetDifficulty(target_difficulty))
×
137
            },
138
            NodeCommsRequest::FetchHeaders(range) => {
1✔
139
                let headers = self.blockchain_db.fetch_chain_headers(range).await?;
1✔
140
                Ok(NodeCommsResponse::BlockHeaders(headers))
1✔
141
            },
142
            NodeCommsRequest::FetchHeadersByHashes(block_hashes) => {
×
143
                if block_hashes.len() > MAX_REQUEST_BY_BLOCK_HASHES {
×
144
                    return Err(CommsInterfaceError::InvalidRequest {
×
145
                        request: "FetchHeadersByHashes",
×
146
                        details: format!(
×
147
                            "Exceeded maximum block hashes request (max: {}, got:{})",
×
148
                            MAX_REQUEST_BY_BLOCK_HASHES,
×
149
                            block_hashes.len()
×
150
                        ),
×
151
                    });
×
152
                }
×
153
                let mut block_headers = Vec::with_capacity(block_hashes.len());
×
154
                for block_hash in block_hashes {
×
155
                    let block_hex = block_hash.to_hex();
×
156
                    match self.blockchain_db.fetch_chain_header_by_block_hash(block_hash).await? {
×
157
                        Some(block_header) => {
×
158
                            block_headers.push(block_header);
×
159
                        },
×
160
                        None => {
161
                            error!(target: LOG_TARGET, "Could not fetch headers with hashes:{}", block_hex);
×
162
                            return Err(CommsInterfaceError::InternalError(format!(
×
163
                                "Could not fetch headers with hashes:{}",
×
164
                                block_hex
×
165
                            )));
×
166
                        },
167
                    }
168
                }
169
                Ok(NodeCommsResponse::BlockHeaders(block_headers))
×
170
            },
UNCOV
171
            NodeCommsRequest::FetchMatchingUtxos(utxo_hashes) => {
×
UNCOV
172
                let mut res = Vec::with_capacity(utxo_hashes.len());
×
UNCOV
173
                for (output, spent) in (self
×
UNCOV
174
                    .blockchain_db
×
UNCOV
175
                    .fetch_outputs_with_spend_status_at_tip(utxo_hashes)
×
UNCOV
176
                    .await?)
×
UNCOV
177
                    .into_iter()
×
UNCOV
178
                    .flatten()
×
179
                {
UNCOV
180
                    if !spent {
×
UNCOV
181
                        res.push(output);
×
UNCOV
182
                    }
×
183
                }
UNCOV
184
                Ok(NodeCommsResponse::TransactionOutputs(res))
×
185
            },
186
            NodeCommsRequest::FetchMatchingBlocks { range, compact } => {
3✔
187
                let blocks = self.blockchain_db.fetch_blocks(range, compact).await?;
3✔
188
                Ok(NodeCommsResponse::HistoricalBlocks(blocks))
3✔
189
            },
190
            NodeCommsRequest::FetchBlocksByKernelExcessSigs(excess_sigs) => {
×
191
                if excess_sigs.len() > MAX_REQUEST_BY_KERNEL_EXCESS_SIGS {
×
192
                    return Err(CommsInterfaceError::InvalidRequest {
×
193
                        request: "FetchBlocksByKernelExcessSigs",
×
194
                        details: format!(
×
195
                            "Exceeded maximum number of kernel excess sigs in request (max: {}, got:{})",
×
196
                            MAX_REQUEST_BY_KERNEL_EXCESS_SIGS,
×
197
                            excess_sigs.len()
×
198
                        ),
×
199
                    });
×
200
                }
×
201
                let mut blocks = Vec::with_capacity(excess_sigs.len());
×
202
                for sig in excess_sigs {
×
203
                    let sig_hex = sig.get_signature().to_hex();
×
204
                    debug!(
×
205
                        target: LOG_TARGET,
×
206
                        "A peer has requested a block with kernel with sig {}", sig_hex
×
207
                    );
208
                    match self.blockchain_db.fetch_block_with_kernel(sig).await {
×
209
                        Ok(Some(block)) => blocks.push(block),
×
210
                        Ok(None) => warn!(
×
211
                            target: LOG_TARGET,
×
212
                            "Could not provide requested block containing kernel with sig {} to peer because not \
×
213
                             stored",
×
214
                            sig_hex
215
                        ),
216
                        Err(e) => warn!(
×
217
                            target: LOG_TARGET,
×
218
                            "Could not provide requested block containing kernel with sig {} to peer because: {}",
×
219
                            sig_hex,
×
220
                            e.to_string()
×
221
                        ),
222
                    }
223
                }
224
                Ok(NodeCommsResponse::HistoricalBlocks(blocks))
×
225
            },
226
            NodeCommsRequest::FetchBlocksByUtxos(commitments) => {
×
227
                if commitments.len() > MAX_REQUEST_BY_UTXO_HASHES {
×
228
                    return Err(CommsInterfaceError::InvalidRequest {
×
229
                        request: "FetchBlocksByUtxos",
×
230
                        details: format!(
×
231
                            "Exceeded maximum number of utxo hashes in request (max: {}, got:{})",
×
232
                            MAX_REQUEST_BY_UTXO_HASHES,
×
233
                            commitments.len()
×
234
                        ),
×
235
                    });
×
236
                }
×
237
                let mut blocks = Vec::with_capacity(commitments.len());
×
238
                for commitment in commitments {
×
239
                    let commitment_hex = commitment.to_hex();
×
240
                    debug!(
×
241
                        target: LOG_TARGET,
×
242
                        "A peer has requested a block with commitment {}", commitment_hex,
×
243
                    );
244
                    match self.blockchain_db.fetch_block_with_utxo(commitment).await {
×
245
                        Ok(Some(block)) => blocks.push(block),
×
246
                        Ok(None) => warn!(
×
247
                            target: LOG_TARGET,
×
248
                            "Could not provide requested block with commitment {} to peer because not stored",
×
249
                            commitment_hex,
250
                        ),
251
                        Err(e) => warn!(
×
252
                            target: LOG_TARGET,
×
253
                            "Could not provide requested block with commitment {} to peer because: {}",
×
254
                            commitment_hex,
×
255
                            e.to_string()
×
256
                        ),
257
                    }
258
                }
259
                Ok(NodeCommsResponse::HistoricalBlocks(blocks))
×
260
            },
261
            NodeCommsRequest::GetHeaderByHash(hash) => {
×
262
                let header = self.blockchain_db.fetch_chain_header_by_block_hash(hash).await?;
×
263
                Ok(NodeCommsResponse::BlockHeader(header))
×
264
            },
265
            NodeCommsRequest::GetBlockByHash(hash) => {
×
266
                let block = self.blockchain_db.fetch_block_by_hash(hash, false).await?;
×
267
                Ok(NodeCommsResponse::HistoricalBlock(Box::new(block)))
×
268
            },
UNCOV
269
            NodeCommsRequest::GetNewBlockTemplate(request) => {
×
UNCOV
270
                let best_block_header = self.blockchain_db.fetch_tip_header().await?;
×
UNCOV
271
                let mut last_seen_hash = self.mempool.get_last_seen_hash().await?;
×
272
                {
UNCOV
273
                    let read_lock = self.cached_block_template.read().await;
×
UNCOV
274
                    if let Some(cache) = read_lock.as_ref() {
×
275
                        if cache.0 == last_seen_hash && &cache.1.header.prev_hash == best_block_header.hash() {
×
276
                            return Ok(NodeCommsResponse::NewBlockTemplate(cache.1.clone()));
×
277
                        }
×
UNCOV
278
                    }
×
279
                }
280
                // Only allow one thread
UNCOV
281
                let mut write_lock = self.cached_block_template.write().await;
×
282
                // double lock check
UNCOV
283
                if let Some(cache) = write_lock.as_ref() {
×
284
                    if cache.0 == last_seen_hash && &cache.1.header.prev_hash == best_block_header.hash() {
×
285
                        return Ok(NodeCommsResponse::NewBlockTemplate(cache.1.clone()));
×
286
                    }
×
UNCOV
287
                }
×
288

UNCOV
289
                let mut is_mempool_synced = false;
×
UNCOV
290
                let start = Instant::now();
×
291
                // this will wait a max of 150ms by default before returning anyway with a potential broken template
292
                // We need to ensure the mempool has seen the latest base node height before we can be confident the
293
                // template is correct
UNCOV
294
                while !is_mempool_synced && start.elapsed().as_millis() < MAX_MEMPOOL_TIMEOUT.into() {
×
UNCOV
295
                    if best_block_header.hash() == &last_seen_hash || last_seen_hash == FixedHash::default() {
×
UNCOV
296
                        is_mempool_synced = true;
×
UNCOV
297
                    } else {
×
298
                        tokio::time::sleep(std::time::Duration::from_millis(10)).await;
×
299
                        last_seen_hash = self.mempool.get_last_seen_hash().await?;
×
300
                    }
301
                }
302

UNCOV
303
                if !is_mempool_synced {
×
304
                    warn!(
×
305
                        target: LOG_TARGET,
×
306
                        "Mempool out of sync - last seen hash '{}' does not match the tip hash '{}'. This condition \
×
307
                         should auto correct with the next block template request",
×
308
                        last_seen_hash, best_block_header.hash()
×
309
                    );
UNCOV
310
                }
×
UNCOV
311
                let mut header = BlockHeader::from_previous(best_block_header.header());
×
UNCOV
312
                let constants = self.consensus_manager.consensus_constants(header.height);
×
UNCOV
313
                header.version = constants.blockchain_version();
×
UNCOV
314
                header.pow.pow_algo = request.algo;
×
UNCOV
315

×
UNCOV
316
                let constants_weight = constants.max_block_transaction_weight();
×
UNCOV
317
                let asking_weight = if request.max_weight > constants_weight || request.max_weight == 0 {
×
UNCOV
318
                    constants_weight
×
319
                } else {
320
                    request.max_weight
×
321
                };
322

UNCOV
323
                debug!(
×
324
                    target: LOG_TARGET,
×
325
                    "Fetching transactions with a maximum weight of {} for the template", asking_weight
×
326
                );
UNCOV
327
                let transactions = self
×
UNCOV
328
                    .mempool
×
UNCOV
329
                    .retrieve(asking_weight)
×
UNCOV
330
                    .await?
×
UNCOV
331
                    .into_iter()
×
UNCOV
332
                    .map(|tx| Arc::try_unwrap(tx).unwrap_or_else(|tx| (*tx).clone()))
×
UNCOV
333
                    .collect::<Vec<_>>();
×
UNCOV
334

×
UNCOV
335
                debug!(
×
336
                    target: LOG_TARGET,
×
337
                    "Adding {} transaction(s) to new block template",
×
338
                    transactions.len(),
×
339
                );
340

UNCOV
341
                let prev_hash = header.prev_hash;
×
UNCOV
342
                let height = header.height;
×
UNCOV
343

×
UNCOV
344
                let block = header.into_builder().with_transactions(transactions).build();
×
UNCOV
345
                let block_hash = block.hash();
×
UNCOV
346
                let block_template = NewBlockTemplate::from_block(
×
UNCOV
347
                    block,
×
UNCOV
348
                    self.get_target_difficulty_for_next_block(request.algo, constants, prev_hash)
×
UNCOV
349
                        .await?,
×
UNCOV
350
                    self.consensus_manager.get_block_reward_at(height),
×
UNCOV
351
                    is_mempool_synced,
×
352
                )?;
×
353

UNCOV
354
                debug!(target: LOG_TARGET,
×
355
                    "New block template requested and prepared at height: #{}, target difficulty: {}, block hash: `{}`, weight: {}, {}",
×
356
                    block_template.header.height,
×
357
                    block_template.target_difficulty,
×
358
                    block_hash.to_hex(),
×
359
                    block_template
×
360
                        .body
×
361
                        .calculate_weight(constants.transaction_weight_params())
×
362
                        .map_err(|e| CommsInterfaceError::InternalError(e.to_string()))?,
×
363
                    block_template.body.to_counts_string()
×
364
                );
365

UNCOV
366
                *write_lock = Some((last_seen_hash, block_template.clone()));
×
UNCOV
367
                Ok(NodeCommsResponse::NewBlockTemplate(block_template))
×
368
            },
UNCOV
369
            NodeCommsRequest::GetNewBlock(block_template) => {
×
UNCOV
370
                let height = block_template.header.height;
×
UNCOV
371
                let target_difficulty = block_template.target_difficulty;
×
UNCOV
372
                let block = self.blockchain_db.prepare_new_block(block_template).await?;
×
UNCOV
373
                let constants = self.consensus_manager.consensus_constants(block.header.height);
×
UNCOV
374
                debug!(target: LOG_TARGET,
×
375
                    "Prepared block: #{}, target difficulty: {}, block hash: `{}`, weight: {}, {}",
×
376
                    height,
×
377
                    target_difficulty,
×
378
                    block.hash().to_hex(),
×
379
                    block
×
380
                        .body
×
381
                        .calculate_weight(constants.transaction_weight_params())
×
382
                        .map_err(|e| CommsInterfaceError::InternalError(e.to_string()))?,
×
383
                    block.body.to_counts_string()
×
384
                );
UNCOV
385
                Ok(NodeCommsResponse::NewBlock {
×
UNCOV
386
                    success: true,
×
UNCOV
387
                    error: None,
×
UNCOV
388
                    block: Some(block),
×
UNCOV
389
                })
×
390
            },
UNCOV
391
            NodeCommsRequest::GetBlockFromAllChains(hash) => {
×
UNCOV
392
                let block_hex = hash.to_hex();
×
UNCOV
393
                debug!(
×
394
                    target: LOG_TARGET,
×
395
                    "A peer has requested a block with hash {}", block_hex
×
396
                );
397

398
                #[allow(clippy::blocks_in_conditions)]
UNCOV
399
                let maybe_block = match self
×
UNCOV
400
                    .blockchain_db
×
UNCOV
401
                    .fetch_block_by_hash(hash, true)
×
UNCOV
402
                    .await
×
UNCOV
403
                    .unwrap_or_else(|e| {
×
404
                        warn!(
×
405
                            target: LOG_TARGET,
×
406
                            "Could not provide requested block {} to peer because: {}",
×
407
                            block_hex,
×
408
                            e.to_string()
×
409
                        );
410

411
                        None
×
UNCOV
412
                    }) {
×
UNCOV
413
                    None => self.blockchain_db.fetch_orphan(hash).await.map_or_else(
×
UNCOV
414
                        |e| {
×
UNCOV
415
                            warn!(
×
416
                                target: LOG_TARGET,
×
417
                                "Could not provide requested block {} to peer because: {}", block_hex, e,
×
418
                            );
419

UNCOV
420
                            None
×
UNCOV
421
                        },
×
UNCOV
422
                        Some,
×
UNCOV
423
                    ),
×
UNCOV
424
                    Some(block) => Some(block.into_block()),
×
425
                };
426

UNCOV
427
                Ok(NodeCommsResponse::Block(Box::new(maybe_block)))
×
428
            },
UNCOV
429
            NodeCommsRequest::FetchKernelByExcessSig(signature) => {
×
UNCOV
430
                let kernels = match self.blockchain_db.fetch_kernel_by_excess_sig(signature).await {
×
UNCOV
431
                    Ok(Some((kernel, _))) => vec![kernel],
×
432
                    Ok(None) => vec![],
×
433
                    Err(err) => {
×
434
                        error!(target: LOG_TARGET, "Could not fetch kernel {}", err.to_string());
×
435
                        return Err(err.into());
×
436
                    },
437
                };
438

UNCOV
439
                Ok(NodeCommsResponse::TransactionKernels(kernels))
×
440
            },
UNCOV
441
            NodeCommsRequest::FetchMempoolTransactionsByExcessSigs { excess_sigs } => {
×
UNCOV
442
                let (transactions, not_found) = self.mempool.retrieve_by_excess_sigs(excess_sigs).await?;
×
UNCOV
443
                Ok(NodeCommsResponse::FetchMempoolTransactionsByExcessSigsResponse(
×
UNCOV
444
                    FetchMempoolTransactionsResponse {
×
UNCOV
445
                        transactions,
×
UNCOV
446
                        not_found,
×
UNCOV
447
                    },
×
UNCOV
448
                ))
×
449
            },
450
            NodeCommsRequest::FetchValidatorNodesKeys { height } => {
×
451
                let active_validator_nodes = self.blockchain_db.fetch_active_validator_nodes(height).await?;
×
452
                Ok(NodeCommsResponse::FetchValidatorNodesKeysResponse(
×
453
                    active_validator_nodes,
×
454
                ))
×
455
            },
456
            NodeCommsRequest::GetShardKey { height, public_key } => {
×
457
                let shard_key = self.blockchain_db.get_shard_key(height, public_key).await?;
×
458
                Ok(NodeCommsResponse::GetShardKeyResponse(shard_key))
×
459
            },
460
            NodeCommsRequest::FetchTemplateRegistrations {
461
                start_height,
×
462
                end_height,
×
463
            } => {
464
                let template_registrations = self
×
465
                    .blockchain_db
×
466
                    .fetch_template_registrations(start_height..=end_height)
×
467
                    .await?;
×
468
                Ok(NodeCommsResponse::FetchTemplateRegistrationsResponse(
×
469
                    template_registrations,
×
470
                ))
×
471
            },
472
            NodeCommsRequest::FetchUnspentUtxosInBlock { block_hash } => {
×
473
                let utxos = self.blockchain_db.fetch_outputs_in_block(block_hash).await?;
×
474
                Ok(NodeCommsResponse::TransactionOutputs(utxos))
×
475
            },
476
        }
477
    }
10✔
478

479
    /// Handles a `NewBlock` message. Only a single `NewBlock` message can be handled at once to prevent extraneous
480
    /// requests for the full block.
481
    /// This may (asynchronously) block until the other request(s) complete or time out and so should typically be
482
    /// executed in a dedicated task.
UNCOV
483
    pub async fn handle_new_block_message(
×
UNCOV
484
        &mut self,
×
UNCOV
485
        new_block: NewBlock,
×
UNCOV
486
        source_peer: NodeId,
×
UNCOV
487
    ) -> Result<(), CommsInterfaceError> {
×
UNCOV
488
        let block_hash = new_block.header.hash();
×
UNCOV
489

×
UNCOV
490
        if self.blockchain_db.inner().is_add_block_disabled() {
×
491
            info!(
×
492
                target: LOG_TARGET,
×
493
                "Ignoring block message ({}) because add_block is locked",
×
494
                block_hash.to_hex()
×
495
            );
496
            return Ok(());
×
UNCOV
497
        }
×
UNCOV
498

×
UNCOV
499
        // Lets check if the block exists before we try and ask for a complete block
×
UNCOV
500
        if self.check_exists_and_not_bad_block(block_hash).await? {
×
501
            return Ok(());
×
UNCOV
502
        }
×
UNCOV
503

×
UNCOV
504
        // lets check that the difficulty at least matches 50% of the tip header. The max difficulty drop is 16%, thus
×
UNCOV
505
        // 50% is way more than that and in order to attack the node, you need 50% of the mining power. We cannot check
×
UNCOV
506
        // the target difficulty as orphan blocks dont have a target difficulty. All we care here is that bad
×
UNCOV
507
        // blocks are not free to make, and that they are more expensive to make then they are to validate. As
×
UNCOV
508
        // soon as a block can be linked to the main chain, a proper full proof of work check will
×
UNCOV
509
        // be done before any other validation.
×
UNCOV
510
        self.check_min_block_difficulty(&new_block).await?;
×
511

512
        {
513
            // we use a double lock to make sure we can only reconcile one unique block at a time. We may receive the
514
            // same block from multiple peer near simultaneously. We should only reconcile each unique block once.
UNCOV
515
            let read_lock = self.list_of_reconciling_blocks.read().await;
×
UNCOV
516
            if read_lock.contains(&block_hash) {
×
517
                debug!(
×
518
                    target: LOG_TARGET,
×
519
                    "Block with hash `{}` is already being reconciled",
×
520
                    block_hash.to_hex()
×
521
                );
522
                return Ok(());
×
UNCOV
523
            }
×
524
        }
525
        {
UNCOV
526
            let mut write_lock = self.list_of_reconciling_blocks.write().await;
×
UNCOV
527
            if self.check_exists_and_not_bad_block(block_hash).await? {
×
528
                return Ok(());
×
UNCOV
529
            }
×
UNCOV
530

×
UNCOV
531
            if !write_lock.insert(block_hash) {
×
532
                debug!(
×
533
                    target: LOG_TARGET,
×
534
                    "Block with hash `{}` is already being reconciled",
×
535
                    block_hash.to_hex()
×
536
                );
537
                return Ok(());
×
UNCOV
538
            }
×
UNCOV
539
        }
×
UNCOV
540

×
UNCOV
541
        debug!(
×
542
            target: LOG_TARGET,
×
543
            "Block with hash `{}` is unknown. Constructing block from known mempool transactions / requesting missing \
×
544
             transactions from peer '{}'.",
×
545
            block_hash.to_hex(),
×
546
            source_peer
547
        );
548

UNCOV
549
        let result = self.reconcile_and_add_block(source_peer.clone(), new_block).await;
×
550

551
        {
UNCOV
552
            let mut write_lock = self.list_of_reconciling_blocks.write().await;
×
UNCOV
553
            write_lock.remove(&block_hash);
×
UNCOV
554
        }
×
UNCOV
555
        result?;
×
UNCOV
556
        Ok(())
×
UNCOV
557
    }
×
558

UNCOV
559
    async fn check_min_block_difficulty(&self, new_block: &NewBlock) -> Result<(), CommsInterfaceError> {
×
UNCOV
560
        let constants = self.consensus_manager.consensus_constants(new_block.header.height);
×
UNCOV
561
        let gen_hash = *self.consensus_manager.get_genesis_block().hash();
×
UNCOV
562
        let mut min_difficulty = constants.min_pow_difficulty(new_block.header.pow.pow_algo);
×
UNCOV
563
        let mut header = self.blockchain_db.fetch_last_chain_header().await?;
×
564
        loop {
UNCOV
565
            if new_block.header.pow_algo() == header.header().pow_algo() {
×
UNCOV
566
                min_difficulty = max(
×
UNCOV
567
                    header
×
UNCOV
568
                        .accumulated_data()
×
UNCOV
569
                        .target_difficulty
×
UNCOV
570
                        .checked_div_u64(2)
×
UNCOV
571
                        .unwrap_or(min_difficulty),
×
UNCOV
572
                    min_difficulty,
×
UNCOV
573
                );
×
UNCOV
574
                break;
×
575
            }
×
576
            if header.height() == 0 {
×
577
                break;
×
578
            }
×
579
            // we have not reached gen block, and the pow algo does not match, so lets go further back
580
            header = self
×
581
                .blockchain_db
×
582
                .fetch_chain_header(header.height().saturating_sub(1))
×
583
                .await?;
×
584
        }
UNCOV
585
        let achieved = match new_block.header.pow_algo() {
×
586
            PowAlgorithm::RandomX => randomx_difficulty(
×
587
                &new_block.header,
×
588
                &self.randomx_factory,
×
589
                &gen_hash,
×
590
                &self.consensus_manager,
×
591
            )?,
×
UNCOV
592
            PowAlgorithm::Sha3x => sha3x_difficulty(&new_block.header)?,
×
593
        };
UNCOV
594
        if achieved < min_difficulty {
×
595
            return Err(CommsInterfaceError::InvalidBlockHeader(
×
596
                BlockHeaderValidationError::ProofOfWorkError(PowError::AchievedDifficultyBelowMin),
×
597
            ));
×
UNCOV
598
        }
×
UNCOV
599
        Ok(())
×
UNCOV
600
    }
×
601

UNCOV
602
    async fn check_exists_and_not_bad_block(&self, block: FixedHash) -> Result<bool, CommsInterfaceError> {
×
UNCOV
603
        if self.blockchain_db.chain_header_or_orphan_exists(block).await? {
×
604
            debug!(
×
605
                target: LOG_TARGET,
×
606
                "Block with hash `{}` already stored",
×
607
                block.to_hex()
×
608
            );
609
            return Ok(true);
×
UNCOV
610
        }
×
UNCOV
611
        let (is_bad_block, reason) = self.blockchain_db.bad_block_exists(block).await?;
×
UNCOV
612
        if is_bad_block {
×
613
            debug!(
×
614
                target: LOG_TARGET,
×
615
                "Block with hash `{}` already validated as a bad block due to `{}`",
×
616
                block.to_hex(), reason
×
617
            );
618
            return Err(CommsInterfaceError::ChainStorageError(
×
619
                ChainStorageError::ValidationError {
×
620
                    source: ValidationError::BadBlockFound {
×
621
                        hash: block.to_hex(),
×
622
                        reason,
×
623
                    },
×
624
                },
×
625
            ));
×
UNCOV
626
        }
×
UNCOV
627
        Ok(false)
×
UNCOV
628
    }
×
629

UNCOV
630
    async fn reconcile_and_add_block(
×
UNCOV
631
        &mut self,
×
UNCOV
632
        source_peer: NodeId,
×
UNCOV
633
        new_block: NewBlock,
×
UNCOV
634
    ) -> Result<(), CommsInterfaceError> {
×
UNCOV
635
        let block = self.reconcile_block(source_peer.clone(), new_block).await?;
×
UNCOV
636
        self.handle_block(block, Some(source_peer)).await?;
×
UNCOV
637
        Ok(())
×
UNCOV
638
    }
×
639

640
    #[allow(clippy::too_many_lines)]
UNCOV
641
    async fn reconcile_block(
×
UNCOV
642
        &mut self,
×
UNCOV
643
        source_peer: NodeId,
×
UNCOV
644
        new_block: NewBlock,
×
UNCOV
645
    ) -> Result<Block, CommsInterfaceError> {
×
UNCOV
646
        let NewBlock {
×
UNCOV
647
            header,
×
UNCOV
648
            coinbase_kernels,
×
UNCOV
649
            coinbase_outputs,
×
UNCOV
650
            kernel_excess_sigs: excess_sigs,
×
UNCOV
651
        } = new_block;
×
UNCOV
652
        // If the block is empty, we dont have to ask for the block, as we already have the full block available
×
UNCOV
653
        // to us.
×
UNCOV
654
        if excess_sigs.is_empty() {
×
UNCOV
655
            let block = BlockBuilder::new(header.version)
×
UNCOV
656
                .add_outputs(coinbase_outputs)
×
UNCOV
657
                .add_kernels(coinbase_kernels)
×
UNCOV
658
                .with_header(header)
×
UNCOV
659
                .build();
×
UNCOV
660
            return Ok(block);
×
UNCOV
661
        }
×
UNCOV
662

×
UNCOV
663
        let block_hash = header.hash();
×
664
        // We check the current tip and orphan status of the block because we cannot guarantee that mempool state is
665
        // correct and the mmr root calculation is only valid if the block is building on the tip.
UNCOV
666
        let current_meta = self.blockchain_db.get_chain_metadata().await?;
×
UNCOV
667
        if header.prev_hash != *current_meta.best_block_hash() {
×
668
            debug!(
×
669
                target: LOG_TARGET,
×
670
                "Orphaned block #{}: ({}), current tip is: #{} ({}). We need to fetch the complete block from peer: \
×
671
                 ({})",
×
672
                header.height,
×
673
                block_hash.to_hex(),
×
674
                current_meta.best_block_height(),
×
675
                current_meta.best_block_hash().to_hex(),
×
676
                source_peer,
677
            );
678
            #[allow(clippy::cast_possible_wrap)]
679
            #[cfg(feature = "metrics")]
680
            metrics::compact_block_tx_misses(header.height).set(excess_sigs.len() as i64);
×
681
            let block = self.request_full_block_from_peer(source_peer, block_hash).await?;
×
682
            return Ok(block);
×
UNCOV
683
        }
×
684

685
        // We know that the block is neither and orphan or a coinbase, so lets ask our mempool for the transactions
UNCOV
686
        let (known_transactions, missing_excess_sigs) = self.mempool.retrieve_by_excess_sigs(excess_sigs).await?;
×
UNCOV
687
        let known_transactions = known_transactions.into_iter().map(|tx| (*tx).clone()).collect();
×
UNCOV
688

×
UNCOV
689
        #[allow(clippy::cast_possible_wrap)]
×
UNCOV
690
        #[cfg(feature = "metrics")]
×
UNCOV
691
        metrics::compact_block_tx_misses(header.height).set(missing_excess_sigs.len() as i64);
×
UNCOV
692

×
UNCOV
693
        let mut builder = BlockBuilder::new(header.version)
×
UNCOV
694
            .add_outputs(coinbase_outputs)
×
UNCOV
695
            .add_kernels(coinbase_kernels)
×
UNCOV
696
            .with_transactions(known_transactions);
×
UNCOV
697

×
UNCOV
698
        if missing_excess_sigs.is_empty() {
×
699
            debug!(
×
700
                target: LOG_TARGET,
×
701
                "All transactions for block #{} ({}) found in mempool",
×
702
                header.height,
×
703
                block_hash.to_hex()
×
704
            );
705
        } else {
UNCOV
706
            debug!(
×
707
                target: LOG_TARGET,
×
708
                "Requesting {} unknown transaction(s) from peer '{}'.",
×
709
                missing_excess_sigs.len(),
×
710
                source_peer
711
            );
712

713
            let FetchMempoolTransactionsResponse {
UNCOV
714
                transactions,
×
UNCOV
715
                not_found,
×
UNCOV
716
            } = self
×
UNCOV
717
                .outbound_nci
×
UNCOV
718
                .request_transactions_by_excess_sig(source_peer.clone(), missing_excess_sigs)
×
UNCOV
719
                .await?;
×
720

721
            // Add returned transactions to unconfirmed pool
UNCOV
722
            if !transactions.is_empty() {
×
723
                self.mempool.insert_all(transactions.clone()).await?;
×
UNCOV
724
            }
×
725

UNCOV
726
            if !not_found.is_empty() {
×
UNCOV
727
                warn!(
×
728
                    target: LOG_TARGET,
×
729
                    "Peer {} was not able to return all transactions for block #{} ({}). {} transaction(s) not found. \
×
730
                     Requesting full block.",
×
731
                    source_peer,
×
732
                    header.height,
×
733
                    block_hash.to_hex(),
×
734
                    not_found.len()
×
735
                );
736

737
                #[cfg(feature = "metrics")]
UNCOV
738
                metrics::compact_block_full_misses(header.height).inc();
×
UNCOV
739
                let block = self.request_full_block_from_peer(source_peer, block_hash).await?;
×
UNCOV
740
                return Ok(block);
×
741
            }
×
742

×
743
            builder = builder.with_transactions(
×
744
                transactions
×
745
                    .into_iter()
×
746
                    .map(|tx| Arc::try_unwrap(tx).unwrap_or_else(|tx| (*tx).clone()))
×
747
                    .collect(),
×
748
            );
×
749
        }
×
750

751
        // NB: Add the header last because `with_transactions` etc updates the current header, but we have the final one
752
        // already
753
        builder = builder.with_header(header.clone());
×
754
        let block = builder.build();
×
755

756
        // Perform a sanity check on the reconstructed block, if the MMR roots don't match then it's possible one or
757
        // more transactions in our mempool had the same excess/signature for a *different* transaction.
758
        // This is extremely unlikely, but still possible. In case of a mismatch, request the full block from the peer.
759
        let (block, mmr_roots) = match self.blockchain_db.calculate_mmr_roots(block).await {
×
760
            Err(_) => {
761
                let block = self.request_full_block_from_peer(source_peer, block_hash).await?;
×
762
                return Ok(block);
×
763
            },
764
            Ok(v) => v,
×
765
        };
766
        if let Err(e) = helpers::check_mmr_roots(&header, &mmr_roots) {
×
767
            warn!(
×
768
                target: LOG_TARGET,
×
769
                "Reconstructed block #{} ({}) failed MMR check validation!. Requesting full block. Error: {}",
×
770
                header.height,
×
771
                block_hash.to_hex(),
×
772
                e,
773
            );
774

775
            #[cfg(feature = "metrics")]
776
            metrics::compact_block_mmr_mismatch(header.height).inc();
×
777
            let block = self.request_full_block_from_peer(source_peer, block_hash).await?;
×
778
            return Ok(block);
×
779
        }
×
780

×
781
        Ok(block)
×
UNCOV
782
    }
×
783

UNCOV
784
    async fn request_full_block_from_peer(
×
UNCOV
785
        &mut self,
×
UNCOV
786
        source_peer: NodeId,
×
UNCOV
787
        block_hash: BlockHash,
×
UNCOV
788
    ) -> Result<Block, CommsInterfaceError> {
×
UNCOV
789
        match self
×
UNCOV
790
            .outbound_nci
×
UNCOV
791
            .request_blocks_by_hashes_from_peer(block_hash, Some(source_peer.clone()))
×
UNCOV
792
            .await
×
793
        {
UNCOV
794
            Ok(Some(block)) => Ok(block),
×
795
            Ok(None) => {
UNCOV
796
                debug!(
×
797
                    target: LOG_TARGET,
×
798
                    "Peer `{}` failed to return the block that was requested.", source_peer
×
799
                );
UNCOV
800
                Err(CommsInterfaceError::InvalidPeerResponse(format!(
×
UNCOV
801
                    "Invalid response from peer `{}`: Peer failed to provide the block that was propagated",
×
UNCOV
802
                    source_peer
×
UNCOV
803
                )))
×
804
            },
805
            Err(CommsInterfaceError::UnexpectedApiResponse) => {
806
                debug!(
×
807
                    target: LOG_TARGET,
×
808
                    "Peer `{}` sent unexpected API response.", source_peer
×
809
                );
810
                Err(CommsInterfaceError::UnexpectedApiResponse)
×
811
            },
812
            Err(e) => Err(e),
×
813
        }
UNCOV
814
    }
×
815

816
    /// Handle inbound blocks from remote nodes and local services.
817
    ///
818
    /// ## Arguments
819
    /// block - the block to store
820
    /// new_block_msg - propagate this new block message
821
    /// source_peer - the peer that sent this new block message, or None if the block was generated by a local miner
822
    pub async fn handle_block(
1✔
823
        &mut self,
1✔
824
        block: Block,
1✔
825
        source_peer: Option<NodeId>,
1✔
826
    ) -> Result<BlockHash, CommsInterfaceError> {
1✔
827
        let block_hash = block.hash();
1✔
828
        let block_height = block.header.height;
1✔
829

1✔
830
        info!(
1✔
831
            target: LOG_TARGET,
×
832
            "Block #{} ({}) received from {}",
×
833
            block_height,
×
834
            block_hash.to_hex(),
×
835
            source_peer
×
836
                .as_ref()
×
837
                .map(|p| format!("remote peer: {}", p))
×
838
                .unwrap_or_else(|| "local services".to_string())
×
839
        );
840
        debug!(target: LOG_TARGET, "Incoming block: {}", block);
1✔
841
        let timer = Instant::now();
1✔
842
        let block = self.hydrate_block(block).await?;
1✔
843

844
        let add_block_result = self.blockchain_db.add_block(block.clone()).await;
1✔
845
        // Create block event on block event stream
UNCOV
846
        match add_block_result {
×
847
            Ok(block_add_result) => {
1✔
848
                debug!(
1✔
849
                    target: LOG_TARGET,
×
850
                    "Block #{} ({}) added ({}) to blockchain in {:.2?}",
×
851
                    block_height,
×
852
                    block_hash.to_hex(),
×
853
                    block_add_result,
×
854
                    timer.elapsed()
×
855
                );
856

857
                let should_propagate = match &block_add_result {
1✔
858
                    BlockAddResult::Ok(_) => true,
1✔
859
                    BlockAddResult::BlockExists => false,
×
860
                    BlockAddResult::OrphanBlock => false,
×
861
                    BlockAddResult::ChainReorg { .. } => true,
×
862
                };
863

864
                #[cfg(feature = "metrics")]
865
                self.update_block_result_metrics(&block_add_result).await?;
1✔
866

867
                self.publish_block_event(BlockEvent::ValidBlockAdded(block.clone(), block_add_result));
1✔
868

1✔
869
                if should_propagate {
1✔
870
                    debug!(
1✔
871
                        target: LOG_TARGET,
×
872
                        "Propagate block ({}) to network.",
×
873
                        block_hash.to_hex()
×
874
                    );
875
                    let exclude_peers = source_peer.into_iter().collect();
1✔
876
                    let new_block_msg = NewBlock::from(&*block);
1✔
877
                    if let Err(e) = self.outbound_nci.propagate_block(new_block_msg, exclude_peers).await {
1✔
878
                        warn!(
×
879
                            target: LOG_TARGET,
×
880
                            "Failed to propagate block ({}) to network: {}.",
×
881
                            block_hash.to_hex(), e
×
882
                        );
883
                    }
1✔
884
                }
×
885
                Ok(block_hash)
1✔
886
            },
887

UNCOV
888
            Err(e @ ChainStorageError::ValidationError { .. }) => {
×
UNCOV
889
                #[cfg(feature = "metrics")]
×
UNCOV
890
                {
×
UNCOV
891
                    let block_hash = block.hash();
×
UNCOV
892
                    metrics::rejected_blocks(block.header.height, &block_hash).inc();
×
UNCOV
893
                }
×
UNCOV
894
                warn!(
×
895
                    target: LOG_TARGET,
×
896
                    "Peer {} sent an invalid block: {}",
×
897
                    source_peer
×
898
                        .as_ref()
×
899
                        .map(ToString::to_string)
×
900
                        .unwrap_or_else(|| "<local request>".to_string()),
×
901
                    e
902
                );
UNCOV
903
                self.publish_block_event(BlockEvent::AddBlockValidationFailed { block, source_peer });
×
UNCOV
904
                Err(e.into())
×
905
            },
906

907
            Err(e) => {
×
908
                #[cfg(feature = "metrics")]
×
909
                metrics::rejected_blocks(block.header.height, &block.hash()).inc();
×
910

×
911
                self.publish_block_event(BlockEvent::AddBlockErrored { block });
×
912
                Err(e.into())
×
913
            },
914
        }
915
    }
1✔
916

917
    async fn hydrate_block(&mut self, block: Block) -> Result<Arc<Block>, CommsInterfaceError> {
1✔
918
        let block_hash = block.hash();
1✔
919
        let block_height = block.header.height;
1✔
920
        if block.body.inputs().is_empty() {
1✔
921
            debug!(
1✔
922
                target: LOG_TARGET,
×
923
                "Block #{} ({}) contains no inputs so nothing to hydrate",
×
924
                block_height,
×
925
                block_hash.to_hex(),
×
926
            );
927
            return Ok(Arc::new(block));
1✔
UNCOV
928
        }
×
UNCOV
929

×
UNCOV
930
        let timer = Instant::now();
×
UNCOV
931
        let (header, mut inputs, outputs, kernels) = block.dissolve();
×
932

UNCOV
933
        let db = self.blockchain_db.inner().db_read_access()?;
×
UNCOV
934
        for input in &mut inputs {
×
UNCOV
935
            if !input.is_compact() {
×
UNCOV
936
                continue;
×
UNCOV
937
            }
×
938

UNCOV
939
            let output_mined_info =
×
UNCOV
940
                db.fetch_output(&input.output_hash())?
×
UNCOV
941
                    .ok_or_else(|| CommsInterfaceError::InvalidFullBlock {
×
942
                        hash: block_hash,
×
943
                        details: format!("Output {} to be spent does not exist in db", input.output_hash()),
×
UNCOV
944
                    })?;
×
945

UNCOV
946
            let rp_hash = match output_mined_info.output.proof {
×
UNCOV
947
                Some(proof) => proof.hash(),
×
948
                None => FixedHash::zero(),
×
949
            };
UNCOV
950
            input.add_output_data(
×
UNCOV
951
                output_mined_info.output.version,
×
UNCOV
952
                output_mined_info.output.features,
×
UNCOV
953
                output_mined_info.output.commitment,
×
UNCOV
954
                output_mined_info.output.script,
×
UNCOV
955
                output_mined_info.output.sender_offset_public_key,
×
UNCOV
956
                output_mined_info.output.covenant,
×
UNCOV
957
                output_mined_info.output.encrypted_data,
×
UNCOV
958
                output_mined_info.output.metadata_signature,
×
UNCOV
959
                rp_hash,
×
UNCOV
960
                output_mined_info.output.minimum_value_promise,
×
UNCOV
961
            );
×
962
        }
UNCOV
963
        debug!(
×
964
            target: LOG_TARGET,
×
965
            "Hydrated block #{} ({}) with {} input(s) in {:.2?}",
×
966
            block_height,
×
967
            block_hash.to_hex(),
×
968
            inputs.len(),
×
969
            timer.elapsed()
×
970
        );
UNCOV
971
        let block = Block::new(header, AggregateBody::new(inputs, outputs, kernels));
×
UNCOV
972
        Ok(Arc::new(block))
×
973
    }
1✔
974

975
    fn publish_block_event(&self, event: BlockEvent) {
1✔
976
        if let Err(event) = self.block_event_sender.send(Arc::new(event)) {
1✔
977
            debug!(target: LOG_TARGET, "No event subscribers. Event {} dropped.", event.0)
×
978
        }
1✔
979
    }
1✔
980

981
    #[cfg(feature = "metrics")]
982
    async fn update_block_result_metrics(&self, block_add_result: &BlockAddResult) -> Result<(), CommsInterfaceError> {
1✔
983
        fn update_target_difficulty(block: &ChainBlock) {
1✔
984
            match block.header().pow_algo() {
1✔
985
                PowAlgorithm::Sha3x => {
1✔
986
                    metrics::target_difficulty_sha()
1✔
987
                        .set(i64::try_from(block.accumulated_data().target_difficulty.as_u64()).unwrap_or(i64::MAX));
1✔
988
                },
1✔
989
                PowAlgorithm::RandomX => {
×
990
                    metrics::target_difficulty_randomx()
×
991
                        .set(i64::try_from(block.accumulated_data().target_difficulty.as_u64()).unwrap_or(i64::MAX));
×
992
                },
×
993
            }
994
        }
1✔
995

996
        match block_add_result {
1✔
997
            BlockAddResult::Ok(ref block) => {
1✔
998
                update_target_difficulty(block);
1✔
999
                #[allow(clippy::cast_possible_wrap)]
1✔
1000
                metrics::tip_height().set(block.height() as i64);
1✔
1001
                let utxo_set_size = self.blockchain_db.utxo_count().await?;
1✔
1002
                metrics::utxo_set_size().set(utxo_set_size.try_into().unwrap_or(i64::MAX));
1✔
1003
            },
1004
            BlockAddResult::ChainReorg { added, removed } => {
×
1005
                if let Some(fork_height) = added.last().map(|b| b.height()) {
×
1006
                    #[allow(clippy::cast_possible_wrap)]
1007
                    metrics::tip_height().set(fork_height as i64);
×
1008
                    metrics::reorg(fork_height, added.len(), removed.len()).inc();
×
1009

1010
                    let utxo_set_size = self.blockchain_db.utxo_count().await?;
×
1011
                    metrics::utxo_set_size().set(utxo_set_size.try_into().unwrap_or(i64::MAX));
×
1012
                }
×
1013
                for block in added {
×
1014
                    update_target_difficulty(block);
×
1015
                }
×
1016
            },
1017
            BlockAddResult::OrphanBlock => {
×
1018
                metrics::orphaned_blocks().inc();
×
1019
            },
×
1020
            _ => {},
×
1021
        }
1022
        Ok(())
1✔
1023
    }
1✔
1024

UNCOV
1025
    async fn get_target_difficulty_for_next_block(
×
UNCOV
1026
        &self,
×
UNCOV
1027
        pow_algo: PowAlgorithm,
×
UNCOV
1028
        constants: &ConsensusConstants,
×
UNCOV
1029
        current_block_hash: HashOutput,
×
UNCOV
1030
    ) -> Result<Difficulty, CommsInterfaceError> {
×
UNCOV
1031
        let target_difficulty = self
×
UNCOV
1032
            .blockchain_db
×
UNCOV
1033
            .fetch_target_difficulty_for_next_block(pow_algo, current_block_hash)
×
UNCOV
1034
            .await?;
×
1035

UNCOV
1036
        let target = target_difficulty.calculate(
×
UNCOV
1037
            constants.min_pow_difficulty(pow_algo),
×
UNCOV
1038
            constants.max_pow_difficulty(pow_algo),
×
UNCOV
1039
        );
×
UNCOV
1040
        trace!(target: LOG_TARGET, "Target difficulty {} for PoW {}", target, pow_algo);
×
UNCOV
1041
        Ok(target)
×
UNCOV
1042
    }
×
1043

1044
    pub async fn get_last_seen_hash(&self) -> Result<FixedHash, CommsInterfaceError> {
×
1045
        self.mempool.get_last_seen_hash().await.map_err(|e| e.into())
×
1046
    }
×
1047
}
1048

1049
impl<B> Clone for InboundNodeCommsHandlers<B> {
1050
    fn clone(&self) -> Self {
6✔
1051
        Self {
6✔
1052
            block_event_sender: self.block_event_sender.clone(),
6✔
1053
            blockchain_db: self.blockchain_db.clone(),
6✔
1054
            mempool: self.mempool.clone(),
6✔
1055
            consensus_manager: self.consensus_manager.clone(),
6✔
1056
            list_of_reconciling_blocks: self.list_of_reconciling_blocks.clone(),
6✔
1057
            outbound_nci: self.outbound_nci.clone(),
6✔
1058
            connectivity: self.connectivity.clone(),
6✔
1059
            randomx_factory: self.randomx_factory.clone(),
6✔
1060
            cached_block_template: self.cached_block_template.clone(),
6✔
1061
        }
6✔
1062
    }
6✔
1063
}
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