6 "github.com/vapor/consensus"
7 "github.com/vapor/database/storage"
8 "github.com/vapor/protocol/bc"
11 // UtxoViewpoint represents a view into the set of unspent transaction outputs
12 type UtxoViewpoint struct {
13 Entries map[bc.Hash]*storage.UtxoEntry
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),
23 func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx, statusFail bool) error {
24 for _, prevout := range tx.SpentOutputIDs {
25 spentOutput, err := tx.IntraChainOutput(prevout)
29 if statusFail && *spentOutput.Source.Value.AssetId != *consensus.BTMAssetID {
33 entry, ok := view.Entries[prevout]
35 return errors.New("fail to find utxo entry")
38 return errors.New("utxo has been spent")
40 if entry.IsCoinBase && entry.BlockHeight+consensus.CoinbasePendingBlockNumber > block.Height {
41 return errors.New("coinbase utxo is not ready for use")
46 for _, id := range tx.TxHeader.ResultIds {
47 output, err := tx.IntraChainOutput(*id)
49 // error due to it's a retirement, utxo doesn't care this output type so skip it
52 if statusFail && *output.Source.Value.AssetId != *consensus.BTMAssetID {
57 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
60 view.Entries[*id] = storage.NewUtxoEntry(isCoinbase, block.Height, false)
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)
71 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
78 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
79 entry := view.Entries[*hash]
80 return entry != nil && !entry.Spent
83 func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
84 for _, prevout := range tx.SpentOutputIDs {
85 spentOutput, err := tx.IntraChainOutput(prevout)
89 if statusFail && *spentOutput.Source.Value.AssetId != *consensus.BTMAssetID {
93 entry, ok := view.Entries[prevout]
94 if ok && !entry.Spent {
95 return errors.New("try to revert an unspent utxo")
98 view.Entries[prevout] = storage.NewUtxoEntry(false, 0, false)
101 entry.UnspendOutput()
104 for _, id := range tx.TxHeader.ResultIds {
105 output, err := tx.IntraChainOutput(*id)
107 // error due to it's a retirement, utxo doesn't care this output type so skip it
110 if statusFail && *output.Source.Value.AssetId != *consensus.BTMAssetID {
114 view.Entries[*id] = storage.NewUtxoEntry(false, 0, true)
119 func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
120 for i := len(block.Transactions) - 1; i >= 0; i-- {
121 statusFail, err := txStatus.GetStatus(i)
125 if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
132 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
133 _, ok := view.Entries[*hash]