Skip to content

Conversation

@chatton
Copy link
Contributor

@chatton chatton commented Jan 8, 2026

Overview

This PR follows the same pattern as the previous PR and creates a decorator around the eth client that injects tracing.

Sample grafana output with metrics enabled.

image image

@github-actions
Copy link
Contributor

github-actions bot commented Jan 8, 2026

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedJan 12, 2026, 8:29 AM

@claude
Copy link
Contributor

claude bot commented Jan 8, 2026

Claude finished @chatton's task in 1m 43s —— View job


Code Review Complete

I've reviewed this PR which adds OpenTelemetry tracing to Ethereum RPC client interactions. The implementation is generally well-structured and follows good practices. Below is my detailed feedback organized by area.


Architecture & Design ✅

Strengths:

  • Excellent use of decorator pattern: The tracedEthRPCClient wrapper cleanly separates tracing concerns from core RPC logic
  • Well-defined interfaces: The new EthRPCClient interface provides a clean abstraction point for both testing and tracing
  • Conditional tracing: The tracingEnabled flag allows runtime control over instrumentation overhead
  • Non-invasive integration: Tracing is added without modifying existing business logic

Minor Observations:

  • The interface extraction is clean, but consider documenting the intended use cases in godoc comments (see documentation section below)

Code Quality & Go Idioms ✅

Strengths:

  • Follows idiomatic Go patterns throughout
  • Proper use of context propagation for tracing
  • Clean separation between production code (eth_rpc_client.go, eth_rpc_tracing.go) and test code
  • No cyclic dependencies introduced

Issues:

  1. Missing nil check in HeaderByNumber - execution/evm/eth_rpc_tracing.go:28-34

    var blockNumber string
    if number == nil {
        blockNumber = "latest"
    } else {
        blockNumber = number.String()
    }

    The nil check is good, but big.Int.String() could theoretically panic if number is somehow a typed nil. Consider defensive programming:

    if number == nil || number.Cmp(big.NewInt(0)) < 0 {
        blockNumber = "latest"
    } else {
        blockNumber = number.String()
    }
  2. Potential for enhanced span naming - The span names "Eth.GetBlockByNumber" and "TxPool.GetTxs" are clear, but consider including the tracer name prefix for consistency:

    • Current: "Eth.GetBlockByNumber"
    • Suggestion: Consider if these should match the RPC method names exactly ("eth_getBlockByNumber") or keep the current semantic naming

Error Handling & Resilience ✅

Strengths:

  • Proper error recording with span.RecordError(err) and span.SetStatus(codes.Error, err.Error())
  • Errors are propagated correctly without suppression
  • Span lifecycle is managed properly with defer span.End()

Issues:

  1. Error message exposure - execution/evm/eth_rpc_tracing.go:47,73
    span.SetStatus(codes.Error, err.Error())
    Be cautious about including full error messages in span status descriptions, as they may be exported to external observability systems. Consider:
    • Ensuring no sensitive data (addresses, transaction details beyond hashes) leaks into error messages
    • The current implementation looks safe, but worth noting for future additions

Testing & Reliability ⭐️

Strengths:

  • Comprehensive test coverage: Tests cover success cases, error cases, and edge cases (nil block number, empty tx pool)
  • Good use of test helpers: setupTestEthRPCTracing and requireAttribute reduce duplication
  • Proper cleanup: Uses t.Cleanup for tracer provider shutdown
  • Mock implementation: Simple, focused mocks that don't over-complicate tests

Issues (from Gemini + my review):

  1. Incomplete attribute validation on error - execution/evm/eth_rpc_tracing_test.go:170-178

    Current code:

    // verify block header attributes NOT set on error
    attrs := span.Attributes()
    for _, attr := range attrs {
        key := string(attr.Key)
        require.NotEqual(t, "block_hash", key)
        require.NotEqual(t, "state_root", key)
        require.NotEqual(t, "gas_limit", key)
        require.NotEqual(t, "gas_used", key)
    }

    Missing: The test doesn't check for "timestamp" attribute, which IS set on success (line 56 of eth_rpc_tracing.go). This is incomplete validation.

    Better approach (as Gemini suggested):

    for _, attr := range attrs {
        switch string(attr.Key) {
        case "block_hash", "state_root", "gas_limit", "gas_used", "timestamp":
            t.Fatalf("unexpected attribute %q found on error span", attr.Key)
        }
    }

    This is clearer and complete.

  2. Verbose error attribute check - execution/evm/eth_rpc_tracing_test.go:273-277

    Current code:

    // verify tx_count NOT set on error
    attrs := span.Attributes()
    for _, attr := range attrs {
        require.NotEqual(t, "tx_count", string(attr.Key))
    }

    Better approach (as Gemini suggested):

    for _, attr := range attrs {
        if string(attr.Key) == "tx_count" {
            t.Fatalf("unexpected attribute 'tx_count' found on error span")
        }
    }

    This expresses intent more clearly and provides better failure messages.


Security 🔒

Strengths:

  • No credentials or secrets exposed in traces
  • Block hashes and transaction counts are safe to expose in telemetry
  • Proper use of context for cancellation propagation

Observations:

  • ✅ No PII or sensitive financial data in span attributes
  • ✅ Transaction hashes (if they were included) would be safe as they're public
  • ✅ State roots and block hashes are public blockchain data

Performance & Resource Efficiency ⚡

Strengths:

  • Tracing is opt-in via tracingEnabled flag - no overhead when disabled
  • Span attributes are set after successful operations, avoiding wasted work on errors
  • Minimal allocations in hot path

Minor Considerations:

  1. String conversion overhead - execution/evm/eth_rpc_tracing.go:33

    blockNumber = number.String()

    big.Int.String() allocates. For high-frequency tracing, consider if this matters. Likely negligible, but worth monitoring if this becomes a hot path.

  2. Attribute allocation - Setting multiple attributes individually (lines 51-57) is fine for readability. OpenTelemetry SDK handles this efficiently.


Documentation 📝

Issues:

  1. Missing godoc for exported interface - execution/evm/execution.go:145-152

    The new EthRPCClient interface is exported but lacks package-level documentation explaining:

    • When to use this interface vs calling ethclient directly
    • That it's designed for tracing/testing decoration
    • The contract around nil parameters (e.g., number *big.Int can be nil for "latest")

    Suggested addition:

    // EthRPCClient abstracts Ethereum JSON-RPC calls for tracing and testing.
    // This interface allows decorating RPC calls with observability (tracing) or
    // replacing the client with mocks in tests.
    //
    // Implementations must be safe for concurrent use.
    type EthRPCClient interface {
        // HeaderByNumber retrieves a block header by number.
        // Pass nil for number to get the latest block.
        HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
    
        // GetTxs retrieves pending transactions from the transaction pool.
        // Returns RLP-encoded transaction hex strings.
        GetTxs(ctx context.Context) ([]string, error)
    }
  2. Unexported function lacks context - execution/evm/eth_rpc_tracing.go:20-26

    The withTracingEthRPCClient function is well-named but could benefit from a comment explaining:

    • That it returns a decorator
    • That the tracer name is fixed ("ev-node/execution/eth-rpc")
    // withTracingEthRPCClient decorates an EthRPCClient with OpenTelemetry tracing.
    // All RPC calls made through the returned client will be recorded as spans under
    // the tracer name "ev-node/execution/eth-rpc".
    func withTracingEthRPCClient(inner EthRPCClient) EthRPCClient {

Integration & Consistency 🔧

Strengths:

  • Consistent with existing Engine API tracing pattern (I can see withTracingEngineRPCClient is used similarly in execution.go:241)
  • W3C trace context propagation is properly set up in the HTTP client (lines 199-202 of execution.go)
  • Tracer naming follows a clear convention: "ev-node/execution/eth-rpc"

Observations:

  • The change from ethclient.Client to EthRPCClient interface in EngineClient struct (line 159) is backwards compatible since the field is unexported
  • The conditional wrapping logic (lines 239-243 of execution.go) matches the Engine API pattern, ensuring consistency

Summary & Recommendations

Overall Assessment: This is a well-implemented addition that follows Go and OpenTelemetry best practices. The code is clean, testable, and production-ready with minor improvements needed.

Priority Fixes:

  1. 🔴 Fix test coverage gap - Add "timestamp" to the error case validation in eth_rpc_tracing_test.go:170-178 (Fix this →)
  2. 🟡 Improve test clarity - Refactor the tx_count error validation at line 273-277 for better readability (Fix this →)
  3. 🟡 Add godoc comments - Document the EthRPCClient interface in execution.go and the withTracingEthRPCClient function

Nice-to-haves:

  • Consider defensive nil checking in HeaderByNumber tracer
  • Monitor string conversion performance if this becomes a hot path

Testing: All modified lines are covered by tests (per Codecov), and the test cases are thorough. The suggested test improvements are about clarity and completeness, not coverage gaps.


@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @chatton, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the observability of Ethereum RPC interactions within the EVM execution engine by integrating OpenTelemetry tracing. It refactors the existing EngineClient to utilize a new EthRPCClient interface, allowing for a clean separation of concerns and the introduction of a tracing decorator. This change ensures that critical Ethereum API calls, such as fetching block headers and transaction pool content, are automatically instrumented with detailed tracing information, providing better insights into performance and potential issues.

Highlights

  • New EthRPCClient Interface: Introduced an EthRPCClient interface in execution.go to abstract Ethereum JSON-RPC calls, enabling easier mocking and decoration (e.g., for tracing).
  • Concrete EthRPCClient Implementation: Added eth_rpc_client.go which provides a concrete implementation of the EthRPCClient interface, wrapping the standard go-ethereum/ethclient.
  • OpenTelemetry Tracing for EthRPCClient: Implemented eth_rpc_tracing.go which contains tracedEthRPCClient, a decorator that wraps an EthRPCClient and adds OpenTelemetry spans for HeaderByNumber and GetTxs methods, including relevant attributes and error handling.
  • Integration into EngineClient: Modified EngineClient in execution.go to use the new EthRPCClient interface. The NewEngineExecutionClient constructor now conditionally wraps the ethclient with the tracing decorator if tracing is enabled.
  • Unit Tests for Tracing: Added eth_rpc_tracing_test.go with comprehensive unit tests for the tracedEthRPCClient, verifying span creation, attribute setting, and error reporting for both successful and failed RPC calls.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces OpenTelemetry tracing for Ethereum RPC client interactions. The changes are well-structured, using a decorator pattern to add tracing capabilities without modifying the core client logic. A new EthRPCClient interface is introduced, which improves modularity and testability. The accompanying tests are thorough and cover success and error cases for the new tracing functionality. My feedback includes a couple of suggestions to improve the clarity and completeness of the test assertions.

Comment on lines +172 to +178
for _, attr := range attrs {
key := string(attr.Key)
require.NotEqual(t, "block_hash", key)
require.NotEqual(t, "state_root", key)
require.NotEqual(t, "gas_limit", key)
require.NotEqual(t, "gas_used", key)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This loop to verify that block header attributes are not set on error is confusing and incomplete. While it correctly fails if an unexpected attribute is present, its logic is hard to follow. Additionally, it's missing a check for the timestamp attribute, which is set on success.

A clearer and more complete approach is to iterate through the attributes and explicitly fail if any of the success-only attributes are found.

for _, attr := range attrs {
		switch string(attr.Key) {
		case "block_hash", "state_root", "gas_limit", "gas_used", "timestamp":
			t.Fatalf("unexpected attribute %q found on error span", attr.Key)
		}
	}

Comment on lines +275 to +277
for _, attr := range attrs {
require.NotEqual(t, "tx_count", string(attr.Key))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This loop to verify that the tx_count attribute is not set on error works, but it can be written more clearly to express the intent of checking for a specific forbidden attribute.

for _, attr := range attrs {
		if string(attr.Key) == "tx_count" {
			t.Fatalf("unexpected attribute 'tx_count' found on error span")
		}
	}

@codecov
Copy link

codecov bot commented Jan 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 57.76%. Comparing base (aaae087) to head (32db6c8).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2960      +/-   ##
==========================================
- Coverage   57.88%   57.76%   -0.12%     
==========================================
  Files          97       97              
  Lines        9306     9306              
==========================================
- Hits         5387     5376      -11     
- Misses       3315     3326      +11     
  Partials      604      604              
Flag Coverage Δ
combined 57.76% <ø> (-0.12%) ⬇️

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.

Base automatically changed from cian/add-tracing-part-3 to main January 8, 2026 15:59
@chatton chatton marked this pull request as ready for review January 12, 2026 09:46
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