OSDN Git Service

Add import private key progress query (#331)
authoryahtoo <yahtoo.ma@gmail.com>
Wed, 31 Jan 2018 07:09:05 +0000 (15:09 +0800)
committerPaladz <yzhu101@uottawa.ca>
Wed, 31 Jan 2018 07:09:05 +0000 (15:09 +0800)
* Add import private key progress query

* Add xpub return when get progress

* Store account address index separately

* Add private key import complete flag

* Add import private key rollback process

* Refactor private key import code

* Fix the divisor zero problem

* Fix rescan progress return logic problem

* Fix variable reading and writing conflicts

blockchain/account/accounts.go
blockchain/reactor.go
blockchain/rpc_reactor.go
blockchain/wallet.go
blockchain/wallet/wallet.go
cmd/bytomcli/commands/bytomcli.go
cmd/bytomcli/commands/key.go

index 6d27c0e..71825ac 100755 (executable)
@@ -62,18 +62,13 @@ func CPKey(hash common.Hash) []byte {
 
 // NewManager creates a new account manager
 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
-       var nextIndex uint64
-       if index := walletDB.Get([]byte(keyNextIndex)); index != nil {
-               nextIndex = uint64(binary.LittleEndian.Uint64(index))
-       }
        return &Manager{
-               db:           walletDB,
-               chain:        chain,
-               utxoDB:       newReserver(chain, walletDB),
-               cache:        lru.New(maxAccountCache),
-               aliasCache:   lru.New(maxAccountCache),
-               delayedACPs:  make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
-               acpIndexNext: nextIndex,
+               db:          walletDB,
+               chain:       chain,
+               utxoDB:      newReserver(chain, walletDB),
+               cache:       lru.New(maxAccountCache),
+               aliasCache:  lru.New(maxAccountCache),
+               delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
        }
 }
 
@@ -90,10 +85,9 @@ type Manager struct {
        delayedACPsMu sync.Mutex
        delayedACPs   map[*txbuilder.TemplateBuilder][]*CtrlProgram
 
-       acpMu        sync.Mutex
-       acpIndexNext uint64 // next acp index in our block
-       acpIndexCap  uint64 // points to end of block
-       accIndexMu   sync.Mutex
+       acpMu       sync.Mutex
+       acpIndexCap uint64 // points to end of block
+       accIndexMu  sync.Mutex
 }
 
 // ExpireReservations removes reservations that have expired periodically.
@@ -255,6 +249,7 @@ func (m *Manager) GetAliasByID(id string) string {
        return account.Alias
 }
 
+// CreateAddress generate an address for the select account
 func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool) (cp *CtrlProgram, err error) {
        account, err := m.findByID(ctx, accountID)
        if err != nil {
@@ -281,7 +276,7 @@ func (m *Manager) createAddress(ctx context.Context, account *Account, change bo
 }
 
 func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
-       idx := m.nextIndex()
+       idx := m.nextIndex(account)
        path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
        derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
        derivedPK := derivedXPubs[0].PublicKey()
@@ -308,7 +303,7 @@ func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool
 }
 
 func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
-       idx := m.nextIndex()
+       idx := m.nextIndex(account)
        path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
        derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
        derivedPKs := chainkd.XPubKeys(derivedXPubs)
@@ -383,16 +378,27 @@ func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
        return program.ControlProgram, nil
 }
 
-func (m *Manager) nextIndex() uint64 {
+func (m *Manager) nextIndex(account *Account) uint64 {
        m.acpMu.Lock()
        defer m.acpMu.Unlock()
 
-       n := m.acpIndexNext
-       m.acpIndexNext++
+       key := make([]byte, 0)
+       key = append(key, account.Signer.XPubs[0].Bytes()...)
+
+       accountIndex := make([]byte, 8)
+       binary.LittleEndian.PutUint64(accountIndex[:], account.Signer.KeyIndex)
+       key = append(key, accountIndex[:]...)
+
+       var nextIndex uint64 = 1
+       if rawIndex := m.db.Get(key); rawIndex != nil {
+               nextIndex = uint64(binary.LittleEndian.Uint64(rawIndex)) + 1
+       }
+
        buf := make([]byte, 8)
-       binary.LittleEndian.PutUint64(buf, m.acpIndexNext)
-       m.db.Set([]byte(keyNextIndex), buf)
-       return n
+       binary.LittleEndian.PutUint64(buf, nextIndex)
+       m.db.Set(key, buf)
+
+       return nextIndex
 }
 
 // DeleteAccount deletes the account's ID or alias matching accountInfo.
index b9931da..6ce6723 100755 (executable)
@@ -50,10 +50,12 @@ type Response struct {
        Data   interface{} `json:"data,omitempty"`
 }
 
+//NewSuccessResponse success response
 func NewSuccessResponse(data interface{}) Response {
        return Response{Status: SUCCESS, Data: data}
 }
 
+//NewErrorResponse error response
 func NewErrorResponse(err error) Response {
        return Response{Status: FAIL, Msg: err.Error()}
 }
@@ -308,7 +310,7 @@ func (bcr *BlockchainReactor) syncRoutine() {
        }
 }
 
-// BroadcastStatusRequest broadcasts `BlockStore` height.
+// BroadcastStatusResponse broadcasts `BlockStore` height.
 func (bcr *BlockchainReactor) BroadcastStatusResponse() {
        block := bcr.chain.BestBlock()
        bcr.Switch.Broadcast(BlockchainChannel, struct{ BlockchainMessage }{NewStatusResponseMessage(block)})
index 542b002..86239e0 100644 (file)
@@ -99,6 +99,7 @@ func (bcr *BlockchainReactor) BuildHandler() {
 
        m.Handle("/export-private-key", jsonHandler(bcr.walletExportKey))
        m.Handle("/import-private-key", jsonHandler(bcr.walletImportKey))
+       m.Handle("/import-key-progress", jsonHandler(bcr.keyImportProgress))
 
        m.Handle("/get-block-header-by-hash", jsonHandler(bcr.getBlockHeaderByHash))
        m.Handle("/get-block-by-hash", jsonHandler(bcr.getBlockByHash))
@@ -124,6 +125,7 @@ func (bcr *BlockchainReactor) BuildHandler() {
        bcr.handler = handler
 }
 
+//AuthHandler access token auth handler
 func AuthHandler(handler http.Handler, accessTokens *accesstoken.CredentialStore) http.Handler {
 
        authenticator := authn.NewAPI(accessTokens)
index d314f14..e470a4c 100644 (file)
@@ -11,6 +11,7 @@ import (
        "github.com/bytom/errors"
 )
 
+//KeyImportParams private key import param
 type KeyImportParams struct {
        KeyAlias     string `json:"alias"`
        Password     string `json:"password"`
@@ -59,3 +60,11 @@ func (bcr *BlockchainReactor) walletImportKey(ctx context.Context, in KeyImportP
        }
        return NewSuccessResponse(xpub)
 }
+
+func (bcr *BlockchainReactor) keyImportProgress(ctx context.Context) Response {
+       data, err := bcr.wallet.GetRescanStatus()
+       if err != nil {
+               return NewErrorResponse(err)
+       }
+       return NewSuccessResponse(data)
+}
index dd8437c..e8fe6fe 100755 (executable)
@@ -21,11 +21,22 @@ import (
 const SINGLE = 1
 
 var walletKey = []byte("walletInfo")
+var privKeyKey = []byte("keysInfo")
 
 //StatusInfo is base valid block info to handle orphan block rollback
 type StatusInfo struct {
-       Height uint64
-       Hash   bc.Hash
+       WorkHeight uint64
+       WorkHash   bc.Hash
+       BestHeight uint64
+       BestHash   bc.Hash
+}
+
+//KeyInfo is key import status
+type KeyInfo struct {
+       Alias    string       `json:"alias"`
+       XPub     chainkd.XPub `json:"xpub"`
+       Percent  uint8        `json:"percent"`
+       Complete bool         `json:"complete"`
 }
 
 //Wallet is related to storing account unspent outputs
@@ -36,6 +47,8 @@ type Wallet struct {
        AssetReg       *asset.Registry
        chain          *protocol.Chain
        rescanProgress chan struct{}
+       ImportPrivKey  bool
+       keysInfo       []KeyInfo
 }
 
 //NewWallet return a new wallet instance
@@ -46,13 +59,20 @@ func NewWallet(walletDB db.DB, account *account.Manager, asset *asset.Registry,
                AssetReg:       asset,
                chain:          chain,
                rescanProgress: make(chan struct{}, 1),
+               keysInfo:       make([]KeyInfo, 0),
        }
 
        if err := w.loadWalletInfo(); err != nil {
                return nil, err
        }
 
+       if err := w.loadKeysInfo(); err != nil {
+               return nil, err
+       }
+
+       w.ImportPrivKey = w.getImportKeyFlag()
        go w.walletUpdater()
+
        return w, nil
 }
 
@@ -67,10 +87,7 @@ func (w *Wallet) loadWalletInfo() error {
        if err != nil {
                return err
        }
-       if err := w.attachBlock(block); err != nil {
-               return err
-       }
-       return nil
+       return w.attachBlock(block)
 }
 
 func (w *Wallet) commitWalletInfo(batch db.Batch) error {
@@ -85,8 +102,37 @@ func (w *Wallet) commitWalletInfo(batch db.Batch) error {
        return nil
 }
 
+//GetWalletInfo return stored wallet info and nil,if error,
+//return initial wallet info and err
+func (w *Wallet) loadKeysInfo() error {
+       if rawKeyInfo := w.DB.Get(privKeyKey); rawKeyInfo != nil {
+               json.Unmarshal(rawKeyInfo, &w.keysInfo)
+               return nil
+       }
+       return nil
+}
+
+func (w *Wallet) commitkeysInfo() error {
+       rawKeysInfo, err := json.Marshal(w.keysInfo)
+       if err != nil {
+               log.WithField("err", err).Error("save wallet info")
+               return err
+       }
+       w.DB.Set(privKeyKey, rawKeysInfo)
+       return nil
+}
+
+func (w *Wallet) getImportKeyFlag() bool {
+       for _, v := range w.keysInfo {
+               if v.Complete == false {
+                       return true
+               }
+       }
+       return false
+}
+
 func (w *Wallet) attachBlock(block *legacy.Block) error {
-       if block.PreviousBlockHash != w.status.Hash {
+       if block.PreviousBlockHash != w.status.WorkHash {
                log.Warn("wallet skip attachBlock due to status hash not equal to previous hash")
                return nil
        }
@@ -95,27 +141,39 @@ func (w *Wallet) attachBlock(block *legacy.Block) error {
        w.indexTransactions(storeBatch, block)
        w.buildAccountUTXOs(storeBatch, block)
 
-       w.status.Height = block.Height
-       w.status.Hash = block.Hash()
+       w.status.WorkHeight = block.Height
+       w.status.WorkHash = block.Hash()
+       if w.status.WorkHeight >= w.status.BestHeight {
+               w.status.BestHeight = w.status.WorkHeight
+               w.status.BestHash = w.status.WorkHash
+       }
        return w.commitWalletInfo(storeBatch)
 }
 
 func (w *Wallet) detachBlock(block *legacy.Block) error {
        storeBatch := w.DB.NewBatch()
        w.reverseAccountUTXOs(storeBatch, block)
-       w.deleteTransactions(storeBatch, w.status.Height)
+       w.deleteTransactions(storeBatch, w.status.BestHeight)
+
+       w.status.BestHeight = block.Height - 1
+       w.status.BestHash = block.PreviousBlockHash
+
+       if w.status.WorkHeight > w.status.BestHeight {
+               w.status.WorkHeight = w.status.BestHeight
+               w.status.WorkHash = w.status.BestHash
+       }
 
-       w.status.Height = block.Height - 1
-       w.status.Hash = block.PreviousBlockHash
        return w.commitWalletInfo(storeBatch)
 }
 
 //WalletUpdate process every valid block and reverse every invalid block which need to rollback
 func (w *Wallet) walletUpdater() {
        for {
+               // config.GenesisBlock().hash
                getRescanNotification(w)
-               for !w.chain.InMainChain(w.status.Height, w.status.Hash) {
-                       block, err := w.chain.GetBlockByHash(&w.status.Hash)
+               checkRescanStatus(w)
+               for !w.chain.InMainChain(w.status.BestHeight, w.status.BestHash) {
+                       block, err := w.chain.GetBlockByHash(&w.status.BestHash)
                        if err != nil {
                                log.WithField("err", err).Error("walletUpdater GetBlockByHash")
                                return
@@ -127,9 +185,9 @@ func (w *Wallet) walletUpdater() {
                        }
                }
 
-               block, _ := w.chain.GetBlockByHeight(w.status.Height + 1)
+               block, _ := w.chain.GetBlockByHeight(w.status.WorkHeight + 1)
                if block == nil {
-                       <-w.chain.BlockWaiter(w.status.Height + 1)
+                       <-w.chain.BlockWaiter(w.status.WorkHeight + 1)
                        continue
                }
 
@@ -143,9 +201,9 @@ func (w *Wallet) walletUpdater() {
 func getRescanNotification(w *Wallet) {
        select {
        case <-w.rescanProgress:
-               w.status.Height = 1
-               block, _ := w.chain.GetBlockByHeight(w.status.Height)
-               w.status.Hash = block.Hash()
+               w.status.WorkHeight = 0
+               block, _ := w.chain.GetBlockByHeight(w.status.WorkHeight)
+               w.status.WorkHash = block.Hash()
        default:
                return
        }
@@ -188,16 +246,24 @@ func (w *Wallet) ImportAccountPrivKey(hsm *pseudohsm.HSM, xprv chainkd.XPrv, key
        if err != nil {
                return nil, err
        }
-       if err := w.recoveryAccountWalletDB(newAccount, xpub, index); err != nil {
+       if err := w.recoveryAccountWalletDB(newAccount, xpub, index, keyAlias); err != nil {
                return nil, err
        }
        return xpub, nil
 }
 
-func (w *Wallet) recoveryAccountWalletDB(account *account.Account, XPub *pseudohsm.XPub, index uint64) error {
+func (w *Wallet) recoveryAccountWalletDB(account *account.Account, XPub *pseudohsm.XPub, index uint64, keyAlias string) error {
        if err := w.createProgram(account, XPub, index); err != nil {
                return err
        }
+       w.ImportPrivKey = true
+       tmp := KeyInfo{
+               Alias:    keyAlias,
+               XPub:     XPub.XPub,
+               Complete: false,
+       }
+       w.keysInfo = append(w.keysInfo, tmp)
+       w.commitkeysInfo()
        w.rescanBlocks()
 
        return nil
@@ -212,7 +278,51 @@ func (w *Wallet) createProgram(account *account.Account, XPub *pseudohsm.XPub, i
        return nil
 }
 
-//WalletUpdate process every valid block and reverse every invalid block which need to rollback
 func (w *Wallet) rescanBlocks() {
        w.rescanProgress <- struct{}{}
 }
+
+//GetRescanStatus return key import rescan status
+func (w *Wallet) GetRescanStatus() ([]KeyInfo, error) {
+       keysInfo := make([]KeyInfo, len(w.keysInfo))
+
+       if rawKeyInfo := w.DB.Get(privKeyKey); rawKeyInfo != nil {
+               if err := json.Unmarshal(rawKeyInfo, &keysInfo); err != nil {
+                       return nil, err
+               }
+       }
+
+       var status StatusInfo
+       if rawWallet := w.DB.Get(walletKey); rawWallet != nil {
+               if err := json.Unmarshal(rawWallet, &status); err != nil {
+                       return nil, err
+               }
+       }
+
+       for i, v := range keysInfo {
+               if v.Complete == true || status.BestHeight == 0 {
+                       keysInfo[i].Percent = 100
+                       continue
+               }
+
+               keysInfo[i].Percent = uint8(status.WorkHeight * 100 / status.BestHeight)
+               if v.Percent == 100 {
+                       keysInfo[i].Complete = true
+               }
+       }
+       return keysInfo, nil
+}
+
+func checkRescanStatus(w *Wallet) {
+       if !w.ImportPrivKey {
+               return
+       }
+       if w.status.WorkHeight >= w.status.BestHeight {
+               w.ImportPrivKey = false
+               for i := range w.keysInfo {
+                       w.keysInfo[i].Complete = true
+               }
+       }
+
+       w.commitkeysInfo()
+}
index c51b01b..5a30d73 100644 (file)
@@ -111,6 +111,7 @@ func AddCommands() {
        BytomcliCmd.AddCommand(listKeysCmd)
        BytomcliCmd.AddCommand(exportPrivateCmd)
        BytomcliCmd.AddCommand(importPrivateCmd)
+       BytomcliCmd.AddCommand(importKeyProgressCmd)
 
        BytomcliCmd.AddCommand(isMiningCmd)
 
index 6cc3e6f..792d39c 100644 (file)
@@ -2,6 +2,7 @@ package commands
 
 import (
        "encoding/hex"
+       "fmt"
        "os"
        "strconv"
 
@@ -121,3 +122,16 @@ var importPrivateCmd = &cobra.Command{
                printJSON(data)
        },
 }
+
+var importKeyProgressCmd = &cobra.Command{
+       Use:   "import-key-progress",
+       Short: "Get import private key progress info",
+       Args:  cobra.NoArgs,
+       Run: func(cmd *cobra.Command, args []string) {
+               data, exitCode := util.ClientCall("/import-key-progress")
+               if exitCode != util.Success {
+                       os.Exit(exitCode)
+               }
+               fmt.Println("data:", data)
+       },
+}