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