Files
zeekayandHanzo Dev 3954a994af Convert BlackholeAddr at the call site now that constants ships an array
constants.BlackholeAddr is a plain [20]byte rather than a geth
common.Address, so constants pulls in neither geth nor crypto. Every site
that wants an address now converts explicitly, and the two that read raw
bytes convert first, since an array has no Bytes method.

Assignability hides most of this from the compiler: a [20]byte argument,
return value or assignment still satisfies a common.Address parameter. The
two require.Equal assertions in rewardmanager do not, because
reflect.DeepEqual compares types as well as contents, and they failed on
identical bytes until converted. The conversion is free and compiles
against both the currently tagged constants and the array, so this does not
have to land with the version bump.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-06 00:30:45 -07:00

313 lines
9.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package simulated
import (
"errors"
"math/big"
"time"
"github.com/luxfi/constants"
"github.com/luxfi/evmgpu/consensus/dummy"
"github.com/luxfi/evmgpu/core"
"github.com/luxfi/evmgpu/eth"
"github.com/luxfi/evmgpu/eth/ethconfig"
"github.com/luxfi/evmgpu/ethclient"
"github.com/luxfi/evmgpu/interfaces"
"github.com/luxfi/evmgpu/node"
"github.com/luxfi/evmgpu/params"
"github.com/luxfi/evmgpu/params/extras"
"github.com/luxfi/evmgpu/rpc"
ethereum "github.com/luxfi/geth"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/rawdb"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/timer/mockable"
)
var _ eth.PushGossiper = (*fakePushGossiper)(nil)
type fakePushGossiper struct{}
func (*fakePushGossiper) Add(*types.Transaction) {}
// Client exposes the methods provided by the Ethereum RPC client.
type Client interface {
ethereum.BlockNumberReader
ethereum.ChainReader
ethereum.ChainStateReader
ethereum.ContractCaller
ethereum.GasEstimator
ethereum.GasPricer
ethereum.GasPricer1559
ethereum.FeeHistoryReader
ethereum.LogFilterer
interfaces.AcceptedStateReader
interfaces.AcceptedContractCaller
ethereum.TransactionReader
ethereum.TransactionSender
ethereum.ChainIDReader
}
// simClient wraps ethclient. This exists to prevent extracting ethclient.Client
// from the Client interface returned by Backend.
type simClient struct {
ethclient.Client
}
// Backend is a simulated blockchain. You can use it to test your contracts or
// other code that interacts with the Ethereum chain.
type Backend struct {
eth *eth.Ethereum
client simClient
clock *mockable.Clock
server *rpc.Server
}
// NewBackend creates a new simulated blockchain that can be used as a backend for
// contract bindings in unit tests.
//
// A simulated backend always uses chainID 1337.
func NewBackend(alloc types.GenesisAlloc, options ...func(nodeConf *node.Config, ethConf *ethconfig.Config)) *Backend {
// Use TestEVMChainConfig which doesn't have Shanghai/Cancun enabled.
// This is needed for backward compatibility with legacy contract bytecode
// that uses pre-Shanghai opcodes (e.g., SELFDESTRUCT, certain JUMP patterns).
chainConfig := params.Copy(params.TestEVMChainConfig)
chainConfig.ChainID = big.NewInt(1337)
// Create the default configurations for the outer node shell and the Ethereum
// service to mutate with the options afterwards
nodeConf := node.DefaultConfig
ethConf := ethconfig.DefaultConfig
// Initialize the extras with proper defaults for EVM without Shanghai/Cancun
extra := &extras.ChainConfig{
FeeConfig: extras.TestFeeConfig, // Use TestFeeConfig with minimal fees for test compatibility
NetworkUpgrades: extras.TestEVMChainConfig.NetworkUpgrades,
}
params.SetExtra(chainConfig, extra)
ethConf.Genesis = &core.Genesis{
Config: chainConfig,
GasLimit: extra.FeeConfig.GasLimit.Uint64(),
Alloc: alloc,
}
ethConf.AllowUnfinalizedQueries = true
ethConf.Miner.Etherbase = common.Address(constants.BlackholeAddr)
ethConf.Miner.TestOnlyAllowDuplicateBlocks = true
ethConf.TxPool.NoLocals = true
// Use hash scheme for simulated backend to avoid pathdb flattening issues
// when accepting blocks on empty/genesis state
ethConf.StateScheme = rawdb.HashScheme
// Disable snapshots for simulated backend to avoid flattening issues
ethConf.SnapshotCache = 0
for _, option := range options {
option(&nodeConf, &ethConf)
}
// Assemble the Ethereum stack to run the chain with
stack, err := node.New(&nodeConf)
if err != nil {
panic(err) // this should never happen
}
sim, err := newWithNode(stack, &ethConf, 0)
if err != nil {
panic(err) // this should never happen
}
return sim
}
// newWithNode sets up a simulated backend on an existing node. The provided node
// must not be started and will be started by this method.
func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backend, error) {
chaindb := rawdb.NewMemoryDatabase()
clock := &mockable.Clock{}
clock.Set(time.Unix(0, 0))
engine := dummy.NewFakerWithModeAndClock(
dummy.Mode{ModeSkipCoinbase: true}, clock,
)
backend, err := eth.New(
stack, conf, &fakePushGossiper{}, chaindb, eth.Settings{}, common.Hash{},
engine, clock,
)
if err != nil {
return nil, err
}
server := rpc.NewServer(0)
for _, api := range backend.APIs() {
if err := server.RegisterName(api.Namespace, api.Service); err != nil {
return nil, err
}
}
return &Backend{
eth: backend,
client: simClient{ethclient.NewClient(rpc.DialInProc(server))},
clock: clock,
server: server,
}, nil
}
// Close shuts down the simBackend.
// The simulated backend can't be used afterwards.
func (n *Backend) Close() error {
if n.client.Client != nil {
n.client.Close()
}
n.server.Stop()
return nil
}
// Commit seals a block and moves the chain forward to a new empty block.
func (n *Backend) Commit(accept bool) common.Hash {
hash, err := n.buildBlock(accept, 10)
if err != nil {
panic(err)
}
return hash
}
func (n *Backend) buildBlock(accept bool, gap uint64) (common.Hash, error) {
chain := n.eth.BlockChain()
parent := chain.CurrentBlock()
if err := n.eth.TxPool().Sync(); err != nil {
return common.Hash{}, err
}
n.clock.Set(time.Unix(int64(parent.Time+gap), 0))
block, err := n.eth.Miner().GenerateBlock(nil)
if err != nil {
return common.Hash{}, err
}
if err := chain.InsertBlock(block); err != nil {
return common.Hash{}, err
}
// For simulated backend, we only accept blocks when explicitly requested
// This avoids snapshot flattening issues during initialization
if accept && block.NumberU64() > 0 {
if err := n.acceptAncestors(block); err != nil {
return common.Hash{}, err
}
chain.DrainAcceptorQueue()
}
return block.Hash(), nil
}
func (n *Backend) acceptAncestors(block *types.Block) error {
chain := n.eth.BlockChain()
lastAccepted := chain.LastConsensusAcceptedBlock()
// Accept all ancestors of the block
toAccept := []*types.Block{block}
for block.ParentHash() != lastAccepted.Hash() {
block = chain.GetBlockByHash(block.ParentHash())
toAccept = append(toAccept, block)
if block.NumberU64() < lastAccepted.NumberU64() {
return errors.New("last accepted must be an ancestor of the block to accept")
}
}
for i := len(toAccept) - 1; i >= 0; i-- {
if err := chain.Accept(toAccept[i]); err != nil {
return err
}
}
return nil
}
// Rollback removes all pending transactions, reverting to the last committed state.
func (n *Backend) Rollback() {
// Flush all transactions from the transaction pools
maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1)
original := n.eth.TxPool().GasTip()
n.eth.TxPool().SetGasTip(maxUint256)
n.eth.TxPool().SetGasTip(original)
}
// Fork creates a side-chain that can be used to simulate reorgs.
//
// This function should be called with the ancestor block where the new side
// chain should be started. Transactions (old and new) can then be applied on
// top and Commit-ed.
//
// Note, the side-chain will only become canonical (and trigger the events) when
// it becomes longer. Until then CallContract will still operate on the current
// canonical chain.
//
// There is a % chance that the side chain becomes canonical at the same length
// to simulate live network behavior.
func (n *Backend) Fork(parentHash common.Hash) error {
chain := n.eth.BlockChain()
if chain.CurrentBlock().Hash() == parentHash {
return nil
}
parent := chain.GetBlockByHash(parentHash)
if parent == nil {
return errors.New("parent block not found")
}
ch := make(chan core.NewTxPoolReorgEvent, 1)
sub := n.eth.TxPool().SubscribeNewReorgEvent(ch)
defer sub.Unsubscribe()
if err := n.eth.BlockChain().SetPreference(parent); err != nil {
return err
}
for {
select {
case reorg := <-ch:
// Wait for tx pool to reorg, then flush the tx pool
if reorg.Head.Hash() == parent.Hash() {
n.Rollback()
return nil
}
case <-time.After(2 * time.Second):
return errors.New("fork not accepted")
}
}
}
// AdjustTime changes the block timestamp and creates a new block.
// It can only be called on empty blocks.
func (n *Backend) AdjustTime(adjustment time.Duration) error {
// Convert duration from nanoseconds to seconds for the block timestamp
_, err := n.buildBlock(false, uint64(adjustment.Seconds()))
return err
}
// Client returns a client that accesses the simulated chain.
func (n *Backend) Client() Client {
return n.client
}