Skip to content

feat: all the things - #1276

Draft
gjermundgaraba wants to merge 73 commits into
developmentfrom
gjermund/full-pupp
Draft

feat: all the things#1276
gjermundgaraba wants to merge 73 commits into
developmentfrom
gjermund/full-pupp

Conversation

@gjermundgaraba

@gjermundgaraba gjermundgaraba commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

stacked pr on top of the e2e pr.
this has an initial implementation of both attestor and the relayer.
mostly fable.

dhfang and others added 30 commits July 10, 2026 18:47
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tore

# Conflicts:
#	link/internal/config/config.go
#	link/internal/config/ibc.yml
#	link/internal/config/relayer.go
#	link/internal/config/relayer_test.go
gjermundgaraba and others added 29 commits July 14, 2026 10:49
- readiness JSON as the single stdout line (logs stay on stderr); ephemeral
  listen support with Server.Addr(); startup order build -> migrate -> ping
  -> per-chain RPC probe (exit 65 on unreachable) -> bind -> engine ->
  readiness; sysexits on all failure paths
- plain HTTP JSON endpoints beside ConnectRPC: GET /health, POST /relay
  (packetIds; 404 'no packet found' for unknown txs incl. missing receipts,
  400 invalid, 502 upstream), GET /status?route=&packet=
- product id scheme: routeID = client alias (else chainID/clientID),
  packetID = routeID-sequence; state mapping pending|complete|timed_out|
  error_ack with effect tx hashes and reasons

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- environment.RelayerSpec{ID, Connection, Authority, AutoRelay, Lookback}
  realized by running the real 'ibc relayer run' binary (managed-attestor
  pattern): real link config YAML + funded signer keyfile, readiness line
  parsing, /health probe, Status/Relay/Stop surface
- attestorSet orientation derived mechanically from the Spec graph (each
  client end's set = attestors bound to that deployed client)
- teardown order relayers -> attestors -> chains; env.Relayer(id) accessor

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The real binary now requires a top-level chains entry and per-attestation
evm{routerAddress, finalityOffset}; the harness config writer emits both
(explicit zero offset — harness lanes finalize instantly and may not serve
the finalized tag). Realization threads the observed chain RPC + router.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- solidityibc gains ICS27 GMP + IFT application deployment (account/gmp
  logic + proxies, router app registration, public relay role grants,
  cross-chain IFT bridge registration, local EVMIFTSendCallConstructor
  artifact) exposed via Environment.DeployIFT/AuthorizePublicRelay
- new e2e/protocol suite: TestIFT_RealRelayer_AutoRelay and _ManualRelay —
  two anvil chains, real attestor quorum, real 'ibc relayer run', on-chain
  burn/credit assertions + /status packet lifecycle; wired into test-e2e
- relayerproc normalizes attestor grpc endpoints to scheme-qualified URLs
- fix(store): sqlite pragmas were silently ignored (journal_mode passed as
  a plain DSN param; url.Values.Set clobbered multiple _pragma values) —
  WAL + busy_timeout(5000) now actually apply, removing transient
  SQLITE_BUSY 500s from /status under concurrent engine writes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…qlite

'ibc migrate up' (real binary) creates the real packets/relay_requests
schema in the same db file the stub daemon opens; the stub's CREATE TABLE
IF NOT EXISTS then no-ops and its queries fail (no such column: packet_id),
wedging /status. Prefix all stub tables with stub_. Fixes the three
pre-existing synthetic failures (restart-survival, pending-signal,
external-chain).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
link:
- proofgen: immutable per-height state-attestation cache + singleflight —
  eliminates the conflicting-state-attestation path that permanently
  freezes the on-chain light client; honor counterpartyChainFinalityOffset
- bootstrap: fail startup on numeric chain-id mismatch (exit 64); bounded
  RPC probes (15s) classified as exit 65
- auto-relay: per-route cursors (007 migration), reorg-safe scanning to
  finalized height, no cursor advance past failed inserts, lookback
  boundary off-by-one
- engine: status persistence gates delivery; submission-persist retry with
  loud double-submit warning; out-of-band completions classified via
  ack/timeout event scans; paged backward scan for old external recvs;
  routes built only from routesToRelay (manual routes included) and /relay
  rejects routeless clients
- http: error_ack only when terminal with confirmed source ack effect;
  FAILED maps to new 'failed' wire state; /status filters pushed into
  indexed store queries

e2e:
- wire.RelayerPacketID canonical constructor; relayerproc verifies
  readiness covers all requested chains; manual-relay test polls through
  the settle window and asserts zero destination balance before /relay

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- delete dead surface: tx-submission resolution queries, PacketBatchTimeout,
  WaitForChainCatchup/Receiptor/Receipt, exported proofgen interface +
  WaitForHeight, Aggregated.Timestamp, unused Services fields, oversized
  attestor interface methods, ListPacketsFilter.Status, abigen tool directive
- squash relay-cursor migrations (006 creates cursor_key directly)
- consolidate saturating-sub and chainID/clientID key helpers; single
  startup chain-id probe path shared by bootstrap and CLI (exit 64/65
  semantics preserved); drop double tx-hash parse in /relay
- fix stale/contradicting comments; validation fail-fast vs structured
  parity test matrix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tcomes

- proofgen: LatestHeight returns the quorum height (threshold-th highest)
  instead of min, so a lagging attestor cannot stall relaying; reduce now
  verifies signer recovery, dedups signers, takes the height from the
  signed data, and truncates to exactly threshold, mirroring the on-chain
  attestation client's rejection rules
- engine/store: tx submissions resolve to SUCCESS/FAILED/EXPIRED with gas
  cost from a single receipt read; create+link is atomic; out-of-band
  error acks are classified like in-band ones
- observability: /metrics (prometheus), Connect health service, env-gated
  OTLP tracing, attestor/RPC/relay metric families with cardinality guard
- service/server: one packet-status derivation shared by HTTP and Connect,
  /health does store-ping readiness, Connect errors map to proper codes
- config: structured validation is the single source (fail-fast derives),
  local attestors must attest the client's counterparty chain, router
  addresses optional pre-deploy and enforced at daemon startup
- store: live probing moved to internal/preflight, canonical clientkey
  format for persisted cursor keys and route ids

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHZxoHqx3joFwonM3EBKXe
Port the harness's proven Solidity IBC deployment orchestration into
link/internal/deploy and expose it as `ibc deploy <client-alias>` and
`ibc connect <client-alias>` (PRD P0: collapse the 9+ manual txs).

- deploy = instance (AccessManager, ICS26Router proxy) + GMP/IFT apps +
  attestation clients + public-relay authorization on both chains;
  connect adds the cross-chain IFT bridge wiring
- preflight resolves and validates everything static (attestor addresses,
  client ids, counterparty pairing) before any transaction; chain
  identity (eth_chainId vs config) verified before deploying
- idempotent re-runs: config-known routers attach and verify, occupied
  client ids require an exact on-chain trust-policy match (attestor set +
  threshold), partial app state fails loud with recovery guidance
- --dry-run prints the planned tx sequence without dialing; --write-config
  patches only router addresses via yaml.Node surgery (comments, ordering
  and ${ENV} placeholders preserved); partial results survive errors
- bytecode from the pinned go-abigen module plus slim embedded forge
  artifacts (AccessManager, EVMIFTSendCallConstructor: abi+bytecode only)
- gated integration test (TEST_ANVIL=1) drives a full two-anvil connect,
  wrong-chain, trust-mismatch and rerun scenarios

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHZxoHqx3joFwonM3EBKXe
- dual mode: RelayerSpec.EmbedAttestors folds attestations + signers into
  one `ibc relayer run` process (local attestor refs, no gRPC); standalone
  attestor processes start per-need so mixed embedded/remote topologies on
  one connection work; TestIFT_RealRelayer_DualMode proves one binary +
  one config end-to-end
- lane-aware protocol fixture: the real-binary IFT suite now runs on all
  three lanes (anvil, anvil-interval, besu) instead of hard-skipping
  outside anvil — first truthful coverage of the PRD's Besu anchor
  (QBFT, 2s blocks; auto, manual and dual mode all pass)
- harness: workspace/key-file/log-tail machinery deduplicated into
  internal/proc; attestor locality inferred from the attestation relation
  (single source of truth); redundant guards and narration trimmed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHZxoHqx3joFwonM3EBKXe
Correctness:
- proofgen enforces attestor identity: every member carries an expected
  EVM address (explicit for remote — now required in config — or derived
  from the loaded local signer's public key, closing the key-file TOCTOU);
  wrong-signer responses are rejected before quorum counting, so a
  mis-keyed endpoint ordered first can no longer poison a healthy quorum
- engine truthfulness: a confirmed recv tx that carries no WriteAck for a
  packet (router no-op race) clears and re-converges instead of stranding
  in WAIT_FOR_WRITE_ACK; exhausted source-gone scans terminalize as
  FAILED_INVARIANT with an operator-visible reason instead of fabricating
  COMPLETE_WITH_ACK; the source-gone ack guard routes through backfill so
  terminal acks always carry real evidence
- all post-broadcast writes (packet hashes + submission + links) commit in
  one transaction; /status regression-proofed at store/service/handler
  layers (root cause was the sqlite pragma bug; accommodations removed)
- config: chains require a supported type block, relayer.signer must be
  local, remote attestor entries require a non-zero evmAddress, KMS-backed
  local attestations need an explicit address; --db override applies
  before validation; migrate validates only the db section
- lifecycle: attestor chain adapters and remote signers close via
  Services.Close with bootstrap unwind; monitor's silent zero finality
  offset removed (construction guarantees the mirror); deploy
  --write-config errors on failure with atomic, mode-preserving,
  symlink-safe replacement; status prober detects wrong-network RPCs

Operator surface (PRD):
- new `ibc status [alias]`: per-route connection state, packet counts,
  stuck packets with age and attempts, and per-leg attestor quorum
  (recv = mirror set, ack/timeout = source set) with ok/unknown/no states
- new `ibc version` (ldflags-stamped); `ibc query` placeholder removed

Docs: attestation signing carries no chain/client domain separator (matches
the deployed light client); noted at the signing seam with operator guidance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHZxoHqx3joFwonM3EBKXe
The real-binary protocol suite grows from 3 to 13 tests, all lane-aware
(anvil, anvil-interval, besu):
- lifecycle: timeout + refund (block-interval-scaled timeout margin), real
  ICS27 GMP delivery, GMP universal-error-ack round trip
- resilience: restart recovery on the same workspace/db with a
  deterministic dest-outage barrier, quorum with a stopped attestor,
  wrong-key attestor ordered FIRST (load-bearing proof of identity
  enforcement), postgres-backed relayer lane, cross-route isolation under
  one multi-connection relayer, destination RPC outage recovery, and a
  focused status-views availability test
- harness: relayer Restart, attestor Stop, SigningKeyAuthority fault
  injection (guarded against embedded attestors), multi-connection
  relayer specs, PostgresURL override, shared await/fixture helpers,
  remote attestor refs carry evmAddress, wire model knows failed state
- Makefile: E2E_TIMEOUT=45m default (slow lanes exceed go test's 10m)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHZxoHqx3joFwonM3EBKXe
The temporary stand-in `ibc-stub` binary has served its purpose: every
wire-contract capability it filled is now satisfied by the real relayer,
so the swap ledger closes. Delete the stub binary, the synthetic driver,
the mock test applications (MockIFT/MockGMP/Counter + forge artifacts),
the ibclink/ and negative/ suites, the stub-side Driver/daemon/bindings
machinery, and the stub-only wire types (~8.3k lines).

Unique stub-lane coverage is ported onto the real stack first:
- cold-send backfill: auto-relay lookback completes a packet sent while
  the relayer was down (TestIFT_RealRelayer_ColdSendBackfill)
- expired cold send: the startup scan times out and refunds a packet
  already dead on arrival (TestTimeout_RealRelayer_ColdSendRefund)
- reverse direction: B->A relaying end to end, previously only A->B
  (TestIFT_RealRelayer_ReverseDirection)
- pending truthfulness: /status stays non-terminal while the recv sits
  unmined under paused mining (TestPending_StatusStaysTruthfulUntilDelivered)
- unknown-tx manual relay is rejected with the daemon's 404
- real-schema `ibc config validate` exit-code contract (0/64/65) with
  hand-rolled product-schema YAML (setup suite)
- attached external chain: the environment relays real traffic over an
  out-of-band node it must not own (external suite, real relayer)

Product change: `ibc config validate --live` now compares the node's
eth chainId against the configured one (numeric ids only, mirroring
bootstrap's label exemption) instead of discarding the fetched value; a
mismatch maps to a located error and exit 65.

The cross-route test now pins its sequence-number collision, the mempool
wait primitive no longer snapshot-and-diffs (WaitPendingTx: a recv
already pending at call time is a pass, not a hang), clean.sh sweeps the
real relayer/attestor daemons by their harness workspace marker, CI
drops the deleted negative suite, and the e2e/harness/architecture docs
describe the stub-free state.

All lanes green: link checks, harness, unit, and the full e2e suites on
anvil, anvil-interval, and besu.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHZxoHqx3joFwonM3EBKXe
Upstream restructured the e2e harness (flattened the nested internal tree,
typed abigen AccessManager bindings, moby-based Docker control, QBFT Besu)
and relocated its stub lane into link/. This branch had retired the stub
and shipped the real relayer/attestor engine with real-binary e2e suites.

Resolution: upstream's structure, this branch's functionality.
- link/: real engine kept everywhere both sides touched (signer message-in
  contract, server, CLI, proto/api with go_package moved to api/v2 and
  bindings regenerated); upstream's stub packages (internal/stub,
  cmd/{relayercmd,configcmd,testappcmd}, testappbindings) dropped;
  link/keyfile adopted at upstream's public location.
- harness: flattened layout adopted; attestor runner rebuilt on upstream's
  readiness/process shape with this branch's fault injection, dual-mode and
  chains+evm config emission grafted on; relayerproc and the shared proc
  primitives moved to flattened homes; solidityibc unified on typed
  bindings with this branch's IFT/GMP app deployment on top.
- e2e: real-binary suites (setup/external/protocol) kept; upstream's
  stub-driven root suites, traffic helpers and mock contracts dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHZxoHqx3joFwonM3EBKXe
The merged harness stops a node by pausing its container, which keeps the
port bound and hangs connections instead of refusing them — a truthful
blackhole. That exposed a real relayer gap: RPC requests had no timeout,
so one unresponsive chain wedged the whole relay loop and source-side
discovery never ran. Give the EVM chain client a per-request HTTP timeout
so a hung endpoint degrades into retryable errors.

Test-side: node-down probes carry their own short deadline (a paused node
hangs a bare Height query forever), and observePacketNeverTerminal grants
first observation the completion budget — discovery may legitimately burn
an RPC timeout against the hung chain before reaching the source chain —
while the never-terminal settle window is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHZxoHqx3joFwonM3EBKXe
Base automatically changed from gjermund/e2e-arch to development July 27, 2026 21:37
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