8b99ad4602
This change fixes #28355, where eth_getProof failed to return the correct codehash under certain conditions. This PR changes the logic to unconditionally look up the codehash, and also adds some more tests.
529 lines
16 KiB
Go
529 lines
16 KiB
Go
// Copyright 2021 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 gethclient
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/ethereum/go-ethereum"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
"github.com/ethereum/go-ethereum/core"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/eth"
|
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
|
"github.com/ethereum/go-ethereum/eth/filters"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
"github.com/ethereum/go-ethereum/node"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
)
|
|
|
|
var (
|
|
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
|
testContract = common.HexToAddress("0xbeef")
|
|
testSlot = common.HexToHash("0xdeadbeef")
|
|
testValue = crypto.Keccak256Hash(testSlot[:])
|
|
testBalance = big.NewInt(2e15)
|
|
)
|
|
|
|
func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
|
|
// Generate test chain.
|
|
genesis, blocks := generateTestChain()
|
|
// Create node
|
|
n, err := node.New(&node.Config{})
|
|
if err != nil {
|
|
t.Fatalf("can't create new node: %v", err)
|
|
}
|
|
// Create Ethereum Service
|
|
config := ðconfig.Config{Genesis: genesis}
|
|
ethservice, err := eth.New(n, config)
|
|
if err != nil {
|
|
t.Fatalf("can't create new ethereum service: %v", err)
|
|
}
|
|
filterSystem := filters.NewFilterSystem(ethservice.APIBackend, filters.Config{})
|
|
n.RegisterAPIs([]rpc.API{{
|
|
Namespace: "eth",
|
|
Service: filters.NewFilterAPI(filterSystem, false),
|
|
}})
|
|
|
|
// Import the test chain.
|
|
if err := n.Start(); err != nil {
|
|
t.Fatalf("can't start test node: %v", err)
|
|
}
|
|
if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil {
|
|
t.Fatalf("can't import test blocks: %v", err)
|
|
}
|
|
return n, blocks
|
|
}
|
|
|
|
func generateTestChain() (*core.Genesis, []*types.Block) {
|
|
genesis := &core.Genesis{
|
|
Config: params.AllEthashProtocolChanges,
|
|
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}},
|
|
testContract: {Nonce: 1, Code: []byte{0x13, 0x37}}},
|
|
ExtraData: []byte("test genesis"),
|
|
Timestamp: 9000,
|
|
}
|
|
generate := func(i int, g *core.BlockGen) {
|
|
g.OffsetTime(5)
|
|
g.SetExtra([]byte("test"))
|
|
}
|
|
_, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, generate)
|
|
blocks = append([]*types.Block{genesis.ToBlock()}, blocks...)
|
|
return genesis, blocks
|
|
}
|
|
|
|
func TestGethClient(t *testing.T) {
|
|
backend, _ := newTestBackend(t)
|
|
client := backend.Attach()
|
|
defer backend.Close()
|
|
defer client.Close()
|
|
|
|
tests := []struct {
|
|
name string
|
|
test func(t *testing.T)
|
|
}{
|
|
{
|
|
"TestGetProof1",
|
|
func(t *testing.T) { testGetProof(t, client, testAddr) },
|
|
}, {
|
|
"TestGetProof2",
|
|
func(t *testing.T) { testGetProof(t, client, testContract) },
|
|
}, {
|
|
"TestGetProofCanonicalizeKeys",
|
|
func(t *testing.T) { testGetProofCanonicalizeKeys(t, client) },
|
|
}, {
|
|
"TestGCStats",
|
|
func(t *testing.T) { testGCStats(t, client) },
|
|
}, {
|
|
"TestMemStats",
|
|
func(t *testing.T) { testMemStats(t, client) },
|
|
}, {
|
|
"TestGetNodeInfo",
|
|
func(t *testing.T) { testGetNodeInfo(t, client) },
|
|
}, {
|
|
"TestSubscribePendingTxHashes",
|
|
func(t *testing.T) { testSubscribePendingTransactions(t, client) },
|
|
}, {
|
|
"TestSubscribePendingTxs",
|
|
func(t *testing.T) { testSubscribeFullPendingTransactions(t, client) },
|
|
}, {
|
|
"TestCallContract",
|
|
func(t *testing.T) { testCallContract(t, client) },
|
|
}, {
|
|
"TestCallContractWithBlockOverrides",
|
|
func(t *testing.T) { testCallContractWithBlockOverrides(t, client) },
|
|
},
|
|
// The testaccesslist is a bit time-sensitive: the newTestBackend imports
|
|
// one block. The `testAcessList` fails if the miner has not yet created a
|
|
// new pending-block after the import event.
|
|
// Hence: this test should be last, execute the tests serially.
|
|
{
|
|
"TestAccessList",
|
|
func(t *testing.T) { testAccessList(t, client) },
|
|
}, {
|
|
"TestSetHead",
|
|
func(t *testing.T) { testSetHead(t, client) },
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, tt.test)
|
|
}
|
|
}
|
|
|
|
func testAccessList(t *testing.T, client *rpc.Client) {
|
|
ec := New(client)
|
|
// Test transfer
|
|
msg := ethereum.CallMsg{
|
|
From: testAddr,
|
|
To: &common.Address{},
|
|
Gas: 21000,
|
|
GasPrice: big.NewInt(765625000),
|
|
Value: big.NewInt(1),
|
|
}
|
|
al, gas, vmErr, err := ec.CreateAccessList(context.Background(), msg)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if vmErr != "" {
|
|
t.Fatalf("unexpected vm error: %v", vmErr)
|
|
}
|
|
if gas != 21000 {
|
|
t.Fatalf("unexpected gas used: %v", gas)
|
|
}
|
|
if len(*al) != 0 {
|
|
t.Fatalf("unexpected length of accesslist: %v", len(*al))
|
|
}
|
|
// Test reverting transaction
|
|
msg = ethereum.CallMsg{
|
|
From: testAddr,
|
|
To: nil,
|
|
Gas: 100000,
|
|
GasPrice: big.NewInt(1000000000),
|
|
Value: big.NewInt(1),
|
|
Data: common.FromHex("0x608060806080608155fd"),
|
|
}
|
|
al, gas, vmErr, err = ec.CreateAccessList(context.Background(), msg)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if vmErr == "" {
|
|
t.Fatalf("wanted vmErr, got none")
|
|
}
|
|
if gas == 21000 {
|
|
t.Fatalf("unexpected gas used: %v", gas)
|
|
}
|
|
if len(*al) != 1 || al.StorageKeys() != 1 {
|
|
t.Fatalf("unexpected length of accesslist: %v", len(*al))
|
|
}
|
|
// address changes between calls, so we can't test for it.
|
|
if (*al)[0].Address == common.HexToAddress("0x0") {
|
|
t.Fatalf("unexpected address: %v", (*al)[0].Address)
|
|
}
|
|
if (*al)[0].StorageKeys[0] != common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000081") {
|
|
t.Fatalf("unexpected storage key: %v", (*al)[0].StorageKeys[0])
|
|
}
|
|
}
|
|
|
|
func testGetProof(t *testing.T, client *rpc.Client, addr common.Address) {
|
|
ec := New(client)
|
|
ethcl := ethclient.NewClient(client)
|
|
result, err := ec.GetProof(context.Background(), addr, []string{testSlot.String()}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Address != addr {
|
|
t.Fatalf("unexpected address, have: %v want: %v", result.Address, addr)
|
|
}
|
|
// test nonce
|
|
if nonce, _ := ethcl.NonceAt(context.Background(), addr, nil); result.Nonce != nonce {
|
|
t.Fatalf("invalid nonce, want: %v got: %v", nonce, result.Nonce)
|
|
}
|
|
// test balance
|
|
if balance, _ := ethcl.BalanceAt(context.Background(), addr, nil); result.Balance.Cmp(balance) != 0 {
|
|
t.Fatalf("invalid balance, want: %v got: %v", balance, result.Balance)
|
|
}
|
|
// test storage
|
|
if len(result.StorageProof) != 1 {
|
|
t.Fatalf("invalid storage proof, want 1 proof, got %v proof(s)", len(result.StorageProof))
|
|
}
|
|
for _, proof := range result.StorageProof {
|
|
if proof.Key != testSlot.String() {
|
|
t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), proof.Key)
|
|
}
|
|
slotValue, _ := ethcl.StorageAt(context.Background(), addr, common.HexToHash(proof.Key), nil)
|
|
if have, want := common.BigToHash(proof.Value), common.BytesToHash(slotValue); have != want {
|
|
t.Fatalf("addr %x, invalid storage proof value: have: %v, want: %v", addr, have, want)
|
|
}
|
|
}
|
|
// test code
|
|
code, _ := ethcl.CodeAt(context.Background(), addr, nil)
|
|
if have, want := result.CodeHash, crypto.Keccak256Hash(code); have != want {
|
|
t.Fatalf("codehash wrong, have %v want %v ", have, want)
|
|
}
|
|
}
|
|
|
|
func testGetProofCanonicalizeKeys(t *testing.T, client *rpc.Client) {
|
|
ec := New(client)
|
|
|
|
// Tests with non-canon input for storage keys.
|
|
// Here we check that the storage key is canonicalized.
|
|
result, err := ec.GetProof(context.Background(), testAddr, []string{"0x0dEadbeef"}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.StorageProof[0].Key != "0xdeadbeef" {
|
|
t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key)
|
|
}
|
|
if result, err = ec.GetProof(context.Background(), testAddr, []string{"0x000deadbeef"}, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.StorageProof[0].Key != "0xdeadbeef" {
|
|
t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key)
|
|
}
|
|
|
|
// If the requested storage key is 32 bytes long, it will be returned as is.
|
|
hashSizedKey := "0x00000000000000000000000000000000000000000000000000000000deadbeef"
|
|
result, err = ec.GetProof(context.Background(), testAddr, []string{hashSizedKey}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.StorageProof[0].Key != hashSizedKey {
|
|
t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key)
|
|
}
|
|
}
|
|
|
|
func testGCStats(t *testing.T, client *rpc.Client) {
|
|
ec := New(client)
|
|
_, err := ec.GCStats(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func testMemStats(t *testing.T, client *rpc.Client) {
|
|
ec := New(client)
|
|
stats, err := ec.MemStats(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if stats.Alloc == 0 {
|
|
t.Fatal("Invalid mem stats retrieved")
|
|
}
|
|
}
|
|
|
|
func testGetNodeInfo(t *testing.T, client *rpc.Client) {
|
|
ec := New(client)
|
|
info, err := ec.GetNodeInfo(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if info.Name == "" {
|
|
t.Fatal("Invalid node info retrieved")
|
|
}
|
|
}
|
|
|
|
func testSetHead(t *testing.T, client *rpc.Client) {
|
|
ec := New(client)
|
|
err := ec.SetHead(context.Background(), big.NewInt(0))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func testSubscribePendingTransactions(t *testing.T, client *rpc.Client) {
|
|
ec := New(client)
|
|
ethcl := ethclient.NewClient(client)
|
|
// Subscribe to Transactions
|
|
ch := make(chan common.Hash)
|
|
ec.SubscribePendingTransactions(context.Background(), ch)
|
|
// Send a transaction
|
|
chainID, err := ethcl.ChainID(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Create transaction
|
|
tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil)
|
|
signer := types.LatestSignerForChainID(chainID)
|
|
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
signedTx, err := tx.WithSignature(signer, signature)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Send transaction
|
|
err = ethcl.SendTransaction(context.Background(), signedTx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Check that the transaction was sent over the channel
|
|
hash := <-ch
|
|
if hash != signedTx.Hash() {
|
|
t.Fatalf("Invalid tx hash received, got %v, want %v", hash, signedTx.Hash())
|
|
}
|
|
}
|
|
|
|
func testSubscribeFullPendingTransactions(t *testing.T, client *rpc.Client) {
|
|
ec := New(client)
|
|
ethcl := ethclient.NewClient(client)
|
|
// Subscribe to Transactions
|
|
ch := make(chan *types.Transaction)
|
|
ec.SubscribeFullPendingTransactions(context.Background(), ch)
|
|
// Send a transaction
|
|
chainID, err := ethcl.ChainID(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Create transaction
|
|
tx := types.NewTransaction(1, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil)
|
|
signer := types.LatestSignerForChainID(chainID)
|
|
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
signedTx, err := tx.WithSignature(signer, signature)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Send transaction
|
|
err = ethcl.SendTransaction(context.Background(), signedTx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Check that the transaction was sent over the channel
|
|
tx = <-ch
|
|
if tx.Hash() != signedTx.Hash() {
|
|
t.Fatalf("Invalid tx hash received, got %v, want %v", tx.Hash(), signedTx.Hash())
|
|
}
|
|
}
|
|
|
|
func testCallContract(t *testing.T, client *rpc.Client) {
|
|
ec := New(client)
|
|
msg := ethereum.CallMsg{
|
|
From: testAddr,
|
|
To: &common.Address{},
|
|
Gas: 21000,
|
|
GasPrice: big.NewInt(1000000000),
|
|
Value: big.NewInt(1),
|
|
}
|
|
// CallContract without override
|
|
if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), nil); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
// CallContract with override
|
|
override := OverrideAccount{
|
|
Nonce: 1,
|
|
}
|
|
mapAcc := make(map[common.Address]OverrideAccount)
|
|
mapAcc[testAddr] = override
|
|
if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOverrideAccountMarshal(t *testing.T) {
|
|
om := map[common.Address]OverrideAccount{
|
|
{0x11}: {
|
|
// Zero-valued nonce is not overriddden, but simply dropped by the encoder.
|
|
Nonce: 0,
|
|
},
|
|
{0xaa}: {
|
|
Nonce: 5,
|
|
},
|
|
{0xbb}: {
|
|
Code: []byte{1},
|
|
},
|
|
{0xcc}: {
|
|
// 'code', 'balance', 'state' should be set when input is
|
|
// a non-nil but empty value.
|
|
Code: []byte{},
|
|
Balance: big.NewInt(0),
|
|
State: map[common.Hash]common.Hash{},
|
|
// For 'stateDiff' the behavior is different, empty map
|
|
// is ignored because it makes no difference.
|
|
StateDiff: map[common.Hash]common.Hash{},
|
|
},
|
|
}
|
|
|
|
marshalled, err := json.MarshalIndent(&om, "", " ")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
expected := `{
|
|
"0x1100000000000000000000000000000000000000": {},
|
|
"0xaa00000000000000000000000000000000000000": {
|
|
"nonce": "0x5"
|
|
},
|
|
"0xbb00000000000000000000000000000000000000": {
|
|
"code": "0x01"
|
|
},
|
|
"0xcc00000000000000000000000000000000000000": {
|
|
"code": "0x",
|
|
"balance": "0x0",
|
|
"state": {}
|
|
}
|
|
}`
|
|
|
|
if string(marshalled) != expected {
|
|
t.Error("wrong output:", string(marshalled))
|
|
t.Error("want:", expected)
|
|
}
|
|
}
|
|
|
|
func TestBlockOverridesMarshal(t *testing.T) {
|
|
for i, tt := range []struct {
|
|
bo BlockOverrides
|
|
want string
|
|
}{
|
|
{
|
|
bo: BlockOverrides{},
|
|
want: `{}`,
|
|
},
|
|
{
|
|
bo: BlockOverrides{
|
|
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
|
|
},
|
|
want: `{"coinbase":"0x1111111111111111111111111111111111111111"}`,
|
|
},
|
|
{
|
|
bo: BlockOverrides{
|
|
Number: big.NewInt(1),
|
|
Difficulty: big.NewInt(2),
|
|
Time: 3,
|
|
GasLimit: 4,
|
|
BaseFee: big.NewInt(5),
|
|
},
|
|
want: `{"number":"0x1","difficulty":"0x2","time":"0x3","gasLimit":"0x4","baseFee":"0x5"}`,
|
|
},
|
|
} {
|
|
marshalled, err := json.Marshal(&tt.bo)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if string(marshalled) != tt.want {
|
|
t.Errorf("Testcase #%d failed. expected\n%s\ngot\n%s", i, tt.want, string(marshalled))
|
|
}
|
|
}
|
|
}
|
|
|
|
func testCallContractWithBlockOverrides(t *testing.T, client *rpc.Client) {
|
|
ec := New(client)
|
|
msg := ethereum.CallMsg{
|
|
From: testAddr,
|
|
To: &common.Address{},
|
|
Gas: 50000,
|
|
GasPrice: big.NewInt(1000000000),
|
|
Value: big.NewInt(1),
|
|
}
|
|
override := OverrideAccount{
|
|
// Returns coinbase address.
|
|
Code: common.FromHex("0x41806000526014600cf3"),
|
|
}
|
|
mapAcc := make(map[common.Address]OverrideAccount)
|
|
mapAcc[common.Address{}] = override
|
|
res, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !bytes.Equal(res, common.FromHex("0x0000000000000000000000000000000000000000")) {
|
|
t.Fatalf("unexpected result: %x", res)
|
|
}
|
|
|
|
// Now test with block overrides
|
|
bo := BlockOverrides{
|
|
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
|
|
}
|
|
res, err = ec.CallContractWithBlockOverrides(context.Background(), msg, big.NewInt(0), &mapAcc, bo)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !bytes.Equal(res, common.FromHex("0x1111111111111111111111111111111111111111")) {
|
|
t.Fatalf("unexpected result: %x", res)
|
|
}
|
|
}
|