github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/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, vote 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 vote && accountUtxo.Vote == nil { 41 continue 42 } 43 44 if accountID == accountUtxo.AccountID || accountID == "" { 45 accountUtxos = append(accountUtxos, accountUtxo) 46 } 47 } 48 return accountUtxos 49 } 50 51 func (w *Wallet) attachUtxos(batch dbm.Batch, b *types.Block) { 52 for _, tx := range b.Transactions { 53 // hand update the transaction input utxos 54 inputUtxos := txInToUtxos(tx) 55 for _, inputUtxo := range inputUtxos { 56 if segwit.IsP2WScript(inputUtxo.ControlProgram) { 57 batch.Delete(account.StandardUTXOKey(inputUtxo.OutputID)) 58 } else { 59 batch.Delete(account.ContractUTXOKey(inputUtxo.OutputID)) 60 } 61 } 62 63 // hand update the transaction output utxos 64 outputUtxos := txOutToUtxos(tx, b.Height) 65 utxos := w.filterAccountUtxo(outputUtxos) 66 if err := batchSaveUtxos(utxos, batch); err != nil { 67 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on batchSaveUtxos") 68 } 69 } 70 } 71 72 func (w *Wallet) detachUtxos(batch dbm.Batch, b *types.Block) { 73 for txIndex := len(b.Transactions) - 1; txIndex >= 0; txIndex-- { 74 tx := b.Transactions[txIndex] 75 for j := range tx.Outputs { 76 resOut, err := tx.OriginalOutput(*tx.ResultIds[j]) 77 if err != nil { 78 continue 79 } 80 81 if segwit.IsP2WScript(resOut.ControlProgram.Code) { 82 batch.Delete(account.StandardUTXOKey(*tx.ResultIds[j])) 83 } else { 84 batch.Delete(account.ContractUTXOKey(*tx.ResultIds[j])) 85 } 86 } 87 88 inputUtxos := txInToUtxos(tx) 89 utxos := w.filterAccountUtxo(inputUtxos) 90 if err := batchSaveUtxos(utxos, batch); err != nil { 91 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on batchSaveUtxos") 92 return 93 } 94 } 95 } 96 97 func (w *Wallet) filterAccountUtxo(utxos []*account.UTXO) []*account.UTXO { 98 outsByScript := make(map[string][]*account.UTXO, len(utxos)) 99 for _, utxo := range utxos { 100 scriptStr := string(utxo.ControlProgram) 101 outsByScript[scriptStr] = append(outsByScript[scriptStr], utxo) 102 } 103 104 result := make([]*account.UTXO, 0, len(utxos)) 105 for s := range outsByScript { 106 if !segwit.IsP2WScript([]byte(s)) { 107 continue 108 } 109 110 var hash [32]byte 111 sha3pool.Sum256(hash[:], []byte(s)) 112 data := w.DB.Get(account.ContractKey(hash)) 113 if data == nil { 114 continue 115 } 116 117 cp := &account.CtrlProgram{} 118 if err := json.Unmarshal(data, cp); err != nil { 119 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("filterAccountUtxo fail on unmarshal control program") 120 continue 121 } 122 123 for _, utxo := range outsByScript[s] { 124 utxo.AccountID = cp.AccountID 125 utxo.Address = cp.Address 126 utxo.ControlProgramIndex = cp.KeyIndex 127 utxo.Change = cp.Change 128 result = append(result, utxo) 129 } 130 } 131 return result 132 } 133 134 func batchSaveUtxos(utxos []*account.UTXO, batch dbm.Batch) error { 135 for _, utxo := range utxos { 136 data, err := json.Marshal(utxo) 137 if err != nil { 138 return errors.Wrap(err, "failed marshal accountutxo") 139 } 140 141 if segwit.IsP2WScript(utxo.ControlProgram) { 142 batch.Set(account.StandardUTXOKey(utxo.OutputID), data) 143 } else { 144 batch.Set(account.ContractUTXOKey(utxo.OutputID), data) 145 } 146 } 147 return nil 148 } 149 150 func txInToUtxos(tx *types.Tx) []*account.UTXO { 151 utxos := []*account.UTXO{} 152 for _, inpID := range tx.Tx.InputIDs { 153 var utxo *account.UTXO 154 e, ok := tx.Entries[inpID] 155 if !ok { 156 continue 157 } 158 159 switch inp := e.(type) { 160 case *bc.Spend: 161 resOut, err := tx.OriginalOutput(*inp.SpentOutputId) 162 if err != nil { 163 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut") 164 continue 165 } 166 167 utxo = &account.UTXO{ 168 OutputID: *inp.SpentOutputId, 169 AssetID: *resOut.Source.Value.AssetId, 170 Amount: resOut.Source.Value.Amount, 171 ControlProgram: resOut.ControlProgram.Code, 172 SourceID: *resOut.Source.Ref, 173 SourcePos: resOut.Source.Position, 174 } 175 case *bc.VetoInput: 176 resOut, err := tx.VoteOutput(*inp.SpentOutputId) 177 if err != nil { 178 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut for vetoInput") 179 continue 180 } 181 if *resOut.Source.Value.AssetId != *consensus.BTMAssetID { 182 continue 183 } 184 utxo = &account.UTXO{ 185 OutputID: *inp.SpentOutputId, 186 AssetID: *resOut.Source.Value.AssetId, 187 Amount: resOut.Source.Value.Amount, 188 ControlProgram: resOut.ControlProgram.Code, 189 SourceID: *resOut.Source.Ref, 190 SourcePos: resOut.Source.Position, 191 Vote: resOut.Vote, 192 } 193 default: 194 continue 195 } 196 utxos = append(utxos, utxo) 197 } 198 return utxos 199 } 200 201 func txOutToUtxos(tx *types.Tx, blockHeight uint64) []*account.UTXO { 202 utxos := []*account.UTXO{} 203 for i, out := range tx.Outputs { 204 validHeight := uint64(0) 205 entryOutput, ok := tx.Entries[*tx.ResultIds[i]] 206 if !ok { 207 log.WithFields(log.Fields{"module": logModule}).Error("txOutToUtxos fail on get entryOutput") 208 continue 209 } 210 211 var utxo *account.UTXO 212 switch bcOut := entryOutput.(type) { 213 case *bc.OriginalOutput: 214 if out.AssetAmount.Amount == uint64(0) { 215 continue 216 } 217 218 if tx.Inputs[0].InputType() == types.CoinbaseInputType { 219 validHeight = blockHeight + consensus.CoinbasePendingBlockNumber 220 } 221 222 utxo = &account.UTXO{ 223 OutputID: *tx.OutputID(i), 224 AssetID: *out.AssetAmount.AssetId, 225 Amount: out.AssetAmount.Amount, 226 ControlProgram: out.ControlProgram, 227 SourceID: *bcOut.Source.Ref, 228 SourcePos: bcOut.Source.Position, 229 ValidHeight: validHeight, 230 } 231 232 case *bc.VoteOutput: 233 voteValidHeight := blockHeight + consensus.VotePendingBlockNums(blockHeight) 234 if validHeight < voteValidHeight { 235 validHeight = voteValidHeight 236 } 237 238 utxo = &account.UTXO{ 239 OutputID: *tx.OutputID(i), 240 AssetID: *out.AssetAmount.AssetId, 241 Amount: out.AssetAmount.Amount, 242 ControlProgram: out.ControlProgram, 243 SourceID: *bcOut.Source.Ref, 244 SourcePos: bcOut.Source.Position, 245 ValidHeight: validHeight, 246 Vote: bcOut.Vote, 247 } 248 249 default: 250 log.WithFields(log.Fields{"module": logModule}).Warn("txOutToUtxos fail on get bcOut") 251 continue 252 } 253 utxos = append(utxos, utxo) 254 } 255 return utxos 256 }