decred.org/dcrdex@v1.0.5/client/db/bolt/upgrades_test.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package bolt 5 6 import ( 7 "bytes" 8 "compress/gzip" 9 "encoding/hex" 10 "errors" 11 "fmt" 12 "io" 13 "os" 14 "path/filepath" 15 "strings" 16 "testing" 17 "time" 18 19 dexdb "decred.org/dcrdex/client/db" 20 "decred.org/dcrdex/dex/order" 21 "go.etcd.io/bbolt" 22 ) 23 24 var dbUpgradeTests = [...]struct { 25 name string 26 upgrade upgradefunc 27 verify func(*testing.T, *bbolt.DB) 28 filename string // in testdata directory 29 newVersion uint32 30 }{ 31 // {"testnetbot", v4Upgrade, verifyV4Upgrade, "dexbot-testnet.db.gz", 6}, // only for TestUpgradeDB, using just filename 32 {"upgradeFromV0", v1Upgrade, verifyV1Upgrade, "v0.db.gz", 1}, 33 {"upgradeFromV1", v2Upgrade, verifyV2Upgrade, "v1.db.gz", 2}, 34 {"upgradeFromV2", v3Upgrade, verifyV3Upgrade, "v2.db.gz", 3}, 35 {"upgradeFromV3", v4Upgrade, verifyV4Upgrade, "v3.db.gz", 4}, 36 {"upgradeFromV4", v5Upgrade, verifyV5Upgrade, "v4.db.gz", 5}, 37 {"upgradeFromV5", v6Upgrade, verifyV6Upgrade, "v5.db.gz", 6}, 38 } 39 40 func TestUpgrades(t *testing.T) { 41 upgradeLog = tLogger 42 t.Run("group", func(t *testing.T) { 43 for _, tc := range dbUpgradeTests { 44 tc := tc // capture range variable 45 t.Run(tc.name, func(t *testing.T) { 46 t.Parallel() 47 dbPath := unpack(t, tc.filename) 48 db, err := bbolt.Open(dbPath, 0600, 49 &bbolt.Options{Timeout: 1 * time.Second}) 50 if err != nil { 51 t.Fatal(err) 52 } 53 defer db.Close() 54 err = db.Update(func(dbtx *bbolt.Tx) error { 55 return doUpgrade(dbtx, tc.upgrade, tc.newVersion) 56 }) 57 if err != nil { 58 t.Fatalf("Upgrade %d -> %d failed: %v", tc.newVersion-1, tc.newVersion, err) 59 } 60 tc.verify(t, db) 61 }) 62 } 63 }) 64 } 65 66 func TestUpgradeDB(t *testing.T) { 67 runUpgrade := func(archiveName string) error { 68 dbPath := unpack(t, archiveName) 69 // NewDB runs upgradeDB. 70 dbi, err := NewDB(dbPath, tLogger) 71 if err != nil { 72 return fmt.Errorf("database initialization or upgrade error: %w", err) 73 } 74 db := dbi.(*BoltDB) 75 // Run upgradeDB again and it should be happy. 76 err = db.upgradeDB() 77 if err != nil { 78 return fmt.Errorf("upgradeDB error: %v", err) 79 } 80 newVersion, err := db.getVersion() 81 if err != nil { 82 return fmt.Errorf("getVersion error: %v", err) 83 } 84 if newVersion != DBVersion { 85 return fmt.Errorf("DB version not set. Expected %d, got %d", DBVersion, newVersion) 86 } 87 return nil 88 } 89 90 for _, tt := range dbUpgradeTests { 91 err := runUpgrade(tt.filename) 92 if err != nil { 93 t.Fatalf("upgrade error for version %d database: %v", tt.newVersion-1, err) 94 } 95 } 96 97 } 98 99 func verifyV1Upgrade(t *testing.T, db *bbolt.DB) { 100 t.Helper() 101 err := db.View(func(dbtx *bbolt.Tx) error { 102 return checkVersion(dbtx, 1) 103 }) 104 if err != nil { 105 t.Error(err) 106 } 107 } 108 109 func verifyV2Upgrade(t *testing.T, db *bbolt.DB) { 110 t.Helper() 111 maxFeeB := uint64Bytes(^uint64(0)) 112 ordersBucket := []byte("orders") 113 114 err := db.View(func(dbtx *bbolt.Tx) error { 115 err := checkVersion(dbtx, 2) 116 if err != nil { 117 return err 118 } 119 120 master := dbtx.Bucket(ordersBucket) 121 if master == nil { 122 return fmt.Errorf("orders bucket not found") 123 } 124 return master.ForEach(func(oid, _ []byte) error { 125 oBkt := master.Bucket(oid) 126 if oBkt == nil { 127 return fmt.Errorf("order %x bucket is not a bucket", oid) 128 } 129 if !bytes.Equal(oBkt.Get(maxFeeRateKey), maxFeeB) { 130 return fmt.Errorf("max fee not upgraded") 131 } 132 return nil 133 }) 134 }) 135 if err != nil { 136 t.Error(err) 137 } 138 } 139 140 // Nothing to really check here. Any errors would have come out during the 141 // upgrade process itself, since we just added a default nil field. 142 func verifyV3Upgrade(t *testing.T, db *bbolt.DB) { 143 t.Helper() 144 err := db.View(func(dbtx *bbolt.Tx) error { 145 return checkVersion(dbtx, 3) 146 }) 147 if err != nil { 148 t.Error(err) 149 } 150 } 151 152 func verifyV4Upgrade(t *testing.T, db *bbolt.DB) { 153 oldOrdersBucket := []byte("orders") 154 newActiveOrdersBucket := []byte("activeOrders") 155 err := db.View(func(dbtx *bbolt.Tx) error { 156 err := checkVersion(dbtx, 4) 157 if err != nil { 158 return err 159 } 160 // Ensure we have both old and new buckets. 161 archivedOrdersBkt := dbtx.Bucket(oldOrdersBucket) 162 if archivedOrdersBkt == nil { 163 return fmt.Errorf("archived orders bucket not found") 164 } 165 activeOrdersBkt := dbtx.Bucket(newActiveOrdersBucket) 166 if activeOrdersBkt == nil { 167 return fmt.Errorf("active orders bucket not found") 168 } 169 170 // Ensure the old bucket now only contains finished orders. 171 err = archivedOrdersBkt.ForEach(func(k, _ []byte) error { 172 archivedOBkt := archivedOrdersBkt.Bucket(k) 173 if archivedOBkt == nil { 174 return fmt.Errorf("order %x bucket is not a bucket", k) 175 } 176 status := order.OrderStatus(intCoder.Uint16(archivedOBkt.Get(statusKey))) 177 if status == order.OrderStatusUnknown { 178 fmt.Printf("Encountered order with unknown status: %x\n", k) 179 return nil 180 } 181 if status.IsActive() { 182 return fmt.Errorf("archived bucket has active order: %x", k) 183 } 184 return nil 185 }) 186 if err != nil { 187 return err 188 } 189 190 // Ensure the new bucket only contains active orders. 191 err = activeOrdersBkt.ForEach(func(k, _ []byte) error { 192 activeOBkt := activeOrdersBkt.Bucket(k) 193 if activeOBkt == nil { 194 return fmt.Errorf("order %x bucket is not a bucket", k) 195 } 196 status := order.OrderStatus(intCoder.Uint16(activeOBkt.Get(statusKey))) 197 if status == order.OrderStatusUnknown { 198 return fmt.Errorf("encountered order with unknown status: %x", k) 199 } 200 if !status.IsActive() { 201 return fmt.Errorf("active orders bucket has archived order: %x", k) 202 } 203 return nil 204 }) 205 if err != nil { 206 return err 207 } 208 return nil 209 }) 210 if err != nil { 211 t.Error(err) 212 } 213 } 214 215 // Ensure that the LegacyEncKey field is populated for the accounts in the DB. 216 func verifyV5Upgrade(t *testing.T, db *bbolt.DB) { 217 if err := db.View(func(tx *bbolt.Tx) error { 218 return checkVersion(tx, 5) 219 }); err != nil { 220 t.Error(err) 221 } 222 223 if err := db.View(func(tx *bbolt.Tx) error { 224 accts := tx.Bucket(accountsBucket) 225 c := accts.Cursor() 226 for acctKey, _ := c.First(); acctKey != nil; acctKey, _ = c.Next() { 227 acct := accts.Bucket(acctKey) 228 if acct == nil { 229 return fmt.Errorf("account bucket %s value not a nested bucket", string(acctKey)) 230 } 231 acctB := getCopy(acct, accountKey) 232 if acctB == nil { 233 return fmt.Errorf("empty account found for %s", string(acctKey)) 234 } 235 acctInfo, err := dexdb.DecodeAccountInfo(acctB) 236 if err != nil { 237 return err 238 } 239 if len(acctInfo.LegacyEncKey) == 0 { 240 return errors.New("LegacyEncKey not sets") 241 } 242 } 243 return nil 244 }); err != nil { 245 t.Error(err) 246 } 247 } 248 249 func verifyV6Upgrade(t *testing.T, db *bbolt.DB) { 250 verifyMatches := map[string]bool{ // matchid: active 251 "52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649": true, // status < MatchComplete, not revoked, not refunded 252 "81855a1e00167939cb6694d2c422acd208a0072939487f6999eb9d18a4478404": false, // status < MatchComplete, revoked at NewlyMatched 253 "5d87f3c67cf2367951baa2ff6cd471c483f15fb90badb37c5821b6d95526a41a": true, // status == MakerSwapCast, side Maker, revoked, requires refund 254 "9504680b4e7c8b763a1b1d49d4955c8486216325253fec738dd7a9e28bf92111": false, // status == TakerSwapCast, side Maker, revoked, refunded 255 "9c160f0702448615bbda08313f6a8eb668d20bf5059875921e668a5bdf2c7fc4": true, // status == MatchComplete, no RedeemSig !!! TODO: missing InitSig 256 "844592d2572bcd0668d2d6c52f5054e2d0836bf84c7174cb7476364cc3dbd968": false, // status == MatchComplete, RedeemSig set 257 "b0f7172ed85794bb358b0c3b525da1786f9fff094279db1944ebd7a19d0f7bba": false, // cancel order match 258 } 259 260 bdb := &BoltDB{DB: db} 261 err := bdb.matchesView(func(mb, amb *bbolt.Bucket) error { 262 // active matches 263 err := mb.ForEach(func(k, _ []byte) error { 264 matchID := hex.EncodeToString(k) 265 mBkt := mb.Bucket(k) 266 if mBkt == nil { 267 return fmt.Errorf("match %s bucket is not a bucket", matchID) 268 } 269 midB := getCopy(mBkt, matchIDKey) 270 if midB == nil { 271 return fmt.Errorf("nil match ID bytes") 272 } 273 mid := hex.EncodeToString(midB) 274 if active, found := verifyMatches[mid]; !found { 275 return fmt.Errorf("match %v not found in test DB", mid) 276 } else if !active { 277 return fmt.Errorf("inactive match found in active matches bucket: %v", mid) 278 } 279 return nil 280 }) 281 if err != nil { 282 return err 283 } 284 285 // archived 286 return amb.ForEach(func(k, _ []byte) error { 287 matchID := hex.EncodeToString(k) 288 mBkt := amb.Bucket(k) 289 if mBkt == nil { 290 return fmt.Errorf("match %s bucket is not a bucket", matchID) 291 } 292 midB := getCopy(mBkt, matchIDKey) 293 if midB == nil { 294 return fmt.Errorf("nil match ID bytes") 295 } 296 mid := hex.EncodeToString(midB) 297 if active, found := verifyMatches[mid]; !found { 298 return fmt.Errorf("match %v not found in test DB", mid) 299 } else if active { 300 return fmt.Errorf("active match found in archived matches bucket: %v", mid) 301 } 302 return nil 303 }) 304 }) 305 if err != nil { 306 t.Error(err) 307 } 308 } 309 310 func checkVersion(dbtx *bbolt.Tx, expectedVersion uint32) error { 311 bkt := dbtx.Bucket(appBucket) 312 if bkt == nil { 313 return fmt.Errorf("appBucket not found") 314 } 315 versionB := bkt.Get(versionKey) 316 if versionB == nil { 317 return fmt.Errorf("expected a non-nil version value") 318 } 319 version := intCoder.Uint32(versionB) 320 if version != expectedVersion { 321 return fmt.Errorf("expected db version %d, got %d", 322 expectedVersion, version) 323 } 324 return nil 325 } 326 327 func unpack(t *testing.T, db string) string { 328 t.Helper() 329 d := t.TempDir() 330 331 t.Helper() 332 archive, err := os.Open(filepath.Join("testdata", db)) 333 if err != nil { 334 t.Fatal(err) 335 } 336 337 r, err := gzip.NewReader(archive) 338 if err != nil { 339 t.Fatal(err) 340 } 341 dbPath := filepath.Join(d, strings.TrimSuffix(db, ".gz")) 342 dbFile, err := os.Create(dbPath) 343 if err != nil { 344 t.Fatal(err) 345 } 346 _, err = io.Copy(dbFile, r) 347 archive.Close() 348 dbFile.Close() 349 if err != nil { 350 t.Fatal(err) 351 } 352 return dbPath 353 }