github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/protocol/state/utxo_view.go (about)

     1  package state
     2  
     3  import (
     4  	"github.com/bytom/bytom/consensus"
     5  	"github.com/bytom/bytom/database/storage"
     6  	"github.com/bytom/bytom/errors"
     7  	"github.com/bytom/bytom/protocol/bc"
     8  )
     9  
    10  // UtxoViewpoint represents a view into the set of unspent transaction outputs
    11  type UtxoViewpoint struct {
    12  	Entries map[bc.Hash]*storage.UtxoEntry
    13  }
    14  
    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),
    19  	}
    20  }
    21  
    22  func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx) error {
    23  	if err := view.applySpendUtxo(block, tx); err != nil {
    24  		return err
    25  	}
    26  
    27  	return view.applyOutputUtxo(block, tx)
    28  }
    29  
    30  func (view *UtxoViewpoint) applySpendUtxo(block *bc.Block, tx *bc.Tx) error {
    31  	for _, prevout := range tx.SpentOutputIDs {
    32  		entry, ok := view.Entries[prevout]
    33  		if !ok {
    34  			return errors.New("fail to find utxo entry")
    35  		}
    36  		if entry.Spent {
    37  			return errors.New("utxo has been spent")
    38  		}
    39  		switch entry.Type {
    40  		case storage.CoinbaseUTXOType:
    41  			if entry.BlockHeight+consensus.CoinbasePendingBlockNumber > block.Height {
    42  				return errors.New("coinbase utxo is not ready for use")
    43  			}
    44  		case storage.VoteUTXOType:
    45  			if entry.BlockHeight + consensus.VotePendingBlockNums(block.Height) > block.Height {
    46  				return errors.New("Coin is  within the voting lock time")
    47  			}
    48  		}
    49  
    50  		entry.SpendOutput()
    51  	}
    52  	return nil
    53  }
    54  
    55  func (view *UtxoViewpoint) applyOutputUtxo(block *bc.Block, tx *bc.Tx) error {
    56  	for _, id := range tx.TxHeader.ResultIds {
    57  		entryOutput, ok := tx.Entries[*id]
    58  		if !ok {
    59  			// error due to it's a retirement, utxo doesn't care this output type so skip it
    60  			continue
    61  		}
    62  
    63  
    64  		var utxoType uint32
    65  		var amount uint64
    66  		switch output := entryOutput.(type) {
    67  		case *bc.OriginalOutput:
    68  			amount = output.Source.Value.Amount
    69  			utxoType = storage.NormalUTXOType
    70  		case *bc.VoteOutput:
    71  			amount = output.Source.Value.Amount
    72  			utxoType = storage.VoteUTXOType
    73  		default:
    74  			// due to it's a retirement, utxo doesn't care this output type so skip it
    75  			continue
    76  		}
    77  
    78  		if amount == 0 {
    79  			continue
    80  		}
    81  
    82  		if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
    83  			utxoType = storage.CoinbaseUTXOType
    84  		}
    85  		view.Entries[*id] = storage.NewUtxoEntry(utxoType, block.Height, false)
    86  	}
    87  	return nil
    88  }
    89  
    90  func (view *UtxoViewpoint) ApplyBlock(block *bc.Block) error {
    91  	for _, tx := range block.Transactions {
    92  		if err := view.ApplyTransaction(block, tx); err != nil {
    93  			return err
    94  		}
    95  	}
    96  	return nil
    97  }
    98  
    99  func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
   100  	entry := view.Entries[*hash]
   101  	return entry != nil && !entry.Spent
   102  }
   103  
   104  func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx) error {
   105  	if err := view.detachSpendUtxo(tx); err != nil {
   106  		return err
   107  	}
   108  
   109  	return view.detachOutputUtxo(tx)
   110  }
   111  
   112  func (view *UtxoViewpoint) detachSpendUtxo(tx *bc.Tx) error {
   113  	for _, prevout := range tx.SpentOutputIDs {
   114  		entryOutput, ok := tx.Entries[prevout]
   115  		if !ok {
   116  			return errors.New("fail to find utxo entry")
   117  		}
   118  
   119  		var utxoType uint32
   120  		switch entryOutput.(type) {
   121  		case *bc.OriginalOutput:
   122  			utxoType = storage.NormalUTXOType
   123  		case *bc.VoteOutput:
   124  			utxoType = storage.VoteUTXOType
   125  		default:
   126  			return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
   127  		}
   128  
   129  		entry, ok := view.Entries[prevout]
   130  		if ok && !entry.Spent {
   131  			return errors.New("try to revert an unspent utxo")
   132  		}
   133  		if !ok {
   134  			view.Entries[prevout] = storage.NewUtxoEntry(utxoType, 0, false)
   135  			continue
   136  		}
   137  		entry.UnspendOutput()
   138  	}
   139  	return nil
   140  }
   141  
   142  func (view *UtxoViewpoint) detachOutputUtxo(tx *bc.Tx) error {
   143  	for _, id := range tx.TxHeader.ResultIds {
   144  		entryOutput, ok := tx.Entries[*id]
   145  		if !ok {
   146  			// error due to it's a retirement, utxo doesn't care this output type so skip it
   147  			continue
   148  		}
   149  
   150  		var utxoType uint32
   151  		var amount uint64
   152  		switch output := entryOutput.(type) {
   153  		case *bc.OriginalOutput:
   154  			amount = output.Source.Value.Amount
   155  			utxoType = storage.NormalUTXOType
   156  		case *bc.VoteOutput:
   157  			amount = output.Source.Value.Amount
   158  			utxoType = storage.VoteUTXOType
   159  		default:
   160  			// due to it's a retirement, utxo doesn't care this output type so skip it
   161  			continue
   162  		}
   163  
   164  		if amount == 0 {
   165  			continue
   166  		}
   167  
   168  		view.Entries[*id] = storage.NewUtxoEntry(utxoType, 0, true)
   169  	}
   170  	return nil
   171  }
   172  
   173  func (view *UtxoViewpoint) DetachBlock(block *bc.Block) error {
   174  	for i := len(block.Transactions) - 1; i >= 0; i-- {
   175  		if err := view.DetachTransaction(block.Transactions[i]); err != nil {
   176  			return err
   177  		}
   178  	}
   179  	return nil
   180  }
   181  
   182  func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
   183  	_, ok := view.Entries[*hash]
   184  	return ok
   185  }