github.com/dim4egster/coreth@v0.10.2/plugin/evm/atomic_trie_test.go (about)

     1  // (c) 2020-2021, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package evm
     5  
     6  import (
     7  	"encoding/binary"
     8  	"testing"
     9  
    10  	"github.com/prometheus/client_golang/prometheus"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  
    14  	"github.com/dim4egster/qmallgo/chains/atomic"
    15  	"github.com/dim4egster/qmallgo/database"
    16  	"github.com/dim4egster/qmallgo/database/leveldb"
    17  	"github.com/dim4egster/qmallgo/database/memdb"
    18  	"github.com/dim4egster/qmallgo/database/versiondb"
    19  	"github.com/dim4egster/qmallgo/ids"
    20  	"github.com/dim4egster/qmallgo/utils/logging"
    21  	"github.com/dim4egster/qmallgo/utils/wrappers"
    22  
    23  	"github.com/ethereum/go-ethereum/common"
    24  )
    25  
    26  const testCommitInterval = 100
    27  
    28  func (tx *Tx) mustAtomicOps() map[ids.ID]*atomic.Requests {
    29  	id, reqs, err := tx.AtomicOps()
    30  	if err != nil {
    31  		panic(err)
    32  	}
    33  	return map[ids.ID]*atomic.Requests{id: reqs}
    34  }
    35  
    36  // indexAtomicTxs updates [tr] with entries in [atomicOps] at height by creating
    37  // a new snapshot, calculating a new root, and calling InsertTrie followed
    38  // by AcceptTrie on the new root.
    39  func indexAtomicTxs(tr AtomicTrie, height uint64, atomicOps map[ids.ID]*atomic.Requests) error {
    40  	snapshot, err := tr.OpenTrie(tr.LastAcceptedRoot())
    41  	if err != nil {
    42  		return err
    43  	}
    44  	if err := tr.UpdateTrie(snapshot, height, atomicOps); err != nil {
    45  		return err
    46  	}
    47  	root, nodes, err := snapshot.Commit(false)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	if err := tr.InsertTrie(nodes, root); err != nil {
    52  		return err
    53  	}
    54  	_, err = tr.AcceptTrie(height, root)
    55  	return err
    56  }
    57  
    58  func TestNearestCommitHeight(t *testing.T) {
    59  	type test struct {
    60  		height, commitInterval, expectedCommitHeight uint64
    61  	}
    62  
    63  	for _, test := range []test{
    64  		{
    65  			height:               4500,
    66  			commitInterval:       4096,
    67  			expectedCommitHeight: 4096,
    68  		},
    69  		{
    70  			height:               8500,
    71  			commitInterval:       4096,
    72  			expectedCommitHeight: 8192,
    73  		},
    74  		{
    75  			height:               950,
    76  			commitInterval:       100,
    77  			expectedCommitHeight: 900,
    78  		},
    79  	} {
    80  		commitHeight := nearestCommitHeight(test.height, test.commitInterval)
    81  		assert.Equal(t, commitHeight, test.expectedCommitHeight)
    82  	}
    83  }
    84  
    85  func TestAtomicTrieInitialize(t *testing.T) {
    86  	type test struct {
    87  		commitInterval, lastAcceptedHeight, expectedCommitHeight uint64
    88  		numTxsPerBlock                                           func(uint64) int
    89  	}
    90  	for name, test := range map[string]test{
    91  		"genesis": {
    92  			commitInterval:       10,
    93  			lastAcceptedHeight:   0,
    94  			expectedCommitHeight: 0,
    95  			numTxsPerBlock:       constTxsPerHeight(0),
    96  		},
    97  		"before first commit": {
    98  			commitInterval:       10,
    99  			lastAcceptedHeight:   5,
   100  			expectedCommitHeight: 0,
   101  			numTxsPerBlock:       constTxsPerHeight(3),
   102  		},
   103  		"first commit": {
   104  			commitInterval:       10,
   105  			lastAcceptedHeight:   10,
   106  			expectedCommitHeight: 10,
   107  			numTxsPerBlock:       constTxsPerHeight(3),
   108  		},
   109  		"past first commit": {
   110  			commitInterval:       10,
   111  			lastAcceptedHeight:   15,
   112  			expectedCommitHeight: 10,
   113  			numTxsPerBlock:       constTxsPerHeight(3),
   114  		},
   115  		"many existing commits": {
   116  			commitInterval:       10,
   117  			lastAcceptedHeight:   1000,
   118  			expectedCommitHeight: 1000,
   119  			numTxsPerBlock:       constTxsPerHeight(3),
   120  		},
   121  		"many existing commits plus 1": {
   122  			commitInterval:       10,
   123  			lastAcceptedHeight:   1001,
   124  			expectedCommitHeight: 1000,
   125  			numTxsPerBlock:       constTxsPerHeight(3),
   126  		},
   127  		"some blocks without atomic tx": {
   128  			commitInterval:       10,
   129  			lastAcceptedHeight:   101,
   130  			expectedCommitHeight: 100,
   131  			numTxsPerBlock: func(height uint64) int {
   132  				if height <= 50 || height == 101 {
   133  					return 1
   134  				}
   135  				return 0
   136  			},
   137  		},
   138  	} {
   139  		t.Run(name, func(t *testing.T) {
   140  			db := versiondb.New(memdb.New())
   141  			codec := testTxCodec()
   142  			repo, err := NewAtomicTxRepository(db, codec, test.lastAcceptedHeight, nil, nil, nil)
   143  			if err != nil {
   144  				t.Fatal(err)
   145  			}
   146  			operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests)
   147  			writeTxs(t, repo, 1, test.lastAcceptedHeight+1, test.numTxsPerBlock, nil, operationsMap)
   148  
   149  			// Construct the atomic trie for the first time
   150  			atomicBackend1, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval)
   151  			if err != nil {
   152  				t.Fatal(err)
   153  			}
   154  			atomicTrie1 := atomicBackend1.AtomicTrie()
   155  
   156  			rootHash1, commitHeight1 := atomicTrie1.LastCommitted()
   157  			assert.EqualValues(t, test.expectedCommitHeight, commitHeight1)
   158  			if test.expectedCommitHeight != 0 {
   159  				assert.NotEqual(t, common.Hash{}, rootHash1)
   160  			}
   161  
   162  			// Verify the operations up to the expected commit height
   163  			verifyOperations(t, atomicTrie1, codec, rootHash1, 1, test.expectedCommitHeight, operationsMap)
   164  
   165  			// Construct the atomic trie again (on the same database) and ensure the last accepted root is correct.
   166  			atomicBackend2, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval)
   167  			if err != nil {
   168  				t.Fatal(err)
   169  			}
   170  			atomicTrie2 := atomicBackend2.AtomicTrie()
   171  			assert.Equal(t, atomicTrie1.LastAcceptedRoot(), atomicTrie2.LastAcceptedRoot())
   172  
   173  			// Construct the atomic trie again (on an empty database) and ensure that it produces the same hash.
   174  			atomicBackend3, err := NewAtomicBackend(
   175  				versiondb.New(memdb.New()), testSharedMemory(), nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval,
   176  			)
   177  			if err != nil {
   178  				t.Fatal(err)
   179  			}
   180  			atomicTrie3 := atomicBackend3.AtomicTrie()
   181  
   182  			rootHash3, commitHeight3 := atomicTrie3.LastCommitted()
   183  			assert.EqualValues(t, commitHeight1, commitHeight3)
   184  			assert.EqualValues(t, rootHash1, rootHash3)
   185  
   186  			// We now index additional operations up the next commit interval in order to confirm that nothing
   187  			// during the initialization phase will cause an invalid root when indexing continues.
   188  			nextCommitHeight := nearestCommitHeight(test.lastAcceptedHeight+test.commitInterval, test.commitInterval)
   189  			for i := test.lastAcceptedHeight + 1; i <= nextCommitHeight; i++ {
   190  				txs := newTestTxs(test.numTxsPerBlock(i))
   191  				if err := repo.Write(i, txs); err != nil {
   192  					t.Fatal(err)
   193  				}
   194  
   195  				atomicOps, err := mergeAtomicOps(txs)
   196  				if err != nil {
   197  					t.Fatal(err)
   198  				}
   199  				if err := indexAtomicTxs(atomicTrie1, i, atomicOps); err != nil {
   200  					t.Fatal(err)
   201  				}
   202  				operationsMap[i] = atomicOps
   203  			}
   204  			updatedRoot, updatedLastCommitHeight := atomicTrie1.LastCommitted()
   205  			assert.EqualValues(t, nextCommitHeight, updatedLastCommitHeight)
   206  			assert.NotEqual(t, common.Hash{}, updatedRoot)
   207  
   208  			// Verify the operations up to the new expected commit height
   209  			verifyOperations(t, atomicTrie1, codec, updatedRoot, 1, updatedLastCommitHeight, operationsMap)
   210  
   211  			// Generate a new atomic trie to compare the root against.
   212  			atomicBackend4, err := NewAtomicBackend(
   213  				versiondb.New(memdb.New()), testSharedMemory(), nil, repo, nextCommitHeight, common.Hash{}, test.commitInterval,
   214  			)
   215  			if err != nil {
   216  				t.Fatal(err)
   217  			}
   218  			atomicTrie4 := atomicBackend4.AtomicTrie()
   219  
   220  			rootHash4, commitHeight4 := atomicTrie4.LastCommitted()
   221  			assert.EqualValues(t, updatedRoot, rootHash4)
   222  			assert.EqualValues(t, updatedLastCommitHeight, commitHeight4)
   223  		})
   224  	}
   225  }
   226  
   227  func TestIndexerInitializesOnlyOnce(t *testing.T) {
   228  	lastAcceptedHeight := uint64(25)
   229  	db := versiondb.New(memdb.New())
   230  	codec := testTxCodec()
   231  	repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight, nil, nil, nil)
   232  	assert.NoError(t, err)
   233  	operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests)
   234  	writeTxs(t, repo, 1, lastAcceptedHeight+1, constTxsPerHeight(2), nil, operationsMap)
   235  
   236  	// Initialize atomic repository
   237  	atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 10 /* commitInterval*/)
   238  	assert.NoError(t, err)
   239  	atomicTrie := atomicBackend.AtomicTrie()
   240  
   241  	hash, height := atomicTrie.LastCommitted()
   242  	assert.NotEqual(t, common.Hash{}, hash)
   243  	assert.Equal(t, uint64(20), height)
   244  
   245  	// We write another tx at a height below the last committed height in the repo and then
   246  	// re-initialize the atomic trie since initialize is not supposed to run again the height
   247  	// at the trie should still be the old height with the old commit hash without any changes.
   248  	// This scenario is not realistic, but is used to test potential double initialization behavior.
   249  	err = repo.Write(15, []*Tx{testDataExportTx()})
   250  	assert.NoError(t, err)
   251  
   252  	// Re-initialize the atomic trie
   253  	atomicBackend, err = NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 10 /* commitInterval */)
   254  	assert.NoError(t, err)
   255  	atomicTrie = atomicBackend.AtomicTrie()
   256  
   257  	newHash, newHeight := atomicTrie.LastCommitted()
   258  	assert.Equal(t, height, newHeight, "height should not have changed")
   259  	assert.Equal(t, hash, newHash, "hash should be the same")
   260  }
   261  
   262  func newTestAtomicTrie(t *testing.T) AtomicTrie {
   263  	db := versiondb.New(memdb.New())
   264  	repo, err := NewAtomicTxRepository(db, testTxCodec(), 0, nil, nil, nil)
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  	atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, 0, common.Hash{}, testCommitInterval)
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  	return atomicBackend.AtomicTrie()
   273  }
   274  
   275  func TestIndexerWriteAndRead(t *testing.T) {
   276  	atomicTrie := newTestAtomicTrie(t)
   277  
   278  	blockRootMap := make(map[uint64]common.Hash)
   279  	lastCommittedBlockHeight := uint64(0)
   280  	var lastCommittedBlockHash common.Hash
   281  
   282  	// process 305 blocks so that we get three commits (100, 200, 300)
   283  	for height := uint64(1); height <= testCommitInterval*3+5; /*=305*/ height++ {
   284  		atomicRequests := testDataImportTx().mustAtomicOps()
   285  		err := indexAtomicTxs(atomicTrie, height, atomicRequests)
   286  		assert.NoError(t, err)
   287  		if height%testCommitInterval == 0 {
   288  			lastCommittedBlockHash, lastCommittedBlockHeight = atomicTrie.LastCommitted()
   289  			assert.NoError(t, err)
   290  			assert.NotEqual(t, common.Hash{}, lastCommittedBlockHash)
   291  			blockRootMap[lastCommittedBlockHeight] = lastCommittedBlockHash
   292  		}
   293  	}
   294  
   295  	// ensure we have 3 roots
   296  	assert.Len(t, blockRootMap, 3)
   297  
   298  	hash, height := atomicTrie.LastCommitted()
   299  	assert.EqualValues(t, lastCommittedBlockHeight, height, "expected %d was %d", 200, lastCommittedBlockHeight)
   300  	assert.Equal(t, lastCommittedBlockHash, hash)
   301  
   302  	// Verify that [atomicTrie] can access each of the expected roots
   303  	for height, hash := range blockRootMap {
   304  		root, err := atomicTrie.Root(height)
   305  		assert.NoError(t, err)
   306  		assert.Equal(t, hash, root)
   307  	}
   308  }
   309  
   310  func TestAtomicOpsAreNotTxOrderDependent(t *testing.T) {
   311  	atomicTrie1 := newTestAtomicTrie(t)
   312  	atomicTrie2 := newTestAtomicTrie(t)
   313  
   314  	for height := uint64(0); height <= testCommitInterval; /*=205*/ height++ {
   315  		tx1 := testDataImportTx()
   316  		tx2 := testDataImportTx()
   317  		atomicRequests1, err := mergeAtomicOps([]*Tx{tx1, tx2})
   318  		assert.NoError(t, err)
   319  		atomicRequests2, err := mergeAtomicOps([]*Tx{tx2, tx1})
   320  		assert.NoError(t, err)
   321  
   322  		err = indexAtomicTxs(atomicTrie1, height, atomicRequests1)
   323  		assert.NoError(t, err)
   324  		err = indexAtomicTxs(atomicTrie2, height, atomicRequests2)
   325  		assert.NoError(t, err)
   326  	}
   327  	root1, height1 := atomicTrie1.LastCommitted()
   328  	root2, height2 := atomicTrie2.LastCommitted()
   329  	assert.NotEqual(t, common.Hash{}, root1)
   330  	assert.Equal(t, uint64(testCommitInterval), height1)
   331  	assert.Equal(t, uint64(testCommitInterval), height2)
   332  	assert.Equal(t, root1, root2)
   333  }
   334  
   335  func TestAtomicTrieSkipsBonusBlocks(t *testing.T) {
   336  	lastAcceptedHeight := uint64(100)
   337  	numTxsPerBlock := 3
   338  	commitInterval := uint64(10)
   339  	expectedCommitHeight := uint64(100)
   340  	db := versiondb.New(memdb.New())
   341  	codec := testTxCodec()
   342  	repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight, nil, nil, nil)
   343  	if err != nil {
   344  		t.Fatal(err)
   345  	}
   346  	operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests)
   347  	writeTxs(t, repo, 1, lastAcceptedHeight, constTxsPerHeight(numTxsPerBlock), nil, operationsMap)
   348  
   349  	bonusBlocks := map[uint64]ids.ID{
   350  		10: {},
   351  		13: {},
   352  		14: {},
   353  	}
   354  	// Construct the atomic trie for the first time
   355  	atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), bonusBlocks, repo, lastAcceptedHeight, common.Hash{}, commitInterval)
   356  	if err != nil {
   357  		t.Fatal(err)
   358  	}
   359  	atomicTrie := atomicBackend.AtomicTrie()
   360  
   361  	rootHash, commitHeight := atomicTrie.LastCommitted()
   362  	assert.EqualValues(t, expectedCommitHeight, commitHeight)
   363  	assert.NotEqual(t, common.Hash{}, rootHash)
   364  
   365  	// Verify the operations are as expected with the bonus block heights removed from the operations map
   366  	for height := range bonusBlocks {
   367  		delete(operationsMap, height)
   368  	}
   369  	verifyOperations(t, atomicTrie, codec, rootHash, 1, expectedCommitHeight, operationsMap)
   370  }
   371  
   372  func TestIndexingNilShouldNotImpactTrie(t *testing.T) {
   373  	// operations to index
   374  	ops := make([]map[ids.ID]*atomic.Requests, 0)
   375  	for i := 0; i <= testCommitInterval; i++ {
   376  		ops = append(ops, testDataImportTx().mustAtomicOps())
   377  	}
   378  
   379  	// without nils
   380  	a1 := newTestAtomicTrie(t)
   381  	for i := uint64(0); i <= testCommitInterval; i++ {
   382  		if i%2 == 0 {
   383  			if err := indexAtomicTxs(a1, i, ops[i]); err != nil {
   384  				t.Fatal(err)
   385  			}
   386  		} else {
   387  			// do nothing
   388  		}
   389  	}
   390  
   391  	root1, height1 := a1.LastCommitted()
   392  	assert.NotEqual(t, common.Hash{}, root1)
   393  	assert.Equal(t, uint64(testCommitInterval), height1)
   394  
   395  	// with nils
   396  	a2 := newTestAtomicTrie(t)
   397  	for i := uint64(0); i <= testCommitInterval; i++ {
   398  		if i%2 == 0 {
   399  			if err := indexAtomicTxs(a2, i, ops[i]); err != nil {
   400  				t.Fatal(err)
   401  			}
   402  		} else {
   403  			if err := indexAtomicTxs(a2, i, nil); err != nil {
   404  				t.Fatal(err)
   405  			}
   406  		}
   407  	}
   408  	root2, height2 := a2.LastCommitted()
   409  	assert.NotEqual(t, common.Hash{}, root2)
   410  	assert.Equal(t, uint64(testCommitInterval), height2)
   411  
   412  	// key assertion of the test
   413  	assert.Equal(t, root1, root2)
   414  }
   415  
   416  type sharedMemories struct {
   417  	thisChain   atomic.SharedMemory
   418  	peerChain   atomic.SharedMemory
   419  	thisChainID ids.ID
   420  	peerChainID ids.ID
   421  }
   422  
   423  func (s *sharedMemories) addItemsToBeRemovedToPeerChain(ops map[ids.ID]*atomic.Requests) error {
   424  	for _, reqs := range ops {
   425  		puts := make(map[ids.ID]*atomic.Requests)
   426  		puts[s.thisChainID] = &atomic.Requests{}
   427  		for _, key := range reqs.RemoveRequests {
   428  			val := []byte{0x1}
   429  			puts[s.thisChainID].PutRequests = append(puts[s.thisChainID].PutRequests, &atomic.Element{Key: key, Value: val})
   430  		}
   431  		if err := s.peerChain.Apply(puts); err != nil {
   432  			return err
   433  		}
   434  	}
   435  	return nil
   436  }
   437  
   438  func (s *sharedMemories) assertOpsApplied(t *testing.T, ops map[ids.ID]*atomic.Requests) {
   439  	t.Helper()
   440  	for _, reqs := range ops {
   441  		// should be able to get put requests
   442  		for _, elem := range reqs.PutRequests {
   443  			val, err := s.peerChain.Get(s.thisChainID, [][]byte{elem.Key})
   444  			if err != nil {
   445  				t.Fatalf("error finding puts in peerChainMemory: %s", err)
   446  			}
   447  			assert.Equal(t, elem.Value, val[0])
   448  		}
   449  
   450  		// should not be able to get remove requests
   451  		for _, key := range reqs.RemoveRequests {
   452  			_, err := s.thisChain.Get(s.peerChainID, [][]byte{key})
   453  			assert.EqualError(t, err, "not found")
   454  		}
   455  	}
   456  }
   457  
   458  func (s *sharedMemories) assertOpsNotApplied(t *testing.T, ops map[ids.ID]*atomic.Requests) {
   459  	t.Helper()
   460  	for _, reqs := range ops {
   461  		// should not be able to get put requests
   462  		for _, elem := range reqs.PutRequests {
   463  			_, err := s.peerChain.Get(s.thisChainID, [][]byte{elem.Key})
   464  			assert.EqualError(t, err, "not found")
   465  		}
   466  
   467  		// should be able to get remove requests (these were previously added as puts on peerChain)
   468  		for _, key := range reqs.RemoveRequests {
   469  			val, err := s.thisChain.Get(s.peerChainID, [][]byte{key})
   470  			assert.NoError(t, err)
   471  			assert.Equal(t, []byte{0x1}, val[0])
   472  		}
   473  	}
   474  }
   475  
   476  func newSharedMemories(atomicMemory *atomic.Memory, thisChainID, peerChainID ids.ID) *sharedMemories {
   477  	return &sharedMemories{
   478  		thisChain:   atomicMemory.NewSharedMemory(thisChainID),
   479  		peerChain:   atomicMemory.NewSharedMemory(peerChainID),
   480  		thisChainID: thisChainID,
   481  		peerChainID: peerChainID,
   482  	}
   483  }
   484  
   485  func TestApplyToSharedMemory(t *testing.T) {
   486  	type test struct {
   487  		commitInterval, lastAcceptedHeight uint64
   488  		setMarker                          func(*atomicBackend) error
   489  		expectOpsApplied                   func(height uint64) bool
   490  	}
   491  
   492  	for name, test := range map[string]test{
   493  		"marker is set to height": {
   494  			commitInterval:     10,
   495  			lastAcceptedHeight: 25,
   496  			setMarker:          func(a *atomicBackend) error { return a.MarkApplyToSharedMemoryCursor(10) },
   497  			expectOpsApplied:   func(height uint64) bool { return height > 10 && height <= 20 },
   498  		},
   499  		"marker is set to height + blockchain ID": {
   500  			commitInterval:     10,
   501  			lastAcceptedHeight: 25,
   502  			setMarker: func(a *atomicBackend) error {
   503  				cursor := make([]byte, wrappers.LongLen+len(blockChainID[:]))
   504  				binary.BigEndian.PutUint64(cursor, 10)
   505  				copy(cursor[wrappers.LongLen:], blockChainID[:])
   506  				return a.metadataDB.Put(appliedSharedMemoryCursorKey, cursor)
   507  			},
   508  			expectOpsApplied: func(height uint64) bool { return height > 10 && height <= 20 },
   509  		},
   510  		"marker not set": {
   511  			commitInterval:     10,
   512  			lastAcceptedHeight: 25,
   513  			setMarker:          func(*atomicBackend) error { return nil },
   514  			expectOpsApplied:   func(uint64) bool { return false },
   515  		},
   516  	} {
   517  		t.Run(name, func(t *testing.T) {
   518  			db := versiondb.New(memdb.New())
   519  			codec := testTxCodec()
   520  			repo, err := NewAtomicTxRepository(db, codec, test.lastAcceptedHeight, nil, nil, nil)
   521  			assert.NoError(t, err)
   522  			operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests)
   523  			writeTxs(t, repo, 1, test.lastAcceptedHeight+1, constTxsPerHeight(2), nil, operationsMap)
   524  
   525  			// Initialize atomic repository
   526  			m := atomic.NewMemory(db)
   527  			sharedMemories := newSharedMemories(m, testCChainID, blockChainID)
   528  			backend, err := NewAtomicBackend(db, sharedMemories.thisChain, nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval)
   529  			assert.NoError(t, err)
   530  			atomicTrie := backend.AtomicTrie().(*atomicTrie)
   531  
   532  			hash, height := atomicTrie.LastCommitted()
   533  			assert.NotEqual(t, common.Hash{}, hash)
   534  			assert.Equal(t, uint64(20), height)
   535  
   536  			// prepare peer chain's shared memory by applying items we expect to remove as puts
   537  			for _, ops := range operationsMap {
   538  				if err := sharedMemories.addItemsToBeRemovedToPeerChain(ops); err != nil {
   539  					t.Fatal(err)
   540  				}
   541  			}
   542  
   543  			assert.NoError(t, test.setMarker(backend.(*atomicBackend)))
   544  			assert.NoError(t, db.Commit())
   545  			assert.NoError(t, backend.ApplyToSharedMemory(test.lastAcceptedHeight))
   546  
   547  			// assert ops were applied as expected
   548  			for height, ops := range operationsMap {
   549  				if test.expectOpsApplied(height) {
   550  					sharedMemories.assertOpsApplied(t, ops)
   551  				} else {
   552  					sharedMemories.assertOpsNotApplied(t, ops)
   553  				}
   554  			}
   555  
   556  			// marker should be removed after ApplyToSharedMemory is complete
   557  			hasMarker, err := atomicTrie.metadataDB.Has(appliedSharedMemoryCursorKey)
   558  			assert.NoError(t, err)
   559  			assert.False(t, hasMarker)
   560  			// reinitialize the atomic trie
   561  			backend, err = NewAtomicBackend(
   562  				db, sharedMemories.thisChain, nil, repo, test.lastAcceptedHeight, common.Hash{}, test.commitInterval,
   563  			)
   564  			assert.NoError(t, err)
   565  			// no further changes should have occurred in shared memory
   566  			// assert they are as they were prior to reinitializing
   567  			for height, ops := range operationsMap {
   568  				if test.expectOpsApplied(height) {
   569  					sharedMemories.assertOpsApplied(t, ops)
   570  				} else {
   571  					sharedMemories.assertOpsNotApplied(t, ops)
   572  				}
   573  			}
   574  
   575  			// marker should be removed after ApplyToSharedMemory is complete
   576  			hasMarker, err = atomicTrie.metadataDB.Has(appliedSharedMemoryCursorKey)
   577  			assert.NoError(t, err)
   578  			assert.False(t, hasMarker)
   579  		})
   580  	}
   581  }
   582  
   583  func BenchmarkAtomicTrieInit(b *testing.B) {
   584  	db := versiondb.New(memdb.New())
   585  	codec := testTxCodec()
   586  
   587  	operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests)
   588  
   589  	lastAcceptedHeight := uint64(25000)
   590  	// add 25000 * 3 = 75000 transactions
   591  	repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight, nil, nil, nil)
   592  	assert.NoError(b, err)
   593  	writeTxs(b, repo, 1, lastAcceptedHeight, constTxsPerHeight(3), nil, operationsMap)
   594  
   595  	var (
   596  		atomicTrie AtomicTrie
   597  		hash       common.Hash
   598  		height     uint64
   599  	)
   600  	b.ReportAllocs()
   601  	b.ResetTimer()
   602  	for i := 0; i < b.N; i++ {
   603  		sharedMemory := testSharedMemory()
   604  		atomicBackend, err := NewAtomicBackend(db, sharedMemory, nil, repo, lastAcceptedHeight, common.Hash{}, 5000)
   605  		assert.NoError(b, err)
   606  		atomicTrie = atomicBackend.AtomicTrie()
   607  
   608  		hash, height = atomicTrie.LastCommitted()
   609  		assert.Equal(b, lastAcceptedHeight, height)
   610  		assert.NotEqual(b, common.Hash{}, hash)
   611  	}
   612  	b.StopTimer()
   613  
   614  	// Verify operations
   615  	verifyOperations(b, atomicTrie, codec, hash, 1, lastAcceptedHeight, operationsMap)
   616  }
   617  
   618  func BenchmarkAtomicTrieIterate(b *testing.B) {
   619  	db := versiondb.New(memdb.New())
   620  	codec := testTxCodec()
   621  
   622  	operationsMap := make(map[uint64]map[ids.ID]*atomic.Requests)
   623  
   624  	lastAcceptedHeight := uint64(25_000)
   625  	// add 25000 * 3 = 75000 transactions
   626  	repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight, nil, nil, nil)
   627  	assert.NoError(b, err)
   628  	writeTxs(b, repo, 1, lastAcceptedHeight, constTxsPerHeight(3), nil, operationsMap)
   629  
   630  	atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, lastAcceptedHeight, common.Hash{}, 5000)
   631  	assert.NoError(b, err)
   632  	atomicTrie := atomicBackend.AtomicTrie()
   633  
   634  	hash, height := atomicTrie.LastCommitted()
   635  	assert.Equal(b, lastAcceptedHeight, height)
   636  	assert.NotEqual(b, common.Hash{}, hash)
   637  
   638  	b.ReportAllocs()
   639  	b.ResetTimer()
   640  	for i := 0; i < b.N; i++ {
   641  		it, err := atomicTrie.Iterator(hash, nil)
   642  		if err != nil {
   643  			b.Fatal("could not initialize atomic trie iterator")
   644  		}
   645  		for it.Next() {
   646  			assert.NotZero(b, it.BlockNumber())
   647  			assert.NotZero(b, it.BlockchainID())
   648  		}
   649  		assert.NoError(b, it.Error())
   650  	}
   651  }
   652  
   653  func levelDB(t testing.TB) database.Database {
   654  	db, err := leveldb.New(t.TempDir(), nil, logging.NoLog{}, "", prometheus.NewRegistry())
   655  	if err != nil {
   656  		t.Fatal(err)
   657  	}
   658  	return db
   659  }
   660  
   661  func BenchmarkApplyToSharedMemory(b *testing.B) {
   662  	tests := []struct {
   663  		name   string
   664  		newDB  func() database.Database
   665  		blocks uint64
   666  	}{
   667  		{
   668  			name:   "memdb-25k",
   669  			newDB:  func() database.Database { return memdb.New() },
   670  			blocks: 25_000,
   671  		},
   672  		{
   673  			name:   "memdb-250k",
   674  			newDB:  func() database.Database { return memdb.New() },
   675  			blocks: 250_000,
   676  		},
   677  		{
   678  			name:   "leveldb-25k",
   679  			newDB:  func() database.Database { return levelDB(b) },
   680  			blocks: 25_000,
   681  		},
   682  		{
   683  			name:   "leveldb-250k",
   684  			newDB:  func() database.Database { return levelDB(b) },
   685  			blocks: 250_000,
   686  		},
   687  	}
   688  	for _, test := range tests {
   689  		b.Run(test.name, func(b *testing.B) {
   690  			disk := test.newDB()
   691  			defer disk.Close()
   692  			benchmarkApplyToSharedMemory(b, disk, test.blocks)
   693  		})
   694  	}
   695  }
   696  
   697  func benchmarkApplyToSharedMemory(b *testing.B, disk database.Database, blocks uint64) {
   698  	db := versiondb.New(disk)
   699  	codec := testTxCodec()
   700  	sharedMemory := testSharedMemory()
   701  
   702  	lastAcceptedHeight := blocks
   703  	repo, err := NewAtomicTxRepository(db, codec, lastAcceptedHeight, nil, nil, nil)
   704  	assert.NoError(b, err)
   705  
   706  	backend, err := NewAtomicBackend(db, sharedMemory, nil, repo, 0, common.Hash{}, 5000)
   707  	if err != nil {
   708  		b.Fatal(err)
   709  	}
   710  	trie := backend.AtomicTrie()
   711  	for height := uint64(1); height <= lastAcceptedHeight; height++ {
   712  		txs := newTestTxs(constTxsPerHeight(3)(height))
   713  		ops, err := mergeAtomicOps(txs)
   714  		assert.NoError(b, err)
   715  		assert.NoError(b, indexAtomicTxs(trie, height, ops))
   716  	}
   717  
   718  	hash, height := trie.LastCommitted()
   719  	assert.Equal(b, lastAcceptedHeight, height)
   720  	assert.NotEqual(b, common.Hash{}, hash)
   721  
   722  	b.ReportAllocs()
   723  	b.ResetTimer()
   724  	for i := 0; i < b.N; i++ {
   725  		backend.(*atomicBackend).sharedMemory = testSharedMemory()
   726  		assert.NoError(b, backend.MarkApplyToSharedMemoryCursor(0))
   727  		assert.NoError(b, db.Commit())
   728  		assert.NoError(b, backend.ApplyToSharedMemory(lastAcceptedHeight))
   729  	}
   730  }