github.com/ethereum/go-ethereum@v1.16.1/ethclient/simulated/backend.go (about)

     1  // Copyright 2023 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package simulated
    18  
    19  import (
    20  	"errors"
    21  	"time"
    22  
    23  	"github.com/ethereum/go-ethereum"
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/core"
    26  	"github.com/ethereum/go-ethereum/core/types"
    27  	"github.com/ethereum/go-ethereum/eth"
    28  	"github.com/ethereum/go-ethereum/eth/catalyst"
    29  	"github.com/ethereum/go-ethereum/eth/ethconfig"
    30  	"github.com/ethereum/go-ethereum/eth/filters"
    31  	"github.com/ethereum/go-ethereum/ethclient"
    32  	"github.com/ethereum/go-ethereum/node"
    33  	"github.com/ethereum/go-ethereum/p2p"
    34  	"github.com/ethereum/go-ethereum/params"
    35  	"github.com/ethereum/go-ethereum/rpc"
    36  )
    37  
    38  // Client exposes the methods provided by the Ethereum RPC client.
    39  type Client interface {
    40  	ethereum.BlockNumberReader
    41  	ethereum.ChainReader
    42  	ethereum.ChainStateReader
    43  	ethereum.ContractCaller
    44  	ethereum.GasEstimator
    45  	ethereum.GasPricer
    46  	ethereum.GasPricer1559
    47  	ethereum.FeeHistoryReader
    48  	ethereum.LogFilterer
    49  	ethereum.PendingStateReader
    50  	ethereum.PendingContractCaller
    51  	ethereum.TransactionReader
    52  	ethereum.TransactionSender
    53  	ethereum.ChainIDReader
    54  }
    55  
    56  // simClient wraps ethclient. This exists to prevent extracting ethclient.Client
    57  // from the Client interface returned by Backend.
    58  type simClient struct {
    59  	*ethclient.Client
    60  }
    61  
    62  // Backend is a simulated blockchain. You can use it to test your contracts or
    63  // other code that interacts with the Ethereum chain.
    64  type Backend struct {
    65  	node   *node.Node
    66  	beacon *catalyst.SimulatedBeacon
    67  	client simClient
    68  }
    69  
    70  // NewBackend creates a new simulated blockchain that can be used as a backend for
    71  // contract bindings in unit tests.
    72  //
    73  // A simulated backend always uses chainID 1337.
    74  func NewBackend(alloc types.GenesisAlloc, options ...func(nodeConf *node.Config, ethConf *ethconfig.Config)) *Backend {
    75  	// Create the default configurations for the outer node shell and the Ethereum
    76  	// service to mutate with the options afterwards
    77  	nodeConf := node.DefaultConfig
    78  	nodeConf.DataDir = ""
    79  	nodeConf.P2P = p2p.Config{NoDiscovery: true}
    80  
    81  	ethConf := ethconfig.Defaults
    82  	ethConf.Genesis = &core.Genesis{
    83  		Config:   params.AllDevChainProtocolChanges,
    84  		GasLimit: ethconfig.Defaults.Miner.GasCeil,
    85  		Alloc:    alloc,
    86  	}
    87  	ethConf.SyncMode = ethconfig.FullSync
    88  	ethConf.TxPool.NoLocals = true
    89  
    90  	for _, option := range options {
    91  		option(&nodeConf, &ethConf)
    92  	}
    93  	// Assemble the Ethereum stack to run the chain with
    94  	stack, err := node.New(&nodeConf)
    95  	if err != nil {
    96  		panic(err) // this should never happen
    97  	}
    98  	sim, err := newWithNode(stack, &ethConf, 0)
    99  	if err != nil {
   100  		panic(err) // this should never happen
   101  	}
   102  	return sim
   103  }
   104  
   105  // newWithNode sets up a simulated backend on an existing node. The provided node
   106  // must not be started and will be started by this method.
   107  func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backend, error) {
   108  	backend, err := eth.New(stack, conf)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	// Register the filter system
   113  	filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{})
   114  	stack.RegisterAPIs([]rpc.API{{
   115  		Namespace: "eth",
   116  		Service:   filters.NewFilterAPI(filterSystem),
   117  	}})
   118  	// Start the node
   119  	if err := stack.Start(); err != nil {
   120  		return nil, err
   121  	}
   122  	// Set up the simulated beacon
   123  	beacon, err := catalyst.NewSimulatedBeacon(blockPeriod, common.Address{}, backend)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	// Reorg our chain back to genesis
   128  	if err := beacon.Fork(backend.BlockChain().GetCanonicalHash(0)); err != nil {
   129  		return nil, err
   130  	}
   131  	return &Backend{
   132  		node:   stack,
   133  		beacon: beacon,
   134  		client: simClient{ethclient.NewClient(stack.Attach())},
   135  	}, nil
   136  }
   137  
   138  // Close shuts down the simBackend.
   139  // The simulated backend can't be used afterwards.
   140  func (n *Backend) Close() error {
   141  	if n.client.Client != nil {
   142  		n.client.Close()
   143  		n.client = simClient{}
   144  	}
   145  	var err error
   146  	if n.beacon != nil {
   147  		err = n.beacon.Stop()
   148  		n.beacon = nil
   149  	}
   150  	if n.node != nil {
   151  		err = errors.Join(err, n.node.Close())
   152  		n.node = nil
   153  	}
   154  	return err
   155  }
   156  
   157  // Commit seals a block and moves the chain forward to a new empty block.
   158  func (n *Backend) Commit() common.Hash {
   159  	return n.beacon.Commit()
   160  }
   161  
   162  // Rollback removes all pending transactions, reverting to the last committed state.
   163  func (n *Backend) Rollback() {
   164  	n.beacon.Rollback()
   165  }
   166  
   167  // Fork creates a side-chain that can be used to simulate reorgs.
   168  //
   169  // This function should be called with the ancestor block where the new side
   170  // chain should be started. Transactions (old and new) can then be applied on
   171  // top and Commit-ed.
   172  //
   173  // Note, the side-chain will only become canonical (and trigger the events) when
   174  // it becomes longer. Until then CallContract will still operate on the current
   175  // canonical chain.
   176  //
   177  // There is a % chance that the side chain becomes canonical at the same length
   178  // to simulate live network behavior.
   179  func (n *Backend) Fork(parentHash common.Hash) error {
   180  	return n.beacon.Fork(parentHash)
   181  }
   182  
   183  // AdjustTime changes the block timestamp and creates a new block.
   184  // It can only be called on empty blocks.
   185  func (n *Backend) AdjustTime(adjustment time.Duration) error {
   186  	return n.beacon.AdjustTime(adjustment)
   187  }
   188  
   189  // Client returns a client that accesses the simulated chain.
   190  func (n *Backend) Client() Client {
   191  	return n.client
   192  }