decred.org/dcrwallet/v3@v3.1.0/wallet/udb/upgrades_test.go (about) 1 // Copyright (c) 2017 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package udb 6 7 import ( 8 "bytes" 9 "compress/gzip" 10 "context" 11 "encoding/hex" 12 "fmt" 13 "io" 14 "os" 15 "path/filepath" 16 "testing" 17 18 _ "decred.org/dcrwallet/v3/wallet/drivers/bdb" 19 "decred.org/dcrwallet/v3/wallet/walletdb" 20 "github.com/decred/dcrd/chaincfg/chainhash" 21 "github.com/decred/dcrd/chaincfg/v3" 22 "github.com/decred/dcrd/dcrutil/v4" 23 "github.com/decred/dcrd/wire" 24 ) 25 26 var dbUpgradeTests = [...]struct { 27 verify func(context.Context, *testing.T, walletdb.DB) 28 filename string // in testdata directory 29 }{ 30 {verifyV2Upgrade, "v1.db.gz"}, 31 {verifyV3Upgrade, "v2.db.gz"}, 32 {verifyV4Upgrade, "v3.db.gz"}, 33 {verifyV5Upgrade, "v4.db.gz"}, 34 {verifyV6Upgrade, "v5.db.gz"}, 35 // No upgrade test for V7, it is a backwards-compatible upgrade 36 {verifyV8Upgrade, "v7.db.gz"}, 37 // No upgrade test for V9, it is a fix for V8 and the previous test still applies 38 // TODO: V10 upgrade test 39 {verifyV12Upgrade, "v11.db.gz"}, 40 // TODO: V13-24 tests 41 {verifyV25Upgrade, "v24.db.gz"}, 42 } 43 44 var pubPass = []byte("public") 45 46 func TestUpgrades(t *testing.T) { 47 ctx := context.Background() 48 d, err := os.MkdirTemp("", "dcrwallet_udb_TestUpgrades") 49 if err != nil { 50 t.Fatal(err) 51 } 52 53 t.Run("group", func(t *testing.T) { 54 for i, test := range dbUpgradeTests { 55 test := test 56 name := fmt.Sprintf("test%d", i) 57 t.Run(name, func(t *testing.T) { 58 t.Parallel() 59 testFile, err := os.Open(filepath.Join("testdata", test.filename)) 60 if err != nil { 61 t.Fatal(err) 62 } 63 defer testFile.Close() 64 r, err := gzip.NewReader(testFile) 65 if err != nil { 66 t.Fatal(err) 67 } 68 dbPath := filepath.Join(d, name+".db") 69 fi, err := os.Create(dbPath) 70 if err != nil { 71 t.Fatal(err) 72 } 73 _, err = io.Copy(fi, r) 74 fi.Close() 75 if err != nil { 76 t.Fatal(err) 77 } 78 db, err := walletdb.Open("bdb", dbPath) 79 if err != nil { 80 t.Fatal(err) 81 } 82 defer db.Close() 83 err = Upgrade(ctx, db, pubPass, chaincfg.TestNet3Params()) 84 if err != nil { 85 t.Fatalf("Upgrade failed: %v", err) 86 } 87 test.verify(ctx, t, db) 88 }) 89 } 90 }) 91 92 os.RemoveAll(d) 93 } 94 95 func verifyV2Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { 96 amgr, _, _, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPass) 97 if err != nil { 98 t.Fatalf("Open after Upgrade failed: %v", err) 99 } 100 101 err = walletdb.View(ctx, db, func(tx walletdb.ReadTx) error { 102 ns := tx.ReadBucket(waddrmgrBucketKey) 103 nsMetaBucket := ns.NestedReadBucket(metaBucketName) 104 105 accounts := []struct { 106 totalAddrs uint32 107 lastUsed uint32 108 }{ 109 {^uint32(0), ^uint32(0)}, 110 {20, 18}, 111 {20, 19}, 112 {20, 19}, 113 {30, 25}, 114 {30, 29}, 115 {30, 29}, 116 {200, 185}, 117 {200, 199}, 118 } 119 120 switch lastAccount, err := fetchLastAccount(ns); { 121 case err != nil: 122 t.Errorf("fetchLastAccount: %v", err) 123 case lastAccount != uint32(len(accounts)-1): 124 t.Errorf("Number of BIP0044 accounts got %v want %v", 125 lastAccount+1, uint32(len(accounts))) 126 } 127 128 for i, a := range accounts { 129 account := uint32(i) 130 131 if nsMetaBucket.Get(accountNumberToAddrPoolKey(false, account)) != nil { 132 t.Errorf("Account %v external address pool bucket still exists", account) 133 } 134 if nsMetaBucket.Get(accountNumberToAddrPoolKey(true, account)) != nil { 135 t.Errorf("Account %v external address pool bucket still exists", account) 136 } 137 138 props, err := amgr.AccountProperties(ns, account) 139 if err != nil { 140 t.Errorf("AccountProperties: %v", err) 141 continue 142 } 143 if props.LastUsedExternalIndex != a.lastUsed { 144 t.Errorf("Account %v last used ext index got %v want %v", 145 account, props.LastUsedExternalIndex, a.lastUsed) 146 } 147 if props.LastUsedInternalIndex != a.lastUsed { 148 t.Errorf("Account %v last used int index got %v want %v", 149 account, props.LastUsedInternalIndex, a.lastUsed) 150 } 151 } 152 153 if ns.NestedReadBucket(usedAddrBucketName) != nil { 154 t.Error("Used address bucket still exists") 155 } 156 157 return nil 158 }) 159 if err != nil { 160 t.Error(err) 161 } 162 } 163 164 func verifyV3Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { 165 _, _, smgr, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPass) 166 if err != nil { 167 t.Fatalf("Open after Upgrade failed: %v", err) 168 } 169 170 err = walletdb.View(ctx, db, func(tx walletdb.ReadTx) error { 171 ns := tx.ReadBucket(wstakemgrBucketKey) 172 173 const ( 174 ticketHashStr = "4516ef1d548f3284c1a27b3e706c4677392031df7071ad2022050af376837033" 175 votingAddrStr = "Tcu5oEdEp1W93fRT9FGSwMin7LonfRjNYe4" 176 ticketPurchaseHex = "01000000024bf0a303a7e6d174833d9eb761815b61f8ba8c6fa8852a6bf51c703daefc0ef60400000000ffffffff4bf0a303a7e6d174833d9eb761815b61f8ba8c6fa8852a6bf51c703daefc0ef60500000000ffffffff056f78d37a00000000000018baa914ec97b165a5f028b50fb12ae717c5f6c1b9057b5f8700000000000000000000206a1e7f686bc0e548bbb92f487db6da070e43a34117288ed59100000000000058000000000000000000001abd76a914000000000000000000000000000000000000000088ac00000000000000000000206a1e9d8e8bdc618035be32a14ab752af2e331f9abf3651074a7a000000000058000000000000000000001abd76a914000000000000000000000000000000000000000088ac00000000ad480000028ed59100000000009c480000010000006b483045022100c240bdd6a656c20e9035b839fc91faae6c766772f76149adb91a1fdcf20faf9c02203d68038b83263293f864b173c8f3f00e4371b67bf36fb9ec9f5132bdf68d2858012102adc226dec4de09a18c5a522f8f00917fb6d4eb2361a105218ac3f87d802ae3d451074a7a000000009c480000010000006a47304402205af53185f2662a30a22014b0d19760c1bfde8ec8f065b19cacab6a7abcec76a202204a2614cfcb4db3fc1c86eb0b1ca577f9039ec6db29e9c44ddcca2fe6e3c8bd5d012102adc226dec4de09a18c5a522f8f00917fb6d4eb2361a105218ac3f87d802ae3d4" 177 178 // Stored timestamp uses time.Now(). The generated database test 179 // artifact uses this time (2017-04-10 11:50:04 -0400 EDT). If the 180 // db is ever regenerated, this expected value be updated as well. 181 timeStamp = 1491839404 182 ) 183 184 // Verify ticket purchase is still present with correct info, and no 185 // vote bits. 186 ticketPurchaseHash, err := chainhash.NewHashFromStr(ticketHashStr) 187 if err != nil { 188 return err 189 } 190 rec, err := fetchSStxRecord(ns, ticketPurchaseHash, 3) 191 if err != nil { 192 return err 193 } 194 if rec.voteBitsSet || rec.voteBits != 0 || rec.voteBitsExt != nil { 195 t.Errorf("Ticket purchase record still has vote bits") 196 } 197 votingAddr, err := smgr.SStxAddress(ns, ticketPurchaseHash) 198 if err != nil { 199 return err 200 } 201 if votingAddr.String() != votingAddrStr { 202 t.Errorf("Unexpected voting address, got %v want %v", 203 votingAddr.String(), votingAddrStr) 204 } 205 if rec.ts.Unix() != timeStamp { 206 t.Errorf("Unexpected timestamp, got %v want %v", rec.ts.Unix(), timeStamp) 207 } 208 var buf bytes.Buffer 209 err = rec.tx.MsgTx().Serialize(&buf) 210 if err != nil { 211 return err 212 } 213 expectedBytes, err := hex.DecodeString(ticketPurchaseHex) 214 if err != nil { 215 return err 216 } 217 if !bytes.Equal(buf.Bytes(), expectedBytes) { 218 t.Errorf("Serialized transaction does not match expected") 219 } 220 221 // Verify that the agenda preferences bucket was created. 222 if tx.ReadBucket(agendaPreferences.defaultBucketKey()) == nil { 223 t.Errorf("Agenda preferences bucket was not created") 224 } 225 226 return nil 227 }) 228 if err != nil { 229 t.Error(err) 230 } 231 } 232 233 func verifyV4Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { 234 err := walletdb.View(ctx, db, func(tx walletdb.ReadTx) error { 235 ns := tx.ReadBucket(waddrmgrBucketKey) 236 mainBucket := ns.NestedReadBucket(mainBucketName) 237 if mainBucket.Get(seedName) != nil { 238 t.Errorf("Seed was not deleted") 239 } 240 return nil 241 }) 242 if err != nil { 243 t.Error(err) 244 } 245 } 246 247 func verifyV5Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { 248 err := walletdb.View(ctx, db, func(tx walletdb.ReadTx) error { 249 ns := tx.ReadBucket(waddrmgrBucketKey) 250 251 data := []struct { 252 acct uint32 253 lastUsedExtChild uint32 254 lastUsedIntChild uint32 255 }{ 256 {0, ^uint32(0), ^uint32(0)}, 257 {1, 0, 0}, 258 {2, 9, 9}, 259 {3, 5, 15}, 260 {4, 19, 20}, 261 {5, 20, 19}, 262 {6, 29, 30}, 263 {7, 30, 29}, 264 {8, 1<<31 - 1, 1<<31 - 1}, 265 {ImportedAddrAccount, 0, 0}, 266 } 267 268 const dbVersion = 5 269 270 for _, d := range data { 271 acct, err := fetchDBAccount(ns, d.acct, dbVersion) 272 if err != nil { 273 return err 274 } 275 a, ok := acct.(*dbBIP0044Account) 276 if !ok { 277 return fmt.Errorf("unknown account type %T", acct) 278 } 279 if a.lastUsedExternalIndex != d.lastUsedExtChild { 280 t.Errorf("Account %d last used ext child mismatch %d != %d", 281 d.acct, a.lastUsedExternalIndex, d.lastUsedExtChild) 282 } 283 if a.lastReturnedExternalIndex != d.lastUsedExtChild { 284 t.Errorf("Account %d last returned ext child mismatch %d != %d", 285 d.acct, a.lastReturnedExternalIndex, d.lastUsedExtChild) 286 } 287 if a.lastUsedInternalIndex != d.lastUsedIntChild { 288 t.Errorf("Account %d last used int child mismatch %d != %d", 289 d.acct, a.lastUsedInternalIndex, d.lastUsedIntChild) 290 } 291 if a.lastReturnedInternalIndex != d.lastUsedIntChild { 292 t.Errorf("Account %d last returned int child mismatch %d != %d", 293 d.acct, a.lastReturnedInternalIndex, d.lastUsedIntChild) 294 } 295 } 296 297 return nil 298 }) 299 if err != nil { 300 t.Error(err) 301 } 302 } 303 304 func verifyV6Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { 305 err := walletdb.View(ctx, db, func(tx walletdb.ReadTx) error { 306 ns := tx.ReadBucket(wtxmgrBucketKey) 307 308 data := []*chainhash.Hash{ 309 decodeHash("7bc19eb0bf3a57be73d6879b6c411404b14b0156353dd47c5e0456768704bfd1"), 310 decodeHash("a6abeb0127c347b5f38ebc2401134b324612d5b1ad9a9b8bdf6a91521842b7b1"), 311 decodeHash("1107757fa4f238803192c617c7b60bf35bdc57bc0fc94b408c71239ff9eaeb98"), 312 decodeHash("3fd00cda28c4d148e0cd38e1d646ba1365116b3ddd9a49aca4483bef80513ff9"), 313 decodeHash("f4bdebefaa174470182960046fa53f554108b8ea09a86de5306a14c3a0124566"), 314 decodeHash("bca8c2649860585f10b27d774b354ea7b80007e9ad79c090ea05596d63995cf5"), 315 } 316 317 c := ns.NestedReadBucket(bucketTickets).ReadCursor() 318 found := 0 319 for k, v := c.First(); k != nil; k, v = c.Next() { 320 var hash chainhash.Hash 321 copy(hash[:], k) 322 var foundHash *chainhash.Hash 323 for _, foundHash = range data { 324 if hash == *foundHash { 325 goto Found 326 } 327 } 328 t.Errorf("tickets bucket records %v as a ticket", &hash) 329 continue 330 Found: 331 found++ 332 if extractRawTicketPickedHeight(v) != -1 { 333 t.Errorf("ticket purchase %v was not set with picked height -1", foundHash) 334 } 335 } 336 if found != len(data) { 337 t.Errorf("missing ticket purchase transactions from tickets bucket") 338 } 339 340 // Ensure that the stakebase input recorded for an unmined vote was 341 // removed. 342 stakebaseKey := canonicalOutPoint(&chainhash.Hash{}, ^uint32(0)) 343 if ns.NestedReadBucket(bucketUnminedInputs).Get(stakebaseKey) != nil { 344 t.Errorf("stakebase input for unmined vote was not removed") 345 } 346 347 return nil 348 }) 349 if err != nil { 350 t.Error(err) 351 } 352 } 353 354 func verifyV8Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { 355 err := walletdb.View(ctx, db, func(tx walletdb.ReadTx) error { 356 ns := tx.ReadBucket(wtxmgrBucketKey) 357 creditBucket := ns.NestedReadBucket(bucketCredits) 358 err := creditBucket.ForEach(func(k []byte, v []byte) error { 359 hasExpiry := fetchRawCreditHasExpiry(v, DBVersion) 360 if !hasExpiry { 361 t.Errorf("expected expiry to be set") 362 } 363 return nil 364 }) 365 if err != nil { 366 t.Error(err) 367 } 368 369 unminedCreditBucket := ns.NestedReadBucket(bucketUnminedCredits) 370 err = unminedCreditBucket.ForEach(func(k []byte, v []byte) error { 371 hasExpiry := fetchRawCreditHasExpiry(v, DBVersion) 372 373 if !hasExpiry { 374 t.Errorf("expected expiry to be set") 375 } 376 return nil 377 }) 378 if err != nil { 379 t.Error(err) 380 } 381 382 txBucket := ns.NestedReadBucket(bucketTxRecords) 383 minedTxWithExpiryCount := 0 384 minedTxWithoutExpiryCount := 0 385 err = txBucket.ForEach(func(k []byte, v []byte) error { 386 var txHash chainhash.Hash 387 var rec TxRecord 388 err := readRawTxRecordHash(k, &txHash) 389 if err != nil { 390 t.Error(err) 391 } 392 err = readRawTxRecord(&txHash, v, &rec) 393 if err != nil { 394 t.Error(err) 395 } 396 397 if rec.MsgTx.Expiry != wire.NoExpiryValue { 398 minedTxWithExpiryCount++ 399 } else { 400 minedTxWithoutExpiryCount++ 401 } 402 return nil 403 }) 404 if err != nil { 405 t.Error(err) 406 } 407 408 if minedTxWithExpiryCount != 3 { 409 t.Errorf("expected 3 txs with expiries set, got %d", minedTxWithExpiryCount) 410 } 411 if minedTxWithoutExpiryCount != 3 { 412 t.Errorf("expected 3 txs without expiries set, got %d", minedTxWithoutExpiryCount) 413 } 414 return err 415 }) 416 if err != nil { 417 t.Error(err) 418 } 419 } 420 421 // verifyV12Upgrade tests whether the upgrade to the v12 database was 422 // successful, using the v11 test database. 423 // 424 // See the v11.db.go file for an explanation of the database layout and test 425 // plan. 426 func verifyV12Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { 427 _, txmgr, _, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPass) 428 if err != nil { 429 t.Fatalf("Open after Upgrade failed: %v", err) 430 } 431 432 err = walletdb.View(ctx, db, func(tx walletdb.ReadTx) error { 433 txmgrns := tx.ReadBucket(wtxmgrBucketKey) 434 435 if b := txmgrns.NestedReadBucket(bucketTicketCommitments); b == nil { 436 t.Fatalf("upgrade should have created bucketTicketCommitments") 437 } 438 439 if b := txmgrns.NestedReadBucket(bucketTicketCommitmentsUsp); b == nil { 440 t.Fatalf("upgrade should have created bucketTicketCommitmentsUsp") 441 } 442 443 balances, err := txmgr.AccountBalances(tx, 0) 444 if err != nil { 445 t.Fatal(err) 446 } 447 448 expectedBalances := []struct { 449 acct uint32 450 spendable dcrutil.Amount 451 votingAuth dcrutil.Amount 452 total dcrutil.Amount 453 unconfirmed dcrutil.Amount 454 locked dcrutil.Amount 455 immatureStakeGen dcrutil.Amount 456 empty bool 457 }{ 458 // unmined ticket 459 {acct: 1, votingAuth: 1100}, 460 {acct: 2, locked: 1000, total: 1000}, 461 462 // mined ticket 463 {acct: 3, votingAuth: 1100}, 464 {acct: 4, locked: 1000, total: 1000}, 465 466 // mined ticket + unmined vote 467 {acct: 5, empty: true}, 468 {acct: 6, total: 1300, immatureStakeGen: 1300}, 469 470 // mined ticket + mined vote 471 {acct: 7, empty: true}, 472 {acct: 8, total: 1300, immatureStakeGen: 1300}, 473 474 // mined ticket + unmined revocation 475 {acct: 9, empty: true}, 476 {acct: 10, total: 700, immatureStakeGen: 700}, 477 478 // mined ticket + mined revocation 479 {acct: 11, empty: true}, 480 {acct: 12, total: 700, immatureStakeGen: 700}, 481 } 482 483 testFunc := func(testIdx int) func(t *testing.T) { 484 return func(t *testing.T) { 485 expected := expectedBalances[testIdx] 486 actual, has := balances[expected.acct] 487 488 if expected.empty { 489 if !has { 490 // this account was actually supposed to be empty 491 return 492 } 493 t.Fatalf("Balance should have been empty") 494 } 495 if !has { 496 t.Fatalf("Database does not have balance for expected account") 497 } 498 499 if actual.Spendable != expected.spendable { 500 t.Errorf("Actual spendable (%d) different than expected (%d)", 501 actual.Spendable, expected.spendable) 502 } 503 if actual.Unconfirmed != expected.unconfirmed { 504 t.Errorf("Actual unconfirmed (%d) different than expected (%d)", 505 actual.Unconfirmed, expected.unconfirmed) 506 } 507 if actual.LockedByTickets != expected.locked { 508 t.Errorf("Actual locked by tickets (%d) different than expected (%d)", 509 actual.LockedByTickets, expected.locked) 510 } 511 if actual.ImmatureStakeGeneration != expected.immatureStakeGen { 512 t.Errorf("Actual immature stake gen (%d) different than expected (%d)", 513 actual.ImmatureStakeGeneration, expected.immatureStakeGen) 514 } 515 if actual.VotingAuthority != expected.votingAuth { 516 t.Errorf("Actual voting authority (%d) different than expected (%d)", 517 actual.VotingAuthority, expected.votingAuth) 518 } 519 if actual.Total != expected.total { 520 t.Errorf("Actual total (%d) different than expected (%d)", 521 actual.Total, expected.total) 522 } 523 } 524 } 525 526 for i, e := range expectedBalances { 527 t.Run(fmt.Sprintf("acct=%d", e.acct), testFunc(i)) 528 } 529 530 return nil 531 }) 532 if err != nil { 533 t.Error(err) 534 } 535 } 536 537 func verifyV25Upgrade(ctx context.Context, t *testing.T, db walletdb.DB) { 538 const wantVer = 25 539 _, _, _, err := Open(ctx, db, chaincfg.TestNet3Params(), pubPass) 540 if err != nil { 541 t.Fatalf("Open after Upgrade failed: %v", err) 542 } 543 if err = walletdb.View(ctx, db, func(tx walletdb.ReadTx) error { 544 metadataBucket := tx.ReadBucket(unifiedDBMetadata{}.rootBucketKey()) 545 546 dbVer, err := unifiedDBMetadata{}.getVersion(metadataBucket) 547 if err != nil { 548 return err 549 } 550 if dbVer != wantVer { 551 return fmt.Errorf("wanted version %d but got %d", wantVer, dbVer) 552 } 553 return nil 554 }); err != nil { 555 t.Fatal(err) 556 } 557 }