github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/bc/bcblock.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 bc 7 8 import ( 9 "context" 10 "encoding/hex" 11 "fmt" 12 "strconv" 13 14 "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" 15 "github.com/spf13/cobra" 16 "google.golang.org/grpc/status" 17 18 "github.com/iotexproject/iotex-proto/golang/iotexapi" 19 "github.com/iotexproject/iotex-proto/golang/iotextypes" 20 21 "github.com/iotexproject/iotex-core/ioctl/config" 22 "github.com/iotexproject/iotex-core/ioctl/output" 23 "github.com/iotexproject/iotex-core/ioctl/util" 24 ) 25 26 var ( 27 verbose bool 28 ) 29 30 // Multi-language support 31 var ( 32 _bcBlockCmdShorts = map[config.Language]string{ 33 config.English: "Get block from block chain", 34 config.Chinese: "获取IoTeX区块链中的区块", 35 } 36 _bcBlockCmdUses = map[config.Language]string{ 37 config.English: "block [HEIGHT|HASH] [--verbose]", 38 config.Chinese: "block [高度|哈希] [--verbose]", 39 } 40 _flagVerboseUsage = map[config.Language]string{ 41 config.English: "returns block info and all actions within this block.", 42 config.Chinese: "返回区块信息和区块内的所有事务", 43 } 44 ) 45 46 // _bcBlockCmd represents the bc Block command 47 var _bcBlockCmd = &cobra.Command{ 48 Use: config.TranslateInLang(_bcBlockCmdUses, config.UILanguage), 49 Short: config.TranslateInLang(_bcBlockCmdShorts, config.UILanguage), 50 Args: cobra.MaximumNArgs(2), 51 RunE: func(cmd *cobra.Command, args []string) error { 52 cmd.SilenceUsage = true 53 err := getBlock(args) 54 return output.PrintError(err) 55 }, 56 } 57 58 func init() { 59 _bcBlockCmd.Flags().BoolVar(&verbose, "verbose", false, config.TranslateInLang(_flagVerboseUsage, config.UILanguage)) 60 } 61 62 type blockMessage struct { 63 Node string `json:"node"` 64 Block *iotextypes.BlockMeta `json:"block"` 65 ActionInfo []*actionInfo `json:"actionInfo"` 66 } 67 68 type log struct { 69 ContractAddress string `json:"contractAddress"` 70 Topics []string `json:"topics"` 71 Data string `json:"data"` 72 BlkHeight uint64 `json:"blkHeight"` 73 ActHash string `json:"actHash"` 74 Index uint32 `json:"index"` 75 } 76 77 func convertLog(src *iotextypes.Log) *log { 78 topics := make([]string, 0, len(src.Topics)) 79 for _, topic := range src.Topics { 80 topics = append(topics, hex.EncodeToString(topic)) 81 } 82 return &log{ 83 ContractAddress: src.ContractAddress, 84 Topics: topics, 85 Data: hex.EncodeToString(src.Data), 86 BlkHeight: src.BlkHeight, 87 ActHash: hex.EncodeToString(src.ActHash), 88 Index: src.Index, 89 } 90 } 91 92 func convertLogs(src []*iotextypes.Log) []*log { 93 logs := make([]*log, 0, len(src)) 94 for _, log := range src { 95 logs = append(logs, convertLog(log)) 96 } 97 return logs 98 } 99 100 type actionInfo struct { 101 Version uint32 `json:"version"` 102 Nonce uint64 `json:"nonce"` 103 GasLimit uint64 `json:"gasLimit"` 104 GasPrice string `json:"gasPrice"` 105 SenderPubKey string `json:"senderPubKey"` 106 Signature string `json:"signature"` 107 Status uint64 `json:"status"` 108 BlkHeight uint64 `json:"blkHeight"` 109 ActHash string `json:"actHash"` 110 GasConsumed uint64 `json:"gasConsumed"` 111 ContractAddress string `json:"contractAddress"` 112 Logs []*log `json:"logs"` 113 } 114 115 type blocksInfo struct { 116 Block *iotextypes.Block 117 Receipts []*iotextypes.Receipt 118 } 119 120 func (m *blockMessage) String() string { 121 if output.Format == "" { 122 message := fmt.Sprintf("Blockchain Node: %s\n%s\n%s", m.Node, output.JSONString(m.Block), output.JSONString(m.ActionInfo)) 123 return message 124 } 125 return output.FormatString(output.Result, m) 126 } 127 128 // getBlock get block from block chain 129 func getBlock(args []string) error { 130 var ( 131 height uint64 132 err error 133 blockMeta *iotextypes.BlockMeta 134 blocksInfos []blocksInfo 135 ) 136 isHeight := true 137 if len(args) != 0 { 138 if height, err = strconv.ParseUint(args[0], 10, 64); err != nil { 139 isHeight = false 140 } 141 } else { 142 chainMeta, err := GetChainMeta() 143 if err != nil { 144 return output.NewError(0, "failed to get chain meta", err) 145 } 146 height = chainMeta.Height 147 } 148 149 if isHeight { 150 blockMeta, err = getBlockMetaByHeight(height) 151 } else { 152 blockMeta, err = getBlockMetaByHash(args[0]) 153 } 154 if err != nil { 155 return output.NewError(0, "failed to get block meta", err) 156 } 157 blockInfoMessage := blockMessage{ 158 Node: config.ReadConfig.Endpoint, 159 Block: blockMeta, 160 } 161 if verbose { 162 blocksInfos, err = getActionInfoWithinBlock(blockMeta.Height) 163 if err != nil { 164 return output.NewError(0, "failed to get actions info", err) 165 } 166 for _, ele := range blocksInfos { 167 for index, item := range ele.Block.Body.Actions { 168 receipt := ele.Receipts[index] 169 actionInfo := actionInfo{ 170 Version: item.Core.Version, 171 Nonce: item.Core.Nonce, 172 GasLimit: item.Core.GasLimit, 173 GasPrice: item.Core.GasPrice, 174 SenderPubKey: hex.EncodeToString(item.SenderPubKey), 175 Signature: hex.EncodeToString(item.Signature), 176 Status: receipt.Status, 177 BlkHeight: receipt.BlkHeight, 178 ActHash: hex.EncodeToString(receipt.ActHash), 179 GasConsumed: receipt.GasConsumed, 180 ContractAddress: receipt.ContractAddress, 181 Logs: convertLogs(receipt.Logs), 182 } 183 blockInfoMessage.ActionInfo = append(blockInfoMessage.ActionInfo, &actionInfo) 184 } 185 } 186 } 187 fmt.Println(blockInfoMessage.String()) 188 return nil 189 } 190 191 // getActionInfoByBlock gets action info by block hash with start index and action count 192 func getActionInfoWithinBlock(height uint64) ([]blocksInfo, error) { 193 conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure) 194 if err != nil { 195 return nil, output.NewError(output.NetworkError, "failed to connect to endpoint", err) 196 } 197 defer conn.Close() 198 cli := iotexapi.NewAPIServiceClient(conn) 199 request := iotexapi.GetRawBlocksRequest{StartHeight: height, Count: 1, WithReceipts: true} 200 ctx := context.Background() 201 202 jwtMD, err := util.JwtAuth() 203 if err == nil { 204 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 205 } 206 207 response, err := cli.GetRawBlocks(ctx, &request) 208 if err != nil { 209 sta, ok := status.FromError(err) 210 if ok { 211 return nil, output.NewError(output.APIError, sta.Message(), nil) 212 } 213 return nil, output.NewError(output.NetworkError, "failed to invoke GetRawBlocks api", err) 214 } 215 if len(response.Blocks) == 0 { 216 return nil, output.NewError(output.APIError, "no actions returned", err) 217 } 218 var blockInfos []blocksInfo 219 for _, ele := range response.Blocks { 220 blockInfos = append(blockInfos, blocksInfo{Block: ele.Block, Receipts: ele.Receipts}) 221 } 222 return blockInfos, nil 223 224 } 225 226 // getBlockMetaByHeight gets block metadata by height 227 func getBlockMetaByHeight(height uint64) (*iotextypes.BlockMeta, error) { 228 conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure) 229 if err != nil { 230 return nil, output.NewError(output.NetworkError, "failed to connect to endpoint", err) 231 } 232 defer conn.Close() 233 cli := iotexapi.NewAPIServiceClient(conn) 234 request := &iotexapi.GetBlockMetasRequest{ 235 Lookup: &iotexapi.GetBlockMetasRequest_ByIndex{ 236 ByIndex: &iotexapi.GetBlockMetasByIndexRequest{ 237 Start: height, 238 Count: 1, 239 }, 240 }, 241 } 242 ctx := context.Background() 243 244 jwtMD, err := util.JwtAuth() 245 if err == nil { 246 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 247 } 248 249 response, err := cli.GetBlockMetas(ctx, request) 250 if err != nil { 251 sta, ok := status.FromError(err) 252 if ok { 253 return nil, output.NewError(output.APIError, sta.Message(), nil) 254 } 255 return nil, output.NewError(output.NetworkError, "failed to invoke GetBlockMetas api", err) 256 } 257 if len(response.BlkMetas) == 0 { 258 return nil, output.NewError(output.APIError, "no block returned", err) 259 } 260 return response.BlkMetas[0], nil 261 } 262 263 // getBlockMetaByHash gets block metadata by hash 264 func getBlockMetaByHash(hash string) (*iotextypes.BlockMeta, error) { 265 conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure) 266 if err != nil { 267 return nil, output.NewError(output.NetworkError, "failed to connect to endpoint", err) 268 } 269 defer conn.Close() 270 cli := iotexapi.NewAPIServiceClient(conn) 271 request := &iotexapi.GetBlockMetasRequest{ 272 Lookup: &iotexapi.GetBlockMetasRequest_ByHash{ 273 ByHash: &iotexapi.GetBlockMetaByHashRequest{BlkHash: hash}, 274 }, 275 } 276 ctx := context.Background() 277 278 jwtMD, err := util.JwtAuth() 279 if err == nil { 280 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 281 } 282 283 response, err := cli.GetBlockMetas(ctx, request) 284 if err != nil { 285 sta, ok := status.FromError(err) 286 if ok { 287 return nil, output.NewError(output.APIError, sta.Message(), nil) 288 } 289 return nil, output.NewError(output.NetworkError, "failed to invoke GetBlockMetas api", err) 290 } 291 if len(response.BlkMetas) == 0 { 292 return nil, output.NewError(output.APIError, "no block returned", err) 293 } 294 return response.BlkMetas[0], nil 295 }