github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/wallet/annotated.go (about)

     1  package wallet
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  
     7  	log "github.com/sirupsen/logrus"
     8  
     9  	"github.com/bytom/bytom/account"
    10  	"github.com/bytom/bytom/asset"
    11  	"github.com/bytom/bytom/blockchain/query"
    12  	"github.com/bytom/bytom/blockchain/signers"
    13  	"github.com/bytom/bytom/common"
    14  	"github.com/bytom/bytom/consensus"
    15  	"github.com/bytom/bytom/consensus/segwit"
    16  	"github.com/bytom/bytom/crypto/sha3pool"
    17  	dbm "github.com/bytom/bytom/database/leveldb"
    18  	"github.com/bytom/bytom/protocol/bc"
    19  	"github.com/bytom/bytom/protocol/bc/types"
    20  	"github.com/bytom/bytom/protocol/vm/vmutil"
    21  )
    22  
    23  // annotateTxs adds asset data to transactions
    24  func annotateTxsAsset(w *Wallet, txs []*query.AnnotatedTx) {
    25  	for i, tx := range txs {
    26  		for j, input := range tx.Inputs {
    27  			alias, definition := w.getAliasDefinition(input.AssetID)
    28  			txs[i].Inputs[j].AssetAlias, txs[i].Inputs[j].AssetDefinition = alias, &definition
    29  		}
    30  		for k, output := range tx.Outputs {
    31  			alias, definition := w.getAliasDefinition(output.AssetID)
    32  			txs[i].Outputs[k].AssetAlias, txs[i].Outputs[k].AssetDefinition = alias, &definition
    33  		}
    34  	}
    35  }
    36  
    37  func (w *Wallet) getExternalDefinition(assetID *bc.AssetID) json.RawMessage {
    38  	definitionByte := w.DB.Get(asset.ExtAssetKey(assetID))
    39  	if definitionByte == nil {
    40  		return nil
    41  	}
    42  
    43  	definitionMap := make(map[string]interface{})
    44  	if err := json.Unmarshal(definitionByte, &definitionMap); err != nil {
    45  		return nil
    46  	}
    47  
    48  	alias := assetID.String()
    49  	externalAsset := &asset.Asset{
    50  		AssetID:           *assetID,
    51  		Alias:             &alias,
    52  		DefinitionMap:     definitionMap,
    53  		RawDefinitionByte: definitionByte,
    54  		Signer:            &signers.Signer{Type: "external"},
    55  	}
    56  
    57  	if err := w.AssetReg.SaveAsset(externalAsset, alias); err != nil {
    58  		log.WithFields(log.Fields{"module": logModule, "err": err, "assetID": alias}).Warning("fail on save external asset to internal asset DB")
    59  	}
    60  	return definitionByte
    61  }
    62  
    63  func (w *Wallet) getAliasDefinition(assetID bc.AssetID) (string, json.RawMessage) {
    64  	//btm
    65  	if assetID.String() == consensus.BTMAssetID.String() {
    66  		alias := consensus.BTMAlias
    67  		definition := []byte(asset.DefaultNativeAsset.RawDefinitionByte)
    68  
    69  		return alias, definition
    70  	}
    71  
    72  	//local asset and saved external asset
    73  	if localAsset, err := w.AssetReg.FindByID(nil, &assetID); err == nil {
    74  		alias := *localAsset.Alias
    75  		definition := []byte(localAsset.RawDefinitionByte)
    76  		return alias, definition
    77  	}
    78  
    79  	//external asset
    80  	if definition := w.getExternalDefinition(&assetID); definition != nil {
    81  		return assetID.String(), definition
    82  	}
    83  
    84  	return "", nil
    85  }
    86  
    87  // annotateTxs adds account data to transactions
    88  func annotateTxsAccount(txs []*query.AnnotatedTx, walletDB dbm.DB) {
    89  	for i, tx := range txs {
    90  		for j, input := range tx.Inputs {
    91  			//issue asset tx input SpentOutputID is nil
    92  			if input.SpentOutputID == nil {
    93  				continue
    94  			}
    95  			localAccount, err := getAccountFromACP(input.ControlProgram, walletDB)
    96  			if localAccount == nil || err != nil {
    97  				continue
    98  			}
    99  			txs[i].Inputs[j].AccountAlias = localAccount.Alias
   100  			txs[i].Inputs[j].AccountID = localAccount.ID
   101  		}
   102  		for j, output := range tx.Outputs {
   103  			localAccount, err := getAccountFromACP(output.ControlProgram, walletDB)
   104  			if localAccount == nil || err != nil {
   105  				continue
   106  			}
   107  			txs[i].Outputs[j].AccountAlias = localAccount.Alias
   108  			txs[i].Outputs[j].AccountID = localAccount.ID
   109  		}
   110  	}
   111  }
   112  
   113  func getAccountFromACP(program []byte, walletDB dbm.DB) (*account.Account, error) {
   114  	var hash common.Hash
   115  	accountCP := account.CtrlProgram{}
   116  	localAccount := account.Account{}
   117  
   118  	sha3pool.Sum256(hash[:], program)
   119  
   120  	rawProgram := walletDB.Get(account.ContractKey(hash))
   121  	if rawProgram == nil {
   122  		return nil, fmt.Errorf("failed get account control program:%x ", hash)
   123  	}
   124  
   125  	if err := json.Unmarshal(rawProgram, &accountCP); err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	accountValue := walletDB.Get(account.Key(accountCP.AccountID))
   130  	if accountValue == nil {
   131  		return nil, fmt.Errorf("failed get account:%s ", accountCP.AccountID)
   132  	}
   133  
   134  	if err := json.Unmarshal(accountValue, &localAccount); err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	return &localAccount, nil
   139  }
   140  
   141  var emptyJSONObject = json.RawMessage(`{}`)
   142  
   143  func isValidJSON(b []byte) bool {
   144  	var v interface{}
   145  	err := json.Unmarshal(b, &v)
   146  	return err == nil
   147  }
   148  
   149  func (w *Wallet) buildAnnotatedTransaction(orig *types.Tx, b *types.Block, statusFail bool, indexInBlock int) *query.AnnotatedTx {
   150  	tx := &query.AnnotatedTx{
   151  		ID:                     orig.ID,
   152  		Timestamp:              b.Timestamp,
   153  		BlockID:                b.Hash(),
   154  		BlockHeight:            b.Height,
   155  		Position:               uint32(indexInBlock),
   156  		BlockTransactionsCount: uint32(len(b.Transactions)),
   157  		Inputs:                 make([]*query.AnnotatedInput, 0, len(orig.Inputs)),
   158  		Outputs:                make([]*query.AnnotatedOutput, 0, len(orig.Outputs)),
   159  		StatusFail:             statusFail,
   160  		Size:                   orig.SerializedSize,
   161  	}
   162  	for i := range orig.Inputs {
   163  		tx.Inputs = append(tx.Inputs, w.BuildAnnotatedInput(orig, uint32(i)))
   164  	}
   165  	for i := range orig.Outputs {
   166  		tx.Outputs = append(tx.Outputs, w.BuildAnnotatedOutput(orig, i))
   167  	}
   168  	return tx
   169  }
   170  
   171  // BuildAnnotatedInput build the annotated input.
   172  func (w *Wallet) BuildAnnotatedInput(tx *types.Tx, i uint32) *query.AnnotatedInput {
   173  	orig := tx.Inputs[i]
   174  	in := &query.AnnotatedInput{
   175  		AssetDefinition: &emptyJSONObject,
   176  	}
   177  	if orig.InputType() != types.CoinbaseInputType {
   178  		in.AssetID = orig.AssetID()
   179  		in.Amount = orig.Amount()
   180  		in.SignData = tx.SigHash(i)
   181  	}
   182  
   183  	id := tx.Tx.InputIDs[i]
   184  	in.InputID = id
   185  	e := tx.Entries[id]
   186  	switch e := e.(type) {
   187  	case *bc.Spend:
   188  		in.Type = "spend"
   189  		in.ControlProgram = orig.ControlProgram()
   190  		in.Address = w.getAddressFromControlProgram(in.ControlProgram)
   191  		in.SpentOutputID = e.SpentOutputId
   192  		arguments := orig.Arguments()
   193  		for _, arg := range arguments {
   194  			in.WitnessArguments = append(in.WitnessArguments, arg)
   195  		}
   196  	case *bc.Issuance:
   197  		in.Type = "issue"
   198  		in.IssuanceProgram = orig.IssuanceProgram()
   199  		arguments := orig.Arguments()
   200  		for _, arg := range arguments {
   201  			in.WitnessArguments = append(in.WitnessArguments, arg)
   202  		}
   203  		if assetDefinition := orig.AssetDefinition(); isValidJSON(assetDefinition) {
   204  			assetDefinition := json.RawMessage(assetDefinition)
   205  			in.AssetDefinition = &assetDefinition
   206  		}
   207  	case *bc.Coinbase:
   208  		in.Type = "coinbase"
   209  		in.Arbitrary = e.Arbitrary
   210  	}
   211  	return in
   212  }
   213  
   214  func (w *Wallet) getAddressFromControlProgram(prog []byte) string {
   215  	if segwit.IsP2WPKHScript(prog) {
   216  		if pubHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
   217  			return buildP2PKHAddress(pubHash)
   218  		}
   219  	} else if segwit.IsP2WSHScript(prog) {
   220  		if scriptHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
   221  			return buildP2SHAddress(scriptHash)
   222  		}
   223  	}
   224  
   225  	return ""
   226  }
   227  
   228  func buildP2PKHAddress(pubHash []byte) string {
   229  	address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
   230  	if err != nil {
   231  		return ""
   232  	}
   233  
   234  	return address.EncodeAddress()
   235  }
   236  
   237  func buildP2SHAddress(scriptHash []byte) string {
   238  	address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
   239  	if err != nil {
   240  		return ""
   241  	}
   242  
   243  	return address.EncodeAddress()
   244  }
   245  
   246  // BuildAnnotatedOutput build the annotated output.
   247  func (w *Wallet) BuildAnnotatedOutput(tx *types.Tx, idx int) *query.AnnotatedOutput {
   248  	orig := tx.Outputs[idx]
   249  	outid := tx.OutputID(idx)
   250  	out := &query.AnnotatedOutput{
   251  		OutputID:        *outid,
   252  		Position:        idx,
   253  		AssetID:         *orig.AssetId,
   254  		AssetDefinition: &emptyJSONObject,
   255  		Amount:          orig.Amount,
   256  		ControlProgram:  orig.ControlProgram,
   257  		Address:         w.getAddressFromControlProgram(orig.ControlProgram),
   258  	}
   259  
   260  	if vmutil.IsUnspendable(out.ControlProgram) {
   261  		out.Type = "retire"
   262  	} else {
   263  		out.Type = "control"
   264  	}
   265  	return out
   266  }