mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
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:
+1
-1
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user