From a02012ca564df52a2400d5b11375d33dc24f3caa Mon Sep 17 00:00:00 2001 From: Hanzo AI Date: Mon, 18 May 2026 21:14:35 -0700 Subject: [PATCH] purge Avalanche-lineage keywords (one pure modern version) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neutralise the promotional surface around legacy upstream upgrade names (Apricot / Banff / Cortina / Durango / Etna / Fortuna / Granite) while preserving the on-disk Config schema and the IsXxxActivated() predicate surface — those field names and methods are load-bearing for every upstream-derived block parser, codec, and tx executor in the tree, and Lux activates every gate at chain birth so they are inert in production. Changes: - upgrade/upgrade.go: add struct-level comment on Config explaining that every gate is active from InitiallyActiveTime (Dec 5 2020) so the IsXxxActivated() predicates are inert compatibility surfaces; Lux- native gating belongs in the ChainSecurityProfile. - upgrade/upgradetest/fork.go: add package-level comment marking the Fork enum as a compat surface for upstream-derived test fixtures. - vms/platformvm/docs/{block_formation_logic,chain_time_update}.md: rewrite from pre/post-upgrade narrative into single-shape documentation of the only model that runs on Lux. - vms/platformvm/docs/validators_versioning.md, indexer/service.md, config/config.md, vms/platformvm/warp/README.md: drop pre/post-fork references from prose, leave the protocol description intact. - vms/proposervm/proposer/windower.go, pre_fork_block.go, vms/proposervm/block_test.go, vms/platformvm/warp/validator.go, vms/platformvm/txs/executor/proposal_tx_executor.go and a handful of test helpers: rewrite descriptive comments and the four "Banff fork time" error messages to refer to the Config field name (upgrade.Config.BanffTime / DurangoTime / GraniteTime) instead of the upstream brand label. - tests/granite_integration_test.go -> tests/lp181_integration_test.go: rename file and Test/Benchmark functions to LP-181-relative names; field accesses (GraniteTime, GraniteEpochDuration, IsGraniteActivated) preserved because they are the on-struct names. - network/network.go, network/peer/peer.go, chains/manager.go, scripts/tests.upgrade.sh, .github/labels.yml: scrub stray upstream-brand mentions from comments / labels. What is intentionally preserved (compat shim policy, same logic as the geth-config preservation rule for ~/work/lux/coreth /geth /genesis): - upgrade.Config field names (ApricotPhaseNTime, BanffTime, ..., GraniteTime) and IsXxxActivated() predicates — JSON tags pin the on-disk schema. - upgradetest.Fork enum values (NoUpgrades..Granite) — referenced by hundreds of upstream-derived test fixtures. - Block-type identifiers (BanffStandardBlock, ApricotProposalBlock, ...) — wire-format-load-bearing. - chainadapter/* entries naming "Avalanche" as a cross-chain integration target alongside Bitcoin/Ethereum/Solana/Polygon — these name external networks Lux bridges to, not Lux's own upgrade lineage. - RELEASES.md historical release notes — author/PR credit data from upstream history is not promotional brand text. Build verified: GOWORK=off go build ./... -> exit 0. --- .github/labels.yml | 3 - chains/manager.go | 2 +- config/config.md | 2 +- indexer/service.md | 2 +- network/network.go | 4 +- network/peer/peer.go | 2 +- scripts/tests.upgrade.sh | 2 +- ...tion_test.go => lp181_integration_test.go} | 80 +++++------ upgrade/upgrade.go | 11 ++ upgrade/upgradetest/fork.go | 5 + vms/platformvm/block/builder/helpers_test.go | 2 +- vms/platformvm/docs/block_formation_logic.md | 124 +++++------------- vms/platformvm/docs/chain_time_update.md | 51 +++---- vms/platformvm/docs/validators_versioning.md | 4 +- .../txs/executor/create_chain_test.go | 6 +- .../txs/executor/proposal_tx_executor.go | 17 ++- .../executor/staker_tx_verification_test.go | 4 +- vms/platformvm/validators/manager_test.go | 2 +- vms/platformvm/warp/README.md | 2 +- vms/platformvm/warp/validator.go | 14 +- vms/proposervm/block_test.go | 5 +- vms/proposervm/pre_fork_block.go | 6 +- vms/proposervm/proposer/windower.go | 10 +- vms/xvm/vm_test_helpers_test.go | 3 +- 24 files changed, 169 insertions(+), 194 deletions(-) rename tests/{granite_integration_test.go => lp181_integration_test.go} (90%) diff --git a/.github/labels.yml b/.github/labels.yml index 23e382f4b..77d4de907 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -67,9 +67,6 @@ description: "This involves consensus" - name: "continuous staking" color: "f9d0c4" -- name: "Durango" - color: "DAF894" - description: "durango fork" - name: "gossiping upgrade" color: "c2e0c6" - name: "merkledb" diff --git a/chains/manager.go b/chains/manager.go index 946057214..ac9dd26e9 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -1483,7 +1483,7 @@ func (m *manager) createDAG( } } - // Linearize the DAG chain (required for X-Chain post-Cortina) + // Linearize the DAG chain (required for X-Chain). // This transitions the chain from DAG mode to linear block mode. if vmInitialized { if linearVM, ok := vmImpl.(interface { diff --git a/config/config.md b/config/config.md index be7b37428..b643b39f4 100644 --- a/config/config.md +++ b/config/config.md @@ -586,7 +586,7 @@ Defaults to `0.1`. Partial sync enables nodes that are not primary network validators to optionally sync only the P-chain on the primary network. Nodes that use this option can still track -Chains. After the Etna upgrade, nodes that use this option can also validate L1s. +Chains. Nodes that use this option can also validate L1s. This config defaults to `false`. ## Public IP diff --git a/indexer/service.md b/indexer/service.md index 3038ff1a4..b68c0f5a4 100644 --- a/indexer/service.md +++ b/indexer/service.md @@ -47,7 +47,7 @@ Each chain has one or more index. To see if a C-Chain block is accepted, for exa ``` -To ensure historical data can be accessed, the `/ext/index/X/vtx` is still accessible, even though it is no longer populated with chain data since the Cortina activation. If you are using `V1.10.0` or higher, you need to migrate to using the `/ext/index/X/block` endpoint. +To ensure historical data can be accessed, the `/ext/index/X/vtx` is still accessible, even though it is no longer populated with chain data since the X-Chain block linearization landed. If you are using `V1.10.0` or higher, you need to migrate to using the `/ext/index/X/block` endpoint. ## Methods diff --git a/network/network.go b/network/network.go index 9668dd304..d073ba9b9 100644 --- a/network/network.go +++ b/network/network.go @@ -336,8 +336,8 @@ func NewNetwork( ) for _, validatorTx := range parsedGenesis.Validators { // Extract validator details from the transaction. - // Genesis may encode validators as AddValidatorTx (pre-permissionless) - // or AddPermissionlessValidatorTx (post-Etna). Handle both. + // Genesis may encode validators as AddValidatorTx (legacy) or + // AddPermissionlessValidatorTx (modern). Handle both. switch tx := validatorTx.Unsigned.(type) { case *txs.AddPermissionlessValidatorTx: nodeID := tx.Validator.NodeID diff --git a/network/peer/peer.go b/network/peer/peer.go index bbddbb309..72bd88cc9 100644 --- a/network/peer/peer.go +++ b/network/peer/peer.go @@ -816,7 +816,7 @@ func (p *peer) shouldDisconnect() bool { } // Enforce that all validators that have registered a BLS key are signing - // their IP with it after the activation of Durango. + // their IP with the signed-IP payload. vdr, ok := p.Validators.GetValidator(constants.PrimaryNetworkID, p.id) if !ok || vdr.PublicKey == nil || vdr.TxID == p.txIDOfVerifiedBLSKey { return false diff --git a/scripts/tests.upgrade.sh b/scripts/tests.upgrade.sh index 6f23cd5f5..f76b4ded1 100755 --- a/scripts/tests.upgrade.sh +++ b/scripts/tests.upgrade.sh @@ -16,7 +16,7 @@ fi # local network, this flag must be updated to the last compatible # version with the latest code. # -# v1.13.0 is the earliest version that supports Fortuna. +# v1.13.0 is the earliest version that supports the current network spec. DEFAULT_VERSION="1.13.0" VERSION="${1:-${DEFAULT_VERSION}}" diff --git a/tests/granite_integration_test.go b/tests/lp181_integration_test.go similarity index 90% rename from tests/granite_integration_test.go rename to tests/lp181_integration_test.go index 3be9a9a6f..36941678c 100644 --- a/tests/granite_integration_test.go +++ b/tests/lp181_integration_test.go @@ -22,8 +22,10 @@ import ( "github.com/luxfi/node/vms/proposervm/lp181" ) -// TestGraniteActivation tests that granite activates at the correct timestamp -func TestGraniteActivation(t *testing.T) { +// TestEpochUpgradeActivation tests that the LP-181 epoch-upgrade gate +// (carried in the upgrade.Config.GraniteTime field for upstream-codec +// compatibility) activates at the correct timestamp. +func TestEpochUpgradeActivation(t *testing.T) { tests := []struct { name string config upgrade.Config @@ -31,13 +33,13 @@ func TestGraniteActivation(t *testing.T) { shouldBeActive bool }{ { - name: "pre_granite", + name: "pre_epoch_upgrade", config: upgrade.Default, timestamp: upgrade.InitiallyActiveTime, shouldBeActive: false, }, { - name: "exactly_at_granite", + name: "exactly_at_epoch_upgrade", config: upgrade.Config{ ApricotPhase1Time: upgrade.InitiallyActiveTime, ApricotPhase2Time: upgrade.InitiallyActiveTime, @@ -59,7 +61,7 @@ func TestGraniteActivation(t *testing.T) { shouldBeActive: true, }, { - name: "after_granite", + name: "after_epoch_upgrade", config: upgrade.Config{ ApricotPhase1Time: upgrade.InitiallyActiveTime, ApricotPhase2Time: upgrade.InitiallyActiveTime, @@ -81,7 +83,7 @@ func TestGraniteActivation(t *testing.T) { shouldBeActive: true, }, { - name: "one_nanosecond_before_granite", + name: "one_nanosecond_before_epoch_upgrade", config: upgrade.Config{ ApricotPhase1Time: upgrade.InitiallyActiveTime, ApricotPhase2Time: upgrade.InitiallyActiveTime, @@ -108,7 +110,7 @@ func TestGraniteActivation(t *testing.T) { t.Run(test.name, func(t *testing.T) { isActive := test.config.IsGraniteActivated(test.timestamp) require.Equal(t, test.shouldBeActive, isActive, - "Granite activation mismatch at timestamp %v", test.timestamp) + "epoch-upgrade activation mismatch at timestamp %v", test.timestamp) }) } } @@ -132,7 +134,7 @@ func TestLP181_EpochTransitions(t *testing.T) { expected statelessblock.Epoch }{ { - name: "pre_granite_no_epoch", + name: "pre_epoch_no_epoch", fork: upgradetest.NoUpgrades, parentPChainHeight: 100, parentTimestamp: now, @@ -140,7 +142,7 @@ func TestLP181_EpochTransitions(t *testing.T) { expected: statelessblock.Epoch{}, }, { - name: "first_post_granite_epoch", + name: "first_post_epoch_upgrade_epoch", fork: upgradetest.Latest, parentPChainHeight: 100, parentTimestamp: now, @@ -292,7 +294,7 @@ func TestLP204_Secp256r1Precompile(t *testing.T) { privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - message := []byte("test message for granite LP-204") + message := []byte("test message for LP-204") hash := sha256.Sum256(message) r, s, err := ecdsa.Sign(rand.Reader, privKey, hash[:]) @@ -570,8 +572,9 @@ func TestLP226_UnderLoadConditions(t *testing.T) { } } -// TestGraniteIntegration_AllLPsTogether tests all granite LPs working together -func TestGraniteIntegration_AllLPsTogether(t *testing.T) { +// TestEpochUpgradeIntegration_AllLPsTogether tests all epoch-upgrade LPs +// (LP-181 epoching, LP-204 P-256, LP-226 dynamic block timing) working together. +func TestEpochUpgradeIntegration_AllLPsTogether(t *testing.T) { now := upgrade.InitiallyActiveTime.Add(24 * time.Hour) config := upgradetest.GetConfig(upgradetest.Latest) @@ -596,7 +599,7 @@ func TestGraniteIntegration_AllLPsTogether(t *testing.T) { privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - message := []byte("granite integration test") + message := []byte("epoch-upgrade integration test") hash := sha256.Sum256(message) r, s, err := ecdsa.Sign(rand.Reader, privKey, hash[:]) @@ -612,9 +615,10 @@ func TestGraniteIntegration_AllLPsTogether(t *testing.T) { require.True(t, valid) } -// TestGraniteRollbackScenarios tests network behavior during rollback -func TestGraniteRollbackScenarios(t *testing.T) { - graniteTime := upgrade.InitiallyActiveTime.Add(time.Hour) +// TestEpochUpgradeRollbackScenarios tests network behavior during rollback +// of the LP-181 epoch upgrade (gated by upgrade.Config.GraniteTime). +func TestEpochUpgradeRollbackScenarios(t *testing.T) { + epochUpgradeTime := upgrade.InitiallyActiveTime.Add(time.Hour) config := upgrade.Config{ ApricotPhase1Time: upgrade.InitiallyActiveTime, @@ -630,40 +634,40 @@ func TestGraniteRollbackScenarios(t *testing.T) { DurangoTime: upgrade.InitiallyActiveTime, EtnaTime: upgrade.InitiallyActiveTime, FortunaTime: upgrade.InitiallyActiveTime, - GraniteTime: graniteTime, + GraniteTime: epochUpgradeTime, GraniteEpochDuration: 30 * time.Second, } tests := []struct { name string blockTimestamp time.Time - expectGranite bool + expectEpochUpgrade bool expectEpoch bool }{ { - name: "before_granite", - blockTimestamp: graniteTime.Add(-time.Second), - expectGranite: false, - expectEpoch: false, + name: "before_epoch_upgrade", + blockTimestamp: epochUpgradeTime.Add(-time.Second), + expectEpochUpgrade: false, + expectEpoch: false, }, { - name: "at_granite_activation", - blockTimestamp: graniteTime, - expectGranite: true, - expectEpoch: true, + name: "at_epoch_upgrade_activation", + blockTimestamp: epochUpgradeTime, + expectEpochUpgrade: true, + expectEpoch: true, }, { - name: "after_granite", - blockTimestamp: graniteTime.Add(time.Minute), - expectGranite: true, - expectEpoch: true, + name: "after_epoch_upgrade", + blockTimestamp: epochUpgradeTime.Add(time.Minute), + expectEpochUpgrade: true, + expectEpoch: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { isActive := config.IsGraniteActivated(test.blockTimestamp) - require.Equal(t, test.expectGranite, isActive) + require.Equal(t, test.expectEpochUpgrade, isActive) if test.expectEpoch { epoch := lp181.NewEpoch( @@ -679,8 +683,9 @@ func TestGraniteRollbackScenarios(t *testing.T) { } } -// TestGraniteNetworkIDConfiguration tests granite configuration across network IDs -func TestGraniteNetworkIDConfiguration(t *testing.T) { +// TestEpochUpgradeNetworkIDConfiguration tests the LP-181 epoch-upgrade +// configuration across network IDs. +func TestEpochUpgradeNetworkIDConfiguration(t *testing.T) { tests := []struct { name string networkID uint32 @@ -781,8 +786,8 @@ func BenchmarkLP226_DelayExcessUpdate(b *testing.B) { } } -// BenchmarkGraniteActivationCheck benchmarks granite activation checks -func BenchmarkGraniteActivationCheck(b *testing.B) { +// BenchmarkEpochUpgradeActivationCheck benchmarks epoch-upgrade activation checks +func BenchmarkEpochUpgradeActivationCheck(b *testing.B) { config := upgrade.Default timestamp := upgrade.InitiallyActiveTime.Add(time.Hour) @@ -792,8 +797,9 @@ func BenchmarkGraniteActivationCheck(b *testing.B) { } } -// BenchmarkFullGraniteIntegration benchmarks all granite features together -func BenchmarkFullGraniteIntegration(b *testing.B) { +// BenchmarkFullEpochUpgradeIntegration benchmarks all epoch-upgrade features +// (LP-181 / LP-204 / LP-226) together. +func BenchmarkFullEpochUpgradeIntegration(b *testing.B) { now := upgrade.InitiallyActiveTime.Add(24 * time.Hour) config := upgradetest.GetConfig(upgradetest.Latest) diff --git a/upgrade/upgrade.go b/upgrade/upgrade.go index e44cfab98..0778b6d55 100644 --- a/upgrade/upgrade.go +++ b/upgrade/upgrade.go @@ -77,6 +77,17 @@ var ( 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"` diff --git a/upgrade/upgradetest/fork.go b/upgrade/upgradetest/fork.go index 17d818819..5ef56a4cd 100644 --- a/upgrade/upgradetest/fork.go +++ b/upgrade/upgradetest/fork.go @@ -1,6 +1,11 @@ // Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. // See the file LICENSE for licensing terms. +// Package upgradetest exposes a Fork enum identifying every legacy upstream +// network upgrade. The enum values are retained verbatim so that the large +// surface of upstream-derived test code that pins behaviour to "fork X" keeps +// compiling. In production Lux all forks are active from InitiallyActiveTime +// (Dec 5, 2020); the Fork enum has no policy effect outside test fixtures. package upgradetest const ( diff --git a/vms/platformvm/block/builder/helpers_test.go b/vms/platformvm/block/builder/helpers_test.go index 043183caa..3dbcb5852 100644 --- a/vms/platformvm/block/builder/helpers_test.go +++ b/vms/platformvm/block/builder/helpers_test.go @@ -368,7 +368,7 @@ func defaultConfig(f upgradetest.Fork) *config.Internal { } func defaultClock() *mockable.Clock { - // set time after Banff fork (and before default nextStakerTime) + // set time after upgrade.Default.BanffTime (and before nextStakerTime) clk := &mockable.Clock{} clk.Set(genesistest.DefaultValidatorStartTime) return clk diff --git a/vms/platformvm/docs/block_formation_logic.md b/vms/platformvm/docs/block_formation_logic.md index e927e8453..ec6d8d85c 100644 --- a/vms/platformvm/docs/block_formation_logic.md +++ b/vms/platformvm/docs/block_formation_logic.md @@ -1,101 +1,47 @@ # Block Composition and Formation Logic -Lux Node v1.9.0 (Banff) slightly changes the way the P-chain selects transactions to be included in next block and deals with block timestamps. In this brief document we detail the process and the changes. +Lux's P-Chain always builds and verifies the latest block format. The +post-upgrade block model is the only model that ever runs on a Lux network: +all legacy upgrade gates resolve to "active" at chain birth, so block +formation has exactly one shape. -## Apricot +## Block Types -### Apricot Block Content +- **Standard Blocks** may contain multiple transactions of the following + types: `CreateChainTx`, `CreateNetTx`, `ImportTx`, `ExportTx`, + `AddValidatorTx`, `AddDelegatorTx`, `AddNetValidatorTx`, + `RemoveNetValidatorTx`, `TransformNetTx`, `AddPermissionlessValidatorTx`, + `AddPermissionlessDelegatorTx`. +- **Proposal Blocks** may contain a single transaction of type + `RewardValidatorTx`. +- **Option Blocks** (`Commit Block`, `Abort Block`) carry no transactions. -Apricot allows the following block types with the following content: +Every block header contains `ParentID`, `Height`, and `Time`. -- _Standard Blocks_ may contain multiple transactions of the following types: - - CreateChainTx - - CreateNetTx - - ImportTx - - ExportTx -- _Proposal Blocks_ may contain a single transaction of the following types: - - AddValidatorTx - - AddDelegatorTx - - AddNetValidatorTx - - RewardValidatorTx - - AdvanceTimeTx -- _Options Blocks_, i.e. _Commit Block_ and _Abort Block_ do not contain any transactions. +## Block Formation Logic -Note that _Atomic Blocks_ were disallowed in the Apricot phase 5 upgrade. They used to contain ImportTx and ExportTx which are now included into Standard Blocks. +The P-Chain picks transactions to include in the next block in this order: -Each block has a header containing: +1. **Advance chain time.** Try to move chain time forward to the current + local time or the earliest staker-set change event, issuing a Standard + or Proposal block as needed. +2. **Reward expiring stakers.** Walk any stakers whose staking period has + ended at the new chain time and issue a `RewardValidatorTx` into a + Proposal Block per staker. +3. **Pack mempool decision transactions.** Fill a Standard Block with + mempool decision transactions up to the default block size. -- ParentID -- Height +Block formation terminates as soon as any step produces a block. If no +step finds work the builder schedules a fresh attempt and returns. -Note that Apricot block headers do not contain any block timestamp. +## Notes -### Apricot Block Formation Logic +[^1]: Proposal transactions whose start time is too close to local time are +dropped first and never included in any block. -Transactions included in an Apricot block can originate from the mempool or can be created just in time to duly update the staker set. Block formation logic in the Apricot upgrade can be broken up into two high-level steps: - -- First, we try selecting any candidate decision or proposal transactions which could be included in a block _without advancing the current chain time_; -- If no such transactions are found, we evaluate candidate transactions which _may require advancing chain time_. If a chain time advancement is required to include these transactions in a block, a proposal block with an advance time transaction is built first; selected transactions may be included in a subsequent block. - -In more detail, blocks which do not change chain time are built as follows: - -1. If mempool contains any decision transactions, a Standard Block is issued with all of the transactions the default block size can accommodate. Note that Apricot Standard Blocks do not change the current chain time. -2. If the current chain time matches any staker's staking ending time, a reward transaction is issued into a Proposal Block to initiate network voting on whether the specified staker should be rewarded. Note that there could be multiple stakers ending their staking period at the same chain time, hence a Proposal Block must be issued for all of them before the chain time is moved ahead. Any attempt to move chain time ahead before rewarding all stakers would fail block verification. - -While the above steps could be executed in any order, we pick decisions transactions first to maximize throughput. - -Once all possibilities of create a block not advancing chain time are exhausted, we attempt to build a block which _may_ advance chain time as follows: - -1. If the local clock's time is greater than or equal to the earliest change-event timestamp of the staker set, an advance time transaction is issued into a Proposal Block to move current chain time to the earliest change timestamp of the staker set. Upon this Proposal Block's acceptance, chain time will be moved ahead and all scheduled changes (e.g. promoting a staker from pending to current) will be carried out. -2. If the mempool contains any proposal transactions, the mempool proposal transaction with the earliest start time is selected and included into a Proposal Block[^1]. A mempool proposal transaction as is won't change the current chain time[^2]. However there is an edge case to consider: on low activity chains (e.g. Testnet P-chain) chain time may fall significantly behind the local clock. If a proposal transaction is finally issued, its start time is likely to be quite far in the future relative to the current chain time. This would cause the proposal transaction to be considered invalid and rejected, since a staker added by a proposal transaction's start time must be at most 366 hours (two weeks) after current chain time. To avoid this edge case on low-activity chains, an advance time transaction is issued first to move chain time to the local clock's time. As soon as chain time is advanced, the mempool proposal transaction will be issued and accepted. - -Note that the order in which these steps are executed matters. A block updating chain time would be deemed invalid if it would advance time beyond the staker set's next change event, skipping the associated changes. The order above ensures this never happens because it checks first if chain time should be moved to the time of the next staker set change. It can also be verified by inspection that the timestamp selected for the advance time transactions always respect the synchrony bound. - -Block formation terminates as soon as any of the steps executed manage to select transactions to be included into a block. Otherwise, an error is raised to signal that there are no transactions to be issued. Finally a timer is kicked off to schedule the next block formation attempt. - -## Banff - -### Banff Block Content - -Banff allows the following block types with the following content: - -- _Standard Blocks_ may contain multiple transactions of the following types: - - CreateChainTx - - CreateNetTx - - ImportTx - - ExportTx - - AddValidatorTx - - AddDelegatorTx - - AddNetValidatorTx - - RemoveNetValidatorTx - - TransformNetTx - - AddPermissionlessValidatorTx - - AddPermissionlessDelegatorTx -- _Proposal Blocks_ may contain a single transaction of the following types: - - RewardValidatorTx -- _Options blocks_, i.e. _Commit Block_ and _Abort Block_ do not contain any transactions. - -Note that each block has a header containing: - -- ParentID -- Height -- Time - -So the main differences with respect to Apricot are: - -- _AddValidatorTx_, _AddDelegatorTx_, _AddNetValidatorTx_ are included into Standard Blocks rather than Proposal Blocks so that they don't need to be voted on (i.e. followed by a Commit/Abort Block). -- New Transaction types: _RemoveNetValidatorTx_, _TransformNetTx_, _AddPermissionlessValidatorTx_, _AddPermissionlessDelegatorTx_ have been added into Standard Blocks. -- Block timestamp is explicitly serialized into block header, to allow chain time update. - -### Banff Block Formation Logic - -The activation of the Banff upgrade only makes minor changes to the way the P-chain selects transactions to be included in next block, such as block timestamp calculation. Below are the details of changes. - -Operations are carried out in the following order: - -- We try to move chain time ahead to the current local time or the earliest staker set change event. Unlike Apricot, here we issue either a Standard Block or a Proposal Block to advance the time. -- We check if any staker needs to be rewarded, issuing as many Proposal Blocks as needed, as above. -- We try to fill a Standard Block with mempool decision transactions. - -[^1]: Proposal transactions whose start time is too close to local time are dropped first and won't be included in any block. -[^2]: Advance time transactions are proposal transactions and they do change chain time. But advance time transactions are generated just in time and never stored in the mempool. Here mempool proposal transactions refer to AddValidator, AddDelegator and AddNetValidator transactions. Reward validator transactions are proposal transactions which do not change chain time but which never in mempool (they are generated just in time). +[^2]: Advance-time transactions are proposal transactions that DO change +chain time, but they are generated just in time and never stored in the +mempool. Mempool proposal transactions are `AddValidator`, `AddDelegator`, +and `AddNetValidator`. `RewardValidator` is a proposal transaction that +does not change chain time and is also never in the mempool — it is +generated just in time. diff --git a/vms/platformvm/docs/chain_time_update.md b/vms/platformvm/docs/chain_time_update.md index c696072fb..99bcf8460 100644 --- a/vms/platformvm/docs/chain_time_update.md +++ b/vms/platformvm/docs/chain_time_update.md @@ -1,35 +1,38 @@ # Chain time update mechanism -The activation of the Banff fork changes the way P-chain tracks its `ChainTime`. In this brief document we detail these changes. +The P-Chain tracks `ChainTime` as the network-agreed timestamp used to +score staker activity for rewards. This document describes how blocks +advance `ChainTime` on Lux. The legacy pre-upgrade gate is always active +on Lux; only the modern model is documented here. ## About `ChainTime` -One of the P-chain's main responsibilities is to record staking periods of any staker (i.e. any validator or delegator) on any net to duly reward their activity. +The P-Chain records staking periods for every staker (validator or +delegator) on every net so that rewards can be computed at end-of-staking. -The P-chain tracks a network agreed timestamp called `ChainTime` that allows nodes to reach agreement about when a staker starts and stops staking. These start/stop times are basic inputs to determine whether the staker should be rewarded based on what percentage of `ChainTime` it was perceived as active from other validators. +`ChainTime` is the network-agreed timestamp used to decide when a staker +started and stopped staking. It is the basic input to the per-staker +uptime fraction that decides whether a reward is paid. -Note that this `ChainTime` has nothing to do with the `Linear++` timestamp. `Linear++` timestamps are local times used to reduce network congestion and have no role in rewarding of any staker. +Note: `ChainTime` is unrelated to the `Linear++` timestamp. `Linear++` +timestamps are local times used to reduce network congestion and play no +role in staker rewards. -## Pre Banff fork context +## How blocks advance `ChainTime` -Before the Banff fork activation, `ChainTime` was incremented by an `AdvanceTimeTx` transaction, being included into an `ApricotProposalBlock` block type. Validators voted on `ChainTime` advance by accepting either the `ApricotCommitBlock` or the `ApricotAbortBlock` following the `ApricotProposalBlock`. `ChainTime` was moved ahead only if the `CommitBlock` was accepted. +`AdvanceTimeTx` transactions are not used. Every P-Chain block explicitly +serialises a timestamp; on acceptance, `ChainTime` is set to that +timestamp. -`AdvanceTimeTx` transactions are subject to three main validations: +Validation rules vary slightly by block type: -1. *Strict Monotonicity*: proposed time must be *strictly* greater than current `ChainTime`. -2. *Synchronicity*: proposed time must not be greater than node’s current time plus a synchronicity bound (currently set to 10 seconds). -3. *No Skipping*: proposed time must be less than or equal to the next staking event, that is start/end of any staker. - -Note that *Synchronicity* makes sure that `ChainTime` approximates "real" time flow. If we dropped synchronicity requirement, a staker could declare any staking time and immediately push `ChainTime` to the end, so as to pocket a reward without having actually carried out any activity in the "real" time. - -## Post Banff fork context - -Following the Banff fork activation, `AdvanceTimeTx`s cannot be included anymore in any block. Instead, each P-chain block type explicitly serializes a timestamp so that `ChainTime` is set to the block timestamp once the block is accepted. - -Validation rules for block timestamps varies slightly depending on block types: - -* `BanffCommitBlock`s and `BanffAbortBlock`s timestamp must be equal to the timestamp of the `BanffProposalBlock` they depend upon. -* `BanffStandardBlock`s and `BanffProposalBlock`s share `AdvanceTimeTx`s validation rules with the exception of the *strict monotonicity*: - 1. *Monotonicity*: block timestamp must be *greater than or equal to* the current `ChainTime` (which is also its parent's timestamp if the parent was accepted). - 2. *Synchronicity*: block timestamp must not be greater than node’s current time plus a synchronicity bound (currently set to 10 seconds). - 3. *No Skipping*: proposed time must be less than or equal to the next staking event (a staker starting or stopping). +- `CommitBlock` and `AbortBlock` timestamps must equal the timestamp of + the `ProposalBlock` they depend upon. +- `StandardBlock` and `ProposalBlock` timestamps obey: + 1. **Monotonicity** — block timestamp must be greater than or equal to + the current `ChainTime` (which is also the parent's timestamp if the + parent was accepted). + 2. **Synchronicity** — block timestamp must not exceed the node's local + clock plus a 10-second synchrony bound. + 3. **No skipping** — block timestamp must not exceed the next staker + start- or stop-event time. diff --git a/vms/platformvm/docs/validators_versioning.md b/vms/platformvm/docs/validators_versioning.md index 5564d745a..61ad090d6 100644 --- a/vms/platformvm/docs/validators_versioning.md +++ b/vms/platformvm/docs/validators_versioning.md @@ -85,7 +85,7 @@ Note that: ## Validators diff usage in rebuilding validators state Now let's see how diffs are used to rebuild the validator set at a given height. The procedure varies slightly between Primary Network and Net validator, so we'll describe them separately. -We assume that the reader knows that, as of the Cortina fork, every Net validator must also be a Primary Network validator. +We assume that the reader knows that every Net validator must also be a Primary Network validator. ### Primary network validator set rebuild @@ -103,7 +103,7 @@ If the P-Chain's current height is `T` and we want to retrieve the Primary Netwo ### Net validator set rebuild -Let's see first the reason why Net validators needs to have handled differently. As of `Cortina` fork, we allow `BLS Public Key` registration only for Primary network validators. A given `NodeID` may be both a Primary Network validator and a Net validator, but it'll register its `BLS Public Key` only when it registers as Primary Network validator. Despite this, we want to provide a validator `BLS Public Key` when `validators.GetValidatorOutput` is called. So we need to fetch it from the Primary Network validator set. +Let's see first the reason why Net validators needs to be handled differently. We allow `BLS Public Key` registration only for Primary network validators. A given `NodeID` may be both a Primary Network validator and a Net validator, but it'll register its `BLS Public Key` only when it registers as Primary Network validator. Despite this, we want to provide a validator `BLS Public Key` when `validators.GetValidatorOutput` is called. So we need to fetch it from the Primary Network validator set. Say P-chain current height is `T` and we want to retrieve Primary network validators at height `H < T`. We proceed as follows: diff --git a/vms/platformvm/txs/executor/create_chain_test.go b/vms/platformvm/txs/executor/create_chain_test.go index fb6cfa84f..6bdecb942 100644 --- a/vms/platformvm/txs/executor/create_chain_test.go +++ b/vms/platformvm/txs/executor/create_chain_test.go @@ -18,9 +18,9 @@ import ( ) func TestCreateNetworkTxAP3FeeChange(t *testing.T) { - // Test the fee change at Apricot Phase 3 for CreateNetworkTx - // Pre-AP3: CreateNetworkTxFee = 0 - // Post-AP3: CreateNetworkTxFee = CreateNetworkTxFee from config (100 * defaultTxFee) + // Test the fee change gated by upgrade.Config.ApricotPhase3Time + // for CreateNetworkTx (Lux ships with all upgrade gates active at + // chain birth; this test verifies the legacy gate is wired up). tests := []struct { name string preAP3 bool diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index 3524c746c..fa832c421 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -160,13 +160,12 @@ func (*proposalTxExecutor) OperationTx(*txs.OperationTx) error { } func (e *proposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { - // AddValidatorTx is a proposal transaction until the Banff fork - // activation. Following the activation, AddValidatorTxs must be issued into - // StandardBlocks. + // 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) >= Banff fork time (%s)", + "%w: timestamp (%s) >= upgrade.Config.BanffTime (%s)", ErrProposedAddStakerTxAfterBanff, currentTimestamp, e.backend.Config.UpgradeConfig.BanffTime, @@ -210,13 +209,13 @@ func (e *proposalTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { } func (e *proposalTxExecutor) AddChainValidatorTx(tx *txs.AddChainValidatorTx) error { - // AddChainValidatorTx is a proposal transaction until the Banff fork + // 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) >= Banff fork time (%s)", + "%w: timestamp (%s) >= upgrade.Config.BanffTime (%s)", ErrProposedAddStakerTxAfterBanff, currentTimestamp, e.backend.Config.UpgradeConfig.BanffTime, @@ -259,13 +258,13 @@ func (e *proposalTxExecutor) AddChainValidatorTx(tx *txs.AddChainValidatorTx) er } func (e *proposalTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { - // AddDelegatorTx is a proposal transaction until the Banff fork + // 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) >= Banff fork time (%s)", + "%w: timestamp (%s) >= upgrade.Config.BanffTime (%s)", ErrProposedAddStakerTxAfterBanff, currentTimestamp, e.backend.Config.UpgradeConfig.BanffTime, @@ -318,7 +317,7 @@ func (e *proposalTxExecutor) AdvanceTimeTx(tx *txs.AdvanceTimeTx) error { newChainTime := tx.Timestamp() if e.backend.Config.UpgradeConfig.IsBanffActivated(newChainTime) { return fmt.Errorf( - "%w: proposed timestamp (%s) >= Banff fork time (%s)", + "%w: proposed timestamp (%s) >= upgrade.Config.BanffTime (%s)", ErrAdvanceTimeTxIssuedAfterBanff, newChainTime, e.backend.Config.UpgradeConfig.BanffTime, diff --git a/vms/platformvm/txs/executor/staker_tx_verification_test.go b/vms/platformvm/txs/executor/staker_tx_verification_test.go index 154749584..678732d7b 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification_test.go +++ b/vms/platformvm/txs/executor/staker_tx_verification_test.go @@ -144,7 +144,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { }, stateF: func(ctrl *gomock.Controller) state.Chain { mockState := state.NewMockChain(ctrl) - mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after Durango fork activation since now.After(activeForkTime) + mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after DurangoTime since now.After(activeForkTime) return mockState }, sTxF: func() *txs.Tx { @@ -505,7 +505,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { }, stateF: func(ctrl *gomock.Controller) state.Chain { mockState := state.NewMockChain(ctrl) - mockState.EXPECT().GetTimestamp().Return(now).Times(3) // chain time is after Durango fork activation since now.After(activeForkTime) + mockState.EXPECT().GetTimestamp().Return(now).Times(3) // chain time is after DurangoTime since now.After(activeForkTime) mockState.EXPECT().GetNetTransformation(netID).Return(&transformTx, nil) mockState.EXPECT().GetCurrentValidator(netID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) mockState.EXPECT().GetPendingValidator(netID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) diff --git a/vms/platformvm/validators/manager_test.go b/vms/platformvm/validators/manager_test.go index 551f0fd3f..13fe9381b 100644 --- a/vms/platformvm/validators/manager_test.go +++ b/vms/platformvm/validators/manager_test.go @@ -67,7 +67,7 @@ func TestGetValidatorSet_AfterEtna(t *testing.T) { } ) - // Add a chain staker during the Etna upgrade + // Add a chain staker at upgradeTime (post-EtnaTime gate) { blk, err := block.NewBanffStandardBlock(upgradeTime, s.GetLastAccepted(), 1, nil) require.NoError(err) diff --git a/vms/platformvm/warp/README.md b/vms/platformvm/warp/README.md index e48c6e2e7..b6cbd782f 100644 --- a/vms/platformvm/warp/README.md +++ b/vms/platformvm/warp/README.md @@ -2,7 +2,7 @@ Lux Interchain Messaging (ICM) provides a primitive for cross-chain communication on the Lux Network. -The Lux P-Chain provides an index of every network's validator set on the Lux Network, including the BLS public key of each validator (as of the [Banff Upgrade](https://github.com/luxfi/node/releases/v1.9.0)). ICM utilizes the weighted validator sets stored on the P-Chain to build a cross-chain communication protocol between any two networks on Lux. +The Lux P-Chain provides an index of every network's validator set on the Lux Network, including the BLS public key of each validator. ICM utilizes the weighted validator sets stored on the P-Chain to build a cross-chain communication protocol between any two networks on Lux. Any Virtual Machine (VM) on Lux can integrate Lux Interchain Messaging to send and receive messages cross-chain. diff --git a/vms/platformvm/warp/validator.go b/vms/platformvm/warp/validator.go index e6206369a..f784d4cfc 100644 --- a/vms/platformvm/warp/validator.go +++ b/vms/platformvm/warp/validator.go @@ -254,7 +254,8 @@ type cacheMetrics struct { misses metric.Counter } -// NewCachedValidatorState creates a new cached validator state with Granite upgrade awareness +// NewCachedValidatorState creates a new cached validator state aware of the +// LP-181 epoch upgrade (gated by upgrade.Config.GraniteTime). func NewCachedValidatorState( state ValidatorState, upgradeConfig *upgrade.Config, @@ -292,14 +293,15 @@ func NewCachedValidatorState( }, nil } -// GetValidatorSet implements ValidatorState with caching for post-Granite queries +// GetValidatorSet implements ValidatorState with caching gated by the +// LP-181 epoch-upgrade activation (upgrade.Config.GraniteTime). func (c *CachedValidatorState) GetValidatorSet( ctx context.Context, height uint64, chainID ids.ID, ) (map[ids.NodeID]*ValidatorData, error) { - // Check if Granite is activated - we only cache post-Granite - // Use current time as approximation since we don't have block timestamp + // 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 { @@ -309,13 +311,13 @@ func (c *CachedValidatorState) GetValidatorSet( c.metrics.misses.Inc() } - // Cache miss or pre-Granite - fetch from underlying state + // 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 Granite is active + // 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) diff --git a/vms/proposervm/block_test.go b/vms/proposervm/block_test.go index 715e07005..9cfcaa131 100644 --- a/vms/proposervm/block_test.go +++ b/vms/proposervm/block_test.go @@ -305,8 +305,9 @@ func TestPreEtnaContextPChainHeight(t *testing.T) { require.NoError(err) } -// Confirm that VM rejects blocks with non-zero epoch prior to granite upgrade activation -func TestPreGraniteBlock_NonZeroEpoch(t *testing.T) { +// Confirm that VM rejects blocks with non-zero epoch prior to the LP-181 +// epoch-upgrade activation (upgrade.Config.GraniteTime). +func TestPreEpochUpgradeBlock_NonZeroEpoch(t *testing.T) { require := require.New(t) var ( diff --git a/vms/proposervm/pre_fork_block.go b/vms/proposervm/pre_fork_block.go index 923ab9283..926f5e3a5 100644 --- a/vms/proposervm/pre_fork_block.go +++ b/vms/proposervm/pre_fork_block.go @@ -182,7 +182,8 @@ func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkB return errTimeNotMonotonic } - // Validate epoch for Granite upgrade (LP-181) + // Validate epoch (LP-181 epoching, gated by upgrade.Config.GraniteTime + // for upstream-codec compatibility). // 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() @@ -282,7 +283,8 @@ func (b *preForkBlock) buildChild(ctx context.Context) (Block, error) { innerBlock = engineBlock } - // Calculate the epoch for the child block based on Granite activation + // Calculate the epoch for the child block (LP-181, gated by + // upgrade.Config.GraniteTime for upstream-codec compatibility). 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. diff --git a/vms/proposervm/proposer/windower.go b/vms/proposervm/proposer/windower.go index 56a68429a..163f75ed3 100644 --- a/vms/proposervm/proposer/windower.go +++ b/vms/proposervm/proposer/windower.go @@ -63,9 +63,10 @@ type Windower interface { maxWindows int, ) (time.Duration, error) - // In the Post-Durango windowing scheme, every validator active at + // In the current per-slot windowing scheme, every validator active at // [pChainHeight] gets specific slots it can propose in (instead of being - // able to propose from a given time on as it happens Pre-Durango). + // able to propose from a given time on as in the legacy time-window + // scheme). // [ExpectedProposer] calculates which nodeID is scheduled to propose a // block of height [blockHeight] at [slot]. // If no validators are currently available, [ErrAnyoneCanPropose] is @@ -77,9 +78,10 @@ type Windower interface { slot uint64, ) (ids.NodeID, error) - // In the Post-Durango windowing scheme, every validator active at + // In the current per-slot windowing scheme, every validator active at // [pChainHeight] gets specific slots it can propose in (instead of being - // able to propose from a given time on as it happens Pre-Durango). + // able to propose from a given time on as in the legacy time-window + // scheme). // [MinDelayForProposer] specifies how long [nodeID] needs to wait for its // slot to start. Delay is specified as starting from slot zero start. // (which is parent timestamp). For efficiency reasons, we cap the slot diff --git a/vms/xvm/vm_test_helpers_test.go b/vms/xvm/vm_test_helpers_test.go index 17407c758..cbbbf8388 100644 --- a/vms/xvm/vm_test_helpers_test.go +++ b/vms/xvm/vm_test_helpers_test.go @@ -34,7 +34,8 @@ import ( // Test keys for use in tests var keys = secp256k1.TestKeys() -// durango is a shorthand for the Durango fork config +// durango is a shorthand for the upgradetest.Durango fork config (kept for +// upstream-codec compatibility; all gates are active on Lux at chain birth). var durango = upgradetest.GetConfig(upgradetest.Durango) // Test constants