mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
The AlwaysOn DEX settlement precompile (0x9999) had its EXTCODESIZE marker baked into EVERY chain's genesis state unconditionally (parent==nil in ApplyPrecompileActivations), mutating the genesis state root. This changed the genesis hash of every chain genesised before the DEX activation, breaking RLP re-import of the real archived Lux mainnet (0x3f4fa2a0), Lux testnet (0x1c5fe377) and Zoo mainnet (0x7c548af4) — all re-genesised Nov 2024, before the Dec-2025 activation. Fix: an AlwaysOn system precompile carries a protocol ActivationTime (Dec 2025 for 0x9999, set by the registry bridge — a fork-like constant, not per-net config). Its genesis marker is installed ONLY IF ActivationTime <= the genesis timestamp; otherwise it activates later, on the block transition that crosses ActivationTime during history replay. Dispatch (GetExtrasRules) is gated on the same timestamp so a codeless address never dispatches. A chain born before activation reproduces its ORIGINAL genesis byte-for-byte; a fresh chain born at/after activation still gets 0x9999 at genesis. Verified against the real RLP block-0 of all three chains (byte-identical reproduction) plus a fresh post-activation chain. Corrects two pre-existing tests that had hardcoded the buggy with-0x9999 hashes as "canonical". Co-authored-by: Hanzo Dev <[email protected]>
170 lines
8.7 KiB
Go
170 lines
8.7 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package core
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/luxfi/evm/core/extstate"
|
|
"github.com/luxfi/evm/core/state"
|
|
"github.com/luxfi/evm/params"
|
|
"github.com/luxfi/evm/precompile/contract"
|
|
"github.com/luxfi/evm/precompile/modules"
|
|
"github.com/luxfi/evm/stateupgrade"
|
|
"github.com/luxfi/geth/core/tracing"
|
|
log "github.com/luxfi/log"
|
|
)
|
|
|
|
// ApplyPrecompileActivations checks if any of the precompiles specified by the chain config are enabled or disabled by the block
|
|
// transition from `parentTimestamp` to the timestamp set in `blockContext`. If this is the case, it calls [modules.Module]'s Configure
|
|
// to apply the necessary state transitions for the upgrade.
|
|
//
|
|
// At genesis (parentTimestamp == nil), this activates any precompiles configured at timestamp 0.
|
|
// The precompile configs are part of the chain config, so genesis state remains deterministic.
|
|
//
|
|
// In block processing and building, [ApplyUpgrades] is called instead which also applies state upgrades.
|
|
func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error {
|
|
blockTimestamp := blockContext.Timestamp()
|
|
|
|
// AlwaysOn system precompiles (the DEX settlement money path 0x9999) are activated by
|
|
// the Lux protocol on every chain with NO per-network config, at their protocol
|
|
// ActivationTime (a fork-like timestamp constant, set by the registry bridge), NOT
|
|
// unconditionally at genesis. Their EXTCODESIZE marker (nonce=1 + a non-empty code
|
|
// byte) is installed exactly ONCE, on the block transition that crosses ActivationTime
|
|
// — which is genesis IFF ActivationTime <= the genesis timestamp. A chain whose genesis
|
|
// predates ActivationTime does NOT get the marker in its genesis state (its original
|
|
// pre-precompile genesis hash is preserved byte-identically); the marker is instead
|
|
// installed later, during history replay, at the first block whose timestamp reaches
|
|
// ActivationTime. This mirrors the config-driven activation crossing logic below.
|
|
applyAlwaysOnActivations(parentTimestamp, blockTimestamp, statedb)
|
|
|
|
// Note: [modules.RegisteredModules] returns precompiles sorted by module addresses.
|
|
// This ensures:
|
|
// - the order we call [modules.Module]'s Configure for each precompile is consistent
|
|
// - even if precompiles read/write state other than their own they will observe
|
|
// an identical global state in a deterministic order when they are configured.
|
|
extra := params.GetExtra(c)
|
|
for _, module := range modules.RegisteredModules() {
|
|
for _, activatingConfig := range extra.GetActivatingPrecompileConfigs(module.Address, parentTimestamp, blockTimestamp, extra.PrecompileUpgrades) {
|
|
// If this transition activates the upgrade, configure the stateful precompile.
|
|
// (or deconfigure it if it is being disabled.)
|
|
if activatingConfig.IsDisabled() {
|
|
log.Info("Disabling precompile", "name", module.ConfigKey)
|
|
statedb.SelfDestruct(module.Address)
|
|
// Calling [state.StateDB]'s Finalise here effectively commits the SelfDestruct call and wipes the contract state.
|
|
// This enables re-configuration of the same contract state in the same block.
|
|
// Without an immediate Finalise call after the SelfDestruct, a reconfigured precompiled state can be wiped out
|
|
// since SelfDestruct will be committed after the reconfiguration.
|
|
statedb.Finalise(true)
|
|
continue
|
|
}
|
|
var printIntf interface{}
|
|
marshalled, err := json.Marshal(activatingConfig)
|
|
if err == nil {
|
|
printIntf = string(marshalled)
|
|
} else {
|
|
printIntf = activatingConfig
|
|
}
|
|
|
|
log.Info("Activating new precompile", "name", module.ConfigKey, "config", printIntf)
|
|
// Set the nonce of the precompile's address (as is done when a contract is created) to ensure
|
|
// that it is marked as non-empty and will not be cleaned up when the statedb is finalized.
|
|
statedb.SetNonce(module.Address, 1, tracing.NonceChangeUnspecified)
|
|
// Set the code of the precompile's address to a non-zero length byte slice to ensure that the precompile
|
|
// can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure
|
|
// that it does not attempt to invoke a non-existent contract.
|
|
_ = statedb.SetCode(module.Address, []byte{0x1}, tracing.CodeChangeUnspecified)
|
|
extstatedb := extstate.New(statedb)
|
|
adapter := extstate.NewPrecompileAdapter(extstatedb)
|
|
if err := module.Configure(params.GetExtra(c), activatingConfig, adapter, blockContext); err != nil {
|
|
return fmt.Errorf("could not configure precompile, name: %s, reason: %w", module.ConfigKey, err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// applyAlwaysOnActivations installs the EXTCODESIZE marker (nonce=1 + a non-empty code
|
|
// byte) for each AlwaysOn system precompile whose protocol ActivationTime is crossed by
|
|
// this block transition, so EXTCODESIZE>0 / eth_getCode!=0x / Solidity's contract-
|
|
// existence guard pass from the activation block forward.
|
|
//
|
|
// "Crossed by this transition" is defined exactly like the config-driven precompile
|
|
// activation below (GetActivatingPrecompileConfigs):
|
|
// - genesis (parentTimestamp == nil): crossed iff ActivationTime <= blockTimestamp,
|
|
// so the marker lands in the committed genesis state root ONLY for a chain genesised
|
|
// at/after ActivationTime. A pre-activation chain's genesis root is untouched.
|
|
// - later block (parentTimestamp != nil): crossed iff
|
|
// *parentTimestamp < ActivationTime <= blockTimestamp — the single transition where
|
|
// the precompile turns on during history replay.
|
|
// The marker is therefore written exactly once, deterministically, on every node. An
|
|
// AlwaysOn module's Configurator is NOT invoked (a system precompile has no activating
|
|
// config; all params resolve at runtime from the consensus context) — only the marker.
|
|
func applyAlwaysOnActivations(parentTimestamp *uint64, blockTimestamp uint64, statedb *state.StateDB) {
|
|
for _, module := range modules.AlwaysOnModules() {
|
|
var crossed bool
|
|
if parentTimestamp == nil {
|
|
crossed = module.ActivationTime <= blockTimestamp
|
|
} else {
|
|
crossed = *parentTimestamp < module.ActivationTime && module.ActivationTime <= blockTimestamp
|
|
}
|
|
if !crossed {
|
|
continue
|
|
}
|
|
statedb.SetNonce(module.Address, 1, tracing.NonceChangeUnspecified)
|
|
_ = statedb.SetCode(module.Address, []byte{0x1}, tracing.CodeChangeUnspecified)
|
|
}
|
|
}
|
|
|
|
// applyStateUpgrades checks if any of the state upgrades specified by the chain config are activated by the block
|
|
// transition from [parentTimestamp] to the timestamp set in [header]. If this is the case, it calls [Configure]
|
|
// to apply the necessary state transitions for the upgrade.
|
|
func applyStateUpgrades(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error {
|
|
// Apply state upgrades
|
|
configExtra := params.GetExtra(c)
|
|
for _, upgrade := range configExtra.GetActivatingStateUpgrades(parentTimestamp, blockContext.Timestamp(), configExtra.StateUpgrades) {
|
|
log.Info("Applying state upgrade", "blockNumber", blockContext.Number(), "upgrade", upgrade)
|
|
adapter := state.NewStateUpgradeAdapter(statedb)
|
|
if err := stateupgrade.Configure(&upgrade, c, adapter, blockContext); err != nil {
|
|
return fmt.Errorf("could not configure state upgrade: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ApplyUpgrades checks if any of the precompile or state upgrades specified by the chain config are activated by the block
|
|
// transition from [parentTimestamp] to the timestamp set in [header]. If this is the case, it calls [Configure]
|
|
// to apply the necessary state transitions for the upgrade.
|
|
// This function is called:
|
|
// - in block processing to update the state when processing a block.
|
|
// - in the miner to apply the state upgrades when producing a block.
|
|
func ApplyUpgrades(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error {
|
|
if err := ApplyPrecompileActivations(c, parentTimestamp, blockContext, statedb); err != nil {
|
|
return err
|
|
}
|
|
return applyStateUpgrades(c, parentTimestamp, blockContext, statedb)
|
|
}
|
|
|
|
// BlockContext implements [contract.ConfigurationBlockContext].
|
|
type BlockContext struct {
|
|
number *big.Int
|
|
timestamp uint64
|
|
}
|
|
|
|
// NewBlockContext creates a [BlockContext] using the block number
|
|
// and block timestamp provided. This function is usually necessary to convert
|
|
// a `*types.Block` to be passed as a [contract.ConfigurationBlockContext]
|
|
// to [ApplyUpgrades].
|
|
func NewBlockContext(number *big.Int, timestamp uint64) *BlockContext {
|
|
return &BlockContext{
|
|
number: number,
|
|
timestamp: timestamp,
|
|
}
|
|
}
|
|
|
|
func (bc *BlockContext) Number() *big.Int { return bc.number }
|
|
func (bc *BlockContext) Timestamp() uint64 { return bc.timestamp }
|