Documentation
¶
Overview ¶
Package validators mounts the Hanzo Cloud /v1/validators/* surface: the "click → provision node + queue registration" pipeline behind GDA/SDM validator onboarding on lux.cloud.
The end-to-end claim, all server-enforced at cloud's ONE auth boundary (SanitizeIdentity → principal.Org):
- GET /v1/validators/challenge?tokenId=N → a single-use, org-bound nonce + the exact message to personal_sign.
- POST /v1/validators {tokenId,nonce,signature} → verify the wallet controls the signature AND holds Validator-tier GenesisNFT #tokenId on Ethereum mainnet (ownerOf), then: generate a luxd staking identity → seal it into KMS (never plaintext) → write a LuxNetwork CR for a NEW node (never the live luxd) → ENQUEUE an owner-gated registration (NEVER auto-submitted to any P-Chain). Returns the slot + node + registration status.
- GET /v1/validators → the org's claimed slots + node status.
- GET /v1/validators/:tokenId → one slot's detail.
Tenant isolation is the org (principal.Org — the VALIDATED IAM owner, never a client header); every store query filters WHERE org=?. The tokenId IS the validator slot. serve.go auto-registers GET /v1/validators/health.
Index ¶
- Constants
- func Mount(app *zip.App, deps cloud.Deps) error
- func Shutdown() error
- type Registration
- type Slot
- type Store
- func (s *Store) ClaimSlot(ctx context.Context, sl Slot) (Slot, error)
- func (s *Store) Close() error
- func (s *Store) ConsumeChallenge(ctx context.Context, nonce, org string, now int64) error
- func (s *Store) EnqueueRegistration(ctx context.Context, r Registration) (Registration, error)
- func (s *Store) GetSlot(ctx context.Context, tokenID uint64) (Slot, error)
- func (s *Store) ListRegistrations(ctx context.Context, org string, limit int) ([]Registration, error)
- func (s *Store) ListSlots(ctx context.Context, org string, limit int) ([]Slot, error)
- func (s *Store) PurgeExpiredChallenges(ctx context.Context, now int64)
- func (s *Store) PutChallenge(ctx context.Context, nonce, org string, expiresAt, createdAt int64) error
- func (s *Store) SetSlotStatus(ctx context.Context, tokenID uint64, status string, updatedAt int64) error
Constants ¶
const GenesisNFTContract = "0x31e0F919C67ceDd2Bc3E294340Dc900735810311"
GenesisNFTContract is the ETH-mainnet ERC-721 whose Validator-tier tokens gate validator onboarding. Overridable via VALIDATORS_NFT_CONTRACT for testnet, but this is the live mainnet address (50 minted / 28 holders as of the Genesis re-mint). NEVER the dead C-Chain copy or the GaugeController mis-address.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Registration ¶
type Registration struct {
ID string `json:"id"`
TokenID uint64 `json:"tokenId"`
Org string `json:"-"`
NodeID string `json:"nodeID"`
BLSPubkey string `json:"blsPubkey"`
Weight uint64 `json:"weight"`
Status string `json:"status"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
Registration is a queued, OWNER-GATED request to add the node to a validator set via a classic AddPermissionlessValidatorTx. It is enqueued `pending_owner_approval` and NEVER auto-submitted — the owner co-signs out of band (Phase 2). This package has NO P-Chain submit path.
type Slot ¶
type Slot struct {
TokenID uint64 `json:"tokenId"`
Org string `json:"-"`
Wallet string `json:"wallet"`
NodeID string `json:"nodeID"`
KMSRef string `json:"-"`
CRName string `json:"crName"`
Namespace string `json:"namespace"`
BLSPubkey string `json:"blsPubkey"`
Status string `json:"status"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
Slot is an org's claimed validator slot: the on-chain NFT tokenId (== slot), the wallet that proved ownership, the generated node identity, and where its node lives. Status walks provisioning → node_created → registration_queued.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the validators database. ONE SQLite file ({DataDir}/validators.db) holds every org's entitlements, the owner-gated registration queue, and the short-lived wallet-signature challenges. Tenant isolation is the `org` column, enforced on EVERY org-scoped query. MaxOpenConns(1) serializes writes against the single-writer file (mirrors clients/ads).
func (*Store) ClaimSlot ¶
ClaimSlot inserts a new slot entitlement. It is idempotent for the SAME org (a re-claim of a slot this org already holds returns the existing row) and fails with errConflict if a DIFFERENT org already holds the slot — a second org can only reach here by owning the NFT, which an ERC-721 forbids, so this is the defense-in-depth backstop.
func (*Store) ConsumeChallenge ¶
ConsumeChallenge atomically validates + burns a nonce: it must exist, belong to org, be unconsumed, and be unexpired at `now`. It returns errChallenge on any of those failing. The single UPDATE...WHERE is the atomic compare-and-set that makes a nonce strictly single-use even under concurrent claims.
func (*Store) EnqueueRegistration ¶
func (s *Store) EnqueueRegistration(ctx context.Context, r Registration) (Registration, error)
EnqueueRegistration inserts an owner-gated registration. Idempotent per tokenId (the unique index): a re-enqueue returns the existing row so a retried provision never queues a duplicate.
func (*Store) GetSlot ¶
GetSlot returns a slot by tokenId REGARDLESS of org (the token is global). The caller compares .Org to enforce tenant ownership. errNotFound when absent.
func (*Store) ListRegistrations ¶
func (s *Store) ListRegistrations(ctx context.Context, org string, limit int) ([]Registration, error)
ListRegistrations returns an org's registration queue, newest first.
func (*Store) PurgeExpiredChallenges ¶
PurgeExpiredChallenges deletes consumed/expired nonces (housekeeping; called opportunistically on issue so the table never grows unbounded).