7 log "github.com/sirupsen/logrus"
8 "github.com/tendermint/tmlibs/db"
10 "github.com/vapor/account"
11 "github.com/vapor/asset"
12 "github.com/vapor/blockchain/pseudohsm"
13 "github.com/vapor/common"
14 "github.com/vapor/protocol"
15 "github.com/vapor/protocol/bc"
16 "github.com/vapor/protocol/bc/types"
24 var walletKey = []byte("walletInfo")
26 //StatusInfo is base valid block info to handle orphan block rollback
27 type StatusInfo struct {
34 //Wallet is related to storing account unspent outputs
39 AccountMgr *account.Manager
40 AssetReg *asset.Registry
43 RecoveryMgr *recoveryManager
44 rescanCh chan struct{}
45 dposAddress common.Address
48 //NewWallet return a new wallet instance
49 func NewWallet(walletDB db.DB, account *account.Manager, asset *asset.Registry, hsm *pseudohsm.HSM, chain *protocol.Chain, dposAddress common.Address) (*Wallet, error) {
56 RecoveryMgr: newRecoveryManager(walletDB, account),
57 rescanCh: make(chan struct{}, 1),
58 dposAddress: dposAddress,
61 if err := w.loadWalletInfo(); err != nil {
65 if err := w.RecoveryMgr.LoadStatusInfo(); err != nil {
70 go w.delUnconfirmedTx()
74 //GetWalletInfo return stored wallet info and nil,if error,
75 //return initial wallet info and err
76 func (w *Wallet) loadWalletInfo() error {
77 if rawWallet := w.DB.Get(walletKey); rawWallet != nil {
78 return json.Unmarshal(rawWallet, &w.status)
81 block, err := w.chain.GetBlockByHeight(0)
85 return w.AttachBlock(block)
88 func (w *Wallet) commitWalletInfo(batch db.Batch) error {
89 rawWallet, err := json.Marshal(w.status)
91 log.WithField("err", err).Error("save wallet info")
95 batch.Set(walletKey, rawWallet)
100 // AttachBlock attach a new block
101 func (w *Wallet) AttachBlock(block *types.Block) error {
105 if block.PreviousBlockHash != w.status.WorkHash {
106 log.Warn("wallet skip attachBlock due to status hash not equal to previous hash")
110 blockHash := block.Hash()
111 txStatus, err := w.chain.GetTransactionStatus(&blockHash)
116 if err := w.RecoveryMgr.FilterRecoveryTxs(block); err != nil {
120 storeBatch := w.DB.NewBatch()
121 if err := w.indexTransactions(storeBatch, block, txStatus); err != nil {
125 w.attachUtxos(storeBatch, block, txStatus)
126 w.status.WorkHeight = block.Height
127 w.status.WorkHash = block.Hash()
128 if w.status.WorkHeight >= w.status.BestHeight {
129 w.status.BestHeight = w.status.WorkHeight
130 w.status.BestHash = w.status.WorkHash
132 return w.commitWalletInfo(storeBatch)
135 // DetachBlock detach a block and rollback state
136 func (w *Wallet) DetachBlock(block *types.Block) error {
140 blockHash := block.Hash()
141 txStatus, err := w.chain.GetTransactionStatus(&blockHash)
146 storeBatch := w.DB.NewBatch()
147 w.detachUtxos(storeBatch, block, txStatus)
148 w.deleteTransactions(storeBatch, w.status.BestHeight)
150 w.status.BestHeight = block.Height - 1
151 w.status.BestHash = block.PreviousBlockHash
153 if w.status.WorkHeight > w.status.BestHeight {
154 w.status.WorkHeight = w.status.BestHeight
155 w.status.WorkHash = w.status.BestHash
158 return w.commitWalletInfo(storeBatch)
161 //WalletUpdate process every valid block and reverse every invalid block which need to rollback
162 func (w *Wallet) walletUpdater() {
164 w.getRescanNotification()
165 for !w.chain.InMainChain(w.status.BestHash) {
166 block, err := w.chain.GetBlockByHash(&w.status.BestHash)
168 log.WithField("err", err).Error("walletUpdater GetBlockByHash")
172 if err := w.DetachBlock(block); err != nil {
173 log.WithField("err", err).Error("walletUpdater detachBlock stop")
178 block, _ := w.chain.GetBlockByHeight(w.status.WorkHeight + 1)
180 w.walletBlockWaiter()
184 if err := w.AttachBlock(block); err != nil {
185 log.WithField("err", err).Error("walletUpdater AttachBlock stop")
191 //RescanBlocks provide a trigger to rescan blocks
192 func (w *Wallet) RescanBlocks() {
194 case w.rescanCh <- struct{}{}:
200 // deleteAccountTxs deletes all txs in wallet
201 func (w *Wallet) deleteAccountTxs() {
202 storeBatch := w.DB.NewBatch()
204 txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
205 defer txIter.Release()
208 storeBatch.Delete(txIter.Key())
211 txIndexIter := w.DB.IteratorPrefix([]byte(TxIndexPrefix))
212 defer txIndexIter.Release()
214 for txIndexIter.Next() {
215 storeBatch.Delete(txIndexIter.Key())
221 // DeleteAccount deletes account matching accountID, then rescan wallet
222 func (w *Wallet) DeleteAccount(accountID string) (err error) {
226 if err := w.AccountMgr.DeleteAccount(accountID); err != nil {
235 func (w *Wallet) UpdateAccountAlias(accountID string, newAlias string) (err error) {
239 if err := w.AccountMgr.UpdateAccountAlias(accountID, newAlias); err != nil {
248 func (w *Wallet) getRescanNotification() {
257 func (w *Wallet) setRescanStatus() {
258 block, _ := w.chain.GetBlockByHeight(0)
259 w.status.WorkHash = bc.Hash{}
263 func (w *Wallet) walletBlockWaiter() {
265 case <-w.chain.BlockWaiter(w.status.WorkHeight + 1):
271 // GetWalletStatusInfo return current wallet StatusInfo
272 func (w *Wallet) GetWalletStatusInfo() StatusInfo {