Skip to content

Add opt-in headless staking (mine → fund → stake without the GUI) - #40

Open
br33zybail wants to merge 1 commit into
ShieldedLabs:mainfrom
br33zybail:headless-cli-staking
Open

Add opt-in headless staking (mine → fund → stake without the GUI)#40
br33zybail wants to merge 1 commit into
ShieldedLabs:mainfrom
br33zybail:headless-cli-staking

Conversation

@br33zybail

@br33zybail br33zybail commented Jul 13, 2026

Copy link
Copy Markdown

Motivation

Operators running headless builds (increasingly common — thanks to #33 the visualizer is now optional) currently have no way to stake: the wallet has stake_to_finalizer() and friends, but they're only reachable from GUI buttons. This came up again in the Signal group today ("How can we stake in the current setup if we're running a node headless? … it looks like there are wallet functions for staking but not sure if they're connected to RPC calls").

What this does

Wires the existing wallet staking actions to the built-in headless wallet, gated by environment variables. Everything is off by default — with no env vars set, behavior is unchanged.

Variable Effect
CROSSLINK_AUTO_SEND=1 runtime switch for the existing (compile-time AUTO_SPEND) miner→user forwarding path
CROSSLINK_AUTO_STAKE=1 bond spendable user funds to a finalizer during staking windows
CROSSLINK_STAKE_TARGET=<64 hex> target finalizer key, same display byte order the GUI stake box accepts; defaults to the node's own finalizer key

Guardrails on the auto-stake path:

  • acts only in the first 60 blocks of each 150-block staking window (so the tx confirms in-window), at most once per window
  • first bond of any size (implicit finalizer registration), afterwards only chunks ≥ 1 cTAZ — largest power-of-10 chunk that fits the spendable balance minus a fee reserve — to avoid dust bonds
  • own-key default reads TENDERLINK_PUBLIC_KEY at stake time with an all-zero guard (never bonds before the BFT service publishes the key)
  • bonds go through the wallet's existing actions_in_flight queue — the identical code path the GUI buttons use, including the in-flight dedup check

Testing

  • cargo build --release -p zebrad clean (no new warnings)
  • The window/chunking/dedup logic ran continuously for days on a live season-2 testnet validator as a private patch (env-gated variant of this diff); the bond path itself is the wallet's existing action queue
  • Byte order verified against the GUI: the env var accepts the same display-form string the GUI stake box does (addr_from_str_bytes semantics mirrored)

Notes for reviewers

  • The CROSSLINK_AUTO_SEND gate deliberately reuses the dev-written AUTO_SPEND block rather than duplicating it — the const stays false, the env var just makes it reachable at runtime.
  • No consensus changes, no new dependencies, wallet thread only, ~90 lines + a README section.

Headless operators currently have no way to stake: the wallet's staking
actions exist but are only reachable from GUI buttons. This wires them to
the built-in headless wallet behind env flags, everything OFF by default:

- CROSSLINK_AUTO_SEND=1: runtime switch for the existing (compile-time
  disabled) AUTO_SPEND miner->user forwarding path
- CROSSLINK_AUTO_STAKE=1: bond spendable user funds to a finalizer during
  staking windows (every 150 blocks; acts only in the first 60 so the tx
  confirms in-window; at most one bond per window; first bond of any size
  registers the finalizer, afterwards only chunks >= 1 cTAZ to avoid dust)
- CROSSLINK_STAKE_TARGET: optional 64-hex target key in the same display
  byte order the GUI stake box accepts; defaults to this node's own
  finalizer key (via TENDERLINK_PUBLIC_KEY, with an all-zero guard)

Bonds go through the wallet's existing action queue (stake_to_finalizer),
identical to the GUI path. Documented in README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hfv59KmT627YnkA3BhhqgD
@azmr

azmr commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Hey @br33zybail, thanks for this effort towards addressing this user need.

I unfortunately don't think this PR should be merged as-is. Concretely:

  • It misunderstands the interaction between mining, the faucet and user wallets. The so-called miner_wallet in wallet/src/lib.rs has evolved in purpose over time and would now more accurately be called faucet_wallet. Unless specifically directed otherwise through the config, mining goes to the an address for personal user_wallet.
  • AUTO_SPEND was added as a way to exercise code paths during development without manual interaction. It should be separated from user-facing code.
  • The staking constants are expected to change during the course of the featurenet and onto mainnet. Having them as hardcoded magic numbers makes it easy to miss, leaving mismatching staking periods. This would likely end up quietly failing most of the time.

More subjective/design considerations:

This may be just a subjective thing - I'm not personally a fan of environment variables for this kind of thing. As it's set-once persistent config, it should probably live in the config TOML.

I think in principle that autostaking would be useful, but without the ability to at least redelegate, it seems like it doesn't let stakers make appropriate adjustments to account for finalizer behaviour - i.e. they can't fully carry out their role within Crosslink. A more basic single staking RPC (allowing for different staking action types) would probably be more useful. Autostaking could be either implemented in the alongside this, or as an external script that uses the RPC.

Thanks again for taking a crack at this, it's genuinely appreciated.

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.

2 participants