Skip to content

feat(net): optional anonymous transaction broadcast over Tor#6866

Open
tanderbold wants to merge 1 commit into
tronprotocol:developfrom
tanderbold:feature/tor-anonymous-tx-broadcast
Open

feat(net): optional anonymous transaction broadcast over Tor#6866
tanderbold wants to merge 1 commit into
tronprotocol:developfrom
tanderbold:feature/tor-anonymous-tx-broadcast

Conversation

@tanderbold

Copy link
Copy Markdown

What

Adds an optional feature to anonymize the origin of transactions a node broadcasts, by
relaying them over Tor instead of advertising them to directly-connected P2P peers. Off by
default (node.tor.enabled = false) — no behaviour change unless explicitly enabled.

Why

When a node submits a transaction through its own broadcast API, it advertises that transaction
to its sync peers, which exposes the node's IP as the transaction's origin. For users who need
origin privacy this links their IP to their transactions. Block sync and normal P2P traffic are
too heavy to run over Tor, but a single transaction is tiny — so only the outbound transaction
broadcast is routed through Tor, while block sync and everything else keep running on the clear
net.

How

When node.tor.enabled is true, a submitted transaction is still validated and pushed locally as
usual, but instead of being advertised to sync peers it is relayed via the native TRON P2P
protocol
through the local Tor SOCKS5 proxy to standard, unmodified full nodes learned from the
discovery layer that are NOT current sync peers. Those nodes re-gossip it as they would any peer's
transaction, hiding the origin.

  • Persistent tunnel pool — reaching a Tor peer and completing the handshake costs several
    seconds, paid per connection, not per transaction. The service keeps a pool of persistent,
    handshaked Tor tunnels to verified at-head relays, reused for every broadcast and kept alive by
    answering keep-alive pings.
  • Batched & async — transactions are buffered and flushed as a single TransactionsMessage
    batch (capped) over broadcastCount tunnels in parallel, so a burst is delivered in about one
    round-trip; broadcastTransaction enqueues and returns immediately.
  • Anonymity — each tunnel advertises a fresh random public IP (never the real one) and a fresh
    random node id per hello, so tunnels can't be clustered or linked to the clear-net node; SOCKS
    connects by raw IP (no DNS leak); there is no clear-net fallback on failure.
  • Resilience — no transaction is lost if Tor or a relay is down or degraded: it stays buffered
    until a tunnel can be (re)built; expired transactions are evicted; a stalled tunnel's write is
    bounded and the tunnel is rebuilt; pool refill is adaptive with exponential backoff so a
    congested Tor isn't flooded with circuit requests; and, optionally, the node signals NEWNYM
    over the Tor control port to rebuild circuits with fresh exit IPs when no tunnel can be built.

libp2p is used as a library only; it is not modified.

Configuration (node.tor)

node.tor {
  enabled = false
  socksHost = "127.0.0.1"
  socksPort = 9050
  connectTimeout = 30000
  readTimeout = 30000
  broadcastCount = 2          # number of relays to push each transaction to
  circuitIsolation = true     # a distinct Tor circuit per connection
  controlPort = 0             # optional: Tor ControlPort for NEWNYM recovery
  controlPassword = ""
}

Testing

  • Automated — end-to-end delivery over a persistent tunnel via an in-process SOCKS5 forwarder
    and a stub node; Tor-outage and relay-degradation recovery; NEWNYM on a stuck pool; and routing
    (Tor path when enabled, normal P2P broadcast when disabled). All tests and checkstyle pass.
  • Live — validated end-to-end on the Nile testnet: a 45-minute run at 5–10 tx/min with 200-tx
    spikes every 5 minutes delivered every transaction on-chain, while block sync continued on the
    clear net.

@tanderbold tanderbold force-pushed the feature/tor-anonymous-tx-broadcast branch from df7fc12 to 60dd0cd Compare July 3, 2026 18:11
@tanderbold tanderbold changed the title Add optional anonymous transaction broadcast over Tor (native P2P) feat(net): optional anonymous transaction broadcast over Tor Jul 3, 2026
@tanderbold tanderbold force-pushed the feature/tor-anonymous-tx-broadcast branch from 60dd0cd to aacb35f Compare July 3, 2026 18:40
When node.tor.enabled is true, transactions submitted to this node's own
broadcast API are validated locally as before but, instead of being
advertised to the node's directly-connected sync peers (which exposes the
origin node's IP), are relayed via the native TRON P2P protocol through the
local Tor SOCKS5 proxy to standard, unmodified nodes learned via the (UDP)
discovery layer that are NOT currently used as sync peers. Those nodes
re-gossip the transaction as usual, hiding the origin. Block sync and normal
P2P traffic are unaffected. The feature is off by default.

Reaching a Tor peer and completing the P2P handshake costs several seconds,
which is paid per connection, not per transaction. To broadcast quickly even
at a high transaction rate, the service keeps a pool of persistent Tor
tunnels to verified, at-head relay nodes: each tunnel is handshaked once,
kept alive (answering keep-alive pings) and reused for every broadcast. The
read timeout of an established tunnel is set well above the peer keep-alive
interval so an idle but healthy tunnel is not torn down between pings.
Transactions are buffered and flushed as a single TransactionsMessage batch
(capped) over broadcastCount tunnels in parallel, so a burst is delivered in
about one round-trip; broadcastTransaction enqueues and returns immediately.
Expired transactions are dropped from the buffer.

Each tunnel advertises a fresh random public IP (never our real one, and a
fresh random node id per hello, so tunnels can't be clustered or linked to
our clear-net node) and echoes the peer's head block so the peer marks the
connection sync-complete before it will fetch a transaction. Relay candidates
are discovered nodes minus current sync peers; only nodes at (or near) our
head are kept.

Resilience: no transaction is lost if Tor or a relay is down or degraded — it
stays buffered and is delivered once a tunnel can be (re)built. A flush never
blocks the pool on a silently-dead tunnel (writes are bounded; a stalled
tunnel is dropped and rebuilt). Pool maintenance refills only the missing
tunnels (plus a small margin) and backs off exponentially when Tor is
congested, so it does not flood a struggling Tor with circuit requests. If no
tunnel can be built for several rounds and node.tor.controlPort is set, the
node signals NEWNYM over the Tor control port to rebuild circuits with fresh
exit IPs. libp2p is used as a library only; it is not modified.

- NodeConfig/Args/CommonParameter: parse and hold node.tor.* config
  (incl. optional controlPort/controlPassword for NEWNYM recovery)
- TronNetService: expose discovery-table nodes (getTableNodes)
- TorBroadcastService: persistent Tor tunnel pool + batched native-P2P relay,
  with bounded writes, adaptive refill/backoff, NEWNYM recovery, idle-safe
  read timeout, expired-tx eviction, and public-only advertised addresses
- Wallet.broadcastTransaction: route through Tor when enabled
- config.conf: documented node.tor block
- Tests: delivery over a persistent tunnel via a SOCKS5 forwarder; Tor-outage
  and relay-degradation recovery; NEWNYM on a stuck pool; and routing (Tor
  path when enabled, normal P2P broadcast when disabled)
@tanderbold tanderbold force-pushed the feature/tor-anonymous-tx-broadcast branch from aacb35f to 972ac35 Compare July 3, 2026 19:08
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