Files
node/vms/platformvm/config/staking_policy_test.go
zeekay 7cf87b230b node-unified is the node
The module already called itself github.com/luxfi/node while containing no node
in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and
the Go column of the matrix measured a VM plugin. A repo that fetches the thing
it claims to be has not replaced it, so the Go sources move in — 1515 files,
the daemon and everything under it — and the build reaches outside this module
for nothing.

Rust and C++ sit beside it unchanged and are still measured against it by the
differential. That is the point of one repo: the reference implementation and
the two ports are the same checkout, so parity is a thing you can run rather
than a thing you assert.

Verified: builds, and the closure carries no lux-private — checked at build time
rather than believed. The daemon starts twelve chains on a local network
(P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
2026-09-07 16:15:25 -07:00

115 lines
3.9 KiB
Go

// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package config
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/luxfi/node/vms/platformvm/stakingparams"
)
// mainnetInternal is the compiled-in policy a mainnet node runs today.
func mainnetInternal() *Internal {
return &Internal{
MinValidatorStake: 2_000 * stakingparams.Lux,
MaxValidatorStake: 5 * stakingparams.GigaLux,
MinDelegationFee: 20_000,
UptimePercentage: 0.8,
MinStakeDuration: 2 * 7 * 24 * time.Hour,
MaxStakeDuration: 365 * 24 * time.Hour,
}
}
// TestUngovernedNodeIsUnchanged is the adoption safety property. A node with no
// staking history must resolve exactly the constants it was built with, so
// shipping the lookup changes nothing on any live network until stake votes.
func TestUngovernedNodeIsUnchanged(t *testing.T) {
require := require.New(t)
c := mainnetInternal()
require.Empty(c.StakingParams)
got := c.StakingPolicyAt(time.Now().Unix())
require.Equal(stakingparams.MainnetGenesis, got,
"an ungoverned node must resolve today's compiled-in mainnet policy verbatim")
// And the answer must not depend on the instant asked about.
require.Equal(got, c.StakingPolicyAt(0))
require.Equal(got, c.StakingPolicyAt(1<<40))
}
// TestGovernedPolicyBindsOnlyTheFuture drives the real lookup through
// the exact scenario the reward gate faces: a validator that bonded under the
// old rule, and a vote that lands afterwards.
func TestGovernedPolicyBindsOnlyTheFuture(t *testing.T) {
require := require.New(t)
const (
bondedAt = int64(1_765_573_611) // real mainnet validator StartTime
votedAt = int64(1_785_000_000)
)
tightened := stakingparams.MainnetGenesis
tightened.UptimeRequirement = 880_000 // 88%
c := mainnetInternal()
c.StakingParams = stakingparams.History{
{Activation: 0, Params: stakingparams.MainnetGenesis},
{Activation: votedAt, Params: tightened},
}
require.NoError(c.StakingParams.Valid())
// This is the call block/executor prefersCommit makes, with the staker's
// bond time. The already-bonded validator keeps its 80% bar.
require.Equal(uint32(800_000), c.StakingPolicyAt(bondedAt).UptimeRequirement)
// A validator bonding after the vote gets the new bar.
require.Equal(uint32(880_000), c.StakingPolicyAt(votedAt+1).UptimeRequirement)
}
// TestGovernedAdmissionThresholdsAreLive proves the other direction: a vote that
// opens the network up is visible to admission immediately.
func TestGovernedAdmissionThresholdsAreLive(t *testing.T) {
require := require.New(t)
const openedAt = int64(1_785_000_000)
opened := stakingparams.MainnetGenesis
opened.MinValidatorStake = 100 * stakingparams.Lux // 2000 LUX -> 100 LUX
// The proposal must itself be admissible under the constitution.
require.NoError(stakingparams.Accept(
stakingparams.MainnetGenesis, opened,
stakingparams.MainnetBounds, stakingparams.MainnetRate,
stakingparams.MainnetRate.MinInterval,
))
c := mainnetInternal()
c.StakingParams = stakingparams.History{
{Activation: 0, Params: stakingparams.MainnetGenesis},
{Activation: openedAt, Params: opened},
}
// getValidatorRules resolves at the CURRENT chain time.
require.Equal(2_000*stakingparams.Lux, c.StakingPolicyAt(openedAt-1).MinValidatorStake)
require.Equal(100*stakingparams.Lux, c.StakingPolicyAt(openedAt).MinValidatorStake)
}
// TestDurationRoundTripDoesNotTruncate guards the one lossy conversion in the
// lookup: config carries time.Duration, the governed value carries seconds.
func TestDurationRoundTripDoesNotTruncate(t *testing.T) {
require := require.New(t)
c := mainnetInternal()
p := c.StakingPolicyAt(0)
require.Equal(uint32(14*24*60*60), p.MinStakeDuration)
require.Equal(uint32(365*24*60*60), p.MaxStakeDuration)
require.Equal(c.MinStakeDuration, time.Duration(p.MinStakeDuration)*time.Second)
require.Equal(c.MaxStakeDuration, time.Duration(p.MaxStakeDuration)*time.Second)
}