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 }