OSDN Git Service

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