fix: enable loopback transport for devnet and testnet modes#21
fix: enable loopback transport for devnet and testnet modes#21mickvandijke wants to merge 8 commits intomainfrom
Conversation
…modes Local devnets and testnets run all nodes on 127.0.0.1, which requires the transport layer to accept loopback connections. Set the env var in Devnet::start(), TestNetwork::start(), and NodeBuilder::build_core_config() for Development and Testnet network modes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR fixes local devnet and testnet startup failures by ensuring Key observations:
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| src/config.rs | Adds TRANSPORT_ALLOW_LOOPBACK_ENV constant with a clear doc comment — straightforward, no issues. |
| src/node.rs | Sets SAORSA_TRANSPORT_ALLOW_LOOPBACK=true for Testnet and Development modes inside build_core_config, a synchronous helper called from an async context; std::env::set_var is not MT-safe and buries a global side effect inside what appears to be a pure config builder. |
| src/devnet.rs | Calls std::env::set_var at the top of Devnet::start() before spawning nodes — placement is reasonable, but inherits the MT-safety concern of set_var in an async runtime. |
| tests/e2e/testnet.rs | Mirrors Devnet::start() pattern for TestNetwork::start(); same MT-safety concern but practically low-risk because the env var is set before any concurrent node startup. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Node Startup] --> B{Entry Point?}
B -->|Devnet| C[Devnet::start]
B -->|E2E Test| D[TestNetwork::start]
B -->|Single Node| E[NodeBuilder::build]
C --> F[set_var SAORSA_TRANSPORT_ALLOW_LOOPBACK=true]
D --> G[set_var SAORSA_TRANSPORT_ALLOW_LOOPBACK=true]
E --> H[build_core_config]
H --> I{NetworkMode?}
I -->|Production| J[No set_var]
I -->|Testnet| K[set_var SAORSA_TRANSPORT_ALLOW_LOOPBACK=true]
I -->|Development| L[set_var SAORSA_TRANSPORT_ALLOW_LOOPBACK=true]
F --> M[spawn bootstrap nodes]
G --> N[spawn test nodes]
J --> O[P2PNode::new]
K --> O
L --> O
M --> P[P2PNode::new per node]
N --> P
O --> Q[saorsa-core reads env var\nduring transport init]
P --> Q
Q --> R{Env var == true?}
R -->|Yes| S[Accept 127.0.0.1 connections]
R -->|No| T[Reject loopback connections]
Comments Outside Diff (1)
-
src/node.rs, line 174-241 (link)Env var side-effect inside a pure config-builder function
build_core_configis a synchronous, pure-looking helper whose name and signature suggest it only builds and returns aCoreNodeConfig. Mutating the process environment inside it is a surprising side effect that:- Makes the function hard to test in isolation (a test calling
build_core_configwithNetworkMode::Developmentwill permanently alter the process environment for every subsequent test in that process, potentially affecting unrelated test cases). - Hides the global-state mutation from callers who read only the function signature.
Consider either documenting the side effect prominently in the doc comment, or moving the
set_varcall up tobuild()where side effects are already expected.Prompt To Fix With AI
This is a comment left during a code review. Path: src/node.rs Line: 174-241 Comment: **Env var side-effect inside a pure config-builder function** `build_core_config` is a synchronous, pure-looking helper whose name and signature suggest it only builds and returns a `CoreNodeConfig`. Mutating the process environment inside it is a surprising side effect that: 1. Makes the function hard to test in isolation (a test calling `build_core_config` with `NetworkMode::Development` will permanently alter the process environment for every subsequent test in that process, potentially affecting unrelated test cases). 2. Hides the global-state mutation from callers who read only the function signature. Consider either documenting the side effect prominently in the doc comment, or moving the `set_var` call up to `build()` where side effects are already expected. How can I resolve this? If you propose a fix, please make it concise.
- Makes the function hard to test in isolation (a test calling
Last reviewed commit: 2b1cbbd
| @@ -226,6 +229,9 @@ impl NodeBuilder { | |||
| } | |||
| } | |||
| NetworkMode::Development => { | |||
| // Allow loopback connections for local development. | |||
| std::env::set_var(TRANSPORT_ALLOW_LOOPBACK_ENV, "true"); | |||
There was a problem hiding this comment.
std::env::set_var is not thread-safe in an async/multithreaded context
std::env::set_var is documented to cause undefined behavior when called concurrently with any other environment-variable operation (including reads via getenv). Because NodeBuilder::build() is an async fn running on Tokio's multi-threaded executor, other OS threads (in saorsa-core, or system libraries) may be reading the environment at the same moment.
This is particularly risky here because build_core_config (a sync function) modifies the global environment as a side effect and is called right before P2PNode::new(core_config).await — meaning saorsa-core's transport layer could be reading the variable from a different OS thread during initialization.
The safest fix is to set the env var once at the very top of NodeBuilder::build() (before build_core_config is called) rather than burying it inside the match arms of build_core_config. Even better would be for saorsa-core to expose a typed config field for this instead of relying on a process-global env var.
The same issue exists in src/devnet.rs:394 and tests/e2e/testnet.rs:1062, where set_var is called inside an async fn that spawns multiple concurrent tasks shortly after.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/node.rs
Line: 210-233
Comment:
**`std::env::set_var` is not thread-safe in an async/multithreaded context**
`std::env::set_var` is documented to cause undefined behavior when called concurrently with any other environment-variable operation (including reads via `getenv`). Because `NodeBuilder::build()` is an `async fn` running on Tokio's multi-threaded executor, other OS threads (in saorsa-core, or system libraries) may be reading the environment at the same moment.
This is particularly risky here because `build_core_config` (a sync function) modifies the global environment as a side effect and is called right before `P2PNode::new(core_config).await` — meaning saorsa-core's transport layer could be reading the variable from a different OS thread during initialization.
The safest fix is to set the env var **once at the very top of `NodeBuilder::build()`** (before `build_core_config` is called) rather than burying it inside the `match` arms of `build_core_config`. Even better would be for saorsa-core to expose a typed config field for this instead of relying on a process-global env var.
The same issue exists in `src/devnet.rs:394` and `tests/e2e/testnet.rs:1062`, where `set_var` is called inside an `async fn` that spawns multiple concurrent tasks shortly after.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Pull request overview
This PR enables loopback (127.0.0.1) connections in saorsa-core’s transport layer for local dev/test networking by standardizing the environment variable name and setting it in devnet/testnet startup paths.
Changes:
- Added
TRANSPORT_ALLOW_LOOPBACK_ENVconstant (SAORSA_TRANSPORT_ALLOW_LOOPBACK) insrc/config.rs. - Set
SAORSA_TRANSPORT_ALLOW_LOOPBACK=truewhen starting localDevnetand the E2ETestNetwork. - Set the same env var during
NodeBuilder::build_core_config()forDevelopmentandTestnetmodes.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/config.rs |
Introduces a shared constant for the transport loopback env var. |
src/devnet.rs |
Enables loopback acceptance before starting the in-process devnet nodes. |
src/node.rs |
Enables loopback acceptance for Development/Testnet NodeBuilder configuration. |
tests/e2e/testnet.rs |
Enables loopback acceptance before starting the E2E test network. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| NetworkMode::Testnet => { | ||
| // Allow loopback connections for local testnet deployments. | ||
| std::env::set_var(TRANSPORT_ALLOW_LOOPBACK_ENV, "true"); | ||
|
|
||
| core_config.production_config = Some(CoreProductionConfig::default()); |
There was a problem hiding this comment.
std::env::set_var here unconditionally overwrites any user-provided SAORSA_TRANSPORT_ALLOW_LOOPBACK value and applies process-wide. Consider only setting it when the env var is currently unset (so users can explicitly disable/override), and/or gate it on actually using loopback peers (e.g., any config.bootstrap addr is ip().is_loopback()).
| NetworkMode::Development => { | ||
| // Allow loopback connections for local development. | ||
| std::env::set_var(TRANSPORT_ALLOW_LOOPBACK_ENV, "true"); | ||
|
|
||
| core_config.production_config = None; | ||
| core_config.diversity_config = Some(CoreDiversityConfig::permissive()); |
There was a problem hiding this comment.
Setting SAORSA_TRANSPORT_ALLOW_LOOPBACK inside build_core_config has global side effects and currently overwrites any existing value. It’s safer to only set it if unset (and ideally restore the previous value if you later introduce multi-mode usage in one process).
| /// `DevnetError::Stabilization` if the network does not stabilize within the timeout. | ||
| pub async fn start(&mut self) -> Result<()> { | ||
| // Allow loopback connections — devnet nodes all run on 127.0.0.1. | ||
| std::env::set_var(TRANSPORT_ALLOW_LOOPBACK_ENV, "true"); |
There was a problem hiding this comment.
std::env::set_var changes process-wide state and is never restored/unset after the devnet is shut down. Since this devnet runs in-process, consider preserving any previous value and restoring it (or only setting when the env var is unset) to avoid leaking this setting into subsequent node runs in the same process.
| std::env::set_var(TRANSPORT_ALLOW_LOOPBACK_ENV, "true"); | |
| if std::env::var_os(TRANSPORT_ALLOW_LOOPBACK_ENV).is_none() { | |
| std::env::set_var(TRANSPORT_ALLOW_LOOPBACK_ENV, "true"); | |
| } |
| // Allow loopback connections — test nodes all run on 127.0.0.1. | ||
| std::env::set_var(TRANSPORT_ALLOW_LOOPBACK_ENV, "true"); | ||
|
|
There was a problem hiding this comment.
This sets a global environment variable at runtime. If multiple tests in this integration test binary run in parallel, it can introduce cross-test coupling via shared process state. Consider setting it once behind a Once/OnceLock guard (and/or restoring the previous value when the test network stops) to keep tests isolated.
Replace SAORSA_TRANSPORT_ALLOW_LOOPBACK env var with the new NodeConfig.allow_loopback config field from saorsa-core. Set it on the core config in devnet start_node(), testnet start_node(), and NodeBuilder::build_core_config() for Development/Testnet modes. Remove the now-unused TRANSPORT_ALLOW_LOOPBACK_ENV constant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
saorsa-core changed bootstrap_peers from Vec<SocketAddr> to Vec<Multiaddr>. Use .iter().map(Into::into).collect() to convert at each assignment site. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enables loopback connections for devnet/local testing when using saorsa-cli for uploads and downloads. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 7 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
Update all references to bootstrap addresses to use `MultiAddr` instead of `SocketAddr` following updates in `saorsa-core`. Adjust imports, configuration, and mappings accordingly.
Update configurations to use `listen_addrs` with `MultiAddr::quic()` exclusively, simplifying node setup and aligning with recent changes in `saorsa-core`.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 7 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| // Use a random port for the client | ||
| config.listen_addr = "127.0.0.1:0".parse().unwrap(); | ||
| config.listen_addrs = vec![]; | ||
| config.listen_addrs = vec![MultiAddr::quic("127.0.0.1:0".parse().unwrap())]; |
| // Set listen address | ||
| core_config.listen_addr = listen_addr; | ||
| core_config.listen_addrs = vec![listen_addr]; | ||
|
|
||
| // Enable IPv6 if configured | ||
| core_config.enable_ipv6 = matches!(config.ip_version, IpVersion::Ipv6 | IpVersion::Dual); | ||
| core_config.listen_addrs = vec![MultiAddr::quic(listen_addr)]; |
Complete the Section 18 test matrix with the remaining scenarios: - #3: Fresh replication stores chunk + updates PaidForList on remote nodes - #9: Fetch retry rotates to alternate source - #10: Fetch retry exhaustion with single source - #11: Repeated ApplicationFailure events decrease peer trust score - #12: Bootstrap node discovers keys stored on multiple peers - #14: Hint construction covers all locally stored keys - #15: Data and PaidForList survive node shutdown (partition) - #17: Neighbor sync request returns valid response (admission test) - #21: Paid-list majority confirmed from multiple peers via verification - #24: PaidNotify propagates paid-list entries after fresh replication - #25: Paid-list convergence verified via majority peer queries - #44: PaidForList persists across restart (cold-start recovery) - #45: PaidForList lost in fresh directory (unrecoverable scenario) All 56 Section 18 scenarios now have test coverage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete the Section 18 test matrix with the remaining scenarios: - #3: Fresh replication stores chunk + updates PaidForList on remote nodes - #9: Fetch retry rotates to alternate source - #10: Fetch retry exhaustion with single source - #11: Repeated ApplicationFailure events decrease peer trust score - #12: Bootstrap node discovers keys stored on multiple peers - #14: Hint construction covers all locally stored keys - #15: Data and PaidForList survive node shutdown (partition) - #17: Neighbor sync request returns valid response (admission test) - #21: Paid-list majority confirmed from multiple peers via verification - #24: PaidNotify propagates paid-list entries after fresh replication - #25: Paid-list convergence verified via majority peer queries - #44: PaidForList persists across restart (cold-start recovery) - #45: PaidForList lost in fresh directory (unrecoverable scenario) All 56 Section 18 scenarios now have test coverage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
allow_loopback = trueonCoreNodeConfigfor Development and Testnet network modes inNodeBuilder::build_core_config()allow_loopback = trueonCoreNodeConfiginDevnet::start_node()andTestNetwork::start_node()NodeConfig.allow_loopbackconfig field from saorsa-core instead of the removedSAORSA_TRANSPORT_ALLOW_LOOPBACKenv varDependencies
95ab6720Test plan
cargo buildpassescargo clippycleancargo test— 254 passed, 1 pre-existing Anvil port conflict failure (unrelated)🤖 Generated with Claude Code