Skip to content

Backport bridge to main#5875

Draft
deuszx wants to merge 13 commits intomainfrom
backport-bridge-to-main
Draft

Backport bridge to main#5875
deuszx wants to merge 13 commits intomainfrom
backport-bridge-to-main

Conversation

@deuszx
Copy link
Copy Markdown
Contributor

@deuszx deuszx commented Mar 29, 2026

No description provided.

deuszx and others added 13 commits March 28, 2026 00:07
…5763)

## Motivation

With the monotonic increase of `nextBlockHeight` in `Microchain.sol`
it's more complicated to keep the messages relayed in a correct order.

## Proposal

BFT-finalized certificates already guarantee block canonicality — a
quorum of validators cannot sign two conflicting blocks at the same
height. The sequential height check added no security value while
forcing the relayer to submit every block (including irrelevant ones) in
strict order, wasting gas and adding fragility.

The verifiedBlocks mapping continues to prevent duplicate processing.

## Test Plan

CI

## Release Plan

None

## Links

- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
## Motivation

Bridge e2e tests are failing again.

## Proposal

Update docker compose to not pass the nextExpectedHeight which was
removed in #5763

## Test Plan

CI

## Release Plan

None

## Links

- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
Post `testnet_conway` merge job is broken.

Comiple the `evm-bridge` contract.

CI

None for now

- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
Right now exporters are crashing on the indexer's ack stream.

Until we fix the root cause, add `/health` endpoint which returns 500
whenever we get a non-recoverable error on any of the streams.

CI and manual

- Test on conway, then backport.

- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
The bridge verifies EVM block finality before processing deposits. The
source chain is Base (OP Stack L2, chain ID 8453), which changes the
threat model:
- L2 RPC providers more commonly misconfigure endpoints (Base Mainnet vs
Sepolia vs other OP Stack chains)
- The `"finalized"` tag has different semantics on OP Stack (tracks L1
finality of the L2 batch)
- 2-second block times mean cache grows 6x faster than L1

- Add `rpc_endpoint` field to `BridgeParameters` and
`verified_block_hashes` to contract state
- `ProcessDeposit` verifies block hash finality inline when
`rpc_endpoint` is configured, querying the source EVM chain
(`eth_getBlockByHash` + `eth_getBlockByNumber("finalized")`)
- Add `VerifyBlockHash` operation for pre-verification; caches the hash
only when submitted by an authenticated signer (chain owner) to prevent
state bloat
- `ProcessDeposit` also caches verified block hashes after replay
protection, so subsequent deposits from the same block skip the RPC
check
- Log a warning when `rpc_endpoint` is empty and finality verification
is skipped

- Add `get_chain_id()` to the `EthereumQueries` trait in
`linera-ethereum`
- Validate the RPC endpoint's chain ID matches `source_chain_id` during
`instantiate()`, catching misconfigured endpoints at deploy time

- Add `ProofError` enum (transient vs permanent) to deposit proof
generation
- Transient: receipt not found, block not found, RPC transport errors →
retry with backoff
- Permanent: header hash mismatch, receipts root mismatch, no
DepositInitiated event → return 400 immediately
- `thiserror` dependency gated behind `offchain` feature so it doesn't
leak into examples

- Retry deposit proof generation up to 5 times with linear backoff
(public testnet RPCs may not have indexed the receipt immediately)
- Permanent errors fail immediately with `400 BAD_REQUEST` instead of
wasting 20s retrying
- Add structured `tracing` logs throughout the deposit handler and main
loop

- Accurate for any EVM-compatible JSON-RPC endpoint, not just Ethereum
L1

- Add fallible `try_create_application` to `ActiveChain` in `linera-sdk`
test framework

- Auto-detect `linera` / `linera-bridge` binaries from `target/debug`,
`target/release`, or `$PATH`
- Wait for EVM transaction inclusion after each `forge create` to avoid
nonce races on public testnets
- Add `sync` + `process-inbox` steps before Linera app deployments
- Pass `EVM_RPC_URL` into evm-bridge application parameters

- Run bridge e2e workflow on `pull_request` events
- Update README with Conway testnet faucet URL and improved `SHARED_DIR`
instructions

- `cargo test -p linera-ethereum --features ethereum` — all Anvil tests
pass including new `test_get_chain_id`
- `cargo test --manifest-path examples/evm-bridge/Cargo.toml` — 9
unit/integration tests pass including
`test_instantiation_fails_with_unreachable_endpoint`
- `cargo test --manifest-path examples/evm-bridge/Cargo.toml --
--ignored` — 2 Anvil tests pass (`test_verify_block_hash_anvil`,
`test_verify_block_hash_not_found`)
- `cargo test -p linera-bridge` — proof/relay tests pass
- `cargo clippy -p linera-bridge --lib --features chain
--no-default-features` — confirms no `thiserror` leakage into examples

- Nothing to do / These changes follow the usual release cycle.

Closes #5629
- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
The bridge relay (`linera-bridge serve`) used ephemeral in-memory
storage and generated fresh keypairs on every startup. Every restart
required re-claiming a chain from the faucet, invalidating all deployed
EVM
contracts that reference the old chain ID. On testnets this forced full
redeployment of everything.

Additionally, the relay's CLI was inconsistent with the `linera` binary
— it used custom `--data-dir` and file-polling flags instead of the
standard `--wallet`, `--keystore`, `--storage` pattern.

`GenesisConfig` (from `linera-client`) and `PersistentWallet` (from
`linera-service`) are moved to `linera-core` to break the cyclic
dependency that prevented `linera-bridge` from using these types. Both
are
re-exported from their original locations for backward compatibility. A
new `fs` feature on `linera-core` gates the `linera-persistent`
dependency.

The relay now accepts the same `--wallet`, `--keystore`, `--storage`
flags and `LINERA_WALLET`/`LINERA_KEYSTORE`/`LINERA_STORAGE` env vars as
the `linera` binary, defaulting to `~/.config/linera/`. It uses
`PersistentWallet` for chain metadata and RocksDB for block storage.

`--linera-bridge-chain-id` selects a pre-existing chain (syncs from
validators, verifies the keystore contains an owner key). Without it, a
new chain is claimed from the faucet.

File polling is removed — `--evm-bridge-address`,
`--linera-bridge-address`, and `--linera-fungible-address` are required
directly.

A new `bridge-chain-init` compose service claims the bridge chain before
the relay starts. The `--http-request-allow-list` flag is added to
`linera net up` (configurable via `HTTP_REQUEST_ALLOW_LIST` env var) for
  EVM finality verification.

`setup.sh` accepts `--linera-wallet`, `--linera-keystore`,
`--linera-storage` to use an existing wallet (or respects env vars). It
no longer claims its own chain and prints all deployed addresses with a
ready-to-copy relay command at the end.

1. `linera wallet init --faucet <URL>`
2. `linera wallet request-chain --faucet <URL>`
3. `setup.sh --linera-bridge-chain-id <ID> --relay-owner <OWNER> ...`
4. `linera-bridge serve --linera-bridge-chain-id <ID> ...`

- `cargo test -p linera-bridge` — all 72 unit tests pass
- `cargo clippy -p linera-bridge --features relay` — clean
- All 3 Docker e2e tests pass (`committee_rotation`,
`evm_to_linera_bridge`, `fungible_bridge`)
- Manual testnet deployment verified (Base Sepolia + Conway testnet)

- Nothing to do / These changes follow the usual release cycle.

- Resolves #5704
- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
## Motivation

Bridge e2e task is currently failing - fix it.

## Proposal

Don't add `--http-request-allow-list` in docker compose of the bridge
test if it's not set.

Should make the CI green and unblock docker image publishing.

## Test Plan

CI

## Release Plan

None.

## Links

- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
…ing requests. (#5793)

The linera-bridge relay had no visibility into the state of bridging
requests. If a deposit was made on EVM but the relay crashed
mid-processing, tokens would be locked on EVM with no minted counterpart
on Linera
— and nobody would know. Similarly, if a Linera→EVM burn was detected
but the certificate failed to forward to EVM, the burn would be silently
lost.

Add active monitoring and automatic retry to the bridge relay, so it
self-heals without operator intervention.

Add `/metrics` endpoint where we serve Prometheus metrics.

Replace scattered `provider`/`chain_client` clones with single
`EvmClient` and `LineraClient` instances shared via `Arc`. All chain
write operations (`ProcessDeposit`, `Burn`, `ProcessInbox`) go through
an mpsc
channel to the main loop, eliminating quorum conflicts from concurrent
block proposals.

- `EvmClient<P>`: wraps alloy Provider with typed API
(`get_deposit_logs`, `get_transfer_logs`, `forward_cert`)
- `LineraClient<E>`: read operations use ChainClient directly (safe on
clones), write operations serialized through channel

- **EVM scan loop**: polls `DepositInitiated` events from the
FungibleBridge contract, checks Linera for completion via
`isDepositProcessed` GraphQL query
- **Linera scan loop**: walks block history for Credit-to-Address20
messages, checks EVM for completion via ERC-20 `Transfer` events

- Scan loops send newly discovered pending items to retry loops via
channels
- **Deposit retry**: generates MPT proof from `tx_hash` and submits
`ProcessDeposit` to Linera (on-chain `processed_deposits` prevents
double-minting)
- **Burn retry**: submits Burn via channel to main loop, forwards cert
to EVM directly (EVM `verifiedBlocks` prevents duplicates)
- Exponential backoff (5s → 80s cap) with configurable max retries
- Guards against duplicate processing: checks `forwarded` status and
sets `last_retry_at` before starting work

- **Fixed duplicate burn/deposit processing**: scan loop re-enqueue
could race with in-progress retry, causing double Burn submissions. Now
sets `last_retry_at` at processing start and skips already-completed
items.
- **Removed unnecessary cert forwarding**: inbox certs and deposit certs
were forwarded to EVM but FungibleBridge ignored them (only burn certs
trigger `_onBlock`)
- **Fixed notification listener**: `extend_chain_mode()` was not called
for pre-existing chains, preventing the relay from receiving
`NewIncomingBundle` notifications
- **Removed `/deposit` HTTP endpoint**: no longer needed since the
scanner auto-detects deposits
- **Frontend stale balance**: added 5s polling interval to catch
relay-driven balance changes

Replaced the monitor HTTP endpoints with a `/metrics` endpoint exposing:
- `linera_bridge_deposits_detected`, `_completed`, `_pending`, `_failed`
- `linera_bridge_burns_detected`, `_completed`, `_pending`, `_failed`
- `linera_bridge_last_scanned_evm_block`, `_last_scanned_linera_height`

- Split `monitor.rs` into `monitor/mod.rs`, `monitor/evm.rs`,
`monitor/linera.rs`
- Split `relay.rs` into `relay/mod.rs`, `relay/evm.rs`,
`relay/linera.rs`, `relay/metrics.rs`
- Introduced `Tracked<T>` generic wrapper to deduplicate
`TrackedDeposit` / `TrackedBurn`
- Added `fungible` and `wrapped-fungible` as dependencies to use real
types instead of inline BCS parsing

- Anvil: `--slots-in-an-epoch 1 --block-time 1` for EVM finality
- Validator: `anvil` added to HTTP request allow list
- Relay waits for `setup-complete` marker before starting
- setup.sh: retry `publish-and-create` up to 3 times, pass `EVM_RPC_URL`
to evm-bridge

- `--monitor-scan-interval <SECONDS>` (default: 30)
- `--monitor-start-block <NUMBER>` (default: 0)
- `--max-retries <N>` (default: 10)

- [x] 10 unit tests for `MonitorState`, backoff logic, deposit key
hashing
- [x] E2E test (`test_evm_to_linera_bridge`): verifies
`isDepositProcessed` query before and after deposit
- [x] E2E test (`test_auto_deposit_scan`): bidirectional — deposits
auto-scanned on EVM and processed on Linera, burns auto-detected and
forwarded to EVM with ERC-20 balance verification
- [x] All existing E2E tests pass (`committee_rotation`,
`evm_to_linera_bridge`, `fungible_bridge`)
- [x] Docker image rebuilt and tested
## Motivation

The bridge relayer needs to be deployable on infrastructure via Docker.
The existing `Dockerfile.bridge` builds the binary but has no
entrypoint, env var support, or exposed ports.

Additionally, `--faucet-url` is unconditionally required even when
running with a pre-existing wallet, which prevents standalone deployment
without a faucet.

## Proposal

- Add `bridge-entrypoint.sh` that maps environment variables to CLI args
for the `serve` subcommand, with pass-through for other subcommands
(`init-light-client`, `generate-deposit-proof`, `sh -c` wrappers).
- Update `Dockerfile.bridge` runtime stage: ENTRYPOINT, CMD, EXPOSE
3001, ENV defaults for optional args, documented required/optional vars.
- Make `--faucet-url` optional in the Rust code. It is now only required
when the wallet doesn't exist (needs genesis config) or
`--linera-bridge-chain-id` is not provided (needs to claim a chain).
Admin chain sync uses `synchronize_from_validators()` instead of
fetching the committee from the faucet.
- Add Makefile targets: `build-relayer`, `relayer-up` (validates
required env vars, prints config, runs container), `relayer-down`,
`relayer-logs`.
- Update `docker-compose.bridge-test.yml` relay service to use the new
entrypoint.
- Fix CI: add missing `cargo build -p linera-bridge --features relay`
step for the `auto_deposit_scan` test which spawns a local relay binary.

## Test Plan

- `cargo check -p linera-bridge --features relay` compiles clean
- Docker image builds via `make -C linera-bridge build-relayer`
- All bridge e2e tests pass locally:
  - `test_evm_to_linera_bridge` — EVM→Linera deposit with proof
- `test_fungible_bridge` — Linera→EVM burn with ERC-20 balance
verification
- `test_auto_deposit_scan` — bidirectional: auto-scanned deposit +
auto-forwarded burn

## Release Plan

- Nothing to do / These changes follow the usual release cycle.

## Links

- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
## Motivation

The bridge relayer currently stores all deposit/burn request state
in-memory via `MonitorState`. On
restart, all request history is lost. We need persistent storage so
that:

1. Request history survives restarts for auditing and debugging.
2. Requests can be queried by source, recipient, amount, timestamp, and
status.
3. Raw BCS-serialized operation bytes can be `SELECT`ed and uploaded to
the chains **without the relayer
  running**.

## Proposal

Add a SQLite write-through layer alongside the existing in-memory
`MonitorState`. Every mutation (track,
complete, fail) now persists to SQLite in addition to updating the
`HashMap`.

**New file: `linera-bridge/src/monitor/db.rs`**
- `BridgeDb` struct wrapping a `SqlitePool`
- Two tables: `deposits` and `burns` with columns for source, recipient,
amount, status, timestamps, and
raw operation bytes (BCS-serialized `BridgeOperation::ProcessDeposit`
for deposits,
`ConfirmedBlockCertificate` for burns)
- All writes are idempotent (`INSERT OR IGNORE` / `UPDATE`)

**Changes to `MonitorState`:**
- Holds an `Option<BridgeDb>` (None in tests without a DB)
- `track_deposit`, `complete_deposit`, `track_burn`, `complete_burn`,
`mark_*_failed` are now `async`
and write through to SQLite internally

**Raw bytes storage:**
- Deposit proofs: BCS-serialized after proof generation in
`monitor/evm.rs`
- Burn certs: BCS-serialized after `linera_client.burn()` in
`monitor/linera.rs` (panics on
serialization failure since the cert must be serializable to forward to
EVM)

**CLI:**
- New `--sqlite-path` option to override the default location
(`bridge_relay.sqlite3` next to the
RocksDB storage directory)
- SQLite open failure is fatal (exits the binary)

## Test Plan

- 9 unit tests parameterized via `test_case` across both in-memory and
file-backed SQLite backends:
insert, complete, fail, idempotency, raw bytes storage for both deposits
and burns
- 1 dedicated file persistence test verifying data survives close and
reopen
- All 92 existing `linera-bridge` tests continue to pass (`cargo test -p
linera-bridge --features
relay`)

## Release Plan

- Nothing to do / These changes follow the usual release cycle.

## Links

- [reviewer

checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
## Motivation

Bridge e2e tests are failing. Turns out the problem is non-deterministic
choice of the owner from the wallet of bridge chain owner.

Another issue was that logs were incorrectly sent to stdout which – if
not consumed – would fill up the buffer (64k) and then block other
processes from writing to it. Which, in our case, caused some problems
down the line where things were read from the log output.

## Proposal

For the logging issue, two fixes:
1. Initialize the tracing subscriber properly (using a method from
`linera_base`)
2. For docker-compose based tests (via `test-containers` crate) we pipe
do /dev/null.

For the non-deteministic owner we add a new argument to the linera
relayer `--linera-bridge-chain-owner` and specify exactly who's the
owner.

## Test Plan

CI

## Release Plan

None for now.

## Links

- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
- Add rocksdb feature to linera-storage
- Adapt relay to main's API signatures (no StorageCacheSizes, 3-arg maybe_create_and_connect)
- Use tracing_subscriber stderr writer instead of linera_base::tracing::init
- Adapt linera-client config.rs to main's GenesisConfig structure
- Add Bytecode::load_from_file_sync for non-async contexts
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