OSDN Git Service

c8071444f9e1afaba545a63c226c9a3564cbd7a0
[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                 if entry.Type == storage.CrosschainUTXOType {
66                         return errors.New("look up spentOutputID but find utxo from mainchain")
67                 }
68                 if entry.Spent {
69                         return errors.New("utxo has been spent")
70                 }
71                 if (entry.Type == storage.CoinbaseUTXOType) && ((entry.BlockHeight + consensus.CoinbasePendingBlockNumber) > block.Height) {
72                         return errors.New("coinbase utxo is not ready for use")
73                 }
74                 entry.SpendOutput()
75         }
76
77         for _, id := range tx.TxHeader.ResultIds {
78                 assetID := bc.AssetID{}
79                 entryOutput, err := tx.Entry(*id)
80                 if err != nil {
81                         continue
82                 }
83
84                 switch output := entryOutput.(type) {
85                 case *bc.IntraChainOutput:
86                         assetID = *output.Source.Value.AssetId
87                 case *bc.VoteOutput:
88                         assetID = *output.Source.Value.AssetId
89                 default:
90                         // due to it's a retirement, utxo doesn't care this output type so skip it
91                         continue
92                 }
93
94                 if statusFail && assetID != *consensus.BTMAssetID {
95                         continue
96                 }
97
98                 utxoType := storage.NormalUTXOType
99                 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
100                         utxoType = storage.CoinbaseUTXOType
101                 }
102                 view.Entries[*id] = storage.NewUtxoEntry(utxoType, block.Height, false)
103         }
104         return nil
105 }
106
107 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
108         for i, tx := range block.Transactions {
109                 statusFail, err := txStatus.GetStatus(i)
110                 if err != nil {
111                         return err
112                 }
113                 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
114                         return err
115                 }
116         }
117         return nil
118 }
119
120 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
121         entry := view.Entries[*hash]
122         return entry != nil && !entry.Spent
123 }
124
125 func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
126         for _, prevout := range tx.MainchainOutputIDs {
127                 // don't simply delete(view.Entries, prevout), because we need to delete from db in saveUtxoView()
128                 entry, ok := view.Entries[prevout]
129                 if ok && (entry.Type != storage.CrosschainUTXOType) {
130                         return errors.New("look up mainchainOutputID but find utxo not from mainchain")
131                 }
132
133                 if ok && !entry.Spent {
134                         return errors.New("try to revert an unspent utxo")
135                 }
136
137                 if !ok {
138                         view.Entries[prevout] = storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false)
139                         continue
140                 }
141                 entry.UnspendOutput()
142         }
143
144         for _, prevout := range tx.SpentOutputIDs {
145                 assetID := bc.AssetID{}
146                 entryOutput, err := tx.Entry(prevout)
147                 if err != nil {
148                         return err
149                 }
150
151                 switch output := entryOutput.(type) {
152                 case *bc.IntraChainOutput:
153                         assetID = *output.Source.Value.AssetId
154                 case *bc.VoteOutput:
155                         assetID = *output.Source.Value.AssetId
156                 default:
157                         return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
158                 }
159
160                 if statusFail && assetID != *consensus.BTMAssetID {
161                         continue
162                 }
163
164                 entry, ok := view.Entries[prevout]
165                 if ok && (entry.Type == storage.CrosschainUTXOType) {
166                         return errors.New("look up SpentOutputIDs but find mainchain utxo")
167                 }
168
169                 if ok && !entry.Spent {
170                         return errors.New("try to revert an unspent utxo")
171                 }
172
173                 if !ok {
174                         view.Entries[prevout] = storage.NewUtxoEntry(storage.NormalUTXOType, 0, false)
175                         continue
176                 }
177                 entry.UnspendOutput()
178         }
179
180         for _, id := range tx.TxHeader.ResultIds {
181                 assetID := bc.AssetID{}
182                 entryOutput, err := tx.Entry(*id)
183                 if err != nil {
184                         continue
185                 }
186
187                 switch output := entryOutput.(type) {
188                 case *bc.IntraChainOutput:
189                         assetID = *output.Source.Value.AssetId
190                 case *bc.VoteOutput:
191                         assetID = *output.Source.Value.AssetId
192                 default:
193                         // due to it's a retirement, utxo doesn't care this output type so skip it
194                         continue
195                 }
196
197                 if statusFail && assetID != *consensus.BTMAssetID {
198                         continue
199                 }
200
201                 view.Entries[*id] = storage.NewUtxoEntry(storage.NormalUTXOType, 0, true)
202         }
203         return nil
204 }
205
206 func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
207         for i := len(block.Transactions) - 1; i >= 0; i-- {
208                 statusFail, err := txStatus.GetStatus(i)
209                 if err != nil {
210                         return err
211                 }
212                 if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
213                         return err
214                 }
215         }
216         return nil
217 }
218
219 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
220         _, ok := view.Entries[*hash]
221         return ok
222 }