OSDN Git Service

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