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  }