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 }