OSDN Git Service

Merge pull request #201 from Bytom/v0.1
[bytom/vapor.git] / wallet / recovery_test.go
index 1aa034c..eff5850 100644 (file)
@@ -9,14 +9,15 @@ import (
        "testing"
        "time"
 
-       dbm "github.com/tendermint/tmlibs/db"
-
        "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"
 )
@@ -57,7 +58,7 @@ func MockSimpleUtxo(index uint64, assetID *bc.AssetID, amount uint64, ctrlProg *
 }
 
 func AddTxOutput(assetID bc.AssetID, amount uint64, controlProgram []byte) *types.TxOutput {
-       out := types.NewTxOutput(assetID, amount, controlProgram)
+       out := types.NewIntraChainOutput(assetID, amount, controlProgram)
        return out
 }
 
@@ -127,6 +128,55 @@ 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)
+       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)
+       }
+
+       acctMgr := account.NewManager(testDB, nil)
+       recoveryMgr := newRecoveryManager(testDB, 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 {
@@ -205,36 +255,54 @@ func TestRecoveryFromXPubs(t *testing.T) {
        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)
-       }
 
-       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 {
+                               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()
        }
 }
 
@@ -276,32 +344,140 @@ func TestRecoveryByRescanAccount(t *testing.T) {
        }
 
        recoveryMgr := newRecoveryManager(recoveryDB, recAcctMgr)
-       if err := recoveryMgr.AddrResurrect(allAccounts); err != nil {
-               t.Fatal("recovery by rescan account err:", err)
+
+       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 {
+                               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")
+                       }
                }
+       }
+
+}
 
-               if acctMgr.GetBip44ContractIndex(acct.ID, false) != recAcctMgr.GetBip44ContractIndex(tmp.ID, false) {
-                       t.Fatal("bip44 external 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)
+       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)
+       }
+
+       acctMgr := account.NewManager(testDB, nil)
+       recoveryMgr := newRecoveryManager(testDB, 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)
+
+       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")
                }
        }
 }
@@ -334,25 +510,49 @@ func TestLoadStatusInfo(t *testing.T) {
        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()
+       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")
        }
 }
 
@@ -404,3 +604,91 @@ func TestStateForScope(t *testing.T) {
                t.Fatal("state for scope test err")
        }
 }
+
+func bip44ContractIndexKey(accountID string, change bool) []byte {
+       contractIndexPrefix := []byte("ContractIndex")
+       key := append(contractIndexPrefix, accountID...)
+       if change {
+               return append(key, []byte{1}...)
+       }
+       return append(key, []byte{0}...)
+}
+
+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)
+       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)
+       acctMgr := account.NewManager(testDB, nil)
+       recoveryMgr := newRecoveryManager(testDB, 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)
+               }
+       }
+}