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 }