// 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),
}
}
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.
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 {
}
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()
}
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)
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.
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
AssetReg *asset.Registry
chain *protocol.Chain
rescanProgress chan struct{}
+ ImportPrivKey bool
+ keysInfo []KeyInfo
}
//NewWallet return a new wallet instance
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
}
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 {
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
}
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
}
}
- 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
}
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
}
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
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()
+}