Skip to content

Add timing instrumentation to debug River contract timeouts #1683

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 18 additions & 65 deletions CLAUDE.local.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,28 @@
- contract states are commutative monoids, they can be "merged" in any order to arrive at the same result. This may reduce some potential race conditions.

## 📁 Organized Notes System

All debugging notes have been reorganized into `claude.notes.local/` for better navigation:

### [→ Go to Main Index](./claude.notes.local/index.md)

### Quick Links
- **[Current Focus: Connection Stability](./claude.notes.local/active/connection-stability-2025-06-25.md)**
- **[Active Hypotheses](./claude.notes.local/active/hypotheses.md)**
- **[Essential Commands](./claude.notes.local/reference/commands.md)**
- **[Code Locations](./claude.notes.local/reference/code-locations.md)**

## Important Testing Notes

### Always Use Network Mode for Testing
- **NEVER use local mode for testing** - it uses very different code paths
- Local mode bypasses critical networking components that need to be tested
- Always test with `freenet network` to ensure realistic behavior

## Quick Reference - Essential Commands

### River Development
```bash
# Publish River (use this, not custom scripts)
cd ~/code/freenet/river && RUST_MIN_STACK=16777216 cargo make publish-river-debug

# Verify River build time (CRITICAL - only way to confirm new version is served)
curl -s http://127.0.0.1:50509/v1/contract/web/BcfxyjCH4snaknrBoCiqhYc9UFvmiJvhsp5d4L5DuvRa/ | grep -o 'Built: [^<]*' | head -1
```

### Freenet Management
```bash
# Start Freenet
./target/release/freenet network > freenet-debug.log 2>&1 &

# Check status
ps aux | grep freenet | grep -v grep | grep -v tail | grep -v journalctl

# Monitor logs
tail -f freenet-debug.log
```

## Detailed Documentation Files

### Current Active Debugging
- **Directory**: `freenet-invitation-bug.local/` (consolidated debugging)
- `README.md` - Overview and quick commands
- `river-notes/` - River-specific debugging documentation
- `contract-test/` - Minimal Rust test to reproduce PUT/GET issue

### River Invitation Bug (2025-01-18)
- **Status**: CONFIRMED - Contract operations hang on live network, work in integration tests
- **Root Cause**: Freenet node receives WebSocket requests but never responds
- **Test Directory**: `freenet-invitation-bug.local/live-network-test/`
- **Confirmed Findings**:
- River correctly sends PUT/GET requests via WebSocket
- Raw WebSocket test: Receives binary error response from server
- freenet-stdlib test: GET request sent but never receives response (2min timeout)
- Integration test `test_put_contract` passes when run in isolation
- Issue affects both PUT and GET operations
- **Current Investigation**: Systematically debugging why Freenet node doesn't respond to contract operations
- **See**: `freenet-invitation-bug.local/river-notes/invitation-bug-analysis-update.md`

### Historical Analysis (Reference Only)
- **Transport Layer Issues**: See lines 3-145 in previous version of this file (archived)
- **River Testing Procedures**: See lines 97-145 in previous version of this file (archived)

### CI Tools
- **GitHub CI Monitoring**: `~/code/agent.scripts/wait-for-ci.sh [PR_NUMBER]`

### Testing Tools
- **Puppeteer Testing Guide**: `puppeteer-testing-guide.local.md` - Essential patterns for testing Dioxus apps with MCP Puppeteer tools
- **Playwright Notes**: `playwright-notes.local.md` - Learnings and patterns for testing River with Playwright MCP tools

## Key Code Locations
- **River Room Creation**: `/home/ian/code/freenet/river/ui/src/components/room_list/create_room_modal.rs`
- **River Room Synchronizer**: `/home/ian/code/freenet/river/ui/src/components/app/freenet_api/room_synchronizer.rs`
- **River Room Data**: `/home/ian/code/freenet/river/ui/src/room_data.rs`
## Current Priority (2025-06-25)

## Organization Rules
1. **Check this file first** for command reference and active debugging directories
2. **Use standard commands** instead of creating custom scripts
3. **Verify River build timestamps** after publishing
4. **Create timestamped .local directories** for complex debugging sessions
5. **Update this index** when adding new debugging directories or tools
Per discussion with Nacho:
1. **Focus**: Connection stability to gateways
2. **Goal**: 10+ minute stable connections
3. **Method**: Create minimal transport test binary
4. **Avoid**: Getting distracted by River/contract issues
2 changes: 1 addition & 1 deletion apps/freenet-ping/app/src/ping_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ pub async fn wait_for_subscribe_response(
pub async fn connect_to_host(
host: &str,
) -> Result<WebApi, Box<dyn std::error::Error + Send + Sync + 'static>> {
let uri = format!("ws://{}/v1/contract/command?encodingProtocol=native", host);
let uri = format!("ws://{host}/v1/contract/command?encodingProtocol=native");
let (stream, _resp) = tokio_tungstenite::connect_async(&uri).await.map_err(|e| {
tracing::error!(err=%e);
e
Expand Down
9 changes: 3 additions & 6 deletions apps/freenet-ping/app/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,7 @@ const WASM_FILE_NAME: &str = "freenet-ping-contract";
pub const APP_TAG: &str = "ping-app";

pub async fn connect_ws_client(ws_port: u16) -> Result<WebApi> {
let uri = format!(
"ws://127.0.0.1:{}/v1/contract/command?encodingProtocol=native",
ws_port
);
let uri = format!("ws://127.0.0.1:{ws_port}/v1/contract/command?encodingProtocol=native");
let (stream, _) = connect_async(&uri).await?;
Ok(WebApi::start(stream))
}
Expand Down Expand Up @@ -334,7 +331,7 @@ pub(crate) fn pipe_std_streams(mut child: Child) -> anyhow::Result<()> {
let reader = std::io::BufReader::new(c_stderr);
for line in reader.lines() {
let line = line?;
writeln!(stderr, "{}", line)?;
writeln!(stderr, "{line}")?;
}
Ok(())
};
Expand All @@ -345,7 +342,7 @@ pub(crate) fn pipe_std_streams(mut child: Child) -> anyhow::Result<()> {
let reader = std::io::BufReader::new(c_stdout);
for line in reader.lines() {
let line = line?;
writeln!(stdout, "{}", line)?;
writeln!(stdout, "{line}")?;
}
Ok(())
};
Expand Down
30 changes: 13 additions & 17 deletions apps/freenet-ping/app/tests/run_app_blocked_peers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,17 +223,13 @@ async fn run_blocked_peers_test(config: BlockedPeersConfig) -> TestResult {
tokio::time::sleep(config.initial_wait).await;

// Connect to nodes via WebSocket
let uri_gw = format!(
"ws://127.0.0.1:{}/v1/contract/command?encodingProtocol=native",
ws_api_port_gw
);
let uri_gw =
format!("ws://127.0.0.1:{ws_api_port_gw}/v1/contract/command?encodingProtocol=native");
let uri_node1 = format!(
"ws://127.0.0.1:{}/v1/contract/command?encodingProtocol=native",
ws_api_port_node1
"ws://127.0.0.1:{ws_api_port_node1}/v1/contract/command?encodingProtocol=native"
);
let uri_node2 = format!(
"ws://127.0.0.1:{}/v1/contract/command?encodingProtocol=native",
ws_api_port_node2
"ws://127.0.0.1:{ws_api_port_node2}/v1/contract/command?encodingProtocol=native"
);

tracing::info!("Connecting to Gateway at {}", uri_gw);
Expand Down Expand Up @@ -496,7 +492,7 @@ async fn run_blocked_peers_test(config: BlockedPeersConfig) -> TestResult {
);

let mut gw_ping_refresh = Ping::default();
let gw_refresh_tag = format!("{}-refresh", gw_tag);
let gw_refresh_tag = format!("{gw_tag}-refresh");
gw_ping_refresh.insert(gw_refresh_tag.clone());
tracing::info!("Gateway sending refresh update: {}", gw_refresh_tag);
client_gw
Expand All @@ -509,7 +505,7 @@ async fn run_blocked_peers_test(config: BlockedPeersConfig) -> TestResult {
.await?;

let mut node1_ping_refresh = Ping::default();
let node1_refresh_tag = format!("{}-refresh", node1_tag);
let node1_refresh_tag = format!("{node1_tag}-refresh");
node1_ping_refresh.insert(node1_refresh_tag.clone());
tracing::info!("Node1 sending refresh update: {}", node1_refresh_tag);
client_node1
Expand All @@ -522,7 +518,7 @@ async fn run_blocked_peers_test(config: BlockedPeersConfig) -> TestResult {
.await?;

let mut node2_ping_refresh = Ping::default();
let node2_refresh_tag = format!("{}-refresh", node2_tag);
let node2_refresh_tag = format!("{node2_tag}-refresh");
node2_ping_refresh.insert(node2_refresh_tag.clone());
tracing::info!("Node2 sending refresh update: {}", node2_refresh_tag);
client_node2
Expand All @@ -540,7 +536,7 @@ async fn run_blocked_peers_test(config: BlockedPeersConfig) -> TestResult {
for i in 1..=config.update_rounds {
// Gateway update
let mut gw_ping_refresh = Ping::default();
let gw_refresh_tag = format!("{}-round{}", gw_tag, i);
let gw_refresh_tag = format!("{gw_tag}-round{i}");
gw_ping_refresh.insert(gw_refresh_tag.clone());
tracing::info!("Gateway sending round {} update: {}", i, gw_refresh_tag);
client_gw
Expand All @@ -554,7 +550,7 @@ async fn run_blocked_peers_test(config: BlockedPeersConfig) -> TestResult {

// Node1 update
let mut node1_ping_refresh = Ping::default();
let node1_refresh_tag = format!("{}-round{}", node1_tag, i);
let node1_refresh_tag = format!("{node1_tag}-round{i}");
node1_ping_refresh.insert(node1_refresh_tag.clone());
tracing::info!("Node1 sending round {} update: {}", i, node1_refresh_tag);
client_node1
Expand All @@ -568,7 +564,7 @@ async fn run_blocked_peers_test(config: BlockedPeersConfig) -> TestResult {

// Node2 update
let mut node2_ping_refresh = Ping::default();
let node2_refresh_tag = format!("{}-round{}", node2_tag, i);
let node2_refresh_tag = format!("{node2_tag}-round{i}");
node2_ping_refresh.insert(node2_refresh_tag.clone());
tracing::info!("Node2 sending round {} update: {}", i, node2_refresh_tag);
client_node2
Expand Down Expand Up @@ -631,7 +627,7 @@ async fn run_blocked_peers_test(config: BlockedPeersConfig) -> TestResult {

// Gateway final update
let mut gw_ping_final = Ping::default();
let gw_final_tag = format!("{}-final", gw_tag);
let gw_final_tag = format!("{gw_tag}-final");
gw_ping_final.insert(gw_final_tag.clone());
tracing::info!("Gateway sending final update: {}", gw_final_tag);
client_gw
Expand All @@ -645,7 +641,7 @@ async fn run_blocked_peers_test(config: BlockedPeersConfig) -> TestResult {

// Node1 final update
let mut node1_ping_final = Ping::default();
let node1_final_tag = format!("{}-final", node1_tag);
let node1_final_tag = format!("{node1_tag}-final");
node1_ping_final.insert(node1_final_tag.clone());
tracing::info!("Node1 sending final update: {}", node1_final_tag);
client_node1
Expand All @@ -659,7 +655,7 @@ async fn run_blocked_peers_test(config: BlockedPeersConfig) -> TestResult {

// Node2 final update
let mut node2_ping_final = Ping::default();
let node2_final_tag = format!("{}-final", node2_tag);
let node2_final_tag = format!("{node2_tag}-final");
node2_ping_final.insert(node2_final_tag.clone());
tracing::info!("Node2 sending final update: {}", node2_final_tag);
client_node2
Expand Down
9 changes: 3 additions & 6 deletions apps/freenet-ping/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,22 +501,19 @@ mod tests {
// Since TTL is 25s, all ping3 entries (4) and most ping2 entries should be included
assert_eq!(
ping3_count, 4,
"Expected all 4 entries from ping3 (newest), but found {}",
ping3_count
"Expected all 4 entries from ping3 (newest), but found {ping3_count}"
);

// Check that we have at least 3 entries from ping2
assert!(
ping2_count >= 3,
"Expected at least 3 entries from ping2 (middle), but found {}",
ping2_count
"Expected at least 3 entries from ping2 (middle), but found {ping2_count}"
);

// Due to TTL, we expect at most 3 entries from ping1
assert!(
ping1_count <= 3,
"Expected at most 3 entries from ping1 (oldest), but got {}",
ping1_count
"Expected at most 3 entries from ping1 (oldest), but got {ping1_count}"
);

// Verify total count matches what we found
Expand Down
4 changes: 4 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ repository = "https://github.com/freenet/freenet-core"
name = "freenet"
path = "src/bin/freenet.rs"

[[bin]]
name = "transport-test"
path = "src/bin/transport_test.rs"

[dependencies]
ahash = "0.8"
anyhow = "1"
Expand Down
Loading
Loading