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  }