app, helper, x/bor: full grpc implementation#576
app, helper, x/bor: full grpc implementation#576marcello33 wants to merge 15 commits intodevelopfrom
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
|
@claude review. |
|
Claude finished @marcello33's task in 2m 20s —— View job Code Review: gRPC Implementation
OverviewThis PR adds comprehensive gRPC communication support between Heimdall and Bor, replacing or augmenting the existing HTTP JSON-RPC calls. Key changes:
Overall this is a well-structured PR with excellent test coverage. The security-sensitive transport handling (TLS enforcement, token leak prevention) is well thought out. Below are findings by severity. Security Findings1. MEDIUM: Excessive gRPC retry configuration — potential for very long blocking callsFile: retryOpts := []grpcRetry.CallOption{
grpcRetry.WithMax(10000),
grpcRetry.WithBackoff(grpcRetry.BackoffLinear(5 * time.Second)),
grpcRetry.WithCodes(codes.Internal, codes.Unavailable, codes.Aborted, codes.NotFound),
}
Recommendation: Lower 2. MEDIUM:
|
There was a problem hiding this comment.
Pull request overview
This WIP PR introduces a Bor gRPC client implementation (including auth token support) and updates Heimdall’s Bor integration to use gRPC-capable APIs, alongside extensive test/config updates to validate and support the new transport.
Changes:
- Add Bor gRPC client features (bearer token auth, batch block info APIs, proto↔eth header conversion) and expand unit tests around these behaviors.
- Update Bor keeper/helper call paths and mocks to pass
context.Contextinto Bor author lookups and support gRPC dispatch paths. - Add config surface (
bor_grpc_token) and introduce a background HTTP↔gRPC hash parity check during init to detect incompatible Bor versions.
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 21 comments.
Show a summary per file
| File | Description |
|---|---|
| x/bor/keeper/side_msg_server_test.go | Update mock expectations for new GetBorChainBlockAuthor(ctx, ...) signature. |
| x/bor/keeper/keeper_test.go | Adjust tests for new contract-caller signature and seed producer storage. |
| x/bor/keeper/keeper.go | Pass ctx into GetBorChainBlockAuthor calls. |
| x/bor/keeper/grpc_query_test.go | Update query tests for new mock signature / seed producer setup. |
| x/bor/grpc/query.go | Implement new gRPC query methods (author/TD/batch) and proto header conversion helpers. |
| x/bor/grpc/query_test.go | Add/extend tests for header conversion and new gRPC query APIs. |
| x/bor/grpc/client.go | Add token-aware transport resolution (http/https/unix/no-scheme), interceptors, and bearer credentials. |
| x/bor/grpc/client_test.go | Expand unit tests for transport resolution, token behavior, dial timeout, and retry interceptors. |
| helper/call.go | Add gRPC dispatch for Bor calls and implement HTTP batch helpers (headers/TD/authors). |
| helper/config.go | Add bor_grpc_token config + background HTTP↔gRPC hash parity check logic. |
| helper/config_test.go | Add tests for nil-client parity dispatcher and mismatch streak state machine; verify flags registration. |
| helper/tx_test.go | Disable t.Parallel() in tests that mutate package-level globals. |
| helper/toml.go / helper/toml_test.go | Add bor_grpc_token to config template + tests. |
| packaging/templates/config/*/app.toml | Add bor_grpc_token to packaged templates. |
| helper/call_grpc_author_test.go | Add unit tests for gRPC author retrieval behavior in helper dispatcher. |
| helper/call_dispatcher_test.go | Add unit tests for gRPC dispatch error paths when gRPC client is nil. |
| helper/call_batch_test.go | Add unit tests for HTTP batch element layout/collation logic. |
| helper/parity_check_test.go | Add unit tests for parity check core loop and edge cases. |
| helper/grpc_vs_http_bench_test.go | Add bench-only comparison harness for HTTP vs gRPC against devnet. |
| helper/mocks/i_contract_caller.go | Regenerate mocks for updated GetBorChainBlockAuthor(ctx, blockNum) signature. |
| app/abci_test.go | Update tests for new contract-caller signature and msg construction patterns. |
| go.mod / go.sum | Bump Go version and update dependencies (polyproto/bor/otel/grpc/etc.). |
| .golangci.yml | Align golangci-lint Go version with module Go version. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (77.5%) is below the target coverage (90.0%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## develop #576 +/- ##
=========================================
+ Coverage 41.6% 43.3% +1.6%
=========================================
Files 172 172
Lines 17500 17772 +272
=========================================
+ Hits 7294 7696 +402
+ Misses 9126 8948 -178
- Partials 1080 1128 +48
... and 2 files with indirect coverage changes
🚀 New features to boost your workflow:
|
|
@claude addressed all your points. |
|
Claude finished @marcello33's task in 3m 10s —— View job Re-Review: gRPC Implementation
All major issues from the first review have been addressed. Here's the verification: Verified Fixes
Test coverage for all fixes is solid — inner nil tests ( One Remaining IssueLOW:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@claude addressed your points from the previous run. |
|
Claude finished @marcello33's task in 1m 54s —— View job Re-Review: gRPC Implementation (Round 3)
All issues from previous review rounds have been addressed. Here's the full verification: Verified Fixes (latest commit
|
| Issue | Status | Location |
|---|---|---|
| Receipt converter uses safe wrappers | Fixed | query.go:227-228,233 — protoH256ToHash/protoH160ToAddress |
| GetAuthor uses safe H160 wrapper | Fixed | query.go:142 — protoH160ToAddress(res.Author) |
| GetBlockInfoInBatch uses safe wrappers | Fixed | query.go:213 — protoH160ToAddress(b.Author) |
| HeaderByNumber nil-from-malformed-header | Fixed | query.go:65-68 — checks h == nil, returns ethereum.NotFound |
| BlockByNumber nil-from-malformed-header | Fixed | query.go:87-90 — checks header == nil, returns ethereum.NotFound |
| GetBlockInfoInBatch nil-from-malformed-header | Fixed | query.go:205-208 — checks h == nil, breaks |
| res == nil guards (GetTdByHash/GetTdByNumber/GetBlockInfoInBatch) | Fixed | query.go:153,167,191 |
| fatalFunc now logs keysAndValues | Fixed | config.go:601-603 — Logger.Error(msg, keysAndValues...) + os.Exit(1) |
| Range validation in GetBorChainBlockInfoInBatch | Fixed | call.go:579-581 — shared start < 0 || end < 0 || end < start guard |
| Duplicate "error" log keys | Fixed | call.go:658 — "headerErr" / "tdErr" |
| IPv6 ServerName | Fixed | client.go:104 — u.Hostname() instead of strings.Split(addr, ":")[0] |
Two Remaining Nits (non-blocking)
1. GetAuthor missing outer res == nil guard — query.go:135
if res.Author == nil {Every sibling method now has if res == nil || ... before accessing response fields. GetAuthor skips the outer nil check. Not a real crash vector (standard gRPC stubs always return non-nil on success), but breaks the defensive pattern this PR established everywhere else. One-line fix: if res == nil || res.Author == nil {.
2. GetAuthor returns fmt.Errorf instead of ethereum.NotFound — query.go:136
return nil, fmt.Errorf("bor grpc GetAuthor: nil author")All sibling methods return ethereum.NotFound for the "response present, payload nil" case. The dispatcher at helper/call.go:761-763 has a dead author == nil guard that would return ethereum.NotFound, but the gRPC path never returns (nil, nil) — it returns (nil, error) with a non-NotFound error. No behavioral impact on current callers since they only check err != nil, but a future errors.Is(err, ethereum.NotFound) caller would diverge between transports.
Summary
The PR is in excellent shape. All security-relevant findings (inner-pointer nil panics, malformed header propagation, receipt converter, retry config, IPv6 SNI, transport safety) have been thoroughly addressed across the three review rounds. The two remaining items are low-severity consistency nits — fine to fix now or defer.
| Branch: mardizzone/grpc_improv | View job
|
@claude addressed your latest comments. Review. |
|
Claude finished @marcello33's task in 1m 10s —— View job Re-Review: gRPC Implementation (Round 4)
All issues from previous review rounds have been addressed. Here's the full verification of commit Verified Fixes (latest commit
|
| Issue | Status | Location |
|---|---|---|
GetAuthor outer res == nil guard |
Fixed | query.go:145 — if res == nil || res.Author == nil |
GetAuthor returns ethereum.NotFound |
Fixed | query.go:146 — return nil, ethereum.NotFound with clear comment explaining the convention match |
Block number validation in HeaderByNumber |
Fixed | query.go:69-71 — if blockID >= 0 && h.Number.Int64() != blockID returns descriptive error |
Block number validation in BlockByNumber |
Fixed | query.go:94-97 — same guard with matching comment |
GetBlockInfoInBatch nil-author break |
Fixed | query.go:219 — if b.Author == nil && b.Header.Number != 0 { break } with comment explaining HTTP parity |
| Test updates for nil-author break | Fixed | query_test.go:907,978 — tests now supply non-nil Author for non-genesis blocks, correctly exercising the new break logic |
No New Issues Found
The latest commit is clean. The block number validation guards correctly use blockID >= 0 to skip sentinel values (negative block numbers like -1 for latest), the nil-author break matches the HTTP collateBorBatchResults semantics, and the GetAuthor refactor aligns with the convention established across all sibling methods.
Overall Assessment
This PR is ready. Across four review rounds, all security findings (inner-pointer nil panics, malformed header propagation, receipt converter safety, retry config, IPv6 SNI, transport security, block number validation, cross-transport parity) have been thoroughly addressed. The code is well-structured, defensively coded, and has excellent test coverage.
The only remaining item is the pre-existing BorGRPCFlag *bool refactor (acknowledged as out of scope for this PR) and pinning polyproto/bor to tagged releases before merge.
| Branch: mardizzone/grpc_improv | View job
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|



Summary
In this PR, we implement the client-side half of the heimdall↔bor gRPC migration. Every bor-facing JSON-RPC now has a gRPC path, selectable per-node via
bor_grpc_flag. The HTTP path remains the default, fully supported, and untouched in behavior (besides some additional checks).Results
GetBorChainBlockInfoInBatchwith 77% latency reduction — the call used in the consensus-criticalExtendVoteHandlerpath.Latency (ns per op)
Allocations (bytes and count per op)
Ambient system-level resource usage (per phase, 5 s interval across 1000 blocks)
CPU samples during bench window (pprof, 120 s each, aligned to bench run)
Heimdall CPU is ~17% higher on gRPC — consistent with the client doing proto decode per call instead of JSON-RPC decode.
Details
GetAuthor,GetTdByHash,GetTdByNumber,GetBlockInfoInBatch— matching bor PR #2194's server-side handlers.Headerround-trip:protoHeaderToEthHeadernow rebuildsethTypes.Headerfields with defensive bounds (see this PR).ContractCaller.BorChainGrpcClientis nowBorGRPCClienterinterface (concrete*grpc.BorGRPCClientstill satisfies it); enables injectable fakes in tests without dialing a real bor.GetBorChainBlockAuthorsignature change — now takesctx context.Contextas first argument, propagated through every caller / mock / test. Deterministic timeout propagation from ABCI handlers.ethTypes.Header.Hash(), andlog.Fatals on three consecutive mismatches (guards against running with a stale bor that doesn't populate the full proto header).GetRootHashresponse validation: both HTTP and gRPC transports share the checks.MaxBlockInfoBatchSize = 10000exported fromx/bor/grpcand consumed byhelper.GetBorChainBlockInfoInBatch, preventingend-start+1int64 overflow and unbounded allocations on both transports from one source of truth.--bor_grpc_tokenCLI flag fully wired throughUpdateWithFlags(alongside--bor_grpc_url/--bor_grpc_flag).What this enables
GetBorChainBlockInfoInBatchreplaces the 3×N JSON-RPC batch (eth_getHeaderByNumber× N,eth_getTdByNumber× N,bor_getAuthor× N). 4.4× faster, 66.8% fewer allocations on the devnet benchmark for a 90-block batchGetRootHashcache properly shared: bor-side fix ensures the LRU persists across gRPC calls. Was 2.1× slower before this (now at parity with HTTP).GetBlockInfoInBatchHTTP timeouts per hour ("failed to get block batch info: Post \"http://localhost:8545\": context deadline exceeded"during milestone proposition generation). This PR is expected to eliminate that timeout class at the root.bor_grpc_flag.Enabling it
gRPC is opt-in. Default config has
bor_grpc_flag = "false"— no behavior change.Prerequisites
[grpc]config section). Heimdall refuses to run withbor_grpc_flag=trueagainst a bor that doesn't serve the expanded header message (the parity check catches it at startup with a Warning / Fatal).Step 1: Bor
On the bor node, enable
[grpc]inconfig.toml(see bor PR description). Default is loopback127.0.0.1:3131.Step 2: Heimdall
Edit
~/.heimdalld/config/app.toml:Restart heimdall. Within ~5–30 seconds after startup the parity check emits one of:
Bor gRPC hash parity check passed— proceed normally.Bor gRPC hash mismatch with HTTP for the same block — counting toward mismatch streak before fatal— followed byFATALon the 3rd consecutive mismatch, typically meaning a bor upgrade is needed.Bor gRPC hash parity check gave up after retries— advisory only; means one transport was unreachable during the check window.Security
bor_grpc_url = "http://remote:3131"with a non-emptybor_grpc_token, heimdall's client refuses to dial rather than transmit the token on the wire. Either usehttps://for remote or leave the token empty on trusted internal networks.bor_grpc_urlstarts withhttps://, heimdall setsRequireTransportSecurity=trueon the per-RPC credentials. bearerToken will not leak if the server cert negotiation fails.Tests
GetBlockInfoInBatch4.4× faster on gRPC, 50–67% allocation reduction across all methods,GetRootHashregression closed via the cached API.Dependencies
[grpc]section). Neither side is functional alone.Headerwith Cancun fields (see this PR).[grpc]port, threadcl_bor_grpc_flag+el_grpc_urlthrough the CL launcher template) if you want to run the end-to-end bench on a devnet. The changes are here.Out of scope
Bloom/Logsnot decoded on the gRPC path — no heimdall consumer reads them today. Small follow-up when needed.BorGRPCFlagbool merge is unconditional-overwrite (same behavior as every bool inCustomConfig.Merge). A proper fix requires switching to*boolorviper.IsSet, a broader config-overlay redesign tracked separately.