12 "github.com/bytom/account"
13 "github.com/bytom/blockchain/pseudohsm"
14 "github.com/bytom/blockchain/signers"
15 "github.com/bytom/blockchain/txbuilder"
16 "github.com/bytom/consensus"
17 "github.com/bytom/crypto/ed25519/chainkd"
18 "github.com/bytom/errors"
19 "github.com/bytom/protocol/bc"
20 "github.com/bytom/protocol/bc/types"
21 dbm "github.com/bytom/database/leveldb"
24 // MockBlock mock a block
25 func MockBlock(txs []*types.Tx) *types.Block {
27 BlockHeader: types.BlockHeader{Timestamp: uint64(time.Now().Nanosecond())},
32 func MockSimpleUtxo(index uint64, assetID *bc.AssetID, amount uint64, ctrlProg *account.CtrlProgram) *account.UTXO {
34 ctrlProg = &account.CtrlProgram{
38 ControlProgram: []byte{81},
43 utxo := &account.UTXO{
44 OutputID: bc.Hash{V0: 1},
45 SourceID: bc.Hash{V0: 1},
49 ControlProgram: ctrlProg.ControlProgram,
50 ControlProgramIndex: ctrlProg.KeyIndex,
51 AccountID: ctrlProg.AccountID,
52 Address: ctrlProg.Address,
59 func AddTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *types.TxOutput {
60 out := types.NewTxOutput(assetID, amount, controlProgram)
64 func BuildTx(baseUtxo *account.UTXO, signer *signers.Signer) (*txbuilder.Template, error) {
65 tplBuilder, err := CreateTxBuilder(baseUtxo, signer)
70 tpl, _, err := tplBuilder.Build()
78 func CreateTxBuilder(baseUtxo *account.UTXO, signer *signers.Signer) (*txbuilder.TemplateBuilder, error) {
79 tplBuilder := txbuilder.NewBuilder(time.Now())
80 txOutput := AddTxOutput(baseUtxo.AssetID, 100, baseUtxo.ControlProgram)
81 tplBuilder.AddOutput(txOutput)
82 return tplBuilder, nil
85 func MockTxsP2PKH(acctMgr *account.Manager, xPub chainkd.XPub, multiTypeAccount bool) ([]*types.Tx, error) {
87 accts := []*account.Account{}
88 for i := uint32(1); i < 32; i = i + 1 + rand.Uint32()%5 {
89 alias := fmt.Sprintf("testAccount%d", i)
90 deriveRule := signers.BIP0044
92 deriveRule = uint8(rand.Uint32() % 2)
94 acct, err := account.CreateAccount([]chainkd.XPub{xPub}, 1, alias, uint64(i), deriveRule)
99 if err := acctMgr.SaveAccount(acct); err != nil {
103 accts = append(accts, acct)
106 for _, acct := range accts {
107 for i := uint32(1); i < 256; i = i + 1 + rand.Uint32()%16 {
108 controlProg, err := account.CreateCtrlProgram(acct, uint64(i), false)
113 if err := acctMgr.SaveControlPrograms(controlProg); err != nil {
117 utxo := MockSimpleUtxo(0, consensus.BTMAssetID, 1000000000, controlProg)
118 tpl, err := BuildTx(utxo, acct.Signer)
123 txs = append(txs, tpl.Transaction)
130 func TestXPubsRecoveryLock(t *testing.T) {
131 dirPath, err := ioutil.TempDir(".", "")
135 defer os.RemoveAll(dirPath)
137 testDB := dbm.NewDB("testdb", "leveldb", dirPath)
138 hsm, err := pseudohsm.New(dirPath)
143 xpub, _, err := hsm.XCreate("test_pub", "password", "en")
148 acctMgr := account.NewManager(testDB, nil)
149 recoveryMgr := newRecoveryManager(testDB, acctMgr)
150 recoveryMgr.state = newRecoveryState()
151 recoveryMgr.state.XPubs = []chainkd.XPub{xpub.XPub}
152 recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow)
154 recoveryMgr.state.StartTime = time.Now()
155 recoveryMgr.commitStatusInfo()
157 if err := recoveryMgr.AcctResurrect([]chainkd.XPub{xpub.XPub}); err != nil {
158 t.Fatal("TestXPubsRecoveryLock err:", err)
161 if err := recoveryMgr.AcctResurrect([]chainkd.XPub{xpub.XPub}); err != errors.Root(ErrRecoveryBusy) {
162 t.Fatal("TestXPubsRecoveryLock err:", err)
165 if err := recoveryMgr.LoadStatusInfo(); err != errors.Root(ErrRecoveryBusy) {
166 t.Fatal("TestXPubsRecoveryLock err:", err)
169 recoveryMgr.stopXPubsRec()
170 if err := recoveryMgr.LoadStatusInfo(); err != nil {
171 t.Fatal("TestXPubsRecoveryLock err:", err)
173 recoveryMgr.finished()
174 if err := recoveryMgr.AcctResurrect([]chainkd.XPub{xpub.XPub}); err != nil {
175 t.Fatal("TestXPubsRecoveryLock err:", err)
179 func TestExtendScanAddresses(t *testing.T) {
180 dirPath, err := ioutil.TempDir(".", "")
184 defer os.RemoveAll(dirPath)
186 testDB := dbm.NewDB("testdb", "leveldb", dirPath)
187 hsm, err := pseudohsm.New(dirPath)
192 xpub, _, err := hsm.XCreate("test_pub", "password", "en")
197 acctMgr := account.NewManager(testDB, nil)
198 recoveryMgr := newRecoveryManager(testDB, acctMgr)
199 acc1 := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}}
200 acc2 := &account.Account{ID: "testB", Alias: "test2"}
201 acc3 := &account.Account{ID: "testC", Alias: "test3", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 2, DeriveRule: 3}}
202 acc4 := &account.Account{ID: "testD", Alias: "test4", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 3, DeriveRule: signers.BIP0032}}
204 recoveryMgr.state.stateForScope(acc1)
205 recoveryMgr.state.stateForScope(acc3)
206 recoveryMgr.state.stateForScope(acc4)
209 acct *account.Account
213 {acc1, nil, addrRecoveryWindow * 2},
214 {acc2, ErrInvalidAcctID, addrRecoveryWindow * 2},
215 {acc3, signers.ErrDeriveRule, addrRecoveryWindow * 2},
216 {acc4, nil, addrRecoveryWindow * 3},
219 for _, c := range cases {
220 if err := recoveryMgr.extendScanAddresses(c.acct.ID, true); err != c.err {
221 t.Fatal("extend scan addresses err:", err)
224 if err := recoveryMgr.extendScanAddresses(c.acct.ID, false); err != c.err {
225 t.Fatal("extend scan addresses err:", err)
228 if uint64(len(recoveryMgr.addresses)) != c.addressLen {
229 t.Fatalf("extend scan addresses err: len:%d,want:%d", len(recoveryMgr.addresses), c.addressLen)
234 func TestRecoveryFromXPubs(t *testing.T) {
235 dirPath, err := ioutil.TempDir(".", "")
239 defer os.RemoveAll(dirPath)
241 testDB := dbm.NewDB("testdb", "leveldb", dirPath)
242 recoveryDB := dbm.NewDB("recdb", "leveldb", dirPath)
243 hsm, err := pseudohsm.New(dirPath)
248 xpub, _, err := hsm.XCreate("test_pub", "password", "en")
253 acctMgr := account.NewManager(testDB, nil)
254 txs, err := MockTxsP2PKH(acctMgr, xpub.XPub, false)
255 recAcctMgr := account.NewManager(recoveryDB, nil)
256 recoveryMgr := newRecoveryManager(recoveryDB, recAcctMgr)
262 {[]chainkd.XPub{xpub.XPub}, nil},
263 {[]chainkd.XPub{xpub.XPub, xpub.XPub}, signers.ErrDupeXPub},
264 {[]chainkd.XPub{}, signers.ErrNoXPubs},
267 for _, c := range cases {
268 if err := recoveryMgr.AcctResurrect(c.xPubs); errors.Root(err) != c.err {
269 t.Fatal("recovery from XPubs err:", err)
273 recoveryMgr.finished()
276 if err := recoveryMgr.FilterRecoveryTxs(MockBlock(txs)); err != nil {
277 t.Fatal("recovery from XPubs err:", err)
280 Accounts, err := acctMgr.ListAccounts("")
282 t.Fatal("recovery from XPubs err:", err)
285 for _, acct := range Accounts {
286 tmp, err := recAcctMgr.GetAccountByXPubsIndex(acct.XPubs, acct.KeyIndex)
288 t.Fatal("recovery from XPubs err:", err)
292 t.Fatal("accout recovery from xpubs err:", acct.KeyIndex)
295 if acctMgr.GetBip44ContractIndex(acct.ID, true) != recAcctMgr.GetBip44ContractIndex(tmp.ID, true) {
296 t.Fatal("bip44 internal address index recovery from xpubs err")
299 if acctMgr.GetBip44ContractIndex(acct.ID, false) != recAcctMgr.GetBip44ContractIndex(tmp.ID, false) {
300 t.Fatal("bip44 external address index recovery from xpubs err")
304 recoveryMgr.finished()
308 func TestRecoveryByRescanAccount(t *testing.T) {
309 dirPath, err := ioutil.TempDir(".", "")
313 defer os.RemoveAll(dirPath)
315 testDB := dbm.NewDB("testdb", "leveldb", dirPath)
316 recoveryDB := dbm.NewDB("recdb", "leveldb", dirPath)
317 hsm, err := pseudohsm.New(dirPath)
322 xpub, _, err := hsm.XCreate("test_pub", "password", "en")
327 acctMgr := account.NewManager(testDB, nil)
328 txs, err := MockTxsP2PKH(acctMgr, xpub.XPub, true)
330 t.Fatal("recovery by rescan account err:", err)
333 allAccounts, err := acctMgr.ListAccounts("")
335 t.Fatal("recovery by rescan account err:", err)
338 recAcctMgr := account.NewManager(recoveryDB, nil)
339 for _, acct := range allAccounts {
340 if err := recAcctMgr.SaveAccount(acct); err != nil {
341 t.Fatal("recovery by rescan account err:", err)
345 recoveryMgr := newRecoveryManager(recoveryDB, recAcctMgr)
347 acct := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: 3}}
350 accounts []*account.Account
354 {[]*account.Account{acct}, signers.ErrDeriveRule},
357 for _, c := range cases {
358 if err := recoveryMgr.AddrResurrect(c.accounts); errors.Root(err) != c.err {
359 t.Fatal("recovery by rescan account err:", err)
365 recoveryMgr.FilterRecoveryTxs(MockBlock(txs))
366 accounts, err := acctMgr.ListAccounts("")
368 t.Fatal("recovery from XPubs err:", err)
371 for _, acct := range accounts {
372 tmp, err := recAcctMgr.GetAccountByXPubsIndex(acct.XPubs, acct.KeyIndex)
374 t.Fatal("recovery from XPubs err:", err)
378 t.Fatal("accout recovery from xpubs err:", acct.KeyIndex)
381 if acctMgr.GetBip44ContractIndex(acct.ID, true) != recAcctMgr.GetBip44ContractIndex(tmp.ID, true) {
382 t.Fatal("bip44 internal address index recovery from xpubs err")
385 if acctMgr.GetBip44ContractIndex(acct.ID, false) != recAcctMgr.GetBip44ContractIndex(tmp.ID, false) {
386 t.Fatal("bip44 external address index recovery from xpubs err")
389 if acctMgr.GetContractIndex(acct.ID) != recAcctMgr.GetContractIndex(tmp.ID) {
390 t.Fatal("bip32 address index recovery from xpubs err")
397 func TestReportFound(t *testing.T) {
398 dirPath, err := ioutil.TempDir(".", "")
402 defer os.RemoveAll(dirPath)
404 testDB := dbm.NewDB("testdb", "leveldb", dirPath)
405 hsm, err := pseudohsm.New(dirPath)
410 xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
415 xpub2, _, err := hsm.XCreate("test_pub2", "password", "en")
420 acctMgr := account.NewManager(testDB, nil)
421 recoveryMgr := newRecoveryManager(testDB, acctMgr)
422 acc1 := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub1.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}}
423 acc2 := &account.Account{ID: "testB", Alias: "test2", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub2.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0032}}
424 acc3 := &account.Account{ID: "testC", Alias: "test3", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub2.XPub}, KeyIndex: 2, DeriveRule: signers.BIP0044}}
426 cp1 := &account.CtrlProgram{AccountID: acc1.ID, Address: "address1", KeyIndex: 10, Change: false}
427 cp2 := &account.CtrlProgram{AccountID: acc1.ID, Address: "address1", KeyIndex: 20, Change: true}
428 cp3 := &account.CtrlProgram{AccountID: acc2.ID, Address: "address1", KeyIndex: 30, Change: false}
429 cp4 := &account.CtrlProgram{AccountID: acc2.ID, Address: "address1", KeyIndex: 40, Change: true}
430 cp5 := &account.CtrlProgram{AccountID: acc3.ID, Address: "address1", KeyIndex: 50, Change: false}
431 cp6 := &account.CtrlProgram{AccountID: acc3.ID, Address: "address1", KeyIndex: 60, Change: true}
433 if err := acctMgr.SaveAccount(acc2); err != nil {
434 t.Fatal("ReportFound test err:", err)
437 if err := acctMgr.SaveAccount(acc3); err != nil {
438 t.Fatal("ReportFound test err:", err)
441 recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow)
442 recoveryMgr.state.XPubs = []chainkd.XPub{xpub1.XPub}
443 recoveryMgr.state.stateForScope(acc1)
444 recoveryMgr.state.stateForScope(acc2)
445 recoveryMgr.state.stateForScope(acc3)
448 acct *account.Account
449 cp *account.CtrlProgram
451 status *addressRecoveryState
454 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 1, 1}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 139, 11}, Account: acc1}},
456 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 1, 1}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 159, 31}, Account: acc2}},
458 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 149, 21}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 139, 11}, Account: acc1}},
460 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 169, 41}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 159, 31}, Account: acc2}},
462 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 1, 1}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 179, 51}, Account: acc3}},
464 &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 189, 61}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 179, 51}, Account: acc3}},
467 for _, c := range cases {
468 if err := recoveryMgr.reportFound(c.acct, c.cp); err != c.err {
469 t.Fatal("ReportFound test err:", err, c.acct.ID)
472 status, ok := recoveryMgr.state.AccountsStatus[c.acct.ID]
474 t.Fatal("ReportFound test err: can not find status")
476 if !reflect.DeepEqual(status, c.status) {
477 t.Log(c.status.Account, c.status.InternalBranch, c.status.ExternalBranch)
478 t.Log(status.Account, status.InternalBranch, status.ExternalBranch)
479 t.Fatal("ReportFound test err: recovery status error")
484 func TestLoadStatusInfo(t *testing.T) {
485 dirPath, err := ioutil.TempDir(".", "")
489 defer os.RemoveAll(dirPath)
491 testDB := dbm.NewDB("testdb", "leveldb", "temp")
492 defer os.RemoveAll("temp")
494 hsm, err := pseudohsm.New(dirPath)
499 xpub, _, err := hsm.XCreate("test_pub", "password", "en")
504 acctMgr := account.NewManager(testDB, nil)
505 recoveryMgr := newRecoveryManager(testDB, acctMgr)
506 // StatusInit init recovery status manager.
507 recoveryMgr.state = newRecoveryState()
508 recoveryMgr.state.XPubs = []chainkd.XPub{xpub.XPub}
509 recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow)
511 recoveryMgr.state.StartTime = time.Now()
512 if err := recoveryMgr.LoadStatusInfo(); err != nil {
513 t.Fatal("TestLoadStatusInfo err:", err)
516 recoveryMgr.commitStatusInfo()
518 recoveryMgrRestore := newRecoveryManager(testDB, acctMgr)
519 if err := recoveryMgrRestore.LoadStatusInfo(); err != nil {
520 t.Fatal("TestLoadStatusInfo err:", err)
523 if !reflect.DeepEqual(recoveryMgrRestore.state.XPubsStatus, recoveryMgr.state.XPubsStatus) {
524 t.Fatalf("TestLoadStatusInfo XPubsStatus reload err")
527 if !reflect.DeepEqual(recoveryMgrRestore.state.XPubs, recoveryMgr.state.XPubs) {
528 t.Fatalf("TestLoadStatusInfo XPubs reload err")
531 if !reflect.DeepEqual(recoveryMgrRestore.state.AccountsStatus, recoveryMgr.state.AccountsStatus) {
532 t.Fatalf("TestLoadStatusInfo AccountsStatus reload err")
535 if !recoveryMgrRestore.state.StartTime.Equal(recoveryMgr.state.StartTime) {
536 t.Fatalf("TestLoadStatusInfo StartTime reload err")
539 acct := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: 3}}
540 recoveryMgr.state.AccountsStatus[acct.ID] = newAddressRecoveryState(addrRecoveryWindow, acct)
541 if err := recoveryMgr.commitStatusInfo(); err != nil {
542 t.Fatal("TestLoadStatusInfo err:", err)
544 if err := recoveryMgr.LoadStatusInfo(); err == nil {
545 t.Fatal("TestLoadStatusInfo err")
548 recoveryMgr.state = nil
549 if err := recoveryMgr.commitStatusInfo(); err != nil {
550 t.Fatal("TestLoadStatusInfo err:", err)
553 if err := recoveryMgr.LoadStatusInfo(); err == nil {
554 t.Fatal("TestLoadStatusInfo err")
558 func TestLock(t *testing.T) {
559 dirPath, err := ioutil.TempDir(".", "")
563 defer os.RemoveAll(dirPath)
565 testDB := dbm.NewDB("testdb", "leveldb", "temp")
566 defer os.RemoveAll("temp")
568 acctMgr := account.NewManager(testDB, nil)
569 recoveryMgr := newRecoveryManager(testDB, acctMgr)
570 if !recoveryMgr.tryStartXPubsRec() {
571 t.Fatal("recovery manager try lock test err")
574 if recoveryMgr.tryStartXPubsRec() {
575 t.Fatal("recovery manager relock test err")
578 recoveryMgr.stopXPubsRec()
580 if !recoveryMgr.tryStartXPubsRec() {
581 t.Fatal("recovery manager try lock test err")
585 func TestStateForScope(t *testing.T) {
586 state := newRecoveryState()
587 acc1 := &account.Account{ID: "test1", Alias: "testA"}
588 state.stateForScope(acc1)
589 if !reflect.DeepEqual(state.AccountsStatus[acc1.ID].Account, acc1) {
590 t.Fatal("state for scope test err")
593 acc2 := &account.Account{ID: "test1", Alias: "testB"}
594 state.stateForScope(acc2)
596 if reflect.DeepEqual(state.AccountsStatus[acc2.ID].Account, acc2) {
597 t.Fatal("state for scope test err")
600 acc3 := &account.Account{ID: "test2", Alias: "testC"}
601 state.stateForScope(acc3)
602 if !reflect.DeepEqual(state.AccountsStatus[acc3.ID].Account, acc3) {
603 t.Fatal("state for scope test err")