github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/scripts/keymigrate/migrate.go (about) 1 // Package keymigrate translates all legacy formatted keys to their 2 // new components. 3 // 4 // The key migration operation as implemented provides a potential 5 // model for database migration operations. Crucially, the migration 6 // as implemented does not depend on any tendermint code. 7 package keymigrate 8 9 import ( 10 "bytes" 11 "context" 12 "encoding/binary" 13 "encoding/hex" 14 "fmt" 15 "math/rand" 16 "runtime" 17 "strconv" 18 19 "github.com/creachadair/taskgroup" 20 "github.com/google/orderedcode" 21 dbm "github.com/tendermint/tm-db" 22 ) 23 24 type ( 25 keyID []byte 26 migrateFunc func(keyID) (keyID, error) 27 ) 28 29 func getAllLegacyKeys(db dbm.DB) ([]keyID, error) { 30 var out []keyID 31 32 iter, err := db.Iterator(nil, nil) 33 if err != nil { 34 return nil, err 35 } 36 37 for ; iter.Valid(); iter.Next() { 38 k := iter.Key() 39 40 // make sure it's a key with a legacy format, and skip 41 // all other keys, to make it safe to resume the migration. 42 if !checkKeyType(k).isLegacy() { 43 continue 44 } 45 46 // Make an explicit copy, since not all tm-db backends do. 47 out = append(out, []byte(string(k))) 48 } 49 50 if err = iter.Error(); err != nil { 51 return nil, err 52 } 53 54 if err = iter.Close(); err != nil { 55 return nil, err 56 } 57 58 return out, nil 59 } 60 61 // keyType is an enumeration for the structural type of a key. 62 type keyType int 63 64 func (t keyType) isLegacy() bool { return t != nonLegacyKey } 65 66 const ( 67 nonLegacyKey keyType = iota // non-legacy key (presumed already converted) 68 consensusParamsKey 69 abciResponsesKey 70 validatorsKey 71 stateStoreKey // state storage record 72 blockMetaKey // H: 73 blockPartKey // P: 74 commitKey // C: 75 seenCommitKey // SC: 76 blockHashKey // BH: 77 lightSizeKey // size 78 lightBlockKey // lb/ 79 evidenceCommittedKey // \x00 80 evidencePendingKey // \x01 81 txHeightKey // tx.height/... (special case) 82 abciEventKey // name/value/height/index 83 txHashKey // 32-byte transaction hash (unprefixed) 84 ) 85 86 var prefixes = []struct { 87 prefix []byte 88 ktype keyType 89 check func(keyID) bool 90 }{ 91 {[]byte("consensusParamsKey:"), consensusParamsKey, nil}, 92 {[]byte("abciResponsesKey:"), abciResponsesKey, nil}, 93 {[]byte("validatorsKey:"), validatorsKey, nil}, 94 {[]byte("stateKey"), stateStoreKey, nil}, 95 {[]byte("H:"), blockMetaKey, nil}, 96 {[]byte("P:"), blockPartKey, nil}, 97 {[]byte("C:"), commitKey, nil}, 98 {[]byte("SC:"), seenCommitKey, nil}, 99 {[]byte("BH:"), blockHashKey, nil}, 100 {[]byte("size"), lightSizeKey, nil}, 101 {[]byte("lb/"), lightBlockKey, nil}, 102 {[]byte("\x00"), evidenceCommittedKey, checkEvidenceKey}, 103 {[]byte("\x01"), evidencePendingKey, checkEvidenceKey}, 104 } 105 106 // checkKeyType classifies a candidate key based on its structure. 107 func checkKeyType(key keyID) keyType { 108 for _, p := range prefixes { 109 if bytes.HasPrefix(key, p.prefix) { 110 if p.check == nil || p.check(key) { 111 return p.ktype 112 } 113 } 114 } 115 116 // A legacy event key has the form: 117 // 118 // <name> / <value> / <height> / <index> 119 // 120 // Transaction hashes are stored as a raw binary hash with no prefix. 121 // 122 // Because a hash can contain any byte, it is possible (though unlikely) 123 // that a hash could have the correct form for an event key, in which case 124 // we would translate it incorrectly. To reduce the likelihood of an 125 // incorrect interpretation, we parse candidate event keys and check for 126 // some structural properties before making a decision. 127 // 128 // Note, though, that nothing prevents event names or values from containing 129 // additional "/" separators, so the parse has to be forgiving. 130 parts := bytes.Split(key, []byte("/")) 131 if len(parts) >= 4 { 132 // Special case for tx.height. 133 if len(parts) == 4 && bytes.Equal(parts[0], []byte("tx.height")) { 134 return txHeightKey 135 } 136 137 // The name cannot be empty, but we don't know where the name ends and 138 // the value begins, so insist that there be something. 139 var n int 140 for _, part := range parts[:len(parts)-2] { 141 n += len(part) 142 } 143 // Check whether the last two fields could be .../height/index. 144 if n > 0 && isDecimal(parts[len(parts)-1]) && isDecimal(parts[len(parts)-2]) { 145 return abciEventKey 146 } 147 } 148 149 // If we get here, it's not an event key. Treat it as a hash if it is the 150 // right length. Note that it IS possible this could collide with the 151 // translation of some other key (though not a hash, since encoded hashes 152 // will be longer). The chance of that is small, but there is nothing we can 153 // do to detect it. 154 if len(key) == 32 { 155 return txHashKey 156 } 157 return nonLegacyKey 158 } 159 160 // isDecimal reports whether buf is a non-empty sequence of Unicode decimal 161 // digits. 162 func isDecimal(buf []byte) bool { 163 for _, c := range buf { 164 if c < '0' || c > '9' { 165 return false 166 } 167 } 168 return len(buf) != 0 169 } 170 171 func migrateKey(key keyID) (keyID, error) { 172 switch checkKeyType(key) { 173 case blockMetaKey: 174 val, err := strconv.Atoi(string(key[2:])) 175 if err != nil { 176 return nil, err 177 } 178 179 return orderedcode.Append(nil, int64(0), int64(val)) 180 case blockPartKey: 181 parts := bytes.Split(key[2:], []byte(":")) 182 if len(parts) != 2 { 183 return nil, fmt.Errorf("block parts key has %d rather than 2 components", 184 len(parts)) 185 } 186 valOne, err := strconv.Atoi(string(parts[0])) 187 if err != nil { 188 return nil, err 189 } 190 191 valTwo, err := strconv.Atoi(string(parts[1])) 192 if err != nil { 193 return nil, err 194 } 195 196 return orderedcode.Append(nil, int64(1), int64(valOne), int64(valTwo)) 197 case commitKey: 198 val, err := strconv.Atoi(string(key[2:])) 199 if err != nil { 200 return nil, err 201 } 202 203 return orderedcode.Append(nil, int64(2), int64(val)) 204 case seenCommitKey: 205 val, err := strconv.Atoi(string(key[3:])) 206 if err != nil { 207 return nil, err 208 } 209 210 return orderedcode.Append(nil, int64(3), int64(val)) 211 case blockHashKey: 212 hash := string(key[3:]) 213 if len(hash)%2 == 1 { 214 hash = "0" + hash 215 } 216 val, err := hex.DecodeString(hash) 217 if err != nil { 218 return nil, err 219 } 220 221 return orderedcode.Append(nil, int64(4), string(val)) 222 case validatorsKey: 223 val, err := strconv.Atoi(string(key[14:])) 224 if err != nil { 225 return nil, err 226 } 227 228 return orderedcode.Append(nil, int64(5), int64(val)) 229 case consensusParamsKey: 230 val, err := strconv.Atoi(string(key[19:])) 231 if err != nil { 232 return nil, err 233 } 234 235 return orderedcode.Append(nil, int64(6), int64(val)) 236 case abciResponsesKey: 237 val, err := strconv.Atoi(string(key[17:])) 238 if err != nil { 239 return nil, err 240 } 241 242 return orderedcode.Append(nil, int64(7), int64(val)) 243 case stateStoreKey: 244 return orderedcode.Append(nil, int64(8)) 245 case evidenceCommittedKey: 246 return convertEvidence(key, 9) 247 case evidencePendingKey: 248 return convertEvidence(key, 10) 249 case lightBlockKey: 250 if len(key) < 24 { 251 return nil, fmt.Errorf("light block evidence %q in invalid format", string(key)) 252 } 253 254 val, err := strconv.Atoi(string(key[len(key)-20:])) 255 if err != nil { 256 return nil, err 257 } 258 259 return orderedcode.Append(nil, int64(11), int64(val)) 260 case lightSizeKey: 261 return orderedcode.Append(nil, int64(12)) 262 case txHeightKey: 263 parts := bytes.Split(key, []byte("/")) 264 if len(parts) != 4 { 265 return nil, fmt.Errorf("key has %d parts rather than 4", len(parts)) 266 } 267 parts = parts[1:] // drop prefix 268 269 elems := make([]interface{}, 0, len(parts)+1) 270 elems = append(elems, "tx.height") 271 272 for idx, pt := range parts { 273 val, err := strconv.Atoi(string(pt)) 274 if err != nil { 275 return nil, err 276 } 277 if idx == 0 { 278 elems = append(elems, fmt.Sprintf("%d", val)) 279 } else { 280 elems = append(elems, int64(val)) 281 } 282 } 283 284 return orderedcode.Append(nil, elems...) 285 case abciEventKey: 286 parts := bytes.Split(key, []byte("/")) 287 288 elems := make([]interface{}, 0, 4) 289 if len(parts) == 4 { 290 elems = append(elems, string(parts[0]), string(parts[1])) 291 292 val, err := strconv.Atoi(string(parts[2])) 293 if err != nil { 294 return nil, err 295 } 296 elems = append(elems, int64(val)) 297 298 val2, err := strconv.Atoi(string(parts[3])) 299 if err != nil { 300 return nil, err 301 } 302 elems = append(elems, int64(val2)) 303 } else { 304 elems = append(elems, string(parts[0])) 305 parts = parts[1:] 306 307 val, err := strconv.Atoi(string(parts[len(parts)-1])) 308 if err != nil { 309 return nil, err 310 } 311 312 val2, err := strconv.Atoi(string(parts[len(parts)-2])) 313 if err != nil { 314 return nil, err 315 } 316 317 appKey := bytes.Join(parts[:len(parts)-3], []byte("/")) 318 elems = append(elems, string(appKey), int64(val), int64(val2)) 319 } 320 return orderedcode.Append(nil, elems...) 321 case txHashKey: 322 return orderedcode.Append(nil, "tx.hash", string(key)) 323 default: 324 return nil, fmt.Errorf("key %q is in the wrong format", string(key)) 325 } 326 } 327 328 func convertEvidence(key keyID, newPrefix int64) ([]byte, error) { 329 parts := bytes.Split(key[1:], []byte("/")) 330 if len(parts) != 2 { 331 return nil, fmt.Errorf("evidence key is malformed with %d parts not 2", 332 len(parts)) 333 } 334 335 hb, err := hex.DecodeString(string(parts[0])) 336 if err != nil { 337 return nil, err 338 } 339 340 evidenceHash, err := hex.DecodeString(string(parts[1])) 341 if err != nil { 342 return nil, err 343 } 344 345 return orderedcode.Append(nil, newPrefix, binary.BigEndian.Uint64(hb), string(evidenceHash)) 346 } 347 348 // checkEvidenceKey reports whether a candidate key with one of the legacy 349 // evidence prefixes has the correct structure for a legacy evidence key. 350 // 351 // This check is needed because transaction hashes are stored without a prefix, 352 // so checking the one-byte prefix alone is not enough to distinguish them. 353 // Legacy evidence keys are suffixed with a string of the format: 354 // 355 // "%0.16X/%X" 356 // 357 // where the first element is the height and the second is the hash. Thus, we 358 // check 359 func checkEvidenceKey(key keyID) bool { 360 parts := bytes.SplitN(key[1:], []byte("/"), 2) 361 if len(parts) != 2 || len(parts[0]) != 16 || !isHex(parts[0]) || !isHex(parts[1]) { 362 return false 363 } 364 return true 365 } 366 367 func isHex(data []byte) bool { 368 for _, b := range data { 369 if ('0' <= b && b <= '9') || ('a' <= b && b <= 'f') || ('A' <= b && b <= 'F') { 370 continue 371 } 372 return false 373 } 374 return len(data) != 0 375 } 376 377 func replaceKey(db dbm.DB, key keyID, gooseFn migrateFunc) error { 378 exists, err := db.Has(key) 379 if err != nil { 380 return err 381 } 382 if !exists { 383 return nil 384 } 385 386 newKey, err := gooseFn(key) 387 if err != nil { 388 return err 389 } 390 391 val, err := db.Get(key) 392 if err != nil { 393 return err 394 } 395 396 batch := db.NewBatch() 397 398 if err = batch.Set(newKey, val); err != nil { 399 return err 400 } 401 if err = batch.Delete(key); err != nil { 402 return err 403 } 404 405 // 10% of the time, force a write to disk, but mostly don't, 406 // because it's faster. 407 if rand.Intn(100)%10 == 0 { // nolint:gosec 408 if err = batch.WriteSync(); err != nil { 409 return err 410 } 411 } else { 412 if err = batch.Write(); err != nil { 413 return err 414 } 415 } 416 417 if err = batch.Close(); err != nil { 418 return err 419 } 420 421 return nil 422 } 423 424 // Migrate converts all legacy key formats to new key formats. The 425 // operation is idempotent, so it's safe to resume a failed 426 // operation. The operation is somewhat parallelized, relying on the 427 // concurrency safety of the underlying databases. 428 // 429 // Migrate has "continue on error" semantics and will iterate through 430 // all legacy keys attempt to migrate them, and will collect all 431 // errors and will return only at the end of the operation. 432 // 433 // The context allows for a safe termination of the operation 434 // (e.g connected to a singal handler,) to abort the operation 435 // in-between migration operations. 436 func Migrate(ctx context.Context, db dbm.DB) error { 437 keys, err := getAllLegacyKeys(db) 438 if err != nil { 439 return err 440 } 441 442 var errs []string 443 g, start := taskgroup.New(func(err error) error { 444 errs = append(errs, err.Error()) 445 return err 446 }).Limit(runtime.NumCPU()) 447 448 for _, key := range keys { 449 key := key 450 start(func() error { 451 if err := ctx.Err(); err != nil { 452 return err 453 } 454 return replaceKey(db, key, migrateKey) 455 }) 456 } 457 if g.Wait() != nil { 458 return fmt.Errorf("encountered errors during migration: %q", errs) 459 } 460 return nil 461 }