Skip to content

wallet_staking_action` fails with "couldn't get anchor" even when Zaino reports Ready #46

Description

@USCMig

Summary

On the s1_dev branch (commit b84cea9, three commits past 26a6564 which introduced
wallet_staking_action), calling wallet_staking_action with CreateNewDelegationBond
consistently fails with:

{"jsonrpc":"2.0","id":1,"error":{"code":-25,"message":"Faucet request for \"CreateNewDelegationBond { amount_zats: 100000, target_finalizer: Pub{ce79} }\" failed: failed to create staking >

The underlying cause appears to be in the wallet's transaction builder, which repeatedly logs:

tx build: couldn't get anchor at BlockHeight(228493)

This recurs continuously (observed over ~10 minutes and ~250+ blocks), including
immediately after Zaino's own status check reports all services Ready:

2026-07-18T15:57:58Z INFO zainodlib::indexer: Zaino status check - ChainState Service: Ready JsonRPC Server: Ready gRPC Server: Ready
2026-07-18T15:58:08Z INFO zainodlib::indexer: Zaino status check - ChainState Service: Ready JsonRPC Server: Ready gRPC Server: Ready
2026-07-18T15:58:35Z zebrad: tx build: couldn't get anchor at BlockHeight(228493)

So this isn't simply "Zaino hasn't caught up yet" — the anchor lookup fails even in a window
where Zaino self-reports Ready.

Ruled out: node still catching up to tip. Initial testing was done while the node was
~6800 blocks behind the estimated chain height, so a first hypothesis was that this was a
sync-lag artifact (wallet scan unable to keep pace with the faster-than-realtime catch-up rate).
This was retested after the node reached full sync (blocks == estimatedheight == 236397,
verificationprogress: 1.0) and the exact same error reproduced immediately:

2026-07-18T21:46:21Z zebrad: tx build: couldn't get anchor at BlockHeight(236394)

At the time of that failure, local tip was 236397 — only 3 blocks ahead of the requested
anchor height, not thousands. So this is not a catch-up/sync-lag issue; the anchor-fetch
problem reproduces at tip.

A related, possibly connected symptom also appeared at tip, from what looks like the
auto-shield logic (moving the shared miner wallet's balance into a user wallet):

TX ERROR: no spend seen for send WalletTxPart { spent_note_count: 0, spent_zats: Zatoshis(0), sent_note_count: 2, sent_zats: Zatoshis(999990000), recv_note_count: 1, recv_zats: Zatoshis(94>

spent_note_count: 0 alongside non-zero sent_note_count/sent_zats looks like a
bookkeeping inconsistency in the wallet's tx-tracking, separate from (but possibly related to)
the anchor lookup failure.

Environment

  • Repo: ShieldedLabs/crosslink_monolith, branch s1_dev, HEAD b84cea9 ("fix compiler error")
  • Built with cargo build --release --bin zebrad (release profile, finished cleanly, no build errors)
  • Network: UnknownTestnet / cTAZ devnet, network_magic = "ClT0" (Crosslink_Testnet_0 / is_clt0)
  • Chain state: resynced from the ctaz-zebra-cache-20260715T233602Z-h226122-bft9536-strict-verified snapshot, independently verified block-for-block against the public explorer up to the >
  • zebrad run via systemd (Restart=always), internal_miner = true, disable_the_headless_wallet = false, disable_zaino = false

Steps to reproduce

  1. Start zebrad fresh (deleted secret.seed so the headless "user_wallet" gets a newly generated, private seed — not the shared workshop one). New wallet addresses on this run:
    • USER WALLET T-ADDRESS: tmN227oXVaNDtMRK3KTmRq7pe1imvABrmBS
    • USER WALLET ADDRESS: utest1905l6hjrhfek8y4d0h90stxgknpml778r30fvvm74yfcd2f6a8x29mtap8mz5nwqd094q3dda4ne87z5tvcuvhud8vca0n5egvgdhx2z
  2. Fund it via the built-in faucet RPC:
    curl -s -X POST -H "Content-Type: application/json" \
      -d '{"jsonrpc":"2.0","id":1,"method":"requestfaucetdonation","params":[{"address":"utest1905l6hjrhfek8y4d0h90stxgknpml778r30fvvm74yfcd2f6a8x29mtap8mz5nwqd094q3dda4ne87z5tvcuvhud8vca0n>
      http://127.0.0.1:8232/
    
    Response: {"amount":50000000} (at chain height 228236).
  3. Wait for confirmation (waited well over ten minutes; chain advanced ~250+ blocks).
  4. Repeatedly call:
    curl -s -X POST -H "Content-Type: application/json" \
      -d '{"jsonrpc":"2.0","id":1,"method":"wallet_staking_action","params":[{"CreateNewDelegationBond":{"amount_zats":100000,"target_finalizer":"2c17982d6fa4065013156f7ee5094b58d14e1c94131>
      http://127.0.0.1:8232/
    
    (finalizer ID taken from a live get_tfl_roster_zec response)
  5. Every call, across ~25+ attempts spanning >10 minutes and both before and after Zaino reported Ready, returned the same "failed to create staking transaction from notes" error, correlat>
  6. Waited for the node to reach full sync (verificationprogress: 1.0, blocks == estimatedheight) and retried the identical wallet_staking_action call. Same error, same `tx build: cou>

Secondary observation (separate from the anchor bug, but found in the same session)

The built-in headless miner wallet (miner_wallet) is derived from a hardcoded literal
BIP-39 mnemonic in wallet/src/lib.rs:

"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"

This is the standard all-zero BIP-39 test vector used across the industry — its private key
is public knowledge, not unique per node. Its transparent address
(tmRB9AEVsxNAQsqtJPqJUje9KCaAijpS77z on this network) shows getaddressbalance history of
~215,878 ctaz received against only ~10 ctaz currently held — i.e. the large majority of
everything ever mined to this address has already been swept out, consistent with it being
sweepable by anyone who recognizes the test mnemonic (which is trivial — it's the most common
placeholder phrase in crypto tooling). Worth flagging separately since it means any real value
sent to "the default miner wallet" on this devnet is not actually safe, even though the coins
themselves are only test-value.

Suggested next steps for maintainers

  • Reproduce with verbose/debug logging around the anchor-fetch path in the wallet tx builder
    (wallet/src/lib.rs, the stake_orchard_to_finalizer / continue_proposed_tx flow) to see
    exactly which height is requested vs. what Zaino has committed at that instant.
  • Consider whether the anchor lookup should retry against the current tree state rather than
    a specific height that may already be stale by the time the request lands, given how fast
    this devnet produces blocks. Note this reproduces at full sync too (3 blocks behind live tip),
    so it's not purely a catch-up artifact.
  • Look at whether the TX ERROR: no spend seen for send bookkeeping mismatch (zero
    spent_note_count alongside non-zero sent_note_count/sent_zats) is upstream of the
    anchor failure — e.g. the wallet losing track of which notes back a pending send.
  • Consider replacing the hardcoded miner_wallet test mnemonic with a per-node generated seed
    (like user_wallet already uses) if this wallet is ever meant to hold anything beyond
    disposable shared test funds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions