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 }