OSDN Git Service

eff5850770d51e124075264a70d3162eb40f2ed0
[bytom/vapor.git] / wallet / recovery_test.go
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/vapor/account"
13         "github.com/vapor/blockchain/pseudohsm"
14         "github.com/vapor/blockchain/signers"
15         "github.com/vapor/blockchain/txbuilder"
16         "github.com/vapor/common"
17         "github.com/vapor/consensus"
18         "github.com/vapor/crypto/ed25519/chainkd"
19         dbm "github.com/vapor/database/leveldb"
20         "github.com/vapor/errors"
21         "github.com/vapor/protocol/bc"
22         "github.com/vapor/protocol/bc/types"
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.NewIntraChainOutput(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 }