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
EtherscanLikeApi.get_transactions() parses transaction-list entries with evm_inquirer=None and indexer=self (rotkehlchen/externalapis/etherscan_like.py).
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).
- 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).
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
- 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.
- 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.
- 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.
- 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.
- 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.
Summary
During an L2 address transaction refresh, transaction-list entries without
L1FeesPaidtrigger 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 containsl1Fee, the earlier fee request was redundant.This affects Optimism and potentially the other chains using
L2WithL1FeesTransaction(Base and Scroll).Current behavior
EtherscanLikeApi.get_transactions()parses transaction-list entries withevm_inquirer=Noneandindexer=self(rotkehlchen/externalapis/etherscan_like.py).deserialize_evm_transaction()usesL1FeesPaidif present. Otherwise, the indexer path callsget_l1_fee()for each transaction (rotkehlchen/serialization/deserialize.py). Blockscout implements this with/v2/transactions/:hash(rotkehlchen/externalapis/blockscout.py).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).DBL2WithL1FeesTx.add_or_ignore_receipt_data()already readsl1Feefrom 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
L1FeesPaidwhen the list provides it. Otherwise, leave the fee unresolved during list parsing instead of callingindexer.get_l1_fee()for each hash.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.optimism_transactions.l1_feecolumn is nullable, but the current code uses zero as a placeholder and the receipt writer does not persist an explicit zero.L2WithL1FeesTransactions.ensure_tx_data_exists(),l1_feeis stored as TEXT but compared with integer0, andINSERT OR IGNOREcannot replace an existing placeholder.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
l1Feecauses no per-hash Blockscout L1-fee request.l1Feeinvokes the indexer fallback only if no resolved fee is stored.