OSDN Git Service

Merge branch 'dev' into dev-verify
[bytom/bytom.git] / protocol / state / utxo_view.go
1 package state
2
3 import (
4         "errors"
5
6         "github.com/bytom/consensus"
7         "github.com/bytom/database/storage"
8         "github.com/bytom/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) HasUtxo(hash *bc.Hash) bool {
24         _, ok := view.Entries[*hash]
25         return ok
26 }
27
28 func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx, statusFail bool) error {
29         for _, prevout := range tx.SpentOutputIDs {
30                 spentOutput, err := tx.Output(prevout)
31                 if err != nil {
32                         return err
33                 }
34                 if statusFail && *spentOutput.Source.Value.AssetId != *consensus.BTMAssetID {
35                         continue
36                 }
37
38                 entry, ok := view.Entries[prevout]
39                 if !ok {
40                         return errors.New("fail to find utxo entry")
41                 }
42                 if entry.Spent {
43                         return errors.New("utxo has been spent")
44                 }
45                 if entry.IsCoinBase && entry.BlockHeight+consensus.CoinbasePendingBlockNumber > block.Height {
46                         return errors.New("coinbase utxo is not ready for use")
47                 }
48                 entry.SpendOutput()
49         }
50
51         for _, id := range tx.TxHeader.ResultIds {
52                 output, err := tx.Output(*id)
53                 if err != nil {
54                         // error due to it's a retirement, utxo doesn't care this output type so skip it
55                         continue
56                 }
57                 if statusFail && *output.Source.Value.AssetId != *consensus.BTMAssetID {
58                         continue
59                 }
60
61                 isCoinbase := false
62                 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
63                         isCoinbase = true
64                 }
65                 view.Entries[*id] = storage.NewUtxoEntry(isCoinbase, block.Height, false)
66         }
67         return nil
68 }
69
70 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
71         for i, tx := range block.Transactions {
72                 statusFail, err := txStatus.GetStatus(i)
73                 if err != nil {
74                         return err
75                 }
76                 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
77                         return err
78                 }
79         }
80         return nil
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 }