github.com/0xsequence/ethkit@v1.25.0/cmd/ethkit/block.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math/big"
     8  	"net/url"
     9  	"strconv"
    10  
    11  	"github.com/spf13/cobra"
    12  
    13  	"github.com/0xsequence/ethkit/ethrpc"
    14  	"github.com/0xsequence/ethkit/go-ethereum/common"
    15  	"github.com/0xsequence/ethkit/go-ethereum/core/types"
    16  )
    17  
    18  const (
    19  	flagBlockField = "field"
    20  	flagBlockFull = "full"
    21  	flagBlockRpcUrl = "rpc-url"
    22  	flagBlockJson = "json"
    23  )
    24  
    25  func init() {
    26  	rootCmd.AddCommand(NewBlockCmd())
    27  }
    28  
    29  type block struct {
    30  }
    31  
    32  // NewBlockCommand returns a new build command to retrieve a block.
    33  func NewBlockCmd() *cobra.Command {
    34  	c := &block{}
    35  	cmd := &cobra.Command{
    36  		Use:     "block [number|tag]",
    37  		Short:   "Get the information about the block",
    38  		Aliases: []string{"bl"},
    39  		Args:    cobra.ExactArgs(1),
    40  		RunE:    c.Run,
    41  	}
    42  
    43  	cmd.Flags().StringP(flagBlockField, "f", "", "Get the specific field of a block")
    44  	cmd.Flags().Bool(flagBlockFull, false, "Get the full block information")
    45  	cmd.Flags().StringP(flagBlockRpcUrl, "r", "", "The RPC endpoint to the blockchain node to interact with")
    46  	cmd.Flags().BoolP(flagBlockJson, "j", false, "Print the block as JSON")
    47  
    48  	return cmd
    49  }
    50  
    51  func (c *block) Run(cmd *cobra.Command, args []string) error {
    52  	fBlock := cmd.Flags().Args()[0]
    53  	fField, err := cmd.Flags().GetString(flagBlockField)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	fFull, err := cmd.Flags().GetBool(flagBlockFull)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	fRpc, err := cmd.Flags().GetString(flagBlockRpcUrl)
    62  	if err != nil {
    63  		return err
    64  	}
    65  	fJson, err := cmd.Flags().GetBool(flagBlockJson)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	if _, err = url.ParseRequestURI(fRpc); err != nil {
    71  		return errors.New("error: please provide a valid rpc url (e.g. https://nodes.sequence.app/mainnet)")
    72  	}
    73  
    74  	provider, err := ethrpc.NewProvider(fRpc)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	bh, err := strconv.ParseUint(fBlock, 10, 64)
    80  	if err != nil {
    81  		// TODO: implement support for all tags: earliest, latest, pending, finalized, safe
    82  		return errors.New("error: invalid block height")
    83  	}
    84  
    85  	block, err := provider.BlockByNumber(context.Background(), big.NewInt(int64(bh)))
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	var obj any
    91  	obj = NewHeader(block)
    92  
    93  	if fFull {
    94  		obj = NewBlock(block)
    95  	}
    96  
    97  	if fField != "" {
    98  		obj = GetValueByJSONTag(obj, fField)
    99  	}
   100  
   101  	if fJson {
   102  		json, err := PrettyJSON(obj)
   103  		if err != nil {
   104  			return err
   105  		}
   106  		obj = *json
   107  	}
   108  
   109  	fmt.Fprintln(cmd.OutOrStdout(), obj)
   110  
   111  	return nil
   112  }
   113  
   114  // Header is a customized block header for cli.
   115  type Header struct {
   116  	ParentHash       common.Hash        `json:"parentHash"`
   117  	UncleHash        common.Hash        `json:"sha3Uncles"`
   118  	Coinbase         common.Address     `json:"miner"`
   119  	Hash             common.Hash        `json:"hash"`
   120  	Root             common.Hash        `json:"stateRoot"`
   121  	TxHash           common.Hash        `json:"transactionsRoot"`
   122  	ReceiptHash      common.Hash        `json:"receiptsRoot"`
   123  	Bloom            types.Bloom        `json:"logsBloom"`
   124  	Difficulty       *big.Int           `json:"difficulty"`
   125  	Number           *big.Int           `json:"number"`
   126  	GasLimit         uint64             `json:"gasLimit"`
   127  	GasUsed          uint64             `json:"gasUsed"`
   128  	Time             uint64             `json:"timestamp"`
   129  	Extra            []byte             `json:"extraData"`
   130  	MixDigest        common.Hash        `json:"mixHash"`
   131  	Nonce            types.BlockNonce   `json:"nonce"`
   132  	BaseFee          *big.Int           `json:"baseFeePerGas"`
   133  	WithdrawalsHash  *common.Hash       `json:"withdrawalsRoot"`
   134  	Size             common.StorageSize `json:"size"`
   135  	// TODO: totalDifficulty to be implemented
   136  	// TotalDifficulty  *big.Int           `json:"totalDifficulty"`
   137  	TransactionsHash []common.Hash      `json:"transactions"`
   138  }
   139  
   140  // NewHeader returns the custom-built Header object.
   141  func NewHeader(b *types.Block) *Header {
   142  	return &Header{
   143  		ParentHash:       b.Header().ParentHash,
   144  		UncleHash:        b.Header().UncleHash,
   145  		Coinbase:         b.Header().Coinbase,
   146  		Hash:             b.Hash(),
   147  		Root:             b.Header().Root,
   148  		TxHash:           b.Header().TxHash,
   149  		ReceiptHash:      b.ReceiptHash(),
   150  		Bloom:            b.Bloom(),
   151  		Difficulty:       b.Header().Difficulty,
   152  		Number:           b.Header().Number,
   153  		GasLimit:         b.Header().GasLimit,
   154  		GasUsed:          b.Header().GasUsed,
   155  		Time:             b.Header().Time,
   156  		Extra:            b.Header().Extra,
   157  		MixDigest:        b.Header().MixDigest,
   158  		Nonce:            b.Header().Nonce,
   159  		BaseFee:          b.Header().BaseFee,
   160  		WithdrawalsHash:  b.Header().WithdrawalsHash,
   161  		Size:             b.Size(),
   162  		// TotalDifficulty:  b.Difficulty(),
   163  		TransactionsHash: TransactionsHash(*b),
   164  	}
   165  }
   166  
   167  // String overrides the standard behavior for Header "to-string".
   168  func (h *Header) String() string {
   169  	var p Printable
   170  	if err := p.FromStruct(h); err != nil {
   171  		panic(err)
   172  	}
   173  	s := p.Columnize(*NewPrintableFormat(20, 0, 0, byte(' ')))
   174  
   175  	return s
   176  }
   177  
   178  // TransactionsHash returns a list of transaction hash starting from a list of transactions contained in a block.
   179  func TransactionsHash(block types.Block) []common.Hash {
   180  	txsh := make([]common.Hash, len(block.Transactions()))
   181  
   182  	for i, tx := range block.Transactions() {
   183  		txsh[i] = tx.Hash()
   184  	}
   185  
   186  	return txsh
   187  }
   188  
   189  // Block is a customized block for cli.
   190  type Block struct {
   191  	ParentHash      common.Hash        `json:"parentHash"`
   192  	UncleHash       common.Hash        `json:"sha3Uncles"`
   193  	Coinbase        common.Address     `json:"miner"`
   194  	Hash            common.Hash        `json:"hash"`
   195  	Root            common.Hash        `json:"stateRoot"`
   196  	TxHash          common.Hash        `json:"transactionsRoot"`
   197  	ReceiptHash     common.Hash        `json:"receiptsRoot"`
   198  	Bloom           types.Bloom        `json:"logsBloom"`
   199  	Difficulty      *big.Int           `json:"difficulty"`
   200  	Number          *big.Int           `json:"number"`
   201  	GasLimit        uint64             `json:"gasLimit"`
   202  	GasUsed         uint64             `json:"gasUsed"`
   203  	Time            uint64             `json:"timestamp"`
   204  	Extra           []byte             `json:"extraData"`
   205  	MixDigest       common.Hash        `json:"mixHash"`
   206  	Nonce           types.BlockNonce   `json:"nonce"`
   207  	BaseFee         *big.Int           `json:"baseFeePerGas"`
   208  	WithdrawalsHash *common.Hash       `json:"withdrawalsRoot"`
   209  	Size            common.StorageSize `json:"size"`
   210  	// TODO: totalDifficulty to be implemented
   211  	// TotalDifficulty *big.Int           `json:"totalDifficulty"`
   212  	Uncles          []*types.Header    `json:"uncles"`
   213  	Transactions    types.Transactions `json:"transactions"`
   214  	Withdrawals     types.Withdrawals  `json:"withdrawals"`
   215  }
   216  
   217  // NewBlock returns the custom-built Block object.
   218  func NewBlock(b *types.Block) *Block {
   219  	return &Block{
   220  		ParentHash:      b.Header().ParentHash,
   221  		UncleHash:       b.Header().UncleHash,
   222  		Coinbase:        b.Header().Coinbase,
   223  		Hash:            b.Hash(),
   224  		Root:            b.Header().Root,
   225  		TxHash:          b.Header().TxHash,
   226  		ReceiptHash:     b.ReceiptHash(),
   227  		Bloom:           b.Bloom(),
   228  		Difficulty:      b.Header().Difficulty,
   229  		Number:          b.Header().Number,
   230  		GasLimit:        b.Header().GasLimit,
   231  		GasUsed:         b.Header().GasUsed,
   232  		Time:            b.Header().Time,
   233  		Extra:           b.Header().Extra,
   234  		MixDigest:       b.Header().MixDigest,
   235  		Nonce:           b.Header().Nonce,
   236  		BaseFee:         b.Header().BaseFee,
   237  		WithdrawalsHash: b.Header().WithdrawalsHash,
   238  		Size:            b.Size(),
   239  		// TotalDifficulty: b.Difficulty(),
   240  		Uncles:          b.Uncles(),
   241  		Transactions:    b.Transactions(),
   242  		// TODO: Withdrawals is empty. To be fixed.
   243  		Withdrawals:     b.Withdrawals(),
   244  	}
   245  }
   246  
   247  // String overrides the standard behavior for Block "to-string".
   248  func (b *Block) String() string {
   249  	var p Printable
   250  	if err := p.FromStruct(b); err != nil {
   251  		panic(err)
   252  	}
   253  	s := p.Columnize(*NewPrintableFormat(20, 0, 0, byte(' ')))
   254  
   255  	return s
   256  }