github.com/klaytn/klaytn@v1.12.1/snapshot/snapshot_test.go (about)

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