stakingparams

package
v1.36.60 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

Documentation

Overview

Package stakingparams is the node-policy the validator set governs, plus the pure rule that decides whether a proposed change is admissible. It is one small, orthogonal definition — values only, no wire, state or config dependencies — composed by the three consumers that need it: the P-Chain tx executor (admission), the block executor (reward gate), and state.

It is the sibling of package security: security says how a network's set is ADMITTED, stakingparams says on what TERMS.

What is here, and what is deliberately not

Here: validator admission thresholds, stake durations, the delegation-fee floor, and the uptime requirement. These are operational policy — a legitimate collective choice of the people running the network.

NOT here, and never to be added: MinConsumptionRate, MaxConsumptionRate, MintingPeriod, SupplyCap, and the fee-split ratio. Those are money. A votable emission schedule is a capture surface, not a feature; predictable money is the product. They stay compiled into the node and change only the way Bitcoin's rules change — by users and validators voluntarily adopting a new release. TestParamsGovernsNoMonetaryField enforces this at build time.

Who decides

Nobody. There is no admin key, owner, council, guardian or upgrader in this package or in the mechanism it serves — no field to hold one and no argument to pass one. Accept is a pure function of (current state, proposal, compiled-in bounds). Every node runs it independently and the stake-weighted consensus resolves the outcome, exactly as it already does for the commit/abort decision on a validator's reward. A dissenting node cannot veto, only vote and lose; a proposing node holds no privilege, because its preference binds no one.

The one invariant worth remembering

Moving toward MORE permissionless is free and instant. Moving toward LESS permissionless is bounded and rate-limited. A cartel that captures a stake majority therefore cannot ambush the minority out of the validator set: every exclusionary step is small, capped, and visible for the interval it takes, which is the window in which the excluded fork. That asymmetry is the whole security argument, and it is one rule applied uniformly to every field.

Index

Constants

View Source
const (
	Lux     uint64 = 1_000_000_000
	KiloLux        = 1_000 * Lux
	MegaLux        = 1_000 * KiloLux
	GigaLux        = 1_000 * MegaLux
)

Denominations, in the P-Chain's base unit (nLUX).

Variables

View Source
var (
	ErrOutOfBounds      = errors.New("stakingparams: proposal outside compiled-in bounds")
	ErrIncoherent       = errors.New("stakingparams: min exceeds max")
	ErrStepTooLarge     = errors.New("stakingparams: exclusionary step exceeds rate limit")
	ErrTooSoon          = errors.New("stakingparams: minimum interval between changes not elapsed")
	ErrNoChange         = errors.New("stakingparams: proposal is identical to current params")
	ErrEmptyHistory     = errors.New("stakingparams: history is empty")
	ErrNotMonotonic     = errors.New("stakingparams: history activations must strictly increase")
	ErrBoundsIncoherent = errors.New("stakingparams: bounds Lo exceeds Hi")
)
View Source
var MainnetBounds = Bounds{
	Lo: Params{
		MinValidatorStake: 1 * Lux,
		MaxValidatorStake: 1 * MegaLux,
		MinStakeDuration:  60 * 60,
		MaxStakeDuration:  7 * 24 * 60 * 60,
		MinDelegationFee:  0,
		UptimeRequirement: 0,
	},
	Hi: Params{
		MinValidatorStake: 100 * KiloLux,
		MaxValidatorStake: 5 * GigaLux,
		MinStakeDuration:  30 * 24 * 60 * 60,
		MaxStakeDuration:  365 * 24 * 60 * 60,
		MinDelegationFee:  200_000,
		UptimeRequirement: 950_000,
	},
}

MainnetBounds is the constitution — the envelope no vote can leave.

Each bound is chosen against one question: what is the most exclusionary value a hostile stake majority could ever be allowed to reach, and is the network still open to an arbitrary global participant there? If the answer at Hi (or Lo, for the widening fields) is "no", the bound is wrong.

  • minValidatorStake Hi = 100_000 LUX. At the most hostile setting the mechanism permits, a holder of 100k LUX — 0.000005% of the 2T supply cap — can still validate. Below Lo = 1 LUX there is nothing to bond.
  • maxValidatorStake Lo = 1 MegaLux stops a cartel shrinking the ceiling to exclude large honest stakers; Hi holds today's 5 GigaLux.
  • minStakeDuration Hi = 30 days caps how long anyone can be forced to lock.
  • maxStakeDuration Hi = 365 days: the reward calculator's MintingPeriod is one year, so a longer bond has no defined emission. This bound is a correctness constraint, not a policy preference.
  • minDelegationFee Hi = 20% caps how much of a delegator's yield validators can vote themselves. Delegators are the one constituency that cannot defend itself by voting, because the vote is the validator's.
  • uptimeRequirement Hi = 95%. Above that, ordinary maintenance forfeits a reward, and the field stops being a liveness incentive and becomes an ejection weapon.
View Source
var MainnetGenesis = Params{
	MinValidatorStake: 2_000 * Lux,
	MaxValidatorStake: 5 * GigaLux,
	MinStakeDuration:  2 * 7 * 24 * 60 * 60,
	MaxStakeDuration:  365 * 24 * 60 * 60,
	MinDelegationFee:  20_000,
	UptimeRequirement: 800_000,
}

MainnetGenesis is the policy Lux mainnet runs under today, transcribed from the constants presently compiled into the node (MinValidatorStake 2000 LUX, MaxValidatorStake 5 GigaLux, MinStakeDuration 2 weeks, MaxStakeDuration 1 year, MinDelegationFee 2%, UptimeRequirement 80%).

Seeding the history with exactly today's values is what makes adopting this mechanism a no-op on day one: nothing about the live network changes until stake votes to change it.

View Source
var MainnetRate = Rate{
	MaxStep:     reward.PercentDenominator / 10,
	MinInterval: 14 * 24 * time.Hour,
}

MainnetRate is the brake on exclusionary change: at most 10% of the current value per accepted change, and at most one accepted change per 14 days.

14 days is not arbitrary — it is mainnet's MinStakeDuration, so no bond can be entered and matured inside a single governance step. Combined with the 10% cap it puts roughly 1.6 years of continuous, public, on-chain effort between today's 2000 LUX floor and the 100k LUX ceiling, which is the window in which anyone being squeezed out can organise, exit, or fork.

Functions

func Accept

func Accept(cur, next Params, b Bounds, r Rate, elapsed time.Duration) error

Accept is the entire governance decision, as a pure function.

It takes no key, no caller identity, no role and no owner — there is nothing to hold and nothing to compromise. Given the same (cur, next, b, r, elapsed) every node on earth reaches the same verdict, which is what lets the existing stake-weighted commit/abort machinery settle it with no leader.

elapsed is the chain time since the last accepted change. Pass a duration at or above r.MinInterval when no change has been made yet.

Types

type Bounds

type Bounds struct {
	Lo Params
	Hi Params
}

Bounds is the constitution: the closed interval outside which no proposal is ever admissible, whatever the vote. Lo and Hi are corner Params — each field of a proposal must lie between the same field of Lo and of Hi.

Bounds is compiled in. Changing it requires a node release that operators choose to run — the Bitcoin path — and is the only route by which the governable envelope itself can move. That is intentional: the envelope is the thing a stake majority must not be able to widen for itself.

func (Bounds) Clamp

func (b Bounds) Clamp(p Params) Params

Clamp returns p with every field pulled inside the envelope. It is the migration path when a node release narrows Bounds under params that are already live: the chain does not halt and no key is needed to rescue it — the constitution simply binds, deterministically and identically on every node.

func (Bounds) Contains

func (b Bounds) Contains(p Params) error

Contains reports whether every field of p lies inside the envelope.

func (Bounds) Valid

func (b Bounds) Valid() error

Valid reports whether the compiled-in envelope is itself coherent. It exists so a node release that ships a nonsensical Bounds fails loudly at startup rather than silently admitting or rejecting everything.

type Entry

type Entry struct {
	Activation int64  `json:"activation"`
	Params     Params `json:"params"`
}

Entry is one activation of a policy: the params, and the chain time from which they bind.

type History

type History []Entry

History is the append-only record of every policy that has been in force, oldest first. It exists for one reason: a validator must be judged on the terms it agreed to when it bonded, never on terms voted in afterwards.

Without it, UptimeRequirement — the one governed field read at REWARD time rather than at admission time — would be retroactive, and a stake majority could raise it the day before a rival's stake matures and confiscate the reward. With it, governance can only bind the future, which is the difference between setting policy and expropriating people.

func (History) At

func (h History) At(t int64) Params

At returns the params in force at unix time t — the latest entry activating at or before t. Before the first activation the first entry applies, so genesis params bind from the beginning of time.

func (History) Current

func (h History) Current() Params

Current returns the params in force now, i.e. the newest entry.

func (History) Valid

func (h History) Valid() error

Valid reports whether h is non-empty and strictly increasing in activation.

type Params

type Params struct {
	MinValidatorStake uint64 `json:"minValidatorStake"`
	MaxValidatorStake uint64 `json:"maxValidatorStake"`
	MinStakeDuration  uint32 `json:"minStakeDuration"`
	MaxStakeDuration  uint32 `json:"maxStakeDuration"`
	MinDelegationFee  uint32 `json:"minDelegationFee"`
	UptimeRequirement uint32 `json:"uptimeRequirement"`
}

Params is the node-level staking policy in force.

Percent-scaled fields use reward.PercentDenominator (1_000_000), so 800_000 is 80%. Durations are seconds, matching the P-Chain wire.

func (Params) Valid

func (p Params) Valid() error

Valid reports whether p is internally coherent: every min at or below its paired max. Coherence is checked separately from Bounds because a proposal can be inside the envelope on every field and still be nonsense as a pair.

type Rate

type Rate struct {
	MaxStep     uint32
	MinInterval time.Duration
}

Rate limits how fast policy may move against the participants it governs.

MaxStep is expressed in reward.PercentDenominator units of the CURRENT value (100_000 = 10% of current per change). MinInterval is the minimum chain-time spacing between two accepted changes. Together they set the cost of a squeeze-out: reaching a target from a starting value takes ceil(log(target/start) / log(1+step)) changes, each MinInterval apart, all of it on-chain and observable.

Jump to

Keyboard shortcuts

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