Skip to content

fix(cross2eth): reuse broadcast lock tx on ebcli retry to prevent double mint - #1320

Draft
33cn wants to merge 1 commit into
masterfrom
agents/fix-https-github-com-33cn-plugin-actions-runs
Draft

fix(cross2eth): reuse broadcast lock tx on ebcli retry to prevent double mint#1320
33cn wants to merge 1 commit into
masterfrom
agents/fix-https-github-com-33cn-plugin-actions-runs

Conversation

@33cn

@33cn 33cn commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Context

ci_cross2eth failed on run https://github.com/33cn/plugin/actions/runs/33836471133/job/100909914007?pr=1312 (branch fix-token-duplicate-finishcreate), while the pull_request run of the same commit passed — a timing flake, not a regression from that PR.

Failure: TestETH2Chain33USDT_proxy_excess (BSC side) — after a single ethereum lock -m 100, the bridge bank balance went 140 → 340 (expected 240). Two locks executed.

Root cause

ebcli's sync lock command retries the whole Manager.LockEthErc20Asset rpc up to 3 times on error (commit 857c01f). The flow is not idempotent (approve + lock txs). In the failing run the first attempt's Lock tx was broadcast/mined but the handler reported a transient error (26s wall time fits a send-stage rpc error: ~10s attempt 1 + 0.5s retry sleep + ~15s attempt 2), so the retry re-executed the lock — locking the same asset twice on ETH/BSC and minting 2x on the chain33 side.

Fix

Make the sync lock idempotent across retries in the ebrelayer:

  • ethtxs.LockEthErc20Asset now returns LockBroadcastError carrying the tx hash at the two post-broadcast error points (lock send error with non-nil tx, and receipt-wait failure).
  • New ethtxs.WaitLockTxConfirmed waits for a recorded tx and distinguishes confirmed-success / confirmed-failed (ErrLockTxFailed) / still-pending (ErrLockTxPending).
  • manager.LockEthErc20Asset records broadcast lock txs keyed by chain+owner+token+amount+receiver (10min TTL, swept on write). On retry with the same params it waits for the recorded tx instead of re-locking; only a definitively reverted tx is re-executed.

This also protects against the rpc-reply-lost-after-success case, at the cost of an idempotency window: an identical lock request within the TTL returns the first tx hash.

Tests

  • New Test_WaitLockTxConfirmedAndBroadcastError in ethtxs (simulator): wait-on-recorded-hash returns the same hash; LockBroadcastError carries the hash via errors.As.
  • go test ./plugin/dapp/cross2eth/ebrelayer/... all pass.
  • ci_cross2eth (docker e2e, ~40min) is the end-to-end verification.

🤖 Generated with Claude Code

…ble mint

ebcli's sync lock command retries Manager.LockEthErc20Asset up to 3 times
on rpc error. When the first attempt already broadcast the lock tx but
failed to confirm it (transient rpc hiccup), the retry re-executed the
whole approve+lock flow, locking the same asset twice and minting 2x on
the chain33 side. ci_cross2eth caught this in
TestETH2Chain33USDT_proxy_excess: bridge bank went 140->340 after a
single lock of 100.

Record broadcast-but-unconfirmed lock txs in the manager keyed by
chain+owner+token+amount+receiver with a 10min TTL; on retry with the
same params, wait for the recorded tx instead of re-locking. ethtxs now
returns the tx hash in LockBroadcastError at the two post-broadcast
error points, and WaitLockTxConfirmed distinguishes confirmed-success /
confirmed-failed / still-pending so the manager can reuse, re-execute,
or keep waiting accordingly.

Co-Authored-By: Claude <noreply@anthropic.com>
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