github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/wallet/wallet.go (about)

     1  package wallet
     2  
     3  import (
     4  	"encoding/json"
     5  	"sync"
     6  
     7  	log "github.com/sirupsen/logrus"
     8  
     9  	"github.com/bytom/bytom/account"
    10  	"github.com/bytom/bytom/asset"
    11  	"github.com/bytom/bytom/blockchain/pseudohsm"
    12  	dbm "github.com/bytom/bytom/database/leveldb"
    13  	"github.com/bytom/bytom/errors"
    14  	"github.com/bytom/bytom/event"
    15  	"github.com/bytom/bytom/protocol"
    16  	"github.com/bytom/bytom/protocol/bc"
    17  	"github.com/bytom/bytom/protocol/bc/types"
    18  )
    19  
    20  const (
    21  	//SINGLE single sign
    22  	SINGLE    = 1
    23  	logModule = "wallet"
    24  )
    25  
    26  var (
    27  	currentVersion = uint(1)
    28  	walletKey      = []byte("walletInfo")
    29  
    30  	errBestBlockNotFoundInCore = errors.New("best block not found in core")
    31  	errWalletVersionMismatch   = errors.New("wallet version mismatch")
    32  )
    33  
    34  //StatusInfo is base valid block info to handle orphan block rollback
    35  type StatusInfo struct {
    36  	Version    uint
    37  	WorkHeight uint64
    38  	WorkHash   bc.Hash
    39  	BestHeight uint64
    40  	BestHash   bc.Hash
    41  }
    42  
    43  //Wallet is related to storing account unspent outputs
    44  type Wallet struct {
    45  	DB              dbm.DB
    46  	rw              sync.RWMutex
    47  	status          StatusInfo
    48  	TxIndexFlag     bool
    49  	AccountMgr      *account.Manager
    50  	AssetReg        *asset.Registry
    51  	Hsm             *pseudohsm.HSM
    52  	chain           *protocol.Chain
    53  	RecoveryMgr     *recoveryManager
    54  	eventDispatcher *event.Dispatcher
    55  	txMsgSub        *event.Subscription
    56  
    57  	rescanCh chan struct{}
    58  }
    59  
    60  //NewWallet return a new wallet instance
    61  func NewWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry, hsm *pseudohsm.HSM, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) (*Wallet, error) {
    62  	w := &Wallet{
    63  		DB:              walletDB,
    64  		AccountMgr:      account,
    65  		AssetReg:        asset,
    66  		chain:           chain,
    67  		Hsm:             hsm,
    68  		RecoveryMgr:     newRecoveryManager(walletDB, account),
    69  		eventDispatcher: dispatcher,
    70  		rescanCh:        make(chan struct{}, 1),
    71  		TxIndexFlag:     txIndexFlag,
    72  	}
    73  
    74  	if err := w.loadWalletInfo(); err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	if err := w.RecoveryMgr.LoadStatusInfo(); err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	var err error
    83  	w.txMsgSub, err = w.eventDispatcher.Subscribe(protocol.TxMsgEvent{})
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	go w.walletUpdater()
    89  	go w.delUnconfirmedTx()
    90  	go w.memPoolTxQueryLoop()
    91  	return w, nil
    92  }
    93  
    94  // memPoolTxQueryLoop constantly pass a transaction accepted by mempool to the wallet.
    95  func (w *Wallet) memPoolTxQueryLoop() {
    96  	for {
    97  		select {
    98  		case obj, ok := <-w.txMsgSub.Chan():
    99  			if !ok {
   100  				log.WithFields(log.Fields{"module": logModule}).Warning("tx pool tx msg subscription channel closed")
   101  				return
   102  			}
   103  
   104  			ev, ok := obj.Data.(protocol.TxMsgEvent)
   105  			if !ok {
   106  				log.WithFields(log.Fields{"module": logModule}).Error("event type error")
   107  				continue
   108  			}
   109  
   110  			switch ev.TxMsg.MsgType {
   111  			case protocol.MsgNewTx:
   112  				w.AddUnconfirmedTx(ev.TxMsg.TxDesc)
   113  			case protocol.MsgRemoveTx:
   114  				w.RemoveUnconfirmedTx(ev.TxMsg.TxDesc)
   115  			default:
   116  				log.WithFields(log.Fields{"module": logModule}).Warn("got unknow message type from the txPool channel")
   117  			}
   118  		}
   119  	}
   120  }
   121  
   122  func (w *Wallet) checkWalletInfo() error {
   123  	if w.status.Version != currentVersion {
   124  		return errWalletVersionMismatch
   125  	} else if !w.chain.BlockExist(&w.status.BestHash) {
   126  		return errBestBlockNotFoundInCore
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  //loadWalletInfo return stored wallet info and nil,
   133  //if error, return initial wallet info and err
   134  func (w *Wallet) loadWalletInfo() error {
   135  	if rawWallet := w.DB.Get(walletKey); rawWallet != nil {
   136  		if err := json.Unmarshal(rawWallet, &w.status); err != nil {
   137  			return err
   138  		}
   139  
   140  		err := w.checkWalletInfo()
   141  		if err == nil {
   142  			return nil
   143  		}
   144  
   145  		log.WithFields(log.Fields{"module": logModule}).Warn(err.Error())
   146  		w.deleteAccountTxs()
   147  		w.deleteUtxos()
   148  	}
   149  
   150  	w.status.Version = currentVersion
   151  	w.status.WorkHash = bc.Hash{}
   152  	block, err := w.chain.GetBlockByHeight(0)
   153  	if err != nil {
   154  		return err
   155  	}
   156  	return w.AttachBlock(block)
   157  }
   158  
   159  func (w *Wallet) commitWalletInfo(batch dbm.Batch) error {
   160  	rawWallet, err := json.Marshal(w.status)
   161  	if err != nil {
   162  		log.WithFields(log.Fields{"module": logModule, "err": err}).Error("save wallet info")
   163  		return err
   164  	}
   165  
   166  	batch.Set(walletKey, rawWallet)
   167  	batch.Write()
   168  	return nil
   169  }
   170  
   171  // AttachBlock attach a new block
   172  func (w *Wallet) AttachBlock(block *types.Block) error {
   173  	w.rw.Lock()
   174  	defer w.rw.Unlock()
   175  
   176  	if block.PreviousBlockHash != w.status.WorkHash {
   177  		log.Warn("wallet skip attachBlock due to status hash not equal to previous hash")
   178  		return nil
   179  	}
   180  
   181  	blockHash := block.Hash()
   182  	txStatus, err := w.chain.GetTransactionStatus(&blockHash)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	if err := w.RecoveryMgr.FilterRecoveryTxs(block); err != nil {
   188  		log.WithField("err", err).Error("filter recovery txs")
   189  		w.RecoveryMgr.finished()
   190  	}
   191  
   192  	storeBatch := w.DB.NewBatch()
   193  	if err := w.indexTransactions(storeBatch, block, txStatus); err != nil {
   194  		return err
   195  	}
   196  
   197  	w.attachUtxos(storeBatch, block, txStatus)
   198  	w.status.WorkHeight = block.Height
   199  	w.status.WorkHash = block.Hash()
   200  	if w.status.WorkHeight >= w.status.BestHeight {
   201  		w.status.BestHeight = w.status.WorkHeight
   202  		w.status.BestHash = w.status.WorkHash
   203  	}
   204  	return w.commitWalletInfo(storeBatch)
   205  }
   206  
   207  // DetachBlock detach a block and rollback state
   208  func (w *Wallet) DetachBlock(block *types.Block) error {
   209  	w.rw.Lock()
   210  	defer w.rw.Unlock()
   211  
   212  	blockHash := block.Hash()
   213  	txStatus, err := w.chain.GetTransactionStatus(&blockHash)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	storeBatch := w.DB.NewBatch()
   219  	w.detachUtxos(storeBatch, block, txStatus)
   220  	w.deleteTransactions(storeBatch, w.status.BestHeight)
   221  
   222  	w.status.BestHeight = block.Height - 1
   223  	w.status.BestHash = block.PreviousBlockHash
   224  
   225  	if w.status.WorkHeight > w.status.BestHeight {
   226  		w.status.WorkHeight = w.status.BestHeight
   227  		w.status.WorkHash = w.status.BestHash
   228  	}
   229  
   230  	return w.commitWalletInfo(storeBatch)
   231  }
   232  
   233  //WalletUpdate process every valid block and reverse every invalid block which need to rollback
   234  func (w *Wallet) walletUpdater() {
   235  	for {
   236  		w.getRescanNotification()
   237  		for !w.chain.InMainChain(w.status.BestHash) {
   238  			block, err := w.chain.GetBlockByHash(&w.status.BestHash)
   239  			if err != nil {
   240  				log.WithFields(log.Fields{"module": logModule, "err": err}).Error("walletUpdater GetBlockByHash")
   241  				return
   242  			}
   243  
   244  			if err := w.DetachBlock(block); err != nil {
   245  				log.WithFields(log.Fields{"module": logModule, "err": err}).Error("walletUpdater detachBlock stop")
   246  				return
   247  			}
   248  		}
   249  
   250  		block, _ := w.chain.GetBlockByHeight(w.status.WorkHeight + 1)
   251  		if block == nil {
   252  			w.walletBlockWaiter()
   253  			continue
   254  		}
   255  
   256  		if err := w.AttachBlock(block); err != nil {
   257  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("walletUpdater AttachBlock stop")
   258  			return
   259  		}
   260  	}
   261  }
   262  
   263  //RescanBlocks provide a trigger to rescan blocks
   264  func (w *Wallet) RescanBlocks() {
   265  	select {
   266  	case w.rescanCh <- struct{}{}:
   267  	default:
   268  		return
   269  	}
   270  }
   271  
   272  // deleteAccountTxs deletes all txs in wallet
   273  func (w *Wallet) deleteAccountTxs() {
   274  	storeBatch := w.DB.NewBatch()
   275  
   276  	txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
   277  	defer txIter.Release()
   278  
   279  	for txIter.Next() {
   280  		storeBatch.Delete(txIter.Key())
   281  	}
   282  
   283  	txIndexIter := w.DB.IteratorPrefix([]byte(TxIndexPrefix))
   284  	defer txIndexIter.Release()
   285  
   286  	for txIndexIter.Next() {
   287  		storeBatch.Delete(txIndexIter.Key())
   288  	}
   289  
   290  	storeBatch.Write()
   291  }
   292  
   293  func (w *Wallet) deleteUtxos() {
   294  	storeBatch := w.DB.NewBatch()
   295  	ruIter := w.DB.IteratorPrefix([]byte(account.UTXOPreFix))
   296  	defer ruIter.Release()
   297  	for ruIter.Next() {
   298  		storeBatch.Delete(ruIter.Key())
   299  	}
   300  
   301  	suIter := w.DB.IteratorPrefix([]byte(account.SUTXOPrefix))
   302  	defer suIter.Release()
   303  	for suIter.Next() {
   304  		storeBatch.Delete(suIter.Key())
   305  	}
   306  	storeBatch.Write()
   307  }
   308  
   309  // DeleteAccount deletes account matching accountID, then rescan wallet
   310  func (w *Wallet) DeleteAccount(accountID string) (err error) {
   311  	w.rw.Lock()
   312  	defer w.rw.Unlock()
   313  
   314  	if err := w.AccountMgr.DeleteAccount(accountID); err != nil {
   315  		return err
   316  	}
   317  
   318  	w.deleteAccountTxs()
   319  	w.RescanBlocks()
   320  	return nil
   321  }
   322  
   323  func (w *Wallet) UpdateAccountAlias(accountID string, newAlias string) (err error) {
   324  	w.rw.Lock()
   325  	defer w.rw.Unlock()
   326  
   327  	if err := w.AccountMgr.UpdateAccountAlias(accountID, newAlias); err != nil {
   328  		return err
   329  	}
   330  
   331  	w.deleteAccountTxs()
   332  	w.RescanBlocks()
   333  	return nil
   334  }
   335  
   336  func (w *Wallet) getRescanNotification() {
   337  	select {
   338  	case <-w.rescanCh:
   339  		w.setRescanStatus()
   340  	default:
   341  		return
   342  	}
   343  }
   344  
   345  func (w *Wallet) setRescanStatus() {
   346  	block, _ := w.chain.GetBlockByHeight(0)
   347  	w.status.WorkHash = bc.Hash{}
   348  	w.AttachBlock(block)
   349  }
   350  
   351  func (w *Wallet) walletBlockWaiter() {
   352  	select {
   353  	case <-w.chain.BlockWaiter(w.status.WorkHeight + 1):
   354  	case <-w.rescanCh:
   355  		w.setRescanStatus()
   356  	}
   357  }
   358  
   359  // GetWalletStatusInfo return current wallet StatusInfo
   360  func (w *Wallet) GetWalletStatusInfo() StatusInfo {
   361  	w.rw.RLock()
   362  	defer w.rw.RUnlock()
   363  
   364  	return w.status
   365  }