github.com/MetalBlockchain/subnet-evm@v0.4.9/sync/statesync/sync_test.go (about) 1 // (c) 2021-2022, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package statesync 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "math/rand" 11 "runtime/pprof" 12 "sync/atomic" 13 "testing" 14 "time" 15 16 "github.com/MetalBlockchain/subnet-evm/core/rawdb" 17 "github.com/MetalBlockchain/subnet-evm/core/state/snapshot" 18 "github.com/MetalBlockchain/subnet-evm/core/types" 19 "github.com/MetalBlockchain/subnet-evm/ethdb" 20 "github.com/MetalBlockchain/subnet-evm/ethdb/memorydb" 21 "github.com/MetalBlockchain/subnet-evm/plugin/evm/message" 22 statesyncclient "github.com/MetalBlockchain/subnet-evm/sync/client" 23 "github.com/MetalBlockchain/subnet-evm/sync/handlers" 24 handlerstats "github.com/MetalBlockchain/subnet-evm/sync/handlers/stats" 25 "github.com/MetalBlockchain/subnet-evm/trie" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/crypto" 28 "github.com/ethereum/go-ethereum/rlp" 29 "github.com/stretchr/testify/assert" 30 ) 31 32 const testSyncTimeout = 30 * time.Second 33 34 var errInterrupted = errors.New("interrupted sync") 35 36 type syncTest struct { 37 ctx context.Context 38 prepareForTest func(t *testing.T) (clientDB ethdb.Database, serverTrieDB *trie.Database, syncRoot common.Hash) 39 expectedError error 40 GetLeafsIntercept func(message.LeafsRequest, message.LeafsResponse) (message.LeafsResponse, error) 41 GetCodeIntercept func([]common.Hash, [][]byte) ([][]byte, error) 42 } 43 44 func testSync(t *testing.T, test syncTest) { 45 t.Helper() 46 ctx := context.Background() 47 if test.ctx != nil { 48 ctx = test.ctx 49 } 50 clientDB, serverTrieDB, root := test.prepareForTest(t) 51 leafsRequestHandler := handlers.NewLeafsRequestHandler(serverTrieDB, nil, message.Codec, handlerstats.NewNoopHandlerStats()) 52 codeRequestHandler := handlers.NewCodeRequestHandler(serverTrieDB.DiskDB(), message.Codec, handlerstats.NewNoopHandlerStats()) 53 mockClient := statesyncclient.NewMockClient(message.Codec, leafsRequestHandler, codeRequestHandler, nil) 54 // Set intercept functions for the mock client 55 mockClient.GetLeafsIntercept = test.GetLeafsIntercept 56 mockClient.GetCodeIntercept = test.GetCodeIntercept 57 58 s, err := NewStateSyncer(&StateSyncerConfig{ 59 Client: mockClient, 60 Root: root, 61 DB: clientDB, 62 BatchSize: 1000, // Use a lower batch size in order to get test coverage of batches being written early. 63 NumCodeFetchingWorkers: DefaultNumCodeFetchingWorkers, 64 MaxOutstandingCodeHashes: DefaultMaxOutstandingCodeHashes, 65 }) 66 if err != nil { 67 t.Fatal(err) 68 } 69 // begin sync 70 s.Start(ctx) 71 waitFor(t, s.Done(), test.expectedError, testSyncTimeout) 72 if test.expectedError != nil { 73 return 74 } 75 76 assertDBConsistency(t, root, serverTrieDB, trie.NewDatabase(clientDB)) 77 } 78 79 // testSyncResumes tests a series of syncTests work as expected, invoking a callback function after each 80 // successive step. 81 func testSyncResumes(t *testing.T, steps []syncTest, stepCallback func()) { 82 for _, test := range steps { 83 testSync(t, test) 84 stepCallback() 85 } 86 } 87 88 // waitFor waits for a result on the [result] channel to match [expected], or a timeout. 89 func waitFor(t *testing.T, result <-chan error, expected error, timeout time.Duration) { 90 t.Helper() 91 select { 92 case err := <-result: 93 if expected != nil { 94 if err == nil { 95 t.Fatalf("Expected error %s, but got nil", expected) 96 } 97 assert.Contains(t, err.Error(), expected.Error()) 98 } else if err != nil { 99 t.Fatal("unexpected error waiting for sync result", err) 100 } 101 case <-time.After(timeout): 102 // print a stack trace to assist with debugging 103 // if the test times out. 104 var stackBuf bytes.Buffer 105 pprof.Lookup("goroutine").WriteTo(&stackBuf, 2) 106 t.Log(stackBuf.String()) 107 // fail the test 108 t.Fatal("unexpected timeout waiting for sync result") 109 } 110 } 111 112 func TestSimpleSyncCases(t *testing.T) { 113 var ( 114 numAccounts = 250 115 numAccountsSmall = 10 116 clientErr = errors.New("dummy client error") 117 ) 118 tests := map[string]syncTest{ 119 "accounts": { 120 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 121 serverTrieDB := trie.NewDatabase(memorydb.New()) 122 root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, numAccounts, nil) 123 return memorydb.New(), serverTrieDB, root 124 }, 125 }, 126 "accounts with code": { 127 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 128 serverTrieDB := trie.NewDatabase(memorydb.New()) 129 root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, numAccounts, func(t *testing.T, index int, account types.StateAccount) types.StateAccount { 130 if index%3 == 0 { 131 codeBytes := make([]byte, 256) 132 _, err := rand.Read(codeBytes) 133 if err != nil { 134 t.Fatalf("error reading random code bytes: %v", err) 135 } 136 137 codeHash := crypto.Keccak256Hash(codeBytes) 138 rawdb.WriteCode(serverTrieDB.DiskDB(), codeHash, codeBytes) 139 account.CodeHash = codeHash[:] 140 } 141 return account 142 }) 143 return memorydb.New(), serverTrieDB, root 144 }, 145 }, 146 "accounts with code and storage": { 147 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 148 serverTrieDB := trie.NewDatabase(memorydb.New()) 149 root := fillAccountsWithStorage(t, serverTrieDB, common.Hash{}, numAccounts) 150 return memorydb.New(), serverTrieDB, root 151 }, 152 }, 153 "accounts with storage": { 154 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 155 serverTrieDB := trie.NewDatabase(memorydb.New()) 156 root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, numAccounts, func(t *testing.T, i int, account types.StateAccount) types.StateAccount { 157 if i%5 == 0 { 158 account.Root, _, _ = trie.GenerateTrie(t, serverTrieDB, 16, common.HashLength) 159 } 160 161 return account 162 }) 163 return memorydb.New(), serverTrieDB, root 164 }, 165 }, 166 "accounts with overlapping storage": { 167 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 168 serverTrieDB := trie.NewDatabase(memorydb.New()) 169 root, _ := FillAccountsWithOverlappingStorage(t, serverTrieDB, common.Hash{}, numAccounts, 3) 170 return memorydb.New(), serverTrieDB, root 171 }, 172 }, 173 "failed to fetch leafs": { 174 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 175 serverTrieDB := trie.NewDatabase(memorydb.New()) 176 root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, numAccountsSmall, nil) 177 return memorydb.New(), serverTrieDB, root 178 }, 179 GetLeafsIntercept: func(_ message.LeafsRequest, _ message.LeafsResponse) (message.LeafsResponse, error) { 180 return message.LeafsResponse{}, clientErr 181 }, 182 expectedError: clientErr, 183 }, 184 "failed to fetch code": { 185 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 186 serverTrieDB := trie.NewDatabase(memorydb.New()) 187 root := fillAccountsWithStorage(t, serverTrieDB, common.Hash{}, numAccountsSmall) 188 return memorydb.New(), serverTrieDB, root 189 }, 190 GetCodeIntercept: func(_ []common.Hash, _ [][]byte) ([][]byte, error) { 191 return nil, clientErr 192 }, 193 expectedError: clientErr, 194 }, 195 } 196 for name, test := range tests { 197 rand.Seed(1) 198 t.Run(name, func(t *testing.T) { 199 testSync(t, test) 200 }) 201 } 202 } 203 204 func TestCancelSync(t *testing.T) { 205 serverTrieDB := trie.NewDatabase(memorydb.New()) 206 // Create trie with 2000 accounts (more than one leaf request) 207 root := fillAccountsWithStorage(t, serverTrieDB, common.Hash{}, 2000) 208 ctx, cancel := context.WithCancel(context.Background()) 209 defer cancel() 210 testSync(t, syncTest{ 211 ctx: ctx, 212 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 213 return memorydb.New(), serverTrieDB, root 214 }, 215 expectedError: context.Canceled, 216 GetLeafsIntercept: func(_ message.LeafsRequest, lr message.LeafsResponse) (message.LeafsResponse, error) { 217 cancel() 218 return lr, nil 219 }, 220 }) 221 } 222 223 // interruptLeafsIntercept provides the parameters to the getLeafsIntercept 224 // function which returns [errInterrupted] after passing through [numRequests] 225 // leafs requests for [root]. 226 type interruptLeafsIntercept struct { 227 numRequests uint32 228 interruptAfter uint32 229 root common.Hash 230 } 231 232 // getLeafsIntercept can be passed to mockClient and returns an unmodified 233 // response for the first [numRequest] requests for leafs from [root]. 234 // After that, all requests for leafs from [root] return [errInterrupted]. 235 func (i *interruptLeafsIntercept) getLeafsIntercept(request message.LeafsRequest, response message.LeafsResponse) (message.LeafsResponse, error) { 236 if request.Root == i.root { 237 if numRequests := atomic.AddUint32(&i.numRequests, 1); numRequests > i.interruptAfter { 238 return message.LeafsResponse{}, errInterrupted 239 } 240 } 241 return response, nil 242 } 243 244 func TestResumeSyncAccountsTrieInterrupted(t *testing.T) { 245 serverTrieDB := trie.NewDatabase(memorydb.New()) 246 root, _ := FillAccountsWithOverlappingStorage(t, serverTrieDB, common.Hash{}, 2000, 3) 247 clientDB := memorydb.New() 248 intercept := &interruptLeafsIntercept{ 249 root: root, 250 interruptAfter: 1, 251 } 252 testSync(t, syncTest{ 253 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 254 return clientDB, serverTrieDB, root 255 }, 256 expectedError: errInterrupted, 257 GetLeafsIntercept: intercept.getLeafsIntercept, 258 }) 259 260 assert.EqualValues(t, 2, intercept.numRequests) 261 262 testSync(t, syncTest{ 263 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 264 return clientDB, serverTrieDB, root 265 }, 266 }) 267 } 268 269 func TestResumeSyncLargeStorageTrieInterrupted(t *testing.T) { 270 serverTrieDB := trie.NewDatabase(memorydb.New()) 271 272 largeStorageRoot, _, _ := trie.GenerateTrie(t, serverTrieDB, 2000, common.HashLength) 273 root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, 2000, func(t *testing.T, index int, account types.StateAccount) types.StateAccount { 274 // Set the root for a single account 275 if index == 10 { 276 account.Root = largeStorageRoot 277 } 278 return account 279 }) 280 clientDB := memorydb.New() 281 intercept := &interruptLeafsIntercept{ 282 root: largeStorageRoot, 283 interruptAfter: 1, 284 } 285 testSync(t, syncTest{ 286 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 287 return clientDB, serverTrieDB, root 288 }, 289 expectedError: errInterrupted, 290 GetLeafsIntercept: intercept.getLeafsIntercept, 291 }) 292 293 testSync(t, syncTest{ 294 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 295 return clientDB, serverTrieDB, root 296 }, 297 }) 298 } 299 300 func TestResumeSyncToNewRootAfterLargeStorageTrieInterrupted(t *testing.T) { 301 serverTrieDB := trie.NewDatabase(memorydb.New()) 302 303 largeStorageRoot1, _, _ := trie.GenerateTrie(t, serverTrieDB, 2000, common.HashLength) 304 largeStorageRoot2, _, _ := trie.GenerateTrie(t, serverTrieDB, 2000, common.HashLength) 305 root1, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, 2000, func(t *testing.T, index int, account types.StateAccount) types.StateAccount { 306 // Set the root for a single account 307 if index == 10 { 308 account.Root = largeStorageRoot1 309 } 310 return account 311 }) 312 root2, _ := trie.FillAccounts(t, serverTrieDB, root1, 100, func(t *testing.T, index int, account types.StateAccount) types.StateAccount { 313 if index == 20 { 314 account.Root = largeStorageRoot2 315 } 316 return account 317 }) 318 clientDB := memorydb.New() 319 intercept := &interruptLeafsIntercept{ 320 root: largeStorageRoot1, 321 interruptAfter: 1, 322 } 323 testSync(t, syncTest{ 324 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 325 return clientDB, serverTrieDB, root1 326 }, 327 expectedError: errInterrupted, 328 GetLeafsIntercept: intercept.getLeafsIntercept, 329 }) 330 331 <-snapshot.WipeSnapshot(clientDB, false) 332 333 testSync(t, syncTest{ 334 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 335 return clientDB, serverTrieDB, root2 336 }, 337 }) 338 } 339 340 func TestResumeSyncLargeStorageTrieWithConsecutiveDuplicatesInterrupted(t *testing.T) { 341 serverTrieDB := trie.NewDatabase(memorydb.New()) 342 343 largeStorageRoot, _, _ := trie.GenerateTrie(t, serverTrieDB, 2000, common.HashLength) 344 root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, 100, func(t *testing.T, index int, account types.StateAccount) types.StateAccount { 345 // Set the root for 2 successive accounts 346 if index == 10 || index == 11 { 347 account.Root = largeStorageRoot 348 } 349 return account 350 }) 351 clientDB := memorydb.New() 352 intercept := &interruptLeafsIntercept{ 353 root: largeStorageRoot, 354 interruptAfter: 1, 355 } 356 testSync(t, syncTest{ 357 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 358 return clientDB, serverTrieDB, root 359 }, 360 expectedError: errInterrupted, 361 GetLeafsIntercept: intercept.getLeafsIntercept, 362 }) 363 364 testSync(t, syncTest{ 365 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 366 return clientDB, serverTrieDB, root 367 }, 368 }) 369 } 370 371 func TestResumeSyncLargeStorageTrieWithSpreadOutDuplicatesInterrupted(t *testing.T) { 372 serverTrieDB := trie.NewDatabase(memorydb.New()) 373 374 largeStorageRoot, _, _ := trie.GenerateTrie(t, serverTrieDB, 2000, common.HashLength) 375 root, _ := trie.FillAccounts(t, serverTrieDB, common.Hash{}, 100, func(t *testing.T, index int, account types.StateAccount) types.StateAccount { 376 if index == 10 || index == 90 { 377 account.Root = largeStorageRoot 378 } 379 return account 380 }) 381 clientDB := memorydb.New() 382 intercept := &interruptLeafsIntercept{ 383 root: largeStorageRoot, 384 interruptAfter: 1, 385 } 386 testSync(t, syncTest{ 387 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 388 return clientDB, serverTrieDB, root 389 }, 390 expectedError: errInterrupted, 391 GetLeafsIntercept: intercept.getLeafsIntercept, 392 }) 393 394 testSync(t, syncTest{ 395 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 396 return clientDB, serverTrieDB, root 397 }, 398 }) 399 } 400 401 func TestResyncNewRootAfterDeletes(t *testing.T) { 402 for name, test := range map[string]struct { 403 deleteBetweenSyncs func(*testing.T, common.Hash, *trie.Database) 404 }{ 405 "delete code": { 406 deleteBetweenSyncs: func(t *testing.T, _ common.Hash, clientTrieDB *trie.Database) { 407 db := clientTrieDB.DiskDB() 408 // delete code 409 it := db.NewIterator(rawdb.CodePrefix, nil) 410 defer it.Release() 411 for it.Next() { 412 if len(it.Key()) != len(rawdb.CodePrefix)+common.HashLength { 413 continue 414 } 415 if err := db.Delete(it.Key()); err != nil { 416 t.Fatal(err) 417 } 418 } 419 if err := it.Error(); err != nil { 420 t.Fatal(err) 421 } 422 }, 423 }, 424 "delete intermediate storage nodes": { 425 deleteBetweenSyncs: func(t *testing.T, root common.Hash, clientTrieDB *trie.Database) { 426 tr, err := trie.New(common.Hash{}, root, clientTrieDB) 427 if err != nil { 428 t.Fatal(err) 429 } 430 it := trie.NewIterator(tr.NodeIterator(nil)) 431 accountsWithStorage := 0 432 433 // keep track of storage tries we delete trie nodes from 434 // so we don't try to do it again if another account has 435 // the same storage root. 436 corruptedStorageRoots := make(map[common.Hash]struct{}) 437 for it.Next() { 438 var acc types.StateAccount 439 if err := rlp.DecodeBytes(it.Value, &acc); err != nil { 440 t.Fatal(err) 441 } 442 if acc.Root == types.EmptyRootHash { 443 continue 444 } 445 if _, found := corruptedStorageRoots[acc.Root]; found { 446 // avoid trying to delete nodes from a trie we have already deleted nodes from 447 continue 448 } 449 accountsWithStorage++ 450 if accountsWithStorage%2 != 0 { 451 continue 452 } 453 corruptedStorageRoots[acc.Root] = struct{}{} 454 trie.CorruptTrie(t, clientTrieDB, acc.Root, 2) 455 } 456 if err := it.Err; err != nil { 457 t.Fatal(err) 458 } 459 }, 460 }, 461 "delete intermediate account trie nodes": { 462 deleteBetweenSyncs: func(t *testing.T, root common.Hash, clientTrieDB *trie.Database) { 463 trie.CorruptTrie(t, clientTrieDB, root, 5) 464 }, 465 }, 466 } { 467 t.Run(name, func(t *testing.T) { 468 testSyncerSyncsToNewRoot(t, test.deleteBetweenSyncs) 469 }) 470 } 471 } 472 473 func testSyncerSyncsToNewRoot(t *testing.T, deleteBetweenSyncs func(*testing.T, common.Hash, *trie.Database)) { 474 rand.Seed(1) 475 clientDB := memorydb.New() 476 serverTrieDB := trie.NewDatabase(memorydb.New()) 477 478 root1, _ := FillAccountsWithOverlappingStorage(t, serverTrieDB, common.Hash{}, 1000, 3) 479 root2, _ := FillAccountsWithOverlappingStorage(t, serverTrieDB, root1, 1000, 3) 480 481 called := false 482 483 testSyncResumes(t, []syncTest{ 484 { 485 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 486 return clientDB, serverTrieDB, root1 487 }, 488 }, 489 { 490 prepareForTest: func(t *testing.T) (ethdb.Database, *trie.Database, common.Hash) { 491 return clientDB, serverTrieDB, root2 492 }, 493 }, 494 }, func() { 495 // Only perform the delete stage once 496 if called { 497 return 498 } 499 called = true 500 // delete snapshot first since this is not the responsibility of the EVM State Syncer 501 <-snapshot.WipeSnapshot(clientDB, false) 502 503 deleteBetweenSyncs(t, root1, trie.NewDatabase(clientDB)) 504 }) 505 }