Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (b *SimulatedBackend) Fork(ctx context.Context, parentHash common.Hash) err
// Deprecated: please use simulated.Backend from package
// github.com/ethereum/go-ethereum/ethclient/simulated instead.
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
b := simulated.New(alloc, gasLimit)
b := simulated.NewBackend(alloc, simulated.WithBlockGasLimit(gasLimit))
return &SimulatedBackend{
Backend: b,
Client: b.Client(),
Expand Down
6 changes: 2 additions & 4 deletions accounts/abi/bind/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,10 @@ var waitDeployedTests = map[string]struct {
func TestWaitDeployed(t *testing.T) {
t.Parallel()
for name, test := range waitDeployedTests {
backend := simulated.New(
backend := simulated.NewBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
10000000,
)
defer backend.Close()

Expand Down Expand Up @@ -102,11 +101,10 @@ func TestWaitDeployed(t *testing.T) {
}

func TestWaitDeployedCornerCases(t *testing.T) {
backend := simulated.New(
backend := simulated.NewBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
10000000,
)
defer backend.Close()

Expand Down
79 changes: 38 additions & 41 deletions ethclient/simulated/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,6 @@ import (
"github.com/ethereum/go-ethereum/rpc"
)

// 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
beacon *catalyst.SimulatedBeacon
client simClient
}

// simClient wraps ethclient. This exists to prevent extracting ethclient.Client
// from the Client interface returned by Backend.
type simClient struct {
*ethclient.Client
}

// Client exposes the methods provided by the Ethereum RPC client.
type Client interface {
ethereum.BlockNumberReader
Expand All @@ -66,70 +52,81 @@ type Client interface {
ethereum.ChainIDReader
}

// New creates a new binding backend using a simulated blockchain
// for testing purposes.
// 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
beacon *catalyst.SimulatedBeacon
client simClient
}

// 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 New(alloc core.GenesisAlloc, gasLimit uint64) *Backend {
// Setup the node object
func NewBackend(alloc core.GenesisAlloc, options ...func(nodeConf *node.Config, ethConf *ethconfig.Config)) *Backend {
// Create the default configurations for the outer node shell and the Ethereum
// service to mutate with the options afterwards
nodeConf := node.DefaultConfig
nodeConf.DataDir = ""
nodeConf.P2P = p2p.Config{NoDiscovery: true}
stack, err := node.New(&nodeConf)
if err != nil {
// This should never happen, if it does, please open an issue
panic(err)
}

// Setup ethereum
genesis := core.Genesis{
ethConf := ethconfig.Defaults
ethConf.Genesis = &core.Genesis{
Config: params.AllDevChainProtocolChanges,
GasLimit: gasLimit,
GasLimit: ethconfig.Defaults.Miner.GasCeil,
Alloc: alloc,
}
conf := ethconfig.Defaults
conf.Genesis = &genesis
conf.SyncMode = downloader.FullSync
conf.TxPool.NoLocals = true
sim, err := newWithNode(stack, &conf, 0)
ethConf.SyncMode = downloader.FullSync
ethConf.TxPool.NoLocals = true

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 {
// This should never happen, if it does, please open an issue
panic(err)
panic(err) // this should never happen
}
return sim
}

// newWithNode sets up a simulated backend on an existing node
// this allows users to do persistent simulations.
// The provided node must not be started and will be started by newWithNode
// 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) {
backend, err := eth.New(stack, conf)
if err != nil {
return nil, err
}

// Register the filter system
filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{})
stack.RegisterAPIs([]rpc.API{{
Namespace: "eth",
Service: filters.NewFilterAPI(filterSystem, false),
}})

// Start the node
if err := stack.Start(); err != nil {
return nil, err
}

// Set up the simulated beacon
beacon, err := catalyst.NewSimulatedBeacon(blockPeriod, backend)
if err != nil {
return nil, err
}

// Reorg our chain back to genesis
if err := beacon.Fork(backend.BlockChain().GetCanonicalHash(0)); err != nil {
return nil, err
}

return &Backend{
eth: backend,
beacon: beacon,
Expand Down
10 changes: 5 additions & 5 deletions ethclient/simulated/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ var (
)

func simTestBackend(testAddr common.Address) *Backend {
return New(
return NewBackend(
core.GenesisAlloc{
testAddr: {Balance: big.NewInt(10000000000000000)},
}, 10000000,
},
)
}

Expand All @@ -70,8 +70,8 @@ func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
return types.SignTx(tx, types.LatestSignerForChainID(chainid), key)
}

func TestNewSim(t *testing.T) {
sim := New(core.GenesisAlloc{}, 30_000_000)
func TestNewBackend(t *testing.T) {
sim := NewBackend(core.GenesisAlloc{})
defer sim.Close()

client := sim.Client()
Expand All @@ -94,7 +94,7 @@ func TestNewSim(t *testing.T) {
}

func TestAdjustTime(t *testing.T) {
sim := New(core.GenesisAlloc{}, 10_000_000)
sim := NewBackend(core.GenesisAlloc{})
defer sim.Close()

client := sim.Client()
Expand Down
39 changes: 39 additions & 0 deletions ethclient/simulated/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2024 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 (
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/node"
)

// WithBlockGasLimit configures the simulated backend to target a specific gas limit
// when producing blocks.
func WithBlockGasLimit(gaslimit uint64) func(nodeConf *node.Config, ethConf *ethconfig.Config) {
return func(nodeConf *node.Config, ethConf *ethconfig.Config) {
ethConf.Genesis.GasLimit = gaslimit
ethConf.Miner.GasCeil = gaslimit
}
}

// WithCallGasLimit configures the simulated backend to cap eth_calls to a specific
// gas limit when running client operations.
func WithCallGasLimit(gaslimit uint64) func(nodeConf *node.Config, ethConf *ethconfig.Config) {
return func(nodeConf *node.Config, ethConf *ethconfig.Config) {
ethConf.RPCGasCap = gaslimit
}
}
73 changes: 73 additions & 0 deletions ethclient/simulated/options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2024 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 (
"context"
"math/big"
"strings"
"testing"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/params"
)

// Tests that the simulator starts with the initial gas limit in the genesis block,
// and that it keeps the same target value.
func TestWithBlockGasLimitOption(t *testing.T) {
// Construct a simulator, targeting a different gas limit
sim := NewBackend(core.GenesisAlloc{}, WithBlockGasLimit(12_345_678))
defer sim.Close()

client := sim.Client()
genesis, err := client.BlockByNumber(context.Background(), big.NewInt(0))
if err != nil {
t.Fatalf("failed to retrieve genesis block: %v", err)
}
if genesis.GasLimit() != 12_345_678 {
t.Errorf("genesis gas limit mismatch: have %v, want %v", genesis.GasLimit(), 12_345_678)
}
// Produce a number of blocks and verify the locked in gas target
sim.Commit()
head, err := client.BlockByNumber(context.Background(), big.NewInt(1))
if err != nil {
t.Fatalf("failed to retrieve head block: %v", err)
}
if head.GasLimit() != 12_345_678 {
t.Errorf("head gas limit mismatch: have %v, want %v", head.GasLimit(), 12_345_678)
}
}

// Tests that the simulator honors the RPC call caps set by the options.
func TestWithCallGasLimitOption(t *testing.T) {
// Construct a simulator, targeting a different gas limit
sim := NewBackend(core.GenesisAlloc{
testAddr: {Balance: big.NewInt(10000000000000000)},
}, WithCallGasLimit(params.TxGas-1))
defer sim.Close()

client := sim.Client()
_, err := client.CallContract(context.Background(), ethereum.CallMsg{
From: testAddr,
To: &testAddr,
Gas: 21000,
}, nil)
if !strings.Contains(err.Error(), core.ErrIntrinsicGas.Error()) {
t.Fatalf("error mismatch: have %v, want %v", err, core.ErrIntrinsicGas)
}
}