Repository navigation
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-fileand noSHADENET_REGISTER_KEYon
a remote RPC used to fail asking for a funded key. It now does whatzkapi-clientd config
does: creates an owner-only funding wallet (funding.keynext 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 frombondForplus 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;0quotes once;SHADENET_FUNDING_POLL_MSsets the poll) bounds the wait; an unfunded
wallet at the deadline exits 4 naming what is still missing. The quote signs nothing and never callseth_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
0x33ec7E53E143e2065887Bd5D5d7EB47159D72982quoted 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 initprints the keylessregister-membercommand and says where the funding
wallet will be created;--key-fileis mentioned as the alternative.- The JavaScript
shade-tree register-memberdoes the same with noSHADE_TREE_REGISTER_KEYon 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.mjswith 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
checksregisterperforms moved intotier_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 offunding.key(and thechmod 600refusal), 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, soHeartbeatDownandBootnodeDownfired on a healthy host.
RpcEndpointFailingandCanopyPinDriftusedseverity: warnand had nocomponent; they now
usewarningwithcomponent: rpcandcomponent: fleet.monitoring/alerts.selftest.mjs
checks both (labels on every rule, compose ports againstbootstrap.sh). -
CanopyPinDriftcompares 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 withpromtool 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 toverifyDirectoryas one key, so
every check failed withdirectory:signer-not-pinnedand the lab vantage point reported the Elder
unreachable while it served a fresh, signed directory. -
The Sepolia record pins
services.*.committo 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
andcargo deny check advisoriesare 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-imagenow 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.
- Without a push,
- toml 0.8 → 1.1 for
config.toml(Dependabot #263). toml 1.x gatesfrom_strbehind itsserde
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 initwrites,
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.
- A new corpus test pins every config people have, field by field: what
- 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 insdk.selftest; - the on-chain
WithdrawGroth16Verifieraccepts it on Anvil and refuses it with a tampered
address; - the pre-bump
testdata/withdraw-proof.jsonfixture still verifies.
- a fresh SDK exit proof (0.7.6) verifies under the hoisted 0.7.5; this is now a permanent
- Checked across versions:
- @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.jsand@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.
- New
- chacha20poly1305 0.10 → 0.11, the XChaCha20-Poly1305 seal on passphrase-protected
identity.json(Dependabot #262, merged as #329). It moves tohybrid-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.jsonlockedChacha011).
- Checked in both directions: every pre-bump fixture opens, and a file locked by the new build
- scrypt 0.11 → 0.12, which derives the key for passphrase-protected
identity.json(Dependabot
#266).Params::newno 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.
- Checked in both directions:
Record RPCs: publicnode out, tenderly in (task 19)
- The Sepolia record's second member-set RPC was publicnode, which intermittently answers the
member-seteth_getLogsreplay withcode 4444, pruned history unavailable. That made
doctorfail, 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 --rpcreports "member log complete (7 live / 11 slots)"; - both v4 preflights pass;
- the JS root provider resolves
finalizedto a block number beforeeth_getLogs(tenderly
rejects the tag itself).
- tenderly returns the full log from the set's deploy block and takes a burst of 20 calls
- Rejected: 1rpc.io (HTTP 429/400 under the preflight), drpc and onfinality (range limits), and
Blast (shut down). scripts/deploy-contracts.mjsdefaults 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 = trueinconfig.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,statusandplanno 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".doctornames the tier count only for a record that lists several.--jsonkeepstierandlimit; the--limitflag is unchanged.
status: the epoch line saidtunnels used 0, left 8next to48 tunnel(s) per epoch. It now
sayssessions (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;--jsonlists
every node.planadvice no longer suggests "tier N would do it in one epoch"; the plan's
one_epoch_tierfield is unchanged.