mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
ethclient/simulated: split up backend and client
Also changes the method signature of Fork, removing the context parameter. To preserve backwards compatibility, a wrapper of Fork that still takes a context is introduced in the old simulated backend.
This commit is contained in:
parent
b5da90d38c
commit
373e65b8b9
4 changed files with 134 additions and 79 deletions
|
|
@ -17,12 +17,23 @@
|
|||
package backends
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/ethclient/simulated"
|
||||
)
|
||||
|
||||
// SimulatedBackend is a simulated blockchain.
|
||||
// Deprecated: use package github.com/ethereum/go-ethereum/ethclient/simulated instead.
|
||||
type SimulatedBackend struct {
|
||||
simulated.Backend
|
||||
*simulated.Backend
|
||||
simulated.Client
|
||||
}
|
||||
|
||||
// Fork sets the head to a new block, which is based on the provided parentHash.
|
||||
func (b *SimulatedBackend) Fork(ctx context.Context, parentHash common.Hash) error {
|
||||
return b.Backend.Fork(parentHash)
|
||||
}
|
||||
|
||||
// New creates a new binding backend using a simulated blockchain
|
||||
|
|
@ -32,6 +43,10 @@ type SimulatedBackend struct {
|
|||
//
|
||||
// Deprecated: please use simulated.Backend from package
|
||||
// github.com/ethereum/go-ethereum/ethclient/simulated instead.
|
||||
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) SimulatedBackend {
|
||||
return SimulatedBackend{simulated.New(alloc, gasLimit)}
|
||||
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
||||
b := simulated.New(alloc, gasLimit)
|
||||
return &SimulatedBackend{
|
||||
Backend: b,
|
||||
Client: b.Client(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package catalyst
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"math"
|
||||
|
|
@ -293,7 +292,7 @@ func (c *SimulatedBeacon) Rollback() {
|
|||
}
|
||||
|
||||
// Fork sets the head to the provided hash.
|
||||
func (c *SimulatedBeacon) Fork(ctx context.Context, parentHash common.Hash) error {
|
||||
func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
|
||||
if len(c.eth.TxPool().Pending(false)) != 0 {
|
||||
return errors.New("pending block dirty")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package simulated
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
|
|
@ -37,32 +36,24 @@ import (
|
|||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
var _ bind.ContractBackend = (Backend)(nil)
|
||||
var _ bind.ContractBackend = (*simBackend)(nil)
|
||||
var _ bind.ContractBackend = (Client)(nil)
|
||||
|
||||
type SimChainManagement interface {
|
||||
// Commit seals a block and moves the chain forward to a new empty block.
|
||||
Commit() common.Hash
|
||||
|
||||
// Rollback un-sends previously added transactions.
|
||||
Rollback()
|
||||
|
||||
// Fork sets the head to a new block, which is based on the provided parentHash.
|
||||
Fork(ctx context.Context, parentHash common.Hash) error
|
||||
|
||||
// AdjustTime changes the block timestamp.
|
||||
AdjustTime(adjustment time.Duration) error
|
||||
|
||||
// Close closes the backend. You need to call this to clean up resources.
|
||||
Close() error
|
||||
// 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
|
||||
}
|
||||
|
||||
// Backend all interfaces in the ethereum package, but is based on a
|
||||
// simulated blockchain. It is intended for testing purposes.
|
||||
type Backend interface {
|
||||
SimChainManagement
|
||||
// simClient wraps ethclient. This exists to prevent extracting ethclient.Client from the
|
||||
// Client interface returned by Backend.
|
||||
type simClient struct {
|
||||
*ethclient.Client
|
||||
}
|
||||
|
||||
// The backend implements all interfaces in the ethereum package.
|
||||
// Client exposes the methods provided by the Ethereum RPC client.
|
||||
type Client interface {
|
||||
ethereum.BlockNumberReader
|
||||
ethereum.ChainReader
|
||||
ethereum.ChainStateReader
|
||||
|
|
@ -78,16 +69,10 @@ type Backend interface {
|
|||
ethereum.ChainIDReader
|
||||
}
|
||||
|
||||
type simBackend struct {
|
||||
eth *eth.Ethereum
|
||||
*catalyst.SimulatedBeacon
|
||||
*ethclient.Client
|
||||
}
|
||||
|
||||
// New creates a new binding backend using a simulated blockchain
|
||||
// for testing purposes.
|
||||
// A simulated backend always uses chainID 1337.
|
||||
func New(alloc core.GenesisAlloc, gasLimit uint64) Backend {
|
||||
func New(alloc core.GenesisAlloc, gasLimit uint64) *Backend {
|
||||
// Setup the node object
|
||||
nodeConf := node.DefaultConfig
|
||||
nodeConf.DataDir = ""
|
||||
|
|
@ -118,7 +103,7 @@ func New(alloc core.GenesisAlloc, gasLimit uint64) Backend {
|
|||
// 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
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -143,26 +128,63 @@ func NewWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (Backen
|
|||
}
|
||||
|
||||
// Reorg our chain back to genesis
|
||||
if err := beacon.Fork(context.Background(), backend.BlockChain().GetCanonicalHash(0)); err != nil {
|
||||
if err := beacon.Fork(backend.BlockChain().GetCanonicalHash(0)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &simBackend{
|
||||
return &Backend{
|
||||
eth: backend,
|
||||
SimulatedBeacon: beacon,
|
||||
Client: ethclient.NewClient(stack.Attach()),
|
||||
beacon: beacon,
|
||||
client: simClient{ethclient.NewClient(stack.Attach())},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *simBackend) Close() error {
|
||||
if n.Client != nil {
|
||||
n.Client.Close()
|
||||
n.Client = nil
|
||||
func (n *Backend) Close() error {
|
||||
if n.client.Client != nil {
|
||||
n.client.Close()
|
||||
n.client = simClient{}
|
||||
}
|
||||
if n.SimulatedBeacon != nil {
|
||||
err := n.SimulatedBeacon.Stop()
|
||||
n.SimulatedBeacon = nil
|
||||
if n.beacon != nil {
|
||||
err := n.beacon.Stop()
|
||||
n.beacon = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit seals a block and moves the chain forward to a new empty block.
|
||||
func (n *Backend) Commit() common.Hash {
|
||||
return n.beacon.Commit()
|
||||
}
|
||||
|
||||
// Rollback removes all pending transactions, reverting to the last committed state.
|
||||
func (n *Backend) Rollback() {
|
||||
n.beacon.Rollback()
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return n.beacon.Fork(parentHash)
|
||||
}
|
||||
|
||||
// AdjustTime changes the block timestamp.
|
||||
// It can only be called on empty blocks.
|
||||
func (n *Backend) AdjustTime(adjustment time.Duration) error {
|
||||
return n.beacon.AdjustTime(adjustment)
|
||||
}
|
||||
|
||||
// Client returns a client that accesses the simulated chain.
|
||||
func (n *Backend) Client() Client {
|
||||
return n.client
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ var (
|
|||
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
)
|
||||
|
||||
func simTestBackend(testAddr common.Address) Backend {
|
||||
func simTestBackend(testAddr common.Address) *Backend {
|
||||
return New(
|
||||
core.GenesisAlloc{
|
||||
testAddr: {Balance: big.NewInt(10000000000000000)},
|
||||
|
|
@ -44,13 +44,15 @@ func simTestBackend(testAddr common.Address) Backend {
|
|||
)
|
||||
}
|
||||
|
||||
func newTx(sim Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
|
||||
func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
|
||||
client := sim.Client()
|
||||
|
||||
// create a signed transaction to send
|
||||
head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||
head, _ := client.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
chainid, _ := sim.ChainID(context.Background())
|
||||
nonce, err := sim.PendingNonceAt(context.Background(), addr)
|
||||
chainid, _ := client.ChainID(context.Background())
|
||||
nonce, err := client.PendingNonceAt(context.Background(), addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -68,7 +70,10 @@ func newTx(sim Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
|
|||
|
||||
func TestNewSim(t *testing.T) {
|
||||
sim := New(core.GenesisAlloc{}, 30_000_000)
|
||||
num, err := sim.BlockNumber(context.Background())
|
||||
defer sim.Close()
|
||||
|
||||
client := sim.Client()
|
||||
num, err := client.BlockNumber(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -77,7 +82,7 @@ func TestNewSim(t *testing.T) {
|
|||
}
|
||||
// Create a block
|
||||
sim.Commit()
|
||||
num, err = sim.BlockNumber(context.Background())
|
||||
num, err = client.BlockNumber(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -90,12 +95,14 @@ func TestAdjustTime(t *testing.T) {
|
|||
sim := New(core.GenesisAlloc{}, 10_000_000)
|
||||
defer sim.Close()
|
||||
|
||||
block1, _ := sim.BlockByNumber(context.Background(), nil)
|
||||
client := sim.Client()
|
||||
block1, _ := client.BlockByNumber(context.Background(), nil)
|
||||
|
||||
// Create a block
|
||||
if err := sim.AdjustTime(time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
block2, _ := sim.BlockByNumber(context.Background(), nil)
|
||||
block2, _ := client.BlockByNumber(context.Background(), nil)
|
||||
prevTime := block1.Time()
|
||||
newTime := block2.Time()
|
||||
if newTime-prevTime != uint64(time.Minute) {
|
||||
|
|
@ -106,19 +113,21 @@ func TestAdjustTime(t *testing.T) {
|
|||
func TestSendTransaction(t *testing.T) {
|
||||
sim := simTestBackend(testAddr)
|
||||
defer sim.Close()
|
||||
bgCtx := context.Background()
|
||||
|
||||
client := sim.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
signedTx, err := newTx(sim, testKey)
|
||||
if err != nil {
|
||||
t.Errorf("could not create transaction: %v", err)
|
||||
}
|
||||
// send tx to simulated backend
|
||||
err = sim.SendTransaction(bgCtx, signedTx)
|
||||
err = client.SendTransaction(ctx, signedTx)
|
||||
if err != nil {
|
||||
t.Errorf("could not add tx to pending block: %v", err)
|
||||
}
|
||||
sim.Commit()
|
||||
block, err := sim.BlockByNumber(bgCtx, big.NewInt(1))
|
||||
block, err := client.BlockByNumber(ctx, big.NewInt(1))
|
||||
if err != nil {
|
||||
t.Errorf("could not get block at height 1: %v", err)
|
||||
}
|
||||
|
|
@ -144,10 +153,11 @@ func TestFork(t *testing.T) {
|
|||
sim := simTestBackend(testAddr)
|
||||
defer sim.Close()
|
||||
|
||||
client := sim.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
// 1.
|
||||
parent, _ := sim.HeaderByNumber(ctx, nil)
|
||||
parent, _ := client.HeaderByNumber(ctx, nil)
|
||||
|
||||
// 2.
|
||||
n := int(rand.Int31n(21))
|
||||
|
|
@ -156,13 +166,13 @@ func TestFork(t *testing.T) {
|
|||
}
|
||||
|
||||
// 3.
|
||||
b, _ := sim.BlockNumber(ctx)
|
||||
b, _ := client.BlockNumber(ctx)
|
||||
if b != uint64(n) {
|
||||
t.Error("wrong chain length")
|
||||
}
|
||||
|
||||
// 4.
|
||||
sim.Fork(ctx, parent.Hash())
|
||||
sim.Fork(parent.Hash())
|
||||
|
||||
// 5.
|
||||
for i := 0; i < n+1; i++ {
|
||||
|
|
@ -170,7 +180,7 @@ func TestFork(t *testing.T) {
|
|||
}
|
||||
|
||||
// 6.
|
||||
b, _ = sim.BlockNumber(ctx)
|
||||
b, _ = client.BlockNumber(ctx)
|
||||
if b != uint64(n+1) {
|
||||
t.Error("wrong chain length")
|
||||
}
|
||||
|
|
@ -191,36 +201,38 @@ func TestForkResendTx(t *testing.T) {
|
|||
sim := simTestBackend(testAddr)
|
||||
defer sim.Close()
|
||||
|
||||
// 1.
|
||||
client := sim.Client()
|
||||
ctx := context.Background()
|
||||
parent, _ := sim.HeaderByNumber(ctx, nil)
|
||||
|
||||
// 1.
|
||||
parent, _ := client.HeaderByNumber(ctx, nil)
|
||||
|
||||
// 2.
|
||||
tx, err := newTx(sim, testKey)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create transaction: %v", err)
|
||||
}
|
||||
sim.SendTransaction(context.Background(), tx)
|
||||
client.SendTransaction(ctx, tx)
|
||||
sim.Commit()
|
||||
|
||||
// 3.
|
||||
receipt, _ := sim.TransactionReceipt(context.Background(), tx.Hash())
|
||||
receipt, _ := client.TransactionReceipt(ctx, tx.Hash())
|
||||
if h := receipt.BlockNumber.Uint64(); h != 1 {
|
||||
t.Errorf("TX included in wrong block: %d", h)
|
||||
}
|
||||
|
||||
// 4.
|
||||
if err := sim.Fork(context.Background(), parent.Hash()); err != nil {
|
||||
if err := sim.Fork(parent.Hash()); err != nil {
|
||||
t.Errorf("forking: %v", err)
|
||||
}
|
||||
|
||||
// 5.
|
||||
sim.Commit()
|
||||
if err := sim.SendTransaction(context.Background(), tx); err != nil {
|
||||
if err := client.SendTransaction(ctx, tx); err != nil {
|
||||
t.Fatalf("sending transaction: %v", err)
|
||||
}
|
||||
sim.Commit()
|
||||
receipt, _ = sim.TransactionReceipt(context.Background(), tx.Hash())
|
||||
receipt, _ = client.TransactionReceipt(ctx, tx.Hash())
|
||||
if h := receipt.BlockNumber.Uint64(); h != 2 {
|
||||
t.Errorf("TX included in wrong block: %d", h)
|
||||
}
|
||||
|
|
@ -232,26 +244,30 @@ func TestCommitReturnValue(t *testing.T) {
|
|||
sim := simTestBackend(testAddr)
|
||||
defer sim.Close()
|
||||
|
||||
client := sim.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
// Test if Commit returns the correct block hash
|
||||
h1 := sim.Commit()
|
||||
cur, _ := sim.HeaderByNumber(context.Background(), nil)
|
||||
cur, _ := client.HeaderByNumber(ctx, nil)
|
||||
if h1 != cur.Hash() {
|
||||
t.Error("Commit did not return the hash of the last block.")
|
||||
}
|
||||
|
||||
// Create a block in the original chain (containing a transaction to force different block hashes)
|
||||
head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||
head, _ := client.HeaderByNumber(ctx, nil) // Should be child's, good enough
|
||||
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
||||
_tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
|
||||
tx, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey)
|
||||
sim.SendTransaction(context.Background(), tx)
|
||||
client.SendTransaction(ctx, tx)
|
||||
|
||||
h2 := sim.Commit()
|
||||
|
||||
// Create another block in the original chain
|
||||
sim.Commit()
|
||||
|
||||
// Fork at the first bock
|
||||
if err := sim.Fork(context.Background(), h1); err != nil {
|
||||
if err := sim.Fork(h1); err != nil {
|
||||
t.Errorf("forking: %v", err)
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +276,7 @@ func TestCommitReturnValue(t *testing.T) {
|
|||
if h2 == h2fork {
|
||||
t.Error("The block in the fork and the original block are the same block!")
|
||||
}
|
||||
if header, err := sim.HeaderByHash(context.Background(), h2fork); err != nil || header == nil {
|
||||
if header, err := client.HeaderByHash(ctx, h2fork); err != nil || header == nil {
|
||||
t.Error("Could not retrieve the just created block (side-chain)")
|
||||
}
|
||||
}
|
||||
|
|
@ -273,15 +289,18 @@ func TestAdjustTimeAfterFork(t *testing.T) {
|
|||
sim := simTestBackend(testAddr)
|
||||
defer sim.Close()
|
||||
|
||||
client := sim.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
sim.Commit() // h1
|
||||
h1, _ := sim.HeaderByNumber(context.Background(), nil)
|
||||
h1, _ := client.HeaderByNumber(ctx, nil)
|
||||
|
||||
sim.Commit() // h2
|
||||
sim.Fork(context.Background(), h1.Hash())
|
||||
sim.Fork(h1.Hash())
|
||||
sim.AdjustTime(1 * time.Second)
|
||||
sim.Commit()
|
||||
|
||||
head, _ := sim.HeaderByNumber(context.Background(), nil)
|
||||
head, _ := client.HeaderByNumber(ctx, nil)
|
||||
if head.Number.Uint64() == 2 && head.ParentHash != h1.Hash() {
|
||||
t.Errorf("failed to build block on fork")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue