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
- func NormalizedStrategy(strategy string) string
- type Aggregate
- type BalanceAdjuster
- type ChangeEvent
- type Key
- type Registry
- func (r *Registry) Aggregate() Aggregate
- func (r *Registry) AnyActive() bool
- func (r *Registry) ByBuilderIndex(builderIndex uint64) *Key
- func (r *Registry) ByPubkey(pubkey phase0.BLSPubKey) *Key
- func (r *Registry) EffectiveBalance(keyIndex uint64) uint64
- func (r *Registry) Key(keyIndex uint64) (*Key, error)
- func (r *Registry) Keys() []*Key
- func (r *Registry) MarkDepositPending(keyIndex uint64)
- func (r *Registry) MarkDepositSubmitted(keyIndex uint64)
- func (r *Registry) MarkExitSubmitted(keyIndex uint64)
- func (r *Registry) MarkToppedUp(keyIndex uint64, epoch phase0.Epoch)
- func (r *Registry) NextDepositCandidate() *Key
- func (r *Registry) NextExitCandidate() *Key
- func (r *Registry) Primary() *Key
- func (r *Registry) PrimeKeyState(keyIndex uint64, mutate func(*State)) (*Key, error)
- func (r *Registry) RecordBid(keyIndex uint64)
- func (r *Registry) RecordWin(keyIndex uint64)
- func (r *Registry) Refresh()
- func (r *Registry) ReleaseDepositPending(keyIndex uint64)
- func (r *Registry) SelectForBid(slot phase0.Slot, req SelectRequest) []*Key
- func (r *Registry) SetBalanceAdjuster(adjuster BalanceAdjuster)
- func (r *Registry) Start(ctx context.Context, chainSvc chain.Service, stateDB *db.Database) error
- func (r *Registry) State(keyIndex uint64) *State
- func (r *Registry) States() []*State
- func (r *Registry) Stop()
- func (r *Registry) SubscribeChanges(capacity int, blocking bool) *utils.Subscription[*ChangeEvent]
- type SelectRequest
- type State
- type Status
- type Usage
- type UsageCodec
Constants ¶
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.
const Namespace = "builder_keys"
Namespace is the kv_store namespace holding per-key usage history.
Variables ¶
This section is empty.
Functions ¶
func NormalizedStrategy ¶
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 ¶
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 ¶
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 ¶
BLSSigner returns the underlying signer for bid, envelope and deposit signing.
func (*Key) BuilderIndex ¶
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.
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) AnyActive ¶
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 ¶
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) EffectiveBalance ¶
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 ¶
Key returns the derived key at the given internal index, deriving it if needed.
func (*Registry) MarkDepositPending ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) 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 ¶
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 ¶
Start attaches usage persistence, runs the initial discovery scan and keeps key states in sync with every epoch's beacon state.
func (*Registry) State ¶
State returns the state snapshot of one derived key, or nil when the index has not been derived.
func (*Registry) States ¶
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.
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 ¶
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.
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.