Skip to content

Avoid duplicate L1 fee lookups during L2 transaction refresh #13218

Description

@yabirgb

Summary

During an L2 address transaction refresh, transaction-list entries without L1FeesPaid trigger a separate indexer request per transaction to obtain the L1 fee. For Blockscout, that request is /v2/transactions/:hash. Full-history processing subsequently fetches those transactions' receipts for decoding, already using batched JSON-RPC requests. When a receipt contains l1Fee, the earlier fee request was redundant.

This affects Optimism and potentially the other chains using L2WithL1FeesTransaction (Base and Scroll).

Current behavior

  1. EtherscanLikeApi.get_transactions() parses transaction-list entries with evm_inquirer=None and indexer=self (rotkehlchen/externalapis/etherscan_like.py).
  2. deserialize_evm_transaction() uses L1FeesPaid if present. Otherwise, the indexer path calls get_l1_fee() for each transaction (rotkehlchen/serialization/deserialize.py). Blockscout implements this with /v2/transactions/:hash (rotkehlchen/externalapis/blockscout.py).
  3. Later, get_receipts_for_transactions_missing_them() fetches missing receipts in JSON-RPC batches of 25, falling back to individual receipt requests when batching is unavailable (rotkehlchen/chain/evm/transactions.py). Full-history processing runs this before decoding (rotkehlchen/history/manager.py).
  4. DBL2WithL1FeesTx.add_or_ignore_receipt_data() already reads l1Fee from a receipt and updates the stored fee when it is nonzero (rotkehlchen/db/l2withl1feestx.py).

With N Blockscout transactions lacking L1FeesPaid, this can add N per-hash Blockscout requests before the receipts are fetched.

Proposed approach

  1. Keep using L1FeesPaid when the list provides it. Otherwise, leave the fee unresolved during list parsing instead of calling indexer.get_l1_fee() for each hash.
  2. Use the existing receipt batch to resolve L1 fees. If a receipt contains l1Fee, persist it. If it does not, call the existing indexer fee fallback only when the fee is still unresolved. Do remote lookups outside the database write transaction.
  3. Distinguish an unresolved fee from a valid zero fee. The optimism_transactions.l1_fee column is nullable, but the current code uses zero as a placeholder and the receipt writer does not persist an explicit zero.
  4. Repair existing unresolved rows without repeatedly fetching the full transaction and receipt. In L2WithL1FeesTransactions.ensure_tx_data_exists(), l1_fee is stored as TEXT but compared with integer 0, and INSERT OR IGNORE cannot replace an existing placeholder.
  5. Preserve the direct transaction-by-hash path, which already checks an RPC receipt and falls back to indexers when needed.

Transaction-only refresh behavior

The transaction-only refresh endpoint currently completes after the list query; it does not necessarily collect receipts. Deferring fee resolution could leave a newly saved transaction with a pending fee until receipt collection. Check whether callers require the fee when this endpoint returns. If they do, fetch and save receipts for newly added hashes with the existing batch mechanism before completing refresh, so the later receipt stage skips them.

Acceptance criteria

  • A receipt containing l1Fee causes no per-hash Blockscout L1-fee request.
  • A receipt missing l1Fee invokes the indexer fallback only if no resolved fee is stored.
  • An explicit zero is distinguishable from an unresolved fee, and legacy unresolved rows can be repaired.
  • Receipt batch failure retains the existing per-receipt fallback behavior.
  • A transaction-only refresh has a defined and tested fee-availability behavior.
  • The direct transaction-by-hash path continues to work.
  • Tests verify the number of receipt and indexer fee requests as well as the final stored fee.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions