github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/core/state/snapshot/snapshot_test.go (about)

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