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