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