github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/wallet/utxo.go (about) 1 package wallet 2 3 import ( 4 "encoding/json" 5 6 log "github.com/sirupsen/logrus" 7 8 "github.com/bytom/bytom/account" 9 "github.com/bytom/bytom/consensus" 10 "github.com/bytom/bytom/consensus/segwit" 11 "github.com/bytom/bytom/crypto/sha3pool" 12 dbm "github.com/bytom/bytom/database/leveldb" 13 "github.com/bytom/bytom/errors" 14 "github.com/bytom/bytom/protocol/bc" 15 "github.com/bytom/bytom/protocol/bc/types" 16 ) 17 18 // GetAccountUtxos return all account unspent outputs 19 func (w *Wallet) GetAccountUtxos(accountID string, id string, unconfirmed, isSmartContract bool) []*account.UTXO { 20 prefix := account.UTXOPreFix 21 if isSmartContract { 22 prefix = account.SUTXOPrefix 23 } 24 25 accountUtxos := []*account.UTXO{} 26 if unconfirmed { 27 accountUtxos = w.AccountMgr.ListUnconfirmedUtxo(accountID, isSmartContract) 28 } 29 30 accountUtxoIter := w.DB.IteratorPrefix([]byte(prefix + id)) 31 defer accountUtxoIter.Release() 32 33 for accountUtxoIter.Next() { 34 accountUtxo := &account.UTXO{} 35 if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil { 36 log.WithFields(log.Fields{"module": logModule, "err": err}).Warn("GetAccountUtxos fail on unmarshal utxo") 37 continue 38 } 39 40 if accountID == accountUtxo.AccountID || accountID == "" { 41 accountUtxos = append(accountUtxos, accountUtxo) 42 } 43 } 44 return accountUtxos 45 } 46 47 func (w *Wallet) attachUtxos(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) { 48 for txIndex, tx := range b.Transactions { 49 statusFail, err := txStatus.GetStatus(txIndex) 50 if err != nil { 51 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on get tx status") 52 continue 53 } 54 55 //hand update the transaction input utxos 56 inputUtxos := txInToUtxos(tx, statusFail) 57 for _, inputUtxo := range inputUtxos { 58 if segwit.IsP2WScript(inputUtxo.ControlProgram) { 59 batch.Delete(account.StandardUTXOKey(inputUtxo.OutputID)) 60 } else { 61 batch.Delete(account.ContractUTXOKey(inputUtxo.OutputID)) 62 } 63 } 64 65 //hand update the transaction output utxos 66 validHeight := uint64(0) 67 if txIndex == 0 { 68 validHeight = b.Height + consensus.CoinbasePendingBlockNumber 69 } 70 outputUtxos := txOutToUtxos(tx, statusFail, validHeight) 71 utxos := w.filterAccountUtxo(outputUtxos) 72 if err := batchSaveUtxos(utxos, batch); err != nil { 73 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on batchSaveUtxos") 74 } 75 } 76 } 77 78 func (w *Wallet) detachUtxos(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) { 79 for txIndex := len(b.Transactions) - 1; txIndex >= 0; txIndex-- { 80 tx := b.Transactions[txIndex] 81 for j := range tx.Outputs { 82 resOut, err := tx.Output(*tx.ResultIds[j]) 83 if err != nil { 84 continue 85 } 86 87 if segwit.IsP2WScript(resOut.ControlProgram.Code) { 88 batch.Delete(account.StandardUTXOKey(*tx.ResultIds[j])) 89 } else { 90 batch.Delete(account.ContractUTXOKey(*tx.ResultIds[j])) 91 } 92 } 93 94 statusFail, err := txStatus.GetStatus(txIndex) 95 if err != nil { 96 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on get tx status") 97 continue 98 } 99 100 inputUtxos := txInToUtxos(tx, statusFail) 101 utxos := w.filterAccountUtxo(inputUtxos) 102 if err := batchSaveUtxos(utxos, batch); err != nil { 103 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on batchSaveUtxos") 104 return 105 } 106 } 107 } 108 109 func (w *Wallet) filterAccountUtxo(utxos []*account.UTXO) []*account.UTXO { 110 outsByScript := make(map[string][]*account.UTXO, len(utxos)) 111 for _, utxo := range utxos { 112 scriptStr := string(utxo.ControlProgram) 113 outsByScript[scriptStr] = append(outsByScript[scriptStr], utxo) 114 } 115 116 result := make([]*account.UTXO, 0, len(utxos)) 117 for s := range outsByScript { 118 if !segwit.IsP2WScript([]byte(s)) { 119 continue 120 } 121 122 var hash [32]byte 123 sha3pool.Sum256(hash[:], []byte(s)) 124 data := w.DB.Get(account.ContractKey(hash)) 125 if data == nil { 126 continue 127 } 128 129 cp := &account.CtrlProgram{} 130 if err := json.Unmarshal(data, cp); err != nil { 131 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("filterAccountUtxo fail on unmarshal control program") 132 continue 133 } 134 135 for _, utxo := range outsByScript[s] { 136 utxo.AccountID = cp.AccountID 137 utxo.Address = cp.Address 138 utxo.ControlProgramIndex = cp.KeyIndex 139 utxo.Change = cp.Change 140 result = append(result, utxo) 141 } 142 } 143 return result 144 } 145 146 func batchSaveUtxos(utxos []*account.UTXO, batch dbm.Batch) error { 147 for _, utxo := range utxos { 148 data, err := json.Marshal(utxo) 149 if err != nil { 150 return errors.Wrap(err, "failed marshal accountutxo") 151 } 152 153 if segwit.IsP2WScript(utxo.ControlProgram) { 154 batch.Set(account.StandardUTXOKey(utxo.OutputID), data) 155 } else { 156 batch.Set(account.ContractUTXOKey(utxo.OutputID), data) 157 } 158 } 159 return nil 160 } 161 162 func txInToUtxos(tx *types.Tx, statusFail bool) []*account.UTXO { 163 utxos := []*account.UTXO{} 164 for _, inpID := range tx.Tx.InputIDs { 165 sp, err := tx.Spend(inpID) 166 if err != nil { 167 continue 168 } 169 170 resOut, err := tx.Output(*sp.SpentOutputId) 171 if err != nil { 172 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut") 173 continue 174 } 175 176 if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID { 177 continue 178 } 179 180 utxos = append(utxos, &account.UTXO{ 181 OutputID: *sp.SpentOutputId, 182 AssetID: *resOut.Source.Value.AssetId, 183 Amount: resOut.Source.Value.Amount, 184 ControlProgram: resOut.ControlProgram.Code, 185 SourceID: *resOut.Source.Ref, 186 SourcePos: resOut.Source.Position, 187 }) 188 } 189 return utxos 190 } 191 192 func txOutToUtxos(tx *types.Tx, statusFail bool, vaildHeight uint64) []*account.UTXO { 193 utxos := []*account.UTXO{} 194 for i, out := range tx.Outputs { 195 bcOut, err := tx.Output(*tx.ResultIds[i]) 196 if err != nil { 197 continue 198 } 199 200 if statusFail && *out.AssetAmount.AssetId != *consensus.BTMAssetID { 201 continue 202 } 203 204 utxos = append(utxos, &account.UTXO{ 205 OutputID: *tx.OutputID(i), 206 AssetID: *out.AssetAmount.AssetId, 207 Amount: out.Amount, 208 ControlProgram: out.ControlProgram, 209 SourceID: *bcOut.Source.Ref, 210 SourcePos: bcOut.Source.Position, 211 ValidHeight: vaildHeight, 212 }) 213 } 214 return utxos 215 }