github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/protocol/state/utxo_view.go (about)

     1  package state
     2  
     3  import (
     4  	"errors"
     5  
     6  	"github.com/bytom/bytom/consensus"
     7  	"github.com/bytom/bytom/database/storage"
     8  	"github.com/bytom/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) 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 := len(block.Transactions) - 1; i >= 0; i-- {
   121  		statusFail, err := txStatus.GetStatus(i)
   122  		if err != nil {
   123  			return err
   124  		}
   125  		if err := view.DetachTransaction(block.Transactions[i], 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  }