diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bf4bc96..55fcf9c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,11 +11,11 @@ env: jobs: msrv: - name: Rust 1.91 MSRV + name: Rust 1.95 MSRV runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.91.0 + - uses: dtolnay/rust-toolchain@1.95.0 - uses: Swatinem/rust-cache@v2 - run: cargo check --all-targets --all-features --locked diff --git a/Cargo.toml b/Cargo.toml index a129dd58..54ca169f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" repository = "https://github.com/WithAutonomi/ant-node" keywords = ["p2p", "decentralized", "quantum-safe", "post-quantum", "dht"] categories = ["network-programming", "cryptography"] -rust-version = "1.91" +rust-version = "1.95" [lib] name = "ant_node" diff --git a/README.md b/README.md index 1f6b1655..6035e015 100644 --- a/README.md +++ b/README.md @@ -862,7 +862,7 @@ ant-node ### Prerequisites -- Rust 1.91+ (for building from source) +- Rust 1.95+ (for building from source) - Linux, macOS, or Windows ### Build from Source diff --git a/docs/WEBRTC_DIRECT_TESTNET.md b/docs/WEBRTC_DIRECT_TESTNET.md index d7c698a6..cae9d65b 100644 --- a/docs/WEBRTC_DIRECT_TESTNET.md +++ b/docs/WEBRTC_DIRECT_TESTNET.md @@ -7,7 +7,7 @@ and serves browser bootstrap metadata; the companion site lives in the sibling ## Start the node testnet -Rust 1.91 or newer is the shared native/WASM baseline. +Rust 1.95 or newer is the shared native/WASM baseline. The startup and transport policy is recorded in [ADR-0015](adr/ADR-0015-direct-browser-clients-over-webrtc-direct.md). ```bash diff --git a/src/config.rs b/src/config.rs index 09b06144..725995a2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -825,7 +825,11 @@ mod tests { let toml_str = r"peers = []"; let config: BootstrapPeersConfig = toml::from_str(toml_str).expect("empty peers should parse"); - assert!(config.peers.is_empty()); + assert!( + config.peers.is_empty(), + "expected no peers, got {:?}", + config.peers + ); } #[test] @@ -833,7 +837,11 @@ mod tests { let toml_str = ""; let config: BootstrapPeersConfig = toml::from_str(toml_str).expect("missing field should use default"); - assert!(config.peers.is_empty()); + assert!( + config.peers.is_empty(), + "expected no peers, got {:?}", + config.peers + ); } #[test] diff --git a/src/node.rs b/src/node.rs index 56813244..9fe7e254 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1537,7 +1537,7 @@ mod tests { fn test_scan_identity_dirs_empty_dir() { let tmp = tempfile::tempdir().unwrap(); let dirs = NodeBuilder::scan_identity_dirs(tmp.path()).unwrap(); - assert!(dirs.is_empty()); + assert!(dirs.is_empty(), "expected no identity dirs, got {dirs:?}"); } #[test] @@ -1545,7 +1545,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let path = tmp.path().join("nonexistent_identity_dir"); let dirs = NodeBuilder::scan_identity_dirs(&path).unwrap(); - assert!(dirs.is_empty()); + assert!(dirs.is_empty(), "expected no identity dirs, got {dirs:?}"); } #[test] diff --git a/src/replication/admission.rs b/src/replication/admission.rs index 445d5644..84e2a0e8 100644 --- a/src/replication/admission.rs +++ b/src/replication/admission.rs @@ -283,9 +283,21 @@ mod tests { rejected_keys: Vec::new(), }; - assert!(result.replica_keys.is_empty()); - assert!(result.paid_only_keys.is_empty()); - assert!(result.rejected_keys.is_empty()); + assert!( + result.replica_keys.is_empty(), + "expected no replica keys, got {:?}", + result.replica_keys + ); + assert!( + result.paid_only_keys.is_empty(), + "expected no paid-only keys, got {:?}", + result.paid_only_keys + ); + assert!( + result.rejected_keys.is_empty(), + "expected no rejected keys, got {:?}", + result.rejected_keys + ); } #[test] diff --git a/src/replication/audit_metrics.rs b/src/replication/audit_metrics.rs index e1f2cf85..16d0e682 100644 --- a/src/replication/audit_metrics.rs +++ b/src/replication/audit_metrics.rs @@ -755,6 +755,11 @@ mod tests { assert_eq!(snapshot.origins[0].total_avg_ms(), 55); assert_eq!(snapshot.origins[0].peak_global_inflight, 31); assert_eq!(snapshot.total.global_pool_drops, 1); - assert!(metrics.take_snapshot().origins.is_empty()); + let drained = metrics.take_snapshot(); + assert!( + drained.origins.is_empty(), + "expected the snapshot to have been drained, got {:?}", + drained.origins + ); } } diff --git a/src/replication/neighbor_sync.rs b/src/replication/neighbor_sync.rs index 946c11d6..b1f0d859 100644 --- a/src/replication/neighbor_sync.rs +++ b/src/replication/neighbor_sync.rs @@ -652,7 +652,7 @@ mod tests { let batch = select_sync_batch(&mut state, 4, Duration::from_secs(0)); - assert!(batch.is_empty()); + assert!(batch.is_empty(), "expected an empty batch, got {batch:?}"); assert_eq!(state.cursor, 0); } @@ -671,8 +671,15 @@ mod tests { let cooldown = Duration::from_hours(1); let batch = select_sync_batch(&mut state, 4, cooldown); - assert!(batch.is_empty()); - assert!(state.order.is_empty()); + assert!( + batch.is_empty(), + "expected an empty batch while every peer is in cooldown, got {batch:?}" + ); + assert!( + state.order.is_empty(), + "expected an empty order, got {:?}", + state.order + ); } // -- handle_sync_failure ------------------------------------------------- @@ -735,7 +742,11 @@ mod tests { let replacement = handle_sync_failure(&mut state, &peer_id_from_byte(1), Duration::from_secs(0)); - assert!(state.order.is_empty()); + assert!( + state.order.is_empty(), + "expected the order to be exhausted, got {:?}", + state.order + ); assert!(replacement.is_none()); } @@ -1154,7 +1165,10 @@ mod tests { // Extra call after cycle complete returns empty. let round4 = select_sync_batch(&mut state, batch_size, no_cooldown); - assert!(round4.is_empty()); + assert!( + round4.is_empty(), + "expected an empty batch once the cycle is complete, got {round4:?}" + ); } /// Scenario 37: Non-`LocalRT` inbound sync behavior. diff --git a/src/replication/protocol.rs b/src/replication/protocol.rs index b2ca7962..b70ae9a7 100644 --- a/src/replication/protocol.rs +++ b/src/replication/protocol.rs @@ -2262,7 +2262,11 @@ mod tests { assert_eq!(decoded.request_id, 6); if let ReplicationMessageBody::NeighborSyncResponse(resp) = decoded.body { assert_eq!(resp.replica_hints.len(), 1); - assert!(resp.paid_hints.is_empty()); + assert!( + resp.paid_hints.is_empty(), + "expected no paid hints, got {:?}", + resp.paid_hints + ); assert!(!resp.bootstrapping); assert_eq!(resp.rejected_keys.len(), 2); } else { diff --git a/src/replication/pruning.rs b/src/replication/pruning.rs index 9d213126..43cc63cf 100644 --- a/src/replication/pruning.rs +++ b/src/replication/pruning.rs @@ -2258,7 +2258,10 @@ mod tests { // The same evidence fails a quorum of 3. let confirmed = confirmed_keys_from_presence(&candidates, &present_by_key, 3); - assert!(confirmed.is_empty()); + assert!( + confirmed.is_empty(), + "expected no confirmed keys at a quorum of 3, got {confirmed:?}" + ); } #[test] diff --git a/src/replication/scheduling.rs b/src/replication/scheduling.rs index e7787bb7..9ec1239a 100644 --- a/src/replication/scheduling.rs +++ b/src/replication/scheduling.rs @@ -1871,7 +1871,11 @@ mod tests { let mut first = test_entry(1); first.next_verify_at = Instant::now() + Duration::from_mins(1); assert!(queues.add_pending_verify(key, first).admitted()); - assert!(queues.ready_pending_keys(Instant::now()).is_empty()); + let ready = queues.ready_pending_keys(Instant::now()); + assert!( + ready.is_empty(), + "expected no ready pending keys before the retry delay elapses, got {ready:?}" + ); assert!(!queues.add_pending_verify(key, test_entry(2)).admitted()); assert_eq!(queues.pending_count(), 1); assert_eq!( @@ -1990,7 +1994,11 @@ mod tests { vec![source_a, source_b], )); - assert!(queues.remove_hint_source(&source_a).is_empty()); + let orphaned = queues.remove_hint_source(&source_a); + assert!( + orphaned.is_empty(), + "expected no orphaned keys while source_b still advertises, got {orphaned:?}" + ); let candidate = queues.dequeue_fetch().expect("candidate remains fetchable"); assert_eq!(candidate.sources, vec![source_b]); assert_eq!( diff --git a/src/replication/storage_commitment_audit.rs b/src/replication/storage_commitment_audit.rs index c20b8e60..d2835610 100644 --- a/src/replication/storage_commitment_audit.rs +++ b/src/replication/storage_commitment_audit.rs @@ -2371,7 +2371,7 @@ mod tests { assert!(structure(&built, &proof, &nonce, &peer).is_ok()); // Round 2: honest responder opens real slices for the sample. let s = sample(&proof, &nonce, built.commitment().key_count); - assert!(!s.is_empty()); + assert!(!s.is_empty(), "expected a non-empty sample"); let openings = openings_for(&s); let items = served_honest_items(&openings, &nonce, &peer); match verify_slice_response(&openings, &nonce, &peer, &items) { diff --git a/src/storage/chunk_store.rs b/src/storage/chunk_store.rs index fe297705..042646d7 100644 --- a/src/storage/chunk_store.rs +++ b/src/storage/chunk_store.rs @@ -3203,7 +3203,11 @@ mod tests { .await .expect("copy rest"); assert_eq!(rest.copied, 3); - assert!(store.legacy_only_keys().is_empty()); + let legacy = store.legacy_only_keys(); + assert!( + legacy.is_empty(), + "expected no legacy-only keys, got {legacy:?}" + ); assert_eq!(store.current_chunks().expect("count"), 5); } @@ -4684,7 +4688,11 @@ mod tests { .copy_batch(&keys, 0, 0, &never_cancelled()) .await .expect("copy"); - assert!(store.legacy_only_keys().is_empty()); + let legacy = store.legacy_only_keys(); + assert!( + legacy.is_empty(), + "expected no legacy-only keys, got {legacy:?}" + ); let victim = keys.first().copied().expect("a key"); let path = dir @@ -4788,7 +4796,11 @@ mod tests { .copy_batch(&late, 0, 0, &never_cancelled()) .await .expect("copy"); - assert!(store.legacy_only_keys().is_empty()); + let legacy = store.legacy_only_keys(); + assert!( + legacy.is_empty(), + "expected no legacy-only keys, got {legacy:?}" + ); assert_eq!( store .get(late.first().expect("a key")) @@ -4844,7 +4856,11 @@ mod tests { .copy_batch(&keys, 0, 0, &never_cancelled()) .await .expect("copy"); - assert!(store.legacy_only_keys().is_empty()); + let legacy = store.legacy_only_keys(); + assert!( + legacy.is_empty(), + "expected no legacy-only keys, got {legacy:?}" + ); // Remove the file without telling the store, which is what the pruner's own // delete looks like if it lands mid-pass. Republishing from the legacy copy here @@ -4986,7 +5002,11 @@ mod tests { .expect("copy"); assert_eq!(report.copied, 0); assert_eq!(report.unusable, 1); - assert!(store.legacy_only_keys().is_empty()); + let legacy = store.legacy_only_keys(); + assert!( + legacy.is_empty(), + "expected no legacy-only keys, got {legacy:?}" + ); // And it is gone from the environment, so the pass below cannot find it and put // it back. That is the loop this is about. diff --git a/src/storage/lmdb.rs b/src/storage/lmdb.rs index 82b97cca..2f991606 100644 --- a/src/storage/lmdb.rs +++ b/src/storage/lmdb.rs @@ -1807,7 +1807,7 @@ mod tests { // Empty storage let keys = storage.all_keys().await.expect("all_keys empty"); - assert!(keys.is_empty()); + assert!(keys.is_empty(), "expected empty storage, got {keys:?}"); // Store some chunks let content1 = b"chunk one for keys"; diff --git a/src/upgrade/apply.rs b/src/upgrade/apply.rs index b375cb14..88bdd8ee 100644 --- a/src/upgrade/apply.rs +++ b/src/upgrade/apply.rs @@ -729,7 +729,10 @@ mod tests { #[test] fn test_auto_apply_upgrader_creation() { let upgrader = AutoApplyUpgrader::new(); - assert!(!upgrader.current_version().to_string().is_empty()); + assert!( + !upgrader.current_version().to_string().is_empty(), + "expected a non-empty current version" + ); } #[test] @@ -743,7 +746,10 @@ mod tests { #[test] fn test_default_impl() { let upgrader = AutoApplyUpgrader::default(); - assert!(!upgrader.current_version().to_string().is_empty()); + assert!( + !upgrader.current_version().to_string().is_empty(), + "expected a non-empty current version" + ); } /// Helper: create a tar.gz archive containing a fake binary. diff --git a/src/upgrade/mod.rs b/src/upgrade/mod.rs index a762ad6d..c6e9323f 100644 --- a/src/upgrade/mod.rs +++ b/src/upgrade/mod.rs @@ -607,7 +607,10 @@ mod tests { fn test_default_impl() { let upgrader = Upgrader::default(); // Should not panic and should have a valid version - assert!(!upgrader.current_version().to_string().is_empty()); + assert!( + !upgrader.current_version().to_string().is_empty(), + "expected a non-empty current version" + ); } /// Test 11: Backup with special characters in filename diff --git a/src/web_rtc.rs b/src/web_rtc.rs index 6bb3e8e0..136045a3 100644 --- a/src/web_rtc.rs +++ b/src/web_rtc.rs @@ -167,7 +167,7 @@ impl ByteBudget { error: &'static str, ) -> ServerResult { self.in_use - .fetch_update(Ordering::AcqRel, Ordering::Acquire, |current| { + .try_update(Ordering::AcqRel, Ordering::Acquire, |current| { current .checked_add(amount) .filter(|next| *next <= self.limit) @@ -199,7 +199,7 @@ impl ByteReservation { fn try_grow(&mut self, amount: usize) -> ServerResult<()> { self.budget .in_use - .fetch_update(Ordering::AcqRel, Ordering::Acquire, |current| { + .try_update(Ordering::AcqRel, Ordering::Acquire, |current| { current .checked_add(amount) .filter(|next| *next <= self.budget.limit) diff --git a/tests/e2e/anvil.rs b/tests/e2e/anvil.rs index 57e70116..f1cd41f4 100644 --- a/tests/e2e/anvil.rs +++ b/tests/e2e/anvil.rs @@ -198,7 +198,10 @@ mod tests { async fn test_anvil_creation() { let anvil = TestAnvil::new().await.unwrap(); let _network = anvil.to_network(); - assert!(!anvil.default_wallet_key().unwrap().is_empty()); + assert!( + !anvil.default_wallet_key().unwrap().is_empty(), + "expected a non-empty default wallet key" + ); } #[test] diff --git a/tests/e2e/integration_tests.rs b/tests/e2e/integration_tests.rs index 3f65ba0c..18f8612a 100644 --- a/tests/e2e/integration_tests.rs +++ b/tests/e2e/integration_tests.rs @@ -121,7 +121,10 @@ async fn test_network_with_evm() { let anvil = harness.anvil().expect("Anvil should be present"); // Verify the Anvil testnet is usable by checking we can get a network config let _network = anvil.to_network(); - assert!(!anvil.default_wallet_key().expect("wallet key").is_empty()); + assert!( + !anvil.default_wallet_key().expect("wallet key").is_empty(), + "expected a non-empty default wallet key" + ); harness.teardown().await.expect("Failed to teardown"); } diff --git a/tests/e2e/payment_flow.rs b/tests/e2e/payment_flow.rs index b8f0af94..c532fab9 100644 --- a/tests/e2e/payment_flow.rs +++ b/tests/e2e/payment_flow.rs @@ -116,7 +116,10 @@ mod helper_tests { // Verify we can create wallets let wallet = env.create_funded_wallet()?; - assert!(!wallet.address().to_string().is_empty()); + assert!( + !wallet.address().to_string().is_empty(), + "expected a non-empty wallet address" + ); // Verify harness is accessible assert!(env.harness.node(0).is_some(), "Node 0 should exist"); diff --git a/tests/poc_bootstrap_stall.rs b/tests/poc_bootstrap_stall.rs index 3215aec9..22f4821d 100644 --- a/tests/poc_bootstrap_stall.rs +++ b/tests/poc_bootstrap_stall.rs @@ -90,11 +90,11 @@ async fn rejection_recorded_after_peer_removal_expires_instead_of_stalling() { // Removal cleanup runs FIRST: nothing is recorded yet, so both halves // are no-ops. - assert!(queues - .write() - .await - .remove_hint_source(&departed) - .is_empty()); + let orphaned = queues.write().await.remove_hint_source(&departed); + assert!( + orphaned.is_empty(), + "expected nothing to clean up before any hint is recorded, got {orphaned:?}" + ); assert!(!clear_capacity_rejected(&bootstrap_state, &departed).await); // The racing admission cycle then records the rejection for the @@ -126,7 +126,11 @@ async fn peer_removal_preserves_hint_with_another_live_source() { let key = [9; 32]; queues.add_pending_verify(key, entry(HashSet::from([departed, remaining]))); - assert!(queues.remove_hint_source(&departed).is_empty()); + let orphaned = queues.remove_hint_source(&departed); + assert!( + orphaned.is_empty(), + "expected the hint to survive while {remaining:?} still advertises it, got {orphaned:?}" + ); let pending = queues.remove_pending(&key).expect("hint remains pending"); assert_eq!(pending.hint_sources, HashSet::from([remaining])); diff --git a/tests/webrtc_direct_devnet.rs b/tests/webrtc_direct_devnet.rs index 033124d2..64f7d47b 100644 --- a/tests/webrtc_direct_devnet.rs +++ b/tests/webrtc_direct_devnet.rs @@ -447,7 +447,7 @@ async fn stalled_connection_times_out_and_allows_a_new_client() -> Result<(), Bo .await?; client.close().await?; assert_eq!(hello["status"], "ok"); - assert!(content.is_empty()); + assert!(content.is_empty(), "expected no content, got {content:?}"); Ok::<(), Box>(()) }) .await??; @@ -501,7 +501,7 @@ async fn encrypted_hello_and_manifest_never_disclose_verification_rpc() -> Resul client.close().await?; devnet.shutdown().await?; assert_eq!(hello["status"], "ok"); - assert!(content.is_empty()); + assert!(content.is_empty(), "expected no content, got {content:?}"); assert_eq!( hello["payment"], json!({ @@ -616,7 +616,10 @@ async fn seeded_public_file_downloads_and_paid_uploads_over_direct_node_endpoint hello["endpoint"]["multiaddr"], endpoint.endpoint.multiaddr.clone() ); - assert!(hello_content.is_empty()); + assert!( + hello_content.is_empty(), + "expected no hello content, got {hello_content:?}" + ); let (closest, closest_content) = seed_client .rpc( @@ -633,7 +636,10 @@ async fn seeded_public_file_downloads_and_paid_uploads_over_direct_node_endpoint assert_eq!(closest["status"], "ok"); assert_eq!(closest["type"], "nodes"); assert_eq!(closest["target"], public_file.address); - assert!(closest_content.is_empty()); + assert!( + closest_content.is_empty(), + "expected no closest-nodes content, got {closest_content:?}" + ); let discovered = closest["nodes"] .as_array() .and_then(|nodes| { @@ -680,7 +686,10 @@ async fn seeded_public_file_downloads_and_paid_uploads_over_direct_node_endpoint assert!(next_hop["nodes"] .as_array() .is_some_and(|nodes| !nodes.is_empty())); - assert!(next_hop_content.is_empty()); + assert!( + next_hop_content.is_empty(), + "expected no next-hop content, got {next_hop_content:?}" + ); let (header, data_map_bytes) = download_client .rpc( json!({