OSDN Git Service

try to fix ban peer bug (#273)
[bytom/vapor.git] / wallet / recovery_test.go
index 1aa034c..c4ec390 100644 (file)
@@ -9,14 +9,16 @@ import (
        "testing"
        "time"
 
-       dbm "github.com/tendermint/tmlibs/db"
-
        "github.com/vapor/account"
+       acc "github.com/vapor/account"
        "github.com/vapor/blockchain/pseudohsm"
        "github.com/vapor/blockchain/signers"
        "github.com/vapor/blockchain/txbuilder"
+       "github.com/vapor/common"
        "github.com/vapor/consensus"
        "github.com/vapor/crypto/ed25519/chainkd"
+       dbm "github.com/vapor/database/leveldb"
+       "github.com/vapor/errors"
        "github.com/vapor/protocol/bc"
        "github.com/vapor/protocol/bc/types"
 )
@@ -56,9 +58,12 @@ func MockSimpleUtxo(index uint64, assetID *bc.AssetID, amount uint64, ctrlProg *
        return utxo
 }
 
+func AddInput(sourceID bc.Hash, assetID bc.AssetID, amount uint64, pos uint64, controlProgram []byte) *types.TxInput {
+       return types.NewSpendInput(nil, sourceID, assetID, amount, pos, controlProgram)
+}
+
 func AddTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *types.TxOutput {
-       out := types.NewTxOutput(assetID, amount, controlProgram)
-       return out
+       return types.NewIntraChainOutput(assetID, amount, controlProgram)
 }
 
 func BuildTx(baseUtxo *account.UTXO, signer *signers.Signer) (*txbuilder.Template, error) {
@@ -77,7 +82,9 @@ func BuildTx(baseUtxo *account.UTXO, signer *signers.Signer) (*txbuilder.Templat
 
 func CreateTxBuilder(baseUtxo *account.UTXO, signer *signers.Signer) (*txbuilder.TemplateBuilder, error) {
        tplBuilder := txbuilder.NewBuilder(time.Now())
+       txInput := AddInput(bc.Hash{V0: 1}, baseUtxo.AssetID, 10000, uint64(1), baseUtxo.ControlProgram)
        txOutput := AddTxOutput(baseUtxo.AssetID, 100, baseUtxo.ControlProgram)
+       tplBuilder.AddInput(txInput, &txbuilder.SigningInstruction{Position: 0})
        tplBuilder.AddOutput(txOutput)
        return tplBuilder, nil
 }
@@ -127,6 +134,57 @@ func MockTxsP2PKH(acctMgr *account.Manager, xPub chainkd.XPub, multiTypeAccount
        return txs, nil
 }
 
+func TestXPubsRecoveryLock(t *testing.T) {
+       dirPath, err := ioutil.TempDir(".", "")
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer os.RemoveAll(dirPath)
+
+       testDB := dbm.NewDB("testdb", "leveldb", dirPath)
+       walletStore := NewMockWalletStore(testDB)
+       hsm, err := pseudohsm.New(dirPath)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       xpub, _, err := hsm.XCreate("test_pub", "password", "en")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       acctStore := NewMockAccountStore(testDB)
+       acctMgr := account.NewManager(acctStore, nil)
+       recoveryMgr := NewRecoveryManager(walletStore, acctMgr)
+       recoveryMgr.state = newRecoveryState()
+       recoveryMgr.state.XPubs = []chainkd.XPub{xpub.XPub}
+       recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow)
+
+       recoveryMgr.state.StartTime = time.Now()
+       recoveryMgr.commitStatusInfo()
+
+       if err := recoveryMgr.AcctResurrect([]chainkd.XPub{xpub.XPub}); err != nil {
+               t.Fatal("TestXPubsRecoveryLock err:", err)
+       }
+
+       if err := recoveryMgr.AcctResurrect([]chainkd.XPub{xpub.XPub}); err != errors.Root(ErrRecoveryBusy) {
+               t.Fatal("TestXPubsRecoveryLock err:", err)
+       }
+
+       if err := recoveryMgr.LoadStatusInfo(); err != errors.Root(ErrRecoveryBusy) {
+               t.Fatal("TestXPubsRecoveryLock err:", err)
+       }
+
+       recoveryMgr.stopXPubsRec()
+       if err := recoveryMgr.LoadStatusInfo(); err != nil {
+               t.Fatal("TestXPubsRecoveryLock err:", err)
+       }
+       recoveryMgr.finished()
+       if err := recoveryMgr.AcctResurrect([]chainkd.XPub{xpub.XPub}); err != nil {
+               t.Fatal("TestXPubsRecoveryLock err:", err)
+       }
+}
+
 func TestExtendScanAddresses(t *testing.T) {
        dirPath, err := ioutil.TempDir(".", "")
        if err != nil {
@@ -135,6 +193,7 @@ func TestExtendScanAddresses(t *testing.T) {
        defer os.RemoveAll(dirPath)
 
        testDB := dbm.NewDB("testdb", "leveldb", dirPath)
+       walletStore := NewMockWalletStore(testDB)
        hsm, err := pseudohsm.New(dirPath)
        if err != nil {
                t.Fatal(err)
@@ -145,8 +204,9 @@ func TestExtendScanAddresses(t *testing.T) {
                t.Fatal(err)
        }
 
-       acctMgr := account.NewManager(testDB, nil)
-       recoveryMgr := newRecoveryManager(testDB, acctMgr)
+       acctStore := NewMockAccountStore(testDB)
+       acctMgr := account.NewManager(acctStore, nil)
+       recoveryMgr := NewRecoveryManager(walletStore, acctMgr)
        acc1 := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}}
        acc2 := &account.Account{ID: "testB", Alias: "test2"}
        acc3 := &account.Account{ID: "testC", Alias: "test3", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 2, DeriveRule: 3}}
@@ -191,6 +251,7 @@ func TestRecoveryFromXPubs(t *testing.T) {
 
        testDB := dbm.NewDB("testdb", "leveldb", dirPath)
        recoveryDB := dbm.NewDB("recdb", "leveldb", dirPath)
+       recoveryStore := NewMockWalletStore(recoveryDB)
        hsm, err := pseudohsm.New(dirPath)
        if err != nil {
                t.Fatal(err)
@@ -201,40 +262,60 @@ func TestRecoveryFromXPubs(t *testing.T) {
                t.Fatal(err)
        }
 
-       acctMgr := account.NewManager(testDB, nil)
+       acctStore := NewMockAccountStore(testDB)
+       acctMgr := account.NewManager(acctStore, nil)
        txs, err := MockTxsP2PKH(acctMgr, xpub.XPub, false)
-       recAcctMgr := account.NewManager(recoveryDB, nil)
-       recoveryMgr := newRecoveryManager(recoveryDB, recAcctMgr)
-       if err := recoveryMgr.AcctResurrect([]chainkd.XPub{xpub.XPub}); err != nil {
-               t.Fatal("recovery from XPubs err:", err)
-       }
+       recActStore := NewMockAccountStore(recoveryDB)
+       recAcctMgr := account.NewManager(recActStore, nil)
+       recoveryMgr := NewRecoveryManager(recoveryStore, recAcctMgr)
 
-       if err := recoveryMgr.FilterRecoveryTxs(MockBlock(txs)); err != nil {
-               t.Fatal("recovery from XPubs err:", err)
+       cases := []struct {
+               xPubs []chainkd.XPub
+               err   error
+       }{
+               {[]chainkd.XPub{xpub.XPub}, nil},
+               {[]chainkd.XPub{xpub.XPub, xpub.XPub}, signers.ErrDupeXPub},
+               {[]chainkd.XPub{}, signers.ErrNoXPubs},
        }
 
-       Accounts, err := acctMgr.ListAccounts("")
-       if err != nil {
-               t.Fatal("recovery from XPubs err:", err)
-       }
+       for _, c := range cases {
+               if err := recoveryMgr.AcctResurrect(c.xPubs); errors.Root(err) != c.err {
+                       t.Fatal("recovery from XPubs err:", err)
+               }
 
-       for _, acct := range Accounts {
-               tmp, err := recAcctMgr.GetAccountByXPubsIndex(acct.XPubs, acct.KeyIndex)
                if err != nil {
+                       recoveryMgr.finished()
+                       continue
+               }
+               if err := recoveryMgr.FilterRecoveryTxs(MockBlock(txs)); err != nil {
                        t.Fatal("recovery from XPubs err:", err)
                }
 
-               if tmp == nil {
-                       t.Fatal("accout recovery from xpubs err:", acct.KeyIndex)
+               Accounts, err := acctMgr.ListAccounts("")
+               if err != nil {
+                       t.Fatal("recovery from XPubs err:", err)
                }
 
-               if acctMgr.GetBip44ContractIndex(acct.ID, true) != recAcctMgr.GetBip44ContractIndex(tmp.ID, true) {
-                       t.Fatal("bip44 internal address index recovery from xpubs err")
-               }
+               for _, acct := range Accounts {
+                       tmp, err := recAcctMgr.GetAccountByXPubsIndex(acct.XPubs, acct.KeyIndex)
+                       if err != nil && err != acc.ErrFindAccount {
+                               t.Fatal("recovery from XPubs err:", err)
+                       }
+
+                       if tmp == nil {
+                               t.Fatal("accout recovery from xpubs err:", acct.KeyIndex)
+                       }
+
+                       if acctMgr.GetBip44ContractIndex(acct.ID, true) != recAcctMgr.GetBip44ContractIndex(tmp.ID, true) {
+                               t.Fatal("bip44 internal address index recovery from xpubs err")
+                       }
 
-               if acctMgr.GetBip44ContractIndex(acct.ID, false) != recAcctMgr.GetBip44ContractIndex(tmp.ID, false) {
-                       t.Fatal("bip44 external address index recovery from xpubs err")
+                       if acctMgr.GetBip44ContractIndex(acct.ID, false) != recAcctMgr.GetBip44ContractIndex(tmp.ID, false) {
+                               t.Fatal("bip44 external address index recovery from xpubs err")
+                       }
                }
+
+               recoveryMgr.finished()
        }
 }
 
@@ -247,6 +328,7 @@ func TestRecoveryByRescanAccount(t *testing.T) {
 
        testDB := dbm.NewDB("testdb", "leveldb", dirPath)
        recoveryDB := dbm.NewDB("recdb", "leveldb", dirPath)
+       recoveryStore := NewMockWalletStore(recoveryDB)
        hsm, err := pseudohsm.New(dirPath)
        if err != nil {
                t.Fatal(err)
@@ -257,7 +339,8 @@ func TestRecoveryByRescanAccount(t *testing.T) {
                t.Fatal(err)
        }
 
-       acctMgr := account.NewManager(testDB, nil)
+       acctStore := NewMockAccountStore(testDB)
+       acctMgr := account.NewManager(acctStore, nil)
        txs, err := MockTxsP2PKH(acctMgr, xpub.XPub, true)
        if err != nil {
                t.Fatal("recovery by rescan account err:", err)
@@ -268,40 +351,151 @@ func TestRecoveryByRescanAccount(t *testing.T) {
                t.Fatal("recovery by rescan account err:", err)
        }
 
-       recAcctMgr := account.NewManager(recoveryDB, nil)
+       recActStore := NewMockAccountStore(recoveryDB)
+       recAcctMgr := account.NewManager(recActStore, nil)
        for _, acct := range allAccounts {
                if err := recAcctMgr.SaveAccount(acct); err != nil {
                        t.Fatal("recovery by rescan account err:", err)
                }
        }
 
-       recoveryMgr := newRecoveryManager(recoveryDB, recAcctMgr)
-       if err := recoveryMgr.AddrResurrect(allAccounts); err != nil {
-               t.Fatal("recovery by rescan account err:", err)
+       recoveryMgr := NewRecoveryManager(recoveryStore, recAcctMgr)
+
+       acct := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: 3}}
+
+       cases := []struct {
+               accounts []*account.Account
+               err      error
+       }{
+               {allAccounts, nil},
+               {[]*account.Account{acct}, signers.ErrDeriveRule},
        }
 
-       recoveryMgr.FilterRecoveryTxs(MockBlock(txs))
-       Accounts, err := acctMgr.ListAccounts("")
-       for _, acct := range Accounts {
-               tmp, err := recAcctMgr.GetAccountByXPubsIndex(acct.XPubs, acct.KeyIndex)
+       for _, c := range cases {
+               if err := recoveryMgr.AddrResurrect(c.accounts); errors.Root(err) != c.err {
+                       t.Fatal("recovery by rescan account err:", err)
+               }
+
+               if err != nil {
+                       continue
+               }
+               recoveryMgr.FilterRecoveryTxs(MockBlock(txs))
+               accounts, err := acctMgr.ListAccounts("")
                if err != nil {
                        t.Fatal("recovery from XPubs err:", err)
                }
 
-               if tmp == nil {
-                       t.Fatal("accout recovery from xpubs err:", acct.KeyIndex)
-               }
+               for _, acct := range accounts {
+                       tmp, err := recAcctMgr.GetAccountByXPubsIndex(acct.XPubs, acct.KeyIndex)
+                       if err != nil && err != acc.ErrFindAccount {
+                               t.Fatal("recovery from XPubs err:", err)
+                       }
+
+                       if tmp == nil {
+                               t.Fatal("accout recovery from xpubs err:", acct.KeyIndex)
+                       }
+
+                       if acctMgr.GetBip44ContractIndex(acct.ID, true) != recAcctMgr.GetBip44ContractIndex(tmp.ID, true) {
+                               t.Fatal("bip44 internal address index recovery from xpubs err")
+                       }
 
-               if acctMgr.GetBip44ContractIndex(acct.ID, true) != recAcctMgr.GetBip44ContractIndex(tmp.ID, true) {
-                       t.Fatal("bip44 internal address index recovery from xpubs err")
+                       if acctMgr.GetBip44ContractIndex(acct.ID, false) != recAcctMgr.GetBip44ContractIndex(tmp.ID, false) {
+                               t.Fatal("bip44 external address index recovery from xpubs err")
+                       }
+
+                       if acctMgr.GetContractIndex(acct.ID) != recAcctMgr.GetContractIndex(tmp.ID) {
+                               t.Fatal("bip32 address index recovery from xpubs err")
+                       }
                }
+       }
+
+}
+
+func TestReportFound(t *testing.T) {
+       dirPath, err := ioutil.TempDir(".", "")
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer os.RemoveAll(dirPath)
+
+       testDB := dbm.NewDB("testdb", "leveldb", dirPath)
+       testStore := NewMockWalletStore(testDB)
+       hsm, err := pseudohsm.New(dirPath)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       xpub2, _, err := hsm.XCreate("test_pub2", "password", "en")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       acctStore := NewMockAccountStore(testDB)
+       acctMgr := account.NewManager(acctStore, nil)
+       recoveryMgr := NewRecoveryManager(testStore, acctMgr)
+       acc1 := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub1.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}}
+       acc2 := &account.Account{ID: "testB", Alias: "test2", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub2.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0032}}
+       acc3 := &account.Account{ID: "testC", Alias: "test3", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub2.XPub}, KeyIndex: 2, DeriveRule: signers.BIP0044}}
+
+       cp1 := &account.CtrlProgram{AccountID: acc1.ID, Address: "address1", KeyIndex: 10, Change: false}
+       cp2 := &account.CtrlProgram{AccountID: acc1.ID, Address: "address1", KeyIndex: 20, Change: true}
+       cp3 := &account.CtrlProgram{AccountID: acc2.ID, Address: "address1", KeyIndex: 30, Change: false}
+       cp4 := &account.CtrlProgram{AccountID: acc2.ID, Address: "address1", KeyIndex: 40, Change: true}
+       cp5 := &account.CtrlProgram{AccountID: acc3.ID, Address: "address1", KeyIndex: 50, Change: false}
+       cp6 := &account.CtrlProgram{AccountID: acc3.ID, Address: "address1", KeyIndex: 60, Change: true}
+
+       if err := acctMgr.SaveAccount(acc2); err != nil {
+               t.Fatal("ReportFound test err:", err)
+       }
+
+       if err := acctMgr.SaveAccount(acc3); err != nil {
+               t.Fatal("ReportFound test err:", err)
+       }
+
+       recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow)
+       recoveryMgr.state.XPubs = []chainkd.XPub{xpub1.XPub}
+       recoveryMgr.state.stateForScope(acc1)
+       recoveryMgr.state.stateForScope(acc2)
+       recoveryMgr.state.stateForScope(acc3)
 
-               if acctMgr.GetBip44ContractIndex(acct.ID, false) != recAcctMgr.GetBip44ContractIndex(tmp.ID, false) {
-                       t.Fatal("bip44 external address index recovery from xpubs err")
+       cases := []struct {
+               acct   *account.Account
+               cp     *account.CtrlProgram
+               err    error
+               status *addressRecoveryState
+       }{
+               {acc1, cp1, nil,
+                       &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 1, 1}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 139, 11}, Account: acc1}},
+               {acc2, cp3, nil,
+                       &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 1, 1}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 159, 31}, Account: acc2}},
+               {acc1, cp2, nil,
+                       &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 149, 21}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 139, 11}, Account: acc1}},
+               {acc2, cp4, nil,
+                       &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 169, 41}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 159, 31}, Account: acc2}},
+               {acc3, cp5, nil,
+                       &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 1, 1}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 179, 51}, Account: acc3}},
+               {acc3, cp6, nil,
+                       &addressRecoveryState{InternalBranch: &branchRecoveryState{addrRecoveryWindow, 189, 61}, ExternalBranch: &branchRecoveryState{addrRecoveryWindow, 179, 51}, Account: acc3}},
+       }
+
+       for _, c := range cases {
+               if err := recoveryMgr.reportFound(c.acct, c.cp); err != c.err {
+                       t.Fatal("ReportFound test err:", err, c.acct.ID)
                }
 
-               if acctMgr.GetContractIndex(acct.ID) != recAcctMgr.GetContractIndex(tmp.ID) {
-                       t.Fatal("bip32 address index recovery from xpubs err")
+               status, ok := recoveryMgr.state.AccountsStatus[c.acct.ID]
+               if !ok {
+                       t.Fatal("ReportFound test err: can not find status")
+               }
+               if !reflect.DeepEqual(status, c.status) {
+                       t.Log(c.status.Account, c.status.InternalBranch, c.status.ExternalBranch)
+                       t.Log(status.Account, status.InternalBranch, status.ExternalBranch)
+                       t.Fatal("ReportFound test err: recovery status error")
                }
        }
 }
@@ -314,6 +508,7 @@ func TestLoadStatusInfo(t *testing.T) {
        defer os.RemoveAll(dirPath)
 
        testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       testStore := NewMockWalletStore(testDB)
        defer os.RemoveAll("temp")
 
        hsm, err := pseudohsm.New(dirPath)
@@ -326,33 +521,58 @@ func TestLoadStatusInfo(t *testing.T) {
                t.Fatal(err)
        }
 
-       acctMgr := account.NewManager(testDB, nil)
-       recoveryMgr := newRecoveryManager(testDB, acctMgr)
+       acctStore := NewMockAccountStore(testDB)
+       acctMgr := account.NewManager(acctStore, nil)
+       recoveryMgr := NewRecoveryManager(testStore, acctMgr)
        // StatusInit init recovery status manager.
        recoveryMgr.state = newRecoveryState()
        recoveryMgr.state.XPubs = []chainkd.XPub{xpub.XPub}
        recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow)
 
        recoveryMgr.state.StartTime = time.Now()
+       if err := recoveryMgr.LoadStatusInfo(); err != nil {
+               t.Fatal("TestLoadStatusInfo err:", err)
+       }
+
        recoveryMgr.commitStatusInfo()
 
-       recoveryMgrRestore := newRecoveryManager(testDB, acctMgr)
-       recoveryMgrRestore.LoadStatusInfo()
+       recoveryMgrRestore := NewRecoveryManager(testStore, acctMgr)
+       if err := recoveryMgrRestore.LoadStatusInfo(); err != nil {
+               t.Fatal("TestLoadStatusInfo err:", err)
+       }
 
        if !reflect.DeepEqual(recoveryMgrRestore.state.XPubsStatus, recoveryMgr.state.XPubsStatus) {
-               t.Fatalf("testLoadStatusInfo XPubsStatus reload err")
+               t.Fatalf("TestLoadStatusInfo XPubsStatus reload err")
        }
 
        if !reflect.DeepEqual(recoveryMgrRestore.state.XPubs, recoveryMgr.state.XPubs) {
-               t.Fatalf("testLoadStatusInfo XPubs reload err")
+               t.Fatalf("TestLoadStatusInfo XPubs reload err")
        }
 
        if !reflect.DeepEqual(recoveryMgrRestore.state.AccountsStatus, recoveryMgr.state.AccountsStatus) {
-               t.Fatalf("testLoadStatusInfo AccountsStatus reload err")
+               t.Fatalf("TestLoadStatusInfo AccountsStatus reload err")
        }
 
        if !recoveryMgrRestore.state.StartTime.Equal(recoveryMgr.state.StartTime) {
-               t.Fatalf("testLoadStatusInfo StartTime reload err")
+               t.Fatalf("TestLoadStatusInfo StartTime reload err")
+       }
+
+       acct := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub.XPub}, KeyIndex: 1, DeriveRule: 3}}
+       recoveryMgr.state.AccountsStatus[acct.ID] = newAddressRecoveryState(addrRecoveryWindow, acct)
+       if err := recoveryMgr.commitStatusInfo(); err != nil {
+               t.Fatal("TestLoadStatusInfo err:", err)
+       }
+       if err := recoveryMgr.LoadStatusInfo(); err == nil {
+               t.Fatal("TestLoadStatusInfo err")
+       }
+
+       recoveryMgr.state = nil
+       if err := recoveryMgr.commitStatusInfo(); err != nil {
+               t.Fatal("TestLoadStatusInfo err:", err)
+       }
+
+       if err := recoveryMgr.LoadStatusInfo(); err == nil {
+               t.Fatal("TestLoadStatusInfo err")
        }
 }
 
@@ -364,10 +584,12 @@ func TestLock(t *testing.T) {
        defer os.RemoveAll(dirPath)
 
        testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       testStore := NewMockWalletStore(testDB)
        defer os.RemoveAll("temp")
 
-       acctMgr := account.NewManager(testDB, nil)
-       recoveryMgr := newRecoveryManager(testDB, acctMgr)
+       acctStore := NewMockAccountStore(testDB)
+       acctMgr := account.NewManager(acctStore, nil)
+       recoveryMgr := NewRecoveryManager(testStore, acctMgr)
        if !recoveryMgr.tryStartXPubsRec() {
                t.Fatal("recovery manager try lock test err")
        }
@@ -404,3 +626,84 @@ func TestStateForScope(t *testing.T) {
                t.Fatal("state for scope test err")
        }
 }
+
+func TestContractIndexResidue(t *testing.T) {
+       dirPath, err := ioutil.TempDir(".", "")
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer os.RemoveAll(dirPath)
+
+       testDB := dbm.NewDB("testdb", "leveldb", dirPath)
+       testStore := NewMockWalletStore(testDB)
+       hsm, err := pseudohsm.New(dirPath)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       contractIndexResidue := uint64(5)
+       acctStore := NewMockAccountStore(testDB)
+       acctMgr := account.NewManager(acctStore, nil)
+       recoveryMgr := NewRecoveryManager(testStore, acctMgr)
+       acct := &account.Account{ID: "testA", Alias: "test1", Signer: &signers.Signer{XPubs: []chainkd.XPub{xpub1.XPub}, KeyIndex: 1, DeriveRule: signers.BIP0044}}
+
+       cp1 := &account.CtrlProgram{AccountID: acct.ID, Address: "address1", KeyIndex: 10, Change: false}
+
+       setContractIndexKey := func(acctMgr *account.Manager, accountID string, change bool) {
+               testDB.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(contractIndexResidue))
+       }
+
+       delAccount := func(acctMgr *account.Manager, accountID string, change bool) {
+               acctMgr.DeleteAccount(accountID)
+       }
+
+       recoveryMgr.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow)
+       recoveryMgr.state.XPubs = []chainkd.XPub{xpub1.XPub}
+       recoveryMgr.state.stateForScope(acct)
+
+       cases := []struct {
+               acct       *account.Account
+               cp         *account.CtrlProgram
+               preProcess func(acctMgr *account.Manager, accountID string, change bool)
+               err        error
+               wantCPNum  uint64
+       }{
+               {acct, cp1, setContractIndexKey, nil, 5},
+               {acct, cp1, delAccount, nil, 10},
+       }
+
+       for _, c := range cases {
+               if c.preProcess != nil {
+                       c.preProcess(acctMgr, c.acct.ID, c.cp.Change)
+               }
+
+               if err := acctMgr.SaveAccount(acct); err != nil {
+                       t.Fatal("ReportFound test err:", err)
+               }
+
+               if err := recoveryMgr.reportFound(c.acct, c.cp); err != c.err {
+                       t.Fatal("ContractIndexResidue test err:", err, c.acct.ID)
+               }
+               cps, err := acctMgr.ListControlProgram()
+               if err != nil {
+                       t.Fatal("list control program err:", err)
+               }
+
+               cpNum := uint64(0)
+               for _, cp := range cps {
+                       if cp.Address == "" || cp.AccountID != c.acct.ID {
+                               continue
+                       }
+                       cpNum++
+               }
+
+               if cpNum != c.wantCPNum {
+                       t.Fatal("Test contract index residue cp num err want:", c.wantCPNum, " got:", cpNum)
+               }
+       }
+}