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

tari-project / tari / 19227006544

10 Nov 2025 09:31AM UTC coverage: 51.608% (-7.9%) from 59.471%
19227006544

push

github

web-flow
feat: add deterministic transaction id (#7541)

Description
---
Added deterministic transaction IDs, which are an 8-byte (u64) hash
based on the transaction output hash in question and the wallet view
key.
- Any scanned or recovered wallet output will have the same transaction
ID across view or spend wallets.
- Sender wallets will be able to calculate the transaction ID for
receiver wallets if they need to, for that specific output.
- Sender wallets will use their change output as the determining output
hash for the transaction; this will result in the same transaction ID
being allocated upon wallet recovery. In the case of no change output,
the hash of the first ordered output will be used for the transaction
ID.
- For coin split transactions, the hash of the first ordered output will
be used for the transaction ID.

Fixed the issue with the Windows test build target link:
```
: error LNK2019: unresolved external symbol __imp_InitializeSecurityDescriptor referenced in function mdb_env_setup_locks
: error LNK2019: unresolved external symbol __imp_SetSecurityDescriptorDacl referenced in function mdb_env_setup_lock
```

Fixes #7485.

Motivation and Context
---
See #7485.

How Has This Been Tested?
---
Added unit tests.
Performed system-level testing.

What process can a PR reviewer use to test or verify this change?
---
Code review.
System-level testing.

<!-- 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 - Please specify

<!-- Does this include a breaking change? If so, include this line as a
footer -->
<!-- BREAKING CHANGE: Description what the user should do, e.g. delete a
database, resync the chain -->


<!-- This is an auto-generated comm... (continued)

52 of 1260 new or added lines in 14 files covered. (4.13%)

9213 existing lines in 93 files now uncovered.

59188 of 114687 relevant lines covered (51.61%)

8172.79 hits per line

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

90.1
/comms/core/src/peer_manager/peer_storage_sql.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::{cmp::min, time::Duration};
24

25
use log::*;
26
use multiaddr::Multiaddr;
27

28
use crate::{
29
    net_address::PeerAddressSource,
30
    peer_manager::{
31
        database::{PeerDatabaseSql, ThisPeerIdentity},
32
        peer::Peer,
33
        peer_id::PeerId,
34
        NodeDistance,
35
        NodeId,
36
        PeerFeatures,
37
        PeerFlags,
38
        PeerManagerError,
39
    },
40
    types::{CommsDatabase, CommsPublicKey, TransportProtocol},
41
};
42

43
const LOG_TARGET: &str = "comms::peer_manager::peer_storage_sql";
44
// The maximum number of peers to return in peer manager
45
const PEER_MANAGER_SYNC_PEERS: usize = 100;
46
// The maximum amount of time a peer can be inactive before being considered stale:
47
// ((5 days, 24h, 60m, 60s)/2 = 2.5 days)
48
pub const STALE_PEER_THRESHOLD_DURATION: Duration = Duration::from_secs(5 * 24 * 60 * 60 / 2);
49

50
/// PeerStorageSql provides a mechanism to keep a datastore and a local copy of all peers in sync and allow fast
51
/// searches using the node_id, public key or net_address of a peer.
52
#[derive(Clone)]
53
pub struct PeerStorageSql {
54
    peer_db: PeerDatabaseSql,
55
}
56

57
impl PeerStorageSql {
58
    /// Constructs a new PeerStorageSql, with indexes populated from the given datastore
59
    pub fn new_indexed(database: PeerDatabaseSql) -> Result<PeerStorageSql, PeerManagerError> {
173✔
60
        trace!(
173✔
61
            target: LOG_TARGET,
×
62
            "Peer storage is initialized. {} total entries.",
×
63
            database.size(),
×
64
        );
65

66
        Ok(PeerStorageSql { peer_db: database })
173✔
67
    }
173✔
68

69
    /// Get this peer's identity
70
    pub fn this_peer_identity(&self) -> ThisPeerIdentity {
1,517✔
71
        self.peer_db.this_peer_identity()
1,517✔
72
    }
1,517✔
73

74
    /// Get the size of the database
75
    pub fn count(&self) -> usize {
5,111✔
76
        self.peer_db.size()
5,111✔
77
    }
5,111✔
78

79
    /// Adds or updates a peer and sets the last connection as successful.
80
    /// If the peer is marked as offline, it will be unmarked.
81
    pub fn add_or_update_peer(&self, peer: Peer) -> Result<PeerId, PeerManagerError> {
3,639✔
82
        Ok(self.peer_db.add_or_update_peer(peer)?)
3,639✔
83
    }
3,639✔
84

85
    /// Adds a peer an online peer if the peer does not already exist. When a peer already
86
    /// exists, the stored version will be replaced with the newly provided peer.
87
    pub fn add_or_update_online_peer(
1✔
88
        &self,
1✔
89
        pubkey: &CommsPublicKey,
1✔
90
        node_id: &NodeId,
1✔
91
        addresses: &[Multiaddr],
1✔
92
        peer_features: &PeerFeatures,
1✔
93
        source: &PeerAddressSource,
1✔
94
    ) -> Result<Peer, PeerManagerError> {
1✔
95
        Ok(self
1✔
96
            .peer_db
1✔
97
            .add_or_update_online_peer(pubkey, node_id, addresses, peer_features, source)?)
1✔
98
    }
1✔
99

100
    /// The peer with the specified node id will be soft deleted (marked as deleted)
101
    pub fn soft_delete_peer(&self, node_id: &NodeId) -> Result<(), PeerManagerError> {
1✔
102
        self.peer_db.soft_delete_peer(node_id)?;
1✔
103
        Ok(())
1✔
104
    }
1✔
105

106
    /// Find the peer with the provided NodeID
107
    pub fn get_peer_by_node_id(&self, node_id: &NodeId) -> Result<Option<Peer>, PeerManagerError> {
367✔
108
        Ok(self.peer_db.get_peer_by_node_id(node_id)?)
367✔
109
    }
367✔
110

111
    /// Get all peers based on a list of their node_ids
112
    pub fn get_peers_by_node_ids(&self, node_ids: &[NodeId]) -> Result<Vec<Peer>, PeerManagerError> {
1✔
113
        Ok(self.peer_db.get_peers_by_node_ids(node_ids)?)
1✔
114
    }
1✔
115

116
    /// Get all peers based on a list of their node_ids
117
    pub fn get_peer_public_keys_by_node_ids(
×
118
        &self,
×
119
        node_ids: &[NodeId],
×
120
    ) -> Result<Vec<CommsPublicKey>, PeerManagerError> {
×
121
        Ok(self.peer_db.get_peer_public_keys_by_node_ids(node_ids)?)
×
122
    }
×
123

124
    /// Get all banned peers
125
    pub fn get_banned_peers(&self) -> Result<Vec<Peer>, PeerManagerError> {
×
126
        Ok(self.peer_db.get_banned_peers()?)
×
127
    }
×
128

129
    pub fn find_all_starts_with(&self, partial: &[u8]) -> Result<Vec<Peer>, PeerManagerError> {
×
130
        Ok(self.peer_db.find_all_peers_match_partial_key(partial)?)
×
131
    }
×
132

133
    /// Find the peer with the provided PublicKey
134
    pub fn find_by_public_key(&self, public_key: &CommsPublicKey) -> Result<Option<Peer>, PeerManagerError> {
99✔
135
        Ok(self.peer_db.get_peer_by_public_key(public_key)?)
99✔
136
    }
99✔
137

138
    /// Check if a peer exist using the specified public_key
139
    pub fn exists_public_key(&self, public_key: &CommsPublicKey) -> Result<bool, PeerManagerError> {
17✔
140
        if let Ok(val) = self.peer_db.peer_exists_by_public_key(public_key) {
17✔
141
            Ok(val.is_some())
17✔
142
        } else {
143
            Ok(false)
×
144
        }
145
    }
17✔
146

147
    /// Check if a peer exist using the specified node_id
148
    pub fn exists_node_id(&self, node_id: &NodeId) -> Result<bool, PeerManagerError> {
×
149
        if let Ok(val) = self.peer_db.peer_exists_by_node_id(node_id) {
×
150
            Ok(val.is_some())
×
151
        } else {
152
            Ok(false)
×
153
        }
154
    }
×
155

156
    /// Return the peer by corresponding to the provided NodeId if it is not banned
157
    pub fn direct_identity_node_id(&self, node_id: &NodeId) -> Result<Peer, PeerManagerError> {
27✔
158
        let peer = self
27✔
159
            .get_peer_by_node_id(node_id)?
27✔
160
            .ok_or(PeerManagerError::peer_not_found(node_id))?;
27✔
161

162
        if peer.is_banned() {
26✔
163
            Err(PeerManagerError::BannedPeer)
×
164
        } else {
165
            Ok(peer)
26✔
166
        }
167
    }
27✔
168

169
    /// Return the peer by corresponding to the provided public key if it is not banned
170
    pub fn direct_identity_public_key(&self, public_key: &CommsPublicKey) -> Result<Peer, PeerManagerError> {
20✔
171
        let peer = self
20✔
172
            .find_by_public_key(public_key)?
20✔
173
            .ok_or(PeerManagerError::peer_not_found(&NodeId::from_public_key(public_key)))?;
20✔
174

175
        if peer.is_banned() {
20✔
176
            Err(PeerManagerError::BannedPeer)
×
177
        } else {
178
            Ok(peer)
20✔
179
        }
180
    }
20✔
181

182
    /// Return all peers, optionally filtering on supplied feature
183
    pub fn all(&self, features: Option<PeerFeatures>) -> Result<Vec<Peer>, PeerManagerError> {
1✔
184
        Ok(self.peer_db.get_all_peers(features)?)
1✔
185
    }
1✔
186

187
    /// Return "good" peers for syncing
188
    /// Criteria:
189
    ///  - Peer is not banned
190
    ///  - Peer has been seen within a defined time span (within the threshold)
191
    ///  - Only returns a maximum number of syncable peers (corresponds with the max possible number of requestable
192
    ///    peers to sync)
193
    ///  - Uses 0 as max PEER_MANAGER_SYNC_PEERS
194
    ///  - Peers has an address that is reachable - with supported transport protocols
195
    pub fn discovery_syncing(
8✔
196
        &self,
8✔
197
        mut n: usize,
8✔
198
        excluded_peers: &[NodeId],
8✔
199
        features: Option<PeerFeatures>,
8✔
200
        external_addresses_only: bool,
8✔
201
    ) -> Result<Vec<Peer>, PeerManagerError> {
8✔
202
        if n == 0 {
8✔
203
            n = PEER_MANAGER_SYNC_PEERS;
×
204
        } else {
8✔
205
            n = min(n, PEER_MANAGER_SYNC_PEERS);
8✔
206
        }
8✔
207

208
        Ok(self.peer_db.get_n_random_active_peers(
8✔
209
            n,
8✔
210
            excluded_peers,
8✔
211
            features,
8✔
212
            None,
8✔
213
            Some(STALE_PEER_THRESHOLD_DURATION),
8✔
214
            external_addresses_only,
8✔
215
            &[],
8✔
216
        )?)
×
217
    }
8✔
218

219
    /// Compile a list of all known peers
220
    pub fn get_not_banned_or_deleted_peers(&self) -> Result<Vec<Peer>, PeerManagerError> {
1✔
221
        Ok(self
1✔
222
            .peer_db
1✔
223
            .get_n_not_banned_or_deleted_peers(PEER_MANAGER_SYNC_PEERS)?)
1✔
224
    }
1✔
225

226
    /// Get available dial candidates that are communication nodes, not banned, not deleted, reachable,
227
    /// optionally not failed, optionally at random, and not in the excluded node IDs list
228
    pub fn get_available_dial_candidates(
24✔
229
        &self,
24✔
230
        exclude_node_ids: &[NodeId],
24✔
231
        limit: Option<usize>,
24✔
232
        transport_protocols: &[TransportProtocol],
24✔
233
        exclude_failed: bool,
24✔
234
        randomize: bool,
24✔
235
    ) -> Result<Vec<Peer>, PeerManagerError> {
24✔
236
        Ok(self.peer_db.get_available_dial_candidates(
24✔
237
            exclude_node_ids,
24✔
238
            limit,
24✔
239
            transport_protocols,
24✔
240
            exclude_failed,
24✔
241
            randomize,
24✔
UNCOV
242
        )?)
×
243
    }
24✔
244

245
    /// Compile a list of closest `n` active peers
246
    pub fn closest_n_active_peers(
1,616✔
247
        &self,
1,616✔
248
        region_node_id: &NodeId,
1,616✔
249
        n: usize,
1,616✔
250
        excluded_peers: &[NodeId],
1,616✔
251
        features: Option<PeerFeatures>,
1,616✔
252
        peer_flags: Option<PeerFlags>,
1,616✔
253
        stale_peer_threshold: Option<Duration>,
1,616✔
254
        exclude_if_all_address_failed: bool,
1,616✔
255
        exclusion_distance: Option<NodeDistance>,
1,616✔
256
        external_addresses_only: bool,
1,616✔
257
        transport_protocols: &[TransportProtocol],
1,616✔
258
    ) -> Result<Vec<Peer>, PeerManagerError> {
1,616✔
259
        Ok(self.peer_db.get_closest_n_active_peers(
1,616✔
260
            region_node_id,
1,616✔
261
            n,
1,616✔
262
            excluded_peers,
1,616✔
263
            features,
1,616✔
264
            peer_flags,
1,616✔
265
            stale_peer_threshold,
1,616✔
266
            exclude_if_all_address_failed,
1,616✔
267
            exclusion_distance,
1,616✔
268
            external_addresses_only,
1,616✔
269
            transport_protocols,
1,616✔
UNCOV
270
        )?)
×
271
    }
1,616✔
272

273
    /// Get all seed peers
274
    pub fn get_seed_peers(&self) -> Result<Vec<Peer>, PeerManagerError> {
19✔
275
        let seed_peers = self.peer_db.get_seed_peers()?;
19✔
276
        trace!(
19✔
UNCOV
277
            target: LOG_TARGET,
×
UNCOV
278
            "Get seed peers: {:?}",
×
UNCOV
279
            seed_peers.iter().map(|p| p.node_id.short_str()).collect::<Vec<_>>(),
×
280
        );
281
        Ok(seed_peers)
19✔
282
    }
19✔
283

284
    /// Compile a random list of communication node peers of size _n_ that are not banned or offline and
285
    /// external addresses support protocols defined in the `transport_protocols` vector.
286
    pub fn random_peers(
72✔
287
        &self,
72✔
288
        n: usize,
72✔
289
        exclude_peers: &[NodeId],
72✔
290
        flags: Option<PeerFlags>,
72✔
291
        transport_protocols: &[TransportProtocol],
72✔
292
    ) -> Result<Vec<Peer>, PeerManagerError> {
72✔
293
        Ok(self
72✔
294
            .peer_db
72✔
295
            .get_n_random_peers(n, exclude_peers, flags, transport_protocols)?)
72✔
296
    }
72✔
297

298
    /// Get the closest `n` not failed, banned or deleted peers, ordered by their distance to the given node ID.
299
    pub fn get_closest_n_good_standing_peers(
2✔
300
        &self,
2✔
301
        n: usize,
2✔
302
        features: PeerFeatures,
2✔
303
    ) -> Result<Vec<Peer>, PeerManagerError> {
2✔
304
        Ok(self.peer_db.get_closest_n_good_standing_peers(n, features)?)
2✔
305
    }
2✔
306

307
    /// Check if a specific node_id is in the network region of the N nearest neighbours of the region specified by
308
    /// region_node_id. If there are less than N known peers, this will _always_ return true
309
    pub fn in_network_region(&self, node_id: &NodeId, n: usize) -> Result<bool, PeerManagerError> {
4✔
310
        let region_node_id = self.this_peer_identity().node_id;
4✔
311
        let region_node_distance = region_node_id.distance(node_id);
4✔
312
        let node_threshold = self.calc_region_threshold(n, PeerFeatures::COMMUNICATION_NODE)?;
4✔
313
        // Is node ID in the base node threshold?
314
        if region_node_distance <= node_threshold {
4✔
315
            return Ok(true);
3✔
316
        }
1✔
317
        let client_threshold = self.calc_region_threshold(n, PeerFeatures::COMMUNICATION_CLIENT)?; // Is node ID in the base client threshold?
1✔
318
        Ok(region_node_distance <= client_threshold)
1✔
319
    }
4✔
320

321
    /// Calculate the threshold for the region specified by region_node_id.
322
    pub fn calc_region_threshold(&self, n: usize, features: PeerFeatures) -> Result<NodeDistance, PeerManagerError> {
9✔
323
        let region_node_id = self.this_peer_identity().node_id;
9✔
324
        if n == 0 {
9✔
UNCOV
325
            return Ok(NodeDistance::max_distance());
×
326
        }
9✔
327

328
        let closest_peers = self.peer_db.get_closest_n_good_standing_peer_node_ids(n, features)?;
9✔
329
        let mut dists = Vec::new();
9✔
330
        for node_id in closest_peers {
42✔
331
            dists.push(region_node_id.distance(&node_id));
33✔
332
        }
33✔
333

334
        if dists.is_empty() {
9✔
UNCOV
335
            return Ok(NodeDistance::max_distance());
×
336
        }
9✔
337

338
        // If we have less than `n` matching peers in our threshold group, the threshold should be max
339
        if dists.len() < n {
9✔
340
            return Ok(NodeDistance::max_distance());
1✔
341
        }
8✔
342

343
        Ok(dists.pop().expect("dists cannot be empty at this point"))
8✔
344
    }
9✔
345

346
    /// Unban the peer
347
    pub fn unban_peer(&self, node_id: &NodeId) -> Result<(), PeerManagerError> {
×
348
        let _node_id = self.peer_db.reset_banned(node_id)?;
×
349
        Ok(())
×
350
    }
×
351

352
    /// Unban the peer
353
    pub fn unban_all_peers(&self) -> Result<usize, PeerManagerError> {
×
354
        let number_unbanned = self.peer_db.reset_all_banned()?;
×
355
        Ok(number_unbanned)
×
UNCOV
356
    }
×
357

358
    pub fn reset_offline_non_wallet_peers(&self) -> Result<usize, PeerManagerError> {
×
359
        let number_offline = self.peer_db.reset_offline_non_wallet_peers()?;
×
360
        Ok(number_offline)
×
361
    }
×
362

363
    /// Ban the peer for the given duration
364
    pub fn ban_peer(
×
365
        &self,
×
366
        public_key: &CommsPublicKey,
×
367
        duration: Duration,
×
368
        reason: String,
×
UNCOV
369
    ) -> Result<NodeId, PeerManagerError> {
×
UNCOV
370
        let node_id = NodeId::from_key(public_key);
×
UNCOV
371
        self.peer_db
×
UNCOV
372
            .set_banned(&node_id, duration, reason)?
×
UNCOV
373
            .ok_or(PeerManagerError::peer_not_found(&NodeId::from_public_key(public_key)))
×
UNCOV
374
    }
×
375

376
    /// Ban the peer for the given duration
377
    pub fn ban_peer_by_node_id(
5✔
378
        &self,
5✔
379
        node_id: &NodeId,
5✔
380
        duration: Duration,
5✔
381
        reason: String,
5✔
382
    ) -> Result<NodeId, PeerManagerError> {
5✔
383
        self.peer_db
5✔
384
            .set_banned(node_id, duration, reason)?
5✔
385
            .ok_or(PeerManagerError::peer_not_found(node_id))
5✔
386
    }
5✔
387

388
    pub fn is_peer_banned(&self, node_id: &NodeId) -> Result<bool, PeerManagerError> {
143✔
389
        let peer = self
143✔
390
            .get_peer_by_node_id(node_id)?
143✔
391
            .ok_or(PeerManagerError::peer_not_found(node_id))?;
143✔
392
        Ok(peer.is_banned())
143✔
393
    }
143✔
394

395
    /// This will store metadata inside of the metadata field in the peer provided by the nodeID.
396
    /// It will return None if the value was empty and the old value if the value was updated
397
    pub fn set_peer_metadata(
140✔
398
        &self,
140✔
399
        node_id: &NodeId,
140✔
400
        key: u8,
140✔
401
        data: Vec<u8>,
140✔
402
    ) -> Result<Option<Vec<u8>>, PeerManagerError> {
140✔
403
        Ok(self.peer_db.set_metadata(node_id, key, data)?)
140✔
404
    }
140✔
405
}
406

407
#[allow(clippy::from_over_into)]
408
impl Into<CommsDatabase> for PeerStorageSql {
UNCOV
409
    fn into(self) -> CommsDatabase {
×
UNCOV
410
        self.peer_db
×
UNCOV
411
    }
×
412
}
413

414
#[cfg(test)]
415
mod test {
416
    #![allow(clippy::indexing_slicing)]
417
    use std::{borrow::BorrowMut, iter::repeat_with};
418

419
    use chrono::{DateTime, Utc};
420
    use multiaddr::Multiaddr;
421
    use rand::Rng;
422
    use tari_common_sqlite::connection::DbConnection;
423

424
    use super::*;
425
    use crate::{
426
        net_address::{MultiaddrWithStats, MultiaddressesWithStats, PeerAddressSource},
427
        peer_manager::{create_test_peer_add_internal_addresses, database::MIGRATIONS, peer::PeerFlags},
428
    };
429

430
    fn get_peer_db_sql_test_db() -> Result<PeerDatabaseSql, PeerManagerError> {
6✔
431
        let db_connection = DbConnection::connect_temp_file_and_migrate(MIGRATIONS).unwrap();
6✔
432
        Ok(PeerDatabaseSql::new(
6✔
433
            db_connection,
6✔
434
            &create_test_peer(PeerFeatures::COMMUNICATION_NODE, false),
6✔
UNCOV
435
        )?)
×
436
    }
6✔
437

438
    fn get_peer_storage_sql_test_db() -> Result<PeerStorageSql, PeerManagerError> {
5✔
439
        PeerStorageSql::new_indexed(get_peer_db_sql_test_db()?)
5✔
440
    }
5✔
441

442
    #[test]
443
    fn test_restore() {
1✔
444
        // Create Peers
445
        let mut rng = rand::rngs::OsRng;
1✔
446
        let (_sk, pk) = CommsPublicKey::random_keypair(&mut rng);
1✔
447
        let node_id = NodeId::from_key(&pk);
1✔
448
        let net_address1 = "/ip4/1.2.3.4/tcp/8000".parse::<Multiaddr>().unwrap();
1✔
449
        let net_address2 = "/ip4/5.6.7.8/tcp/8000".parse::<Multiaddr>().unwrap();
1✔
450
        let net_address3 = "/ip4/5.6.7.8/tcp/7000".parse::<Multiaddr>().unwrap();
1✔
451
        let mut net_addresses =
1✔
452
            MultiaddressesWithStats::from_addresses_with_source(vec![net_address1], &PeerAddressSource::Config);
1✔
453
        net_addresses.add_or_update_addresses(&[net_address2], &PeerAddressSource::Config);
1✔
454
        net_addresses.add_or_update_addresses(&[net_address3], &PeerAddressSource::Config);
1✔
455
        let peer1 = Peer::new(
1✔
456
            pk,
1✔
457
            node_id,
1✔
458
            net_addresses,
1✔
459
            PeerFlags::default(),
1✔
460
            PeerFeatures::empty(),
1✔
461
            Default::default(),
1✔
462
            Default::default(),
1✔
463
        );
464

465
        let (_sk, pk) = CommsPublicKey::random_keypair(&mut rng);
1✔
466
        let node_id = NodeId::from_key(&pk);
1✔
467
        let net_address4 = "/ip4/9.10.11.12/tcp/7000".parse::<Multiaddr>().unwrap();
1✔
468
        let net_addresses =
1✔
469
            MultiaddressesWithStats::from_addresses_with_source(vec![net_address4], &PeerAddressSource::Config);
1✔
470
        let peer2: Peer = Peer::new(
1✔
471
            pk,
1✔
472
            node_id,
1✔
473
            net_addresses,
1✔
474
            PeerFlags::default(),
1✔
475
            PeerFeatures::empty(),
1✔
476
            Default::default(),
1✔
477
            Default::default(),
1✔
478
        );
479

480
        let (_sk, pk) = CommsPublicKey::random_keypair(&mut rng);
1✔
481
        let node_id = NodeId::from_key(&pk);
1✔
482
        let net_address5 = "/ip4/13.14.15.16/tcp/6000".parse::<Multiaddr>().unwrap();
1✔
483
        let net_address6 = "/ip4/17.18.19.20/tcp/8000".parse::<Multiaddr>().unwrap();
1✔
484
        let mut net_addresses =
1✔
485
            MultiaddressesWithStats::from_addresses_with_source(vec![net_address5], &PeerAddressSource::Config);
1✔
486
        net_addresses.add_or_update_addresses(&[net_address6], &PeerAddressSource::Config);
1✔
487
        let peer3 = Peer::new(
1✔
488
            pk,
1✔
489
            node_id,
1✔
490
            net_addresses,
1✔
491
            PeerFlags::default(),
1✔
492
            PeerFeatures::empty(),
1✔
493
            Default::default(),
1✔
494
            Default::default(),
1✔
495
        );
496

497
        // Create new datastore with a peer database
498
        let mut db = Some(get_peer_db_sql_test_db().unwrap());
1✔
499
        {
500
            let peer_storage = db.take().unwrap();
1✔
501

502
            // Test adding and searching for peers
503
            assert!(peer_storage.add_or_update_peer(peer1.clone()).is_ok());
1✔
504
            assert!(peer_storage.add_or_update_peer(peer2.clone()).is_ok());
1✔
505
            assert!(peer_storage.add_or_update_peer(peer3.clone()).is_ok());
1✔
506

507
            assert_eq!(peer_storage.size(), 3);
1✔
508
            assert!(peer_storage.get_peer_by_public_key(&peer1.public_key).is_ok());
1✔
509
            assert!(peer_storage.get_peer_by_public_key(&peer2.public_key).is_ok());
1✔
510
            assert!(peer_storage.get_peer_by_public_key(&peer3.public_key).is_ok());
1✔
511
            db = Some(peer_storage);
1✔
512
        }
513
        // Restore from existing database
514
        let peer_storage = PeerStorageSql::new_indexed(db.take().unwrap()).unwrap();
1✔
515

516
        assert_eq!(peer_storage.peer_db.size(), 3);
1✔
517
        assert!(peer_storage.find_by_public_key(&peer1.public_key).is_ok());
1✔
518
        assert!(peer_storage.find_by_public_key(&peer2.public_key).is_ok());
1✔
519
        assert!(peer_storage.find_by_public_key(&peer3.public_key).is_ok());
1✔
520
    }
1✔
521

522
    #[allow(clippy::too_many_lines)]
523
    #[test]
524
    fn test_add_delete_find_peer() {
1✔
525
        let peer_storage = get_peer_storage_sql_test_db().unwrap();
1✔
526

527
        // Create Peers
528
        let mut rng = rand::rngs::OsRng;
1✔
529
        let (_sk, pk) = CommsPublicKey::random_keypair(&mut rng);
1✔
530
        let node_id = NodeId::from_key(&pk);
1✔
531
        let net_address1 = "/ip4/1.2.3.4/tcp/8000".parse::<Multiaddr>().unwrap();
1✔
532
        let net_address2 = "/ip4/5.6.7.8/tcp/8000".parse::<Multiaddr>().unwrap();
1✔
533
        let net_address3 = "/ip4/5.6.7.8/tcp/7000".parse::<Multiaddr>().unwrap();
1✔
534
        let mut net_addresses =
1✔
535
            MultiaddressesWithStats::from_addresses_with_source(vec![net_address1], &PeerAddressSource::Config);
1✔
536
        net_addresses.add_or_update_addresses(&[net_address2], &PeerAddressSource::Config);
1✔
537
        net_addresses.add_or_update_addresses(&[net_address3], &PeerAddressSource::Config);
1✔
538
        let peer1 = Peer::new(
1✔
539
            pk,
1✔
540
            node_id,
1✔
541
            net_addresses,
1✔
542
            PeerFlags::default(),
1✔
543
            PeerFeatures::empty(),
1✔
544
            Default::default(),
1✔
545
            Default::default(),
1✔
546
        );
547

548
        let (_sk, pk) = CommsPublicKey::random_keypair(&mut rng);
1✔
549
        let node_id = NodeId::from_key(&pk);
1✔
550
        let net_address4 = "/ip4/9.10.11.12/tcp/7000".parse::<Multiaddr>().unwrap();
1✔
551
        let net_addresses =
1✔
552
            MultiaddressesWithStats::from_addresses_with_source(vec![net_address4], &PeerAddressSource::Config);
1✔
553
        let peer2: Peer = Peer::new(
1✔
554
            pk,
1✔
555
            node_id,
1✔
556
            net_addresses,
1✔
557
            PeerFlags::default(),
1✔
558
            PeerFeatures::empty(),
1✔
559
            Default::default(),
1✔
560
            Default::default(),
1✔
561
        );
562

563
        let (_sk, pk) = CommsPublicKey::random_keypair(&mut rng);
1✔
564
        let node_id = NodeId::from_key(&pk);
1✔
565
        let net_address5 = "/ip4/13.14.15.16/tcp/6000".parse::<Multiaddr>().unwrap();
1✔
566
        let net_address6 = "/ip4/17.18.19.20/tcp/8000".parse::<Multiaddr>().unwrap();
1✔
567
        let mut net_addresses =
1✔
568
            MultiaddressesWithStats::from_addresses_with_source(vec![net_address5], &PeerAddressSource::Config);
1✔
569
        net_addresses.add_or_update_addresses(&[net_address6], &PeerAddressSource::Config);
1✔
570
        let peer3 = Peer::new(
1✔
571
            pk,
1✔
572
            node_id,
1✔
573
            net_addresses,
1✔
574
            PeerFlags::default(),
1✔
575
            PeerFeatures::empty(),
1✔
576
            Default::default(),
1✔
577
            Default::default(),
1✔
578
        );
579
        // Test adding and searching for peers
580
        peer_storage.add_or_update_peer(peer1.clone()).unwrap(); // assert!(peer_storage.add_or_update_peer(peer1.clone()).is_ok());
1✔
581
        assert!(peer_storage.add_or_update_peer(peer2.clone()).is_ok());
1✔
582
        assert!(peer_storage.add_or_update_peer(peer3.clone()).is_ok());
1✔
583

584
        assert_eq!(peer_storage.peer_db.size(), 3);
1✔
585

586
        assert_eq!(
1✔
587
            peer_storage
1✔
588
                .find_by_public_key(&peer1.public_key)
1✔
589
                .unwrap()
1✔
590
                .unwrap()
1✔
591
                .public_key,
592
            peer1.public_key
593
        );
594
        assert_eq!(
1✔
595
            peer_storage
1✔
596
                .find_by_public_key(&peer2.public_key)
1✔
597
                .unwrap()
1✔
598
                .unwrap()
1✔
599
                .public_key,
600
            peer2.public_key
601
        );
602
        assert_eq!(
1✔
603
            peer_storage
1✔
604
                .find_by_public_key(&peer3.public_key)
1✔
605
                .unwrap()
1✔
606
                .unwrap()
1✔
607
                .public_key,
608
            peer3.public_key
609
        );
610

611
        assert_eq!(
1✔
612
            peer_storage
1✔
613
                .get_peer_by_node_id(&peer1.node_id)
1✔
614
                .unwrap()
1✔
615
                .unwrap()
1✔
616
                .node_id,
617
            peer1.node_id
618
        );
619
        assert_eq!(
1✔
620
            peer_storage
1✔
621
                .get_peer_by_node_id(&peer2.node_id)
1✔
622
                .unwrap()
1✔
623
                .unwrap()
1✔
624
                .node_id,
625
            peer2.node_id
626
        );
627
        assert_eq!(
1✔
628
            peer_storage
1✔
629
                .get_peer_by_node_id(&peer3.node_id)
1✔
630
                .unwrap()
1✔
631
                .unwrap()
1✔
632
                .node_id,
633
            peer3.node_id
634
        );
635

636
        peer_storage.find_by_public_key(&peer1.public_key).unwrap().unwrap();
1✔
637
        peer_storage.find_by_public_key(&peer2.public_key).unwrap().unwrap();
1✔
638
        peer_storage.find_by_public_key(&peer3.public_key).unwrap().unwrap();
1✔
639

640
        // Test delete of border case peer
641
        assert!(peer_storage.soft_delete_peer(&peer3.node_id).is_ok());
1✔
642

643
        // It is a logical delete, so there should still be 3 peers in the db
644
        assert_eq!(peer_storage.peer_db.size(), 3);
1✔
645

646
        assert_eq!(
1✔
647
            peer_storage
1✔
648
                .find_by_public_key(&peer1.public_key)
1✔
649
                .unwrap()
1✔
650
                .unwrap()
1✔
651
                .public_key,
652
            peer1.public_key
653
        );
654
        assert_eq!(
1✔
655
            peer_storage
1✔
656
                .find_by_public_key(&peer2.public_key)
1✔
657
                .unwrap()
1✔
658
                .unwrap()
1✔
659
                .public_key,
660
            peer2.public_key
661
        );
662
        assert!(peer_storage
1✔
663
            .find_by_public_key(&peer3.public_key)
1✔
664
            .unwrap()
1✔
665
            .unwrap()
1✔
666
            .deleted_at
1✔
667
            .is_some());
1✔
668

669
        assert_eq!(
1✔
670
            peer_storage
1✔
671
                .get_peer_by_node_id(&peer1.node_id)
1✔
672
                .unwrap()
1✔
673
                .unwrap()
1✔
674
                .node_id,
675
            peer1.node_id
676
        );
677
        assert_eq!(
1✔
678
            peer_storage
1✔
679
                .get_peer_by_node_id(&peer2.node_id)
1✔
680
                .unwrap()
1✔
681
                .unwrap()
1✔
682
                .node_id,
683
            peer2.node_id
684
        );
685
        assert!(peer_storage
1✔
686
            .get_peer_by_node_id(&peer3.node_id)
1✔
687
            .unwrap()
1✔
688
            .unwrap()
1✔
689
            .deleted_at
1✔
690
            .is_some());
1✔
691
    }
1✔
692

693
    fn create_test_peer(features: PeerFeatures, ban: bool) -> Peer {
30✔
694
        let mut rng = rand::rngs::OsRng;
30✔
695

696
        let (_sk, pk) = CommsPublicKey::random_keypair(&mut rng);
30✔
697
        let node_id = NodeId::from_key(&pk);
30✔
698

699
        let mut net_addresses = MultiaddressesWithStats::from_addresses_with_source(vec![], &PeerAddressSource::Config);
30✔
700

701
        // Create 1 to 4 random addresses
702
        for _i in 1..=rand::thread_rng().gen_range(1..4) {
61✔
703
            let n = [
61✔
704
                rand::thread_rng().gen_range(1..255),
61✔
705
                rand::thread_rng().gen_range(1..255),
61✔
706
                rand::thread_rng().gen_range(1..255),
61✔
707
                rand::thread_rng().gen_range(1..255),
61✔
708
                rand::thread_rng().gen_range(5000..9000),
61✔
709
            ];
61✔
710
            let net_address = format!("/ip4/{}.{}.{}.{}/tcp/{}", n[0], n[1], n[2], n[3], n[4])
61✔
711
                .parse::<Multiaddr>()
61✔
712
                .unwrap();
61✔
713
            net_addresses.add_or_update_addresses(&[net_address], &PeerAddressSource::Config);
61✔
714
        }
61✔
715

716
        let mut peer = Peer::new(
30✔
717
            pk,
30✔
718
            node_id,
30✔
719
            net_addresses,
30✔
720
            PeerFlags::default(),
30✔
721
            features,
30✔
722
            Default::default(),
30✔
723
            Default::default(),
30✔
724
        );
725
        if ban {
30✔
726
            peer.ban_for(Duration::from_secs(600), "".to_string());
1✔
727
        }
29✔
728
        peer
30✔
729
    }
30✔
730

731
    #[test]
732
    fn test_in_network_region() {
1✔
733
        let peer_storage = get_peer_storage_sql_test_db().unwrap();
1✔
734

735
        let mut nodes = repeat_with(|| create_test_peer(PeerFeatures::COMMUNICATION_NODE, false))
5✔
736
            .take(5)
1✔
737
            .chain(repeat_with(|| create_test_peer(PeerFeatures::COMMUNICATION_CLIENT, false)).take(4))
4✔
738
            .collect::<Vec<_>>();
1✔
739

740
        for p in &nodes {
10✔
741
            peer_storage.add_or_update_peer(p.clone()).unwrap();
9✔
742
        }
9✔
743

744
        let main_peer_node_id = peer_storage.this_peer_identity().node_id;
1✔
745

746
        nodes.sort_by(|a, b| {
27✔
747
            a.node_id
27✔
748
                .distance(&main_peer_node_id)
27✔
749
                .cmp(&b.node_id.distance(&main_peer_node_id))
27✔
750
        });
27✔
751

752
        let db_nodes = peer_storage.peer_db.get_all_peers(None).unwrap();
1✔
753
        assert_eq!(db_nodes.len(), 9);
1✔
754

755
        let close_node = &nodes.first().unwrap().node_id;
1✔
756
        let far_node = &nodes.last().unwrap().node_id;
1✔
757

758
        let is_in_region = peer_storage.in_network_region(&main_peer_node_id, 1).unwrap();
1✔
759
        assert!(is_in_region);
1✔
760

761
        let is_in_region = peer_storage.in_network_region(close_node, 1).unwrap();
1✔
762
        assert!(is_in_region);
1✔
763

764
        let is_in_region = peer_storage.in_network_region(far_node, 9).unwrap();
1✔
765
        assert!(is_in_region);
1✔
766

767
        let is_in_region = peer_storage.in_network_region(far_node, 3).unwrap();
1✔
768
        assert!(!is_in_region);
1✔
769
    }
1✔
770

771
    #[test]
772
    fn get_just_seeds() {
1✔
773
        let peer_storage = get_peer_storage_sql_test_db().unwrap();
1✔
774

775
        let seeds = repeat_with(|| {
5✔
776
            let mut peer = create_test_peer(PeerFeatures::COMMUNICATION_NODE, false);
5✔
777
            peer.add_flags(PeerFlags::SEED);
5✔
778
            peer
5✔
779
        })
5✔
780
        .take(5)
1✔
781
        .collect::<Vec<_>>();
1✔
782

783
        for p in &seeds {
6✔
784
            peer_storage.add_or_update_peer(p.clone()).unwrap();
5✔
785
        }
5✔
786

787
        let nodes = repeat_with(|| create_test_peer(PeerFeatures::COMMUNICATION_NODE, false))
5✔
788
            .take(5)
1✔
789
            .collect::<Vec<_>>();
1✔
790

791
        for p in &nodes {
6✔
792
            peer_storage.add_or_update_peer(p.clone()).unwrap();
5✔
793
        }
5✔
794
        let retrieved_seeds = peer_storage.get_seed_peers().unwrap();
1✔
795
        assert_eq!(retrieved_seeds.len(), seeds.len());
1✔
796
        for seed in seeds {
6✔
797
            assert!(retrieved_seeds.iter().any(|p| p.node_id == seed.node_id));
15✔
798
        }
799
    }
1✔
800

801
    #[test]
802
    fn discovery_syncing_returns_correct_peers() {
1✔
803
        let peer_storage = get_peer_storage_sql_test_db().unwrap();
1✔
804

805
        // Threshold duration + a minute
806
        #[allow(clippy::cast_possible_wrap)] // Won't wrap around, numbers are static
807
        let above_the_threshold = Utc::now().timestamp() - (STALE_PEER_THRESHOLD_DURATION.as_secs() + 60) as i64;
1✔
808

809
        let never_seen_peer = create_test_peer(PeerFeatures::COMMUNICATION_NODE, false);
1✔
810
        let banned_peer = create_test_peer(PeerFeatures::COMMUNICATION_NODE, true);
1✔
811

812
        let mut not_active_peer = create_test_peer(PeerFeatures::COMMUNICATION_NODE, false);
1✔
813
        let address = not_active_peer.addresses.best().unwrap();
1✔
814
        let mut address = MultiaddrWithStats::new(address.address().clone(), PeerAddressSource::Config);
1✔
815
        address.mark_last_attempted(DateTime::from_timestamp(above_the_threshold, 0).unwrap().naive_utc());
1✔
816
        not_active_peer
1✔
817
            .addresses
1✔
818
            .merge(&MultiaddressesWithStats::from(vec![address]));
1✔
819

820
        let mut good_peer = create_test_peer(PeerFeatures::COMMUNICATION_NODE, false);
1✔
821
        let good_addresses = good_peer.addresses.borrow_mut();
1✔
822
        let good_address = good_addresses.addresses()[0].address().clone();
1✔
823
        good_addresses.mark_last_seen_now(&good_address);
1✔
824

825
        let mut good_seed = create_test_peer(PeerFeatures::COMMUNICATION_NODE, false);
1✔
826
        good_seed.flags = PeerFlags::SEED;
1✔
827
        let good_addresses = good_seed.addresses.borrow_mut();
1✔
828
        let good_address = good_addresses.addresses()[0].address().clone();
1✔
829
        good_addresses.mark_last_seen_now(&good_address);
1✔
830

831
        assert!(peer_storage.add_or_update_peer(never_seen_peer).is_ok());
1✔
832
        assert!(peer_storage.add_or_update_peer(not_active_peer).is_ok());
1✔
833
        assert!(peer_storage.add_or_update_peer(banned_peer).is_ok());
1✔
834
        assert!(peer_storage.add_or_update_peer(good_peer).is_ok());
1✔
835
        assert!(peer_storage.add_or_update_peer(good_seed.clone()).is_ok());
1✔
836

837
        assert_eq!(peer_storage.all(None).unwrap().len(), 5);
1✔
838
        assert_eq!(
1✔
839
            peer_storage
1✔
840
                .discovery_syncing(100, &[good_seed.node_id], Some(PeerFeatures::COMMUNICATION_NODE), false,)
1✔
841
                .unwrap()
1✔
842
                .len(),
1✔
843
            1
844
        );
845
        assert_eq!(
1✔
846
            peer_storage
1✔
847
                .discovery_syncing(100, &[], Some(PeerFeatures::COMMUNICATION_NODE), false)
1✔
848
                .unwrap()
1✔
849
                .len(),
1✔
850
            2
851
        );
852
    }
1✔
853

854
    #[test]
855
    fn discovery_syncing_peers_with_external_addresses_only() {
1✔
856
        let peer_storage = get_peer_storage_sql_test_db().unwrap();
1✔
857
        let nodes = repeat_with(|| create_test_peer_add_internal_addresses(false, PeerFeatures::COMMUNICATION_NODE))
5✔
858
            .take(5)
1✔
859
            .collect::<Vec<_>>();
1✔
860
        let wallets =
1✔
861
            repeat_with(|| create_test_peer_add_internal_addresses(false, PeerFeatures::COMMUNICATION_CLIENT))
5✔
862
                .take(5)
1✔
863
                .collect::<Vec<_>>();
1✔
864
        for peer in nodes.iter().chain(wallets.iter()) {
10✔
865
            peer_storage.add_or_update_peer(peer.clone()).unwrap();
10✔
866
        }
10✔
867

868
        // Assert that peers have internal and external addresses
869
        let nodes_all_addresses = peer_storage
1✔
870
            .discovery_syncing(100, &[], Some(PeerFeatures::COMMUNICATION_NODE), false)
1✔
871
            .unwrap();
1✔
872
        assert!(nodes_all_addresses
1✔
873
            .iter()
1✔
874
            .all(|p| { p.addresses.addresses().iter().any(|addr| addr.is_external()) }));
5✔
875
        assert!(nodes_all_addresses
1✔
876
            .iter()
1✔
877
            .all(|p| { p.addresses.addresses().iter().any(|addr| !addr.is_external()) }));
14✔
878

879
        // Assert that peers have external addresses only
880
        let nodes_external_addresses_only = peer_storage
1✔
881
            .discovery_syncing(100, &[], Some(PeerFeatures::COMMUNICATION_NODE), true)
1✔
882
            .unwrap();
1✔
883
        assert!(nodes_external_addresses_only
1✔
884
            .iter()
1✔
885
            .all(|p| { p.addresses.addresses().iter().all(|addr| addr.is_external()) }));
9✔
886
    }
1✔
887
}
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