diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 0654ea28f8..2a82139c8c 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -19,8 +19,12 @@ package backends import ( "context" "math" + "math/big" + "time" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/catalyst" @@ -34,9 +38,51 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -var _ bind.ContractBackend = (*SimulatedBackend)(nil) +var _ bind.ContractBackend = (*simBackend)(nil) -type SimulatedBackend struct { +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 +} + +// TODO: these methods should have their own interface in the ethereum package. +type SimChainExtras interface { + BlockNumber(context.Context) (uint64, error) + ChainID(context.Context) (*big.Int, error) +} + +// SimulatedBackend all interfaces in the ethereum package, but is based on a +// simulated blockchain. It is intended for testing purposes. +type SimulatedBackend interface { + SimChainManagement + + // The backend implements all interfaces in the ethereum package. + ethereum.ChainReader + ethereum.ChainStateReader + ethereum.ContractCaller + ethereum.GasEstimator + ethereum.LogFilterer + ethereum.GasPricer + ethereum.PendingStateReader + ethereum.PendingContractCaller + ethereum.TransactionReader + ethereum.TransactionSender + SimChainExtras +} + +type simBackend struct { eth *eth.Ethereum *catalyst.SimulatedBeacon *ethclient.Client @@ -45,7 +91,7 @@ type SimulatedBackend struct { // NewSimulatedBackend creates a new binding backend using a simulated blockchain // for testing purposes. // A simulated backend always uses chainID 1337. -func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { +func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) SimulatedBackend { // Setup the node object nodeConf := node.DefaultConfig nodeConf.DataDir = "" @@ -76,7 +122,7 @@ func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBac // NewSimWithNode 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 NewSimWithNode -func NewSimWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*SimulatedBackend, error) { +func NewSimWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (SimulatedBackend, error) { backend, err := eth.New(stack, conf) if err != nil { return nil, err @@ -105,14 +151,14 @@ func NewSimWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Si return nil, err } - return &SimulatedBackend{ + return &simBackend{ eth: backend, SimulatedBeacon: beacon, Client: ethclient.NewClient(stack.Attach()), }, nil } -func (n *SimulatedBackend) Close() error { +func (n *simBackend) Close() error { if n.Client != nil { n.Client.Close() n.Client = nil diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index f036fbeb1f..da45e0ccdf 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -36,7 +36,7 @@ var ( testAddr = crypto.PubkeyToAddress(testKey.PublicKey) ) -func simTestBackend(testAddr common.Address) *SimulatedBackend { +func simTestBackend(testAddr common.Address) SimulatedBackend { return NewSimulatedBackend( core.GenesisAlloc{ testAddr: {Balance: big.NewInt(10000000000000000)}, @@ -44,12 +44,7 @@ func simTestBackend(testAddr common.Address) *SimulatedBackend { ) } -func (sim *SimulatedBackend) currentBlock() *types.Block { - current, _ := sim.BlockByNumber(context.Background(), nil) - return current -} - -func (sim *SimulatedBackend) newTx(key *ecdsa.PrivateKey) (*types.Transaction, error) { +func newTx(sim SimulatedBackend, key *ecdsa.PrivateKey) (*types.Transaction, error) { // create a signed transaction to send head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) @@ -113,7 +108,7 @@ func TestSendTransaction(t *testing.T) { defer sim.Close() bgCtx := context.Background() - signedTx, err := sim.newTx(testKey) + signedTx, err := newTx(sim, testKey) if err != nil { t.Errorf("could not create transaction: %v", err) } @@ -148,25 +143,35 @@ func TestFork(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) defer sim.Close() + + ctx := context.Background() + // 1. - parent := sim.currentBlock() + parent, _ := sim.HeaderByNumber(ctx, nil) + // 2. n := int(rand.Int31n(21)) for i := 0; i < n; i++ { sim.Commit() } + // 3. - if sim.currentBlock().Number().Uint64() != uint64(n) { + b, _ := sim.BlockNumber(ctx) + if b != uint64(n) { t.Error("wrong chain length") } + // 4. - sim.Fork(context.Background(), parent.Hash()) + sim.Fork(ctx, parent.Hash()) + // 5. for i := 0; i < n+1; i++ { sim.Commit() } + // 6. - if sim.currentBlock().Number().Uint64() != uint64(n+1) { + b, _ = sim.BlockNumber(ctx) + if b != uint64(n+1) { t.Error("wrong chain length") } } @@ -185,24 +190,30 @@ func TestForkResendTx(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) defer sim.Close() + // 1. - parent := sim.currentBlock() + ctx := context.Background() + parent, _ := sim.HeaderByNumber(ctx, nil) + // 2. - tx, err := sim.newTx(testKey) + tx, err := newTx(sim, testKey) if err != nil { t.Fatalf("could not create transaction: %v", err) } sim.SendTransaction(context.Background(), tx) sim.Commit() + // 3. receipt, _ := sim.TransactionReceipt(context.Background(), 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 { t.Errorf("forking: %v", err) } + // 5. sim.Commit() if err := sim.SendTransaction(context.Background(), tx); err != nil { @@ -223,7 +234,8 @@ func TestCommitReturnValue(t *testing.T) { // Test if Commit returns the correct block hash h1 := sim.Commit() - if h1 != sim.currentBlock().Hash() { + cur, _ := sim.HeaderByNumber(context.Background(), nil) + if h1 != cur.Hash() { t.Error("Commit did not return the hash of the last block.") } @@ -262,14 +274,15 @@ func TestAdjustTimeAfterFork(t *testing.T) { defer sim.Close() sim.Commit() // h1 - h1 := sim.currentBlock().Hash() + h1, _ := sim.HeaderByNumber(context.Background(), nil) + sim.Commit() // h2 - sim.Fork(context.Background(), h1) + sim.Fork(context.Background(), h1.Hash()) sim.AdjustTime(1 * time.Second) sim.Commit() - head := sim.currentBlock() - if head.Number() == common.Big2 && head.ParentHash() != h1 { + head, _ := sim.HeaderByNumber(context.Background(), nil) + if head.Number.Uint64() == 2 && head.ParentHash != h1.Hash() { t.Errorf("failed to build block on fork") } }