validators

package
v1.801.475 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 39 Imported by: 0

Documentation

Overview

Package validators is one-click validator onboarding: prove your Genesis NFT, get a node provisioned, queue its registration.

The /v1/validators/* routes are 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):

  1. GET /v1/validators/challenge?tokenId=N → a single-use, org-bound nonce + the exact message to personal_sign.
  2. 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.
  3. GET /v1/validators → the org's claimed slots + node status.
  4. 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

View Source
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

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires the validators surface onto app per HIP-0106.

func Shutdown

func Shutdown() error

Shutdown closes the validators store. Idempotent.

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 — the deployment's own "validators" subsystem — 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

func (s *Store) ClaimSlot(ctx context.Context, sl Slot) (Slot, error)

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) Close

func (s *Store) Close() error

Close closes the underlying database. Idempotent-safe via sql.DB.

func (*Store) ConsumeChallenge

func (s *Store) ConsumeChallenge(ctx context.Context, nonce, org string, now int64) error

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

func (s *Store) GetSlot(ctx context.Context, tokenID uint64) (Slot, error)

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) ListSlots

func (s *Store) ListSlots(ctx context.Context, org string, limit int) ([]Slot, error)

ListSlots returns an org's claimed slots, most-recently-updated first.

func (*Store) PurgeExpiredChallenges

func (s *Store) PurgeExpiredChallenges(ctx context.Context, now int64)

PurgeExpiredChallenges deletes consumed/expired nonces (housekeeping; called opportunistically on issue so the table never grows unbounded).

func (*Store) PutChallenge

func (s *Store) PutChallenge(ctx context.Context, nonce, org string, expiresAt, createdAt int64) error

PutChallenge stores a fresh nonce for an org with an absolute expiry.

func (*Store) SetSlotStatus

func (s *Store) SetSlotStatus(ctx context.Context, tokenID uint64, status string, updatedAt int64) error

SetSlotStatus advances a slot's lifecycle status (provisioning → node_created → registration_queued). Scoped by tokenId; the caller has already verified org.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL