Skip to content

Implement the RPCBlockHeaderSubscriber for indexing finalized results #728

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 80 commits into
base: main
Choose a base branch
from

Conversation

m-Peter
Copy link
Collaborator

@m-Peter m-Peter commented Jan 21, 2025

Closes: #727

Description


For contributor use:

  • Targeted PR against master branch
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Code follows the standards mentioned here.
  • Updated relevant documentation
  • Re-reviewed Files changed in the Github PR explorer
  • Added appropriate labels

Summary by CodeRabbit

  • New Features

    • Added experimental options for soft finality and sealing verification, enabling advanced EVM block and event data handling.
    • Introduced new command-line flags to control experimental features and gas price enforcement.
    • Implemented enhanced event hash storage and verification for improved data integrity.
    • Added robust mechanisms for tracking and verifying blockchain events.
    • Extended Ethereum transaction support to include Blob and SetCode transaction types.
    • Added new EIP-7702 contract write and transaction sending test suites.
    • Introduced new blockchain special block number constants for finer block state handling.
    • Enhanced debug tracing and call tracing with chain configuration integration.
  • Improvements

    • Enhanced error messages for better clarity during event indexing and storage operations.
    • Unified block number resolution logic across APIs for consistency.
    • Improved bloom filter computation by aggregating from receipts rather than logs.
    • Refined transaction argument handling and gas estimation to support new transaction types.
    • Updated test assertions and added validations for logs bloom filters.
    • Improved trace call and debug trace outputs with updated expected values.
  • Dependency Updates

    • Upgraded several dependencies to newer versions for improved stability and compatibility.

@m-Peter m-Peter self-assigned this Jan 21, 2025
Copy link
Contributor

coderabbitai bot commented Jan 21, 2025

Walkthrough

This change introduces experimental support for indexing EVM execution data from unsealed finalized blocks using a polling mechanism, with optional sealing verification. It adds new command-line flags, configuration fields, storage for event hashes, and implements new event subscriber and verifier components to enable and manage this feature.

Changes

File(s) Change Summary
bootstrap/bootstrap.go Refactored event ingestion startup to support experimental soft finality and sealing verification, added EventsHash storage field, and improved error handling in storage setup.
services/ingestion/block_tracking_subscriber.go Added new RPCBlockTrackingSubscriber struct and methods for subscribing to and tracking EVM events from unsealed finalized blocks.
services/ingestion/sealing_verifier.go Introduced the SealingVerifier struct and methods for verifying hashes of unsealed vs. sealed event data.
services/requester/cross-spork_client.go Added methods to retrieve events for block headers and subscribe to block headers from a start height.
cmd/run/cmd.go Added CLI flags for enabling experimental soft finality and sealing verification, and for enforcing gas price; updated config parsing accordingly.
config/config.go Added new config fields for gas price enforcement, soft finality, and sealing verification.
storage/pebble/events_hash.go Added new file implementing EventsHash storage for event hashes and processed sealed heights.
storage/pebble/db.go Added WithBatch helper for atomic batch storage operations.
storage/pebble/keys.go Added constants for event hash and sealed events height storage keys.
services/ingestion/engine.go Improved error messages in event indexing for better traceability.
Makefile Minor: updated check-tidy to use verbose flag and consolidated commands.
go.mod, tests/go.mod Upgraded multiple dependencies to newer versions.

Sequence Diagram(s)

sequenceDiagram
    participant CLI
    participant Bootstrap
    participant Config
    participant Storage
    participant CrossSporkClient
    participant RPCBlockTrackingSubscriber
    participant SealingVerifier

    CLI->>Bootstrap: StartEventIngestion()
    Bootstrap->>Config: Check ExperimentalSoftFinalityEnabled
    alt Soft Finality Enabled
        Bootstrap->>Config: Check ExperimentalSealingVerificationEnabled
        alt Sealing Verification Enabled
            Bootstrap->>Storage: Init EventsHash
            Bootstrap->>SealingVerifier: NewSealingVerifier(...)
            Bootstrap->>RPCBlockTrackingSubscriber: NewRPCBlockTrackingSubscriber(..., verifier)
        else Not Enabled
            Bootstrap->>RPCBlockTrackingSubscriber: NewRPCBlockTrackingSubscriber(..., nil)
        end
        Bootstrap->>RPCBlockTrackingSubscriber: Subscribe(ctx)
        alt Sealing Verification Enabled
            SealingVerifier->>CrossSporkClient: Subscribe to block events
            SealingVerifier->>Storage: Store/Get event hashes
            SealingVerifier->>RPCBlockTrackingSubscriber: Verify events
        end
    else Not Enabled
        Bootstrap->>RPCEventSubscriber: NewRPCEventSubscriber(...)
        RPCEventSubscriber->>CrossSporkClient: Subscribe to sealed events
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Support indexing unsealed finalized execution results via polling Access API for EVM events (#727)
Subscribe to new finalized blocks and fetch EVM events for each via polling (#727)
Allow manual rollback and ensure compatibility with new state index (#727)
Handle (or document) possible incorrect results from unsealed execution, with operator intervention (#727)

Possibly related PRs

Suggested labels

Improvement, EVM, Testing

Suggested reviewers

  • peterargue
  • janezpodhostnik
  • zhangchiqing
  • m-Peter

Poem

In the warren of code, a soft finality found,
Rabbits hopped to index blocks unbound.
With hashes stored and events verified,
Now unsealed secrets can't be denied.
Experimental flags, a burrower's delight—
EVM hops forward, through day and night!
🐇✨

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.

✨ 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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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.

Copy link
Contributor

@peterargue peterargue left a comment

Choose a reason for hiding this comment

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

added a couple small comments, but otherwise this looks good. let's get it running against one of the live networks.

@m-Peter
Copy link
Collaborator Author

m-Peter commented Jan 23, 2025

@peterargue Thanks for the review 🙌 I've addressed all the comments.
Did you perhaps have the chance to take a look at the behavior described in #727 (comment) ? This is puzzling to me 🤔

@m-Peter m-Peter marked this pull request as ready for review January 23, 2025 15:29
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@m-Peter m-Peter force-pushed the mpeter/poc-index-finalized-block-results branch from 314e7cc to bfe6188 Compare January 27, 2025 09:43
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

Copy link
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: 9

🔭 Outside diff range comments (1)
services/requester/requester.go (1)

320-333: ⚠️ Potential issue

Transaction type is hard-wired to Legacy – breaks Blob / Dynamic-Fee / Set-Code calls

txArgs.ToTransaction(types.LegacyTxType, ...) forces every eth_call
request to be encoded as a legacy transaction, ignoring the type field the
caller may have supplied.
Blob (0x05), Dynamic-Fee (0x02) and upcoming Set-Code (0x7E) transactions will
thus be simulated with the wrong rules and gas accounting.

- tx := txArgs.ToTransaction(types.LegacyTxType, blockGasLimit)
+ txType := types.LegacyTxType
+ if txArgs.Type != nil {
+     txType = uint8(*txArgs.Type)
+ }
+ tx := txArgs.ToTransaction(txType, blockGasLimit)

The same fix is needed in EstimateGas.

♻️ Duplicate comments (2)
tests/web3js/eth_pectra_upgrade_test.js (1)

24-41: Same flakiness & timeout issues as in the 7702 test suite

Apply the receipt-polling + extended Mocha timeout advice here as well (lines 24-29 & 36-41).

Also applies to: 36-37

services/requester/requester.go (1)

345-356: Repeat the tx-type fix in EstimateGas

EstimateGas suffers from the same hard-coded legacy type.
Apply the patch suggested above before each ToTransaction invocation to keep
the estimator accurate for EIP-1559 and blob transactions.

🧹 Nitpick comments (7)
tests/web3js/viem/config.js (1)

23-24: Caution: Hardcoded private key in source code.

While acceptable for testing purposes, hardcoding private keys in source code is generally not a best practice. Consider using environment variables or test configurations in the future.

tests/web3js/eth_eip_7702_contract_write_test.js (1)

10-39: Fixed timeouts could lead to flaky tests.

The before hook uses fixed timeouts (e.g., line 20, 32) which could cause intermittent failures if network conditions vary. Consider using a polling mechanism or transaction confirmation callback instead.

-    await new Promise((res) => setTimeout(() => res(), 1500))
+    // Poll until transaction is confirmed or timeout occurs
+    const maxAttempts = 10
+    let attempts = 0
+    while (attempts < maxAttempts) {
+        const receipt = await publicClient.getTransactionReceipt({hash})
+        if (receipt) {
+            break
+        }
+        await new Promise((res) => setTimeout(() => res(), 500))
+        attempts++
+    }
api/utils.go (1)

70-84: Guard against unknown negative block tags

The switch exhaustively lists the known special tags, but a malformed client
could still submit another negative value (e.g. -6).
Such input currently falls through to return uint64(blockNumber), nil, which
under-flows to a very large positive height.

Consider adding a sanity check:

     return height, nil
 }
 
 // fallback for explicit block heights
-if blockNumber < 0 {
-    return 0, fmt.Errorf("%w: unsupported block tag %v", errs.ErrInvalid, blockNumber)
-}
 return uint64(blockNumber), nil
tests/web3js/eth_pectra_upgrade_test.js (1)

65-68: Make the rejection assertion resilient to error-message changes

assert.equal(errMsg, 'transaction type not supported: type 4 rejected, pool not yet in Prague')

is brittle – any wording tweak, punctuation change or upstream library i18n will break the test.

Prefer a substring / regex check:

-assert.equal(errMsg, 'transaction type not supported: type 4 rejected, pool not yet in Prague')
+assert.match(errMsg, /transaction type .*type 4 .*not.*supported/i)
api/debug.go (1)

474-479: Pass a non-nil ChainConfig – but consider network upgrades

emulator.MakeChainConfig(config.EVMNetworkID) bakes the current rules, yet Tracer.New may need fork-specific params (Prague vs Pectra).
Ensure the config passed here is dynamically fork-aware (e.g. via ChainConfig.Copy() + Config.AtBlock(forkHeight)), otherwise tracers will mis-interpret op-codes around upgrades.

api/api.go (1)

965-980: Receipt fetching is duplicated – cache or reuse to save I/O

fetchBlockTransactions already calls prepareTransactionResponse, which loads
each receipt once.
In the second loop (building receipts to compute LogsBloom) the same
b.receipts.GetByTransactionID call is executed again, doubling the database
round-trips for large blocks.

Consider passing the previously fetched receipt slice back from
fetchBlockTransactions (or retrieving it there) and re-using it here:

-	for _, tx := range transactions {
-		txReceipt, err := b.receipts.GetByTransactionID(tx.Hash)
+    receiptsGeth := types.Receipts{}
+	for i, tx := range transactions {
+		txReceipt := tx.Receipt            // assuming fetched earlier
 		...
-		receipts = append(receipts, txReceipt.ToGethReceipt())
+        receiptsGeth = append(receiptsGeth, txReceipt.ToGethReceipt())
 	}
-	blockResponse.LogsBloom = types.MergeBloom(receipts).Bytes()
+	blockResponse.LogsBloom = types.MergeBloom(receiptsGeth).Bytes()

This removes redundant reads and cuts latency roughly in half for
eth_getBlockBy* with fullTx=true.

services/requester/requester.go (1)

447-453: Authorisation-list gas add-on: guard against integer overflow

passingGasLimit += uint64(len(txArgs.AuthorizationList)) * gethParams.CallNewAccountGas

len(AuthorizationList) is unbounded input. While overflow is unlikely in
practice, it will wrap silently. Consider:

extraGas := uint64(len(txArgs.AuthorizationList)) * gethParams.CallNewAccountGas
if math.MaxUint64-extraGas < passingGasLimit {
    return 0, fmt.Errorf("gas estimate overflow")
}
passingGasLimit += extraGas
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b7e6803 and 2fa4139.

⛔ Files ignored due to path filters (3)
  • go.sum is excluded by !**/*.sum
  • tests/go.sum is excluded by !**/*.sum
  • tests/web3js/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (29)
  • api/api.go (13 hunks)
  • api/debug.go (14 hunks)
  • api/stream.go (2 hunks)
  • api/utils.go (1 hunks)
  • bootstrap/bootstrap.go (5 hunks)
  • eth/types/types.go (3 hunks)
  • go.mod (8 hunks)
  • models/block.go (1 hunks)
  • models/receipt.go (1 hunks)
  • models/transaction.go (1 hunks)
  • models/transaction_test.go (5 hunks)
  • services/logs/filter_test.go (1 hunks)
  • services/replayer/blocks_provider_test.go (2 hunks)
  • services/replayer/call_tracer_collector.go (5 hunks)
  • services/requester/requester.go (12 hunks)
  • tests/e2e_web3js_test.go (3 hunks)
  • tests/go.mod (8 hunks)
  • tests/web3js/build_evm_state_test.js (2 hunks)
  • tests/web3js/contract_call_overrides_test.js (1 hunks)
  • tests/web3js/debug_traces_test.js (8 hunks)
  • tests/web3js/eth_batch_tx_logs_test.js (2 hunks)
  • tests/web3js/eth_deploy_contract_and_interact_test.js (1 hunks)
  • tests/web3js/eth_eip_7702_contract_write_test.js (1 hunks)
  • tests/web3js/eth_eip_7702_sending_transactions_test.js (1 hunks)
  • tests/web3js/eth_pectra_upgrade_test.js (1 hunks)
  • tests/web3js/package.json (1 hunks)
  • tests/web3js/viem/Delegation.sol (1 hunks)
  • tests/web3js/viem/config.js (1 hunks)
  • tests/web3js/viem/contract.js (1 hunks)
✅ Files skipped from review due to trivial changes (7)
  • tests/web3js/eth_deploy_contract_and_interact_test.js
  • tests/web3js/package.json
  • tests/web3js/viem/contract.js
  • tests/web3js/viem/Delegation.sol
  • services/replayer/blocks_provider_test.go
  • models/block.go
  • tests/go.mod
🚧 Files skipped from review as they are similar to previous changes (2)
  • bootstrap/bootstrap.go
  • go.mod
🧰 Additional context used
🧬 Code Graph Analysis (7)
tests/web3js/build_evm_state_test.js (2)
tests/web3js/eth_deploy_contract_and_interact_test.js (2)
  • callRetrieve (45-45)
  • deployed (7-7)
tests/web3js/eth_get_storage_at_test.js (2)
  • callRetrieve (14-14)
  • deployed (7-7)
tests/web3js/eth_batch_tx_logs_test.js (4)
tests/web3js/contract_call_overrides_test.js (4)
  • receipt (19-19)
  • receipt (101-101)
  • block (53-53)
  • block (144-144)
tests/web3js/debug_traces_test.js (2)
  • receipt (19-19)
  • receipt (381-381)
tests/e2e-network/e2e_test.js (1)
  • assert (3-3)
tests/web3js/eth_deploy_contract_and_interact_test.js (1)
  • block (105-105)
api/stream.go (2)
models/receipt.go (1)
  • Receipt (18-36)
storage/pebble/receipts.go (1)
  • Receipts (20-22)
tests/web3js/viem/config.js (2)
tests/web3js/eth_eip_7702_contract_write_test.js (4)
  • require (1-1)
  • require (2-2)
  • require (3-3)
  • require (4-4)
tests/web3js/eth_eip_7702_sending_transactions_test.js (5)
  • require (1-1)
  • require (2-2)
  • require (3-3)
  • require (4-4)
  • require (5-5)
api/utils.go (1)
models/block.go (5)
  • EarliestBlockNumber (17-17)
  • SafeBlockNumber (18-18)
  • FinalizedBlockNumber (19-19)
  • LatestBlockNumber (20-20)
  • PendingBlockNumber (21-21)
eth/types/types.go (1)
models/transaction.go (1)
  • Transaction (34-53)
api/api.go (5)
models/transaction.go (1)
  • Transaction (34-53)
eth/types/types.go (3)
  • Transaction (276-301)
  • Block (422-444)
  • MarshalReceipt (473-529)
models/block.go (3)
  • Block (45-55)
  • EarliestBlockNumber (17-17)
  • PendingBlockNumber (21-21)
models/receipt.go (1)
  • Receipt (18-36)
storage/pebble/receipts.go (1)
  • Receipts (20-22)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build
  • GitHub Check: Test
🔇 Additional comments (31)
tests/web3js/build_evm_state_test.js (2)

159-159: Gas estimation value updated

The assertion for estimated gas has been increased from 21358n to 21646n, which aligns with the updated gas calculation logic that likely includes additional costs for authorization lists and new transaction types.


178-178: Fixed incorrect await usage

The await keyword has been correctly removed from encodeABI() call since this method is synchronous and does not return a Promise.

models/transaction.go (1)

210-210: Updated bloom filter creation method signature

Changed from passing a slice of receipts []*gethTypes.Receipt{gethReceipt} to passing a single receipt gethReceipt directly to CreateBloom. This aligns with similar changes elsewhere in the codebase for consistent bloom filter computation.

Let's verify that this change is consistent with other bloom filter creations in the codebase:

#!/bin/bash
# Check other usages of CreateBloom in the codebase
rg "CreateBloom\(" --type go
services/logs/filter_test.go (1)

116-116: Updated bloom filter creation method signature

Changed from passing a slice of receipts []*gethTypes.Receipt{rcp} to passing a single receipt rcp directly to CreateBloom. This aligns with the similar change in models/transaction.go for consistent bloom filter computation across the codebase.

models/receipt.go (1)

38-56: New conversion method looks good!

The ToGethReceipt() method correctly maps all relevant fields from the custom Receipt struct to the go-ethereum gethTypes.Receipt struct. The implementation is straightforward and will facilitate interoperability between the custom and go-ethereum receipt types.

tests/web3js/eth_batch_tx_logs_test.js (4)

15-15: Enhanced test coverage for bloom filters.

Adding an array to store individual transaction receipt bloom filters is a good approach for the verification logic that follows.


45-46: Collecting bloom filters for verification.

Good approach to collect all receipt bloom filters for later verification against the block bloom filter.


48-52: Block level bloom filter verification.

This assertion verifies that the block's overall logsBloom matches the expected hardcoded value, ensuring that the bloom aggregation logic works correctly.


54-59: Individual vs. aggregated bloom verification.

This test ensures that none of the individual transaction bloom filters match the block-level bloom filter, which is expected since the block bloom should be an aggregation of all transaction blooms rather than equal to any single one.

models/transaction_test.go (5)

364-369: Updated Header initialization for test.

Setting the Difficulty to zero is appropriate for compatibility with modern consensus mechanisms.


377-384: Added support for SetCodeTxType.

The ValidationOptions.Accept bitmask has been extended to include the SetCodeTxType transaction type, which is necessary for testing the new transaction type support.


414-419: Consistent Header updates in TestValidateConsensusRules.

Same changes as in TestValidateTransaction for header initialization, ensuring consistency across tests.


427-437: Consistent validation options for consensus rules test.

Same update to the ValidationOptions.Accept bitmask as seen earlier, ensuring consistent transaction type support across tests.


478-478: Increased gas limit for transaction size tests.

The gas limit has been increased from 2,500,000 to 5,500,000, which aligns with the system updates to handle larger or more complex transactions, particularly with the new transaction types support.

tests/web3js/debug_traces_test.js (3)

33-33: Updated return value assertion.

Changed from a null check to asserting the length equals 9806n, providing more precise validation.


189-189: Standardized empty return value representation.

Updated all empty return value assertions from empty string '' to '0x', which is the standard hex representation for empty data in Ethereum. This ensures consistent empty value handling across the codebase.

Also applies to: 198-198, 264-264, 273-273, 412-412


358-359: Updated gas usage expectations.

The gas and gasUsed values have been adjusted to reflect the current behavior of the system, likely due to changes in transaction processing or gas calculation logic.

Also applies to: 366-366, 688-688, 696-696

tests/e2e_web3js_test.go (4)

8-8: LGTM: Required imports for new tests.

The addition of the time package and the flow-go/fvm/evm/emulator package are necessary for the new EIP-7702 and pre-Pectra tests added in this PR.

Also applies to: 12-12


200-201: Improved test robustness.

Replacing fixed input values with dynamic inputs derived from the loop index creates more varied test cases and better coverage.


357-363: LGTM: Added EIP-7702 tests.

These new test cases correctly test the contract write and transaction sending functionality using EIP-7702 authorization mechanisms, which is central to the PR's purpose.


365-374: Smart temporal test setup for pre-Pectra upgrade.

This test correctly sets the Prague hard-fork activation timestamp to the future (24 hours from now) to ensure the code behaves appropriately before the upgrade. The deferred reset ensures clean test isolation.

tests/web3js/viem/config.js (2)

1-21: LGTM: Well-structured Viem client configuration.

The configuration correctly defines a custom Flow EVM Local blockchain with the appropriate chain ID, native currency details, and RPC endpoint for testing.


26-39: LGTM: Proper client setup and exports.

The wallet and public clients are correctly initialized with the appropriate configuration, and all necessary objects are exported for use in the test files.

tests/web3js/eth_eip_7702_contract_write_test.js (3)

1-9: LGTM: Well-structured imports and account setup.

The test file correctly imports necessary dependencies and sets up the test account with a private key.


41-75: LGTM: Comprehensive relay account testing.

The test correctly validates the contract write workflow with relay authorization, verifying both transaction success and transaction types.


77-111: LGTM: Self-execution testing covers important flow.

The self-execution test complements the relay account test by covering an alternative authorization model, providing good coverage of the EIP-7702 functionality.

api/stream.go (2)

139-139: Improved bloom filter initialization.

Changed from using empty logs to using an empty receipt for initializing the bloom filter, which is more consistent with Ethereum's standard approach.


150-151: Optimized bloom filter calculation from receipts.

The implementation now:

  1. Uses gethTypes.Receipts{} to collect receipts
  2. Converts each custom receipt to a Geth receipt with ToGethReceipt()
  3. Uses MergeBloom(receipts) to generate the combined bloom filter

This approach is more efficient and aligns better with how Geth processes bloom filters.

Also applies to: 157-158, 161-161

tests/web3js/eth_eip_7702_sending_transactions_test.js (1)

42-80: Increase Mocha timeout or the suite will time-out before receipts arrive

Mocha’s default timeout is 2 seconds per test; each transaction round-trip plus the extra delay already exceeds that on moderately loaded runners.

Add at the top of the file (or inside each it) :

// top-level
this.timeout?.(20_000);     // Node <18 guard
// OR
mochaTimeout(20_000);       // If using cross-env wrapper

(or pass --timeout 20000 to the mocha CLI).

Without it CI intermittently reports Error: Timeout of 2000ms exceeded.

Also applies to: 82-120

api/api.go (1)

298-304: Use of resolveBlockNumber looks good, but propagate “earliest” correctly

resolveBlockNumber replaces the older ad-hoc logic, which is great for consistency.
Please double-check that every negative constant (Latest, Finalized, Safe, Earliest) is covered in the helper; otherwise callers like GetTransactionByBlockNumberAndIndex will silently fall through to an incorrect height.

No code change required if the helper is already complete.

services/requester/requester.go (1)

157-161: Hard-coded header.Number may drift from actual chain height

Number: big.NewInt(20_182_324) is embedded at start-up.
If the gateway runs long enough the real Cadence height will surpass this
constant, making nonce/gas validation pessimistic.

Instead query the latest EVM height once during NewEVM construction and use
that value.

@@ -179,7 +179,7 @@ it('should apply block overrides on debug_traceCall', async () => {

// Override the `block.prevrandao` value to `0x7914bb5b13bac6f621bc37bbf6e406fbf4472aaaaf17ec2f309a92aca4e27fc0`.
let random = '0x7914bb5b13bac6f621bc37bbf6e406fbf4472aaaaf17ec2f309a92aca4e27fc0'
callTracer.blockOverrides = { random: random }
callTracer.blockOverrides = { prevRandao: random }
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Updated block override property name from 'random' to 'prevRandao'

This change correctly updates the property name to match Ethereum's post-Merge terminology, where the previous "random" field is now correctly called "prevRandao".

The update maintains consistency with line 90 where the field name is still "random" for the eth_call method, which might need a similar update for consistency across API methods.


🏁 Script executed:

#!/bin/bash
# Check if random is used consistently elsewhere
rg "\brandom\s*:" --type js

Length of output: 125


Ensure consistency: update eth_call override property to prevRandao

The block override rename to prevRandao is correct for post-Merge terminology, but the test for the eth_call method still uses random. Please update it to match:

• File: tests/web3js/contract_call_overrides_test.js
Replace at line ~182:

- [call, 'latest', null, { random: random }]
+ [call, 'latest', null, { prevRandao: random }]

This will keep all override properties consistent across API methods.

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

Comment on lines +21 to 29
func DefaultCallTracer(evmChainID *big.Int) (*tracers.Tracer, error) {
evmChainConfig := emulator.MakeChainConfig(evmChainID)

tracer, err := tracers.DefaultDirectory.New(
TracerName,
&tracers.Context{},
json.RawMessage(TracerConfig),
evmChainConfig,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle nil Chain-ID to avoid panics

emulator.MakeChainConfig will dereference the supplied *big.Int.
If evmChainID is ever nil (e.g. config error, tests), the process will panic long before
an error can be returned.

 func DefaultCallTracer(evmChainID *big.Int) (*tracers.Tracer, error) {
-    evmChainConfig := emulator.MakeChainConfig(evmChainID)
+    if evmChainID == nil {
+        return nil, fmt.Errorf("evmChainID must not be nil")
+    }
+    evmChainConfig := emulator.MakeChainConfig(evmChainID)
📝 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
func DefaultCallTracer(evmChainID *big.Int) (*tracers.Tracer, error) {
evmChainConfig := emulator.MakeChainConfig(evmChainID)
tracer, err := tracers.DefaultDirectory.New(
TracerName,
&tracers.Context{},
json.RawMessage(TracerConfig),
evmChainConfig,
)
func DefaultCallTracer(evmChainID *big.Int) (*tracers.Tracer, error) {
if evmChainID == nil {
return nil, fmt.Errorf("evmChainID must not be nil")
}
evmChainConfig := emulator.MakeChainConfig(evmChainID)
tracer, err := tracers.DefaultDirectory.New(
TracerName,
&tracers.Context{},
json.RawMessage(TracerConfig),
evmChainConfig,
)
// … remainder of function …
}

Comment on lines 73 to 76
func (t *CallTracerCollector) ResetTracer() error {
var err error
t.tracer, err = DefaultCallTracer()
t.tracer, err = DefaultCallTracer(t.evmChainID)
return err
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Preserve the previous tracer when reset fails

If DefaultCallTracer returns an error, t.tracer is replaced with nil,
leaving the collector unusable and causing a nil-pointer panic on the next
callback invocation.

-    t.tracer, err = DefaultCallTracer(t.evmChainID)
-    return err
+    newTracer, err := DefaultCallTracer(t.evmChainID)
+    if err != nil {
+        // keep the previous tracer to remain operational
+        return err
+    }
+    t.tracer = newTracer
+    return nil
📝 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
func (t *CallTracerCollector) ResetTracer() error {
var err error
t.tracer, err = DefaultCallTracer()
t.tracer, err = DefaultCallTracer(t.evmChainID)
return err
func (t *CallTracerCollector) ResetTracer() error {
var err error
newTracer, err := DefaultCallTracer(t.evmChainID)
if err != nil {
// keep the previous tracer to remain operational
return err
}
t.tracer = newTracer
return nil
}

Comment on lines +135 to +175
case types.SetCodeTxType:
al := types.AccessList{}
if txArgs.AccessList != nil {
al = *txArgs.AccessList
}
authList := []types.SetCodeAuthorization{}
if txArgs.AuthorizationList != nil {
authList = txArgs.AuthorizationList
}
data = &types.SetCodeTx{
To: *txArgs.To,
ChainID: uint256.MustFromBig(txArgs.ChainID.ToInt()),
Nonce: nonce,
Gas: uint64(*txArgs.Gas),
GasFeeCap: uint256.MustFromBig((*big.Int)(txArgs.MaxFeePerGas)),
GasTipCap: uint256.MustFromBig((*big.Int)(txArgs.MaxPriorityFeePerGas)),
Value: uint256.MustFromBig((*big.Int)(txArgs.Value)),
Data: txArgs.data(),
AccessList: al,
AuthList: authList,
}

case types.BlobTxType:
al := types.AccessList{}
if txArgs.AccessList != nil {
al = *txArgs.AccessList
}
data = &types.BlobTx{
To: *txArgs.To,
ChainID: uint256.MustFromBig((*big.Int)(txArgs.ChainID)),
Nonce: nonce,
Gas: uint64(*txArgs.Gas),
GasFeeCap: uint256.MustFromBig((*big.Int)(txArgs.MaxFeePerGas)),
GasTipCap: uint256.MustFromBig((*big.Int)(txArgs.MaxPriorityFeePerGas)),
Value: uint256.MustFromBig((*big.Int)(txArgs.Value)),
Data: txArgs.data(),
AccessList: al,
BlobHashes: txArgs.BlobHashes,
BlobFeeCap: uint256.MustFromBig((*big.Int)(txArgs.BlobFeeCap)),
}
if txArgs.Blobs != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Nil-pointer panics when optional fees / chain-ID are omitted

uint256.MustFromBig panics on a nil argument.
Fields such as ChainID, MaxFeePerGas, MaxPriorityFeePerGas, Value,
BlobFeeCap, and To are all optional according to the JSON-RPC spec, but they
are blindly dereferenced here for SetCodeTx and BlobTx construction.

This makes the gateway crash on perfectly valid user input (e.g. blob tx with
default fee caps).

-        ChainID:    uint256.MustFromBig(txArgs.ChainID.ToInt()),
+        ChainID:    uint256.MustFromBig(models.BigOrZero(txArgs.ChainID)),
 ...
-        GasFeeCap:  uint256.MustFromBig((*big.Int)(txArgs.MaxFeePerGas)),
+        GasFeeCap:  uint256.MustFromBig(models.BigOrZero(txArgs.MaxFeePerGas)),

Where models.BigOrZero is a tiny helper:

// models/helpers.go
func BigOrZero(h *hexutil.Big) *big.Int {
    if h == nil {
        return big.NewInt(0)
    }
    return h.ToInt()
}

Apply the same defensive conversion for all fee / value fields and check
txArgs.To != nil before dereferencing.

Failing fast with a descriptive error (or defaulting to zero as go-ethereum
does) will make the API robust.

Comment on lines +19 to +25
let hash = await walletClient.sendRawTransaction({ serializedTransaction })

await new Promise((res) => setTimeout(() => res(), 1500))
let transaction = await publicClient.getTransactionReceipt({
hash: hash
})
assert.equal(transaction.status, 'success')
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace fixed setTimeout delays with receipt-polling utilities to avoid flaky tests

The test waits a hard-coded 1.5 second delay after every send.
Block-time, local dev speed, CI load or network latency can all vary and turn the suite red/green nondeterministically.

Recommend waiting for the receipt instead, e.g.:

-await new Promise((res) => setTimeout(() => res(), 1500))
-const transaction = await publicClient.getTransactionReceipt({ hash })
+const transaction = await publicClient.waitForTransactionReceipt({ hash })

(or expose a tiny helper that polls getTransactionReceipt until status !== undefined with a timeout).

Besides stability you shave ~6 s off this file alone.

Also applies to: 33-37, 59-60, 74-75, 98-100, 114-115

Comment on lines +100 to +110
// submit a transaction that calls verifyArchCallToFlowBlockHeight()
let flowBlockHeightData = deployed.contract.methods.verifyArchCallToFlowBlockHeight().encodeABI()
res = await helpers.signAndSend({
from: conf.eoa.address,
to: contractAddress,
data: flowBlockHeightData,
value: '0',
gasPrice: conf.minGasPrice,
})
assert.equal(res.receipt.status, conf.successStatus)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

res is written without let/const – creates an implicit global and fails under "use strict"

- res = await helpers.signAndSend({
+ const res = await helpers.signAndSend({

Running Node with --strict or under ESM will throw ReferenceError: res is not defined.
Even in sloppy mode you end up mutating the global object and leaking state across tests.

📝 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
// submit a transaction that calls verifyArchCallToFlowBlockHeight()
let flowBlockHeightData = deployed.contract.methods.verifyArchCallToFlowBlockHeight().encodeABI()
res = await helpers.signAndSend({
from: conf.eoa.address,
to: contractAddress,
data: flowBlockHeightData,
value: '0',
gasPrice: conf.minGasPrice,
})
assert.equal(res.receipt.status, conf.successStatus)
// submit a transaction that calls verifyArchCallToFlowBlockHeight()
let flowBlockHeightData = deployed.contract.methods.verifyArchCallToFlowBlockHeight().encodeABI()
const res = await helpers.signAndSend({
from: conf.eoa.address,
to: contractAddress,
data: flowBlockHeightData,
value: '0',
gasPrice: conf.minGasPrice,
})
assert.equal(res.receipt.status, conf.successStatus)

Comment on lines +144 to 146
tx := args.ToTransaction(gethTypes.LegacyTxType, BlockGasLimit)

// Default address in case user does not provide one
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

User-supplied gas limit is ignored – every trace uses the full block gas limit

args.ToTransaction(gethTypes.LegacyTxType, BlockGasLimit) overwrites any gas field provided in the RPC call.
Downstream you pass tx.Gas() to DryCall, so the simulation may:

  • succeed locally but OOG on-chain (or vice-versa);
  • hide genuine optimisation issues.

Patch suggestion:

- tx := args.ToTransaction(gethTypes.LegacyTxType, BlockGasLimit)
+gasLimit := BlockGasLimit
+if args.Gas != nil {
+    gasLimit = uint64(*args.Gas)
+}
+tx := args.ToTransaction(gethTypes.LegacyTxType, gasLimit)

…and keep passing tx.Gas() further down.

Please verify other call-paths that relied on the old encodeTxFromArgs helper.

Also applies to: 242-245

Comment on lines 203 to 206
to := gethCommon.Address{}
if tx.To != nil {
to = *tx.To
if tx.To() != nil {
to = *tx.To()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Contract-creation calls are converted into calls to the zero address

When args.To == nil (contract creation) the code sets

to := gethCommon.Address{}

so the dry-run executes a message call to 0x00…00, not a create.
That changes execution semantics (init code vs runtime code, value handling, etc.).

Either:

  • propagate nil and teach the view layer to treat it as CREATE, or
  • call the EVM’s Create path directly.

At minimum, reject unsupported contract-creation traces to avoid silent mis-simulation.

Comment on lines 631 to 642
// if special value, use latest block number
if from.Cmp(models.EarliestBlockNumber) < 0 {
if from.Cmp(models.EarliestBlockNumber) == 0 {
from = big.NewInt(0)
} else if from.Cmp(models.PendingBlockNumber) < 0 {
from = latest
}
if to.Cmp(models.EarliestBlockNumber) < 0 {

if to.Cmp(models.EarliestBlockNumber) == 0 {
to = big.NewInt(0)
} else if to.Cmp(models.PendingBlockNumber) < 0 {
to = latest
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Earliest handled, but Finalized and Safe silently map to latest

The new branch keeps the previous rule “anything < Pending ⇒ latest”, then adds a special‐case for Earliest.
Consequences:

  • -3 (finalized) and -4 (safe) are now treated exactly the same as -2 (latest).
  • That deviates from the semantics exposed by other endpoints (see comment above GetBlockByNumber).

If the intention is to support finalized/safe later, the current behaviour may mis-lead API users.

-} else if from.Cmp(models.PendingBlockNumber) < 0 {
-    from = latest
+} else if from.Cmp(models.FinalizedBlockNumber) == 0 {
+    // map to the most recent finalized height
+    from = latest  // TODO: replace once finalized height is tracked
+} else if from.Cmp(models.SafeBlockNumber) == 0 {
+    // map to the most recent safe height
+    from = latest  // TODO: replace once safe height is tracked
+} else if from.Cmp(models.PendingBlockNumber) < 0 {
+    from = latest
 }

Introduce dedicated branches (even if the value ultimately falls back to
latest) to make the intent explicit and future refactors safer.

📝 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
// if special value, use latest block number
if from.Cmp(models.EarliestBlockNumber) < 0 {
if from.Cmp(models.EarliestBlockNumber) == 0 {
from = big.NewInt(0)
} else if from.Cmp(models.PendingBlockNumber) < 0 {
from = latest
}
if to.Cmp(models.EarliestBlockNumber) < 0 {
if to.Cmp(models.EarliestBlockNumber) == 0 {
to = big.NewInt(0)
} else if to.Cmp(models.PendingBlockNumber) < 0 {
to = latest
}
// if special value, use latest block number
if from.Cmp(models.EarliestBlockNumber) == 0 {
from = big.NewInt(0)
} else if from.Cmp(models.FinalizedBlockNumber) == 0 {
// map to the most recent finalized height
from = latest // TODO: replace once finalized height is tracked
} else if from.Cmp(models.SafeBlockNumber) == 0 {
// map to the most recent safe height
from = latest // TODO: replace once safe height is tracked
} else if from.Cmp(models.PendingBlockNumber) < 0 {
from = latest
}
if to.Cmp(models.EarliestBlockNumber) == 0 {
to = big.NewInt(0)
} else if to.Cmp(models.PendingBlockNumber) < 0 {
to = latest
}

Copy link
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

♻️ Duplicate comments (2)
eth/types/types.go (2)

135-155: ⚠️ Potential issue

Fix nil pointer dereferences in SetCodeTx construction

The SetCodeTx constructor doesn't handle nil pointer cases for optional fields like To, ChainID, MaxFeePerGas, MaxPriorityFeePerGas, and Value. This can cause panics when these fields are omitted, which is valid according to the JSON-RPC spec.

Apply this defensive conversion pattern:

data = &types.SetCodeTx{
-    To:         *txArgs.To,
+    To:         *txArgs.To,  // To is required for SetCodeTx
-    ChainID:    uint256.MustFromBig(txArgs.ChainID.ToInt()),
+    ChainID:    uint256.MustFromBig(models.BigOrZero(txArgs.ChainID)),
     Nonce:      nonce,
     Gas:        uint64(*txArgs.Gas),
-    GasFeeCap:  uint256.MustFromBig((*big.Int)(txArgs.MaxFeePerGas)),
-    GasTipCap:  uint256.MustFromBig((*big.Int)(txArgs.MaxPriorityFeePerGas)),
-    Value:      uint256.MustFromBig((*big.Int)(txArgs.Value)),
+    GasFeeCap:  uint256.MustFromBig(models.BigOrZero(txArgs.MaxFeePerGas)),
+    GasTipCap:  uint256.MustFromBig(models.BigOrZero(txArgs.MaxPriorityFeePerGas)),
+    Value:      uint256.MustFromBig(models.BigOrZero(txArgs.Value)),
     Data:       txArgs.data(),
     AccessList: al,
     AuthList:   authList,
}

Where models.BigOrZero is the helper:

// models/helpers.go
func BigOrZero(h *hexutil.Big) *big.Int {
    if h == nil {
        return big.NewInt(0)
    }
    return h.ToInt()
}

157-174: ⚠️ Potential issue

Fix nil pointer dereferences in BlobTx construction

Similar to the SetCodeTx issue, the BlobTx constructor doesn't handle nil pointer cases for optional fields like To, ChainID, MaxFeePerGas, MaxPriorityFeePerGas, Value, and BlobFeeCap. This can cause panics when these fields are omitted.

Apply this defensive conversion pattern:

data = &types.BlobTx{
-    To:         *txArgs.To,
+    To:         *txArgs.To, // To is required for BlobTx
-    ChainID:    uint256.MustFromBig((*big.Int)(txArgs.ChainID)),
+    ChainID:    uint256.MustFromBig(models.BigOrZero(txArgs.ChainID)),
     Nonce:      nonce,
     Gas:        uint64(*txArgs.Gas),
-    GasFeeCap:  uint256.MustFromBig((*big.Int)(txArgs.MaxFeePerGas)),
-    GasTipCap:  uint256.MustFromBig((*big.Int)(txArgs.MaxPriorityFeePerGas)),
-    Value:      uint256.MustFromBig((*big.Int)(txArgs.Value)),
+    GasFeeCap:  uint256.MustFromBig(models.BigOrZero(txArgs.MaxFeePerGas)),
+    GasTipCap:  uint256.MustFromBig(models.BigOrZero(txArgs.MaxPriorityFeePerGas)),
+    Value:      uint256.MustFromBig(models.BigOrZero(txArgs.Value)),
     Data:       txArgs.data(),
     AccessList: al,
     BlobHashes: txArgs.BlobHashes,
-    BlobFeeCap: uint256.MustFromBig((*big.Int)(txArgs.BlobFeeCap)),
+    BlobFeeCap: uint256.MustFromBig(models.BigOrZero(txArgs.BlobFeeCap)),
}
🧹 Nitpick comments (1)
eth/types/types.go (1)

103-104: Consider returning errors rather than panicking

The ToTransaction method doesn't return an error, which means any failure conditions like nil pointer dereferences will cause panics rather than graceful error handling.

Consider changing the function signature to:

-func (txArgs TransactionArgs) ToTransaction(defaultType int, defaultGas uint64) *types.Transaction {
+func (txArgs TransactionArgs) ToTransaction(defaultType int, defaultGas uint64) (*types.Transaction, error) {

This would allow proper error handling and propagation throughout the call stack.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f39360d and 092753c.

📒 Files selected for processing (4)
  • eth/types/types.go (7 hunks)
  • models/transaction.go (3 hunks)
  • tests/web3js/eth_eip_7702_contract_write_test.js (1 hunks)
  • tests/web3js/eth_eip_7702_sending_transactions_test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • models/transaction.go
  • tests/web3js/eth_eip_7702_contract_write_test.js
  • tests/web3js/eth_eip_7702_sending_transactions_test.js
🧰 Additional context used
🧬 Code Graph Analysis (1)
eth/types/types.go (1)
models/transaction.go (1)
  • Transaction (34-54)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Test
🔇 Additional comments (3)
eth/types/types.go (3)

409-413: LGTM - SetCodeAuthorizations handling

The conditional check for transaction types and accessing SetCodeAuthorizations is correct. This ensures proper display of authorization lists for EIP-7702 transactions.


366-388: Excellent documentation of transaction type hierarchy

The comments explaining the transaction type hierarchy and backward compatibility are very helpful for understanding how different transaction types relate to each other. This will aid future developers in maintaining the code.


231-240: LGTM - data helper method implementation

The data helper method correctly handles the precedence of Input over Data, following Ethereum conventions. It also properly handles nil pointers.

Comment on lines +118 to +132
usedType := types.LegacyTxType
switch {
case txArgs.AuthorizationList != nil || defaultType == types.SetCodeTxType:
usedType = types.SetCodeTxType
case txArgs.BlobHashes != nil || defaultType == types.BlobTxType:
usedType = types.BlobTxType
case txArgs.MaxFeePerGas != nil || defaultType == types.DynamicFeeTxType:
usedType = types.DynamicFeeTxType
case txArgs.AccessList != nil || defaultType == types.AccessListTxType:
usedType = types.AccessListTxType
}
// Make it possible to default to newer tx, but use legacy if gasprice is provided
if txArgs.GasPrice != nil {
usedType = types.LegacyTxType
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider validating fields before transaction construction

The transaction type selection logic prioritizes legacy transactions when GasPrice is set, but doesn't validate that required fields for each type are present. For example, SetCodeTx requires a non-nil To field, but this isn't checked until dereferencing.

Consider validating that required fields exist before constructing the transaction:

usedType := types.LegacyTxType
switch {
case txArgs.AuthorizationList != nil || defaultType == types.SetCodeTxType:
+    if txArgs.To == nil {
+        // Fall back to legacy or return error
+    }
    usedType = types.SetCodeTxType
case txArgs.BlobHashes != nil || defaultType == types.BlobTxType:
+    if txArgs.To == nil {
+        // Fall back to legacy or return error
+    }
    usedType = types.BlobTxType
// ... other cases
}

// Return descriptive errors if required fields are missing

This would enable the API to fail with descriptive errors rather than panicking.

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

Comment on lines +175 to +181
if txArgs.Blobs != nil {
data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{
Blobs: txArgs.Blobs,
Commitments: txArgs.Commitments,
Proofs: txArgs.Proofs,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Verify BlobTx sidecar field validation

The code assigns the sidecar if Blobs are present, but there's no validation that Commitments and Proofs are also present or have matching lengths.

Ensure that the BlobTx sidecar construction properly validates:

  1. All three fields (Blobs, Commitments, Proofs) are present
  2. All three fields have the same length
  3. The length matches the BlobHashes length

Consider adding validation before constructing the sidecar:


🏁 Script executed:

#!/bin/bash
# Check if there are any validations for blob sidecar fields elsewhere in the codebase

rg -A 5 "func.*BlobTxSidecar" --type go
rg -A 5 "BlobTxSidecar.*validate" --type go
rg -A 5 "BlobTx.*validate" --type go

Length of output: 126


🏁 Script executed:

#!/bin/bash
# Inspect the BlobTx and BlobTxSidecar type definitions and search for any Validate methods (case-insensitive)
rg -A 5 'type BlobTxSidecar' --type go
rg -A 5 'type BlobTx ' --type go
rg -i -A 5 'func .*BlobTxSidecar.*validate' --type go
rg -i -A 5 'func .*BlobTx.*validate' --type go

Length of output: 180


Add validation for BlobTx sidecar fields

We didn’t find any existing checks for matching lengths or presence of all three slices. Before assigning the sidecar, validate that:

  • Blobs, Commitments and Proofs are all non-nil
  • They all have the same length
  • That length matches data.(*types.BlobTx).BlobHashes

Suggested patch:

--- a/eth/types/types.go
+++ b/eth/types/types.go
@@ -175,6 +175,17 @@ func convertTxArgsToCore(txArgs TxArgs, data core.TxData) error {
        if txArgs.Blobs != nil {
+           // Validate blob sidecar fields
+           if txArgs.Commitments == nil || txArgs.Proofs == nil {
+               return fmt.Errorf("blob sidecar requires commitments and proofs when blobs are provided")
+           }
+           if len(txArgs.Blobs) != len(txArgs.Commitments) || len(txArgs.Blobs) != len(txArgs.Proofs) {
+               return fmt.Errorf(
+                   "invalid blob sidecar lengths: blobs=%d, commitments=%d, proofs=%d",
+                   len(txArgs.Blobs), len(txArgs.Commitments), len(txArgs.Proofs),
+               )
+           }
+           if len(txArgs.Blobs) != len(data.(*types.BlobTx).BlobHashes) {
+               return fmt.Errorf(
+                   "blob count %d does not match BlobHashes count %d",
+                   len(txArgs.Blobs), len(data.(*types.BlobTx).BlobHashes),
+               )
+           }
            data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{
                Blobs:       txArgs.Blobs,
                Commitments: txArgs.Commitments,
                Proofs:      txArgs.Proofs,
            }

Don’t forget to import fmt.

📝 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
if txArgs.Blobs != nil {
data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{
Blobs: txArgs.Blobs,
Commitments: txArgs.Commitments,
Proofs: txArgs.Proofs,
}
}
if txArgs.Blobs != nil {
+ // Validate blob sidecar fields
+ if txArgs.Commitments == nil || txArgs.Proofs == nil {
+ return fmt.Errorf("blob sidecar requires commitments and proofs when blobs are provided")
+ }
+ if len(txArgs.Blobs) != len(txArgs.Commitments) || len(txArgs.Blobs) != len(txArgs.Proofs) {
+ return fmt.Errorf(
+ "invalid blob sidecar lengths: blobs=%d, commitments=%d, proofs=%d",
+ len(txArgs.Blobs), len(txArgs.Commitments), len(txArgs.Proofs),
+ )
+ }
+ if len(txArgs.Blobs) != len(data.(*types.BlobTx).BlobHashes) {
+ return fmt.Errorf(
+ "blob count %d does not match BlobHashes count %d",
+ len(txArgs.Blobs), len(data.(*types.BlobTx).BlobHashes),
+ )
+ }
data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{
Blobs: txArgs.Blobs,
Commitments: txArgs.Commitments,
Proofs: txArgs.Proofs,
}
}

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.

PoC to allow indexing unsealed finalized execution results
4 participants