github.com/ethereum/go-ethereum@v1.16.1/triedb/pathdb/lookup.go (about) 1 // Copyright 2024 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 pathdb 18 19 import ( 20 "fmt" 21 "sync" 22 "time" 23 24 "github.com/ethereum/go-ethereum/common" 25 "golang.org/x/sync/errgroup" 26 ) 27 28 // storageKey returns a key for uniquely identifying the storage slot. 29 func storageKey(accountHash common.Hash, slotHash common.Hash) [64]byte { 30 var key [64]byte 31 copy(key[:32], accountHash[:]) 32 copy(key[32:], slotHash[:]) 33 return key 34 } 35 36 // lookup is an internal structure used to efficiently determine the layer in 37 // which a state entry resides. 38 type lookup struct { 39 // accounts represents the mutation history for specific accounts. 40 // The key is the account address hash, and the value is a slice 41 // of **diff layer** IDs indicating where the account was modified, 42 // with the order from oldest to newest. 43 accounts map[common.Hash][]common.Hash 44 45 // storages represents the mutation history for specific storage 46 // slot. The key is the account address hash and the storage key 47 // hash, the value is a slice of **diff layer** IDs indicating 48 // where the slot was modified, with the order from oldest to newest. 49 storages map[[64]byte][]common.Hash 50 51 // descendant is the callback indicating whether the layer with 52 // given root is a descendant of the one specified by `ancestor`. 53 descendant func(state common.Hash, ancestor common.Hash) bool 54 } 55 56 // newLookup initializes the lookup structure. 57 func newLookup(head layer, descendant func(state common.Hash, ancestor common.Hash) bool) *lookup { 58 var ( 59 current = head 60 layers []layer 61 ) 62 for current != nil { 63 layers = append(layers, current) 64 current = current.parentLayer() 65 } 66 l := &lookup{ 67 accounts: make(map[common.Hash][]common.Hash), 68 storages: make(map[[64]byte][]common.Hash), 69 descendant: descendant, 70 } 71 // Apply the diff layers from bottom to top 72 for i := len(layers) - 1; i >= 0; i-- { 73 switch diff := layers[i].(type) { 74 case *diskLayer: 75 continue 76 case *diffLayer: 77 l.addLayer(diff) 78 } 79 } 80 return l 81 } 82 83 // accountTip traverses the layer list associated with the given account in 84 // reverse order to locate the first entry that either matches the specified 85 // stateID or is a descendant of it. 86 // 87 // If found, the account data corresponding to the supplied stateID resides 88 // in that layer. Otherwise, two scenarios are possible: 89 // 90 // (a) the account remains unmodified from the current disk layer up to the state 91 // layer specified by the stateID: fallback to the disk layer for data retrieval, 92 // (b) or the layer specified by the stateID is stale: reject the data retrieval. 93 func (l *lookup) accountTip(accountHash common.Hash, stateID common.Hash, base common.Hash) common.Hash { 94 // Traverse the mutation history from latest to oldest one. Several 95 // scenarios are possible: 96 // 97 // Chain: 98 // D->C1->C2->C3->C4 (HEAD) 99 // ->C1'->C2'->C3' 100 // State: 101 // x: [C1, C1', C3', C3] 102 // y: [] 103 // 104 // - (x, C4) => C3 105 // - (x, C3) => C3 106 // - (x, C2) => C1 107 // - (x, C3') => C3' 108 // - (x, C2') => C1' 109 // - (y, C4) => D 110 // - (y, C3') => D 111 // - (y, C0) => null 112 list := l.accounts[accountHash] 113 for i := len(list) - 1; i >= 0; i-- { 114 // If the current state matches the stateID, or the requested state is a 115 // descendant of it, return the current state as the most recent one 116 // containing the modified data. Otherwise, the current state may be ahead 117 // of the requested one or belong to a different branch. 118 if list[i] == stateID || l.descendant(stateID, list[i]) { 119 return list[i] 120 } 121 } 122 // No layer matching the stateID or its descendants was found. Use the 123 // current disk layer as a fallback. 124 if base == stateID || l.descendant(stateID, base) { 125 return base 126 } 127 // The layer associated with 'stateID' is not the descendant of the current 128 // disk layer, it's already stale, return nothing. 129 return common.Hash{} 130 } 131 132 // storageTip traverses the layer list associated with the given account and 133 // slot hash in reverse order to locate the first entry that either matches 134 // the specified stateID or is a descendant of it. 135 // 136 // If found, the storage data corresponding to the supplied stateID resides 137 // in that layer. Otherwise, two scenarios are possible: 138 // 139 // (a) the storage slot remains unmodified from the current disk layer up to 140 // the state layer specified by the stateID: fallback to the disk layer for 141 // data retrieval, (b) or the layer specified by the stateID is stale: reject 142 // the data retrieval. 143 func (l *lookup) storageTip(accountHash common.Hash, slotHash common.Hash, stateID common.Hash, base common.Hash) common.Hash { 144 list := l.storages[storageKey(accountHash, slotHash)] 145 for i := len(list) - 1; i >= 0; i-- { 146 // If the current state matches the stateID, or the requested state is a 147 // descendant of it, return the current state as the most recent one 148 // containing the modified data. Otherwise, the current state may be ahead 149 // of the requested one or belong to a different branch. 150 if list[i] == stateID || l.descendant(stateID, list[i]) { 151 return list[i] 152 } 153 } 154 // No layer matching the stateID or its descendants was found. Use the 155 // current disk layer as a fallback. 156 if base == stateID || l.descendant(stateID, base) { 157 return base 158 } 159 // The layer associated with 'stateID' is not the descendant of the current 160 // disk layer, it's already stale, return nothing. 161 return common.Hash{} 162 } 163 164 // addLayer traverses the state data retained in the specified diff layer and 165 // integrates it into the lookup set. 166 // 167 // This function assumes that all layers older than the provided one have already 168 // been processed, ensuring that layers are processed strictly in a bottom-to-top 169 // order. 170 func (l *lookup) addLayer(diff *diffLayer) { 171 defer func(now time.Time) { 172 lookupAddLayerTimer.UpdateSince(now) 173 }(time.Now()) 174 175 var ( 176 wg sync.WaitGroup 177 state = diff.rootHash() 178 ) 179 wg.Add(1) 180 go func() { 181 defer wg.Done() 182 for accountHash := range diff.states.accountData { 183 list, exists := l.accounts[accountHash] 184 if !exists { 185 list = make([]common.Hash, 0, 16) // TODO(rjl493456442) use sync pool 186 } 187 list = append(list, state) 188 l.accounts[accountHash] = list 189 } 190 }() 191 192 wg.Add(1) 193 go func() { 194 defer wg.Done() 195 for accountHash, slots := range diff.states.storageData { 196 for slotHash := range slots { 197 key := storageKey(accountHash, slotHash) 198 list, exists := l.storages[key] 199 if !exists { 200 list = make([]common.Hash, 0, 16) // TODO(rjl493456442) use sync pool 201 } 202 list = append(list, state) 203 l.storages[key] = list 204 } 205 } 206 }() 207 wg.Wait() 208 } 209 210 // removeFromList removes the specified element from the provided list. 211 // It returns a flag indicating whether the element was found and removed. 212 func removeFromList(list []common.Hash, element common.Hash) (bool, []common.Hash) { 213 // Traverse the list from oldest to newest to quickly locate the element. 214 for i := 0; i < len(list); i++ { 215 if list[i] == element { 216 if i != 0 { 217 list = append(list[:i], list[i+1:]...) 218 } else { 219 // Remove the first element by shifting the slice forward. 220 // Pros: zero-copy. 221 // Cons: may retain large backing array, causing memory leaks. 222 // Mitigation: release the array if capacity exceeds threshold. 223 list = list[1:] 224 if cap(list) > 1024 { 225 list = append(make([]common.Hash, 0, len(list)), list...) 226 } 227 } 228 return true, list 229 } 230 } 231 return false, nil 232 } 233 234 // removeLayer traverses the state data retained in the specified diff layer and 235 // unlink them from the lookup set. 236 func (l *lookup) removeLayer(diff *diffLayer) error { 237 defer func(now time.Time) { 238 lookupRemoveLayerTimer.UpdateSince(now) 239 }(time.Now()) 240 241 var ( 242 eg errgroup.Group 243 state = diff.rootHash() 244 ) 245 eg.Go(func() error { 246 for accountHash := range diff.states.accountData { 247 found, list := removeFromList(l.accounts[accountHash], state) 248 if !found { 249 return fmt.Errorf("account lookup is not found, %x, state: %x", accountHash, state) 250 } 251 if len(list) != 0 { 252 l.accounts[accountHash] = list 253 } else { 254 delete(l.accounts, accountHash) 255 } 256 } 257 return nil 258 }) 259 260 eg.Go(func() error { 261 for accountHash, slots := range diff.states.storageData { 262 for slotHash := range slots { 263 key := storageKey(accountHash, slotHash) 264 found, list := removeFromList(l.storages[key], state) 265 if !found { 266 return fmt.Errorf("storage lookup is not found, %x %x, state: %x", accountHash, slotHash, state) 267 } 268 if len(list) != 0 { 269 l.storages[key] = list 270 } else { 271 delete(l.storages, key) 272 } 273 } 274 } 275 return nil 276 }) 277 return eg.Wait() 278 }