github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/newcmd/bc/bcblock.go (about) 1 // Copyright (c) 2022 IoTeX 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 "fmt" 11 "strconv" 12 13 "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" 14 "github.com/iotexproject/go-pkgs/hash" 15 "github.com/iotexproject/iotex-proto/golang/iotexapi" 16 "github.com/iotexproject/iotex-proto/golang/iotextypes" 17 "github.com/pkg/errors" 18 "github.com/spf13/cobra" 19 "google.golang.org/grpc/codes" 20 "google.golang.org/grpc/status" 21 22 "github.com/iotexproject/iotex-core/ioctl" 23 "github.com/iotexproject/iotex-core/ioctl/config" 24 "github.com/iotexproject/iotex-core/ioctl/util" 25 "github.com/iotexproject/iotex-core/ioctl/validator" 26 ) 27 28 // Multi-language support 29 var ( 30 _bcBlockCmdShorts = map[config.Language]string{ 31 config.English: "Get block from block chain", 32 config.Chinese: "获取IoTeX区块链中的区块", 33 } 34 _bcBlockCmdUses = map[config.Language]string{ 35 config.English: "block [HEIGHT|HASH] [--verbose]", 36 config.Chinese: "block [高度|哈希] [--verbose]", 37 } 38 _flagVerboseUsages = map[config.Language]string{ 39 config.English: "returns block info and all actions within this block.", 40 config.Chinese: "返回区块信息和区块内的所有事务", 41 } 42 ) 43 44 // NewBCBlockCmd represents the bc block command 45 func NewBCBlockCmd(c ioctl.Client) *cobra.Command { 46 bcBlockCmdUse, _ := c.SelectTranslation(_bcBlockCmdUses) 47 bcBlockCmdShort, _ := c.SelectTranslation(_bcBlockCmdShorts) 48 flagVerboseUsage, _ := c.SelectTranslation(_flagVerboseUsages) 49 var verbose bool 50 51 cmd := &cobra.Command{ 52 Use: bcBlockCmdUse, 53 Short: bcBlockCmdShort, 54 Args: cobra.MaximumNArgs(2), 55 RunE: func(cmd *cobra.Command, args []string) error { 56 cmd.SilenceUsage = true 57 var ( 58 err error 59 request *iotexapi.GetBlockMetasRequest 60 blockMeta *iotextypes.BlockMeta 61 blocksInfo []*iotexapi.BlockInfo 62 ) 63 64 apiServiceClient, err := c.APIServiceClient() 65 if err != nil { 66 return err 67 } 68 if len(args) != 0 { 69 request, err = parseArg(c, args[0]) 70 } else { 71 request, err = parseArg(c, "") 72 } 73 if err != nil { 74 return err 75 } 76 77 blockMeta, err = getBlockMeta(&apiServiceClient, request) 78 if err != nil { 79 return errors.Wrap(err, "failed to get block meta") 80 } 81 message := blockMessage{Node: c.Config().Endpoint, Block: blockMeta, Actions: nil} 82 if verbose { 83 blocksInfo, err = getActionInfoWithinBlock(&apiServiceClient, blockMeta.Height, uint64(blockMeta.NumActions)) 84 if err != nil { 85 return errors.Wrap(err, "failed to get actions info") 86 } 87 for _, ele := range blocksInfo { 88 for i, item := range ele.Block.Body.Actions { 89 actionInfo := actionInfo{ 90 Version: item.Core.Version, 91 Nonce: item.Core.Nonce, 92 GasLimit: item.Core.GasLimit, 93 GasPrice: item.Core.GasPrice, 94 SenderPubKey: item.SenderPubKey, 95 Signature: item.Signature, 96 Status: ele.Receipts[i].Status, 97 BlkHeight: ele.Receipts[i].BlkHeight, 98 ActHash: hash.Hash256b(ele.Receipts[i].ActHash), 99 GasConsumed: ele.Receipts[i].GasConsumed, 100 ContractAddress: ele.Receipts[i].ContractAddress, 101 Logs: ele.Receipts[i].Logs, 102 } 103 message.Actions = append(message.Actions, actionInfo) 104 } 105 } 106 } 107 cmd.Println(fmt.Sprintf("Blockchain Node: %s\n%s\n%s", message.Node, JSONString(message.Block), JSONString(message.Actions))) 108 return nil 109 }, 110 } 111 112 cmd.PersistentFlags().BoolVar(&verbose, "verbose", false, flagVerboseUsage) 113 114 return cmd 115 } 116 117 type blockMessage struct { 118 Node string `json:"node"` 119 Block *iotextypes.BlockMeta `json:"block"` 120 Actions []actionInfo `json:"actions"` 121 } 122 123 type actionInfo struct { 124 Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` 125 Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"` 126 GasLimit uint64 `protobuf:"varint,3,opt,name=gasLimit,proto3" json:"gasLimit,omitempty"` 127 GasPrice string `protobuf:"bytes,4,opt,name=gasPrice,proto3" json:"gasPrice,omitempty"` 128 SenderPubKey []byte `protobuf:"bytes,2,opt,name=senderPubKey,proto3" json:"senderPubKey,omitempty"` 129 Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` 130 Status uint64 `protobuf:"varint,4,opt,name=status,proto3" json:"status,omitempty"` 131 BlkHeight uint64 `protobuf:"varint,5,opt,name=blkheight,proto3" json:"blkheight,omitempty"` 132 ActHash hash.Hash256 `protobuf:"bytes,4,opt,name=acthash,proto3" json:"acthash,omitempty"` 133 GasConsumed uint64 `protobuf:"varint,6,opt,name=gasconsumed,proto3" json:"gasconsumed,omitempty"` 134 ContractAddress string `protobuf:"bytes,5,opt,name=contractaddress,proto3" json:"contractaddress,omitempty"` 135 Logs []*iotextypes.Log `protobuf:"bytes,6,opt,name=logs,proto3" json:"logs,omitempty"` 136 } 137 138 // getActionInfoByBlock gets action info by block hash with start index and action count 139 func getActionInfoWithinBlock(cli *iotexapi.APIServiceClient, height uint64, count uint64) ([]*iotexapi.BlockInfo, error) { 140 request := iotexapi.GetRawBlocksRequest{StartHeight: height, Count: count, WithReceipts: true} 141 ctx := context.Background() 142 143 jwtMD, err := util.JwtAuth() 144 if err == nil { 145 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 146 } 147 148 response, err := (*cli).GetRawBlocks(ctx, &request) 149 if err != nil { 150 if sta, ok := status.FromError(err); ok { 151 if sta.Code() == codes.Unavailable { 152 return nil, ioctl.ErrInvalidEndpointOrInsecure 153 } 154 return nil, errors.New(sta.Message()) 155 } 156 return nil, errors.Wrap(err, "failed to invoke GetRawBlocks api") 157 } 158 if len(response.Blocks) == 0 { 159 return nil, errors.New("no actions returned") 160 } 161 return response.Blocks, nil 162 163 } 164 165 // getBlockMeta gets block metadata 166 func getBlockMeta(cli *iotexapi.APIServiceClient, request *iotexapi.GetBlockMetasRequest) (*iotextypes.BlockMeta, error) { 167 ctx := context.Background() 168 169 jwtMD, err := util.JwtAuth() 170 if err == nil { 171 ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) 172 } 173 174 response, err := (*cli).GetBlockMetas(ctx, request) 175 if err != nil { 176 if sta, ok := status.FromError(err); ok { 177 if sta.Code() == codes.Unavailable { 178 return nil, ioctl.ErrInvalidEndpointOrInsecure 179 } 180 return nil, errors.New(sta.Message()) 181 } 182 return nil, errors.Wrap(err, "failed to invoke GetBlockMetas api") 183 } 184 if len(response.BlkMetas) == 0 { 185 return nil, errors.New("no block returned") 186 } 187 return response.BlkMetas[0], nil 188 } 189 190 // parseArg parse argument and returns GetBlockMetasRequest 191 func parseArg(c ioctl.Client, arg string) (*iotexapi.GetBlockMetasRequest, error) { 192 var ( 193 height uint64 194 err error 195 ) 196 if arg != "" { 197 height, err = strconv.ParseUint(arg, 10, 64) 198 if err != nil { 199 return &iotexapi.GetBlockMetasRequest{ 200 Lookup: &iotexapi.GetBlockMetasRequest_ByHash{ 201 ByHash: &iotexapi.GetBlockMetaByHashRequest{BlkHash: arg}, 202 }, 203 }, nil 204 } 205 if err = validator.ValidatePositiveNumber(int64(height)); err != nil { 206 return nil, errors.Wrap(err, "invalid height") 207 } 208 return &iotexapi.GetBlockMetasRequest{ 209 Lookup: &iotexapi.GetBlockMetasRequest_ByIndex{ 210 ByIndex: &iotexapi.GetBlockMetasByIndexRequest{ 211 Start: height, 212 Count: 1, 213 }, 214 }, 215 }, nil 216 } 217 chainMeta, err := GetChainMeta(c) 218 if err != nil { 219 return nil, err 220 } 221 height = chainMeta.Height 222 return &iotexapi.GetBlockMetasRequest{ 223 Lookup: &iotexapi.GetBlockMetasRequest_ByIndex{ 224 ByIndex: &iotexapi.GetBlockMetasByIndexRequest{ 225 Start: height, 226 Count: 1, 227 }, 228 }, 229 }, nil 230 }