decomplect: delete IsXxxActivated predicates (Apricot/Banff/Cortina/Durango/Etna/Fortuna/Granite); inline all callsites to always-on; activate-all-implicitly from genesis

Phase 1b of the upstream-upgrade purge per ~/work/lux/proofs/UPGRADE_RIP.md.

Production code:
- upgrade.Config: 17 Time fields + 14 predicate methods deleted; kept only
  CortinaXChainStopVertexID (X-Chain genesis pin, a value) and
  GraniteEpochDuration (LP-181 epoch duration, a tunable).
- upgrade.AlwaysOn: tiny adapter that satisfies runtime.NetworkUpgrades
  (every predicate returns true). Used by chains/manager.go to bridge to the
  external runtime interface until that package follows the same rip.
- All call sites in vms/platformvm/{txs/executor, block/{builder,executor},
  state, warp}, vms/proposervm/{vm, block, pre_fork_block, lp181} and
  vms/components/lux/base_tx.go inlined to the always-active branch.
- ApricotAtomicBlock, apricotCommonBlock, AdvanceTimeTx, proposal-style
  AddValidatorTx/AddDelegatorTx/AddChainValidatorTx, AddValidatorTx,
  AddDelegatorTx, TransformChainTx: now permanently reject (their
  upgrade-name errors are the only behaviour). No legacy logic remains.
- node.go: NewNetwork's minCompatibleTime is upgrade.InitiallyActiveTime
  instead of the deleted FortunaTime.
- xvm/config.Config.EtnaTime field deleted; xvm.Linearize uses
  upgrade.InitiallyActiveTime for genesis chain-state initialization.

upgradetest:
- GetConfig/GetConfigWithUpgradeTime/SetTimesTo/GetConfigForVersion all
  collapse to upgrade.Default; the Fork enum stays (deleted in Phase 4
  alongside the upgrade.UnscheduledActivationTime constant the tests use).

No backwards compatibility for old chaindata: deleted upgrade.Time fields
break wire compatibility for codec-version-0 P-Chain state. Intentional per
the activate-all-implicitly + no-compat-shims directive.

Build: `GOCACHE=/tmp/gocache-decomplect-r2 GOWORK=off go build ./...` green.
Test files still reference deleted upgrade.Config fields; those land in
Phase 4 (delete legacy pre-upgrade test scenarios outright).
This commit is contained in:
Hanzo AI
2026-05-18 22:16:55 -07:00
parent 60a13820f7
commit ab3d1e6b8f
28 changed files with 295 additions and 1364 deletions
+1 -1
View File
@@ -924,7 +924,7 @@ func (m *manager) buildChain(chainParams ChainParameters, sb nets.Net) (*chainIn
Metrics: chainMetricsGatherer,
Log: chainLog,
WarpSigner: warpSigner,
NetworkUpgrades: &m.Upgrades,
NetworkUpgrades: upgrade.AlwaysOn{},
}
// Get a factory for the vm we want to use on our chain
+1 -1
View File
@@ -205,7 +205,7 @@ func NewTestNetwork(
return NewNetwork(
cfg,
upgrade.GetConfig(cfg.NetworkID).FortunaTime, // Must be updated for each network upgrade
upgrade.InitiallyActiveTime,
msgCreator,
registry,
log,
+2 -1
View File
@@ -56,6 +56,7 @@ import (
"github.com/luxfi/node/service/metrics"
luxsecurity "github.com/luxfi/node/service/security"
"github.com/luxfi/node/staking"
"github.com/luxfi/node/upgrade"
"github.com/luxfi/node/version"
"github.com/luxfi/node/vms"
"github.com/luxfi/node/vms/xvm"
@@ -722,7 +723,7 @@ func (n *Node) initNetworking(reg metric.Registerer) error {
n.Net, err = network.NewNetwork(
&n.Config.NetworkConfig,
n.Config.UpgradeConfig.FortunaTime,
upgrade.InitiallyActiveTime,
n.msgCreator,
networkRegistry,
n.Log,
+55 -176
View File
@@ -1,200 +1,59 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package upgrade publishes the canonical Lux genesis-activation timestamp and
// the small set of runtime tunables that survived the upstream upgrade rip.
//
// All historical "fork timestamp + IsXxxActivated()" gates were deleted under
// the activate-all-implicitly directive: every Lux chain runs the post-Granite
// rule-set from genesis, so the legacy predicates are unreachable. The fields
// retained here encode real runtime values, not gates.
package upgrade
import (
"errors"
"fmt"
"time"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
)
var (
InitiallyActiveTime = time.Date(2020, time.December, 5, 5, 0, 0, 0, time.UTC)
UnscheduledActivationTime = time.Date(9999, time.December, 1, 0, 0, 0, 0, time.UTC)
// InitiallyActiveTime is the canonical Lux genesis-activation timestamp. It is
// referenced by code paths that historically demanded "post-fork timestamp" as
// an input and now want a single deterministic value.
var InitiallyActiveTime = time.Date(2020, time.December, 5, 5, 0, 0, 0, time.UTC)
// Config carries the runtime tunables that survived the upstream upgrade rip.
//
// - CortinaXChainStopVertexID encodes the per-network stop-vertex ID that
// pins X-Chain genesis state at boot. It is a value, not a gate.
// - GraniteEpochDuration is the LP-181 epoch duration; per-network value
// (5m on mainnet, 30s on test/dev) tunes consensus pacing.
type Config struct {
CortinaXChainStopVertexID ids.ID `json:"cortinaXChainStopVertexID"`
GraniteEpochDuration time.Duration `json:"graniteEpochDuration"`
}
// Validate is retained for callers that still invoke it; under activate-all-
// implicitly there is no upgrade ordering to enforce, so the function is a
// no-op.
func (*Config) Validate() error { return nil }
var (
Mainnet = Config{
ApricotPhase1Time: InitiallyActiveTime,
ApricotPhase2Time: InitiallyActiveTime,
ApricotPhase3Time: InitiallyActiveTime,
ApricotPhase4Time: InitiallyActiveTime,
ApricotPhase4MinPChainHeight: 0,
ApricotPhase5Time: InitiallyActiveTime,
ApricotPhasePre6Time: InitiallyActiveTime,
ApricotPhase6Time: InitiallyActiveTime,
ApricotPhasePost6Time: InitiallyActiveTime,
BanffTime: InitiallyActiveTime,
CortinaTime: InitiallyActiveTime,
CortinaXChainStopVertexID: ids.FromStringOrPanic("jrGWDh5Po9FMj54depyunNixpia5PN4aAYxfmNzU8n752Rjga"),
DurangoTime: InitiallyActiveTime, // Shanghai EVM opcodes (PUSH0)
EtnaTime: InitiallyActiveTime, // Cancun EVM opcodes (MCOPY, TSTORE, TLOAD)
FortunaTime: InitiallyActiveTime,
GraniteTime: InitiallyActiveTime,
GraniteEpochDuration: 5 * time.Minute,
CortinaXChainStopVertexID: ids.FromStringOrPanic("jrGWDh5Po9FMj54depyunNixpia5PN4aAYxfmNzU8n752Rjga"),
GraniteEpochDuration: 5 * time.Minute,
}
Testnet = Config{
ApricotPhase1Time: InitiallyActiveTime,
ApricotPhase2Time: InitiallyActiveTime,
ApricotPhase3Time: InitiallyActiveTime,
ApricotPhase4Time: InitiallyActiveTime,
ApricotPhase4MinPChainHeight: 0,
ApricotPhase5Time: InitiallyActiveTime,
ApricotPhasePre6Time: InitiallyActiveTime,
ApricotPhase6Time: InitiallyActiveTime,
ApricotPhasePost6Time: InitiallyActiveTime,
BanffTime: InitiallyActiveTime,
CortinaTime: InitiallyActiveTime,
CortinaXChainStopVertexID: ids.FromStringOrPanic("2D1cmbiG36BqQMRyHt4kFhWarmatA1ighSpND3FeFgz3vFVtCZ"),
DurangoTime: InitiallyActiveTime,
EtnaTime: InitiallyActiveTime,
FortunaTime: InitiallyActiveTime,
GraniteTime: InitiallyActiveTime,
GraniteEpochDuration: 30 * time.Second,
CortinaXChainStopVertexID: ids.FromStringOrPanic("2D1cmbiG36BqQMRyHt4kFhWarmatA1ighSpND3FeFgz3vFVtCZ"),
GraniteEpochDuration: 30 * time.Second,
}
Default = Config{
ApricotPhase1Time: InitiallyActiveTime,
ApricotPhase2Time: InitiallyActiveTime,
ApricotPhase3Time: InitiallyActiveTime,
ApricotPhase4Time: InitiallyActiveTime,
ApricotPhase4MinPChainHeight: 0,
ApricotPhase5Time: InitiallyActiveTime,
ApricotPhasePre6Time: InitiallyActiveTime,
ApricotPhase6Time: InitiallyActiveTime,
ApricotPhasePost6Time: InitiallyActiveTime,
BanffTime: InitiallyActiveTime,
CortinaTime: InitiallyActiveTime,
CortinaXChainStopVertexID: ids.Empty,
DurangoTime: InitiallyActiveTime,
EtnaTime: InitiallyActiveTime,
FortunaTime: InitiallyActiveTime,
GraniteTime: UnscheduledActivationTime,
GraniteEpochDuration: 30 * time.Second,
CortinaXChainStopVertexID: ids.Empty,
GraniteEpochDuration: 30 * time.Second,
}
ErrInvalidUpgradeTimes = errors.New("invalid upgrade configuration")
)
// Config carries the timestamp schedule for legacy network upgrades inherited
// from the upstream codebase. In Lux all of these are activated at
// InitiallyActiveTime (Dec 5, 2020) which is before mainnet genesis, so every
// IsXxxActivated() predicate evaluates to true under all real timestamps.
//
// The field names and predicate methods are kept verbatim so that upstream-
// derived chain code (P-Chain block parsers, X-Chain VM, tx codec, etc.) and
// any third-party tooling that consumes the JSON `*Time` tags continues to
// load Lux genesis without modification. The gates are no longer policy knobs
// — they are inert compatibility surfaces. Lux-native gating belongs in the
// ChainSecurityProfile (see service/security/), not here.
type Config struct {
ApricotPhase1Time time.Time `json:"apricotPhase1Time"`
ApricotPhase2Time time.Time `json:"apricotPhase2Time"`
ApricotPhase3Time time.Time `json:"apricotPhase3Time"`
ApricotPhase4Time time.Time `json:"apricotPhase4Time"`
ApricotPhase4MinPChainHeight uint64 `json:"apricotPhase4MinPChainHeight"`
ApricotPhase5Time time.Time `json:"apricotPhase5Time"`
ApricotPhasePre6Time time.Time `json:"apricotPhasePre6Time"`
ApricotPhase6Time time.Time `json:"apricotPhase6Time"`
ApricotPhasePost6Time time.Time `json:"apricotPhasePost6Time"`
BanffTime time.Time `json:"banffTime"`
CortinaTime time.Time `json:"cortinaTime"`
CortinaXChainStopVertexID ids.ID `json:"cortinaXChainStopVertexID"`
DurangoTime time.Time `json:"durangoTime"`
EtnaTime time.Time `json:"etnaTime"`
FortunaTime time.Time `json:"fortunaTime"`
GraniteTime time.Time `json:"graniteTime"`
GraniteEpochDuration time.Duration `json:"graniteEpochDuration"`
}
func (c *Config) Validate() error {
upgrades := []time.Time{
c.ApricotPhase1Time,
c.ApricotPhase2Time,
c.ApricotPhase3Time,
c.ApricotPhase4Time,
c.ApricotPhase5Time,
c.ApricotPhasePre6Time,
c.ApricotPhase6Time,
c.ApricotPhasePost6Time,
c.BanffTime,
c.CortinaTime,
c.DurangoTime,
c.EtnaTime,
c.FortunaTime,
c.GraniteTime,
}
for i := 0; i < len(upgrades)-1; i++ {
if upgrades[i].After(upgrades[i+1]) {
return fmt.Errorf("%w: upgrade %d (%s) is after upgrade %d (%s)",
ErrInvalidUpgradeTimes,
i,
upgrades[i],
i+1,
upgrades[i+1],
)
}
}
return nil
}
func (c *Config) IsApricotPhase1Activated(t time.Time) bool {
return !t.Before(c.ApricotPhase1Time)
}
func (c *Config) IsApricotPhase2Activated(t time.Time) bool {
return !t.Before(c.ApricotPhase2Time)
}
func (c *Config) IsApricotPhase3Activated(t time.Time) bool {
return !t.Before(c.ApricotPhase3Time)
}
func (c *Config) IsApricotPhase4Activated(t time.Time) bool {
return !t.Before(c.ApricotPhase4Time)
}
func (c *Config) IsApricotPhase5Activated(t time.Time) bool {
return !t.Before(c.ApricotPhase5Time)
}
func (c *Config) IsApricotPhasePre6Activated(t time.Time) bool {
return !t.Before(c.ApricotPhasePre6Time)
}
func (c *Config) IsApricotPhase6Activated(t time.Time) bool {
return !t.Before(c.ApricotPhase6Time)
}
func (c *Config) IsApricotPhasePost6Activated(t time.Time) bool {
return !t.Before(c.ApricotPhasePost6Time)
}
func (c *Config) IsBanffActivated(t time.Time) bool {
return !t.Before(c.BanffTime)
}
func (c *Config) IsCortinaActivated(t time.Time) bool {
return !t.Before(c.CortinaTime)
}
func (c *Config) IsDurangoActivated(t time.Time) bool {
return !t.Before(c.DurangoTime)
}
func (c *Config) IsEtnaActivated(t time.Time) bool {
return !t.Before(c.EtnaTime)
}
func (c *Config) IsFortunaActivated(t time.Time) bool {
return !t.Before(c.FortunaTime)
}
func (c *Config) IsGraniteActivated(t time.Time) bool {
return !t.Before(c.GraniteTime)
}
// GetConfig resolves the per-network runtime config.
func GetConfig(networkID uint32) Config {
switch networkID {
case constants.MainnetID:
@@ -205,3 +64,23 @@ func GetConfig(networkID uint32) Config {
return Default
}
}
// AlwaysOn is the activate-all-implicitly bridge that satisfies
// runtime.NetworkUpgrades for callers that haven't yet been migrated off the
// legacy predicate interface. Every method returns true.
type AlwaysOn struct{}
func (AlwaysOn) IsApricotPhase1Activated(time.Time) bool { return true }
func (AlwaysOn) IsApricotPhase2Activated(time.Time) bool { return true }
func (AlwaysOn) IsApricotPhase3Activated(time.Time) bool { return true }
func (AlwaysOn) IsApricotPhase4Activated(time.Time) bool { return true }
func (AlwaysOn) IsApricotPhase5Activated(time.Time) bool { return true }
func (AlwaysOn) IsApricotPhasePre6Activated(time.Time) bool { return true }
func (AlwaysOn) IsApricotPhase6Activated(time.Time) bool { return true }
func (AlwaysOn) IsApricotPhasePost6Activated(time.Time) bool { return true }
func (AlwaysOn) IsBanffActivated(time.Time) bool { return true }
func (AlwaysOn) IsCortinaActivated(time.Time) bool { return true }
func (AlwaysOn) IsDurangoActivated(time.Time) bool { return true }
func (AlwaysOn) IsEtnaActivated(time.Time) bool { return true }
func (AlwaysOn) IsFortunaActivated(time.Time) bool { return true }
func (AlwaysOn) IsGraniteActivated(time.Time) bool { return true }
+6 -29
View File
@@ -5,44 +5,21 @@ package upgrade
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestValidDefaultUpgrades(t *testing.T) {
for _, upgradeTest := range []struct {
for _, c := range []struct {
name string
upgrade Config
}{
{
name: "Default",
upgrade: Default,
},
{
name: "Testnet",
upgrade: Testnet,
},
{
name: "Mainnet",
upgrade: Mainnet,
},
{name: "Default", upgrade: Default},
{name: "Testnet", upgrade: Testnet},
{name: "Mainnet", upgrade: Mainnet},
} {
t.Run(upgradeTest.name, func(t *testing.T) {
require := require.New(t)
require.NoError(upgradeTest.upgrade.Validate())
t.Run(c.name, func(t *testing.T) {
require.NoError(t, c.upgrade.Validate())
})
}
}
func TestInvalidUpgrade(t *testing.T) {
require := require.New(t)
firstUpgradeTime := time.Now()
invalidSecondUpgradeTime := firstUpgradeTime.Add(-1 * time.Second)
upgrade := Config{
ApricotPhase1Time: firstUpgradeTime,
ApricotPhase2Time: invalidSecondUpgradeTime,
}
err := upgrade.Validate()
require.ErrorIs(err, ErrInvalidUpgradeTimes)
}
+10 -63
View File
@@ -9,70 +9,17 @@ import (
"github.com/luxfi/node/upgrade"
)
// GetConfig returns an upgrade config with the provided fork scheduled to have
// been initially activated and all other forks to be unscheduled.
func GetConfig(fork Fork) upgrade.Config {
return GetConfigWithUpgradeTime(fork, upgrade.InitiallyActiveTime)
// GetConfig returns the activate-all-implicitly upgrade config used by tests.
// The fork enum is ignored: every upgrade is live from genesis.
func GetConfig(Fork) upgrade.Config {
return upgrade.Default
}
// GetConfigWithUpgradeTime returns an upgrade config with the provided fork
// scheduled to be activated at the provided upgradeTime and all other forks to
// be unscheduled.
func GetConfigWithUpgradeTime(fork Fork, upgradeTime time.Time) upgrade.Config {
c := upgrade.Config{
GraniteEpochDuration: upgrade.Default.GraniteEpochDuration,
}
// Initialize all forks to be unscheduled
SetTimesTo(&c, Latest, upgrade.UnscheduledActivationTime)
// Schedule the requested forks at the provided upgrade time
SetTimesTo(&c, fork, upgradeTime)
return c
// GetConfigWithUpgradeTime is preserved for tests that still pass an upgrade
// time; the time is ignored under activate-all-implicitly.
func GetConfigWithUpgradeTime(Fork, time.Time) upgrade.Config {
return upgrade.Default
}
// SetTimesTo sets the upgrade time of the provided fork, and all prior forks,
// to the provided upgradeTime.
func SetTimesTo(c *upgrade.Config, fork Fork, upgradeTime time.Time) {
switch fork {
case Granite:
c.GraniteTime = upgradeTime
fallthrough
case Fortuna:
c.FortunaTime = upgradeTime
fallthrough
case Etna:
c.EtnaTime = upgradeTime
fallthrough
case Durango:
c.DurangoTime = upgradeTime
fallthrough
case Cortina:
c.CortinaTime = upgradeTime
fallthrough
case Banff:
c.BanffTime = upgradeTime
fallthrough
case ApricotPhasePost6:
c.ApricotPhasePost6Time = upgradeTime
fallthrough
case ApricotPhase6:
c.ApricotPhase6Time = upgradeTime
fallthrough
case ApricotPhasePre6:
c.ApricotPhasePre6Time = upgradeTime
fallthrough
case ApricotPhase5:
c.ApricotPhase5Time = upgradeTime
fallthrough
case ApricotPhase4:
c.ApricotPhase4Time = upgradeTime
fallthrough
case ApricotPhase3:
c.ApricotPhase3Time = upgradeTime
fallthrough
case ApricotPhase2:
c.ApricotPhase2Time = upgradeTime
fallthrough
case ApricotPhase1:
c.ApricotPhase1Time = upgradeTime
}
}
// SetTimesTo is preserved as a no-op for tests that still call it.
func SetTimesTo(*upgrade.Config, Fork, time.Time) {}
+7 -18
View File
@@ -3,25 +3,14 @@
package upgradetest
import (
"time"
import "github.com/luxfi/node/upgrade"
"github.com/luxfi/node/upgrade"
)
// TestConfig returns a test upgrade configuration
func TestConfig() map[string]time.Time {
return map[string]time.Time{
"testUpgrade": time.Now().Add(time.Hour),
}
}
// LatestVersion represents the latest upgrade configuration for testing
// LatestVersion is a stable token identifying the activate-all-implicitly
// upgrade snapshot used by tests.
const LatestVersion = "latest"
// GetConfigForVersion returns an upgrade configuration for testing
func GetConfigForVersion(version string) upgrade.Config {
return upgrade.Config{
GraniteTime: time.Now().Add(time.Hour),
}
// GetConfigForVersion returns the canonical upgrade config; under activate-
// all-implicitly all version tokens map to the same Lux default.
func GetConfigForVersion(string) upgrade.Config {
return upgrade.Default
}
+3 -7
View File
@@ -67,13 +67,9 @@ func (t *BaseTx) Verify(rt *runtime.Runtime) error {
}
}
// VerifyMemoFieldLength validates memo field length based on Durango activation status
func VerifyMemoFieldLength(memo types.JSONByteSlice, isDurangoActive bool) error {
if !isDurangoActive {
// SyntacticVerify validates this field pre-Durango
return nil
}
// VerifyMemoFieldLength enforces the Lux memo-field length rule: under
// activate-all-implicitly the memo field must be empty on every tx.
func VerifyMemoFieldLength(memo types.JSONByteSlice, _ bool) error {
if len(memo) != 0 {
return fmt.Errorf(
"%w: %d > %d",
+11 -38
View File
@@ -364,19 +364,6 @@ func (b *builder) PackAllBlockTxs() ([]*txs.Tx, error) {
}
}
if !b.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) {
return packDurangoBlockTxs(
context.TODO(),
preferredID,
preferredState,
b.Mempool,
b.txExecutorBackend,
b.blkManager,
timestamp,
recommendedPChainHeight,
math.MaxInt,
)
}
return packEtnaBlockTxs(
context.TODO(),
preferredID,
@@ -405,31 +392,17 @@ func buildBlock(
blockTxs []*txs.Tx
err error
)
if builder.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) {
blockTxs, err = packEtnaBlockTxs(
ctx,
parentID,
parentState,
builder.Mempool,
builder.txExecutorBackend,
builder.blkManager,
timestamp,
pChainHeight,
0, // minCapacity is 0 as we want to honor the capacity in state.
)
} else {
blockTxs, err = packDurangoBlockTxs(
ctx,
parentID,
parentState,
builder.Mempool,
builder.txExecutorBackend,
builder.blkManager,
timestamp,
pChainHeight,
targetBlockSize,
)
}
blockTxs, err = packEtnaBlockTxs(
ctx,
parentID,
parentState,
builder.Mempool,
builder.txExecutorBackend,
builder.blkManager,
timestamp,
pChainHeight,
0, // minCapacity is 0 as we want to honor the capacity in state.
)
if err != nil {
logger := builder.txExecutorBackend.Runtime.Log.(log.Logger)
logger.Warn("failed to pack block transactions: " + err.Error())
+1 -1
View File
@@ -182,7 +182,7 @@ func (m *manager) VerifyTx(tx *txs.Tx) error {
return fmt.Errorf("failed to advance the chain time: %w", err)
}
if timestamp := stateDiff.GetTimestamp(); m.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) {
{
complexity, err := fee.TxComplexity(tx.Unsigned)
if err != nil {
return fmt.Errorf("failed to calculate tx complexity: %w", err)
+9 -69
View File
@@ -205,64 +205,12 @@ func (v *verifier) ApricotStandardBlock(b *block.ApricotStandardBlock) error {
}
func (v *verifier) ApricotAtomicBlock(b *block.ApricotAtomicBlock) error {
// We call [commonBlock] here rather than [apricotCommonBlock] because below
// this check we perform the more strict check that ApricotPhase5 isn't
// activated.
// Atomic blocks must go through the standard-block path under
// activate-all-implicitly; legacy ApricotAtomicBlocks are never accepted.
if err := v.commonBlock(b); err != nil {
return err
}
parentID := b.Parent()
currentTimestamp := v.getTimestamp(parentID)
cfg := v.txExecutorBackend.Config
if cfg.UpgradeConfig.IsApricotPhase5Activated(currentTimestamp) {
return fmt.Errorf(
"the chain timestamp (%d) is after the apricot phase 5 time (%d), hence atomic transactions should go through the standard block",
currentTimestamp.Unix(),
cfg.UpgradeConfig.ApricotPhase5Time.Unix(),
)
}
feeCalculator := txfee.NewSimpleCalculator(0)
onAcceptState, atomicInputs, atomicRequests, err := txexecutor.AtomicTx(
v.txExecutorBackend,
feeCalculator,
parentID,
v,
b.Tx,
)
if err != nil {
txID := b.Tx.ID()
v.MarkDropped(txID, err) // cache tx as dropped
return err
}
onAcceptState.AddTx(b.Tx, status.Committed)
if err := v.verifyUniqueInputs(parentID, atomicInputs); err != nil {
return err
}
v.Mempool.Remove(b.Tx)
blkID := b.ID()
v.setBlockState(blkID, &blockState{
statelessBlock: b,
onAcceptState: onAcceptState,
inputs: atomicInputs,
timestamp: onAcceptState.GetTimestamp(),
atomicRequests: atomicRequests,
verifiedHeights: set.Of(v.pChainHeight),
metrics: calculateBlockMetrics(
v.txExecutorBackend.Config,
b,
onAcceptState,
0,
),
})
return nil
return errApricotBlockIssuedAfterFork
}
func (v *verifier) banffOptionBlock(b block.BanffBlock) error {
@@ -310,20 +258,12 @@ func (v *verifier) banffNonOptionBlock(b block.BanffBlock) error {
}
func (v *verifier) apricotCommonBlock(b block.Block) error {
// We can use the parent timestamp here, because we are guaranteed that the
// parent was verified. Apricot blocks only update the timestamp with
// AdvanceTimeTxs. This means that this block's timestamp will be equal to
// the parent block's timestamp; unless this is a CommitBlock. In order for
// the timestamp of the CommitBlock to be after the Banff activation,
// the parent ApricotProposalBlock must include an AdvanceTimeTx with a
// timestamp after the Banff timestamp. This is verified not to occur
// during the verification of the ProposalBlock.
parentID := b.Parent()
timestamp := v.getTimestamp(parentID)
if v.txExecutorBackend.Config.UpgradeConfig.IsBanffActivated(timestamp) {
return fmt.Errorf("%w: timestamp = %s", errApricotBlockIssuedAfterFork, timestamp)
// Apricot blocks are permanently refused under activate-all-implicitly;
// every chain-time is post-Banff.
if err := v.commonBlock(b); err != nil {
return err
}
return v.commonBlock(b)
return errApricotBlockIssuedAfterFork
}
func (v *verifier) commonBlock(b block.Block) error {
@@ -523,7 +463,7 @@ func (v *verifier) processStandardTxs(txs []*txs.Tx, feeCalculator txfee.Calcula
) {
// Complexity is limited first to avoid processing too large of a block.
var gasConsumed gas.Gas
if timestamp := diff.GetTimestamp(); v.txExecutorBackend.Config.UpgradeConfig.IsEtnaActivated(timestamp) {
{
var blockComplexity gas.Dimensions
for _, tx := range txs {
txComplexity, err := txfee.TxComplexity(tx.Unsigned)
+2 -7
View File
@@ -134,16 +134,11 @@ func getNextL1ValidatorEvictionTime(
return nextTime, nil
}
// PickFeeCalculator creates either a simple or a dynamic fee calculator,
// depending on the active upgrade.
// PickFeeCalculator returns the dynamic fee calculator; the legacy simple
// (zero-fee) calculator is unreachable under activate-all-implicitly.
//
// PickFeeCalculator does not modify [state].
func PickFeeCalculator(config *config.Internal, state Chain) txfee.Calculator {
timestamp := state.GetTimestamp()
if !config.UpgradeConfig.IsEtnaActivated(timestamp) {
return txfee.NewSimpleCalculator(0)
}
feeState := state.GetFeeState()
gasPrice := gas.CalculatePrice(
config.DynamicFeeConfig.MinPrice,
+1 -4
View File
@@ -61,10 +61,7 @@ func (s *state) Close() error {
}
func (s *state) write(updateValidators bool, height uint64) error {
codecVersion := CodecVersion1
if !s.upgrades.IsDurangoActivated(s.GetTimestamp()) {
codecVersion = CodecVersion0
}
const codecVersion = CodecVersion1
return errors.Join(
s.writeBlocks(),
@@ -159,184 +159,28 @@ func (*proposalTxExecutor) OperationTx(*txs.OperationTx) error {
return ErrWrongTxType
}
func (e *proposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error {
// AddValidatorTx is a proposal transaction until upgrade.Config.BanffTime.
// After that, AddValidatorTxs must be issued into StandardBlocks.
currentTimestamp := e.onCommitState.GetTimestamp()
if e.backend.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) {
return fmt.Errorf(
"%w: timestamp (%s) >= upgrade.Config.BanffTime (%s)",
ErrProposedAddStakerTxAfterBanff,
currentTimestamp,
e.backend.Config.UpgradeConfig.BanffTime,
)
}
onAbortOuts, err := verifyAddValidatorTx(
e.backend,
e.feeCalculator,
e.onCommitState,
e.tx,
tx,
)
if err != nil {
return err
}
txID := e.tx.ID()
// Set up the state if this tx is committed
// Consume the UTXOs
lux.Consume(e.onCommitState, tx.Ins)
// Produce the UTXOs
lux.Produce(e.onCommitState, txID, tx.Outs)
newStaker, err := state.NewPendingStaker(txID, tx)
if err != nil {
return err
}
if err := e.onCommitState.PutPendingValidator(newStaker); err != nil {
return err
}
// Set up the state if this tx is aborted
// Consume the UTXOs
lux.Consume(e.onAbortState, tx.Ins)
// Produce the UTXOs
lux.Produce(e.onAbortState, txID, onAbortOuts)
return nil
func (*proposalTxExecutor) AddValidatorTx(*txs.AddValidatorTx) error {
// Proposal-style AddValidatorTx is permanently rejected; only standard
// blocks may carry staker txs.
return ErrProposedAddStakerTxAfterBanff
}
func (e *proposalTxExecutor) AddChainValidatorTx(tx *txs.AddChainValidatorTx) error {
// AddChainValidatorTx is a proposal transaction until upgrade.Config.BanffTime.
// activation. Following the activation, AddChainValidatorTxs must be
// issued into StandardBlocks.
currentTimestamp := e.onCommitState.GetTimestamp()
if e.backend.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) {
return fmt.Errorf(
"%w: timestamp (%s) >= upgrade.Config.BanffTime (%s)",
ErrProposedAddStakerTxAfterBanff,
currentTimestamp,
e.backend.Config.UpgradeConfig.BanffTime,
)
}
if err := verifyAddChainValidatorTx(
e.backend,
e.feeCalculator,
e.onCommitState,
e.tx,
tx,
); err != nil {
return err
}
txID := e.tx.ID()
// Set up the state if this tx is committed
// Consume the UTXOs
lux.Consume(e.onCommitState, tx.Ins)
// Produce the UTXOs
lux.Produce(e.onCommitState, txID, tx.Outs)
newStaker, err := state.NewPendingStaker(txID, tx)
if err != nil {
return err
}
if err := e.onCommitState.PutPendingValidator(newStaker); err != nil {
return err
}
// Set up the state if this tx is aborted
// Consume the UTXOs
lux.Consume(e.onAbortState, tx.Ins)
// Produce the UTXOs
lux.Produce(e.onAbortState, txID, tx.Outs)
return nil
func (*proposalTxExecutor) AddChainValidatorTx(*txs.AddChainValidatorTx) error {
// Proposal-style AddChainValidatorTx is permanently rejected; only standard
// blocks may carry staker txs.
return ErrProposedAddStakerTxAfterBanff
}
func (e *proposalTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error {
// AddDelegatorTx is a proposal transaction until upgrade.Config.BanffTime.
// activation. Following the activation, AddDelegatorTxs must be issued into
// StandardBlocks.
currentTimestamp := e.onCommitState.GetTimestamp()
if e.backend.Config.UpgradeConfig.IsBanffActivated(currentTimestamp) {
return fmt.Errorf(
"%w: timestamp (%s) >= upgrade.Config.BanffTime (%s)",
ErrProposedAddStakerTxAfterBanff,
currentTimestamp,
e.backend.Config.UpgradeConfig.BanffTime,
)
}
onAbortOuts, err := verifyAddDelegatorTx(
e.backend,
e.feeCalculator,
e.onCommitState,
e.tx,
tx,
)
if err != nil {
return err
}
txID := e.tx.ID()
// Set up the state if this tx is committed
// Consume the UTXOs
lux.Consume(e.onCommitState, tx.Ins)
// Produce the UTXOs
lux.Produce(e.onCommitState, txID, tx.Outs)
newStaker, err := state.NewPendingStaker(txID, tx)
if err != nil {
return err
}
e.onCommitState.PutPendingDelegator(newStaker)
// Set up the state if this tx is aborted
// Consume the UTXOs
lux.Consume(e.onAbortState, tx.Ins)
// Produce the UTXOs
lux.Produce(e.onAbortState, txID, onAbortOuts)
return nil
func (*proposalTxExecutor) AddDelegatorTx(*txs.AddDelegatorTx) error {
// Proposal-style AddDelegatorTx is permanently rejected; only standard
// blocks may carry staker txs.
return ErrProposedAddStakerTxAfterBanff
}
func (e *proposalTxExecutor) AdvanceTimeTx(tx *txs.AdvanceTimeTx) error {
switch {
case tx == nil:
return txs.ErrNilTx
case len(e.tx.Creds) != 0:
return errWrongNumberOfCredentials
}
// Validate [newChainTime]
newChainTime := tx.Timestamp()
if e.backend.Config.UpgradeConfig.IsBanffActivated(newChainTime) {
return fmt.Errorf(
"%w: proposed timestamp (%s) >= upgrade.Config.BanffTime (%s)",
ErrAdvanceTimeTxIssuedAfterBanff,
newChainTime,
e.backend.Config.UpgradeConfig.BanffTime,
)
}
now := e.backend.Clk.Time()
if err := VerifyNewChainTime(
e.backend.Config.ValidatorFeeConfig,
newChainTime,
now,
e.onCommitState,
); err != nil {
return err
}
// Note that state doesn't change if this proposal is aborted
_, err := AdvanceTimeTo(e.backend, e.onCommitState, newChainTime)
return err
func (*proposalTxExecutor) AdvanceTimeTx(*txs.AdvanceTimeTx) error {
// AdvanceTimeTx is permanently rejected post-Banff; chain time advances
// implicitly via Banff standard blocks.
return ErrAdvanceTimeTxIssuedAfterBanff
}
func (e *proposalTxExecutor) RewardValidatorTx(tx *txs.RewardValidatorTx) error {
@@ -608,54 +452,28 @@ func (e *proposalTxExecutor) rewardDelegatorTx(uDelegatorTx txs.DelegatorTx, del
return nil
}
// Reward the delegatee here
if e.backend.Config.UpgradeConfig.IsCortinaActivated(validator.StartTime) {
previousDelegateeReward, err := e.onCommitState.GetDelegateeReward(
validator.ChainID,
validator.NodeID,
)
if err != nil {
return fmt.Errorf("failed to get delegatee reward: %w", err)
}
// Reward the delegatee here. Post-Cortina, the delegatee reward is
// deferred until the staking period ends, so accumulate it into the
// delegatee-reward ledger instead of emitting a UTXO here.
previousDelegateeReward, err := e.onCommitState.GetDelegateeReward(
validator.ChainID,
validator.NodeID,
)
if err != nil {
return fmt.Errorf("failed to get delegatee reward: %w", err)
}
// Invariant: The rewards calculator can never return a
// [potentialReward] that would overflow the
// accumulated rewards.
newDelegateeReward := previousDelegateeReward + delegateeReward
// Invariant: The rewards calculator can never return a
// [potentialReward] that would overflow the
// accumulated rewards.
newDelegateeReward := previousDelegateeReward + delegateeReward
// For any validators starting after [CortinaTime], we defer rewarding the
// [reward] until their staking period is over.
err = e.onCommitState.SetDelegateeReward(
validator.ChainID,
validator.NodeID,
newDelegateeReward,
)
if err != nil {
return fmt.Errorf("failed to update delegatee reward: %w", err)
}
} else {
// For any validators who started prior to [CortinaTime], we issue the
// [delegateeReward] immediately.
delegationRewardsOwner := vdrTx.DelegationRewardsOwner()
outIntf, err := e.backend.Fx.CreateOutput(delegateeReward, delegationRewardsOwner)
if err != nil {
return fmt.Errorf("failed to create output: %w", err)
}
out, ok := outIntf.(verify.State)
if !ok {
return ErrInvalidState
}
utxo := &lux.UTXO{
UTXOID: lux.UTXOID{
TxID: txID,
OutputIndex: uint32(len(outputs) + len(stake) + utxosOffset),
},
Asset: stakeAsset,
Out: out,
}
e.onCommitState.AddUTXO(utxo)
e.onCommitState.AddRewardUTXO(txID, utxo)
if err := e.onCommitState.SetDelegateeReward(
validator.ChainID,
validator.NodeID,
newDelegateeReward,
); err != nil {
return fmt.Errorf("failed to update delegatee reward: %w", err)
}
return nil
}
@@ -15,11 +15,10 @@ import (
)
var (
errGraniteUpgradeNotActive = errors.New("attempting to use a Granite-upgrade feature prior to activation")
errValidatorNotFound = errors.New("validator not found in current staker set")
errValidatorNoBLSKey = errors.New("validator has no BLS public key registered")
errInvalidEvidenceSignature = errors.New("evidence signature does not verify against validator BLS key")
errSlashPercentMismatch = errors.New("slash percentage does not match expected value for evidence type")
errValidatorNotFound = errors.New("validator not found in current staker set")
errValidatorNoBLSKey = errors.New("validator has no BLS public key registered")
errInvalidEvidenceSignature = errors.New("evidence signature does not verify against validator BLS key")
errSlashPercentMismatch = errors.New("slash percentage does not match expected value for evidence type")
)
// Default slash percentages per evidence type.
@@ -29,11 +28,6 @@ const (
)
func (e *standardTxExecutor) SlashValidatorTx(tx *txs.SlashValidatorTx) error {
currentTimestamp := e.state.GetTimestamp()
if !e.backend.Config.UpgradeConfig.IsGraniteActivated(currentTimestamp) {
return errGraniteUpgradeNotActive
}
if err := e.tx.SyntacticVerify(e.backend.Runtime); err != nil {
return err
}
@@ -47,7 +47,6 @@ var (
// network requirements for [chainValidator]. An error is returned if they
// are not fulfilled.
func verifyChainValidatorPrimaryNetworkRequirements(
isDurangoActive bool,
chainState state.Chain,
chainValidator txs.Validator,
) error {
@@ -69,12 +68,8 @@ func verifyChainValidatorPrimaryNetworkRequirements(
// Ensure that the period this validator validates the specified chain
// is a subset of the time they validate the primary network.
startTime := chainState.GetTimestamp()
if !isDurangoActive {
startTime = chainValidator.StartTime()
}
if !txs.BoundedBy(
startTime,
chainState.GetTimestamp(),
chainValidator.EndTime(),
primaryNetworkValidator.StartTime,
primaryNetworkValidator.EndTime,
@@ -85,104 +80,19 @@ func verifyChainValidatorPrimaryNetworkRequirements(
return nil
}
// verifyAddValidatorTx carries out the validation for an AddValidatorTx.
// It returns the tx outputs that should be returned if this validator is not
// added to the staking set.
// verifyAddValidatorTx permanently rejects AddValidatorTx; the legacy
// scheduled-staker flow has no role under activate-all-implicitly.
func verifyAddValidatorTx(
backend *Backend,
feeCalculator fee.Calculator,
chainState state.Chain,
sTx *txs.Tx,
tx *txs.AddValidatorTx,
*Backend,
fee.Calculator,
state.Chain,
*txs.Tx,
*txs.AddValidatorTx,
) (
[]*lux.TransferableOutput,
error,
) {
currentTimestamp := chainState.GetTimestamp()
if backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) {
return nil, ErrAddValidatorTxPostDurango
}
// Verify the tx is well-formed
if err := sTx.SyntacticVerify(backend.Runtime); err != nil {
return nil, err
}
if err := lux.VerifyMemoFieldLength(tx.Memo, false /*=isDurangoActive*/); err != nil {
return nil, err
}
startTime := tx.StartTime()
duration := tx.EndTime().Sub(startTime)
switch {
case tx.Validator.Wght < backend.Config.MinValidatorStake:
// Ensure validator is staking at least the minimum amount
return nil, ErrWeightTooSmall
case tx.Validator.Wght > backend.Config.MaxValidatorStake:
// Ensure validator isn't staking too much
return nil, ErrWeightTooLarge
case tx.DelegationShares < backend.Config.MinDelegationFee:
// Ensure the validator fee is at least the minimum amount
return nil, ErrInsufficientDelegationFee
case duration < backend.Config.MinStakeDuration:
// Ensure staking length is not too short
return nil, ErrStakeTooShort
case duration > backend.Config.MaxStakeDuration:
// Ensure staking length is not too long
return nil, ErrStakeTooLong
}
outs := make([]*lux.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts))
copy(outs, tx.Outs)
copy(outs[len(tx.Outs):], tx.StakeOuts)
if !backend.Bootstrapped.Get() {
return outs, nil
}
if err := verifyStakerStartTime(false /*=isDurangoActive*/, currentTimestamp, startTime); err != nil {
return nil, err
}
_, err := GetValidator(chainState, constants.PrimaryNetworkID, tx.Validator.NodeID)
if err == nil {
return nil, fmt.Errorf(
"%s is %w of the primary network",
tx.Validator.NodeID,
ErrAlreadyValidator,
)
}
if err != database.ErrNotFound {
return nil, fmt.Errorf(
"failed to find whether %s is a primary network validator: %w",
tx.Validator.NodeID,
err,
)
}
// Verify the flowcheck
fee, err := feeCalculator.CalculateFee(tx)
if err != nil {
return nil, err
}
if err := backend.FlowChecker.VerifySpend(
tx,
chainState,
tx.Ins,
outs,
sTx.Creds,
map[ids.ID]uint64{
backend.Runtime.XAssetID: fee,
},
); err != nil {
return nil, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err)
}
return outs, nil
return nil, ErrAddValidatorTxPostDurango
}
// verifyAddChainValidatorTx carries out the validation for an
@@ -199,18 +109,12 @@ func verifyAddChainValidatorTx(
return err
}
var (
currentTimestamp = chainState.GetTimestamp()
isDurangoActive = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
)
if err := lux.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
currentTimestamp := chainState.GetTimestamp()
startTime := currentTimestamp
if !isDurangoActive {
startTime = tx.StartTime()
}
duration := tx.EndTime().Sub(startTime)
switch {
@@ -227,7 +131,7 @@ func verifyAddChainValidatorTx(
return nil
}
if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil {
if err := verifyStakerStartTime(currentTimestamp, startTime); err != nil {
return err
}
@@ -248,7 +152,7 @@ func verifyAddChainValidatorTx(
)
}
if err := verifyChainValidatorPrimaryNetworkRequirements(isDurangoActive, chainState, tx.Validator); err != nil {
if err := verifyChainValidatorPrimaryNetworkRequirements(chainState, tx.Validator); err != nil {
return err
}
@@ -298,11 +202,7 @@ func verifyRemoveChainValidatorTx(
return nil, false, err
}
var (
currentTimestamp = chainState.GetTimestamp()
isDurangoActive = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
)
if err := lux.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return nil, false, err
}
@@ -358,124 +258,19 @@ func verifyRemoveChainValidatorTx(
return vdr, isCurrentValidator, nil
}
// verifyAddDelegatorTx carries out the validation for an AddDelegatorTx.
// It returns the tx outputs that should be returned if this delegator is not
// added to the staking set.
// verifyAddDelegatorTx permanently rejects AddDelegatorTx; the legacy
// scheduled-staker flow has no role under activate-all-implicitly.
func verifyAddDelegatorTx(
backend *Backend,
feeCalculator fee.Calculator,
chainState state.Chain,
sTx *txs.Tx,
tx *txs.AddDelegatorTx,
*Backend,
fee.Calculator,
state.Chain,
*txs.Tx,
*txs.AddDelegatorTx,
) (
[]*lux.TransferableOutput,
error,
) {
currentTimestamp := chainState.GetTimestamp()
if backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) {
return nil, ErrAddDelegatorTxPostDurango
}
// Verify the tx is well-formed
if err := sTx.SyntacticVerify(backend.Runtime); err != nil {
return nil, err
}
if err := lux.VerifyMemoFieldLength(tx.Memo, false /*=isDurangoActive*/); err != nil {
return nil, err
}
var (
endTime = tx.EndTime()
startTime = tx.StartTime()
duration = endTime.Sub(startTime)
)
switch {
case duration < backend.Config.MinStakeDuration:
// Ensure staking length is not too short
return nil, ErrStakeTooShort
case duration > backend.Config.MaxStakeDuration:
// Ensure staking length is not too long
return nil, ErrStakeTooLong
case tx.Validator.Wght < backend.Config.MinDelegatorStake:
// Ensure validator is staking at least the minimum amount
return nil, ErrWeightTooSmall
}
outs := make([]*lux.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts))
copy(outs, tx.Outs)
copy(outs[len(tx.Outs):], tx.StakeOuts)
if !backend.Bootstrapped.Get() {
return outs, nil
}
if err := verifyStakerStartTime(false /*=isDurangoActive*/, currentTimestamp, startTime); err != nil {
return nil, err
}
primaryNetworkValidator, err := GetValidator(chainState, constants.PrimaryNetworkID, tx.Validator.NodeID)
if err != nil {
return nil, fmt.Errorf(
"failed to fetch the primary network validator for %s: %w",
tx.Validator.NodeID,
err,
)
}
maximumWeight, err := safemath.Mul64(MaxValidatorWeightFactor, primaryNetworkValidator.Weight)
if err != nil {
return nil, ErrStakeOverflow
}
if backend.Config.UpgradeConfig.IsApricotPhase3Activated(currentTimestamp) {
maximumWeight = min(maximumWeight, backend.Config.MaxValidatorStake)
}
if !txs.BoundedBy(
startTime,
endTime,
primaryNetworkValidator.StartTime,
primaryNetworkValidator.EndTime,
) {
return nil, ErrPeriodMismatch
}
overDelegated, err := overDelegated(
chainState,
primaryNetworkValidator,
maximumWeight,
tx.Validator.Wght,
startTime,
endTime,
)
if err != nil {
return nil, err
}
if overDelegated {
return nil, ErrOverDelegated
}
// Verify the flowcheck
fee, err := feeCalculator.CalculateFee(tx)
if err != nil {
return nil, err
}
if err := backend.FlowChecker.VerifySpend(
tx,
chainState,
tx.Ins,
outs,
sTx.Creds,
map[ids.ID]uint64{
backend.Runtime.XAssetID: fee,
},
); err != nil {
return nil, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err)
}
return outs, nil
return nil, ErrAddDelegatorTxPostDurango
}
// verifyAddPermissionlessValidatorTx carries out the validation for an
@@ -492,11 +287,7 @@ func verifyAddPermissionlessValidatorTx(
return err
}
var (
currentTimestamp = chainState.GetTimestamp()
isDurangoActive = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
)
if err := lux.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -504,13 +295,11 @@ func verifyAddPermissionlessValidatorTx(
return nil
}
currentTimestamp := chainState.GetTimestamp()
startTime := currentTimestamp
if !isDurangoActive {
startTime = tx.StartTime()
}
duration := tx.EndTime().Sub(startTime)
if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil {
if err := verifyStakerStartTime(currentTimestamp, startTime); err != nil {
return err
}
@@ -570,7 +359,7 @@ func verifyAddPermissionlessValidatorTx(
}
if tx.Chain != constants.PrimaryNetworkID {
if err := verifyChainValidatorPrimaryNetworkRequirements(isDurangoActive, chainState, tx.Validator); err != nil {
if err := verifyChainValidatorPrimaryNetworkRequirements(chainState, tx.Validator); err != nil {
return err
}
}
@@ -614,11 +403,7 @@ func verifyAddPermissionlessDelegatorTx(
return err
}
var (
currentTimestamp = chainState.GetTimestamp()
isDurangoActive = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
)
if err := lux.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -627,15 +412,13 @@ func verifyAddPermissionlessDelegatorTx(
}
var (
endTime = tx.EndTime()
startTime = currentTimestamp
currentTimestamp = chainState.GetTimestamp()
endTime = tx.EndTime()
startTime = currentTimestamp
)
if !isDurangoActive {
startTime = tx.StartTime()
}
duration := endTime.Sub(startTime)
if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil {
if err := verifyStakerStartTime(currentTimestamp, startTime); err != nil {
return err
}
@@ -759,20 +542,12 @@ func verifyTransferChainOwnershipTx(
sTx *txs.Tx,
tx *txs.TransferChainOwnershipTx,
) error {
var (
currentTimestamp = chainState.GetTimestamp()
upgrades = backend.Config.UpgradeConfig
)
if !upgrades.IsDurangoActivated(currentTimestamp) {
return ErrDurangoUpgradeNotActive
}
// Verify the tx is well-formed
if err := sTx.SyntacticVerify(backend.Runtime); err != nil {
return err
}
if err := lux.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -807,21 +582,9 @@ func verifyTransferChainOwnershipTx(
return nil
}
// Ensure the proposed validator starts after the current time
func verifyStakerStartTime(isDurangoActive bool, chainTime, stakerTime time.Time) error {
// Pre Durango activation, start time must be after current chain time.
// Post Durango activation, start time is not validated
if isDurangoActive {
return nil
}
if !chainTime.Before(stakerTime) {
return fmt.Errorf(
"%w: %s >= %s",
ErrTimestampNotBeforeStartTime,
chainTime,
stakerTime,
)
}
// verifyStakerStartTime is a no-op under activate-all-implicitly: post-Durango,
// start time is not validated. Retained as the explicit call site so the
// staker-tx verifiers keep a single canonical timing-check seam.
func verifyStakerStartTime(_, _ time.Time) error {
return nil
}
@@ -8,7 +8,6 @@ import (
"context"
"errors"
"fmt"
"time"
"github.com/luxfi/log"
@@ -194,11 +193,7 @@ func (e *standardTxExecutor) CreateChainTx(tx *txs.CreateChainTx) error {
return err
}
var (
currentTimestamp = e.state.GetTimestamp()
isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
)
if err := lux.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -253,11 +248,7 @@ func (e *standardTxExecutor) CreateNetworkTx(tx *txs.CreateNetworkTx) error {
return err
}
var (
currentTimestamp = e.state.GetTimestamp()
isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
)
if err := lux.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -296,11 +287,7 @@ func (e *standardTxExecutor) ImportTx(tx *txs.ImportTx) error {
return err
}
var (
currentTimestamp = e.state.GetTimestamp()
isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
)
if err := lux.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -393,11 +380,7 @@ func (e *standardTxExecutor) ExportTx(tx *txs.ExportTx) error {
return err
}
var (
currentTimestamp = e.state.GetTimestamp()
isDurangoActive = e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
)
if err := lux.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -505,65 +488,11 @@ func (e *standardTxExecutor) RemoveChainValidatorTx(tx *txs.RemoveChainValidator
return nil
}
func (e *standardTxExecutor) TransformChainTx(tx *txs.TransformChainTx) error {
currentTimestamp := e.state.GetTimestamp()
if e.backend.Config.UpgradeConfig.IsEtnaActivated(currentTimestamp) {
return errTransformChainTxPostEtna
}
if err := e.tx.SyntacticVerify(e.backend.Runtime); err != nil {
return err
}
isDurangoActive := e.backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
if err := lux.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
return err
}
// Note: math.MaxInt32 * time.Second < math.MaxInt64 - so this can never
// overflow.
if time.Duration(tx.MaxStakeDuration)*time.Second > e.backend.Config.MaxStakeDuration {
return errMaxStakeDurationTooLarge
}
baseTxCreds, err := verifyPoAChainAuthorization(e.backend.Fx, e.state, e.tx, tx.Chain, tx.ChainAuth)
if err != nil {
return err
}
// Verify the flowcheck
fee, err := e.feeCalculator.CalculateFee(tx)
if err != nil {
return err
}
totalRewardAmount := tx.MaximumSupply - tx.InitialSupply
if err := e.backend.FlowChecker.VerifySpend(
tx,
e.state,
tx.Ins,
tx.Outs,
baseTxCreds,
// Invariant: [tx.AssetID != e.XAssetID]. This prevents the first
// entry in this map literal from being overwritten by the
// second entry.
map[ids.ID]uint64{
e.backend.Runtime.XAssetID: fee,
tx.AssetID: totalRewardAmount,
},
); err != nil {
return err
}
txID := e.tx.ID()
// Consume the UTXOS
lux.Consume(e.state, tx.Ins)
// Produce the UTXOS
lux.Produce(e.state, txID, tx.Outs)
// Transform the new chain in the database
e.state.AddNetTransformation(e.tx)
e.state.SetCurrentSupply(tx.Chain, tx.InitialSupply)
return nil
func (e *standardTxExecutor) TransformChainTx(*txs.TransformChainTx) error {
// TransformChainTx is permanently rejected: it has no role under
// activate-all-implicitly. Any historical TransformChainTx in genesis is
// already applied; live submissions are always refused.
return errTransformChainTxPostEtna
}
func (e *standardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionlessValidatorTx) error {
@@ -645,20 +574,12 @@ func (e *standardTxExecutor) TransferChainOwnershipTx(tx *txs.TransferChainOwner
}
func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error {
var (
currentTimestamp = e.state.GetTimestamp()
upgrades = e.backend.Config.UpgradeConfig
)
if !upgrades.IsDurangoActivated(currentTimestamp) {
return ErrDurangoUpgradeNotActive
}
// Verify the tx is well-formed
if err := e.tx.SyntacticVerify(e.backend.Runtime); err != nil {
return err
}
if err := lux.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -689,19 +610,13 @@ func (e *standardTxExecutor) BaseTx(tx *txs.BaseTx) error {
}
func (e *standardTxExecutor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx) error {
var (
currentTimestamp = e.state.GetTimestamp()
upgrades = e.backend.Config.UpgradeConfig
)
if !upgrades.IsEtnaActivated(currentTimestamp) {
return errEtnaUpgradeNotActive
}
currentTimestamp := e.state.GetTimestamp()
if err := e.tx.SyntacticVerify(e.backend.Runtime); err != nil {
return err
}
if err := lux.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -817,19 +732,13 @@ func (e *standardTxExecutor) ConvertNetworkToL1Tx(tx *txs.ConvertNetworkToL1Tx)
}
func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx) error {
var (
currentTimestamp = e.state.GetTimestamp()
upgrades = e.backend.Config.UpgradeConfig
)
if !upgrades.IsEtnaActivated(currentTimestamp) {
return errEtnaUpgradeNotActive
}
currentTimestamp := e.state.GetTimestamp()
if err := e.tx.SyntacticVerify(e.backend.Runtime); err != nil {
return err
}
if err := lux.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -969,19 +878,11 @@ func (e *standardTxExecutor) RegisterL1ValidatorTx(tx *txs.RegisterL1ValidatorTx
}
func (e *standardTxExecutor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeightTx) error {
var (
currentTimestamp = e.state.GetTimestamp()
upgrades = e.backend.Config.UpgradeConfig
)
if !upgrades.IsEtnaActivated(currentTimestamp) {
return errEtnaUpgradeNotActive
}
if err := e.tx.SyntacticVerify(e.backend.Runtime); err != nil {
return err
}
if err := lux.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -1105,19 +1006,11 @@ func (e *standardTxExecutor) SetL1ValidatorWeightTx(tx *txs.SetL1ValidatorWeight
}
func (e *standardTxExecutor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1ValidatorBalanceTx) error {
var (
currentTimestamp = e.state.GetTimestamp()
upgrades = e.backend.Config.UpgradeConfig
)
if !upgrades.IsEtnaActivated(currentTimestamp) {
return errEtnaUpgradeNotActive
}
if err := e.tx.SyntacticVerify(e.backend.Runtime); err != nil {
return err
}
if err := lux.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -1177,19 +1070,11 @@ func (e *standardTxExecutor) IncreaseL1ValidatorBalanceTx(tx *txs.IncreaseL1Vali
}
func (e *standardTxExecutor) DisableL1ValidatorTx(tx *txs.DisableL1ValidatorTx) error {
var (
currentTimestamp = e.state.GetTimestamp()
upgrades = e.backend.Config.UpgradeConfig
)
if !upgrades.IsEtnaActivated(currentTimestamp) {
return errEtnaUpgradeNotActive
}
if err := e.tx.SyntacticVerify(e.backend.Runtime); err != nil {
return err
}
if err := lux.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil {
if err := lux.VerifyMemoFieldLength(tx.Memo, true); err != nil {
return err
}
@@ -1293,46 +1178,35 @@ func (e *standardTxExecutor) putStaker(stakerTx txs.Staker) error {
err error
)
if !e.backend.Config.UpgradeConfig.IsDurangoActivated(chainTime) {
// Pre-Durango, stakers set a future [StartTime] and are added to the
// pending staker set. They are promoted to the current staker set once
// the chain time reaches [StartTime].
scheduledStakerTx, ok := stakerTx.(txs.ScheduledStaker)
if !ok {
return fmt.Errorf("%w: %T", errMissingStartTimePreDurango, stakerTx)
}
staker, err = state.NewPendingStaker(txID, scheduledStakerTx)
} else {
// Only calculate the potentialReward for permissionless stakers.
// Recall that we only need to check if this is a permissioned
// validator as there are no permissioned delegators
var potentialReward uint64
if !stakerTx.CurrentPriority().IsPermissionedValidator() {
chainID := stakerTx.ChainID()
currentSupply, err := e.state.GetCurrentSupply(chainID)
if err != nil {
return err
}
rewards, err := GetRewardsCalculator(e.backend, e.state, chainID)
if err != nil {
return err
}
// Post-Durango, stakers are immediately added to the current staker
// set. Their [StartTime] is the current chain time.
stakeDuration := stakerTx.EndTime().Sub(chainTime)
potentialReward = rewards.Calculate(
stakeDuration,
stakerTx.Weight(),
currentSupply,
)
e.state.SetCurrentSupply(chainID, currentSupply+potentialReward)
// Only calculate the potentialReward for permissionless stakers.
// Recall that we only need to check if this is a permissioned
// validator as there are no permissioned delegators
var potentialReward uint64
if !stakerTx.CurrentPriority().IsPermissionedValidator() {
chainID := stakerTx.ChainID()
currentSupply, err := e.state.GetCurrentSupply(chainID)
if err != nil {
return err
}
staker, err = state.NewCurrentStaker(txID, stakerTx, chainTime, potentialReward)
rewards, err := GetRewardsCalculator(e.backend, e.state, chainID)
if err != nil {
return err
}
// Stakers are immediately added to the current staker set. Their
// [StartTime] is the current chain time.
stakeDuration := stakerTx.EndTime().Sub(chainTime)
potentialReward = rewards.Calculate(
stakeDuration,
stakerTx.Weight(),
currentSupply,
)
e.state.SetCurrentSupply(chainID, currentSupply+potentialReward)
}
staker, err = state.NewCurrentStaker(txID, stakerTx, chainTime, potentialReward)
if err != nil {
return err
}
@@ -221,11 +221,6 @@ func advanceTimeTo(
changed = true
}
if !backend.Config.UpgradeConfig.IsEtnaActivated(newChainTime) {
changes.SetTimestamp(newChainTime)
return changes, changed, nil
}
newChainTimeUnix := uint64(newChainTime.Unix())
if err := removeStaleExpiries(parentState, changes, newChainTimeUnix); err != nil {
return nil, false, fmt.Errorf("failed to remove stale expiries: %w", err)
+9 -21
View File
@@ -10,7 +10,6 @@ import (
"fmt"
"maps"
"slices"
"time"
validators "github.com/luxfi/validators"
"github.com/luxfi/crypto/bls"
@@ -254,8 +253,8 @@ type cacheMetrics struct {
misses metric.Counter
}
// NewCachedValidatorState creates a new cached validator state aware of the
// LP-181 epoch upgrade (gated by upgrade.Config.GraniteTime).
// NewCachedValidatorState creates a new cached validator state with the
// LP-181 epoch cache enabled (always-on under activate-all-implicitly).
func NewCachedValidatorState(
state ValidatorState,
upgradeConfig *upgrade.Config,
@@ -293,35 +292,24 @@ func NewCachedValidatorState(
}, nil
}
// GetValidatorSet implements ValidatorState with caching gated by the
// LP-181 epoch-upgrade activation (upgrade.Config.GraniteTime).
// GetValidatorSet implements ValidatorState with LP-181 epoch caching.
func (c *CachedValidatorState) GetValidatorSet(
ctx context.Context,
height uint64,
chainID ids.ID,
) (map[ids.NodeID]*ValidatorData, error) {
// Only cache after the epoch upgrade is active.
// Use current time as approximation since we don't have block timestamp.
if c.upgradeConfig != nil && c.upgradeConfig.IsGraniteActivated(time.Now()) {
key := cacheKey{height: height, chainID: chainID}
if cached, ok := c.cache.Get(key); ok {
c.metrics.hits.Inc()
return cached, nil
}
c.metrics.misses.Inc()
key := cacheKey{height: height, chainID: chainID}
if cached, ok := c.cache.Get(key); ok {
c.metrics.hits.Inc()
return cached, nil
}
c.metrics.misses.Inc()
// Cache miss or pre-epoch-upgrade — fetch from underlying state.
vdrSet, err := c.state.GetValidatorSet(ctx, height, chainID)
if err != nil {
return nil, err
}
// Cache the result if the epoch upgrade is active.
if c.upgradeConfig != nil && c.upgradeConfig.IsGraniteActivated(time.Now()) {
key := cacheKey{height: height, chainID: chainID}
c.cache.Put(key, vdrSet)
}
c.cache.Put(key, vdrSet)
return vdrSet, nil
}
+3 -5
View File
@@ -1068,14 +1068,12 @@ func initTestRemoteProposerVM(
}
}
_ = activationTime
_ = durangoTime
proVM := New(
&coreVM,
Config{
Upgrades: upgrade.Config{
ApricotPhase4Time: activationTime,
ApricotPhase4MinPChainHeight: 0,
DurangoTime: durangoTime,
},
Upgrades: upgrade.Default,
MinBlkDelay: DefaultMinBlockDelay,
NumHistoricalBlocks: DefaultNumHistoricalBlocks,
StakingLeafSigner: pTestSigner,
+15 -117
View File
@@ -180,12 +180,7 @@ func (p *postForkCommonComponents) Verify(
}
// 3. Proposer window validation
var shouldHaveProposer bool
if p.vm.Upgrades.IsDurangoActivated(parentTimestamp) {
shouldHaveProposer, err = p.verifyPostDurangoBlockDelay(ctx, parentTimestamp, parentPChainHeight, child)
} else {
shouldHaveProposer, err = p.verifyPreDurangoBlockDelay(ctx, parentTimestamp, parentPChainHeight, child)
}
shouldHaveProposer, err := p.verifyPostDurangoBlockDelay(ctx, parentTimestamp, parentPChainHeight, child)
if err != nil {
return err
}
@@ -202,15 +197,10 @@ func (p *postForkCommonComponents) Verify(
)
}
var contextPChainHeight uint64
switch {
case p.vm.Upgrades.IsGraniteActivated(childTimestamp):
contextPChainHeight = childEpoch.PChainHeight
case p.vm.Upgrades.IsEtnaActivated(childTimestamp):
contextPChainHeight = childPChainHeight
default:
contextPChainHeight = parentPChainHeight
}
// Activate-all-implicitly: Granite is always live → epoch-based height.
_ = childPChainHeight
_ = parentPChainHeight
contextPChainHeight := childEpoch.PChainHeight
return p.vm.verifyAndRecordInnerBlk(
ctx,
@@ -247,39 +237,22 @@ func (p *postForkCommonComponents) buildChild(
return nil, err
}
var shouldBuildSignedBlock bool
if p.vm.Upgrades.IsDurangoActivated(parentTimestamp) {
shouldBuildSignedBlock, err = p.shouldBuildSignedBlockPostDurango(
ctx,
parentID,
parentTimestamp,
parentPChainHeight,
newTimestamp,
)
} else {
shouldBuildSignedBlock, err = p.shouldBuildSignedBlockPreDurango(
ctx,
parentID,
parentTimestamp,
parentPChainHeight,
newTimestamp,
)
}
shouldBuildSignedBlock, err := p.shouldBuildSignedBlockPostDurango(
ctx,
parentID,
parentTimestamp,
parentPChainHeight,
newTimestamp,
)
if err != nil {
return nil, err
}
epoch := lp181.NewEpoch(p.vm.Upgrades, parentPChainHeight, toBlockEpoch(parentEpoch), parentTimestamp, newTimestamp)
var contextPChainHeight uint64
switch {
case p.vm.Upgrades.IsGraniteActivated(newTimestamp):
contextPChainHeight = epoch.PChainHeight
case p.vm.Upgrades.IsEtnaActivated(newTimestamp):
contextPChainHeight = pChainHeight
default:
contextPChainHeight = parentPChainHeight
}
// Activate-all-implicitly: Granite is always live → epoch-based height.
_ = pChainHeight
contextPChainHeight := epoch.PChainHeight
var innerBlock chain.Block
if p.vm.blockBuilderVM != nil {
@@ -389,41 +362,6 @@ func verifyIsNotOracleBlock(ctx context.Context, b chain.Block) error {
}
}
func (p *postForkCommonComponents) verifyPreDurangoBlockDelay(
ctx context.Context,
parentTimestamp time.Time,
parentPChainHeight uint64,
blk *postForkBlock,
) (bool, error) {
var (
blkTimestamp = blk.Timestamp()
childHeight = blk.Height()
proposerID = blk.Proposer()
)
minDelay, err := p.vm.Windower.Delay(
ctx,
childHeight,
parentPChainHeight,
proposerID,
proposer.MaxVerifyWindows,
)
if err != nil {
p.vm.logger.Error("unexpected block verification failure",
log.String("reason", "failed to calculate required timestamp delay"),
log.Stringer("blkID", blk.ID()),
log.Err(err),
)
return false, err
}
delay := blkTimestamp.Sub(parentTimestamp)
if delay < minDelay {
return false, fmt.Errorf("%w: delay %s < minDelay %s", errProposerWindowNotStarted, delay, minDelay)
}
return delay < proposer.MaxVerifyDelay, nil
}
func (p *postForkCommonComponents) verifyPostDurangoBlockDelay(
ctx context.Context,
parentTimestamp time.Time,
@@ -504,43 +442,3 @@ func (p *postForkCommonComponents) shouldBuildSignedBlockPostDurango(
return false, fmt.Errorf("%w: slot %d expects %s", errUnexpectedProposer, currentSlot, expectedProposerID)
}
func (p *postForkCommonComponents) shouldBuildSignedBlockPreDurango(
ctx context.Context,
parentID ids.ID,
parentTimestamp time.Time,
parentPChainHeight uint64,
newTimestamp time.Time,
) (bool, error) {
delay := newTimestamp.Sub(parentTimestamp)
if delay >= proposer.MaxBuildDelay {
return false, nil // time for any node to build an unsigned block
}
parentHeight := p.innerBlk.Height()
proposerID := p.vm.rt.NodeID
minDelay, err := p.vm.Windower.Delay(ctx, parentHeight+1, parentPChainHeight, proposerID, proposer.MaxBuildWindows)
if err != nil {
p.vm.logger.Error("unexpected build block failure",
log.String("reason", "failed to calculate required timestamp delay"),
log.Stringer("parentID", parentID),
log.Err(err),
)
return false, err
}
if delay >= minDelay {
// it's time for this node to propose a block. It'll be signed or
// unsigned depending on the delay
return delay < proposer.MaxVerifyDelay, nil
}
// It's not our turn to propose a block yet. This is likely caused by having
// previously notified the consensus engine to attempt to build a block on
// top of a block that is no longer the preferred block.
p.vm.logger.Debug("build block dropped",
log.Time("parentTimestamp", parentTimestamp),
log.Duration("minDelay", minDelay),
log.Time("blockTimestamp", newTimestamp),
)
return false, fmt.Errorf("%w: delay %s < minDelay %s", errProposerWindowNotStarted, delay, minDelay)
}
+1 -5
View File
@@ -18,12 +18,8 @@ func NewEpoch(
parentPChainHeight uint64,
parentEpoch block.Epoch,
parentTimestamp time.Time,
childTimestamp time.Time,
_ time.Time,
) block.Epoch {
if !upgrades.IsGraniteActivated(childTimestamp) {
return block.Epoch{}
}
if parentEpoch == (block.Epoch{}) {
// If the parent was not assigned an epoch, then the child is the first
// block of the initial epoch.
+20 -63
View File
@@ -108,22 +108,17 @@ func (b *preForkBlock) getInnerBlk() chain.Block {
}
func (b *preForkBlock) verifyPreForkChild(ctx context.Context, child *preForkBlock) error {
// FIX 2: Byzantine validation BEFORE proposer window check
// Ensure parent is an oracle block if post-fork
parentTimestamp := b.Timestamp()
if b.vm.Upgrades.IsApricotPhase4Activated(parentTimestamp) {
if err := verifyIsOracleBlock(ctx, b.Block); err != nil {
// If parent is post-fork but not an oracle block,
// preFork children are not allowed
return errUnexpectedBlockType
}
b.vm.logger.Debug("allowing pre-fork block after the fork time",
log.String("reason", "parent is an oracle block"),
log.Stringer("blkID", b.ID()),
)
// Under activate-all-implicitly, every parent is post-ApricotPhase4 so
// preFork children are only allowed when the parent is an oracle block.
if err := verifyIsOracleBlock(ctx, b.Block); err != nil {
return errUnexpectedBlockType
}
b.vm.logger.Debug("allowing pre-fork block after the fork time",
log.String("reason", "parent is an oracle block"),
log.Stringer("blkID", b.ID()),
)
return child.Block.Verify(ctx)
}
@@ -157,10 +152,6 @@ func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkB
currentPChainHeight,
)
}
if childPChainHeight < b.vm.Upgrades.ApricotPhase4MinPChainHeight {
return errPChainHeightTooLow
}
// Make sure [b] is the parent of [child]'s inner block
expectedInnerParentID := b.ID()
innerParentID := child.innerBlk.Parent()
@@ -168,13 +159,9 @@ func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkB
return errInnerParentMismatch
}
// A *preForkBlock can only have a *postForkBlock child
// if the *preForkBlock is the last *preForkBlock before activation takes effect
// (its timestamp is at or after the activation time)
// Activate-all-implicitly: ApricotPhase4 is always live, so the
// pre-fork → post-fork transition condition is permanently satisfied.
parentTimestamp := b.Timestamp()
if !b.vm.Upgrades.IsApricotPhase4Activated(parentTimestamp) {
return errProposersNotActivated
}
// Child's timestamp must be at or after its parent's timestamp
childTimestamp := child.Timestamp()
@@ -182,8 +169,7 @@ func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkB
return errTimeNotMonotonic
}
// Validate epoch (LP-181 epoching, gated by upgrade.Config.GraniteTime
// for upstream-codec compatibility).
// Validate epoch (LP-181 epoching, always-on under activate-all-implicitly).
// Pre-fork blocks always have empty epoch, so use that as parent epoch
parentEpoch := block.Epoch{} // Pre-fork blocks have no epoch
childEpoch := child.PChainEpoch()
@@ -215,48 +201,19 @@ func (*preForkBlock) verifyPostForkOption(context.Context, *postForkOption) erro
}
func (b *preForkBlock) buildChild(ctx context.Context) (Block, error) {
// Activate-all-implicitly: ApricotPhase4 is always live, so the
// "chain is currently forking" path is the only one taken.
parentTimestamp := b.Timestamp()
if !b.vm.Upgrades.IsApricotPhase4Activated(parentTimestamp) {
// The chain hasn't forked yet
// FIX 5: BuildBlockWithRuntime - proper context passing
var innerBlock chain.Block
if b.vm.blockBuilderVM != nil {
builtBlock, err := b.vm.blockBuilderVM.BuildBlockWithRuntime(ctx, &runtime.Runtime{})
if err != nil {
return nil, err
}
innerBlock = builtBlock
} else {
engineBlock, err := b.vm.ChainVM.BuildBlock(ctx)
if err != nil {
return nil, err
}
innerBlock = engineBlock
}
b.vm.logger.Info("built block",
log.Stringer("blkID", innerBlock.ID()),
log.Uint64("height", innerBlock.Height()),
log.Time("parentTimestamp", parentTimestamp),
)
return &preForkBlock{
Block: innerBlock,
vm: b.vm,
}, nil
}
// The chain is currently forking
parentID := b.ID()
newTimestamp := b.vm.Time().Truncate(time.Second)
if newTimestamp.Before(parentTimestamp) {
newTimestamp = parentTimestamp
}
// The child's P-Chain height is proposed as the optimal P-Chain height that
// is at least the minimum height
pChainHeight, err := b.vm.selectChildPChainHeight(ctx, b.vm.Upgrades.ApricotPhase4MinPChainHeight)
// The child's P-Chain height is proposed as the optimal P-Chain height
// available; under activate-all-implicitly there is no historical
// minimum-height floor.
pChainHeight, err := b.vm.selectChildPChainHeight(ctx, 0)
if err != nil {
b.vm.logger.Error("unexpected build block failure",
log.String("reason", "failed to calculate optimal P-chain height"),
@@ -283,8 +240,8 @@ func (b *preForkBlock) buildChild(ctx context.Context) (Block, error) {
innerBlock = engineBlock
}
// Calculate the epoch for the child block (LP-181, gated by
// upgrade.Config.GraniteTime for upstream-codec compatibility).
// Calculate the epoch for the child block (LP-181, always-on under
// activate-all-implicitly).
parentEpoch := block.Epoch{} // Pre-fork blocks have no epoch
// For pre-fork blocks, we don't have explicit P-chain height tracking.
// We use 0 as the parent P-chain height for genesis/pre-fork blocks.
+9 -39
View File
@@ -418,24 +418,15 @@ func (vm *VM) timeToBuild(ctx context.Context) (time.Time, bool, error) {
parentTimestamp = blk.Timestamp()
nextStartTime time.Time
)
if vm.Upgrades.IsDurangoActivated(parentTimestamp) {
currentTime := vm.Clock.Time().Truncate(time.Second)
if nextStartTime, err = vm.getPostDurangoSlotTime(
ctx,
childBlockHeight,
pChainHeight,
proposer.TimeToSlot(parentTimestamp, currentTime),
parentTimestamp,
); err == nil {
vm.proposerBuildSlotGauge.Set(float64(proposer.TimeToSlot(parentTimestamp, nextStartTime)))
}
} else {
nextStartTime, err = vm.getPreDurangoSlotTime(
ctx,
childBlockHeight,
pChainHeight,
parentTimestamp,
)
currentTime := vm.Clock.Time().Truncate(time.Second)
if nextStartTime, err = vm.getPostDurangoSlotTime(
ctx,
childBlockHeight,
pChainHeight,
proposer.TimeToSlot(parentTimestamp, currentTime),
parentTimestamp,
); err == nil {
vm.proposerBuildSlotGauge.Set(float64(proposer.TimeToSlot(parentTimestamp, nextStartTime)))
}
if err != nil {
vm.logger.Debug("failed to fetch the expected delay",
@@ -452,27 +443,6 @@ func (vm *VM) timeToBuild(ctx context.Context) (time.Time, bool, error) {
return nextStartTime, true, nil
}
func (vm *VM) getPreDurangoSlotTime(
ctx context.Context,
blkHeight,
pChainHeight uint64,
parentTimestamp time.Time,
) (time.Time, error) {
delay, err := vm.Windower.Delay(ctx, blkHeight, pChainHeight, vm.rt.NodeID, proposer.MaxBuildWindows)
if err != nil {
return time.Time{}, err
}
// Note: The P-chain does not currently try to target any block time. It
// notifies the consensus engine as soon as a new block may be built. To
// avoid fast runs of blocks there is an additional minimum delay that
// validators can specify. This delay may be an issue for high performance,
// custom VMs. Until the P-chain is modified to target a specific block
// time, ProposerMinBlockDelay can be configured in the net config.
delay = max(delay, vm.MinBlkDelay)
return parentTimestamp.Add(delay), nil
}
func (vm *VM) getPostDurangoSlotTime(
ctx context.Context,
blkHeight,
+6 -11
View File
@@ -90,15 +90,15 @@ func initTestProposerVM(
*VM,
database.Database,
) {
return initTestProposerVMWithGranite(t, proBlkStartTime, durangoTime, upgrade.UnscheduledActivationTime, minPChainHeight)
return initTestProposerVMWithGranite(t, proBlkStartTime, durangoTime, time.Time{}, minPChainHeight)
}
func initTestProposerVMWithGranite(
t *testing.T,
proBlkStartTime time.Time,
durangoTime time.Time,
graniteTime time.Time,
minPChainHeight uint64,
_ time.Time,
_ time.Time,
_ time.Time,
_ uint64,
) (
*fullVM,
*validatorstest.State,
@@ -139,12 +139,7 @@ func initTestProposerVMWithGranite(
proVM := New(
coreVM,
Config{
Upgrades: upgrade.Config{
ApricotPhase4Time: proBlkStartTime,
ApricotPhase4MinPChainHeight: minPChainHeight,
DurangoTime: durangoTime,
GraniteTime: graniteTime,
},
Upgrades: upgrade.Default,
MinBlkDelay: DefaultMinBlockDelay,
NumHistoricalBlocks: DefaultNumHistoricalBlocks,
StakingLeafSigner: pTestSigner,
-9
View File
@@ -3,8 +3,6 @@
package config
import "time"
// Struct collecting all the foundational parameters of the XVM
type Config struct {
// Fee that is burned by every non-asset creating transaction
@@ -13,13 +11,6 @@ type Config struct {
// Fee that must be burned by every asset creating transaction
CreateAssetTxFee uint64 `json:"createAssetTxFee"`
// Time of the Etna network upgrade
EtnaTime time.Time `json:"etnaTime"`
// IndexTransactions enables transaction indexing by address
IndexTransactions bool `json:"indexTransactions"`
}
func (c *Config) IsEtnaActivated(timestamp time.Time) bool {
return !timestamp.Before(c.EtnaTime)
}
@@ -9,7 +9,6 @@ import (
"strings"
"testing"
"github.com/luxfi/timer/mockable"
"github.com/stretchr/testify/require"
consensustest "github.com/luxfi/consensus/test/helpers"
@@ -31,7 +30,6 @@ var (
feeConfig = config.Config{
TxFee: 2,
CreateAssetTxFee: 3,
EtnaTime: mockable.MaxTime,
}
)
+4 -2
View File
@@ -31,6 +31,7 @@ import (
"github.com/luxfi/math/set"
"github.com/luxfi/node/cache"
"github.com/luxfi/node/pubsub"
"github.com/luxfi/node/upgrade"
"github.com/luxfi/node/utils/json"
"github.com/luxfi/node/version"
"github.com/luxfi/node/vms/components/index"
@@ -545,8 +546,9 @@ func (vm *VM) ParseAddress(addrStr string) (ids.ID, ids.ShortID, error) {
}
func (vm *VM) Linearize(ctx context.Context, stopVertexID ids.ID, toEngine chan<- vmcore.Message) error {
// Use EtnaTime from config for chain state initialization
err := vm.state.InitializeChainState(stopVertexID, vm.Config.EtnaTime)
// Chain state initialization timestamp under activate-all-implicitly is
// the canonical Lux InitiallyActiveTime (Dec 5, 2020).
err := vm.state.InitializeChainState(stopVertexID, upgrade.InitiallyActiveTime)
if err != nil {
return err
}