code.vegaprotocol.io/vega@v0.79.0/vegatools/snapshotdb/snapshotdb.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 snapshotdb 17 18 import ( 19 "bufio" 20 "encoding/hex" 21 "errors" 22 "fmt" 23 "os" 24 "sort" 25 26 metadatadb "code.vegaprotocol.io/vega/core/snapshot/databases/metadata" 27 snapshotdb "code.vegaprotocol.io/vega/core/snapshot/databases/snapshot" 28 "code.vegaprotocol.io/vega/core/types" 29 "code.vegaprotocol.io/vega/paths" 30 snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" 31 32 cometbftdb "github.com/cometbft/cometbft-db" 33 "github.com/cosmos/iavl" 34 "github.com/gogo/protobuf/jsonpb" 35 pb "github.com/gogo/protobuf/proto" 36 "github.com/syndtr/goleveldb/leveldb/opt" 37 "google.golang.org/protobuf/proto" 38 ) 39 40 // Data is a representation of the information we scrape from the avl tree. 41 type Data struct { 42 Height uint64 `json:"height,omitempty"` 43 Version int64 `json:"version"` 44 Size int64 `json:"size"` 45 Hash string `json:"hash"` 46 } 47 48 type Database interface { 49 cometbftdb.DB 50 Clear() error 51 } 52 53 func initialiseTree(dbPath string) (*cometbftdb.GoLevelDB, *iavl.MutableTree, error) { 54 conn, err := cometbftdb.NewGoLevelDBWithOpts( 55 "snapshot", 56 dbPath, 57 &opt.Options{ 58 ErrorIfMissing: true, 59 }) 60 if err != nil { 61 return nil, nil, fmt.Errorf("failed to open database located at %s : %w", dbPath, err) 62 } 63 64 tree, err := iavl.NewMutableTree(conn, 0, false) 65 if err != nil { 66 return nil, nil, err 67 } 68 69 if _, err = tree.Load(); err != nil { 70 return nil, nil, err 71 } 72 return conn, tree, nil 73 } 74 75 // SnapshotData returns an overview of each snapshot saved to disk, namely the height and its hash. 76 func SnapshotData(dbPath string, heightToOutput uint64) ([]Data, []Data, error) { 77 conn, tree, err := initialiseTree(dbPath) 78 defer func() { 79 if conn != nil { 80 conn.Close() 81 } 82 }() 83 if err != nil { 84 return nil, nil, err 85 } 86 versions := tree.AvailableVersions() 87 trees := make([]Data, 0, len(versions)) 88 invalidVersions := make([]Data, 0) 89 90 for _, version := range versions { 91 v, err := tree.LazyLoadVersion(int64(version)) 92 if err != nil { 93 return nil, nil, err 94 } 95 96 snapshotHash, _ := tree.Hash() 97 app, err := types.AppStateFromTree(tree.ImmutableTree) 98 if err != nil { 99 invalidVersions = append(invalidVersions, Data{ 100 Version: v, 101 Hash: hex.EncodeToString(snapshotHash), 102 }) 103 continue 104 } 105 106 data := Data{ 107 Version: v, 108 Height: app.AppState.Height, 109 Hash: hex.EncodeToString(snapshotHash), 110 Size: tree.Size(), 111 } 112 113 if heightToOutput > 0 { 114 if app.AppState.Height == heightToOutput { 115 return []Data{data}, nil, nil 116 } 117 } 118 trees = append(trees, data) 119 } 120 sort.SliceStable(trees, func(i, j int) bool { 121 return trees[i].Height > trees[j].Height 122 }) 123 124 return trees, invalidVersions, nil 125 } 126 127 // SavePayloadsToFile given a block height and file path writes all the payloads for that snapshot height 128 // to the file in json format. 129 func SavePayloadsToFile(dbPath string, outputPath string, heightToOutput uint64) error { 130 conn, tree, err := initialiseTree(dbPath) 131 defer func() { 132 if conn != nil { 133 conn.Close() 134 } 135 }() 136 if err != nil { 137 return err 138 } 139 140 versions := tree.AvailableVersions() 141 for i := len(versions) - 1; i > -1; i-- { 142 _, err := tree.LazyLoadVersion(int64(versions[i])) 143 if err != nil { 144 return err 145 } 146 147 // looking up the appstate directly by its key first then unmarshalling all payloads 148 // is quicker 149 app, err := types.AppStateFromTree(tree.ImmutableTree) 150 if err != nil { 151 return err 152 } 153 if app.AppState.Height != heightToOutput { 154 continue 155 } 156 payloads, _, _ := getAllPayloads(tree) 157 return writePayloads(payloads, outputPath) 158 } 159 160 return errors.New("failed to find snapshot for block-height") 161 } 162 163 func writePayloads(payloads []*snapshotpb.Payload, outputPath string) error { 164 f, err := os.Create(outputPath) 165 if err != nil { 166 return err 167 } 168 defer func() { _ = f.Close() }() 169 170 w := bufio.NewWriter(f) 171 m := jsonpb.Marshaler{Indent: " "} 172 173 payloadData := struct { 174 Data []*snapshotpb.Payload `json:"data,omitempty" protobuf:"bytes,1,rep,name=data"` 175 pb.Message 176 }{ 177 Data: payloads, 178 } 179 180 s, err := m.MarshalToString(&payloadData) 181 if err != nil { 182 return err 183 } 184 185 if _, err = w.WriteString(s); err != nil { 186 return err 187 } 188 189 if err = w.Flush(); err != nil { 190 return err 191 } 192 193 return nil 194 } 195 196 func getAllPayloads(tree *iavl.MutableTree) (payloads []*snapshotpb.Payload, blockHeight uint64, err error) { 197 _, err = tree.Iterate(func(key []byte, val []byte) bool { 198 p := new(snapshotpb.Payload) 199 if err = proto.Unmarshal(val, p); err != nil { 200 return true 201 } 202 203 if appState := p.GetAppState(); appState != nil { 204 blockHeight = appState.GetHeight() 205 } 206 207 payloads = append(payloads, p) 208 return false 209 }) 210 if err != nil { 211 return 212 } 213 214 return payloads, blockHeight, err 215 } 216 217 // SetProtocolUpgrade will take the latest snapshot in the tree and rewrites it with the protocolUpgrade flag set to 218 // true so that we can pretend that the snapshot was taken for a upgrade to help with testing. 219 func SetProtocolUpgrade(vegaPaths paths.Paths) error { 220 snap, _ := snapshotdb.NewLevelDBDatabase(vegaPaths) 221 meta, _ := metadatadb.NewLevelDBDatabase(vegaPaths) 222 defer snap.Close() 223 defer meta.Close() 224 225 tree, err := iavl.NewMutableTree(snap, 0, false) 226 if err != nil { 227 return err 228 } 229 230 if _, err = tree.Load(); err != nil { 231 return err 232 } 233 234 tree.LazyLoadVersion(-1) 235 oldVersion := tree.Version() 236 237 var perr error 238 var k, v []byte 239 240 _, err = tree.Iterate(func(key []byte, val []byte) bool { 241 p := &snapshotpb.Payload{} 242 if perr = proto.Unmarshal(val, p); perr != nil { 243 return true 244 } 245 246 appState := p.GetAppState() 247 if appState == nil { 248 return false 249 } 250 251 // change the value 252 appState.ProtocolUpgrade = true 253 254 // marshal it up again 255 pp := &snapshotpb.Payload{ 256 Data: &snapshotpb.Payload_AppState{ 257 AppState: appState, 258 }, 259 } 260 k = key 261 v, perr = proto.Marshal(pp) 262 return true 263 }) 264 265 if err != nil { 266 return fmt.Errorf("failed to traverse snapshot payloads: %w", err) 267 } 268 269 if perr != nil { 270 return fmt.Errorf("failed to unpack appstate payload: %w", perr) 271 } 272 273 if _, err = tree.Set(k, v); err != nil { 274 return fmt.Errorf("failed to save new appstate to snapshot: %w", err) 275 } 276 277 _, newVersion, err := tree.SaveVersion() 278 if err != nil { 279 return err 280 } 281 282 // delete the old version so that we do not have two with the same block-height 283 tree.DeleteVersion(oldVersion) 284 285 // update the version <-> block-height map in the meta-data 286 metaSnap, err := meta.Load(oldVersion) 287 if err != nil { 288 return err 289 } 290 291 // delete the meta-data pegged against the old version 292 if err := meta.Delete(oldVersion); err != nil { 293 return err 294 } 295 296 // new it against the new version 297 if err := meta.Save(newVersion, metaSnap); err != nil { 298 return err 299 } 300 return nil 301 }