github.com/ethereum/go-ethereum@v1.16.1/eth/api_debug_test.go (about)

     1  // Copyright 2017 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 eth
    18  
    19  import (
    20  	"bytes"
    21  	"crypto/ecdsa"
    22  	"fmt"
    23  	"math/big"
    24  	"reflect"
    25  	"slices"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/davecgh/go-spew/spew"
    31  	"github.com/ethereum/go-ethereum/common"
    32  	"github.com/ethereum/go-ethereum/consensus/ethash"
    33  	"github.com/ethereum/go-ethereum/core"
    34  	"github.com/ethereum/go-ethereum/core/rawdb"
    35  	"github.com/ethereum/go-ethereum/core/state"
    36  	"github.com/ethereum/go-ethereum/core/tracing"
    37  	"github.com/ethereum/go-ethereum/core/types"
    38  	"github.com/ethereum/go-ethereum/crypto"
    39  	"github.com/ethereum/go-ethereum/params"
    40  	"github.com/ethereum/go-ethereum/triedb"
    41  	"github.com/holiman/uint256"
    42  	"github.com/stretchr/testify/assert"
    43  )
    44  
    45  var dumper = spew.ConfigState{Indent: "    "}
    46  
    47  type Account struct {
    48  	key  *ecdsa.PrivateKey
    49  	addr common.Address
    50  }
    51  
    52  func newAccounts(n int) (accounts []Account) {
    53  	for i := 0; i < n; i++ {
    54  		key, _ := crypto.GenerateKey()
    55  		addr := crypto.PubkeyToAddress(key.PublicKey)
    56  		accounts = append(accounts, Account{key: key, addr: addr})
    57  	}
    58  	slices.SortFunc(accounts, func(a, b Account) int { return a.addr.Cmp(b.addr) })
    59  	return accounts
    60  }
    61  
    62  // newTestBlockChain creates a new test blockchain. OBS: After test is done, teardown must be
    63  // invoked in order to release associated resources.
    64  func newTestBlockChain(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *core.BlockChain {
    65  	engine := ethash.NewFaker()
    66  	// Generate blocks for testing
    67  	_, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator)
    68  
    69  	// Import the canonical chain
    70  	options := &core.BlockChainConfig{
    71  		TrieCleanLimit: 256,
    72  		TrieDirtyLimit: 256,
    73  		TrieTimeLimit:  5 * time.Minute,
    74  		SnapshotLimit:  0,
    75  		Preimages:      true,
    76  		ArchiveMode:    true, // Archive mode
    77  	}
    78  	chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), gspec, engine, options)
    79  	if err != nil {
    80  		t.Fatalf("failed to create tester chain: %v", err)
    81  	}
    82  	if n, err := chain.InsertChain(blocks); err != nil {
    83  		t.Fatalf("block %d: failed to insert into chain: %v", n, err)
    84  	}
    85  	return chain
    86  }
    87  
    88  func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.Dump {
    89  	result := statedb.RawDump(&state.DumpConfig{
    90  		SkipCode:          true,
    91  		SkipStorage:       true,
    92  		OnlyWithAddresses: false,
    93  		Start:             start.Bytes(),
    94  		Max:               uint64(requestedNum),
    95  	})
    96  
    97  	if len(result.Accounts) != expectedNum {
    98  		t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts))
    99  	}
   100  	for addr, acc := range result.Accounts {
   101  		if strings.HasSuffix(addr, "pre") || acc.Address == nil {
   102  			t.Fatalf("account without prestate (address) returned: %v", addr)
   103  		}
   104  		if !statedb.Exist(*acc.Address) {
   105  			t.Fatalf("account not found in state %s", acc.Address.Hex())
   106  		}
   107  	}
   108  	return result
   109  }
   110  
   111  func TestAccountRange(t *testing.T) {
   112  	t.Parallel()
   113  
   114  	var (
   115  		mdb     = rawdb.NewMemoryDatabase()
   116  		statedb = state.NewDatabase(triedb.NewDatabase(mdb, &triedb.Config{Preimages: true}), nil)
   117  		sdb, _  = state.New(types.EmptyRootHash, statedb)
   118  		addrs   = [AccountRangeMaxResults * 2]common.Address{}
   119  		m       = map[common.Address]bool{}
   120  	)
   121  
   122  	for i := range addrs {
   123  		hash := common.HexToHash(fmt.Sprintf("%x", i))
   124  		addr := common.BytesToAddress(crypto.Keccak256Hash(hash.Bytes()).Bytes())
   125  		addrs[i] = addr
   126  		sdb.SetBalance(addrs[i], uint256.NewInt(1), tracing.BalanceChangeUnspecified)
   127  		if _, ok := m[addr]; ok {
   128  			t.Fatalf("bad")
   129  		} else {
   130  			m[addr] = true
   131  		}
   132  	}
   133  	root, _ := sdb.Commit(0, true, false)
   134  	sdb, _ = state.New(root, statedb)
   135  
   136  	trie, err := statedb.OpenTrie(root)
   137  	if err != nil {
   138  		t.Fatal(err)
   139  	}
   140  	accountRangeTest(t, &trie, sdb, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2)
   141  	// test pagination
   142  	firstResult := accountRangeTest(t, &trie, sdb, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults)
   143  	secondResult := accountRangeTest(t, &trie, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults)
   144  
   145  	hList := make([]common.Hash, 0)
   146  	for addr1, acc := range firstResult.Accounts {
   147  		// If address is non-available, then it makes no sense to compare
   148  		// them as they might be two different accounts.
   149  		if acc.Address == nil {
   150  			continue
   151  		}
   152  		if _, duplicate := secondResult.Accounts[addr1]; duplicate {
   153  			t.Fatalf("pagination test failed:  results should not overlap")
   154  		}
   155  		hList = append(hList, crypto.Keccak256Hash(acc.Address.Bytes()))
   156  	}
   157  	// Test to see if it's possible to recover from the middle of the previous
   158  	// set and get an even split between the first and second sets.
   159  	slices.SortFunc(hList, common.Hash.Cmp)
   160  	middleH := hList[AccountRangeMaxResults/2]
   161  	middleResult := accountRangeTest(t, &trie, sdb, middleH, AccountRangeMaxResults, AccountRangeMaxResults)
   162  	missing, infirst, insecond := 0, 0, 0
   163  	for h := range middleResult.Accounts {
   164  		if _, ok := firstResult.Accounts[h]; ok {
   165  			infirst++
   166  		} else if _, ok := secondResult.Accounts[h]; ok {
   167  			insecond++
   168  		} else {
   169  			missing++
   170  		}
   171  	}
   172  	if missing != 0 {
   173  		t.Fatalf("%d hashes in the 'middle' set were neither in the first not the second set", missing)
   174  	}
   175  	if infirst != AccountRangeMaxResults/2 {
   176  		t.Fatalf("Imbalance in the number of first-test results: %d != %d", infirst, AccountRangeMaxResults/2)
   177  	}
   178  	if insecond != AccountRangeMaxResults/2 {
   179  		t.Fatalf("Imbalance in the number of second-test results: %d != %d", insecond, AccountRangeMaxResults/2)
   180  	}
   181  }
   182  
   183  func TestEmptyAccountRange(t *testing.T) {
   184  	t.Parallel()
   185  
   186  	var (
   187  		statedb = state.NewDatabaseForTesting()
   188  		st, _   = state.New(types.EmptyRootHash, statedb)
   189  	)
   190  	// Commit(although nothing to flush) and re-init the statedb
   191  	st.Commit(0, true, false)
   192  	st, _ = state.New(types.EmptyRootHash, statedb)
   193  
   194  	results := st.RawDump(&state.DumpConfig{
   195  		SkipCode:          true,
   196  		SkipStorage:       true,
   197  		OnlyWithAddresses: true,
   198  		Max:               uint64(AccountRangeMaxResults),
   199  	})
   200  	if bytes.Equal(results.Next, (common.Hash{}).Bytes()) {
   201  		t.Fatalf("Empty results should not return a second page")
   202  	}
   203  	if len(results.Accounts) != 0 {
   204  		t.Fatalf("Empty state should not return addresses: %v", results.Accounts)
   205  	}
   206  }
   207  
   208  func TestStorageRangeAt(t *testing.T) {
   209  	t.Parallel()
   210  
   211  	// Create a state where account 0x010000... has a few storage entries.
   212  	var (
   213  		mdb    = rawdb.NewMemoryDatabase()
   214  		tdb    = triedb.NewDatabase(mdb, &triedb.Config{Preimages: true})
   215  		db     = state.NewDatabase(tdb, nil)
   216  		sdb, _ = state.New(types.EmptyRootHash, db)
   217  		addr   = common.Address{0x01}
   218  		keys   = []common.Hash{ // hashes of Keys of storage
   219  			common.HexToHash("340dd630ad21bf010b4e676dbfa9ba9a02175262d1fa356232cfde6cb5b47ef2"),
   220  			common.HexToHash("426fcb404ab2d5d8e61a3d918108006bbb0a9be65e92235bb10eefbdb6dcd053"),
   221  			common.HexToHash("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5"),
   222  			common.HexToHash("5723d2c3a83af9b735e3b7f21531e5623d183a9095a56604ead41f3582fdfb75"),
   223  		}
   224  		storage = storageMap{
   225  			keys[0]: {Key: &common.Hash{0x02}, Value: common.Hash{0x01}},
   226  			keys[1]: {Key: &common.Hash{0x04}, Value: common.Hash{0x02}},
   227  			keys[2]: {Key: &common.Hash{0x01}, Value: common.Hash{0x03}},
   228  			keys[3]: {Key: &common.Hash{0x03}, Value: common.Hash{0x04}},
   229  		}
   230  	)
   231  	for _, entry := range storage {
   232  		sdb.SetState(addr, *entry.Key, entry.Value)
   233  	}
   234  	root, _ := sdb.Commit(0, false, false)
   235  	sdb, _ = state.New(root, db)
   236  
   237  	// Check a few combinations of limit and start/end.
   238  	tests := []struct {
   239  		start []byte
   240  		limit int
   241  		want  StorageRangeResult
   242  	}{
   243  		{
   244  			start: []byte{}, limit: 0,
   245  			want: StorageRangeResult{storageMap{}, &keys[0]},
   246  		},
   247  		{
   248  			start: []byte{}, limit: 100,
   249  			want: StorageRangeResult{storage, nil},
   250  		},
   251  		{
   252  			start: []byte{}, limit: 2,
   253  			want: StorageRangeResult{storageMap{keys[0]: storage[keys[0]], keys[1]: storage[keys[1]]}, &keys[2]},
   254  		},
   255  		{
   256  			start: []byte{0x00}, limit: 4,
   257  			want: StorageRangeResult{storage, nil},
   258  		},
   259  		{
   260  			start: []byte{0x40}, limit: 2,
   261  			want: StorageRangeResult{storageMap{keys[1]: storage[keys[1]], keys[2]: storage[keys[2]]}, &keys[3]},
   262  		},
   263  	}
   264  	for _, test := range tests {
   265  		result, err := storageRangeAt(sdb, root, addr, test.start, test.limit)
   266  		if err != nil {
   267  			t.Error(err)
   268  		}
   269  		if !reflect.DeepEqual(result, test.want) {
   270  			t.Fatalf("wrong result for range %#x.., limit %d:\ngot %s\nwant %s",
   271  				test.start, test.limit, dumper.Sdump(result), dumper.Sdump(&test.want))
   272  		}
   273  	}
   274  }
   275  
   276  func TestGetModifiedAccounts(t *testing.T) {
   277  	t.Parallel()
   278  
   279  	// Initialize test accounts
   280  	accounts := newAccounts(4)
   281  	genesis := &core.Genesis{
   282  		Config: params.TestChainConfig,
   283  		Alloc: types.GenesisAlloc{
   284  			accounts[0].addr: {Balance: big.NewInt(params.Ether)},
   285  			accounts[1].addr: {Balance: big.NewInt(params.Ether)},
   286  			accounts[2].addr: {Balance: big.NewInt(params.Ether)},
   287  			accounts[3].addr: {Balance: big.NewInt(params.Ether)},
   288  		},
   289  	}
   290  	genBlocks := 1
   291  	signer := types.HomesteadSigner{}
   292  	blockChain := newTestBlockChain(t, genBlocks, genesis, func(_ int, b *core.BlockGen) {
   293  		// Transfer from account[0] to account[1]
   294  		//    value: 1000 wei
   295  		//    fee:   0 wei
   296  		for _, account := range accounts[:3] {
   297  			tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
   298  				Nonce:    0,
   299  				To:       &accounts[3].addr,
   300  				Value:    big.NewInt(1000),
   301  				Gas:      params.TxGas,
   302  				GasPrice: b.BaseFee(),
   303  				Data:     nil}),
   304  				signer, account.key)
   305  			b.AddTx(tx)
   306  		}
   307  	})
   308  	defer blockChain.Stop()
   309  
   310  	// Create a debug API instance.
   311  	api := NewDebugAPI(&Ethereum{blockchain: blockChain})
   312  
   313  	// Test GetModifiedAccountsByNumber
   314  	t.Run("GetModifiedAccountsByNumber", func(t *testing.T) {
   315  		addrs, err := api.GetModifiedAccountsByNumber(uint64(genBlocks), nil)
   316  		assert.NoError(t, err)
   317  		assert.Len(t, addrs, len(accounts)+1) // +1 for the coinbase
   318  		for _, account := range accounts {
   319  			if !slices.Contains(addrs, account.addr) {
   320  				t.Fatalf("account %s not found in modified accounts", account.addr.Hex())
   321  			}
   322  		}
   323  	})
   324  
   325  	// Test GetModifiedAccountsByHash
   326  	t.Run("GetModifiedAccountsByHash", func(t *testing.T) {
   327  		header := blockChain.GetHeaderByNumber(uint64(genBlocks))
   328  		addrs, err := api.GetModifiedAccountsByHash(header.Hash(), nil)
   329  		assert.NoError(t, err)
   330  		assert.Len(t, addrs, len(accounts)+1) // +1 for the coinbase
   331  		for _, account := range accounts {
   332  			if !slices.Contains(addrs, account.addr) {
   333  				t.Fatalf("account %s not found in modified accounts", account.addr.Hex())
   334  			}
   335  		}
   336  	})
   337  }