Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Atomic trie height map repair #413

Merged
merged 34 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3184a5e
initial height map repair
darioush Dec 1, 2023
61bf560
add vdb commit
darioush Dec 1, 2023
0c348e9
Atomic trie repair script
darioush Dec 5, 2023
a66baaf
clearer vdb commit
darioush Dec 5, 2023
eb88bf1
update comment
darioush Dec 5, 2023
b0fae16
improve UT
darioush Dec 5, 2023
87291f7
update comment
darioush Dec 5, 2023
1617244
review comments
darioush Dec 5, 2023
65703a4
return int from repairAtomicTrie
darioush Dec 5, 2023
03c28de
pr comments
darioush Dec 5, 2023
58342c9
Merge branch 'repair-atomic-trie-bonus-blocks' of github.com:ava-labs…
darioush Dec 5, 2023
c660366
start the migration on VM initialize
darioush Dec 5, 2023
abfad39
add comment
darioush Dec 5, 2023
73370b8
fix bug
darioush Dec 7, 2023
1eea6d5
Merge branch 'repair-atomic-trie-bonus-blocks' of github.com:ava-labs…
darioush Dec 7, 2023
a9b26d1
fix bug, improve UT
darioush Dec 7, 2023
d6660db
reduce logs
darioush Dec 7, 2023
ce883bc
pr comments
darioush Dec 15, 2023
5145688
test names
darioush Dec 18, 2023
853f61a
remove nil check
darioush Dec 18, 2023
96f85e6
batch the sleeping
darioush Dec 18, 2023
7020e24
move block parsing to init
darioush Dec 18, 2023
022d284
clarify the UT
darioush Dec 18, 2023
33213e4
Merge branch 'master' of github.com:ava-labs/coreth into repair-atomi…
darioush Dec 18, 2023
645c827
add err check
darioush Dec 18, 2023
8bb888b
pr comment
darioush Dec 18, 2023
a4846b0
comment
darioush Dec 18, 2023
1573b17
Merge branch 'repair-atomic-trie-bonus-blocks' of github.com:ava-labs…
darioush Dec 19, 2023
14e8f36
pr comment
darioush Dec 19, 2023
03cd74e
remove delay as argument
darioush Dec 19, 2023
46e70aa
Merge branch 'master' into repair-atomic-trie-bonus-blocks
darioush Dec 20, 2023
d26e494
Merge branch 'repair-atomic-trie-bonus-blocks' of github.com:ava-labs…
darioush Dec 20, 2023
f5b99b7
change log time
darioush Dec 20, 2023
fb0fcf4
Merge branch 'master' of github.com:ava-labs/coreth into repair-atomi…
darioush Dec 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions plugin/evm/atomic_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/ava-labs/avalanchego/database/versiondb"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/wrappers"
"github.com/ava-labs/coreth/core/types"
syncclient "github.com/ava-labs/coreth/sync/client"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -88,13 +89,35 @@ func NewAtomicBackend(
bonusBlocks map[uint64]ids.ID, repo AtomicTxRepository,
lastAcceptedHeight uint64, lastAcceptedHash common.Hash, commitInterval uint64,
) (AtomicBackend, error) {
atomicBacked, _, err := NewAtomicBackendWithBonusBlockRepair(
db, sharedMemory, bonusBlocks, nil, repo,
lastAcceptedHeight, lastAcceptedHash, commitInterval,
)
return atomicBacked, err
}

func NewAtomicBackendWithBonusBlockRepair(
db *versiondb.Database, sharedMemory atomic.SharedMemory,
bonusBlocks map[uint64]ids.ID, bonusBlocksParsed map[uint64]*types.Block,
repo AtomicTxRepository,
lastAcceptedHeight uint64, lastAcceptedHash common.Hash, commitInterval uint64,
) (AtomicBackend, int, error) {
atomicTrieDB := prefixdb.New(atomicTrieDBPrefix, db)
metadataDB := prefixdb.New(atomicTrieMetaDBPrefix, db)
codec := repo.Codec()

atomicTrie, err := newAtomicTrie(atomicTrieDB, metadataDB, codec, lastAcceptedHeight, commitInterval)
if err != nil {
return nil, err
return nil, 0, err
}
var heightsRepaired int
if len(bonusBlocksParsed) > 0 {
if heightsRepaired, err = atomicTrie.repairAtomicTrie(bonusBlocks, bonusBlocksParsed); err != nil {
return nil, 0, err
}
if err := db.Commit(); err != nil {
return nil, 0, err
}
}
atomicBackend := &atomicBackend{
codec: codec,
Expand All @@ -113,9 +136,9 @@ func NewAtomicBackend(
// return an atomic trie that is out of sync with shared memory.
// In normal operation, the cursor is not set, such that this call will be a no-op.
if err := atomicBackend.ApplyToSharedMemory(lastAcceptedHeight); err != nil {
return nil, err
return nil, 0, err
}
return atomicBackend, atomicBackend.initialize(lastAcceptedHeight)
return atomicBackend, heightsRepaired, atomicBackend.initialize(lastAcceptedHeight)
}

// initializes the atomic trie using the atomic repository height index.
Expand Down
8 changes: 8 additions & 0 deletions plugin/evm/atomic_trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var (
_ AtomicTrie = &atomicTrie{}
lastCommittedKey = []byte("atomicTrieLastCommittedBlock")
appliedSharedMemoryCursorKey = []byte("atomicTrieLastAppliedToSharedMemory")
heightMapRepairKey = []byte("atomicTrieHeightMapRepair")
)

// AtomicTrie maintains an index of atomic operations by blockchainIDs for every block
Expand Down Expand Up @@ -82,6 +83,10 @@ type AtomicTrie interface {

// RejectTrie dereferences root from the trieDB, freeing memory.
RejectTrie(root common.Hash) error

// RepairHeightMap repairs the height map of the atomic trie by iterating
// over all leaves in the trie and committing the trie at every commit interval.
RepairHeightMap(to uint64) (bool, error)
}

// AtomicTrieIterator is a stateful iterator that iterates the leafs of an AtomicTrie
Expand All @@ -94,6 +99,9 @@ type AtomicTrieIterator interface {
// returned []byte can be freely modified
Key() []byte

// Value returns the current database value that the iterator is iterating
Value() []byte

// BlockNumber returns the current block number
BlockNumber() uint64

Expand Down
133 changes: 133 additions & 0 deletions plugin/evm/atomic_trie_height_map_repair.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// (c) 2020-2021, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package evm

import (
"errors"
"fmt"
"math"
"time"

"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/trie/trienode"
"github.com/ethereum/go-ethereum/log"
)

const (
repairDone = math.MaxUint64 // used as a marker for when the height map is repaired

iterationsPerDelay = 1000 // after this many iterations, pause for [iterationDelay]
iterationDelay = 100 * time.Millisecond // delay between iterations of the repair loop
)

func (a *atomicTrie) RepairHeightMap(to uint64) (bool, error) {
repairFrom, err := database.GetUInt64(a.metadataDB, heightMapRepairKey)
switch {
case errors.Is(err, database.ErrNotFound):
repairFrom = 0 // height map not repaired yet, start at 0
case err != nil:
return false, err
case repairFrom == repairDone:
// height map already repaired, nothing to do
return false, nil
}
return true, a.repairHeightMap(repairFrom, to)
}

func (a *atomicTrie) repairHeightMap(from, to uint64) error {
// open the atomic trie at the last known root with correct height map
// correspondance
fromRoot, err := getRoot(a.metadataDB, from)
if err != nil {
return fmt.Errorf("could not get root at height %d: %w", from, err)
}
hasher, err := a.OpenTrie(fromRoot)
if err != nil {
return fmt.Errorf("could not open atomic trie at root %s: %w", fromRoot, err)
}

// hashes values inserted in [hasher], and stores the result in the height
// map at [commitHeight]. Additionally, it updates the resume marker and
// re-opens [hasher] to respect the trie's no use after commit invariant.
var (
lastLog = time.Now()
logEach = 5 * time.Minute
)
commitRepairedHeight := func(commitHeight uint64) error {
root, nodes := hasher.Commit(false)
if nodes != nil {
err := a.trieDB.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes))
if err != nil {
return err
}
err = a.trieDB.Commit(root, false)
if err != nil {
return err
}
}
err = a.metadataDB.Put(database.PackUInt64(commitHeight), root[:])
if err != nil {
return err
}
err = database.PutUInt64(a.metadataDB, heightMapRepairKey, commitHeight)
if err != nil {
return err
}
if time.Since(lastLog) > logEach {
log.Info("repairing atomic trie height map", "height", commitHeight, "root", root)
lastLog = time.Now()
}
hasher, err = a.OpenTrie(root)
return err
}

// iterate over all leaves in the current atomic trie
root, _ := a.LastCommitted()
it, err := a.Iterator(root, database.PackUInt64(from+1))
if err != nil {
return fmt.Errorf("could not create iterator for atomic trie at root %s: %w", root, err)
}

var height uint64
lastCommit := from
numIterations := 0
for it.Next() {
height = it.BlockNumber()
if height > to {
break
}

for next := lastCommit + a.commitInterval; next < height; next += a.commitInterval {
if err := commitRepairedHeight(next); err != nil {
return err
}
lastCommit = next
}

if err := hasher.Update(it.Key(), it.Value()); err != nil {
return fmt.Errorf("could not update atomic trie at root %s: %w", root, err)
}

numIterations++
if numIterations%iterationsPerDelay == 0 {
time.Sleep(iterationDelay) // pause to avoid putting a spike of load on the disk
}
}
if err := it.Error(); err != nil {
return fmt.Errorf("error iterating atomic trie: %w", err)
}
for next := lastCommit + a.commitInterval; next <= to; next += a.commitInterval {
if err := commitRepairedHeight(next); err != nil {
return err
}
}

// mark height map as repaired
if err := database.PutUInt64(a.metadataDB, heightMapRepairKey, repairDone); err != nil {
return err
}
log.Info("atomic trie height map repair complete", "height", height, "root", root)
return nil
}
116 changes: 116 additions & 0 deletions plugin/evm/atomic_trie_height_map_repair_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package evm

import (
"testing"

"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/database/memdb"
"github.com/ava-labs/avalanchego/database/versiondb"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

func TestAtomicTrieRepairHeightMap(t *testing.T) {
for name, test := range map[string]testAtomicTrieRepairHeightMap{
"last accepted after commit interval": {
lastAccepted: 3*testCommitInterval + 5,
skipAtomicTxs: func(height uint64) bool { return false },
},
"last accepted exactly a commit interval": {
lastAccepted: 3 * testCommitInterval,
skipAtomicTxs: func(height uint64) bool { return false },
},
"no atomic txs in a commit interval": {
lastAccepted: 3 * testCommitInterval,
skipAtomicTxs: func(height uint64) bool { return height > testCommitInterval && height <= 2*testCommitInterval },
},
"no atomic txs in the most recent commit intervals": {
lastAccepted: 3 * testCommitInterval,
skipAtomicTxs: func(height uint64) bool { return height > testCommitInterval+1 },
},
} {
t.Run(name, func(t *testing.T) { test.run(t) })
}
}

type testAtomicTrieRepairHeightMap struct {
lastAccepted uint64
skipAtomicTxs func(height uint64) bool
}

func (test testAtomicTrieRepairHeightMap) run(t *testing.T) {
require := require.New(t)

db := versiondb.New(memdb.New())
repo, err := NewAtomicTxRepository(db, testTxCodec(), 0, nil, nil, nil)
require.NoError(err)
atomicBackend, err := NewAtomicBackend(db, testSharedMemory(), nil, repo, 0, common.Hash{}, testCommitInterval)
require.NoError(err)
atomicTrie := atomicBackend.AtomicTrie().(*atomicTrie)

heightMap := make(map[uint64]common.Hash)
for height := uint64(1); height <= test.lastAccepted; height++ {
atomicRequests := testDataImportTx().mustAtomicOps()
if test.skipAtomicTxs(height) {
atomicRequests = nil
}
err := indexAtomicTxs(atomicTrie, height, atomicRequests)
require.NoError(err)
if height%testCommitInterval == 0 {
root, _ := atomicTrie.LastCommitted()
heightMap[height] = root
}
}

// Verify that [atomicTrie] can access each of the expected roots
verifyRoots := func(expectZero bool) {
for height, hash := range heightMap {
root, err := atomicTrie.Root(height)
require.NoError(err)
if expectZero {
require.Zero(root)
} else {
require.Equal(hash, root)
}
}
}
verifyRoots(false)

// destroy the height map
for height := range heightMap {
err := atomicTrie.metadataDB.Delete(database.PackUInt64(height))
require.NoError(err)
}
require.NoError(db.Commit())
verifyRoots(true)

// repair the height map
repaired, err := atomicTrie.RepairHeightMap(test.lastAccepted)
require.NoError(err)
verifyRoots(false)
require.True(repaired)

// partially destroy the height map
_, lastHeight := atomicTrie.LastCommitted()
err = atomicTrie.metadataDB.Delete(database.PackUInt64(lastHeight))
require.NoError(err)
err = atomicTrie.metadataDB.Put(
heightMapRepairKey,
database.PackUInt64(lastHeight-testCommitInterval),
)
require.NoError(err)

// repair the height map
repaired, err = atomicTrie.RepairHeightMap(test.lastAccepted)
require.NoError(err)
verifyRoots(false)
require.True(repaired)

// try to repair the height map again
repaired, err = atomicTrie.RepairHeightMap(test.lastAccepted)
require.NoError(err)
require.False(repaired)
}
5 changes: 5 additions & 0 deletions plugin/evm/atomic_trie_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,8 @@ func (a *atomicTrieIterator) AtomicOps() *atomic.Requests {
func (a *atomicTrieIterator) Key() []byte {
return a.key
}

// Value returns the current database value that the iterator is iterating
func (a *atomicTrieIterator) Value() []byte {
return a.trieIterator.Value
}
Loading