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  }