Skip to content

Commit

Permalink
all: implement path-based state scheme
Browse files Browse the repository at this point in the history
  • Loading branch information
rjl493456442 committed Feb 10, 2023
1 parent 77380b9 commit 72d4d6d
Show file tree
Hide file tree
Showing 40 changed files with 4,155 additions and 1,094 deletions.
4 changes: 2 additions & 2 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -964,8 +964,8 @@ func (bc *BlockChain) Stop() {
}
}
// Flush the collected preimages to disk
if err := bc.stateCache.TrieDB().CommitPreimages(); err != nil {
log.Error("Failed to commit trie preimages", "err", err)
if err := bc.stateCache.TrieDB().Close(); err != nil {
log.Error("Failed to close trie db", "err", err)
}
// Ensure all live cached entries be saved into disk, so that we can skip
// cache warmup when node restarts.
Expand Down
2 changes: 1 addition & 1 deletion core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1701,7 +1701,7 @@ func TestTrieForkGC(t *testing.T) {
chain.stateCache.TrieDB().Dereference(blocks[len(blocks)-1-i].Root())
chain.stateCache.TrieDB().Dereference(forks[len(blocks)-1-i].Root())
}
if len(chain.stateCache.TrieDB().Nodes()) > 0 {
if nodes, _ := chain.TrieDB().Size(); nodes > 0 {
t.Fatalf("stale tries still alive after garbase collection")
}
}
Expand Down
87 changes: 87 additions & 0 deletions core/rawdb/accessors_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package rawdb

import (
"encoding/binary"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -92,3 +94,88 @@ func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) {
log.Crit("Failed to delete contract code", "err", err)
}
}

// ReadTrieHistory retrieves the trie history with the given id. Calculate
// the real position of trie history in freezer by minus one since the first
// history object is started from one(zero for empty state).
func ReadTrieHistory(db ethdb.AncientReaderOp, id uint64) []byte {
blob, err := db.Ancient(trieHistoryTable, id-1)
if err != nil {
return nil
}
return blob
}

// WriteTrieHistory writes the provided trie history to database. Calculate the
// real position of trie history in freezer by minus one since the first history
// object is started from one(zero is not existent corresponds to empty state).
func WriteTrieHistory(db ethdb.AncientWriter, id uint64, blob []byte) {
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
op.AppendRaw(trieHistoryTable, id-1, blob)
return nil
})
}

// ReadStateLookup retrieves the state id with the provided state root.
func ReadStateLookup(db ethdb.KeyValueReader, root common.Hash) (uint64, bool) {
data, err := db.Get(stateLookupKey(root))
if err != nil || len(data) == 0 {
return 0, false
}
return binary.BigEndian.Uint64(data), true
}

// WriteStateLookup writes the provided state lookup to database.
func WriteStateLookup(db ethdb.KeyValueWriter, root common.Hash, id uint64) {
var buff [8]byte
binary.BigEndian.PutUint64(buff[:], id)
if err := db.Put(stateLookupKey(root), buff[:]); err != nil {
log.Crit("Failed to store state lookup", "err", err)
}
}

// DeleteStateLookup deletes the specified state lookup from the database.
func DeleteStateLookup(db ethdb.KeyValueWriter, root common.Hash) {
if err := db.Delete(stateLookupKey(root)); err != nil {
log.Crit("Failed to delete state lookup", "err", err)
}
}

// ReadHeadState retrieves the id of the disk state from the database.
func ReadHeadState(db ethdb.KeyValueReader) uint64 {
data, _ := db.Get(headStateKey)
if len(data) != 8 {
return 0
}
return binary.BigEndian.Uint64(data)
}

// WriteHeadState stores the id of the disk state into database.
func WriteHeadState(db ethdb.KeyValueWriter, number uint64) {
if err := db.Put(headStateKey, encodeBlockNumber(number)); err != nil {
log.Crit("Failed to store the head state id", "err", err)
}
}

// ReadTrieJournal retrieves the serialized in-memory trie node diff layers saved at
// the last shutdown. The blob is expected to be max a few 10s of megabytes.
func ReadTrieJournal(db ethdb.KeyValueReader) []byte {
data, _ := db.Get(triesJournalKey)
return data
}

// WriteTrieJournal stores the serialized in-memory trie node diff layers to save at
// shutdown. The blob is expected to be max a few 10s of megabytes.
func WriteTrieJournal(db ethdb.KeyValueWriter, journal []byte) {
if err := db.Put(triesJournalKey, journal); err != nil {
log.Crit("Failed to store tries journal", "err", err)
}
}

// DeleteTrieJournal deletes the serialized in-memory trie node diff layers saved at
// the last shutdown
func DeleteTrieJournal(db ethdb.KeyValueWriter) {
if err := db.Delete(triesJournalKey); err != nil {
log.Crit("Failed to remove tries journal", "err", err)
}
}
26 changes: 24 additions & 2 deletions core/rawdb/ancient_scheme.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package rawdb

import "path/filepath"

// The list of table names of chain freezer.
const (
// chainFreezerHeaderTable indicates the name of the freezer header table.
Expand Down Expand Up @@ -44,10 +46,30 @@ var chainFreezerNoSnappy = map[string]bool{
chainFreezerDifficultyTable: true,
}

const (
// trieHistoryTableSize defines the maximum size of freezer data files.
trieHistoryTableSize = 2 * 1000 * 1000 * 1000

// trieHistoryTable indicates the name of the trie history table.
trieHistoryTable = "history"
)

// TrieHistoryFreezerNoSnappy configures whether compression is disabled for the ancient
// trie histories
var TrieHistoryFreezerNoSnappy = map[string]bool{
trieHistoryTable: false,
}

// The list of identifiers of ancient stores.
var (
chainFreezerName = "chain" // the folder name of chain segment ancient store.
chainFreezerName = "chain" // the folder name of chain segment ancient store.
trieHistoryFreezerName = "triehistory" // the folder name of trie history ancient store.
)

// freezers the collections of all builtin freezers.
var freezers = []string{chainFreezerName}
var freezers = []string{chainFreezerName, trieHistoryFreezerName}

// NewTrieHistoryFreezer initializes the freezer for trie histories.
func NewTrieHistoryFreezer(ancientDir string, readOnly bool) (*ResettableFreezer, error) {
return NewResettableFreezer(filepath.Join(ancientDir, trieHistoryFreezerName), "eth/db/triehistory", readOnly, trieHistoryTableSize, TrieHistoryFreezerNoSnappy)
}
82 changes: 72 additions & 10 deletions core/rawdb/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"encoding/binary"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics"
)

Expand All @@ -42,6 +43,9 @@ var (
// headFinalizedBlockKey tracks the latest known finalized block hash.
headFinalizedBlockKey = []byte("LastFinalized")

// headStateKey tracks the id of latest stored state(for path-based only).
headStateKey = []byte("LastState")

// lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead).
lastPivotKey = []byte("LastPivot")

Expand Down Expand Up @@ -69,6 +73,9 @@ var (
// skeletonSyncStatusKey tracks the skeleton sync status across restarts.
skeletonSyncStatusKey = []byte("SkeletonSyncStatus")

// triesJournalKey tracks the in-memory trie node layers across restarts.
triesJournalKey = []byte("TriesJournal")

// txIndexTailKey tracks the oldest block whose transactions have been indexed.
txIndexTailKey = []byte("TransactionIndexTail")

Expand Down Expand Up @@ -103,6 +110,7 @@ var (
// Path-based trie node scheme.
trieNodeAccountPrefix = []byte("A") // trieNodeAccountPrefix + hexPath -> trie node
trieNodeStoragePrefix = []byte("O") // trieNodeStoragePrefix + accountHash + hexPath -> trie node
stateLookupPrefix = []byte("L") // stateLookupPrefix + state root -> state id

PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
configPrefix = []byte("ethereum-config-") // config prefix for the db
Expand Down Expand Up @@ -229,16 +237,6 @@ func IsCodeKey(key []byte) (bool, []byte) {
return false, nil
}

// configKey = configPrefix + hash
func configKey(hash common.Hash) []byte {
return append(configPrefix, hash.Bytes()...)
}

// genesisStateSpecKey = genesisPrefix + hash
func genesisStateSpecKey(hash common.Hash) []byte {
return append(genesisPrefix, hash.Bytes()...)
}

// accountTrieNodeKey = trieNodeAccountPrefix + nodePath.
func accountTrieNodeKey(path []byte) []byte {
return append(trieNodeAccountPrefix, path...)
Expand All @@ -248,3 +246,67 @@ func accountTrieNodeKey(path []byte) []byte {
func storageTrieNodeKey(accountHash common.Hash, path []byte) []byte {
return append(append(trieNodeStoragePrefix, accountHash.Bytes()...), path...)
}

// IsLegacyTrieNode reports whether a provided database entry is a legacy trie
// node. The characteristics of legacy trie node are:
// - the key length is 32 bytes
// - the key is the hash of val
func IsLegacyTrieNode(key []byte, val []byte) bool {
if len(key) != common.HashLength {
return false
}
return bytes.Equal(key, crypto.Keccak256(val))
}

// IsAccountTrieNode reports whether a provided database entry is an account
// trie node in path-based state scheme.
func IsAccountTrieNode(key []byte) (bool, []byte) {
if !bytes.HasPrefix(key, trieNodeAccountPrefix) {
return false, nil
}
// The remaining key should only consist a hex node path
// whose length is in the range 0 to 64 (64 is excluded
// since leaves are always embedded in parent).
remain := key[len(trieNodeAccountPrefix):]
if len(remain) >= common.HashLength*2 {
return false, nil
}
return true, remain
}

// IsStorageTrieNode reports whether a provided database entry is a storage
// trie node in path-based state scheme.
func IsStorageTrieNode(key []byte) (bool, common.Hash, []byte) {
if !bytes.HasPrefix(key, trieNodeStoragePrefix) {
return false, common.Hash{}, nil
}
// The remaining key consists of 2 parts:
// - 32 bytes account hash
// - hex node path whose length is in the range 0 to 64
remain := key[len(trieNodeStoragePrefix):]
if len(remain) < common.HashLength {
return false, common.Hash{}, nil
}
accountHash := common.BytesToHash(remain[:common.HashLength])
remain = remain[common.HashLength:]

if len(remain) >= common.HashLength*2 {
return false, common.Hash{}, nil
}
return true, accountHash, remain
}

// stateLookupKey = stateLookupPrefix + root (32 bytes)
func stateLookupKey(root common.Hash) []byte {
return append(stateLookupPrefix, root.Bytes()...)
}

// configKey = configPrefix + hash
func configKey(hash common.Hash) []byte {
return append(configPrefix, hash.Bytes()...)
}

// genesisStateSpecKey = genesisPrefix + hash
func genesisStateSpecKey(hash common.Hash) []byte {
return append(genesisPrefix, hash.Bytes()...)
}
13 changes: 12 additions & 1 deletion core/state/iterator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/crypto"
)

// Tests that the node iterator indeed walks over the entire database contents.
Expand Down Expand Up @@ -85,9 +86,19 @@ func TestNodeIteratorCoverage(t *testing.T) {
// database entry belongs to a trie node or not.
func isTrieNode(scheme string, key, val []byte) (bool, common.Hash) {
if scheme == rawdb.HashScheme {
if len(key) == common.HashLength {
ok := rawdb.IsLegacyTrieNode(key, val)
if ok {
return true, common.BytesToHash(key)
}
} else {
ok, _ := rawdb.IsAccountTrieNode(key)
if ok {
return true, crypto.Keccak256Hash(val)
}
ok, _, _ = rawdb.IsStorageTrieNode(key)
if ok {
return true, crypto.Keccak256Hash(val)
}
}
return false, common.Hash{}
}
14 changes: 6 additions & 8 deletions core/state/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ package state
import "github.com/ethereum/go-ethereum/metrics"

var (
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
accountTrieUpdatedMeter = metrics.NewRegisteredMeter("state/update/accountnodes", nil)
storageTriesUpdatedMeter = metrics.NewRegisteredMeter("state/update/storagenodes", nil)
accountTrieDeletedMeter = metrics.NewRegisteredMeter("state/delete/accountnodes", nil)
storageTriesDeletedMeter = metrics.NewRegisteredMeter("state/delete/storagenodes", nil)
accountUpdatedMeter = metrics.NewRegisteredMeter("state/update/account", nil)
storageUpdatedMeter = metrics.NewRegisteredMeter("state/update/storage", nil)
accountDeletedMeter = metrics.NewRegisteredMeter("state/delete/account", nil)
storageDeletedMeter = metrics.NewRegisteredMeter("state/delete/storage", nil)
accountTrieNodesMeter = metrics.NewRegisteredMeter("state/trie/account", nil)
storageTriesNodesMeter = metrics.NewRegisteredMeter("state/trie/storage", nil)
)
2 changes: 1 addition & 1 deletion core/state/snapshot/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi
}
root, nodes := snapTrie.Commit(false)
if nodes != nil {
tdb.Update(trie.NewWithNodeSet(nodes))
tdb.Update(root, common.Hash{}, trie.NewWithNodeSet(nodes))
tdb.Commit(root, false)
}
resolver = func(owner common.Hash, path []byte, hash common.Hash) []byte {
Expand Down
2 changes: 1 addition & 1 deletion core/state/snapshot/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ func (t *testHelper) Commit() common.Hash {
if nodes != nil {
t.nodes.Merge(nodes)
}
t.triedb.Update(t.nodes)
t.triedb.Update(root, common.Hash{}, t.nodes)
t.triedb.Commit(root, false)
return root
}
Expand Down
Loading

0 comments on commit 72d4d6d

Please sign in to comment.