OSDN Git Service

90c71b17691be4960c953e22c08db4b35fd695b1
[bytom/vapor.git] / protocol / state / utxo_view.go
1 package state
2
3 import (
4         "github.com/vapor/consensus"
5         "github.com/vapor/database/storage"
6         "github.com/vapor/errors"
7         "github.com/vapor/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, statusFail bool) error {
23         for _, prevout := range tx.MainchainOutputIDs {
24                 entry, ok := view.Entries[prevout]
25                 if !ok {
26                         return errors.New("fail to find mainchain output entry")
27                 }
28
29                 if entry.Type != storage.CrosschainUTXOType {
30                         return errors.New("look up mainchainOutputID but find utxo not from mainchain")
31                 }
32
33                 if entry.Spent {
34                         return errors.New("mainchain output has been spent")
35                 }
36
37                 entry.BlockHeight = block.Height
38                 entry.SpendOutput()
39         }
40
41         for _, prevout := range tx.SpentOutputIDs {
42                 assetID := bc.AssetID{}
43                 entryOutput, err := tx.Entry(prevout)
44                 if err != nil {
45                         return err
46                 }
47
48                 switch output := entryOutput.(type) {
49                 case *bc.IntraChainOutput:
50                         assetID = *output.Source.Value.AssetId
51                 case *bc.VoteOutput:
52                         assetID = *output.Source.Value.AssetId
53                 default:
54                         return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
55                 }
56
57                 if statusFail && assetID != *consensus.BTMAssetID {
58                         continue
59                 }
60
61                 entry, ok := view.Entries[prevout]
62                 if !ok {
63                         return errors.New("fail to find utxo entry")
64                 }
65
66                 if entry.Spent {
67                         return errors.New("utxo has been spent")
68                 }
69
70                 switch entry.Type {
71                 case storage.CrosschainUTXOType:
72                         return errors.New("look up spentOutputID but find utxo from mainchain")
73
74                 case storage.CoinbaseUTXOType:
75                         if (entry.BlockHeight + consensus.CoinbasePendingBlockNumber) > block.Height {
76                                 return errors.New("coinbase utxo is not ready for use")
77                         }
78
79                 case storage.VoteUTXOType:
80                         if (entry.BlockHeight + consensus.VotePendingBlockNumber) > block.Height {
81                                 return errors.New("Coin is  within the voting lock time")
82                         }
83                 }
84
85                 entry.SpendOutput()
86         }
87
88         for _, id := range tx.TxHeader.ResultIds {
89                 assetID := bc.AssetID{}
90                 entryOutput, err := tx.Entry(*id)
91                 if err != nil {
92                         continue
93                 }
94
95                 utxoType := storage.NormalUTXOType
96
97                 switch output := entryOutput.(type) {
98                 case *bc.IntraChainOutput:
99                         if output.Source.Value.Amount == uint64(0) {
100                                 continue
101                         }
102                         assetID = *output.Source.Value.AssetId
103                 case *bc.VoteOutput:
104                         assetID = *output.Source.Value.AssetId
105                         utxoType = storage.VoteUTXOType
106                 default:
107                         // due to it's a retirement, utxo doesn't care this output type so skip it
108                         continue
109                 }
110
111                 if statusFail && assetID != *consensus.BTMAssetID {
112                         continue
113                 }
114
115                 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
116                         utxoType = storage.CoinbaseUTXOType
117                 }
118                 view.Entries[*id] = storage.NewUtxoEntry(utxoType, block.Height, false)
119         }
120         return nil
121 }
122
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)
126                 if err != nil {
127                         return err
128                 }
129                 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
130                         return err
131                 }
132         }
133         return nil
134 }
135
136 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
137         entry := view.Entries[*hash]
138         return entry != nil && !entry.Spent
139 }
140
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")
147                 }
148
149                 if ok && !entry.Spent {
150                         return errors.New("try to revert an unspent utxo")
151                 }
152
153                 if !ok {
154                         view.Entries[prevout] = storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false)
155                         continue
156                 }
157                 entry.UnspendOutput()
158         }
159
160         for _, prevout := range tx.SpentOutputIDs {
161                 assetID := bc.AssetID{}
162                 entryOutput, err := tx.Entry(prevout)
163                 if err != nil {
164                         return err
165                 }
166
167                 utxoType := storage.NormalUTXOType
168                 switch output := entryOutput.(type) {
169                 case *bc.IntraChainOutput:
170                         assetID = *output.Source.Value.AssetId
171                 case *bc.VoteOutput:
172                         assetID = *output.Source.Value.AssetId
173                         utxoType = storage.VoteUTXOType
174                 default:
175                         return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
176                 }
177
178                 if statusFail && assetID != *consensus.BTMAssetID {
179                         continue
180                 }
181
182                 entry, ok := view.Entries[prevout]
183                 if ok && (entry.Type == storage.CrosschainUTXOType) {
184                         return errors.New("look up SpentOutputIDs but find mainchain utxo")
185                 }
186
187                 if ok && !entry.Spent {
188                         return errors.New("try to revert an unspent utxo")
189                 }
190
191                 if !ok {
192                         view.Entries[prevout] = storage.NewUtxoEntry(utxoType, 0, false)
193                         continue
194                 }
195                 entry.UnspendOutput()
196         }
197
198         for _, id := range tx.TxHeader.ResultIds {
199                 assetID := bc.AssetID{}
200                 entryOutput, err := tx.Entry(*id)
201                 if err != nil {
202                         continue
203                 }
204
205                 utxoType := storage.NormalUTXOType
206                 switch output := entryOutput.(type) {
207                 case *bc.IntraChainOutput:
208                         if output.Source.Value.Amount == uint64(0) {
209                                 continue
210                         }
211                         assetID = *output.Source.Value.AssetId
212                 case *bc.VoteOutput:
213                         assetID = *output.Source.Value.AssetId
214                         utxoType = storage.VoteUTXOType
215                 default:
216                         // due to it's a retirement, utxo doesn't care this output type so skip it
217                         continue
218                 }
219
220                 if statusFail && assetID != *consensus.BTMAssetID {
221                         continue
222                 }
223
224                 view.Entries[*id] = storage.NewUtxoEntry(utxoType, 0, true)
225         }
226         return nil
227 }
228
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)
232                 if err != nil {
233                         return err
234                 }
235                 if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
236                         return err
237                 }
238         }
239         return nil
240 }
241
242 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
243         _, ok := view.Entries[*hash]
244         return ok
245 }