mirror of
https://github.com/luxfi/evmgpu.git
synced 2026-10-30 16:57:11 +00:00
Wire-level renames in EVMGPU (parallel of luxfi/evm):
- extras.NetworkUpgrades.EtnaTimestamp → QuasarTimestamp
json tag etnaTimestamp → quasarTimestamp
- extras.NetworkUpgrades.IsEtna() → IsQuasar()
- extras.LuxRules.IsEtna → IsQuasar
- params.RulesExtra.IsEtna → IsQuasar
- extras.TestEtnaChainConfig → TestQuasarChainConfig
- params.TestEtnaChainConfig → TestQuasarChainConfig
- upgradetest.Etna fork enum → Quasar (luxfi/upgrade dep)
Bumps luxfi/upgrade@main + luxfi/constants@main.
Excludes user WIP on core/parallel/gpu_*.go (pkg-config + $HOME path
edits — separate change set).
No backcompat aliases — forwards-only.
459 lines
16 KiB
Plaintext
459 lines
16 KiB
Plaintext
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package evm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/holiman/uint256"
|
|
consensusInterfaces "github.com/luxfi/consensus/core/interfaces"
|
|
consensuscore "github.com/luxfi/consensus/core"
|
|
gethcrypto "github.com/luxfi/geth/crypto"
|
|
"github.com/luxfi/crypto"
|
|
"github.com/luxfi/evm/core"
|
|
"github.com/luxfi/evm/params"
|
|
"github.com/luxfi/evm/params/extras"
|
|
"github.com/luxfi/evm/plugin/evm/vmerrors"
|
|
"github.com/luxfi/evm/precompile/contracts/txallowlist"
|
|
"github.com/luxfi/evm/utils"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/common/hexutil"
|
|
"github.com/luxfi/geth/common/math"
|
|
"github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/geth/rlp"
|
|
"github.com/luxfi/geth/trie"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/consensus/utils/set"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var DefaultQuasarTime = uint64(0) // Use genesis time as default
|
|
|
|
// getEthBlockFromConsensusBlock safely extracts the Ethereum block from a consensus block interface
|
|
func getEthBlockFromConsensusBlock(t *testing.T, blk consensusInterfaces.Block) *types.Block {
|
|
// Try direct cast to *Block first
|
|
if internalBlock, ok := blk.(*Block); ok {
|
|
return internalBlock.ethBlock
|
|
}
|
|
|
|
// If that fails, try to access through interface methods
|
|
if blockID := blk.ID(); blockID != ids.Empty {
|
|
// Extract block data through the block interface
|
|
if data := blk.Bytes(); len(data) > 0 {
|
|
// Try to decode as RLP-encoded block
|
|
var block types.Block
|
|
if err := rlp.DecodeBytes(data, &block); err == nil {
|
|
return &block
|
|
}
|
|
}
|
|
}
|
|
|
|
// Last resort: create a minimal block for testing
|
|
t.Logf("Warning: Creating minimal block for test - original type: %T", blk)
|
|
header := &types.Header{
|
|
Number: big.NewInt(int64(blk.Height())),
|
|
Time: uint64(time.Now().Unix()),
|
|
GasLimit: 8000000,
|
|
GasUsed: 0,
|
|
Difficulty: common.Big1,
|
|
}
|
|
body := &types.Body{}
|
|
return types.NewBlock(header, body, nil, trie.NewStackTrie(nil))
|
|
}
|
|
|
|
func TestVMUpgradeBytesPrecompile(t *testing.T) {
|
|
// SKIP: This test requires VM restart functionality, but the current implementation
|
|
// closes the chainDb during shutdown, making the database unusable for restart.
|
|
// This requires architectural changes to properly handle database lifecycle in tests.
|
|
t.Skip("VM restart tests need architectural fix for database lifecycle")
|
|
|
|
// Make a TxAllowListConfig upgrade at genesis and convert it to JSON to apply as upgradeBytes.
|
|
enableAllowListTimestamp := time.Unix(0, 0) // enable at genesis
|
|
upgradeConfig := &extras.UpgradeConfig{
|
|
PrecompileUpgrades: []extras.PrecompileUpgrade{
|
|
{
|
|
Config: txallowlist.NewConfig(utils.TimeToNewUint64(enableAllowListTimestamp), testEthAddrs[0:1], nil, nil),
|
|
},
|
|
},
|
|
}
|
|
upgradeBytesJSON, err := json.Marshal(upgradeConfig)
|
|
if err != nil {
|
|
t.Fatalf("could not marshal upgradeConfig to json: %s", err)
|
|
}
|
|
|
|
// initialize the VM with these upgrade bytes
|
|
tvm := newVM(t, testVMConfig{
|
|
genesisJSON: genesisJSONSubnetEVM,
|
|
upgradeJSON: string(upgradeBytesJSON),
|
|
})
|
|
|
|
tvm.vm.clock.Set(enableAllowListTimestamp)
|
|
|
|
// Submit a successful transaction
|
|
tx0 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil)
|
|
key0ECDSA, err := crypto.ToECDSA(testKeys[0].Bytes())
|
|
assert.NoError(t, err)
|
|
signedTx0, err := types.SignTx(tx0, types.NewEIP155Signer(tvm.vm.chainConfig.ChainID), key0ECDSA)
|
|
assert.NoError(t, err)
|
|
|
|
errs := tvm.vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0})
|
|
if err := errs[0]; err != nil {
|
|
t.Fatalf("Failed to add tx at index: %s", err)
|
|
}
|
|
|
|
// Submit a rejected transaction, should throw an error
|
|
tx1 := types.NewTransaction(uint64(0), testEthAddrs[1], big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil)
|
|
key1ECDSA, err := crypto.ToECDSA(testKeys[1].Bytes())
|
|
assert.NoError(t, err)
|
|
signedTx1, err := types.SignTx(tx1, types.NewEIP155Signer(tvm.vm.chainConfig.ChainID), key1ECDSA)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
errs = tvm.vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1})
|
|
if err := errs[0]; !errors.Is(err, vmerrors.ErrSenderAddressNotAllowListed) {
|
|
t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err)
|
|
}
|
|
|
|
// save the clock time before shutdown
|
|
clockTime := tvm.vm.clock.Time()
|
|
|
|
// shutdown the vm
|
|
if err := tvm.vm.Shutdown(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// prepare the new upgrade bytes to disable the TxAllowList
|
|
disableAllowListTimestamp := clockTime.Add(10 * time.Hour) // arbitrary choice
|
|
upgradeConfig.PrecompileUpgrades = append(
|
|
upgradeConfig.PrecompileUpgrades,
|
|
extras.PrecompileUpgrade{
|
|
Config: txallowlist.NewDisableConfig(utils.TimeToNewUint64(disableAllowListTimestamp)),
|
|
},
|
|
)
|
|
upgradeBytesJSON, err = json.Marshal(upgradeConfig)
|
|
if err != nil {
|
|
t.Fatalf("could not marshal upgradeConfig to json: %s", err)
|
|
}
|
|
|
|
// Create a fresh VM instance to restart (can't reuse tvm.vm after shutdown)
|
|
restartedVM := &VM{}
|
|
|
|
if err := restartedVM.Initialize(
|
|
context.Background(), tvm.vm.chainCtx, tvm.db, []byte(genesisJSONSubnetEVM), upgradeBytesJSON, []byte{}, nil, nil, tvm.appSender,
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err := restartedVM.Shutdown(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}()
|
|
// Set the VM's state to NormalOp to initialize the tx pool.
|
|
if err := restartedVM.SetState(context.Background(), uint32(consensuscore.VMBootstrapping)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := restartedVM.SetState(context.Background(), uint32(consensuscore.VMNormalOp)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1)
|
|
restartedVM.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan)
|
|
restartedVM.clock.Set(disableAllowListTimestamp)
|
|
|
|
// Make a block, previous rules still apply (TxAllowList is active)
|
|
// Submit a successful transaction
|
|
errs = restartedVM.txPool.AddRemotesSync([]*types.Transaction{signedTx0})
|
|
if err := errs[0]; err != nil {
|
|
t.Fatalf("Failed to add tx at index: %s", err)
|
|
}
|
|
|
|
// Submit a rejected transaction, should throw an error
|
|
errs = restartedVM.txPool.AddRemotesSync([]*types.Transaction{signedTx1})
|
|
if err := errs[0]; !errors.Is(err, vmerrors.ErrSenderAddressNotAllowListed) {
|
|
t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err)
|
|
}
|
|
|
|
blk := issueAndAccept(t, restartedVM)
|
|
|
|
// Verify that the constructed block only has the whitelisted tx
|
|
// Get internal block safely using proper type assertion
|
|
block := getEthBlockFromConsensusBlock(t, blk)
|
|
txs := block.Transactions()
|
|
if txs.Len() != 1 {
|
|
t.Fatalf("Expected number of txs to be %d, but found %d", 1, txs.Len())
|
|
}
|
|
assert.Equal(t, signedTx0.Hash(), txs[0].Hash())
|
|
|
|
// verify the issued block is after the network upgrade
|
|
assert.GreaterOrEqual(t, int64(block.Time()), disableAllowListTimestamp.Unix())
|
|
|
|
<-newTxPoolHeadChan // wait for new head in tx pool
|
|
|
|
// retry the rejected Tx, which should now succeed
|
|
errs = restartedVM.txPool.AddRemotesSync([]*types.Transaction{signedTx1})
|
|
if err := errs[0]; err != nil {
|
|
t.Fatalf("Failed to add tx at index: %s", err)
|
|
}
|
|
|
|
restartedVM.clock.Set(restartedVM.clock.Time().Add(2 * time.Second)) // add 2 seconds for gas fee to adjust
|
|
blk = issueAndAccept(t, restartedVM)
|
|
|
|
// Verify that the constructed block only has the previously rejected tx
|
|
// Get internal block safely using proper type assertion
|
|
block = getEthBlockFromConsensusBlock(t, blk)
|
|
txs = block.Transactions()
|
|
if txs.Len() != 1 {
|
|
t.Fatalf("Expected number of txs to be %d, but found %d", 1, txs.Len())
|
|
}
|
|
assert.Equal(t, signedTx1.Hash(), txs[0].Hash())
|
|
}
|
|
|
|
func TestNetworkUpgradesOverriden(t *testing.T) {
|
|
// Use genesisJSONSubnetEVM which has EVMTimestamp=0 (required by validation)
|
|
// Note: EVMTimestamp must always be 0, so we can't override it to non-zero
|
|
// This test now focuses on overriding DurangoTimestamp which IS allowed
|
|
genesisBytes := []byte(genesisJSONSubnetEVM)
|
|
|
|
// Only override DurangoTimestamp - EVMTimestamp must stay at 0
|
|
upgradeBytesJSON := `{
|
|
"networkUpgradeOverrides": {
|
|
"durangoTimestamp": 1607144402
|
|
}
|
|
}`
|
|
var err error
|
|
|
|
vm := &VM{}
|
|
ctx, dbManager, _, _ := setupGenesis(t, "Latest")
|
|
appSender := &TestSender{T: t}
|
|
appSender.CantSendAppGossip = true
|
|
appSender.SendAppGossipF = func(context.Context, set.Set[ids.NodeID], []byte) error { return nil }
|
|
err = vm.Initialize(
|
|
context.Background(),
|
|
ctx,
|
|
dbManager,
|
|
genesisBytes,
|
|
[]byte(upgradeBytesJSON),
|
|
nil,
|
|
nil, // toEngine parameter
|
|
[]interface{}{}, // fxs as []interface{}
|
|
appSender,
|
|
)
|
|
require.NoError(t, err, "error initializing GenesisVM")
|
|
|
|
require.NoError(t, vm.SetState(context.Background(), uint32(consensuscore.VMBootstrapping)))
|
|
require.NoError(t, vm.SetState(context.Background(), uint32(consensuscore.VMNormalOp)))
|
|
|
|
defer func() {
|
|
if err := vm.Shutdown(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}()
|
|
|
|
// verify upgrade overrides
|
|
// SubnetEVM is always active at genesis (timestamp 0) - this is required
|
|
require.True(t, vm.chainConfigExtra().IsSubnetEVM(0))
|
|
require.True(t, vm.chainConfigExtra().IsSubnetEVM(2))
|
|
// DurangoTimestamp was overridden to 1607144402
|
|
require.False(t, vm.chainConfigExtra().IsDurango(0))
|
|
require.False(t, vm.chainConfigExtra().IsDurango(1607144401)) // Just before activation
|
|
require.True(t, vm.chainConfigExtra().IsDurango(1607144402)) // At activation
|
|
}
|
|
|
|
func mustMarshal(t *testing.T, v interface{}) string {
|
|
b, err := json.Marshal(v)
|
|
require.NoError(t, err)
|
|
return string(b)
|
|
}
|
|
|
|
func TestVMStateUpgrade(t *testing.T) {
|
|
// modify genesis to add a key to the state
|
|
// Parse as JSON map to preserve network upgrade timestamps
|
|
var genesisMap map[string]interface{}
|
|
require.NoError(t, json.Unmarshal([]byte(genesisJSONSubnetEVM), &genesisMap))
|
|
|
|
// Modify the alloc section to add storage and code
|
|
allocMap := genesisMap["alloc"].(map[string]interface{})
|
|
testAddrKey := strings.ToLower(testEthAddrs[0].Hex()[2:]) // remove 0x prefix and lowercase
|
|
accountMap := allocMap[testAddrKey].(map[string]interface{})
|
|
|
|
storageKey := common.HexToHash("0x1234")
|
|
accountMap["storage"] = map[string]string{storageKey.Hex(): common.HexToHash("0x5555").Hex()}
|
|
accountMap["code"] = "0xabcd"
|
|
accountMap["nonce"] = "0x2" // set to a non-zero value to test that it is preserved
|
|
|
|
genesisBytes, err := json.Marshal(genesisMap)
|
|
require.NoError(t, err)
|
|
genesisStr := string(genesisBytes)
|
|
|
|
upgradedCodeStr := "0xdeadbeef" // this code will be applied during the upgrade
|
|
upgradedCode, err := hexutil.Decode(upgradedCodeStr)
|
|
// This modification will be applied to an existing account
|
|
genesisAccountUpgrade := &extras.StateUpgradeAccount{
|
|
BalanceChange: (*math.HexOrDecimal256)(big.NewInt(100)),
|
|
Storage: map[common.Hash]common.Hash{storageKey: {}},
|
|
Code: upgradedCode,
|
|
}
|
|
|
|
// This modification will be applied to a new account
|
|
newAccount := common.Address{42}
|
|
require.NoError(t, err)
|
|
newAccountUpgrade := &extras.StateUpgradeAccount{
|
|
BalanceChange: (*math.HexOrDecimal256)(big.NewInt(100)),
|
|
Storage: map[common.Hash]common.Hash{storageKey: common.HexToHash("0x6666")},
|
|
Code: upgradedCode,
|
|
}
|
|
|
|
upgradeTimestamp := time.Unix(0, 0).Add(10 * time.Hour) // 10 hours after genesis
|
|
upgradeBytesJSON := fmt.Sprintf(
|
|
`{
|
|
"stateUpgrades": [
|
|
{
|
|
"blockTimestamp": %d,
|
|
"accounts": {
|
|
"%s": %s,
|
|
"%s": %s
|
|
}
|
|
}
|
|
]
|
|
}`,
|
|
upgradeTimestamp.Unix(),
|
|
testEthAddrs[0].Hex(),
|
|
mustMarshal(t, genesisAccountUpgrade),
|
|
newAccount.Hex(),
|
|
mustMarshal(t, newAccountUpgrade),
|
|
)
|
|
require.Contains(t, upgradeBytesJSON, upgradedCodeStr)
|
|
|
|
// initialize the VM with these upgrade bytes
|
|
tvm := newVM(t, testVMConfig{
|
|
genesisJSON: genesisStr,
|
|
upgradeJSON: upgradeBytesJSON,
|
|
})
|
|
|
|
defer func() { require.NoError(t, tvm.vm.Shutdown(context.Background())) }()
|
|
|
|
// Verify the new account doesn't exist yet
|
|
genesisState, err := tvm.vm.blockChain.State()
|
|
require.NoError(t, err)
|
|
require.Equal(t, common.U2560, genesisState.GetBalance(newAccount))
|
|
|
|
// Advance the chain to the upgrade time
|
|
tvm.vm.clock.Set(upgradeTimestamp)
|
|
|
|
// Submit a successful (unrelated) transaction, so we can build a block
|
|
// in this tx, testEthAddrs[1] sends 1 wei to itself.
|
|
tx0 := types.NewTransaction(uint64(0), testEthAddrs[1], big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil)
|
|
key1ECDSA, err := crypto.ToECDSA(testKeys[1].Bytes())
|
|
assert.NoError(t, err)
|
|
signedTx0, err := types.SignTx(tx0, types.NewEIP155Signer(tvm.vm.chainConfig.ChainID), key1ECDSA)
|
|
require.NoError(t, err)
|
|
|
|
errs := tvm.vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0})
|
|
require.NoError(t, errs[0], "Failed to add tx")
|
|
|
|
blk := issueAndAccept(t, tvm.vm)
|
|
require.NotNil(t, blk)
|
|
require.EqualValues(t, 1, blk.Height())
|
|
|
|
// Verify the state upgrade was applied
|
|
state, err := tvm.vm.blockChain.State()
|
|
require.NoError(t, err)
|
|
|
|
// Existing account
|
|
// Original balance was 10 ETH (from toGenesisJSON), plus the 100 wei upgrade
|
|
originalBalance := new(big.Int).Mul(big.NewInt(params.Ether), big.NewInt(10))
|
|
expectedGenesisAccountBalance := uint256.MustFromBig(
|
|
new(big.Int).Add(
|
|
originalBalance,
|
|
(*big.Int)(genesisAccountUpgrade.BalanceChange),
|
|
),
|
|
)
|
|
require.Equal(t, state.GetBalance(testEthAddrs[0]), expectedGenesisAccountBalance)
|
|
require.Equal(t, state.GetState(testEthAddrs[0], storageKey), genesisAccountUpgrade.Storage[storageKey])
|
|
require.Equal(t, state.GetCode(testEthAddrs[0]), upgradedCode)
|
|
require.Equal(t, state.GetCodeHash(testEthAddrs[0]), gethcrypto.Keccak256Hash(upgradedCode))
|
|
require.Equal(t, state.GetNonce(testEthAddrs[0]), uint64(2)) // Nonce should be preserved since it was set to 2
|
|
|
|
// New account
|
|
expectedNewAccountBalance := uint256.MustFromBig((*big.Int)(newAccountUpgrade.BalanceChange))
|
|
require.Equal(t, state.GetBalance(newAccount), expectedNewAccountBalance)
|
|
require.Equal(t, state.GetCode(newAccount), upgradedCode)
|
|
require.Equal(t, state.GetCodeHash(newAccount), gethcrypto.Keccak256Hash(upgradedCode))
|
|
require.Equal(t, state.GetNonce(newAccount), uint64(1)) // Nonce should be set to 1 when code is set if nonce was 0
|
|
require.Equal(t, state.GetState(newAccount, storageKey), newAccountUpgrade.Storage[storageKey])
|
|
}
|
|
|
|
func TestVMEupgradeActivatesCancun(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
genesisJSON string
|
|
upgradeJSON string
|
|
check func(*testing.T, *VM) // function to check the VM state
|
|
}{
|
|
{
|
|
name: "Quasar activates Cancun",
|
|
genesisJSON: toGenesisJSON(forkToChainConfig["Latest"]),
|
|
check: func(t *testing.T, vm *VM) {
|
|
require.True(t, vm.chainConfig.IsCancun(common.Big0, DefaultQuasarTime))
|
|
},
|
|
},
|
|
{
|
|
name: "Later Quasar activates Cancun",
|
|
// Use "Quasar" fork (not "Latest") to avoid Fortuna/Granite fork conflicts
|
|
genesisJSON: toGenesisJSON(forkToChainConfig["Quasar"]),
|
|
upgradeJSON: func() string {
|
|
upgrade := &extras.UpgradeConfig{
|
|
NetworkUpgradeOverrides: &extras.NetworkUpgrades{
|
|
QuasarTimestamp: utils.NewUint64(DefaultQuasarTime + 2),
|
|
},
|
|
}
|
|
b, err := json.Marshal(upgrade)
|
|
require.NoError(t, err)
|
|
return string(b)
|
|
}(),
|
|
check: func(t *testing.T, vm *VM) {
|
|
require.False(t, vm.chainConfig.IsCancun(common.Big0, DefaultQuasarTime))
|
|
require.True(t, vm.chainConfig.IsCancun(common.Big0, DefaultQuasarTime+2))
|
|
},
|
|
},
|
|
{
|
|
name: "Changed Quasar changes Cancun",
|
|
// Use "Quasar" fork (not "Latest") to avoid Fortuna/Granite fork conflicts
|
|
genesisJSON: toGenesisJSON(forkToChainConfig["Quasar"]),
|
|
upgradeJSON: func() string {
|
|
upgrade := &extras.UpgradeConfig{
|
|
NetworkUpgradeOverrides: &extras.NetworkUpgrades{
|
|
QuasarTimestamp: utils.NewUint64(DefaultQuasarTime + 2),
|
|
},
|
|
}
|
|
b, err := json.Marshal(upgrade)
|
|
require.NoError(t, err)
|
|
return string(b)
|
|
}(),
|
|
check: func(t *testing.T, vm *VM) {
|
|
require.False(t, vm.chainConfig.IsCancun(common.Big0, DefaultQuasarTime))
|
|
require.True(t, vm.chainConfig.IsCancun(common.Big0, DefaultQuasarTime+2))
|
|
},
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
tvm := newVM(t, testVMConfig{
|
|
genesisJSON: test.genesisJSON,
|
|
upgradeJSON: test.upgradeJSON,
|
|
})
|
|
|
|
defer func() { require.NoError(t, tvm.vm.Shutdown(context.Background())) }()
|
|
test.check(t, tvm.vm)
|
|
})
|
|
}
|
|
}
|