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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this project are documented here.
This project adheres to [Semantic Versioning](https://semver.org) and
[Conventional Commits](https://www.conventionalcommits.org).

## [0.190.0] - 2026-08-31

### Bug Fixes
- **peers:** Withhold a non-destination address from `dig.getPeers` at the type boundary rather than serving `[::]:0` to remote peers as a dial candidate (#349)
- **relay:** Fail closed on a relay endpoint that cannot be read, instead of accepting any scheme and silently dialling 443 on a malformed port (#285)
- **config:** Read one shared off-token across `DIG_PEER_NETWORK`, `DIG_RELAY_URL` and `DIG_BOOTSTRAP_PEERS`, so `DIG_PEER_NETWORK=OFF` no longer leaves the peer network running (#282)
- **config:** Treat `DIG_RELAY_URL=` as "no relay" rather than the compiled-in public relay, and log which bootstrap branch was taken (#352)
- **wallet:** Hold a possibly-in-flight bundle's inputs to the TTL unless the mempool definitively refused it, closing the under-claim fail-open into the double-select window (#348)

## [0.189.0] - 2026-08-31

### Features
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ edition = "2021"
# the ROOT manifest (`[workspace.package].version`), so it MUST be set here for a
# release to fire (§3.6). The library crates (dig-node-core/dig-runtime/dig-wallet)
# keep their own independent versions — only the released binary tracks the workspace version.
version = "0.190.0"
version = "0.191.0"

# Release hardening, matching digstore: keep integer-overflow checks ON in release.
# The node parses untrusted serialized input and does offset/length arithmetic over
Expand Down
41 changes: 39 additions & 2 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,27 @@ does not own them (except `DIG_NODE_UPSTREAM`, which the shell SETS — see belo
| `DIG_WALLET_WC_PROJECT_ID` | initial/default WalletConnect projectId for the wallet host (§16) | *(unset ⇒ none)* | A persisted `wc_project_id` in `config.json` wins over this; a blank persisted value falls through to this env. Blank ⇒ treated as unset. |
| `DIG_NODE_MAX_OUTGOING_BYTES_PER_SEC` | outgoing-bandwidth throttle cap, in bytes/second (§17) | `0` (UNLIMITED — opt-in) | Parsed as `u64`; `0`, unparsable, or unset ⇒ unlimited (the throttle is a no-op until an operator configures a cap). Resolved ONCE at node construction. |

The peer-network layer additionally honors `DIG_PEER_NETWORK` (set to a falsy value to disable the L7
peer network) and `DIG_RELAY_URL` (override or disable the relay), which gate the P2P bring-up, and
### The shared off-token

`DIG_PEER_NETWORK`, `DIG_RELAY_URL` and `DIG_BOOTSTRAP_PEERS` are the three knobs that decide whether
this node reaches the network at all. All three read ONE off-vocabulary: **`off`, `disabled`, `0`,
`false`, `no`, or an explicitly empty value** — trimmed and case-insensitive. Any of those disables
the knob; anything else does not.

A node MUST NOT accept a disable token on one of these knobs and ignore the same token on another.
An operator who writes `OFF` and gets an isolated relay but a live peer network has been told the
switch worked when it did not.

An explicitly EMPTY value counts as a disable on all three, for the reason given under
`DIG_BOOTSTRAP_PEERS` below: a variable set to nothing is an operator saying "none", and resolving
it to the compiled-in default makes a node believed to be isolated dial production infrastructure.
An UNSET variable is a different thing and keeps its documented default.

An unrecognised value is NOT a disable. For `DIG_RELAY_URL` an unrecognised value is a relay URL, so
reading one as a disable would silently unplug a configured relay.

The peer-network layer honors `DIG_PEER_NETWORK` (disable the L7 peer network) and `DIG_RELAY_URL`
(override or disable the relay), which gate the P2P bring-up, and
**`DIG_PEER_PORT`** — the mTLS peer-RPC server listen port (dig-node-to-dig-node RPC traffic, §5.2).
Parsed as `u16`; unparsable/unset ⇒ the default **`9444`** (`peer::DEFAULT_P2P_PORT`).
Bound dual-stack IPv6-first with an IPv4 fallback, per §5.2.
Expand Down Expand Up @@ -2493,6 +2512,13 @@ which fail unless the parser really accepts the verb and carries its operands th
port `0`, which is what dig-nat records for a relay-accepted circuit with no configured relay
endpoint — MUST OMIT the key rather than emit the wildcard. A consumer MUST therefore treat a
missing `address` as "this peer has no known dialable address", never as a malformed element.
The peer-facing `dig.getPeers` carries the SAME rule in the shape its own wire uses: each peer
row's `addresses` is an ARRAY, so a peer with no dialable destination MUST be emitted with an
EMPTY array — the row kept, the address withheld. The row MUST NOT be dropped (the asking node
would not learn the peer exists, and it may still be reachable via the relay), and the key MUST
NOT be omitted or set to null (`dig.announce` validates that `addresses` IS an array). A node
MUST NOT serve a non-destination to a remote peer as a dial candidate: a peer that dials it
wastes one of its few dial slots, and a peer that caches it caches a hole.
The array is present whenever a peer network is running and
omitted (count only) on the in-process FFI path / before bring-up. The per-peer `peer_id` is the
machine-checkable proof of a mutual A↔B connection (each side lists the other's `peer_id`). Peer
Expand Down Expand Up @@ -5379,6 +5405,17 @@ bundle is definitively refused — and the expiry is the backstop that keeps a r
runs from stranding a coin permanently. Failing to record a reservation MUST NOT fail a push that the
mempool already accepted.

A push MUST reserve its inputs unless the network DEFINITIVELY refused the bundle. A refusal is
definitive only when the mempool stated its reason (`accepted:false` WITH a `rejection`); a bare
denial carrying no reason, and any transport failure, MUST be treated as POSSIBLY IN FLIGHT and hold
the inputs to the TTL. The node cannot distinguish "never relayed" from "relayed, and the
acknowledgement was lost", and under §13 every dialled peer is untrusted, so a source that denies a
relay it performed MUST NOT thereby return the coins to selection — a second send inside the
confirmation window could otherwise reselect the same inputs. The TTL MUST NOT be shortened to
compensate for the wider hold: that trades a double-select for a lockout, and a lockout is the worse
failure. Requiring a STATED reason is what keeps a genuine mempool rejection from holding a user's
coins for the full TTL.

18.8. **Method surface — reads (served).** `login`, `logout`, `get_version`,
`get_sync_status`, `check_address`, `get_derivations`, `get_are_coins_spendable`,
`get_spendable_coin_count`, `get_coins`, `get_coins_by_ids`, `get_cats`, `get_all_cats`, `get_token`,
Expand Down
2 changes: 1 addition & 1 deletion crates/dig-node-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ name = "dig-node-core"
# dig-node#276/#296). Changing a public return type is BREAKING for an out-of-workspace implementor;
# this crate is consumed in-workspace only and is pre-1.0, so it is a MINOR bump under SemVer's 0.x
# rule -- recorded here rather than letting the number imply the locator surface held still.
version = "0.64.0"
version = "0.65.0"
edition = "2021"
license = "GPL-2.0-only"
description = "The canonical DIG node ENGINE library (crate `dig_node_core`): the JSON-RPC dispatch (`handle_rpc`, the same contract as rpc.dig.net), local-first content serve/fetch/redirect from LOCAL .dig store modules (via digstore_host::serve_blind), chain-anchored-root resolution, chain-watch + subscriptions + generation gap-fill, the LRU cache, and the full P2P stack. Shared UNCHANGED by both host shells: the `dig-node` OS-service binary (dig-node-service) and the DIG Browser's in-process cdylib (dig-runtime). Native Rust so the compiled-module serve path works."
Expand Down
Loading
Loading