github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/core/state/snapshot/snapshot_test.go (about)

     1  // Copyright 2019 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 snapshot
    18  
    19  import (
    20  	"encoding/binary"
    21  	"fmt"
    22  	"math/big"
    23  	"math/rand"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/VictoriaMetrics/fastcache"
    28  
    29  	"github.com/scroll-tech/go-ethereum/common"
    30  	"github.com/scroll-tech/go-ethereum/core/rawdb"
    31  	"github.com/scroll-tech/go-ethereum/rlp"
    32  )
    33  
    34  // randomHash generates a random blob of data and returns it as a hash.
    35  func randomHash() common.Hash {
    36  	var hash common.Hash
    37  	if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil {
    38  		panic(err)
    39  	}
    40  	return hash
    41  }
    42  
    43  // randomAccount generates a random account and returns it RLP encoded.
    44  func randomAccount() []byte {
    45  	root := randomHash()
    46  	a := Account{
    47  		Balance:          big.NewInt(rand.Int63()),
    48  		Nonce:            rand.Uint64(),
    49  		Root:             root[:],
    50  		KeccakCodeHash:   emptyKeccakCode[:],
    51  		PoseidonCodeHash: emptyPoseidonCode[:],
    52  		CodeSize:         0,
    53  	}
    54  	data, _ := rlp.EncodeToBytes(a)
    55  	return data
    56  }
    57  
    58  // randomAccountSet generates a set of random accounts with the given strings as
    59  // the account address hashes.
    60  func randomAccountSet(hashes ...string) map[common.Hash][]byte {
    61  	accounts := make(map[common.Hash][]byte)
    62  	for _, hash := range hashes {
    63  		accounts[common.HexToHash(hash)] = randomAccount()
    64  	}
    65  	return accounts
    66  }
    67  
    68  // randomStorageSet generates a set of random slots with the given strings as
    69  // the slot addresses.
    70  func randomStorageSet(accounts []string, hashes [][]string, nilStorage [][]string) map[common.Hash]map[common.Hash][]byte {
    71  	storages := make(map[common.Hash]map[common.Hash][]byte)
    72  	for index, account := range accounts {
    73  		storages[common.HexToHash(account)] = make(map[common.Hash][]byte)
    74  
    75  		if index < len(hashes) {
    76  			hashes := hashes[index]
    77  			for _, hash := range hashes {
    78  				storages[common.HexToHash(account)][common.HexToHash(hash)] = randomHash().Bytes()
    79  			}
    80  		}
    81  		if index < len(nilStorage) {
    82  			nils := nilStorage[index]
    83  			for _, hash := range nils {
    84  				storages[common.HexToHash(account)][common.HexToHash(hash)] = nil
    85  			}
    86  		}
    87  	}
    88  	return storages
    89  }
    90  
    91  // Tests that if a disk layer becomes stale, no active external references will
    92  // be returned with junk data. This version of the test flattens every diff layer
    93  // to check internal corner case around the bottom-most memory accumulator.
    94  func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) {
    95  	// Create an empty base layer and a snapshot tree out of it
    96  	base := &diskLayer{
    97  		diskdb: rawdb.NewMemoryDatabase(),
    98  		root:   common.HexToHash("0x01"),
    99  		cache:  fastcache.New(1024 * 500),
   100  	}
   101  	snaps := &Tree{
   102  		layers: map[common.Hash]snapshot{
   103  			base.root: base,
   104  		},
   105  	}
   106  	// Retrieve a reference to the base and commit a diff on top
   107  	ref := snaps.Snapshot(base.root)
   108  
   109  	accounts := map[common.Hash][]byte{
   110  		common.HexToHash("0xa1"): randomAccount(),
   111  	}
   112  	if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil {
   113  		t.Fatalf("failed to create a diff layer: %v", err)
   114  	}
   115  	if n := len(snaps.layers); n != 2 {
   116  		t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 2)
   117  	}
   118  	// Commit the diff layer onto the disk and ensure it's persisted
   119  	if err := snaps.Cap(common.HexToHash("0x02"), 0); err != nil {
   120  		t.Fatalf("failed to merge diff layer onto disk: %v", err)
   121  	}
   122  	// Since the base layer was modified, ensure that data retrieval on the external reference fail
   123  	if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale {
   124  		t.Errorf("stale reference returned account: %#x (err: %v)", acc, err)
   125  	}
   126  	if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale {
   127  		t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err)
   128  	}
   129  	if n := len(snaps.layers); n != 1 {
   130  		t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 1)
   131  		fmt.Println(snaps.layers)
   132  	}
   133  }
   134  
   135  // Tests that if a disk layer becomes stale, no active external references will
   136  // be returned with junk data. This version of the test retains the bottom diff
   137  // layer to check the usual mode of operation where the accumulator is retained.
   138  func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) {
   139  	// Create an empty base layer and a snapshot tree out of it
   140  	base := &diskLayer{
   141  		diskdb: rawdb.NewMemoryDatabase(),
   142  		root:   common.HexToHash("0x01"),
   143  		cache:  fastcache.New(1024 * 500),
   144  	}
   145  	snaps := &Tree{
   146  		layers: map[common.Hash]snapshot{
   147  			base.root: base,
   148  		},
   149  	}
   150  	// Retrieve a reference to the base and commit two diffs on top
   151  	ref := snaps.Snapshot(base.root)
   152  
   153  	accounts := map[common.Hash][]byte{
   154  		common.HexToHash("0xa1"): randomAccount(),
   155  	}
   156  	if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil {
   157  		t.Fatalf("failed to create a diff layer: %v", err)
   158  	}
   159  	if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil {
   160  		t.Fatalf("failed to create a diff layer: %v", err)
   161  	}
   162  	if n := len(snaps.layers); n != 3 {
   163  		t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3)
   164  	}
   165  	// Commit the diff layer onto the disk and ensure it's persisted
   166  	defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit)
   167  	aggregatorMemoryLimit = 0
   168  
   169  	if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil {
   170  		t.Fatalf("failed to merge accumulator onto disk: %v", err)
   171  	}
   172  	// Since the base layer was modified, ensure that data retrievald on the external reference fail
   173  	if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale {
   174  		t.Errorf("stale reference returned account: %#x (err: %v)", acc, err)
   175  	}
   176  	if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale {
   177  		t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err)
   178  	}
   179  	if n := len(snaps.layers); n != 2 {
   180  		t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2)
   181  		fmt.Println(snaps.layers)
   182  	}
   183  }
   184  
   185  // Tests that if a diff layer becomes stale, no active external references will
   186  // be returned with junk data. This version of the test retains the bottom diff
   187  // layer to check the usual mode of operation where the accumulator is retained.
   188  func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) {
   189  	// Create an empty base layer and a snapshot tree out of it
   190  	base := &diskLayer{
   191  		diskdb: rawdb.NewMemoryDatabase(),
   192  		root:   common.HexToHash("0x01"),
   193  		cache:  fastcache.New(1024 * 500),
   194  	}
   195  	snaps := &Tree{
   196  		layers: map[common.Hash]snapshot{
   197  			base.root: base,
   198  		},
   199  	}
   200  	// Commit three diffs on top and retrieve a reference to the bottommost
   201  	accounts := map[common.Hash][]byte{
   202  		common.HexToHash("0xa1"): randomAccount(),
   203  	}
   204  	if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil {
   205  		t.Fatalf("failed to create a diff layer: %v", err)
   206  	}
   207  	if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil {
   208  		t.Fatalf("failed to create a diff layer: %v", err)
   209  	}
   210  	if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, accounts, nil); err != nil {
   211  		t.Fatalf("failed to create a diff layer: %v", err)
   212  	}
   213  	if n := len(snaps.layers); n != 4 {
   214  		t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 4)
   215  	}
   216  	ref := snaps.Snapshot(common.HexToHash("0x02"))
   217  
   218  	// Doing a Cap operation with many allowed layers should be a no-op
   219  	exp := len(snaps.layers)
   220  	if err := snaps.Cap(common.HexToHash("0x04"), 2000); err != nil {
   221  		t.Fatalf("failed to flatten diff layer into accumulator: %v", err)
   222  	}
   223  	if got := len(snaps.layers); got != exp {
   224  		t.Errorf("layers modified, got %d exp %d", got, exp)
   225  	}
   226  	// Flatten the diff layer into the bottom accumulator
   227  	if err := snaps.Cap(common.HexToHash("0x04"), 1); err != nil {
   228  		t.Fatalf("failed to flatten diff layer into accumulator: %v", err)
   229  	}
   230  	// Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail
   231  	if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale {
   232  		t.Errorf("stale reference returned account: %#x (err: %v)", acc, err)
   233  	}
   234  	if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale {
   235  		t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err)
   236  	}
   237  	if n := len(snaps.layers); n != 3 {
   238  		t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 3)
   239  		fmt.Println(snaps.layers)
   240  	}
   241  }
   242  
   243  // TestPostCapBasicDataAccess tests some functionality regarding capping/flattening.
   244  func TestPostCapBasicDataAccess(t *testing.T) {
   245  	// setAccount is a helper to construct a random account entry and assign it to
   246  	// an account slot in a snapshot
   247  	setAccount := func(accKey string) map[common.Hash][]byte {
   248  		return map[common.Hash][]byte{
   249  			common.HexToHash(accKey): randomAccount(),
   250  		}
   251  	}
   252  	// Create a starting base layer and a snapshot tree out of it
   253  	base := &diskLayer{
   254  		diskdb: rawdb.NewMemoryDatabase(),
   255  		root:   common.HexToHash("0x01"),
   256  		cache:  fastcache.New(1024 * 500),
   257  	}
   258  	snaps := &Tree{
   259  		layers: map[common.Hash]snapshot{
   260  			base.root: base,
   261  		},
   262  	}
   263  	// The lowest difflayer
   264  	snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil)
   265  	snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil)
   266  	snaps.Update(common.HexToHash("0xb2"), common.HexToHash("0xa1"), nil, setAccount("0xb2"), nil)
   267  
   268  	snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil)
   269  	snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xb2"), nil, setAccount("0xb3"), nil)
   270  
   271  	// checkExist verifies if an account exiss in a snapshot
   272  	checkExist := func(layer *diffLayer, key string) error {
   273  		if data, _ := layer.Account(common.HexToHash(key)); data == nil {
   274  			return fmt.Errorf("expected %x to exist, got nil", common.HexToHash(key))
   275  		}
   276  		return nil
   277  	}
   278  	// shouldErr checks that an account access errors as expected
   279  	shouldErr := func(layer *diffLayer, key string) error {
   280  		if data, err := layer.Account(common.HexToHash(key)); err == nil {
   281  			return fmt.Errorf("expected error, got data %x", data)
   282  		}
   283  		return nil
   284  	}
   285  	// check basics
   286  	snap := snaps.Snapshot(common.HexToHash("0xb3")).(*diffLayer)
   287  
   288  	if err := checkExist(snap, "0xa1"); err != nil {
   289  		t.Error(err)
   290  	}
   291  	if err := checkExist(snap, "0xb2"); err != nil {
   292  		t.Error(err)
   293  	}
   294  	if err := checkExist(snap, "0xb3"); err != nil {
   295  		t.Error(err)
   296  	}
   297  	// Cap to a bad root should fail
   298  	if err := snaps.Cap(common.HexToHash("0x1337"), 0); err == nil {
   299  		t.Errorf("expected error, got none")
   300  	}
   301  	// Now, merge the a-chain
   302  	snaps.Cap(common.HexToHash("0xa3"), 0)
   303  
   304  	// At this point, a2 got merged into a1. Thus, a1 is now modified, and as a1 is
   305  	// the parent of b2, b2 should no longer be able to iterate into parent.
   306  
   307  	// These should still be accessible
   308  	if err := checkExist(snap, "0xb2"); err != nil {
   309  		t.Error(err)
   310  	}
   311  	if err := checkExist(snap, "0xb3"); err != nil {
   312  		t.Error(err)
   313  	}
   314  	// But these would need iteration into the modified parent
   315  	if err := shouldErr(snap, "0xa1"); err != nil {
   316  		t.Error(err)
   317  	}
   318  	if err := shouldErr(snap, "0xa2"); err != nil {
   319  		t.Error(err)
   320  	}
   321  	if err := shouldErr(snap, "0xa3"); err != nil {
   322  		t.Error(err)
   323  	}
   324  	// Now, merge it again, just for fun. It should now error, since a3
   325  	// is a disk layer
   326  	if err := snaps.Cap(common.HexToHash("0xa3"), 0); err == nil {
   327  		t.Error("expected error capping the disk layer, got none")
   328  	}
   329  }
   330  
   331  // TestSnaphots tests the functionality for retrieving the snapshot
   332  // with given head root and the desired depth.
   333  func TestSnaphots(t *testing.T) {
   334  	// setAccount is a helper to construct a random account entry and assign it to
   335  	// an account slot in a snapshot
   336  	setAccount := func(accKey string) map[common.Hash][]byte {
   337  		return map[common.Hash][]byte{
   338  			common.HexToHash(accKey): randomAccount(),
   339  		}
   340  	}
   341  	makeRoot := func(height uint64) common.Hash {
   342  		var buffer [8]byte
   343  		binary.BigEndian.PutUint64(buffer[:], height)
   344  		return common.BytesToHash(buffer[:])
   345  	}
   346  	// Create a starting base layer and a snapshot tree out of it
   347  	base := &diskLayer{
   348  		diskdb: rawdb.NewMemoryDatabase(),
   349  		root:   makeRoot(1),
   350  		cache:  fastcache.New(1024 * 500),
   351  	}
   352  	snaps := &Tree{
   353  		layers: map[common.Hash]snapshot{
   354  			base.root: base,
   355  		},
   356  	}
   357  	// Construct the snapshots with 129 layers, flattening whatever's above that
   358  	var (
   359  		last = common.HexToHash("0x01")
   360  		head common.Hash
   361  	)
   362  	for i := 0; i < 129; i++ {
   363  		head = makeRoot(uint64(i + 2))
   364  		snaps.Update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil)
   365  		last = head
   366  		snaps.Cap(head, 128) // 130 layers (128 diffs + 1 accumulator + 1 disk)
   367  	}
   368  	var cases = []struct {
   369  		headRoot     common.Hash
   370  		limit        int
   371  		nodisk       bool
   372  		expected     int
   373  		expectBottom common.Hash
   374  	}{
   375  		{head, 0, false, 0, common.Hash{}},
   376  		{head, 64, false, 64, makeRoot(129 + 2 - 64)},
   377  		{head, 128, false, 128, makeRoot(3)}, // Normal diff layers, no accumulator
   378  		{head, 129, true, 129, makeRoot(2)},  // All diff layers, including accumulator
   379  		{head, 130, false, 130, makeRoot(1)}, // All diff layers + disk layer
   380  	}
   381  	for i, c := range cases {
   382  		layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk)
   383  		if len(layers) != c.expected {
   384  			t.Errorf("non-overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers))
   385  		}
   386  		if len(layers) == 0 {
   387  			continue
   388  		}
   389  		bottommost := layers[len(layers)-1]
   390  		if bottommost.Root() != c.expectBottom {
   391  			t.Errorf("non-overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root())
   392  		}
   393  	}
   394  	// Above we've tested the normal capping, which leaves the accumulator live.
   395  	// Test that if the bottommost accumulator diff layer overflows the allowed
   396  	// memory limit, the snapshot tree gets capped to one less layer.
   397  	// Commit the diff layer onto the disk and ensure it's persisted
   398  	defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit)
   399  	aggregatorMemoryLimit = 0
   400  
   401  	snaps.Cap(head, 128) // 129 (128 diffs + 1 overflown accumulator + 1 disk)
   402  
   403  	cases = []struct {
   404  		headRoot     common.Hash
   405  		limit        int
   406  		nodisk       bool
   407  		expected     int
   408  		expectBottom common.Hash
   409  	}{
   410  		{head, 0, false, 0, common.Hash{}},
   411  		{head, 64, false, 64, makeRoot(129 + 2 - 64)},
   412  		{head, 128, false, 128, makeRoot(3)}, // All diff layers, accumulator was flattened
   413  		{head, 129, true, 128, makeRoot(3)},  // All diff layers, accumulator was flattened
   414  		{head, 130, false, 129, makeRoot(2)}, // All diff layers + disk layer
   415  	}
   416  	for i, c := range cases {
   417  		layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk)
   418  		if len(layers) != c.expected {
   419  			t.Errorf("overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers))
   420  		}
   421  		if len(layers) == 0 {
   422  			continue
   423  		}
   424  		bottommost := layers[len(layers)-1]
   425  		if bottommost.Root() != c.expectBottom {
   426  			t.Errorf("overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root())
   427  		}
   428  	}
   429  }
   430  
   431  // TestReadStateDuringFlattening tests the scenario that, during the
   432  // bottom diff layers are merging which tags these as stale, the read
   433  // happens via a pre-created top snapshot layer which tries to access
   434  // the state in these stale layers. Ensure this read can retrieve the
   435  // right state back(block until the flattening is finished) instead of
   436  // an unexpected error(snapshot layer is stale).
   437  func TestReadStateDuringFlattening(t *testing.T) {
   438  	// setAccount is a helper to construct a random account entry and assign it to
   439  	// an account slot in a snapshot
   440  	setAccount := func(accKey string) map[common.Hash][]byte {
   441  		return map[common.Hash][]byte{
   442  			common.HexToHash(accKey): randomAccount(),
   443  		}
   444  	}
   445  	// Create a starting base layer and a snapshot tree out of it
   446  	base := &diskLayer{
   447  		diskdb: rawdb.NewMemoryDatabase(),
   448  		root:   common.HexToHash("0x01"),
   449  		cache:  fastcache.New(1024 * 500),
   450  	}
   451  	snaps := &Tree{
   452  		layers: map[common.Hash]snapshot{
   453  			base.root: base,
   454  		},
   455  	}
   456  	// 4 layers in total, 3 diff layers and 1 disk layers
   457  	snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil)
   458  	snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil)
   459  	snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil)
   460  
   461  	// Obtain the topmost snapshot handler for state accessing
   462  	snap := snaps.Snapshot(common.HexToHash("0xa3"))
   463  
   464  	// Register the testing hook to access the state after flattening
   465  	var result = make(chan *Account)
   466  	snaps.onFlatten = func() {
   467  		// Spin up a thread to read the account from the pre-created
   468  		// snapshot handler. It's expected to be blocked.
   469  		go func() {
   470  			account, _ := snap.Account(common.HexToHash("0xa1"))
   471  			result <- account
   472  		}()
   473  		select {
   474  		case res := <-result:
   475  			t.Fatalf("Unexpected return %v", res)
   476  		case <-time.NewTimer(time.Millisecond * 300).C:
   477  		}
   478  	}
   479  	// Cap the snap tree, which will mark the bottom-most layer as stale.
   480  	snaps.Cap(common.HexToHash("0xa3"), 1)
   481  	select {
   482  	case account := <-result:
   483  		if account == nil {
   484  			t.Fatal("Failed to retrieve account")
   485  		}
   486  	case <-time.NewTimer(time.Millisecond * 300).C:
   487  		t.Fatal("Unexpected blocker")
   488  	}
   489  }