code.vegaprotocol.io/vega@v0.79.0/core/types/snapshot.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 types
    17  
    18  import (
    19  	"context"
    20  	"encoding/hex"
    21  	"errors"
    22  	"fmt"
    23  
    24  	"code.vegaprotocol.io/vega/libs/crypto"
    25  	"code.vegaprotocol.io/vega/libs/proto"
    26  	snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    27  
    28  	tmtypes "github.com/cometbft/cometbft/abci/types"
    29  	"github.com/cosmos/iavl"
    30  )
    31  
    32  // StateProvider - not a huge fan of this interface being here, but it ensures that the state providers
    33  // don't have to import the snapshot package
    34  //
    35  //go:generate go run github.com/golang/mock/mockgen -destination mocks/state_provider_mock.go -package mocks code.vegaprotocol.io/vega/core/types StateProvider
    36  type StateProvider interface {
    37  	Namespace() SnapshotNamespace
    38  	Keys() []string
    39  	// GetState must be thread-safe as it may be called from multiple goroutines concurrently!
    40  	GetState(key string) ([]byte, []StateProvider, error)
    41  	LoadState(ctx context.Context, pl *Payload) ([]StateProvider, error)
    42  	Stopped() bool
    43  }
    44  
    45  // PostRestore is basically a StateProvider which, after the full core state is restored, expects a callback to finalise the state restore
    46  // Note that the order in which the calls to this OnStateLoaded functions are called is not pre-defined. As such, this method should only be used
    47  // for engine internals (upkeep, essentially)
    48  //
    49  //go:generate go run github.com/golang/mock/mockgen -destination mocks/restore_state_provider_mock.go -package mocks code.vegaprotocol.io/vega/core/types PostRestore
    50  type PostRestore interface {
    51  	StateProvider
    52  	OnStateLoaded(ctx context.Context) error
    53  }
    54  
    55  type PreRestore interface {
    56  	StateProvider
    57  	OnStateLoadStarts(ctx context.Context) error
    58  }
    59  
    60  type SnapshotNamespace string
    61  
    62  const (
    63  	undefinedSnapshot              SnapshotNamespace = ""
    64  	AppSnapshot                    SnapshotNamespace = "app"
    65  	AssetsSnapshot                 SnapshotNamespace = "assets"
    66  	WitnessSnapshot                SnapshotNamespace = "witness" // Must be done before any engine that call RestoreResource
    67  	BankingSnapshot                SnapshotNamespace = "banking"
    68  	CheckpointSnapshot             SnapshotNamespace = "checkpoint"
    69  	CollateralSnapshot             SnapshotNamespace = "collateral"
    70  	NetParamsSnapshot              SnapshotNamespace = "netparams"
    71  	DelegationSnapshot             SnapshotNamespace = "delegation"
    72  	GovernanceSnapshot             SnapshotNamespace = "governance"
    73  	PositionsSnapshot              SnapshotNamespace = "positions"
    74  	MatchingSnapshot               SnapshotNamespace = "matching"
    75  	ExecutionSnapshot              SnapshotNamespace = "execution"
    76  	EpochSnapshot                  SnapshotNamespace = "epoch"
    77  	StakingSnapshot                SnapshotNamespace = "staking"
    78  	RewardSnapshot                 SnapshotNamespace = "rewards"
    79  	SpamSnapshot                   SnapshotNamespace = "spam"
    80  	LimitSnapshot                  SnapshotNamespace = "limits"
    81  	NotarySnapshot                 SnapshotNamespace = "notary"
    82  	StakeVerifierSnapshot          SnapshotNamespace = "stakeverifier"
    83  	EventForwarderSnapshot         SnapshotNamespace = "eventforwarder"
    84  	TopologySnapshot               SnapshotNamespace = "topology"
    85  	LiquiditySnapshot              SnapshotNamespace = "liquidity"
    86  	LiquidityV2Snapshot            SnapshotNamespace = "liquidityV2"
    87  	LiquidityTargetSnapshot        SnapshotNamespace = "liquiditytarget"
    88  	FloatingPointConsensusSnapshot SnapshotNamespace = "floatingpoint"
    89  	MarketActivityTrackerSnapshot  SnapshotNamespace = "marketActivityTracker"
    90  	ERC20MultiSigTopologySnapshot  SnapshotNamespace = "erc20multisigtopology"
    91  	EVMMultiSigTopologiesSnapshot  SnapshotNamespace = "evmmultisigtopologies"
    92  	PoWSnapshot                    SnapshotNamespace = "pow"
    93  	ProtocolUpgradeSnapshot        SnapshotNamespace = "protocolUpgradeProposals"
    94  	SettlementSnapshot             SnapshotNamespace = "settlement"
    95  	HoldingAccountTrackerSnapshot  SnapshotNamespace = "holdingAccountTracker"
    96  	EthereumOracleVerifierSnapshot SnapshotNamespace = "ethereumoracleverifier"
    97  	L2EthereumOraclesSnapshot      SnapshotNamespace = "l2EthereumOracles"
    98  	TeamsSnapshot                  SnapshotNamespace = "teams"
    99  	PartiesSnapshot                SnapshotNamespace = "parties"
   100  	VestingSnapshot                SnapshotNamespace = "vesting"
   101  	ReferralProgramSnapshot        SnapshotNamespace = "referralProgram"
   102  	ActivityStreakSnapshot         SnapshotNamespace = "activitystreak"
   103  	VolumeDiscountProgramSnapshot  SnapshotNamespace = "volumeDiscountProgram"
   104  	LiquidationSnapshot            SnapshotNamespace = "liquidation"
   105  	TxCacheSnapshot                SnapshotNamespace = "txCache"
   106  	EVMHeartbeatSnapshot           SnapshotNamespace = "evmheartbeat"
   107  	VolumeRebateProgramSnapshot    SnapshotNamespace = "volumeRebateProgram"
   108  
   109  	MaxChunkSize   = 16 * 1000 * 1000 // technically 16 * 1024 * 1024, but you know
   110  	IdealChunkSize = 10 * 1000 * 1000 // aim for 10MB
   111  )
   112  
   113  var (
   114  	ErrUnknownSnapshotNamespace     = errors.New("unknown snapshot namespace")
   115  	ErrNoPrefixFound                = errors.New("no prefix in chunk keys")
   116  	ErrInconsistentNamespaceKeys    = errors.New("chunk contains several namespace keys")
   117  	ErrChunkHashMismatch            = errors.New("loaded chunk hash does not match metadata")
   118  	ErrChunkOutOfRange              = errors.New("chunk number out of range")
   119  	ErrMissingChunks                = errors.New("missing previous chunks")
   120  	ErrSnapshotKeyDoesNotExist      = errors.New("unknown key for snapshot")
   121  	ErrInvalidSnapshotNamespace     = errors.New("invalid snapshot namespace")
   122  	ErrUnknownSnapshotType          = errors.New("snapshot data type not known")
   123  	ErrUnknownSnapshotChunkHeight   = errors.New("no snapshot or chunk found for given height")
   124  	ErrInvalidSnapshotFormat        = errors.New("invalid snapshot format")
   125  	ErrSnapshotFormatMismatch       = errors.New("snapshot formats do not match")
   126  	ErrUnexpectedKey                = errors.New("snapshot namespace has unknown/unexpected key(s)")
   127  	ErrInvalidSnapshotStorageMethod = errors.New("invalid snapshot storage method")
   128  )
   129  
   130  type SnapshotFormat = snapshot.Format
   131  
   132  type RawChunk struct {
   133  	Nr     uint32
   134  	Data   []byte
   135  	Height uint64
   136  	Format SnapshotFormat
   137  }
   138  
   139  func SnapshotFromTM(tms *tmtypes.Snapshot) (*Snapshot, error) {
   140  	snap := Snapshot{
   141  		Height:     tms.Height,
   142  		Format:     SnapshotFormat(tms.Format),
   143  		Chunks:     tms.Chunks,
   144  		Hash:       tms.Hash,
   145  		ByteChunks: make([][]byte, int(tms.Chunks)), // have the chunk slice ready for loading
   146  	}
   147  	meta := &snapshot.Metadata{}
   148  	if err := proto.Unmarshal(tms.Metadata, meta); err != nil {
   149  		return nil, err
   150  	}
   151  	md, err := MetadataFromProto(meta)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	snap.Meta = md
   156  	return &snap, nil
   157  }
   158  
   159  func (s *Snapshot) ToTM() (*tmtypes.Snapshot, error) {
   160  	md, err := proto.Marshal(s.Meta.IntoProto())
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	return &tmtypes.Snapshot{
   165  		Height:   s.Height,
   166  		Format:   uint32(s.Format),
   167  		Chunks:   s.Chunks,
   168  		Hash:     s.Hash,
   169  		Metadata: md,
   170  	}, nil
   171  }
   172  
   173  func AppStateFromTree(tree *iavl.ImmutableTree) (*PayloadAppState, error) {
   174  	appState := &Payload{
   175  		Data: &PayloadAppState{AppState: &AppState{}},
   176  	}
   177  	key := appState.TreeKey()
   178  	data, _ := tree.Get([]byte(key))
   179  	if data == nil {
   180  		return nil, ErrSnapshotKeyDoesNotExist
   181  	}
   182  	prp := appState.IntoProto()
   183  	if err := proto.Unmarshal(data, prp); err != nil {
   184  		return nil, err
   185  	}
   186  	appState = PayloadFromProto(prp)
   187  	return appState.GetAppState(), nil
   188  }
   189  
   190  // SnapshotFromTree traverses the given avl tree and represents it as a Snapshot.
   191  func SnapshotFromTree(tree *iavl.ImmutableTree) (*Snapshot, error) {
   192  	hash, err := tree.Hash()
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	snap := Snapshot{
   197  		Hash: hash,
   198  		Meta: &Metadata{
   199  			Version:     tree.Version(),
   200  			NodeHashes:  []*NodeHash{}, // a slice of the data for each node in the tree without the payload value, just its hash
   201  			ChunkHashes: []string{},
   202  		},
   203  
   204  		// a slice of payloads that correspond to the node hashes. Note that len(NodeHashes) != len(Nodes) since
   205  		// only the leaf nodes of the tree contain payload data, the sub-tree roots only exist for the merkle-hash.
   206  		Nodes: []*Payload{},
   207  	}
   208  
   209  	exporter, err := tree.Export()
   210  	if err != nil {
   211  		return nil, fmt.Errorf("could not export the AVL tree: %w", err)
   212  	}
   213  	defer exporter.Close()
   214  
   215  	exportedNode, err := exporter.Next()
   216  	for err == nil {
   217  		hash := hex.EncodeToString(crypto.Hash(exportedNode.Value))
   218  		node := &NodeHash{
   219  			Hash:    hash,
   220  			Height:  int32(exportedNode.Height),
   221  			Version: exportedNode.Version,
   222  			Key:     string(exportedNode.Key),
   223  			IsLeaf:  exportedNode.Value != nil,
   224  		}
   225  
   226  		snap.Meta.NodeHashes = append(snap.Meta.NodeHashes, node)
   227  
   228  		// its only the nodes at the end of the tree which have the payload data
   229  		// all intermediary nodes have empty values and just make up the merkle-tree
   230  		// if we are a payload-less node just step again
   231  		if !node.IsLeaf {
   232  			exportedNode, err = exporter.Next()
   233  			continue
   234  		}
   235  
   236  		// sort out the payload for this node
   237  		pl := &snapshot.Payload{}
   238  		if perr := proto.Unmarshal(exportedNode.Value, pl); perr != nil {
   239  			return nil, perr
   240  		}
   241  
   242  		payload := PayloadFromProto(pl)
   243  
   244  		snap.Nodes = append(snap.Nodes, payload)
   245  
   246  		// if it happens to be the appstate payload grab the snapshot height while we're there
   247  		if payload.Namespace() == AppSnapshot {
   248  			p, _ := payload.Data.(*PayloadAppState)
   249  			snap.Height = p.AppState.Height
   250  			snap.Meta.ProtocolVersion = p.AppState.ProtocolVersion
   251  			snap.Meta.ProtocolUpgrade = p.AppState.ProtocolUpdgade
   252  		}
   253  
   254  		// move onto the next node
   255  		exportedNode, err = exporter.Next()
   256  	}
   257  
   258  	if !errors.Is(err, iavl.ErrorExportDone) {
   259  		// either an error occurred while traversing, or we never reached the end
   260  		return nil, fmt.Errorf("failed to export AVL tree: %w", err)
   261  	}
   262  
   263  	if snap.Height == 0 {
   264  		return nil, errors.New("the block height is 0 after export, the app state might by missing")
   265  	}
   266  
   267  	// set chunks, ready to send in case we need it
   268  	snap.nodesToChunks()
   269  	return &snap, nil
   270  }
   271  
   272  func (s *Snapshot) RawChunkByIndex(idx uint32) (*RawChunk, error) {
   273  	if s.Chunks < idx {
   274  		return nil, ErrUnknownSnapshotChunkHeight
   275  	}
   276  	return &RawChunk{
   277  		Nr:     idx,
   278  		Data:   s.ByteChunks[int(idx)],
   279  		Height: s.Height,
   280  		Format: s.Format,
   281  	}, nil
   282  }
   283  
   284  func (s *Snapshot) nodesToChunks() {
   285  	all := &Chunk{
   286  		Data: s.Nodes[:],
   287  		Nr:   1,
   288  		Of:   1,
   289  	}
   290  
   291  	b, _ := proto.Marshal(all.IntoProto())
   292  	if len(b) < MaxChunkSize {
   293  		s.DataChunks = []*Chunk{
   294  			all,
   295  		}
   296  		s.hashChunks()
   297  		return
   298  	}
   299  	parts := len(b) / IdealChunkSize
   300  	if t := parts * IdealChunkSize; t != len(b) {
   301  		parts++
   302  	}
   303  	s.ByteChunks = make([][]byte, 0, parts)
   304  	step := len(b) / parts
   305  	for i := 0; i < len(b); i += step {
   306  		end := i + step
   307  		if end > len(b) {
   308  			end = len(b)
   309  		}
   310  		s.ByteChunks = append(s.ByteChunks, b[i:end])
   311  	}
   312  
   313  	s.hashByteChunks()
   314  }
   315  
   316  func (s *Snapshot) hashByteChunks() {
   317  	s.Meta.ChunkHashes = make([]string, 0, len(s.ByteChunks))
   318  	for _, b := range s.ByteChunks {
   319  		s.Meta.ChunkHashes = append(s.Meta.ChunkHashes, hex.EncodeToString(crypto.Hash(b)))
   320  		s.Chunks++
   321  	}
   322  }
   323  
   324  func (s *Snapshot) hashChunks() {
   325  	s.Meta.ChunkHashes = make([]string, 0, len(s.DataChunks))
   326  	s.ByteChunks = make([][]byte, 0, len(s.DataChunks))
   327  	for _, c := range s.DataChunks {
   328  		pc := c.IntoProto()
   329  		b, _ := proto.Marshal(pc)
   330  		s.Meta.ChunkHashes = append(s.Meta.ChunkHashes, hex.EncodeToString(crypto.Hash(b)))
   331  		s.ByteChunks = append(s.ByteChunks, b)
   332  		s.Chunks++
   333  	}
   334  }
   335  
   336  func (s *Snapshot) LoadChunk(chunk *RawChunk) error {
   337  	if chunk.Nr > s.Chunks {
   338  		return ErrChunkOutOfRange
   339  	}
   340  	if len(s.ByteChunks) == 0 {
   341  		s.ByteChunks = make([][]byte, int(s.Chunks))
   342  	}
   343  	i := int(chunk.Nr)
   344  	if len(s.Meta.ChunkHashes) <= i {
   345  		return ErrChunkOutOfRange
   346  	}
   347  	hash := hex.EncodeToString(crypto.Hash(chunk.Data))
   348  	if s.Meta.ChunkHashes[i] != hash {
   349  		return ErrChunkHashMismatch
   350  	}
   351  	s.ByteChunks[i] = chunk.Data
   352  	s.byteLen += len(chunk.Data)
   353  	s.ChunksSeen++
   354  	chunk.Height = s.Height
   355  	chunk.Format = s.Format
   356  	if s.Chunks == s.ChunksSeen {
   357  		return s.unmarshalChunks()
   358  	}
   359  	// this ought to be the last one, but we're clearly missing some
   360  	if j := i + 1; j == int(s.Chunks) {
   361  		return ErrMissingChunks
   362  	}
   363  	return nil
   364  }
   365  
   366  func (s *Snapshot) MissingChunks() []uint32 {
   367  	// no need to check seen vs expected, seen will always be smaller
   368  	chunkIndexes := make([]uint32, 0, int(s.Chunks-s.ChunksSeen))
   369  	for i := range s.ByteChunks {
   370  		if len(s.ByteChunks) == 0 {
   371  			chunkIndexes = append(chunkIndexes, uint32(i))
   372  		}
   373  	}
   374  	return chunkIndexes
   375  }
   376  
   377  func (s *Snapshot) unmarshalChunks() error {
   378  	data := make([]byte, 0, s.byteLen)
   379  	for _, b := range s.ByteChunks {
   380  		data = append(data, b...)
   381  	}
   382  	sChunk := &snapshot.Chunk{}
   383  	if err := proto.Unmarshal(data, sChunk); err != nil {
   384  		return err
   385  	}
   386  	s.DataChunks = []*Chunk{
   387  		ChunkFromProto(sChunk),
   388  	}
   389  	s.Nodes = make([]*Payload, 0, len(sChunk.Data))
   390  	for _, pl := range sChunk.Data {
   391  		s.Nodes = append(s.Nodes, PayloadFromProto(pl))
   392  	}
   393  	return nil
   394  }
   395  
   396  func (s *Snapshot) Ready() bool {
   397  	return s.ChunksSeen == s.Chunks
   398  }
   399  
   400  func (n SnapshotNamespace) String() string {
   401  	return string(n)
   402  }
   403  
   404  func SnapshotFormatFromU32(f uint32) (SnapshotFormat, error) {
   405  	i32 := int32(f)
   406  	if _, ok := snapshot.Format_name[i32]; !ok {
   407  		return snapshot.Format_FORMAT_UNSPECIFIED, ErrInvalidSnapshotFormat
   408  	}
   409  	return SnapshotFormat(i32), nil
   410  }