Skip to content

Networking and Bootstrapping configuration changes#6901

Open
jmwample wants to merge 6 commits into
developfrom
jmwample/config-dns
Open

Networking and Bootstrapping configuration changes#6901
jmwample wants to merge 6 commits into
developfrom
jmwample/config-dns

Conversation

@jmwample

@jmwample jmwample commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

NYM-927

This PR moves toward distilling and synchronizing the specific networking information that is needed to bootstrap connections such that the structure can be shares and re-used in both nym core applications and in VPN / discovery use cases.

This change:

  • Updates the NetworkDetails structure
    • removing the nym_vpn_api_url field (this was deprecated by the addition of nym_vpn_api_urls)
    • moves nym_vpn_api_urls and nym_api_urls into a new NetworkingSpecifics structure
    • Adds placeholder fields for internal DNS resolver and covert channel information in the networking specifics.
    • Adds mainnet defaults for the updated NetworkDetails and new NetworkingSpecifics
  • moves sandbox information from the nym-wallet-types to the nym-network-details crate to match the mainnet network details.
  • update setter and getter functions

This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Added sandbox network defaults, including endpoints, contract addresses, and API settings.
    • Network configuration now supports multiple API URLs and related network-specific settings.
  • Bug Fixes

    • Improved API URL selection to use the primary configured endpoint more consistently.
    • Updated clients and SDK flows to handle missing or invalid API URLs more clearly.
  • Refactor

    • Streamlined network configuration handling across the codebase for more consistent behavior.

@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nym-explorer-v2 Error Error Jun 22, 2026 9:10pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs-nextra Ignored Ignored Preview Jun 22, 2026 9:10pm
nym-node-status Ignored Ignored Preview Jun 22, 2026 9:10pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

NymNetworkDetails' optional API URL fields are consolidated into a required NetworkingSpecifics struct (nym_api_urls, nym_vpn_api_urls, dns_fallbacks). A new sandbox network module is added, env parsing/export logic is updated, and consumers across validator-client, http-api-client, gateway-probe, SDK, wallet-types, and localnet-orchestrator are updated to the new accessor API. Two unrelated delegator files refactor match arms to use guards.

Changes

NetworkingSpecifics refactor and consumers

Layer / File(s) Summary
NetworkingSpecifics struct and NymNetworkDetails wiring
common/network-defaults/src/network.rs
Adds DnsFallback and NetworkingSpecifics types, replaces optional URL fields with a single networking field, and rewrites setters/getters (set_nym_api_urls, set_nym_vpn_api_urls, with_nym_vpn_api_urls, nym_api_urls(), nym_vpn_api_urls(), serialization helpers, try_parse_api_urls).
Env-based construction and export
common/network-defaults/src/network.rs
new_from_env derives nym_api/nym_api_urls from NYM_APIS without the old fallback; new_mainnet/mainnet_specifics populate networking; export_to_env serializes networking.* into NYM_APIS/NYM_VPN_APIS.
New sandbox network module
common/network-defaults/src/lib.rs, common/network-defaults/src/sandbox.rs
Adds a sandbox submodule with network identity/contract/endpoint constants and validators()/network_details()/network_specifics() builders.
Downstream consumers updated to new API
common/client-libs/validator-client/src/client.rs, common/http-api-client/src/lib.rs, common/http-api-client/src/tests.rs, common/client-core/src/client/base_client/mod.rs, nym-gateway-probe/src/common/helpers.rs, sdk/rust/nym-sdk/src/mixnet/client.rs, sdk/rust/nym-sdk/src/ipr_wrapper/ip_mix_stream.rs, tools/internal/localnet-orchestrator/src/orchestrator/network.rs
Updates URL derivation and setters to use networking.nym_api_urls/nym_api_urls()/set_nym_api_urls() instead of the removed optional fields, including validator API URL selection now using only the first endpoint.
Wallet sandbox module removal
nym-wallet/nym-wallet-types/src/network.rs, nym-wallet/nym-wallet-types/src/network/sandbox.rs
Removes the local sandbox module and parse_optional_str helper, importing DenomDetails/NymNetworkDetails from nym_config::defaults instead.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Delegation Event Match-Guard Refactor

Layer / File(s) Summary
Match guard refactor for delegation events
common/commands/src/validator/mixnet/delegators/delegate_to_multiple_mixnodes.rs, common/commands/src/validator/mixnet/delegators/query_for_delegations.rs
Rewrites owner-address checks in Delegate/Undelegate match arms to use match-arm guards instead of nested if blocks, preserving existing behavior.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Suggested reviewers: simonwicky, serinko

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is related to the refactor but too generic to clearly describe the main change. Use a more specific title like "Refactor network defaults into NetworkingSpecifics and update bootstrapping URLs".
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jmwample/config-dns

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jmwample jmwample changed the title Jmwample/config dns Networking and Bootstrapping configuration changes Jun 29, 2026
@jmwample
jmwample marked this pull request as ready for review July 7, 2026 16:10

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
common/network-defaults/src/network.rs (1)

157-220: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Improve the NYM_APIS panic message common/network-defaults/src/network.rs:173-175try_parse_api_urls() collapses missing and malformed JSON into an empty vec, so first().expect("nym api not set") panics with the same message in both cases. A message that names NYM_APIS, or propagates the parse error, would make startup failures much easier to diagnose.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/network-defaults/src/network.rs` around lines 157 - 220, The panic in
NymNetworkDetails::new_from_env for NYM_APIS is ambiguous because
try_parse_api_urls() can return an empty list for both missing and malformed
input, so first().expect("nym api not set") hides the real failure. Update the
NYM_APIS handling in new_from_env() to distinguish absent from invalid values by
propagating or surfacing the parse error, and make the panic/error message
explicitly reference NYM_APIS so startup issues are diagnosable.
🧹 Nitpick comments (2)
common/http-api-client/src/lib.rs (1)

652-657: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistent access pattern: bypasses the nym_api_urls() accessor.

Other consumers (tests.rs, gateway-probe, ip_mix_stream.rs) use network.nym_api_urls(), but this reaches into network.networking.nym_api_urls.clone() directly, coupling to the internal struct layout. Since this function is already #[deprecated], the practical payoff of fixing this is limited, but for consistency:

-        let urls = network.networking.nym_api_urls.clone();
+        let urls = network.nym_api_urls();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/http-api-client/src/lib.rs` around lines 652 - 657, `from_network` is
reaching into `NymNetworkDetails` internals by cloning
`network.networking.nym_api_urls` directly instead of using the `nym_api_urls()`
accessor. Update this method to follow the same access pattern as the other call
sites by retrieving the URLs through `network.nym_api_urls()` and passing them
into `Self::new_with_fronted_urls`, keeping the implementation consistent with
`NymNetworkDetails` usage elsewhere.
common/network-defaults/src/sandbox.rs (1)

73-82: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate/inconsistent sandbox nym-api URL representation.

validators() hardcodes the sandbox nym-api endpoint as "https://sandbox-nym-api1.nymtech.net/api" (no trailing slash), while NYM_APIS hardcodes the same host as "https://sandbox-nym-api1.nymtech.net/api/" (trailing slash). Two independent literals for the same endpoint risk drifting apart on future updates, and the trailing-slash mismatch could matter if either consumer resolves relative paths via Url::join.

Consider deriving one from the other (e.g., building validators()'s api_url from NYM_APIS[0].url) to keep a single source of truth.

Also applies to: 88-94

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/network-defaults/src/sandbox.rs` around lines 73 - 82, The sandbox
nym-api endpoint is defined twice with inconsistent trailing slashes, which can
drift and affect URL joining. Update the sandbox URL handling so `validators()`
and `NYM_APIS` share a single source of truth, ideally by reusing
`NYM_APIS[0].url` when building the API URL. Keep the `validators()` and
`NYM_APIS` symbols aligned so both resolve to the exact same sandbox endpoint
representation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@common/client-libs/validator-client/src/client.rs`:
- Around line 115-128: The Validator client configuration currently only reads
details.endpoints[0].api_url, so it ignores the richer network metadata and
never uses the fallback/fronting URLs. Update try_from_nym_network_details in
client::Config to carry the network API URL list and front hosts through from
NymNetworkDetails, and thread those values into
nym_http_api_client::ClientBuilder::new_with_fronted_urls instead of collapsing
to a single api_url. Make sure the Config type and any constructor paths are
adjusted to preserve the full URL set for the client setup.

In `@common/network-defaults/src/network.rs`:
- Around line 103-126: `NymNetworkDetails::new_empty()` is inheriting mainnet
networking defaults through `NetworkingSpecifics::default()`, which makes the
supposedly empty constructor carry non-empty API/DNS state. Update `new_empty()`
to build `networking` explicitly with empty `nym_api_urls`, `nym_vpn_api_urls`,
and `dns_fallbacks` instead of relying on `NetworkingSpecifics::default()`,
while leaving the rest of the `NymNetworkDetails` initialization unchanged.

---

Outside diff comments:
In `@common/network-defaults/src/network.rs`:
- Around line 157-220: The panic in NymNetworkDetails::new_from_env for NYM_APIS
is ambiguous because try_parse_api_urls() can return an empty list for both
missing and malformed input, so first().expect("nym api not set") hides the real
failure. Update the NYM_APIS handling in new_from_env() to distinguish absent
from invalid values by propagating or surfacing the parse error, and make the
panic/error message explicitly reference NYM_APIS so startup issues are
diagnosable.

---

Nitpick comments:
In `@common/http-api-client/src/lib.rs`:
- Around line 652-657: `from_network` is reaching into `NymNetworkDetails`
internals by cloning `network.networking.nym_api_urls` directly instead of using
the `nym_api_urls()` accessor. Update this method to follow the same access
pattern as the other call sites by retrieving the URLs through
`network.nym_api_urls()` and passing them into `Self::new_with_fronted_urls`,
keeping the implementation consistent with `NymNetworkDetails` usage elsewhere.

In `@common/network-defaults/src/sandbox.rs`:
- Around line 73-82: The sandbox nym-api endpoint is defined twice with
inconsistent trailing slashes, which can drift and affect URL joining. Update
the sandbox URL handling so `validators()` and `NYM_APIS` share a single source
of truth, ideally by reusing `NYM_APIS[0].url` when building the API URL. Keep
the `validators()` and `NYM_APIS` symbols aligned so both resolve to the exact
same sandbox endpoint representation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 95aa6595-d14e-4b4d-8e68-fc6a7c9b0f04

📥 Commits

Reviewing files that changed from the base of the PR and between 55cd7ba and 260cc4b.

📒 Files selected for processing (15)
  • common/client-core/src/client/base_client/mod.rs
  • common/client-libs/validator-client/src/client.rs
  • common/commands/src/validator/mixnet/delegators/delegate_to_multiple_mixnodes.rs
  • common/commands/src/validator/mixnet/delegators/query_for_delegations.rs
  • common/http-api-client/src/lib.rs
  • common/http-api-client/src/tests.rs
  • common/network-defaults/src/lib.rs
  • common/network-defaults/src/network.rs
  • common/network-defaults/src/sandbox.rs
  • nym-gateway-probe/src/common/helpers.rs
  • nym-wallet/nym-wallet-types/src/network.rs
  • nym-wallet/nym-wallet-types/src/network/sandbox.rs
  • sdk/rust/nym-sdk/src/ipr_wrapper/ip_mix_stream.rs
  • sdk/rust/nym-sdk/src/mixnet/client.rs
  • tools/internal/localnet-orchestrator/src/orchestrator/network.rs
💤 Files with no reviewable changes (1)
  • nym-wallet/nym-wallet-types/src/network/sandbox.rs

Comment on lines +115 to +128
/// Create a Validator Client configuration from a provided Network Details.
/// This uses the included nym api url of the defined validator
pub fn try_from_nym_network_details(
details: &NymNetworkDetails,
) -> Result<Self, ValidatorClientError> {
let mut api_url = details
.endpoints
.iter()
.filter_map(|d| d.api_url.as_ref())
.map(|url| Url::parse(url))
.collect::<Result<Vec<_>, _>>()?;

if let Some(nym_api_urls) = details.nym_api_urls.as_ref() {
api_url.extend(
nym_api_urls
.iter()
.map(|url| url.url.parse())
.collect::<Result<Vec<_>, _>>()?,
);
}

if api_url.is_empty() {
return Err(ValidatorClientError::NoAPIUrlAvailable);
}
let api_url = details.endpoints[0]
.api_url
.clone()
.ok_or(ValidatorClientError::NoAPIUrlAvailable)?
.parse()
.map_err(ValidatorClientError::MalformedUrlProvided)?;

Ok(Config {
api_url: api_url.pop().unwrap(),
api_url,

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Files\n'
git ls-files 'common/client-libs/validator-client/src/client.rs' 'common/network-defaults/src/network.rs' 'nym-gateway-probe/src/common/helpers.rs' 'common/commands/src/context/mod.rs' 'sdk/rust/nym-sdk/src/mixnet/client.rs'

printf '\n## Outline: validator-client/src/client.rs\n'
ast-grep outline common/client-libs/validator-client/src/client.rs --view expanded || true

printf '\n## Outline: common/network-defaults/src/network.rs\n'
ast-grep outline common/network-defaults/src/network.rs --view expanded || true

printf '\n## Search nym_api_urls usage\n'
rg -n "nym_api_urls\\(" common nym-gateway-probe sdk/rust -g '!**/target/**' || true

printf '\n## Relevant slices\n'
sed -n '1,220p' common/client-libs/validator-client/src/client.rs
printf '\n--- network.rs ---\n'
sed -n '1,260p' common/network-defaults/src/network.rs
printf '\n--- gateway probe helper ---\n'
sed -n '1,220p' nym-gateway-probe/src/common/helpers.rs
printf '\n--- context mod ---\n'
sed -n '1,220p' common/commands/src/context/mod.rs
printf '\n--- sdk client ---\n'
sed -n '1,220p' sdk/rust/nym-sdk/src/mixnet/client.rs

Repository: nymtech/nym

Length of output: 46673


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## validator-client client methods around construction\n'
sed -n '220,380p' common/client-libs/validator-client/src/client.rs

printf '\n## http-api-client fronted URL support\n'
rg -n "new_with_fronted_urls|front_hosts|ApiUrl" common/http-api-client common/client-core common/client-libs -g '!**/target/**' || true

printf '\n## relevant nym_api client constructor signatures\n'
rg -n "pub fn new_url|pub fn new_with_timeout|pub fn new_with_user_agent|pub fn new_with_fronted_urls" common/http-api-client -g '!**/target/**' || true

Repository: nymtech/nym

Length of output: 10162


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Diff for validator-client config function\n'
git diff --unified=40 -- common/client-libs/validator-client/src/client.rs | sed -n '1,220p'

printf '\n## Diff summary for network defaults and validator client\n'
git diff --stat -- common/client-libs/validator-client/src/client.rs common/network-defaults/src/network.rs

Repository: nymtech/nym

Length of output: 254


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Search validator-client for multi-URL support\n'
rg -n "with_nym_api_urls|new_with_fronted_urls|front_hosts|nym_api_urls\\(" common/client-libs/validator-client common -g '!**/target/**' | sed -n '1,220p'

printf '\n## Validator-client docs/comments around Config\n'
sed -n '90,140p' common/client-libs/validator-client/src/client.rs

Repository: nymtech/nym

Length of output: 6599


Thread the network API URL list through validator-client

try_from_nym_network_details collapses NymNetworkDetails to endpoints[0].api_url, so validator-client never consumes networking.nym_api_urls or front_hosts. That drops the fallback/domain-fronting path available elsewhere in the repo; if this client is meant to use the new network metadata, Config needs to carry the URL list into nym_http_api_client::ClientBuilder::new_with_fronted_urls instead of a single URL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/client-libs/validator-client/src/client.rs` around lines 115 - 128,
The Validator client configuration currently only reads
details.endpoints[0].api_url, so it ignores the richer network metadata and
never uses the fallback/fronting URLs. Update try_from_nym_network_details in
client::Config to carry the network API URL list and front hosts through from
NymNetworkDetails, and thread those values into
nym_http_api_client::ClientBuilder::new_with_fronted_urls instead of collapsing
to a single api_url. Make sure the Config type and any constructor paths are
adjusted to preserve the full URL set for the client setup.

Comment on lines +103 to +126
#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct DnsFallback {
pub url: String,
pub addresses: Vec<String>,
}

#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct NetworkingSpecifics {
pub nym_api_urls: Vec<ApiUrl>,
pub nym_vpn_api_urls: Vec<ApiUrl>,
pub dns_fallbacks: Vec<DnsFallback>,
// pub internal_nameservers: std::any::Any,
// pub covert channels: std::any::Any,
}

// by default we assume the same defaults as mainnet, i.e. same prefixes and denoms
impl Default for NetworkingSpecifics {
fn default() -> Self {
NymNetworkDetails::mainnet_specifics()
}
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- network.rs outline ---'
ast-grep outline common/network-defaults/src/network.rs --view expanded || true

echo
echo '--- relevant line ranges ---'
sed -n '1,260p' common/network-defaults/src/network.rs | nl -ba | sed -n '1,260p'

echo
echo '--- search for new_empty and mainnet_specifics ---'
rg -n "new_empty\(|mainnet_specifics\(|struct NymNetworkDetails|impl Default for NetworkingSpecifics|set_nym_api_urls|nym_network_details\(" -S .

echo
echo '--- files containing NymNetworkDetails::new_empty ---'
rg -n "NymNetworkDetails::new_empty\(" -S .

Repository: nymtech/nym

Length of output: 5496


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path("common/network-defaults/src/network.rs")
text = p.read_text()
for needle in ["pub struct NetworkingSpecifics", "impl Default for NetworkingSpecifics", "pub fn new_empty()", "mainnet_specifics()", "set_nym_api_urls"]:
    idx = text.find(needle)
    print(f"\n### {needle} @ {idx}")
    if idx != -1:
        start = max(0, idx - 600)
        end = min(len(text), idx + 1200)
        print(text[start:end])
PY

Repository: nymtech/nym

Length of output: 9339


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- caller file ---'
sed -n '1,260p' tools/internal/localnet-orchestrator/src/orchestrator/network.rs | nl -ba | sed -n '1,260p'

Repository: nymtech/nym

Length of output: 209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
path = Path("tools/internal/localnet-orchestrator/src/orchestrator/network.rs")
text = path.read_text().splitlines()
for i, line in enumerate(text, 1):
    if "nym_network_details" in line or "new_empty()" in line or "set_nym_api_urls" in line or "nym_api_endpoint" in line:
        start = max(1, i - 12)
        end = min(len(text), i + 30)
        print(f"\n--- lines {start}-{end} ---")
        for j in range(start, end + 1):
            print(f"{j:4d}: {text[j-1]}")
PY

Repository: nymtech/nym

Length of output: 20367


new_empty() should not inherit networking defaults. NymNetworkDetails::new_empty() currently uses NetworkingSpecifics::default(), and that default is wired to mainnet_specifics(). That makes the “empty” constructor carry non-empty networking state, so callers like the localnet orchestrator can end up with unexpected API URLs when nym_api_endpoint is unset. Initialize networking with empty vectors in new_empty() instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/network-defaults/src/network.rs` around lines 103 - 126,
`NymNetworkDetails::new_empty()` is inheriting mainnet networking defaults
through `NetworkingSpecifics::default()`, which makes the supposedly empty
constructor carry non-empty API/DNS state. Update `new_empty()` to build
`networking` explicitly with empty `nym_api_urls`, `nym_vpn_api_urls`, and
`dns_fallbacks` instead of relying on `NetworkingSpecifics::default()`, while
leaving the rest of the `NymNetworkDetails` initialization unchanged.

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.

2 participants