diff --git a/jsonrpc/eth_blockchain_test.go b/jsonrpc/eth_blockchain_test.go index ad1f1c2e0a..73bb5ef46c 100644 --- a/jsonrpc/eth_blockchain_test.go +++ b/jsonrpc/eth_blockchain_test.go @@ -68,6 +68,59 @@ func TestEth_Block_GetBlockByHash(t *testing.T) { assert.Nil(t, res) } +func TestEth_Block_GetHeaderByNumber(t *testing.T) { + store := &mockBlockStore{} + for i := 0; i < 10; i++ { + store.add(newTestBlock(uint64(i), hash1)) + } + + eth := newTestEthEndpoint(store) + + cases := []struct { + description string + blockNum BlockNumber + isNotNil bool + err bool + }{ + {"should be able to get the latest block number", LatestBlockNumber, true, false}, + {"should be able to get the earliest block number", EarliestBlockNumber, true, false}, + {"should not be able to get block with negative number", BlockNumber(-50), false, true}, + {"should be able to get block with number 0", BlockNumber(0), true, false}, + {"should be able to get block with number 2", BlockNumber(2), true, false}, + {"should be able to get block with number greater than latest block", BlockNumber(50), false, false}, + } + for _, c := range cases { + res, err := eth.GetHeadarByNumber(c.blockNum) + + if c.isNotNil { + assert.NotNil(t, res, "expected to return block, but got nil") + } else { + assert.Nil(t, res, "expected to return nil, but got data") + } + + if c.err { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + } +} + +func TestEth_Block_GetHeaderByHash(t *testing.T) { + store := &mockBlockStore{} + store.add(newTestBlock(1, hash1)) + + eth := newTestEthEndpoint(store) + + res, err := eth.GetHeadarByHash(hash1) + assert.NoError(t, err) + assert.NotNil(t, res) + + res, err = eth.GetHeadarByHash(hash2) + assert.NoError(t, err) + assert.Nil(t, res) +} + func TestEth_Block_BlockNumber(t *testing.T) { store := &mockBlockStore{} store.add(&types.Block{ diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index 4527fe32a0..c63fa3e6f6 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -155,6 +155,41 @@ func (e *Eth) GetBlockByHash(hash types.Hash, fullTx bool) (interface{}, error) return toBlock(block, fullTx), nil } +// GetHeaderByNumber returns the requested canonical block header. +// * When blockNr is -1 the chain head is returned. +// * When blockNr is -2 the pending chain head is returned. +func (e *Eth) GetHeadarByNumber(number BlockNumber) (interface{}, error) { + num, err := GetNumericBlockNumber(number, e.store) + if err != nil { + return nil, err + } + + block, ok := e.store.GetBlockByNumber(num, true) + if !ok { + return nil, nil + } + + if err := e.filterExtra(block); err != nil { + return nil, err + } + + return toHeader(block.Header), nil +} + +// GetHeaderByHash returns the requested header by hash. +func (e *Eth) GetHeadarByHash(hash types.Hash) (interface{}, error) { + block, ok := e.store.GetBlockByHash(hash, true) + if !ok { + return nil, nil + } + + if err := e.filterExtra(block); err != nil { + return nil, err + } + + return toHeader(block.Header), nil +} + func (e *Eth) filterExtra(block *types.Block) error { // we need to copy it because the store returns header from storage directly // and not a copy, so changing it, actually changes it in storage as well diff --git a/jsonrpc/types.go b/jsonrpc/types.go index fdb34b7323..e135c590b9 100644 --- a/jsonrpc/types.go +++ b/jsonrpc/types.go @@ -108,6 +108,27 @@ func toTransaction( return res } +type header struct { + ParentHash types.Hash `json:"parentHash"` + Sha3Uncles types.Hash `json:"sha3Uncles"` + Miner argBytes `json:"miner"` + StateRoot types.Hash `json:"stateRoot"` + TxRoot types.Hash `json:"transactionsRoot"` + ReceiptsRoot types.Hash `json:"receiptsRoot"` + LogsBloom types.Bloom `json:"logsBloom"` + Difficulty argUint64 `json:"difficulty"` + TotalDifficulty argUint64 `json:"totalDifficulty"` + Number argUint64 `json:"number"` + GasLimit argUint64 `json:"gasLimit"` + GasUsed argUint64 `json:"gasUsed"` + Timestamp argUint64 `json:"timestamp"` + ExtraData argBytes `json:"extraData"` + MixHash types.Hash `json:"mixHash"` + Nonce types.Nonce `json:"nonce"` + Hash types.Hash `json:"hash"` + BaseFee argUint64 `json:"baseFeePerGas,omitempty"` +} + type block struct { ParentHash types.Hash `json:"parentHash"` Sha3Uncles types.Hash `json:"sha3Uncles"` @@ -198,6 +219,31 @@ func toBlock(b *types.Block, fullTx bool) *block { return res } +func toHeader(h *types.Header) *header { + res := &header{ + ParentHash: h.ParentHash, + Sha3Uncles: h.Sha3Uncles, + Miner: argBytes(h.Miner), + StateRoot: h.StateRoot, + TxRoot: h.TxRoot, + ReceiptsRoot: h.ReceiptsRoot, + LogsBloom: h.LogsBloom, + Difficulty: argUint64(h.Difficulty), + TotalDifficulty: argUint64(h.Difficulty), // not needed for POS + Number: argUint64(h.Number), + GasLimit: argUint64(h.GasLimit), + GasUsed: argUint64(h.GasUsed), + Timestamp: argUint64(h.Timestamp), + ExtraData: argBytes(h.ExtraData), + MixHash: h.MixHash, + Nonce: h.Nonce, + Hash: h.Hash, + BaseFee: argUint64(h.BaseFee), + } + + return res +} + type receipt struct { Root types.Hash `json:"root"` CumulativeGasUsed argUint64 `json:"cumulativeGasUsed"`