github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/protocol/state/utxo_view.go (about) 1 package state 2 3 import ( 4 "errors" 5 6 "github.com/bytom/bytom/consensus" 7 "github.com/bytom/bytom/database/storage" 8 "github.com/bytom/bytom/protocol/bc" 9 ) 10 11 // UtxoViewpoint represents a view into the set of unspent transaction outputs 12 type UtxoViewpoint struct { 13 Entries map[bc.Hash]*storage.UtxoEntry 14 } 15 16 // NewUtxoViewpoint returns a new empty unspent transaction output view. 17 func NewUtxoViewpoint() *UtxoViewpoint { 18 return &UtxoViewpoint{ 19 Entries: make(map[bc.Hash]*storage.UtxoEntry), 20 } 21 } 22 23 func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx, statusFail bool) error { 24 for _, prevout := range tx.SpentOutputIDs { 25 spentOutput, err := tx.Output(prevout) 26 if err != nil { 27 return err 28 } 29 if statusFail && *spentOutput.Source.Value.AssetId != *consensus.BTMAssetID { 30 continue 31 } 32 33 entry, ok := view.Entries[prevout] 34 if !ok { 35 return errors.New("fail to find utxo entry") 36 } 37 if entry.Spent { 38 return errors.New("utxo has been spent") 39 } 40 if entry.IsCoinBase && entry.BlockHeight+consensus.CoinbasePendingBlockNumber > block.Height { 41 return errors.New("coinbase utxo is not ready for use") 42 } 43 entry.SpendOutput() 44 } 45 46 for _, id := range tx.TxHeader.ResultIds { 47 output, err := tx.Output(*id) 48 if err != nil { 49 // error due to it's a retirement, utxo doesn't care this output type so skip it 50 continue 51 } 52 if statusFail && *output.Source.Value.AssetId != *consensus.BTMAssetID { 53 continue 54 } 55 56 isCoinbase := false 57 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID { 58 isCoinbase = true 59 } 60 view.Entries[*id] = storage.NewUtxoEntry(isCoinbase, block.Height, false) 61 } 62 return nil 63 } 64 65 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error { 66 for i, tx := range block.Transactions { 67 statusFail, err := txStatus.GetStatus(i) 68 if err != nil { 69 return err 70 } 71 if err := view.ApplyTransaction(block, tx, statusFail); err != nil { 72 return err 73 } 74 } 75 return nil 76 } 77 78 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool { 79 entry := view.Entries[*hash] 80 return entry != nil && !entry.Spent 81 } 82 83 func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error { 84 for _, prevout := range tx.SpentOutputIDs { 85 spentOutput, err := tx.Output(prevout) 86 if err != nil { 87 return err 88 } 89 if statusFail && *spentOutput.Source.Value.AssetId != *consensus.BTMAssetID { 90 continue 91 } 92 93 entry, ok := view.Entries[prevout] 94 if ok && !entry.Spent { 95 return errors.New("try to revert an unspent utxo") 96 } 97 if !ok { 98 view.Entries[prevout] = storage.NewUtxoEntry(false, 0, false) 99 continue 100 } 101 entry.UnspendOutput() 102 } 103 104 for _, id := range tx.TxHeader.ResultIds { 105 output, err := tx.Output(*id) 106 if err != nil { 107 // error due to it's a retirement, utxo doesn't care this output type so skip it 108 continue 109 } 110 if statusFail && *output.Source.Value.AssetId != *consensus.BTMAssetID { 111 continue 112 } 113 114 view.Entries[*id] = storage.NewUtxoEntry(false, 0, true) 115 } 116 return nil 117 } 118 119 func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error { 120 for i := len(block.Transactions) - 1; i >= 0; i-- { 121 statusFail, err := txStatus.GetStatus(i) 122 if err != nil { 123 return err 124 } 125 if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil { 126 return err 127 } 128 } 129 return nil 130 } 131 132 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool { 133 _, ok := view.Entries[*hash] 134 return ok 135 }