Skip to content

v0.8.3: node fixes (#143 #144 #146 #149) - #150

Merged
dendisuhubdy merged 4 commits into
mainfrom
fix/v083-node
Oct 28, 2026
Merged

dendisuhubdy merged 4 commits into
mainfrom
fix/v083-node

Conversation

@dendisuhubdy

Copy link
Copy Markdown
Member

Four fixes from the chain-21 network experiment, one commit each.

#143: no ANSI colour in journald; unit Description names no chain

  • rand-node (logs to stdout) and rand-prover (logs to stderr) call .with_ansi(log_ansi(<stream>.is_terminal(), NO_COLOR)). Colour is on only at a terminal, and never when NO_COLOR is set and not empty (no-color.org). The rand CLI has no tracing subscriber, so it needs no change.
  • deploy: cutover-droplet-chain14.sh wrote Description=RAND full node ($NAME, chain 14). Later cutovers only sed the datadir suffix, so live units still say "chain 14". The script now writes RAND full node ($NAME), and update-droplet.sh (the roll) rewrites an existing …, chain N) to RAND full node (<host>) and runs daemon-reload.
  • Test: log_ansi_tests::colour_only_at_a_terminal_and_never_under_no_color (rand-node) and tests::log_colour_only_at_a_terminal_and_never_under_no_color (rand-prover).

#144: duplicate-publish WARN spam

  • Source: broadcast_status. It runs on every PeerConnected and on the 3 s status tick. Status { height, head_hash, view, floor } names no author, and the gossip message id was blake3 of the bytes alone. So an unchanged status had the same id as last time, and every node at one head published the same id as every other node. The second publish was dropped with libp2p's own "Not publishing a message that has already been published" WARN. That produced the burst on connect and the steady trickle. It was also a real loss: the status meant for a newly connected peer never went out. The PeerBinding announce is not the cause, since it is re-signed with the clock every time.
  • Fix: message_id() covers the author and sequence number on the status topic only. Transactions, consensus messages and bindings keep the content id, so one transaction forwarded by two peers is still one message. In a mixed fleet, ids are compared only in IHAVE/IWANT, so an old peer may at worst pull a status it already holds (a few dozen bytes).
  • PublishError::Duplicate is now logged at debug at the call site.
  • Tests: a_status_id_covers_its_author_and_sequence_and_every_other_id_is_the_content, an_unchanged_status_published_again_is_delivered_again. In tests/network.rs, the two tests that pinned a status's content id now pin it on a transaction, and add the contrast that a status published three times is three deliveries.

#146: transaction gossip no longer names its author

  • A second gossipsub behaviour, RandBehaviour::tx_gossip, carries transactions only. It uses MessageAuthenticity::Anonymous under ValidationMode::Anonymous, so a message carrying a source, seqno or signature is refused. It runs on its own protocol /rand/{chain}/txsub (two behaviours cannot share /meshsub), topic rand/{chain}/tx/2. Consensus, status and peer bindings stay Signed.
  • The NetworkBehaviour derive maps both behaviours' events to one variant, so the lane is taken from the topic: each behaviour subscribes only its own topics, and gossipsub drops messages for unsubscribed topics. GossipId.lane routes each verdict back to the behaviour that delivered the message.
  • A transaction's from is now its forwarder on either lane. Rate limiting is unchanged: it already keyed on propagation_source (Peer::tx_bucket).
  • libp2p 0.57.0 / libp2p-gossipsub 0.50.0 (Cargo.lock).

Rollout plan (also in docs/deploy.md, "The anonymous transaction lane")

  1. Roll v0.8.3 normally, flag off (the default). A v0.8.3 node subscribes to both topics, publishes each transaction on both, and relays on both. Older nodes speak only /meshsub and see exactly what they saw before. The legacy copy is still signed, so an older node under Strict (--strict-gossip) or Permissive accepts it. A v0.8.3 node never publishes unsigned on /meshsub. An older node's transactions reach v0.8.3 nodes on the legacy topic. The two lanes stay in step through TxCross: a transaction heard on both is handed to the node once, and the other lane's copy is held for that verdict. Without the hold, the second copy would be a pool duplicate, reported Ignore and never relayed on its lane, so older nodes would stop getting transactions past one hop. The transition test fails that way when the hold is disabled. During this phase the legacy copy still names the author.
  2. When every node on the chain runs ≥ v0.8.3 (validators, observers, archives, site and bridge nodes), restart each with --anonymous-tx-only, in any order. Each flagged node stops publishing on and subscribing to rand/{chain}/tx. The privacy gain starts here.
  3. The next release can make the flag the default and drop the legacy topic.

Out of scope: timing analysis (the first relayer of a transaction is still the likeliest author). That is the Dandelion++ issue's to close.

  • Tests:
    • a_gossiped_transaction_carries_no_author_and_the_legacy_copy_follows_the_flag: a bare gossipsub listener on tx/2 receives the node's transactions with source == None and sequence_number == None. A bare pre-v0.8.3 node receives the signed legacy copy without the flag, and nothing with it.
    • during_the_roll_a_transaction_is_judged_once_and_relayed_on_both_lanes: in B → A → C, with C a legacy-only node behind A, A's node loop sees each transaction once and C still receives B's transactions through A.
    • tx_cross_judges_the_first_copy_and_answers_the_other_lane_with_its_verdict
    • the_transaction_lane_is_anonymous_by_configuration

#149: deploy/caddy/README.md

  • rand_getUnsealed is not blocked. The sale proxy's RPC_ALLOWED (randprotocol.org server/sale/src/rpc.rs at 8cebd54) and the node's rpc::PUBLIC_METHODS are the same 62 methods (compared mechanically).
  • New table: for each method, what the proxy, the node's public listener and its operator listener do.
  • The --public-rpc note no longer says "not yet done". Nothing in the repo records obs1's state after the 2026-10-22 rebuild, so the note says so and gives the check: rand_getPeers through 127.0.0.1:18545 from the web droplet.
  • The per-IP read limit is 1200 a minute (READS_PER_MINUTE), not 120. The note also flags that the 2026-10-11 sale deploy was built before 8cebd54.

Tests run

Run locally on macOS: nice -n 19, CARGO_BUILD_JOBS=4, node crate only.

  • cargo test -p randprotocol-node --lib network::: 82 passed, 0 failed
  • cargo test -p randprotocol-node --test network: 14 passed, 0 failed
  • cargo test -p randprotocol-node --bin rand-node: 63 passed, 0 failed
  • cargo test -p randprotocol-node --lib -- gossip status binding compact: 80 passed, 0 failed
  • cargo test -p randprotocol-prover --features service --bin rand-prover: 3 passed, 0 failed
  • cargo clippy --no-deps -p randprotocol-node -p randprotocol-prover --all-targets -- -D warnings: clean
  • Not run locally: the full workspace suite (CI runs it).

Closes #143
Closes #144
Closes #146
Closes #149

🤖 Generated with Claude Code

https://claude.ai/code/session_01GFs5h141Ct6RTGGrCTQ1vy

dendisuhubdy and others added 4 commits October 28, 2026 00:25
…on names no chain

rand-node (stdout) and rand-prover (stderr) build their subscriber with
`.with_ansi(log_ansi(<stream>.is_terminal(), NO_COLOR))`, so journald gets plain
lines and `grep '  INFO randprotocol_node::node: committed'` works again. NO_COLOR
follows no-color.org (present and not empty). The `rand` CLI has no subscriber.

deploy: cutover-droplet-chain14.sh writes "RAND full node ($NAME)", and
update-droplet.sh rewrites a unit's "RAND full node (d, chain 14)" to
"RAND full node (d)" on the next roll (later cutovers only swap the datadir).

Fixes #143

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFs5h141Ct6RTGGrCTQ1vy
…at debug

The WARN spam "Not publishing a message that has already been published" came
from `broadcast_status`: it runs on every `peer connected` and on the 3 s
status tick, and a `Status { height, head_hash, view, floor }` names no author.
Under the content-only message id (blake3 of the bytes), an unchanged status
was the same id as last time, and every node at one head published the same id
as every other, so the second publisher's status was dropped (and the new peer
a connect-time status was meant for never heard it). The peer-binding announce
is not it: it is signed afresh with the clock every time.

The message id now covers the author and sequence number on the status topic
only (`message_id`). Transactions, consensus and bindings keep the content id,
so one transaction forwarded by two peers is still one message. In a mixed
fleet only IHAVE/IWANT carry ids: an old peer may pull a status it already
holds, a few dozen bytes. `PublishError::Duplicate` is logged at debug.

Tests: a_status_id_covers_its_author_and_sequence_and_every_other_id_is_the_content,
an_unchanged_status_published_again_is_delivered_again.

Fixes #144

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFs5h141Ct6RTGGrCTQ1vy
…which method

- `rand_getUnsealed` is not refused: the sale proxy's `RPC_ALLOWED`
  (randprotocol.org server/sale/src/rpc.rs at 8cebd54) and the node's
  `rpc::PUBLIC_METHODS` are the same 62 methods, and it is on both.
- One table names, per method, what the proxy, the node's public listener and
  its operator listener each do.
- The `--public-rpc` note no longer says "not yet done": nothing in the repo
  records obs1's state after the 2026-10-22 rebuild, so it says that and how
  to check from the web droplet (rand_getPeers through 127.0.0.1:18545).
- The proxy's per-IP read limit is 1200 a minute, not 120.

Fixes #149

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFs5h141Ct6RTGGrCTQ1vy
…gned

Every `rand/{chain}/tx` message was `MessageAuthenticity::Signed`, so it
carried the publishing node's PeerId, a signature and a sequence number, and
every node learned which peer first published each transaction.

`MessageAuthenticity` is per behaviour, so transactions get a second gossipsub
behaviour (`RandBehaviour::tx_gossip`): `Anonymous` under
`ValidationMode::Anonymous` (a message naming an author is refused), on its own
protocol `/rand/{chain}/txsub` so it never competes with `/meshsub` for a
stream, topic `rand/{chain}/tx/2`. Consensus, status and peer bindings stay
signed. The derive maps both behaviours' events to one variant, so the lane is
read off the topic (each behaviour subscribes only its own); `GossipId.lane`
routes the verdict back. A transaction's `from` is now its forwarder on either
lane; rate limiting already keyed on `propagation_source`.

Rolling upgrade: by default a node also subscribes to, publishes on and relays
the legacy signed topic, which an older node (under Permissive or Strict) still
accepts because it is signed. A transaction heard on both lanes is handed to
the node once; the other lane's copy is held for that verdict (`TxCross`), so a
transaction the node accepts is relayed on both lanes and an older node behind
it still gets it (without the hold the second copy is a pool duplicate,
`Ignore`, and is not relayed; the transition test fails that way).
`--anonymous-tx-only` drops the legacy topic once no older node is left
(docs/deploy.md).

The two integration tests that pinned a status's content-addressed id
(tests/network.rs) now pin it on a transaction, and check the #144 contrast:
an unchanged status published three times is three deliveries.

Tests: a_gossiped_transaction_carries_no_author_and_the_legacy_copy_follows_the_flag,
during_the_roll_a_transaction_is_judged_once_and_relayed_on_both_lanes,
tx_cross_judges_the_first_copy_and_answers_the_other_lane_with_its_verdict,
the_transaction_lane_is_anonymous_by_configuration.

Fixes #146

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFs5h141Ct6RTGGrCTQ1vy
@codecov

codecov Bot commented Oct 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@dendisuhubdy
dendisuhubdy merged commit 9181f49 into main Oct 28, 2026
23 checks passed
@dendisuhubdy
dendisuhubdy deleted the fix/v083-node branch October 28, 2026 01:25
dendisuhubdy added a commit that referenced this pull request Oct 28, 2026
…fter submit, display-unit token create, fixed-page scan, --proxy (PR #154)

CHANGELOG: the Unreleased section is now one v0.8.3 entry, the node items
(#150, #155) under "Node" and this branch's items under "Wallet".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFs5h141Ct6RTGGrCTQ1vy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment