OSDN Git Service

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