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 }