Skip to content
This repository was archived by the owner on May 30, 2025. It is now read-only.

Conversation

@gupadhyaya
Copy link
Member

@gupadhyaya gupadhyaya commented Apr 24, 2025

Overview

Summary by CodeRabbit

  • New Features
    • Introduced an Ethereum execution engine client that supports chain initialization, transaction retrieval, execution, and block finalization.
  • Tests
    • Added a comprehensive end-to-end test suite to validate transaction processing, state transitions, empty block handling, and chain synchronization.
  • Chores
    • Added a new module dependency file to manage project dependencies.
  • Refactor
    • Updated package names and adjusted file paths for improved organization.

@coderabbitai
Copy link

coderabbitai bot commented Apr 24, 2025

Walkthrough

This update introduces a new EngineClient implementation for interacting with an Ethereum execution engine via the Engine API and JSON-RPC, supporting chain initialization, transaction retrieval, execution, and forkchoice management. A comprehensive end-to-end test suite validates the client by building and syncing a chain using a Reth engine container with JWT authentication. The module dependencies are formalized in a new go.mod file, and the pure execution client is moved to a separate package. Minor adjustments are made to test utility paths for the pure client.

Changes

File(s) Change Summary
execution/op/execution.go Introduces EngineClient struct and methods for Ethereum execution engine interaction, including chain initialization, transaction management, execution, and forkchoice/finalization handling. Implements JWT-based authentication and utility functions.
execution/op/execution_test.go Adds a comprehensive end-to-end test suite for the EngineClient, including Reth engine container setup, chain building, transaction execution, state verification, and chain syncing. Includes utility functions for testing and JWT handling.
execution/op/go.mod Adds a new Go module file specifying dependencies, including Ethereum client libraries, JWT, test frameworks, and a forked Geth implementation.
execution/pure/execution.go Changes package declaration from execution to pure.
execution/pure/execution_test.go Changes package declaration from execution to pure and updates the Docker path constant.

Sequence Diagram(s)

sequenceDiagram
    participant Client as EngineClient
    participant Engine as Ethereum Engine API
    participant EthRPC as Ethereum JSON-RPC

    Client->>Engine: Authenticate (JWT)
    Client->>Engine: Set forkchoice (InitChain)
    Client->>EthRPC: Get block info
    Client->>EthRPC: Get pending/queued txs (GetTxs)
    Client->>Engine: Update forkchoice (ExecuteTxs)
    Client->>Engine: Request new payload ID
    Client->>Engine: Get execution payload
    Client->>Engine: Submit payload
    Engine-->>Client: Payload status
    Client->>Engine: Update forkchoice (finalization)
    Client->>EthRPC: Get updated block info
Loading

Possibly related PRs

  • rollkit/go-execution-evm#28: Refines JWT secret decoding and token generation functions, which are directly built upon in this PR.
  • rollkit/go-execution-evm#16: Improves integration test reliability and JWT handling for Reth engine container setup, overlapping with the new test suite and JWT utilities in this PR.
  • rollkit/go-execution-evm#10: Introduces a similar EngineAPIExecutionClient for Ethereum execution engine interaction, indicating related implementation efforts.

Suggested labels

enhancement, execution-api

Suggested reviewers

  • tuxcanfly
  • yarikbratashchuk
  • Manav-Aggarwal
  • MSevey

Poem

In burrows deep where blockchains hum,
The EngineClient’s work is never done.
With JWT and Reth in Docker’s den,
It runs the chain again and again.
Transactions hop, the tests all pass—
This bunny’s code is built to last!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@RollkitBot RollkitBot requested review from a team, tuxcanfly, tzdybal and yarikbratashchuk and removed request for a team April 24, 2025 13:22
@gupadhyaya gupadhyaya changed the title refactor the two execution clients (pure and op) to main branch chore: refactor the two execution clients (pure and op) to main branch Apr 24, 2025
Copy link

@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: 6

🔭 Outside diff range comments (1)
execution/pure/execution_test.go (1)

69-87: 🛠️ Refactor suggestion

Use the sub-test handle (tt) for assertions inside the sub-test

Within the “Build chain” sub-test most assertions still use the outer t handle.
If any of those assertions fail you will not get the nice sub-test name in the
output and – when the parent test is executed in parallel – you can even get
data races.

-       executionClient, err := NewPureEngineExecutionClient(
+       executionClient, err := NewPureEngineExecutionClient(
 ...
-       require.NoError(t, err)
+       require.NoError(tt, err)
 ...
-       require.NoError(t, err)
-       require.Equal(t, rollkitGenesisStateRoot, stateRoot)
-       require.NotZero(t, gasLimit)
+       require.NoError(tt, err)
+       require.Equal(tt, rollkitGenesisStateRoot, stateRoot)
+       require.NotZero(tt, gasLimit)

Do the same search-&-replace for the remaining assertions that run under the
sub-test (including the “Sync chain” sub-test).
This keeps the test output and failure attribution consistent.

🧹 Nitpick comments (8)
execution/pure/execution.go (2)

31-34: Fix stale type name in interface–conformance comment

The comment still refers to EngineAPIExecutionClient, but the actual type is now PureEngineClient. This can confuse future readers and automated lint tools.

-// Ensure EngineAPIExecutionClient implements the execution.Execute interface
-var _ execution.Executor = (*PureEngineClient)(nil)
+// Ensure PureEngineClient implements the execution.Executor interface
+var _ execution.Executor = (*PureEngineClient)(nil)

45-53: Doc-comment mismatch with constructor name

The constructor is called NewPureEngineExecutionClient, but its doc-comment says it creates an EngineAPIExecutionClient. Align them to avoid revive warnings and improve IDE hints.

-// NewPureEngineExecutionClient creates a new instance of EngineAPIExecutionClient
+// NewPureEngineExecutionClient creates a new PureEngineClient
execution/op/go.mod (2)

7-10: Double-check the replace directive for go-ethereum

Replacing github.com/ethereum/go-ethereum with the Optimism fork (op-geth) is fine if all packages in this repo expect the altered behaviour/extra RPCs the fork provides.
However, it also pins you to a fork‐specific release cadence and may break downstream users that import your module and expect vanilla geth APIs.

Action items

  1. Confirm that every internal consumer truly needs OP-specific code.
  2. If the fork is needed only in the new op client, consider adding it via go.work or a sub-module instead of a root-level replace, so the “pure” client can still compile against stock geth.

11-224: Run go mod tidy to trim 200+ stale indirect deps

The file lists ~210 indirect requirements; many (e.g. docker, k8s.io, aws-sdk-go-v2, etc.) do not appear in the code under execution/op.
Large, unused dep graphs increase go.sum, download time, and vulnerability surface.

go mod tidy -v

will keep only transitive deps actually needed after the next build/test and makes future scans faster.

execution/op/execution.go (2)

31-33: Update interface-conformance comment

The guard variable is correct, but the preceding comment mentions the old type name.

-// Ensure EngineAPIExecutionClient implements the execution.Execute interface
+// Ensure EngineClient implements the execution.Executor interface

45-53: Constructor doc-comment & function name diverge

The function is NewEngineExecutionClient, but the comment says NewPureEngineExecutionClient. Fix to silence revive and avoid confusion.

-// NewPureEngineExecutionClient creates a new instance of EngineAPIExecutionClient
+// NewEngineExecutionClient creates a new EngineClient
🧰 Tools
🪛 golangci-lint (1.64.8)

[warning] 45-45: exported: comment on exported function NewEngineExecutionClient should be of the form "NewEngineExecutionClient ..."

(revive)

execution/op/execution_test.go (2)

401-402: Global lastNonce is not concurrency-safe

lastNonce is a package-level mutable variable updated from multiple tests
(TestEngineExecution, TestSubmitTransaction). If the package is ever run
with go test -parallel or individual tests are executed concurrently (e.g.
via t.Parallel()), non-atomic increments will cause nonce reuse and
transaction rejection.

If you need shared state, protect it with a mutex or, simpler, make lastNonce
a local variable passed around.


38-68: Large amount of duplicated helper code between pure and op tests

Both test suites define identical helpers (createEthClient, checkLatestBlock,
generateJWTSecret, docker orchestration, tx utilities, …). Moving these into
a shared test-only package (e.g. execution/testsupport) would:

• Remove ~400 lines of duplication
• Ensure behaviour stays identical between clients
• Make future fixes (like the nonce or off-by-one issues) in one place.

No blocking issue, but worth addressing before the helpers diverge further.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcf087c and 2c9ac2b.

⛔ Files ignored due to path filters (2)
  • execution/op/go.sum is excluded by !**/*.sum
  • execution/pure/go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • execution/op/execution.go (1 hunks)
  • execution/op/execution_test.go (1 hunks)
  • execution/op/go.mod (1 hunks)
  • execution/pure/execution.go (1 hunks)
  • execution/pure/execution_test.go (2 hunks)
🧰 Additional context used
🪛 golangci-lint (1.64.8)
execution/op/execution.go

[high] 193-193: G115: integer overflow conversion int64 -> uint64

(gosec)


[warning] 45-45: exported: comment on exported function NewEngineExecutionClient should be of the form "NewEngineExecutionClient ..."

(revive)


290-290: func (*EngineClient).derivePrevRandao is unused

(unused)

🔇 Additional comments (2)
execution/op/execution.go (1)

290-292: derivePrevRandao now used – or remove

If you accept the earlier suggestion to wire derivePrevRandao, this method will no longer be “unused”.
Otherwise, delete it to keep the package warning-free.

🧰 Tools
🪛 golangci-lint (1.64.8)

290-290: func (*EngineClient).derivePrevRandao is unused

(unused)

execution/pure/execution_test.go (1)

34-36: Verify new DOCKER_PATH relative location

DOCKER_PATH moved two levels up ("../../docker").
If someone runs the test from another working directory (e.g. go test ./...
from repo root) filepath.Abs will resolve against that directory, not the
file’s directory, and the path may break.

Consider anchoring the path to the module root by using testing’s helper
filepath.Abs on t.TempDir() or by detecting the repo root via go list -m -f '{{.Dir}}' in init code.

Comment on lines +247 to +254
blockHash := payloadResult.ExecutionPayload.BlockHash
err = c.setFinal(ctx, blockHash, false)
if err != nil {
return nil, 0, err
}

return payloadResult.ExecutionPayload.StateRoot.Bytes(), payloadResult.ExecutionPayload.GasUsed, nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Return GasLimit, not GasUsed, for maxBytes

The sibling “pure” client returns ExecutionPayload.GasLimit.
Returning GasUsed here changes the meaning of maxBytes and may break anything consuming the execution.Executor contract.

-return payloadResult.ExecutionPayload.StateRoot.Bytes(), payloadResult.ExecutionPayload.GasUsed, nil
+return payloadResult.ExecutionPayload.StateRoot.Bytes(), payloadResult.ExecutionPayload.GasLimit, nil

Comment on lines +206 to +214
&engine.PayloadAttributes{
Timestamp: ts,
Random: prevBlockHash, //c.derivePrevRandao(height),
SuggestedFeeRecipient: c.feeRecipient,
Withdrawals: []*types.Withdrawal{},
BeaconRoot: &c.genesisHash,
Transactions: txsPayload, // force to use txsPayload
NoTxPool: true,
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use derivePrevRandao and avoid dead code

derivePrevRandao is currently unused, triggering golangci-lint.
You already compute prevBlockHash but reuse it as Random. Either delete the helper or use it here:

-			Random:                prevBlockHash, //c.derivePrevRandao(height),
+			Random:                c.derivePrevRandao(blockHeight),

This keeps the helper relevant and documents how the RANDAO value is derived.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
&engine.PayloadAttributes{
Timestamp: ts,
Random: prevBlockHash, //c.derivePrevRandao(height),
SuggestedFeeRecipient: c.feeRecipient,
Withdrawals: []*types.Withdrawal{},
BeaconRoot: &c.genesisHash,
Transactions: txsPayload, // force to use txsPayload
NoTxPool: true,
},
&engine.PayloadAttributes{
Timestamp: ts,
- Random: prevBlockHash, //c.derivePrevRandao(height),
+ Random: c.derivePrevRandao(blockHeight),
SuggestedFeeRecipient: c.feeRecipient,
Withdrawals: []*types.Withdrawal{},
BeaconRoot: &c.genesisHash,
Transactions: txsPayload, // force to use txsPayload
NoTxPool: true,
},

Comment on lines +155 to +176
func (c *EngineClient) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight uint64, timestamp time.Time, prevStateRoot []byte) (updatedStateRoot []byte, maxBytes uint64, err error) {
// convert rollkit tx to eth tx
ethTxs := make([]*types.Transaction, len(txs))
for i, tx := range txs {
ethTxs[i] = new(types.Transaction)
err := ethTxs[i].UnmarshalBinary(tx)
if err != nil {
return nil, 0, fmt.Errorf("failed to unmarshal transaction: %w", err)
}
}

// encode
txsPayload := make([][]byte, len(txs))
for i, tx := range ethTxs {
buf := bytes.Buffer{}
err := tx.EncodeRLP(&buf)
if err != nil {
return nil, 0, fmt.Errorf("failed to RLP encode tx: %w", err)
}
txsPayload[i] = buf.Bytes()
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential integer-overflow & clarity for timestamp conversion

timestamp.Unix() returns int64; casting directly to uint64 triggers gosec G115 because a negative value would wrap.
While real block timestamps are non-negative, adding an explicit guard silences the warning and documents intent.

-ts := uint64(timestamp.Unix())
+rawTs := timestamp.Unix()
+if rawTs < 0 {
+	return nil, 0, fmt.Errorf("negative timestamp is invalid: %d", rawTs)
+}
+ts := uint64(rawTs)

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +174 to +181
newStateRoot, maxBytes, err := executionClient.ExecuteTxs(ctx, payload, blockHeight, time.Now(), prevStateRoot)
require.NoError(t, err)
if len(payload) > 0 {
require.NotZero(tt, maxBytes)
}
require.NotEqual(tt, prevStateRoot, newStateRoot)

err = executionClient.SetFinal(ctx, blockHeight)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Empty-block handling inconsistent with build phase

During sync you always assert that the state root changes:

require.NotEqual(tt, prevStateRoot, newStateRoot)

But in the build phase you correctly treat an empty block (block 4) as
state-root preserving.
The same block height will be replayed here, so this assertion will fail (or,
worse, silently hide a divergence by forcing a change).

Either replicate the earlier check or make the assertion conditional on
len(payload) > 0.

Comment on lines +72 to +88
executionClient, err := NewEngineExecutionClient(
TEST_ETH_URL,
TEST_ENGINE_URL,
jwtSecret,
genesisHash,
common.Address{},
)
require.NoError(t, err)

ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
defer cancel()
stateRoot, gasLimit, err := executionClient.InitChain(ctx, genesisTime, initialHeight, CHAIN_ID)
require.NoError(t, err)
require.Equal(t, rollkitGenesisStateRoot, stateRoot)
require.NotZero(t, gasLimit)

prevStateRoot := rollkitGenesisStateRoot
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use the correct testing.T handle inside sub-tests

Analogous to the pure client tests, several assertions still call
require.*(t, …) instead of require.*(tt, …) inside the sub-test, e.g.:

require.NoError(t, err)
require.Equal(t, rollkitGenesisStateRoot, stateRoot)

Replace t with the sub-test handle (tt) throughout “Build chain” and “Sync
chain” to ensure proper failure attribution and avoid data races when tests
are run in parallel.

Comment on lines +91 to +100
for blockHeight := initialHeight; blockHeight <= 10; blockHeight++ {
nTxs := int(blockHeight) + 10
// randomly use no transactions
if blockHeight == 4 {
nTxs = 0
}
txs := make([]*ethTypes.Transaction, nTxs)
for i := range txs {
txs[i] = getRandomTransaction(t, 22000)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Off-by-one: executing the genesis height again

The build-phase loop starts at blockHeight := initialHeight (which is 1),
but InitChain already initialises the chain at that height.
Running ExecuteTxs immediately for height 1 re-executes the genesis slot,
which most engines reject or silently ignore.

-       for blockHeight := initialHeight; blockHeight <= 10; blockHeight++ {
+       for blockHeight := initialHeight + 1; blockHeight <= 10; blockHeight++ {

Remember to adjust the allPayloads index and the sync-phase loop
accordingly:

-       payload := allPayloads[blockHeight-1]
+       payload := allPayloads[blockHeight-initialHeight-1]

Committable suggestion skipped: line range outside the PR's diff.

@gupadhyaya
Copy link
Member Author

we have decided to do: #41, hence closing

@gupadhyaya gupadhyaya closed this Apr 25, 2025
@github-project-automation github-project-automation bot moved this to Done in Evolve Apr 25, 2025
@tac0turtle tac0turtle removed this from Evolve Aug 25, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants