security(operator): Terra watcher must wait for confirmation depth #182

Open
opened 2026-09-12 12:38:22 +00:00 by PlasticDigits · 0 comments

Summary

The operator Terra Classic deposit watcher ingests through LCD latest height and immediately persists last_terra_block plus terra_deposits rows. There is no confirmation-depth subtraction. The EVM watcher already does this: get_finalized_block returns quorum eth_blockNumber minus finality_blocks. After a Terra reorg, orphaned rows stay pending and are never revalidated against the canonical chain.

This is not #115 (operator EVM RPC hardening / finality_blocks default of 1). This is not #114 (canceler EVM poll-to-latest). Those tickets covered EVM confirmation depth, not this Terra ingest path. This is not #180 (wasm src_decimals) or #175 (min_signatures).

Internal review id: RS-H1 (high). Still in source as of 2026-09-12 (packages/operator/src/watchers/terra.rs on main).

Bundle (same ticket, do not split):

  1. Configurable Terra confirmation depth on TerraConfig (env + deserialize default). Poll end must be latest.saturating_sub(finality_blocks), matching the EVM watcher.
  2. Before dest WithdrawApprove, re-check the deposit against canonical LCD state at a finalized height (tx still in the block, hash still in bridge storage). Do not approve from a tip-height DB row alone.
  3. Revalidation: if a previously ingested terra_deposits row is absent from the canonical height window, mark it invalid / drop it from pending. Do not leave reorged rows pending forever.
  4. Tests that a deposit observed only at tip height is not dest-approved until N confirmations, and that a disappeared (reorged) row is not approved.

Founder-required operator ingest / dest approve. No community autoland. Do not add ready.

Impact (today vs hypothetical)

Funds at risk today on any live operator that dest-approves Terra-origin withdrawals from LCD-latest state. Terra Classic LCD /blocks/latest is not a finality gadget. A deposit (lock / CW20 lock / mintable burn) that appears in a tip block can be stored and used as the source of truth for dest WithdrawApprove on EVM or Solana. If that block is later orphaned, dest may already be approved (cancel window running or elapsed) while the Terra lock never landed on the canonical chain.

This is not permissionless theft of unrelated vault inventory: the attacker (or a colliding honest user after a halt/reorg) needs a Terra deposit that the operator observed at the tip. Severity is high because dest mint/unlock is the payout, and the operator is the watchtower that is supposed to wait for source finality. Rate limits and dest cancel windows are not a substitute for source confirmation depth.

Hypothetical-only if every deployed operator already waits off-tree (they do not: TerraConfig has no finality_blocks field) or if dest approve always failed closed on missing LCD storage (V2 verify_terra_deposit queries latest contract state, which still includes a tip-height deposit). Sticky until the watcher cursor and dest-approve re-check land.

Do not publish a mainnet tip-deposit then dest-approve sequence.

Current codebase

Terra watcher processes LCD latest with no subtraction

TerraWatcher::run reads get_last_terra_block, then get_current_height(), then:

for height in (last_height + 1) as u64..=current_height

On success it calls update_last_terra_block(..., height). There is no saturating_sub. TerraWatcher holds lcd_url, bridge_address, chain_id, db, http only — no finality field.

get_current_height uses LCD /cosmos/base/tendermint/v1beta1/blocks/latest and parses block.header.height. That is the same class of “head” the EVM watcher deliberately does not poll through.

Insert is once; cursor never rewinds

process_block queries txs at that height, parses V2 deposit_native / deposit_cw20_lock / deposit_cw20_mintable_burn, and insert_terra_deposit if terra_deposit_exists(tx_hash, nonce) is false. No later pass re-fetches that height. After a reorg:

  • last_terra_block has already advanced past the orphaned height, so the loop will not re-scan it.
  • The row stays in terra_deposits (pending until dest sync marks processed).
  • Tests in the same file cover JSON height parse and event parse only. There is no test that tip height is excluded from ingest or dest approve.

EVM watcher already subtracts finality_blocks

EvmWatcher::get_finalized_block (poll uses this as current_block):

head.latest_block.saturating_sub(self.finality_blocks)

EvmConfig.finality_blocks is env FINALITY_BLOCKS with per-chain defaults (default_finality_for_native_chain: BSC 15, opBNB 12). TerraConfig has rpc_url, lcd_url, chain_id, bridge_address, mnemonic, fee_recipient, this_chain_id — no confirmation depth.

Closed #115 EVM-02 called out a weak default of 1 on that EVM field; it treated the mechanic as already present. Terra never got the mechanic.

Dest approve re-queries LCD latest, not a finalized height

V2 dest approve (EvmWriter::enumerate_and_approve → verify_deposit_on_source → verify_terra_deposit) smart-queries Terra xchain_hash_id on current LCD. A deposit that exists only in the tip block still returns data. There is no check that deposit.block_height + N <= lcd_latest.

A leftover process_deposit path still builds dest approval from a TerraDeposit DB row (hash from stored sender/amount/nonce) without a finalized-height re-fetch. process_pending today prefers on-chain enumeration; do not re-wire that helper without the same N-deep canonical check.

Solana dest has CommitmentConfig (finalized default) for Solana RPC. That does not protect Terra-source ingest.

Invariants

  • INV-OP-T1 (new): Terra ingest poll end ≤ LCD latest − terra_finality_blocks. Depth 0 is test-only; production default must be ≥ 1 and documented.
  • INV-OP-T2 (new): Dest WithdrawApprove for a Terra-source hash requires the deposit still present in bridge storage and its source height at least N behind LCD latest (or equivalently: included in a block at finalized_height). Tip-height inclusion is not enough.
  • INV-OP-T3 (new): If a stored terra_deposits row’s tx/hash is missing at the finalized height, it must leave pending (invalidated). Status/pending_deposits must not keep advertising a reorged lock.
  • Do not weaken dest cancel window, dest user withdrawSubmit, hash inputs, or EVM finality_blocks. Do not skip dest verify and approve solely because a DB row exists.

Constraints / guardrails

  • Mirror the EVM pattern; do not invent a second cursor language. Prefer TerraConfig.finality_blocks (name can match EVM) and TERRA_FINALITY_BLOCKS (do not overload FINALITY_BLOCKS used by EVM).
  • Catch-up must still be bounded (existing 50-block yield / LCD backoff). Confirmation depth must not disable catch-up; it only caps the end of the window.
  • Re-check before dest approve must fail closed on LCD errors (same as today’s verify). Do not treat “DB row exists” as proof.
  • Do not rewind last_terra_block globally on every LCD blip; invalidate rows whose txs vanished, or re-scan a short lookback behind finalized height if a height drop is detected. Pick one; document it; test a height drop.
  • Do not change CosmWasm. Do not retune BSC/opBNB FINALITY_BLOCKS here (#115).
  • Founder-required operator / dest approve. No community autoland. Do not add ready. No public mainnet reorg recipe.

Relevant files

Path Why
packages/operator/src/watchers/terra.rs Polls ..=current_height; insert + cursor; no finality; tests are parse-only
packages/operator/src/watchers/evm.rs Pattern to copy: get_finalized_block
packages/operator/src/config.rs EvmConfig.finality_blocks vs TerraConfig with none
packages/operator/src/writers/evm.rs verify_terra_deposit (LCD latest); leftover process_deposit
packages/operator/src/db/mod.rs get_last_terra_block / update_last_terra_block / get_pending_terra_deposits
Operator watcher/writer tests Add tip-height vs N-deep fixtures
  1. Add finality_blocks: u64 to TerraConfig (default documented and ≥ 1 for non-test). TerraWatcher stores it. Poll end = current_height.saturating_sub(finality_blocks). If current_height <= last_height or finalized end ≤ last, sleep (same as today).
  2. Dest-approve path: after LCD xchain_hash_id hit, require source block height ≤ finalized height (from the same LCD latest minus N, or from the deposit’s block_height vs N). If only the tx hash is known, re-query txs at that height (existing lcd_get_txs_event_url_contract_at_height) and confirm the tx is still included.
  3. Revalidation job or watcher pass: pending terra_deposits whose height is now behind finalized head but whose tx is missing → status invalid / reorged (name as in repo conventions). Do not dest-approve them.
  4. Tests: mock LCD latest = H, deposit at H, N = 2 → neither insert-for-approve nor dest approve. Advance latest to H+2 → ingest allowed; dest approve allowed only if the tx is still at H. Drop the tx from height H after ingest → dest approve denied and pending cleared.

Acceptance criteria

  • AC1. With terra_finality_blocks = N (N ≥ 1), the watcher does not process_block for height > lcd_latest - N and does not advance last_terra_block into that window.
  • AC2. Dest WithdrawApprove is not submitted for a Terra-source hash whose deposit height is still within N of LCD latest.
  • AC3. After ingest, if the same height’s tx list no longer contains the deposit (reorg fixture), dest approve is refused and the DB row is not left pending.
  • AC4. EVM FINALITY_BLOCKS / get_finalized_block behavior unchanged. Terra env is a separate knob.
  • AC5. Existing V2 parse tests stay green. New unit tests cover AC1–AC3 without a live columbus-5 node.
  • AC6. Docs (operator README / watcher notes) state Terra confirmation depth and that tip deposits are not dest-approved.

Test plan (functional paths)

# Path Expect
T1 LCD latest H, last = H−5, N = 2 Process through H−2 only; cursor stops at H−2
T2 LCD latest H, last = H−2, N = 2 No new heights; sleep
T3 N = 0 (test override) Documented test-only; production default is not 0
T4 Deposit at H, latest = H, dest enumerate verify_terra_deposit / approve gate false (AC2)
T5 Same deposit, latest = H+N Approve allowed if tx still at H
T6 Ingested row, then tx missing at H Not approved; not pending
T7 LCD height error Fail closed; no cursor bump; no approve
T8 EVM watcher fixture with finality_blocks Unchanged

Test plan (attack, hack, and abuse)

Non-exploitative. Local LCD mocks / operator unit tests only. Do not use these as a mainnet recipe.

# Vector Expect
A1 Tip-only deposit, dest withdrawSubmit immediately Operator does not dest-approve until N confirmations
A2 Ingest at tip, LCD later omits the tx (reorg) No dest approve; row not pending
A3 Replacement tx at same height with different hash/nonce Old row invalid; new tx only after it is N-deep
A4 LCD latest jumps backward No dest approve from stale cursor-ahead rows; lookback/invalidate path runs
A5 DB row present, LCD storage empty Fail closed (today’s empty data already false; keep it)

Verification criteria

  • Operator package tests: T1–T8 and A1–A4. Grep watchers/terra.rs for saturating_sub (or equivalent) on the poll end; grep TerraConfig for finality_blocks.
  • Dest-approve path has an explicit height/finality gate, not only “LCD JSON data is non-null”.
  • Do not verify by forcing a columbus-5 reorg or by dest-approving a tip deposit on production.

Out of scope

  • #114 / #115 EVM RPC consensus, HTTPS, /health, eth_chainId, and raising EVM default confirmation depth.
  • #180 / #175 CosmWasm approve/decimals.
  • #170 dest-approved Terra→EVM execute stall.
  • #138 EVM writer livelock.
  • Solana commitment string (already finalized by default) except where a Terra-source dest-approve on Solana must use the same N-deep Terra re-check.
  • Live operator redeploy / LCD URL changes (ops).

First-pass model recommendation

Recommendation: grok-high

Rationale: Security class and founder-required operator dest-approve / source ingest (wallet / bridge funds). Composer is disallowed (High/security; not a low-risk first pass). Scope is not a local three-file tweak: watchers/terra.rs poll+cursor, TerraConfig + env, dest-approve re-check in writers/evm.rs (and any Solana dest Terra-source verify), DB pending invalidation, and new tests. A wrong allow (ingest or dest-approve at LCD latest) can mint dest assets for a lock that never finalized. Verify with mocked LCD height/tx fixtures, not a live chain reorg.

## Summary The operator Terra Classic deposit watcher ingests through LCD **latest** height and immediately persists `last_terra_block` plus `terra_deposits` rows. There is no confirmation-depth subtraction. The EVM watcher already does this: `get_finalized_block` returns quorum `eth_blockNumber` minus `finality_blocks`. After a Terra reorg, orphaned rows stay `pending` and are never revalidated against the canonical chain. This is not [#115](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/115) (operator **EVM** RPC hardening / `finality_blocks` default of 1). This is not [#114](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/114) (canceler EVM poll-to-`latest`). Those tickets covered EVM confirmation depth, not this Terra ingest path. This is not [#180](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/180) (wasm `src_decimals`) or [#175](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/175) (`min_signatures`). Internal review id: **RS-H1** (high). Still in source as of 2026-09-12 (`packages/operator/src/watchers/terra.rs` on `main`). Bundle (same ticket, do not split): 1. Configurable Terra confirmation depth on `TerraConfig` (env + deserialize default). Poll end must be `latest.saturating_sub(finality_blocks)`, matching the EVM watcher. 2. Before dest `WithdrawApprove`, re-check the deposit against **canonical** LCD state at a finalized height (tx still in the block, hash still in bridge storage). Do not approve from a tip-height DB row alone. 3. Revalidation: if a previously ingested `terra_deposits` row is absent from the canonical height window, mark it invalid / drop it from pending. Do not leave reorged rows pending forever. 4. Tests that a deposit observed only at tip height is **not** dest-approved until N confirmations, and that a disappeared (reorged) row is not approved. Founder-required operator ingest / dest approve. No community autoland. Do not add `ready`. ## Impact (today vs hypothetical) Funds at risk **today** on any live operator that dest-approves Terra-origin withdrawals from LCD-latest state. Terra Classic LCD `/blocks/latest` is not a finality gadget. A deposit (lock / CW20 lock / mintable burn) that appears in a tip block can be stored and used as the source of truth for dest `WithdrawApprove` on EVM or Solana. If that block is later orphaned, dest may already be approved (cancel window running or elapsed) while the Terra lock never landed on the canonical chain. This is not permissionless theft of unrelated vault inventory: the attacker (or a colliding honest user after a halt/reorg) needs a Terra deposit that the operator observed at the tip. Severity is **high** because dest mint/unlock is the payout, and the operator is the watchtower that is supposed to wait for source finality. Rate limits and dest cancel windows are not a substitute for source confirmation depth. Hypothetical-only if every deployed operator already waits off-tree (they do not: `TerraConfig` has no `finality_blocks` field) or if dest approve always failed closed on missing LCD storage (V2 `verify_terra_deposit` queries **latest** contract state, which still includes a tip-height deposit). Sticky until the watcher cursor and dest-approve re-check land. Do not publish a mainnet tip-deposit then dest-approve sequence. ## Current codebase ### Terra watcher processes LCD latest with no subtraction [`TerraWatcher::run`](packages/operator/src/watchers/terra.rs) reads `get_last_terra_block`, then `get_current_height()`, then: ```text for height in (last_height + 1) as u64..=current_height ``` On success it calls `update_last_terra_block(..., height)`. There is no `saturating_sub`. `TerraWatcher` holds `lcd_url`, `bridge_address`, `chain_id`, `db`, `http` only — no finality field. `get_current_height` uses LCD `/cosmos/base/tendermint/v1beta1/blocks/latest` and parses `block.header.height`. That is the same class of “head” the EVM watcher deliberately does **not** poll through. ### Insert is once; cursor never rewinds `process_block` queries txs at that height, parses V2 `deposit_native` / `deposit_cw20_lock` / `deposit_cw20_mintable_burn`, and `insert_terra_deposit` if `terra_deposit_exists(tx_hash, nonce)` is false. No later pass re-fetches that height. After a reorg: - `last_terra_block` has already advanced past the orphaned height, so the loop will not re-scan it. - The row stays in `terra_deposits` (`pending` until dest sync marks `processed`). - Tests in the same file cover JSON height parse and event parse only. There is **no** test that tip height is excluded from ingest or dest approve. ### EVM watcher already subtracts `finality_blocks` [`EvmWatcher::get_finalized_block`](packages/operator/src/watchers/evm.rs) (poll uses this as `current_block`): ```text head.latest_block.saturating_sub(self.finality_blocks) ``` [`EvmConfig.finality_blocks`](packages/operator/src/config.rs) is env `FINALITY_BLOCKS` with per-chain defaults (`default_finality_for_native_chain`: BSC 15, opBNB 12). [`TerraConfig`](packages/operator/src/config.rs) has `rpc_url`, `lcd_url`, `chain_id`, `bridge_address`, `mnemonic`, `fee_recipient`, `this_chain_id` — **no** confirmation depth. Closed [#115](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/115) EVM-02 called out a **weak default of 1** on that EVM field; it treated the mechanic as already present. Terra never got the mechanic. ### Dest approve re-queries LCD latest, not a finalized height V2 dest approve ([`EvmWriter::enumerate_and_approve`](packages/operator/src/writers/evm.rs) → `verify_deposit_on_source` → `verify_terra_deposit`) smart-queries Terra `xchain_hash_id` on **current** LCD. A deposit that exists only in the tip block still returns data. There is no check that `deposit.block_height + N <= lcd_latest`. A leftover [`process_deposit`](packages/operator/src/writers/evm.rs) path still builds dest approval from a `TerraDeposit` DB row (hash from stored sender/amount/nonce) without a finalized-height re-fetch. `process_pending` today prefers on-chain enumeration; do not re-wire that helper without the same N-deep canonical check. Solana dest has `CommitmentConfig` (`finalized` default) for **Solana** RPC. That does not protect Terra-source ingest. ## Invariants - **INV-OP-T1 (new):** Terra ingest poll end ≤ LCD latest − `terra_finality_blocks`. Depth `0` is test-only; production default must be ≥ 1 and documented. - **INV-OP-T2 (new):** Dest `WithdrawApprove` for a Terra-source hash requires the deposit still present in bridge storage **and** its source height at least N behind LCD latest (or equivalently: included in a block at `finalized_height`). Tip-height inclusion is not enough. - **INV-OP-T3 (new):** If a stored `terra_deposits` row’s tx/hash is missing at the finalized height, it must leave `pending` (invalidated). Status/`pending_deposits` must not keep advertising a reorged lock. - Do not weaken dest cancel window, dest user `withdrawSubmit`, hash inputs, or EVM `finality_blocks`. Do not skip dest verify and approve solely because a DB row exists. ## Constraints / guardrails - Mirror the EVM pattern; do not invent a second cursor language. Prefer `TerraConfig.finality_blocks` (name can match EVM) and `TERRA_FINALITY_BLOCKS` (do not overload `FINALITY_BLOCKS` used by EVM). - Catch-up must still be bounded (existing 50-block yield / LCD backoff). Confirmation depth must not disable catch-up; it only caps the **end** of the window. - Re-check before dest approve must fail closed on LCD errors (same as today’s verify). Do not treat “DB row exists” as proof. - Do not rewind `last_terra_block` globally on every LCD blip; invalidate **rows** whose txs vanished, or re-scan a short lookback behind finalized height if a height drop is detected. Pick one; document it; test a height drop. - Do not change CosmWasm. Do not retune BSC/opBNB `FINALITY_BLOCKS` here ([#115](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/115)). - Founder-required operator / dest approve. No community autoland. Do not add `ready`. No public mainnet reorg recipe. ## Relevant files | Path | Why | | --- | --- | | `packages/operator/src/watchers/terra.rs` | Polls `..=current_height`; insert + cursor; no finality; tests are parse-only | | `packages/operator/src/watchers/evm.rs` | Pattern to copy: `get_finalized_block` | | `packages/operator/src/config.rs` | `EvmConfig.finality_blocks` vs `TerraConfig` with none | | `packages/operator/src/writers/evm.rs` | `verify_terra_deposit` (LCD latest); leftover `process_deposit` | | `packages/operator/src/db/mod.rs` | `get_last_terra_block` / `update_last_terra_block` / `get_pending_terra_deposits` | | Operator watcher/writer tests | Add tip-height vs N-deep fixtures | ## Recommended direction 1. Add `finality_blocks: u64` to `TerraConfig` (default documented and ≥ 1 for non-test). `TerraWatcher` stores it. Poll end = `current_height.saturating_sub(finality_blocks)`. If `current_height <= last_height` or finalized end ≤ last, sleep (same as today). 2. Dest-approve path: after LCD `xchain_hash_id` hit, require source block height ≤ finalized height (from the same LCD latest minus N, or from the deposit’s `block_height` vs N). If only the tx hash is known, re-query txs at that height (existing `lcd_get_txs_event_url_contract_at_height`) and confirm the tx is still included. 3. Revalidation job or watcher pass: pending `terra_deposits` whose height is now behind finalized head but whose tx is missing → status `invalid` / `reorged` (name as in repo conventions). Do not dest-approve them. 4. Tests: mock LCD latest = H, deposit at H, N = 2 → neither insert-for-approve nor dest approve. Advance latest to H+2 → ingest allowed; dest approve allowed only if the tx is still at H. Drop the tx from height H after ingest → dest approve denied and pending cleared. ## Acceptance criteria - AC1. With `terra_finality_blocks = N` (N ≥ 1), the watcher does not `process_block` for `height > lcd_latest - N` and does not advance `last_terra_block` into that window. - AC2. Dest `WithdrawApprove` is not submitted for a Terra-source hash whose deposit height is still within N of LCD latest. - AC3. After ingest, if the same height’s tx list no longer contains the deposit (reorg fixture), dest approve is refused and the DB row is not left `pending`. - AC4. EVM `FINALITY_BLOCKS` / `get_finalized_block` behavior unchanged. Terra env is a separate knob. - AC5. Existing V2 parse tests stay green. New unit tests cover AC1–AC3 without a live columbus-5 node. - AC6. Docs (operator README / watcher notes) state Terra confirmation depth and that tip deposits are not dest-approved. ## Test plan (functional paths) | # | Path | Expect | | --- | --- | --- | | T1 | LCD latest H, last = H−5, N = 2 | Process through H−2 only; cursor stops at H−2 | | T2 | LCD latest H, last = H−2, N = 2 | No new heights; sleep | | T3 | N = 0 (test override) | Documented test-only; production default is not 0 | | T4 | Deposit at H, latest = H, dest enumerate | `verify_terra_deposit` / approve gate false (AC2) | | T5 | Same deposit, latest = H+N | Approve allowed if tx still at H | | T6 | Ingested row, then tx missing at H | Not approved; not `pending` | | T7 | LCD height error | Fail closed; no cursor bump; no approve | | T8 | EVM watcher fixture with `finality_blocks` | Unchanged | ## Test plan (attack, hack, and abuse) Non-exploitative. Local LCD mocks / operator unit tests only. Do not use these as a mainnet recipe. | # | Vector | Expect | | --- | --- | --- | | A1 | Tip-only deposit, dest `withdrawSubmit` immediately | Operator does not dest-approve until N confirmations | | A2 | Ingest at tip, LCD later omits the tx (reorg) | No dest approve; row not pending | | A3 | Replacement tx at same height with different hash/nonce | Old row invalid; new tx only after it is N-deep | | A4 | LCD latest jumps backward | No dest approve from stale cursor-ahead rows; lookback/invalidate path runs | | A5 | DB row present, LCD storage empty | Fail closed (today’s empty `data` already false; keep it) | ## Verification criteria - Operator package tests: T1–T8 and A1–A4. Grep `watchers/terra.rs` for `saturating_sub` (or equivalent) on the poll end; grep `TerraConfig` for `finality_blocks`. - Dest-approve path has an explicit height/finality gate, not only “LCD JSON `data` is non-null”. - Do not verify by forcing a columbus-5 reorg or by dest-approving a tip deposit on production. ## Out of scope - [#114](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/114) / [#115](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/115) EVM RPC consensus, HTTPS, `/health`, `eth_chainId`, and raising EVM default confirmation depth. - [#180](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/180) / [#175](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/175) CosmWasm approve/decimals. - [#170](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/170) dest-approved Terra→EVM execute stall. - [#138](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/138) EVM writer livelock. - Solana commitment string (already `finalized` by default) except where a Terra-source dest-approve on Solana must use the same N-deep Terra re-check. - Live operator redeploy / LCD URL changes (ops). ## First-pass model recommendation Recommendation: grok-high Rationale: Security class and founder-required operator dest-approve / source ingest (wallet / bridge funds). Composer is disallowed (High/security; not a low-risk first pass). Scope is not a local three-file tweak: `watchers/terra.rs` poll+cursor, `TerraConfig` + env, dest-approve re-check in `writers/evm.rs` (and any Solana dest Terra-source verify), DB pending invalidation, and new tests. A wrong allow (ingest or dest-approve at LCD latest) can mint dest assets for a lock that never finalized. Verify with mocked LCD height/tx fixtures, not a live chain reorg.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-bridge-monorepo#182
No description provided.