OSDN Git Service

new repo
[bytom/vapor.git] / protocol / state / utxo_view.go
1 package state
2
3 import (
4         "errors"
5
6         "github.com/vapor/consensus"
7         "github.com/vapor/database/storage"
8         "github.com/vapor/protocol/bc"
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         for _, prevout := range tx.SpentOutputIDs {
25                 spentOutput, err := tx.Output(prevout)
26                 if err != nil {
27                         return err
28                 }
29                 if statusFail && *spentOutput.Source.Value.AssetId != *consensus.BTMAssetID {
30                         continue
31                 }
32
33                 entry, ok := view.Entries[prevout]
34                 if !ok {
35                         return errors.New("fail to find utxo entry")
36                 }
37                 if entry.Spent {
38                         return errors.New("utxo has been spent")
39                 }
40                 if entry.IsCoinBase && entry.BlockHeight+consensus.CoinbasePendingBlockNumber > block.Height {
41                         return errors.New("coinbase utxo is not ready for use")
42                 }
43                 entry.SpendOutput()
44         }
45
46         for _, id := range tx.TxHeader.ResultIds {
47                 output, err := tx.Output(*id)
48                 if err != nil {
49                         // error due to it's a retirement, utxo doesn't care this output type so skip it
50                         continue
51                 }
52                 if statusFail && *output.Source.Value.AssetId != *consensus.BTMAssetID {
53                         continue
54                 }
55
56                 isCoinbase := false
57                 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
58                         isCoinbase = true
59                 }
60                 view.Entries[*id] = storage.NewUtxoEntry(isCoinbase, block.Height, false)
61         }
62         return nil
63 }
64
65 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
66         for i, tx := range block.Transactions {
67                 statusFail, err := txStatus.GetStatus(i)
68                 if err != nil {
69                         return err
70                 }
71                 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
72                         return err
73                 }
74         }
75         return nil
76 }
77
78 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
79         entry := view.Entries[*hash]
80         return entry != nil && !entry.Spent
81 }
82
83 func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
84         for _, prevout := range tx.SpentOutputIDs {
85                 spentOutput, err := tx.Output(prevout)
86                 if err != nil {
87                         return err
88                 }
89                 if statusFail && *spentOutput.Source.Value.AssetId != *consensus.BTMAssetID {
90                         continue
91                 }
92
93                 entry, ok := view.Entries[prevout]
94                 if ok && !entry.Spent {
95                         return errors.New("try to revert an unspent utxo")
96                 }
97                 if !ok {
98                         view.Entries[prevout] = storage.NewUtxoEntry(false, 0, false)
99                         continue
100                 }
101                 entry.UnspendOutput()
102         }
103
104         for _, id := range tx.TxHeader.ResultIds {
105                 output, err := tx.Output(*id)
106                 if err != nil {
107                         // error due to it's a retirement, utxo doesn't care this output type so skip it
108                         continue
109                 }
110                 if statusFail && *output.Source.Value.AssetId != *consensus.BTMAssetID {
111                         continue
112                 }
113
114                 view.Entries[*id] = storage.NewUtxoEntry(false, 0, true)
115         }
116         return nil
117 }
118
119 func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
120         for i, tx := range block.Transactions {
121                 statusFail, err := txStatus.GetStatus(i)
122                 if err != nil {
123                         return err
124                 }
125                 if err := view.DetachTransaction(tx, statusFail); err != nil {
126                         return err
127                 }
128         }
129         return nil
130 }
131
132 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
133         _, ok := view.Entries[*hash]
134         return ok
135 }