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

     1  package wallet
     2  
     3  import (
     4  	"encoding/hex"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	log "github.com/sirupsen/logrus"
     9  
    10  	"github.com/bytom/bytom/account"
    11  	"github.com/bytom/bytom/asset"
    12  	"github.com/bytom/bytom/blockchain/query"
    13  	"github.com/bytom/bytom/blockchain/signers"
    14  	"github.com/bytom/bytom/common"
    15  	"github.com/bytom/bytom/consensus"
    16  	"github.com/bytom/bytom/consensus/segwit"
    17  	"github.com/bytom/bytom/crypto/sha3pool"
    18  	dbm "github.com/bytom/bytom/database/leveldb"
    19  	"github.com/bytom/bytom/protocol/bc"
    20  	"github.com/bytom/bytom/protocol/bc/types"
    21  	"github.com/bytom/bytom/protocol/vm/vmutil"
    22  )
    23  
    24  // annotateTxs adds asset data to transactions
    25  func annotateTxsAsset(w *Wallet, txs []*query.AnnotatedTx) {
    26  	for i, tx := range txs {
    27  		for j, input := range tx.Inputs {
    28  			alias, definition := w.getAliasDefinition(input.AssetID)
    29  			txs[i].Inputs[j].AssetAlias, txs[i].Inputs[j].AssetDefinition = alias, &definition
    30  		}
    31  		for k, output := range tx.Outputs {
    32  			alias, definition := w.getAliasDefinition(output.AssetID)
    33  			txs[i].Outputs[k].AssetAlias, txs[i].Outputs[k].AssetDefinition = alias, &definition
    34  		}
    35  	}
    36  }
    37  
    38  func (w *Wallet) getExternalDefinition(assetID *bc.AssetID) json.RawMessage {
    39  	definitionByte := w.DB.Get(asset.ExtAssetKey(assetID))
    40  	if definitionByte == nil {
    41  		return nil
    42  	}
    43  
    44  	definitionMap := make(map[string]interface{})
    45  	if err := json.Unmarshal(definitionByte, &definitionMap); err != nil {
    46  		return nil
    47  	}
    48  
    49  	alias := assetID.String()
    50  	externalAsset := &asset.Asset{
    51  		AssetID:           *assetID,
    52  		Alias:             &alias,
    53  		DefinitionMap:     definitionMap,
    54  		RawDefinitionByte: definitionByte,
    55  		Signer:            &signers.Signer{Type: "external"},
    56  	}
    57  
    58  	if err := w.AssetReg.SaveAsset(externalAsset, alias); err != nil {
    59  		log.WithFields(log.Fields{"module": logModule, "err": err, "assetID": alias}).Warning("fail on save external asset to internal asset DB")
    60  	}
    61  	return definitionByte
    62  }
    63  
    64  func (w *Wallet) getAliasDefinition(assetID bc.AssetID) (string, json.RawMessage) {
    65  	//btm
    66  	if assetID.String() == consensus.BTMAssetID.String() {
    67  		alias := consensus.BTMAlias
    68  		definition := []byte(asset.DefaultNativeAsset.RawDefinitionByte)
    69  
    70  		return alias, definition
    71  	}
    72  
    73  	//local asset and saved external asset
    74  	if localAsset, err := w.AssetReg.FindByID(nil, &assetID); err == nil {
    75  		alias := *localAsset.Alias
    76  		definition := []byte(localAsset.RawDefinitionByte)
    77  		return alias, definition
    78  	}
    79  
    80  	//external asset
    81  	if definition := w.getExternalDefinition(&assetID); definition != nil {
    82  		return assetID.String(), definition
    83  	}
    84  
    85  	return "", nil
    86  }
    87  
    88  // annotateTxs adds account data to transactions
    89  func annotateTxsAccount(txs []*query.AnnotatedTx, walletDB dbm.DB) {
    90  	for i, tx := range txs {
    91  		for j, input := range tx.Inputs {
    92  			//issue asset tx input SpentOutputID is nil
    93  			if input.SpentOutputID == nil {
    94  				continue
    95  			}
    96  			localAccount, err := getAccountFromACP(input.ControlProgram, walletDB)
    97  			if localAccount == nil || err != nil {
    98  				continue
    99  			}
   100  			txs[i].Inputs[j].AccountAlias = localAccount.Alias
   101  			txs[i].Inputs[j].AccountID = localAccount.ID
   102  		}
   103  		for j, output := range tx.Outputs {
   104  			localAccount, err := getAccountFromACP(output.ControlProgram, walletDB)
   105  			if localAccount == nil || err != nil {
   106  				continue
   107  			}
   108  			txs[i].Outputs[j].AccountAlias = localAccount.Alias
   109  			txs[i].Outputs[j].AccountID = localAccount.ID
   110  		}
   111  	}
   112  }
   113  
   114  func getAccountFromACP(program []byte, walletDB dbm.DB) (*account.Account, error) {
   115  	var hash common.Hash
   116  	accountCP := account.CtrlProgram{}
   117  	localAccount := account.Account{}
   118  
   119  	sha3pool.Sum256(hash[:], program)
   120  
   121  	rawProgram := walletDB.Get(account.ContractKey(hash))
   122  	if rawProgram == nil {
   123  		return nil, fmt.Errorf("failed get account control program:%x ", hash)
   124  	}
   125  
   126  	if err := json.Unmarshal(rawProgram, &accountCP); err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	accountValue := walletDB.Get(account.Key(accountCP.AccountID))
   131  	if accountValue == nil {
   132  		return nil, fmt.Errorf("failed get account:%s ", accountCP.AccountID)
   133  	}
   134  
   135  	if err := json.Unmarshal(accountValue, &localAccount); err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	return &localAccount, nil
   140  }
   141  
   142  var emptyJSONObject = json.RawMessage(`{}`)
   143  
   144  func isValidJSON(b []byte) bool {
   145  	var v interface{}
   146  	err := json.Unmarshal(b, &v)
   147  	return err == nil
   148  }
   149  
   150  func (w *Wallet) buildAnnotatedTransaction(orig *types.Tx, b *types.Block, indexInBlock int) *query.AnnotatedTx {
   151  	tx := &query.AnnotatedTx{
   152  		ID:                     orig.ID,
   153  		Timestamp:              b.Timestamp,
   154  		BlockID:                b.Hash(),
   155  		BlockHeight:            b.Height,
   156  		Position:               uint32(indexInBlock),
   157  		BlockTransactionsCount: uint32(len(b.Transactions)),
   158  		Inputs:                 make([]*query.AnnotatedInput, 0, len(orig.Inputs)),
   159  		Outputs:                make([]*query.AnnotatedOutput, 0, len(orig.Outputs)),
   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.ControlProgram()
   199  		arguments := orig.Arguments()
   200  		for _, arg := range arguments {
   201  			in.WitnessArguments = append(in.WitnessArguments, arg)
   202  		}
   203  
   204  		if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok && isValidJSON(ii.AssetDefinition) {
   205  			assetDefinition := json.RawMessage(ii.AssetDefinition)
   206  			in.AssetDefinition = &assetDefinition
   207  		}
   208  	case *bc.Coinbase:
   209  		in.Type = "coinbase"
   210  		in.Arbitrary = e.Arbitrary
   211  	case *bc.VetoInput:
   212  		in.Type = "veto"
   213  		in.ControlProgram = orig.ControlProgram()
   214  		in.Address = w.getAddressFromControlProgram(in.ControlProgram)
   215  		in.SpentOutputID = e.SpentOutputId
   216  		arguments := orig.Arguments()
   217  		for _, arg := range arguments {
   218  			in.WitnessArguments = append(in.WitnessArguments, arg)
   219  		}
   220  		if vetoInput, ok := orig.TypedInput.(*types.VetoInput); ok {
   221  			in.Vote = hex.EncodeToString(vetoInput.Vote)
   222  			in.Amount = vetoInput.Amount
   223  		}
   224  	}
   225  	return in
   226  }
   227  
   228  func (w *Wallet) getAddressFromControlProgram(prog []byte) string {
   229  	if segwit.IsP2WPKHScript(prog) {
   230  		if pubHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
   231  			return buildP2PKHAddress(pubHash)
   232  		}
   233  	} else if segwit.IsP2WSHScript(prog) {
   234  		if scriptHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
   235  			return buildP2SHAddress(scriptHash)
   236  		}
   237  	}
   238  
   239  	return ""
   240  }
   241  
   242  func buildP2PKHAddress(pubHash []byte) string {
   243  	address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
   244  	if err != nil {
   245  		return ""
   246  	}
   247  
   248  	return address.EncodeAddress()
   249  }
   250  
   251  func buildP2SHAddress(scriptHash []byte) string {
   252  	address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
   253  	if err != nil {
   254  		return ""
   255  	}
   256  
   257  	return address.EncodeAddress()
   258  }
   259  
   260  // BuildAnnotatedOutput build the annotated output.
   261  func (w *Wallet) BuildAnnotatedOutput(tx *types.Tx, idx int) *query.AnnotatedOutput {
   262  	orig := tx.Outputs[idx]
   263  	outid := tx.OutputID(idx)
   264  	out := &query.AnnotatedOutput{
   265  		OutputID:        *outid,
   266  		Position:        idx,
   267  		AssetID:         *orig.AssetId,
   268  		AssetDefinition: &emptyJSONObject,
   269  		Amount:          orig.Amount,
   270  		ControlProgram:  orig.ControlProgram,
   271  		Address:         w.getAddressFromControlProgram(orig.ControlProgram),
   272  	}
   273  
   274  	switch {
   275  	// must deal with retirement first due to cases' priorities in the switch statement
   276  	case vmutil.IsUnspendable(out.ControlProgram):
   277  		// retirement
   278  		out.Type = "retire"
   279  	case orig.OutputType() == types.OriginalOutputType:
   280  		out.Type = "control"
   281  		if e, ok := tx.Entries[*outid]; ok {
   282  			if output, ok := e.(*bc.OriginalOutput); ok {
   283  				out.StateData = stateDataStrings(output.StateData)
   284  			}
   285  		}
   286  	case orig.OutputType() == types.VoteOutputType:
   287  		out.Type = "vote"
   288  		if e, ok := tx.Entries[*outid]; ok {
   289  			if output, ok := e.(*bc.VoteOutput); ok {
   290  				out.Vote = hex.EncodeToString(output.Vote)
   291  				out.StateData = stateDataStrings(output.StateData)
   292  			}
   293  		}
   294  	default:
   295  		log.Warn("unknown outType")
   296  	}
   297  
   298  	return out
   299  }
   300  
   301  func stateDataStrings(stateData [][]byte) []string {
   302  	ss := make([]string, 0, len(stateData))
   303  	for _, bytes := range stateData {
   304  		ss = append(ss, hex.EncodeToString(bytes))
   305  	}
   306  	return ss
   307  }