feat: all the things - #1276
Draft
gjermundgaraba wants to merge 73 commits into
Draft
Conversation
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
- 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
# Conflicts: # proto/buf.gen.yaml
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
stacked pr on top of the e2e pr.
this has an initial implementation of both attestor and the relayer.
mostly fable.