github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/action/actionhash.go (about)

     1  // Copyright (c) 2022 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package action
     7  
     8  import (
     9  	"context"
    10  	"encoding/hex"
    11  	"fmt"
    12  	"log"
    13  	"math/big"
    14  	"strconv"
    15  
    16  	protoV1 "github.com/golang/protobuf/proto"
    17  	"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
    18  	"github.com/iotexproject/go-pkgs/crypto"
    19  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    20  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    21  	"github.com/spf13/cobra"
    22  	"google.golang.org/grpc/codes"
    23  	"google.golang.org/grpc/status"
    24  
    25  	"github.com/iotexproject/iotex-core/action/protocol/staking"
    26  	"github.com/iotexproject/iotex-core/ioctl/cmd/alias"
    27  	"github.com/iotexproject/iotex-core/ioctl/config"
    28  	"github.com/iotexproject/iotex-core/ioctl/output"
    29  	"github.com/iotexproject/iotex-core/ioctl/util"
    30  )
    31  
    32  // Multi-language support
    33  var (
    34  	_hashCmdShorts = map[config.Language]string{
    35  		config.English: "Get action by hash",
    36  		config.Chinese: "依据哈希值,获取交易",
    37  	}
    38  	_hashCmdUses = map[config.Language]string{
    39  		config.English: "hash ACTION_HASH",
    40  		config.Chinese: "hash 交易哈希",
    41  	}
    42  )
    43  
    44  // _actionHashCmd represents the action hash command
    45  var _actionHashCmd = &cobra.Command{
    46  	Use:   config.TranslateInLang(_hashCmdUses, config.UILanguage),
    47  	Short: config.TranslateInLang(_hashCmdShorts, config.UILanguage),
    48  	Args:  cobra.MinimumNArgs(1),
    49  	RunE: func(cmd *cobra.Command, args []string) error {
    50  		cmd.SilenceUsage = true
    51  		err := getActionByHash(args)
    52  		return output.PrintError(err)
    53  	},
    54  }
    55  
    56  type actionState int
    57  
    58  const (
    59  	// Pending action is in the action pool but not executed by blockchain
    60  	Pending actionState = iota
    61  	// Executed action has been run and recorded on blockchain
    62  	Executed
    63  )
    64  
    65  type actionMessage struct {
    66  	State   actionState          `json:"state"`
    67  	Proto   *iotexapi.ActionInfo `json:"proto"`
    68  	Receipt *iotextypes.Receipt  `json:"receipt"`
    69  }
    70  
    71  func (m *actionMessage) String() string {
    72  	if output.Format == "" {
    73  		message, err := printAction(m.Proto)
    74  		if err != nil {
    75  			log.Panic(err.Error())
    76  		}
    77  		if m.State == Pending {
    78  			message += "\n#This action is pending"
    79  		} else {
    80  			message += "\n#This action has been written on blockchain\n\n" + printReceiptProto(m.Receipt)
    81  		}
    82  		return message
    83  	}
    84  	return output.FormatString(output.Result, m)
    85  }
    86  
    87  // getActionByHash gets action of IoTeX Blockchain by hash
    88  func getActionByHash(args []string) error {
    89  	hash := args[0]
    90  	conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure)
    91  	if err != nil {
    92  		return output.NewError(output.NetworkError, "failed to connect to endpoint", err)
    93  	}
    94  	defer conn.Close()
    95  	cli := iotexapi.NewAPIServiceClient(conn)
    96  	ctx := context.Background()
    97  
    98  	jwtMD, err := util.JwtAuth()
    99  	if err == nil {
   100  		ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx)
   101  	}
   102  
   103  	// search action on blockchain
   104  	requestGetAction := iotexapi.GetActionsRequest{
   105  		Lookup: &iotexapi.GetActionsRequest_ByHash{
   106  			ByHash: &iotexapi.GetActionByHashRequest{
   107  				ActionHash:   hash,
   108  				CheckPending: false,
   109  			},
   110  		},
   111  	}
   112  	response, err := cli.GetActions(ctx, &requestGetAction)
   113  	if err != nil {
   114  		sta, ok := status.FromError(err)
   115  		if ok {
   116  			return output.NewError(output.APIError, sta.Message(), nil)
   117  		}
   118  		return output.NewError(output.NetworkError, "failed to invoke GetActions api", err)
   119  	}
   120  	if len(response.ActionInfo) == 0 {
   121  		return output.NewError(output.APIError, "no action info returned", nil)
   122  	}
   123  	message := actionMessage{Proto: response.ActionInfo[0]}
   124  
   125  	requestGetReceipt := &iotexapi.GetReceiptByActionRequest{ActionHash: hash}
   126  	responseReceipt, err := cli.GetReceiptByAction(ctx, requestGetReceipt)
   127  	if err != nil {
   128  		sta, ok := status.FromError(err)
   129  		if ok && sta.Code() == codes.NotFound {
   130  			message.State = Pending
   131  		} else if ok {
   132  			return output.NewError(output.APIError, sta.Message(), nil)
   133  		}
   134  		return output.NewError(output.NetworkError, "failed to invoke GetReceiptByAction api", err)
   135  	}
   136  	message.State = Executed
   137  	message.Receipt = responseReceipt.ReceiptInfo.Receipt
   138  	fmt.Println(message.String())
   139  	return nil
   140  }
   141  
   142  func printAction(actionInfo *iotexapi.ActionInfo) (string, error) {
   143  	result, err := printActionProto(actionInfo.Action)
   144  	if err != nil {
   145  		return "", err
   146  	}
   147  	if actionInfo.Timestamp != nil {
   148  		if err := actionInfo.Timestamp.CheckValid(); err != nil {
   149  			return "", err
   150  		}
   151  		ts := actionInfo.Timestamp.AsTime()
   152  		result += fmt.Sprintf("timeStamp: %d\n", ts.Unix())
   153  		result += fmt.Sprintf("blkHash: %s\n", actionInfo.BlkHash)
   154  	}
   155  	result += fmt.Sprintf("actHash: %s\n", actionInfo.ActHash)
   156  	return result, nil
   157  }
   158  
   159  func printActionProto(action *iotextypes.Action) (string, error) {
   160  	pubKey, err := crypto.BytesToPublicKey(action.SenderPubKey)
   161  	if err != nil {
   162  		return "", output.NewError(output.ConvertError, "failed to convert public key from bytes", err)
   163  	}
   164  	senderAddress := pubKey.Address()
   165  	if senderAddress == nil {
   166  		return "", output.NewError(output.ConvertError, "failed to convert bytes into address", nil)
   167  	}
   168  	//ioctl action should display IOTX unit instead Raul
   169  	core := action.Core
   170  	gasPriceUnitIOTX, err := util.StringToIOTX(core.GasPrice)
   171  	if err != nil {
   172  		return "", output.NewError(output.ConfigError, "failed to convert string to IOTX", err)
   173  	}
   174  	result := fmt.Sprintf("\nversion: %d  ", core.GetVersion()) +
   175  		fmt.Sprintf("nonce: %d  ", core.GetNonce()) +
   176  		fmt.Sprintf("gasLimit: %d  ", core.GasLimit) +
   177  		fmt.Sprintf("gasPrice: %s IOTX  ", gasPriceUnitIOTX) +
   178  		fmt.Sprintf("chainID: %d  ", core.GetChainID()) +
   179  		fmt.Sprintf("encoding: %d\n", action.GetEncoding()) +
   180  		fmt.Sprintf("senderAddress: %s %s\n", senderAddress.String(),
   181  			Match(senderAddress.String(), "address"))
   182  	switch {
   183  	case core.GetTransfer() != nil:
   184  		transfer := core.GetTransfer()
   185  		amount, err := util.StringToIOTX(transfer.Amount)
   186  		if err != nil {
   187  			return "", output.NewError(output.ConvertError, "failed to convert string into IOTX amount", err)
   188  		}
   189  		result += "transfer: <\n" +
   190  			fmt.Sprintf("  recipient: %s %s\n", transfer.Recipient,
   191  				Match(transfer.Recipient, "address")) +
   192  			fmt.Sprintf("  amount: %s IOTX\n", amount)
   193  		if len(transfer.Payload) != 0 {
   194  			result += fmt.Sprintf("  payload: %s\n", transfer.Payload)
   195  		}
   196  		result += ">\n"
   197  	case core.GetExecution() != nil:
   198  		execution := core.GetExecution()
   199  		result += "execution: <\n" +
   200  			fmt.Sprintf("  contract: %s %s\n", execution.Contract,
   201  				Match(execution.Contract, "address"))
   202  		if execution.Amount != "0" {
   203  			amount, err := util.StringToIOTX(execution.Amount)
   204  			if err != nil {
   205  				return "", output.NewError(output.ConvertError, "failed to convert string into IOTX amount", err)
   206  			}
   207  			result += fmt.Sprintf("  amount: %s IOTX\n", amount)
   208  		}
   209  		result += fmt.Sprintf("  data: %x\n", execution.Data) + ">\n"
   210  	case core.GetPutPollResult() != nil:
   211  		putPollResult := core.GetPutPollResult()
   212  		result += "putPollResult: <\n" +
   213  			fmt.Sprintf("  height: %d\n", putPollResult.Height) +
   214  			"  candidates: <\n"
   215  		for _, candidate := range putPollResult.Candidates.Candidates {
   216  			result += "    candidate: <\n" +
   217  				fmt.Sprintf("      address: %s\n", candidate.Address)
   218  			votes := big.NewInt(0).SetBytes(candidate.Votes)
   219  			result += fmt.Sprintf("      votes: %s\n", votes.String()) +
   220  				fmt.Sprintf("      rewardAdress: %s\n", candidate.RewardAddress) +
   221  				"    >\n"
   222  		}
   223  		result += "  >\n" +
   224  			">\n"
   225  	default:
   226  		result += protoV1.MarshalTextString(core)
   227  	}
   228  	result += fmt.Sprintf("senderPubKey: %x\n", action.SenderPubKey) +
   229  		fmt.Sprintf("signature: %x\n", action.Signature)
   230  
   231  	return result, nil
   232  }
   233  
   234  func printReceiptProto(receipt *iotextypes.Receipt) string {
   235  	result := fmt.Sprintf("status: %d %s\n", receipt.Status,
   236  		Match(strconv.Itoa(int(receipt.Status)), "status")) +
   237  		fmt.Sprintf("actHash: %x\n", receipt.ActHash) +
   238  		fmt.Sprintf("blkHeight: %d\n", receipt.BlkHeight) +
   239  		fmt.Sprintf("gasConsumed: %d\n", receipt.GasConsumed) +
   240  		printLogs(receipt.Logs)
   241  	if len(receipt.ContractAddress) != 0 {
   242  		result += fmt.Sprintf("\ncontractAddress: %s %s", receipt.ContractAddress,
   243  			Match(receipt.ContractAddress, "address"))
   244  	}
   245  	if len(receipt.Logs) > 0 {
   246  		if index, ok := staking.BucketIndexFromReceiptLog(receipt.Logs[0]); ok {
   247  			result += fmt.Sprintf("\nbucket index: %d", index)
   248  		}
   249  	}
   250  	if receipt.Status == uint64(iotextypes.ReceiptStatus_ErrExecutionReverted) {
   251  		result += fmt.Sprintf("\nexecution revert reason: %s", receipt.ExecutionRevertMsg)
   252  	}
   253  	return result
   254  }
   255  
   256  func printLogs(logs []*iotextypes.Log) string {
   257  	result := "logs:<\n"
   258  	for _, l := range logs {
   259  		result += "  <\n" +
   260  			fmt.Sprintf("    contractAddress: %s\n", l.ContractAddress) +
   261  			"    topics:<\n"
   262  		for _, topic := range l.Topics {
   263  			result += fmt.Sprintf("      %s\n", hex.EncodeToString(topic))
   264  		}
   265  		result += "    >\n"
   266  		if len(l.Data) > 0 {
   267  			result += fmt.Sprintf("    data: %s\n", hex.EncodeToString(l.Data))
   268  		}
   269  		result += "  >\n"
   270  
   271  	}
   272  	result += ">\n"
   273  	return result
   274  }
   275  
   276  // Match returns human readable expression
   277  func Match(in string, matchType string) string {
   278  	switch matchType {
   279  	case "address":
   280  		alias, err := alias.Alias(in)
   281  		if err != nil {
   282  			return ""
   283  		}
   284  		return "(" + alias + ")"
   285  	case "status":
   286  		switch in {
   287  		case "0":
   288  			return "(Failure)"
   289  		case "1":
   290  			return "(Success)"
   291  		case "100":
   292  			return "(Failure : Unknown)"
   293  		case "101":
   294  			return "(Failure : Execution out of gas)"
   295  		case "102":
   296  			return "(Failure : Deployment out of gas - not enough gas to store code)"
   297  		case "103":
   298  			return "(Failure : Max call depth exceeded)"
   299  		case "104":
   300  			return "(Failure : Contract address collision)"
   301  		case "105":
   302  			return "(Failure : No compatible interpreter)"
   303  		case "106":
   304  			return "(Failure : Execution reverted)"
   305  		case "107":
   306  			return "(Failure : Max code size exceeded)"
   307  		case "108":
   308  			return "(Failure : Write protection)"
   309  		}
   310  	}
   311  	return ""
   312  }