Skip to content

aj - #45

Open
ajit2903 wants to merge 36 commits into
ajit2903:masterfrom
blockscout:master
Open

aj#45
ajit2903 wants to merge 36 commits into
ajit2903:masterfrom
blockscout:master

Conversation

@ajit2903

@ajit2903 ajit2903 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

GitHub keywords to close any associated issues

Motivation

Why we should merge these changes. If using GitHub keywords to close issues, this is optional as the motivation can be read on the issue page.

Changelog

Enhancements

Things you added that don't break anything. Regression tests for Bug Fixes count as Enhancements.

Bug Fixes

Things you changed that fix bugs. If it fixes a bug but, in so doing, adds a new requirement, removes code, or requires a database reset and reindex, the breaking part of the change should also be added to "Incompatible Changes" below.

Incompatible Changes

Things you broke while doing Enhancements and Bug Fixes. Breaking changes include (1) adding new requirements and (2) removing code. Renaming counts as (2) because a rename is a removal followed by an add.

Upgrading

If you have any Incompatible Changes in the above Changelog, outline how users of prior versions can upgrade once this PR lands or when reviewers are testing locally. A common upgrading step is "Database reset and re-index required".

Checklist for your Pull Request (PR)

  • I verified this PR does not break any public APIs, contracts, or interfaces that external consumers depend on.
  • If I added new functionality, I added tests covering it.
  • If I fixed a bug, I added a regression test to prevent the bug from silently reappearing again.
  • I updated documentation if needed:
  • If I modified API endpoints, I updated the Swagger/OpenAPI schemas accordingly and checked that schemas are asserted in tests, and highlighted the change in the PR description.
  • If I added new DB indices, I checked, that they are not redundant, with PGHero or other tools.
  • If I added/removed chain type, I modified the Github CI matrix and PR labels accordingly.

nikitosing and others added 30 commits July 22, 2026 12:48
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…14623)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@codeant-ai

codeant-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Skipping CodeAnt AI review — this PR changes more than 100 files, which usually means a migration, codemod, or vendored drop. Line-level review on diffs this large produces duplicate findings on the same rewrite pattern and drowns out anything that actually matters.

If you still want a review, comment @codeant-ai : review. For better signal, consider splitting the PR into smaller chunks.

@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

@Qwerty5Uiop is attempting to deploy a commit to the aj Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: eeae818c-f11f-4cb6-ac83-7c61580b0f69

📥 Commits

Reviewing files that changed from the base of the PR and between ac94729 and cf4c6f5.

📒 Files selected for processing (1)
  • apps/indexer/lib/indexer/fetcher/on_demand/token_balance.ex
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/indexer/lib/indexer/fetcher/on_demand/token_balance.ex

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added Eden support, including sponsored transactions, fee-payer details, batched calls, API types, and multi-platform container publishing.
    • Expanded indexing health reporting with deposit and withdrawal status.
    • Added API token TTL headers, configurable realtime batching, and refetch-needed block metrics.
  • Bug Fixes
    • Improved proxy verification, constructor decoding, transaction rendering, exchange-rate handling, and smart-contract verification recovery.
    • Improved realtime event batching and token-balance notifications.
  • Chores
    • Updated the release to version 11.2.4 across supported deployment workflows.

Walkthrough

The release updates version 11.2.4 across applications and Docker workflows. Eden support is added across configuration, JSON-RPC conversion, transaction storage, API rendering, indexing, and image publishing. Health monitoring now tracks deposits and withdrawals. Realtime events and notification delivery now support batching and separate API paths.

Changes

Eden support and release workflows

Layer / File(s) Summary
Eden transaction and repository support
apps/ethereum_jsonrpc/..., apps/explorer/..., config/..., types-package/...
Eden sponsored transactions support fee payers, batched calls, repository selection, persistence, and chain-type exports.
Eden API responses and schemas
apps/block_scout_web/lib/block_scout_web/controllers/api/v2/..., apps/block_scout_web/lib/block_scout_web/views/api/v2/..., apps/block_scout_web/lib/block_scout_web/schemas/api/v2/...
Eden responses expose fee payers, calls, and the sponsored_transaction type.
Eden image workflows
.github/workflows/*eden.yml, .github/workflows/config.yml, .github/workflows/generate-swagger.yml, .github/workflows/public-release.yml
Eden receives CI matrix entries, multi-platform Docker workflows, registry publication, and public image verification.

Health and realtime processing

Layer / File(s) Summary
Health status and indexing metrics
apps/explorer/lib/explorer/chain/health/*, apps/explorer/lib/explorer/chain/*/reader*, apps/explorer/lib/explorer/prometheus/instrumenter.ex, apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex
Deposit and withdrawal readers, gauges, freshness checks, and API status fields are added.
Realtime event batching and notification delivery
apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex, apps/block_scout_web/lib/block_scout_web/realtime_event_handlers/*, apps/explorer/lib/explorer/chain/events/*, apps/block_scout_web/lib/block_scout_web/notifier.ex
Realtime handlers batch and merge events. Database event notifications use bounded bulk expansion. Notification paths separate V1 and V2 behavior.
Transaction rendering and market data
apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex, apps/explorer/lib/explorer/market/*, apps/block_scout_web/test/block_scout_web/views/api/v2/transaction_view_test.exs
Historic exchange rates are loaded by date and reused. Eden transaction fields are added to rendered responses.

Platform maintenance and API corrections

Layer / File(s) Summary
Smart-contract verification and decoding
apps/explorer/lib/explorer/chain/smart_contract*, apps/explorer/lib/explorer/chain/fetcher/*, apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
Verification statuses reconcile after successful verification. Bytecode proxy source lookup is skipped. Tuple decoding supports nested values.
Import, address, and process behavior
apps/explorer/lib/explorer/chain/import*, apps/indexer/lib/indexer/*, apps/indexer/test/*
Import broadcasts exclude placeholder results. Zero-value filtering occurs at persistence. Fee-payer addresses enter address extraction. Buffered tasks can hibernate.
API contracts and response corrections
apps/block_scout_web/lib/block_scout_web/controllers/api/v2/*, apps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.ex, apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex
Countdown fields are renamed and serialized as strings. Fallback statuses and log levels are updated. Temporary-token responses include a TTL header.
Release and maintenance updates
.github/workflows/*, mix.exs, apps/*/mix.exs, docker/Makefile, CHANGELOG.md, config/runtime.exs
Release values use 11.2.4. Runtime batch settings, logging levels, schemas, and changelog entries are updated.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • ajit2903/blockscout#43: Both PRs modify overlapping transaction API, JSON-RPC transaction, and Explorer transaction code for chain-specific transaction data and response handling.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title "aj" is too vague to identify the primary change in this large pull request. Replace "aj" with a concise title that identifies the main changes, such as Eden support and the 11.2.4 release updates.
Description check ❓ Inconclusive The description contains only the default template and does not explain the pull request changes or objectives. Complete the description with the motivation, main changes, testing details, API or database impacts, and upgrade instructions.
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch master
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ajit2903-blockscout Ready Ready Preview Aug 2, 2026 11:55am
ajit2903-blockscout2 Ready Ready Preview Aug 2, 2026 11:55am
blockscout Ready Ready Preview Aug 2, 2026 11:55am
blockscout-a9xo Ready Ready Preview Aug 2, 2026 11:55am
blockscout-e3sk Ready Ready Preview Aug 2, 2026 11:55am
blockscout-g2h9 Ready Ready Preview Aug 2, 2026 11:55am
blockscout-yyq6 Ready Ready Preview Aug 2, 2026 11:55am

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/explorer/lib/explorer/chain/health/monitor.ex (1)

20-22: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Restrict Explorer.Chain.Health.Monitor to indexer mode.

configure/1 starts this process in API mode when enabled. Use configure_mode_dependent_process(Explorer.Chain.Health.Monitor, :indexer) instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/explorer/lib/explorer/chain/health/monitor.ex` around lines 20 - 22,
Update the health monitor startup configuration in configure/1 to use
configure_mode_dependent_process(Explorer.Chain.Health.Monitor, :indexer),
ensuring Explorer.Chain.Health.Monitor starts only in indexer mode while
preserving its existing GenServer start_link implementation.

Source: Coding guidelines

🧹 Nitpick comments (8)
apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex (1)

146-146: 🔒 Security & Privacy | 🔵 Trivial

Preserve security-failure observability.

Production sets the Logger level to :info, so these Logger.debug/1 calls are discarded. Emit a non-debug, rate-limited security event or metric for each failure before relying on abuse detection, audit, or alerting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex`
at line 146, Update the failure handling around the Logger.debug call in
FallbackController to emit a non-debug, rate-limited security event or metric
for every security failure, ensuring it remains observable when production
logging is set to :info. Preserve the existing failure behavior and use the
established abuse-detection, audit, or alerting mechanism where available.
apps/block_scout_web/lib/block_scout_web/plug/logger.ex (1)

36-49: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid an unlinked task for every response.

Task.start/1 creates an unlinked process inside Conn.register_before_send/2, and this code ignores its result. Under sustained load, this adds one process spawn per response without bounded concurrency or start-failure handling. During node shutdown, a pending task can terminate before Logger.log/3 runs. Call Logger.log/3 directly, or use a supervised, bounded logger worker.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/block_scout_web/lib/block_scout_web/plug/logger.ex` around lines 36 -
49, Remove the Task.start wrapper around the Logger.metadata and Logger.log
calls in the before-send callback, and invoke Logger.log directly so each
response does not spawn an unlinked process. Preserve the existing metadata
setup, log message construction, and merged options.
apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex (2)

210-228: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Top-level healthy silently ignores deposits/withdrawals status.

maybe_put_batches documents with a TODO that batches.healthy is intentionally not folded into the top-level healthy flag. maybe_put_deposits_and_withdrawals (Line 220-227) has the identical limitation — a chain can report metadata.deposits.healthy: false while the endpoint still returns healthy: true and HTTP 200 — but there is no comment documenting this as a known gap.

Add a similar TODO, or factor deposits/withdrawals health into the top-level healthy value so operators relying on the top-level flag for alerting are not misled.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex`
around lines 210 - 228, Add a TODO to maybe_put_deposits_and_withdrawals
documenting that deposits.healthy and withdrawals.healthy are currently not
folded into the top-level healthy flag, matching the existing maybe_put_batches
limitation; preserve the current metadata population behavior.

13-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Chain-type-with-messages list is duplicated across two files. health_controller.ex encodes the supported chain types as an atom list (@chain_types_with_messages), while health/monitor.ex encodes the same set as separate deposit_and_withdrawal_module_for_chain_type/1 function-head clauses. Both currently agree on [:arbitrum, :optimism, :scroll, :ethereum], but nothing enforces they stay in sync.

  • apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex#L13-L15: derive @chain_types_with_messages from a single shared source (e.g., a public list exposed by Explorer.Chain.Health.Monitor or a shared config module) instead of hardcoding it separately here.
  • apps/explorer/lib/explorer/chain/health/monitor.ex#L98-L104: expose the chain-type-to-module mapping's keys (or the module itself) so health_controller.ex can reuse them instead of maintaining a parallel list.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex`
around lines 13 - 15, Eliminate the duplicated chain-type list by exposing the
supported chain-type keys or mapping from Explorer.Chain.Health.Monitor near
deposit_and_withdrawal_module_for_chain_type/1, then derive
`@chain_types_with_messages` in health_controller.ex from that shared source
instead of hardcoding the atoms. Update both referenced files:
health_controller.ex lines 13-15 must consume the shared source, and monitor.ex
lines 98-104 must expose it; keep the existing chain types and module mappings
unchanged.
apps/explorer/lib/explorer/chain/health/monitor.ex (1)

106-137: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Batches/deposits/withdrawals handling is triplicated across three files. Each file extends its existing batches-only logic by copy-pasting the same shape twice more (deposits, withdrawals) instead of extracting one parameterized helper keyed by metric name. This increases the chance that a future field rename or bug fix is applied to only some of the copies.

  • apps/explorer/lib/explorer/chain/health/monitor.ex#L106-L137: replace maybe_add_batch_info_to_params/3, maybe_add_deposit_info_to_params/3, and maybe_add_withdrawal_info_to_params/3 with one function parameterized by a metric-name prefix and field map.
  • apps/explorer/lib/explorer/chain/health/monitor.ex#L196-L237: replace get_latest_deposit_info_from_module/1 and get_latest_withdrawal_info_from_module/1 (and, ideally, get_latest_batch_info_from_module/1) with one function that takes the expected response keys as arguments.
  • apps/explorer/lib/explorer/chain/health/helper.ex#L292-L332: replace deposits_indexing_healthy?/1 and withdrawals_indexing_healthy?/1 with one function parameterized by the health-status key and the corresponding :healthy_*_period config key, matching batches_indexing_healthy?/1's shape.
  • apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex#L323-L394: replace deposits_indexing_status/1 and withdrawals_indexing_status/1 with one function parameterized by field names, matching batches_indexing_status/1's shape.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/explorer/lib/explorer/chain/health/monitor.ex` around lines 106 - 137,
Deduplicate the health handling across all listed sites: in
apps/explorer/lib/explorer/chain/health/monitor.ex (106-137), replace the three
maybe_add_*_info_to_params/3 helpers with one prefix- and field-parameterized
helper; in apps/explorer/lib/explorer/chain/health/monitor.ex (196-237), replace
the deposit and withdrawal latest-info functions, and preferably the batch
variant, with one function accepting expected response keys; in
apps/explorer/lib/explorer/chain/health/helper.ex (292-332), unify the deposit
and withdrawal indexing checks into a parameterized helper matching
batches_indexing_healthy?/1; and in
apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex
(323-394), unify the deposit and withdrawal status functions into a
field-parameterized helper matching batches_indexing_status/1.
apps/explorer/lib/explorer/chain/metrics/queries/indexer_metrics.ex (2)

97-118: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated range-filter builder.

Lines 97-118 repeat the reduce block from missing_current_token_balances_count/0 (lines 178-200). Only the column prefix differs. A shared private helper removes the copy and keeps the parameter-index arithmetic in one place.

♻️ Proposed helper
defp build_range_filter(block_ranges, column) do
  {range_conditions, params} =
    Enum.reduce(block_ranges, {[], []}, fn
      first..last//_, {conditions, acc_params} ->
        from = min(first, last)
        to = max(first, last)
        param_index_from = length(acc_params) + 1
        param_index_to = length(acc_params) + 2

        condition = "(#{column} >= $#{param_index_from}::bigint AND #{column} <= $#{param_index_to}::bigint)"

        {[condition | conditions], [to, from | acc_params]}

      start_from, {conditions, acc_params} ->
        param_index = length(acc_params) + 1
        {["#{column} >= $#{param_index}::bigint" | conditions], [start_from | acc_params]}
    end)

  {range_conditions |> Enum.reverse() |> Enum.join(" OR "), Enum.reverse(params)}
end

Then the new function becomes:

-      {range_conditions, params} =
-        Enum.reduce(block_ranges, {[], []}, fn
-          ...
-        end)
-
-      range_filter =
-        range_conditions
-        |> Enum.reverse()
-        |> Enum.join(" OR ")
+      {range_filter, params} = build_range_filter(block_ranges, "b.number")

The column argument comes from module-internal string literals only, so no user input reaches the SQL string.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/explorer/lib/explorer/chain/metrics/queries/indexer_metrics.ex` around
lines 97 - 118, Extract the duplicated range-filter reduction into a private
helper such as build_range_filter/2, parameterized by block_ranges and the
column expression. Replace the inline builders in the current function and
missing_current_token_balances_count/0, passing their respective internal column
literals, while preserving condition ordering and parameter ordering.

120-138: 🚀 Performance & Scalability | 🔵 Trivial

Confirm index support for this count query.

The query scans blocks with consensus AND refetch_needed plus a block-number range. It runs on the hourly metrics schedule with timeout: :infinity. On a large chain a sequential scan over blocks can take a long time and can compete with indexing writes.

A partial index such as blocks (number) WHERE consensus AND refetch_needed keeps this count cheap, because the set of blocks that need refetch is normally small.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/explorer/lib/explorer/chain/metrics/queries/indexer_metrics.ex` around
lines 120 - 138, Add and verify a partial index on blocks(number) covering rows
where consensus and refetch_needed are true, and ensure the migration or schema
change is included with the metrics query using the range_filter. Confirm the
database can use this index for the refetch_needed_blocks_count query without
changing its result or parameters.
apps/explorer/lib/explorer/prometheus/instrumenter.ex (1)

353-396: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider extracting the shared metric preparation logic.

prepare_batch_metric/1, prepare_deposit_metric/1, and prepare_withdrawal_metric/1 now hold three near-identical bodies. Each body repeats the same empty / single / multiple branching and the same average calculation. Only the gauge setters and the result keys differ.

A single private helper keeps the three public functions as thin wrappers. This removes about 90 duplicated lines and makes future changes to the average calculation apply to all three metrics.

♻️ Sketch of the shared helper
# Called as:
#   prepare_metric(deposits, %{
#     number: :latest_deposit_l1_number,
#     timestamp: :latest_deposit_timestamp,
#     average: :average_deposit_time
#   })
defp prepare_metric(items, keys) do
  case items do
    [] ->
      set_gauges(keys, 0, 0, 0)
      {:error, :not_found}

    [item] ->
      set_gauges(keys, item.number, DateTime.to_unix(item.timestamp), 0)
      {:ok, build_result(keys, item.number, item.timestamp, 0)}

    [latest | _] = items ->
      oldest = List.last(items)
      average = div(DateTime.diff(latest.timestamp, oldest.timestamp, :second), length(items) - 1)
      set_gauges(keys, latest.number, DateTime.to_unix(latest.timestamp), average)
      {:ok, build_result(keys, latest.number, latest.timestamp, average)}
  end
end

Also applies to: 440-483

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/explorer/lib/explorer/prometheus/instrumenter.ex` around lines 353 -
396, Extract the duplicated branching and average-calculation logic from
prepare_batch_metric/1, prepare_deposit_metric/1, and
prepare_withdrawal_metric/1 into one private prepare_metric/2 helper. Pass each
metric’s gauge and result-key mapping to the helper, centralize empty,
single-item, and multi-item handling plus gauge updates and result construction,
and keep the three public functions as thin wrappers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/pre-release-eden.yml:
- Line 26: Disable persisted checkout credentials for the actions/checkout steps
in .github/workflows/pre-release-eden.yml (lines 26-26),
.github/workflows/publish-docker-image-for-eden.yml (lines 20-20), and
.github/workflows/release-eden.yml (lines 23-23) by configuring
persist-credentials as false under each checkout action.

In
`@apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex`:
- Around line 189-202: Update transactions_necessity_by_association/0 to add the
Eden-specific fee_payer_address preload with scam_badge, names, smart_contract,
and proxy_implementations_association(), matching the existing :eden branch in
transaction/2. Preserve the current preload behavior for other networks.

In `@apps/block_scout_web/lib/block_scout_web/notifier.ex`:
- Around line 421-435: Update handle_event/1 to deduplicate
address_current_token_balances before grouping and broadcasting, using
address_hash, token_contract_address_hash, and token_id as the identity key. For
each key, retain the entry with the greatest block_number, then value_fetched_at
as the tie-breaker, before calculating the top 50 and overflow behavior.

In `@apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex`:
- Around line 79-85: Update handle_event_batch/1 to safely read max_batch_size
from the application configuration, falling back to 1 when the shared
configuration value is absent, so subtraction occurs only on a valid numeric
value before calling drain_events/2.

In `@apps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.ex`:
- Around line 34-38: Update the estimated_time_in_seconds field in the countdown
schema to include the existing General.float_pattern() constraint while
retaining type: :string, so non-negative float strings such as "150.0" remain
valid and negative or non-numeric values are rejected.

In `@apps/explorer/lib/explorer/chain/events/listener.ex`:
- Around line 55-70: Update expand_payloads/1 to detect when a parsed {:id, id}
payload has no matching entry in id_to_data before returning the wrapped result.
Log the missing notification ID through the module’s existing logging mechanism,
while preserving the current output for found IDs and direct {:data, data}
payloads.
- Around line 32-53: The batch processing in handle_info/2 currently crashes the
listener when decode_payload!/1 or broadcast/1 fails, losing the remaining
drained notifications. Wrap each expanded_payload’s decode-and-broadcast
pipeline in per-item error handling, logging or discarding only the failing
payload while allowing Enum.each to continue processing all other events and
return {:noreply, state}.

In
`@apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex`:
- Line 272: Update the proxy eligibility flow in maybe_fetch_address/1 to call
Implementation.get_proxy_implementations/1 with api?: false, ensuring the check
uses the primary Repo rather than Repo.replica().

In `@apps/explorer/lib/explorer/chain/import/runner/transactions.ex`:
- Around line 290-363: Align the transaction `calls` field type with the
database migration by changing its schema representation from an array of maps
to a single map matching the `jsonb` column, while preserving the existing
`default_on_conflict` handling. Do not alter `fee_payer_address_hash`, which
already matches the `belongs_to` association and `:bytea` column.

In `@apps/explorer/lib/explorer/chain/optimism/reader.ex`:
- Around line 97-113: Add the consensus predicate to the inner join in
get_latest_withdrawal_info/1 so l2_block.consensus must be true alongside the
existing block-number match. Leave the ordering, limit, selected fields, and
Instrumenter.prepare_withdrawal_metric/1 call unchanged.

In `@apps/explorer/lib/explorer/chain/smart_contract.ex`:
- Around line 1025-1041: Extract the duplicated transaction-failure Logger.error
logic from create_smart_contract/3 and update_smart_contract/3 into a private
helper such as log_transaction_failure/4, parameterized by action, address_hash,
failed_operation, and failed_value. Replace both error clauses with calls to the
helper, passing "create" and "update" respectively, while preserving their
existing {:error, failed_value} returns.
- Around line 913-943: Wrap the post-success call to
VerificationStatus.set_pending_statuses_to_passed(address_hash) in the
create_or_update_smart_contract flow with exception handling so reconciliation
failures are contained and the original successful result remains {:ok, _}.
Preserve the existing reconciliation attempt and log the rescued error before
continuing.

In `@apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex`:
- Around line 111-113: Add a composite database index on
smart_contract_verification_statuses covering contract_address_hash and status
to support the update query in verification_status.ex and the backfill migration
in
apps/explorer/priv/repo/migrations/20260724144709_reconcile_pending_smart_contract_verification_statuses.exs
(lines 5-13); update both affected sites as needed, using the project’s
migration/index conventions.

In `@apps/explorer/lib/explorer/microservice_interfaces/multichain_search.ex`:
- Line 895: Update compose_balances_queue/2 to deduplicate balance entries by
the Repo.insert_all/3 conflict key (address_hash,
token_contract_address_hash_or_native, and token_id) before sorting and
queueing. Ensure send_data_to_queue/1 receives at most one entry per key while
preserving the existing value and updated_at replacement behavior.

In
`@apps/explorer/priv/eden/migrations/20260730065928_add_eden_transaction_fields.exs`:
- Line 8: Update the migration’s calls column definition to use the array type
expected by Explorer.Chain.Transaction and the Eden import binding, rather than
scalar :jsonb; verify the Eden transaction-import test passes against a database
migrated from scratch.

In `@apps/indexer/test/indexer/buffered_task_test.exs`:
- Line 247: Update noreply_maybe_hibernate/1 in
apps/indexer/lib/indexer/buffered_task.ex to return {:noreply, state,
:hibernate} when flushing leaves no work, while preserving the two-element reply
for non-empty flushes. Ensure both assertions at
apps/indexer/test/indexer/buffered_task_test.exs lines 247-247 and 274-274
remain valid after the empty-flush and shed-work recovery paths use the
hibernating reply.

---

Outside diff comments:
In `@apps/explorer/lib/explorer/chain/health/monitor.ex`:
- Around line 20-22: Update the health monitor startup configuration in
configure/1 to use
configure_mode_dependent_process(Explorer.Chain.Health.Monitor, :indexer),
ensuring Explorer.Chain.Health.Monitor starts only in indexer mode while
preserving its existing GenServer start_link implementation.

---

Nitpick comments:
In
`@apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex`:
- Around line 210-228: Add a TODO to maybe_put_deposits_and_withdrawals
documenting that deposits.healthy and withdrawals.healthy are currently not
folded into the top-level healthy flag, matching the existing maybe_put_batches
limitation; preserve the current metadata population behavior.
- Around line 13-15: Eliminate the duplicated chain-type list by exposing the
supported chain-type keys or mapping from Explorer.Chain.Health.Monitor near
deposit_and_withdrawal_module_for_chain_type/1, then derive
`@chain_types_with_messages` in health_controller.ex from that shared source
instead of hardcoding the atoms. Update both referenced files:
health_controller.ex lines 13-15 must consume the shared source, and monitor.ex
lines 98-104 must expose it; keep the existing chain types and module mappings
unchanged.

In
`@apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex`:
- Line 146: Update the failure handling around the Logger.debug call in
FallbackController to emit a non-debug, rate-limited security event or metric
for every security failure, ensuring it remains observable when production
logging is set to :info. Preserve the existing failure behavior and use the
established abuse-detection, audit, or alerting mechanism where available.

In `@apps/block_scout_web/lib/block_scout_web/plug/logger.ex`:
- Around line 36-49: Remove the Task.start wrapper around the Logger.metadata
and Logger.log calls in the before-send callback, and invoke Logger.log directly
so each response does not spawn an unlinked process. Preserve the existing
metadata setup, log message construction, and merged options.

In `@apps/explorer/lib/explorer/chain/health/monitor.ex`:
- Around line 106-137: Deduplicate the health handling across all listed sites:
in apps/explorer/lib/explorer/chain/health/monitor.ex (106-137), replace the
three maybe_add_*_info_to_params/3 helpers with one prefix- and
field-parameterized helper; in
apps/explorer/lib/explorer/chain/health/monitor.ex (196-237), replace the
deposit and withdrawal latest-info functions, and preferably the batch variant,
with one function accepting expected response keys; in
apps/explorer/lib/explorer/chain/health/helper.ex (292-332), unify the deposit
and withdrawal indexing checks into a parameterized helper matching
batches_indexing_healthy?/1; and in
apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex
(323-394), unify the deposit and withdrawal status functions into a
field-parameterized helper matching batches_indexing_status/1.

In `@apps/explorer/lib/explorer/chain/metrics/queries/indexer_metrics.ex`:
- Around line 97-118: Extract the duplicated range-filter reduction into a
private helper such as build_range_filter/2, parameterized by block_ranges and
the column expression. Replace the inline builders in the current function and
missing_current_token_balances_count/0, passing their respective internal column
literals, while preserving condition ordering and parameter ordering.
- Around line 120-138: Add and verify a partial index on blocks(number) covering
rows where consensus and refetch_needed are true, and ensure the migration or
schema change is included with the metrics query using the range_filter. Confirm
the database can use this index for the refetch_needed_blocks_count query
without changing its result or parameters.

In `@apps/explorer/lib/explorer/prometheus/instrumenter.ex`:
- Around line 353-396: Extract the duplicated branching and average-calculation
logic from prepare_batch_metric/1, prepare_deposit_metric/1, and
prepare_withdrawal_metric/1 into one private prepare_metric/2 helper. Pass each
metric’s gauge and result-key mapping to the helper, centralize empty,
single-item, and multi-item handling plus gauge updates and result construction,
and keep the three public functions as thin wrappers.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: db3554b8-1041-4f16-b614-71ffd5b1b6e7

📥 Commits

Reviewing files that changed from the base of the PR and between 4b98ee0 and 1bcac5c.

⛔ Files ignored due to path filters (2)
  • mix.lock is excluded by !**/*.lock
  • types-package/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (148)
  • .github/workflows/config.yml
  • .github/workflows/generate-swagger.yml
  • .github/workflows/pre-release-arbitrum.yml
  • .github/workflows/pre-release-celo.yml
  • .github/workflows/pre-release-eden.yml
  • .github/workflows/pre-release-eth.yml
  • .github/workflows/pre-release-filecoin.yml
  • .github/workflows/pre-release-fuse.yml
  • .github/workflows/pre-release-gnosis.yml
  • .github/workflows/pre-release-optimism.yml
  • .github/workflows/pre-release-rootstock.yml
  • .github/workflows/pre-release-scroll.yml
  • .github/workflows/pre-release-zilliqa.yml
  • .github/workflows/pre-release-zksync.yml
  • .github/workflows/pre-release.yml
  • .github/workflows/public-release.yml
  • .github/workflows/publish-docker-image-custom-build.yml
  • .github/workflows/publish-docker-image-every-push.yml
  • .github/workflows/publish-docker-image-for-arbitrum.yml
  • .github/workflows/publish-docker-image-for-celo.yml
  • .github/workflows/publish-docker-image-for-eden.yml
  • .github/workflows/publish-docker-image-for-eth-sepolia.yml
  • .github/workflows/publish-docker-image-for-eth.yml
  • .github/workflows/publish-docker-image-for-filecoin.yml
  • .github/workflows/publish-docker-image-for-fuse.yml
  • .github/workflows/publish-docker-image-for-gnosis-chain.yml
  • .github/workflows/publish-docker-image-for-optimism-exeperimental.yml
  • .github/workflows/publish-docker-image-for-optimism.yml
  • .github/workflows/publish-docker-image-for-rootstock.yml
  • .github/workflows/publish-docker-image-for-scroll.yml
  • .github/workflows/publish-docker-image-for-zetachain.yml
  • .github/workflows/publish-docker-image-for-zilliqa.yml
  • .github/workflows/publish-docker-image-for-zksync.yml
  • .github/workflows/publish-docker-image-old-ui.yml
  • .github/workflows/release-arbitrum.yml
  • .github/workflows/release-celo.yml
  • .github/workflows/release-default.yml
  • .github/workflows/release-eden.yml
  • .github/workflows/release-eth.yml
  • .github/workflows/release-filecoin.yml
  • .github/workflows/release-fuse.yml
  • .github/workflows/release-gnosis.yml
  • .github/workflows/release-optimism.yml
  • .github/workflows/release-rootstock.yml
  • .github/workflows/release-scroll.yml
  • .github/workflows/release-zetachain.yml
  • .github/workflows/release-zilliqa.yml
  • .github/workflows/release-zksync.yml
  • CHANGELOG.md
  • apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex
  • apps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.ex
  • apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex
  • apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex
  • apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/solidity_scan_controller.ex
  • apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex
  • apps/block_scout_web/lib/block_scout_web/notifier.ex
  • apps/block_scout_web/lib/block_scout_web/plug/logger.ex
  • apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
  • apps/block_scout_web/lib/block_scout_web/realtime_event_handlers/main.ex
  • apps/block_scout_web/lib/block_scout_web/realtime_event_handlers/main_page.ex
  • apps/block_scout_web/lib/block_scout_web/realtime_event_handlers/smart_contract.ex
  • apps/block_scout_web/lib/block_scout_web/realtime_event_handlers/token_transfer.ex
  • apps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.ex
  • apps/block_scout_web/lib/block_scout_web/schemas/api/v2/smart_contract.ex
  • apps/block_scout_web/lib/block_scout_web/schemas/api/v2/transaction.ex
  • apps/block_scout_web/lib/block_scout_web/views/api/v2/celo_view.ex
  • apps/block_scout_web/lib/block_scout_web/views/api/v2/eden_view.ex
  • apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
  • apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex
  • apps/block_scout_web/mix.exs
  • apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs
  • apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs
  • apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
  • apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs
  • apps/block_scout_web/test/block_scout_web/notifier_subscriber_filter_test.exs
  • apps/block_scout_web/test/block_scout_web/rate_limit_test.exs
  • apps/block_scout_web/test/block_scout_web/views/api/v2/transaction_view_test.exs
  • apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
  • apps/ethereum_jsonrpc/mix.exs
  • apps/ethereum_jsonrpc/test/ethereum_jsonrpc/eden_test.exs
  • apps/explorer/config/dev.exs
  • apps/explorer/config/prod.exs
  • apps/explorer/config/test.exs
  • apps/explorer/lib/explorer/chain/address/counters.ex
  • apps/explorer/lib/explorer/chain/arbitrum/reader/common.ex
  • apps/explorer/lib/explorer/chain/decoding_helper.ex
  • apps/explorer/lib/explorer/chain/ethereum/reader.ex
  • apps/explorer/lib/explorer/chain/events/listener.ex
  • apps/explorer/lib/explorer/chain/events/publisher.ex
  • apps/explorer/lib/explorer/chain/events/subscriber.ex
  • apps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.ex
  • apps/explorer/lib/explorer/chain/health/helper.ex
  • apps/explorer/lib/explorer/chain/health/monitor.ex
  • apps/explorer/lib/explorer/chain/import.ex
  • apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
  • apps/explorer/lib/explorer/chain/import/runner/transactions.ex
  • apps/explorer/lib/explorer/chain/log.ex
  • apps/explorer/lib/explorer/chain/metrics/queries/indexer_metrics.ex
  • apps/explorer/lib/explorer/chain/optimism/reader.ex
  • apps/explorer/lib/explorer/chain/scroll/reader.ex
  • apps/explorer/lib/explorer/chain/smart_contract.ex
  • apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex
  • apps/explorer/lib/explorer/chain/transaction.ex
  • apps/explorer/lib/explorer/market/market.ex
  • apps/explorer/lib/explorer/market/market_history.ex
  • apps/explorer/lib/explorer/microservice_interfaces/multichain_search.ex
  • apps/explorer/lib/explorer/prometheus/instrumenter.ex
  • apps/explorer/lib/explorer/repo.ex
  • apps/explorer/lib/explorer/token/metadata_retriever.ex
  • apps/explorer/mix.exs
  • apps/explorer/priv/eden/migrations/20260730065928_add_eden_transaction_fields.exs
  • apps/explorer/priv/repo/migrations/20260724144709_reconcile_pending_smart_contract_verification_statuses.exs
  • apps/explorer/test/explorer/chain/decoding_helper_test.exs
  • apps/explorer/test/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand_test.exs
  • apps/explorer/test/explorer/chain/health/helper_test.exs
  • apps/explorer/test/explorer/chain/import/runner/eden/transactions_test.exs
  • apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs
  • apps/explorer/test/explorer/chain/metrics/indexer_metrics_test.exs
  • apps/explorer/test/explorer/chain/smart_contract/verification_status_test.exs
  • apps/explorer/test/explorer/chain/smart_contract_test.exs
  • apps/explorer/test/explorer/chain/transaction_test.exs
  • apps/explorer/test/explorer/market/market_history_test.exs
  • apps/indexer/lib/indexer/block/catchup/fetcher.ex
  • apps/indexer/lib/indexer/block/realtime/fetcher.ex
  • apps/indexer/lib/indexer/buffered_task.ex
  • apps/indexer/lib/indexer/fetcher/on_demand/token_balance.ex
  • apps/indexer/lib/indexer/fetcher/token_balance/current.ex
  • apps/indexer/lib/indexer/prometheus/instrumenter.ex
  • apps/indexer/lib/indexer/prometheus/metrics.ex
  • apps/indexer/lib/indexer/transform/addresses.ex
  • apps/indexer/mix.exs
  • apps/indexer/test/indexer/buffered_task_test.exs
  • apps/indexer/test/indexer/fetcher/internal_transaction_test.exs
  • apps/indexer/test/indexer/fetcher/on_demand/token_balance_test.exs
  • apps/indexer/test/indexer/transform/addresses.exs
  • apps/nft_media_handler/mix.exs
  • apps/utils/mix.exs
  • config/config_helper.exs
  • config/runtime.exs
  • config/runtime/dev.exs
  • config/runtime/prod.exs
  • cspell.json
  • docker-compose/envs/common-blockscout.env
  • docker/Makefile
  • mix.exs
  • rel/config.exs
  • types-package/index.ts
  • types-package/scripts/chain-types.sh
💤 Files with no reviewable changes (1)
  • apps/indexer/lib/indexer/block/realtime/fetcher.ex

env:
RELEASE_VERSION: 11.2.4
steps:
- uses: actions/checkout@v5

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Disable persisted checkout credentials.

These jobs grant packages: write. actions/checkout stores its token in the local Git configuration by default. The later local action and build steps do not need that checkout credential because setup-repo receives its token explicitly.

  • .github/workflows/pre-release-eden.yml#L26-L26: add with: persist-credentials: false.
  • .github/workflows/publish-docker-image-for-eden.yml#L20-L20: add with: persist-credentials: false.
  • .github/workflows/release-eden.yml#L23-L23: add with: persist-credentials: false.
🧰 Tools
🪛 zizmor (1.28.0)

[warning] 26-26: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

📍 Affects 3 files
  • .github/workflows/pre-release-eden.yml#L26-L26 (this comment)
  • .github/workflows/publish-docker-image-for-eden.yml#L20-L20
  • .github/workflows/release-eden.yml#L23-L23
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pre-release-eden.yml at line 26, Disable persisted
checkout credentials for the actions/checkout steps in
.github/workflows/pre-release-eden.yml (lines 26-26),
.github/workflows/publish-docker-image-for-eden.yml (lines 20-20), and
.github/workflows/release-eden.yml (lines 23-23) by configuring
persist-credentials as false under each checkout action.

Source: Linters/SAST tools

Comment on lines 421 to 435
@current_token_balances_limit 50
def handle_event(
{:chain_event, :address_current_token_balances, type,
%{address_current_token_balances: address_current_token_balances, address_hash: address_hash}}
)
def handle_event({:chain_event, :address_current_token_balances, type, address_current_token_balances})
when type in [:realtime, :on_demand] do
if address_has_subscribers?(address_hash) do
address_current_token_balances
|> Repo.preload(token: Reputation.reputation_association())
address_current_token_balances
|> Enum.filter(&address_has_subscribers?(&1.address_hash))
|> Repo.preload(token: Reputation.reputation_association())
|> Enum.group_by(& &1.address_hash)
|> Enum.each(fn {address_hash, balances} ->
balances
|> Enum.group_by(& &1.token_type)
|> Enum.each(fn {token_type, balances} ->
broadcast_token_balances(address_hash, token_type, balances)
|> Enum.each(fn {token_type, token_type_balances} ->
broadcast_token_balances(address_hash, token_type, token_type_balances)
end)
end
end

def handle_event({:chain_event, :address_current_token_balances, :realtime, _empty_balances_params}) do
# Don't broadcast empty balances params from realtime block fetcher
:ok
end)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm CurrentTokenBalance's unique-key fields for the dedup fix.
ast-grep run --pattern 'schema "address_current_token_balances" do $$$ end' --lang elixir apps/explorer/lib/explorer/chain/address/current_token_balance.ex 2>/dev/null
fd -i current_token_balance -e ex apps/explorer | xargs -r cat -n

Repository: ajit2903/blockscout

Length of output: 41289


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- notifier handlers and broadcast ---'
sed -n '400,455p;560,635p;900,970p' apps/block_scout_web/lib/block_scout_web/notifier.ex
printf '%s\n' '--- realtime event merge configuration and drain logic ---'
fd -i realtime_event_handler -e ex apps | xargs -r -n1 sh -c 'echo "--- $0"; sed -n "1,260p" "$0"'
printf '%s\n' '--- CurrentTokenBalance writes and conflict semantics ---'
rg -n -C 5 'address_current_token_balances|uniq_by|sort_by|default_on_conflict|insert_changes_list_with_and_without_token_id|mergeable_events' apps/block_scout_web apps/explorer | head -n 500
printf '%s\n' '--- database constraints and indexes ---'
rg -n -C 4 'current_token_balances.*(unique|index)|COALESCE\\(token_id|token_id.*unique|unique_index' apps/explorer/priv apps/explorer -g '*.ex' -g '*.sql' 2>/dev/null | head -n 300

Repository: ajit2903/blockscout

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- broadcast_token_balances implementation ---'
rg -n -C 18 'defp? broadcast_token_balances|`@current_token_balances_limit`|overflow' apps/block_scout_web/lib/block_scout_web/notifier.ex
printf '%s\n' '--- notifier token-balance tests ---'
sed -n '420,540p' apps/block_scout_web/test/block_scout_web/notifier_subscriber_filter_test.exs
rg -n -C 12 'updated_token_balances|broadcast_token_balances|overflow|current_token_balances' apps/block_scout_web/test -g '*.exs' | head -n 350
printf '%s\n' '--- CurrentTokenBalance migrations and constraints ---'
rg -l -i 'address_current_token_balances' apps/explorer/priv apps/explorer -g '*.ex' -g '*.sql' | head -n 100
rg -n -i -C 8 'address_current_token_balances|coalesce\\(token_id|current_token_balances.*index' apps/explorer/priv apps/explorer -g '*.ex' -g '*.sql' | grep -E -B 8 -A 8 'address_current_token_balances|coalesce\\(token_id|current_token_balances.*index' | head -n 500
printf '%s\n' '--- event producers ---'
rg -n -C 10 'address_current_token_balances' apps -g '*.ex' | head -n 350

Repository: ajit2903/blockscout

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- exact broadcast and event call sites ---'
rg -n -C 8 'broadcast\\(:address_current_token_balances|broadcast\\([^)]*address_current_token_balances|publish.*address_current_token_balances|address_current_token_balances.*broadcast' apps/indexer apps/explorer apps/block_scout_web -g '*.ex' | head -n 300
printf '%s\n' '--- token balance fetcher files ---'
fd -i token_balance -e ex apps/indexer apps/explorer | head -n 100
printf '%s\n' '--- current-balance event producer context ---'
rg -n -C 12 'address_current_token_balances' apps/indexer -g '*.ex' | head -n 450
printf '%s\n' '--- table creation and unique constraints ---'
rg -n -F -C 8 'address_current_token_balances' apps/explorer/priv/repo/migrations apps/explorer/priv/repo/migrations/scripts -g '*.exs' -g '*.sql' | head -n 500
printf '%s\n' '--- notifier function body with line numbers ---'
sed -n '505,550p' apps/block_scout_web/lib/block_scout_web/notifier.ex

Repository: ajit2903/blockscout

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- current token balance fetch and filtering ---'
sed -n '1,270p' apps/indexer/lib/indexer/fetcher/token_balance/current.ex
sed -n '1,145p' apps/indexer/lib/indexer/token_balances.ex
sed -n '180,235p' apps/indexer/lib/indexer/fetcher/on_demand/token_balance.ex
printf '%s\n' '--- current token balance importer ordering and returned rows ---'
sed -n '1,135p' apps/indexer/lib/indexer/fetcher/current_token_balance_importer.ex
sed -n '612,770p' apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex
printf '%s\n' '--- migration/index definitions only ---'
rg -n -F -C 3 'create table(:address_current_token_balances' apps/explorer/priv/repo/migrations -g '*.exs'
rg -n -F -C 3 'COALESCE(token_id' apps/explorer/priv/repo/migrations apps/explorer/lib -g '*.exs' -g '*.ex'

Repository: ajit2903/blockscout

Length of output: 24691


Deduplicate merged address_current_token_balances entries per address/token.

Use {address_hash, token_contract_address_hash, token_id} as the key. Keep the entry with the greatest {block_number, value_fetched_at}. block_number alone does not distinguish updates fetched at the same block. Duplicate entries can affect the rendered top 50 balances and inflate overflow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/block_scout_web/lib/block_scout_web/notifier.ex` around lines 421 - 435,
Update handle_event/1 to deduplicate address_current_token_balances before
grouping and broadcasting, using address_hash, token_contract_address_hash, and
token_id as the identity key. For each key, retain the entry with the greatest
block_number, then value_fetched_at as the tie-breaker, before calculating the
top 50 and overflow behavior.

Comment on lines +79 to +85
@spec handle_event_batch(term()) :: :ok
def handle_event_batch(event) do
[event]
|> drain_events(Application.get_env(:block_scout_web, __MODULE__)[:max_batch_size] - 1)
|> merge_events()
|> Enum.each(&Notifier.handle_event/1)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check how max_batch_size is configured for realtime event handlers.
rg -n "RealtimeEventHandler" -g '*.exs' -A5 -B2
rg -n "max_batch_size" -g '*.exs' -A5 -B2

Repository: ajit2903/blockscout

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
wc -l apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
sed -n '1,180p' apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex

printf '%s\n' '--- handler modules and macro usage ---'
rg -n "use BlockScoutWeb.RealtimeEventHandler|defmodule .*Realtime|handle_event_batch|max_batch_size" apps/block_scout_web apps -g '*.ex' -g '*.exs' | head -250

printf '%s\n' '--- application configuration files ---'
git ls-files '*.exs' '*.ex' | xargs rg -n -C 3 "max_batch_size|RealtimeEventHandler" || true

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- all realtime batch configuration ---'
rg -n -C 4 "config\s+:block_scout_web,\s+BlockScoutWeb\.RealtimeEventHandler|REALTIME_EVENT_HANDLERS_BATCH_SIZE|BlockScoutWeb\.RealtimeEventHandler" config apps/block_scout_web -g '*.exs' -g '*.ex' --glob '!**/test/**'

printf '%s\n' '--- relevant application startup ---'
sed -n '70,115p' apps/block_scout_web/lib/block_scout_web/application.ex
sed -n '125,145p' config/runtime.exs

printf '%s\n' '--- handler module definitions ---'
for f in apps/block_scout_web/lib/block_scout_web/realtime_event_handlers/*.ex; do
  echo "--- $f"
  sed -n '1,80p' "$f"
done

printf '%s\n' '--- test configuration references ---'
rg -n -C 3 "REALTIME_EVENT_HANDLERS_BATCH_SIZE|RealtimeEventHandler" . -g '*.exs' -g '*.ex' --glob '!deps/**' --glob '!_build/**' | head -200

Repository: ajit2903/blockscout

Length of output: 33055


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- config loading ---'
rg -n -C 5 "runtime\.exs|import_config|Config\.|config_env" config/config.exs config -g '*.exs' | head -200

printf '%s\n' '--- application mode and handler startup conditions ---'
rg -n -C 5 "disable_api|`@disable_api`|setup_and_define_children|RealtimeEventHandlers" apps/block_scout_web/lib/block_scout_web/application.ex config/runtime.exs

printf '%s\n' '--- project test/runtime configuration metadata ---'
rg -n -C 3 "config/runtime|MIX_ENV|mix test|runtime.exs" mix.exs config apps/block_scout_web -g '*.exs' -g '*.ex' | head -200

Repository: ajit2903/blockscout

Length of output: 12855


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- environment-specific configuration ---'
for f in config/dev.exs config/test.exs config/prod.exs; do
  if [ -f "$f" ]; then
    echo "--- $f"
    rg -n -C 3 "RealtimeEventHandler|max_batch_size|runtime\.exs|import_config" "$f" || true
  fi
done

printf '%s\n' '--- application-level configuration ---'
git ls-files 'apps/*/config/*.exs' 'apps/*/config/**/*.exs' | xargs rg -n -C 3 "RealtimeEventHandler|max_batch_size" || true

printf '%s\n' '--- runtime configuration usage in project docs/scripts ---'
rg -n -C 3 "runtime\.exs|REALTIME_EVENT_HANDLERS_BATCH_SIZE" README.md docs docker* .github scripts config apps -g '*' 2>/dev/null | head -200 || true

Repository: ajit2903/blockscout

Length of output: 2513


🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
from pathlib import Path
import re

handler = Path("apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex").read_text()
runtime = Path("config/runtime.exs").read_text()
concrete = sorted(Path("apps/block_scout_web/lib/block_scout_web/realtime_event_handlers").glob("*.ex"))

assert "Application.get_env(:block_scout_web, __MODULE__)[:max_batch_size] - 1" in handler
assert handler.count("BlockScoutWeb.RealtimeEventHandler.handle_event_batch(event)") == 1
assert re.search(
    r"config\s+:block_scout_web,\s+BlockScoutWeb\.RealtimeEventHandler,\s*\n\s*max_batch_size:\s*"
    r"ConfigHelper\.parse_integer_env_var\(\"REALTIME_EVENT_HANDLERS_BATCH_SIZE\",\s*100,\s*min:\s*1\)",
    runtime,
)
assert len(concrete) == 4
for path in concrete:
    text = path.read_text()
    assert "use BlockScoutWeb.RealtimeEventHandler" in text, path
    assert "def handle_event_batch" not in text, path

# Model the missing-key path without loading repository code.
config = {}
max_batch_size = config.get("max_batch_size")
try:
    max_batch_size - 1
except TypeError:
    missing_key_fails = True
else:
    missing_key_fails = False
assert missing_key_fails

print("shared module lookup: present")
print("runtime config: one shared key, default 100, minimum 1")
print("concrete handlers: all four delegate to the shared function")
print("missing max_batch_size: subtraction fails before drain_events")
PY

Repository: ajit2903/blockscout

Length of output: 364


Add a fallback for max_batch_size.

If the shared configuration is absent, nil - 1 raises ArithmeticError before drain_events/2 runs. Use a safe default such as 1.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex` around
lines 79 - 85, Update handle_event_batch/1 to safely read max_batch_size from
the application configuration, falling back to 1 when the shared configuration
value is absent, so subtraction occurs only on a valid numeric value before
calling drain_events/2.

Comment on lines +34 to 38
estimated_time_in_seconds: %Schema{
type: :string,
description: "Estimated time in seconds until the target block is reached",
minimum: 0,
example: 404_868.0
example: "404868.0"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

file="apps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.ex"

printf '%s\n' '--- target file ---'
cat -n "$file"

printf '%s\n' '--- countdown references ---'
rg -n -C 4 'estimated_time_in_seconds|Countdown' apps test 2>/dev/null | head -n 300

printf '%s\n' '--- schema pattern usage ---'
rg -n -C 3 'pattern:' apps/block_scout_web test 2>/dev/null | head -n 240

Repository: ajit2903/blockscout

Length of output: 38816


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- producer and controller context ---'
sed -n '1,90p' apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex
sed -n '520,580p' apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex

printf '%s\n' '--- numeric string pattern definitions ---'
rg -n -C 5 'def (non_negative_integer_pattern|integer_pattern|float_pattern)|@.*pattern' apps/block_scout_web/lib/block_scout_web/schemas/api/v2/general.ex

printf '%s\n' '--- relevant history summary ---'
git diff --stat
git log -8 --oneline -- apps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.ex apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex

Repository: ajit2903/blockscout

Length of output: 10770


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- countdown controller implementation ---'
sed -n '580,680p' apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex

printf '%s\n' '--- change that introduced the string schema ---'
git show --format=fuller --no-ext-diff --unified=25 b11a5d0c -- \
  apps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.ex \
  apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex \
  apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex

printf '%s\n' '--- all countdown calculation references ---'
rg -n -C 8 'estimated_time_in_sec|average_block_time|remaining_blocks' apps/block_scout/lib apps/block_scout_web/lib apps/*/test 2>/dev/null | head -n 400

Repository: ajit2903/blockscout

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import re

patterns = {
    "proposed": r"^(0|[1-9][0-9]*)(\.[0-9]+)?$",
    "repository_float_pattern": r"^([1-9][0-9]*|0)(\.[0-9]+)?$",
}

values = ["0", "0.0", "150.0", "404868.0", "1.25", "-1", "-1.0", "unknown", "", "01", "01.0", "1.", ".5", "NaN", "Infinity"]

for name, pattern in patterns.items():
    compiled = re.compile(pattern)
    print(name)
    for value in values:
        print(f"  {value!r}: {bool(compiled.fullmatch(value))}")

print("\npatterns_equal:", patterns["proposed"] == patterns["repository_float_pattern"])
PY

printf '%s\n' '--- OpenApiSpex dependency metadata ---'
rg -n -C 3 'open_api_spex|OpenApiSpex' mix.exs mix.lock apps/*/mix.exs 2>/dev/null | head -n 160

printf '%s\n' '--- schema fields using literal and regex patterns ---'
rg -n 'pattern:\s*(~r|General\.[A-Za-z_]+|")' apps/block_scout_web/lib/block_scout_web/schemas | head -n 160

Repository: ajit2903/blockscout

Length of output: 8920


Constrain estimated_time_in_seconds with the existing non-negative float pattern. type: :string also accepts values such as "-1" and "unknown". Use General.float_pattern() so producer output such as "150.0" remains valid.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.ex`
around lines 34 - 38, Update the estimated_time_in_seconds field in the
countdown schema to include the existing General.float_pattern() constraint
while retaining type: :string, so non-negative float strings such as "150.0"
remain valid and negative or non-numeric values are rejected.

Comment on lines 1025 to +1041
case insert_result do
{:ok, %{smart_contract: smart_contract}} ->
{:ok, smart_contract}

{:error, :smart_contract, changeset, _} ->
{:error, changeset}
{:error, failed_operation, failed_value, _changes_so_far} ->
Logger.error(fn ->
[
"Failed to create smart contract for ",
to_string(address_hash),
" at step ",
inspect(failed_operation),
": ",
inspect(failed_value)
]
end)

{:error, :set_address_verified, message, _} ->
{:error, message}
{:error, failed_value}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Extract the duplicated transaction-failure logging into a shared helper.

create_smart_contract/3 and update_smart_contract/3 now contain the same error-logging block, differing only in the "create"/"update" wording. Duplicating this logic risks the two copies drifting apart over time.

Extract a private helper, for example log_transaction_failure(action, address_hash, failed_operation, failed_value), and call it from both clauses.

♻️ Proposed refactor to share the logging logic
+  defp log_transaction_failure(action, address_hash, failed_operation, failed_value) do
+    Logger.error(fn ->
+      [
+        "Failed to #{action} smart contract for ",
+        to_string(address_hash),
+        " at step ",
+        inspect(failed_operation),
+        ": ",
+        inspect(failed_value)
+      ]
+    end)
+  end
+
   case insert_result do
     {:ok, %{smart_contract: smart_contract}} ->
       {:ok, smart_contract}

     {:error, failed_operation, failed_value, _changes_so_far} ->
-      Logger.error(fn ->
-        [
-          "Failed to create smart contract for ",
-          to_string(address_hash),
-          " at step ",
-          inspect(failed_operation),
-          ": ",
-          inspect(failed_value)
-        ]
-      end)
-
+      log_transaction_failure("create", address_hash, failed_operation, failed_value)
       {:error, failed_value}
   end

(apply the analogous change in update_smart_contract/3, passing "update")

Also applies to: 1142-1154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/explorer/lib/explorer/chain/smart_contract.ex` around lines 1025 - 1041,
Extract the duplicated transaction-failure Logger.error logic from
create_smart_contract/3 and update_smart_contract/3 into a private helper such
as log_transaction_failure/4, parameterized by action, address_hash,
failed_operation, and failed_value. Replace both error clauses with calls to the
helper, passing "create" and "update" respectively, while preserving their
existing {:error, failed_value} returns.

Comment on lines +111 to +113
__MODULE__
|> where([vs], vs.contract_address_hash == ^hash and vs.status == 0)
|> Repo.update_all(set: [status: 1, updated_at: now])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
fd -a . apps/explorer/priv/repo/migrations | xargs rg -l "smart_contract_verification_statuses" | sort
echo "---"
fd -a . apps/explorer/priv/repo/migrations | xargs rg -ni "create.*index" | rg "smart_contract_verification_statuses"

Repository: ajit2903/blockscout

Length of output: 506


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf '%s\n' '--- relevant migrations ---'
for f in \
  apps/explorer/priv/repo/migrations/20231227170848_add_proxy_verification_status.exs \
  apps/explorer/priv/repo/migrations/20260107090004_alter_contract_verification_status_table.exs \
  apps/explorer/priv/repo/migrations/20260724144709_reconcile_pending_smart_contract_verification_statuses.exs
do
  echo "### $f"
  cat -n "$f"
done
printf '%s\n' '--- all references and index declarations ---'
rg -n -i -C 3 'smart_contract_verification_statuses|create(_index)?|index\(' \
  apps/explorer/priv/repo/migrations \
  apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex
printf '%s\n' '--- schema definition ---'
rg -n -C 8 'schema "smart_contract_verification_statuses"|defmodule .*VerificationStatus' apps/explorer/lib apps/explorer/priv

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail
for f in \
  apps/explorer/priv/repo/migrations/20231227170848_add_proxy_verification_status.exs \
  apps/explorer/priv/repo/migrations/20260107090004_alter_contract_verification_status_table.exs \
  apps/explorer/priv/repo/migrations/20260724144709_reconcile_pending_smart_contract_verification_statuses.exs
do
  echo "### $f"
  cat -n "$f"
done
echo '--- exact table references in migrations ---'
rg -n -i -C 5 'smart_contract_verification_statuses' apps/explorer/priv/repo/migrations
echo '--- exact index references for the table ---'
rg -n -i -C 3 'index\(.*smart_contract_verification_statuses|smart_contract_verification_statuses.*index|CREATE(\s+UNIQUE)?\s+INDEX.*smart_contract_verification_statuses' apps/explorer/priv/repo/migrations
echo '--- schema and query ---'
rg -n -C 12 'schema "smart_contract_verification_statuses"|defmodule Explorer\.Chain\.SmartContract\.VerificationStatus|Repo\.update_all|contract_address_hash == \^hash' apps/explorer/lib/explorer/chain/smart_contract apps/explorer/priv

Repository: ajit2903/blockscout

Length of output: 8134


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo '--- original table definition and indexes ---'
rg -n -i -C 8 'contract_verification_status' apps/explorer/priv/repo/migrations apps/explorer/lib
echo '--- verification status module ---'
fd -a -i '*verification*status*' apps/explorer/lib apps/explorer/priv

Repository: ajit2903/blockscout

Length of output: 12424


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo '--- runtime query and callers ---'
cat -n apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex | sed -n '1,180p'
echo '--- calls to status update/query functions ---'
rg -n -C 4 'VerificationStatus\.(update|.*status|mark|set)|update_all\(set: \[status: 1|contract_address_hash == \^hash' apps/explorer/lib apps/explorer/test
echo '--- read-only structural check ---'
python3 - <<'PY'
from pathlib import Path
import re

migration_dir = Path("apps/explorer/priv/repo/migrations")
contents = [(p.name, p.read_text()) for p in migration_dir.glob("*.exs")]
table_mentions = [(name, text.count("smart_contract_verification_statuses")) for name, text in contents if "smart_contract_verification_statuses" in text]
index_mentions = []
for name, text in contents:
    if re.search(r"(?is)(?:create|create_if_not_exists)\s*\(\s*(?:unique_)?index\s*\(\s*:?(?:smart_contract_verification_statuses|contract_verification_status)", text):
        index_mentions.append(name)
    if re.search(r"(?is)CREATE\s+(?:UNIQUE\s+)?INDEX.*(?:smart_contract_verification_statuses|contract_verification_status)", text):
        index_mentions.append(name)
print("table_mentions:", table_mentions)
print("index_mentions_for_status_table:", sorted(set(index_mentions)))
PY

Repository: ajit2903/blockscout

Length of output: 21583


Add an index on smart_contract_verification_statuses(contract_address_hash, status). The table currently defines only its uid primary key. Without this index, the runtime update and backfill migration can scan the full table and prolong row locks.

📍 Affects 2 files
  • apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex#L111-L113 (this comment)
  • apps/explorer/priv/repo/migrations/20260724144709_reconcile_pending_smart_contract_verification_statuses.exs#L5-L13
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex`
around lines 111 - 113, Add a composite database index on
smart_contract_verification_statuses covering contract_address_hash and status
to support the update query in verification_status.ex and the backfill migration
in
apps/explorer/priv/repo/migrations/20260724144709_reconcile_pending_smart_contract_verification_statuses.exs
(lines 5-13); update both affected sites as needed, using the project’s
migration/index conventions.

end)

main_queue = hashes_to_queue ++ addresses_to_queue
main_queue = Enum.sort_by(hashes_to_queue ++ addresses_to_queue, &{&1.hash, &1.hash_type})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 \
  'prepare_export_data_for_queue|MainExportQueue|BalancesExportQueue|conflict_target|default_on_conflict|address_token_balances' \
  apps/explorer/lib apps/explorer/test

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="apps/explorer/lib/explorer/microservice_interfaces/multichain_search.ex"
ast-grep outline "$file" | sed -n '1,220p'
printf '\n--- target ranges ---\n'
sed -n '820,970p' "$file"
printf '\n--- focused references ---\n'
rg -n -C 12 \
  'hashes_to_queue|addresses_to_queue|balances_queue|Repo\.insert_all|MainExportQueue|BalancesExportQueue|prepare_export_data_for_queue' \
  "$file" apps/explorer/test

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- queue definitions and transformations ---'
rg -n -C 20 \
  'hashes_to_queue|addresses_to_queue|balances_queue|main_queue|block_range|value' \
  apps/explorer/lib/explorer/microservice_interfaces/multichain_search.ex

printf '%s\n' '--- schema and migration definitions ---'
rg -n -C 12 \
  'MainExportQueue|BalancesExportQueue|main_export_queue|balances_export_queue|export_queue' \
  apps/explorer/lib apps/explorer/priv apps/explorer/test \
  -g '*.ex' -g '*.exs' -g '*.sql'

printf '%s\n' '--- all direct insert_all calls in the target module ---'
rg -n -C 18 'insert_all' apps/explorer/lib/explorer/microservice_interfaces/multichain_search.ex

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- queue schema files ---'
fd -i '.*(main|balances).*export.*queue.*\.ex$|.*multichain.*search.*\.ex$' apps/explorer/lib apps/explorer/priv || true
rg -l 'defmodule .*MainExportQueue|defmodule .*BalancesExportQueue' apps/explorer/lib

printf '%s\n' '--- exact schema implementations ---'
for file in $(rg -l 'defmodule .*MainExportQueue|defmodule .*BalancesExportQueue' apps/explorer/lib); do
  echo "FILE: $file"
  cat -n "$file"
done

printf '%s\n' '--- relevant preparation and chunking tests ---'
rg -n -C 15 \
  'extract_batch_import_params_into_chunks|send_data_to_queue|prepare_export_data_for_queue|address_token_balances|address_coin_balances|block_transaction_hashes' \
  apps/explorer/test \
  -g '*multichain*' -g '*.exs'

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

p = Path("apps/explorer/lib/explorer/microservice_interfaces/multichain_search.ex")
lines = p.read_text().splitlines()

ranges = [(1, 125), (1000, 1165)]
for start, end in ranges:
    print(f"--- {p}:{start}-{end} ---")
    for n in range(start, min(end, len(lines)) + 1):
        print(f"{n:5}: {lines[n-1]}")
PY

Repository: ajit2903/blockscout

Length of output: 13260


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- unique indexes and migrations for queue tables ---'
rg -n -C 8 \
  'multichain_search_db_main_export_queue|multichain_search_db_export_balances_queue|unique_index|create unique_index|index\(' \
  apps/explorer/priv/repo/migrations apps/explorer/priv \
  -g '*.exs' -g '*.sql' | head -n 500

printf '%s\n' '--- callers supplying address balance inputs ---'
rg -n -C 10 \
  'send_data_to_queue\(|address_current_token_balances:|address_token_balances:|address_coin_balances:' \
  apps/explorer/lib apps/indexer/lib apps/explorer/test \
  -g '*.ex' -g '*.exs' | head -n 1200

printf '%s\n' '--- focused tests for MultichainSearch ---'
fd -i 'multichain' apps/explorer/test | sort

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- every queue-table occurrence in migrations ---'
rg -n \
  'multichain_search_db_main_export_queue|multichain_search_db_export_balances_queue' \
  apps/explorer/priv/repo/migrations apps/explorer/priv \
  -g '*.exs' -g '*.sql' || true

printf '%s\n' '--- focused MultichainSearch tests and callers ---'
rg -l \
  'MultichainSearch|send_data_to_queue|batch_import|address_current_token_balances|address_token_balances' \
  apps/explorer/test \
  -g '*.exs' | sort | head -n 100

rg -n -C 8 \
  'send_data_to_queue\(|batch_import\(' \
  apps/explorer/lib apps/indexer/lib apps/explorer/test \
  -g '*.ex' -g '*.exs' | head -n 500

Repository: ajit2903/blockscout

Length of output: 30779


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for file in \
  apps/explorer/priv/repo/migrations/20250526111446_rename_multichain_search_db_export_retry_queue_table.exs \
  apps/explorer/priv/repo/migrations/20250609130618_add_multichain_search_db_balances_export_queue_table.exs \
  apps/explorer/priv/repo/migrations/20250526114340_add_block_range_and_retries_number_to_multichain_search_db_export_queue_table.exs; do
  echo "--- $file ---"
  cat -n "$file"
done

printf '%s\n' '--- exact queue-related test sections ---'
file="apps/explorer/test/explorer/microservice_interfaces/multichain_search_test.exs"
rg -n -C 20 \
  'BalancesExportQueue|address_coin_balances|address_token_balances|send_data_to_queue|on_error|duplicate|same' \
  "$file"

Repository: ajit2903/blockscout

Length of output: 7865


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from collections import Counter

def compose_balances_queue(coin_balances, token_balances):
    rows = [
        {
            "address_hash": row["address_hash"],
            "token_contract_address_hash_or_native": "native",
            "value": row["value"],
        }
        for row in coin_balances
    ]
    rows += [
        {
            "address_hash": row["address_hash"],
            "token_contract_address_hash_or_native": row["token_address_hash"],
            "value": row["value"],
            "token_id": row["token_id"],
        }
        for row in token_balances
    ]
    return sorted(
        rows,
        key=lambda row: (
            row["address_hash"],
            row["token_contract_address_hash_or_native"],
            row.get("token_id"),
        ),
    )

rows = compose_balances_queue(
    [],
    [
        {"address_hash": "A", "token_address_hash": "T", "token_id": 7, "value": 100},
        {"address_hash": "A", "token_address_hash": "T", "token_id": 7, "value": 200},
    ],
)
keys = [
    (row["address_hash"], row["token_contract_address_hash_or_native"], row.get("token_id", -1))
    for row in rows
]
print("rows:", rows)
print("conflict_keys:", keys)
print("duplicate_keys:", {key: count for key, count in Counter(keys).items() if count > 1})
assert len(rows) == 2
assert len(set(keys)) == 1
PY

Repository: ajit2903/blockscout

Length of output: 441


Deduplicate balance entries before Repo.insert_all/3.

compose_balances_queue/2 sorts by the unique conflict key but does not remove duplicate keys. send_data_to_queue/1 uses on_conflict: {:replace, [:value, :updated_at]}. Duplicate keys in one insert can make PostgreSQL reject the statement. Merge entries by (address_hash, token_contract_address_hash_or_native, token_id) before sorting, or enforce and test upstream uniqueness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/explorer/lib/explorer/microservice_interfaces/multichain_search.ex` at
line 895, Update compose_balances_queue/2 to deduplicate balance entries by the
Repo.insert_all/3 conflict key (address_hash,
token_contract_address_hash_or_native, and token_id) before sorting and
queueing. Ensure send_data_to_queue/1 receives at most one entry per key while
preserving the existing value and updated_at replacement behavior.

def change do
alter table(:transactions) do
add(:fee_payer_address_hash, :bytea, null: true)
add(:calls, :jsonb, null: true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 2 \
  'field\(:calls, \{:array, :map\}\)|add\(:calls,' \
  apps/explorer/lib/explorer/chain/transaction.ex \
  apps/explorer/priv/eden/migrations/20260730065928_add_eden_transaction_fields.exs

Repository: ajit2903/blockscout

Length of output: 1133


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- transaction schema ---'
sed -n '55,95p' apps/explorer/lib/explorer/chain/transaction.ex

printf '%s\n' '--- migration ---'
cat -n apps/explorer/priv/eden/migrations/20260730065928_add_eden_transaction_fields.exs

printf '%s\n' '--- calls references ---'
rg -n -C 3 '\bcalls\b' apps/explorer/lib apps/explorer/test apps/explorer/priv/eden 2>/dev/null | head -240

printf '%s\n' '--- Ecto/PostgreSQL dependency versions ---'
rg -n 'ecto_sql|postgrex|ecto:' mix.exs mix.lock apps/*/mix.exs apps/*/mix.lock 2>/dev/null | head -120

Repository: ajit2903/blockscout

Length of output: 30329


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

schema = Path("apps/explorer/lib/explorer/chain/transaction.ex").read_text()
migration = Path("apps/explorer/priv/eden/migrations/20260730065928_add_eden_transaction_fields.exs").read_text()
tests = Path("apps/explorer/test/explorer/chain/transaction_test.exs").read_text()
runner = Path("apps/explorer/lib/explorer/chain/import/runner/transactions.ex").read_text()

assert 'field(:calls, {:array, :map})' in schema
assert 'add(:calls, :jsonb' in migration
assert 'calls: fragment("EXCLUDED.calls")' in runner
assert re.search(r'"calls"\s*=>\s*\[\s*%\{', tests)
print("schema_calls_type=Ecto array of map")
print("migration_calls_type=PostgreSQL jsonb")
print("import_upsert_writes_calls=true")
print("fixture_calls_shape=list_of_maps=true")
print("type_mismatch=true")
PY

printf '%s\n' '--- repository examples of array/map migrations ---'
rg -n -C 2 'add\(.*\{:array, :map\}|add\(.*:jsonb|field\(.*\{:array, :map\}' \
  apps --glob '*.ex' --glob '*.exs' | head -160

Repository: ajit2903/blockscout

Length of output: 9016


🌐 Web query:

Ecto PostgreSQL {:array, :map} jsonb[] Postgrex type mapping

💡 Result:

In Ecto, using {:array, :map} in a migration maps to the jsonb[] PostgreSQL column type [1]. This structure allows you to store an array of JSON objects directly in a single database column [1]. Key considerations for this mapping include: 1. Database Schema: When you define a field as field :my_field, {:array, :map} in an Ecto schema or add :my_field, {:array, :map} in a migration, Ecto configures the PostgreSQL adapter to use jsonb[] [1]. This is distinct from a single :jsonb column, which stores a single JSON object or array [1]. 2. Querying Challenges: Querying jsonb[] columns can be more complex than querying a single :jsonb field [1]. While a single :jsonb field (often used with embeds_many) can be queried using standard JSONB operators, jsonb[] may require manual SQL fragments, such as using jsonb_to_recordset to expand the array elements for complex filtering or joins [2]. 3. Alternatives: It is frequently recommended to use a single :jsonb column instead of jsonb[] whenever possible [1]. Storing an array inside a single :jsonb object often provides better performance, more flexible querying, and aligns better with Ecto's embeds_many functionality [1]. 4. Custom Types: If your application requires advanced validation or custom serialization logic for these structures, you can implement a custom Ecto.Type [3][4]. When doing so, ensure your cast, load, and dump callbacks handle the expected Elixir data structures (e.g., lists of maps) correctly, and note that map types generally use the configured JSON library (defaulting to Jason) for serialization [5][6][4]. For most use cases, preferring a single :jsonb column with embeds_many will simplify your schema and improve queryability compared to {:array, :map} [1].

Citations:


Use an array column for calls.

Explorer.Chain.Transaction declares calls as {:array, :map}, but line 8 creates a scalar jsonb column. The import path binds non-empty calls values as jsonb[], which PostgreSQL rejects for this column.

Proposed fix
-      add(:calls, :jsonb, null: true)
+      add(:calls, {:array, :map}, null: true)

Run the Eden transaction-import test against a database migrated from scratch.

📝 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
add(:calls, :jsonb, null: true)
add(:calls, {:array, :map}, null: true)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/explorer/priv/eden/migrations/20260730065928_add_eden_transaction_fields.exs`
at line 8, Update the migration’s calls column definition to use the array type
expected by Explorer.Chain.Transaction and the Eden import binding, rather than
scalar :jsonb; verify the Eden transaction-import test passes against a database
migrated from scratch.

start_supervised!({Task.Supervisor, name: BufferedTaskSup})

assert {:noreply, %BufferedTask{flush_timer: flush_timer}} =
assert {:noreply, %BufferedTask{flush_timer: flush_timer}, :hibernate} =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Implement the hibernation reply before asserting it.

handle_info(:flush, state) calls noreply_maybe_hibernate/1, but the supplied implementation at apps/indexer/lib/indexer/buffered_task.ex:1440-1442 returns only {:noreply, state}. Both assertions now fail. Return {:noreply, state, :hibernate} when the flush leaves no work, while retaining the two-element reply for non-empty flushes.

  • apps/indexer/test/indexer/buffered_task_test.exs#L247-L247: keep this assertion only after the empty-flush hibernation path exists.
  • apps/indexer/test/indexer/buffered_task_test.exs#L274-L274: keep this assertion only after the shed-work recovery path returns the same hibernation reply.
📍 Affects 1 file
  • apps/indexer/test/indexer/buffered_task_test.exs#L247-L247 (this comment)
  • apps/indexer/test/indexer/buffered_task_test.exs#L274-L274
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/indexer/test/indexer/buffered_task_test.exs` at line 247, Update
noreply_maybe_hibernate/1 in apps/indexer/lib/indexer/buffered_task.ex to return
{:noreply, state, :hibernate} when flushing leaves no work, while preserving the
two-element reply for non-empty flushes. Ensure both assertions at
apps/indexer/test/indexer/buffered_task_test.exs lines 247-247 and 274-274
remain valid after the empty-flush and shed-work recovery paths use the
hibernating reply.

@ajit2903 ajit2903 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

hii

Commented in CodeRabbit Change Stack

@ajit2903 ajit2903 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

approve

Commented in CodeRabbit Change Stack

@ajit2903

ajit2903 commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resolve merge conflict

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

An unexpected error occurred while resolving merge conflicts:

Resource not accessible by integration - https://docs.github.com/rest/git/trees#create-a-tree

@ajit2903 ajit2903 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

all set

Commented in CodeRabbit Change Stack

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex (1)

146-148: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep operational and security failures above debug.

{:file_error, _} indicates a server-side read failure. {:lost_consensus, ...} indicates a chain-integrity event. {:sensitive_endpoints_api_key, _} indicates server misconfiguration. Restricted access, reCAPTCHA failures, unauthorized requests, and wrong API keys are security telemetry.

At production log levels above debug, these events become invisible. Keep server failures at Logger.error. Use Logger.warning or dedicated, rate-limited security metrics for authentication events.

Also applies to: 177-179, 197-209, 216-219, 227-229, 238-240, 249-251

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex`
around lines 146 - 148, Update the logging branches in the fallback controller,
including the restricted-access branch and the additional locations referenced
by the review, so server-side failures such as file errors, lost consensus, and
sensitive endpoint API-key misconfiguration use Logger.error, while
authentication and security events use Logger.warning or the existing dedicated
rate-limited security telemetry instead of Logger.debug.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex`:
- Around line 146-148: Update the logging branches in the fallback controller,
including the restricted-access branch and the additional locations referenced
by the review, so server-side failures such as file errors, lost consensus, and
sensitive endpoint API-key misconfiguration use Logger.error, while
authentication and security events use Logger.warning or the existing dedicated
rate-limited security telemetry instead of Logger.debug.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab2bc783-9729-4789-ac55-7926259ed350

📥 Commits

Reviewing files that changed from the base of the PR and between 790e4bb and bd0b3c0.

📒 Files selected for processing (6)
  • apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex
  • apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex
  • apps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.ex
  • apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex
  • apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs
  • apps/block_scout_web/test/block_scout_web/views/api/v2/block_view_test.exs
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex
  • apps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.ex
  • apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/explorer/lib/explorer/chain/address/coin_balance.ex`:
- Around line 389-400: Move the Block join or equivalent block-existence filter
into latest_balances_query so matching blocks are selected before the two-row
limit, preserving correct delta calculation when the newest balance lacks a
Block; add a regression test covering that missing-block scenario.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f2cbb09-d02e-49bd-beb8-3ac7b57d5f5e

📥 Commits

Reviewing files that changed from the base of the PR and between bd0b3c0 and ac94729.

📒 Files selected for processing (1)
  • apps/explorer/lib/explorer/chain/address/coin_balance.ex

Comment on lines +389 to +400
latest_balances_query =
from(
cb in CoinBalance,
where: cb.address_hash == ^address_hash,
where: cb.block_number <= ^block_number,
order_by: [desc: :block_number],
limit: ^2
)

coin_balance_subquery =
from(
cb in subquery(latest_balances_query),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 6 \
  -e 'address_coin_balances' \
  -e 'CoinBalance' \
  -e 'foreign_key.*blocks' \
  -e 'block_number.*number' \
  apps/explorer/lib apps/explorer/priv apps/explorer/test || true

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo '--- relevant files ---'
git ls-files apps/explorer | rg 'coin_balance|coin_balances|schema|migration' | head -200

echo '--- CoinBalance schema and query paths ---'
rg -n -C 8 \
  'defmodule Explorer\.Chain\.Address\.CoinBalance|schema "address_coin_balances"|def fetch_coin_balance|def fetch_coin_balances|inner_join: b in Block|last_fetched_counters' \
  apps/explorer/lib/explorer/chain/address apps/explorer/lib/explorer/chain.ex apps/explorer/test/explorer/chain/address 2>/dev/null | head -800

echo '--- database definitions and constraints ---'
rg -n -C 10 \
  'address_coin_balances|coin_balances.*block_number|foreign_key.*blocks|references.*blocks|block_number.*number' \
  apps/explorer/priv apps/explorer/lib 2>/dev/null | head -1000

echo '--- writers of address_coin_balances ---'
rg -n -C 8 \
  'Address\.CoinBalance|CoinBalance|address_coin_balances' \
  apps/explorer/lib/explorer/chain/import apps/explorer/lib/explorer/chain/address apps/explorer/test/explorer/chain/address 2>/dev/null | head -1200

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

sed -n '350,450p' apps/explorer/lib/explorer/chain/address/coin_balance.ex
rg -n -C 12 'schema "address_coin_balances"|address_coin_balances' apps/explorer/lib apps/explorer/priv
rg -n -C 10 'CoinBalance\.insert|CoinBalance\.upsert|address_coin_balances|block_number:' apps/explorer/lib/explorer/chain/import apps/explorer/test/explorer/chain/address

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo '--- CoinBalance schema ---'
sed -n '1,90p' apps/explorer/lib/explorer/chain/address/coin_balance.ex

echo '--- fetch_coin_balance implementation ---'
sed -n '360,420p' apps/explorer/lib/explorer/chain/address/coin_balance.ex

echo '--- address_coin_balances migration ---'
sed -n '1,180p' apps/explorer/priv/repo/migrations/20180717204948_create_address_coin_balances.exs

echo '--- CoinBalances writer ---'
sed -n '1,190p' apps/explorer/lib/explorer/chain/import/runner/address/coin_balances.ex

echo '--- block import ordering and deletion references ---'
rg -n -C 8 \
  'Runner\.Blocks|blocks.*runner|delete_all.*blocks|foreign_key.*address_coin_balances|address_coin_balances.*blocks|on_delete.*blocks' \
  apps/explorer/lib/explorer/chain/import apps/explorer/lib/explorer/chain apps/explorer/priv/repo/migrations \
  | head -600

Repository: ajit2903/blockscout

Length of output: 25759


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

sed -n '1,180p' apps/explorer/priv/repo/migrations/20180717204948_create_address_coin_balances.exs
sed -n '1,190p' apps/explorer/lib/explorer/chain/import/runner/address/coin_balances.ex
rg -n -C 8 'def fetch_coin_balance|fetch_coin_balance\(' apps/explorer/lib apps/explorer/test | head -300

Repository: ajit2903/blockscout

Length of output: 10412


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo '--- import stage order ---'
rg -n -C 10 \
  'def stages|`@stages`|Stage\.Blocks|Stage\.Main|Stage\.Address|stages\(\)' \
  apps/explorer/lib/explorer/chain/import apps/explorer/lib/explorer/chain/import.ex | head -800

echo '--- block transaction and coin-balance deletion ---'
sed -n '160,240p' apps/explorer/lib/explorer/chain/import/runner/blocks.ex
sed -n '680,780p' apps/explorer/lib/explorer/chain/import/runner/blocks.ex

echo '--- all direct CoinBalance inserts and test factories ---'
rg -n -C 6 \
  'insert\([^)]*:fetched_balance|insert\([^)]*:unfetched_balance|CoinBalance|address_coin_balances' \
  apps/explorer/test/support apps/explorer/test apps/explorer/lib \
  | rg -v 'coin_balance_daily|current_token_balance|token_balance' | head -1000

Repository: ajit2903/blockscout

Length of output: 50376


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
balances = [(100, 10), (90, 5), (80, 2)]  # (block_number, value)
blocks = {100}

limited_before_join = [row for row in balances[:2] if row[0] in blocks]
join_before_limit = [row for row in balances if row[0] in blocks][:2]

def delta(rows):
    return rows[0][1] - (rows[1][1] if len(rows) > 1 else 0)

print({
    "limit_before_join": limited_before_join,
    "join_before_limit": join_before_limit,
    "delta_limit_before_join": delta(limited_before_join),
    "delta_join_before_limit": delta(join_before_limit),
})
PY

rg -n -C 5 \
  'add\(:block_number|references\(:blocks|delete_address_coin_balances|lose_consensus|Stage\.Blocks|Stage\.Main' \
  apps/explorer/priv/repo/migrations/20180717204948_create_address_coin_balances.exs \
  apps/explorer/lib/explorer/chain/import \
  | head -800

Repository: ajit2903/blockscout

Length of output: 14640


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
balances = [(100, 10), (90, 5), (80, 2)]  # (block_number, value), newest first
blocks = {100, 80}

limited_before_join = [row for row in balances[:2] if row[0] in blocks]
join_before_limit = [row for row in balances if row[0] in blocks][:2]

def delta(rows):
    return rows[0][1] - (rows[1][1] if len(rows) > 1 else 0)

assert limited_before_join == [(100, 10)]
assert join_before_limit == [(100, 10), (80, 2)]
assert delta(limited_before_join) == 10
assert delta(join_before_limit) == 8

print("limit-before-join:", limited_before_join, "delta:", delta(limited_before_join))
print("join-before-limit:", join_before_limit, "delta:", delta(join_before_limit))
PY

echo '--- invariant evidence ---'
printf 'block foreign-key declarations in address_coin_balances migration: '
rg -n 'references\(:blocks|add\(:block_number' \
  apps/explorer/priv/repo/migrations/20180717204948_create_address_coin_balances.exs

printf 'import stage order: '
sed -n '16,35p' apps/explorer/lib/explorer/chain/import.ex

printf 'block cleanup: '
sed -n '712,725p' apps/explorer/lib/explorer/chain/import/runner/blocks.ex

Repository: ajit2903/blockscout

Length of output: 1424


Keep the Block join before the two-row limit. The address_coin_balances table has no foreign key on block_number. If the newest balance has no matching Block, the query drops it after limiting and computes the remaining balance’s delta against zero. Move the join into latest_balances_query or filter block existence before applying limit: ^2. Add a regression test for this case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/explorer/lib/explorer/chain/address/coin_balance.ex` around lines 389 -
400, Move the Block join or equivalent block-existence filter into
latest_balances_query so matching blocks are selected before the two-row limit,
preserving correct delta calculation when the newest balance lacks a Block; add
a regression test covering that missing-block scenario.

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.

4 participants