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