github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/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  	dbm "github.com/bytom/bytom/database/leveldb"
    20  	"github.com/bytom/bytom/errors"
    21  	"github.com/bytom/bytom/protocol/bc"
    22  	"github.com/bytom/bytom/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.NewOriginalTxOutput(assetID, amount, controlProgram, nil)
    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  }