fix(operator): dest-approved Terra→EVM withdrawals stay unexecuted #170

Closed
opened 2026-09-11 06:21:18 +00:00 by PlasticDigits · 4 comments

Summary

Hash Verification (/verify?hash=) for a Terra Classic → BNB economic-token transfer shows overall Pending, Hash matches, source deposit present, and destination withdraw Approved (not Executed, not Canceled). That aggregate badge is the intended mapping for “approved, awaiting execution.” The defect is that execution never completes after the cancel window, and the verify surface gives the user no EVM-side reason.

Bundle (do not split):

  1. Operator must withdrawExecuteMint / withdrawExecuteUnlock dest-approved EVM withdrawals once the on-chain cancel window has elapsed (Terra→EVM uses the same EVM writer as EVM→EVM).
  2. Retryable execute failures (period rate-limit, CancelWindowActive, RPC) must not be treated as terminal and dropped from the in-memory execute queue.
  3. Hash Verification must surface EVM dest execute blockers the same way it already does for Terra dest (rate-limit banners) and Solana dest (cancel remaining + execute panel). Transfer Status already has EVM rate-limit detection (GL-127); Verify does not wire it.

This is not a request to weaken on-chain withdraw caps. Manual unstick of one live row stays off this ticket (ops bot).

Related but not this ticket: #164 (MegaETH/LCD stalling approval / getDeposit), #127 (Transfer Status 4/4 UX when rate-limited), #139 (Terra pending-history pagination), closed #47 (Monitor list always Pending), closed #100 (BelowMin after fee — different classifier).

Current codebase

Hash-query status (correct for this screenshot shape)

  • packages/frontend/src/hooks/useHashVerification.ts sets status to verified only when dest.executed, canceled when dest.cancelled, and pending when dest.approved (“approved, awaiting execution”).
  • packages/frontend/src/components/verify/DestHashCard.tsx labels dest state Executed / Canceled / Approved / Pending from those flags.
  • packages/frontend/src/pages/HashVerificationPage.tsx queries all configured chains, shows source vs dest cards, and records monitor results. showRateLimitInfo is destChain.type === 'cosmos' only. Solana dest gets SolanaRecipientExecutePanel + useApprovalCountdown. EVM dest has neither rate-limit hook nor execute CTA on this page.
  • packages/frontend/src/hooks/useEvmExecutionRateLimitStatus.ts + packages/frontend/src/services/evmExecutionRateLimit.ts already compute temporarily-blocked / permanently-blocked for Transfer Status (GL-127). HashComparisonPanel already renders those banners from a terraRateLimitStatus prop — Verify never passes EVM dest into that path.

Operator execute after approve

V2 dest lifecycle: deposit (source) → WithdrawSubmit (dest) → WithdrawApprove → cancel window → withdrawExecuteMint / withdrawExecuteUnlock. EVM contract comment: after the window, anyone may execute.

  • packages/operator/src/writers/evm.rs::process_pending runs process_pending_executions, then enumerate_and_approve, then execute again (INV-OP-W11 restart recovery).
  • Enumeration: if the hash is already in approved_hashes but still on the pending set, it re-queues execute with delay 0. If getPendingWithdraw says approved and not executed/cancelled, it enqueues with delay_from_onchain_approved_at.
  • enqueue_execution_if_absent returns immediately when terminal_executions contains the hash — no later retry even if a daily window later has headroom.
  • process_pending_executions calls submit_execute_withdraw when the in-memory delay elapsed. Success or is_terminal_execute_error both move the hash into terminal_executions and out of pending_executions.
  • packages/operator/src/writers/mod.rs::is_terminal_execute_error is terminal for already-executed / cancelled / missing / BelowMinPerTransaction. It is not terminal for CancelWindowActive or period/per-tx rate-limit selectors — those should retry. If execute never enters pending_executions (writer livelock, single-RPC submit_execute_withdraw using only self.rpc_url, process restart without re-enumeration), dest stays Approved forever.
  • submit_execute_withdraw does not use the method-level RPC fallback used for eth_getLogs (#138). A send/receipt failure on the one URL is a retry or a silent stall depending on the error string.

Frontend Transfer Status vs Verify

  • Transfer Status (/transfer/:xchainHashId) is the stepper (GL-127 / #127) and can show rate-limit freeze on step 4/4.
  • Users who only open Hash Verification (xchain hash id lookup) see Pending + Approved + Hash matches and nothing about cancel remaining or EVM window fullness.

Why the new implementation is needed

  1. Funds stay locked after a successful approve. Dest Approved means the operator already verified the source deposit. The remaining protocol step is execute. Leaving that step undone is a user-visible stuck transfer, not a “still matching hashes” cosmetic issue.
  2. Verify is the diagnostic page and currently lies by omission for EVM dest. Pending is technically correct, but without cancel remaining / rate-limit / last execute error the page looks like a protocol hang. Terra dest already has banners; EVM dest does not.
  3. Retry vs terminal is a funds invariant. Dropping a hash into terminal_executions after a retryable revert (or never enqueueing it) makes dest-Approved rows unrecoverable until a process restart that also misses terminal_executions — and restart still skips hashes already in that map.

Constraints / guardrails

  • On-chain rate limits stay. Do not raise maxPerTransaction / maxPerPeriod, skip TokenRateLimit, or add an operator bypass that executes below-min or over-period amounts. Retry after periodEndsAt for period blocks; keep BelowMinPerTransaction terminal (#100 / INV-OP-W11).
  • Cancel window stays. Do not execute while CancelWindowActive. Zero delay after approvedAt + window may still revert once; that is retryable, not terminal.
  • Fail closed on approve. This ticket does not change deposit verification. Never approve without a verified source deposit (#164 is a separate approval-path ticket).
  • Do not execute cancelled or already-executed hashes. Re-queue only approved && !executed && !cancelled.
  • HashStatus mapping stays. Do not call dest-Approved “verified.” Verified = executed. UX must explain why it is still pending.
  • No second Transfer Status page. Reuse computeEvmExecutionRateLimitStatus / useEvmExecutionRateLimitStatus and existing HashComparisonPanel banners.
  • No Coolify/host/RPC inventory on this issue. Operator logs and live hash ids stay off Forgejo.
  • Do not expand #164, #127, or #139 into this patch.

Relevant files

Path Why
packages/operator/src/writers/evm.rs process_pending, enqueue_execution_if_absent, process_pending_executions, submit_execute_withdraw
packages/operator/src/writers/mod.rs remaining_cancel_window_secs, is_terminal_execute_error, tests
packages/operator/src/rpc_fallback.rs Existing method-level RPC fallback to reuse on execute send/receipt
packages/frontend/src/hooks/useHashVerification.ts Status mapping — keep pending while approved-not-executed
packages/frontend/src/pages/HashVerificationPage.tsx Wire EVM rate-limit + cancel remaining for dest-approved EVM
packages/frontend/src/hooks/useEvmExecutionRateLimitStatus.ts Already exists for Transfer Status
packages/frontend/src/services/evmExecutionRateLimit.ts Shared classifier
packages/frontend/src/components/verify/HashComparisonPanel.tsx Banners already accept the status object
packages/frontend/src/components/verify/DestHashCard.tsx Approved vs Executed labels
Operator writer tests next to evm.rs / mod.rs Terminal vs retryable execute; re-queue after cancel window
packages/frontend/src/hooks/useHashVerification.integration.test.ts Verify page does not mark approved-not-executed as verified
  1. Diagnose execute, not the badge. For a dest-approved Terra→EVM hash past approvedAt + cancelWindow: confirm getPendingWithdraw (approved, !executed, !cancelled). If execute is not in pending_executions, fix enumeration re-queue. If it is queued but submit_execute_withdraw fails, classify the revert.
  2. Operator execute path. Use RPC URL fallback on execute send/receipt (same family as #138 getLogs), increment attempts, and keep retryable errors in pending_executions with backoff (period-end or short poll). Only is_terminal_execute_error may enter terminal_executions. Period rate-limit must not be added to the terminal list.
  3. Verify UX (EVM dest). When dest.approved && !dest.executed && destChain.type === 'evm', pass useEvmExecutionRateLimitStatus into HashComparisonPanel (same banners as Terra). Show cancel-window remaining from on-chain approvedAt + dest cancelWindow (do not hardcode 24h — #44). Keep Hash matches / Pending.
  4. Do not require a new recipient-execute product unless operator execute cannot be made reliable in this ticket. Protocol allows anyone to execute after the window; Solana already exposes that on Verify. An EVM recipient execute control is acceptable only if it reuses the existing dest-chain execute call, stays behind cancel-window + rate-limit gates, and does not become a second bridge UI. Prefer operator-complete as the default.

Acceptance criteria

  • AC1. Given Terra Classic source deposit + BNB dest WithdrawSubmit Approved and cancel window elapsed, operator withdrawExecute* succeeds and dest executed becomes true without a manual contract call.
  • AC2. Hash Verification for that id then shows status verified (not Pending) and dest state Executed. Hash matches remains true.
  • AC3. While still dest-Approved and not executed, Verify shows Pending (not verified) and a visible blocker: cancel remaining, temporary period-full, or permanent over-max — not a blank Approved card.
  • AC4. CancelWindowActive and period rate-limit reverts are retried; they are not stored in terminal_executions.
  • AC5. BelowMinPerTransaction / cancelled / already-executed stay terminal; no retry storm; no execute of cancelled hashes.
  • AC6. On-chain TokenRateLimit / min-per-tx unchanged. No new admin execute-bypass.
  • AC7. Existing hash-match comparison and Monitor recording still use executed vs approved correctly (useHashVerification).
  • AC8. #164 / #127 / #139 behavior not regressed by this change set.

Given / When / Then

  • Given a Terra Classic → EVM dest withdraw that is Approved, not Executed, not Canceled, source deposit verified, and block.timestamp > approvedAt + cancelWindow

  • When the EVM writer process_pending cycle runs

  • Then the writer sends withdrawExecuteMint or withdrawExecuteUnlock for that xchain hash and dest executed is true (unless a non-terminal on-chain limiter is active, in which case it retries after the window and Verify names that limiter)

  • Given the same hash queried on Hash Verification with dest Approved and not executed

  • When dest chain is EVM

  • Then overall status is Pending, dest state is Approved, and the page shows cancel remaining and/or EVM execution rate-limit status (reuse GL-127 classifier)

Test plan (functional paths)

# Path Expect
T1 Dest approved, window elapsed, execute succeeds Dest executed; Verify → verified
T2 Dest approved, window still open No execute; Verify Pending + remaining time; retry after elapsed
T3 CancelWindowActive on first execute Retry, not terminal_executions
T4 Period rate-limit revert Retry after period; Verify temporarily-blocked; not terminal
T5 Payout > maxPerPeriod No execute; Verify permanently-blocked; no cap change
T6 BelowMinPerTransaction Terminal drop; no retry storm
T7 Writer restart with dest still approved on pending set INV-OP-W11 re-queue; execute without waiting a full cancel window again if already elapsed
T8 Hash matches, dest approved Comparison still match; status not verified until executed
T9 Terra dest approved-not-executed Existing Terra banners still work (no EVM-only break)
T10 Solana dest approved-not-executed Existing execute panel unchanged

Test plan (attack, hack, and abuse)

# Vector Expect
A1 Execute before cancel window Revert / no send; retry later
A2 Execute cancelled hash No send; terminal
A3 Execute without verified deposit (approve path) Unchanged fail-closed; this ticket does not approve
A4 Re-queue loop resetting delay every poll enqueue_execution_if_absent must not reset an in-flight timer
A5 Attacker-created unapproved dest row Still must not approve without source deposit; execute path only for approved
A6 Rate-limit classified terminal so operator “gives up” while period will reset Forbidden; period-full is retryable
A7 Operator bypass of TokenRateLimit Forbidden
A8 Verify page marking Approved as verified Forbidden — phishing-adjacent false completion
A9 Recipient EVM execute (if added) while window active or rate-blocked Button disabled; no send
A10 RPC URL that answers chain id but fails eth_send Fallback / retry; no silent terminal drop

Verification criteria

  • Operator unit tests: remaining_cancel_window_secs, is_terminal_execute_error (retryable vs terminal), re-queue of dest-approved hashes, no delay reset.
  • Frontend: Hash Verification EVM dest approved-not-executed shows rate-limit/cancel chrome; executed → verified. Existing useHashVerification tests stay green.
  • Manual: /verify?hash= for a dest-approved Terra→BNB fixture after the window → execute lands or blocker is named; after execute, refresh shows verified.
  • Do not use production hashes or operator log dumps in the PR.

Out of scope

  • MegaETH/LCD getDeposit / Terra writer approval resilience (#164).
  • Transfer Status step 4/4 copy-only work (#127) except shared classifier reuse.
  • Terra PENDING_WITHDRAWS history index (#139).
  • CCTP (#169), clickwrap, WalletConnect.
  • Changing HashStatus so Approved displays as verified.
  • Publishing live xchain ids, accounts, or operator host status.

First-pass model recommendation

Recommendation: grok-high

Rationale: Operator execute queue + terminal vs retryable revert classification + Hash Verification EVM wiring is more than three production files and crosses packages/operator writers and packages/frontend verify. It is wallet / bridge / 2-of-3 adjacent (founder-required): a wrong terminal drop or a premature execute skips the cancel window or rate limit. Verification is mixed (writer unit tests + Verify UI), not a single helper with a deterministic local fixture. Composer criteria fail on file/subsystem count, cross-cutting state, and human-required surface — not because the title is long.

## Summary Hash Verification (`/verify?hash=`) for a **Terra Classic → BNB** economic-token transfer shows overall **Pending**, **Hash matches**, source deposit present, and destination withdraw **Approved** (not Executed, not Canceled). That aggregate badge is the intended mapping for “approved, awaiting execution.” The defect is that **execution never completes after the cancel window**, and the verify surface gives the user no EVM-side reason. Bundle (do not split): 1. Operator must `withdrawExecuteMint` / `withdrawExecuteUnlock` dest-approved EVM withdrawals once the on-chain cancel window has elapsed (Terra→EVM uses the same EVM writer as EVM→EVM). 2. Retryable execute failures (period rate-limit, `CancelWindowActive`, RPC) must not be treated as terminal and dropped from the in-memory execute queue. 3. Hash Verification must surface EVM dest execute blockers the same way it already does for Terra dest (rate-limit banners) and Solana dest (cancel remaining + execute panel). Transfer Status already has EVM rate-limit detection (GL-127); Verify does not wire it. This is **not** a request to weaken on-chain withdraw caps. Manual unstick of one live row stays off this ticket (ops bot). Related but **not** this ticket: #164 (MegaETH/LCD stalling **approval** / `getDeposit`), #127 (Transfer Status **4/4 UX** when rate-limited), #139 (Terra pending-history pagination), closed #47 (Monitor list always Pending), closed #100 (BelowMin after fee — different classifier). ## Current codebase ### Hash-query status (correct for this screenshot shape) - `packages/frontend/src/hooks/useHashVerification.ts` sets `status` to `verified` only when `dest.executed`, `canceled` when `dest.cancelled`, and **`pending` when `dest.approved`** (“approved, awaiting execution”). - `packages/frontend/src/components/verify/DestHashCard.tsx` labels dest state Executed / Canceled / **Approved** / Pending from those flags. - `packages/frontend/src/pages/HashVerificationPage.tsx` queries all configured chains, shows source vs dest cards, and records monitor results. `showRateLimitInfo` is **`destChain.type === 'cosmos'` only**. Solana dest gets `SolanaRecipientExecutePanel` + `useApprovalCountdown`. **EVM dest has neither rate-limit hook nor execute CTA on this page.** - `packages/frontend/src/hooks/useEvmExecutionRateLimitStatus.ts` + `packages/frontend/src/services/evmExecutionRateLimit.ts` already compute `temporarily-blocked` / `permanently-blocked` for **Transfer Status** (GL-127). `HashComparisonPanel` already renders those banners from a `terraRateLimitStatus` prop — Verify never passes EVM dest into that path. ### Operator execute after approve V2 dest lifecycle: deposit (source) → `WithdrawSubmit` (dest) → `WithdrawApprove` → cancel window → `withdrawExecuteMint` / `withdrawExecuteUnlock`. EVM contract comment: after the window, **anyone** may execute. - `packages/operator/src/writers/evm.rs::process_pending` runs `process_pending_executions`, then `enumerate_and_approve`, then execute again (INV-OP-W11 restart recovery). - Enumeration: if the hash is already in `approved_hashes` but still on the pending set, it re-queues execute with **delay 0**. If `getPendingWithdraw` says `approved` and not executed/cancelled, it enqueues with `delay_from_onchain_approved_at`. - `enqueue_execution_if_absent` **returns immediately** when `terminal_executions` contains the hash — no later retry even if a daily window later has headroom. - `process_pending_executions` calls `submit_execute_withdraw` when the in-memory delay elapsed. Success **or** `is_terminal_execute_error` both move the hash into `terminal_executions` and out of `pending_executions`. - `packages/operator/src/writers/mod.rs::is_terminal_execute_error` is terminal for already-executed / cancelled / missing / **`BelowMinPerTransaction`**. It is **not** terminal for `CancelWindowActive` or period/per-tx rate-limit selectors — those should retry. If execute never enters `pending_executions` (writer livelock, single-RPC `submit_execute_withdraw` using only `self.rpc_url`, process restart without re-enumeration), dest stays Approved forever. - `submit_execute_withdraw` does **not** use the method-level RPC fallback used for `eth_getLogs` (#138). A send/receipt failure on the one URL is a retry or a silent stall depending on the error string. ### Frontend Transfer Status vs Verify - Transfer Status (`/transfer/:xchainHashId`) is the stepper (GL-127 / #127) and can show rate-limit freeze on step 4/4. - Users who only open **Hash Verification** (xchain hash id lookup) see Pending + Approved + Hash matches and nothing about cancel remaining or EVM window fullness. ## Why the new implementation is needed 1. **Funds stay locked after a successful approve.** Dest Approved means the operator already verified the source deposit. The remaining protocol step is execute. Leaving that step undone is a user-visible stuck transfer, not a “still matching hashes” cosmetic issue. 2. **Verify is the diagnostic page and currently lies by omission for EVM dest.** Pending is technically correct, but without cancel remaining / rate-limit / last execute error the page looks like a protocol hang. Terra dest already has banners; EVM dest does not. 3. **Retry vs terminal is a funds invariant.** Dropping a hash into `terminal_executions` after a retryable revert (or never enqueueing it) makes dest-Approved rows unrecoverable until a process restart that also misses `terminal_executions` — and restart still skips hashes already in that map. ## Constraints / guardrails - **On-chain rate limits stay.** Do not raise `maxPerTransaction` / `maxPerPeriod`, skip `TokenRateLimit`, or add an operator bypass that executes below-min or over-period amounts. Retry after `periodEndsAt` for period blocks; keep `BelowMinPerTransaction` terminal (#100 / INV-OP-W11). - **Cancel window stays.** Do not execute while `CancelWindowActive`. Zero delay after `approvedAt + window` may still revert once; that is retryable, not terminal. - **Fail closed on approve.** This ticket does not change deposit verification. Never approve without a verified source deposit (#164 is a separate approval-path ticket). - **Do not execute cancelled or already-executed hashes.** Re-queue only `approved && !executed && !cancelled`. - **HashStatus mapping stays.** Do not call dest-Approved “verified.” Verified = executed. UX must explain *why* it is still pending. - **No second Transfer Status page.** Reuse `computeEvmExecutionRateLimitStatus` / `useEvmExecutionRateLimitStatus` and existing `HashComparisonPanel` banners. - **No Coolify/host/RPC inventory on this issue.** Operator logs and live hash ids stay off Forgejo. - Do not expand #164, #127, or #139 into this patch. ## Relevant files | Path | Why | | --- | --- | | `packages/operator/src/writers/evm.rs` | `process_pending`, `enqueue_execution_if_absent`, `process_pending_executions`, `submit_execute_withdraw` | | `packages/operator/src/writers/mod.rs` | `remaining_cancel_window_secs`, `is_terminal_execute_error`, tests | | `packages/operator/src/rpc_fallback.rs` | Existing method-level RPC fallback to reuse on execute send/receipt | | `packages/frontend/src/hooks/useHashVerification.ts` | Status mapping — keep pending while approved-not-executed | | `packages/frontend/src/pages/HashVerificationPage.tsx` | Wire EVM rate-limit + cancel remaining for dest-approved EVM | | `packages/frontend/src/hooks/useEvmExecutionRateLimitStatus.ts` | Already exists for Transfer Status | | `packages/frontend/src/services/evmExecutionRateLimit.ts` | Shared classifier | | `packages/frontend/src/components/verify/HashComparisonPanel.tsx` | Banners already accept the status object | | `packages/frontend/src/components/verify/DestHashCard.tsx` | Approved vs Executed labels | | Operator writer tests next to `evm.rs` / `mod.rs` | Terminal vs retryable execute; re-queue after cancel window | | `packages/frontend/src/hooks/useHashVerification.integration.test.ts` | Verify page does not mark approved-not-executed as verified | ## Recommended direction 1. **Diagnose execute, not the badge.** For a dest-approved Terra→EVM hash past `approvedAt + cancelWindow`: confirm `getPendingWithdraw` (`approved`, `!executed`, `!cancelled`). If execute is not in `pending_executions`, fix enumeration re-queue. If it is queued but `submit_execute_withdraw` fails, classify the revert. 2. **Operator execute path.** Use RPC URL fallback on execute send/receipt (same family as #138 getLogs), increment `attempts`, and keep retryable errors in `pending_executions` with backoff (period-end or short poll). Only `is_terminal_execute_error` may enter `terminal_executions`. Period rate-limit must not be added to the terminal list. 3. **Verify UX (EVM dest).** When `dest.approved && !dest.executed && destChain.type === 'evm'`, pass `useEvmExecutionRateLimitStatus` into `HashComparisonPanel` (same banners as Terra). Show cancel-window remaining from on-chain `approvedAt` + dest `cancelWindow` (do not hardcode 24h — #44). Keep Hash matches / Pending. 4. **Do not require a new recipient-execute product** unless operator execute cannot be made reliable in this ticket. Protocol allows anyone to execute after the window; Solana already exposes that on Verify. An EVM recipient execute control is acceptable **only** if it reuses the existing dest-chain execute call, stays behind cancel-window + rate-limit gates, and does not become a second bridge UI. Prefer operator-complete as the default. ## Acceptance criteria - AC1. Given Terra Classic source deposit + BNB dest `WithdrawSubmit` **Approved** and cancel window elapsed, operator `withdrawExecute*` succeeds and dest `executed` becomes true without a manual contract call. - AC2. Hash Verification for that id then shows status **verified** (not Pending) and dest state **Executed**. Hash matches remains true. - AC3. While still dest-Approved and not executed, Verify shows **Pending** (not verified) **and** a visible blocker: cancel remaining, temporary period-full, or permanent over-max — not a blank Approved card. - AC4. `CancelWindowActive` and period rate-limit reverts are retried; they are not stored in `terminal_executions`. - AC5. `BelowMinPerTransaction` / cancelled / already-executed stay terminal; no retry storm; no execute of cancelled hashes. - AC6. On-chain `TokenRateLimit` / min-per-tx unchanged. No new admin execute-bypass. - AC7. Existing hash-match comparison and Monitor recording still use executed vs approved correctly (`useHashVerification`). - AC8. #164 / #127 / #139 behavior not regressed by this change set. ### Given / When / Then - **Given** a Terra Classic → EVM dest withdraw that is Approved, not Executed, not Canceled, source deposit verified, and `block.timestamp > approvedAt + cancelWindow` - **When** the EVM writer `process_pending` cycle runs - **Then** the writer sends `withdrawExecuteMint` or `withdrawExecuteUnlock` for that xchain hash and dest `executed` is true (unless a **non-terminal** on-chain limiter is active, in which case it retries after the window and Verify names that limiter) - **Given** the same hash queried on Hash Verification with dest Approved and not executed - **When** dest chain is EVM - **Then** overall status is Pending, dest state is Approved, and the page shows cancel remaining and/or EVM execution rate-limit status (reuse GL-127 classifier) ## Test plan (functional paths) | # | Path | Expect | | --- | --- | --- | | T1 | Dest approved, window elapsed, execute succeeds | Dest executed; Verify → verified | | T2 | Dest approved, window still open | No execute; Verify Pending + remaining time; retry after elapsed | | T3 | `CancelWindowActive` on first execute | Retry, not `terminal_executions` | | T4 | Period rate-limit revert | Retry after period; Verify `temporarily-blocked`; not terminal | | T5 | Payout > `maxPerPeriod` | No execute; Verify `permanently-blocked`; no cap change | | T6 | `BelowMinPerTransaction` | Terminal drop; no retry storm | | T7 | Writer restart with dest still approved on pending set | INV-OP-W11 re-queue; execute without waiting a full cancel window again if already elapsed | | T8 | Hash matches, dest approved | Comparison still match; status not verified until executed | | T9 | Terra dest approved-not-executed | Existing Terra banners still work (no EVM-only break) | | T10 | Solana dest approved-not-executed | Existing execute panel unchanged | ## Test plan (attack, hack, and abuse) | # | Vector | Expect | | --- | --- | --- | | A1 | Execute before cancel window | Revert / no send; retry later | | A2 | Execute cancelled hash | No send; terminal | | A3 | Execute without verified deposit (approve path) | Unchanged fail-closed; this ticket does not approve | | A4 | Re-queue loop resetting delay every poll | `enqueue_execution_if_absent` must not reset an in-flight timer | | A5 | Attacker-created unapproved dest row | Still must not approve without source deposit; execute path only for approved | | A6 | Rate-limit classified terminal so operator “gives up” while period will reset | Forbidden; period-full is retryable | | A7 | Operator bypass of `TokenRateLimit` | Forbidden | | A8 | Verify page marking Approved as verified | Forbidden — phishing-adjacent false completion | | A9 | Recipient EVM execute (if added) while window active or rate-blocked | Button disabled; no send | | A10 | RPC URL that answers chain id but fails eth_send | Fallback / retry; no silent terminal drop | ## Verification criteria - Operator unit tests: `remaining_cancel_window_secs`, `is_terminal_execute_error` (retryable vs terminal), re-queue of dest-approved hashes, no delay reset. - Frontend: Hash Verification EVM dest approved-not-executed shows rate-limit/cancel chrome; executed → verified. Existing `useHashVerification` tests stay green. - Manual: `/verify?hash=` for a dest-approved Terra→BNB fixture after the window → execute lands or blocker is named; after execute, refresh shows verified. - Do not use production hashes or operator log dumps in the PR. ## Out of scope - MegaETH/LCD `getDeposit` / Terra writer approval resilience (#164). - Transfer Status step 4/4 copy-only work (#127) except shared classifier reuse. - Terra `PENDING_WITHDRAWS` history index (#139). - CCTP (#169), clickwrap, WalletConnect. - Changing HashStatus so Approved displays as verified. - Publishing live xchain ids, accounts, or operator host status. ## First-pass model recommendation Recommendation: grok-high Rationale: Operator execute queue + terminal vs retryable revert classification + Hash Verification EVM wiring is more than three production files and crosses `packages/operator` writers and `packages/frontend` verify. It is wallet / bridge / 2-of-3 adjacent (founder-required): a wrong terminal drop or a premature execute skips the cancel window or rate limit. Verification is mixed (writer unit tests + Verify UI), not a single helper with a deterministic local fixture. Composer criteria fail on file/subsystem count, cross-cutting state, and human-required surface — not because the title is long.
Author
Owner

cl8y-agent-control: queued implement job b421e8bc-ada6-482d-a41f-fff888be4ed9 (not executed; no Hetzner VM).

cl8y-agent-control: queued `implement` job `b421e8bc-ada6-482d-a41f-fff888be4ed9` (not executed; no Hetzner VM).
Author
Owner

PR #171 merged to main at 2906caa (#171). Branch issue/170 deleted on origin.

Acceptance criteria (code review + local tests)

AC1–AC8 pass in tree: dest-approved EVM execute after cancel window with method-level RPC fallback; CancelWindowActive / period-full stay retryable (not terminal_executions); BelowMin / cancelled / already-executed stay terminal; Hash Verification keeps dest-Approved as Pending (not verified) and names EVM cancel remaining + GL-127 rate-limit banners. No contract / rate-limit bypass in the diff. #164 / #127 / #139 not touched.

Local sanity on 73d1852 (this workstation):

  • cargo test --bins writers:: — 47 passed
  • cargo test --bins rpc_fallback — 8 passed
  • cargo clippy --bins -- -D warnings — clean
  • Frontend: hashVerifyExecuteBlocker + HashComparisonPanel (19), plus DestHashCard / evmExecutionRateLimit / hash verification tests (50)

Woodpecker (problem)

Required check ci/woodpecker/pr/woodpecker never posted (0 commit statuses on the PR head, same as #166 / #168). This repo has no Forgejo webhook to ci.cl8y.com and no .woodpecker.yaml. fj pr merge returned 405 until a maintainer force_merge after the local suite. OpenBao (10.10.0.4) was unreachable from this session (wg-quick@wg0 inactive), so the Woodpecker project could not be enabled here.

Still open after merge (not this ticket)

  • Redeploy operator so AC1 can run on live dest-approved Terra→BNB rows
  • /verify?hash= live check for Pending + blocker, then verified after execute
  • Wire Woodpecker for this repo so the required check can actually run

Follow-up issue to be filed next (this ticket stays closed).

**PR #171 merged** to `main` at `2906caa` (https://git.cl8y.com/code/cl8y-bridge-monorepo/pulls/171). Branch `issue/170` deleted on origin. ### Acceptance criteria (code review + local tests) AC1–AC8 pass in tree: dest-approved EVM execute after cancel window with method-level RPC fallback; `CancelWindowActive` / period-full stay retryable (not `terminal_executions`); BelowMin / cancelled / already-executed stay terminal; Hash Verification keeps dest-Approved as **Pending** (not verified) and names EVM cancel remaining + GL-127 rate-limit banners. No contract / rate-limit bypass in the diff. #164 / #127 / #139 not touched. Local sanity on `73d1852` (this workstation): - `cargo test --bins writers::` — 47 passed - `cargo test --bins rpc_fallback` — 8 passed - `cargo clippy --bins -- -D warnings` — clean - Frontend: `hashVerifyExecuteBlocker` + `HashComparisonPanel` (19), plus `DestHashCard` / `evmExecutionRateLimit` / hash verification tests (50) ### Woodpecker (problem) Required check `ci/woodpecker/pr/woodpecker` never posted (0 commit statuses on the PR head, same as #166 / #168). This repo has **no Forgejo webhook** to `ci.cl8y.com` and **no** `.woodpecker.yaml`. `fj pr merge` returned 405 until a maintainer `force_merge` after the local suite. OpenBao (`10.10.0.4`) was unreachable from this session (`wg-quick@wg0` inactive), so the Woodpecker project could not be enabled here. ### Still open after merge (not this ticket) - Redeploy operator so AC1 can run on live dest-approved Terra→BNB rows - `/verify?hash=` live check for Pending + blocker, then verified after execute - Wire Woodpecker for this repo so the required check can actually run Follow-up issue to be filed next (this ticket stays closed).
Author
Owner

Follow-up tracking: #172

Follow-up tracking: https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/172
Author
Owner

cl8y-agent-control: needs_human inbox card POST failed. Job stays parked.

cl8y-agent-control: needs_human inbox card POST failed. Job stays parked.
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#170
No description provided.