github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/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 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 accountID == accountUtxo.AccountID || accountID == "" {
    41  			accountUtxos = append(accountUtxos, accountUtxo)
    42  		}
    43  	}
    44  	return accountUtxos
    45  }
    46  
    47  func (w *Wallet) attachUtxos(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
    48  	for txIndex, tx := range b.Transactions {
    49  		statusFail, err := txStatus.GetStatus(txIndex)
    50  		if err != nil {
    51  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on get tx status")
    52  			continue
    53  		}
    54  
    55  		//hand update the transaction input utxos
    56  		inputUtxos := txInToUtxos(tx, statusFail)
    57  		for _, inputUtxo := range inputUtxos {
    58  			if segwit.IsP2WScript(inputUtxo.ControlProgram) {
    59  				batch.Delete(account.StandardUTXOKey(inputUtxo.OutputID))
    60  			} else {
    61  				batch.Delete(account.ContractUTXOKey(inputUtxo.OutputID))
    62  			}
    63  		}
    64  
    65  		//hand update the transaction output utxos
    66  		validHeight := uint64(0)
    67  		if txIndex == 0 {
    68  			validHeight = b.Height + consensus.CoinbasePendingBlockNumber
    69  		}
    70  		outputUtxos := txOutToUtxos(tx, statusFail, validHeight)
    71  		utxos := w.filterAccountUtxo(outputUtxos)
    72  		if err := batchSaveUtxos(utxos, batch); err != nil {
    73  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on batchSaveUtxos")
    74  		}
    75  	}
    76  }
    77  
    78  func (w *Wallet) detachUtxos(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
    79  	for txIndex := len(b.Transactions) - 1; txIndex >= 0; txIndex-- {
    80  		tx := b.Transactions[txIndex]
    81  		for j := range tx.Outputs {
    82  			resOut, err := tx.Output(*tx.ResultIds[j])
    83  			if err != nil {
    84  				continue
    85  			}
    86  
    87  			if segwit.IsP2WScript(resOut.ControlProgram.Code) {
    88  				batch.Delete(account.StandardUTXOKey(*tx.ResultIds[j]))
    89  			} else {
    90  				batch.Delete(account.ContractUTXOKey(*tx.ResultIds[j]))
    91  			}
    92  		}
    93  
    94  		statusFail, err := txStatus.GetStatus(txIndex)
    95  		if err != nil {
    96  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on get tx status")
    97  			continue
    98  		}
    99  
   100  		inputUtxos := txInToUtxos(tx, statusFail)
   101  		utxos := w.filterAccountUtxo(inputUtxos)
   102  		if err := batchSaveUtxos(utxos, batch); err != nil {
   103  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on batchSaveUtxos")
   104  			return
   105  		}
   106  	}
   107  }
   108  
   109  func (w *Wallet) filterAccountUtxo(utxos []*account.UTXO) []*account.UTXO {
   110  	outsByScript := make(map[string][]*account.UTXO, len(utxos))
   111  	for _, utxo := range utxos {
   112  		scriptStr := string(utxo.ControlProgram)
   113  		outsByScript[scriptStr] = append(outsByScript[scriptStr], utxo)
   114  	}
   115  
   116  	result := make([]*account.UTXO, 0, len(utxos))
   117  	for s := range outsByScript {
   118  		if !segwit.IsP2WScript([]byte(s)) {
   119  			continue
   120  		}
   121  
   122  		var hash [32]byte
   123  		sha3pool.Sum256(hash[:], []byte(s))
   124  		data := w.DB.Get(account.ContractKey(hash))
   125  		if data == nil {
   126  			continue
   127  		}
   128  
   129  		cp := &account.CtrlProgram{}
   130  		if err := json.Unmarshal(data, cp); err != nil {
   131  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("filterAccountUtxo fail on unmarshal control program")
   132  			continue
   133  		}
   134  
   135  		for _, utxo := range outsByScript[s] {
   136  			utxo.AccountID = cp.AccountID
   137  			utxo.Address = cp.Address
   138  			utxo.ControlProgramIndex = cp.KeyIndex
   139  			utxo.Change = cp.Change
   140  			result = append(result, utxo)
   141  		}
   142  	}
   143  	return result
   144  }
   145  
   146  func batchSaveUtxos(utxos []*account.UTXO, batch dbm.Batch) error {
   147  	for _, utxo := range utxos {
   148  		data, err := json.Marshal(utxo)
   149  		if err != nil {
   150  			return errors.Wrap(err, "failed marshal accountutxo")
   151  		}
   152  
   153  		if segwit.IsP2WScript(utxo.ControlProgram) {
   154  			batch.Set(account.StandardUTXOKey(utxo.OutputID), data)
   155  		} else {
   156  			batch.Set(account.ContractUTXOKey(utxo.OutputID), data)
   157  		}
   158  	}
   159  	return nil
   160  }
   161  
   162  func txInToUtxos(tx *types.Tx, statusFail bool) []*account.UTXO {
   163  	utxos := []*account.UTXO{}
   164  	for _, inpID := range tx.Tx.InputIDs {
   165  		sp, err := tx.Spend(inpID)
   166  		if err != nil {
   167  			continue
   168  		}
   169  
   170  		resOut, err := tx.Output(*sp.SpentOutputId)
   171  		if err != nil {
   172  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut")
   173  			continue
   174  		}
   175  
   176  		if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
   177  			continue
   178  		}
   179  
   180  		utxos = append(utxos, &account.UTXO{
   181  			OutputID:       *sp.SpentOutputId,
   182  			AssetID:        *resOut.Source.Value.AssetId,
   183  			Amount:         resOut.Source.Value.Amount,
   184  			ControlProgram: resOut.ControlProgram.Code,
   185  			SourceID:       *resOut.Source.Ref,
   186  			SourcePos:      resOut.Source.Position,
   187  		})
   188  	}
   189  	return utxos
   190  }
   191  
   192  func txOutToUtxos(tx *types.Tx, statusFail bool, vaildHeight uint64) []*account.UTXO {
   193  	utxos := []*account.UTXO{}
   194  	for i, out := range tx.Outputs {
   195  		bcOut, err := tx.Output(*tx.ResultIds[i])
   196  		if err != nil {
   197  			continue
   198  		}
   199  
   200  		if statusFail && *out.AssetAmount.AssetId != *consensus.BTMAssetID {
   201  			continue
   202  		}
   203  
   204  		utxos = append(utxos, &account.UTXO{
   205  			OutputID:       *tx.OutputID(i),
   206  			AssetID:        *out.AssetAmount.AssetId,
   207  			Amount:         out.Amount,
   208  			ControlProgram: out.ControlProgram,
   209  			SourceID:       *bcOut.Source.Ref,
   210  			SourcePos:      bcOut.Source.Position,
   211  			ValidHeight:    vaildHeight,
   212  		})
   213  	}
   214  	return utxos
   215  }