go-ethereum/ethclient/simulated/backend_test.go
Daniel Liu 3fad5e1ab4
refactor(ethclient): implement new sim backend #28202 (#2062)
Introduce a new simulated backend implementation under ethclient/simulated and migrate bind-facing wrappers/tests to use it.

Key changes:

- Add the new backend entry points and behavior in ethclient/simulated, including chain control helpers used by tests (commit/rollback/fork/time adjustment).

- Update legacy bind wrappers in accounts/abi/bind/backends to delegate to the new simulated backend while preserving old APIs.

- Align bind v2/backend and utility tests to the new simulated backend client surface and transaction flow.

- Refresh abigen/bind tests and shared interfaces impacted by the backend migration.

Compatibility notes:

- Keep backward-compatible wrapper constructors for existing contract test code.

- Preserve legacy transaction paths in tests while supporting EIP-1559-aware flows where applicable.

Validation:

- Verified affected packages compile and pass tests.

- Verified repository-wide go test ./... passes after follow-up compatibility fixes.
2026-03-06 11:09:10 +05:30

1304 lines
44 KiB
Go

// Copyright 2019 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 (
"bytes"
"context"
"crypto/ecdsa"
"errors"
"math/big"
"math/rand"
"reflect"
"strings"
"testing"
"time"
ethereum "github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/params"
)
var _ bind.ContractBackend = (Client)(nil)
var (
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
)
const callableAbi = "[{\"anonymous\":false,\"inputs\":[],\"name\":\"Called\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"Call\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"
const callableBin = "6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806334e2292114602d575b600080fd5b60336035565b005b7f81fab7a4a0aa961db47eefc81f143a5220e8c8495260dd65b1356f1d19d3c7b860405160405180910390a156fea2646970667358221220029436d24f3ac598ceca41d4d712e13ced6d70727f4cdc580667de66d2f51d8b64736f6c63430008010033"
const abiJSON = `[ { "constant": false, "inputs": [ { "name": "memo", "type": "bytes" } ], "name": "receive", "outputs": [ { "name": "res", "type": "string" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" }, { "indexed": false, "name": "amount", "type": "uint256" }, { "indexed": false, "name": "memo", "type": "bytes" } ], "name": "received", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" } ], "name": "receivedAddr", "type": "event" } ]`
const abiBin = `0x608060405234801561001057600080fd5b506102a0806100206000396000f3fe60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029`
const deployedCode = `60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029`
var expectedReturn = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
func simTestBackend(testAddr common.Address) *Backend {
return New(
types.GenesisAlloc{
testAddr: {Balance: big.NewInt(10000000000000000)},
}, 10000000,
)
}
func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
client := sim.Client()
// create a signed transaction to send
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, _ := client.ChainID(context.Background())
nonce, err := client.PendingNonceAt(context.Background(), addr)
if err != nil {
return nil, err
}
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: chainid,
Nonce: nonce,
GasTipCap: big.NewInt(1),
GasFeeCap: gasPrice,
Gas: 21000,
To: &addr,
})
return types.SignTx(tx, types.LatestSignerForChainID(chainid), key)
}
func newContractCreationTx(sim *Backend, key *ecdsa.PrivateKey, bytecode []byte, gas uint64) (*types.Transaction, common.Address, error) {
client := sim.Client()
head, _ := client.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
gasFeeCap := new(big.Int).Add(head.BaseFee, big.NewInt(1))
from := crypto.PubkeyToAddress(key.PublicKey)
chainID, _ := client.ChainID(context.Background())
nonce, err := client.PendingNonceAt(context.Background(), from)
if err != nil {
return nil, common.Address{}, err
}
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: chainID,
Nonce: nonce,
GasTipCap: big.NewInt(1),
GasFeeCap: gasFeeCap,
Gas: gas,
Data: bytecode,
})
signed, err := types.SignTx(tx, types.LatestSignerForChainID(chainID), key)
if err != nil {
return nil, common.Address{}, err
}
return signed, crypto.CreateAddress(from, nonce), nil
}
func TestSimulatedBackend(t *testing.T) {
t.Parallel()
key, _ := crypto.GenerateKey()
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
alloc := types.GenesisAlloc{auth.From: {Balance: big.NewInt(9223372036854775807)}}
sim := New(alloc, 8000029)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
_, pending, err := client.TransactionByHash(ctx, common.HexToHash("0x2"))
if pending || !errors.Is(err, ethereum.ErrNotFound) {
t.Fatalf("expected not found and not pending, got err=%v pending=%v", err, pending)
}
head, _ := client.HeaderByNumber(ctx, nil)
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
tx := types.NewContractCreation(0, big.NewInt(0), 3000000, gasPrice, common.FromHex("6060604052600a8060106000396000f360606040526008565b00"))
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, key)
if err := client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("send tx failed: %v", err)
}
_, pending, err = client.TransactionByHash(ctx, tx.Hash())
if err != nil || !pending {
t.Fatalf("expected pending tx, err=%v pending=%v", err, pending)
}
sim.Commit()
_, pending, err = client.TransactionByHash(ctx, tx.Hash())
if err != nil || pending {
t.Fatalf("expected mined tx, err=%v pending=%v", err, pending)
}
}
func TestNewSimulatedBackend(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
bal, err := sim.Client().BalanceAt(context.Background(), testAddr, nil)
if err != nil {
t.Fatal(err)
}
if bal.Cmp(big.NewInt(10000000000000000)) != 0 {
t.Fatalf("unexpected balance %v", bal)
}
}
func TestAdjustTime(t *testing.T) {
sim := New(types.GenesisAlloc{}, 10_000_000)
defer sim.Close()
client := sim.Client()
block1, _ := client.BlockByNumber(context.Background(), nil)
// Create a block
if err := sim.AdjustTime(time.Minute); err != nil {
t.Fatal(err)
}
block2, _ := client.BlockByNumber(context.Background(), nil)
prevTime := block1.Time()
newTime := block2.Time()
if newTime-prevTime != uint64(time.Minute.Seconds()) {
t.Errorf("adjusted time not equal to 60 seconds. prev: %v, new: %v", prevTime, newTime)
}
}
func TestNewAdjustTimeFail(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
head, _ := client.HeaderByNumber(ctx, nil)
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
signedTx, _ := types.SignTx(tx, types.HomesteadSigner{}, testKey)
_ = client.SendTransaction(ctx, signedTx)
if err := sim.AdjustTime(time.Second); err == nil {
t.Fatal("expected adjust time to fail on non-empty block")
}
sim.Commit()
prevTime := sim.pendingBlock.Time()
if err := sim.AdjustTime(time.Minute); err != nil {
t.Fatal(err)
}
newTime := sim.pendingBlock.Time()
if newTime-prevTime != uint64(time.Minute.Seconds()) {
t.Fatalf("adjusted time mismatch")
}
tx2 := types.NewTransaction(1, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
signedTx2, _ := types.SignTx(tx2, types.HomesteadSigner{}, testKey)
_ = client.SendTransaction(ctx, signedTx2)
sim.Commit()
newTime = sim.pendingBlock.Time()
if newTime < prevTime {
t.Fatalf("time moved backwards unexpectedly")
}
}
func TestBalanceAt(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
bal, err := sim.Client().BalanceAt(context.Background(), testAddr, nil)
if err != nil {
t.Fatal(err)
}
if bal.Cmp(big.NewInt(10000000000000000)) != 0 {
t.Fatalf("unexpected balance %v", bal)
}
}
func TestBlockByHash(t *testing.T) {
t.Parallel()
sim := New(types.GenesisAlloc{}, 10000000)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
block, _ := client.BlockByNumber(ctx, nil)
byHash, err := client.BlockByHash(ctx, block.Hash())
if err != nil || byHash.Hash() != block.Hash() {
t.Fatalf("block by hash mismatch: err=%v", err)
}
}
func TestBlockByNumber(t *testing.T) {
t.Parallel()
sim := New(types.GenesisAlloc{}, 10000000)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
block, _ := client.BlockByNumber(ctx, nil)
if block.NumberU64() != 0 {
t.Fatalf("expected block 0")
}
sim.Commit()
latest, _ := client.BlockByNumber(ctx, nil)
if latest.NumberU64() != 1 {
t.Fatalf("expected block 1")
}
one, err := client.BlockByNumber(ctx, big.NewInt(1))
if err != nil || one.Hash() != latest.Hash() {
t.Fatalf("block by number mismatch: err=%v", err)
}
}
func TestNonceAt(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
nonce, _ := client.NonceAt(ctx, testAddr, big.NewInt(0))
if nonce != 0 {
t.Fatalf("expected nonce 0")
}
head, _ := client.HeaderByNumber(ctx, nil)
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
tx := types.NewTransaction(nonce, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
signed, _ := types.SignTx(tx, types.HomesteadSigner{}, testKey)
_ = client.SendTransaction(ctx, signed)
sim.Commit()
n1, _ := client.NonceAt(ctx, testAddr, big.NewInt(1))
if n1 != 1 {
t.Fatalf("expected nonce 1")
}
sim.Commit()
n1Again, _ := client.NonceAt(ctx, testAddr, big.NewInt(1))
if n1Again != 1 {
t.Fatalf("expected historical nonce 1")
}
}
func TestSendTransaction(t *testing.T) {
sim := simTestBackend(testAddr)
defer sim.Close()
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 = client.SendTransaction(ctx, signedTx)
if err != nil {
t.Errorf("could not add tx to pending block: %v", err)
}
sim.Commit()
block, err := client.BlockByNumber(ctx, big.NewInt(1))
if err != nil {
t.Errorf("could not get block at height 1: %v", err)
}
if signedTx.Hash() != block.Transactions()[0].Hash() {
t.Errorf("did not commit sent transaction. expected hash %v got hash %v", block.Transactions()[0].Hash(), signedTx.Hash())
}
}
func TestTransactionByHash(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
head, _ := client.HeaderByNumber(ctx, nil)
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey)
if err != nil {
t.Fatalf("could not sign tx: %v", err)
}
if err := client.SendTransaction(ctx, signedTx); err != nil {
t.Fatalf("could not send tx: %v", err)
}
receivedTx, pending, err := client.TransactionByHash(ctx, signedTx.Hash())
if err != nil || !pending || receivedTx.Hash() != signedTx.Hash() {
t.Fatalf("expected pending tx by hash, err=%v pending=%v", err, pending)
}
sim.Commit()
receivedTx, pending, err = client.TransactionByHash(ctx, signedTx.Hash())
if err != nil || pending || receivedTx.Hash() != signedTx.Hash() {
t.Fatalf("expected mined tx by hash, err=%v pending=%v", err, pending)
}
}
func TestEstimateGas(t *testing.T) {
t.Parallel()
const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"
const contractBin = "0x60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040523480156100115760006000fd5b506004361061005c5760003560e01c806350f6fe3414610062578063aa8b1d301461006c578063b9b046f914610076578063d8b9839114610080578063e09fface1461008a5761005c565b60006000fd5b61006a610094565b005b6100746100ad565b005b61007e6100b5565b005b6100886100c2565b005b610092610135565b005b6000600090505b5b808060010191505061009b565b505b565b60006000fd5b565b600015156100bf57fe5b5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f72657665727420726561736f6e0000000000000000000000000000000000000081526020015060200191505060405180910390fd5b565b5b56fea2646970667358221220345bbcbb1a5ecf22b53a78eaebf95f8ee0eceff6d10d4b9643495084d2ec934a64736f6c63430006040033"
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
sim := New(types.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, 10000000)
defer sim.Close()
parsed, _ := abi.JSON(strings.NewReader(contractAbi))
contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim)
sim.Commit()
cases := []struct {
message ethereum.CallMsg
expect uint64
expectError error
expectData interface{}
}{
{ethereum.CallMsg{From: addr, To: &addr, GasPrice: big.NewInt(0), Value: big.NewInt(1)}, params.TxGas, nil, nil},
{ethereum.CallMsg{From: addr, To: &contractAddr, GasPrice: big.NewInt(0), Value: big.NewInt(1)}, 0, errors.New("execution reverted"), nil},
{ethereum.CallMsg{From: addr, To: &contractAddr, GasPrice: big.NewInt(0), Data: common.Hex2Bytes("d8b98391")}, 0, errors.New("execution reverted: revert reason"), "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000"},
{ethereum.CallMsg{From: addr, To: &contractAddr, GasPrice: big.NewInt(0), Data: common.Hex2Bytes("aa8b1d30")}, 0, errors.New("execution reverted"), nil},
{ethereum.CallMsg{From: addr, To: &contractAddr, Gas: 100000, GasPrice: big.NewInt(0), Data: common.Hex2Bytes("50f6fe34")}, 0, errors.New("gas required exceeds allowance (100000)"), nil},
{ethereum.CallMsg{From: addr, To: &contractAddr, Gas: 100000, GasPrice: big.NewInt(0), Data: common.Hex2Bytes("b9b046f9")}, 0, errors.New("invalid opcode: INVALID"), nil},
{ethereum.CallMsg{From: addr, To: &contractAddr, Gas: 100000, GasPrice: big.NewInt(0), Data: common.Hex2Bytes("e09fface")}, 21483, nil, nil},
}
for _, c := range cases {
got, err := sim.EstimateGas(context.Background(), c.message)
if c.expectError != nil {
if err == nil || err.Error() != c.expectError.Error() {
t.Fatalf("expected error %v, got %v", c.expectError, err)
}
if c.expectData != nil {
rerr, ok := err.(*revertError)
if !ok || !reflect.DeepEqual(rerr.ErrorData(), c.expectData) {
t.Fatalf("revert data mismatch")
}
}
continue
}
if got != c.expect {
t.Fatalf("gas mismatch, want %d got %d", c.expect, got)
}
}
}
func TestEstimateGasWithPrice(t *testing.T) {
t.Parallel()
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
sim := New(types.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}, 10000000)
defer sim.Close()
recipient := common.HexToAddress("deadbeef")
cases := []ethereum.CallMsg{
{From: addr, To: &recipient, GasPrice: big.NewInt(0), Value: big.NewInt(100000000000)},
{From: addr, To: &recipient, GasPrice: big.NewInt(100000000000), Value: big.NewInt(100000000000)},
{From: addr, To: &recipient, GasPrice: big.NewInt(1e14), Value: big.NewInt(1e17)},
}
for i, c := range cases {
got, err := sim.EstimateGas(context.Background(), c)
if err != nil || got != 21000 {
t.Fatalf("case %d failed, gas=%d err=%v", i, got, err)
}
}
}
func TestHeaderByHash(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
ctx := context.Background()
client := sim.Client()
head, _ := client.HeaderByNumber(ctx, nil)
byHash, err := client.HeaderByHash(ctx, head.Hash())
if err != nil || byHash.Hash() != head.Hash() {
t.Fatalf("header by hash mismatch: err=%v", err)
}
}
func TestHeaderByNumber(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
ctx := context.Background()
client := sim.Client()
head0, _ := client.HeaderByNumber(ctx, nil)
if head0.Number.Uint64() != 0 {
t.Fatalf("expected head 0")
}
sim.Commit()
latest, _ := client.HeaderByNumber(ctx, nil)
head1, _ := client.HeaderByNumber(ctx, big.NewInt(1))
if head1.Hash() != latest.Hash() || head1.Number.Uint64() != 1 {
t.Fatalf("header by number mismatch")
}
block1, _ := client.BlockByNumber(ctx, big.NewInt(1))
if block1.Hash() != head1.Hash() {
t.Fatalf("block/header hash mismatch")
}
}
func TestTransactionCount(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
ctx := context.Background()
client := sim.Client()
cur, _ := client.BlockByNumber(ctx, nil)
count, _ := sim.TransactionCount(ctx, cur.Hash())
if count != 0 {
t.Fatalf("expected 0 tx count")
}
head, _ := client.HeaderByNumber(ctx, nil)
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
signed, _ := types.SignTx(tx, types.HomesteadSigner{}, testKey)
_ = client.SendTransaction(ctx, signed)
sim.Commit()
last, _ := client.BlockByNumber(ctx, nil)
count, _ = sim.TransactionCount(ctx, last.Hash())
if count != 1 {
t.Fatalf("expected 1 tx count")
}
}
func TestTransactionInBlock(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
ctx := context.Background()
client := sim.Client()
if tx, err := sim.TransactionInBlock(ctx, sim.pendingBlock.Hash(), 0); err == nil || tx != nil {
t.Fatalf("expected missing tx in empty pending block")
}
head, _ := client.HeaderByNumber(ctx, nil)
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
signed, _ := types.SignTx(tx, types.HomesteadSigner{}, testKey)
_ = client.SendTransaction(ctx, signed)
sim.Commit()
last, _ := client.BlockByNumber(ctx, nil)
if tx1, err := sim.TransactionInBlock(ctx, last.Hash(), 1); err == nil || tx1 != nil {
t.Fatalf("expected missing tx at index 1")
}
tx0, err := sim.TransactionInBlock(ctx, last.Hash(), 0)
if err != nil || tx0.Hash() != signed.Hash() {
t.Fatalf("tx in block mismatch: err=%v", err)
}
}
func TestPendingNonceAt(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
ctx := context.Background()
client := sim.Client()
p0, _ := client.PendingNonceAt(ctx, testAddr)
if p0 != 0 {
t.Fatalf("expected pending nonce 0")
}
head, _ := client.HeaderByNumber(ctx, nil)
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
tx0 := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
signed0, _ := types.SignTx(tx0, types.HomesteadSigner{}, testKey)
_ = client.SendTransaction(ctx, signed0)
p1, _ := client.PendingNonceAt(ctx, testAddr)
if p1 != 1 {
t.Fatalf("expected pending nonce 1")
}
tx1 := types.NewTransaction(1, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
signed1, _ := types.SignTx(tx1, types.HomesteadSigner{}, testKey)
_ = client.SendTransaction(ctx, signed1)
p2, _ := client.PendingNonceAt(ctx, testAddr)
if p2 != 2 {
t.Fatalf("expected pending nonce 2")
}
}
func TestTransactionReceipt(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
ctx := context.Background()
client := sim.Client()
head, _ := client.HeaderByNumber(ctx, nil)
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil)
signed, _ := types.SignTx(tx, types.HomesteadSigner{}, testKey)
_ = client.SendTransaction(ctx, signed)
sim.Commit()
receipt, err := client.TransactionReceipt(ctx, signed.Hash())
if err != nil {
t.Fatal(err)
}
if receipt.TxHash != signed.Hash() {
t.Fatalf("receipt tx hash mismatch")
}
}
func TestSuggestGasPrice(t *testing.T) {
t.Parallel()
sim := New(types.GenesisAlloc{}, 10000000)
defer sim.Close()
price, err := sim.Client().SuggestGasPrice(context.Background())
if err != nil {
t.Fatal(err)
}
baseFee := sim.pendingBlock.Header().BaseFee
if baseFee == nil {
baseFee = big.NewInt(1)
}
if price.Cmp(baseFee) != 0 {
t.Fatalf("unexpected gas price %v want %v", price, baseFee)
}
}
func TestPendingCodeAt(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
ctx := context.Background()
client := sim.Client()
code, _ := client.CodeAt(ctx, testAddr, nil)
if len(code) != 0 {
t.Fatalf("expected no code at EOA")
}
parsed, _ := abi.JSON(strings.NewReader(abiJSON))
auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337))
contractAddr, _, _, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim)
if err != nil {
t.Fatal(err)
}
pendingCode, err := client.PendingCodeAt(ctx, contractAddr)
if err != nil || len(pendingCode) == 0 {
t.Fatalf("pending code unavailable: err=%v", err)
}
if !bytes.Equal(pendingCode, common.FromHex(deployedCode)) {
t.Fatalf("pending code mismatch")
}
}
func TestCodeAt(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
ctx := context.Background()
client := sim.Client()
code, _ := client.CodeAt(ctx, testAddr, nil)
if len(code) != 0 {
t.Fatalf("expected no code at EOA")
}
parsed, _ := abi.JSON(strings.NewReader(abiJSON))
auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337))
contractAddr, _, _, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim)
if err != nil {
t.Fatal(err)
}
sim.Commit()
code, err = client.CodeAt(ctx, contractAddr, nil)
if err != nil || len(code) == 0 {
t.Fatalf("code unavailable: err=%v", err)
}
if !bytes.Equal(code, common.FromHex(deployedCode)) {
t.Fatalf("code mismatch")
}
}
func TestCodeAtHash(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
ctx := context.Background()
client := sim.Client()
head, _ := client.HeaderByNumber(ctx, nil)
code, err := sim.CodeAtHash(ctx, testAddr, head.Hash())
if err != nil || len(code) != 0 {
t.Fatalf("expected no code at EOA: err=%v", err)
}
parsed, _ := abi.JSON(strings.NewReader(abiJSON))
auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337))
contractAddr, _, _, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim)
if err != nil {
t.Fatal(err)
}
blockHash := sim.Commit()
code, err = sim.CodeAtHash(ctx, contractAddr, blockHash)
if err != nil || len(code) == 0 {
t.Fatalf("code at hash unavailable: err=%v", err)
}
if !bytes.Equal(code, common.FromHex(deployedCode)) {
t.Fatalf("code at hash mismatch")
}
}
func TestPendingAndCallContract(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
ctx := context.Background()
parsed, _ := abi.JSON(strings.NewReader(abiJSON))
auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337))
addr, _, _, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim)
if err != nil {
t.Fatal(err)
}
input, _ := parsed.Pack("receive", []byte("X"))
res, err := sim.PendingCallContract(ctx, ethereum.CallMsg{From: testAddr, To: &addr, Data: input})
if err != nil || len(res) == 0 {
t.Fatalf("pending call failed: err=%v", err)
}
if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") {
t.Fatalf("unexpected pending call return")
}
blockHash := sim.Commit()
res, err = sim.CallContract(ctx, ethereum.CallMsg{From: testAddr, To: &addr, Data: input}, nil)
if err != nil || len(res) == 0 {
t.Fatalf("call failed: err=%v", err)
}
if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") {
t.Fatalf("unexpected call return")
}
res, err = sim.CallContractAtHash(ctx, ethereum.CallMsg{From: testAddr, To: &addr, Data: input}, blockHash)
if err != nil || len(res) == 0 {
t.Fatalf("call at hash failed: err=%v", err)
}
if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") {
t.Fatalf("unexpected call at hash return")
}
}
func TestCallContractRevert(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
bytecode := common.FromHex("6005600c60003960056000f360006000fd")
tx, contractAddr, err := newContractCreationTx(sim, testKey, bytecode, 300000)
if err != nil {
t.Fatalf("could not create deploy tx: %v", err)
}
if err := client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("could not send deploy tx: %v", err)
}
sim.Commit()
_, err = client.CallContract(ctx, ethereum.CallMsg{From: testAddr, To: &contractAddr}, nil)
if err == nil || !strings.Contains(err.Error(), "execution reverted") {
t.Fatalf("expected execution reverted error, got: %v", err)
}
}
func TestFork(t *testing.T) {
t.Parallel()
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
// 1.
parent, _ := client.HeaderByNumber(ctx, nil)
// 2.
n := int(rand.Int31n(21))
for i := 0; i < n; i++ {
sim.Commit()
}
// 3.
b, _ := client.BlockNumber(ctx)
if b != uint64(n) {
t.Error("wrong chain length")
}
// 4.
sim.Fork(parent.Hash())
// 5.
for i := 0; i < n+1; i++ {
sim.Commit()
}
// 6.
b, _ = client.BlockNumber(ctx)
if b != uint64(n+1) {
t.Error("wrong chain length")
}
}
func TestForkLogsReborn(t *testing.T) {
t.Parallel()
sim, client, ctx, auth, contract, _, _, parentHash := setupForkLogsRebornScenario(t)
defer sim.Close()
var err error
logs, sub, err := contract.WatchLogs(nil, "Called")
if err != nil {
t.Fatalf("watching logs: %v", err)
}
defer sub.Unsubscribe()
tx, err := contract.Transact(auth, "Call")
if err != nil {
t.Fatalf("sending contract tx: %v", err)
}
sim.Commit()
lg := mustReadWatchedLog(t, logs, sub, "included")
if lg.TxHash != tx.Hash() {
t.Fatalf("wrong included event tx hash: got %s want %s", lg.TxHash, tx.Hash())
}
if lg.Removed {
t.Fatalf("event should be included")
}
if err := sim.Fork(parentHash); err != nil {
t.Fatalf("forking: %v", err)
}
sim.Commit()
sim.Commit()
lg = mustReadWatchedLog(t, logs, sub, "removed")
if lg.TxHash != tx.Hash() {
t.Fatalf("wrong removed event tx hash: got %s want %s", lg.TxHash, tx.Hash())
}
if !lg.Removed {
t.Fatalf("event should be removed after reorg")
}
if err := client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("re-sending transaction: %v", err)
}
sim.Commit()
lg = mustReadWatchedLog(t, logs, sub, "reborn")
if lg.TxHash != tx.Hash() {
t.Fatalf("wrong reborn event tx hash: got %s want %s", lg.TxHash, tx.Hash())
}
if lg.Removed {
t.Fatalf("event should be reborn as included")
}
}
func TestForkResendTx(t *testing.T) {
t.Parallel()
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
// 1.
parent, _ := client.HeaderByNumber(ctx, nil)
// 2.
tx, err := newTx(sim, testKey)
if err != nil {
t.Fatalf("could not create transaction: %v", err)
}
client.SendTransaction(ctx, tx)
sim.Commit()
// 3.
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(parent.Hash()); err != nil {
t.Errorf("forking: %v", err)
}
// 5.
sim.Commit()
if err := client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("sending transaction: %v", err)
}
sim.Commit()
receipt, _ = client.TransactionReceipt(ctx, tx.Hash())
if h := receipt.BlockNumber.Uint64(); h != 2 {
t.Errorf("TX included in wrong block: %d", h)
}
}
func TestCommitReturnValue(t *testing.T) {
t.Parallel()
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
// Test if Commit returns the correct block hash
h1 := sim.Commit()
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, _ := 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)
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(h1); err != nil {
t.Errorf("forking: %v", err)
}
// Test if Commit returns the correct block hash after the reorg
h2fork := sim.Commit()
if h2 == h2fork {
t.Error("The block in the fork and the original block are the same block!")
}
if header, err := client.HeaderByHash(ctx, h2fork); err != nil || header == nil {
t.Error("Could not retrieve the just created block (side-chain)")
}
}
func TestAdjustTimeAfterFork(t *testing.T) {
t.Parallel()
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
sim.Commit() // h1
h1, _ := client.HeaderByNumber(ctx, nil)
sim.Commit() // h2
sim.Fork(h1.Hash())
sim.AdjustTime(1 * time.Second)
sim.Commit()
head, _ := client.HeaderByNumber(ctx, nil)
if head.Number.Uint64() == 2 && head.ParentHash != h1.Hash() {
t.Errorf("failed to build block on fork")
}
}
func TestNewSim(t *testing.T) {
sim := New(types.GenesisAlloc{}, 30_000_000)
defer sim.Close()
client := sim.Client()
num, err := client.BlockNumber(context.Background())
if err != nil {
t.Fatal(err)
}
if num != 0 {
t.Fatalf("expected 0 got %v", num)
}
// Create a block
sim.Commit()
num, err = client.BlockNumber(context.Background())
if err != nil {
t.Fatal(err)
}
if num != 1 {
t.Fatalf("expected 1 got %v", num)
}
}
func TestTransactionByHashLifecycle(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
if _, pending, err := client.TransactionByHash(ctx, common.HexToHash("0x1234")); !errors.Is(err, ethereum.ErrNotFound) || pending {
t.Fatalf("expected not found and not pending, got err=%v pending=%v", err, pending)
}
tx, err := newTx(sim, testKey)
if err != nil {
t.Fatalf("could not create tx: %v", err)
}
if err := client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("could not send tx: %v", err)
}
got, pending, err := client.TransactionByHash(ctx, tx.Hash())
if err != nil || !pending || got.Hash() != tx.Hash() {
t.Fatalf("expected pending tx before commit, got err=%v pending=%v", err, pending)
}
sim.Commit()
got, pending, err = client.TransactionByHash(ctx, tx.Hash())
if err != nil || pending || got.Hash() != tx.Hash() {
t.Fatalf("expected mined tx after commit, got err=%v pending=%v", err, pending)
}
}
func TestTransactionReceiptLifecycle(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
if _, err := client.TransactionReceipt(ctx, common.HexToHash("0x1234")); !errors.Is(err, ethereum.ErrNotFound) {
t.Fatalf("expected not found before tx mining, got %v", err)
}
tx, err := newTx(sim, testKey)
if err != nil {
t.Fatalf("could not create tx: %v", err)
}
if err := client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("could not send tx: %v", err)
}
sim.Commit()
receipt, err := client.TransactionReceipt(ctx, tx.Hash())
if err != nil {
t.Fatalf("could not fetch receipt: %v", err)
}
if receipt.TxHash != tx.Hash() || receipt.BlockNumber == nil || receipt.BlockNumber.Uint64() != 1 {
t.Fatalf("unexpected receipt content: tx=%s block=%v", receipt.TxHash, receipt.BlockNumber)
}
}
func TestSuggestGasPriceAndTipCap(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
head, err := client.HeaderByNumber(ctx, nil)
if err != nil {
t.Fatalf("could not fetch header: %v", err)
}
price, err := client.SuggestGasPrice(ctx)
if err != nil {
t.Fatalf("could not suggest gas price: %v", err)
}
if head.BaseFee != nil && price.Cmp(head.BaseFee) != 0 {
t.Fatalf("unexpected suggested gas price: got %v want %v", price, head.BaseFee)
}
tip, err := client.SuggestGasTipCap(ctx)
if err != nil {
t.Fatalf("could not suggest tip cap: %v", err)
}
if tip.Cmp(big.NewInt(1)) != 0 {
t.Fatalf("unexpected tip cap: got %v want 1", tip)
}
}
func TestEstimateGasSimpleTransfer(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
gas, err := client.EstimateGas(ctx, ethereum.CallMsg{
From: testAddr,
To: &testAddr,
})
if err != nil {
t.Fatalf("estimate gas failed: %v", err)
}
if gas < params.TxGas {
t.Fatalf("estimated gas too low: got %d want >= %d", gas, params.TxGas)
}
}
func TestFeeHistoryBasic(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
for i := 0; i < 3; i++ {
tx, err := newTx(sim, testKey)
if err != nil {
t.Fatalf("could not create tx: %v", err)
}
if err := client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("could not send tx: %v", err)
}
sim.Commit()
}
history, err := client.FeeHistory(ctx, 2, nil, []float64{50, 90})
if err != nil {
t.Fatalf("fee history failed: %v", err)
}
if history == nil || history.OldestBlock == nil {
t.Fatalf("fee history response is incomplete")
}
if len(history.BaseFee) != 3 || len(history.GasUsedRatio) != 2 || len(history.Reward) != 2 {
t.Fatalf("unexpected fee history lengths: base=%d gas=%d reward=%d", len(history.BaseFee), len(history.GasUsedRatio), len(history.Reward))
}
for i, rewards := range history.Reward {
if len(rewards) != 2 {
t.Fatalf("unexpected reward percentiles at block %d: got %d", i, len(rewards))
}
}
}
func TestPendingCodeAtAndCodeAt(t *testing.T) {
t.Parallel()
sim := simTestBackend(testAddr)
defer sim.Close()
client := sim.Client()
ctx := context.Background()
bytecode := common.FromHex("6005600c60003960056000f360006000fd")
tx, contractAddr, err := newContractCreationTx(sim, testKey, bytecode, 300000)
if err != nil {
t.Fatalf("could not create deploy tx: %v", err)
}
code, err := client.CodeAt(ctx, contractAddr, nil)
if err != nil {
t.Fatalf("could not query code before deploy: %v", err)
}
if len(code) != 0 {
t.Fatalf("expected empty code before deployment, got length %d", len(code))
}
if err := client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("could not send deploy tx: %v", err)
}
pendingCode, err := client.PendingCodeAt(ctx, contractAddr)
if err != nil {
t.Fatalf("could not query pending code: %v", err)
}
if len(pendingCode) == 0 {
t.Fatalf("expected pending code for contract")
}
sim.Commit()
code, err = client.CodeAt(ctx, contractAddr, nil)
if err != nil {
t.Fatalf("could not query code after deploy: %v", err)
}
if len(code) == 0 {
t.Fatalf("expected non-empty code after deployment")
}
}
func TestForkLogsRebornFilterLogs(t *testing.T) {
t.Parallel()
sim, client, ctx, auth, contract, contractAddr, calledEventID, parentHash := setupForkLogsRebornScenario(t)
defer sim.Close()
var err error
tx, err := contract.Transact(auth, "Call")
if err != nil {
t.Fatalf("sending contract tx: %v", err)
}
sim.Commit()
query := calledEventQuery(contractAddr, calledEventID, nil, nil)
logs, err := client.FilterLogs(ctx, query)
if err != nil {
t.Fatalf("filter logs before fork: %v", err)
}
if len(logs) != 1 || logs[0].TxHash != tx.Hash() {
t.Fatalf("expected exactly one canonical log before fork, got len=%d tx=%v", len(logs), logs)
}
if err := sim.Fork(parentHash); err != nil {
t.Fatalf("forking: %v", err)
}
sim.Commit()
sim.Commit()
logs, err = client.FilterLogs(ctx, query)
if err != nil {
t.Fatalf("filter logs after reorg removal: %v", err)
}
if len(logs) != 0 {
t.Fatalf("expected no canonical logs after reorg removal, got len=%d", len(logs))
}
if err := client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("re-sending transaction: %v", err)
}
sim.Commit()
logs, err = client.FilterLogs(ctx, query)
if err != nil {
t.Fatalf("filter logs after reborn: %v", err)
}
if len(logs) != 1 || logs[0].TxHash != tx.Hash() {
t.Fatalf("expected exactly one canonical log after reborn, got len=%d tx=%v", len(logs), logs)
}
}
func TestForkLogsRebornFilterLogsWithRange(t *testing.T) {
t.Parallel()
sim, client, ctx, auth, contract, contractAddr, calledEventID, parentHash := setupForkLogsRebornScenario(t)
defer sim.Close()
var err error
tx, err := contract.Transact(auth, "Call")
if err != nil {
t.Fatalf("sending contract tx: %v", err)
}
sim.Commit() // block 2
queryBlock2 := calledEventQuery(contractAddr, calledEventID, big.NewInt(2), big.NewInt(2))
logs, err := client.FilterLogs(ctx, queryBlock2)
if err != nil {
t.Fatalf("filter logs in [2,2] before reorg: %v", err)
}
if len(logs) != 1 || logs[0].TxHash != tx.Hash() || logs[0].BlockNumber != 2 {
t.Fatalf("expected one log at block 2 before reorg, got len=%d logs=%v", len(logs), logs)
}
if err := sim.Fork(parentHash); err != nil {
t.Fatalf("forking: %v", err)
}
sim.Commit() // block 2 on new branch
sim.Commit() // block 3 on new branch
logs, err = client.FilterLogs(ctx, queryBlock2)
if err != nil {
t.Fatalf("filter logs in [2,2] after reorg removal: %v", err)
}
if len(logs) != 0 {
t.Fatalf("expected zero logs at block 2 after reorg removal, got len=%d", len(logs))
}
if err := client.SendTransaction(ctx, tx); err != nil {
t.Fatalf("re-sending transaction: %v", err)
}
sim.Commit() // block 4 on new branch
queryBlock4 := calledEventQuery(contractAddr, calledEventID, big.NewInt(4), big.NewInt(4))
logs, err = client.FilterLogs(ctx, queryBlock4)
if err != nil {
t.Fatalf("filter logs in [4,4] after reborn: %v", err)
}
if len(logs) != 1 || logs[0].TxHash != tx.Hash() || logs[0].BlockNumber != 4 {
t.Fatalf("expected one log at block 4 after reborn, got len=%d logs=%v", len(logs), logs)
}
}
// TestFork check that the chain length after a reorg is correct.
// Steps:
// 1. Save the current block which will serve as parent for the fork.
// 2. Mine n blocks with n ∈ [0, 20].
// 3. Assert that the chain length is n.
// 4. Fork by using the parent block as ancestor.
// 5. Mine n+1 blocks which should trigger a reorg.
// 6. Assert that the chain length is n+1.
// Since Commit() was called 2n+1 times in total,
// having a chain length of just n+1 means that a reorg occurred.
// TestForkResendTx checks that re-sending a TX after a fork
// is possible and does not cause a "nonce mismatch" panic.
// Steps:
// 1. Save the current block which will serve as parent for the fork.
// 2. Send a transaction.
// 3. Check that the TX is included in block 1.
// 4. Fork by using the parent block as ancestor.
// 5. Mine a block, Re-send the transaction and mine another one.
// 6. Check that the TX is now included in block 2.
// TestAdjustTimeAfterFork ensures that after a fork, AdjustTime uses the pending fork
// block's parent rather than the canonical head's parent.
func setupForkLogsRebornScenario(t *testing.T) (*Backend, Client, context.Context, *bind.TransactOpts, *bind.BoundContract, common.Address, common.Hash, common.Hash) {
t.Helper()
sim := simTestBackend(testAddr)
client := sim.Client()
ctx := context.Background()
parsed, err := abi.JSON(strings.NewReader(callableAbi))
if err != nil {
t.Fatalf("parsing callable ABI: %v", err)
}
calledEvent, ok := parsed.Events["Called"]
if !ok {
t.Fatalf("missing Called event in ABI")
}
chainID, err := client.ChainID(ctx)
if err != nil {
t.Fatalf("fetching chain id: %v", err)
}
auth, err := bind.NewKeyedTransactorWithChainID(testKey, chainID)
if err != nil {
t.Fatalf("creating transactor: %v", err)
}
contractAddr, _, contract, err := bind.DeployContract(auth, parsed, common.FromHex(callableBin), client)
if err != nil {
t.Fatalf("deploying contract: %v", err)
}
sim.Commit()
parent, err := client.HeaderByNumber(ctx, nil)
if err != nil {
t.Fatalf("fetching parent header: %v", err)
}
return sim, client, ctx, auth, contract, contractAddr, calledEvent.ID, parent.Hash()
}
func mustReadWatchedLog(t *testing.T, logs <-chan types.Log, sub ethereum.Subscription, step string) types.Log {
t.Helper()
select {
case lg := <-logs:
return lg
case err := <-sub.Err():
t.Fatalf("subscription error at %s: %v", step, err)
case <-time.After(2 * time.Second):
t.Fatalf("timeout waiting for log at %s", step)
}
return types.Log{}
}
func calledEventQuery(contractAddr common.Address, calledEventID common.Hash, fromBlock, toBlock *big.Int) ethereum.FilterQuery {
return ethereum.FilterQuery{
FromBlock: fromBlock,
ToBlock: toBlock,
Addresses: []common.Address{contractAddr},
Topics: [][]common.Hash{{calledEventID}},
}
}