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 }