Skip to content
Merged
2 changes: 2 additions & 0 deletions cmd/rpcdaemon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ The following table shows the current implementation status of Erigon's RPC daem
| | | |
| eth_getBlockByHash | Yes | |
| eth_getBlockByNumber | Yes | |
| eth_getHeaderByHash | Yes | |
| eth_getHeaderByNumber | Yes | |
| eth_getBlockTransactionCountByHash | Yes | |
| eth_getBlockTransactionCountByNumber | Yes | |
| eth_getUncleByBlockHashAndIndex | Yes | |
Expand Down
2 changes: 1 addition & 1 deletion docs/site/docs/fundamentals/modules/rpc-daemon.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Flags:
--rpc.overlay.getlogstimeout duration Maximum amount of time to wait for the answer from the overlay_getLogs call. (default 5m0s)
--rpc.overlay.replayblocktimeout duration Maximum amount of time to wait for the answer to replay a single block when called from an overlay_getLogs call. (default 10s)
--rpc.returndata.limit int Maximum number of bytes returned from eth_call or similar invocations (default 100000)
--rpc.slow duration Print in logs RPC requests slower than given threshold: 100ms, 1s, 1m. Excluded methods: eth_getBlock,eth_getBlockByNumber,eth_getBlockByHash,eth_blockNumber,erigon_blockNumber,erigon_getHeaderByNumber,erigon_getHeaderByHash,erigon_getBlockByTimestamp,eth_call
--rpc.slow duration Print in logs RPC requests slower than given threshold: 100ms, 1s, 1m. Excluded methods: eth_getBlock,eth_getBlockByNumber,eth_getBlockByHash,eth_getHeaderByNumber,eth_getHeaderByHash,eth_blockNumber,erigon_blockNumber,erigon_getHeaderByNumber,erigon_getHeaderByHash,erigon_getBlockByTimestamp,eth_call
--rpc.streaming.disable Erigon has enabled json streaming for some heavy endpoints (like trace_*). It's a trade-off: greatly reduce amount of RAM (in some cases from 30GB to 30mb), but it produce invalid json format if error happened in the middle of streaming (because json is not streaming-friendly format)
--rpc.subscription.filters.maxaddresses int Maximum number of addresses accepted per log subscription.
--rpc.subscription.filters.maxheaders int Maximum number of block headers to store per subscription.
Expand Down
2 changes: 1 addition & 1 deletion docs/site/static/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4582,7 +4582,7 @@ Flags:
--rpc.overlay.getlogstimeout duration Maximum amount of time to wait for the answer from the overlay_getLogs call. (default 5m0s)
--rpc.overlay.replayblocktimeout duration Maximum amount of time to wait for the answer to replay a single block when called from an overlay_getLogs call. (default 10s)
--rpc.returndata.limit int Maximum number of bytes returned from eth_call or similar invocations (default 100000)
--rpc.slow duration Print in logs RPC requests slower than given threshold: 100ms, 1s, 1m. Excluded methods: eth_getBlock,eth_getBlockByNumber,eth_getBlockByHash,eth_blockNumber,erigon_blockNumber,erigon_getHeaderByNumber,erigon_getHeaderByHash,erigon_getBlockByTimestamp,eth_call
--rpc.slow duration Print in logs RPC requests slower than given threshold: 100ms, 1s, 1m. Excluded methods: eth_getBlock,eth_getBlockByNumber,eth_getBlockByHash,eth_getHeaderByNumber,eth_getHeaderByHash,eth_blockNumber,erigon_blockNumber,erigon_getHeaderByNumber,erigon_getHeaderByHash,erigon_getBlockByTimestamp,eth_call
--rpc.streaming.disable Erigon has enabled json streaming for some heavy endpoints (like trace_*). It's a trade-off: greatly reduce amount of RAM (in some cases from 30GB to 30mb), but it produce invalid json format if error happened in the middle of streaming (because json is not streaming-friendly format)
--rpc.subscription.filters.maxaddresses int Maximum number of addresses accepted per log subscription.
--rpc.subscription.filters.maxheaders int Maximum number of block headers to store per subscription.
Expand Down
2 changes: 1 addition & 1 deletion llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4582,7 +4582,7 @@ Flags:
--rpc.overlay.getlogstimeout duration Maximum amount of time to wait for the answer from the overlay_getLogs call. (default 5m0s)
--rpc.overlay.replayblocktimeout duration Maximum amount of time to wait for the answer to replay a single block when called from an overlay_getLogs call. (default 10s)
--rpc.returndata.limit int Maximum number of bytes returned from eth_call or similar invocations (default 100000)
--rpc.slow duration Print in logs RPC requests slower than given threshold: 100ms, 1s, 1m. Excluded methods: eth_getBlock,eth_getBlockByNumber,eth_getBlockByHash,eth_blockNumber,erigon_blockNumber,erigon_getHeaderByNumber,erigon_getHeaderByHash,erigon_getBlockByTimestamp,eth_call
--rpc.slow duration Print in logs RPC requests slower than given threshold: 100ms, 1s, 1m. Excluded methods: eth_getBlock,eth_getBlockByNumber,eth_getBlockByHash,eth_getHeaderByNumber,eth_getHeaderByHash,eth_blockNumber,erigon_blockNumber,erigon_getHeaderByNumber,erigon_getHeaderByHash,erigon_getBlockByTimestamp,eth_call
--rpc.streaming.disable Erigon has enabled json streaming for some heavy endpoints (like trace_*). It's a trade-off: greatly reduce amount of RAM (in some cases from 30GB to 30mb), but it produce invalid json format if error happened in the middle of streaming (because json is not streaming-friendly format)
--rpc.subscription.filters.maxaddresses int Maximum number of addresses accepted per log subscription.
--rpc.subscription.filters.maxheaders int Maximum number of block headers to store per subscription.
Expand Down
6 changes: 3 additions & 3 deletions rpc/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,10 +439,10 @@ func (b *RPCBlock) MarkPending() {
b.Hash, b.Nonce, b.Miner = nil, nil, nil
}

// rpcMarshalHeader converts the given header to the RPC output. The hash is passed
// RPCMarshalHeader converts the given header to the RPC output. The hash is passed
// in because types.Block hands out header copies that drop the memoized hash, and
// recomputing it costs an RLP encode plus a keccak per call.
func rpcMarshalHeader(head *types.Header, hash common.Hash) *RPCHeader {
func RPCMarshalHeader(head *types.Header, hash common.Hash) *RPCHeader {
result := &RPCHeader{
Number: (*hexutil.U256)(&head.Number),
Hash: &hash,
Expand Down Expand Up @@ -502,7 +502,7 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool) *RPCBlock {
}

result := &RPCBlock{
RPCHeader: *rpcMarshalHeader(block.Header(), block.Hash()),
RPCHeader: *RPCMarshalHeader(block.Header(), block.Hash()),
Size: hexutil.Uint64(block.Size()),
Transactions: transactions,
Uncles: uncleHashes,
Expand Down
2 changes: 1 addition & 1 deletion rpc/ethapi/api_json_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func BenchmarkRPCMarshalHeader(b *testing.B) {
b.Run(tc.name, func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
out, err := json.Marshal(rpcMarshalHeader(tc.head, tc.head.Hash()))
out, err := json.Marshal(RPCMarshalHeader(tc.head, tc.head.Hash()))
if err != nil || len(out) == 0 {
b.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion rpc/ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func TestToTransactionBlobWithoutMaxFeePerBlobGas(t *testing.T) {
require.True(t, blobTx.MaxFeePerBlobGas.IsZero())
}

// rpcMarshalHeader aliases the header it is given: the U256 quantities, the
// RPCMarshalHeader aliases the header it is given: the U256 quantities, the
// extraData slice and the fields callers may null out all point straight at it.
// RPCMarshalBlock must therefore hand it a copy — the result is mutable and
// exported, and the block keeps its memoized hash, so a caller writing through
Expand Down
2 changes: 2 additions & 0 deletions rpc/jsonrpc/eth_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ type EthAPI interface {
// Block related (proposed file: ./eth_blocks.go)
GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (*ethapi.RPCBlock, error)
GetBlockByHash(ctx context.Context, hash rpc.BlockNumberOrHash, fullTx bool) (*ethapi.RPCBlock, error)
GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*ethapi.RPCHeader, error)
GetHeaderByHash(ctx context.Context, hash common.Hash) (*ethapi.RPCHeader, error)
GetBlockTransactionCountByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*hexutil.Uint, error)
GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (*hexutil.Uint, error)

Expand Down
44 changes: 44 additions & 0 deletions rpc/jsonrpc/eth_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,50 @@ func (api *APIImpl) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber
return response, nil
}

// GetHeaderByNumber implements eth_getHeaderByNumber. Returns a block's header given a block
Comment thread
AskAlexSharov marked this conversation as resolved.
// number. Per ethereum/execution-apis#877, the result is null for an unknown block, for the
// pending tag, and for a safe or finalized tag that cannot be resolved to a block.
func (api *APIImpl) GetHeaderByNumber(ctx context.Context, blockNumber rpc.BlockNumber) (*ethapi.RPCHeader, error) {
tx, err := api.filters.BeginTemporalRoWithOverlay(ctx, api.db)
if err != nil {
return nil, err
}
defer tx.Rollback()

header, err := api.headerByNumber(ctx, blockNumber, tx)
if err != nil {
var unresolvedTag *rpc.CustomError
if errors.As(err, &rpc.BlockNotFoundErr{}) ||
(errors.As(err, &unresolvedTag) && unresolvedTag.Code == rpchelper.UnknownBlockCode) {
return nil, nil
}
return nil, err
}
if header == nil {
return nil, nil
}
return ethapi.RPCMarshalHeader(header, header.Hash()), nil
}

// GetHeaderByHash implements eth_getHeaderByHash. Returns a block's header given a block's hash,
// or null if the block is unknown.
func (api *APIImpl) GetHeaderByHash(ctx context.Context, hash common.Hash) (*ethapi.RPCHeader, error) {
tx, err := api.filters.BeginTemporalRoWithOverlay(ctx, api.db)
if err != nil {
return nil, err
}
defer tx.Rollback()

header, err := api.headerByHash(ctx, hash, tx)
if err != nil {
return nil, err
}
if header == nil {
return nil, nil
}
return ethapi.RPCMarshalHeader(header, header.Hash()), nil
}

// GetBlockByHash implements eth_getBlockByHash. Returns information about a block given the block's hash.
func (api *APIImpl) GetBlockByHash(ctx context.Context, numberOrHash rpc.BlockNumberOrHash, fullTx bool) (*ethapi.RPCBlock, error) {
if numberOrHash.BlockHash == nil {
Expand Down
47 changes: 47 additions & 0 deletions rpc/jsonrpc/eth_block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,53 @@ func TestGetBlockAccessListRegeneratesPrunedBAL(t *testing.T) {
}
}

func TestGetHeaderByNumber(t *testing.T) {
m, _, _ := rpcdaemontest.CreateTestExecModule(t)
api := newEthApiForTest(newBaseApiForTest(m), m.DB, nil, nil)
ctx := context.Background()

header, err := api.GetHeaderByNumber(ctx, rpc.LatestBlockNumber)
require.NoError(t, err)
require.NotNil(t, header)
assert.Equal(t, common.HexToHash("0x9c47d5780744fa24ccdb1543a9b715e53431d5560b9e460b8b7a68f7c58310ae"), *header.Hash)

for _, blockNum := range []rpc.BlockNumber{rpc.SafeBlockNumber, rpc.FinalizedBlockNumber} {
header, err = api.GetHeaderByNumber(ctx, blockNum)
require.NoError(t, err, "block %d", blockNum)
require.NotNil(t, header, "block %d resolves in the test module", blockNum)
}

require.NoError(t, m.DB.Update(ctx, func(tx kv.RwTx) error {
if err := tx.Delete(kv.LastForkchoice, []byte("safeBlockHash")); err != nil {
return err
}
return tx.Delete(kv.LastForkchoice, []byte("finalizedBlockHash"))
}))

unresolvable := []rpc.BlockNumber{rpc.SafeBlockNumber, rpc.FinalizedBlockNumber}
for _, blockNum := range append(unresolvable, 1_000_000, rpc.PendingBlockNumber) {
header, err = api.GetHeaderByNumber(ctx, blockNum)
require.NoError(t, err, "block %d", blockNum)
require.Nil(t, header, "block %d", blockNum)
}
}

func TestGetHeaderByHash(t *testing.T) {
m, _, _ := rpcdaemontest.CreateTestExecModule(t)
api := newEthApiForTest(newBaseApiForTest(m), m.DB, nil, nil)
ctx := context.Background()

latestHash := common.HexToHash("0x9c47d5780744fa24ccdb1543a9b715e53431d5560b9e460b8b7a68f7c58310ae")
header, err := api.GetHeaderByHash(ctx, latestHash)
require.NoError(t, err)
require.NotNil(t, header)
assert.Equal(t, latestHash, *header.Hash)

header, err = api.GetHeaderByHash(ctx, common.HexToHash("0xdeadbeef"))
require.NoError(t, err)
require.Nil(t, header)
}

// Gets the latest block number with the latest tag
func TestGetBlockByNumberWithLatestTag(t *testing.T) {
m, _, _ := rpcdaemontest.CreateTestExecModule(t)
Expand Down
17 changes: 17 additions & 0 deletions rpc/jsonrpc/overlay_race_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2060,6 +2060,9 @@ func TestPublishCycleDuringTxAcquisition(t *testing.T) {
detailsHash := func(v any) common.Hash {
return blockHash(v.(map[string]any)["block"])
}
headerHash := func(v any) common.Hash {
return *v.(*ethapi.RPCHeader).Hash
}

cases := []struct {
name string
Expand Down Expand Up @@ -2107,6 +2110,20 @@ func TestPublishCycleDuringTxAcquisition(t *testing.T) {
return api.GetRawHeader(h.m.Ctx, rpc.BlockNumberOrHashWithNumber(head(h)))
},
},
{
name: "eth_getHeaderByNumber",
call: func(t *testing.T, h *overlayAheadHarness, db kv.TemporalRoDB) (any, error) {
return newEthApiForTest(h.base, db, nil, nil).GetHeaderByNumber(h.m.Ctx, head(h))
},
hashOf: headerHash,
},
{
name: "eth_getHeaderByHash",
call: func(t *testing.T, h *overlayAheadHarness, db kv.TemporalRoDB) (any, error) {
return newEthApiForTest(h.base, db, nil, nil).GetHeaderByHash(h.m.Ctx, h.overlayHeader.Hash())
},
hashOf: headerHash,
},
{
name: "erigon_getHeaderByNumber",
call: func(t *testing.T, h *overlayAheadHarness, db kv.TemporalRoDB) (any, error) {
Expand Down
6 changes: 6 additions & 0 deletions rpc/jsonrpc/prune_gating_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@ var pruneGatingEndpoints = []pruneGatingEndpoint{
}},
// Header endpoints read the header alone: a retention window takes away
// transactions and state history, never headers.
{"eth_getHeaderByNumber", notGated, func(ctx context.Context, apis pruneGatingAPIs, ref pruneGatingRef) (any, error) {
return apis.eth.GetHeaderByNumber(ctx, rpc.BlockNumber(ref.num))
}},
{"eth_getHeaderByHash", notGated, func(ctx context.Context, apis pruneGatingAPIs, ref pruneGatingRef) (any, error) {
return apis.eth.GetHeaderByHash(ctx, ref.hash)
}},
{"erigon_getHeaderByNumber", notGated, func(ctx context.Context, apis pruneGatingAPIs, ref pruneGatingRef) (any, error) {
return apis.erigon.GetHeaderByNumber(ctx, rpc.BlockNumber(ref.num))
}},
Expand Down
2 changes: 1 addition & 1 deletion rpc/rpccfg/rpccfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ type OverlayApiConfig struct {
}

var SlowLogBlackList = []string{
"eth_getBlock", "eth_getBlockByNumber", "eth_getBlockByHash", "eth_blockNumber",
"eth_getBlock", "eth_getBlockByNumber", "eth_getBlockByHash", "eth_getHeaderByNumber", "eth_getHeaderByHash", "eth_blockNumber",
"erigon_blockNumber", "erigon_getHeaderByNumber", "erigon_getHeaderByHash", "erigon_getBlockByTimestamp",
"eth_call",
}
Loading