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  }