Skip to content

fix(deploy): make Blockscout verification work without an RPC and bound its runtime#55

Merged
RonTuretzky merged 1 commit into
mainfrom
fix/blockscout-verify-hang
Jul 11, 2026
Merged

fix(deploy): make Blockscout verification work without an RPC and bound its runtime#55
RonTuretzky merged 1 commit into
mainfrom
fix/blockscout-verify-hang

Conversation

@RonTuretzky

Copy link
Copy Markdown
Contributor

Problem

verify-blockscout.sh has never successfully verified anything for consumers that don't export ETH_RPC_URL (e.g. crowdstake.fun's deploy workflow, which exports RPC_URL — a variable forge doesn't read), and its retry loop is effectively unbounded:

  1. Every submission silently failed: --guess-constructor-args hard-errors with You have to provide a valid RPC URL to use --guess-constructor-args feature when no RPC is configured. The || true swallowed it, so Phase 1 submitted nothing.
  2. Phase 3 then hangs: the per-contract retry loop is up to MAX_CHECKS × CHECK_DELAY = 300 s per contract. A 20-contract deploy has a ~100-minute worst case — the first contract alone blows a 5-minute step timeout, so the step gets killed before anything verifies. This is exactly what happened on crowdstake.fun runs 29133058331 / 29133083853 / 29133084514 (2026-07-11): The action 'Verify on Blockscout' has timed out after 5 minutes on every run, zero contracts verified.
  3. Two latent bugs surfaced once (1) was fixed:
    • bare contract names carry no compiler version → forge errors with "Compiler version has to be set in foundry.toml";
    • some Blockscout deployments 301 to a canonical host (optimism.blockscout.com → explorer.optimism.io); forge's verification POST doesn't survive the redirect (re-issued as GET, payload dropped), and curl -sf status checks fail on the 301.

Fix (interface unchanged: BLOCKSCOUT_URL, BROADCAST_FILE, MAX_CHECKS, CHECK_DELAY, INITIAL_WAIT)

  • Derive constructor args offline from the broadcast file: creation tx input minus the compiled creation bytecode (forge inspect <name> bytecode). No RPC needed at all. Falls back to letting Blockscout extract args from the creation tx if the prefix doesn't match (e.g. library placeholders).
  • Pass --compiler-version from the build artifact metadata (forge inspect <name> metadata).
  • Resolve the canonical Blockscout host up front (follow the redirect once) and add -L to the status-poll curl.
  • Poll verification status in rounds across all pending contracts: worst case is now INITIAL_WAIT + MAX_CHECKS × CHECK_DELAY regardless of contract count, with early exit when everything is verified. Dropped the blind resubmit-on-every-check.
  • Truncate echoed submission responses (some instances echo the full standard-json back — megabytes per contract in the logs).

Validation

Ran the fixed script end-to-end against the 2026-07-11 crowdstake.fun DeployCrowdStakeDeployer broadcasts: all 20 broadcast contracts verified with exit 0 on each of:

  • Gnosis (https://gnosis.blockscout.com)
  • Arbitrum (https://arbitrum.blockscout.com)
  • Optimism (https://optimism.blockscout.com → auto-resolved to explorer.optimism.io)

including the redirect-host path and the constructor-args derivation for contracts with 1, 2, and 10 constructor arguments.

Note for consumers: _deploy-testnet.yml keeps working unchanged (it exported ETH_RPC_URL, which is simply no longer needed). Callers with tight step timeouts can now tune INITIAL_WAIT/CHECK_DELAY/MAX_CHECKS to a hard bound.

…nd its runtime

verify-blockscout.sh never verified anything for callers that don't export
ETH_RPC_URL, and could run for MAX_CHECKS*CHECK_DELAY seconds PER contract:

- --guess-constructor-args hard-fails without an RPC ("You have to provide a
  valid RPC URL"), so every submission silently failed. Constructor args are
  now derived offline from the broadcast file: creation tx input minus the
  compiled creation bytecode (forge inspect), falling back to letting
  Blockscout extract them when the prefix doesn't match.
- Bare contract names carry no compiler version, which forge also errors on;
  the version is now read from the build artifact metadata.
- The status poll now runs in rounds across all pending contracts, bounding
  the worst case at INITIAL_WAIT + MAX_CHECKS*CHECK_DELAY regardless of
  contract count (previously ~100 minutes for a 20-contract deploy, which
  blew every step timeout before a single check passed). Blind resubmits on
  every failed check are gone.
- Some Blockscout deployments 301 to a canonical host (optimism.blockscout.com
  -> explorer.optimism.io); forge's verification POST doesn't survive the
  redirect, so the final host is resolved up front, and the status-poll curl
  follows redirects.
- Submission responses are truncated in logs (some instances echo the entire
  standard-json back, megabytes per contract).

Validated end-to-end against the 2026-07-11 crowdstake.fun deploys: all 20
broadcast contracts verified on Gnosis, Arbitrum, and Optimism Blockscout
with exit 0.
@RonTuretzky
RonTuretzky merged commit 180aa91 into main Jul 11, 2026
11 checks passed
llvcianov pushed a commit to llvcianov/crowdstake.fun that referenced this pull request Jul 19, 2026
The 'Verify on Blockscout' step has timed out after 5 minutes on every
deploy run and never verified anything: etherform's verify-blockscout.sh
passes --guess-constructor-args, which hard-fails without an RPC (the
workflow exports RPC_URL, a variable forge doesn't read), and its
per-contract retry loop has a ~100-minute worst case for a 20-contract
deploy.

Pin the etherform sparse-checkout to fix/blockscout-verify-hang
(BreadchainCoop/etherform#55), which derives constructor args offline
from the broadcast file and polls in bounded rounds. Repoint to main
once that PR merges.

Drop the now-unused RPC_URL env from the verify step and tune the
polling (INITIAL_WAIT=30, CHECK_DELAY=20, MAX_CHECKS=6) to a 150s hard
bound, well inside the 5-minute step timeout.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants