github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/stateroot/module.go (about) 1 package stateroot 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "sync" 9 "sync/atomic" 10 "time" 11 12 "github.com/nspcc-dev/neo-go/pkg/config" 13 "github.com/nspcc-dev/neo-go/pkg/config/netmode" 14 "github.com/nspcc-dev/neo-go/pkg/core/mpt" 15 "github.com/nspcc-dev/neo-go/pkg/core/state" 16 "github.com/nspcc-dev/neo-go/pkg/core/storage" 17 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 18 "github.com/nspcc-dev/neo-go/pkg/crypto/hash" 19 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 20 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 21 "github.com/nspcc-dev/neo-go/pkg/util" 22 "go.uber.org/zap" 23 ) 24 25 type ( 26 // VerifierFunc is a function that allows to check witness of account 27 // for Hashable item with GAS limit. 28 VerifierFunc func(util.Uint160, hash.Hashable, *transaction.Witness, int64) (int64, error) 29 // Module represents module for local processing of state roots. 30 Module struct { 31 Store *storage.MemCachedStore 32 network netmode.Magic 33 srInHead bool 34 mode mpt.TrieMode 35 mpt *mpt.Trie 36 verifier VerifierFunc 37 log *zap.Logger 38 39 currentLocal atomic.Value 40 localHeight atomic.Uint32 41 validatedHeight atomic.Uint32 42 43 mtx sync.RWMutex 44 keys []keyCache 45 46 updateValidatorsCb func(height uint32, publicKeys keys.PublicKeys) 47 } 48 49 keyCache struct { 50 height uint32 51 validatorsKeys keys.PublicKeys 52 validatorsHash util.Uint160 53 validatorsScript []byte 54 } 55 ) 56 57 // NewModule returns new instance of stateroot module. 58 func NewModule(cfg config.Blockchain, verif VerifierFunc, log *zap.Logger, s *storage.MemCachedStore) *Module { 59 var mode mpt.TrieMode 60 if cfg.Ledger.KeepOnlyLatestState { 61 mode |= mpt.ModeLatest 62 } 63 if cfg.Ledger.RemoveUntraceableBlocks { 64 mode |= mpt.ModeGC 65 } 66 return &Module{ 67 network: cfg.Magic, 68 srInHead: cfg.StateRootInHeader, 69 mode: mode, 70 verifier: verif, 71 log: log, 72 Store: s, 73 } 74 } 75 76 // GetState returns value at the specified key fom the MPT with the specified root. 77 func (s *Module) GetState(root util.Uint256, key []byte) ([]byte, error) { 78 // Allow accessing old values, it's RO thing. 79 tr := mpt.NewTrie(mpt.NewHashNode(root), s.mode&^mpt.ModeGCFlag, storage.NewMemCachedStore(s.Store)) 80 return tr.Get(key) 81 } 82 83 // FindStates returns a set of key-value pairs with keys matching the prefix starting 84 // from the `prefix`+`start` path from MPT with the specified root. `max` is 85 // the maximum number of elements to be returned. If nil `start` is specified, then the 86 // item with the key equal to the prefix is included into the result; if empty `start` is specified, 87 // then the item with the key equal to the prefix is not included into the result. 88 // In case there are no results (prefix is unused, start is after the last available 89 // element) mpt.ErrNotFound is returned. 90 func (s *Module) FindStates(root util.Uint256, prefix, start []byte, max int) ([]storage.KeyValue, error) { 91 // Allow accessing old values, it's RO thing. 92 tr := mpt.NewTrie(mpt.NewHashNode(root), s.mode&^mpt.ModeGCFlag, storage.NewMemCachedStore(s.Store)) 93 return tr.Find(prefix, start, max) 94 } 95 96 // SeekStates traverses over contract storage with the state based on the 97 // specified root. `prefix` is expected to consist of contract ID and the desired 98 // storage items prefix. `cont` is called for every matching key-value pair; 99 // the resulting key does not include contract ID and the desired storage item 100 // prefix (they are stripped to match the Blockchain's SeekStorage behaviour. 101 // The result includes item with the key that equals to the `prefix` (if 102 // such item is found in the storage). Traversal process is stopped when `false` 103 // is returned from `cont`. 104 func (s *Module) SeekStates(root util.Uint256, prefix []byte, cont func(k, v []byte) bool) { 105 // Allow accessing old values, it's RO thing. 106 store := mpt.NewTrieStore(root, s.mode&^mpt.ModeGCFlag, storage.NewMemCachedStore(s.Store)) 107 108 // Tiny hack to satisfy TrieStore with the given prefix. This 109 // storage.STStorage prefix is a stub that will be stripped by the 110 // TrieStore.Seek while performing MPT traversal and isn't actually relevant 111 // here. 112 key := make([]byte, len(prefix)+1) 113 key[0] = byte(storage.STStorage) 114 copy(key[1:], prefix) 115 116 store.Seek(storage.SeekRange{Prefix: key}, func(k, v []byte) bool { 117 // Cut the prefix to match the Blockchain's SeekStorage behaviour. 118 return cont(k[len(key):], v) 119 }) 120 } 121 122 // GetStateProof returns proof of having key in the MPT with the specified root. 123 func (s *Module) GetStateProof(root util.Uint256, key []byte) ([][]byte, error) { 124 // Allow accessing old values, it's RO thing. 125 tr := mpt.NewTrie(mpt.NewHashNode(root), s.mode&^mpt.ModeGCFlag, storage.NewMemCachedStore(s.Store)) 126 return tr.GetProof(key) 127 } 128 129 // GetStateRoot returns state root for a given height. 130 func (s *Module) GetStateRoot(height uint32) (*state.MPTRoot, error) { 131 return s.getStateRoot(makeStateRootKey(height)) 132 } 133 134 // GetLatestStateHeight returns the latest blockchain height by the given stateroot. 135 func (s *Module) GetLatestStateHeight(root util.Uint256) (uint32, error) { 136 rootBytes := root.BytesBE() 137 rootStartOffset := 1 + 4 // stateroot version (1 byte) + stateroot index (4 bytes) 138 rootEndOffset := rootStartOffset + util.Uint256Size 139 var ( 140 h uint32 141 found bool 142 rootKey = makeStateRootKey(s.localHeight.Load()) 143 ) 144 s.Store.Seek(storage.SeekRange{ 145 Prefix: []byte{rootKey[0]}, // DataMPTAux 146 Start: rootKey[1:], // Start is a value that should be appended to the Prefix 147 Backwards: true, 148 }, func(k, v []byte) bool { 149 if len(k) == 5 && bytes.Equal(v[rootStartOffset:rootEndOffset], rootBytes) { 150 h = binary.BigEndian.Uint32(k[1:]) // cut prefix DataMPTAux 151 found = true 152 return false 153 } 154 return true 155 }) 156 if found { 157 return h, nil 158 } 159 return h, storage.ErrKeyNotFound 160 } 161 162 // CurrentLocalStateRoot returns hash of the local state root. 163 func (s *Module) CurrentLocalStateRoot() util.Uint256 { 164 return s.currentLocal.Load().(util.Uint256) 165 } 166 167 // CurrentLocalHeight returns height of the local state root. 168 func (s *Module) CurrentLocalHeight() uint32 { 169 return s.localHeight.Load() 170 } 171 172 // CurrentValidatedHeight returns current state root validated height. 173 func (s *Module) CurrentValidatedHeight() uint32 { 174 return s.validatedHeight.Load() 175 } 176 177 // Init initializes state root module at the given height. 178 func (s *Module) Init(height uint32) error { 179 data, err := s.Store.Get([]byte{byte(storage.DataMPTAux), prefixValidated}) 180 if err == nil { 181 h := binary.LittleEndian.Uint32(data) 182 s.validatedHeight.Store(h) 183 updateStateHeightMetric(h) 184 } 185 186 if height == 0 { 187 s.mpt = mpt.NewTrie(nil, s.mode, s.Store) 188 s.currentLocal.Store(util.Uint256{}) 189 return nil 190 } 191 r, err := s.getStateRoot(makeStateRootKey(height)) 192 if err != nil { 193 return err 194 } 195 s.currentLocal.Store(r.Root) 196 s.localHeight.Store(r.Index) 197 s.mpt = mpt.NewTrie(mpt.NewHashNode(r.Root), s.mode, s.Store) 198 return nil 199 } 200 201 // CleanStorage removes all MPT-related data from the storage (MPT nodes, validated stateroots) 202 // except local stateroot for the current height and GC flag. This method is aimed to clean 203 // outdated MPT data before state sync process can be started. 204 // Note: this method is aimed to be called for genesis block only, an error is returned otherwise. 205 func (s *Module) CleanStorage() error { 206 lH := s.localHeight.Load() 207 if lH != 0 { 208 return fmt.Errorf("can't clean MPT data for non-genesis block: expected local stateroot height 0, got %d", lH) 209 } 210 b := storage.NewMemCachedStore(s.Store) 211 s.Store.Seek(storage.SeekRange{Prefix: []byte{byte(storage.DataMPT)}}, func(k, _ []byte) bool { 212 // #1468, but don't need to copy here, because it is done by Store. 213 b.Delete(k) 214 return true 215 }) 216 _, err := b.Persist() 217 if err != nil { 218 return fmt.Errorf("failed to remove outdated MPT-reated items: %w", err) 219 } 220 return nil 221 } 222 223 // JumpToState performs jump to the state specified by given stateroot index. 224 func (s *Module) JumpToState(sr *state.MPTRoot) { 225 s.addLocalStateRoot(s.Store, sr) 226 227 data := make([]byte, 4) 228 binary.LittleEndian.PutUint32(data, sr.Index) 229 s.Store.Put([]byte{byte(storage.DataMPTAux), prefixValidated}, data) 230 s.validatedHeight.Store(sr.Index) 231 232 s.currentLocal.Store(sr.Root) 233 s.localHeight.Store(sr.Index) 234 s.mpt = mpt.NewTrie(mpt.NewHashNode(sr.Root), s.mode, s.Store) 235 } 236 237 // ResetState resets MPT state to the given height. 238 func (s *Module) ResetState(height uint32, cache *storage.MemCachedStore) error { 239 // Update local stateroot. 240 sr, err := s.GetStateRoot(height) 241 if err != nil { 242 return fmt.Errorf("failed to retrieve state root for height %d: %w", height, err) 243 } 244 s.addLocalStateRoot(cache, sr) 245 246 // Remove all stateroots newer than the given height. 247 srKey := makeStateRootKey(height) 248 var srSeen bool 249 cache.Seek(storage.SeekRange{ 250 Prefix: srKey[0:1], 251 Start: srKey[1:5], 252 Backwards: false, 253 }, func(k, v []byte) bool { 254 if len(k) == 5 { 255 if srSeen { 256 cache.Delete(k) 257 } else if bytes.Equal(k, srKey) { 258 srSeen = true 259 } 260 } 261 return true 262 }) 263 264 // Retrieve the most recent validated stateroot before the given height. 265 witnessesLenOffset := 1 /* version */ + 4 /* index */ + smartcontract.Hash256Len /* root */ 266 var validated *uint32 267 cache.Seek(storage.SeekRange{ 268 Prefix: srKey[0:1], 269 Start: srKey[1:5], 270 Backwards: true, 271 }, func(k, v []byte) bool { 272 if len(k) == 5 { 273 if len(v) > witnessesLenOffset && v[witnessesLenOffset] != 0 { 274 i := binary.BigEndian.Uint32(k[1:]) 275 validated = &i 276 return false 277 } 278 } 279 return true 280 }) 281 if validated != nil { 282 validatedBytes := make([]byte, 4) 283 binary.LittleEndian.PutUint32(validatedBytes, *validated) 284 cache.Put([]byte{byte(storage.DataMPTAux), prefixValidated}, validatedBytes) 285 s.validatedHeight.Store(*validated) 286 } else { 287 cache.Delete([]byte{byte(storage.DataMPTAux), prefixValidated}) 288 } 289 290 s.currentLocal.Store(sr.Root) 291 s.localHeight.Store(sr.Index) 292 s.mpt = mpt.NewTrie(mpt.NewHashNode(sr.Root), s.mode, s.Store) 293 294 // Do not reset MPT nodes, leave the trie state itself as is. 295 return nil 296 } 297 298 // GC performs garbage collection. 299 func (s *Module) GC(index uint32, store storage.Store) time.Duration { 300 if !s.mode.GC() { 301 panic("stateroot: GC invoked, but not enabled") 302 } 303 var removed int 304 var stored int64 305 s.log.Info("starting MPT garbage collection", zap.Uint32("index", index)) 306 start := time.Now() 307 err := store.SeekGC(storage.SeekRange{ 308 Prefix: []byte{byte(storage.DataMPT)}, 309 }, func(k, v []byte) bool { 310 stored++ 311 if !mpt.IsActiveValue(v) { 312 h := binary.LittleEndian.Uint32(v[len(v)-4:]) 313 if h <= index { 314 removed++ 315 stored-- 316 return false 317 } 318 } 319 return true 320 }) 321 dur := time.Since(start) 322 if err != nil { 323 s.log.Error("failed to flush MPT GC changeset", zap.Duration("time", dur), zap.Error(err)) 324 } else { 325 s.log.Info("finished MPT garbage collection", 326 zap.Int("removed", removed), 327 zap.Int64("kept", stored), 328 zap.Duration("time", dur)) 329 } 330 return dur 331 } 332 333 // AddMPTBatch updates using provided batch. 334 func (s *Module) AddMPTBatch(index uint32, b mpt.Batch, cache *storage.MemCachedStore) (*mpt.Trie, *state.MPTRoot, error) { 335 mpt := *s.mpt 336 mpt.Store = cache 337 if _, err := mpt.PutBatch(b); err != nil { 338 return nil, nil, err 339 } 340 mpt.Flush(index) 341 sr := &state.MPTRoot{ 342 Index: index, 343 Root: mpt.StateRoot(), 344 } 345 s.addLocalStateRoot(cache, sr) 346 return &mpt, sr, nil 347 } 348 349 // UpdateCurrentLocal updates local caches using provided state root. 350 func (s *Module) UpdateCurrentLocal(mpt *mpt.Trie, sr *state.MPTRoot) { 351 s.mpt = mpt 352 s.currentLocal.Store(sr.Root) 353 s.localHeight.Store(sr.Index) 354 if s.srInHead { 355 s.validatedHeight.Store(sr.Index) 356 updateStateHeightMetric(sr.Index) 357 } 358 } 359 360 // VerifyStateRoot checks if state root is valid. 361 func (s *Module) VerifyStateRoot(r *state.MPTRoot) error { 362 _, err := s.getStateRoot(makeStateRootKey(r.Index - 1)) 363 if err != nil { 364 return errors.New("can't get previous state root") 365 } 366 if len(r.Witness) != 1 { 367 return errors.New("no witness") 368 } 369 return s.verifyWitness(r) 370 } 371 372 const maxVerificationGAS = 2_00000000 373 374 // verifyWitness verifies state root witness. 375 func (s *Module) verifyWitness(r *state.MPTRoot) error { 376 s.mtx.Lock() 377 h := s.getKeyCacheForHeight(r.Index).validatorsHash 378 s.mtx.Unlock() 379 _, err := s.verifier(h, r, &r.Witness[0], maxVerificationGAS) 380 return err 381 }