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