Skip to content
Merged
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
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ stopped at v0.4 until 2026-09-30, when the entries v0.5 to v0.6.7 were written f

## Unreleased

Nothing yet.
- **Transactions gossip anonymously** (issue #146): a second gossipsub behaviour,
`MessageAuthenticity::Anonymous` under `ValidationMode::Anonymous`, protocol
`/rand/<chain>/txsub`, topic `rand/<chain>/tx/2`; consensus, status and peer bindings stay
signed. Rolls node by node with the legacy signed topic still published and relayed;
`--anonymous-tx-only` drops it once no older node is left (`docs/deploy.md`).
- **No duplicate-publish WARN spam** (issue #144): a status's gossip id covers its author and
sequence number, so an unchanged status re-announced on a connect or the 3 s tick goes out
instead of being dropped as "already published"; `PublishError::Duplicate` is a debug line.
- **No ANSI colour in journald** (issue #143): `rand-node` and `rand-prover` colour their log
only at a terminal and never under `NO_COLOR`; `deploy/update-droplet.sh` drops "chain 14"
from an old unit's `Description=`.

## v0.8.2 — 2026-10-27 (chain 21, node-only; the genesis-gated rules wait for the next cut)

Expand Down
33 changes: 32 additions & 1 deletion crates/randprotocol-node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,12 @@ enum Cmd {
/// exchange honest gossip both ways.
#[arg(long)]
strict_gossip: bool,
/// Gossip transactions only on the anonymous lane `rand/{chain}/tx/2` (issue #146): stop
/// subscribing to and publishing on the legacy signed topic `rand/{chain}/tx`, whose
/// messages name the node that first published them. Only once every node on the chain
/// runs v0.8.3 or later: an older node hears transactions on the legacy topic alone.
#[arg(long)]
anonymous_tx_only: bool,
#[arg(long, default_value = "127.0.0.1:8545")]
rpc: SocketAddr,
/// Take part in consensus with this node's key. A key in no current epoch's validator
Expand Down Expand Up @@ -2490,10 +2496,34 @@ fn opens_storage(cmd: &Cmd) -> bool {
matches!(cmd, Cmd::Run { .. } | Cmd::Verify { .. } | Cmd::Db { .. } | Cmd::Init { .. } | Cmd::Safety { .. })
}

/// Colour in the log only for a person at a terminal (issue #143): under systemd stdout is a
/// pipe to journald, and escapes there break `grep`, log shippers and every parser downstream.
/// `NO_COLOR` turns it off anywhere (<https://no-color.org>: present and not empty).
fn log_ansi(is_terminal: bool, no_color: Option<std::ffi::OsString>) -> bool {
is_terminal && no_color.is_none_or(|v| v.is_empty())
}

#[cfg(test)]
mod log_ansi_tests {
use super::log_ansi;

#[test]
fn colour_only_at_a_terminal_and_never_under_no_color() {
assert!(log_ansi(true, None), "a terminal with no NO_COLOR gets colour");
assert!(!log_ansi(false, None), "a pipe (journald) gets none");
assert!(!log_ansi(true, Some("1".into())), "NO_COLOR=1 turns it off at a terminal");
assert!(!log_ansi(false, Some("1".into())));
assert!(log_ansi(true, Some("".into())), "an empty NO_COLOR is unset (no-color.org)");
}
}

#[tokio::main]
async fn main() -> Result<()> {
use std::io::IsTerminal;
// `fmt()` writes to stdout, so stdout is the stream whose terminal-ness counts.
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info,libp2p=warn,libp2p_mdns=off".into()))
.with_ansi(log_ansi(std::io::stdout().is_terminal(), std::env::var_os("NO_COLOR")))
.init();
let cli = Cli::parse();
let test_profile_allowed = test_fri_profile_allowed(&cli);
Expand Down Expand Up @@ -2976,6 +3006,7 @@ async fn main() -> Result<()> {
bootstrap,
reserved_peer,
strict_gossip,
anonymous_tx_only,
rpc,
validator,
no_mdns,
Expand Down Expand Up @@ -3068,7 +3099,7 @@ async fn main() -> Result<()> {
prune_history,
gas_policy: randprotocol_core::gas::GasPolicy::from_prices(gas_price, byte_price),
msauth: randprotocol_node::msauth_key::MsauthKeyOptions { path: msauth_vk, build: build_msauth_key },
}, rpc_options, node::NetOptions { reserved_peers: reserved_peer, strict_gossip })
}, rpc_options, node::NetOptions { reserved_peers: reserved_peer, strict_gossip, anonymous_tx_only })
.await?;
// The prover is served once the node's RPC is up and stops with the node; a prover
// that exits stops the node too.
Expand Down
10 changes: 10 additions & 0 deletions crates/randprotocol-node/src/network/behaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,17 @@ pub struct RandBehaviour {
/// Second, for the same reason: the per-source pending cap and the per-peer cap that holds
/// reserved peers too (audit v6, NET-1; [`super::edge`]). It emits nothing either.
pub guard: super::edge::Guard,
/// Consensus, status, peer bindings and — while [`super::EdgeConfig::anonymous_tx_only`] is
/// off — the legacy transaction topic: `MessageAuthenticity::Signed`, on gossipsub's own
/// `/meshsub` protocol.
pub gossipsub: gossipsub::Behaviour,
/// Transactions only (issue #146): `MessageAuthenticity::Anonymous` under
/// `ValidationMode::Anonymous`, so a transaction carries no author, signature or sequence
/// number, and one that does is refused. `MessageAuthenticity` is per behaviour, hence a
/// second one, on a protocol of its own (`/rand/{chain}/txsub`) so the two never compete for
/// one stream. Its events arrive as `RandEvent::Gossipsub` like the first's; the topic tells
/// them apart, because each behaviour subscribes only its own.
pub tx_gossip: gossipsub::Behaviour,
pub identify: identify::Behaviour,
pub kademlia: kad::Behaviour<kad::store::MemoryStore>,
pub mdns: Toggle<mdns::tokio::Behaviour>,
Expand Down
Loading
Loading