builder_keys

package
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Aug 12, 2026 License: GPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package builder_keys owns buildoor's managed set of builder BLS keys: the internal derivation from the operator's entry key, each key's on-chain state (registration, balance, pending payments), the persisted usage history that makes withdrawn keys reusable, and the selection of a ready key per bid.

Two index spaces meet here and must never be conflated:

  • the KEY INDEX is our internal derivation index. It is stable forever; index 0 is the operator's entry key, so a single-key deployment keeps its identity when the fleet grows.
  • the BUILDER INDEX is the beacon registry index assigned at deposit time. It only exists once a key is registered and is reused by other builders after an exit.

Index

Constants

View Source
const (
	// StrategyRoundRobin rotates through the ready keys by slot, spreading bids
	// and their payments evenly across the fleet.
	StrategyRoundRobin = "round_robin"
	// StrategySingle always uses the lowest-index ready key. One key bids per
	// slot, which is how a single-key deployment behaves.
	StrategySingle = "single"
	// StrategyRandom shuffles the ready keys per slot (deterministically, so a
	// slot's assignment is reproducible).
	StrategyRandom = "random"
	// StrategyLeastUsed prefers the keys that have bid least, which keeps
	// balance drain even when slots are not evenly distributed.
	StrategyLeastUsed = "least_used"
)

Key selection strategies: which of the ready keys a bid is signed with when more keys are ready than the slot needs.

View Source
const Namespace = "builder_keys"

Namespace is the kv_store namespace holding per-key usage history.

Variables

This section is empty.

Functions

func NormalizedStrategy

func NormalizedStrategy(strategy string) string

NormalizedStrategy returns the strategy, falling back to round-robin for unknown values (config and plan overrides are free-form strings).

Types

type Aggregate

type Aggregate struct {
	Target  uint64 `json:"target"`
	Managed uint64 `json:"managed"`

	Unused     uint64 `json:"unused"`
	Depositing uint64 `json:"depositing"`
	Pending    uint64 `json:"pending"`
	Active     uint64 `json:"active"`
	Exiting    uint64 `json:"exiting"`
	Exited     uint64 `json:"exited"`
	Withdrawn  uint64 `json:"withdrawn"`

	TotalBalance         uint64 `json:"total_balance_gwei"`
	TotalPendingPayments uint64 `json:"total_pending_payments_gwei"`
	TotalEffective       uint64 `json:"total_effective_gwei"`
}

Aggregate summarises the whole key set for the dashboard.

type BalanceAdjuster

type BalanceAdjuster interface {
	GetBalanceAdjustment(keyIndex uint64) int64
}

BalanceAdjuster supplies the local balance delta of a key: credits from top-ups and debits from revealed payments that the latest beacon state snapshot does not reflect yet. Implemented by payload_bidder.PaymentTracker.

type ChangeEvent

type ChangeEvent struct {
	States    []*State
	Aggregate Aggregate
}

ChangeEvent carries the full key set whenever any key's state changes, so subscribers (the WebUI bridge) can push a complete snapshot without querying.

type Key

type Key struct {
	// contains filtered or unexported fields
}

Key is one derived builder key: a stable identity plus the latest snapshot of its on-chain state. Instances are created once by the Registry and are safe for concurrent use; the state snapshot is swapped atomically on every refresh, so readers on the bid/reveal hot paths never block or observe a torn value.

func (*Key) BLSSigner

func (k *Key) BLSSigner() *signer.BLSSigner

BLSSigner returns the underlying signer for bid, envelope and deposit signing.

func (*Key) BuilderIndex

func (k *Key) BuilderIndex() (uint64, bool)

BuilderIndex returns the on-chain builder index and whether the key is registered at all. Index 0 is a valid builder index, so the boolean — not a zero check — decides whether the value may be used.

func (*Key) KeyIndex

func (k *Key) KeyIndex() uint64

KeyIndex returns the internal derivation index (0 = the entry key).

func (*Key) Pubkey

func (k *Key) Pubkey() phase0.BLSPubKey

Pubkey returns the key's BLS public key.

func (*Key) State

func (k *Key) State() *State

State returns the latest state snapshot. Never nil.

func (*Key) Status

func (k *Key) Status() Status

Status returns the key's current lifecycle status.

func (*Key) String

func (k *Key) String() string

String renders the key for logs: internal index plus a pubkey prefix.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is the single owner of the builder key set: derivation, per-key on-chain state, usage history and selection. It is the identity dependency of every module that used to hold a single *signer.BLSSigner.

func NewRegistry

func NewRegistry(cfg *config.Config, entryPrivkeyHex string, log logrus.FieldLogger) (*Registry, error)

NewRegistry creates the key set rooted at the operator's entry key. It derives key 0 eagerly, which validates the supplied key material; every other key is derived on demand and cached for the process lifetime.

func (*Registry) Aggregate

func (r *Registry) Aggregate() Aggregate

Aggregate summarises the key set for the dashboard.

func (*Registry) AnyActive

func (r *Registry) AnyActive() bool

AnyActive reports whether at least one key is active on chain — the fleet-wide availability gate that replaces the single-builder registration check.

func (*Registry) ByBuilderIndex

func (r *Registry) ByBuilderIndex(builderIndex uint64) *Key

ByBuilderIndex returns the key registered under the given on-chain builder index, or nil when the index is not ours. This is how a won block's bid is traced back to the key that must sign the reveal.

func (*Registry) ByPubkey

func (r *Registry) ByPubkey(pubkey phase0.BLSPubKey) *Key

ByPubkey returns the key with the given public key, or nil.

func (*Registry) EffectiveBalance

func (r *Registry) EffectiveBalance(keyIndex uint64) uint64

EffectiveBalance returns the key's live spendable balance in gwei: the latest beacon snapshot combined with the current local adjustment, rather than the adjustment captured at the last refresh. Bid readiness and top-up decisions read this — a payment settled seconds ago must not still look spendable.

func (*Registry) Key

func (r *Registry) Key(keyIndex uint64) (*Key, error)

Key returns the derived key at the given internal index, deriving it if needed.

func (*Registry) Keys

func (r *Registry) Keys() []*Key

Keys returns every tracked key, key-index ascending.

func (*Registry) MarkDepositPending

func (r *Registry) MarkDepositPending(keyIndex uint64)

MarkDepositPending claims a key for a deposit that is being submitted, so a batch picking several candidates in a row does not pick the same key twice and a concurrent pass does not deposit for it again. Release it with ReleaseDepositPending when the submission never reached the chain, or confirm it with MarkDepositSubmitted.

func (*Registry) MarkDepositSubmitted

func (r *Registry) MarkDepositSubmitted(keyIndex uint64)

MarkDepositSubmitted records a confirmed deposit transaction for a key: it bumps the persisted use count (the key has consumed a deposit generation) and holds the key in the depositing state until the beacon state catches up.

func (*Registry) MarkExitSubmitted

func (r *Registry) MarkExitSubmitted(keyIndex uint64)

MarkExitSubmitted records a submitted exit request for a key and holds it in the exiting state until the beacon state carries the withdrawable epoch. Without that marker the key keeps reading active for the couple of epochs the exit needs to appear, and the reconciler re-submits it — paying the queue fee — on every pass.

func (*Registry) MarkToppedUp

func (r *Registry) MarkToppedUp(keyIndex uint64, epoch phase0.Epoch)

MarkToppedUp records the epoch of a submitted top-up, arming the per-key cooldown that keeps a low balance from queueing duplicate deposits before the pending one lands.

func (*Registry) NextDepositCandidate

func (r *Registry) NextDepositCandidate() *Key

NextDepositCandidate returns the lowest-index key eligible for a fresh deposit: never used, or used before and since gone from the builder registry. Picking the lowest index keeps the highest derivation index bounded as the operator ramps the target up and down over a devnet's lifetime.

It returns nil when the derivation cap leaves no eligible key.

func (*Registry) NextExitCandidate

func (r *Registry) NextExitCandidate() *Key

NextExitCandidate returns the highest-index key that can be exited: active on chain and free of pending payments, since the beacon chain silently ignores an exit request while a builder still owes one.

It returns nil while a higher-index key is still on its way to active. Those in-flight keys are the actual surplus — exiting a lower, usable key in their place would burn a key we just paid for and leave the fleet no smaller once they register.

func (*Registry) Primary

func (r *Registry) Primary() *Key

Primary returns the key that stands in wherever a single builder identity is required (the pre-Gloas Builder API, the legacy lifecycle endpoints): the lowest-index active key, or the entry key when none is active.

func (*Registry) PrimeKeyState

func (r *Registry) PrimeKeyState(keyIndex uint64, mutate func(*State)) (*Key, error)

PrimeKeyState derives the key at the given index, applies mutate to a copy of its state snapshot and publishes the result. It bypasses the chain refresh, so it is only for tests and for priming a key before any beacon state is available; the next Refresh overwrites whatever it set.

func (*Registry) RecordBid

func (r *Registry) RecordBid(keyIndex uint64)

RecordBid counts a submitted bid against a key. The counter feeds the least-used selection strategy and the UI, so it is published to the key's state snapshot immediately rather than at the next refresh — selection happens many times per slot, refreshes once per epoch.

func (*Registry) RecordWin

func (r *Registry) RecordWin(keyIndex uint64)

RecordWin counts a won slot against a key.

func (*Registry) Refresh

func (r *Registry) Refresh()

Refresh recomputes every key's state from the current beacon state and rescans for keys we have used before.

The scan always covers the target count and everything already tracked, and keeps going past that while it finds keys of ours — stopping only after a full run of never-used indices (the discovery gap). Unused indices above the target are derived but not tracked, so the fleet view stays the size of the fleet rather than the size of the scan. Fires a ChangeEvent when anything changed.

func (*Registry) ReleaseDepositPending

func (r *Registry) ReleaseDepositPending(keyIndex uint64)

ReleaseDepositPending clears the in-flight marker of a key whose deposit never reached the chain, so it becomes a candidate again immediately instead of waiting out the TTL.

func (*Registry) SelectForBid

func (r *Registry) SelectForBid(slot phase0.Slot, req SelectRequest) []*Key

SelectForBid returns the keys to sign a slot's bids with, ordered by the requested strategy. It returns nil when no ready key qualifies.

func (*Registry) SetBalanceAdjuster

func (r *Registry) SetBalanceAdjuster(adjuster BalanceAdjuster)

SetBalanceAdjuster wires the source of local balance deltas. Optional: without it, effective balances reflect the last beacon state snapshot only.

func (*Registry) Start

func (r *Registry) Start(ctx context.Context, chainSvc chain.Service, stateDB *db.Database) error

Start attaches usage persistence, runs the initial discovery scan and keeps key states in sync with every epoch's beacon state.

func (*Registry) State

func (r *Registry) State(keyIndex uint64) *State

State returns the state snapshot of one derived key, or nil when the index has not been derived.

func (*Registry) States

func (r *Registry) States() []*State

States returns a snapshot of every derived key's state, key-index ascending.

func (*Registry) Stop

func (r *Registry) Stop()

Stop stops the registry's refresh loop and flushes usage persistence.

func (*Registry) SubscribeChanges

func (r *Registry) SubscribeChanges(capacity int, blocking bool) *utils.Subscription[*ChangeEvent]

SubscribeChanges subscribes to key set changes.

type SelectRequest

type SelectRequest struct {
	// Strategy is the selection strategy; unknown values fall back to
	// round-robin.
	Strategy string
	// RequiredGwei is the bid value a returned key should be able to cover: a
	// builder whose effective balance is below its bid has the bid rejected on
	// chain. It is a preference, not a filter — keys that can cover it come
	// first, and an underfunded key is still returned when nothing else is
	// available, because deliberately underfunded bids are a scenario buildoor
	// exists to test.
	RequiredGwei uint64
	// Count caps how many keys to return; 0 means all ready keys.
	Count uint64
	// Exclude holds key indices already committed for this slot.
	Exclude map[uint64]struct{}
}

SelectRequest describes the keys a caller needs for one slot.

A key may only bid once per slot: the gossip rules ignore every bid after a builder's first for a slot, so a second bid from the same key never propagates. Callers therefore ask for distinct keys and pass the ones they already committed in Exclude.

type State

type State struct {
	KeyIndex uint64           `json:"key_index"`
	Pubkey   phase0.BLSPubKey `json:"-"`
	// PubkeyHex is the 0x-prefixed public key, for JSON consumers.
	PubkeyHex string `json:"pubkey"`
	Status    Status `json:"status"`

	// BuilderIndex is the on-chain registry index; only meaningful when
	// HasBuilderIndex is set (index 0 is a valid builder index).
	BuilderIndex    uint64 `json:"builder_index"`
	HasBuilderIndex bool   `json:"has_builder_index"`

	Balance           uint64 `json:"balance_gwei"`
	PendingPayments   uint64 `json:"pending_payments_gwei"`
	BalanceAdjustment int64  `json:"balance_adjustment_gwei"`
	// EffectiveBalance is the balance the key can actually bid against:
	// chain balance plus local adjustments minus pending payments.
	EffectiveBalance uint64 `json:"effective_balance_gwei"`

	DepositEpoch      uint64 `json:"deposit_epoch"`
	WithdrawableEpoch uint64 `json:"withdrawable_epoch"`

	// UseCount is how many deposit generations this key has gone through.
	UseCount      uint32 `json:"use_count"`
	LastDepositAt int64  `json:"last_deposit_at,omitempty"`
	LastExitAt    int64  `json:"last_exit_at,omitempty"`

	// LastTopupEpoch guards the per-key top-up cooldown (0 = never topped up).
	LastTopupEpoch phase0.Epoch `json:"last_topup_epoch,omitempty"`

	BidsSubmitted uint64 `json:"bids_submitted"`
	BidsWon       uint64 `json:"bids_won"`
}

State is an immutable snapshot of one builder key. It doubles as the API wire shape for the builder keys endpoint and SSE event.

func (*State) Ready

func (s *State) Ready(requiredGwei uint64) bool

Ready reports whether the key may be used for a bid of the given value: it must be active on chain and able to cover the payment. A builder whose effective balance is below the bid value has its bid rejected by the consensus layer, so bidding from it is pure noise.

type Status

type Status string

Status is a builder key's lifecycle position, derived on every refresh from the beacon state, the pending-deposit queue and our own usage history.

const (
	// StatusUnused: derived but never deposited. Available for a first deposit.
	StatusUnused Status = "unused"
	// StatusDepositing: a deposit was submitted (our own transaction or an entry
	// in the pending-deposit queue) but the key is not in the builder registry yet.
	StatusDepositing Status = "depositing"
	// StatusPending: registered, but the deposit epoch is not finalized, so the
	// key may not bid yet.
	StatusPending Status = "pending"
	// StatusActive: registered, finalized and not exiting — usable for bidding.
	StatusActive Status = "active"
	// StatusExiting: an exit was initiated; the withdrawable epoch is set but not
	// reached. The key can never be reactivated.
	StatusExiting Status = "exiting"
	// StatusExited: the withdrawable epoch has passed while the entry is still in
	// the registry.
	StatusExited Status = "exited"
	// StatusWithdrawn: the key was used before and its pubkey has left the builder
	// registry, so it is depositable again.
	StatusWithdrawn Status = "withdrawn"
)

func (Status) Depositable

func (s Status) Depositable() bool

Depositable reports whether a fresh deposit may be submitted for the key. Exited entries are excluded: per the Gloas spec a deposit cannot reactivate them, it is only swept back to the wallet.

func (Status) Managed

func (s Status) Managed() bool

Managed reports whether the status counts toward the target key count: keys that are registered or on their way there. Exiting/exited keys do not count — they can never bid again, so the reconciler must replace them.

type Usage

type Usage struct {
	KeyIndex uint64 `json:"key_index"`
	// Pubkey is the derived public key at the time of writing. A mismatch on
	// load means the entry key changed and the whole record set is about a
	// different fleet.
	Pubkey string `json:"pubkey"`
	// UseCount is how many deposit generations this key has gone through.
	UseCount      uint32 `json:"use_count"`
	FirstUsedAt   int64  `json:"first_used_at,omitempty"`
	LastDepositAt int64  `json:"last_deposit_at,omitempty"`
	LastExitAt    int64  `json:"last_exit_at,omitempty"`
}

Usage is the persisted history of one builder key. It survives restarts so a key that was deposited in an earlier run is recognised as ours even before the beacon state confirms it, and so a key that has cycled out of the registry can be reused instead of pushing the highest derivation index up forever.

type UsageCodec

type UsageCodec struct{}

UsageCodec translates key usage records to their persisted kv_store form: decimal key-index keys, JSON-encoded values.

func (UsageCodec) DecodeKey

func (UsageCodec) DecodeKey(key string) (uint64, error)

DecodeKey parses a decimal key index.

func (UsageCodec) DecodeValue

func (UsageCodec) DecodeValue(value []byte) (*Usage, error)

DecodeValue JSON-decodes a usage record.

func (UsageCodec) EncodeKey

func (UsageCodec) EncodeKey(keyIndex uint64) string

EncodeKey encodes an internal key index as its decimal string form.

func (UsageCodec) EncodeValue

func (UsageCodec) EncodeValue(usage *Usage) ([]byte, error)

EncodeValue JSON-encodes a usage record.

Jump to

Keyboard shortcuts

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