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

tari-project / tari / 23005727524

12 Mar 2026 02:00PM UTC coverage: 61.737% (-0.3%) from 62.017%
23005727524

push

github

web-flow
chore: remove neighbour/distance peer pool distinction from DHT connectivity (#7702)

DHT connectivity maintained two separate peer pools — a `neighbours`
pool (originally distance-ordered) and a `random_pool` — which became
functionally identical after XOR-distance selection was removed. This PR
collapses them into a single unified pool.

## DHT Connectivity (`comms/dht/src/connectivity/mod.rs`)

- Removed `neighbours: Vec<NodeId>` field; all managed peers live in
`random_pool`
- Total pool capacity = `num_neighbouring_nodes + num_random_nodes`
(config fields preserved for backward compat)
- Removed functions: `refresh_neighbour_pool`,
`refresh_neighbour_pool_if_required`, `num_connected_neighbours`,
`insert_neighbour`, `redial_neighbours_as_required`,
`fetch_neighbouring_peers`
- `replace_pool_peer` now has a single branch (random pool only)
- `log_status` reports a single peer pool instead of separate
neighbour/random pools
- Ticker loop drops the `refresh_neighbour_pool_if_required` call;
single `refresh_random_pool_if_required` handles everything

## Config & Builder (`config.rs`, `builder.rs`)

- Updated doc comments on `num_neighbouring_nodes` / `num_random_nodes`
to drop "neighbour" framing — they now describe pool partitioning, not
topology
- `DhtConnectivityConfig` comment updated accordingly

## Tests (`connectivity/test.rs`)

- `insert_neighbour` → `insert_into_pool` (tests unified
`insert_random_peer` cap behaviour)
- `added_neighbours` → `added_pool_peers`
- `replace_peer_when_peer_goes_offline` rewritten to not rely on
specific peer indices (random selection makes index-based assertions
fragile)
- `initialize` checks dial count rather than specific peer IDs

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Investigate removing all notions of distance to
peer</issue_title>
> <issue_description>I think w... (continued)

98 of 115 new or added lines in 9 files covered. (85.22%)

72 existing lines in 18 files now uncovered.

70646 of 114431 relevant lines covered (61.74%)

227169.5 hits per line

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

64.6
/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::Ordering,
25
    convert::{TryFrom, TryInto},
26
    fmt,
27
    hash::{Hash, Hasher},
28
    marker::PhantomData,
29
    ops::BitXor,
30
};
31

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

44
use crate::types::CommsPublicKey;
45

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

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

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

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

67
    /// 104-bit/13 byte as per RFC-0151
68
    pub const fn byte_size() -> usize {
124,705✔
69
        13
124,705✔
70
    }
124,705✔
71

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

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

UNCOV
89
    pub fn into_inner(self) -> NodeIdArray {
×
UNCOV
90
        self.0
×
91
    }
×
92

93
    pub fn short_str(&self) -> String {
342✔
94
        to_hex(self.0.get(..8).expect("Index should exist"))
342✔
95
    }
342✔
96
}
97

98
impl ByteArray for NodeId {
99
    /// Try and convert the given byte array to a NodeId. Any failures (incorrect array length,
100
    /// implementation-specific checks, etc) return a [ByteArrayError](enum.ByteArrayError.html).
101
    fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ByteArrayError> {
113,023✔
102
        bytes.try_into().map_err(|err| ByteArrayError::ConversionError {
113,023✔
103
            reason: format!("{err:?}"),
×
104
        })
×
105
    }
113,023✔
106

107
    /// Return the NodeId as a byte array
108
    fn as_bytes(&self) -> &[u8] {
53,428✔
109
        self.0.as_ref()
53,428✔
110
    }
53,428✔
111
}
112

113
impl ByteArray for Box<NodeId> {
114
    /// Try and convert the given byte array to a NodeId. Any failures (incorrect array length,
115
    /// implementation-specific checks, etc) return a [ByteArrayError](enum.ByteArrayError.html).
116
    fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ByteArrayError> {
×
117
        let node_id = NodeId::try_from(bytes).map_err(|err| ByteArrayError::ConversionError {
×
118
            reason: format!("{err:?}"),
×
119
        })?;
×
120
        Ok(Box::new(node_id))
×
121
    }
×
122

123
    /// Return the NodeId as a byte array
124
    fn as_bytes(&self) -> &[u8] {
×
125
        &self.as_ref().0
×
126
    }
×
127
}
128

129
impl PartialEq for NodeId {
130
    fn eq(&self, nid: &NodeId) -> bool {
63,249✔
131
        self.0 == nid.0
63,249✔
132
    }
63,249✔
133
}
134

135
impl PartialOrd<NodeId> for NodeId {
136
    fn partial_cmp(&self, other: &NodeId) -> Option<Ordering> {
267✔
137
        Some(self.cmp(other))
267✔
138
    }
267✔
139
}
140

141
impl Ord for NodeId {
142
    fn cmp(&self, other: &Self) -> Ordering {
268✔
143
        self.0.cmp(&other.0)
268✔
144
    }
268✔
145
}
146

147
impl BitXor for &NodeId {
148
    type Output = NodeIdArray;
149

UNCOV
150
    fn bitxor(self, rhs: Self) -> Self::Output {
×
UNCOV
151
        let mut xor = [0u8; NodeId::byte_size()];
×
152
        #[allow(clippy::needless_range_loop)]
UNCOV
153
        for i in 0..NodeId::byte_size() {
×
UNCOV
154
            *xor.get_mut(i).expect("Index should exist") =
×
UNCOV
155
                self.0.get(i).expect("Index should exist") ^ rhs.0.get(i).expect("Index should exist");
×
UNCOV
156
        }
×
UNCOV
157
        xor
×
UNCOV
158
    }
×
159
}
160

161
impl TryFrom<&[u8]> for NodeId {
162
    type Error = NodeIdError;
163

164
    /// Construct a node id from 32 bytes
165
    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
113,026✔
166
        if bytes.len() != NodeId::byte_size() {
113,026✔
167
            return Err(NodeIdError::IncorrectByteCount);
×
168
        }
113,026✔
169

170
        let mut buf = [0; NodeId::byte_size()];
113,026✔
171
        buf.copy_from_slice(bytes);
113,026✔
172
        Ok(NodeId(buf))
113,026✔
173
    }
113,026✔
174
}
175

176
impl From<CommsPublicKey> for NodeId {
177
    fn from(pk: CommsPublicKey) -> Self {
×
178
        NodeId::from_public_key(&pk)
×
179
    }
×
180
}
181

182
impl Hash for NodeId {
183
    /// Require the implementation of the Hash trait for Hashmaps
184
    fn hash<H: Hasher>(&self, state: &mut H) {
56,590✔
185
        self.0.hash(state);
56,590✔
186
    }
56,590✔
187
}
188

189
impl AsRef<[u8]> for NodeId {
190
    fn as_ref(&self) -> &[u8] {
×
191
        &self.0
×
192
    }
×
193
}
194

195
impl fmt::Display for NodeId {
196
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
9,903✔
197
        write!(f, "{}", to_hex(&self.0))
9,903✔
198
    }
9,903✔
199
}
200

201
impl fmt::Debug for NodeId {
202
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83✔
203
        write!(f, "NodeId({})", to_hex(&self.0))
83✔
204
    }
83✔
205
}
206

207
pub fn deserialize_node_id_from_hex<'de, D>(des: D) -> Result<NodeId, D::Error>
×
208
where D: Deserializer<'de> {
×
209
    struct KeyStringVisitor<K> {
210
        marker: PhantomData<K>,
211
    }
212

213
    impl de::Visitor<'_> for KeyStringVisitor<NodeId> {
214
        type Value = NodeId;
215

216
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
×
217
            formatter.write_str("a node id in hex format")
×
218
        }
×
219

220
        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
×
221
        where E: de::Error {
×
222
            NodeId::from_hex(v).map_err(E::custom)
×
223
        }
×
224
    }
225
    des.deserialize_str(KeyStringVisitor { marker: PhantomData })
×
226
}
×
227

228
#[cfg(test)]
229
mod test {
230
    #![allow(clippy::indexing_slicing)]
231
    use tari_crypto::keys::SecretKey;
232

233
    use super::*;
234
    use crate::types::CommsSecretKey;
235

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

240
        let result = format!("{node_id}");
1✔
241
        assert_eq!("901c6a70dcc5d87709d92a4d9f", result);
1✔
242
    }
1✔
243

244
    #[test]
245
    fn test_from_public_key() {
1✔
246
        let mut rng = rand::rngs::OsRng;
1✔
247
        let sk = CommsSecretKey::random(&mut rng);
1✔
248
        let pk = CommsPublicKey::from_secret_key(&sk);
1✔
249
        let node_id = NodeId::from_key(&pk);
1✔
250
        assert_ne!(node_id.0.to_vec(), NodeId::new().0.to_vec());
1✔
251
        // Ensure node id is different to original public key
252
        let mut pk_array: [u8; 32] = [0; 32];
1✔
253
        pk_array.copy_from_slice(pk.as_bytes());
1✔
254
        assert_ne!(node_id.0.to_vec(), pk_array.to_vec());
1✔
255
    }
1✔
256

257
    #[test]
258
    fn partial_eq() {
1✔
259
        let bytes = &[173, 218, 34, 188, 211, 173, 235, 82, 18, 159, 55, 47, 242][..];
1✔
260
        let nid1 = NodeId::try_from(bytes).unwrap();
1✔
261
        let nid2 = NodeId::try_from(bytes).unwrap();
1✔
262

263
        assert_eq!(nid1, nid2);
1✔
264
    }
1✔
265
}
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