7 log "github.com/sirupsen/logrus"
8 "github.com/tendermint/go-wire/data/base58"
9 "github.com/tendermint/tmlibs/db"
11 "github.com/bytom/blockchain/account"
12 "github.com/bytom/blockchain/asset"
13 "github.com/bytom/blockchain/pseudohsm"
14 "github.com/bytom/crypto/ed25519/chainkd"
15 "github.com/bytom/crypto/sha3pool"
16 "github.com/bytom/protocol"
17 "github.com/bytom/protocol/bc"
18 "github.com/bytom/protocol/bc/legacy"
24 //RecoveryIndex walletdb recovery cp number
25 const RecoveryIndex = 5000
27 var walletKey = []byte("walletInfo")
28 var privKeyKey = []byte("keysInfo")
30 //StatusInfo is base valid block info to handle orphan block rollback
31 type StatusInfo struct {
38 //KeyInfo is key import status
40 Alias string `json:"alias"`
41 XPub chainkd.XPub `json:"xpub"`
42 Percent uint8 `json:"percent"`
43 Complete bool `json:"complete"`
46 //Wallet is related to storing account unspent outputs
50 AccountMgr *account.Manager
51 AssetReg *asset.Registry
53 rescanProgress chan struct{}
58 //NewWallet return a new wallet instance
59 func NewWallet(walletDB db.DB, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, xpubs []pseudohsm.XPub) (*Wallet, error) {
65 rescanProgress: make(chan struct{}, 1),
66 keysInfo: make([]KeyInfo, 0),
69 if err := w.loadWalletInfo(xpubs); err != nil {
73 if err := w.loadKeysInfo(); err != nil {
77 w.ImportPrivKey = w.getImportKeyFlag()
84 //GetWalletInfo return stored wallet info and nil,if error,
85 //return initial wallet info and err
86 func (w *Wallet) loadWalletInfo(xpubs []pseudohsm.XPub) error {
87 if rawWallet := w.DB.Get(walletKey); rawWallet != nil {
88 return json.Unmarshal(rawWallet, &w.status)
91 for i, v := range xpubs {
92 if err := w.ImportAccountXpubKey(i, v, RecoveryIndex); err != nil {
97 block, err := w.chain.GetBlockByHeight(0)
101 return w.attachBlock(block)
104 func (w *Wallet) commitWalletInfo(batch db.Batch) error {
105 rawWallet, err := json.Marshal(w.status)
107 log.WithField("err", err).Error("save wallet info")
111 batch.Set(walletKey, rawWallet)
116 //GetWalletInfo return stored wallet info and nil,if error,
117 //return initial wallet info and err
118 func (w *Wallet) loadKeysInfo() error {
119 if rawKeyInfo := w.DB.Get(privKeyKey); rawKeyInfo != nil {
120 json.Unmarshal(rawKeyInfo, &w.keysInfo)
126 func (w *Wallet) commitkeysInfo() error {
127 rawKeysInfo, err := json.Marshal(w.keysInfo)
129 log.WithField("err", err).Error("save wallet info")
132 w.DB.Set(privKeyKey, rawKeysInfo)
136 func (w *Wallet) getImportKeyFlag() bool {
137 for _, v := range w.keysInfo {
138 if v.Complete == false {
145 func (w *Wallet) attachBlock(block *legacy.Block) error {
146 if block.PreviousBlockHash != w.status.WorkHash {
147 log.Warn("wallet skip attachBlock due to status hash not equal to previous hash")
151 storeBatch := w.DB.NewBatch()
152 w.indexTransactions(storeBatch, block)
153 w.buildAccountUTXOs(storeBatch, block)
155 w.status.WorkHeight = block.Height
156 w.status.WorkHash = block.Hash()
157 if w.status.WorkHeight >= w.status.BestHeight {
158 w.status.BestHeight = w.status.WorkHeight
159 w.status.BestHash = w.status.WorkHash
161 return w.commitWalletInfo(storeBatch)
164 func (w *Wallet) detachBlock(block *legacy.Block) error {
165 storeBatch := w.DB.NewBatch()
166 w.reverseAccountUTXOs(storeBatch, block)
167 w.deleteTransactions(storeBatch, w.status.BestHeight)
169 w.status.BestHeight = block.Height - 1
170 w.status.BestHash = block.PreviousBlockHash
172 if w.status.WorkHeight > w.status.BestHeight {
173 w.status.WorkHeight = w.status.BestHeight
174 w.status.WorkHash = w.status.BestHash
177 return w.commitWalletInfo(storeBatch)
180 //WalletUpdate process every valid block and reverse every invalid block which need to rollback
181 func (w *Wallet) walletUpdater() {
183 getRescanNotification(w)
185 for !w.chain.InMainChain(w.status.BestHeight, w.status.BestHash) {
186 block, err := w.chain.GetBlockByHash(&w.status.BestHash)
188 log.WithField("err", err).Error("walletUpdater GetBlockByHash")
192 if err := w.detachBlock(block); err != nil {
193 log.WithField("err", err).Error("walletUpdater detachBlock")
198 block, _ := w.chain.GetBlockByHeight(w.status.WorkHeight + 1)
200 <-w.chain.BlockWaiter(w.status.WorkHeight + 1)
204 if err := w.attachBlock(block); err != nil {
205 log.WithField("err", err).Error("walletUpdater stop")
211 func getRescanNotification(w *Wallet) {
213 case <-w.rescanProgress:
214 w.status.WorkHeight = 0
215 block, _ := w.chain.GetBlockByHeight(w.status.WorkHeight)
216 w.status.WorkHash = block.Hash()
222 // ExportAccountPrivKey exports the account private key as a WIF for encoding as a string
223 // in the Wallet Import Formt.
224 func (w *Wallet) ExportAccountPrivKey(hsm *pseudohsm.HSM, xpub chainkd.XPub, auth string) (*string, error) {
225 xprv, err := hsm.LoadChainKDKey(xpub, auth)
230 sha3pool.Sum256(hashed[:], xprv[:])
232 tmp := append(xprv[:], hashed[:4]...)
233 res := base58.Encode(tmp)
237 // ImportAccountPrivKey imports the account key in the Wallet Import Formt.
238 func (w *Wallet) ImportAccountPrivKey(hsm *pseudohsm.HSM, xprv chainkd.XPrv, keyAlias, auth string, index uint64, accountAlias string) (*pseudohsm.XPub, error) {
239 if hsm.HasAlias(keyAlias) {
240 return nil, pseudohsm.ErrDuplicateKeyAlias
242 if hsm.HasKey(xprv) {
243 return nil, pseudohsm.ErrDuplicateKey
246 if acc, _ := w.AccountMgr.FindByAlias(nil, accountAlias); acc != nil {
247 return nil, account.ErrDuplicateAlias
250 xpub, _, err := hsm.ImportXPrvKey(auth, keyAlias, xprv)
255 newAccount, err := w.AccountMgr.Create(nil, []chainkd.XPub{xpub.XPub}, SINGLE, accountAlias, nil)
259 if err := w.recoveryAccountWalletDB(newAccount, xpub, index, keyAlias); err != nil {
265 // ImportAccountXpubKey imports the account key in the Wallet Import Formt.
266 func (w *Wallet) ImportAccountXpubKey(xpubIndex int, xpub pseudohsm.XPub, cpIndex uint64) error {
267 accountAlias := fmt.Sprintf("recovery_%d", xpubIndex)
269 if acc, _ := w.AccountMgr.FindByAlias(nil, accountAlias); acc != nil {
270 return account.ErrDuplicateAlias
273 newAccount, err := w.AccountMgr.Create(nil, []chainkd.XPub{xpub.XPub}, SINGLE, accountAlias, nil)
278 return w.recoveryAccountWalletDB(newAccount, &xpub, cpIndex, xpub.Alias)
281 func (w *Wallet) recoveryAccountWalletDB(account *account.Account, XPub *pseudohsm.XPub, index uint64, keyAlias string) error {
282 if err := w.createProgram(account, XPub, index); err != nil {
285 w.ImportPrivKey = true
291 w.keysInfo = append(w.keysInfo, tmp)
298 func (w *Wallet) createProgram(account *account.Account, XPub *pseudohsm.XPub, index uint64) error {
299 for i := uint64(0); i < index; i++ {
300 if _, err := w.AccountMgr.CreateAddress(nil, account.ID, false); err != nil {
307 func (w *Wallet) rescanBlocks() {
309 case <-w.rescanProgress:
310 w.rescanProgress <- struct{}{}
316 //GetRescanStatus return key import rescan status
317 func (w *Wallet) GetRescanStatus() ([]KeyInfo, error) {
318 keysInfo := make([]KeyInfo, len(w.keysInfo))
320 if rawKeyInfo := w.DB.Get(privKeyKey); rawKeyInfo != nil {
321 if err := json.Unmarshal(rawKeyInfo, &keysInfo); err != nil {
326 var status StatusInfo
327 if rawWallet := w.DB.Get(walletKey); rawWallet != nil {
328 if err := json.Unmarshal(rawWallet, &status); err != nil {
333 for i, v := range keysInfo {
334 if v.Complete == true || status.BestHeight == 0 {
335 keysInfo[i].Percent = 100
339 keysInfo[i].Percent = uint8(status.WorkHeight * 100 / status.BestHeight)
340 if v.Percent == 100 {
341 keysInfo[i].Complete = true
347 func checkRescanStatus(w *Wallet) {
348 if !w.ImportPrivKey {
351 if w.status.WorkHeight >= w.status.BestHeight {
352 w.ImportPrivKey = false
353 for i := range w.keysInfo {
354 w.keysInfo[i].Complete = true