accounts, ethclient: minor tweaks on the new simulated backend (#28799)

* accounts, ethclient: minor tweaks on the new simulated backend

* ethclient/simulated: add an initial batch of gas options

* accounts, ethclient: remove mandatory gasLimit constructor param

* accounts, ethclient: minor option naming tweaks
This commit is contained in:
Péter Szilágyi 2024-01-12 15:58:49 +02:00 committed by GitHub
parent 7280a5b31a
commit 065f82a8cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 158 additions and 51 deletions

@ -44,7 +44,7 @@ func (b *SimulatedBackend) Fork(ctx context.Context, parentHash common.Hash) err
// Deprecated: please use simulated.Backend from package // Deprecated: please use simulated.Backend from package
// github.com/ethereum/go-ethereum/ethclient/simulated instead. // github.com/ethereum/go-ethereum/ethclient/simulated instead.
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
b := simulated.New(alloc, gasLimit) b := simulated.NewBackend(alloc, simulated.WithBlockGasLimit(gasLimit))
return &SimulatedBackend{ return &SimulatedBackend{
Backend: b, Backend: b,
Client: b.Client(), Client: b.Client(),

@ -56,11 +56,10 @@ var waitDeployedTests = map[string]struct {
func TestWaitDeployed(t *testing.T) { func TestWaitDeployed(t *testing.T) {
t.Parallel() t.Parallel()
for name, test := range waitDeployedTests { for name, test := range waitDeployedTests {
backend := simulated.New( backend := simulated.NewBackend(
core.GenesisAlloc{ core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
}, },
10000000,
) )
defer backend.Close() defer backend.Close()
@ -102,11 +101,10 @@ func TestWaitDeployed(t *testing.T) {
} }
func TestWaitDeployedCornerCases(t *testing.T) { func TestWaitDeployedCornerCases(t *testing.T) {
backend := simulated.New( backend := simulated.NewBackend(
core.GenesisAlloc{ core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
}, },
10000000,
) )
defer backend.Close() defer backend.Close()

@ -34,20 +34,6 @@ import (
"github.com/ethereum/go-ethereum/rpc" "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. // Client exposes the methods provided by the Ethereum RPC client.
type Client interface { type Client interface {
ethereum.BlockNumberReader ethereum.BlockNumberReader
@ -66,70 +52,81 @@ type Client interface {
ethereum.ChainIDReader ethereum.ChainIDReader
} }
// New creates a new binding backend using a simulated blockchain // simClient wraps ethclient. This exists to prevent extracting ethclient.Client
// for testing purposes. // 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. // A simulated backend always uses chainID 1337.
func New(alloc core.GenesisAlloc, gasLimit uint64) *Backend { func NewBackend(alloc core.GenesisAlloc, options ...func(nodeConf *node.Config, ethConf *ethconfig.Config)) *Backend {
// Setup the node object // Create the default configurations for the outer node shell and the Ethereum
// service to mutate with the options afterwards
nodeConf := node.DefaultConfig nodeConf := node.DefaultConfig
nodeConf.DataDir = "" nodeConf.DataDir = ""
nodeConf.P2P = p2p.Config{NoDiscovery: true} 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 ethConf := ethconfig.Defaults
genesis := core.Genesis{ ethConf.Genesis = &core.Genesis{
Config: params.AllDevChainProtocolChanges, Config: params.AllDevChainProtocolChanges,
GasLimit: gasLimit, GasLimit: ethconfig.Defaults.Miner.GasCeil,
Alloc: alloc, Alloc: alloc,
} }
conf := ethconfig.Defaults ethConf.SyncMode = downloader.FullSync
conf.Genesis = &genesis ethConf.TxPool.NoLocals = true
conf.SyncMode = downloader.FullSync
conf.TxPool.NoLocals = true for _, option := range options {
sim, err := newWithNode(stack, &conf, 0) option(&nodeConf, &ethConf)
}
// Assemble the Ethereum stack to run the chain with
stack, err := node.New(&nodeConf)
if err != nil { if err != nil {
// This should never happen, if it does, please open an issue panic(err) // this should never happen
panic(err) }
sim, err := newWithNode(stack, &ethConf, 0)
if err != nil {
panic(err) // this should never happen
} }
return sim return sim
} }
// newWithNode sets up a simulated backend on an existing node // newWithNode sets up a simulated backend on an existing node. The provided node
// this allows users to do persistent simulations. // must not be started and will be started by this method.
// The provided node must not be started and will be started by newWithNode
func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backend, error) { func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backend, error) {
backend, err := eth.New(stack, conf) backend, err := eth.New(stack, conf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Register the filter system // Register the filter system
filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{}) filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{})
stack.RegisterAPIs([]rpc.API{{ stack.RegisterAPIs([]rpc.API{{
Namespace: "eth", Namespace: "eth",
Service: filters.NewFilterAPI(filterSystem, false), Service: filters.NewFilterAPI(filterSystem, false),
}}) }})
// Start the node // Start the node
if err := stack.Start(); err != nil { if err := stack.Start(); err != nil {
return nil, err return nil, err
} }
// Set up the simulated beacon // Set up the simulated beacon
beacon, err := catalyst.NewSimulatedBeacon(blockPeriod, backend) beacon, err := catalyst.NewSimulatedBeacon(blockPeriod, backend)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Reorg our chain back to genesis // Reorg our chain back to genesis
if err := beacon.Fork(backend.BlockChain().GetCanonicalHash(0)); err != nil { if err := beacon.Fork(backend.BlockChain().GetCanonicalHash(0)); err != nil {
return nil, err return nil, err
} }
return &Backend{ return &Backend{
eth: backend, eth: backend,
beacon: beacon, beacon: beacon,

@ -40,10 +40,10 @@ var (
) )
func simTestBackend(testAddr common.Address) *Backend { func simTestBackend(testAddr common.Address) *Backend {
return New( return NewBackend(
core.GenesisAlloc{ core.GenesisAlloc{
testAddr: {Balance: big.NewInt(10000000000000000)}, testAddr: {Balance: big.NewInt(10000000000000000)},
}, 10000000, },
) )
} }
@ -70,8 +70,8 @@ func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
return types.SignTx(tx, types.LatestSignerForChainID(chainid), key) return types.SignTx(tx, types.LatestSignerForChainID(chainid), key)
} }
func TestNewSim(t *testing.T) { func TestNewBackend(t *testing.T) {
sim := New(core.GenesisAlloc{}, 30_000_000) sim := NewBackend(core.GenesisAlloc{})
defer sim.Close() defer sim.Close()
client := sim.Client() client := sim.Client()
@ -94,7 +94,7 @@ func TestNewSim(t *testing.T) {
} }
func TestAdjustTime(t *testing.T) { func TestAdjustTime(t *testing.T) {
sim := New(core.GenesisAlloc{}, 10_000_000) sim := NewBackend(core.GenesisAlloc{})
defer sim.Close() defer sim.Close()
client := sim.Client() client := sim.Client()

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

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