OSDN Git Service

tmp save (#123)
[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                         assetID = *output.Source.Value.AssetId
100                 case *bc.VoteOutput:
101                         assetID = *output.Source.Value.AssetId
102                         utxoType = storage.VoteUTXOType
103                 default:
104                         // due to it's a retirement, utxo doesn't care this output type so skip it
105                         continue
106                 }
107
108                 if statusFail && assetID != *consensus.BTMAssetID {
109                         continue
110                 }
111
112                 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
113                         utxoType = storage.CoinbaseUTXOType
114                 }
115                 view.Entries[*id] = storage.NewUtxoEntry(utxoType, block.Height, false)
116         }
117         return nil
118 }
119
120 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
121         for i, tx := range block.Transactions {
122                 statusFail, err := txStatus.GetStatus(i)
123                 if err != nil {
124                         return err
125                 }
126                 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
127                         return err
128                 }
129         }
130         return nil
131 }
132
133 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
134         entry := view.Entries[*hash]
135         return entry != nil && !entry.Spent
136 }
137
138 func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
139         for _, prevout := range tx.MainchainOutputIDs {
140                 // don't simply delete(view.Entries, prevout), because we need to delete from db in saveUtxoView()
141                 entry, ok := view.Entries[prevout]
142                 if ok && (entry.Type != storage.CrosschainUTXOType) {
143                         return errors.New("look up mainchainOutputID but find utxo not from mainchain")
144                 }
145
146                 if ok && !entry.Spent {
147                         return errors.New("try to revert an unspent utxo")
148                 }
149
150                 if !ok {
151                         view.Entries[prevout] = storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false)
152                         continue
153                 }
154                 entry.UnspendOutput()
155         }
156
157         for _, prevout := range tx.SpentOutputIDs {
158                 assetID := bc.AssetID{}
159                 entryOutput, err := tx.Entry(prevout)
160                 if err != nil {
161                         return err
162                 }
163
164                 utxoType := storage.NormalUTXOType
165                 switch output := entryOutput.(type) {
166                 case *bc.IntraChainOutput:
167                         assetID = *output.Source.Value.AssetId
168                 case *bc.VoteOutput:
169                         assetID = *output.Source.Value.AssetId
170                         utxoType = storage.VoteUTXOType
171                 default:
172                         return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
173                 }
174
175                 if statusFail && assetID != *consensus.BTMAssetID {
176                         continue
177                 }
178
179                 entry, ok := view.Entries[prevout]
180                 if ok && (entry.Type == storage.CrosschainUTXOType) {
181                         return errors.New("look up SpentOutputIDs but find mainchain utxo")
182                 }
183
184                 if ok && !entry.Spent {
185                         return errors.New("try to revert an unspent utxo")
186                 }
187
188                 if !ok {
189                         view.Entries[prevout] = storage.NewUtxoEntry(utxoType, 0, false)
190                         continue
191                 }
192                 entry.UnspendOutput()
193         }
194
195         for _, id := range tx.TxHeader.ResultIds {
196                 assetID := bc.AssetID{}
197                 entryOutput, err := tx.Entry(*id)
198                 if err != nil {
199                         continue
200                 }
201
202                 utxoType := storage.NormalUTXOType
203                 switch output := entryOutput.(type) {
204                 case *bc.IntraChainOutput:
205                         assetID = *output.Source.Value.AssetId
206                 case *bc.VoteOutput:
207                         assetID = *output.Source.Value.AssetId
208                         utxoType = storage.VoteUTXOType
209                 default:
210                         // due to it's a retirement, utxo doesn't care this output type so skip it
211                         continue
212                 }
213
214                 if statusFail && assetID != *consensus.BTMAssetID {
215                         continue
216                 }
217
218                 view.Entries[*id] = storage.NewUtxoEntry(utxoType, 0, true)
219         }
220         return nil
221 }
222
223 func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
224         for i := len(block.Transactions) - 1; i >= 0; i-- {
225                 statusFail, err := txStatus.GetStatus(i)
226                 if err != nil {
227                         return err
228                 }
229                 if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
230                         return err
231                 }
232         }
233         return nil
234 }
235
236 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
237         _, ok := view.Entries[*hash]
238         return ok
239 }