Skip to content

feat: add grpc socket and flattn tx batches to allow for lower allocations#3297

Merged
tac0turtle merged 5 commits intomainfrom
marko/flatten_grpc
Apr 29, 2026
Merged

feat: add grpc socket and flattn tx batches to allow for lower allocations#3297
tac0turtle merged 5 commits intomainfrom
marko/flatten_grpc

Conversation

@tac0turtle
Copy link
Copy Markdown
Contributor

@tac0turtle tac0turtle commented Apr 28, 2026

Overview

Flatten tx batch to avoid allocations and add in a unix socket over grpc to avoid tcp overhead

Summary by CodeRabbit

  • New Features

    • Unix domain socket support for gRPC execution endpoints
    • New execution RPCs: GetExecutionInfo and FilterTxs (per‑tx filter statuses)
    • Contiguous transaction batching for more efficient encoding
  • Breaking Changes

    • gRPC payloads replace per‑tx repeated fields with a tx_batch model
    • InitChain response no longer includes the max_bytes field
  • Documentation

    • Updated gRPC docs and examples for TCP and unix socket URLs
  • Tests

    • Added tests for unix-socket transport, batching, and filtering behavior

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3007d064-7eab-46ea-8083-a5ae010b0dd5

📥 Commits

Reviewing files that changed from the base of the PR and between e39161d and bd2bf19.

📒 Files selected for processing (1)
  • CHANGELOG.md

📝 Walkthrough

Walkthrough

Adds Unix domain socket transport for the gRPC execution endpoint and replaces per-tx repeated txs fields with a contiguous TxBatch protobuf. Implements encode/decode utilities, updates client/server logic and CLI docs, adjusts generated client types, and expands tests across batching and unix-socket flows.

Changes

Cohort / File(s) Summary
Documentation
apps/grpc/README.md, execution/grpc/README.md, CHANGELOG.md
Docs updated to document unix:/// socket URIs, update examples and flag descriptions, remove "remote" phrasing, and note breaking change from txstx_batch.
CLI
apps/grpc/cmd/run.go
Flag help text updated to mention Unix socket URLs; minor comment/return simplification.
Protobuf
proto/evnode/v1/execution.proto
Add TxBatch { bytes data; repeated uint32 tx_sizes }; replace/reserve legacy txs fields and add tx_batch fields for GetTxsResponse, ExecuteTxsRequest, FilterTxsRequest.
Go Client
execution/grpc/client.go, execution/grpc/client_test.go, execution/grpc/go.mod
NewClient now returns (*Client, error) and supports unix:// targets by constructing an h2c HTTP/2 transport over a unix dialer; request/response paths now encode/decode TxBatch; tests add unix-socket coverage and FilterTxs validation; go.mod adds local replace for development.
Go Server
execution/grpc/server.go, execution/grpc/server_test.go
RPC handlers switched to use TxBatch (encode on GetTxs; decode on ExecuteTxs/FilterTxs); decode/encode errors map to proper connect codes; tests updated/extended including malformed-batch cases and FilterTxs suite.
TxBatch Impl & Tests
execution/grpc/tx_batch.go, execution/grpc/tx_batch_test.go
New encode/decode utilities that serialize a contiguous data buffer with per-tx sizes, validate size limits and invariants; tests cover round-trip, overlapping-capacity avoidance, and malformed inputs.
Unix Socket Support & Tests
execution/grpc/unix.go, execution/grpc/unix_test.go
Add ListenUnix and ListenAndServeUnix with stale-socket cleanup and validation; tests ensure non-socket path rejection and stale-socket reuse.
Generated Client Types (Rust)
client/crates/types/src/proto/...
Generated types/services updated to include TxBatch, new execution RPCs/messages (GetExecutionInfo, FilterTxs), DA/P2P/raft-related DTOs; InitChainResponse max_bytes removed.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Caller
    participant Client as gRPC Client
    participant H2 as HTTP/2 (h2c)
    participant Unix as Unix Socket
    participant Server as gRPC Server
    participant Exec as Executor

    Caller->>Client: ExecuteTxs(txs [][]byte)
    Client->>Client: encodeTxBatch(txs)
    alt unix:// target
        Client->>H2: create h2c transport with unix dialer
        H2->>Unix: dial socket path
    else tcp/http target
        Client->>H2: create h2c transport over TCP
    end
    Client->>Server: Send ExecuteTxsRequest{tx_batch} (over HTTP/2)
    Server->>Server: decodeTxBatch(req.TxBatch)
    Server->>Exec: Execute transactions
    Exec-->>Server: Result
    Server-->>Client: ExecuteTxsResponse
    Client-->>Caller: Return result
Loading
sequenceDiagram
    participant Client as gRPC Client
    participant Encoder as TxBatch Encoder
    participant Server as gRPC Server
    participant Decoder as TxBatch Decoder

    Client->>Encoder: encodeTxBatch(txs)
    Encoder-->>Client: TxBatch{data, tx_sizes}
    Client->>Server: Send RPC with TxBatch
    Server->>Decoder: decodeTxBatch(TxBatch)
    Decoder-->>Server: [][]byte txs
    Server->>Server: Process txs (Filter/Execute)
    Server-->>Client: Response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • julienrbrt
  • chatton
  • alpe

Poem

🐰 In my burrow I stitch bytes into one long patch,

unix doors open where local hops catch,
txs bundled snug in a cozy little batch,
gRPC hums softly — no network to catch,
I nibble the code and hop off to dispatch ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main objectives: adding gRPC Unix socket support and flattening transaction batches for reduced allocations.
Description check ✅ Passed The description provides a clear overview of the two key changes (flattening tx batches and Unix socket support) but lacks specific implementation details and context.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch marko/flatten_grpc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 28, 2026

Claude finished @tac0turtle's task in 2m 4s —— View job


Code Review: feat: add grpc socket and flattn tx batches

Tasks

  • Gather context and understand the PR
  • Read changed files and analyze code
  • Provide comprehensive review feedback

Overview

This PR adds two performance improvements to the gRPC execution layer: Unix domain socket transport to avoid TCP overhead when the executor runs on the same machine, and a contiguous TxBatch encoding that replaces repeated protobuf fields with a single data buffer + sizes array to reduce allocations. Overall the implementation is well-structured and clean. Below are findings across the review dimensions.


🔴 Issues

1. decodeTxBatch(nil) silently treats missing tx_batch as zero transactions

execution/grpc/tx_batch.go:44-47

func decodeTxBatch(batch *pb.TxBatch) ([][]byte, error) {
    if batch == nil {
        return nil, nil   // ← silent empty result
    }

When an older or buggy client omits tx_batch entirely (proto default is nil), ExecuteTxs and FilterTxs will proceed with an empty transaction list rather than rejecting the request. TestDecodeTxBatchNil documents this as intentional, but it hides protocol mismatches. The PR is a breaking change, so unupgraded callers sending nil should get a clear error, not silent no-op execution. Consider returning fmt.Errorf("tx_batch is required") for nil, and representing a legitimate empty batch as &pb.TxBatch{}. Fix this →

2. ExecuteTxsResponse.max_bytes is dead code

proto/evnode/v1/execution.proto:94

message ExecuteTxsResponse {
  bytes updated_state_root = 1;
  uint64 max_bytes = 2;   // ← never set by server, never read by client
}

The server (server.go:126-128) never populates max_bytes, and the client (client.go:172) only reads UpdatedStateRoot. The PR description notes "InitChain response no longer includes max_bytes" but ExecuteTxsResponse still has it. This field should either be removed with a reserved marker, or properly implemented. Fix this →

3. FilterTxs response length never validated against request length

execution/grpc/server.go:180

result, err := s.executor.FilterTxs(ctx, txs, req.Msg.MaxBytes, req.Msg.MaxGas, ...)
// ↑ no check that len(result) == len(txs)

If the executor returns fewer statuses than transactions (e.g., a buggy implementation), the client will silently get a truncated status list. Add a post-call assertion: if len(result) != len(txs) { return nil, connect.NewError(connect.CodeInternal, ...) }. Fix this →


🟡 Warnings

4. maxInt calculation in encodeTxBatch is obscure

execution/grpc/tx_batch.go:18

maxInt := uint64(int(^uint(0) >> 1))

This is correct but hard to read. The equivalent uint64(math.MaxInt) (from "math") expresses the intent clearly with no risk of misreading the bit-shift. Fix this →

5. TestServer_ExecuteTxs "success" case doesn't verify decoded txs reach the executor

execution/grpc/server_test.go:215

mockFunc: func(ctx context.Context, t [][]byte, ...) ([]byte, error) {
    return expectedStateRoot, nil  // ← never inspects `t`
},

The mock never checks that t equals the original txs. If decodeTxBatch silently dropped or reordered transactions the test would still pass. TestClient_ExecuteTxs in client_test.go does check this correctly — the same rigour should apply to the server-level test.

6. TestServer_FilterTxs only checks response length, not enum values

execution/grpc/server_test.go:473

if len(resp.Msg.Statuses) != len(expectedStatuses) {  // ← length only

A wrong enum mapping (e.g., FilterOKFilterRemove) would go undetected. Compare element-by-element: if resp.Msg.Statuses[i] != pb.FilterStatus(expectedStatuses[i]).

7. ListenAndServeUnix is not context-aware

execution/grpc/unix.go:39

return http.Serve(listener, NewExecutorServiceHandler(executor, opts...))

Callers must close the listener externally to stop the server. Accepting a context.Context and deriving shutdown from it (via http.Server.Shutdown) would be idiomatic and more composable. Not a blocker for this PR, but worth a follow-up.


🟢 Positive Observations

decodeTxBatch correctly uses three-index slices: batch.Data[offset:end:end] (tx_batch.go:70) caps capacity so mutations to one decoded slice can't corrupt adjacent ones. This is verified by TestEncodeDecodeTxBatch and is the right pattern.

removeStaleUnixSocket is safely defensive: it refuses to remove non-socket files (unix.go:62-64), protecting against accidental removal of regular files at the configured path. The stale-socket cleanup logic is correct and well-tested.

clientTransportForTarget cleanly separates transports: client.go:66-76 delegates to newUnixHTTP2Client or newHTTP2Client based purely on the URL prefix — clean, testable, no hidden branching in callers.

Field reservations in the proto are correct: All removed txs fields use both reserved N; and reserved "txs"; which prevents both field-number and field-name reuse — correct protobuf migration practice.

Error wrapping is consistent: All paths use fmt.Errorf("...: %w", err), enabling errors.Is/errors.As unwrapping throughout.

encodeTxBatch pre-allocates the full buffer: make([]byte, 0, int(total)) before the append loop avoids reallocations — exactly the kind of allocation reduction this PR aims for.


Summary

Three actionable issues worth addressing in follow-up:

  1. decodeTxBatch(nil) should return an error for missing batches (not silently empty)
  2. max_bytes in ExecuteTxsResponse is dead and should be reserved-out
  3. FilterTxs server should validate that response length matches request length

The unix socket implementation and TxBatch encoding are sound. Test coverage is adequate but the ExecuteTxs and FilterTxs server-side test assertions could be tightened.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 28, 2026

The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedApr 29, 2026, 12:07 PM

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

❌ Patch coverage is 72.22222% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.71%. Comparing base (fdc79ad) to head (bd2bf19).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
execution/grpc/unix.go 35.48% 17 Missing and 3 partials ⚠️
execution/grpc/tx_batch.go 83.33% 4 Missing and 3 partials ⚠️
execution/grpc/client.go 85.36% 3 Missing and 3 partials ⚠️
execution/grpc/server.go 83.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3297      +/-   ##
==========================================
+ Coverage   62.50%   62.71%   +0.21%     
==========================================
  Files         122      124       +2     
  Lines       13131    13248     +117     
==========================================
+ Hits         8207     8309     +102     
- Misses       4032     4037       +5     
- Partials      892      902      +10     
Flag Coverage Δ
combined 62.71% <72.22%> (+0.21%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tac0turtle tac0turtle marked this pull request as ready for review April 28, 2026 15:31
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
proto/evnode/v1/execution.proto (1)

63-67: Clarify precedence when both txs and tx_batch are populated.

Please document a single authoritative rule (e.g., "tx_batch takes precedence; txs is legacy fallback") to avoid cross-client ambiguity during migration.

Also applies to: 84-85, 142-143

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@proto/evnode/v1/execution.proto` around lines 63 - 67, The proto comments for
the repeated bytes field txs and the TxBatch field tx_batch are ambiguous about
which to use when both are populated; update the comments for txs and tx_batch
(including the other occurrences at the ranges noted) to state a single
authoritative rule: tx_batch takes precedence and txs is a legacy fallback
(i.e., when tx_batch is present, implementations MUST ignore txs; when tx_batch
is absent, clients MAY use txs for backward compatibility). Ensure the same
wording is applied to every message where txs and tx_batch appear to avoid
cross-client ambiguity.
execution/grpc/unix.go (1)

33-47: Consider documenting that http.Serve blocks until error.

The function works correctly, but callers should understand this blocks. The defer order is correct (LIFO): socket cleanup runs after listener close.

Minor note: ignoring errors from listener.Close() and removeStaleUnixSocket in defers is acceptable for cleanup, but logging could help debugging shutdown issues.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/grpc/unix.go` around lines 33 - 47, Update the ListenAndServeUnix
doc comment to explicitly state that http.Serve blocks until it returns an error
and that the deferred listener.Close() and removeStaleUnixSocket() run when
Serve returns (LIFO order); mention callers must expect blocking behavior and
handle cancellation accordingly. While optional, add a brief note that errors
from listener.Close() and removeStaleUnixSocket() are currently ignored in the
defers and could be logged for shutdown diagnostics; reference
ListenAndServeUnix, http.Serve, listener.Close, removeStaleUnixSocket, and
NewExecutorServiceHandler when making this documentation change.
execution/grpc/client.go (1)

50-64: Consider validating socket path at creation time.

The empty socket path check inside DialTLSContext (line 56-58) will only trigger on the first dial attempt rather than at client creation. Since clientTransportForTarget already extracts the path, validation could happen there to fail fast.

That said, this is defensive coding within the closure, and the path extraction in unixSocketPath (line 74-79) already ensures a non-empty path when the prefix is present. The check here guards against misuse if newUnixHTTP2Client("") is called directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/grpc/client.go` around lines 50 - 64, The client builder
newUnixHTTP2Client currently defers an empty-socket check until DialTLSContext;
change it to validate socketPath up-front and return an error instead of
creating a client for an empty path. Update newUnixHTTP2Client signature to
return (*http.Client, error), check if socketPath == "" at the start and return
a clear error, and then construct the http.Client as before. Propagate the
change to callers such as clientTransportForTarget (which calls
newUnixHTTP2Client) so they handle the error and fail fast when unixSocketPath
extraction yields an empty string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/grpc/README.md`:
- Line 3: Update the README intro sentence to use the correct indefinite
article: replace the phrase "a Evolve node" with "an Evolve node" in the README
content so the sentence reads "This application runs an Evolve node with a
single sequencer..." (look for the README sentence containing "a Evolve node" to
make the change).

In `@execution/grpc/server.go`:
- Around line 85-87: GetTxsResponse currently only sets TxBatch and drops the
legacy txs field; update the server handler that returns GetTxsResponse (the
GetTxs implementation in grpc/server.go) to also populate the legacy txs field
with an equivalent flattened slice of transactions derived from txBatch (or a
converted form matching the old txs element type) so older clients continue to
see the expected list; ensure both fields are kept in sync (construct txs from
txBatch before returning) and preserve existing types/names used in
GetTxsResponse, tx_batch, and txs.

---

Nitpick comments:
In `@execution/grpc/client.go`:
- Around line 50-64: The client builder newUnixHTTP2Client currently defers an
empty-socket check until DialTLSContext; change it to validate socketPath
up-front and return an error instead of creating a client for an empty path.
Update newUnixHTTP2Client signature to return (*http.Client, error), check if
socketPath == "" at the start and return a clear error, and then construct the
http.Client as before. Propagate the change to callers such as
clientTransportForTarget (which calls newUnixHTTP2Client) so they handle the
error and fail fast when unixSocketPath extraction yields an empty string.

In `@execution/grpc/unix.go`:
- Around line 33-47: Update the ListenAndServeUnix doc comment to explicitly
state that http.Serve blocks until it returns an error and that the deferred
listener.Close() and removeStaleUnixSocket() run when Serve returns (LIFO
order); mention callers must expect blocking behavior and handle cancellation
accordingly. While optional, add a brief note that errors from listener.Close()
and removeStaleUnixSocket() are currently ignored in the defers and could be
logged for shutdown diagnostics; reference ListenAndServeUnix, http.Serve,
listener.Close, removeStaleUnixSocket, and NewExecutorServiceHandler when making
this documentation change.

In `@proto/evnode/v1/execution.proto`:
- Around line 63-67: The proto comments for the repeated bytes field txs and the
TxBatch field tx_batch are ambiguous about which to use when both are populated;
update the comments for txs and tx_batch (including the other occurrences at the
ranges noted) to state a single authoritative rule: tx_batch takes precedence
and txs is a legacy fallback (i.e., when tx_batch is present, implementations
MUST ignore txs; when tx_batch is absent, clients MAY use txs for backward
compatibility). Ensure the same wording is applied to every message where txs
and tx_batch appear to avoid cross-client ambiguity.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: acfb26c3-ba2a-423a-832a-163fd34eb7d3

📥 Commits

Reviewing files that changed from the base of the PR and between 389e904 and 58a3002.

⛔ Files ignored due to path filters (1)
  • types/pb/evnode/v1/execution.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (13)
  • apps/grpc/README.md
  • apps/grpc/cmd/run.go
  • execution/grpc/README.md
  • execution/grpc/client.go
  • execution/grpc/client_test.go
  • execution/grpc/go.mod
  • execution/grpc/server.go
  • execution/grpc/server_test.go
  • execution/grpc/tx_batch.go
  • execution/grpc/tx_batch_test.go
  • execution/grpc/unix.go
  • execution/grpc/unix_test.go
  • proto/evnode/v1/execution.proto

Comment thread apps/grpc/README.md Outdated
Comment thread execution/grpc/server.go
Comment on lines 85 to 87
return connect.NewResponse(&pb.GetTxsResponse{
Txs: txs,
TxBatch: txBatch,
}), nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

GetTxs currently drops legacy txs payload and can break older consumers.

GetTxsResponse still defines txs, but this response now only sets tx_batch. Any client not yet upgraded to decode tx_batch will observe an empty tx list.

🔁 Compatibility-safe response shape
 return connect.NewResponse(&pb.GetTxsResponse{
+    Txs:     txs, // legacy compatibility during migration
     TxBatch: txBatch,
 }), nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/grpc/server.go` around lines 85 - 87, GetTxsResponse currently only
sets TxBatch and drops the legacy txs field; update the server handler that
returns GetTxsResponse (the GetTxs implementation in grpc/server.go) to also
populate the legacy txs field with an equivalent flattened slice of transactions
derived from txBatch (or a converted form matching the old txs element type) so
older clients continue to see the expected list; ensure both fields are kept in
sync (construct txs from txBatch before returning) and preserve existing
types/names used in GetTxsResponse, tx_batch, and txs.

Copy link
Copy Markdown
Member

@julienrbrt julienrbrt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK.

We should rename the grpc service specifically (the service and the file imho), as it is only used there and because the execution/grpc is an adaption of the execution interface.
We could even move the proto definition under execution/grpc but that's more a nit that is annoying to do.

option go_package = "github.com/evstack/ev-node/types/pb/evnode/v1";

// ExecutorService defines the execution layer interface for EVNode
service ExecutorService {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
service ExecutionGRPCService {

Comment thread proto/evnode/v1/execution.proto Outdated
message FilterTxsRequest {
// All transactions (force-included + mempool)
// All transactions (force-included + mempool).
repeated bytes txs = 1;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's kill the ones we don't use.

Comment thread CHANGELOG.md Outdated
### Changes

- Optimization of mutex usage in cache for reaper [#3286](https://github.com/evstack/ev-node/pull/3286)
- Add Unix domain socket support for gRPC execution endpoints via `unix:///path/to/socket`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing PR name.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
client/crates/types/src/proto/evnode.v1.messages.rs (1)

304-335: ⚠️ Potential issue | 🟠 Major

Keep the legacy txs encoding alive during rollout.

Replacing txs with tx_batch here breaks mixed-version peers: older GetTxs consumers will see no transactions, and older ExecuteTxs callers will be parsed as empty batches by the new Go gRPC layer. Please make this change in the .proto source by carrying both encodings during migration, or gate the protocol version explicitly before deleting txs, then regenerate.

Based on learnings: "Maintain backward compatibility when modifying protobuf definitions".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/crates/types/src/proto/evnode.v1.messages.rs` around lines 304 - 335,
GetTxsResponse and ExecuteTxsRequest now only carry tx_batch which breaks
mixed-version peers; restore the legacy encoding by keeping the original txs
fields alongside tx_batch in the .proto (e.g., re-add the previous txs field
names/types) and regenerate the Rust types so GetTxsResponse and
ExecuteTxsRequest include both txs and tx_batch; alternatively gate removal
behind an explicit protocol version check so older peers still read/write the
legacy txs encoding—update the proto, regenerate code, and ensure the generated
structs (GetTxsResponse, ExecuteTxsRequest, TxBatch) preserve both encodings
during rollout.
🧹 Nitpick comments (2)
execution/grpc/server_test.go (2)

208-218: Assert the decoded tx slice in the happy path.

This case only checks the returned state root, so it still passes if Server.ExecuteTxs drops or reorders the decoded batch before calling the executor. Verifying the txs argument inside the mock would pin down the new TxBatch decode path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/grpc/server_test.go` around lines 208 - 218, The test’s happy-path
mock currently only checks the returned state root, so update the mockFunc used
in the "success" case to assert that the decoded transaction slice passed to the
executor matches the original txs from the test: inside mockFunc(ctx, t
[][]byte, ...) decode or compare t against the expected encoded tx bytes
produced by mustEncodeTxBatch (or the original txs variable) using
reflect.DeepEqual or your test comparator and return a non-nil error if they
differ; leave the successful return of expectedStateRoot when the slice matches.
This targets Server.ExecuteTxs / ExecuteTxsRequest / mockFunc to ensure the
TxBatch decode path preserves order and contents.

400-420: Tighten the FilterTxs assertions around both payload and status mapping.

This test currently validates batch length / limits on the request side and only len(resp.Msg.Statuses) on the response side. It would still pass if the decoded transactions were reordered or if the execution.FilterStatus → protobuf enum conversion returned the wrong values.

Also applies to: 473-475

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/grpc/server_test.go` around lines 400 - 420, The test's assertions
in the "success" case are too weak: it only checks request length/limits in
mockFunc and only len(resp.Msg.Statuses) on response, so reordering or incorrect
enum mapping could slip through; update the test to assert that the decoded tx
payloads passed into mockFunc equal the original tx bytes (use mustEncodeTxBatch
and the mockFunc txsIn parameter) and that each returned execution.FilterStatus
in expectedStatuses maps to the exact protobuf enum value in resp.Msg.Statuses
(compare element-by-element rather than only lengths), and apply the same
stronger checks to the second case around lines noted (ensure element-wise
comparison between expectedStatuses and resp.Msg.Statuses and verify the order
matches).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@execution/grpc/tx_batch.go`:
- Around line 44-52: The function decodeTxBatch should not treat a nil
*pb.TxBatch as an explicit empty batch; change the handling in decodeTxBatch so
that when batch == nil it returns a non-nil error (e.g., "missing tx_batch" or
similar) instead of (nil, nil), leaving the existing behavior for an actual
empty struct (&pb.TxBatch{}) intact; update callers such as ExecuteTxs /
FilterTxs (if they rely on nil meaning empty) to propagate or handle this error
accordingly so missing/omitted tx_batch from clients is rejected rather than
treated as zero transactions.

---

Duplicate comments:
In `@client/crates/types/src/proto/evnode.v1.messages.rs`:
- Around line 304-335: GetTxsResponse and ExecuteTxsRequest now only carry
tx_batch which breaks mixed-version peers; restore the legacy encoding by
keeping the original txs fields alongside tx_batch in the .proto (e.g., re-add
the previous txs field names/types) and regenerate the Rust types so
GetTxsResponse and ExecuteTxsRequest include both txs and tx_batch;
alternatively gate removal behind an explicit protocol version check so older
peers still read/write the legacy txs encoding—update the proto, regenerate
code, and ensure the generated structs (GetTxsResponse, ExecuteTxsRequest,
TxBatch) preserve both encodings during rollout.

---

Nitpick comments:
In `@execution/grpc/server_test.go`:
- Around line 208-218: The test’s happy-path mock currently only checks the
returned state root, so update the mockFunc used in the "success" case to assert
that the decoded transaction slice passed to the executor matches the original
txs from the test: inside mockFunc(ctx, t [][]byte, ...) decode or compare t
against the expected encoded tx bytes produced by mustEncodeTxBatch (or the
original txs variable) using reflect.DeepEqual or your test comparator and
return a non-nil error if they differ; leave the successful return of
expectedStateRoot when the slice matches. This targets Server.ExecuteTxs /
ExecuteTxsRequest / mockFunc to ensure the TxBatch decode path preserves order
and contents.
- Around line 400-420: The test's assertions in the "success" case are too weak:
it only checks request length/limits in mockFunc and only len(resp.Msg.Statuses)
on response, so reordering or incorrect enum mapping could slip through; update
the test to assert that the decoded tx payloads passed into mockFunc equal the
original tx bytes (use mustEncodeTxBatch and the mockFunc txsIn parameter) and
that each returned execution.FilterStatus in expectedStatuses maps to the exact
protobuf enum value in resp.Msg.Statuses (compare element-by-element rather than
only lengths), and apply the same stronger checks to the second case around
lines noted (ensure element-wise comparison between expectedStatuses and
resp.Msg.Statuses and verify the order matches).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 07fec0ff-4531-4cc9-9ab5-a9b357898657

📥 Commits

Reviewing files that changed from the base of the PR and between 58a3002 and e39161d.

⛔ Files ignored due to path filters (1)
  • types/pb/evnode/v1/execution.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (15)
  • CHANGELOG.md
  • apps/grpc/README.md
  • apps/grpc/cmd/run.go
  • buf.yaml
  • client/crates/types/src/proto/evnode.v1.messages.rs
  • client/crates/types/src/proto/evnode.v1.services.rs
  • execution/grpc/README.md
  • execution/grpc/client.go
  • execution/grpc/client_test.go
  • execution/grpc/server.go
  • execution/grpc/server_test.go
  • execution/grpc/tx_batch.go
  • execution/grpc/tx_batch_test.go
  • execution/grpc/unix.go
  • proto/evnode/v1/execution.proto
✅ Files skipped from review due to trivial changes (2)
  • CHANGELOG.md
  • buf.yaml
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/grpc/cmd/run.go
  • execution/grpc/tx_batch_test.go
  • proto/evnode/v1/execution.proto
  • execution/grpc/README.md
  • apps/grpc/README.md
  • execution/grpc/unix.go

Comment on lines +44 to +52
func decodeTxBatch(batch *pb.TxBatch) ([][]byte, error) {
if batch == nil {
return nil, nil
}
if len(batch.TxSizes) == 0 {
if len(batch.Data) != 0 {
return nil, fmt.Errorf("tx batch has %d data bytes but no tx sizes", len(batch.Data))
}
return nil, nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't treat a missing batch as an explicit empty batch.

Returning (nil, nil) for batch == nil makes every caller interpret an omitted tx_batch as “zero transactions”. That hides mixed-version/client bugs and lets ExecuteTxs / FilterTxs proceed with empty work instead of rejecting the request. An explicit empty batch is already representable as &pb.TxBatch{}.

🛠️ Proposed fix
 func decodeTxBatch(batch *pb.TxBatch) ([][]byte, error) {
 	if batch == nil {
-		return nil, nil
+		return nil, fmt.Errorf("tx_batch is required")
 	}
 	if len(batch.TxSizes) == 0 {
 		if len(batch.Data) != 0 {
 			return nil, fmt.Errorf("tx batch has %d data bytes but no tx sizes", len(batch.Data))

As per coding guidelines: "Validate all inputs from external sources in Go code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/grpc/tx_batch.go` around lines 44 - 52, The function decodeTxBatch
should not treat a nil *pb.TxBatch as an explicit empty batch; change the
handling in decodeTxBatch so that when batch == nil it returns a non-nil error
(e.g., "missing tx_batch" or similar) instead of (nil, nil), leaving the
existing behavior for an actual empty struct (&pb.TxBatch{}) intact; update
callers such as ExecuteTxs / FilterTxs (if they rely on nil meaning empty) to
propagate or handle this error accordingly so missing/omitted tx_batch from
clients is rejected rather than treated as zero transactions.

@tac0turtle tac0turtle merged commit 4063698 into main Apr 29, 2026
15 of 16 checks passed
@tac0turtle tac0turtle deleted the marko/flatten_grpc branch April 29, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants