Skip to content

feat(relayer): TokenBridge adapter foundation - #373

Open
sadiq1971 wants to merge 4 commits into
mainfrom
feat/bridge-foundation
Open

feat(relayer): TokenBridge adapter foundation#373
sadiq1971 wants to merge 4 commits into
mainfrom
feat/bridge-foundation

Conversation

@sadiq1971

@sadiq1971 sadiq1971 commented Jul 23, 2026

Copy link
Copy Markdown
Member

Closes #356. Part of epic #361.

What

The multi-token bridging foundation: a TokenBridge adapter interface driven by a generic step loop. No behavior change — the legacy single-token (PROMPT) pipeline is untouched and keeps running; its port to an adapter is deferred (#372). No adapter mechanisms are registered yet, so the new driver is dormant until the xreserve adapter lands (#357).

Design: docs/bridging-design.md (included in this PR).

How it works

A transfer is a durable record advanced by an idempotent Step(ctx, *Transfer) (StepResult, error) until it reaches a terminal status; mechanisms differ only in their stage sequences.

  • pkg/relayer/bridge.goTokenBridge (Key/Sources/Step), Source, StepResult, Registry
  • pkg/relayer/engine/driver.go — one ingest loop per adapter source (detection only; offsets under bridge-scoped chain-state keys <bridge>:<chain> so adapters never share a cursor with the legacy pipeline) + one step loop for all mechanisms (retry backoff, max-retry failure, adapter-bug guards)
  • transfers schema — bridge_key (existing + legacy-pipeline rows default 'wayfinder'), token_symbol, stage, metadata jsonb, next_step_at; idempotent migration so fresh DBs (whose migration 1 creates the full model) pass through unchanged
  • store — GetSteppableTransfers / ApplyStep (merges metadata, clears next_step_at on terminal) / RecordStepError; instrumented wrappers + regenerated mocks
  • app wiring — registry built from a new optional bridge.tokens config map (mechanism, evm_address, decimals); the driver only starts when adapters are registered

Notes for review

Testing

  • Unit: registry, driver step semantics (apply/error/empty-status/max-retries/orphaned-key), ingest (event → transfer + offset), TransferFromEvent
  • Store: testcontainers tests for the three new methods (steppable filtering incl. bridge-key/status/due-time, jsonb merge, retry accounting) — skip locally without Docker, run in CI
  • golangci-lint clean; existing relayer tests pass unchanged

Introduce the multi-token bridging foundation (#356): a TokenBridge
adapter interface driven by a generic step loop, coexisting with the
legacy single-token pipeline (untouched; port tracked in #372).

- pkg/relayer: TokenBridge/Source/StepResult interfaces + Registry;
  per-token config map (mechanism, evm_address, decimals)
- pkg/relayer/engine: Driver with one ingest loop per adapter source
  (detection only, bridge-scoped chain-state keys) and a step loop
  that advances adapter-owned transfers until terminal, with retry
  backoff and max-retry failure handling
- transfers schema: bridge_key (legacy rows default 'wayfinder'),
  token_symbol, stage, metadata jsonb, next_step_at + indexes
- store: GetSteppableTransfers / ApplyStep (jsonb metadata merge) /
  RecordStepError, instrumented + mocked
- app wiring: registry built from bridge.tokens config; driver starts
  only when adapters are registered (none yet - xreserve lands in #357)
- docs: bridging design (docs/bridging-design.md)
@codecov-commenter

codecov-commenter commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.24365% with 133 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@9fb5f48). Learn more about missing BASE report.

Files with missing lines Patch % Lines
pkg/relayer/engine/driver.go 71.78% 39 Missing and 18 partials ⚠️
pkg/migrations/relayerdb/3_multi_token.go 0.00% 27 Missing ⚠️
pkg/relayer/store/instrumented.go 0.00% 21 Missing ⚠️
pkg/relayer/store/pg.go 78.87% 7 Missing and 8 partials ⚠️
pkg/app/relayer/server.go 0.00% 13 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #373   +/-   ##
=======================================
  Coverage        ?   34.91%           
=======================================
  Files           ?      167           
  Lines           ?    13004           
  Branches        ?        0           
=======================================
  Hits            ?     4540           
  Misses          ?     8098           
  Partials        ?      366           
Flag Coverage Δ
unittests 34.91% <66.24%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
pkg/relayer/bridge.go 100.00% <100.00%> (ø)
pkg/relayer/engine/engine.go 46.32% <ø> (ø)
pkg/relayer/engine/metrics.go 95.23% <100.00%> (ø)
pkg/relayer/store/metrics.go 0.00% <ø> (ø)
pkg/relayer/store/model.go 100.00% <100.00%> (ø)
pkg/relayer/types.go 100.00% <100.00%> (ø)
pkg/app/relayer/server.go 0.00% <0.00%> (ø)
pkg/relayer/store/pg.go 81.28% <78.87%> (ø)
pkg/relayer/store/instrumented.go 0.00% <0.00%> (ø)
pkg/migrations/relayerdb/3_multi_token.go 0.00% <0.00%> (ø)
... and 1 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The driver treats pending and in_progress identically, so the extra
status added nothing; the stage column already communicates progress
detail. Status enum is back to the legacy pending/completed/failed.
Also update the design doc for stateless quotes (applied in the bridge
API branch).
- ingest aborts the stream on a CreateTransfer failure instead of
  advancing the offset past an unrecorded event (was: silent event loss
  on transient DB errors)
- ApplyStep resets retry_count on a successful step so the max-retries
  gate counts consecutive, not lifetime, failures (was: long-lived
  observer transfers force-failed by scattered transient errors)
- Driver.Start tears down already-launched ingest goroutines when a
  later bridge's Sources() errors (was: leaked loops on partial start)
- GetSteppableTransfers orders by next_step_at (due-time fairness) so a
  steady backlog cannot starve newer transfers
- legacy GetPendingTransfers filters to the legacy bridge key so the
  reconcile loop never processes adapter-owned rows
- per-Step timeout so one hung adapter can't stall the shared loop
- StepResult.Reason persisted on adapter-terminated failure
@sadiq1971
sadiq1971 marked this pull request as ready for review July 29, 2026 09:44

@dhyaniarun1993 dhyaniarun1993 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one question:
Does this TokenBridge interface supposed to work in both direction toCanton and fromCanton?

@sadiq1971

Copy link
Copy Markdown
Member Author

Does this TokenBridge interface supposed to work in both direction toCanton and fromCanton?

yes

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.

feat(relayer): TokenBridge adapter foundation (interface, registry, driver)

3 participants