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

tari-project / tari / 18097567115

29 Sep 2025 12:50PM UTC coverage: 58.554% (-2.3%) from 60.88%
18097567115

push

github

web-flow
chore(ci): switch rust toolchain to stable (#7524)

Description
switch rust toolchain to stable

Motivation and Context
use stable rust toolchain


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

## Summary by CodeRabbit

* **Chores**
* Standardized Rust toolchain on stable across CI workflows for more
predictable builds.
* Streamlined setup by removing unnecessary components and aligning
toolchain configuration with environment variables.
  * Enabled an environment flag to improve rustup behavior during CI.
* Improved coverage workflow consistency with dynamic toolchain
selection.

* **Tests**
* Removed nightly-only requirements, simplifying test commands and
improving compatibility.
* Expanded CI triggers to include ci-* branches for better pre-merge
validation.
* Maintained existing job logic while improving reliability and
maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

66336 of 113291 relevant lines covered (58.55%)

551641.45 hits per line

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

83.33
/comms/core/src/peer_manager/node_id.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::{
24
    cmp,
25
    cmp::Ordering,
26
    convert::{TryFrom, TryInto},
27
    fmt,
28
    hash::{Hash, Hasher},
29
    marker::PhantomData,
30
    ops::BitXor,
31
};
32

33
use blake2::{
34
    digest::{Update, VariableOutput},
35
    Blake2bVar,
36
};
37
use serde::{de, Deserialize, Deserializer, Serialize};
38
use tari_utilities::{
39
    hex::{to_hex, Hex},
40
    ByteArray,
41
    ByteArrayError,
42
};
43
use thiserror::Error;
44

45
use crate::{peer_manager::node_distance::NodeDistance, types::CommsPublicKey};
46

47
pub(super) type NodeIdArray = [u8; NodeId::byte_size()];
48

49
/// Error type for NodeId
50
#[derive(Debug, Error, Clone)]
51
pub enum NodeIdError {
52
    #[error("Incorrect byte count (expected {} bytes)", NodeId::byte_size())]
53
    IncorrectByteCount,
54
    #[error("Invalid digest output size")]
55
    InvalidDigestOutputSize,
56
}
57

58
/// A Node Identity is used as a unique identifier for a node in the Tari communications network.
59
#[derive(Clone, Eq, Deserialize, Serialize, Default)]
60
pub struct NodeId(NodeIdArray);
61

62
impl NodeId {
63
    /// Construct a new node id on the origin
64
    pub fn new() -> Self {
8✔
65
        Default::default()
8✔
66
    }
8✔
67

68
    /// 104-bit/13 byte as per RFC-0151
69
    pub const fn byte_size() -> usize {
1,545,284✔
70
        13
1,545,284✔
71
    }
1,545,284✔
72

73
    /// Derive a node id from a public key: node_id=hash(public_key)
74
    pub fn from_key<K: ByteArray>(key: &K) -> Self {
2,800✔
75
        let bytes = key.as_bytes();
2,800✔
76
        let mut buf = [0u8; NodeId::byte_size()];
2,800✔
77
        Blake2bVar::new(NodeId::byte_size())
2,800✔
78
            .expect("NodeId::byte_size() is invalid")
2,800✔
79
            .chain(bytes)
2,800✔
80
            // Safety: output size and buf size are equal
81
            .finalize_variable(&mut buf).unwrap();
2,800✔
82
        NodeId(buf)
2,800✔
83
    }
2,800✔
84

85
    /// Derive a node id from a public key: node_id = hash(public_key)
86
    pub fn from_public_key(key: &CommsPublicKey) -> Self {
471✔
87
        Self::from_key(key)
471✔
88
    }
471✔
89

90
    /// Calculate the distance between the current node id and the provided node id using the XOR metric
91
    pub fn distance(&self, node_id: &NodeId) -> NodeDistance {
1,420,516✔
92
        NodeDistance::from_node_ids(self, node_id)
1,420,516✔
93
    }
1,420,516✔
94

95
    /// Find and return the indices of the K nearest neighbours from the provided node id list
96
    pub fn closest_indices(&self, node_ids: &[NodeId], k: usize) -> Vec<usize> {
2✔
97
        let k = cmp::min(k, node_ids.len());
2✔
98
        let mut indices: Vec<usize> = Vec::with_capacity(node_ids.len());
2✔
99
        let mut dists: Vec<NodeDistance> = Vec::with_capacity(node_ids.len());
2✔
100
        for (i, node_id) in node_ids.iter().enumerate() {
18✔
101
            indices.push(i);
18✔
102
            dists.push(self.distance(node_id))
18✔
103
        }
104
        // Perform partial sort of elements only up to K elements
105
        let mut nearest_node_indices: Vec<usize> = Vec::with_capacity(k);
2✔
106
        for i in 0..k {
12✔
107
            for j in i + 1..node_ids.len() {
57✔
108
                if dists.get(i).expect("Index should exist") > dists.get(j).expect("Index should exist") {
57✔
109
                    dists.swap(i, j);
38✔
110
                    indices.swap(i, j);
38✔
111
                }
38✔
112
            }
113
            nearest_node_indices.push(*indices.get(i).expect("Index should exist"));
12✔
114
        }
115
        nearest_node_indices
2✔
116
    }
2✔
117

118
    /// Find and return the node ids of the K nearest neighbours from the provided node id list
119
    pub fn closest(&self, node_ids: &[NodeId], k: usize) -> Vec<NodeId> {
2✔
120
        let nearest_node_indices = self.closest_indices(node_ids, k);
2✔
121
        let mut nearest_node_ids: Vec<NodeId> = Vec::with_capacity(nearest_node_indices.len());
2✔
122
        for nearest in nearest_node_indices {
14✔
123
            nearest_node_ids.push(node_ids.get(nearest).expect("Index should exist").clone());
12✔
124
        }
12✔
125
        nearest_node_ids
2✔
126
    }
2✔
127

128
    pub fn into_inner(self) -> NodeIdArray {
×
129
        self.0
×
130
    }
×
131

132
    pub fn short_str(&self) -> String {
157✔
133
        to_hex(self.0.get(..8).expect("Index should exist"))
157✔
134
    }
157✔
135
}
136

137
impl ByteArray for NodeId {
138
    /// Try and convert the given byte array to a NodeId. Any failures (incorrect array length,
139
    /// implementation-specific checks, etc) return a [ByteArrayError](enum.ByteArrayError.html).
140
    fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ByteArrayError> {
121,805✔
141
        bytes.try_into().map_err(|err| ByteArrayError::ConversionError {
121,805✔
142
            reason: format!("{err:?}"),
×
143
        })
×
144
    }
121,805✔
145

146
    /// Return the NodeId as a byte array
147
    fn as_bytes(&self) -> &[u8] {
64,910✔
148
        self.0.as_ref()
64,910✔
149
    }
64,910✔
150
}
151

152
impl ByteArray for Box<NodeId> {
153
    /// Try and convert the given byte array to a NodeId. Any failures (incorrect array length,
154
    /// implementation-specific checks, etc) return a [ByteArrayError](enum.ByteArrayError.html).
155
    fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ByteArrayError> {
×
156
        let node_id = NodeId::try_from(bytes).map_err(|err| ByteArrayError::ConversionError {
×
157
            reason: format!("{err:?}"),
×
158
        })?;
×
159
        Ok(Box::new(node_id))
×
160
    }
×
161

162
    /// Return the NodeId as a byte array
163
    fn as_bytes(&self) -> &[u8] {
×
164
        &self.as_ref().0
×
165
    }
×
166
}
167

168
impl PartialEq for NodeId {
169
    fn eq(&self, nid: &NodeId) -> bool {
2,469✔
170
        self.0 == nid.0
2,469✔
171
    }
2,469✔
172
}
173

174
impl PartialOrd<NodeId> for NodeId {
175
    fn partial_cmp(&self, other: &NodeId) -> Option<Ordering> {
6✔
176
        Some(self.cmp(other))
6✔
177
    }
6✔
178
}
179

180
impl Ord for NodeId {
181
    fn cmp(&self, other: &Self) -> Ordering {
7✔
182
        self.0.cmp(&other.0)
7✔
183
    }
7✔
184
}
185

186
impl BitXor for &NodeId {
187
    type Output = NodeIdArray;
188

189
    fn bitxor(self, rhs: Self) -> Self::Output {
1,420,616✔
190
        let mut xor = [0u8; NodeId::byte_size()];
1,420,616✔
191
        #[allow(clippy::needless_range_loop)]
192
        for i in 0..NodeId::byte_size() {
18,468,008✔
193
            *xor.get_mut(i).expect("Index should exist") =
18,468,008✔
194
                self.0.get(i).expect("Index should exist") ^ rhs.0.get(i).expect("Index should exist");
18,468,008✔
195
        }
18,468,008✔
196
        xor
1,420,616✔
197
    }
1,420,616✔
198
}
199

200
impl TryFrom<&[u8]> for NodeId {
201
    type Error = NodeIdError;
202

203
    /// Construct a node id from 32 bytes
204
    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
121,824✔
205
        if bytes.len() != NodeId::byte_size() {
121,824✔
206
            return Err(NodeIdError::IncorrectByteCount);
×
207
        }
121,824✔
208

209
        let mut buf = [0; NodeId::byte_size()];
121,824✔
210
        buf.copy_from_slice(bytes);
121,824✔
211
        Ok(NodeId(buf))
121,824✔
212
    }
121,824✔
213
}
214

215
impl From<CommsPublicKey> for NodeId {
216
    fn from(pk: CommsPublicKey) -> Self {
×
217
        NodeId::from_public_key(&pk)
×
218
    }
×
219
}
220

221
impl Hash for NodeId {
222
    /// Require the implementation of the Hash trait for Hashmaps
223
    fn hash<H: Hasher>(&self, state: &mut H) {
2,432✔
224
        self.0.hash(state);
2,432✔
225
    }
2,432✔
226
}
227

228
impl AsRef<[u8]> for NodeId {
229
    fn as_ref(&self) -> &[u8] {
×
230
        &self.0
×
231
    }
×
232
}
233

234
impl fmt::Display for NodeId {
235
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
561✔
236
        write!(f, "{}", to_hex(&self.0))
561✔
237
    }
561✔
238
}
239

240
impl fmt::Debug for NodeId {
241
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78✔
242
        write!(f, "NodeId({})", to_hex(&self.0))
78✔
243
    }
78✔
244
}
245

246
pub fn deserialize_node_id_from_hex<'de, D>(des: D) -> Result<NodeId, D::Error>
×
247
where D: Deserializer<'de> {
×
248
    struct KeyStringVisitor<K> {
249
        marker: PhantomData<K>,
250
    }
251

252
    impl de::Visitor<'_> for KeyStringVisitor<NodeId> {
253
        type Value = NodeId;
254

255
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
×
256
            formatter.write_str("a node id in hex format")
×
257
        }
×
258

259
        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
×
260
        where E: de::Error {
×
261
            NodeId::from_hex(v).map_err(E::custom)
×
262
        }
×
263
    }
264
    des.deserialize_str(KeyStringVisitor { marker: PhantomData })
×
265
}
×
266

267
#[cfg(test)]
268
mod test {
269
    #![allow(clippy::indexing_slicing)]
270
    use tari_crypto::keys::SecretKey;
271

272
    use super::*;
273
    use crate::types::CommsSecretKey;
274

275
    #[test]
276
    fn display() {
1✔
277
        let node_id = NodeId::try_from(&[144u8, 28, 106, 112, 220, 197, 216, 119, 9, 217, 42, 77, 159][..]).unwrap();
1✔
278

279
        let result = format!("{node_id}");
1✔
280
        assert_eq!("901c6a70dcc5d87709d92a4d9f", result);
1✔
281
    }
1✔
282

283
    #[test]
284
    fn test_from_public_key() {
1✔
285
        let mut rng = rand::rngs::OsRng;
1✔
286
        let sk = CommsSecretKey::random(&mut rng);
1✔
287
        let pk = CommsPublicKey::from_secret_key(&sk);
1✔
288
        let node_id = NodeId::from_key(&pk);
1✔
289
        assert_ne!(node_id.0.to_vec(), NodeId::new().0.to_vec());
1✔
290
        // Ensure node id is different to original public key
291
        let mut pk_array: [u8; 32] = [0; 32];
1✔
292
        pk_array.copy_from_slice(pk.as_bytes());
1✔
293
        assert_ne!(node_id.0.to_vec(), pk_array.to_vec());
1✔
294
    }
1✔
295

296
    #[test]
297
    fn test_distance_and_ordering() {
1✔
298
        let node_id1 = NodeId::try_from(&[144, 28, 106, 112, 220, 197, 216, 119, 9, 217, 42, 77, 159][..]).unwrap();
1✔
299
        let node_id2 = NodeId::try_from(&[186, 43, 62, 14, 60, 214, 9, 180, 145, 122, 55, 160, 83][..]).unwrap();
1✔
300
        let node_id3 = NodeId::try_from(&[60, 32, 246, 39, 108, 201, 214, 91, 30, 230, 3, 126, 31][..]).unwrap();
1✔
301
        assert!(node_id1.0 < node_id2.0);
1✔
302
        assert!(node_id1.0 > node_id3.0);
1✔
303
        // XOR metric
304
        let desired_n1_to_n2_dist =
1✔
305
            NodeDistance::try_from(&[42, 55, 84, 126, 224, 19, 209, 195, 152, 163, 29, 237, 204][..]).unwrap();
1✔
306
        let desired_n1_to_n3_dist =
1✔
307
            NodeDistance::try_from(&[172, 60, 156, 87, 176, 12, 14, 44, 23, 63, 41, 51, 128][..]).unwrap();
1✔
308

309
        let n1_to_n2_dist = node_id1.distance(&node_id2);
1✔
310
        let n1_to_n3_dist = node_id1.distance(&node_id3);
1✔
311
        // Big-endian ordering
312
        assert!(n1_to_n2_dist < n1_to_n3_dist);
1✔
313
        assert_eq!(n1_to_n2_dist, desired_n1_to_n2_dist);
1✔
314
        assert_eq!(n1_to_n3_dist, desired_n1_to_n3_dist);
1✔
315

316
        // Commutative
317
        let n1_to_n2_dist = node_id1.distance(&node_id2);
1✔
318
        let n2_to_n1_dist = node_id2.distance(&node_id1);
1✔
319

320
        assert_eq!(n1_to_n2_dist, n2_to_n1_dist);
1✔
321
    }
1✔
322

323
    #[test]
324
    #[allow(clippy::vec_init_then_push)]
325
    fn test_closest() {
1✔
326
        let mut node_ids: Vec<NodeId> = Vec::new();
1✔
327
        node_ids.push(NodeId::try_from(&[144, 28, 106, 112, 220, 197, 216, 119, 9, 217, 42, 77, 159][..]).unwrap());
1✔
328
        node_ids.push(NodeId::try_from(&[75, 249, 102, 1, 2, 166, 155, 37, 22, 54, 84, 98, 56][..]).unwrap());
1✔
329
        node_ids.push(NodeId::try_from(&[60, 32, 246, 39, 108, 201, 214, 91, 30, 230, 3, 126, 31][..]).unwrap());
1✔
330
        node_ids.push(NodeId::try_from(&[134, 116, 78, 53, 246, 206, 200, 147, 126, 96, 54, 113, 67][..]).unwrap());
1✔
331
        node_ids.push(NodeId::try_from(&[75, 146, 162, 130, 22, 63, 247, 182, 156, 103, 174, 32, 134][..]).unwrap());
1✔
332
        node_ids.push(NodeId::try_from(&[186, 43, 62, 14, 60, 214, 9, 180, 145, 122, 55, 160, 83][..]).unwrap());
1✔
333
        node_ids.push(NodeId::try_from(&[143, 189, 32, 210, 30, 231, 82, 5, 86, 85, 28, 82, 154][..]).unwrap());
1✔
334
        node_ids.push(NodeId::try_from(&[155, 210, 214, 160, 153, 70, 172, 234, 177, 178, 62, 82, 166][..]).unwrap());
1✔
335
        node_ids.push(NodeId::try_from(&[173, 218, 34, 188, 211, 173, 235, 82, 18, 159, 55, 47, 242][..]).unwrap());
1✔
336

337
        let node_id = NodeId::try_from(&[169, 125, 200, 137, 210, 73, 241, 238, 25, 108, 8, 48, 66][..]).unwrap();
1✔
338

339
        let k = 3;
1✔
340
        let knn_node_ids = node_id.closest(&node_ids, k);
1✔
341
        assert_eq!(knn_node_ids.len(), k);
1✔
342
        // XOR metric nearest neighbours
343
        assert_eq!(knn_node_ids[0].0, [
1✔
344
            173, 218, 34, 188, 211, 173, 235, 82, 18, 159, 55, 47, 242
345
        ]);
346
        assert_eq!(knn_node_ids[1].0, [
1✔
347
            186, 43, 62, 14, 60, 214, 9, 180, 145, 122, 55, 160, 83
348
        ]);
349
        assert_eq!(knn_node_ids[2].0, [
1✔
350
            143, 189, 32, 210, 30, 231, 82, 5, 86, 85, 28, 82, 154
351
        ]);
352

353
        assert_eq!(node_id.closest(&node_ids, node_ids.len() + 1).len(), node_ids.len());
1✔
354
    }
1✔
355

356
    #[test]
357
    fn partial_eq() {
1✔
358
        let bytes = &[173, 218, 34, 188, 211, 173, 235, 82, 18, 159, 55, 47, 242][..];
1✔
359
        let nid1 = NodeId::try_from(bytes).unwrap();
1✔
360
        let nid2 = NodeId::try_from(bytes).unwrap();
1✔
361

362
        assert_eq!(nid1, nid2);
1✔
363
    }
1✔
364

365
    #[test]
366
    fn convert_xor_distance_to_u128() {
1✔
367
        let node_id1 = NodeId::try_from(&[128, 28, 106, 112, 220, 197, 216, 119, 9, 128, 42, 77, 55][..]).unwrap();
1✔
368
        let node_id2 = NodeId::try_from(&[160, 28, 106, 112, 220, 197, 216, 119, 9, 128, 42, 77, 54][..]).unwrap();
1✔
369
        let node_id3 = NodeId::try_from(&[64, 28, 106, 112, 220, 197, 216, 119, 9, 128, 42, 77, 54][..]).unwrap();
1✔
370
        let n12_distance = node_id1.distance(&node_id2);
1✔
371
        let n13_distance = node_id1.distance(&node_id3);
1✔
372
        assert_eq!(n12_distance.to_bytes()[..4], [0, 0, 0, 32]);
1✔
373
        assert_eq!(n13_distance.to_bytes()[..4], [0, 0, 0, 192]);
1✔
374
        assert!(n12_distance < n13_distance);
1✔
375
        assert_eq!(n12_distance.as_u128(), ((128 ^ 160) << (12 * 8)) + 1);
1✔
376
        assert_eq!(n13_distance.as_u128(), ((128 ^ 64) << (12 * 8)) + 1);
1✔
377
    }
1✔
378
}
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