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 }