github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/wallet/utxo.go (about)

     1  package wallet
     2  
     3  import (
     4  	"encoding/json"
     5  
     6  	log "github.com/sirupsen/logrus"
     7  
     8  	"github.com/bytom/bytom/account"
     9  	"github.com/bytom/bytom/consensus"
    10  	"github.com/bytom/bytom/consensus/segwit"
    11  	"github.com/bytom/bytom/crypto/sha3pool"
    12  	dbm "github.com/bytom/bytom/database/leveldb"
    13  	"github.com/bytom/bytom/errors"
    14  	"github.com/bytom/bytom/protocol/bc"
    15  	"github.com/bytom/bytom/protocol/bc/types"
    16  )
    17  
    18  // GetAccountUtxos return all account unspent outputs
    19  func (w *Wallet) GetAccountUtxos(accountID string, id string, unconfirmed, isSmartContract, vote bool) []*account.UTXO {
    20  	prefix := account.UTXOPreFix
    21  	if isSmartContract {
    22  		prefix = account.SUTXOPrefix
    23  	}
    24  
    25  	accountUtxos := []*account.UTXO{}
    26  	if unconfirmed {
    27  		accountUtxos = w.AccountMgr.ListUnconfirmedUtxo(accountID, isSmartContract)
    28  	}
    29  
    30  	accountUtxoIter := w.DB.IteratorPrefix([]byte(prefix + id))
    31  	defer accountUtxoIter.Release()
    32  
    33  	for accountUtxoIter.Next() {
    34  		accountUtxo := &account.UTXO{}
    35  		if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
    36  			log.WithFields(log.Fields{"module": logModule, "err": err}).Warn("GetAccountUtxos fail on unmarshal utxo")
    37  			continue
    38  		}
    39  
    40  		if vote && accountUtxo.Vote == nil {
    41  			continue
    42  		}
    43  
    44  		if accountID == accountUtxo.AccountID || accountID == "" {
    45  			accountUtxos = append(accountUtxos, accountUtxo)
    46  		}
    47  	}
    48  	return accountUtxos
    49  }
    50  
    51  func (w *Wallet) attachUtxos(batch dbm.Batch, b *types.Block) {
    52  	for _, tx := range b.Transactions {
    53  		// hand update the transaction input utxos
    54  		inputUtxos := txInToUtxos(tx)
    55  		for _, inputUtxo := range inputUtxos {
    56  			if segwit.IsP2WScript(inputUtxo.ControlProgram) {
    57  				batch.Delete(account.StandardUTXOKey(inputUtxo.OutputID))
    58  			} else {
    59  				batch.Delete(account.ContractUTXOKey(inputUtxo.OutputID))
    60  			}
    61  		}
    62  
    63  		// hand update the transaction output utxos
    64  		outputUtxos := txOutToUtxos(tx, b.Height)
    65  		utxos := w.filterAccountUtxo(outputUtxos)
    66  		if err := batchSaveUtxos(utxos, batch); err != nil {
    67  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on batchSaveUtxos")
    68  		}
    69  	}
    70  }
    71  
    72  func (w *Wallet) detachUtxos(batch dbm.Batch, b *types.Block) {
    73  	for txIndex := len(b.Transactions) - 1; txIndex >= 0; txIndex-- {
    74  		tx := b.Transactions[txIndex]
    75  		for j := range tx.Outputs {
    76  			resOut, err := tx.OriginalOutput(*tx.ResultIds[j])
    77  			if err != nil {
    78  				continue
    79  			}
    80  
    81  			if segwit.IsP2WScript(resOut.ControlProgram.Code) {
    82  				batch.Delete(account.StandardUTXOKey(*tx.ResultIds[j]))
    83  			} else {
    84  				batch.Delete(account.ContractUTXOKey(*tx.ResultIds[j]))
    85  			}
    86  		}
    87  
    88  		inputUtxos := txInToUtxos(tx)
    89  		utxos := w.filterAccountUtxo(inputUtxos)
    90  		if err := batchSaveUtxos(utxos, batch); err != nil {
    91  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on batchSaveUtxos")
    92  			return
    93  		}
    94  	}
    95  }
    96  
    97  func (w *Wallet) filterAccountUtxo(utxos []*account.UTXO) []*account.UTXO {
    98  	outsByScript := make(map[string][]*account.UTXO, len(utxos))
    99  	for _, utxo := range utxos {
   100  		scriptStr := string(utxo.ControlProgram)
   101  		outsByScript[scriptStr] = append(outsByScript[scriptStr], utxo)
   102  	}
   103  
   104  	result := make([]*account.UTXO, 0, len(utxos))
   105  	for s := range outsByScript {
   106  		if !segwit.IsP2WScript([]byte(s)) {
   107  			continue
   108  		}
   109  
   110  		var hash [32]byte
   111  		sha3pool.Sum256(hash[:], []byte(s))
   112  		data := w.DB.Get(account.ContractKey(hash))
   113  		if data == nil {
   114  			continue
   115  		}
   116  
   117  		cp := &account.CtrlProgram{}
   118  		if err := json.Unmarshal(data, cp); err != nil {
   119  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("filterAccountUtxo fail on unmarshal control program")
   120  			continue
   121  		}
   122  
   123  		for _, utxo := range outsByScript[s] {
   124  			utxo.AccountID = cp.AccountID
   125  			utxo.Address = cp.Address
   126  			utxo.ControlProgramIndex = cp.KeyIndex
   127  			utxo.Change = cp.Change
   128  			result = append(result, utxo)
   129  		}
   130  	}
   131  	return result
   132  }
   133  
   134  func batchSaveUtxos(utxos []*account.UTXO, batch dbm.Batch) error {
   135  	for _, utxo := range utxos {
   136  		data, err := json.Marshal(utxo)
   137  		if err != nil {
   138  			return errors.Wrap(err, "failed marshal accountutxo")
   139  		}
   140  
   141  		if segwit.IsP2WScript(utxo.ControlProgram) {
   142  			batch.Set(account.StandardUTXOKey(utxo.OutputID), data)
   143  		} else {
   144  			batch.Set(account.ContractUTXOKey(utxo.OutputID), data)
   145  		}
   146  	}
   147  	return nil
   148  }
   149  
   150  func txInToUtxos(tx *types.Tx) []*account.UTXO {
   151  	utxos := []*account.UTXO{}
   152  	for _, inpID := range tx.Tx.InputIDs {
   153  		var utxo *account.UTXO
   154  		e, ok := tx.Entries[inpID]
   155  		if !ok {
   156  			continue
   157  		}
   158  
   159  		switch inp := e.(type) {
   160  		case *bc.Spend:
   161  			resOut, err := tx.OriginalOutput(*inp.SpentOutputId)
   162  			if err != nil {
   163  				log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut")
   164  				continue
   165  			}
   166  
   167  			utxo = &account.UTXO{
   168  				OutputID:       *inp.SpentOutputId,
   169  				AssetID:        *resOut.Source.Value.AssetId,
   170  				Amount:         resOut.Source.Value.Amount,
   171  				ControlProgram: resOut.ControlProgram.Code,
   172  				SourceID:       *resOut.Source.Ref,
   173  				SourcePos:      resOut.Source.Position,
   174  			}
   175  		case *bc.VetoInput:
   176  			resOut, err := tx.VoteOutput(*inp.SpentOutputId)
   177  			if err != nil {
   178  				log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut for vetoInput")
   179  				continue
   180  			}
   181  			if *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
   182  				continue
   183  			}
   184  			utxo = &account.UTXO{
   185  				OutputID:       *inp.SpentOutputId,
   186  				AssetID:        *resOut.Source.Value.AssetId,
   187  				Amount:         resOut.Source.Value.Amount,
   188  				ControlProgram: resOut.ControlProgram.Code,
   189  				SourceID:       *resOut.Source.Ref,
   190  				SourcePos:      resOut.Source.Position,
   191  				Vote:           resOut.Vote,
   192  			}
   193  		default:
   194  			continue
   195  		}
   196  		utxos = append(utxos, utxo)
   197  	}
   198  	return utxos
   199  }
   200  
   201  func txOutToUtxos(tx *types.Tx, blockHeight uint64) []*account.UTXO {
   202  	utxos := []*account.UTXO{}
   203  	for i, out := range tx.Outputs {
   204  		validHeight := uint64(0)
   205  		entryOutput, ok := tx.Entries[*tx.ResultIds[i]]
   206  		if !ok {
   207  			log.WithFields(log.Fields{"module": logModule}).Error("txOutToUtxos fail on get entryOutput")
   208  			continue
   209  		}
   210  
   211  		var utxo *account.UTXO
   212  		switch bcOut := entryOutput.(type) {
   213  		case *bc.OriginalOutput:
   214  			if out.AssetAmount.Amount == uint64(0) {
   215  				continue
   216  			}
   217  
   218  			if tx.Inputs[0].InputType() == types.CoinbaseInputType {
   219  				validHeight = blockHeight + consensus.CoinbasePendingBlockNumber
   220  			}
   221  
   222  			utxo = &account.UTXO{
   223  				OutputID:       *tx.OutputID(i),
   224  				AssetID:        *out.AssetAmount.AssetId,
   225  				Amount:         out.AssetAmount.Amount,
   226  				ControlProgram: out.ControlProgram,
   227  				SourceID:       *bcOut.Source.Ref,
   228  				SourcePos:      bcOut.Source.Position,
   229  				ValidHeight:    validHeight,
   230  			}
   231  
   232  		case *bc.VoteOutput:
   233  			voteValidHeight := blockHeight + consensus.VotePendingBlockNums(blockHeight)
   234  			if validHeight < voteValidHeight {
   235  				validHeight = voteValidHeight
   236  			}
   237  
   238  			utxo = &account.UTXO{
   239  				OutputID:       *tx.OutputID(i),
   240  				AssetID:        *out.AssetAmount.AssetId,
   241  				Amount:         out.AssetAmount.Amount,
   242  				ControlProgram: out.ControlProgram,
   243  				SourceID:       *bcOut.Source.Ref,
   244  				SourcePos:      bcOut.Source.Position,
   245  				ValidHeight:    validHeight,
   246  				Vote:           bcOut.Vote,
   247  			}
   248  
   249  		default:
   250  			log.WithFields(log.Fields{"module": logModule}).Warn("txOutToUtxos fail on get bcOut")
   251  			continue
   252  		}
   253  		utxos = append(utxos, utxo)
   254  	}
   255  	return utxos
   256  }