Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/keeper/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/time v0.12.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
Expand Down
1 change: 1 addition & 0 deletions cmd/keeper/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
Expand Down
22 changes: 21 additions & 1 deletion consensus/bor/bor.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ type Bor struct {
// ctx is cancelled when Close() is called, allowing in-flight operations to abort promptly.
ctx context.Context
ctxCancel context.CancelFunc

// api is the bor engine API instance reused across all callers (JSON-RPC and gRPC).
api *API
apiOnce sync.Once
}

type signer struct {
Expand Down Expand Up @@ -1520,11 +1524,27 @@ func (c *Bor) SealHash(header *types.Header) common.Hash {

// APIs implements consensus.Engine, returning the user facing RPC API to allow
// controlling the signer voting.
//
// The returned *API is cached on the first call so that per-API state (e.g.,
// rootHashCache) persists across calls. JSON-RPC only invokes APIs() once at
// node startup, but the gRPC backend fetches it on every handler call — without
// the cache those calls would each start from an empty state.
//
// rootHashCache is initialized here (inside the sync.Once) rather than lazily
// in GetRootHash so that concurrent gRPC handlers sharing the cached *API
// cannot race in initializeRootHashCache.
func (c *Bor) APIs(chain consensus.ChainHeaderReader) []rpc.API {
c.apiOnce.Do(func() {
a := &API{chain: chain, bor: c}
if err := a.initializeRootHashCache(); err != nil {
panic(fmt.Errorf("bor: failed to initialize rootHashCache: %w", err))
}
c.api = a
})
return []rpc.API{{
Namespace: "bor",
Version: "1.0",
Service: &API{chain: chain, bor: c},
Service: c.api,
Public: false,
}}
}
Expand Down
14 changes: 14 additions & 0 deletions consensus/bor/bor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2039,6 +2039,20 @@ func TestAPIs_ReturnsBorNamespace(t *testing.T) {
require.Equal(t, "1.0", apis[0].Version)
}

// TestAPIs_ReturnsSameInstanceAcrossCalls verifies that repeated calls to APIs() must return the same *API
// so per-API state such as rootHashCache persists across calls. This matters for the gRPC backend
// which fetches APIs() on every handler invocation; returning a fresh *API each call defeats caching.
func TestAPIs_ReturnsSameInstanceAcrossCalls(t *testing.T) {
t.Parallel()
sp := &fakeSpanner{vals: []*valset.Validator{{Address: common.HexToAddress("0x1"), VotingPower: 1}}}
borCfg := defaultBorConfig()
chain, b := newChainAndBorForTest(t, sp, borCfg, false, common.Address{}, uint64(time.Now().Unix()))

first := b.APIs(chain.HeaderChain())
second := b.APIs(chain.HeaderChain())
require.Same(t, first[0].Service, second[0].Service, "APIs must return the cached *API on repeated calls")
}

func TestClose_Idempotent(t *testing.T) {
t.Parallel()
sp := &fakeSpanner{vals: []*valset.Validator{{Address: common.HexToAddress("0x1"), VotingPower: 1}}}
Expand Down
20 changes: 10 additions & 10 deletions eth/tracers/data.csv
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
TransactionIndex, Incarnation, VersionTxIdx, VersionInc, Path, Operation
0 , 0, -1 , -1, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Read
0 , 0, -1 , -1, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000103, Read
0 , 0, -1 , -1, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Read
0 , 0, -1 , -1, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Read
0 , 0, 0 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Write
0 , 0, -1 , -1, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Read
0 , 0, 0 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Write
0 , 0, 0 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000103, Write
0 , 0, 0 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Write
1 , 0, 0 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Read
0 , 0, 0 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Write
1 , 0, 0 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Read
Comment thread
claude[bot] marked this conversation as resolved.
1 , 0, 0 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000103, Read
1 , 0, 0 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Read
1 , 0, 1 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Write
1 , 0, 1 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Write
1 , 0, 0 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Read
1 , 0, 1 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Write
1 , 0, 1 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000103, Write
2 , 0, 1 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Read
2 , 0, 1 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Read
1 , 0, 1 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Write
1 , 0, 1 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Write
2 , 0, 1 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000103, Read
2 , 0, 1 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Read
2 , 0, 1 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Read
2 , 0, 1 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Read
2 , 0, 2 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Write
2 , 0, 2 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000103, Write
2 , 0, 2 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Write
2 , 0, 2 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Write
2 , 0, 2 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Write
3 , 0, 2 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Read
3 , 0, 2 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Read
3 , 0, 2 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Read
3 , 0, 2 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000103, Read
3 , 0, 2 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Read
3 , 0, 3 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Write
3 , 0, 3 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000103, Write
3 , 0, 3 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Write
3 , 0, 3 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Write
4 , 0, 3 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Read
4 , 0, 3 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000103, Read
4 , 0, 3 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103, Read
4 , 0, 3 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Read
4 , 0, 3 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Read
4 , 0, 4 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000001, Write
4 , 0, 4 , 0, 000000000000000000000000000000000000dead00000000000000000000000000000000000000000000000000000000000000000103, Write
4 , 0, 4 , 0, 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, Write
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ go 1.26.2
require (
github.com/0xPolygon/crand v1.0.3
github.com/0xPolygon/heimdall-v2 v0.6.0
github.com/0xPolygon/polyproto v0.0.7
github.com/0xPolygon/polyproto v0.0.8-0.20260423132317-7d955b45ef8a
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2
github.com/BurntSushi/toml v1.4.0
github.com/JekaMas/go-grpc-net-conn v0.0.0-20220708155319-6aff21f2d13d
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ github.com/0xPolygon/crand v1.0.3 h1:BYYflmgLhmGPEgqtopG4muq6wV6DOkwD8uPymNz5WeQ
github.com/0xPolygon/crand v1.0.3/go.mod h1:km4366oC7EVFl1xNUCwzxUXNM10swZqd8LZ0E5SgbAE=
github.com/0xPolygon/heimdall-v2 v0.6.0 h1:rA8RISMnns1w08PxTLvDBS5WiaTOFHJGSrhDWDJLtHc=
github.com/0xPolygon/heimdall-v2 v0.6.0/go.mod h1:fVkGiODG6cGLaDyrE3qxIrvz1rbUr4Zdrr3dOm2SPgg=
github.com/0xPolygon/polyproto v0.0.7 h1:Ody+kFyCRK4QXRPXbsP5pdxKrDgwAAXtFB8NPgaIxRs=
github.com/0xPolygon/polyproto v0.0.7/go.mod h1:2Iw93k2LismvckKKeXQITuhJH9vLbqOa212AMskH6no=
github.com/0xPolygon/polyproto v0.0.8-0.20260423132317-7d955b45ef8a h1:vVtSjO29FcFBZbNVsVGy6z3lv3RRr/sI1vPR7IzbZZE=
github.com/0xPolygon/polyproto v0.0.8-0.20260423132317-7d955b45ef8a/go.mod h1:2Iw93k2LismvckKKeXQITuhJH9vLbqOa212AMskH6no=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
Expand Down
177 changes: 170 additions & 7 deletions internal/cli/server/api_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"errors"
"math"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
Expand All @@ -13,6 +14,10 @@
protoutil "github.com/0xPolygon/polyproto/utils"
)

// maxBlockInfoBatchSize caps the per-call range to prevent abuse of the batch endpoint.
// Must be >= heimdall's MaxMilestonePropositionLength.
const maxBlockInfoBatchSize = 256

func (s *Server) GetRootHash(ctx context.Context, req *protobor.GetRootHashRequest) (*protobor.GetRootHashResponse, error) {
rootHash, err := s.backend.APIBackend.GetRootHash(ctx, req.StartBlockNumber, req.EndBlockNumber)
if err != nil {
Expand All @@ -31,12 +36,51 @@
return &protobor.GetVoteOnHashResponse{Response: vote}, nil
}

func headerToProtoborHeader(h *types.Header) *protobor.Header {
return &protobor.Header{
Number: h.Number.Uint64(),
ParentHash: protoutil.ConvertHashToH256(h.ParentHash),
Time: h.Time,
func headerToProtoBorHeader(h *types.Header) *protobor.Header {
out := &protobor.Header{
Number: h.Number.Uint64(),
ParentHash: protoutil.ConvertHashToH256(h.ParentHash),
Time: h.Time,
UncleHash: protoutil.ConvertHashToH256(h.UncleHash),
Coinbase: protoutil.ConvertAddressToH160(h.Coinbase),
StateRoot: protoutil.ConvertHashToH256(h.Root),
TxRoot: protoutil.ConvertHashToH256(h.TxHash),
ReceiptRoot: protoutil.ConvertHashToH256(h.ReceiptHash),
Bloom: append([]byte(nil), h.Bloom.Bytes()...),
GasLimit: h.GasLimit,
GasUsed: h.GasUsed,
ExtraData: append([]byte(nil), h.Extra...),
MixDigest: protoutil.ConvertHashToH256(h.MixDigest),
Nonce: append([]byte(nil), h.Nonce[:]...),
}
if h.Difficulty != nil {
out.Difficulty = h.Difficulty.Bytes()
}
if h.BaseFee != nil {
out.BaseFee = h.BaseFee.Bytes()
}
if h.WithdrawalsHash != nil {
out.WithdrawalsHash = protoutil.ConvertHashToH256(*h.WithdrawalsHash)
}
// BlobGasUsed and ExcessBlobGas are proto3 optional. *uint64 preserves
// nil-vs-zero; we copy through a fresh variable so the proto doesn't alias
// the source header's pointers (consistent with how ExtraData/Bloom/Nonce
// are handled above).
if h.BlobGasUsed != nil {
v := *h.BlobGasUsed
out.BlobGasUsed = &v
}
if h.ExcessBlobGas != nil {
v := *h.ExcessBlobGas
out.ExcessBlobGas = &v
}
if h.ParentBeaconRoot != nil {
out.ParentBeaconBlockRoot = protoutil.ConvertHashToH256(*h.ParentBeaconRoot)
}
if h.RequestsHash != nil {
out.RequestsHash = protoutil.ConvertHashToH256(*h.RequestsHash)
}
return out
}

func (s *Server) HeaderByNumber(ctx context.Context, req *protobor.GetHeaderByNumberRequest) (*protobor.GetHeaderByNumberResponse, error) {
Expand All @@ -53,7 +97,7 @@
return nil, errors.New("header not found")
}

return &protobor.GetHeaderByNumberResponse{Header: headerToProtoborHeader(header)}, nil
return &protobor.GetHeaderByNumberResponse{Header: headerToProtoBorHeader(header)}, nil
}

func (s *Server) BlockByNumber(ctx context.Context, req *protobor.GetBlockByNumberRequest) (*protobor.GetBlockByNumberResponse, error) {
Expand All @@ -75,7 +119,7 @@

func blockToProtoBlock(h *types.Block) *protobor.Block {
return &protobor.Block{
Header: headerToProtoborHeader(h.Header()),
Header: headerToProtoBorHeader(h.Header()),
}
}

Expand Down Expand Up @@ -107,6 +151,125 @@
return &protobor.ReceiptResponse{Receipt: ConvertReceiptToProtoReceipt(receipt)}, nil
}
Comment thread
marcello33 marked this conversation as resolved.

func (s *Server) GetAuthor(ctx context.Context, req *protobor.GetAuthorRequest) (*protobor.GetAuthorResponse, error) {
bN, err := getRpcBlockNumberFromString(req.Number)
if err != nil {
return nil, err
}

header, err := s.backend.APIBackend.HeaderByNumber(ctx, bN)
if err != nil {
return nil, err
}
if header == nil {
return nil, errors.New("header not found")
}

author, err := s.backend.Engine().Author(header)
if err != nil {
return nil, err
}

return &protobor.GetAuthorResponse{Author: protoutil.ConvertAddressToH160(author)}, nil
}

func (s *Server) GetTdByHash(ctx context.Context, req *protobor.GetTdByHashRequest) (*protobor.GetTdResponse, error) {
hash := common.Hash(protoutil.ConvertH256ToHash(req.Hash))

td := s.backend.APIBackend.GetTd(ctx, hash)
if td == nil {
return nil, errors.New("total difficulty not found")
}
if !td.IsUint64() {
return nil, errors.New("total difficulty overflows uint64")
}
return &protobor.GetTdResponse{TotalDifficulty: td.Uint64()}, nil
}

Check failure on line 187 in internal/cli/server/api_service.go

View check run for this annotation

Claude / Claude Code Review

GetTdByHash panics on unset/partial Hash field

The newly-added `GetTdByHash` handler passes `req.Hash` directly to `protoutil.ConvertH256ToHash` without a nil guard — any request with `Hash=nil` (proto3 default), `Hash=&H256{}`, or a partially populated `H256` panics inside `ConvertH256ToHash` when it dereferences `h256.Hi.Hi` / `h256.Lo.Hi`. grpc-go recovers the panic and returns `codes.Internal`, so the process survives, but every malformed request logs a full stack trace (log-flood vector) and clients receive the wrong status code (should
Comment thread
marcello33 marked this conversation as resolved.

func (s *Server) GetTdByNumber(ctx context.Context, req *protobor.GetTdByNumberRequest) (*protobor.GetTdResponse, error) {
bN, err := getRpcBlockNumberFromString(req.Number)
if err != nil {
return nil, err
}
td := s.backend.APIBackend.GetTdByNumber(ctx, bN)
if td == nil {
return nil, errors.New("total difficulty not found")
}
if !td.IsUint64() {
return nil, errors.New("total difficulty overflows uint64")
}
return &protobor.GetTdResponse{TotalDifficulty: td.Uint64()}, nil
}

Check notice on line 202 in internal/cli/server/api_service.go

View check run for this annotation

Claude / Claude Code Review

GetTdByNumber non-deterministic for special block tags

The new gRPC `GetTdByNumber` (api_service.go:189-202) forwards special block tags ("latest", "earliest", "finalized", "safe", "pending") straight into `EthAPIBackend.GetTdByNumber` (eth/api_backend.go:357-362), which calls `blockchain.GetTd(header.Hash(), uint64(blockNr.Int64()))`. For tag=-1 (latest) that becomes `uint64(-1)=MaxUint64`, so the DB key is wrong — the RPC returns the cached TD on a hash cache hit and `"total difficulty not found"` on cache miss, i.e. it's non-deterministic. This i
Comment thread
marcello33 marked this conversation as resolved.

func (s *Server) GetBlockInfoInBatch(ctx context.Context, req *protobor.GetBlockInfoInBatchRequest) (*protobor.GetBlockInfoInBatchResponse, error) {
if req.EndBlockNumber < req.StartBlockNumber {
return nil, errors.New("invalid range: end < start")
}
if req.EndBlockNumber-req.StartBlockNumber >= uint64(maxBlockInfoBatchSize) {
Comment thread
marcello33 marked this conversation as resolved.
return nil, errors.New("invalid range: exceeds max batch size")
}
Comment thread
marcello33 marked this conversation as resolved.
if req.EndBlockNumber > math.MaxInt64 {
return nil, errors.New("invalid range: end exceeds max int64")
Comment thread
marcello33 marked this conversation as resolved.
Outdated
}

count := req.EndBlockNumber - req.StartBlockNumber + 1
out := &protobor.GetBlockInfoInBatchResponse{
Blocks: make([]*protobor.BlockInfo, 0, count),
}

for j := uint64(0); j < count; j++ {
if err := ctx.Err(); err != nil {
return nil, err
}
info, ok := s.fetchBlockInfo(ctx, req.StartBlockNumber+j)
// this requires APIBackend mock returning a missing block mid-range
// mutator-disable-next-line gap-stop semantics
if !ok {
// Match HTTP batch semantics: stop at the first gap, return what we have.
break
}
out.Blocks = append(out.Blocks, info)
}

return out, nil
}

// fetchBlockInfo loads header, total difficulty, and author for blockNum.
// Returns (nil, false) if any piece is missing — the caller should stop the loop.
// Author is left as a nil *H160 for genesis; callers must nil-check before
// decoding.
func (s *Server) fetchBlockInfo(ctx context.Context, blockNum uint64) (*protobor.BlockInfo, bool) {
if blockNum > math.MaxInt64 {
return nil, false
}
header, err := s.backend.APIBackend.HeaderByNumber(ctx, rpc.BlockNumber(blockNum))
// the negate_conditional requires mocking both err!=nil and nil-header paths
// mutator-disable-next-line defensive APIBackend guard
if err != nil || header == nil {
return nil, false
}

td := s.backend.APIBackend.GetTd(ctx, header.Hash())
if td == nil || !td.IsUint64() {
return nil, false
}

info := &protobor.BlockInfo{
Header: headerToProtoBorHeader(header),
TotalDifficulty: td.Uint64(),
}

if blockNum > 0 {
author, err := s.backend.Engine().Author(header)
if err != nil {
return nil, false
}
info.Author = protoutil.ConvertAddressToH160(author)
}

return info, true

Check warning on line 270 in internal/cli/server/api_service.go

View check run for this annotation

Claude / Claude Code Review

fetchBlockInfo swallows backend errors as gap-stop

`fetchBlockInfo` (api_service.go:241-271) collapses three outcomes — a legitimate gap (header==nil && err==nil), a backend error from HeaderByNumber, and an ecrecover/Author failure — into the same `(nil, false)` return, which the caller treats as gap-stop with no error/log/metric. An Author() error in particular is never a gap (it indicates a corrupted seal) yet silently truncates the response. Practical exposure today is narrow (HeaderByNumber currently swallows backend errors to nil, and Auth
Comment thread
marcello33 marked this conversation as resolved.
Outdated
}

Comment thread
marcello33 marked this conversation as resolved.
Outdated
func getRpcBlockNumberFromString(blockNumber string) (rpc.BlockNumber, error) {
switch blockNumber {
case "latest":
Expand Down
Loading
Loading