mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-03 18:42:14 +00:00
163 lines
3.4 KiB
Go
163 lines
3.4 KiB
Go
// Package scmigrate implements a migration for SeenCommit data
|
|
// between 0.34 and 0.35
|
|
//
|
|
// The Migrate implementation is idempotent and finds all seen commit
|
|
// records and deletes all *except* the record corresponding to the
|
|
// highest height.
|
|
package scmigrate
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/google/orderedcode"
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
type toMigrate struct {
|
|
key []byte
|
|
commit *types.Commit
|
|
}
|
|
|
|
const prefixSeenCommit = int64(3)
|
|
|
|
func makeKeyFromPrefix(ids ...int64) []byte {
|
|
vals := make([]interface{}, len(ids))
|
|
for idx := range ids {
|
|
vals[idx] = ids[idx]
|
|
}
|
|
|
|
key, err := orderedcode.Append(nil, vals...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return key
|
|
}
|
|
|
|
func makeToMigrate(val []byte) (*types.Commit, error) {
|
|
if len(val) == 0 {
|
|
return nil, errors.New("empty value")
|
|
}
|
|
|
|
var pbc = new(tmproto.Commit)
|
|
|
|
if err := proto.Unmarshal(val, pbc); err != nil {
|
|
return nil, fmt.Errorf("error reading block seen commit: %w", err)
|
|
}
|
|
|
|
commit, err := types.CommitFromProto(pbc)
|
|
if commit == nil {
|
|
// theoretically we should error for all errors, but
|
|
// there's no reason to keep junk data in the
|
|
// database, and it makes testing easier.
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error from proto commit: %w", err)
|
|
}
|
|
return nil, fmt.Errorf("missing commit")
|
|
}
|
|
|
|
return commit, nil
|
|
}
|
|
|
|
func sortMigrations(scData []toMigrate) {
|
|
// put this in it's own function just to make it testable
|
|
sort.SliceStable(scData, func(i, j int) bool {
|
|
return scData[i].commit.Height > scData[j].commit.Height
|
|
})
|
|
}
|
|
|
|
func getMigrationsToDelete(in []toMigrate) []toMigrate { return in[1:] }
|
|
|
|
func getAllSeenCommits(ctx context.Context, db dbm.DB) ([]toMigrate, error) {
|
|
scKeyPrefix := makeKeyFromPrefix(prefixSeenCommit)
|
|
iter, err := db.Iterator(
|
|
scKeyPrefix,
|
|
makeKeyFromPrefix(prefixSeenCommit+1),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
scData := []toMigrate{}
|
|
for ; iter.Valid(); iter.Next() {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
k := iter.Key()
|
|
nk := make([]byte, len(k))
|
|
copy(nk, k)
|
|
|
|
if !bytes.HasPrefix(nk, scKeyPrefix) {
|
|
break
|
|
}
|
|
commit, err := makeToMigrate(iter.Value())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
scData = append(scData, toMigrate{
|
|
key: nk,
|
|
commit: commit,
|
|
})
|
|
}
|
|
if err := iter.Error(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := iter.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
return scData, nil
|
|
}
|
|
|
|
func deleteRecords(ctx context.Context, db dbm.DB, scData []toMigrate) error {
|
|
// delete all the remaining stale values in a single batch
|
|
batch := db.NewBatch()
|
|
|
|
for _, mg := range scData {
|
|
if err := batch.Delete(mg.key); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := batch.WriteSync(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := batch.Close(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Migrate(ctx context.Context, db dbm.DB) error {
|
|
scData, err := getAllSeenCommits(ctx, db)
|
|
if err != nil {
|
|
return fmt.Errorf("sourcing tasks to migrate: %w", err)
|
|
}
|
|
|
|
// sort earliest->latest commits.
|
|
sortMigrations(scData)
|
|
|
|
// trim the one we want to save:
|
|
scData = getMigrationsToDelete(scData)
|
|
|
|
if len(scData) <= 1 {
|
|
return nil
|
|
}
|
|
|
|
// write the migration (remove )
|
|
if err := deleteRecords(ctx, db, scData); err != nil {
|
|
return fmt.Errorf("writing data: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|