Skip to content

v0.7.5

Latest

Choose a tag to compare

@github-actions github-actions released this 21 Oct 02:47
· 17 commits to main since this release
2eaf922

0.7.5 — register-member funds itself

shadenet register-member with no key creates its own funding wallet, quotes one amount, waits for it and stakes (zkAPI's deposit step). Also: the proxy as a scheduler with --preopen session books, CLI output that names the budget, record RPC changes, monitoring fixes and dependency security bumps.

register-member funds itself (feature request: mirror zkAPI's funding path)

  • shadenet register-member --identity … with no --key-file and no SHADENET_REGISTER_KEY on
    a remote RPC used to fail asking for a funded key. It now does what zkapi-clientd config
    does: creates an owner-only funding wallet (funding.key next to the identity file;
    --funding-key <path> to move it), prints the wallet's address and the one amount to send
    (the tier's bond from bondFor plus a gas reserve: the smallest of 0.002, 0.004, 0.008 … ETH
    that covers 1 800 000 gas at the current EIP-1559 fee; the lowest rung covers up to 1.11 gwei,
    since Sepolia idles at a few wei and then spends a week near 1 gwei), polls the balance every
    15 s, and stakes once the balance covers it. The reserve moves in those coarse steps rather
    than tracking the fee to the wei because the amount is public next to the stake: on the
    bundled Sepolia set every quiet-fee registration sends the same 0.012 ETH, so the amount does
    not single out its sender. Top-ups after a partial payment are rounded up to 0.0001 ETH.
  • With the first amount it prints the Layer-0 advice from docs/PAYMENTS.md: the address, amount
    and stake are public and link the sending wallet to the membership (not to its use), so send
    from a fresh address funded through a large shared pool (Railgun, Privacy Pools, a CEX
    withdrawal) and send exactly the quoted amount. After the stake it prints the leftover gas
    money still in the funding wallet and says it is tied to the stake: keep it for a later
    top-up or send it on to a fresh address, not back to the wallet that funded it. The same command resumes after Ctrl-C and reuses the key; an already-staked
    identity is reported before anyone is asked for money. --wait-timeout <seconds> (default
    3600; 0 quotes once; SHADENET_FUNDING_POLL_MS sets the poll) bounds the wait; an unfunded
    wallet at the deadline exits 4 naming what is still missing. The quote signs nothing and never calls eth_estimateGas (an
    unfunded sender cannot be estimated against), which is why the reserve is a ceiling; what
    the registration does not spend stays in the wallet. --key-file, the environment key and
    the loopback Anvil default are unchanged.
  • Proven live on 2026-10-11 against the bundled Sepolia set 0x789967F0…680b: a fresh identity,
    shadenet register-member --identity … with no key, funding wallet
    0x33ec7E53E143e2065887Bd5D5d7EB47159D72982 quoted 0.0116 ETH (0.01 bond + 1 600 000 gas at the
    1 gwei floor, the ceiling at the time); the deployer sent exactly that
    (0x80dd0656d783f4279fa06e4448e1d3f40672b0f9369401a88bb1456d2cbe67ef, block 11897250); the
    waiting command saw it on its next poll and staked
    (0xd0ebc6fbc3ca69fbf95a490b0205d4f70c89ccad88f500f139f3e9faf1ea8a17, block 11897251,
    1 471 267 gas at 0.001 gwei, member-status → active, index 14), leaving 0.0016 ETH in the
    wallet. Two earlier one-hour waits also ran to the deadline and exited 4, and retried two
    transient RPC errors (eth_call: expected 0x-hex data, a 15 s timeout) without losing the
    wait. The stake used more gas than the first live registration (1 317 674), and the signer
    prices at estimate + 20 % (about 1.77M), so the ceiling moved from 1 600 000 to 1 800 000 after
    this run, and the reserve ladder above replaced the per-fee figure: the quote is now 0.012 ETH
    at quiet fees.
  • shadenet init prints the keyless register-member command and says where the funding
    wallet will be created; --key-file is mentioned as the alternative.
  • The JavaScript shade-tree register-member does the same with no SHADE_TREE_REGISTER_KEY on a
    remote RPC: --funding-key <path> / SHADE_TREE_FUNDING_KEY (default ~/.config/shade-tree/funding.key),
    --wait-timeout, SHADE_TREE_FUNDING_POLL_MS, exit 4, same quote and the same printed
    lines. The arithmetic, rounding, key file and wait loop live in
    packages/node/lib/funding-wallet.mjs with a selftest (quote and reserve ladder, privacy text, rounding, owner-only
    creation and reuse, refusal of a world-readable or malformed file, the wait loop against an
    injected clock: one quote, progress on partial payments, a retried RPC error, exit 4 at the
    deadline, no quote for a staked leaf).
  • Library: Wallet::generate(), member::fee_terms (factored out of the signer, same
    numbers), member::funding_quote / FundingQuote / REGISTER_GAS_CEILING / QUOTE_MIN_FEE_PER_GAS; the bond
    checks register performs moved into tier_bond, shared with the quote. Tests cover the
    quote arithmetic and the staked / wrong-chain refusals against the mock RPC, the owner-only
    creation and reuse of funding.key (and the chmod 600 refusal), the default key location,
    the parser, and the rounding.

Monitoring

  • The compose monitoring bundle scraped the heartbeat on 9102 and the Elder on 9103; the defaults
    are heartbeat 9103 and Elder 9100, so HeartbeatDown and BootnodeDown fired on a healthy host.
    RpcEndpointFailing and CanopyPinDrift used severity: warn and had no component; they now
    use warning with component: rpc and component: fleet. monitoring/alerts.selftest.mjs
    checks both (labels on every rule, compose ports against bootstrap.sh).

  • CanopyPinDrift compares release versions, not commits. The published node image is built at the
    release tag's commit while systemd hosts run the record's commit; both report the record's
    version, so the orbital-one container (0.7.4 at 6709328) no longer fires it next to the fleet
    (0.7.4 at 114065d). A role left on another release still fires; an off-pin commit on one host is
    CanopyOffRecordPin's job. Checked with promtool test rules: same version and two commits stays
    quiet, while two versions fire after 30 minutes.

  • The uptime probe accepts a comma-separated SHADE_TREE_DIR_SIGNER, which is what the record sets
    for a canopy with two Elder Trees. It passed the joined string to verifyDirectory as one key, so
    every check failed with directory:signer-not-pinned and the lab vantage point reported the Elder
    unreachable while it served a fresh, signed directory.

  • The Sepolia record pins services.*.commit to 114065d, the commit the fleet runs (written with
    scripts/record-canopy.mjs, as agent-devops' copy already was). It still named 954216e from 0.7.2,
    so the probe reported every Elder off pin.

Dependencies

  • quinn-proto 0.11.16 → 0.11.19 (cargo update -p quinn-proto, lock only; it arrives through
    arti/reqwest). RustSec published five advisories against 0.11.16 on 2026-10-12
    (RUSTSEC-2026-0339 to -0343, stream-reassembly and connection-ID memory exhaustion; fix

    = 0.11.17), which turned the supply-chain audit red on every PR, including #353. cargo audit
    and cargo deny check advisories are clean on the new lock; the workspace builds --locked.

  • docker/setup-qemu-action v3 → v4.4.0 in node-image (Dependabot #256).
    • Without a push, node-image now also builds the arm64 image through QEMU (neither loaded nor
      pushed), so a QEMU or base-image break shows up on a dispatch instead of during a release.
    • Checked with a dispatch on the branch (run 37545890531, push=false): the amd64 build and
      smoke test passed, and the arm64 build through QEMU v4 passed.
  • toml 0.8 → 1.1 for config.toml (Dependabot #263). toml 1.x gates from_str behind its serde
    feature, so the dependency now enables ["parse", "serde"]; Dependabot's version alone did not
    compile.
    • A new corpus test pins every config people have, field by field: what shadenet init writes,
      what agent-devops renders for Hermes, and every key at once with inline comments, a literal
      string, a multi-line array with a trailing comma, and CRLF line endings.
    • It also checks that an unknown key (named in the error), a wrong type, a duplicate key and
      broken syntax stay errors.
    • It passes unchanged on 0.8 and 1.1.
  • snarkjs 0.7.5 → 0.7.6 in packages/sdk, which proves exit/withdraw actions in the browser
    (proveAction; Dependabot #257). The repo's hoisted snarkjs, used by the nodes and the JS client
    for RLN proofs, stays 0.7.5.
    • Checked across versions:
      • a fresh SDK exit proof (0.7.6) verifies under the hoisted 0.7.5; this is now a permanent
        check in sdk.selftest;
      • the on-chain WithdrawGroth16Verifier accepts it on Anvil and refuses it with a tampered
        address;
      • the pre-bump testdata/withdraw-proof.json fixture still verifies.
  • @noble/curves 1.2.0 → 2.4.0, the browser backend's ed25519 (Dependabot #259). The import is
    @noble/curves/ed25519.js; sign/verify (RFC 8032, zip215: false) are unchanged.
    • ethers, @semaphore-protocol/proof and @zk-kit/lean-imt keep their own nested 1.x.
    • test/crypto-backends.selftest.mjs (32 checks), directory forward-compat under both backends
      and the SDK conformance vectors pass.
    • The stake page bundle is byte-identical.
  • @noble/hashes 1.3.2 → 2.4.0, the browser crypto backend's sha256 and sha3-256
    (packages/node/lib/crypto-browser.mjs; Dependabot #260, merged as #330). v2 exports explicit .js
    subpaths, so the imports are @noble/hashes/sha3.js and @noble/hashes/sha2.js.
    • New test/crypto-backends.selftest.mjs (32 checks) pins the browser and Node (OpenSSL)
      backends to each other: FIPS known answers, the RFC 8032 ed25519 vector, cross-verification,
      and refusal of a corrupted or non-canonical-S signature.
    • The stake page bundle is byte-identical.
  • chacha20poly1305 0.10 → 0.11, the XChaCha20-Poly1305 seal on passphrase-protected
    identity.json (Dependabot #262, merged as #329). It moves to hybrid-array (the key from the
    32-byte array, XNonce::try_from / from).
    • Checked in both directions: every pre-bump fixture opens, and a file locked by the new build
      opens with the v0.7.4 CLI and the JS reader.
    • New fixture rust-init-locked-chacha011.json (vectors.json lockedChacha011).
  • scrypt 0.11 → 0.12, which derives the key for passphrase-protected identity.json (Dependabot
    #266). Params::new no longer takes the output length; the 32-byte key buffer sets it, so the
    key is identical.
    • Checked in both directions:
      • the existing Rust- and SDK-locked fixtures open under the new code;
      • a file locked by the new build opens with the v0.7.4 (scrypt 0.11) CLI and with the JS
        reader, to the same secret.
    • New fixture testdata/identity/rust-init-locked-scrypt012.json (vectors.json
      lockedScrypt012), read by both test suites.

Record RPCs: publicnode out, tenderly in (task 19)

  • The Sepolia record's second member-set RPC was publicnode, which intermittently answers the
    member-set eth_getLogs replay with code 4444, pruned history unavailable. That made
    doctor fail, and SearXNG's proxy refused every CONNECT. It is replaced by
    https://sepolia.gateway.tenderly.co; the list is now ethpandaops, tenderly.
  • Checked 2026-10-06:
    • tenderly returns the full log from the set's deploy block and takes a burst of 20 calls
      without throttling;
    • the Rust client's doctor --rpc reports "member log complete (7 live / 11 slots)";
    • both v4 preflights pass;
    • the JS root provider resolves finalized to a block number before eth_getLogs (tenderly
      rejects the tag itself).
  • Rejected: 1rpc.io (HTTP 429/400 under the preflight), drpc and onfinality (range limits), and
    Blast (shut down).
  • scripts/deploy-contracts.mjs defaults to the same pair for future records.
  • The stake page bundle is rebuilt; its only change is this URL.

shadenet proxy --preopen: a session book ready before the first request

  • A cold first request spent most of its time proving and opening a session book: 8 s or more
    through a nearby node from a running proxy, and 68 s through Singapore from New York (task 71).
  • --preopen (SHADENET_PREOPEN=1, preopen_books = true in config.toml) opens a book ahead
    of requests: one at start, then again after a book idles out or runs dry, but only while
    requests came in within the last 10 minutes. An idle proxy stops spending proofs.
  • Each book costs one proof of the epoch's budget. A pre-open needs at least two proofs left, so
    one stays for a request, and it never opens a second book while one still has tickets.
  • No wire change: a session initialization already carries no target (its signal binds the node,
    class, nonce and book digest). The node accepts it as today.
  • Measured on the live canopy (2026-10-06, via gcc-shade-1): the book was pre-opened at start; the
    first request then spent ticket 0 directly and returned in 9.9 s.
  • Off by default.

CLI output names the budget, not a tier

  • The public record has one tier, so init, doctor, status and plan no longer print
    "tier 8", "(tier 8)" or "1 tier(s), default tier 8". They state the budget instead:
    "8 sessions per 60s epoch" (with session tickets) or "N tunnels per epoch".
    • doctor names the tier count only for a record that lists several.
    • --json keeps tier and limit; the --limit flag is unchanged.
  • status: the epoch line said tunnels used 0, left 8 next to 48 tunnel(s) per epoch. It now
    says sessions (6 tunnels each) used 0 of 8, left 8, so the two numbers agree.
  • status: the node line silently stopped at six nodes. It now adds , +N more; --json lists
    every node.
  • plan advice no longer suggests "tier N would do it in one epoch"; the plan's
    one_epoch_tier field is unchanged.