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  }