4 "github.com/vapor/consensus"
5 "github.com/vapor/database/storage"
6 "github.com/vapor/errors"
7 "github.com/vapor/protocol/bc"
10 // UtxoViewpoint represents a view into the set of unspent transaction outputs
11 type UtxoViewpoint struct {
12 Entries map[bc.Hash]*storage.UtxoEntry
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),
22 func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx, statusFail bool) error {
23 for _, prevout := range tx.MainchainOutputIDs {
24 entry, ok := view.Entries[prevout]
26 return errors.New("fail to find mainchain output entry")
29 if entry.Type != storage.CrosschainUTXOType {
30 return errors.New("look up mainchainOutputID but find utxo not from mainchain")
34 return errors.New("mainchain output has been spent")
37 entry.BlockHeight = block.Height
41 for _, prevout := range tx.SpentOutputIDs {
42 assetID := bc.AssetID{}
43 entryOutput, err := tx.Entry(prevout)
48 switch output := entryOutput.(type) {
49 case *bc.IntraChainOutput:
50 assetID = *output.Source.Value.AssetId
52 assetID = *output.Source.Value.AssetId
54 return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
57 if statusFail && assetID != *consensus.BTMAssetID {
61 entry, ok := view.Entries[prevout]
63 return errors.New("fail to find utxo entry")
67 return errors.New("utxo has been spent")
71 case storage.CrosschainUTXOType:
72 return errors.New("look up spentOutputID but find utxo from mainchain")
74 case storage.CoinbaseUTXOType:
75 if (entry.BlockHeight + consensus.CoinbasePendingBlockNumber) > block.Height {
76 return errors.New("coinbase utxo is not ready for use")
79 case storage.VoteUTXOType:
80 if (entry.BlockHeight + consensus.VotePendingBlockNumber) > block.Height {
81 return errors.New("Coin is within the voting lock time")
88 for _, id := range tx.TxHeader.ResultIds {
89 assetID := bc.AssetID{}
90 entryOutput, err := tx.Entry(*id)
95 utxoType := storage.NormalUTXOType
97 switch output := entryOutput.(type) {
98 case *bc.IntraChainOutput:
99 if output.Source.Value.Amount == uint64(0) {
102 assetID = *output.Source.Value.AssetId
104 assetID = *output.Source.Value.AssetId
105 utxoType = storage.VoteUTXOType
107 // due to it's a retirement, utxo doesn't care this output type so skip it
111 if statusFail && assetID != *consensus.BTMAssetID {
115 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
116 utxoType = storage.CoinbaseUTXOType
118 view.Entries[*id] = storage.NewUtxoEntry(utxoType, block.Height, false)
123 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
124 for i, tx := range block.Transactions {
125 statusFail, err := txStatus.GetStatus(i)
129 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
136 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
137 entry := view.Entries[*hash]
138 return entry != nil && !entry.Spent
141 func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
142 for _, prevout := range tx.MainchainOutputIDs {
143 // don't simply delete(view.Entries, prevout), because we need to delete from db in saveUtxoView()
144 entry, ok := view.Entries[prevout]
145 if ok && (entry.Type != storage.CrosschainUTXOType) {
146 return errors.New("look up mainchainOutputID but find utxo not from mainchain")
149 if ok && !entry.Spent {
150 return errors.New("try to revert an unspent utxo")
154 view.Entries[prevout] = storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false)
157 entry.UnspendOutput()
160 for _, prevout := range tx.SpentOutputIDs {
161 assetID := bc.AssetID{}
162 entryOutput, err := tx.Entry(prevout)
167 utxoType := storage.NormalUTXOType
168 switch output := entryOutput.(type) {
169 case *bc.IntraChainOutput:
170 assetID = *output.Source.Value.AssetId
172 assetID = *output.Source.Value.AssetId
173 utxoType = storage.VoteUTXOType
175 return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
178 if statusFail && assetID != *consensus.BTMAssetID {
182 entry, ok := view.Entries[prevout]
183 if ok && (entry.Type == storage.CrosschainUTXOType) {
184 return errors.New("look up SpentOutputIDs but find mainchain utxo")
187 if ok && !entry.Spent {
188 return errors.New("try to revert an unspent utxo")
192 view.Entries[prevout] = storage.NewUtxoEntry(utxoType, 0, false)
195 entry.UnspendOutput()
198 for _, id := range tx.TxHeader.ResultIds {
199 assetID := bc.AssetID{}
200 entryOutput, err := tx.Entry(*id)
205 utxoType := storage.NormalUTXOType
206 switch output := entryOutput.(type) {
207 case *bc.IntraChainOutput:
208 if output.Source.Value.Amount == uint64(0) {
211 assetID = *output.Source.Value.AssetId
213 assetID = *output.Source.Value.AssetId
214 utxoType = storage.VoteUTXOType
216 // due to it's a retirement, utxo doesn't care this output type so skip it
220 if statusFail && assetID != *consensus.BTMAssetID {
224 view.Entries[*id] = storage.NewUtxoEntry(utxoType, 0, true)
229 func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
230 for i := len(block.Transactions) - 1; i >= 0; i-- {
231 statusFail, err := txStatus.GetStatus(i)
235 if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
242 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
243 _, ok := view.Entries[*hash]