code.vegaprotocol.io/vega@v0.79.0/core/snapshot/engine.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package snapshot 17 18 import ( 19 "bufio" 20 "context" 21 "errors" 22 "fmt" 23 "os" 24 "reflect" 25 "sort" 26 "sync" 27 "sync/atomic" 28 "time" 29 30 "code.vegaprotocol.io/vega/core/metrics" 31 "code.vegaprotocol.io/vega/core/snapshot/tree" 32 "code.vegaprotocol.io/vega/core/types" 33 vegactx "code.vegaprotocol.io/vega/libs/context" 34 vgcontext "code.vegaprotocol.io/vega/libs/context" 35 "code.vegaprotocol.io/vega/libs/num" 36 "code.vegaprotocol.io/vega/libs/proto" 37 "code.vegaprotocol.io/vega/logging" 38 "code.vegaprotocol.io/vega/paths" 39 snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" 40 "code.vegaprotocol.io/vega/version" 41 42 tmtypes "github.com/cometbft/cometbft/abci/types" 43 "github.com/gogo/protobuf/jsonpb" 44 "github.com/libp2p/go-libp2p/p2p/host/autonat/pb" 45 "go.uber.org/zap" 46 "golang.org/x/exp/slices" 47 ) 48 49 const ( 50 namedLogger = "snapshot" 51 numWorkers = 1000 52 53 // This is a limitation by Tendermint. It must be strictly positive, and 54 // non-zero. 55 maxLengthOfSnapshotList = 10 56 ) 57 58 var ErrEngineHasAlreadyBeenStarted = errors.New("the engine has already been started") 59 60 //go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/snapshot TimeService,StatsService 61 62 type DoneCh <-chan interface{} 63 64 type TimeService interface { 65 GetTimeNow() time.Time 66 SetTimeNow(context.Context, time.Time) 67 GetTimeLastBatch() time.Time 68 SetPrevTime(t time.Time) 69 } 70 71 type StatsService interface { 72 SetHeight(uint64) 73 } 74 75 // Engine the snapshot engine. 76 type Engine struct { 77 config Config 78 log *logging.Logger 79 80 timeService TimeService 81 statsService StatsService 82 83 // started tells if the snapshot engine is started or not. It is set by the 84 // method `Start()`. 85 // Because the snapshot engine requires providers to be registered to be useful, 86 // after being initialized, it can't just load the snapshots during 87 // initialization. As a result, it needs to separate the initialization 88 // and start steps. This also benefits the caller, as it has full control on 89 // when to start the state restoration process, like after additional setup. 90 started atomic.Bool 91 92 // snapshotLock is used when accessing the maps below while concurrently 93 // constructing the new snapshot 94 snapshotLock sync.RWMutex 95 // registeredNamespaces holds all the namespaces of the registered providers 96 // when added with the method `AddProvider()`. 97 registeredNamespaces []types.SnapshotNamespace 98 // namespacesToProviderKeys takes us from a namespace to the provider keys in that 99 // namespace (e.g governance => {active, enacted}). 100 namespacesToProviderKeys map[types.SnapshotNamespace][]string 101 // namespacesToTreeKeys takes us from a namespace to the AVL tree keys in 102 // that namespace (e.g governance => {governance.active, governance.enacted}). 103 namespacesToTreeKeys map[types.SnapshotNamespace][][]byte 104 // treeKeysToProviderKeys takes us from the key of the AVL tree node, to the provider 105 // key (e.g checkpoint.all => all). 106 treeKeysToProviderKeys map[string]string 107 // treeKeysToProviders tracks all the components that need state to be reloaded. 108 treeKeysToProviders map[string]types.StateProvider 109 // preRestoreProviders tracks all the components that need to be called 110 // before the state as been reloaded in all providers. 111 preRestoreProviders []types.PreRestore 112 // postRestoreProviders tracks all the components that need to be called 113 // after the state as been reloaded in all providers. 114 postRestoreProviders []types.PostRestore 115 116 // offeredSnapshot holds the snapshot that is currently being loaded through 117 // state-sync. If nil, it means there is no snapshot being loaded through 118 // state-sync. 119 offeredSnapshot *types.Snapshot 120 // attemptsToApplySnapshotChunk counts the number of attempts made to apply 121 // a chunk to a snapshot, during a state-sync. When the number of attempts 122 // exceeds Config.RetryLimit, the state-sync is aborted, and the counter 123 // reset. 124 attemptsToApplySnapshotChunk uint 125 // stateRestored tells if the state has been restored already or not. This 126 // is use to guard against multiple state restoration. 127 stateRestored atomic.Bool 128 129 // loadedSnapshot holds the snapshot that is currently being used to share 130 // snapshot chunks with peers on the network. 131 loadedSnapshot *types.Snapshot 132 133 // appState holds the general state of the application. It is the responsibility 134 // of the snapshot engine to update, and snapshot it. 135 appState *types.AppState 136 137 // snapshotTree hold the snapshot as an AVL tree. 138 snapshotTree *tree.Tree 139 // snapshotTreeLock is used every time it is needed to read from or writing to 140 // the AVL tree. 141 snapshotTreeLock sync.Mutex 142 143 // intervalBetweenSnapshots defines the internal between snapshots. The unit 144 // is based on the network commits. An interval of 10 means a snapshot is taken 145 // every 10 commits. 146 intervalBetweenSnapshots uint64 147 // commitsLeftBeforeSnapshot tracks the number of commits left before the 148 // engine snapshots the state of the node. 149 commitsLeftBeforeSnapshot uint64 150 } 151 152 // NewEngine returns a new snapshot engine. 153 func NewEngine(vegaPath paths.Paths, conf Config, log *logging.Logger, timeSvc TimeService, statsSvc StatsService) (*Engine, error) { 154 if err := conf.Validate(); err != nil { 155 return nil, fmt.Errorf("invalid configuration: %w", err) 156 } 157 158 log = log.Named(namedLogger) 159 log.SetLevel(conf.Level.Get()) 160 161 appStatePayload := &types.PayloadAppState{} 162 163 eng := &Engine{ 164 config: conf, 165 log: log, 166 167 timeService: timeSvc, 168 statsService: statsSvc, 169 170 registeredNamespaces: []types.SnapshotNamespace{}, 171 namespacesToProviderKeys: map[types.SnapshotNamespace][]string{ 172 types.AppSnapshot: {appStatePayload.Key()}, 173 }, 174 namespacesToTreeKeys: map[types.SnapshotNamespace][][]byte{ 175 types.AppSnapshot: { 176 []byte(types.KeyFromPayload(appStatePayload)), 177 }, 178 }, 179 treeKeysToProviderKeys: map[string]string{}, 180 181 treeKeysToProviders: map[string]types.StateProvider{}, 182 appState: &types.AppState{}, 183 184 // By default, a snapshot is triggered after each commit. 185 intervalBetweenSnapshots: 1, 186 // This must not be set to 0, otherwise a snapshot will be generated 187 // right after the state has been reload, leading effectively to a 188 // duplicated snapshot. 189 commitsLeftBeforeSnapshot: 1, 190 } 191 192 if err := eng.initializeTree(vegaPath); err != nil { 193 return nil, err 194 } 195 196 return eng, nil 197 } 198 199 func (e *Engine) Start(ctx context.Context) error { 200 if e.started.Load() { 201 return ErrEngineHasAlreadyBeenStarted 202 } 203 204 if !e.snapshotTree.HasSnapshotsLoaded() { 205 e.started.Store(true) 206 return nil 207 } 208 209 e.log.Info("Local snapshots found, initiating state restoration") 210 211 if err := e.restoreStateFromTree(ctx); err != nil { 212 return fmt.Errorf("could not load local snapshot: %w", err) 213 } 214 215 e.log.Info("The state has been restored", zap.Uint64("block-height", e.appState.Height)) 216 217 e.started.Store(true) 218 219 return nil 220 } 221 222 func (e *Engine) OnSnapshotIntervalUpdate(_ context.Context, newIntervalBetweenSnapshots *num.Uint) error { 223 newIntervalBetweenSnapshotsU := newIntervalBetweenSnapshots.Uint64() 224 if newIntervalBetweenSnapshotsU < e.commitsLeftBeforeSnapshot || e.commitsLeftBeforeSnapshot == 0 { 225 e.commitsLeftBeforeSnapshot = newIntervalBetweenSnapshotsU 226 } else if newIntervalBetweenSnapshotsU > e.intervalBetweenSnapshots { 227 e.commitsLeftBeforeSnapshot += newIntervalBetweenSnapshotsU - e.intervalBetweenSnapshots 228 } 229 e.intervalBetweenSnapshots = newIntervalBetweenSnapshotsU 230 return nil 231 } 232 233 func (e *Engine) ReloadConfig(cfg Config) { 234 e.log.Info("Reloading configuration") 235 236 if e.log.GetLevel() != cfg.Level.Get() { 237 e.log.Info("Updating log level", 238 logging.String("old", e.log.GetLevel().String()), 239 logging.String("new", cfg.Level.String()), 240 ) 241 e.log.SetLevel(cfg.Level.Get()) 242 } 243 e.config = cfg 244 } 245 246 func (e *Engine) Close() { 247 // Locking in case a write operation is happening on the snapshot tree, 248 // before releasing. 249 e.snapshotTreeLock.Lock() 250 defer e.snapshotTreeLock.Unlock() 251 252 e.snapshotTree.Release() 253 } 254 255 func (e *Engine) Info() ([]byte, int64, string) { 256 if !e.stateRestored.Load() { 257 return nil, 0, "" 258 } 259 260 return e.snapshotTree.Hash(), int64(e.appState.Height), e.appState.ChainID 261 } 262 263 // AddProviders add a state providers to the engine. Added providers will be called 264 // when a snapshot is taken, and when the state is restored. 265 // It supports multiple providers on the same namespace, but their generated 266 // tree keys (namespace + key) must be unique. 267 func (e *Engine) AddProviders(newProviders ...types.StateProvider) { 268 e.snapshotLock.Lock() 269 defer e.snapshotLock.Unlock() 270 271 e.addProviders(newProviders...) 272 } 273 274 func (e *Engine) HasRestoredStateAlready() bool { 275 return e.stateRestored.Load() 276 } 277 278 // ListLatestSnapshots list the last N snapshots in accordance to the variable 279 // `maxLengthOfSnapshotList`. 280 func (e *Engine) ListLatestSnapshots() ([]*tmtypes.Snapshot, error) { 281 e.snapshotTreeLock.Lock() 282 defer e.snapshotTreeLock.Unlock() 283 284 snapshots, err := e.snapshotTree.ListLatestSnapshots(maxLengthOfSnapshotList) 285 if err != nil { 286 return nil, fmt.Errorf("could not list lastest snapshots: %w", err) 287 } 288 289 return snapshots, nil 290 } 291 292 // HasSnapshots will return whether we have snapshots, or not. This can be use to 293 // safely call Info(). 294 func (e *Engine) HasSnapshots() (bool, error) { 295 return e.snapshotTree.HasSnapshotsLoaded(), nil 296 } 297 298 // ReceiveSnapshot is called by Tendermint to restore state from state-sync. 299 // This must be called before load snapshot chunks. 300 // If this method is called while a snapshot is already being loaded, the 301 // current snapshot loading is aborted, and the new one is used instead. 302 // Proceeding as such allows Tendermint to start over when an error occurs during 303 // state-sync. 304 func (e *Engine) ReceiveSnapshot(offeredSnapshot *types.Snapshot) tmtypes.ResponseOfferSnapshot { 305 e.ensureEngineIsStarted() 306 307 if e.stateRestored.Load() { 308 e.log.Error("Attempt to offer a snapshot whereas the state has already been restored, aborting offer") 309 return tmtypes.ResponseOfferSnapshot{ 310 Result: tmtypes.ResponseOfferSnapshot_ABORT, 311 } 312 } 313 314 if offeredSnapshot.Meta == nil { 315 e.log.Info("The received snapshot is missing meta-data, rejecting offer", 316 logging.Uint64("snapshot-height", offeredSnapshot.Height), 317 ) 318 return tmtypes.ResponseOfferSnapshot{ 319 Result: tmtypes.ResponseOfferSnapshot_REJECT, 320 } 321 } 322 e.log.Info("New snapshot received from state-sync", 323 logging.Uint64("snapshot-height", offeredSnapshot.Height), 324 logging.String("from-version", offeredSnapshot.Meta.ProtocolVersion), 325 logging.Bool("protocol-upgrade", offeredSnapshot.Meta.ProtocolUpgrade), 326 ) 327 328 if e.config.StartHeight > 0 && offeredSnapshot.Height != uint64(e.config.StartHeight) { 329 e.log.Info("The block height of the received snapshot does not match the expected one, rejecting offer", 330 logging.Uint64("snapshot-height", offeredSnapshot.Height), 331 logging.Int64("expected-height", e.config.StartHeight), 332 ) 333 return tmtypes.ResponseOfferSnapshot{ 334 Result: tmtypes.ResponseOfferSnapshot_REJECT, 335 } 336 } 337 338 // If Tendermint fails to fetch a chunk after some time, it will reject the 339 // snapshot and try a different one via `OfferSnapshot()`. Therefore, the 340 // ongoing snapshot process must be reset. 341 if e.offeredSnapshot != nil { 342 e.log.Warn("Resetting the process loading from state-sync to accept the new one") 343 e.resetOfferedSnapshot() 344 } 345 346 e.offeredSnapshot = offeredSnapshot 347 348 e.log.Info("New snapshot received from state-sync accepted", 349 logging.Uint64("snapshot-height", offeredSnapshot.Height), 350 ) 351 352 return tmtypes.ResponseOfferSnapshot{ 353 Result: tmtypes.ResponseOfferSnapshot_ACCEPT, 354 } 355 } 356 357 // ReceiveSnapshotChunk is called by Tendermint to restore state from state-sync. 358 // It receives the chunks matching the snapshot received via the `ReceiveSnapshot()`. 359 func (e *Engine) ReceiveSnapshotChunk(ctx context.Context, chunk *types.RawChunk, sender string) tmtypes.ResponseApplySnapshotChunk { 360 e.ensureEngineIsStarted() 361 362 if e.stateRestored.Load() { 363 e.log.Error("Attempt to load snapshot chunks whereas the state has already been loaded from local snapshots, aborting state-sync") 364 return e.abortStateSync() 365 } 366 367 e.log.Info("New snapshot chunk received from state-sync", 368 zap.Uint32("chunk-number", chunk.Nr), 369 ) 370 371 if e.offeredSnapshot == nil { 372 // If this condition is valid, it means the engine is tasked to load 373 // snapshot chunks, without being offered one, first. It does not seem 374 // Tendermint will ever do that, according to the ABCI documentation, so 375 // it seems it would all come down to a programming error. 376 // However, prior the refactoring, this was interpreted as "not being 377 // ready". The reason remains obscure. 378 // Panicking would probably be the best thing to do. 379 // In the meantime, we should monitor the error messages, and see if it's 380 // ever happening, to know if we can safely remove it. 381 e.log.Error("Attempt to load snapshot chunks without offering a snapshot first, this should not have happened, aborting state-sync") 382 return tmtypes.ResponseApplySnapshotChunk{ 383 Result: tmtypes.ResponseApplySnapshotChunk_RETRY_SNAPSHOT, 384 } 385 } 386 387 if err := e.offeredSnapshot.LoadChunk(chunk); err != nil { 388 if errors.Is(err, types.ErrChunkOutOfRange) { 389 if e.shouldAbortStateSync() { 390 e.log.Error("Engine reached the maximum number of retry for loading snapshot chunk, aborting state-sync", logging.Error(err)) 391 return e.abortStateSync() 392 } else { 393 e.log.Warn("Reject offered snapshot as received chunk does not match", 394 zap.String("sender", sender), 395 logging.Error(err), 396 ) 397 return tmtypes.ResponseApplySnapshotChunk{ 398 Result: tmtypes.ResponseApplySnapshotChunk_REJECT_SNAPSHOT, 399 } 400 } 401 } else if errors.Is(err, types.ErrMissingChunks) { 402 if e.shouldAbortStateSync() { 403 e.log.Error("Engine reached the maximum number of retry for loading snapshot chunk, aborting state-sync", logging.Error(err)) 404 return e.abortStateSync() 405 } else { 406 e.log.Warn("Snapshot is missing chunks, retrying", 407 logging.Error(err), 408 ) 409 return tmtypes.ResponseApplySnapshotChunk{ 410 Result: tmtypes.ResponseApplySnapshotChunk_RETRY, 411 RefetchChunks: e.offeredSnapshot.MissingChunks(), 412 } 413 } 414 } else if errors.Is(err, types.ErrChunkHashMismatch) { 415 if e.shouldAbortStateSync() { 416 e.log.Error("Engine reached the maximum number of retry for loading snapshot chunk, aborting state-sync", logging.Error(err)) 417 return e.abortStateSync() 418 } else { 419 e.log.Warn("Received chunk is not consistent with metadata from the offered snapshot, rejecting sender and retrying", 420 zap.String("rejected-sender", sender), 421 logging.Error(err), 422 ) 423 return tmtypes.ResponseApplySnapshotChunk{ 424 Result: tmtypes.ResponseApplySnapshotChunk_RETRY, 425 RejectSenders: []string{sender}, 426 } 427 } 428 } 429 430 e.log.Error("An error occurred while loading chunk in the snapshot during state-sync, aborting state-sync", 431 logging.Uint32("chunk-number", chunk.Nr), 432 logging.Error(err), 433 ) 434 return e.abortStateSync() 435 } 436 437 e.log.Info("New snapshot chunk received from state-sync accepted", 438 zap.Uint32("chunk-number", chunk.Nr), 439 ) 440 441 if !e.offeredSnapshot.Ready() { 442 return tmtypes.ResponseApplySnapshotChunk{ 443 Result: tmtypes.ResponseApplySnapshotChunk_ACCEPT, 444 } 445 } 446 447 e.log.Info("All snapshot chunks received, initiating state restoration") 448 449 // Saving snapshot in the tree. At this point, this should be the only snapshot 450 // it has, as restoring state from state-sync require an empty snapshot 451 // database, and thus, an empty tree. 452 e.snapshotTreeLock.Lock() 453 defer e.snapshotTreeLock.Unlock() 454 455 if err := e.snapshotTree.AddSnapshot(e.offeredSnapshot); err != nil { 456 e.log.Error("Could not add offered snapshot to the tree, aborting state-sync", logging.Error(err)) 457 return e.abortStateSync() 458 } 459 460 if err := e.restoreStateFromSnapshot(ctx, e.offeredSnapshot.Nodes); err != nil { 461 e.log.Error("Could not restore state, aborting state-sync", logging.Error(err)) 462 return e.abortStateSync() 463 } 464 465 // The state has been successfully restored, so resources are released. 466 e.resetOfferedSnapshot() 467 468 e.log.Info("The state has been restored") 469 470 return tmtypes.ResponseApplySnapshotChunk{ 471 Result: tmtypes.ResponseApplySnapshotChunk_ACCEPT, 472 } 473 } 474 475 // RetrieveSnapshotChunk is called by Tendermint to retrieve a snapshot chunk 476 // to help a peer node to restore its state from state-sync. 477 func (e *Engine) RetrieveSnapshotChunk(height uint64, format, chunkIndex uint32) (*types.RawChunk, error) { 478 if e.loadedSnapshot == nil || height != e.loadedSnapshot.Height { 479 loadedSnapshot, err := e.findTreeByBlockHeight(height) 480 if err != nil { 481 return nil, err 482 } 483 e.loadedSnapshot = loadedSnapshot 484 } 485 486 expectedFormat, err := types.SnapshotFormatFromU32(format) 487 if err != nil { 488 return nil, fmt.Errorf("could not deserialize snapshot format: %w", err) 489 } 490 491 if expectedFormat != e.loadedSnapshot.Format { 492 return nil, types.ErrSnapshotFormatMismatch 493 } 494 495 if e.loadedSnapshot.Chunks == chunkIndex { 496 // The network is asking for the last chunk of the loaded snapshot. 497 // Let's free up some memory. 498 defer func() { 499 e.loadedSnapshot = nil 500 }() 501 } 502 503 return e.loadedSnapshot.RawChunkByIndex(chunkIndex) 504 } 505 506 // Snapshot triggers the snapshot process at defined interval. Do nothing if the 507 // the interval bound is not reached. 508 func (e *Engine) Snapshot(ctx context.Context) ([]byte, DoneCh, error) { 509 e.ensureEngineIsStarted() 510 511 e.commitsLeftBeforeSnapshot-- 512 513 if e.commitsLeftBeforeSnapshot > 0 { 514 return nil, nil, nil 515 } 516 517 e.commitsLeftBeforeSnapshot = e.intervalBetweenSnapshots 518 519 return e.snapshotNow(ctx, true) 520 } 521 522 // SnapshotDump takes a snapshot on demand, without persisting it to the underlying DB 523 // it's meant to just dump to a file for debugging. 524 func (e *Engine) SnapshotDump(ctx context.Context, path string) ([]byte, error) { 525 e.ensureEngineIsStarted() 526 // dump file 527 f, err := os.Create(path) 528 if err != nil { 529 return nil, err 530 } 531 defer func() { _ = f.Close() }() 532 hash, ch, err := e.snapshotNow(ctx, true) 533 if err != nil { 534 return nil, err 535 } 536 <-ch 537 payloads, err := e.snapshotTree.AsProtoPayloads() 538 if err != nil { 539 return hash, err 540 } 541 542 w := bufio.NewWriter(f) 543 m := jsonpb.Marshaler{Indent: " "} 544 545 payloadData := struct { 546 Data []*snapshotpb.Payload `json:"data,omitempty" protobuf:"bytes,1,rep,name=data"` 547 pb.Message 548 }{ 549 Data: payloads, 550 } 551 552 s, err := m.MarshalToString(&payloadData) 553 if err != nil { 554 return hash, err 555 } 556 557 if _, err = w.WriteString(s); err != nil { 558 return hash, err 559 } 560 561 if err = w.Flush(); err != nil { 562 return hash, err 563 } 564 565 return hash, nil 566 } 567 568 // SnapshotNow triggers the snapshot process right now, ignoring the defined 569 // interval. 570 func (e *Engine) SnapshotNow(ctx context.Context) ([]byte, error) { 571 e.ensureEngineIsStarted() 572 573 now, _, err := e.snapshotNow(ctx, false) 574 575 return now, err 576 } 577 578 func (e *Engine) snapshotNow(ctx context.Context, saveAsync bool) ([]byte, DoneCh, error) { 579 defer metrics.StartSnapshot("all")() 580 e.snapshotTreeLock.Lock() 581 582 // When a node requests a snapshot, it means it holds state, regardless 583 // it reloaded from a snapshot or not. Therefore, the engine must mark 584 // the state as restored to ensure it won't have multiple state restorations. 585 e.stateRestored.Store(true) 586 587 treeKeysToSnapshot := make([]treeKeyToSnapshot, 0, len(e.registeredNamespaces)) 588 for _, namespace := range e.registeredNamespaces { 589 treeKeys := e.namespacesToTreeKeys[namespace] 590 591 for _, treeKey := range treeKeys { 592 treeKeysToSnapshot = append(treeKeysToSnapshot, treeKeyToSnapshot{treeKey: treeKey, namespace: namespace}) 593 } 594 } 595 596 treeKeysCounter := atomic.Int64{} 597 treeKeysCounter.Store(int64(len(treeKeysToSnapshot))) 598 599 // we push the tree keys into this channel so it can only have at most len(treeKeysToSnapshot) things in it 600 treeKeysToSnapshotChan := make(chan treeKeyToSnapshot, len(treeKeysToSnapshot)) 601 602 // this is the channel where the workers send their result back to the main thread here, so one slot per worker is enough 603 serializedStateChan := make(chan snapshotResult, numWorkers) 604 605 snapMetricsRecord := newSnapMetricsState() 606 defer func() { 607 blockHeight, _ := vgcontext.BlockHeightFromContext(ctx) 608 snapMetricsRecord.Report(blockHeight) 609 }() 610 611 // Start the gathering of providers state asynchronously. 612 wg := &sync.WaitGroup{} 613 for i := 0; i < numWorkers; i++ { 614 wg.Add(1) 615 go func() { 616 gatherState(e, treeKeysToSnapshotChan, serializedStateChan, &treeKeysCounter, snapMetricsRecord) 617 wg.Done() 618 }() 619 } 620 621 // the above loop sets the worker threads ready to read keys from treeKeysToSnapshotChan 622 // so we can push the keys in async while the loop below over serializedStateChan starts reading the results 623 go func() { 624 for _, treeKeyToSnapshot := range treeKeysToSnapshot { 625 treeKeysToSnapshotChan <- treeKeyToSnapshot 626 } 627 }() 628 629 if len(treeKeysToSnapshot) == 0 { 630 close(treeKeysToSnapshotChan) 631 close(serializedStateChan) 632 } 633 634 // analyse the results 635 results := make([]snapshotResult, 0, len(treeKeysToSnapshot)) 636 for res := range serializedStateChan { 637 if res.err != nil { 638 e.log.Panic("Failed to update snapshot namespace", 639 logging.String("snapshot-namespace", res.input.namespace.String()), 640 logging.Error(res.err), 641 ) 642 } 643 results = append(results, res) 644 } 645 646 // wait for all workers to complete 647 wg.Wait() 648 649 // all results are int - split them by namespace first 650 resultByTreeKey := make(map[string]snapshotResult, len(results)) 651 for _, tkRes := range results { 652 resultByTreeKey[string(tkRes.input.treeKey)] = tkRes 653 } 654 655 nsSlice := make([]types.SnapshotNamespace, len(e.registeredNamespaces)) 656 copy(nsSlice, e.registeredNamespaces) 657 sort.Slice(nsSlice, func(i, j int) bool { return nsSlice[i] < nsSlice[j] }) 658 659 for _, ns := range nsSlice { 660 treeKeys, ok := e.namespacesToTreeKeys[ns] 661 if !ok { 662 continue 663 } 664 665 // sort the tree keys because providers may be added in a random order 666 sort.Slice(treeKeys, func(i, j int) bool { return string(treeKeys[i]) < string(treeKeys[j]) }) 667 668 toRemove := []int{} 669 for i, treeKey := range treeKeys { 670 snapshotRes, ok := resultByTreeKey[string(treeKey)] 671 if !ok { 672 continue 673 } 674 675 if !snapshotRes.updated || snapshotRes.toRemove { 676 if snapshotRes.toRemove { 677 toRemove = append(toRemove, i) 678 } 679 continue 680 } 681 682 e.log.Info("State updated", logging.String("tree-key", string(treeKey))) 683 684 if len(snapshotRes.state) == 0 { 685 // empty state -> remove data from snapshot 686 e.snapshotTree.RemoveKey(treeKey) 687 continue 688 } 689 e.snapshotTree.AddState(treeKey, snapshotRes.state) 690 } 691 692 if len(toRemove) == 0 { 693 continue 694 } 695 696 for ind := len(toRemove) - 1; ind >= 0; ind-- { 697 i := toRemove[ind] 698 tk := treeKeys[i] 699 tkRes, ok := resultByTreeKey[string(tk)] 700 if !ok { 701 continue 702 } 703 treeKey := tkRes.input.treeKey 704 treeKeyStr := string(treeKey) 705 706 // delete everything we've got stored 707 e.log.Debug("State to be removed", logging.String("tree-key", treeKeyStr)) 708 delete(e.treeKeysToProviders, treeKeyStr) 709 delete(e.treeKeysToProviderKeys, treeKeyStr) 710 711 if removed, err := e.snapshotTree.RemoveKey(treeKey); err != nil { 712 e.log.Panic("failed to remove node from AVL tree", logging.String("key", treeKeyStr), logging.Error(err), logging.Bool("removed", removed)) 713 } else if !removed { 714 e.log.Error("trying to remove a non-existent key from snapshot", logging.String("key", treeKeyStr)) 715 } 716 717 e.namespacesToTreeKeys[ns] = append(e.namespacesToTreeKeys[ns][:i], e.namespacesToTreeKeys[ns][i+1:]...) 718 } 719 } 720 721 // update appstate separately 722 height, err := vegactx.BlockHeightFromContext(ctx) 723 if err != nil { 724 e.snapshotTreeLock.Unlock() 725 return nil, nil, err 726 } 727 e.appState.Height = height 728 729 _, block := vegactx.TraceIDFromContext(ctx) 730 e.appState.Block = block 731 e.appState.Time = e.timeService.GetTimeNow().UnixNano() 732 e.appState.PrevBlockTime = e.timeService.GetTimeLastBatch().UnixNano() 733 cid, err := vegactx.ChainIDFromContext(ctx) 734 if err != nil { 735 e.snapshotTreeLock.Unlock() 736 return nil, nil, err 737 } 738 e.appState.ChainID = cid 739 e.appState.ProtocolVersion = version.Get() 740 e.appState.ProtocolUpdgade = !saveAsync 741 742 if err = e.updateAppState(); err != nil { 743 e.snapshotTreeLock.Unlock() 744 return nil, nil, err 745 } 746 747 doneCh := make(chan interface{}) 748 749 save := func() { 750 defer func() { 751 close(doneCh) 752 753 if r := recover(); r != nil { 754 e.log.Panic("Panic occurred", zap.Any("reason", r)) 755 } 756 }() 757 758 if err := e.snapshotTree.SaveVersion(); err != nil { 759 // If this fails, we are screwed. The tree version is used to construct 760 // the root-hash so if we can't save it, the next snapshot we take 761 // will mismatch so we need to fail hard here. 762 e.log.Panic("Could not save the snapshot tree", logging.Error(err)) 763 } 764 } 765 766 var hash []byte 767 if saveAsync { 768 // Using the working hash instead of the hash computed on save. This is an 769 // "optimistic" hack, that comes from the fact the tree is saved asynchronously. 770 // As a result, the hash for the working version of the tree is not computed 771 // yet. So, using calling `Hash()` will either return an empty hash (when first tree), 772 // either is returns the one from the previous version. 773 // Therefore, we have to use the working hash. It shouldn't be a problem 774 // as long as the tree is not modified past this point (this is the 775 // "optimistic" part). In the end, the hashes should match. 776 hash = e.snapshotTree.WorkingHash() 777 go func() { 778 save() 779 e.snapshotTreeLock.Unlock() 780 }() 781 } else { 782 save() 783 hash = e.snapshotTree.Hash() 784 e.snapshotTreeLock.Unlock() 785 } 786 787 e.log.Info("Snapshot taken", 788 logging.Uint64("height", height), 789 logging.ByteString("hash", hash), 790 logging.String("protocol-version", e.appState.ProtocolVersion), 791 logging.Bool("protocol-upgrade", e.appState.ProtocolUpdgade), 792 ) 793 794 return hash, doneCh, nil 795 } 796 797 func (e *Engine) updateAppState() error { 798 keys, ok := e.namespacesToTreeKeys[types.AppSnapshot] 799 if !ok { 800 return types.ErrNoPrefixFound 801 } 802 // there should be only 1 entry here 803 if len(keys) > 1 || len(keys) == 0 { 804 return types.ErrUnexpectedKey 805 } 806 807 pl := types.Payload{ 808 Data: &types.PayloadAppState{ 809 AppState: e.appState, 810 }, 811 } 812 813 data, err := proto.Marshal(pl.IntoProto()) 814 if err != nil { 815 return fmt.Errorf("could not serialize the payload to proto: %w", err) 816 } 817 818 e.snapshotTree.AddState(keys[0], data) 819 return nil 820 } 821 822 func (e *Engine) findTreeByBlockHeight(height uint64) (*types.Snapshot, error) { 823 e.snapshotTreeLock.Lock() 824 defer e.snapshotTreeLock.Unlock() 825 826 immutableTree, err := e.snapshotTree.FindImmutableTreeByHeight(height) 827 if err != nil { 828 return nil, fmt.Errorf("could not find snapshot associated to block height %d: %w", height, err) 829 } 830 831 loadedSnapshot, err := types.SnapshotFromTree(immutableTree) 832 if err != nil { 833 return nil, fmt.Errorf("could not convert tree into snapshot: %w", err) 834 } 835 836 return loadedSnapshot, nil 837 } 838 839 func (e *Engine) shouldAbortStateSync() bool { 840 e.attemptsToApplySnapshotChunk++ 841 842 return e.attemptsToApplySnapshotChunk >= e.config.RetryLimit 843 } 844 845 func (e *Engine) abortStateSync() tmtypes.ResponseApplySnapshotChunk { 846 e.resetOfferedSnapshot() 847 848 return tmtypes.ResponseApplySnapshotChunk{ 849 Result: tmtypes.ResponseApplySnapshotChunk_ABORT, 850 } 851 } 852 853 func (e *Engine) resetOfferedSnapshot() { 854 e.offeredSnapshot = nil 855 e.attemptsToApplySnapshotChunk = 0 856 } 857 858 func (e *Engine) restoreStateFromTree(ctx context.Context) error { 859 e.snapshotTreeLock.Lock() 860 defer e.snapshotTreeLock.Unlock() 861 862 lastSnapshotPayloads, err := e.snapshotTree.AsPayloads() 863 if err != nil { 864 return fmt.Errorf("could not generate the immutable AVL tree: %w", err) 865 } 866 867 if err := e.restoreStateFromSnapshot(ctx, lastSnapshotPayloads); err != nil { 868 return fmt.Errorf("could not restore the state from the local snapshot: %w", err) 869 } 870 871 return nil 872 } 873 874 func (e *Engine) restoreStateFromSnapshot(ctx context.Context, payloads []*types.Payload) error { 875 payloadsPerNamespace := groupPayloadsPerNamespace(payloads) 876 877 // this is the state of the application when the snapshot was taken 878 e.appState = payloadsPerNamespace[types.AppSnapshot][0].GetAppState().AppState 879 880 // These values are needed in the context by providers, to send events. 881 ctx = vegactx.WithTraceID(vegactx.WithBlockHeight(ctx, e.appState.Height), e.appState.Block) 882 ctx = vegactx.WithChainID(ctx, e.appState.ChainID) 883 884 ctx = vegactx.WithSnapshotInfo(ctx, e.appState.ProtocolVersion, e.appState.ProtocolUpdgade) 885 886 // Restoring state in globally shared services. 887 e.timeService.SetTimeNow(ctx, time.Unix(0, e.appState.Time)) 888 e.timeService.SetPrevTime(time.Unix(0, e.appState.PrevBlockTime)) 889 e.statsService.SetHeight(e.appState.Height) 890 891 e.log.Info("loading state from snapshot", 892 logging.Uint64("block-height", e.appState.Height), 893 logging.String("version-taken", e.appState.ProtocolVersion), 894 logging.Bool("protocol-upgrade", e.appState.ProtocolUpdgade), 895 ) 896 897 // Calling providers that need to be called before restoring their state. 898 for _, provider := range e.preRestoreProviders { 899 if err := provider.OnStateLoadStarts(ctx); err != nil { 900 return fmt.Errorf("an error occurred on provider %q during snapshot pre-restoration: %w", provider.Namespace(), err) 901 } 902 } 903 904 // Restoring state in providers. 905 for _, namespace := range providersInCallOrder { 906 for _, payload := range payloadsPerNamespace[namespace] { 907 provider, ok := e.treeKeysToProviders[payload.TreeKey()] 908 if !ok { 909 return fmt.Errorf("%w %s", types.ErrUnknownSnapshotNamespace, payload.TreeKey()) 910 } 911 912 e.log.Info("Restoring state in provider", logging.String("tree-key", payload.TreeKey())) 913 914 newProviders, err := provider.LoadState(ctx, payload) 915 if err != nil { 916 return fmt.Errorf("an error occurred on provider %q while restoring state on tree-key %q: %w", provider.Namespace(), payload.TreeKey(), err) 917 } 918 919 // Some providers depend on resources that also need to have their 920 // state restored. Therefore, we add them, on the fly, to the existing 921 // provider list. 922 if len(newProviders) != 0 { 923 e.addProviders(newProviders...) 924 } 925 } 926 } 927 928 // Calling providers that need to be called after their state has been restored. 929 for _, provider := range e.postRestoreProviders { 930 if err := provider.OnStateLoaded(ctx); err != nil { 931 return fmt.Errorf("an error occurred on provider %q during snapshot post-restoration: %w", provider.Namespace(), err) 932 } 933 } 934 935 e.stateRestored.Store(true) 936 937 return nil 938 } 939 940 func (e *Engine) initializeTree(vegaPaths paths.Paths) error { 941 var storageOption tree.Options 942 switch e.config.Storage { 943 case InMemoryDB: 944 storageOption = tree.WithInMemoryDatabase() 945 case LevelDB: 946 storageOption = tree.WithLevelDBDatabase(vegaPaths) 947 default: 948 return types.ErrInvalidSnapshotStorageMethod 949 } 950 951 snapshotTree, err := tree.New( 952 e.log, 953 tree.WithMaxNumberOfSnapshotsToKeep(uint64(e.config.KeepRecent)), 954 tree.StartingAtBlockHeight(uint64(e.config.StartHeight)), 955 storageOption, 956 ) 957 if err != nil { 958 return fmt.Errorf("could not initialize the snapshot tree: %w", err) 959 } 960 e.snapshotTree = snapshotTree 961 962 return nil 963 } 964 965 func (e *Engine) addProviders(newProviders ...types.StateProvider) { 966 for _, newProvider := range newProviders { 967 newKeys := newProvider.Keys() 968 namespace := newProvider.Namespace() 969 970 if !slices.Contains(providersInCallOrder, namespace) { 971 // This is a programming error that can happen when introducing a new 972 // provider. All namespaces must be listed to the list providersInCallOrder, 973 // otherwise their state won't be restored, as the state restoration 974 // iterates through this list, is strict order, to know in which order 975 // state must be restored. 976 e.log.Panic(fmt.Sprintf("The provider %q is not listed in the sorted provider list", namespace)) 977 } 978 979 existingKeys, ok := e.namespacesToProviderKeys[namespace] 980 if !ok { 981 e.namespacesToTreeKeys[namespace] = make([][]byte, 0, len(newKeys)) 982 e.registeredNamespaces = append(e.registeredNamespaces, namespace) 983 } 984 985 duplicatedKeys := findDuplicatedKeys(existingKeys, newKeys) 986 if len(duplicatedKeys) > 0 { 987 // This is a programming error that might happen when adding a 988 // provider to the codebase. 989 e.log.Panic("A state provider in the same namespace is already using these keys", 990 zap.String("namespace", namespace.String()), 991 zap.Any("keys", duplicatedKeys), 992 zap.String("culprit", reflect.TypeOf(newProvider).String()), 993 ) 994 } 995 996 e.namespacesToProviderKeys[namespace] = append(e.namespacesToProviderKeys[namespace], newKeys...) 997 for _, newKey := range newKeys { 998 treeKey := types.GetNodeKey(namespace, newKey) 999 e.treeKeysToProviderKeys[treeKey] = newKey 1000 e.treeKeysToProviders[treeKey] = newProvider 1001 e.namespacesToTreeKeys[namespace] = append(e.namespacesToTreeKeys[namespace], []byte(treeKey)) 1002 } 1003 1004 if p, ok := newProvider.(types.PostRestore); ok { 1005 e.postRestoreProviders = append(e.postRestoreProviders, p) 1006 } 1007 if p, ok := newProvider.(types.PreRestore); ok { 1008 e.preRestoreProviders = append(e.preRestoreProviders, p) 1009 } 1010 } 1011 } 1012 1013 func (e *Engine) ensureEngineIsStarted() { 1014 if !e.started.Load() { 1015 // This is a programming error. 1016 e.log.Panic("The snapshot engine has not started!") 1017 } 1018 } 1019 1020 func findDuplicatedKeys(existingKeys, newKeys []string) []string { 1021 duplicatedKeys := []string{} 1022 for _, newKey := range newKeys { 1023 if slices.Contains(existingKeys, newKey) { 1024 duplicatedKeys = append(duplicatedKeys, newKey) 1025 } 1026 } 1027 return duplicatedKeys 1028 }