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

tari-project / tari / 15484013348

06 Jun 2025 06:08AM UTC coverage: 72.04% (+0.3%) from 71.789%
15484013348

push

github

web-flow
fix(network-discovery): add back idle event handling (#7194)

Description
---
fix(network-discovery): add back idle event handling

Motivation and Context
---
network discovery was spinning at full speed because the Idle event
transition was removed. Network logs would rotate < 1s.

```
[comms::dht::network_discovery::ready] [Thread:123190302967360] DEBUG NetworkDiscovery::Ready: Peer list contains 759 entries. Current discovery rounds in this cycle: 0.
[comms::dht::network_discovery::ready] [Thread:123190302967360] DEBUG First active round (current_num_rounds = 0) and num_peers (759) >= min_desired_peers (16). Forcing DHT discovery.
 [comms::dht::network_discovery::ready] [Thread:123190302967360] DEBUG Selecting 5 random peers for discovery (last round info available: false, new peers in last round: false).
[comms::dht::network_discovery::ready] [Thread:123190302967360] DEBUG No suitable peers found for the forced DHT discovery round (current_num_rounds = 0 path). Transitioning to Idle.
 [comms::dht::network_discovery] [Thread:123190302967360] DEBUG Transition triggered from current state `Ready` by event `Idle`
comms::dht::network_discovery] [Thread:123190302967360] DEBUG No state transition for event `Idle`. The current state is `Ready`

...instant rinse and repeat...
```

This PR adds the idle state transition back. Note that idle will idle
for 30 minutes so should only transition when all work is done and we
have downloaded sufficient peers.

How Has This Been Tested?
---
Manually - console wallet with empty peer db

What process can a PR reviewer use to test or verify this change?
---

<!-- Checklist -->
<!-- 1. Is the title of your PR in the form that would make nice release
notes? The title, excluding the conventional commit
tag, will be included exactly as is in the CHANGELOG, so please think
about it carefully. -->


Breaking Changes
---

- [x] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [ ] Other... (continued)

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

412 existing lines in 30 files now uncovered.

80882 of 112274 relevant lines covered (72.04%)

242938.65 hits per line

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

20.63
/base_layer/core/src/base_node/comms_interface/local_interface.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
use std::{ops::RangeInclusive, sync::Arc};
24

25
use tari_common_types::{
26
    chain_metadata::ChainMetadata,
27
    types::{BlockHash, CompressedCommitment, CompressedPublicKey, HashOutput, Signature},
28
};
29
use tari_service_framework::{reply_channel::SenderService, Service};
30
use tokio::sync::broadcast;
31

32
use crate::{
33
    base_node::comms_interface::{
34
        comms_request::GetNewBlockTemplateRequest,
35
        error::CommsInterfaceError,
36
        BlockEvent,
37
        NodeCommsRequest,
38
        NodeCommsResponse,
39
    },
40
    blocks::{Block, ChainHeader, HistoricalBlock, NewBlockTemplate},
41
    chain_storage::TemplateRegistrationEntry,
42
    proof_of_work::{Difficulty, PowAlgorithm},
43
    transactions::transaction_components::{TransactionKernel, TransactionOutput},
44
};
45

46
pub type BlockEventSender = broadcast::Sender<Arc<BlockEvent>>;
47
pub type BlockEventReceiver = broadcast::Receiver<Arc<BlockEvent>>;
48

49
/// The InboundNodeCommsInterface provides an interface to request information from the current local node by other
50
/// internal services.
51
#[derive(Clone)]
52
pub struct LocalNodeCommsInterface {
53
    request_sender: SenderService<NodeCommsRequest, Result<NodeCommsResponse, CommsInterfaceError>>,
54
    block_sender: SenderService<Block, Result<BlockHash, CommsInterfaceError>>,
55
    block_event_sender: BlockEventSender,
56
}
57

58
impl LocalNodeCommsInterface {
59
    /// Construct a new LocalNodeCommsInterface with the specified SenderService.
60
    pub fn new(
62✔
61
        request_sender: SenderService<NodeCommsRequest, Result<NodeCommsResponse, CommsInterfaceError>>,
62✔
62
        block_sender: SenderService<Block, Result<BlockHash, CommsInterfaceError>>,
62✔
63
        block_event_sender: BlockEventSender,
62✔
64
    ) -> Self {
62✔
65
        Self {
62✔
66
            request_sender,
62✔
67
            block_sender,
62✔
68
            block_event_sender,
62✔
69
        }
62✔
70
    }
62✔
71

72
    pub fn get_block_event_stream(&self) -> BlockEventReceiver {
107✔
73
        self.block_event_sender.subscribe()
107✔
74
    }
107✔
75

76
    /// Request metadata from the current local node.
77
    pub async fn get_metadata(&mut self) -> Result<ChainMetadata, CommsInterfaceError> {
82✔
78
        match self.request_sender.call(NodeCommsRequest::GetChainMetadata).await?? {
82✔
79
            NodeCommsResponse::ChainMetadata(metadata) => Ok(metadata),
82✔
80
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
81
        }
82
    }
82✔
83

84
    pub async fn get_target_difficulty_for_next_block(
×
85
        &mut self,
×
86
        algo: PowAlgorithm,
×
87
    ) -> Result<Difficulty, CommsInterfaceError> {
×
88
        match self
×
89
            .request_sender
×
90
            .call(NodeCommsRequest::GetTargetDifficultyNextBlock(algo))
×
91
            .await??
×
92
        {
93
            NodeCommsResponse::TargetDifficulty(target_difficulty) => Ok(target_difficulty),
×
94
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
95
        }
96
    }
×
97

98
    /// Request the block headers within the given range
99
    pub async fn get_blocks(
×
100
        &mut self,
×
101
        range: RangeInclusive<u64>,
×
102
        compact: bool,
×
103
    ) -> Result<Vec<HistoricalBlock>, CommsInterfaceError> {
×
104
        match self
×
105
            .request_sender
×
106
            .call(NodeCommsRequest::FetchMatchingBlocks { range, compact })
×
107
            .await??
×
108
        {
109
            NodeCommsResponse::HistoricalBlocks(blocks) => Ok(blocks),
×
110
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
111
        }
112
    }
×
113

114
    /// Request the block header at the given height
115
    pub async fn get_block(
×
116
        &mut self,
×
117
        height: u64,
×
118
        compact: bool,
×
119
    ) -> Result<Option<HistoricalBlock>, CommsInterfaceError> {
×
120
        match self
×
121
            .request_sender
×
122
            .call(NodeCommsRequest::FetchMatchingBlocks {
×
123
                range: height..=height,
×
124
                compact,
×
125
            })
×
126
            .await??
×
127
        {
128
            NodeCommsResponse::HistoricalBlocks(mut blocks) => Ok(blocks.pop()),
×
129
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
130
        }
131
    }
×
132

133
    /// Request the block headers with the given range of heights. The returned headers are ordered from lowest to
134
    /// highest block height
135
    pub async fn get_headers(&mut self, range: RangeInclusive<u64>) -> Result<Vec<ChainHeader>, CommsInterfaceError> {
×
136
        match self
×
137
            .request_sender
×
138
            .call(NodeCommsRequest::FetchHeaders(range))
×
139
            .await??
×
140
        {
141
            NodeCommsResponse::BlockHeaders(headers) => Ok(headers),
×
142
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
143
        }
144
    }
×
145

146
    /// Request the block header with the height.
147
    pub async fn get_header(&mut self, height: u64) -> Result<Option<ChainHeader>, CommsInterfaceError> {
×
148
        match self
×
149
            .request_sender
×
150
            .call(NodeCommsRequest::FetchHeaders(height..=height))
×
151
            .await??
×
152
        {
153
            NodeCommsResponse::BlockHeaders(mut headers) => Ok(headers.pop()),
×
154
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
155
        }
156
    }
×
157

158
    /// Request the construction of a new mineable block template from the base node service.
159
    pub async fn get_new_block_template(
3✔
160
        &mut self,
3✔
161
        pow_algorithm: PowAlgorithm,
3✔
162
        max_weight: u64,
3✔
163
    ) -> Result<NewBlockTemplate, CommsInterfaceError> {
3✔
164
        let request = GetNewBlockTemplateRequest {
3✔
165
            algo: pow_algorithm,
3✔
166
            max_weight,
3✔
167
        };
3✔
168
        match self
3✔
169
            .request_sender
3✔
170
            .call(NodeCommsRequest::GetNewBlockTemplate(request))
3✔
171
            .await??
3✔
172
        {
173
            NodeCommsResponse::NewBlockTemplate(new_block_template) => Ok(new_block_template),
3✔
174
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
175
        }
176
    }
3✔
177

178
    /// Request from base node service the construction of a block from a block template.
179
    pub async fn get_new_block(&mut self, block_template: NewBlockTemplate) -> Result<Block, CommsInterfaceError> {
3✔
180
        match self
3✔
181
            .request_sender
3✔
182
            .call(NodeCommsRequest::GetNewBlock(block_template))
3✔
183
            .await??
3✔
184
        {
185
            NodeCommsResponse::NewBlock { success, error, block } => {
3✔
186
                if success {
3✔
187
                    if let Some(block) = block {
3✔
188
                        Ok(block)
3✔
189
                    } else {
190
                        Err(CommsInterfaceError::UnexpectedApiResponse)
×
191
                    }
192
                } else {
193
                    Err(CommsInterfaceError::ApiError(
×
194
                        error.unwrap_or_else(|| "Unspecified error".to_string()),
×
195
                    ))
×
196
                }
197
            },
198
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
199
        }
200
    }
3✔
201

202
    /// Submit a block to the base node service.
203
    pub async fn submit_block(&mut self, block: Block) -> Result<BlockHash, CommsInterfaceError> {
12✔
204
        self.block_sender.call(block).await?
12✔
205
    }
12✔
206

UNCOV
207
    pub fn publish_block_event(&self, event: BlockEvent) -> usize {
×
UNCOV
208
        // If event send fails, that means that there are no receivers (i.e. it was sent to zero receivers)
×
UNCOV
209
        self.block_event_sender.send(Arc::new(event)).unwrap_or(0)
×
UNCOV
210
    }
×
211

212
    pub async fn fetch_matching_utxos(
×
213
        &mut self,
×
214
        hashes: Vec<HashOutput>,
×
215
    ) -> Result<Vec<TransactionOutput>, CommsInterfaceError> {
×
216
        match self
×
217
            .request_sender
×
218
            .call(NodeCommsRequest::FetchMatchingUtxos(hashes))
×
219
            .await??
×
220
        {
221
            NodeCommsResponse::TransactionOutputs(outputs) => Ok(outputs),
×
222
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
223
        }
224
    }
×
225

226
    /// Fetches the blocks with the specified utxo commitments
227
    pub async fn fetch_blocks_with_utxos(
×
228
        &mut self,
×
229
        commitments: Vec<CompressedCommitment>,
×
230
    ) -> Result<Vec<HistoricalBlock>, CommsInterfaceError> {
×
231
        match self
×
232
            .request_sender
×
233
            .call(NodeCommsRequest::FetchBlocksByUtxos(commitments))
×
234
            .await??
×
235
        {
236
            NodeCommsResponse::HistoricalBlocks(blocks) => Ok(blocks),
×
237
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
238
        }
239
    }
×
240

241
    /// Fetches the blocks with the specified kernel signatures commitments
242
    pub async fn get_blocks_with_kernels(
×
243
        &mut self,
×
244
        kernels: Vec<Signature>,
×
245
    ) -> Result<Vec<HistoricalBlock>, CommsInterfaceError> {
×
246
        match self
×
247
            .request_sender
×
248
            .call(NodeCommsRequest::FetchBlocksByKernelExcessSigs(kernels))
×
249
            .await??
×
250
        {
251
            NodeCommsResponse::HistoricalBlocks(blocks) => Ok(blocks),
×
252
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
253
        }
254
    }
×
255

256
    /// Return header matching the given hash. If the header cannot be found `Ok(None)` is returned.
257
    pub async fn get_header_by_hash(&mut self, hash: HashOutput) -> Result<Option<ChainHeader>, CommsInterfaceError> {
×
258
        match self
×
259
            .request_sender
×
260
            .call(NodeCommsRequest::GetHeaderByHash(hash))
×
261
            .await??
×
262
        {
263
            NodeCommsResponse::BlockHeader(header) => Ok(header),
×
264
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
265
        }
266
    }
×
267

268
    /// Return block matching the given hash. If the block cannot be found `Ok(None)` is returned.
269
    pub async fn get_block_by_hash(
×
270
        &mut self,
×
271
        hash: HashOutput,
×
272
    ) -> Result<Option<HistoricalBlock>, CommsInterfaceError> {
×
273
        match self
×
274
            .request_sender
×
275
            .call(NodeCommsRequest::GetBlockByHash(hash))
×
276
            .await??
×
277
        {
278
            NodeCommsResponse::HistoricalBlock(block) => Ok(*block),
×
279
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
280
        }
281
    }
×
282

283
    /// Searches for a kernel via the excess sig
284
    pub async fn get_kernel_by_excess_sig(
×
285
        &mut self,
×
286
        kernel: Signature,
×
287
    ) -> Result<Vec<TransactionKernel>, CommsInterfaceError> {
×
288
        match self
×
289
            .request_sender
×
290
            .call(NodeCommsRequest::FetchKernelByExcessSig(kernel))
×
291
            .await??
×
292
        {
293
            NodeCommsResponse::TransactionKernels(kernels) => Ok(kernels),
×
294
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
295
        }
296
    }
×
297

298
    pub async fn get_active_validator_nodes(
×
299
        &mut self,
×
300
        height: u64,
×
301
    ) -> Result<Vec<(CompressedPublicKey, [u8; 32])>, CommsInterfaceError> {
×
302
        match self
×
303
            .request_sender
×
304
            .call(NodeCommsRequest::FetchValidatorNodesKeys { height })
×
305
            .await??
×
306
        {
307
            NodeCommsResponse::FetchValidatorNodesKeysResponse(validator_node) => Ok(validator_node),
×
308
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
309
        }
310
    }
×
311

312
    pub async fn get_shard_key(
×
313
        &mut self,
×
314
        height: u64,
×
315
        public_key: CompressedPublicKey,
×
316
    ) -> Result<Option<[u8; 32]>, CommsInterfaceError> {
×
317
        match self
×
318
            .request_sender
×
319
            .call(NodeCommsRequest::GetShardKey { height, public_key })
×
320
            .await??
×
321
        {
322
            NodeCommsResponse::GetShardKeyResponse(shard_key) => Ok(shard_key),
×
323
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
324
        }
325
    }
×
326

327
    pub async fn get_template_registrations(
×
328
        &mut self,
×
329
        start_height: u64,
×
330
        end_height: u64,
×
331
    ) -> Result<Vec<TemplateRegistrationEntry>, CommsInterfaceError> {
×
332
        match self
×
333
            .request_sender
×
334
            .call(NodeCommsRequest::FetchTemplateRegistrations {
×
335
                start_height,
×
336
                end_height,
×
337
            })
×
338
            .await??
×
339
        {
340
            NodeCommsResponse::FetchTemplateRegistrationsResponse(template_registrations) => Ok(template_registrations),
×
341
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
342
        }
343
    }
×
344

345
    /// Fetches UTXOs that are not spent for the given block hash up to the current chain tip.
346
    pub async fn fetch_unspent_utxos_in_block(
×
347
        &mut self,
×
348
        block_hash: BlockHash,
×
349
    ) -> Result<Vec<TransactionOutput>, CommsInterfaceError> {
×
350
        match self
×
351
            .request_sender
×
352
            .call(NodeCommsRequest::FetchUnspentUtxosInBlock { block_hash })
×
353
            .await??
×
354
        {
355
            NodeCommsResponse::TransactionOutputs(outputs) => Ok(outputs),
×
356
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
357
        }
358
    }
×
359
}
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