Files
node/vms/proposervm/pre_fork_block.go
T
Hanzo AI a02012ca56 purge Avalanche-lineage keywords (one pure modern version)
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.
2026-05-18 21:14:35 -07:00

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)
}