* all: activate pbss * core/rawdb: fix compilation error * cma, core, eth, les, trie: address comments * cmd, core, eth, trie: polish code * core, cmd, eth: address comments * cmd, core, eth, les, light, tests: address comment * cmd/utils: shorten log message * trie/triedb/pathdb: limit node buffer size to 1gb * cmd/utils: fix opening non-existing db * cmd/utils: rename flag name * cmd, core: group chain history flags and fix tests * core, eth, trie: fix memory leak in snapshot generation * cmd, eth, internal: deprecate flags * all: enable state tests for pathdb, fixes * cmd, core: polish code * trie/triedb/pathdb: limit the node buffer size to 256mb --------- Co-authored-by: Martin Holst Swende <martin@swende.se> Co-authored-by: Péter Szilágyi <peterke@gmail.com>
		
			
				
	
	
		
			288 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			288 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2022 The go-ethereum Authors
 | 
						|
// This file is part of the go-ethereum library.
 | 
						|
//
 | 
						|
// The go-ethereum library is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU Lesser General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// The go-ethereum library is distributed in the hope that it will be useful,
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
						|
// GNU Lesser General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU Lesser General Public License
 | 
						|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
 | 
						|
 | 
						|
package rawdb
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"sync"
 | 
						|
 | 
						|
	"github.com/ethereum/go-ethereum/common"
 | 
						|
	"github.com/ethereum/go-ethereum/crypto"
 | 
						|
	"github.com/ethereum/go-ethereum/ethdb"
 | 
						|
	"github.com/ethereum/go-ethereum/log"
 | 
						|
	"golang.org/x/crypto/sha3"
 | 
						|
)
 | 
						|
 | 
						|
// HashScheme is the legacy hash-based state scheme with which trie nodes are
 | 
						|
// stored in the disk with node hash as the database key. The advantage of this
 | 
						|
// scheme is that different versions of trie nodes can be stored in disk, which
 | 
						|
// is very beneficial for constructing archive nodes. The drawback is it will
 | 
						|
// store different trie nodes on the same path to different locations on the disk
 | 
						|
// with no data locality, and it's unfriendly for designing state pruning.
 | 
						|
//
 | 
						|
// Now this scheme is still kept for backward compatibility, and it will be used
 | 
						|
// for archive node and some other tries(e.g. light trie).
 | 
						|
const HashScheme = "hash"
 | 
						|
 | 
						|
// PathScheme is the new path-based state scheme with which trie nodes are stored
 | 
						|
// in the disk with node path as the database key. This scheme will only store one
 | 
						|
// version of state data in the disk, which means that the state pruning operation
 | 
						|
// is native. At the same time, this scheme will put adjacent trie nodes in the same
 | 
						|
// area of the disk with good data locality property. But this scheme needs to rely
 | 
						|
// on extra state diffs to survive deep reorg.
 | 
						|
const PathScheme = "path"
 | 
						|
 | 
						|
// hasher is used to compute the sha256 hash of the provided data.
 | 
						|
type hasher struct{ sha crypto.KeccakState }
 | 
						|
 | 
						|
var hasherPool = sync.Pool{
 | 
						|
	New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} },
 | 
						|
}
 | 
						|
 | 
						|
func newHasher() *hasher {
 | 
						|
	return hasherPool.Get().(*hasher)
 | 
						|
}
 | 
						|
 | 
						|
func (h *hasher) hash(data []byte) common.Hash {
 | 
						|
	return crypto.HashData(h.sha, data)
 | 
						|
}
 | 
						|
 | 
						|
func (h *hasher) release() {
 | 
						|
	hasherPool.Put(h)
 | 
						|
}
 | 
						|
 | 
						|
// ReadAccountTrieNode retrieves the account trie node and the associated node
 | 
						|
// hash with the specified node path.
 | 
						|
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.Hash) {
 | 
						|
	data, err := db.Get(accountTrieNodeKey(path))
 | 
						|
	if err != nil {
 | 
						|
		return nil, common.Hash{}
 | 
						|
	}
 | 
						|
	h := newHasher()
 | 
						|
	defer h.release()
 | 
						|
	return data, h.hash(data)
 | 
						|
}
 | 
						|
 | 
						|
// HasAccountTrieNode checks the account trie node presence with the specified
 | 
						|
// node path and the associated node hash.
 | 
						|
func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte, hash common.Hash) bool {
 | 
						|
	data, err := db.Get(accountTrieNodeKey(path))
 | 
						|
	if err != nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	h := newHasher()
 | 
						|
	defer h.release()
 | 
						|
	return h.hash(data) == hash
 | 
						|
}
 | 
						|
 | 
						|
// WriteAccountTrieNode writes the provided account trie node into database.
 | 
						|
func WriteAccountTrieNode(db ethdb.KeyValueWriter, path []byte, node []byte) {
 | 
						|
	if err := db.Put(accountTrieNodeKey(path), node); err != nil {
 | 
						|
		log.Crit("Failed to store account trie node", "err", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// DeleteAccountTrieNode deletes the specified account trie node from the database.
 | 
						|
func DeleteAccountTrieNode(db ethdb.KeyValueWriter, path []byte) {
 | 
						|
	if err := db.Delete(accountTrieNodeKey(path)); err != nil {
 | 
						|
		log.Crit("Failed to delete account trie node", "err", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ReadStorageTrieNode retrieves the storage trie node and the associated node
 | 
						|
// hash with the specified node path.
 | 
						|
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) ([]byte, common.Hash) {
 | 
						|
	data, err := db.Get(storageTrieNodeKey(accountHash, path))
 | 
						|
	if err != nil {
 | 
						|
		return nil, common.Hash{}
 | 
						|
	}
 | 
						|
	h := newHasher()
 | 
						|
	defer h.release()
 | 
						|
	return data, h.hash(data)
 | 
						|
}
 | 
						|
 | 
						|
// HasStorageTrieNode checks the storage trie node presence with the provided
 | 
						|
// node path and the associated node hash.
 | 
						|
func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte, hash common.Hash) bool {
 | 
						|
	data, err := db.Get(storageTrieNodeKey(accountHash, path))
 | 
						|
	if err != nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	h := newHasher()
 | 
						|
	defer h.release()
 | 
						|
	return h.hash(data) == hash
 | 
						|
}
 | 
						|
 | 
						|
// WriteStorageTrieNode writes the provided storage trie node into database.
 | 
						|
func WriteStorageTrieNode(db ethdb.KeyValueWriter, accountHash common.Hash, path []byte, node []byte) {
 | 
						|
	if err := db.Put(storageTrieNodeKey(accountHash, path), node); err != nil {
 | 
						|
		log.Crit("Failed to store storage trie node", "err", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// DeleteStorageTrieNode deletes the specified storage trie node from the database.
 | 
						|
func DeleteStorageTrieNode(db ethdb.KeyValueWriter, accountHash common.Hash, path []byte) {
 | 
						|
	if err := db.Delete(storageTrieNodeKey(accountHash, path)); err != nil {
 | 
						|
		log.Crit("Failed to delete storage trie node", "err", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ReadLegacyTrieNode retrieves the legacy trie node with the given
 | 
						|
// associated node hash.
 | 
						|
func ReadLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte {
 | 
						|
	data, err := db.Get(hash.Bytes())
 | 
						|
	if err != nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return data
 | 
						|
}
 | 
						|
 | 
						|
// HasLegacyTrieNode checks if the trie node with the provided hash is present in db.
 | 
						|
func HasLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) bool {
 | 
						|
	ok, _ := db.Has(hash.Bytes())
 | 
						|
	return ok
 | 
						|
}
 | 
						|
 | 
						|
// WriteLegacyTrieNode writes the provided legacy trie node to database.
 | 
						|
func WriteLegacyTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) {
 | 
						|
	if err := db.Put(hash.Bytes(), node); err != nil {
 | 
						|
		log.Crit("Failed to store legacy trie node", "err", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// DeleteLegacyTrieNode deletes the specified legacy trie node from database.
 | 
						|
func DeleteLegacyTrieNode(db ethdb.KeyValueWriter, hash common.Hash) {
 | 
						|
	if err := db.Delete(hash.Bytes()); err != nil {
 | 
						|
		log.Crit("Failed to delete legacy trie node", "err", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// HasTrieNode checks the trie node presence with the provided node info and
 | 
						|
// the associated node hash.
 | 
						|
func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) bool {
 | 
						|
	switch scheme {
 | 
						|
	case HashScheme:
 | 
						|
		return HasLegacyTrieNode(db, hash)
 | 
						|
	case PathScheme:
 | 
						|
		if owner == (common.Hash{}) {
 | 
						|
			return HasAccountTrieNode(db, path, hash)
 | 
						|
		}
 | 
						|
		return HasStorageTrieNode(db, owner, path, hash)
 | 
						|
	default:
 | 
						|
		panic(fmt.Sprintf("Unknown scheme %v", scheme))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ReadTrieNode retrieves the trie node from database with the provided node info
 | 
						|
// and associated node hash.
 | 
						|
// hashScheme-based lookup requires the following:
 | 
						|
//   - hash
 | 
						|
//
 | 
						|
// pathScheme-based lookup requires the following:
 | 
						|
//   - owner
 | 
						|
//   - path
 | 
						|
func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) []byte {
 | 
						|
	switch scheme {
 | 
						|
	case HashScheme:
 | 
						|
		return ReadLegacyTrieNode(db, hash)
 | 
						|
	case PathScheme:
 | 
						|
		var (
 | 
						|
			blob  []byte
 | 
						|
			nHash common.Hash
 | 
						|
		)
 | 
						|
		if owner == (common.Hash{}) {
 | 
						|
			blob, nHash = ReadAccountTrieNode(db, path)
 | 
						|
		} else {
 | 
						|
			blob, nHash = ReadStorageTrieNode(db, owner, path)
 | 
						|
		}
 | 
						|
		if nHash != hash {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		return blob
 | 
						|
	default:
 | 
						|
		panic(fmt.Sprintf("Unknown scheme %v", scheme))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WriteTrieNode writes the trie node into database with the provided node info
 | 
						|
// and associated node hash.
 | 
						|
// hashScheme-based lookup requires the following:
 | 
						|
//   - hash
 | 
						|
//
 | 
						|
// pathScheme-based lookup requires the following:
 | 
						|
//   - owner
 | 
						|
//   - path
 | 
						|
func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte, scheme string) {
 | 
						|
	switch scheme {
 | 
						|
	case HashScheme:
 | 
						|
		WriteLegacyTrieNode(db, hash, node)
 | 
						|
	case PathScheme:
 | 
						|
		if owner == (common.Hash{}) {
 | 
						|
			WriteAccountTrieNode(db, path, node)
 | 
						|
		} else {
 | 
						|
			WriteStorageTrieNode(db, owner, path, node)
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		panic(fmt.Sprintf("Unknown scheme %v", scheme))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// DeleteTrieNode deletes the trie node from database with the provided node info
 | 
						|
// and associated node hash.
 | 
						|
// hashScheme-based lookup requires the following:
 | 
						|
//   - hash
 | 
						|
//
 | 
						|
// pathScheme-based lookup requires the following:
 | 
						|
//   - owner
 | 
						|
//   - path
 | 
						|
func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, scheme string) {
 | 
						|
	switch scheme {
 | 
						|
	case HashScheme:
 | 
						|
		DeleteLegacyTrieNode(db, hash)
 | 
						|
	case PathScheme:
 | 
						|
		if owner == (common.Hash{}) {
 | 
						|
			DeleteAccountTrieNode(db, path)
 | 
						|
		} else {
 | 
						|
			DeleteStorageTrieNode(db, owner, path)
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		panic(fmt.Sprintf("Unknown scheme %v", scheme))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ReadStateScheme reads the state scheme of persistent state, or none
 | 
						|
// if the state is not present in database.
 | 
						|
func ReadStateScheme(db ethdb.Reader) string {
 | 
						|
	// Check if state in path-based scheme is present
 | 
						|
	blob, _ := ReadAccountTrieNode(db, nil)
 | 
						|
	if len(blob) != 0 {
 | 
						|
		return PathScheme
 | 
						|
	}
 | 
						|
	// In a hash-based scheme, the genesis state is consistently stored
 | 
						|
	// on the disk. To assess the scheme of the persistent state, it
 | 
						|
	// suffices to inspect the scheme of the genesis state.
 | 
						|
	header := ReadHeader(db, ReadCanonicalHash(db, 0), 0)
 | 
						|
	if header == nil {
 | 
						|
		return "" // empty datadir
 | 
						|
	}
 | 
						|
	blob = ReadLegacyTrieNode(db, header.Root)
 | 
						|
	if len(blob) == 0 {
 | 
						|
		return "" // no state in disk
 | 
						|
	}
 | 
						|
	return HashScheme
 | 
						|
}
 |