Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ const (
// clean cache's underlying fastcache.
trieCleanCacheStatsNamespace = "hashdb/memcache/clean/fastcache"

firewoodFileName = "firewood_state"
firewoodFileName = "firewood.db"
)

// cacheableFeeConfig encapsulates fee configuration itself and the block number that it has changed at,
Expand Down
6 changes: 4 additions & 2 deletions core/extstate/firewood_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ func (db *firewoodAccessorDb) OpenTrie(root common.Hash) (state.Trie, error) {
}

// OpenStorageTrie opens a wrapped version of the account trie.
func (*firewoodAccessorDb) OpenStorageTrie(_ common.Hash, _ common.Address, accountRoot common.Hash, self state.Trie) (state.Trie, error) {
//
//nolint:revive // removing names loses context.
func (*firewoodAccessorDb) OpenStorageTrie(stateRoot common.Hash, addr common.Address, accountRoot common.Hash, self state.Trie) (state.Trie, error) {
accountTrie, ok := self.(*firewood.AccountTrie)
if !ok {
return nil, fmt.Errorf("Invalid account trie type: %T", self)
}
return firewood.NewStorageTrie(accountTrie, accountRoot)
return firewood.NewStorageTrie(accountTrie)
}

// CopyTrie returns a deep copy of the given trie.
Expand Down
45 changes: 31 additions & 14 deletions triedb/firewood/account_trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
// 2. The `Hash` method actually creates the proposal, since Firewood cannot calculate
// the hash of the trie without committing it. It is immediately dropped, and this
// can likely be optimized.
//
// Note this is not concurrent safe.
type AccountTrie struct {
fw *Database
parentRoot common.Hash
Expand All @@ -49,7 +51,10 @@ func NewAccountTrie(root common.Hash, db *Database) (*AccountTrie, error) {
}, nil
}

// GetAccount implements state.Trie.
// GetAccount returns the state account associated with an address.
// - If the account has been updated, the new value is returned.
// - If the account has been deleted, (nil, nil) is returned.
// - If the account does not exist, (nil, nil) is returned.
func (a *AccountTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
key := crypto.Keccak256Hash(addr.Bytes()).Bytes()

Expand Down Expand Up @@ -83,7 +88,10 @@ func (a *AccountTrie) GetAccount(addr common.Address) (*types.StateAccount, erro
return account, err
}

// GetStorage implements state.Trie.
// GetStorage returns the value associated with a storage key for a given account address.
// - If the storage slot has been updated, the new value is returned.
// - If the storage slot has been deleted, (nil, nil) is returned.
// - If the storage slot does not exist, (nil, nil) is returned.
func (a *AccountTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
// If the account has been deleted, we should return nil
accountKey := crypto.Keccak256Hash(addr.Bytes()).Bytes()
Expand Down Expand Up @@ -119,7 +127,8 @@ func (a *AccountTrie) GetStorage(addr common.Address, key []byte) ([]byte, error
return decoded, err
}

// UpdateAccount implements state.Trie.
// UpdateAccount replaces or creates the state account associated with an address.
// This new value will be returned for subsequent `GetAccount` calls.
func (a *AccountTrie) UpdateAccount(addr common.Address, account *types.StateAccount) error {
// Queue the keys and values for later commit
key := crypto.Keccak256Hash(addr.Bytes()).Bytes()
Expand All @@ -134,7 +143,8 @@ func (a *AccountTrie) UpdateAccount(addr common.Address, account *types.StateAcc
return nil
}

// UpdateStorage implements state.Trie.
// UpdateStorage replaces or creates the value associated with a storage key for a given account address.
// This new value will be returned for subsequent `GetStorage` calls.
func (a *AccountTrie) UpdateStorage(addr common.Address, key []byte, value []byte) error {
var combinedKey [2 * common.HashLength]byte
accountKey := crypto.Keccak256Hash(addr.Bytes()).Bytes()
Expand All @@ -155,7 +165,7 @@ func (a *AccountTrie) UpdateStorage(addr common.Address, key []byte, value []byt
return nil
}

// DeleteAccount implements state.Trie.
// DeleteAccount removes the state account associated with an address.
func (a *AccountTrie) DeleteAccount(addr common.Address) error {
key := crypto.Keccak256Hash(addr.Bytes()).Bytes()
// Queue the key for deletion
Expand All @@ -166,7 +176,7 @@ func (a *AccountTrie) DeleteAccount(addr common.Address) error {
return nil
}

// DeleteStorage implements state.Trie.
// DeleteStorage removes the value associated with a storage key for a given account address.
func (a *AccountTrie) DeleteStorage(addr common.Address, key []byte) error {
var combinedKey [2 * common.HashLength]byte
accountKey := crypto.Keccak256Hash(addr.Bytes()).Bytes()
Expand All @@ -182,7 +192,9 @@ func (a *AccountTrie) DeleteStorage(addr common.Address, key []byte) error {
return nil
}

// Hash implements state.Trie.
// Hash returns the current hash of the state trie.
// This will create a proposal and drop it, so it is not efficient to call for each transaction.
// If there are no changes since the last call, the cached root is returned.
func (a *AccountTrie) Hash() common.Hash {
hash, err := a.hash()
if err != nil {
Expand All @@ -205,16 +217,18 @@ func (a *AccountTrie) hash() (common.Hash, error) {
return a.root, nil
}

// Commit implements state.Trie.
func (a *AccountTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) {
// Commit returns the new root hash of the trie and a NodeSet containing all modified accounts and storage slots.
// The format of the NodeSet is different than in go-ethereum's trie implementation due to Firewood's design.
// This boolean is ignored, as it is a relic of the StateTrie implementation.
func (a *AccountTrie) Commit(bool) (common.Hash, *trienode.NodeSet, error) {
// Get the hash of the trie.
hash, err := a.hash()
if err != nil {
return common.Hash{}, nil, err
}

// Create the NodeSet. This will be sent to `triedb.Update` later.
nodeset := trienode.NewNodeSet(a.parentRoot)
nodeset := trienode.NewNodeSet(common.Hash{})
for i, key := range a.updateKeys {
nodeset.AddNode(key, &trienode.Node{
Blob: a.updateValues[i],
Expand All @@ -231,17 +245,20 @@ func (*AccountTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte
}

// GetKey implements state.Trie.
func (*AccountTrie) GetKey(_ []byte) []byte {
return nil // Not implemented, as this is only used in APIs
// This should not be used, since any user should not be accessing by raw key.
func (*AccountTrie) GetKey([]byte) []byte {
return nil
}

// NodeIterator implements state.Trie.
func (*AccountTrie) NodeIterator(_ []byte) (trie.NodeIterator, error) {
// Firewood does not support iterating over internal nodes.
func (*AccountTrie) NodeIterator([]byte) (trie.NodeIterator, error) {
return nil, errors.New("NodeIterator not implemented for Firewood")
}

// Prove implements state.Trie.
func (*AccountTrie) Prove(_ []byte, _ ethdb.KeyValueWriter) error {
// Firewood does not yet support providing key proofs.
func (*AccountTrie) Prove([]byte, ethdb.KeyValueWriter) error {
return errors.New("Prove not implemented for Firewood")
}

Expand Down
47 changes: 24 additions & 23 deletions triedb/firewood/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,12 @@ var Defaults = Config{
ReadCacheStrategy: ffi.CacheAllReads,
}

func (c Config) BackendConstructor(_ ethdb.Database) triedb.DBOverride {
return New(&c)
func (c Config) BackendConstructor(ethdb.Database) triedb.DBOverride {
db, err := New(c)
if err != nil {
log.Crit("firewood: error creating database", "error", err)
}
return db
}

type Database struct {
Expand All @@ -94,14 +98,9 @@ type Database struct {

// New creates a new Firewood database with the given disk database and configuration.
// Any error during creation will cause the program to exit.
func New(config *Config) *Database {
if config == nil {
log.Crit("firewood: config must be provided")
}

err := validatePath(config.FilePath)
if err != nil {
log.Crit("firewood: error validating config", "error", err)
func New(config Config) (*Database, error) {
if err := validatePath(config.FilePath); err != nil {
return nil, err
}

fw, err := ffi.New(config.FilePath, &ffi.Config{
Expand All @@ -111,12 +110,12 @@ func New(config *Config) *Database {
ReadCacheStrategy: config.ReadCacheStrategy,
})
if err != nil {
log.Crit("firewood: error creating firewood database", "error", err)
return nil, err
}

currentRoot, err := fw.Root()
if err != nil {
log.Crit("firewood: error getting current root", "error", err)
return nil, err
}

return &Database{
Expand All @@ -125,7 +124,7 @@ func New(config *Config) *Database {
proposalTree: &ProposalContext{
Root: common.Hash(currentRoot),
},
}
}, nil
}

func validatePath(path string) error {
Expand All @@ -135,17 +134,19 @@ func validatePath(path string) error {

// Check that the directory exists
dir := filepath.Dir(path)
_, err := os.Stat(dir)
if err == nil {
return nil // Directory exists
}
if !os.IsNotExist(err) {
switch info, err := os.Stat(dir); {
case os.IsNotExist(err):
log.Info("Database directory not found, creating", "path", dir)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("error creating database directory: %w", err)
}
return nil
case err != nil:
return fmt.Errorf("error checking database directory: %w", err)
case !info.IsDir():
return fmt.Errorf("database directory path is not a directory: %s", dir)
}
log.Info("Database directory not found, creating", "path", dir)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("error creating database directory: %w", err)
}

return nil
}

Expand All @@ -161,7 +162,7 @@ func (*Database) Scheme() string {
}

// Initialized checks whether a non-empty genesis block has been written.
func (db *Database) Initialized(_ common.Hash) bool {
func (db *Database) Initialized(common.Hash) bool {
rootBytes, err := db.fwDisk.Root()
if err != nil {
log.Error("firewood: error getting current root", "error", err)
Expand Down
18 changes: 9 additions & 9 deletions triedb/firewood/storage_trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ import (

type StorageTrie struct {
*AccountTrie
storageRoot common.Hash
}

// `NewStorageTrie` returns a wrapper around an `AccountTrie` since Firewood
// does not require a separate storage trie. All changes are managed by the account trie.
func NewStorageTrie(accountTrie *AccountTrie, storageRoot common.Hash) (*StorageTrie, error) {
func NewStorageTrie(accountTrie *AccountTrie) (*StorageTrie, error) {
return &StorageTrie{
AccountTrie: accountTrie,
storageRoot: storageRoot,
}, nil
}

// Actual commit is handled by the account trie.
// Return the old storage root as if there was no change - we don't want to use the
// actual account trie hash and nodeset here.
func (s *StorageTrie) Commit(bool) (common.Hash, *trienode.NodeSet, error) {
return s.storageRoot, nil, nil
// Return the old storage root as if there was no change since Firewood
// will manage the hash calculations without it.
// All changes are managed by the account trie.
func (*StorageTrie) Commit(bool) (common.Hash, *trienode.NodeSet, error) {
return common.Hash{}, nil, nil
}

// Firewood doesn't require tracking storage roots inside of an account.
func (s *StorageTrie) Hash() common.Hash {
return s.storageRoot // only used in statedb to populate a `StateAccount`
// They will be updated in place when hashing of the proposal takes place.
func (*StorageTrie) Hash() common.Hash {
return common.Hash{}
}

// Copy should never be called on a storage trie, as it is just a wrapper around the account trie.
Expand Down