aj - #45
Conversation
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>
|
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 |
|
@Qwerty5Uiop is attempting to deploy a commit to the aj Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe 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. ChangesEden support and release workflows
Health and realtime processing
Platform maintenance and API corrections
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 2⚔️ Resolve merge conflicts 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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 winRestrict
Explorer.Chain.Health.Monitorto indexer mode.
configure/1starts this process in API mode when enabled. Useconfigure_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 | 🔵 TrivialPreserve security-failure observability.
Production sets the Logger level to
:info, so theseLogger.debug/1calls 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 winAvoid an unlinked task for every response.
Task.start/1creates an unlinked process insideConn.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 beforeLogger.log/3runs. CallLogger.log/3directly, 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 winTop-level
healthysilently ignores deposits/withdrawals status.
maybe_put_batchesdocuments with a TODO thatbatches.healthyis intentionally not folded into the top-levelhealthyflag.maybe_put_deposits_and_withdrawals(Line 220-227) has the identical limitation — a chain can reportmetadata.deposits.healthy: falsewhile the endpoint still returnshealthy: trueand 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
healthyvalue 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 winChain-type-with-messages list is duplicated across two files.
health_controller.exencodes the supported chain types as an atom list (@chain_types_with_messages), whilehealth/monitor.exencodes the same set as separatedeposit_and_withdrawal_module_for_chain_type/1function-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_messagesfrom a single shared source (e.g., a public list exposed byExplorer.Chain.Health.Monitoror 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) sohealth_controller.excan 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 liftBatches/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: replacemaybe_add_batch_info_to_params/3,maybe_add_deposit_info_to_params/3, andmaybe_add_withdrawal_info_to_params/3with one function parameterized by a metric-name prefix and field map.apps/explorer/lib/explorer/chain/health/monitor.ex#L196-L237: replaceget_latest_deposit_info_from_module/1andget_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: replacedeposits_indexing_healthy?/1andwithdrawals_indexing_healthy?/1with one function parameterized by the health-status key and the corresponding:healthy_*_periodconfig key, matchingbatches_indexing_healthy?/1's shape.apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex#L323-L394: replacedeposits_indexing_status/1andwithdrawals_indexing_status/1with one function parameterized by field names, matchingbatches_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 winExtract 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)} endThen 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
columnargument 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 | 🔵 TrivialConfirm index support for this count query.
The query scans
blockswithconsensus AND refetch_neededplus a block-number range. It runs on the hourly metrics schedule withtimeout: :infinity. On a large chain a sequential scan overblockscan take a long time and can compete with indexing writes.A partial index such as
blocks (number) WHERE consensus AND refetch_neededkeeps 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 tradeoffConsider extracting the shared metric preparation logic.
prepare_batch_metric/1,prepare_deposit_metric/1, andprepare_withdrawal_metric/1now 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 endAlso 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
⛔ Files ignored due to path filters (2)
mix.lockis excluded by!**/*.locktypes-package/package-lock.jsonis 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.ymlCHANGELOG.mdapps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.exapps/block_scout_web/lib/block_scout_web/controllers/api/v2/api_key_controller.exapps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.exapps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.exapps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/solidity_scan_controller.exapps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.exapps/block_scout_web/lib/block_scout_web/notifier.exapps/block_scout_web/lib/block_scout_web/plug/logger.exapps/block_scout_web/lib/block_scout_web/realtime_event_handler.exapps/block_scout_web/lib/block_scout_web/realtime_event_handlers/main.exapps/block_scout_web/lib/block_scout_web/realtime_event_handlers/main_page.exapps/block_scout_web/lib/block_scout_web/realtime_event_handlers/smart_contract.exapps/block_scout_web/lib/block_scout_web/realtime_event_handlers/token_transfer.exapps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.exapps/block_scout_web/lib/block_scout_web/schemas/api/v2/smart_contract.exapps/block_scout_web/lib/block_scout_web/schemas/api/v2/transaction.exapps/block_scout_web/lib/block_scout_web/views/api/v2/celo_view.exapps/block_scout_web/lib/block_scout_web/views/api/v2/eden_view.exapps/block_scout_web/lib/block_scout_web/views/api/v2/helper.exapps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.exapps/block_scout_web/mix.exsapps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exsapps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exsapps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exsapps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exsapps/block_scout_web/test/block_scout_web/notifier_subscriber_filter_test.exsapps/block_scout_web/test/block_scout_web/rate_limit_test.exsapps/block_scout_web/test/block_scout_web/views/api/v2/transaction_view_test.exsapps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.exapps/ethereum_jsonrpc/mix.exsapps/ethereum_jsonrpc/test/ethereum_jsonrpc/eden_test.exsapps/explorer/config/dev.exsapps/explorer/config/prod.exsapps/explorer/config/test.exsapps/explorer/lib/explorer/chain/address/counters.exapps/explorer/lib/explorer/chain/arbitrum/reader/common.exapps/explorer/lib/explorer/chain/decoding_helper.exapps/explorer/lib/explorer/chain/ethereum/reader.exapps/explorer/lib/explorer/chain/events/listener.exapps/explorer/lib/explorer/chain/events/publisher.exapps/explorer/lib/explorer/chain/events/subscriber.exapps/explorer/lib/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand.exapps/explorer/lib/explorer/chain/health/helper.exapps/explorer/lib/explorer/chain/health/monitor.exapps/explorer/lib/explorer/chain/import.exapps/explorer/lib/explorer/chain/import/runner/internal_transactions.exapps/explorer/lib/explorer/chain/import/runner/transactions.exapps/explorer/lib/explorer/chain/log.exapps/explorer/lib/explorer/chain/metrics/queries/indexer_metrics.exapps/explorer/lib/explorer/chain/optimism/reader.exapps/explorer/lib/explorer/chain/scroll/reader.exapps/explorer/lib/explorer/chain/smart_contract.exapps/explorer/lib/explorer/chain/smart_contract/verification_status.exapps/explorer/lib/explorer/chain/transaction.exapps/explorer/lib/explorer/market/market.exapps/explorer/lib/explorer/market/market_history.exapps/explorer/lib/explorer/microservice_interfaces/multichain_search.exapps/explorer/lib/explorer/prometheus/instrumenter.exapps/explorer/lib/explorer/repo.exapps/explorer/lib/explorer/token/metadata_retriever.exapps/explorer/mix.exsapps/explorer/priv/eden/migrations/20260730065928_add_eden_transaction_fields.exsapps/explorer/priv/repo/migrations/20260724144709_reconcile_pending_smart_contract_verification_statuses.exsapps/explorer/test/explorer/chain/decoding_helper_test.exsapps/explorer/test/explorer/chain/fetcher/look_up_smart_contract_sources_on_demand_test.exsapps/explorer/test/explorer/chain/health/helper_test.exsapps/explorer/test/explorer/chain/import/runner/eden/transactions_test.exsapps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exsapps/explorer/test/explorer/chain/metrics/indexer_metrics_test.exsapps/explorer/test/explorer/chain/smart_contract/verification_status_test.exsapps/explorer/test/explorer/chain/smart_contract_test.exsapps/explorer/test/explorer/chain/transaction_test.exsapps/explorer/test/explorer/market/market_history_test.exsapps/indexer/lib/indexer/block/catchup/fetcher.exapps/indexer/lib/indexer/block/realtime/fetcher.exapps/indexer/lib/indexer/buffered_task.exapps/indexer/lib/indexer/fetcher/on_demand/token_balance.exapps/indexer/lib/indexer/fetcher/token_balance/current.exapps/indexer/lib/indexer/prometheus/instrumenter.exapps/indexer/lib/indexer/prometheus/metrics.exapps/indexer/lib/indexer/transform/addresses.exapps/indexer/mix.exsapps/indexer/test/indexer/buffered_task_test.exsapps/indexer/test/indexer/fetcher/internal_transaction_test.exsapps/indexer/test/indexer/fetcher/on_demand/token_balance_test.exsapps/indexer/test/indexer/transform/addresses.exsapps/nft_media_handler/mix.exsapps/utils/mix.exsconfig/config_helper.exsconfig/runtime.exsconfig/runtime/dev.exsconfig/runtime/prod.exscspell.jsondocker-compose/envs/common-blockscout.envdocker/Makefilemix.exsrel/config.exstypes-package/index.tstypes-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 |
There was a problem hiding this comment.
🔒 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: addwith: persist-credentials: false..github/workflows/publish-docker-image-for-eden.yml#L20-L20: addwith: persist-credentials: false..github/workflows/release-eden.yml#L23-L23: addwith: 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
| @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 |
There was a problem hiding this comment.
🗄️ 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 -nRepository: 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 300Repository: 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 350Repository: 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.exRepository: 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.
| @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 |
There was a problem hiding this comment.
🎯 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 -B2Repository: 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" || trueRepository: 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 -200Repository: 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 -200Repository: 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 || trueRepository: 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")
PYRepository: 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.
| 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" | ||
| } |
There was a problem hiding this comment.
🎯 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 240Repository: 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.exRepository: 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 400Repository: 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 160Repository: 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.
| 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} |
There was a problem hiding this comment.
📐 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.
| __MODULE__ | ||
| |> where([vs], vs.contract_address_hash == ^hash and vs.status == 0) | ||
| |> Repo.update_all(set: [status: 1, updated_at: now]) |
There was a problem hiding this comment.
🚀 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/privRepository: 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/privRepository: 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/privRepository: 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)))
PYRepository: 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}) |
There was a problem hiding this comment.
🗄️ 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/testRepository: 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/testRepository: 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.exRepository: 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]}")
PYRepository: 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 | sortRepository: 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 500Repository: 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
PYRepository: 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) |
There was a problem hiding this comment.
🗄️ 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.exsRepository: 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 -120Repository: 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 -160Repository: 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:
- 1: https://thoughtbot.com/blog/why-ecto-s-way-of-storing-embedded-lists-of-maps-makes-querying-hard
- 2: https://michaelfich.com/postgresql-queries-on-jsonb-fields-with-ecto/
- 3: https://stackoverflow.com/questions/55579922/ecto-jsonb-array-and-map-cast-issue
- 4: https://hexdocs.pm/ecto/Ecto.Type.html
- 5: https://hexdocs.pm/ecto/Ecto.Schema.html
- 6: https://hexdocs.pm/postgrex/Postgrex.Types.html
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.
| 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} = |
There was a problem hiding this comment.
🎯 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
left a comment
There was a problem hiding this comment.
hii
Commented in CodeRabbit Change Stack
ajit2903
left a comment
There was a problem hiding this comment.
approve
Commented in CodeRabbit Change Stack
|
@coderabbitai resolve merge conflict |
|
❌ An unexpected error occurred while resolving merge conflicts: Resource not accessible by integration - https://docs.github.com/rest/git/trees#create-a-tree |
ajit2903
left a comment
There was a problem hiding this comment.
all set
Commented in CodeRabbit Change Stack
There was a problem hiding this comment.
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 winKeep 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 atLogger.error. UseLogger.warningor 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
📒 Files selected for processing (6)
apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.exapps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.exapps/block_scout_web/lib/block_scout_web/schemas/api/v2/block/countdown.exapps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.exapps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exsapps/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
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
apps/explorer/lib/explorer/chain/address/coin_balance.ex
| 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), |
There was a problem hiding this comment.
🎯 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 || trueRepository: 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 -1200Repository: 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/addressRepository: 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 -600Repository: 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 -300Repository: 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 -1000Repository: 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 -800Repository: 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.exRepository: 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.
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)
master.