action_plan

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: 22 Imported by: 0

Documentation

Overview

Package action_plan implements the per-slot action plan: sparse, persisted per-slot operation modes for bidding, builder-api serving and reveal, with freeze semantics so a slot's plan becomes immutable once execution for it can begin.

Index

Constants

View Source
const (
	// BuildSkipReasonSchedule marks slots skipped by the schedule (start
	// slot, every_nth cadence or an exhausted next_n budget).
	BuildSkipReasonSchedule = "schedule"
	// BuildSkipReasonPlanDisabled marks slots where the per-slot plan
	// suppressed a consumer that would otherwise be active, leaving no
	// effectively active consumer.
	BuildSkipReasonPlanDisabled = "plan_disabled"
	// BuildSkipReasonNoConsumer marks slots where no consumer is effectively
	// active (all globally disabled or not applicable) without a plan being
	// responsible for it.
	BuildSkipReasonNoConsumer = "no_consumer"
)

Build skip reasons carried by ResolvedBuildSettings.SkipReason.

View Source
const MaxRules = 32

MaxRules bounds the persisted rule set. Rules are evaluated per slot, and a long list is a sign that explicit plans are the better tool.

View Source
const MaxSlotsPerUpdate = 6400

MaxSlotsPerUpdate bounds the unique slots one ApplyUpdates call may target (200 epochs at 32 slots each).

View Source
const Namespace = "slot_plans"

Namespace is the kv_store namespace holding the persisted slot plans.

View Source
const RulesNamespace = "slot_rules"

RulesNamespace is the kv_store namespace holding the persisted recurring rules.

Variables

View Source
var ErrSlotLocked = errors.New("slot is in the past or already frozen")

ErrSlotLocked is returned (wrapped) when an update targets a slot that is in the past or whose plan is already frozen. API handlers map it to 409 Conflict.

Functions

This section is empty.

Types

type BidPlan

type BidPlan struct {
	Mode Mode `json:"mode"`

	BidStartTime *int64  `json:"bid_start_time,omitempty"`
	BidEndTime   *int64  `json:"bid_end_time,omitempty"`
	BidMinAmount *uint64 `json:"bid_min_amount,omitempty"` // gwei
	BidIncrease  *uint64 `json:"bid_increase,omitempty"`   // gwei
	BidInterval  *int64  `json:"bid_interval,omitempty"`   // ms, >= 0, 0 = single bid
	BidSubsidy   *uint64 `json:"bid_subsidy,omitempty"`    // gwei

	// BidCandidate overrides which built candidate payload this slot's p2p
	// bids commit to: auto, all, or a specific candidate key.
	BidCandidate *string `json:"bid_candidate,omitempty"`

	// KeyStrategy overrides which managed builder key signs this slot's bids:
	// round_robin, single, random or least_used.
	KeyStrategy *string `json:"key_strategy,omitempty"`

	// BidKeysPerSlot overrides how many distinct builder keys may bid this
	// slot (0 = no cap beyond the fleet).
	BidKeysPerSlot *uint64 `json:"bid_keys_per_slot,omitempty"`

	// BidKeysPerStep overrides how many keys bid a payload per interval step
	// (0 = every remaining key at once).
	BidKeysPerStep *uint64 `json:"bid_keys_per_step,omitempty"`

	// BidValueGwei is an absolute bid base value replacing
	// max(blockValue, min) + subsidy; BidIncrease still applies per re-bid.
	// Allows underbidding the block value for testing.
	BidValueGwei *uint64 `json:"bid_value_gwei,omitempty"`

	// IgnoreMissingPrefs bids with the payload's fee recipient when no gossip
	// proposer preferences arrived for the slot, bypassing the skip gate.
	IgnoreMissingPrefs bool `json:"ignore_missing_prefs,omitempty"`
}

BidPlan is the per-slot p2p bidding instruction. All override fields are optional; nil inherits the effective global configuration at freeze time. Timing fields are signed milliseconds relative to slot start (negative = before slot start), matching the global EPBS config semantics.

type BuildPlan

type BuildPlan struct {
	// ReorgParentPayload builds on the grandparent (n-2) execution payload
	// instead of the immediate parent: the FCU head block hash and the payload
	// attributes' withdrawals are taken from the PARENT slot's payload
	// attributes (whose parent is n-2), while every other property comes from
	// the current slot. This is a deliberate parent-payload reorg attempt —
	// rejected by mainnet forkchoice, but useful for exercising the reveal /
	// inclusion path against a withheld parent.
	ReorgParentPayload bool `json:"reorg_parent_payload,omitempty"`

	// Candidates overrides the global build-candidate policy for this slot:
	// candidate key (parent_full, parent_empty, grandparent_full,
	// grandparent_empty) -> mode (auto, always, never). Absent keys inherit
	// the global policy.
	Candidates map[string]string `json:"candidates,omitempty"`
}

BuildPlan is the per-slot payload-build instruction. Unlike the consumer categories it carries no custom/disabled mode: it only tweaks HOW the slot's payload is built when a build happens (the build decision itself stays with the schedule + consumer plans).

type BuilderAPIPlan

type BuilderAPIPlan struct {
	Mode Mode `json:"mode"`

	// ValueSubsidyGwei replaces the global BlockValueSubsidyGwei for this slot.
	ValueSubsidyGwei *uint64 `json:"value_subsidy_gwei,omitempty"`

	// TotalValueOverrideGwei is the absolute total proposer-visible bid value
	// (before the Gloas execution-payment split), replacing block value +
	// subsidy. May exceed the block value to test payment edge cases.
	TotalValueOverrideGwei *uint64 `json:"total_value_override_gwei,omitempty"`

	// ResponseDelayMs delays the bid response by this many milliseconds
	// (context-cancellable, capped at one slot).
	ResponseDelayMs *int64 `json:"response_delay_ms,omitempty"`

	// KeyStrategy overrides which managed builder key signs this slot's served
	// bids: round_robin, single, random or least_used.
	KeyStrategy *string `json:"key_strategy,omitempty"`

	// ServeCandidates overrides which built candidate payloads bid requests
	// for this slot may be answered from: all, canonical_only, or a
	// comma-separated candidate key list.
	ServeCandidates *string `json:"serve_candidates,omitempty"`
}

BuilderAPIPlan is the per-slot Builder API serving instruction for both the legacy getHeader and the Gloas getExecutionPayloadBid endpoints.

type FrozenPlan

type FrozenPlan struct {
	Slot     phase0.Slot `json:"slot"`
	Plan     *SlotPlan   `json:"plan,omitempty"` // raw sparse plan; nil = none existed
	Fork     string      `json:"fork"`           // fork name at the target slot
	FrozenAt time.Time   `json:"frozen_at"`

	// Build is the complete resolved build decision for the slot (schedule +
	// plan force/suppress). Always non-nil.
	Build *ResolvedBuildSettings `json:"build"`

	// Resolved effective settings; a nil category = suppressed for this slot
	// (by plan or by the global enable flags / protocol applicability).
	Bid        *ResolvedBidSettings        `json:"bid,omitempty"`
	BuilderAPI *ResolvedBuilderAPISettings `json:"builder_api,omitempty"`
	Reveal     *ResolvedRevealSettings     `json:"reveal,omitempty"`

	// Transforms carries the effective jq transform expressions (empty when no
	// transform plan applies). Nil only when no plan expression is set.
	Transforms *ResolvedTransforms `json:"transforms,omitempty"`
}

FrozenPlan is the immutable per-slot execution snapshot taken when execution for the slot can begin: the raw sparse plan plus all effective settings resolved from the live global config at freeze time. Consumers act on the resolved values for the rest of the slot, so later global config changes never rewrite a partially executed slot. Runtime availability gates (registration, signer, server) are still checked per action.

type Mode

type Mode string

Mode is a per-category plan mode. Only explicit instructions are persisted: an absent category inherits the global baseline; "custom" force-activates the category for the slot (even when the module is globally disabled) with optional setting overrides; "disabled" suppresses it.

const (
	// ModeDisabled suppresses the category for the slot.
	ModeDisabled Mode = "disabled"
	// ModeCustom force-activates the category for the slot with optional
	// setting overrides (omitted overrides inherit the global config).
	ModeCustom Mode = "custom"
)

type PlanChangeEvent

type PlanChangeEvent struct {
	Slots []uint64    `json:"slots"`
	Plans []*SlotPlan `json:"plans"`
}

PlanChangeEvent describes one committed ApplyUpdates call: the authoritative normalized result, not merely a count. Plans is index-aligned with Slots; a nil entry means the slot's plan was deleted.

type PlanCodec

type PlanCodec struct{}

PlanCodec translates slot plans to their persisted form in the kv_store: decimal slot string keys, JSON-encoded values. JSON is deliberate — the SlotPlan is a local aggregate, not a spec SSZ type.

func (PlanCodec) DecodeKey

func (PlanCodec) DecodeKey(key string) (phase0.Slot, error)

DecodeKey parses a decimal slot string.

func (PlanCodec) DecodeValue

func (PlanCodec) DecodeValue(value []byte) (*SlotPlan, error)

DecodeValue JSON-decodes a slot plan.

func (PlanCodec) EncodeKey

func (PlanCodec) EncodeKey(slot phase0.Slot) string

EncodeKey encodes a slot as its decimal string form.

func (PlanCodec) EncodeValue

func (PlanCodec) EncodeValue(plan *SlotPlan) ([]byte, error)

EncodeValue JSON-encodes a slot plan.

type PlanService

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

PlanService owns the sparse per-slot action plan store, the recurring rules that fill the slots it does not cover, their freeze state and their persistence. It is the single writer; all reads return deep copies.

func NewPlanService

func NewPlanService(cfg *config.Config, chainSvc chain.Service, log logrus.FieldLogger) *PlanService

NewPlanService creates the plan service. The config pointer is the shared live config; enable flags and default settings are read from it at freeze time.

func (*PlanService) ApplyUpdates

func (s *PlanService) ApplyUpdates(updates []*PlanUpdate, actor string) (*PlanChangeEvent, error)

ApplyUpdates validates and applies a bulk plan mutation atomically: either every targeted slot is updated or none is. Overlapping updates are applied in request order. The returned event is the authoritative normalized result (also fired to change subscribers).

func (*PlanService) Freeze

func (s *PlanService) Freeze(slot phase0.Slot) *FrozenPlan

Freeze resolves and records the slot's immutable execution snapshot. The first caller wins; every later caller (and every other decision point of the same slot) receives the identical snapshot. From this moment on the slot's plan can no longer be edited, and later rule changes no longer affect it.

func (*PlanService) Get

func (s *PlanService) Get(slot phase0.Slot) *SlotPlan

Get returns a deep copy of the slot's explicitly stored plan, or nil when none exists. Recurring rules are deliberately not consulted — use PlanForSlot for the effective plan.

func (*PlanService) GetRange

func (s *PlanService) GetRange(minSlot, maxSlot phase0.Slot) []*SlotPlan

GetRange returns deep copies of all effective plans within [minSlot, maxSlot], slot-ascending: every explicitly stored plan plus the plans recurring rules contribute to the still-open slots of the range.

func (*PlanService) GetSlotsBuilt

func (s *PlanService) GetSlotsBuilt() uint64

GetSlotsBuilt returns the number of schedule-consuming builds so far.

func (*PlanService) GetSlotsRemaining

func (s *PlanService) GetSlotsRemaining() int

GetSlotsRemaining returns the remaining next_n build budget, or -1 when the schedule is unlimited.

func (*PlanService) IsFrozen

func (s *PlanService) IsFrozen(slot phase0.Slot) bool

IsFrozen reports whether the slot's plan has been frozen already.

func (*PlanService) OnSlotBuilt

func (s *PlanService) OnSlotBuilt(slot phase0.Slot)

OnSlotBuilt records a successfully built slot for the next_n schedule accounting. Forced (plan-activated) builds never consume the budget.

func (*PlanService) PlanForSlot

func (s *PlanService) PlanForSlot(slot phase0.Slot) *SlotPlan

PlanForSlot returns the effective plan for a slot: the explicitly stored plan if there is one, otherwise the first matching recurring rule materialized for the slot (marked with its rule id). Precedence is wholesale — an explicit plan is never merged with a rule.

func (*PlanService) Rules

func (s *PlanService) Rules() []*SlotRule

Rules returns deep copies of all recurring rules, id-ascending — which is also the order in which they are matched.

func (*PlanService) SetPersistence

func (s *PlanService) SetPersistence(ctx context.Context, stateDB *db.Database)

SetPersistence attaches the state-db backed persistence (kv_store namespaces "slot_plans" and "slot_rules") and rehydrates previously stored plans and recurring rules.

func (*PlanService) SetRules

func (s *PlanService) SetRules(rules []*SlotRule, actor string) ([]*SlotRule, error)

SetRules atomically replaces the whole recurring rule set: either every rule validates and is committed, or nothing changes. Already frozen slots keep the plan they froze with; every later slot resolves against the new set.

func (*PlanService) Start

func (s *PlanService) Start(ctx context.Context) error

Start launches the pruning loop (driven by epoch transitions).

func (*PlanService) Stop

func (s *PlanService) Stop()

Stop terminates the pruning loop and flushes the store. Must be called before the state-db closes.

func (*PlanService) SubscribeChanges

func (s *PlanService) SubscribeChanges(capacity int) *utils.Subscription[*PlanChangeEvent]

SubscribeChanges subscribes to committed plan mutations (non-blocking delivery; intended for the SSE bridge).

func (*PlanService) SubscribeRuleChanges

func (s *PlanService) SubscribeRuleChanges(capacity int) *utils.Subscription[*RuleChangeEvent]

SubscribeRuleChanges subscribes to committed recurring-rule mutations (non-blocking delivery; intended for the SSE bridge).

func (*PlanService) UpdateConfig

func (s *PlanService) UpdateConfig()

UpdateConfig reacts to global settings changes. Switching to next_n mode resets the built-slot counter so the budget restarts (mirroring the previous slot-manager behavior).

type PlanUpdate

type PlanUpdate struct {
	Slots    []uint64 `json:"slots,omitempty"`
	FromSlot *uint64  `json:"from_slot,omitempty"`
	ToSlot   *uint64  `json:"to_slot,omitempty"`
	Delete   bool     `json:"delete,omitempty"`

	Bid        json.RawMessage `json:"bid,omitempty"`
	BuilderAPI json.RawMessage `json:"builder_api,omitempty"`
	Reveal     json.RawMessage `json:"reveal,omitempty"`
	Build      json.RawMessage `json:"build,omitempty"`
	Transforms json.RawMessage `json:"transforms,omitempty"`

	// IgnoreRules is two-state on purpose: absent = unchanged, true/false =
	// set the slot's recurring-rule opt-out.
	IgnoreRules *bool `json:"ignore_rules,omitempty"`

	Set map[string]json.RawMessage `json:"set,omitempty"`
}

PlanUpdate is one mutation unit of the bulk-update API. Targets are the union of Slots and the inclusive FromSlot..ToSlot range. Category members are three-state: absent = unchanged, JSON null = clear (back to inherit), object = replace. Delete removes the whole plan for the targeted slots.

Set applies fine-grained path updates AFTER the category members, so consumers never need to send a full category object for partial edits:

"bid"                    → whole category (null = clear, object = replace)
"bid.mode"               → single field ("custom" | "disabled")
"bid.bid_min_amount"     → single override (null = back to inherit)

Setting a field on an absent category creates it with mode "custom". Paths are applied in lexicographic order (deterministic).

func (*PlanUpdate) TargetSlots

func (u *PlanUpdate) TargetSlots() ([]phase0.Slot, error)

TargetSlots expands the update's slot list and range into the full target set. The expansion is overflow-safe and bounded by MaxSlotsPerUpdate.

type ResolvedBidSettings

type ResolvedBidSettings struct {
	StartMs      int64  `json:"start_ms"`
	EndMs        int64  `json:"end_ms"`
	IntervalMs   int64  `json:"interval_ms"`
	MinGwei      uint64 `json:"min_gwei"`
	IncreaseGwei uint64 `json:"increase_gwei"`
	SubsidyGwei  uint64 `json:"subsidy_gwei"`

	// ValueGwei, when set, is the absolute bid base value replacing the
	// max(blockValue, min) + subsidy formula (IncreaseGwei still applies).
	ValueGwei *uint64 `json:"value_gwei,omitempty"`

	// IgnoreMissingPrefs bids without gossip proposer preferences.
	IgnoreMissingPrefs bool `json:"ignore_missing_prefs,omitempty"`

	// BidCandidate is the effective bid candidate selection for the slot:
	// auto, all, or a specific candidate key.
	BidCandidate string `json:"bid_candidate,omitempty"`

	// KeyStrategy is the effective builder key selection strategy for the
	// slot's bids.
	KeyStrategy string `json:"key_strategy,omitempty"`

	// BidKeysPerSlot caps how many distinct builder keys bid the slot
	// (0 = no cap beyond the fleet).
	BidKeysPerSlot uint64 `json:"bid_keys_per_slot,omitempty"`

	// BidKeysPerStep is how many keys bid a payload per interval step
	// (0 = every remaining key at once).
	BidKeysPerStep uint64 `json:"bid_keys_per_step,omitempty"`

	// Forced marks that the plan activated bidding although the module is
	// globally disabled.
	Forced bool `json:"forced,omitempty"`
}

ResolvedBidSettings are the effective p2p bidding parameters for the slot.

func (*ResolvedBidSettings) EffectiveKeysPerStep

func (s *ResolvedBidSettings) EffectiveKeysPerStep() uint64

EffectiveKeysPerStep returns how many keys one step may spend: the configured count, or every key the fleet has left when unset.

type ResolvedBuildSettings

type ResolvedBuildSettings struct {
	// Build is the final decision: build a payload for this slot or not.
	Build bool `json:"build"`

	// Forced marks builds the plan pushed past the schedule (they never
	// consume the next_n budget).
	Forced bool `json:"forced,omitempty"`

	// SkipReason is one of the BuildSkipReason* constants when Build is
	// false, empty otherwise.
	SkipReason string `json:"skip_reason,omitempty"`

	// PlanInvolved marks decisions where a per-slot plan existed or any
	// consumer was effectively active — i.e. skips worth surfacing.
	PlanInvolved bool `json:"plan_involved,omitempty"`

	// BuildStartTimeMs is the effective build start time, milliseconds
	// relative to slot start (signed).
	BuildStartTimeMs int64 `json:"build_start_time_ms"`

	// ReorgParentPayload builds the slot's payload on the grandparent (n-2)
	// execution payload instead of the immediate parent — a deliberate
	// parent-payload reorg attempt (see BuildPlan.ReorgParentPayload).
	ReorgParentPayload bool `json:"reorg_parent_payload,omitempty"`

	// CandidateModes is the effective build-candidate policy for the slot:
	// candidate key -> auto/always/never, merged from the global config and
	// the plan's build.candidates overrides.
	CandidateModes map[string]string `json:"candidate_modes,omitempty"`
}

ResolvedBuildSettings is the effective build decision and timing for a slot. The plan service is the single scheduling authority: it resolves the global schedule (all/every_nth/next_n, start slot), the per-slot plan's force/ suppress instructions and the effective build timing in one place.

type ResolvedBuilderAPISettings

type ResolvedBuilderAPISettings struct {
	SubsidyGwei uint64 `json:"subsidy_gwei"`

	// TotalValueGwei, when set, is the absolute total proposer-visible bid
	// value (before the Gloas execution-payment split).
	TotalValueGwei *uint64 `json:"total_value_gwei,omitempty"`

	DelayMs int64 `json:"delay_ms,omitempty"`

	// ServeCandidates is the effective serve-candidates policy for the slot
	// (all, canonical_only, or a comma-separated candidate key list).
	ServeCandidates string `json:"serve_candidates,omitempty"`

	// KeyStrategy is the effective builder key selection strategy for the
	// slot's served bids.
	KeyStrategy string `json:"key_strategy,omitempty"`

	// Forced marks that the plan activated serving although the module is
	// globally disabled.
	Forced bool `json:"forced,omitempty"`
}

ResolvedBuilderAPISettings are the effective Builder API bid-serving parameters for the slot.

type ResolvedRevealSettings

type ResolvedRevealSettings struct {
	Suppressed   bool  `json:"suppressed,omitempty"`
	RevealTimeMs int64 `json:"reveal_time_ms"`

	// GateMode decides the reveal moment: time | vote | vote_or_time |
	// vote_and_time.
	GateMode string `json:"gate_mode"`

	// VoteThresholdPct is the participation threshold (percent) opening the
	// vote gate.
	VoteThresholdPct uint64 `json:"vote_threshold_pct"`

	// BroadcastValidation is the envelope submission's broadcast_validation
	// level: gossip | consensus | consensus_and_equivocation.
	BroadcastValidation string `json:"broadcast_validation"`

	// MaxAttempts / RetryIntervalMs are the publish retry policy
	// (global-only, no per-slot override).
	MaxAttempts     uint64 `json:"max_attempts"`
	RetryIntervalMs int64  `json:"retry_interval_ms"`

	// BypassDeadline disables the "past the in-slot deadline → skip" check so
	// deliberately late reveals are attempted.
	BypassDeadline bool `json:"bypass_deadline,omitempty"`
}

ResolvedRevealSettings are the effective reveal parameters for the slot. The category is always present with a Suppressed marker instead of being nil: suppression comes from the plan's disabled mode or the global reveal.enabled switch (a plan-custom slot force-activates either way).

type ResolvedTransforms

type ResolvedTransforms struct {
	Payload  string `json:"payload,omitempty"`
	Bid      string `json:"bid,omitempty"`
	Envelope string `json:"envelope,omitempty"`
}

ResolvedTransforms are the effective jq transform expressions for the slot. An empty string means no transform for that object.

type RevealPlan

type RevealPlan struct {
	Mode Mode `json:"mode"`

	// RevealTimeMs is signed milliseconds relative to slot start (time gate).
	RevealTimeMs *int64 `json:"reveal_time_ms,omitempty"`

	// GateMode overrides the reveal gate: time | vote | vote_or_time |
	// vote_and_time.
	GateMode *string `json:"gate_mode,omitempty"`

	// VoteThresholdPct overrides the vote gate's participation threshold.
	VoteThresholdPct *uint64 `json:"vote_threshold_pct,omitempty"`

	// BroadcastValidation overrides the envelope submission's broadcast
	// validation level: gossip | consensus | consensus_and_equivocation.
	BroadcastValidation *string `json:"broadcast_validation,omitempty"`
}

RevealPlan is the per-slot payload reveal instruction. "disabled" withholds the envelope, "custom" force-activates the reveal (even when globally disabled) and overrides the gate settings (possibly past the in-slot deadline for adverse testing, clamped to at most one additional slot after slot end).

type RuleChangeEvent

type RuleChangeEvent struct {
	Rules []*SlotRule `json:"rules"`
}

RuleChangeEvent carries the authoritative rule set after a committed change.

type RuleCodec

type RuleCodec struct{}

RuleCodec translates recurring slot rules to their persisted form in the kv_store: the rule id as key, JSON-encoded values.

func (RuleCodec) DecodeKey

func (RuleCodec) DecodeKey(key string) (string, error)

DecodeKey uses the stored key verbatim.

func (RuleCodec) DecodeValue

func (RuleCodec) DecodeValue(value []byte) (*SlotRule, error)

DecodeValue JSON-decodes a slot rule.

func (RuleCodec) EncodeKey

func (RuleCodec) EncodeKey(id string) string

EncodeKey uses the rule id verbatim.

func (RuleCodec) EncodeValue

func (RuleCodec) EncodeValue(rule *SlotRule) ([]byte, error)

EncodeValue JSON-encodes a slot rule.

type SlotPlan

type SlotPlan struct {
	Slot       phase0.Slot     `json:"slot"`
	Bid        *BidPlan        `json:"bid,omitempty"`
	BuilderAPI *BuilderAPIPlan `json:"builder_api,omitempty"`
	Reveal     *RevealPlan     `json:"reveal,omitempty"`
	Build      *BuildPlan      `json:"build,omitempty"`
	Transforms *TransformPlan  `json:"transforms,omitempty"`

	// IgnoreRules opts the slot out of every recurring rule, so it runs on the
	// plain global baseline. Without it an empty plan is dropped, which would
	// hand the slot straight back to the matching rule.
	IgnoreRules bool `json:"ignore_rules,omitempty"`

	// RuleID marks a plan synthesized from a recurring rule. Synthesized plans
	// are never stored — the field is empty on every persisted plan.
	RuleID string `json:"rule_id,omitempty"`

	UpdatedAt time.Time `json:"updated_at"`
	UpdatedBy string    `json:"updated_by"`
}

SlotPlan is the persisted per-slot instruction set. Each category is optional; an absent category inherits the global baseline (including the module enable flags).

func ApplyUpdateToPlan

func ApplyUpdateToPlan(existing *SlotPlan, u *PlanUpdate) (*SlotPlan, error)

ApplyUpdateToPlan merges a single update into an existing plan (which may be nil) and returns the resulting plan, or nil when the result carries no instruction anymore. The existing plan is never mutated. The returned plan is not yet validated against the slot duration; callers run Validate.

func (*SlotPlan) BidOverride

func (p *SlotPlan) BidOverride() *bool

BidOverride returns the per-slot enable override for p2p bidding: nil = inherit the global flag, true = force-active, false = suppressed.

func (*SlotPlan) BuilderAPIOverride

func (p *SlotPlan) BuilderAPIOverride() *bool

BuilderAPIOverride returns the per-slot enable override for Builder API bid serving: nil = inherit, true = force-active, false = suppressed.

func (*SlotPlan) Clone

func (p *SlotPlan) Clone() *SlotPlan

Clone returns a deep copy. All plan values crossing the package boundary are clones, so callers can never mutate stored state.

func (*SlotPlan) IsEmpty

func (p *SlotPlan) IsEmpty() bool

IsEmpty reports whether the plan carries no instruction at all. An explicit rule opt-out is an instruction, so such a plan is kept.

func (*SlotPlan) RevealOverride

func (p *SlotPlan) RevealOverride() *bool

RevealOverride returns the per-slot enable override for the reveal: nil = inherit (reveals always run), true = custom, false = suppressed.

func (*SlotPlan) Validate

func (p *SlotPlan) Validate(secondsPerSlot time.Duration) error

Validate checks all category plans against the slot duration.

type SlotRule

type SlotRule struct {
	ID          string `json:"id"`
	Enabled     bool   `json:"enabled"`
	Description string `json:"description,omitempty"`

	// SlotsInEpoch are the matched slot indices within an epoch (0-based, so
	// 31 is the last slot of a 32-slot epoch).
	SlotsInEpoch []uint64 `json:"slots_in_epoch"`

	// FromEpoch / ToEpoch bound the rule to an inclusive epoch window; nil is
	// unbounded on that side.
	FromEpoch *uint64 `json:"from_epoch,omitempty"`
	ToEpoch   *uint64 `json:"to_epoch,omitempty"`

	Bid        *BidPlan        `json:"bid,omitempty"`
	BuilderAPI *BuilderAPIPlan `json:"builder_api,omitempty"`
	Reveal     *RevealPlan     `json:"reveal,omitempty"`
	Build      *BuildPlan      `json:"build,omitempty"`
	Transforms *TransformPlan  `json:"transforms,omitempty"`

	UpdatedAt time.Time `json:"updated_at"`
	UpdatedBy string    `json:"updated_by"`
}

SlotRule is a recurring plan template: every slot whose index within the epoch matches gets the rule's categories as its plan. It exists for scenarios that must repeat for a whole devnet run — e.g. "win slot 31 of every epoch and withhold the payload" — which explicit per-slot plans can only express for a bounded horizon.

Precedence is wholesale, never merged: an explicit plan for a slot replaces any rule, and a plan with IgnoreRules opts the slot out entirely. Rules are resolved at freeze time from the live rule set, exactly like the global config baseline, so a rule change never rewrites an already frozen slot.

func (*SlotRule) Clone

func (r *SlotRule) Clone() *SlotRule

Clone returns a deep copy. Every rule crossing the package boundary is a clone, so callers can never mutate stored state.

func (*SlotRule) Matches

func (r *SlotRule) Matches(slot phase0.Slot, slotsPerEpoch uint64) bool

Matches reports whether the rule applies to the slot. A disabled rule never matches.

func (*SlotRule) Validate

func (r *SlotRule) Validate(slotsPerEpoch uint64, secondsPerSlot time.Duration) error

Validate checks the matcher and, through a materialized plan, every category against the chain spec.

type TransformPlan

type TransformPlan struct {
	Payload  string `json:"payload,omitempty"`
	Bid      string `json:"bid,omitempty"`
	Envelope string `json:"envelope,omitempty"`
}

TransformPlan carries operator-supplied jq expressions applied to the JSON form of builder objects for arbitrary custom modifications. Like build it is modeless. Each expression is empty (no-op) or a valid jq program (validated on update). Semantics (see payload_builder / payload_bidder):

  • Payload rewrites the built execution payload before it feeds both the bid commitment and the envelope reveal.
  • Bid rewrites the bid MESSAGE just before signing; the modified message is then re-signed, so the result is validly signed but customized.
  • Envelope rewrites the envelope MESSAGE just before signing, then re-signs.

Jump to

Keyboard shortcuts

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