OSDN Git Service

V0.1 votetx utxo (#73)
[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.SpentOutputIDs {
24                 assetID := bc.AssetID{}
25                 entryOutput, err := tx.Entry(prevout)
26                 if err != nil {
27                         return err
28                 }
29
30                 switch output := entryOutput.(type) {
31                 case *bc.IntraChainOutput:
32                         assetID = *output.Source.Value.AssetId
33                 case *bc.VoteOutput:
34                         assetID = *output.Source.Value.AssetId
35                 default:
36                         return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
37                 }
38
39                 if statusFail && assetID != *consensus.BTMAssetID {
40                         continue
41                 }
42
43                 entry, ok := view.Entries[prevout]
44                 if !ok {
45                         return errors.New("fail to find utxo entry")
46                 }
47                 if entry.Spent {
48                         return errors.New("utxo has been spent")
49                 }
50                 if entry.IsCoinBase && entry.BlockHeight+consensus.CoinbasePendingBlockNumber > block.Height {
51                         return errors.New("coinbase utxo is not ready for use")
52                 }
53                 entry.SpendOutput()
54         }
55
56         for _, id := range tx.TxHeader.ResultIds {
57                 assetID := bc.AssetID{}
58                 entryOutput, err := tx.Entry(*id)
59                 if err != nil {
60                         continue
61                 }
62
63                 switch output := entryOutput.(type) {
64                 case *bc.IntraChainOutput:
65                         assetID = *output.Source.Value.AssetId
66                 case *bc.VoteOutput:
67                         assetID = *output.Source.Value.AssetId
68                 default:
69                         // due to it's a retirement, utxo doesn't care this output type so skip it
70                         continue
71                 }
72
73                 if statusFail && assetID != *consensus.BTMAssetID {
74                         continue
75                 }
76
77                 isCoinbase := false
78                 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
79                         isCoinbase = true
80                 }
81                 view.Entries[*id] = storage.NewUtxoEntry(isCoinbase, block.Height, false)
82         }
83         return nil
84 }
85
86 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
87         for i, tx := range block.Transactions {
88                 statusFail, err := txStatus.GetStatus(i)
89                 if err != nil {
90                         return err
91                 }
92                 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
93                         return err
94                 }
95         }
96         return nil
97 }
98
99 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
100         entry := view.Entries[*hash]
101         return entry != nil && !entry.Spent
102 }
103
104 func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
105         for _, prevout := range tx.SpentOutputIDs {
106                 assetID := bc.AssetID{}
107                 entryOutput, err := tx.Entry(prevout)
108                 if err != nil {
109                         return err
110                 }
111
112                 switch output := entryOutput.(type) {
113                 case *bc.IntraChainOutput:
114                         assetID = *output.Source.Value.AssetId
115                 case *bc.VoteOutput:
116                         assetID = *output.Source.Value.AssetId
117                 default:
118                         return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
119                 }
120
121                 if statusFail && assetID != *consensus.BTMAssetID {
122                         continue
123                 }
124
125                 entry, ok := view.Entries[prevout]
126                 if ok && !entry.Spent {
127                         return errors.New("try to revert an unspent utxo")
128                 }
129                 if !ok {
130                         view.Entries[prevout] = storage.NewUtxoEntry(false, 0, false)
131                         continue
132                 }
133                 entry.UnspendOutput()
134         }
135
136         for _, id := range tx.TxHeader.ResultIds {
137                 assetID := bc.AssetID{}
138                 entryOutput, err := tx.Entry(*id)
139                 if err != nil {
140                         continue
141                 }
142
143                 switch output := entryOutput.(type) {
144                 case *bc.IntraChainOutput:
145                         assetID = *output.Source.Value.AssetId
146                 case *bc.VoteOutput:
147                         assetID = *output.Source.Value.AssetId
148                 default:
149                         // due to it's a retirement, utxo doesn't care this output type so skip it
150                         continue
151                 }
152
153                 if statusFail && assetID != *consensus.BTMAssetID {
154                         continue
155                 }
156
157                 view.Entries[*id] = storage.NewUtxoEntry(false, 0, true)
158         }
159         return nil
160 }
161
162 func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
163         for i := len(block.Transactions) - 1; i >= 0; i-- {
164                 statusFail, err := txStatus.GetStatus(i)
165                 if err != nil {
166                         return err
167                 }
168                 if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
169                         return err
170                 }
171         }
172         return nil
173 }
174
175 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
176         _, ok := view.Entries[*hash]
177         return ok
178 }