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

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