Skip to content

refactor: strip non-chunk data types, harden payment, add AlreadyStored optimization#28

Merged
mickvandijke merged 7 commits intomainfrom
saorsa-client-out
Mar 18, 2026
Merged

refactor: strip non-chunk data types, harden payment, add AlreadyStored optimization#28
mickvandijke merged 7 commits intomainfrom
saorsa-client-out

Conversation

@grumbach
Copy link
Copy Markdown
Collaborator

@grumbach grumbach commented Mar 18, 2026

Remove all non-chunk data types (GraphEntry, Pointer, Scratchpad) from the protocol layer, storage handler, and client module. Delete the deprecated QuantumClient (1121 lines) and self_encrypt wrapper (877 lines) — their functionality now lives in the saorsa-client crate. Delete the saorsa-cli binary (645 lines) — replaced by the CLI in saorsa-client.

Payment hardening:

  • Remove --disable-payment-verification flag and PaymentConfig::enabled field. Payment verification is now always on — no production bypass.
  • Remove EvmVerifierConfig::enabled field. EVM verification is unconditional.
  • Make local_rewards_address required (not Option) on PaymentVerifierConfig. Nodes must know their own rewards address.
  • Make rewards_address required in NodeBuilder — node startup fails without one instead of silently using a zero address.
  • Add cache_insert() on PaymentVerifier for test setups (replaces the old disable-EVM test bypass).
  • Add payment_verifier() accessor on AntProtocol so tests can pre-populate the cache.

Duplicate-payment prevention:

  • Add already_stored: bool field to ChunkQuoteResponse::Success. When handle_quote() detects a chunk already exists in LMDB, it sets already_stored = true on the quote response. The quote is still generated normally so clients have full pricing info — the flag simply tells them to skip the pay-then-PUT cycle. No new enum variant; existing client match arms work unchanged.

Storage additions:

  • Add put_raw(), put_overwrite(), get_raw() on LmdbStorage for non-content-addressed data (preparing for future mutable data types).
  • Fix put_overwrite() stats: reads old value length before writing and adjusts bytes_stored correctly on updates instead of unconditionally incrementing chunks_stored.

Public API changes (lib.rs):

  • Remove re-exports of QuantumClient, QuantumConfig, and all self_encrypt functions.
  • Add hex_node_id_to_encoded_peer_id re-export (moved from deleted quantum.rs into client/mod.rs).
  • Add saorsa_node::core module re-exporting PeerId, CoreNodeConfig, NodeMode, P2PNode from saorsa-core, so downstream crates can depend on saorsa-node alone.

Dependency changes:

  • Switch self_encryption from a git branch dependency to crates.io 0.35.0.

Message handler refactor (node.rs):

  • Generalize the protocol dispatch loop to use a handler_info pattern, preparing for future protocol IDs beyond CHUNK_PROTOCOL_ID.

Code quality:

  • Replace direct-indexed xor_distance loop with std::array::from_fn.
  • Gut E2E tests that depended on deleted QuantumClient / disabled-EVM bypass. Protocol-level security tests preserved. Client-level tests ported to saorsa-client.
  • Update all unit tests in verifier.rs and handler.rs to use cache_insert() instead of enabled: false.

30 files changed, +484 / −5547 lines.

…ed optimization

Remove all non-chunk data types (GraphEntry, Pointer, Scratchpad) from the
protocol layer, storage handler, and client module. Delete the deprecated
QuantumClient (891 lines) and self_encrypt wrapper (819 lines) -- their
functionality now lives in the saorsa-client crate. Delete the saorsa-cli
binary (636 lines) -- replaced by the ant CLI in saorsa-client.

Payment hardening:
- Remove --disable-payment-verification flag and PaymentConfig::enabled
  field. Payment verification is now always on -- no production bypass.
- Make local_rewards_address required (not Option) on PaymentVerifierConfig.
  Nodes must know their own rewards address.
- Add cache_insert() on PaymentVerifier for test setups (replaces the old
  disable-EVM test bypass).
- Add AlreadyStored variant to ChunkQuoteResponse. When handle_quote()
  detects a chunk already exists in LMDB, it returns AlreadyStored instead
  of generating a priced quote -- preventing duplicate payments.

Storage additions:
- Add put_raw(), put_overwrite(), get_raw() on LmdbStorage for
  non-content-addressed data (preparing for future mutable data types).

Code quality:
- Replace direct-indexed xor_distance loop with std::array::from_fn.
- Gut E2E tests that depended on deleted QuantumClient. Protocol-level
  security tests preserved. Client-level tests ported to saorsa-client.

30 files changed, +438 / -5207 lines.
Copilot AI review requested due to automatic review settings March 18, 2026 03:29
@grumbach grumbach changed the title refactor: strip non-chunk data types, harden payment, add AlreadyStor… refactor: strip non-chunk data types, harden payment, add AlreadyStored optimization Mar 18, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors saorsa-node to focus the protocol/storage layers on chunks only, deletes deprecated client/CLI components now owned by saorsa-client, and hardens the payment path by removing all “disable verification” bypasses and adding an AlreadyStored quote response to prevent duplicate payments.

Changes:

  • Remove non-chunk data types and delete the deprecated QuantumClient, self_encrypt wrapper, and saorsa-cli binary.
  • Enforce always-on EVM payment verification (remove disable flags/fields, require local rewards address) and add cache preloading helpers for tests.
  • Extend LMDB storage with raw/overwrite APIs for future non-content-addressed (mutable) data types, and update test harnesses accordingly.

Reviewed changes

Copilot reviewed 27 out of 30 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/e2e/testnet.rs Removes QuantumClient usage and switches test PUTs to pre-populating the payment cache.
tests/e2e/security_attacks.rs Removes E2E attack scenarios that depended on QuantumClient; keeps protocol-level proof-size/security tests.
tests/e2e/payment_flow.rs Removes QuantumClient-based end-to-end payment flow tests.
tests/e2e/mod.rs Adds clippy allowances to accommodate updated testnet module patterns.
tests/e2e/integration_tests.rs Removes the QuantumClient round-trip integration test.
tests/e2e/harness.rs Adds helper to prepopulate payment cache on a specific peer for test setup.
tests/e2e/data_types/scratchpad.rs Deletes scratchpad E2E tests (non-chunk type removed).
tests/e2e/data_types/pointer.rs Deletes pointer E2E tests (non-chunk type removed).
tests/e2e/data_types/mod.rs Updates data-type test module to chunk-only and removes constants for deleted types.
tests/e2e/data_types/graph_entry.rs Deletes graph-entry E2E tests (non-chunk type removed).
tests/e2e/data_types/chunk.rs Updates protocol creation calls and removes QuantumClient-driven payment tests.
tests/e2e/complete_payment_e2e.rs Removes QuantumClient-based complete payment E2E suite; leaves migration note.
src/storage/lmdb.rs Adds put_raw, put_overwrite, get_raw for non-content-addressed storage preparation.
src/storage/handler.rs Adds AlreadyStored quote path and exposes payment verifier/cache accessors used by tests.
src/payment/verifier.rs Removes enabled toggles and implements always-on verification plus cache_insert() for tests.
src/payment/mod.rs Updates module docs to reflect always-on payment and test-cache strategy.
src/node.rs Removes payment-disable paths and requires a configured rewards address; slightly refactors protocol message handling/logs.
src/lib.rs Removes old client/self-encrypt re-exports; adds saorsa_core re-exports for downstream consumers.
src/devnet.rs Updates devnet payment verifier setup to always-on EVM and requires rewards address.
src/config.rs Removes PaymentConfig::enabled and updates defaults/tests accordingly.
src/client/self_encrypt.rs Deletes the self-encryption wrapper (moved out to saorsa-client).
src/client/quantum.rs Deletes QuantumClient implementation (moved out to saorsa-client).
src/client/mod.rs Reframes module as protocol helpers and rehomes hex_node_id_to_encoded_peer_id.
src/client/data_types.rs Refactors xor_distance using std::array::from_fn.
src/bin/saorsa-node/cli.rs Removes --disable-payment-verification and always configures payment verification.
src/bin/saorsa-cli/main.rs Deletes saorsa-cli binary entrypoint (replaced by ant CLI in saorsa-client).
src/bin/saorsa-cli/cli.rs Deletes saorsa-cli clap definitions/tests.
src/ant_protocol/mod.rs Updates docs to reflect chunk-only protocol support.
src/ant_protocol/chunk.rs Adds ChunkQuoteResponse::AlreadyStored.
Cargo.toml Removes saorsa-cli bin and updates self_encryption dependency source/version.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/storage/lmdb.rs
Comment on lines +340 to +365

spawn_blocking(move || -> Result<()> {
let mut wtxn = env
.write_txn()
.map_err(|e| Error::Storage(format!("Failed to create write txn: {e}")))?;

db.put(&mut wtxn, &key, &value)
.map_err(|e| Error::Storage(format!("Failed to put record: {e}")))?;
wtxn.commit()
.map_err(|e| Error::Storage(format!("Failed to commit put: {e}")))?;
Ok(())
})
.await
.map_err(|e| Error::Storage(format!("LMDB put_overwrite task failed: {e}")))??;

{
let mut stats = self.stats.write();
stats.chunks_stored += 1;
stats.bytes_stored += data.len() as u64;
}

debug!(
"Overwritten record {} ({} bytes)",
hex::encode(address),
data.len()
);
Comment thread src/storage/lmdb.rs
Comment on lines +355 to +359
{
let mut stats = self.stats.write();
stats.chunks_stored += 1;
stats.bytes_stored += data.len() as u64;
}
Comment thread src/storage/handler.rs

/// Get a reference to the payment verifier.
#[must_use]
pub fn payment_verifier(&self) -> &PaymentVerifier {
Comment thread src/payment/verifier.rs
///
/// This marks the address as already paid, so subsequent `verify_payment`
/// calls will return `CachedAsVerified` without on-chain verification.
/// Useful for test setups where real EVM payment is not needed.
Comment thread tests/e2e/harness.rs
Comment on lines +357 to +366
for node in self.network.nodes() {
if let Some(ref p2p) = node.p2p_node {
if p2p.peer_id() == peer_id {
if let Some(ref protocol) = node.ant_protocol {
protocol.payment_verifier().cache_insert(*address);
}
return;
}
}
}
Comment thread src/storage/handler.rs Outdated
Comment on lines +234 to +239
// If the chunk is already stored, no payment is needed
match self.storage.exists(&request.address) {
Ok(true) => {
debug!("Chunk {addr_hex} already stored — returning AlreadyStored");
return ChunkQuoteResponse::AlreadyStored;
}
Comment thread src/storage/lmdb.rs
Comment on lines +250 to +267
/// Store data at the given address without content-address verification.
///
/// Use this for data types where the address is not `BLAKE3(content)`.
///
/// # Errors
///
/// Returns an error if the write fails.
pub async fn put_raw(&self, address: &XorName, data: &[u8]) -> Result<bool> {
// Fast-path duplicate check
if self.exists(address)? {
trace!("Record {} already exists", hex::encode(address));
self.stats.write().duplicates += 1;
return Ok(false);
}

let key = *address;
let value = data.to_vec();
let env = self.env.clone();
Add MultiAddr, NodeMode, IPDiversityConfig, MlDsa65, NodeIdentity,
and P2PEvent to the pub mod core re-exports so saorsa-client can
drop its direct saorsa-core dependency.
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 18, 2026

Greptile Summary

This PR is a significant cleanup and hardening refactor: it strips ~2,600 lines of deprecated client code (QuantumClient, self_encrypt wrapper, saorsa-cli), removes all non-chunk data type protocol support, and tightens the payment pipeline by making EVM verification unconditional and rewards_address a required field. The codebase is substantially simpler as a result.

Key changes:

  • Payment hardening: PaymentConfig::enabled and EvmVerifierConfig::enabled bypass flags removed; rewards_address is now required at node startup; cache_insert() replaces the old disable-EVM test bypass.
  • AlreadyStored optimisation: handle_quote now checks LMDB before generating a priced quote and signals already_stored: true in the response, allowing clients to skip a payment cycle for chunks already present on the node.
  • New raw-storage primitives: put_raw, put_overwrite, and get_raw on LmdbStorage prepare the storage layer for future mutable data types without requiring content-address verification.
  • Public API cleanup: Removes QuantumClient/QuantumConfig re-exports and adds a saorsa_node::core re-export module so downstream crates can avoid a direct saorsa-core dependency.
  • Wire protocol concern: Adding already_stored: bool to ChunkQuoteResponse::Success changes the postcard-serialized layout in a backwards-incompatible way without bumping PROTOCOL_VERSION or CHUNK_PROTOCOL_ID.
  • Test bypass still reachable in production: PaymentVerifier::cache_insert() and AntProtocol::payment_verifier() are public and not #[cfg(test)]-gated, so the payment cache can still be pre-populated from production code.

Confidence Score: 3/5

  • Safe to merge in isolated deployments, but the postcard wire-format break risks incompatibility in any mixed-version rollout.
  • Two notable issues hold back a higher score: (1) ChunkQuoteResponse::Success gains a new already_stored field with no protocol version bump — this is a backwards-incompatible postcard wire change that will break any client or node still on the old format; (2) cache_insert() and payment_verifier() remain public non-test APIs that can silently bypass EVM verification in production builds, somewhat undermining the stated hardening goal. The deletions are clean, the payment validation logic itself is solid, and all unit tests use the new cache_insert pattern correctly.
  • src/ant_protocol/chunk.rs (wire protocol break), src/storage/handler.rs and src/payment/verifier.rs (test-bypass APIs not gated by #[cfg(test)])

Important Files Changed

Filename Overview
src/ant_protocol/chunk.rs Adds already_stored: bool field to ChunkQuoteResponse::Success — a backwards-incompatible postcard wire change without a protocol version bump.
src/payment/verifier.rs Removes enabled bypass field, adds cache_insert() for test-only cache pre-population. Security hardening is solid except cache_insert is not #[cfg(test)]-gated.
src/storage/handler.rs Adds AlreadyStored optimisation to handle_quote, payment_verifier() accessor for tests, and put_local test helper. The payment_verifier() accessor is not #[cfg(test)]-gated.
src/storage/lmdb.rs Adds put_raw, put_overwrite, and get_raw for non-content-addressed data. Stats tracking mixes chunk and raw-record counters under chunks_stored.
src/node.rs Rewards address is now required at startup; handler_info pattern prepares for future protocol IDs. Clean and correct.
Cargo.toml Switches self_encryption from a git branch to crates.io 0.35.0 and removes saorsa-cli binary entry. Clean dependency changes.

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant N as Node (AntProtocol)
    participant L as LmdbStorage
    participant Q as QuoteGenerator
    participant EVM as Arbitrum EVM

    C->>N: ChunkQuoteRequest(address, data_size)
    N->>L: exists(address)?
    L-->>N: true / false

    alt already_stored = true
        N->>Q: create_quote(address, …)
        Q-->>N: PaymentQuote
        N-->>C: ChunkQuoteResponse::Success { quote, already_stored: true }
        Note over C: Skip payment & PUT cycle
    else already_stored = false
        N->>Q: create_quote(address, …)
        Q-->>N: PaymentQuote
        N-->>C: ChunkQuoteResponse::Success { quote, already_stored: false }
        C->>EVM: payForQuotes(quote)
        EVM-->>C: payment_proof
        C->>N: ChunkPutRequest(address, content, payment_proof)
        N->>N: validate size & BLAKE3(content)==address
        N->>L: exists(address)?
        L-->>N: false
        N->>N: PaymentVerifier::verify_payment(address, proof)
        N->>EVM: verify_data_payment(digest)
        EVM-->>N: ok
        N->>L: put(address, content)
        L-->>N: stored
        N-->>C: ChunkPutResponse::Success { address }
    end
Loading

Comments Outside Diff (3)

  1. src/storage/handler.rs, line 294-298 (link)

    payment_verifier() accessor is not test-only

    payment_verifier() is an unrestricted public method that exposes cache_insert() to any caller — including production code. The PR's stated goal is "no production bypass," but both this accessor and PaymentVerifier::cache_insert (verifier.rs line 249) are compilable and callable in release builds.

    Marking these #[cfg(test)] would make the intent explicit and prevent future callers from accidentally (or intentionally) bypassing EVM verification in a production node:

    The same #[cfg(test)] guard should be applied to PaymentVerifier::cache_insert in src/payment/verifier.rs.

  2. src/ant_protocol/chunk.rs, line 224-238 (link)

    Breaking postcard wire format change without a protocol version bump

    Adding already_stored: bool to the ChunkQuoteResponse::Success struct variant is a backwards-incompatible wire protocol change under postcard serialization.

    Postcard serializes struct fields in order, so:

    • Old wire format: Success = [variant_tag, len(quote), quote_bytes…]
    • New wire format: Success = [variant_tag, len(quote), quote_bytes…, already_stored_byte]

    A node running the old protocol will fail to decode new Success messages (unexpected trailing byte). A new node will fail to decode old Success messages (missing already_stored field). This affects any mixed-version deployment where both old and new nodes coexist.

    Despite this change, PROTOCOL_VERSION remains 1 and CHUNK_PROTOCOL_ID remains "saorsa/ant/chunk/v1", so peers have no way to negotiate compatibility.

    Options to address this:

    1. Bump PROTOCOL_VERSION and CHUNK_PROTOCOL_ID (e.g. "saorsa/ant/chunk/v2") so old and new nodes can coexist.
    2. Use a separate AlreadyStored variant instead of adding a field to Success, and handle old clients seeing an unknown variant gracefully.
    3. If this is intentionally a hard cut-over (no mixed-version rollout planned), document this explicitly in the protocol constants.
  3. src/storage/lmdb.rs, line 257-324 (link)

    put_raw updates chunks_stored stat, conflating chunks and raw records

    put_raw is described as being "for non-content-addressed data types" (mutable records, future Pointers, Scratchpads, etc.), but on a successful write it increments stats.chunks_stored and stats.bytes_stored — the same counters used by put for content-addressed chunks. This will make it impossible to distinguish chunk-storage from raw-record-storage in the stats output at StorageStats.

    Consider adding records_stored / bytes_records_stored counters to StorageStats, or documenting clearly that these counters now cover all entry types rather than chunks only. Similarly, the max_chunks capacity limit in put_raw silently caps all stored entries regardless of type — the field name makes this surprising to operators.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/storage/handler.rs
Line: 294-298

Comment:
**`payment_verifier()` accessor is not test-only**

`payment_verifier()` is an unrestricted public method that exposes `cache_insert()` to any caller — including production code. The PR's stated goal is "no production bypass," but both this accessor and `PaymentVerifier::cache_insert` (verifier.rs line 249) are compilable and callable in release builds.

Marking these `#[cfg(test)]` would make the intent explicit and prevent future callers from accidentally (or intentionally) bypassing EVM verification in a production node:

```suggestion
    /// Get a reference to the payment verifier.
    #[must_use]
    #[cfg(test)]
    pub fn payment_verifier(&self) -> &PaymentVerifier {
        &self.payment_verifier
    }
```

The same `#[cfg(test)]` guard should be applied to `PaymentVerifier::cache_insert` in `src/payment/verifier.rs`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/ant_protocol/chunk.rs
Line: 224-238

Comment:
**Breaking postcard wire format change without a protocol version bump**

Adding `already_stored: bool` to the `ChunkQuoteResponse::Success` struct variant is a **backwards-incompatible wire protocol change** under postcard serialization.

Postcard serializes struct fields in order, so:
- **Old wire format:** `Success` = `[variant_tag, len(quote), quote_bytes…]`
- **New wire format:** `Success` = `[variant_tag, len(quote), quote_bytes…, already_stored_byte]`

A node running the old protocol will fail to decode new `Success` messages (unexpected trailing byte). A new node will fail to decode old `Success` messages (missing `already_stored` field). This affects any mixed-version deployment where both old and new nodes coexist.

Despite this change, `PROTOCOL_VERSION` remains `1` and `CHUNK_PROTOCOL_ID` remains `"saorsa/ant/chunk/v1"`, so peers have no way to negotiate compatibility.

Options to address this:
1. Bump `PROTOCOL_VERSION` and `CHUNK_PROTOCOL_ID` (e.g. `"saorsa/ant/chunk/v2"`) so old and new nodes can coexist.
2. Use a separate `AlreadyStored` variant instead of adding a field to `Success`, and handle old clients seeing an unknown variant gracefully.
3. If this is intentionally a hard cut-over (no mixed-version rollout planned), document this explicitly in the protocol constants.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/storage/lmdb.rs
Line: 257-324

Comment:
**`put_raw` updates `chunks_stored` stat, conflating chunks and raw records**

`put_raw` is described as being "for non-content-addressed data types" (mutable records, future Pointers, Scratchpads, etc.), but on a successful write it increments `stats.chunks_stored` and `stats.bytes_stored` — the same counters used by `put` for content-addressed chunks. This will make it impossible to distinguish chunk-storage from raw-record-storage in the stats output at `StorageStats`.

Consider adding `records_stored` / `bytes_records_stored` counters to `StorageStats`, or documenting clearly that these counters now cover all entry types rather than chunks only. Similarly, the `max_chunks` capacity limit in `put_raw` silently caps all stored entries regardless of type — the field name makes this surprising to operators.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix: replace Already..."

…write stats

- ChunkQuoteResponse::AlreadyStored was a new variant that clients
  matching only Success/Error would miss, causing silent timeouts.
  Replace with `already_stored: bool` on Success so existing client
  match arms work unchanged.

- handle_quote() checks storage.exists() and sets the flag; quote is
  still generated normally so the client has full pricing info.

- put_overwrite() now reads the old value length before writing and
  adjusts bytes_stored correctly on updates instead of unconditionally
  incrementing chunks_stored + bytes_stored.
Copilot AI review requested due to automatic review settings March 18, 2026 04:21
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors saorsa-node to be chunk-only at the protocol/client/test layers, removes deprecated client/CLI functionality now housed in saorsa-client, and hardens payment verification by making EVM verification unconditional and requiring a configured rewards address.

Changes:

  • Remove non-chunk protocol/client/test support and delete deprecated QuantumClient, self_encrypt wrapper, and the saorsa-cli binary.
  • Harden payments: eliminate disable-bypasses, require local_rewards_address, and add test-only cache priming via PaymentVerifier::cache_insert() + AntProtocol::payment_verifier().
  • Add LMDB raw/overwrite APIs and extend quote responses with an already_stored signal.

Reviewed changes

Copilot reviewed 27 out of 30 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/e2e/testnet.rs Removes QuantumClient usage and switches tests to cache pre-population for payment acceptance.
tests/e2e/security_attacks.rs Removes E2E flows dependent on QuantumClient / on-chain payment wiring; keeps protocol-level attack tests.
tests/e2e/payment_flow.rs Deletes QuantumClient-based payment E2E flows (migration pending).
tests/e2e/mod.rs Adjusts clippy allowances for the testnet module.
tests/e2e/integration_tests.rs Removes QuantumClient round-trip E2E test now that the client moved out.
tests/e2e/harness.rs Adds helper to prepopulate payment cache for a peer in tests.
tests/e2e/data_types/scratchpad.rs Deletes scratchpad E2E tests (non-chunk types removed).
tests/e2e/data_types/pointer.rs Deletes pointer E2E tests (non-chunk types removed).
tests/e2e/data_types/mod.rs Updates data-types test module to be chunk-only.
tests/e2e/data_types/graph_entry.rs Deletes graph entry E2E tests (non-chunk types removed).
tests/e2e/data_types/chunk.rs Updates chunk E2E tests to prime payment cache instead of disabling verification.
tests/e2e/complete_payment_e2e.rs Removes QuantumClient-based “complete payment” E2E tests and leaves a migration note.
src/storage/lmdb.rs Adds put_raw, put_overwrite, and get_raw for non-content-addressed storage.
src/storage/handler.rs Adds quote “already stored” check and exposes a payment_verifier() accessor for tests.
src/payment/verifier.rs Makes EVM verification always-on; requires local_rewards_address; adds cache_insert().
src/payment/mod.rs Updates payment module docs for always-on verification and test cache priming.
src/node.rs Removes payment-disable paths; requires rewards address at startup; refactors protocol dispatch loop.
src/lib.rs Removes QuantumClient/self_encrypt re-exports; adds core re-exports and hex_node_id_to_encoded_peer_id.
src/devnet.rs Updates devnet payment verifier initialization for always-on EVM verification.
src/config.rs Removes PaymentConfig::enabled and updates docs/tests accordingly.
src/client/self_encrypt.rs Deletes the in-crate self-encryption wrapper (moved to saorsa-client).
src/client/quantum.rs Deletes deprecated QuantumClient implementation (moved to saorsa-client).
src/client/mod.rs Reorients module as protocol helpers; keeps hex_node_id_to_encoded_peer_id locally.
src/client/data_types.rs Refactors xor_distance to use std::array::from_fn.
src/bin/saorsa-node/cli.rs Removes --disable-payment-verification and updates payment config wiring.
src/bin/saorsa-cli/main.rs Deletes deprecated saorsa-cli binary.
src/bin/saorsa-cli/cli.rs Deletes deprecated saorsa-cli CLI definitions/tests.
src/ant_protocol/mod.rs Updates protocol docs to reflect chunk-only support.
src/ant_protocol/chunk.rs Extends quote response format with already_stored.
Cargo.toml Removes saorsa-cli bin and switches self_encryption to crates.io 0.35.0.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ant_protocol/chunk.rs
Comment on lines 223 to 235
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChunkQuoteResponse {
/// Quote generated successfully.
///
/// When `already_stored` is `true` the node already holds this chunk and no
/// payment is required — the client should skip the pay-then-PUT cycle for
/// this address. The quote is still included for informational purposes.
Success {
/// Serialized `PaymentQuote`.
quote: Vec<u8>,
/// `true` when the chunk already exists on this node (skip payment).
already_stored: bool,
},
Comment thread src/storage/lmdb.rs
Comment on lines +335 to +339
pub async fn put_overwrite(&self, address: &XorName, data: &[u8]) -> Result<()> {
let key = *address;
let value = data.to_vec();
let env = self.env.clone();
let db = self.db;
Comment thread tests/e2e/harness.rs
Comment on lines +352 to +366
pub fn prepopulate_payment_cache_for_peer(
&self,
peer_id: &saorsa_core::identity::PeerId,
address: &XorName,
) {
for node in self.network.nodes() {
if let Some(ref p2p) = node.p2p_node {
if p2p.peer_id() == peer_id {
if let Some(ref protocol) = node.ant_protocol {
protocol.payment_verifier().cache_insert(*address);
}
return;
}
}
}
Comment thread src/devnet.rs
Comment on lines +581 to 586
let evm_config = EvmVerifierConfig {
network: config
.evm_network
.clone()
.unwrap_or(EvmNetwork::ArbitrumOne),
};
Comment thread src/storage/handler.rs
};

if already_stored {
debug!("Chunk {addr_hex} already stored — returning quote with already_stored=true");
@grumbach
Copy link
Copy Markdown
Collaborator Author

@claude @greptile please review

@claude
Copy link
Copy Markdown

claude Bot commented Mar 18, 2026

Claude finished @grumbach's task —— View job


Review of PR #28

Todo List:

  • Examine PR diff and understand scope of changes
  • Review payment verification hardening changes
  • Review AlreadyStored optimization implementation
  • Review new storage methods (put_raw, put_overwrite, get_raw)
  • Review test changes and coverage
  • Provide comprehensive review feedback

Overview

This is a well-executed refactoring that successfully removes ~5,200 lines of deprecated code while hardening the payment verification system. The PR correctly enforces payment-always-on at the type level and adds sensible optimizations for duplicate storage prevention.

High-Level Assessment

Strengths:

  • Clean removal of deprecated client code (QuantumClient, self_encrypt wrapper, saorsa-cli)
  • Strong payment hardening by making verification unconditional
  • Good test coverage updates using cache preloading pattern
  • Sensible AlreadyStored optimization to prevent duplicate payments

Areas of Concern:
The automated reviewers (Greptile and Copilot) have identified legitimate issues that should be addressed. I'll highlight the most critical ones below.


Critical Issues

1. put_overwrite Stats Inflation (src/storage/lmdb.rs:361-373)

Issue: The put_overwrite method has correct logic for updating bytes_stored based on the delta when overwriting (lines 365-368), but it always increments chunks_stored when inserting a new record (line 371). However, this method can also overwrite existing records, and the code path at lines 361-373 will increment chunks_stored even when prev_len is Some(_), meaning it's an update not an insert.

Wait, looking more carefully at the code:

if let Some(prev) = old_len {
    // Update: adjust byte count, chunk count stays the same.
    stats.bytes_stored = stats
        .bytes_stored
        .saturating_sub(prev as u64)
        .saturating_add(data.len() as u64);
} else {
    // New record.
    stats.chunks_stored += 1;
    stats.bytes_stored += data.len() as u64;
}

Actually, the code is correct - it only increments chunks_stored in the else branch (new record). The Greptile comment appears to be incorrect on this point. The logic properly distinguishes between updates and inserts.

Status: No issue here - code is correct.

2. AlreadyStored Protocol Handling (src/ant_protocol/chunk.rs:230-234)

Issue: The PR changed ChunkQuoteResponse to include an already_stored: bool field rather than a separate enum variant as described in the PR description:

Success {
    quote: Vec<u8>,
    already_stored: bool,
}

Concerns:

  • The PR description says "Add AlreadyStored variant to ChunkQuoteResponse" but the actual implementation adds a boolean field instead
  • This is a wire protocol change that affects the saorsa-client crate
  • Clients must be updated to check the already_stored flag to skip payment when true
  • The PR description should be updated to match the actual implementation, or the implementation should be changed to use a separate variant

Recommendation: Update the PR description to accurately describe the implementation (already_stored: bool field, not a separate variant). Confirm that saorsa-client has been updated to handle this field correctly.

3. Duplicate Trace Log in put_raw (src/storage/lmdb.rs:260 and 306)

Issue: There are two trace logs with identical messages when a duplicate is detected:

  • Line 260: Fast-path duplicate check (before transaction)
  • Line 306: Transaction-level duplicate check (after losing write race)

Impact: Minor - just duplicate logging in edge cases where two writers race

Recommendation: Remove the second trace log at line 306 since it's redundant.

4. Public payment_verifier() Accessor (src/storage/handler.rs:296-298)

Issue: The payment_verifier() method exposes public access to the payment verifier, which allows any caller with an AntProtocol handle to call cache_insert() and bypass EVM verification.

Recommendation: Consider one of these options:

  • Make it pub(crate) if only used within the crate
  • Gate it with #[cfg(test)] if only needed for tests
  • Add a comment documenting that this is intentionally public for test utilities

The test harness at tests/e2e/harness.rs:366 uses this accessor, so it needs to remain accessible to the test suite.


Medium Priority Issues

5. Missing Test Coverage for AlreadyStored Path

Issue: The new handle_quote() logic that returns already_stored: true when a chunk exists (lines 234-246 in handler.rs) doesn't have a dedicated unit test.

Recommendation: Add a test that:

  1. Stores a chunk via put_local() or directly into LMDB
  2. Requests a quote for that address
  3. Asserts the response has already_stored: true

This would prevent regressions in this important payment optimization.

6. No Tests for New Storage Methods

Issue: The new put_raw, put_overwrite, and get_raw methods in lmdb.rs don't have unit tests.

Recommendation: Add tests covering:

  • put_raw accepting mismatched address/content
  • put_raw enforcing max_chunks capacity
  • put_overwrite correctly updating bytes_stored delta
  • put_overwrite creating new records when key doesn't exist
  • get_raw returning exact stored bytes without verification

Minor Issues

7. prepopulate_payment_cache_for_peer Silent Failure (tests/e2e/harness.rs:366)

Issue: The test helper silently returns if the peer_id isn't found, which could hide test setup errors.

Recommendation: Consider panicking or returning a Result to make failures explicit in test code.


Positive Observations

  1. Payment Hardening: Removing PaymentConfig::enabled and EvmVerifierConfig::enabled is excellent - makes "payment always on" an invariant at the type level rather than runtime check.

  2. Test Strategy: Using cache_insert() to simulate previously-paid data in tests is the right approach - clean and explicit.

  3. Code Deletion: Removing 5,547 lines of deprecated code is a significant win for maintainability.

  4. Dependency Hygiene: Switching from git branch to crates.io self_encryption 0.35.0 is good practice.

  5. Stats Accuracy: Despite Greptile's comment, the put_overwrite stats logic is actually correct - only increments chunks_stored for new records.


Recommendations Summary

Must Address Before Merge:

  • ✅ Update PR description to match actual implementation (already_stored: bool not AlreadyStored variant)
  • ✅ Confirm saorsa-client handles the already_stored field correctly

Should Address Before Merge:

  • Add unit test for already_stored: true quote response path
  • Add tests for new storage methods (put_raw, put_overwrite, get_raw)
  • Document or restrict access to payment_verifier() accessor

Nice to Have:

  • Remove duplicate trace log in put_raw (line 306)
  • Make prepopulate_payment_cache_for_peer fail loudly on missing peer

Conclusion

This is a solid refactoring with strong type-level safety improvements. The payment hardening changes are well-designed and correctly implemented. The main concerns are around protocol documentation accuracy and test coverage for new code paths.

Overall Assessment: Approve with minor changes requested. The flagged issues are not blocking but should be addressed to maintain code quality and prevent future issues.


Job Run | Branch: saorsa-client-out

grumbach and others added 2 commits March 18, 2026 17:45
…ct payment_verifier()

- Add test_quote_already_stored_flag: verifies already_stored=true for
  existing chunks and false for new ones in the quote response path.
  Wires ML-DSA-65 signing in create_test_protocol so quote requests work.

- Add test_put_raw_and_get_raw, test_get_raw_not_found,
  test_put_overwrite_new_record, test_put_overwrite_updates_stats_correctly:
  full coverage for the new storage methods including stats correctness.

- Mark payment_verifier() as #[doc(hidden)] with clear test-only docs.

- Remove duplicate trace log in put_raw (race-condition path).

- Make prepopulate_payment_cache_for_peer panic on missing peer
  to catch test configuration errors early.
…vm field

- Gate `PaymentVerifier::cache_insert()` and `AntProtocol::payment_verifier()`
  behind `#[cfg(any(test, feature = "test-utils"))]` so production builds
  cannot bypass on-chain payment verification.
- Add `test-utils` feature to Cargo.toml; E2E tests require it.
- Remove dead `DevnetConfig::enable_evm` field (no longer affects behavior
  since `EvmVerifierConfig::enabled` was removed).
- Update CLAUDE.md payment docs to reflect hardened payment policy.
- Remove unnecessary `.to_string()` allocation in message dispatch hot path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 18, 2026 10:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors saorsa-node to remove deprecated non-chunk protocol/client layers (including QuantumClient, self_encrypt, and the old CLI), hardens payment verification by making EVM verification unconditional and requiring a rewards address, and adds a quote-time optimization to signal when a chunk is already stored.

Changes:

  • Remove deprecated client/CLI/self-encryption code paths and update E2E tests to the new testing strategy (cache prepopulation instead of “disable EVM”).
  • Enforce “payment verification always on” across config, node startup, devnet, and verifier logic (with new test-only cache insertion helpers).
  • Add storage APIs for non-content-addressed records (put_raw, put_overwrite, get_raw) and introduce already_stored flag on quote success responses.

Reviewed changes

Copilot reviewed 28 out of 33 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/e2e/testnet.rs Removes QuantumClient usage; updates PUT flows to pre-populate payment cache for tests.
tests/e2e/security_attacks.rs Removes payment E2E attack scenarios dependent on deleted client/bypass paths; keeps protocol-level tests.
tests/e2e/payment_flow.rs Removes QuantumClient-based payment flow tests (migration pending).
tests/e2e/mod.rs Adds clippy allowances for the testnet module.
tests/e2e/integration_tests.rs Removes QuantumClient round-trip E2E test.
tests/e2e/harness.rs Adds helper to prepopulate a node’s payment cache by peer ID.
tests/e2e/data_types/mod.rs Updates data-type E2E module to chunk-only and removes old constants/docs.
tests/e2e/data_types/chunk.rs Updates chunk E2E tests to prepopulate payment cache before unpaid PUTs.
tests/e2e/data_types/scratchpad.rs Deletes scratchpad E2E tests (type removed from protocol layer).
tests/e2e/data_types/pointer.rs Deletes pointer E2E tests (type removed from protocol layer).
tests/e2e/data_types/graph_entry.rs Deletes graph entry E2E tests (type removed from protocol layer).
tests/e2e/complete_payment_e2e.rs Removes old complete payment E2E (pending reimplementation in saorsa-client).
src/storage/lmdb.rs Adds raw record APIs and fixes overwrite stats accounting; adds tests.
src/storage/handler.rs Adds already_stored flag on quote responses; exposes payment_verifier() accessor for tests; updates unit tests to use cache insertion.
src/payment/verifier.rs Removes all “enabled/disable” bypasses; requires local_rewards_address; adds test-only cache_insert.
src/payment/mod.rs Updates documentation to reflect always-on verification and test cache prepopulation.
src/node.rs Removes payment disable paths; makes rewards address required at startup; refactors protocol dispatch.
src/lib.rs Removes deprecated re-exports; re-exports hex_node_id_to_encoded_peer_id and introduces saorsa_node::core re-export module.
src/devnet.rs Removes EVM enable toggle; configures verifier unconditionally and requires rewards address.
src/config.rs Removes PaymentConfig::enabled; updates defaults/tests for always-on verification model.
src/client/mod.rs Removes QuantumClient module; keeps low-level protocol helpers and moves hex_node_id_to_encoded_peer_id here.
src/client/data_types.rs Refactors xor_distance implementation to std::array::from_fn.
src/bin/saorsa-node/cli.rs Removes --disable-payment-verification; updates payment config wiring.
src/bin/saorsa-devnet/main.rs Updates Anvil wiring to set only evm_network (no enable flag).
src/bin/saorsa-devnet/cli.rs Updates --enable-evm help text to match always-on verification behavior.
src/bin/saorsa-cli/main.rs Deletes deprecated saorsa-cli binary.
src/bin/saorsa-cli/cli.rs Deletes deprecated saorsa-cli CLI definitions/tests.
src/ant_protocol/mod.rs Updates protocol docs to reflect chunk-only support.
src/ant_protocol/chunk.rs Adds already_stored field to quote success response and documents semantics.
Cargo.toml Removes saorsa-cli bin; switches self_encryption to crates.io; gates E2E tests behind test-utils.
CLAUDE.md Updates policy documentation to “payment verification always on” and the new testing approach.

💡 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.

Comment thread src/ant_protocol/chunk.rs
Comment on lines 230 to +234
Success {
/// Serialized `PaymentQuote`.
quote: Vec<u8>,
/// `true` when the chunk already exists on this node (skip payment).
already_stored: bool,
Comment thread tests/e2e/harness.rs
Comment on lines +363 to +367
if let Some(ref p2p) = node.p2p_node {
if p2p.peer_id() == peer_id {
if let Some(ref protocol) = node.ant_protocol {
protocol.payment_verifier().cache_insert(*address);
}
Comment thread src/storage/handler.rs
let ml_dsa = MlDsa65::new();
ml_dsa
.sign(&sk, msg)
.map_or_else(|_| vec![], |sig| sig.as_bytes().to_vec())
Minor version bump reflecting new features and breaking changes since v0.3.1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mickvandijke mickvandijke merged commit d06df74 into main Mar 18, 2026
15 checks passed
@mickvandijke mickvandijke deleted the saorsa-client-out branch March 18, 2026 10:59
mickvandijke added a commit that referenced this pull request Apr 1, 2026
Add unit and e2e tests covering the remaining Section 18 scenarios:

Unit tests (32 new):
- Quorum: #4 fail→abandoned, #16 timeout→inconclusive, #27 single-round
  dual-evidence, #28 dynamic threshold undersized, #33 batched per-key,
  #34 partial response unresolved, #42 quorum-derived paid-list auth
- Admission: #5 unauthorized peer, #7 out-of-range rejected
- Config: #18 invalid config rejected, #26 dynamic paid threshold
- Scheduling: #8 dedup safety, #8 replica/paid collapse
- Neighbor sync: #35 round-robin cooldown skip, #36 cycle completion,
  #38 snapshot stability mid-join, #39 unreachable removal + slot fill,
  #40 cooldown peer removed, #41 cycle termination guarantee,
  consecutive rounds, cycle preserves sync times
- Pruning: #50 hysteresis prevents premature delete, #51 timestamp reset
  on heal, #52 paid/record timestamps independent, #23 entry removal
- Audit: #19/#53 partial failure mixed responsibility, #54 all pass,
  #55 empty failure discard, #56 repair opportunity filter,
  response count validation, digest uses full record bytes
- Types: #13 bootstrap drain, repair opportunity edge cases,
  terminal state variants
- Bootstrap claims: #46 first-seen recorded, #49 cleared on normal

E2e tests (4 new):
- #2 fresh offer with empty PoP rejected
- #5/#37 neighbor sync request returns response
- #11 audit challenge multi-key (present + absent)
- Fetch not-found for non-existent key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mickvandijke added a commit that referenced this pull request Apr 1, 2026
Add unit and e2e tests covering the remaining Section 18 scenarios:

Unit tests (32 new):
- Quorum: #4 fail→abandoned, #16 timeout→inconclusive, #27 single-round
  dual-evidence, #28 dynamic threshold undersized, #33 batched per-key,
  #34 partial response unresolved, #42 quorum-derived paid-list auth
- Admission: #5 unauthorized peer, #7 out-of-range rejected
- Config: #18 invalid config rejected, #26 dynamic paid threshold
- Scheduling: #8 dedup safety, #8 replica/paid collapse
- Neighbor sync: #35 round-robin cooldown skip, #36 cycle completion,
  #38 snapshot stability mid-join, #39 unreachable removal + slot fill,
  #40 cooldown peer removed, #41 cycle termination guarantee,
  consecutive rounds, cycle preserves sync times
- Pruning: #50 hysteresis prevents premature delete, #51 timestamp reset
  on heal, #52 paid/record timestamps independent, #23 entry removal
- Audit: #19/#53 partial failure mixed responsibility, #54 all pass,
  #55 empty failure discard, #56 repair opportunity filter,
  response count validation, digest uses full record bytes
- Types: #13 bootstrap drain, repair opportunity edge cases,
  terminal state variants
- Bootstrap claims: #46 first-seen recorded, #49 cleared on normal

E2e tests (4 new):
- #2 fresh offer with empty PoP rejected
- #5/#37 neighbor sync request returns response
- #11 audit challenge multi-key (present + absent)
- Fetch not-found for non-existent key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

3 participants