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

KIP-111 Use ExtHash in trie database #1859

Merged
merged 15 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
9 changes: 5 additions & 4 deletions blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {

// StateAtWithPersistent returns a new mutable state based on a particular point in time with persistent trie nodes.
func (bc *BlockChain) StateAtWithPersistent(root common.Hash) (*state.StateDB, error) {
exist := bc.stateCache.TrieDB().DoesExistNodeInPersistent(root)
exist := bc.stateCache.TrieDB().DoesExistNodeInPersistent(root.ExtendLegacy())
if !exist {
return nil, ErrNotExistNode
}
Expand All @@ -700,7 +700,7 @@ func (bc *BlockChain) StateAtWithPersistent(root common.Hash) (*state.StateDB, e
func (bc *BlockChain) StateAtWithGCLock(root common.Hash) (*state.StateDB, error) {
bc.RLockGCCachedNode()

exist := bc.stateCache.TrieDB().DoesExistCachedNode(root)
exist := bc.stateCache.TrieDB().DoesExistCachedNode(root.ExtendLegacy())
if !exist {
bc.RUnlockGCCachedNode()
return nil, ErrNotExistNode
Expand Down Expand Up @@ -977,8 +977,9 @@ func (bc *BlockChain) GetLogsByHash(hash common.Hash) [][]*types.Log {

// TrieNode retrieves a blob of data associated with a trie node
// either from ephemeral in-memory cache, or from persistent storage.
// Cannot retrieve nodes keyed with ExtHash
func (bc *BlockChain) TrieNode(hash common.Hash) ([]byte, error) {
return bc.stateCache.TrieDB().Node(hash)
return bc.stateCache.TrieDB().Node(hash.ExtendLegacy())
}

// ContractCode retrieves a blob of data associated with a contract hash
Expand Down Expand Up @@ -1303,7 +1304,7 @@ func (bc *BlockChain) writeStateTrie(block *types.Block, state *state.StateDB) e
bc.lastCommittedBlock = block.NumberU64()
} else {
// Full but not archive node, do proper garbage collection
trieDB.Reference(root, common.Hash{}) // metadata reference to keep trie alive
trieDB.ReferenceRoot(root) // metadata reference to keep trie alive

// If we exceeded our memory allowance, flush matured singleton nodes to disk
var (
Expand Down
8 changes: 5 additions & 3 deletions blockchain/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type Database interface {
OpenTrie(root common.Hash, opts *statedb.TrieOpts) (Trie, error)

// OpenStorageTrie opens the storage trie of an account.
OpenStorageTrie(root common.Hash, opts *statedb.TrieOpts) (Trie, error)
OpenStorageTrie(root common.ExtHash, opts *statedb.TrieOpts) (Trie, error)

// CopyTrie returns an independent copy of the given trie.
CopyTrie(Trie) Trie
Expand Down Expand Up @@ -94,9 +94,11 @@ type Trie interface {
// Hash returns the root hash of the trie. It does not write to the database and
// can be used even if the trie doesn't have one.
Hash() common.Hash
HashExt() common.ExtHash
// Commit writes all nodes to the trie's memory database, tracking the internal
// and external (for account tries) references.
Commit(onleaf statedb.LeafCallback) (common.Hash, error)
CommitExt(onleaf statedb.LeafCallback) (common.ExtHash, error)
// NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key.
NodeIterator(startKey []byte) statedb.NodeIterator
Expand Down Expand Up @@ -167,8 +169,8 @@ func (db *cachingDB) OpenTrie(root common.Hash, opts *statedb.TrieOpts) (Trie, e
}

// OpenStorageTrie opens the storage trie of an account.
func (db *cachingDB) OpenStorageTrie(root common.Hash, opts *statedb.TrieOpts) (Trie, error) {
return statedb.NewSecureTrie(root, db.db, opts)
func (db *cachingDB) OpenStorageTrie(root common.ExtHash, opts *statedb.TrieOpts) (Trie, error) {
return statedb.NewSecureStorageTrie(root, db.db, opts)
}

// CopyTrie returns an independent copy of the given trie.
Expand Down
2 changes: 1 addition & 1 deletion blockchain/state/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (self *StateDB) RawDump() Dump {
Storage: make(map[string]string),
}
if pa := account.GetProgramAccount(data); pa != nil {
acc.Root = common.Bytes2Hex(pa.GetStorageRoot().Bytes())
acc.Root = common.Bytes2Hex(pa.GetStorageRoot().Unextend().Bytes())
acc.CodeHash = common.Bytes2Hex(pa.GetCodeHash())
acc.Code = common.Bytes2Hex(obj.Code(self.db))
} else {
Expand Down
11 changes: 6 additions & 5 deletions blockchain/state/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func CheckStateConsistencyParallel(oldDB Database, newDB Database, root common.H
return errors.WithMessage(err, "can not open newDB trie")
}
// get children hash
children, err := oldDB.TrieDB().NodeChildren(root)
children, err := oldDB.TrieDB().NodeChildren(root.ExtendLegacy()) // does not work with Online Pruning
if err != nil {
logger.Error("cannot start CheckStateConsistencyParallel", "err", err)
return errors.WithMessage(err, "cannot get children before consistency check")
Expand Down Expand Up @@ -246,19 +246,20 @@ func CheckStateConsistencyParallel(oldDB Database, newDB Database, root common.H
}

// concurrentIterator checks the consistency of all state/storage trie of given two state database
// and pass the result via the channel.
func concurrentIterator(oldDB Database, newDB Database, root common.Hash, quit chan struct{}, resultCh chan struct{}, finishCh chan error) (resultErr error) {
// and pass the result via the channel. The 'root' here can be a subtrie root.
// Does not work with Online Pruning.
func concurrentIterator(oldDB Database, newDB Database, root common.ExtHash, quit chan struct{}, resultCh chan struct{}, finishCh chan error) (resultErr error) {
defer func() {
finishCh <- resultErr
}()

// Create and iterate a state trie rooted in a sub-node
oldState, err := New(root, oldDB, nil, nil)
oldState, err := New(root.Unextend(), oldDB, nil, nil)
if err != nil {
return errors.WithMessage(err, "can not open oldDB trie")
}

newState, err := New(root, newDB, nil, nil)
newState, err := New(root.Unextend(), newDB, nil, nil)
if err != nil {
return errors.WithMessage(err, "can not open newDB trie")
}
Expand Down
45 changes: 30 additions & 15 deletions blockchain/state/iterator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ import (
"testing"

"github.com/klaytn/klaytn/common"
"github.com/klaytn/klaytn/log"
"github.com/klaytn/klaytn/storage/database"
)

// Tests that the node iterator indeed walks over the entire database contents.
func TestNodeIteratorCoverage(t *testing.T) {
log.EnableLogForTest(log.LvlCrit, log.LvlCrit)
// Create some arbitrary test state to iterate
db, root, _ := makeTestState(t)
db.TrieDB().Commit(root, false, 0)
Expand All @@ -37,37 +40,49 @@ func TestNodeIteratorCoverage(t *testing.T) {
t.Fatalf("failed to create state trie at %x: %v", root, err)
}
// Gather all the node hashes found by the iterator
hashes := make(map[common.Hash]struct{})
iterated := make(map[common.Hash]struct{})
for it := NewNodeIterator(state); it.Next(); {
if it.Hash != (common.Hash{}) {
hashes[it.Hash] = struct{}{}
iterated[it.Hash] = struct{}{}
}
}

// Cross check the iterated hashes and the database/nodepool content
for hash := range hashes {
if _, err = db.TrieDB().Node(hash); err != nil {
_, err = db.ContractCode(hash)
}
if err != nil {
t.Errorf("failed to retrieve reported node %x", hash)
// (TrieDB.Node + ContractCode) contains all iterated hashes
for itHash := range iterated {
_, err1 := db.TrieDB().Node(itHash.ExtendLegacy())
_, err2 := db.ContractCode(itHash)
if err1 != nil && err2 != nil { // both failed
t.Errorf("failed to retrieve reported node %x", itHash)
}
}
for _, hash := range db.TrieDB().Nodes() {
if _, ok := hashes[hash]; !ok && hash != emptyCode {
// iterated hashes contains all TrieDB.Nodes
for _, exthash := range db.TrieDB().Nodes() {
hash := exthash.Unextend()
if hash == emptyCode {
continue // skip emptyCode
}
if _, ok := iterated[hash]; !ok {
t.Errorf("state entry not reported %x", hash)
}
}
// iterated hashes contains all DiskDB keys
// StateTrieDB contains preimages, codes and nodes
it := db.TrieDB().DiskDB().GetMemDB().NewIterator(nil, nil)
for it.Next() {
key := it.Key()
if bytes.HasPrefix(key, []byte("secure-key-")) {
continue
continue // skip preimages
}
if bytes.Compare(emptyCode[:], common.BytesToHash(key).Bytes()) == 0 {
continue
if isCode, _ := database.IsCodeKey(key); isCode {
continue // skip codes
}
if _, ok := hashes[common.BytesToHash(key)]; !ok {
t.Errorf("state entry not reported %x", key)
hash := common.BytesToExtHash(key).Unextend()
if hash == emptyCode {
continue // skip emptyCode
}
if _, ok := iterated[hash]; !ok {
t.Errorf("state entry not reported %x", hash)
}
}
it.Release()
Expand Down
12 changes: 6 additions & 6 deletions blockchain/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func (c *stateObject) touch() {
}
}

func (c *stateObject) openStorageTrie(hash common.Hash, db Database) (Trie, error) {
func (c *stateObject) openStorageTrie(hash common.ExtHash, db Database) (Trie, error) {
return db.OpenStorageTrie(hash, &statedb.TrieOpts{Prefetching: c.db.prefetching})
}

Expand All @@ -166,12 +166,12 @@ func (c *stateObject) getStorageTrie(db Database) Trie {
var err error
c.storageTrie, err = c.openStorageTrie(acc.GetStorageRoot(), db)
if err != nil {
c.storageTrie, _ = c.openStorageTrie(common.Hash{}, db)
c.storageTrie, _ = c.openStorageTrie(common.ExtHash{}, db)
c.setError(fmt.Errorf("can't create storage trie: %v", err))
}
} else {
// not a contract account, just returns the empty trie.
c.storageTrie, _ = c.openStorageTrie(common.Hash{}, db)
c.storageTrie, _ = c.openStorageTrie(common.ExtHash{}, db)
}
}
return c.storageTrie
Expand Down Expand Up @@ -376,7 +376,7 @@ func (self *stateObject) updateStorageRoot(db Database) {
if EnabledExpensive {
defer func(start time.Time) { self.db.StorageHashes += time.Since(start) }(time.Now())
}
acc.SetStorageRoot(self.storageTrie.Hash())
acc.SetStorageRoot(self.storageTrie.HashExt())
}
}

Expand All @@ -389,7 +389,7 @@ func (self *stateObject) setStorageRoot(updateStorageRoot bool, objectsToUpdate
if EnabledExpensive {
defer func(start time.Time) { self.db.StorageHashes += time.Since(start) }(time.Now())
}
acc.SetStorageRoot(self.storageTrie.Hash())
acc.SetStorageRoot(self.storageTrie.HashExt())
return
}
// If updateStorageRoot == false, it just marks the object and updates its storage root later.
Expand All @@ -409,7 +409,7 @@ func (self *stateObject) CommitStorageTrie(db Database) error {
defer func(start time.Time) { self.db.StorageCommits += time.Since(start) }(time.Now())
}
if acc := account.GetProgramAccount(self.account); acc != nil {
root, err := self.storageTrie.Commit(nil)
root, err := self.storageTrie.CommitExt(nil)
if err != nil {
return err
}
Expand Down
12 changes: 6 additions & 6 deletions blockchain/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -1015,15 +1015,15 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error)
if EnabledExpensive {
defer func(start time.Time) { s.AccountCommits += time.Since(start) }(time.Now())
}
root, err = s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash, parentDepth int) error {
root, err = s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.ExtHash, parentDepth int) error {
serializer := account.NewAccountSerializer()
if err := rlp.DecodeBytes(leaf, serializer); err != nil {
logger.Warn("RLP decode failed", "err", err, "leaf", string(leaf))
return nil
}
acc := serializer.GetAccount()
if pa := account.GetProgramAccount(acc); pa != nil {
if pa.GetStorageRoot() != emptyState {
if pa.GetStorageRoot().Unextend() != emptyState {
s.db.TrieDB().Reference(pa.GetStorageRoot(), parent)
}
}
Expand Down Expand Up @@ -1064,17 +1064,17 @@ var (
errNotContractAddress = fmt.Errorf("given address is not a contract address")
)

func (s *StateDB) GetContractStorageRoot(contractAddr common.Address) (common.Hash, error) {
func (s *StateDB) GetContractStorageRoot(contractAddr common.Address) (common.ExtHash, error) {
acc := s.GetAccount(contractAddr)
if acc == nil {
return common.Hash{}, errNotExistingAddress
return common.ExtHash{}, errNotExistingAddress
}
if acc.Type() != account.SmartContractAccountType {
return common.Hash{}, errNotContractAddress
return common.ExtHash{}, errNotContractAddress
}
contract, true := acc.(*account.SmartContractAccount)
if !true {
return common.Hash{}, errNotContractAddress
return common.ExtHash{}, errNotContractAddress
}
return contract.GetStorageRoot(), nil
}
Expand Down
2 changes: 1 addition & 1 deletion blockchain/state/statedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ func TestCopyOfCopy(t *testing.T) {
// TestZeroHashNode checks returning values of `(db *Database) Node` function.
// The function should return (nil, ErrZeroHashNode) for default common.Hash{} value.
func TestZeroHashNode(t *testing.T) {
zeroHash := common.Hash{}
zeroHash := common.ExtHash{}

db := database.NewMemoryDBManager()
sdb := NewDatabase(db)
Expand Down
10 changes: 5 additions & 5 deletions blockchain/state/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ import (
// LRU cache is mendatory when state syncing and block processing are executed simultaneously
func NewStateSync(root common.Hash, database statedb.StateTrieReadDB, bloom *statedb.SyncBloom, lruCache *lru.Cache, onLeaf func(paths [][]byte, leaf []byte) error) *statedb.TrieSync {
// Register the storage slot callback if the external callback is specified.
var onSlot func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash, parentDepth int) error
var onSlot statedb.LeafCallback
if onLeaf != nil {
onSlot = func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash, parentDepth int) error {
onSlot = func(paths [][]byte, _ []byte, leaf []byte, _ common.ExtHash, _ int) error {
return onLeaf(paths, leaf)
}
}
// Register the account callback to connect the state trie and the storage
// trie belongs to the contract.
var syncer *statedb.TrieSync
onAccount := func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash, parentDepth int) error {
onAccount := func(paths [][]byte, hexpath []byte, leaf []byte, parent common.ExtHash, parentDepth int) error {
if onLeaf != nil {
if err := onLeaf(paths, leaf); err != nil {
return err
Expand All @@ -55,8 +55,8 @@ func NewStateSync(root common.Hash, database statedb.StateTrieReadDB, bloom *sta
}
obj := serializer.GetAccount()
if pa := account.GetProgramAccount(obj); pa != nil {
syncer.AddSubTrie(pa.GetStorageRoot(), hexpath, parentDepth+1, parent, onSlot)
syncer.AddCodeEntry(common.BytesToHash(pa.GetCodeHash()), hexpath, parentDepth+1, parent)
syncer.AddSubTrie(pa.GetStorageRoot().Unextend(), hexpath, parentDepth+1, parent.Unextend(), onSlot)
syncer.AddCodeEntry(common.BytesToHash(pa.GetCodeHash()), hexpath, parentDepth+1, parent.Unextend())
}
return nil
}
Expand Down
Loading
Loading