github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/protocol/state/utxo_view.go (about) 1 package state 2 3 import ( 4 "github.com/bytom/bytom/consensus" 5 "github.com/bytom/bytom/database/storage" 6 "github.com/bytom/bytom/errors" 7 "github.com/bytom/bytom/protocol/bc" 8 ) 9 10 // UtxoViewpoint represents a view into the set of unspent transaction outputs 11 type UtxoViewpoint struct { 12 Entries map[bc.Hash]*storage.UtxoEntry 13 } 14 15 // NewUtxoViewpoint returns a new empty unspent transaction output view. 16 func NewUtxoViewpoint() *UtxoViewpoint { 17 return &UtxoViewpoint{ 18 Entries: make(map[bc.Hash]*storage.UtxoEntry), 19 } 20 } 21 22 func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx) error { 23 if err := view.applySpendUtxo(block, tx); err != nil { 24 return err 25 } 26 27 return view.applyOutputUtxo(block, tx) 28 } 29 30 func (view *UtxoViewpoint) applySpendUtxo(block *bc.Block, tx *bc.Tx) error { 31 for _, prevout := range tx.SpentOutputIDs { 32 entry, ok := view.Entries[prevout] 33 if !ok { 34 return errors.New("fail to find utxo entry") 35 } 36 if entry.Spent { 37 return errors.New("utxo has been spent") 38 } 39 switch entry.Type { 40 case storage.CoinbaseUTXOType: 41 if entry.BlockHeight+consensus.CoinbasePendingBlockNumber > block.Height { 42 return errors.New("coinbase utxo is not ready for use") 43 } 44 case storage.VoteUTXOType: 45 if entry.BlockHeight + consensus.VotePendingBlockNums(block.Height) > block.Height { 46 return errors.New("Coin is within the voting lock time") 47 } 48 } 49 50 entry.SpendOutput() 51 } 52 return nil 53 } 54 55 func (view *UtxoViewpoint) applyOutputUtxo(block *bc.Block, tx *bc.Tx) error { 56 for _, id := range tx.TxHeader.ResultIds { 57 entryOutput, ok := tx.Entries[*id] 58 if !ok { 59 // error due to it's a retirement, utxo doesn't care this output type so skip it 60 continue 61 } 62 63 64 var utxoType uint32 65 var amount uint64 66 switch output := entryOutput.(type) { 67 case *bc.OriginalOutput: 68 amount = output.Source.Value.Amount 69 utxoType = storage.NormalUTXOType 70 case *bc.VoteOutput: 71 amount = output.Source.Value.Amount 72 utxoType = storage.VoteUTXOType 73 default: 74 // due to it's a retirement, utxo doesn't care this output type so skip it 75 continue 76 } 77 78 if amount == 0 { 79 continue 80 } 81 82 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID { 83 utxoType = storage.CoinbaseUTXOType 84 } 85 view.Entries[*id] = storage.NewUtxoEntry(utxoType, block.Height, false) 86 } 87 return nil 88 } 89 90 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block) error { 91 for _, tx := range block.Transactions { 92 if err := view.ApplyTransaction(block, tx); err != nil { 93 return err 94 } 95 } 96 return nil 97 } 98 99 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool { 100 entry := view.Entries[*hash] 101 return entry != nil && !entry.Spent 102 } 103 104 func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx) error { 105 if err := view.detachSpendUtxo(tx); err != nil { 106 return err 107 } 108 109 return view.detachOutputUtxo(tx) 110 } 111 112 func (view *UtxoViewpoint) detachSpendUtxo(tx *bc.Tx) error { 113 for _, prevout := range tx.SpentOutputIDs { 114 entryOutput, ok := tx.Entries[prevout] 115 if !ok { 116 return errors.New("fail to find utxo entry") 117 } 118 119 var utxoType uint32 120 switch entryOutput.(type) { 121 case *bc.OriginalOutput: 122 utxoType = storage.NormalUTXOType 123 case *bc.VoteOutput: 124 utxoType = storage.VoteUTXOType 125 default: 126 return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput) 127 } 128 129 entry, ok := view.Entries[prevout] 130 if ok && !entry.Spent { 131 return errors.New("try to revert an unspent utxo") 132 } 133 if !ok { 134 view.Entries[prevout] = storage.NewUtxoEntry(utxoType, 0, false) 135 continue 136 } 137 entry.UnspendOutput() 138 } 139 return nil 140 } 141 142 func (view *UtxoViewpoint) detachOutputUtxo(tx *bc.Tx) error { 143 for _, id := range tx.TxHeader.ResultIds { 144 entryOutput, ok := tx.Entries[*id] 145 if !ok { 146 // error due to it's a retirement, utxo doesn't care this output type so skip it 147 continue 148 } 149 150 var utxoType uint32 151 var amount uint64 152 switch output := entryOutput.(type) { 153 case *bc.OriginalOutput: 154 amount = output.Source.Value.Amount 155 utxoType = storage.NormalUTXOType 156 case *bc.VoteOutput: 157 amount = output.Source.Value.Amount 158 utxoType = storage.VoteUTXOType 159 default: 160 // due to it's a retirement, utxo doesn't care this output type so skip it 161 continue 162 } 163 164 if amount == 0 { 165 continue 166 } 167 168 view.Entries[*id] = storage.NewUtxoEntry(utxoType, 0, true) 169 } 170 return nil 171 } 172 173 func (view *UtxoViewpoint) DetachBlock(block *bc.Block) error { 174 for i := len(block.Transactions) - 1; i >= 0; i-- { 175 if err := view.DetachTransaction(block.Transactions[i]); err != nil { 176 return err 177 } 178 } 179 return nil 180 } 181 182 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool { 183 _, ok := view.Entries[*hash] 184 return ok 185 }