Skip to content

[codex] Implement transport peer selection modes#7853

Closed
CharlieLZ wants to merge 1 commit into
tari-project:developmentfrom
CharlieLZ:codex/7830-transport-types
Closed

[codex] Implement transport peer selection modes#7853
CharlieLZ wants to merge 1 commit into
tari-project:developmentfrom
CharlieLZ:codex/7830-transport-types

Conversation

@CharlieLZ
Copy link
Copy Markdown

@CharlieLZ CharlieLZ commented May 25, 2026

Fixes #7830

Summary

  • Add the requested public peer-selection transport modes: Tor, Tcp, TorTcp, and TcpTor, with TcpTor as the new default.
  • Propagate the configured transport protocol order into both peer candidate selection and dial address selection.
  • Sort and filter peer candidates and dial addresses by the configured protocol preference, while retaining the existing Memory and Socks5 transport variants for compatibility.
  • Add unit coverage for protocol ordering/filtering and a cucumber feature covering all four requested modes.

Validation

  • cargo test -p tari_p2p --no-default-features
  • cargo test -p tari_comms transport_protocol_preference --no-default-features
  • cargo test -p tari_integration_tests --test cucumber --no-run
  • git diff --check

Notes

  • I also attempted the full cargo test -p tari_comms --no-default-features suite locally. The transport-focused tests passed, but the full package run hit local baseline failures in temp SQLite/DNS-related tests outside this change.
  • cargo fmt --check is not usable in my local stable toolchain for this repo because rustfmt.toml enables nightly-only rustfmt options and stable reports broad baseline diffs. No source files were modified by that check.

@github-actions
Copy link
Copy Markdown

⚠️ This PR contains unsigned commits. To get your PR merged, please sign those commits (git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}) and force push them to this branch (git push --force-with-lease).

If you're new to commit signing, there are different ways to set it up:

Sign commits with gpg

Follow the steps below to set up commit signing with gpg:

  1. Generate a GPG key
  2. Add the GPG key to your GitHub account
  3. Configure git to use your GPG key for commit signing
Sign commits with ssh-agent

Follow the steps below to set up commit signing with ssh-agent:

  1. Generate an SSH key and add it to ssh-agent
  2. Add the SSH key to your GitHub account
  3. Configure git to use your SSH key for commit signing
Sign commits with 1Password

You can also sign commits using 1Password, which lets you sign commits with biometrics without the signing key leaving the local 1Password process.

Learn how to use 1Password to sign your commits.

Watch the demo

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces new transport types (TorTcp and TcpTor) to support preferred peer selection and dial address ordering based on transport protocols. The reviewer provided valuable feedback on optimizing address filtering to avoid cloning metadata, using sort_by_cached_key to prevent redundant string allocations during peer sorting, and addressing a potential non-determinism issue in SQLite when ordering distinct queries.

Comment on lines +766 to +778
let addresses = peer
.addresses
.clone()
.into_vec()
.into_iter()
.filter(|address| {
let protocol = TransportProtocol::from(address);
!excluded_dial_addresses
.iter()
.any(|excluded| excluded.contains(address))
&& preferred_transport_protocols.contains(&protocol)
})
.collect::<Vec<_>>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Cloning the entire MultiaddressesWithStats struct and converting it to a vector is inefficient because it clones all address statistics and metadata only to discard them. Instead, you can iterate over the addresses slice directly using .addresses() and clone only the Multiaddr values that match the filter criteria.

        let addresses = peer
            .addresses
            .addresses()
            .iter()
            .filter_map(|address| {
                let addr = address.address();
                let protocol = TransportProtocol::from(addr);
                if !excluded_dial_addresses.iter().any(|excluded| excluded.contains(addr))
                    && preferred_transport_protocols.contains(&protocol)
                {
                    Some(addr.clone())
                } else {
                    None
                }
            })
            .collect::<Vec<_>>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current patch already iterates peer.addresses.addresses() and only clones the Multiaddr values that pass the filter. It does not clone the whole MultiaddressesWithStats value.

Comment on lines +1337 to +1350
fn order_peers_by_node_id_order(peers: &mut [Peer], node_ids: &[String]) {
let node_id_positions = node_ids
.iter()
.enumerate()
.map(|(index, node_id)| (node_id.clone(), index))
.collect::<HashMap<_, _>>();

peers.sort_by_key(|peer| {
node_id_positions
.get(&peer.node_id.to_hex())
.copied()
.unwrap_or(usize::MAX)
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using sort_by_key with peer.node_id.to_hex() will perform a string allocation and hex conversion O(n log n) times during sorting. Using sort_by_cached_key instead ensures that the key function is evaluated exactly once per element, reducing the number of allocations to O(n).

Suggested change
fn order_peers_by_node_id_order(peers: &mut [Peer], node_ids: &[String]) {
let node_id_positions = node_ids
.iter()
.enumerate()
.map(|(index, node_id)| (node_id.clone(), index))
.collect::<HashMap<_, _>>();
peers.sort_by_key(|peer| {
node_id_positions
.get(&peer.node_id.to_hex())
.copied()
.unwrap_or(usize::MAX)
});
}
fn order_peers_by_node_id_order(peers: &mut [Peer], node_ids: &[String]) {
let node_id_positions = node_ids
.iter()
.enumerate()
.map(|(index, node_id)| (node_id.clone(), index))
.collect::<HashMap<_, _>>();
peers.sort_by_cached_key(|peer| {
node_id_positions
.get(&peer.node_id.to_hex())
.copied()
.unwrap_or(usize::MAX)
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current patch already uses sort_by_cached_key here, so peer.node_id.to_hex() is evaluated once per peer during sorting.

Comment on lines +1317 to +1335
fn build_addr_preference_order_sql(transport_protocols: &[TransportProtocol]) -> Option<String> {
if transport_protocols.is_empty() {
return None;
}

let conditions = transport_protocols
.iter()
.enumerate()
.map(|(index, protocol)| {
format!(
"WHEN multi_addresses.address LIKE '{}%' THEN {}",
sql_escape(protocol.get_prefix()),
index
)
})
.collect::<Vec<_>>();

Some(format!("CASE {} ELSE {} END", conditions.join(" "), transport_protocols.len()))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When a peer has multiple addresses (e.g., both Onion and IPv4), the joined query produces multiple rows for that peer. Because get_available_dial_candidates uses DISTINCT peers.node_id, SQLite collapses these rows. Ordering by multi_addresses.address (via order_sql) on a DISTINCT query is non-deterministic in SQLite because the value used for ordering the collapsed row is arbitrary. This can lead to incorrect candidates being selected when LIMIT is applied.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This query does not use DISTINCT; it selects ordered address rows, over-fetches by MAX_MULTIADDRESSES_PER_PEER, deduplicates node IDs in Rust while preserving row order, then fetches the peers and reapplies transport preference in memory. That avoids SQLite choosing an arbitrary address value for a collapsed distinct peer row.

@CharlieLZ CharlieLZ force-pushed the codex/7830-transport-types branch from dd9e230 to e68f552 Compare May 25, 2026 16:35
@CharlieLZ CharlieLZ marked this pull request as ready for review May 25, 2026 16:36
@CharlieLZ CharlieLZ force-pushed the codex/7830-transport-types branch from e68f552 to 8e94e76 Compare May 25, 2026 17:02
.await?
},
TransportType::Tor => {
TransportType::Tor | TransportType::TorTcp | TransportType::TcpTor => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need the handle the cases above also

Comment on lines +326 to 327
self.connection_manager_config.transport_protocols = protocols.clone();
self.transport_protocols = protocols;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why clone it and store it twice

Comment on lines +760 to +761
supported_transport_protocols: &[TransportProtocol],
preferred_transport_protocols: &[TransportProtocol],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these seem like duplicates?

query = query.limit(limit);
}

let node_ids = Self::unique_node_ids_preserving_order(query.load::<String>(conn)?, Some(n));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this combo will break the limit and qeury more

@SWvheerden
Copy link
Copy Markdown
Collaborator

closing in favour of #7851

@SWvheerden SWvheerden closed this May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Connection types

3 participants