Skip to content

cache chain ID at startup to prevent nil panic in ChainID()#3

Open
djosse wants to merge 8 commits intomasterfrom
f/fix_rpc_transaction
Open

cache chain ID at startup to prevent nil panic in ChainID()#3
djosse wants to merge 8 commits intomasterfrom
f/fix_rpc_transaction

Conversation

@djosse
Copy link
Copy Markdown

@djosse djosse commented Apr 2, 2026

Summary by CodeRabbit

  • New Features

    • Added a reindex subcommand to clear indexed data for a specified block range.
  • Bug Fixes

    • Improved error handling during RPC backend startup to avoid running with invalid backends.
    • Made transaction parsing tolerant of log-parsing failures so indexing continues.
    • Ensured failed or partial transactions are still recorded when message-result entries are missing.
  • Performance

    • Cache chain ID at startup to avoid per-request lookups.
  • Logging

    • Added more informational diagnostics during indexing and lookup operations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Backend.NewBackend now returns an error and RPC API creator closures panic on backend-init errors; Backend caches parsed chain ID and ChainID() returns it. A new reindex CLI subcommand calls indexer.ClearBlockRange to delete indexed blocks/txs in a range. ParseTxResult ignores ABCI log-parse errors for failed txs.

Changes

Cohort / File(s) Summary
RPC API creators & backend init
internal/evm/rpc/apis.go, internal/evm/rpc/backend/backend.go
NewBackend signature changed to (*Backend, error); RPC namespace creator closures now handle the error and call panic(err) on failure.
Backend chain ID caching & ChainID()
internal/evm/rpc/backend/backend.go, internal/evm/rpc/backend/chain_info.go
Added cachedChainID *big.Int to Backend; parse and store clientCtx.ChainID during construction; ChainID() now returns the cached value.
Reindex CLI command
cmd/evm-gateway/main.go
Added reindex subcommand and runReindex(args []string) which parses flags, loads config, initializes logger, opens index DB, and calls the indexer clear routine.
Indexer clear logic
internal/indexer/reindex.go
New exported ClearBlockRange(db, logger, from, to) error that validates range and deletes BlockMeta/BlockLogs, BlockHash mappings, and Tx/Receipt/index keys in batched passes.
Indexer indexing behavior & diagnostics
internal/indexer/kv_indexer.go
Indexing now records failed txs when parsed txs are missing by deriving GasUsed/txHash from message; added informational logs and diagnostic logging in GetByTxHash.
ParseTxResult resilience
internal/evm/rpc/types/parsed_tx.go
When parsing ABCI log for failed txs, log-parse errors are ignored so partially parsed results are returned instead of aborting.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant CLI as "evm-gateway CLI"
    participant Main as "cmd/main (runReindex)"
    participant Config as "Config Loader"
    participant Logger as "Logger"
    participant DB as "Indexer DB"
    participant Indexer as "internal/indexer"

    CLI->>Main: invoke `reindex` with args (--from, --to,...)
    Main->>Config: load config & apply overrides
    Main->>Logger: init logger
    Main->>DB: open indexer DB
    Main->>Indexer: ClearBlockRange(db, logger, from, to)
    Indexer->>DB: iterate heights in batches -> read `BlockMeta`
    Indexer->>DB: batch delete `BlockMeta` and `BlockLogs`
    Indexer->>DB: delete `BlockHash` entries for collected hashes
    Indexer->>DB: iterate heights -> scan & delete `TxIndex`, `TxHash`, `Receipt`, RPC keys
    Indexer->>Logger: log progress and completion
    Main->>CLI: exit (success/failure)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
I hopped through code to cache the chain,
Panicked when backend errors couldn't explain.
I cleared old blocks, batch by batch,
Kept partial txs so records won't detach.
Logs now guide the path like a lantern's lane.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: caching the chain ID at startup to prevent a nil panic in the ChainID() method. This aligns with the core modification shown in backend.go and chain_info.go.

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

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

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/evm/rpc/apis.go (1)

68-71: Extract the repeated backend constructor block.

The same NewBackend + panic(err) sequence now lives in four namespace creators. A small helper keeps these sites aligned if the constructor arguments or startup failure policy change again.

Also applies to: 120-123, 140-143, 160-163

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

In `@internal/evm/rpc/apis.go` around lines 68 - 71, Extract the repeated
backend.NewBackend(...) + panic(err) sequence into a single helper (e.g.,
initBackend or mustNewBackend) that accepts the same arguments (logger, cfg,
clientCtx, allowUnprotectedTxs, indexer, status) and returns the constructed
backend or panics on error; replace the four occurrences that currently call
backend.NewBackend and then panic(err) with calls to this helper (refer to
backend.NewBackend and the existing panic(err) sites in the namespace creator
functions) so startup behavior and constructor arguments remain centralized and
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/evm/rpc/backend/chain_info.go`:
- Around line 29-30: ChainID() currently returns a pointer to the shared
cachedChainID allowing callers to mutate the cached value; change ChainID() to
return a defensive copy by allocating a new big.Int (converted to *hexutil.Big)
copied from b.cachedChainID so callers get an independent instance; update any
callers (e.g., SetTxDefaults which assigns args.ChainID) to continue using the
returned value without risk of modifying the backend's cachedChainID.

---

Nitpick comments:
In `@internal/evm/rpc/apis.go`:
- Around line 68-71: Extract the repeated backend.NewBackend(...) + panic(err)
sequence into a single helper (e.g., initBackend or mustNewBackend) that accepts
the same arguments (logger, cfg, clientCtx, allowUnprotectedTxs, indexer,
status) and returns the constructed backend or panics on error; replace the four
occurrences that currently call backend.NewBackend and then panic(err) with
calls to this helper (refer to backend.NewBackend and the existing panic(err)
sites in the namespace creator functions) so startup behavior and constructor
arguments remain centralized and consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6e4064c7-67bd-438f-9736-7b6eee2956e1

📥 Commits

Reviewing files that changed from the base of the PR and between 564b2ec and 72f577b.

📒 Files selected for processing (3)
  • internal/evm/rpc/apis.go
  • internal/evm/rpc/backend/backend.go
  • internal/evm/rpc/backend/chain_info.go

Copy link
Copy Markdown

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/evm-gateway/main.go`:
- Around line 202-205: The fmt.Fprintf call in the fs.Parse error branch is
missing the err argument for the "%v" format specifier; update the error
handling after fs.Parse(args) to pass err into fmt.Fprintf (i.e.,
fmt.Fprintf(os.Stderr, "reindex: %v\n\nUsage:\n", err)) so the actual parse
error is printed before calling fs.PrintDefaults() and exiting; locate this in
the main function where fs.Parse, fmt.Fprintf and fs.PrintDefaults are used.

In `@internal/indexer/reindex.go`:
- Around line 74-79: The code currently ignores errors from db.Get and
json.Unmarshal when loading CachedBlockMeta, which can leave stale BlockHash
mappings; update the logic in the reindex loop where db.Get(metaKey) and
json.Unmarshal are used (referencing db.Get, metaKey, CachedBlockMeta,
json.Unmarshal, and blockHashes) to explicitly handle failures: if db.Get
returns an error or empty value, log/return the error or continue with a
warning; if json.Unmarshal fails or meta.Hash is empty, remove the stale mapping
(e.g., call db.Delete(metaKey) or equivalent) and log the failure instead of
silently skipping, and only append to blockHashes when unmarshalling succeeds
and meta.Hash is valid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: df4211a2-4b2d-45f9-8060-9bb8d4ec59c5

📥 Commits

Reviewing files that changed from the base of the PR and between 72f577b and 3700ae8.

📒 Files selected for processing (3)
  • cmd/evm-gateway/main.go
  • internal/evm/rpc/types/parsed_tx.go
  • internal/indexer/reindex.go

Copy link
Copy Markdown

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

🧹 Nitpick comments (1)
internal/indexer/kv_indexer.go (1)

133-150: Consider extracting duplicated fallback logic.

Lines 136-138 and 148-150 perform identical operations:

txResult.GasUsed = ethMsg.GetGas()
txResult.Failed = true
txHash = ethMsg.Hash()

This could be extracted into a small helper or consolidated, though the two branches handle different failure scenarios (non-EVM codespace vs. EVM codespace with missing parsed data). If the duplication is intentional for clarity, a brief inline comment linking the two cases would help future readers.

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

In `@internal/indexer/kv_indexer.go` around lines 133 - 150, There is duplicated
fallback logic setting txResult.GasUsed, txResult.Failed and txHash from ethMsg
in two branches (the non-EVM codespace branch and the parsedTx==nil branch);
extract that into a small helper function (e.g., setFallbackTxResult(txResult
*TxResult, ethMsg *MsgEthereumTx) string or similar) and call it from both
places (replace the three lines in the branch handling result.Codespace !=
evmtypes.ModuleName and the three lines in the parsedTx == nil branch), or
consolidate by calling that helper and returning its txHash; keep existing
comments about the different failure scenarios or add a short inline note
linking the two cases for clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/indexer/kv_indexer.go`:
- Around line 133-150: There is duplicated fallback logic setting
txResult.GasUsed, txResult.Failed and txHash from ethMsg in two branches (the
non-EVM codespace branch and the parsedTx==nil branch); extract that into a
small helper function (e.g., setFallbackTxResult(txResult *TxResult, ethMsg
*MsgEthereumTx) string or similar) and call it from both places (replace the
three lines in the branch handling result.Codespace != evmtypes.ModuleName and
the three lines in the parsedTx == nil branch), or consolidate by calling that
helper and returning its txHash; keep existing comments about the different
failure scenarios or add a short inline note linking the two cases for clarity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d313caa-cd9a-480f-a08c-e4c0efb05596

📥 Commits

Reviewing files that changed from the base of the PR and between 3700ae8 and 9d88426.

📒 Files selected for processing (1)
  • internal/indexer/kv_indexer.go

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/indexer/kv_indexer.go`:
- Around line 142-154: The positional lookup via txs.GetTxByMsgIndex(msgIndex)
can return nil for sparse msgIndex values, and the current branch skips indexing
for successful txs; change the nil-handling so that when parsedTx == nil and
result.Code == abci.CodeTypeOK you do NOT continue/skip but instead construct
the necessary tx indexing entities from the decoded ethMsg (set txResult.GasUsed
= ethMsg.GetGas(), set txResult.Failed = false, set txHash = ethMsg.Hash(),
build a receipt/RPC tx entry as the code normally would when parsedTx exists)
and then call the existing saveTxResult / receipt save / RPC tx write paths (the
same functions used for parsedTx cases) so the successful EVM message is
indexed; keep the logger.Error for diagnostics but remove the continue in that
success path so saveTxResult and downstream writes still run.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 54b80e03-b2d9-4fe1-a111-a073182669fc

📥 Commits

Reviewing files that changed from the base of the PR and between 9d88426 and c2c6763.

📒 Files selected for processing (1)
  • internal/indexer/kv_indexer.go

Comment on lines 142 to +154
parsedTx := txs.GetTxByMsgIndex(msgIndex)
if parsedTx == nil {
kv.logger.Error("msg index not found in results", "msgIndex", msgIndex)
continue
if result.Code != abci.CodeTypeOK {
// No ethereum_tx events and no parseable ABCI log — derive
// what we can directly from the decoded message so the tx
// is still recorded and findable by hash.
txResult.GasUsed = ethMsg.GetGas()
txResult.Failed = true
txHash = ethMsg.Hash()
} else {
kv.logger.Error("msg index not found in results for successful tx", "msgIndex", msgIndex)
continue
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid dropping successful EVM messages on positional lookup miss.

At Line 142, GetTxByMsgIndex(msgIndex) is positional; if msgIndex is sparse relative to parsed entries, Line 153 skips indexing entirely for a successful tx (no saveTxResult, receipt, or RPC tx write).

💡 Suggested fix
-				parsedTx := txs.GetTxByMsgIndex(msgIndex)
+				parsedTx := txs.GetTxByMsgIndex(msgIndex)
+				if parsedTx == nil {
+					// Fallback to semantic lookup by ParsedTx.MsgIndex when
+					// positional indexing is sparse/non-contiguous.
+					for i := range txs.Txs {
+						if txs.Txs[i].MsgIndex == msgIndex {
+							parsedTx = &txs.Txs[i]
+							break
+						}
+					}
+				}
 				if parsedTx == nil {
 					if result.Code != abci.CodeTypeOK {

Based on learnings, internal/indexer/**/*.go must implement a KV indexer for EVM transaction results; silently skipping successful EVM messages breaks index completeness.

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

In `@internal/indexer/kv_indexer.go` around lines 142 - 154, The positional lookup
via txs.GetTxByMsgIndex(msgIndex) can return nil for sparse msgIndex values, and
the current branch skips indexing for successful txs; change the nil-handling so
that when parsedTx == nil and result.Code == abci.CodeTypeOK you do NOT
continue/skip but instead construct the necessary tx indexing entities from the
decoded ethMsg (set txResult.GasUsed = ethMsg.GetGas(), set txResult.Failed =
false, set txHash = ethMsg.Hash(), build a receipt/RPC tx entry as the code
normally would when parsedTx exists) and then call the existing saveTxResult /
receipt save / RPC tx write paths (the same functions used for parsedTx cases)
so the successful EVM message is indexed; keep the logger.Error for diagnostics
but remove the continue in that success path so saveTxResult and downstream
writes still run.

Copy link
Copy Markdown

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

🧹 Nitpick comments (2)
internal/indexer/kv_indexer.go (2)

313-313: Downgrade GetByTxHash diagnostic log to Debug level.

This method is invoked for every eth_getTransactionByHash RPC call. Logging at Info level will create significant noise in production. Additionally, consider logging only on error or non-empty results to reduce volume.

♻️ Suggested change
 func (kv *KVIndexer) GetByTxHash(hash common.Hash) (*chaintypes.TxResult, error) {
 	bz, err := kv.db.Get(TxHashKey(hash))
-	kv.logger.Info("[diag] GetByTxHash", "hash", hash.Hex(), "bytesLen", len(bz), "err", err)
+	kv.logger.Debug("[diag] GetByTxHash", "hash", hash.Hex(), "bytesLen", len(bz), "err", err)
 	if err != nil {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/indexer/kv_indexer.go` at line 313, The diagnostic log in
GetByTxHash currently uses kv.logger.Info and logs every call; change it to use
kv.logger.Debug and only emit when there is an error or when the result is
non-empty (e.g., len(bz) > 0) to reduce noise. Update the kv.logger.Info(...)
call to kv.logger.Debug(...) and guard the call so it runs only if err != nil ||
len(bz) > 0, keeping the same fields ("hash", hash.Hex(), "bytesLen", len(bz),
"err", err) for context.

139-139: Downgrade diagnostic logs to Debug level for production.

The [diag] prefix indicates these are debugging aids rather than operational logs. At Info level, these logs will fire for every EVM message in every block, creating significant noise and potential performance overhead in production.

♻️ Suggested change to use Debug level
-				kv.logger.Info("[diag] tx hash from ethMsg (block-gas-limit path)", "height", block.Height, "txIndex", txIndex, "hash", txHash.Hex())
+				kv.logger.Debug("[diag] tx hash from ethMsg (block-gas-limit path)", "height", block.Height, "txIndex", txIndex, "hash", txHash.Hex())
-				kv.logger.Info("[diag] parsedTx lookup", "height", block.Height, "txIndex", txIndex, "msgIndex", msgIndex,
+				kv.logger.Debug("[diag] parsedTx lookup", "height", block.Height, "txIndex", txIndex, "msgIndex", msgIndex,
 					"resultCode", result.Code, "codespace", result.Codespace,
 					"parsedTxNil", parsedTx == nil,
 					"parsedTxsCount", len(txs.Txs),
 				)

Apply similar changes to all other [diag] logs (lines 157, 174, 176).

If these logs are needed temporarily for debugging a specific issue, consider removing them before merging or gating them behind a debug flag.

Also applies to: 144-148, 157-157, 174-177

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

In `@internal/indexer/kv_indexer.go` at line 139, Change the diagnostic "[diag]"
logs from Info to Debug: replace the kv.logger.Info(...) calls that log "[diag]"
messages (e.g., the tx hash log using txHash.Hex()) with kv.logger.Debug(...)
and do the same for the other "[diag]" occurrences in this file (the other
kv.logger.Info calls that include "[diag]" context). Ensure you only adjust log
level and keep the same message keys/arguments so diagnostics remain available
at debug level.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/indexer/kv_indexer.go`:
- Line 313: The diagnostic log in GetByTxHash currently uses kv.logger.Info and
logs every call; change it to use kv.logger.Debug and only emit when there is an
error or when the result is non-empty (e.g., len(bz) > 0) to reduce noise.
Update the kv.logger.Info(...) call to kv.logger.Debug(...) and guard the call
so it runs only if err != nil || len(bz) > 0, keeping the same fields ("hash",
hash.Hex(), "bytesLen", len(bz), "err", err) for context.
- Line 139: Change the diagnostic "[diag]" logs from Info to Debug: replace the
kv.logger.Info(...) calls that log "[diag]" messages (e.g., the tx hash log
using txHash.Hex()) with kv.logger.Debug(...) and do the same for the other
"[diag]" occurrences in this file (the other kv.logger.Info calls that include
"[diag]" context). Ensure you only adjust log level and keep the same message
keys/arguments so diagnostics remain available at debug level.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 961bf409-6988-4fcc-8d24-7f4c2d8d6e02

📥 Commits

Reviewing files that changed from the base of the PR and between c2c6763 and 9fdd225.

📒 Files selected for processing (1)
  • internal/indexer/kv_indexer.go

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.

1 participant