mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
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.
334 lines
9.3 KiB
Go
334 lines
9.3 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package proposervm
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/luxfi/log"
|
|
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/node/vms/proposervm/block"
|
|
"github.com/luxfi/node/vms/proposervm/lp181"
|
|
"github.com/luxfi/runtime"
|
|
chain "github.com/luxfi/vm/chain"
|
|
)
|
|
|
|
var (
|
|
_ Block = (*preForkBlock)(nil)
|
|
|
|
errChildOfPreForkBlockHasProposer = errors.New("child of pre-fork block has proposer")
|
|
)
|
|
|
|
type preForkBlock struct {
|
|
chain.Block
|
|
vm *VM
|
|
}
|
|
|
|
// EpochBit returns the epoch bit for FPC
|
|
func (b *preForkBlock) EpochBit() bool {
|
|
// Forward to inner block if it supports it
|
|
if innerBlk, ok := b.Block.(interface{ EpochBit() bool }); ok {
|
|
return innerBlk.EpochBit()
|
|
}
|
|
return false
|
|
}
|
|
|
|
// FPCVotes returns embedded fast-path vote references
|
|
func (b *preForkBlock) FPCVotes() [][]byte {
|
|
// Forward to inner block if it supports it
|
|
if innerBlk, ok := b.Block.(interface{ FPCVotes() [][]byte }); ok {
|
|
return innerBlk.FPCVotes()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Timestamp returns the timestamp of the inner block
|
|
func (b *preForkBlock) Timestamp() time.Time {
|
|
// Forward to inner block if it supports it
|
|
if innerBlk, ok := b.Block.(interface{ Timestamp() time.Time }); ok {
|
|
return innerBlk.Timestamp()
|
|
}
|
|
// Fallback to current time
|
|
return b.vm.Time()
|
|
}
|
|
|
|
func (b *preForkBlock) Accept(ctx context.Context) error {
|
|
if err := b.acceptOuterBlk(); err != nil {
|
|
return err
|
|
}
|
|
return b.acceptInnerBlk(ctx)
|
|
}
|
|
|
|
func (*preForkBlock) acceptOuterBlk() error {
|
|
return nil
|
|
}
|
|
|
|
func (b *preForkBlock) acceptInnerBlk(ctx context.Context) error {
|
|
return b.Block.Accept(ctx)
|
|
}
|
|
|
|
func (b *preForkBlock) Verify(ctx context.Context) error {
|
|
parent, err := b.vm.getPreForkBlock(ctx, b.Block.Parent())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return parent.verifyPreForkChild(ctx, b)
|
|
}
|
|
|
|
func (b *preForkBlock) Options(ctx context.Context) ([2]chain.Block, error) {
|
|
oracleBlk, ok := b.Block.(OracleBlock)
|
|
if !ok {
|
|
return [2]chain.Block{}, errNotOracle
|
|
}
|
|
|
|
options, err := oracleBlk.Options(ctx)
|
|
if err != nil {
|
|
return [2]chain.Block{}, err
|
|
}
|
|
// A pre-fork block's child options are always pre-fork blocks
|
|
return [2]chain.Block{
|
|
&preForkBlock{
|
|
Block: options[0],
|
|
vm: b.vm,
|
|
},
|
|
&preForkBlock{
|
|
Block: options[1],
|
|
vm: b.vm,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (b *preForkBlock) getInnerBlk() chain.Block {
|
|
return b.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()),
|
|
)
|
|
}
|
|
|
|
return child.Block.Verify(ctx)
|
|
}
|
|
|
|
// This method only returns nil once (during the transition)
|
|
func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkBlock) error {
|
|
// FIX 4: Oracle parent validation - check if parent is oracle
|
|
parentIsOracle := verifyIsOracleBlock(ctx, b.Block) == nil
|
|
if parentIsOracle && child.SignedBlock.Proposer() != ids.EmptyNodeID {
|
|
return errChildOfPreForkBlockHasProposer
|
|
}
|
|
|
|
if err := verifyIsNotOracleBlock(ctx, b.Block); err != nil {
|
|
return err
|
|
}
|
|
|
|
childID := child.ID()
|
|
childPChainHeight := child.PChainHeight()
|
|
currentPChainHeight, err := b.vm.validatorState.GetCurrentHeight(ctx)
|
|
if err != nil {
|
|
b.vm.logger.Error("block verification failed",
|
|
log.String("reason", "failed to get current P-Chain height"),
|
|
log.Stringer("blkID", childID),
|
|
log.Err(err),
|
|
)
|
|
return err
|
|
}
|
|
if childPChainHeight > currentPChainHeight {
|
|
return fmt.Errorf("%w: %d > %d",
|
|
errPChainHeightNotReached,
|
|
childPChainHeight,
|
|
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()
|
|
if innerParentID != expectedInnerParentID {
|
|
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)
|
|
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()
|
|
if childTimestamp.Before(parentTimestamp) {
|
|
return errTimeNotMonotonic
|
|
}
|
|
|
|
// 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()
|
|
// 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.
|
|
parentPChainHeight := uint64(0)
|
|
expectedEpoch := lp181.NewEpoch(b.vm.Upgrades, parentPChainHeight, parentEpoch, parentTimestamp, childTimestamp)
|
|
if childEpoch != expectedEpoch {
|
|
return fmt.Errorf("%w: epoch %v != expected %v", errEpochMismatch, childEpoch, expectedEpoch)
|
|
}
|
|
|
|
// Child timestamp can't be too far in the future
|
|
maxTimestamp := b.vm.Time().Add(maxSkew)
|
|
if childTimestamp.After(maxTimestamp) {
|
|
return errTimeTooAdvanced
|
|
}
|
|
|
|
// Verify the lack of signature on the node
|
|
if child.SignedBlock.Proposer() != ids.EmptyNodeID {
|
|
return errChildOfPreForkBlockHasProposer
|
|
}
|
|
|
|
// Verify the inner block and track it as verified
|
|
return b.vm.verifyAndRecordInnerBlk(ctx, nil, child)
|
|
}
|
|
|
|
func (*preForkBlock) verifyPostForkOption(context.Context, *postForkOption) error {
|
|
return errUnexpectedBlockType
|
|
}
|
|
|
|
func (b *preForkBlock) buildChild(ctx context.Context) (Block, error) {
|
|
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)
|
|
if err != nil {
|
|
b.vm.logger.Error("unexpected build block failure",
|
|
log.String("reason", "failed to calculate optimal P-chain height"),
|
|
log.Stringer("parentID", parentID),
|
|
log.Err(err),
|
|
)
|
|
return nil, err
|
|
}
|
|
|
|
var innerBlock chain.Block
|
|
if b.vm.blockBuilderVM != nil {
|
|
// VM supports BuildBlockWithRuntime
|
|
builtBlock, err := b.vm.blockBuilderVM.BuildBlockWithRuntime(ctx, &runtime.Runtime{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
innerBlock = builtBlock
|
|
} else {
|
|
// VM doesn't support BuildBlockWithRuntime, use BuildBlock
|
|
engineBlock, err := b.vm.ChainVM.BuildBlock(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
innerBlock = engineBlock
|
|
}
|
|
|
|
// 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.
|
|
parentPChainHeight := uint64(0)
|
|
childEpoch := lp181.NewEpoch(b.vm.Upgrades, parentPChainHeight, parentEpoch, parentTimestamp, newTimestamp)
|
|
|
|
statelessBlock, err := block.BuildUnsigned(
|
|
parentID,
|
|
newTimestamp,
|
|
pChainHeight,
|
|
childEpoch,
|
|
innerBlock.Bytes(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
blk := &postForkBlock{
|
|
SignedBlock: statelessBlock,
|
|
postForkCommonComponents: postForkCommonComponents{
|
|
vm: b.vm,
|
|
innerBlk: innerBlock,
|
|
},
|
|
}
|
|
|
|
b.vm.logger.Info("built block",
|
|
log.Stringer("blkID", blk.ID()),
|
|
log.Stringer("innerBlkID", innerBlock.ID()),
|
|
log.Uint64("height", blk.Height()),
|
|
log.Uint64("pChainHeight", pChainHeight),
|
|
log.Time("parentTimestamp", parentTimestamp),
|
|
log.Time("blockTimestamp", newTimestamp))
|
|
return blk, nil
|
|
}
|
|
|
|
func (*preForkBlock) pChainHeight(context.Context) (uint64, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
func (*preForkBlock) pChainEpoch(context.Context) (chain.Epoch, error) {
|
|
return chain.Epoch{}, nil
|
|
}
|
|
|
|
func (b *preForkBlock) selectChildPChainHeight(ctx context.Context) (uint64, error) {
|
|
return b.vm.selectChildPChainHeight(ctx, 0)
|
|
}
|