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 }