Skip to content

feat(cli): let clearnet-test-mode go online, so the mixnet's cost is … - #2717

Draft
zancas wants to merge 8 commits into
devfrom
debug_resource_usage
Draft

feat(cli): let clearnet-test-mode go online, so the mixnet's cost is …#2717
zancas wants to merge 8 commits into
devfrom
debug_resource_usage

Conversation

@zancas

@zancas zancas commented Aug 21, 2026

Copy link
Copy Markdown
Member

…measurable

A sync measurement cannot currently separate the mixnet's cost from the scan's, because no build offers the arm that would. ADR 0026 makes the mixnet a default capability and ADR 0024 forces Mixnet Mode on at the go-online moment, so every online session carries both; and a build without nym refuses to go online at all, answering an online consent act with "Offline Mode is its only mode". Both halves are correct policy and neither is the problem. The problem is that the two of them together leave no configuration in which a scan runs with no mixnet beside it.

That gap produced a false alarm worth recording. A comparison of the zingolib_beta_ironwood tag against dev showed roughly an eleven-fold sync slowdown, which read as a regression. It is not one: at the tag, nym was off by default, and on dev it is on and forced, so the two runs differed by an entire mixnet session — proxy spawn, quartet proof, Server-Selection Sweep, and continuous cover traffic beside the scan. A matched harness run over an identical seed, server, birthday, and window puts dev at 1966 outputs/s against the tag's 1874, so the scan engine is not slower.

clearnet-test-mode now suspends that refusal. The feature already existed for exactly this kind of act — the quarantined clearnet server-selection sweep ADR 0029 deletes from ordinary builds, compiled back in as a deliberate, review-gated test act under the 2026-08-06 ruling — and the two capabilities share one rationale, so its comment widens rather than a second flag appearing beside it.

The gate sits inside the existing #[cfg(not(feature = "nym"))] variant of get_communications, so enabling the feature alongside nym changes nothing and still only reaches the sweep. A session that takes this path announces itself at launch: it names the feature, states that every request travels clearnet, and says the indexer sees the machine's IP.

sync-bench gains --clearnet, which forwards to run-cli so one arm of a pair boots no mixnet. Run against a build without the feature it will fail fast on the refusal, which is the correct outcome rather than a silent clearnet session.

Verified across all six of this crate's feature combinations with cargo hack check --feature-powerset, including clearnet-test-mode,nym.

Fixes:

zancas and others added 8 commits August 20, 2026 10:12
…sion

An in-process harness measures the sync engine alone, which answers nothing
about a session where four proven exits bootstrap beside the scan. So each
run here drives the CLI: `makers run-cli --online` where that task exists,
and a direct `cargo build -p zingo-cli` where it does not, which is the only
way to reach the era this comparison looks back into.

An arm is a commit and a build kind, written `<commit>[:clearnet]`. Sync
rides clearnet in both kinds, because the sync engine takes a plain gRPC
indexer and never a SOCKS5 route. So one commit against itself in the two
kinds measures what the mixnet's presence costs a clearnet sync, which is a
different question from what a mixnet carrier would cost, and the one worth
asking of a wallet that bundles a proxy it must launch.

The reading comes from the engine's own clock, and the tool grafts that span
into each arm's worktree rather than requiring the arm to carry one. A fixed
patch could not do this: the call's argument list grew across the range, so
the graft anchors on the spawn both eras share, walks to its matching brace,
and surrounds whatever lies between without reading it. It refuses unless
the anchor appears exactly once and the block it found calls the sync
engine, so a step that would instrument the wrong work fails loudly rather
than producing a number. Tests pin both eras' shapes, both refusals, and a
truncated reading.

A benchmark that varies more than one thing measures nothing, so everything
but the arm is held fixed: the indexer, the seed, the window, the
performance level, and the release profile. Each arm gets its own worktree,
so neither meets the other's build artifacts. A warm-up per arm pays for the
build and is discarded. The measured rounds alternate. The window is the
caller's, since `--birthday` is required and every arm receives the one
value, so nothing here asks a moving tip where to start.

The task takes an optional reserved first word naming the session kind,
following the idiom that gives `makers test` its `packages` and `live`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… them

Measuring a long range meant scanning it to the end, which put a real
question out of reach. A wallet restored from a birthday hundreds of
thousands of blocks back scans millions of outputs, so a single run takes
minutes and twelve rounds across two arms takes hours. The window a user
actually complains about is the first minute, not the last.

`--seconds <n>` stops each scan that long after its span opens. The engine
answers a stop by returning the session's own counts, so a bounded run
reports the same quantity a finished one does, and the rate means what it
meant before. A range too long to finish becomes affordable to compare, and
the comparison lands on the phase the user waits through.

Nothing else changes. The stop rides the same stdin the tool already uses to
launch a scan on an arm that cannot consent to going online. Omitting the
option runs to completion as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wallet is a covariate, and the tool pinned it to one fundless mnemonic.
That is the right default, because a wallet holding nothing measures pure
scanning: every output is trial decrypted and discarded, and none is
decrypted twice for its note. It is the wrong fixture for reproducing
somebody else's measurement, since a wallet with history does more work per
output and the difference belongs to the wallet rather than to the code.

`--seed <mnemonic>` restores a named wallet in every arm. Both arms receive
the one value, as they do the birthday, so the wallet stays fixed across the
comparison while ceasing to be fixed across comparisons.

The default is unchanged, so an invocation that names no seed measures what
it measured before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comparability guard compared the arms' output counts, which was right
while every run scanned a window to its end: the counts should then agree,
and a disagreement means the arms did different work. Bounding a run broke
that premise. A bounded run scans whatever its seconds reach, so its counts
differ by design, and the guard fired on the tool's own arrangement. A real
comparison of Oscar's window reported 68% drift and warned that its rates
were not comparable, when nothing was wrong with them.

The guard now checks durations when a run is bounded and counts when it is
not, and it names which it checked. Bounded runs get their own tolerance,
because a stop lands after the batch in flight rather than on the instant it
is asked for, so a span overshoots by up to one batch and the spread a fixed
window allows would fire on that alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…measurable

A sync measurement cannot currently separate the mixnet's cost from the
scan's, because no build offers the arm that would. ADR 0026 makes the
mixnet a default capability and ADR 0024 forces Mixnet Mode on at the
go-online moment, so every online session carries both; and a build without
`nym` refuses to go online at all, answering an online consent act with
"Offline Mode is its only mode". Both halves are correct policy and neither
is the problem. The problem is that the two of them together leave no
configuration in which a scan runs with no mixnet beside it.

That gap produced a false alarm worth recording. A comparison of the
`zingolib_beta_ironwood` tag against dev showed roughly an eleven-fold sync
slowdown, which read as a regression. It is not one: at the tag, `nym` was
off by default, and on dev it is on and forced, so the two runs differed by
an entire mixnet session — proxy spawn, quartet proof, Server-Selection
Sweep, and continuous cover traffic beside the scan. A matched harness run
over an identical seed, server, birthday, and window puts dev at 1966
outputs/s against the tag's 1874, so the scan engine is not slower.

`clearnet-test-mode` now suspends that refusal. The feature already existed
for exactly this kind of act — the quarantined clearnet server-selection
sweep ADR 0029 deletes from ordinary builds, compiled back in as a
deliberate, review-gated test act under the 2026-08-06 ruling — and the two
capabilities share one rationale, so its comment widens rather than a second
flag appearing beside it.

The gate sits inside the existing `#[cfg(not(feature = "nym"))]` variant of
`get_communications`, so enabling the feature alongside `nym` changes
nothing and still only reaches the sweep. A session that takes this path
announces itself at launch: it names the feature, states that every request
travels clearnet, and says the indexer sees the machine's IP.

`sync-bench` gains `--clearnet`, which forwards to `run-cli` so one arm of a
pair boots no mixnet. Run against a build without the feature it will fail
fast on the refusal, which is the correct outcome rather than a silent
clearnet session.

Verified across all six of this crate's feature combinations with
`cargo hack check --feature-powerset`, including `clearnet-test-mode,nym`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	tools/workbench/src/bin/sync-ab.rs
The module header explained what the tool measures before it said how to
run it, so a reader who wanted a number had to reconstruct the invocation
from the argument parser or from the cargo-make description. A how-to now
opens the header, and the former opening text follows under a heading that
names it as the rationale it is.

The how-to gives one command per scenario: the mixnet arm alone, which is
the default because the mixnet is a default capability under ADR 0026; the
clearnet arm alone, which runs the same session with the mixnet left out of
the build; and both arms alternated round by round, which is what attributes
a difference to the mixnet rather than to the hour the run fell in. A
closing paragraph names the three constraints a first run trips over: the
required birthday and the fifteen-minute budget a session fails against,
the refusal of --clearnet together with --compare, and the two release
builds that alternating arms leave under target/sync-bench/builds.

Co-Authored-By: Claude Opus 5 (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.

1 participant