4 "github.com/vapor/consensus"
5 "github.com/vapor/database/storage"
6 "github.com/vapor/errors"
7 "github.com/vapor/protocol/bc"
10 // UtxoViewpoint represents a view into the set of unspent transaction outputs
11 type UtxoViewpoint struct {
12 Entries map[bc.Hash]*storage.UtxoEntry
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),
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)
30 switch output := entryOutput.(type) {
31 case *bc.IntraChainOutput:
32 assetID = *output.Source.Value.AssetId
34 assetID = *output.Source.Value.AssetId
36 return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
39 if statusFail && assetID != *consensus.BTMAssetID {
43 entry, ok := view.Entries[prevout]
45 return errors.New("fail to find utxo entry")
48 return errors.New("utxo has been spent")
50 if entry.IsCoinBase && entry.BlockHeight+consensus.CoinbasePendingBlockNumber > block.Height {
51 return errors.New("coinbase utxo is not ready for use")
56 for _, id := range tx.TxHeader.ResultIds {
57 assetID := bc.AssetID{}
58 entryOutput, err := tx.Entry(*id)
63 switch output := entryOutput.(type) {
64 case *bc.IntraChainOutput:
65 assetID = *output.Source.Value.AssetId
67 assetID = *output.Source.Value.AssetId
69 // due to it's a retirement, utxo doesn't care this output type so skip it
73 if statusFail && assetID != *consensus.BTMAssetID {
78 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
81 view.Entries[*id] = storage.NewUtxoEntry(isCoinbase, block.Height, false)
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)
92 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
99 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
100 entry := view.Entries[*hash]
101 return entry != nil && !entry.Spent
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)
112 switch output := entryOutput.(type) {
113 case *bc.IntraChainOutput:
114 assetID = *output.Source.Value.AssetId
116 assetID = *output.Source.Value.AssetId
118 return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
121 if statusFail && assetID != *consensus.BTMAssetID {
125 entry, ok := view.Entries[prevout]
126 if ok && !entry.Spent {
127 return errors.New("try to revert an unspent utxo")
130 view.Entries[prevout] = storage.NewUtxoEntry(false, 0, false)
133 entry.UnspendOutput()
136 for _, id := range tx.TxHeader.ResultIds {
137 assetID := bc.AssetID{}
138 entryOutput, err := tx.Entry(*id)
143 switch output := entryOutput.(type) {
144 case *bc.IntraChainOutput:
145 assetID = *output.Source.Value.AssetId
147 assetID = *output.Source.Value.AssetId
149 // due to it's a retirement, utxo doesn't care this output type so skip it
153 if statusFail && assetID != *consensus.BTMAssetID {
157 view.Entries[*id] = storage.NewUtxoEntry(false, 0, true)
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)
168 if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
175 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
176 _, ok := view.Entries[*hash]