Skip to content

[Tron Support] Do not panic on Invalid state roots#409

Open
Wizdave97 wants to merge 7 commits intosubquery:mainfrom
polytope-labs:david/tron
Open

[Tron Support] Do not panic on Invalid state roots#409
Wizdave97 wants to merge 7 commits intosubquery:mainfrom
polytope-labs:david/tron

Conversation

@Wizdave97
Copy link

@Wizdave97 Wizdave97 commented Feb 5, 2026

Description

This PR prevents panics when the state root of the fetched Ethereum block is missing or malformed. This fix allows indexing of Events on the Tron network.
The Tron Network Ethereum compatibility RPC layer sometimes returns invalid state roots in block headers.

Also added some fixes for json rpc calls that accept a block number parameter, tron only allows the "latest" tag, this pr ensures that's the case for tron

We also strip unsupported parameters from transaction objects to the tron rpc, fields like type and accessList which are sometimes added by ethers are deleted if the chain id is tron.

With this fix i was able to index events from contracts on the tron network without any errors.

Fixes # (issue)

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist

  • I have tested locally
  • I have performed a self review of my changes
  • Updated any relevant documentation
  • Linked to any relevant issues
  • I have added tests relevant to my changes
  • Any dependent changes have been merged and published in downstream modules
  • My code is up to date with the base branch
  • I have updated relevant changelogs. We suggest using chan

Summary by CodeRabbit

  • New Features
    • Added comprehensive Tron network support to the Ethereum API, enabling users to seamlessly interact with Tron blockchains in addition to Ethereum networks
    • Implemented specialized Tron-optimized network providers with full support for WebSocket and JSON-RPC connection protocols
    • Introduced automatic parameter transformation and validation system for enhanced Tron blockchain compatibility and stability

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 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

This PR adds Tron blockchain network support to the Ethereum API client by introducing Tron-specific JSON-RPC providers that transform method parameters for compatibility, integrating these providers into the main EthereumApi class for automatic use on Tron chains, and implementing utilities for parameter sanitization and block number handling specific to Tron's limitations.

Changes

Cohort / File(s) Summary
Tron Utility Functions
packages/node/src/ethereum/ethers/tron/tron-utils.ts
New file introducing Tron constants (TRON_CHAIN_IDS, TRON_TRANSACTION_METHODS, TRON_BLOCK_NUMBER_METHODS), parameter sanitization function (cleanParamsForTron), block number replacement utility (replaceBlockNumberForTron), and coordinator function (applyTronParamTransforms). Includes documentation of Tron limitations regarding numeric block tags and historical state queries.
Tron Provider Implementations
packages/node/src/ethereum/ethers/tron/tron-provider.ts
New file with three Tron-specific provider classes: TronJsonRpcProvider, TronJsonRpcBatchProvider, and TronWsProvider. Each extends the corresponding Ethers.js provider and overrides the send method to apply Tron parameter transformations before delegating to parent implementation.
EthereumApi Integration
packages/node/src/ethereum/api.ethereum.ts
Imports Tron providers and chain constants. Modifies init() to conditionally use Tron-specific providers (TronWsProvider, TronJsonRpcBatchProvider, or TronJsonRpcProvider) when chainId belongs to TRON_CHAIN_IDS. Updates getBlockPromise() to apply conditional stateRoot handling: replaces '0x' with 64-byte zero value for Tron chains, otherwise hashes stateRoot as before.

Sequence Diagram

sequenceDiagram
    participant Client
    participant EthereumApi
    participant ProviderFactory
    participant TronProvider as TronProvider<br/>(JSON-RPC/WS)
    participant TronUtils
    participant TronNetwork

    Client->>EthereumApi: init(chainId)
    EthereumApi->>EthereumApi: Check if chainId in TRON_CHAIN_IDS
    alt Tron Chain Detected
        EthereumApi->>ProviderFactory: Create TronJsonRpcProvider<br/>or TronWsProvider
        ProviderFactory->>TronProvider: instantiate(url, network)
    else Non-Tron Chain
        EthereumApi->>ProviderFactory: Create standard provider
    end

    Client->>EthereumApi: send(method, params)
    EthereumApi->>TronProvider: send(method, params)
    TronProvider->>TronUtils: applyTronParamTransforms(method,<br/>params, chainId)
    TronUtils->>TronUtils: cleanParamsForTron()
    TronUtils->>TronUtils: replaceBlockNumberForTron()
    TronUtils-->>TronProvider: transformedParams
    TronProvider->>TronNetwork: send(transformedParams)
    TronNetwork-->>TronProvider: result
    TronProvider-->>EthereumApi: result
    EthereumApi-->>Client: result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A Tron-shaped path now opens wide,
With providers dancing side by side,
Block numbers tagged, parameters cleaned,
The smoothest Tron state ever seen!
✨ Hops forward to compatibility's light,
Where chains now work in perfect sight.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding Tron network support with handling for invalid state roots, which is the core focus of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

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

@ianhe8x
Copy link

ianhe8x commented Feb 8, 2026

Thanks, will look into this today.

@Wizdave97
Copy link
Author

Thanks, will look into this today.

Thank you

@Wizdave97
Copy link
Author

@ianhe8x any updates here?

@ianhe8x
Copy link

ianhe8x commented Feb 11, 2026

@ianhe8x any updates here?

sorry for the late reply.
overall, for this PR i would suggest

  1. Removes the try-catch - no longer silently swallowing all errors
  2. Only checks for '0x' - specifically targets the known Tron issue, not generic formatting failures
  3. Checks chain ID - only applies to Tron mainnet/testnet
  4. Preserves normal behavior - formatter.hash() still throws on unexpected errors, which is correct

@Wizdave97
Copy link
Author

@ianhe8x attended to your comments

@Wizdave97 Wizdave97 requested a review from ianhe8x February 18, 2026 10:31
@Wizdave97
Copy link
Author

@ianhe8x Added some new fixes for tron support, please take a look.

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: 1

🧹 Nitpick comments (2)
packages/node/src/ethereum/ethers/tron-utils.ts (1)

7-17: eth_call receives both param-cleaning transforms — worth documenting

eth_call appears in both TRON_TRANSACTION_METHODS and TRON_BLOCK_NUMBER_METHODS, so at every call site both cleanParamsForTron and replaceBlockNumberForTron are applied to it sequentially. A brief comment here (or at the call sites) would make the dual-pass intent explicit for future maintainers.

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

In `@packages/node/src/ethereum/ethers/tron-utils.ts` around lines 7 - 17, Update
the module-level exports TRON_TRANSACTION_METHODS and TRON_BLOCK_NUMBER_METHODS
to include a short clarifying comment that eth_call intentionally appears in
both lists so that both cleanParamsForTron and replaceBlockNumberForTron are
applied in sequence at call sites; mention the dual-pass behavior and reference
the transforming functions cleanParamsForTron and replaceBlockNumberForTron so
future maintainers understand this is intentional and not a duplication bug.
packages/node/src/ethereum/ethers/json-rpc-provider.ts (1)

35-47: Extract duplicated Tron parameter transformation logic

The Tron processing block (lines 35–47) is copy-pasted identically into json-rpc-batch-provider.ts lines 58–70. Extract this into a utility function in tron-utils.ts to eliminate duplication and improve maintainability:

Proposed extraction

In tron-utils.ts:

+/**
+ * Apply all Tron-specific parameter transformations for the given method.
+ */
+export function applyTronParamTransforms(
+  method: string,
+  params: Array<any>,
+  chainId: number,
+): Array<any> {
+  let result = params;
+  if (TRON_TRANSACTION_METHODS.includes(method)) {
+    result = cleanParamsForTron(result, chainId);
+  }
+  if (TRON_BLOCK_NUMBER_METHODS[method] !== undefined) {
+    result = replaceBlockNumberForTron(method, result, chainId);
+  }
+  return result;
+}

Then in both providers' send:

-   let cleanedParams = params;
-   if (TRON_TRANSACTION_METHODS.includes(method)) {
-     cleanedParams = cleanParamsForTron(cleanedParams, chainId);
-   }
-   if (TRON_BLOCK_NUMBER_METHODS[method] !== undefined) {
-     cleanedParams = replaceBlockNumberForTron(method, cleanedParams, chainId);
-   }
+   const cleanedParams = applyTronParamTransforms(method, params, chainId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/node/src/ethereum/ethers/json-rpc-provider.ts` around lines 35 - 47,
Extract the duplicated Tron param transformation into a new utility function
(e.g., normalizeTronParams) in tron-utils.ts and call it from both providers'
send paths; specifically, move the logic that checks TRON_TRANSACTION_METHODS
and TRON_BLOCK_NUMBER_METHODS and that calls cleanParamsForTron(cleanedParams,
chainId) and replaceBlockNumberForTron(method, cleanedParams, chainId) into
normalizeTronParams(method, params, network) (or similar) and replace the
existing inline blocks in json-rpc-provider.ts and json-rpc-batch-provider.ts
with a single call to that function, ensuring you preserve the chainId
derivation from this.network and the original method/params contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/node/src/ethereum/ethers/tron-utils.ts`:
- Around line 44-61: Add a prominent comment inside replaceBlockNumberForTron
(and near TRON_BLOCK_NUMBER_METHODS if helpful) explaining that Tron RPC rejects
numeric block tags and we forcibly replace numeric block parameters with
'latest', and explicitly state the limitation: all state-querying methods
(eth_call, eth_getBalance, eth_getCode, eth_getTransactionCount,
eth_getStorageAt) will return tip/current state rather than point-in-time state
for historical indexing, so SubQL/indexers cannot obtain historical state via
these calls; include the RPC error text ("QUANTITY not supported, just support
TAG as latest") and the JSON-RPC code (-32602) in the comment to make the
limitation and its operational impact clear.

---

Duplicate comments:
In `@packages/node/src/ethereum/ethers/json-rpc-batch-provider.ts`:
- Around line 58-74: The Tron-specific param transformations in
json-rpc-batch-provider.ts duplicate logic from json-rpc-provider.ts and use the
same confusing guard; extract the shared logic into a new function
applyTronParamTransforms(method, params, chainId) in tron-utils.ts that
internally calls TRON_TRANSACTION_METHODS, cleanParamsForTron,
TRON_BLOCK_NUMBER_METHODS, and replaceBlockNumberForTron, then replace the
copied block in json-rpc-batch-provider.ts with a call to that helper; also
compute chainId with a clear safe expression (e.g. this.network?.chainId ?? 0)
before calling the helper to avoid the misleading this.network ? guard.

---

Nitpick comments:
In `@packages/node/src/ethereum/ethers/json-rpc-provider.ts`:
- Around line 35-47: Extract the duplicated Tron param transformation into a new
utility function (e.g., normalizeTronParams) in tron-utils.ts and call it from
both providers' send paths; specifically, move the logic that checks
TRON_TRANSACTION_METHODS and TRON_BLOCK_NUMBER_METHODS and that calls
cleanParamsForTron(cleanedParams, chainId) and replaceBlockNumberForTron(method,
cleanedParams, chainId) into normalizeTronParams(method, params, network) (or
similar) and replace the existing inline blocks in json-rpc-provider.ts and
json-rpc-batch-provider.ts with a single call to that function, ensuring you
preserve the chainId derivation from this.network and the original method/params
contract.

In `@packages/node/src/ethereum/ethers/tron-utils.ts`:
- Around line 7-17: Update the module-level exports TRON_TRANSACTION_METHODS and
TRON_BLOCK_NUMBER_METHODS to include a short clarifying comment that eth_call
intentionally appears in both lists so that both cleanParamsForTron and
replaceBlockNumberForTron are applied in sequence at call sites; mention the
dual-pass behavior and reference the transforming functions cleanParamsForTron
and replaceBlockNumberForTron so future maintainers understand this is
intentional and not a duplication bug.

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.

🧹 Nitpick comments (1)
packages/node/src/ethereum/ethers/tron-utils.ts (1)

7-8: eth_estimateGas is absent from both TRON_TRANSACTION_METHODS and TRON_BLOCK_NUMBER_METHODS

eth_estimateGas(transaction, blockTag) has the same shape as eth_call: its first parameter is a transaction object (susceptible to the same type/accessList rejection) and its second parameter is an optional block tag. If any caller invokes eth_estimateGas on a Tron chain with EIP-2930/EIP-1559 transaction fields, the params will not be cleaned, likely producing a Tron RPC error.

♻️ Suggested addition
 // Methods that accept transaction objects as parameters
-export const TRON_TRANSACTION_METHODS = ['eth_call'];
+export const TRON_TRANSACTION_METHODS = ['eth_call', 'eth_estimateGas'];
 export const TRON_BLOCK_NUMBER_METHODS: Record<string, number> = {
   eth_call: 1,
+  eth_estimateGas: 1,
   eth_getStorageAt: 2,
   eth_getBalance: 1,
   eth_getCode: 1,
   eth_getTransactionCount: 1,
 };

Also applies to: 27-33

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

In `@packages/node/src/ethereum/ethers/tron-utils.ts` around lines 7 - 8, Add
eth_estimateGas to the arrays that treat RPC calls with a transaction object
like eth_call so its params are cleaned before sending to Tron: update
TRON_TRANSACTION_METHODS to include 'eth_estimateGas' and also include
'eth_estimateGas' in TRON_BLOCK_NUMBER_METHODS where transaction+optional
blockTag methods are listed; ensure any logic that strips EIP-2930/EIP-1559
fields (type/accessList/maxFeePerGas/etc.) for eth_call also runs for
eth_estimateGas (refer to TRON_TRANSACTION_METHODS and TRON_BLOCK_NUMBER_METHODS
to find where the cleaning is applied).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/node/src/ethereum/ethers/tron-utils.ts`:
- Around line 7-8: Add eth_estimateGas to the arrays that treat RPC calls with a
transaction object like eth_call so its params are cleaned before sending to
Tron: update TRON_TRANSACTION_METHODS to include 'eth_estimateGas' and also
include 'eth_estimateGas' in TRON_BLOCK_NUMBER_METHODS where
transaction+optional blockTag methods are listed; ensure any logic that strips
EIP-2930/EIP-1559 fields (type/accessList/maxFeePerGas/etc.) for eth_call also
runs for eth_estimateGas (refer to TRON_TRANSACTION_METHODS and
TRON_BLOCK_NUMBER_METHODS to find where the cleaning is applied).

@Wizdave97 Wizdave97 changed the title Do not panic on Invalid state roots [Tron Support] Do not panic on Invalid state roots Feb 18, 2026
@ianhe8x
Copy link

ianhe8x commented Feb 18, 2026

@Wizdave97 If you need to customise rpc provider for Tron, can you do the same way we did for Celo.

check it out here, packages/node/src/ethereum/ethers/celo/celo-provider.ts

@Wizdave97
Copy link
Author

Wizdave97 commented Feb 19, 2026

@ianhe8x I've updated the implementation

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: 1

🧹 Nitpick comments (2)
packages/node/src/ethereum/ethers/tron/tron-utils.ts (1)

7-8: Consider adding eth_estimateGas to TRON_TRANSACTION_METHODS.

eth_estimateGas also receives a transaction object as its first parameter (params: [txObj, blockTag?]). If ethers.js ever calls it with an EIP-1559/EIP-2930 transaction (which would include type and accessList), Tron would reject it with the same error. Adding it here and to TRON_BLOCK_NUMBER_METHODS at index 1 (block tag is optional but at index 1 when present) would make the handling symmetric with the documented intent.

♻️ Proposed addition
-export const TRON_TRANSACTION_METHODS = ['eth_call'];
+export const TRON_TRANSACTION_METHODS = ['eth_call', 'eth_estimateGas'];
 export const TRON_BLOCK_NUMBER_METHODS: Record<string, number> = {
   eth_call: 1,
+  eth_estimateGas: 1,
   eth_getStorageAt: 2,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/node/src/ethereum/ethers/tron/tron-utils.ts` around lines 7 - 8, The
TRON handling currently treats only 'eth_call' as a method that accepts a
transaction object, which misses 'eth_estimateGas' and can allow
EIP-1559/EIP-2930 fields to reach Tron and fail; update the
TRON_TRANSACTION_METHODS array to include 'eth_estimateGas' and also add
'eth_estimateGas' at index 1 of TRON_BLOCK_NUMBER_METHODS (so the optional
blockTag param is handled symmetrically) to ensure transaction objects passed to
estimateGas are sanitized like eth_call.
packages/node/src/ethereum/ethers/tron/tron-provider.ts (1)

16-18: Redundant no-op constructors can be removed.

All three constructors simply delegate to super(url, network) with no additional logic, which is already TypeScript's default behavior when no constructor is declared. Removing them reduces noise.

♻️ Proposed simplification (shown for TronJsonRpcProvider; apply identically to TronJsonRpcBatchProvider and TronWsProvider)
 export class TronJsonRpcProvider extends JsonRpcProvider {
-  constructor(url: string | ConnectionInfo, network?: Networkish) {
-    super(url, network);
-  }
-
   async send(method: string, params: Array<any>): Promise<any> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/node/src/ethereum/ethers/tron/tron-provider.ts` around lines 16 -
18, Remove the redundant no-op constructors that only call super(url, network)
in the Tron provider classes: TronJsonRpcProvider, TronJsonRpcBatchProvider, and
TronWsProvider in tron-provider.ts; delete each empty constructor method so the
classes inherit the default constructor behavior from their base class (i.e.,
remove the constructor(url: string | ConnectionInfo, network?: Networkish) {
super(url, network); } declarations).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/node/src/ethereum/ethers/tron/tron-provider.ts`:
- Around line 20-24: The Tron provider constructors are not being passed the
already-resolved network from const network = await this.client.getNetwork(),
which causes send() (in send(method, params)) to see chainId = 0 until the
provider resolves itself; update the instantiation sites to pass the resolved
network into TronWsProvider, TronJsonRpcBatchProvider and TronJsonRpcProvider
(and apply the same change to the analogous Celo provider constructors) so the
provider instances are constructed with the known network and
applyTronParamTransforms receives the correct chainId immediately.

---

Nitpick comments:
In `@packages/node/src/ethereum/ethers/tron/tron-provider.ts`:
- Around line 16-18: Remove the redundant no-op constructors that only call
super(url, network) in the Tron provider classes: TronJsonRpcProvider,
TronJsonRpcBatchProvider, and TronWsProvider in tron-provider.ts; delete each
empty constructor method so the classes inherit the default constructor behavior
from their base class (i.e., remove the constructor(url: string |
ConnectionInfo, network?: Networkish) { super(url, network); } declarations).

In `@packages/node/src/ethereum/ethers/tron/tron-utils.ts`:
- Around line 7-8: The TRON handling currently treats only 'eth_call' as a
method that accepts a transaction object, which misses 'eth_estimateGas' and can
allow EIP-1559/EIP-2930 fields to reach Tron and fail; update the
TRON_TRANSACTION_METHODS array to include 'eth_estimateGas' and also add
'eth_estimateGas' at index 1 of TRON_BLOCK_NUMBER_METHODS (so the optional
blockTag param is handled symmetrically) to ensure transaction objects passed to
estimateGas are sanitized like eth_call.

@ianhe8x
Copy link

ianhe8x commented Feb 19, 2026

@Wizdave97 if tron doesn't support query a historical states, instead of replacing it to latest, maybe the better way is throwing error? This may lead to unexpected behaviour

@Wizdave97
Copy link
Author

Wizdave97 commented Feb 20, 2026

Throwing an error will break indexing and means the user has to modify these methods manually if they need to use them. How about we just add the behaviour of these calls to the docs? Anyone indexing Tron knows historical queries are not supported. I don't think they would want to go through the hassle of using manual RPC calls to use these methods instead of relying on the api provided by the indexer

@ianhe8x
Copy link

ianhe8x commented Feb 20, 2026

I can't think of a reason why people want to query latest states in a history block in a subquery project. so let people be aware of this is a correct way.

@Wizdave97
Copy link
Author

I can't think of a reason why people want to query latest states in a history block in a subquery project. so let people be aware of this is a correct way.

But Tron only supports latest state queries so they have no choice, without this fix, it's not possible to index Tron with the subquery project.

@ianhe8x
Copy link

ianhe8x commented Feb 22, 2026

in a indexer project, the data flows with the blocks, should not query latest states and utilize latest states in the mapping.
query latest states can be done in application layer, but not in the indexer layer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants