Tenzro
Operators

Validator lifecycle.

Four primitives keep the validator set stable across binary upgrades, peer churn, and key rotations. None of them require coordinated downtime; all are individually safe and independently composable. The full operator reference lives at docs/operators/OPERATOR_GUIDE.md §9.
STATUS
Testnet
TYPE
Reference
STABILITY
Stable
CRATES
tenzro-node / tenzro-token / tenzro-cli
01

Stable image upgrades — DB is preserved

Image rotation is a stop-and-restart against the same /var/lib/tenzro data directory. On boot the node runs verify_chain_compat against the configured genesis: if chain_id or genesis_state_roothas drifted from what’s on disk, the node refuses to start with an actionable error. Same genesis = preserve DB; different genesis = fail loud, operator decides.

This is the primitive every other lifecycle operation relies on. The canonical rotation script lives at tools/deploy/tenzro-stable-deploy.sh and is one sed + systemctl restart away from an in-place upgrade. The pre-stable deploy that wiped /var/lib/tenzro/db on every roll is no longer used.

02

Bootstrap discovery via DNS — no hardcoded peer IDs

Set --bootstrap-dns <zone> and the node resolves _tenzro-boot._tcp.<zone> SRV plus _tenzro-id._tcp.<target>TXT records to derive the boot peer list. Rotating a bootstrap validator’s key is a zone edit, not a fleet-wide wrapper-script update.

_tenzro-boot._tcp.boot.example.network. 60 IN SRV 10 0 9000 v0.boot.example.network.
_tenzro-boot._tcp.boot.example.network. 60 IN SRV 20 0 9000 v1.boot.example.network.
_tenzro-id._tcp.v0.boot.example.network. 60 IN TXT "peer_id=12D3KooW..."
_tenzro-id._tcp.v1.boot.example.network. 60 IN TXT "peer_id=12D3KooW..."
v0.boot.example.network. 60 IN A 198.51.100.10
v1.boot.example.network. 60 IN A 198.51.100.11

Each SRV target is paired with one TXT record carrying its libp2p peer ID. The node emits both TCP and QUIC multiaddrs per target so libp2p picks whichever NAT permits.

The DNS zone itself is the single source of truth for which validators are currently advertised as bootstrap entrypoints — at a known cost of DNS being a SPOF for that zone. That’s intentionally the same trust boundary as the genesis file.

03

Validator key rotation — tenzro_rotateValidatorKey

A validator rotates its Ed25519 / ML-DSA-65 / BLS12-381 keys without re-issuing genesis or re-staking. The RPC is gated on a signature produced by the validator’s current consensus key, the node verifies against the registry’s recorded consensus_pubkey, and on success the new ValidatorInfo is upserted into EpochManager.pending_validators.

Request shape:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tenzro_rotateValidatorKey",
  "params": {
    "address":               "0x<32-byte-hex>",
    "new_consensus_pubkey":  "0x<32-byte-hex>",
    "new_pq_pubkey":         "0x<1952-byte-hex>",
    "new_bls_pubkey":        "0x<48-byte-hex>",
    "nonce":                 7,
    "signature":             "0x<64-byte-hex>"
  }
}

Signing preimage:

SHA-256(
  "tenzro/rotate-validator-key" ||
  address (32B)          ||
  new_consensus           ||
  new_pq                  ||
  new_bls                 ||
  nonce_le (8B)
)

The atomic semantic: the validator continues to sign with its old keys until the next epoch boundary; at the boundary, every validator picks up the new key tuple at once.

Operators MUST fan the request out to every active validator before the boundary — see tools/deploy/rotate-validator-key.sh. Without the fan-out, validators that didn’t see the rotation will reject the rotating validator’s votes after the boundary.

SDK / CLI / MCP / A2A surfaces:

# Rust SDK
client.validators().rotate_keys(&RotateValidatorKeyRequest { ... }).await?;

# TypeScript SDK
await client.validators.rotateKeys({ ... });

# CLI
tenzro validator rotate-keys --address 0x... --new-consensus-pubkey 0x... \
  --new-pq-pubkey 0x... --new-bls-pubkey 0x... --nonce 7 --signature 0x...

# MCP (server.py tool)
await client.call_tool("rotate_validator_key", { ... })

# A2A (skill "validator-lifecycle", metadata.op = "rotate_keys")
04

Snapshot-based auto-catchup — fresh validators self-bootstrap

When a fresh validator boots with an empty data directory and --bootstrap-dns is set and the genesis carries a [weak_subjectivity] block, the node:

  • Resolves --bootstrap-dns to derive the boot peer list.
  • Derives an HTTPS RPC URL from the first usable multiaddr.
  • Calls tenzro_listSnapshots on that peer, picks the newest, fetches chunks, and verifies the manifest’s declared state_root against the genesis-embedded state_root_hex bit-for-bit, committing to local storage atomically.
  • Tail-replays the gap via gossipsub block-fetch.

The weak-subjectivity anchor is the trust gate. Without it the bootstrap is unauthenticated — the snapshot bootstrap is fail-closed by design and refuses to apply any chunk. Embedding the anchor in genesis.toml:

[weak_subjectivity]
height = 1450000
state_root_hex = "0xc1d8a7f3..."

The explicit --state-sync-from + --state-sync-anchor flags continue to take precedence when provided.

Related
← All docs