github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/scripts/scmigrate/migrate.go (about)

     1  // Package scmigrate implements a migration for SeenCommit data
     2  // between 0.34 and 0.35
     3  //
     4  // The Migrate implementation is idempotent and finds all seen commit
     5  // records and deletes all *except* the record corresponding to the
     6  // highest height.
     7  package scmigrate
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"errors"
    13  	"fmt"
    14  	"sort"
    15  
    16  	"github.com/gogo/protobuf/proto"
    17  	"github.com/google/orderedcode"
    18  	dbm "github.com/tendermint/tm-db"
    19  
    20  	tmproto "github.com/ari-anchor/sei-tendermint/proto/tendermint/types"
    21  	"github.com/ari-anchor/sei-tendermint/types"
    22  )
    23  
    24  type toMigrate struct {
    25  	key    []byte
    26  	commit *types.Commit
    27  }
    28  
    29  const prefixSeenCommit = int64(3)
    30  
    31  func makeKeyFromPrefix(ids ...int64) []byte {
    32  	vals := make([]interface{}, len(ids))
    33  	for idx := range ids {
    34  		vals[idx] = ids[idx]
    35  	}
    36  
    37  	key, err := orderedcode.Append(nil, vals...)
    38  	if err != nil {
    39  		panic(err)
    40  	}
    41  	return key
    42  }
    43  
    44  func makeToMigrate(val []byte) (*types.Commit, error) {
    45  	if len(val) == 0 {
    46  		return nil, errors.New("empty value")
    47  	}
    48  
    49  	var pbc = new(tmproto.Commit)
    50  
    51  	if err := proto.Unmarshal(val, pbc); err != nil {
    52  		return nil, fmt.Errorf("error reading block seen commit: %w", err)
    53  	}
    54  
    55  	commit, err := types.CommitFromProto(pbc)
    56  	if commit == nil {
    57  		// theoretically we should error for all errors, but
    58  		// there's no reason to keep junk data in the
    59  		// database, and it makes testing easier.
    60  		if err != nil {
    61  			return nil, fmt.Errorf("error from proto commit: %w", err)
    62  		}
    63  		return nil, fmt.Errorf("missing commit")
    64  	}
    65  
    66  	return commit, nil
    67  }
    68  
    69  func sortMigrations(scData []toMigrate) {
    70  	// put this in it's own function just to make it testable
    71  	sort.SliceStable(scData, func(i, j int) bool {
    72  		return scData[i].commit.Height > scData[j].commit.Height
    73  	})
    74  }
    75  
    76  func getAllSeenCommits(ctx context.Context, db dbm.DB) ([]toMigrate, error) {
    77  	scKeyPrefix := makeKeyFromPrefix(prefixSeenCommit)
    78  	iter, err := db.Iterator(
    79  		scKeyPrefix,
    80  		makeKeyFromPrefix(prefixSeenCommit+1),
    81  	)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	scData := []toMigrate{}
    87  	for ; iter.Valid(); iter.Next() {
    88  		if err := ctx.Err(); err != nil {
    89  			return nil, err
    90  		}
    91  
    92  		k := iter.Key()
    93  		nk := make([]byte, len(k))
    94  		copy(nk, k)
    95  
    96  		if !bytes.HasPrefix(nk, scKeyPrefix) {
    97  			break
    98  		}
    99  		commit, err := makeToMigrate(iter.Value())
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  
   104  		scData = append(scData, toMigrate{
   105  			key:    nk,
   106  			commit: commit,
   107  		})
   108  	}
   109  	if err := iter.Error(); err != nil {
   110  		return nil, err
   111  	}
   112  	if err := iter.Close(); err != nil {
   113  		return nil, err
   114  	}
   115  	return scData, nil
   116  }
   117  
   118  func renameRecord(db dbm.DB, keep toMigrate) error {
   119  	wantKey := makeKeyFromPrefix(prefixSeenCommit)
   120  	if bytes.Equal(keep.key, wantKey) {
   121  		return nil // we already did this conversion
   122  	}
   123  
   124  	// This record's key has already been converted to the "new" format, we just
   125  	// now need to trim off the tail.
   126  	val, err := db.Get(keep.key)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	batch := db.NewBatch()
   132  	if err := batch.Delete(keep.key); err != nil {
   133  		return err
   134  	}
   135  	if err := batch.Set(wantKey, val); err != nil {
   136  		return err
   137  	}
   138  	werr := batch.Write()
   139  	cerr := batch.Close()
   140  	if werr != nil {
   141  		return werr
   142  	}
   143  	return cerr
   144  }
   145  
   146  func deleteRecords(db dbm.DB, scData []toMigrate) error {
   147  	// delete all the remaining stale values in a single batch
   148  	batch := db.NewBatch()
   149  
   150  	for _, mg := range scData {
   151  		if err := batch.Delete(mg.key); err != nil {
   152  			return err
   153  		}
   154  	}
   155  
   156  	if err := batch.WriteSync(); err != nil {
   157  		return err
   158  	}
   159  
   160  	if err := batch.Close(); err != nil {
   161  		return err
   162  	}
   163  	return nil
   164  }
   165  
   166  func Migrate(ctx context.Context, db dbm.DB) error {
   167  	scData, err := getAllSeenCommits(ctx, db)
   168  	if err != nil {
   169  		return fmt.Errorf("sourcing tasks to migrate: %w", err)
   170  	} else if len(scData) == 0 {
   171  		return nil // nothing to do
   172  	}
   173  
   174  	// Sort commits in decreasing order of height.
   175  	sortMigrations(scData)
   176  
   177  	// Keep and rename the newest seen commit, delete the rest.
   178  	// In TM < v0.35 we kept a last-seen commit for each height; in v0.35 we
   179  	// retain only the latest.
   180  	keep, remove := scData[0], scData[1:]
   181  
   182  	if err := renameRecord(db, keep); err != nil {
   183  		return fmt.Errorf("renaming seen commit record: %w", err)
   184  	}
   185  
   186  	if len(remove) == 0 {
   187  		return nil
   188  	}
   189  
   190  	// Remove any older seen commits. Prior to v0.35, we kept these records for
   191  	// all heights, but v0.35 keeps only the latest.
   192  	if err := deleteRecords(db, remove); err != nil {
   193  		return fmt.Errorf("writing data: %w", err)
   194  	}
   195  
   196  	return nil
   197  }