github.com/ethereum/go-ethereum@v1.14.3/core/rawdb/accessors_trie.go (about)

     1  // Copyright 2022 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>
    16  
    17  package rawdb
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  
    23  	"github.com/ethereum/go-ethereum/common"
    24  	"github.com/ethereum/go-ethereum/crypto"
    25  	"github.com/ethereum/go-ethereum/ethdb"
    26  	"github.com/ethereum/go-ethereum/log"
    27  )
    28  
    29  // HashScheme is the legacy hash-based state scheme with which trie nodes are
    30  // stored in the disk with node hash as the database key. The advantage of this
    31  // scheme is that different versions of trie nodes can be stored in disk, which
    32  // is very beneficial for constructing archive nodes. The drawback is it will
    33  // store different trie nodes on the same path to different locations on the disk
    34  // with no data locality, and it's unfriendly for designing state pruning.
    35  //
    36  // Now this scheme is still kept for backward compatibility, and it will be used
    37  // for archive node and some other tries(e.g. light trie).
    38  const HashScheme = "hash"
    39  
    40  // PathScheme is the new path-based state scheme with which trie nodes are stored
    41  // in the disk with node path as the database key. This scheme will only store one
    42  // version of state data in the disk, which means that the state pruning operation
    43  // is native. At the same time, this scheme will put adjacent trie nodes in the same
    44  // area of the disk with good data locality property. But this scheme needs to rely
    45  // on extra state diffs to survive deep reorg.
    46  const PathScheme = "path"
    47  
    48  // hasher is used to compute the sha256 hash of the provided data.
    49  type hasher struct{ sha crypto.KeccakState }
    50  
    51  var hasherPool = sync.Pool{
    52  	New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} },
    53  }
    54  
    55  func newHasher() *hasher {
    56  	return hasherPool.Get().(*hasher)
    57  }
    58  
    59  func (h *hasher) hash(data []byte) common.Hash {
    60  	return crypto.HashData(h.sha, data)
    61  }
    62  
    63  func (h *hasher) release() {
    64  	hasherPool.Put(h)
    65  }
    66  
    67  // ReadAccountTrieNode retrieves the account trie node with the specified node path.
    68  func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte {
    69  	data, _ := db.Get(accountTrieNodeKey(path))
    70  	return data
    71  }
    72  
    73  // HasAccountTrieNode checks the presence of the account trie node with the
    74  // specified node path, regardless of the node hash.
    75  func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte) bool {
    76  	has, err := db.Has(accountTrieNodeKey(path))
    77  	if err != nil {
    78  		return false
    79  	}
    80  	return has
    81  }
    82  
    83  // WriteAccountTrieNode writes the provided account trie node into database.
    84  func WriteAccountTrieNode(db ethdb.KeyValueWriter, path []byte, node []byte) {
    85  	if err := db.Put(accountTrieNodeKey(path), node); err != nil {
    86  		log.Crit("Failed to store account trie node", "err", err)
    87  	}
    88  }
    89  
    90  // DeleteAccountTrieNode deletes the specified account trie node from the database.
    91  func DeleteAccountTrieNode(db ethdb.KeyValueWriter, path []byte) {
    92  	if err := db.Delete(accountTrieNodeKey(path)); err != nil {
    93  		log.Crit("Failed to delete account trie node", "err", err)
    94  	}
    95  }
    96  
    97  // ReadStorageTrieNode retrieves the storage trie node with the specified node path.
    98  func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) []byte {
    99  	data, _ := db.Get(storageTrieNodeKey(accountHash, path))
   100  	return data
   101  }
   102  
   103  // HasStorageTrieNode checks the presence of the storage trie node with the
   104  // specified account hash and node path, regardless of the node hash.
   105  func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) bool {
   106  	has, err := db.Has(storageTrieNodeKey(accountHash, path))
   107  	if err != nil {
   108  		return false
   109  	}
   110  	return has
   111  }
   112  
   113  // WriteStorageTrieNode writes the provided storage trie node into database.
   114  func WriteStorageTrieNode(db ethdb.KeyValueWriter, accountHash common.Hash, path []byte, node []byte) {
   115  	if err := db.Put(storageTrieNodeKey(accountHash, path), node); err != nil {
   116  		log.Crit("Failed to store storage trie node", "err", err)
   117  	}
   118  }
   119  
   120  // DeleteStorageTrieNode deletes the specified storage trie node from the database.
   121  func DeleteStorageTrieNode(db ethdb.KeyValueWriter, accountHash common.Hash, path []byte) {
   122  	if err := db.Delete(storageTrieNodeKey(accountHash, path)); err != nil {
   123  		log.Crit("Failed to delete storage trie node", "err", err)
   124  	}
   125  }
   126  
   127  // ReadLegacyTrieNode retrieves the legacy trie node with the given
   128  // associated node hash.
   129  func ReadLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte {
   130  	data, err := db.Get(hash.Bytes())
   131  	if err != nil {
   132  		return nil
   133  	}
   134  	return data
   135  }
   136  
   137  // HasLegacyTrieNode checks if the trie node with the provided hash is present in db.
   138  func HasLegacyTrieNode(db ethdb.KeyValueReader, hash common.Hash) bool {
   139  	ok, _ := db.Has(hash.Bytes())
   140  	return ok
   141  }
   142  
   143  // WriteLegacyTrieNode writes the provided legacy trie node to database.
   144  func WriteLegacyTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) {
   145  	if err := db.Put(hash.Bytes(), node); err != nil {
   146  		log.Crit("Failed to store legacy trie node", "err", err)
   147  	}
   148  }
   149  
   150  // DeleteLegacyTrieNode deletes the specified legacy trie node from database.
   151  func DeleteLegacyTrieNode(db ethdb.KeyValueWriter, hash common.Hash) {
   152  	if err := db.Delete(hash.Bytes()); err != nil {
   153  		log.Crit("Failed to delete legacy trie node", "err", err)
   154  	}
   155  }
   156  
   157  // HasTrieNode checks the trie node presence with the provided node info and
   158  // the associated node hash.
   159  func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) bool {
   160  	switch scheme {
   161  	case HashScheme:
   162  		return HasLegacyTrieNode(db, hash)
   163  	case PathScheme:
   164  		var blob []byte
   165  		if owner == (common.Hash{}) {
   166  			blob = ReadAccountTrieNode(db, path)
   167  		} else {
   168  			blob = ReadStorageTrieNode(db, owner, path)
   169  		}
   170  		if len(blob) == 0 {
   171  			return false
   172  		}
   173  		h := newHasher()
   174  		defer h.release()
   175  		return h.hash(blob) == hash // exists but not match
   176  	default:
   177  		panic(fmt.Sprintf("Unknown scheme %v", scheme))
   178  	}
   179  }
   180  
   181  // ReadTrieNode retrieves the trie node from database with the provided node info
   182  // and associated node hash.
   183  func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) []byte {
   184  	switch scheme {
   185  	case HashScheme:
   186  		return ReadLegacyTrieNode(db, hash)
   187  	case PathScheme:
   188  		var blob []byte
   189  		if owner == (common.Hash{}) {
   190  			blob = ReadAccountTrieNode(db, path)
   191  		} else {
   192  			blob = ReadStorageTrieNode(db, owner, path)
   193  		}
   194  		if len(blob) == 0 {
   195  			return nil
   196  		}
   197  		h := newHasher()
   198  		defer h.release()
   199  		if h.hash(blob) != hash {
   200  			return nil // exists but not match
   201  		}
   202  		return blob
   203  	default:
   204  		panic(fmt.Sprintf("Unknown scheme %v", scheme))
   205  	}
   206  }
   207  
   208  // WriteTrieNode writes the trie node into database with the provided node info.
   209  //
   210  // hash-scheme requires the node hash as the identifier.
   211  // path-scheme requires the node owner and path as the identifier.
   212  func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte, scheme string) {
   213  	switch scheme {
   214  	case HashScheme:
   215  		WriteLegacyTrieNode(db, hash, node)
   216  	case PathScheme:
   217  		if owner == (common.Hash{}) {
   218  			WriteAccountTrieNode(db, path, node)
   219  		} else {
   220  			WriteStorageTrieNode(db, owner, path, node)
   221  		}
   222  	default:
   223  		panic(fmt.Sprintf("Unknown scheme %v", scheme))
   224  	}
   225  }
   226  
   227  // DeleteTrieNode deletes the trie node from database with the provided node info.
   228  //
   229  // hash-scheme requires the node hash as the identifier.
   230  // path-scheme requires the node owner and path as the identifier.
   231  func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, scheme string) {
   232  	switch scheme {
   233  	case HashScheme:
   234  		DeleteLegacyTrieNode(db, hash)
   235  	case PathScheme:
   236  		if owner == (common.Hash{}) {
   237  			DeleteAccountTrieNode(db, path)
   238  		} else {
   239  			DeleteStorageTrieNode(db, owner, path)
   240  		}
   241  	default:
   242  		panic(fmt.Sprintf("Unknown scheme %v", scheme))
   243  	}
   244  }
   245  
   246  // ReadStateScheme reads the state scheme of persistent state, or none
   247  // if the state is not present in database.
   248  func ReadStateScheme(db ethdb.Reader) string {
   249  	// Check if state in path-based scheme is present.
   250  	if HasAccountTrieNode(db, nil) {
   251  		return PathScheme
   252  	}
   253  	// The root node might be deleted during the initial snap sync, check
   254  	// the persistent state id then.
   255  	if id := ReadPersistentStateID(db); id != 0 {
   256  		return PathScheme
   257  	}
   258  	// In a hash-based scheme, the genesis state is consistently stored
   259  	// on the disk. To assess the scheme of the persistent state, it
   260  	// suffices to inspect the scheme of the genesis state.
   261  	header := ReadHeader(db, ReadCanonicalHash(db, 0), 0)
   262  	if header == nil {
   263  		return "" // empty datadir
   264  	}
   265  	if !HasLegacyTrieNode(db, header.Root) {
   266  		return "" // no state in disk
   267  	}
   268  	return HashScheme
   269  }
   270  
   271  // ParseStateScheme checks if the specified state scheme is compatible with
   272  // the stored state.
   273  //
   274  //   - If the provided scheme is none, use the scheme consistent with persistent
   275  //     state, or fallback to path-based scheme if state is empty.
   276  //
   277  //   - If the provided scheme is hash, use hash-based scheme or error out if not
   278  //     compatible with persistent state scheme.
   279  //
   280  //   - If the provided scheme is path: use path-based scheme or error out if not
   281  //     compatible with persistent state scheme.
   282  func ParseStateScheme(provided string, disk ethdb.Database) (string, error) {
   283  	// If state scheme is not specified, use the scheme consistent
   284  	// with persistent state, or fallback to hash mode if database
   285  	// is empty.
   286  	stored := ReadStateScheme(disk)
   287  	if provided == "" {
   288  		if stored == "" {
   289  			log.Info("State schema set to default", "scheme", "path")
   290  			return PathScheme, nil // use default scheme for empty database
   291  		}
   292  		log.Info("State scheme set to already existing", "scheme", stored)
   293  		return stored, nil // reuse scheme of persistent scheme
   294  	}
   295  	// If state scheme is specified, ensure it's compatible with
   296  	// persistent state.
   297  	if stored == "" || provided == stored {
   298  		log.Info("State scheme set by user", "scheme", provided)
   299  		return provided, nil
   300  	}
   301  	return "", fmt.Errorf("incompatible state scheme, stored: %s, provided: %s", stored, provided)
   302  }