github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/wallet/recovery_test.go (about) 1 package wallet 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "math/rand" 7 "os" 8 "reflect" 9 "testing" 10 "time" 11 12 "github.com/bytom/bytom/account" 13 "github.com/bytom/bytom/blockchain/pseudohsm" 14 "github.com/bytom/bytom/blockchain/signers" 15 "github.com/bytom/bytom/blockchain/txbuilder" 16 "github.com/bytom/bytom/common" 17 "github.com/bytom/bytom/consensus" 18 "github.com/bytom/bytom/crypto/ed25519/chainkd" 19 "github.com/bytom/bytom/errors" 20 "github.com/bytom/bytom/protocol/bc" 21 "github.com/bytom/bytom/protocol/bc/types" 22 dbm "github.com/bytom/bytom/database/leveldb" 23 ) 24 25 // MockBlock mock a block 26 func MockBlock(txs []*types.Tx) *types.Block { 27 return &types.Block{ 28 BlockHeader: types.BlockHeader{Timestamp: uint64(time.Now().Nanosecond())}, 29 Transactions: txs, 30 } 31 } 32 33 func MockSimpleUtxo(index uint64, assetID *bc.AssetID, amount uint64, ctrlProg *account.CtrlProgram) *account.UTXO { 34 if ctrlProg == nil { 35 ctrlProg = &account.CtrlProgram{ 36 AccountID: "", 37 Address: "", 38 KeyIndex: uint64(0), 39 ControlProgram: []byte{81}, 40 Change: false, 41 } 42 } 43 44 utxo := &account.UTXO{ 45 OutputID: bc.Hash{V0: 1}, 46 SourceID: bc.Hash{V0: 1}, 47 AssetID: *assetID, 48 Amount: amount, 49 SourcePos: index, 50 ControlProgram: ctrlProg.ControlProgram, 51 ControlProgramIndex: ctrlProg.KeyIndex, 52 AccountID: ctrlProg.AccountID, 53 Address: ctrlProg.Address, 54 ValidHeight: 0, 55 } 56 57 return utxo 58 } 59 60 func AddTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *types.TxOutput { 61 out := types.NewTxOutput(assetID, amount, controlProgram) 62 return out 63 } 64 65 func BuildTx(baseUtxo *account.UTXO, signer *signers.Signer) (*txbuilder.Template, error) { 66 tplBuilder, err := CreateTxBuilder(baseUtxo, signer) 67 if err != nil { 68 return nil, err 69 } 70 71 tpl, _, err := tplBuilder.Build() 72 if err != nil { 73 return nil, err 74 } 75 76 return tpl, nil 77 } 78 79 func CreateTxBuilder(baseUtxo *account.UTXO, signer *signers.Signer) (*txbuilder.TemplateBuilder, error) { 80 tplBuilder := txbuilder.NewBuilder(time.Now()) 81 txOutput := AddTxOutput(baseUtxo.AssetID, 100, baseUtxo.ControlProgram) 82 tplBuilder.AddOutput(txOutput) 83 return tplBuilder, nil 84 } 85 86 func MockTxsP2PKH(acctMgr *account.Manager, xPub chainkd.XPub, multiTypeAccount bool) ([]*types.Tx, error) { 87 txs := []*types.Tx{} 88 accts := []*account.Account{} 89 for i := uint32(1); i < 32; i = i + 1 + rand.Uint32()%5 { 90 alias := fmt.Sprintf("testAccount%d", i) 91 deriveRule := signers.BIP0044 92 if multiTypeAccount { 93 deriveRule = uint8(rand.Uint32() % 2) 94 } 95 acct, err := account.CreateAccount([]chainkd.XPub{xPub}, 1, alias, uint64(i), deriveRule) 96 if err != nil { 97 return nil, err 98 } 99 100 if err := acctMgr.SaveAccount(acct); err != nil { 101 return nil, err 102 } 103 104 accts = append(accts, acct) 105 } 106 107 for _, acct := range accts { 108 for i := uint32(1); i < 256; i = i + 1 + rand.Uint32()%16 { 109 controlProg, err := account.CreateCtrlProgram(acct, uint64(i), false) 110 if err != nil { 111 return nil, err 112 } 113 114 if err := acctMgr.SaveControlPrograms(controlProg); err != nil { 115 return nil, err 116 } 117 118 utxo := MockSimpleUtxo(0, consensus.BTMAssetID, 1000000000, controlProg) 119 tpl, err := BuildTx(utxo, acct.Signer) 120 if err != nil { 121 return nil, err 122 } 123 124 txs = append(txs, tpl.Transaction) 125 } 126 } 127 128 return txs, nil 129 } 130 131 func TestXPubsRecoveryLock(t *testing.T) { 132 dirPath, err := ioutil.TempDir(".", "") 133 if err != nil { 134 t.Fatal(err) 135 } 136 defer os.RemoveAll(dirPath) 137 138 testDB := dbm.NewDB("testdb", "leveldb", dirPath) 139 hsm, err := pseudohsm.New(dirPath) 140 if err != nil { 141 t.Fatal(err) 142 } 143 144 xpub, _, err := hsm.XCreate("test_pub", "password", "en") 145 if err != nil { 146 t.Fatal(err) 147 } 148 149 acctMgr := account.NewManager(testDB, nil) 150 recoveryMgr := newRecoveryManager(testDB, acctMgr) 151 recoveryMgr.state = newRecoveryState() 152 recoveryMgr.state.XPubs = []chainkd.XPub{xpub.XPub} 153 recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow) 154 155 recoveryMgr.state.StartTime = time.Now() 156 recoveryMgr.commitStatusInfo() 157 158 if err := recoveryMgr.AcctResurrect([]chainkd.XPub{xpub.XPub}); err != nil { 159 t.Fatal("TestXPubsRecoveryLock err:", err) 160 } 161 162 if err := recoveryMgr.AcctResurrect([]chainkd.XPub{xpub.XPub}); err != errors.Root(ErrRecoveryBusy) { 163 t.Fatal("TestXPubsRecoveryLock err:", err) 164 } 165 166 if err := recoveryMgr.LoadStatusInfo(); err != errors.Root(ErrRecoveryBusy) { 167 t.Fatal("TestXPubsRecoveryLock err:", err) 168 } 169 170 recoveryMgr.stopXPubsRec() 171 if err := recoveryMgr.LoadStatusInfo(); err != nil { 172 t.Fatal("TestXPubsRecoveryLock err:", err) 173 } 174 recoveryMgr.finished() 175 if err := recoveryMgr.AcctResurrect([]chainkd.XPub{xpub.XPub}); err != nil { 176 t.Fatal("TestXPubsRecoveryLock err:", err) 177 } 178 } 179 180 func TestExtendScanAddresses(t *testing.T) { 181 dirPath, err := ioutil.TempDir(".", "") 182 if err != nil { 183 t.Fatal(err) 184 } 185 defer os.RemoveAll(dirPath) 186 187 testDB := dbm.NewDB("testdb", "leveldb", dirPath) 188 hsm, err := pseudohsm.New(dirPath) 189 if err != nil { 190 t.Fatal(err) 191 } 192 193 xpub, _, err := hsm.XCreate("test_pub", "password", "en") 194 if err != nil { 195 t.Fatal(err) 196 } 197 198 acctMgr := account.NewManager(testDB, nil) 199 recoveryMgr := newRecoveryManager(testDB, acctMgr) 200 acc1 := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}} 201 acc2 := &account.Account{ID: "testB", Alias: "test2"} 202 acc3 := &account.Account{ID: "testC", Alias: "test3", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 2, DeriveRule: 3}} 203 acc4 := &account.Account{ID: "testD", Alias: "test4", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 3, DeriveRule: signers.BIP0032}} 204 205 recoveryMgr.state.stateForScope(acc1) 206 recoveryMgr.state.stateForScope(acc3) 207 recoveryMgr.state.stateForScope(acc4) 208 209 cases := []struct { 210 acct *account.Account 211 err error 212 addressLen uint64 213 }{ 214 {acc1, nil, addrRecoveryWindow * 2}, 215 {acc2, ErrInvalidAcctID, addrRecoveryWindow * 2}, 216 {acc3, signers.ErrDeriveRule, addrRecoveryWindow * 2}, 217 {acc4, nil, addrRecoveryWindow * 3}, 218 } 219 220 for _, c := range cases { 221 if err := recoveryMgr.extendScanAddresses(c.acct.ID, true); err != c.err { 222 t.Fatal("extend scan addresses err:", err) 223 } 224 225 if err := recoveryMgr.extendScanAddresses(c.acct.ID, false); err != c.err { 226 t.Fatal("extend scan addresses err:", err) 227 } 228 229 if uint64(len(recoveryMgr.addresses)) != c.addressLen { 230 t.Fatalf("extend scan addresses err: len:%d,want:%d", len(recoveryMgr.addresses), c.addressLen) 231 } 232 } 233 } 234 235 func TestRecoveryFromXPubs(t *testing.T) { 236 dirPath, err := ioutil.TempDir(".", "") 237 if err != nil { 238 t.Fatal(err) 239 } 240 defer os.RemoveAll(dirPath) 241 242 testDB := dbm.NewDB("testdb", "leveldb", dirPath) 243 recoveryDB := dbm.NewDB("recdb", "leveldb", dirPath) 244 hsm, err := pseudohsm.New(dirPath) 245 if err != nil { 246 t.Fatal(err) 247 } 248 249 xpub, _, err := hsm.XCreate("test_pub", "password", "en") 250 if err != nil { 251 t.Fatal(err) 252 } 253 254 acctMgr := account.NewManager(testDB, nil) 255 txs, err := MockTxsP2PKH(acctMgr, xpub.XPub, false) 256 recAcctMgr := account.NewManager(recoveryDB, nil) 257 recoveryMgr := newRecoveryManager(recoveryDB, recAcctMgr) 258 259 cases := []struct { 260 xPubs []chainkd.XPub 261 err error 262 }{ 263 {[]chainkd.XPub{xpub.XPub}, nil}, 264 {[]chainkd.XPub{xpub.XPub, xpub.XPub}, signers.ErrDupeXPub}, 265 {[]chainkd.XPub{}, signers.ErrNoXPubs}, 266 } 267 268 for _, c := range cases { 269 if err := recoveryMgr.AcctResurrect(c.xPubs); errors.Root(err) != c.err { 270 t.Fatal("recovery from XPubs err:", err) 271 } 272 273 if err != nil { 274 recoveryMgr.finished() 275 continue 276 } 277 if err := recoveryMgr.FilterRecoveryTxs(MockBlock(txs)); err != nil { 278 t.Fatal("recovery from XPubs err:", err) 279 } 280 281 Accounts, err := acctMgr.ListAccounts("") 282 if err != nil { 283 t.Fatal("recovery from XPubs err:", err) 284 } 285 286 for _, acct := range Accounts { 287 tmp, err := recAcctMgr.GetAccountByXPubsIndex(acct.XPubs, acct.KeyIndex) 288 if err != nil { 289 t.Fatal("recovery from XPubs err:", err) 290 } 291 292 if tmp == nil { 293 t.Fatal("accout recovery from xpubs err:", acct.KeyIndex) 294 } 295 296 if acctMgr.GetBip44ContractIndex(acct.ID, true) != recAcctMgr.GetBip44ContractIndex(tmp.ID, true) { 297 t.Fatal("bip44 internal address index recovery from xpubs err") 298 } 299 300 if acctMgr.GetBip44ContractIndex(acct.ID, false) != recAcctMgr.GetBip44ContractIndex(tmp.ID, false) { 301 t.Fatal("bip44 external address index recovery from xpubs err") 302 } 303 } 304 305 recoveryMgr.finished() 306 } 307 } 308 309 func TestRecoveryByRescanAccount(t *testing.T) { 310 dirPath, err := ioutil.TempDir(".", "") 311 if err != nil { 312 t.Fatal(err) 313 } 314 defer os.RemoveAll(dirPath) 315 316 testDB := dbm.NewDB("testdb", "leveldb", dirPath) 317 recoveryDB := dbm.NewDB("recdb", "leveldb", dirPath) 318 hsm, err := pseudohsm.New(dirPath) 319 if err != nil { 320 t.Fatal(err) 321 } 322 323 xpub, _, err := hsm.XCreate("test_pub", "password", "en") 324 if err != nil { 325 t.Fatal(err) 326 } 327 328 acctMgr := account.NewManager(testDB, nil) 329 txs, err := MockTxsP2PKH(acctMgr, xpub.XPub, true) 330 if err != nil { 331 t.Fatal("recovery by rescan account err:", err) 332 } 333 334 allAccounts, err := acctMgr.ListAccounts("") 335 if err != nil { 336 t.Fatal("recovery by rescan account err:", err) 337 } 338 339 recAcctMgr := account.NewManager(recoveryDB, nil) 340 for _, acct := range allAccounts { 341 if err := recAcctMgr.SaveAccount(acct); err != nil { 342 t.Fatal("recovery by rescan account err:", err) 343 } 344 } 345 346 recoveryMgr := newRecoveryManager(recoveryDB, recAcctMgr) 347 348 acct := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: 3}} 349 350 cases := []struct { 351 accounts []*account.Account 352 err error 353 }{ 354 {allAccounts, nil}, 355 {[]*account.Account{acct}, signers.ErrDeriveRule}, 356 } 357 358 for _, c := range cases { 359 if err := recoveryMgr.AddrResurrect(c.accounts); errors.Root(err) != c.err { 360 t.Fatal("recovery by rescan account err:", err) 361 } 362 363 if err != nil { 364 continue 365 } 366 recoveryMgr.FilterRecoveryTxs(MockBlock(txs)) 367 accounts, err := acctMgr.ListAccounts("") 368 if err != nil { 369 t.Fatal("recovery from XPubs err:", err) 370 } 371 372 for _, acct := range accounts { 373 tmp, err := recAcctMgr.GetAccountByXPubsIndex(acct.XPubs, acct.KeyIndex) 374 if err != nil { 375 t.Fatal("recovery from XPubs err:", err) 376 } 377 378 if tmp == nil { 379 t.Fatal("accout recovery from xpubs err:", acct.KeyIndex) 380 } 381 382 if acctMgr.GetBip44ContractIndex(acct.ID, true) != recAcctMgr.GetBip44ContractIndex(tmp.ID, true) { 383 t.Fatal("bip44 internal address index recovery from xpubs err") 384 } 385 386 if acctMgr.GetBip44ContractIndex(acct.ID, false) != recAcctMgr.GetBip44ContractIndex(tmp.ID, false) { 387 t.Fatal("bip44 external address index recovery from xpubs err") 388 } 389 390 if acctMgr.GetContractIndex(acct.ID) != recAcctMgr.GetContractIndex(tmp.ID) { 391 t.Fatal("bip32 address index recovery from xpubs err") 392 } 393 } 394 } 395 396 } 397 398 func TestReportFound(t *testing.T) { 399 dirPath, err := ioutil.TempDir(".", "") 400 if err != nil { 401 t.Fatal(err) 402 } 403 defer os.RemoveAll(dirPath) 404 405 testDB := dbm.NewDB("testdb", "leveldb", dirPath) 406 hsm, err := pseudohsm.New(dirPath) 407 if err != nil { 408 t.Fatal(err) 409 } 410 411 xpub1, _, err := hsm.XCreate("test_pub1", "password", "en") 412 if err != nil { 413 t.Fatal(err) 414 } 415 416 xpub2, _, err := hsm.XCreate("test_pub2", "password", "en") 417 if err != nil { 418 t.Fatal(err) 419 } 420 421 acctMgr := account.NewManager(testDB, nil) 422 recoveryMgr := newRecoveryManager(testDB, acctMgr) 423 acc1 := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub1.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}} 424 acc2 := &account.Account{ID: "testB", Alias: "test2", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub2.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0032}} 425 acc3 := &account.Account{ID: "testC", Alias: "test3", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub2.XPub}, KeyIndex: 2, DeriveRule: signers.BIP0044}} 426 427 cp1 := &account.CtrlProgram{AccountID: acc1.ID, Address: "address1", KeyIndex: 10, Change: false} 428 cp2 := &account.CtrlProgram{AccountID: acc1.ID, Address: "address1", KeyIndex: 20, Change: true} 429 cp3 := &account.CtrlProgram{AccountID: acc2.ID, Address: "address1", KeyIndex: 30, Change: false} 430 cp4 := &account.CtrlProgram{AccountID: acc2.ID, Address: "address1", KeyIndex: 40, Change: true} 431 cp5 := &account.CtrlProgram{AccountID: acc3.ID, Address: "address1", KeyIndex: 50, Change: false} 432 cp6 := &account.CtrlProgram{AccountID: acc3.ID, Address: "address1", KeyIndex: 60, Change: true} 433 434 if err := acctMgr.SaveAccount(acc2); err != nil { 435 t.Fatal("ReportFound test err:", err) 436 } 437 438 if err := acctMgr.SaveAccount(acc3); err != nil { 439 t.Fatal("ReportFound test err:", err) 440 } 441 442 recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow) 443 recoveryMgr.state.XPubs = []chainkd.XPub{xpub1.XPub} 444 recoveryMgr.state.stateForScope(acc1) 445 recoveryMgr.state.stateForScope(acc2) 446 recoveryMgr.state.stateForScope(acc3) 447 448 cases := []struct { 449 acct *account.Account 450 cp *account.CtrlProgram 451 err error 452 status *addressRecoveryState 453 }{ 454 {acc1, cp1, nil, 455 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 1, 1}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 139, 11}, Account: acc1}}, 456 {acc2, cp3, nil, 457 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 1, 1}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 159, 31}, Account: acc2}}, 458 {acc1, cp2, nil, 459 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 149, 21}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 139, 11}, Account: acc1}}, 460 {acc2, cp4, nil, 461 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 169, 41}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 159, 31}, Account: acc2}}, 462 {acc3, cp5, nil, 463 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 1, 1}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 179, 51}, Account: acc3}}, 464 {acc3, cp6, nil, 465 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 189, 61}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 179, 51}, Account: acc3}}, 466 } 467 468 for _, c := range cases { 469 if err := recoveryMgr.reportFound(c.acct, c.cp); err != c.err { 470 t.Fatal("ReportFound test err:", err, c.acct.ID) 471 } 472 473 status, ok := recoveryMgr.state.AccountsStatus[c.acct.ID] 474 if !ok { 475 t.Fatal("ReportFound test err: can not find status") 476 } 477 if !reflect.DeepEqual(status, c.status) { 478 t.Log(c.status.Account, c.status.InternalBranch, c.status.ExternalBranch) 479 t.Log(status.Account, status.InternalBranch, status.ExternalBranch) 480 t.Fatal("ReportFound test err: recovery status error") 481 } 482 } 483 } 484 485 func TestLoadStatusInfo(t *testing.T) { 486 dirPath, err := ioutil.TempDir(".", "") 487 if err != nil { 488 t.Fatal(err) 489 } 490 defer os.RemoveAll(dirPath) 491 492 testDB := dbm.NewDB("testdb", "leveldb", "temp") 493 defer os.RemoveAll("temp") 494 495 hsm, err := pseudohsm.New(dirPath) 496 if err != nil { 497 t.Fatal(err) 498 } 499 500 xpub, _, err := hsm.XCreate("test_pub", "password", "en") 501 if err != nil { 502 t.Fatal(err) 503 } 504 505 acctMgr := account.NewManager(testDB, nil) 506 recoveryMgr := newRecoveryManager(testDB, acctMgr) 507 // StatusInit init recovery status manager. 508 recoveryMgr.state = newRecoveryState() 509 recoveryMgr.state.XPubs = []chainkd.XPub{xpub.XPub} 510 recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow) 511 512 recoveryMgr.state.StartTime = time.Now() 513 if err := recoveryMgr.LoadStatusInfo(); err != nil { 514 t.Fatal("TestLoadStatusInfo err:", err) 515 } 516 517 recoveryMgr.commitStatusInfo() 518 519 recoveryMgrRestore := newRecoveryManager(testDB, acctMgr) 520 if err := recoveryMgrRestore.LoadStatusInfo(); err != nil { 521 t.Fatal("TestLoadStatusInfo err:", err) 522 } 523 524 if !reflect.DeepEqual(recoveryMgrRestore.state.XPubsStatus, recoveryMgr.state.XPubsStatus) { 525 t.Fatalf("TestLoadStatusInfo XPubsStatus reload err") 526 } 527 528 if !reflect.DeepEqual(recoveryMgrRestore.state.XPubs, recoveryMgr.state.XPubs) { 529 t.Fatalf("TestLoadStatusInfo XPubs reload err") 530 } 531 532 if !reflect.DeepEqual(recoveryMgrRestore.state.AccountsStatus, recoveryMgr.state.AccountsStatus) { 533 t.Fatalf("TestLoadStatusInfo AccountsStatus reload err") 534 } 535 536 if !recoveryMgrRestore.state.StartTime.Equal(recoveryMgr.state.StartTime) { 537 t.Fatalf("TestLoadStatusInfo StartTime reload err") 538 } 539 540 acct := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: 3}} 541 recoveryMgr.state.AccountsStatus[acct.ID] = newAddressRecoveryState(addrRecoveryWindow, acct) 542 if err := recoveryMgr.commitStatusInfo(); err != nil { 543 t.Fatal("TestLoadStatusInfo err:", err) 544 } 545 if err := recoveryMgr.LoadStatusInfo(); err == nil { 546 t.Fatal("TestLoadStatusInfo err") 547 } 548 549 recoveryMgr.state = nil 550 if err := recoveryMgr.commitStatusInfo(); err != nil { 551 t.Fatal("TestLoadStatusInfo err:", err) 552 } 553 554 if err := recoveryMgr.LoadStatusInfo(); err == nil { 555 t.Fatal("TestLoadStatusInfo err") 556 } 557 } 558 559 func TestLock(t *testing.T) { 560 dirPath, err := ioutil.TempDir(".", "") 561 if err != nil { 562 t.Fatal(err) 563 } 564 defer os.RemoveAll(dirPath) 565 566 testDB := dbm.NewDB("testdb", "leveldb", "temp") 567 defer os.RemoveAll("temp") 568 569 acctMgr := account.NewManager(testDB, nil) 570 recoveryMgr := newRecoveryManager(testDB, acctMgr) 571 if !recoveryMgr.tryStartXPubsRec() { 572 t.Fatal("recovery manager try lock test err") 573 } 574 575 if recoveryMgr.tryStartXPubsRec() { 576 t.Fatal("recovery manager relock test err") 577 } 578 579 recoveryMgr.stopXPubsRec() 580 581 if !recoveryMgr.tryStartXPubsRec() { 582 t.Fatal("recovery manager try lock test err") 583 } 584 } 585 586 func TestStateForScope(t *testing.T) { 587 state := newRecoveryState() 588 acc1 := &account.Account{ID: "test1", Alias: "testA"} 589 state.stateForScope(acc1) 590 if !reflect.DeepEqual(state.AccountsStatus[acc1.ID].Account, acc1) { 591 t.Fatal("state for scope test err") 592 } 593 594 acc2 := &account.Account{ID: "test1", Alias: "testB"} 595 state.stateForScope(acc2) 596 597 if reflect.DeepEqual(state.AccountsStatus[acc2.ID].Account, acc2) { 598 t.Fatal("state for scope test err") 599 } 600 601 acc3 := &account.Account{ID: "test2", Alias: "testC"} 602 state.stateForScope(acc3) 603 if !reflect.DeepEqual(state.AccountsStatus[acc3.ID].Account, acc3) { 604 t.Fatal("state for scope test err") 605 } 606 } 607 608 func bip44ContractIndexKey(accountID string, change bool) []byte { 609 contractIndexPrefix := []byte("ContractIndex") 610 key := append(contractIndexPrefix, accountID...) 611 if change { 612 return append(key, []byte{1}...) 613 } 614 return append(key, []byte{0}...) 615 } 616 617 func TestContractIndexResidue(t *testing.T) { 618 dirPath, err := ioutil.TempDir(".", "") 619 if err != nil { 620 t.Fatal(err) 621 } 622 defer os.RemoveAll(dirPath) 623 624 testDB := dbm.NewDB("testdb", "leveldb", dirPath) 625 hsm, err := pseudohsm.New(dirPath) 626 if err != nil { 627 t.Fatal(err) 628 } 629 630 xpub1, _, err := hsm.XCreate("test_pub1", "password", "en") 631 if err != nil { 632 t.Fatal(err) 633 } 634 635 contractIndexResidue := uint64(5) 636 acctMgr := account.NewManager(testDB, nil) 637 recoveryMgr := newRecoveryManager(testDB, acctMgr) 638 acct := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub1.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}} 639 640 cp1 := &account.CtrlProgram{AccountID: acct.ID, Address: "address1", KeyIndex: 10, Change: false} 641 642 setContractIndexKey := func(acctMgr *account.Manager, accountID string, change bool) { 643 testDB.Set(bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(contractIndexResidue)) 644 } 645 646 delAccount := func(acctMgr *account.Manager, accountID string, change bool) { 647 acctMgr.DeleteAccount(accountID) 648 } 649 650 recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow) 651 recoveryMgr.state.XPubs = []chainkd.XPub{xpub1.XPub} 652 recoveryMgr.state.stateForScope(acct) 653 654 cases := []struct { 655 acct *account.Account 656 cp *account.CtrlProgram 657 preProcess func(acctMgr *account.Manager, accountID string, change bool) 658 err error 659 wantCPNum uint64 660 }{ 661 {acct, cp1, setContractIndexKey, nil, 5}, 662 {acct, cp1, delAccount, nil, 10}, 663 } 664 665 for _, c := range cases { 666 if c.preProcess != nil { 667 c.preProcess(acctMgr, c.acct.ID, c.cp.Change) 668 } 669 670 if err := acctMgr.SaveAccount(acct); err != nil { 671 t.Fatal("ReportFound test err:", err) 672 } 673 674 if err := recoveryMgr.reportFound(c.acct, c.cp); err != c.err { 675 t.Fatal("ContractIndexResidue test err:", err, c.acct.ID) 676 } 677 cps, err := acctMgr.ListControlProgram() 678 if err != nil { 679 t.Fatal("list control program err:", err) 680 } 681 682 cpNum := uint64(0) 683 for _, cp := range cps { 684 if cp.Address == "" || cp.AccountID != c.acct.ID { 685 continue 686 } 687 cpNum++ 688 } 689 690 if cpNum != c.wantCPNum { 691 t.Fatal("Test contract index residue cp num err want:", c.wantCPNum, " got:", cpNum) 692 } 693 } 694 }