Skip to content

Portal SDK: cutover for all non-Sonic containers - #85

Draft
apexearth wants to merge 2 commits into
mainfrom
portal-sdk
Draft

Portal SDK: cutover for all non-Sonic containers#85
apexearth wants to merge 2 commits into
mainfrom
portal-sdk

Conversation

@apexearth

@apexearth apexearth commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Depends on OriginProtocol/squid-utils#1this branch does not compile until squid-utils publishes (exactly 3 errors, all in src/utils/dumps.ts: createPortalClient, runPortal, SquidProcessor.portalClient). Merge order is squid-utils release first.

Scope doc: https://github.com/oplabs/off-chain/blob/main/notes/squid-portal-migration.md

What

  • The cutover is one place. src/utils/dumps.tsGATEWAY_CHAIN_IDS (default: Sonic) selects run() vs runPortal(). No main-*.ts is touched, because none of them call run() directly; they all go through initProcessorFromDump(). Per-container rollback is an env var, not a code change.
  • Deleted the addTrace monkeypatch in templates/otoken/otoken-2.ts (−31 lines). It reached into the gateway processor's private assertNotRunning()/add() and would TypeError at setup on the portal path, blocking oeth/ousd/oethb. It existed only to dodge the gateway's address lowercasing; evm-stream doesn't lowercase, so callTo collapses to the single lowercase address and the getAddress import goes away.
  • Ported polyfills/portal-cache.tsbatch.meta.finalizedHeadNumber (finality moved in 0.7.0), the finalized third arg forwarded, and setupPortalCache()withPortalCache(client, stateSchema) which mutates one instance's own getStream instead of patching PortalClient.prototype. That kills a real hazard: the tree resolves multiple physically distinct portal-client copies, so a prototype patch can attach to a class nobody instantiates and silently cache nothing. Algorithm, SQLite schema, zstd and stats are unchanged.
  • Type debt: optional-sighash guard (utils/traceFilter.ts), EvmBatchProcessor annotation (oeth/processors/oeth.ts), Trace imported from squid-utils.
  • @subsquid/portal-client^0.7.0; patches/@subsquid__portal-client@0.3.2.patch and its pnpm-workspace.yaml entry deleted — the fix is upstream in 0.7.0 and the patch no longer applies.
  • AGENTS.md documents GATEWAY_CHAIN_IDS and the local file: link/revert procedure.

Validation — local Postgres, four containers

container path reached
mainnet portal (sqd:batch-processor) 25878064 / 25878064
oeth portal 25878035 / 25878035
oethb portal 50711727 / 50711727
sonic gateway (sqd:processor) tracking, catching up

4,981 rows across 40 tables. Zero error-level lines and zero block not found across all four.

Caching — the thing most at risk, and it works

PORTAL_CACHE, oethb over base 50,400,000–50,600,000, fresh cache dir:

cold warm
wall clock 249.0 s 68.1 s (3.7×)
cache saved=2 chunks / 200,001 blocks / 9.93 MB, sqlite 0 → 10.2 MB hit_rate=100.0%, replayed 2 chunk(s)

Row parity cold vs warm identical — 3126 / 1569 / 1737 across o_token_history / erc20_transfer / o_token_activity, both ending at height 50600000.

RPC_CACHE needed zero edits: same pair, cold eth_getCode total=578 miss=434 hit=0.0%, warm total=2192 hit=100.0%.

⚠️ Existing .portal-cache files are invalidated by this change — the two SDK generations build different query JSON and the cache key is a SHA of it. Old rows aren't corrupt, just unreachable. Wipe and rebuild once, then pnpm cache:backup / cache:restore so one person pays for it.

Decisions worth a reviewer's eye

  1. Sonic/OS lose PORTAL_CACHE. The gateway setPortal() takes a URL, not a client, so there's no instance to wrap — and after the 0.7.0 bump, origin-squid's PortalClient is a different physical copy from the prerelease's 0.3.2 anyway. Documented in the file header. Local-dev only; no prod impact.
  2. The block not found retry lives in squid-utils as its own revertable commit. The portal delivers blocks slightly before the RPC node has them, which the gateway path structurally cannot hit. Alternative is holding state reads back to a confirmed height.

Validation note — read before reproducing

A mid-chain BLOCK_FROM against an empty database is not a cold rebuild. Several origin-squid processors learn state by observing historic events, so skipping those blocks produces failures that look like contract or SDK bugs and are neither.

Concretely: an early validation run started mainnet at BLOCK_FROM=25877800, 534,849 blocks above the Ethena ARM upgrade at 25,342,951. Arm.upgradeBlock is learned at runtime from the BaseAssetAdded log (origin-arm.ts:917), never hardcoded — so it stayed NULL, upgraded evaluated false, and the code called the now-deleted liquidityAmountInCooldown() and reverted. That is the correct consequence of skipping the upgrade event, not a defect. Production restores a dump and learned upgradeBlock = 25342951 correctly.

Because a dropped log would have meant a genuine bug in this PR's filter translation, it was checked directly: processing 25,342,900–25,343,050 with no Arm row present, portal and gateway both learn upgrade_block = 25342951, with identical row counts across all 105 tables and zero errors on either path. The {where, include} translation drops nothing.

One pre-existing bug found, not introduced here

erc20 duplicate-key on a cold mid-chain start. erc20-entry.ts and erc20-event.ts both find-then-insert the same ERC20 id within one batch, violating PK_8d43ce15401ba044c55a72a8ceb and aborting the transaction. Reproduces identically on the gateway path (GATEWAY_CHAIN_IDS=1), so it predates this work; prod is immune because it restores a dump. Fix is an upsert — out of scope here.

🤖 Generated with Claude Code

Cuts origin-squid over to squid-utils' `runPortal()` (@subsquid/evm-stream +
@subsquid/batch-processor), which consumes the portal's real-time /stream
instead of polling RPC for the chain head. Sonic has no real-time Portal
dataset and stays on `run()`; `GATEWAY_CHAIN_IDS` makes that list env-driven
so any container can be rolled back without a code change. No `main-*.ts`
changes — every container goes through `initProcessorFromDump()`.

- dumps.ts: the one cutover branch, plus wiring the portal cache in.
- otoken-2.ts: delete the `addTrace` monkeypatch. It reached into the gateway
  processor's private `assertNotRunning()`/`add()`, which the portal builder
  does not have — a hard TypeError blocking oeth/ousd/oethb. It only existed
  to dodge the gateway's address lowercasing; evm-stream does none, and
  squid-utils now normalizes registrations itself, so the second checksummed
  `callTo` entry goes too. Trace now comes from squid-utils, not evm-processor.
- portal-cache.ts: ported to portal-client 0.7 — finality moved to
  `batch.meta.finalizedHeadNumber`, `getStream` grew a third `finalized` arg,
  and the prototype patch became an instance wrap (the tree resolves more than
  one physical copy of the class, so a prototype patch can attach to one nobody
  instantiates). Algorithm, schema, zstd and stats are unchanged. Existing
  .portal-cache data is 100% invalidated by the query-shape change — clear it
  or use a fresh PORTAL_CACHE_DIR.
- portal-client 0.7.0; the 0.3.2 pnpm patch is dropped, its TraceCreateResult
  fix is upstream.
- traceFilter/oeth.ts: optional sighash guard and a processor type annotation.

Does not compile until squid-utils publishes `runPortal`, `createPortalClient`
and `SquidProcessor.portalClient` — 3 errors in dumps.ts until then. Validated
against a local `file:` link (see AGENTS.md).
…2 switch

335a727 repointed the OUSD/USD oracle to the 18-decimal v2 without moving the
height gate, and c73c1da added the decimal correction 40 minutes later. v2 has no
code until block 25,427,435, so replaying any earlier range calls a codeless
address and throws FunctionResultDecodeError on decode. Route by height instead:
v1 (x10^10) below the switch, v2 above.

The constant is production's actual switchover block, so a resync reproduces
deployed history rather than introducing a pricing change. Verified against prod:
wOUSD_USD at 25841364 already carries the v2 value.
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.

1 participant