fix(terraclassic): bound withdrawal polling with an active index (#139) #158
No reviewers
Labels
No labels
agent:implement
agent:ready
backend
bug
cannot-reproduce
confirmed
desktop
docs
documentation
duplicate
enhancement
feature
frontend
good first issue
help wanted
high-risk
in-review
invalid
mobile
needs-triage
P0-critical
P1-high
P2-medium
P3-low
qa
QA
question
ready
report
responsive
security
security-escalate
smart-contract
solana
tablet
test-pass
ux
wallet-issue
wallet:keplr
wallet:metamask
wallet:station
wallet:walletconnect
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-bridge-monorepo!158
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/gl-139-terra-active-withdrawals"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
ACTIVE_WITHDRAW_HASHESso Terra list queries used by the operator and canceler iterate in-flight withdrawals only, not lifetime executed/cancelled history (GL-139).PENDING_WITHDRAWSas canonical status/replay storage.pending_withdrawalsremains all-status; newactive_withdrawals/active_withdraw_indexare versioned.pending_withdrawalson old contracts or incomplete migrate.d5aeda4): migrating from a wasm that does not maintain the index (< 2.1.0, including rollback to 2.0.0 then re-upgrade) resets leftovercomplete=trueand rebuilds (insert active, remove terminal leftovers). AdminContinueActiveIndexMigratecontinues batches when same-code_idwasm migrate is rejected, and supports an emergency rebuild.active_withdrawalsscan work is capped; clients continue vianext_start_after. Canceler fallback clears accumulated candidates.Related #139 (kept open: migrate gas measurement, soak, frontend historical-status manual check)
Invariants / docs
pending_withdrawals)Test plan
pending_withdrawalsstill all-statuscontract::migrateentry-point tests: empty, mixed resume, idempotent 2.1 continue, stalecomplete=trueafter 2.0, terminal leftover removal, cw-multi-test wiringContinueActiveIndexMigrateunauthorized + rebuild/resumenext_start_aftercontinuationpackages/contracts-terraclassiccargo test(then retest withdraw-flow + proptest + migrate tests)cargo check --binsoperator and canceler; clippy via pre-commitmultichain-rsTerra query serialization testsPENDING_WITHDRAWSmap with measured gas (needs deployed state)pending_withdrawAcceptance criteria (from #139)
(src_chain, nonce)still rejected after archival and after migratepending_withdrawunchangedpending_withdrawalsall-status semantics preserved (no silent break)next_start_afterfor skip-capped pages)active_withdrawalswith legacy fallback; canceler clears accumulators on fallback; no per-entry terminal debug logsNot in this MR
active_withdrawals(would hide completed transfers)marked this merge request as ready
RECOMMEND: FIX
Reviewed against #139. The active-index design is sound for the happy path (canonical
PENDING_WITHDRAWS, membership index, atomicsave_pending_and_sync_index, replay maps unchanged,pending_withdrawalsall-status preserved). It is not a BLOCK: this does not create a double-payout or strip RBAC. It does need a migrate/rollback fix before it is safe to document “rollback then re-upgrade”.Security / contract
PENDING_WITHDRAWS;(src_chain, nonce)stillWITHDRAW_NONCE_USEDinconsistent_skipped); lifecycle writes both in one txactive_withdrawalserrors → operator/canceler fall back topending_withdrawalsHigh: stale index after rollback + re-upgrade
migrate_active_index_batchis a no-op whencomplete=true. v2.0.0 does not maintainACTIVE_WITHDRAW_HASHES.Sequence:
active_index_complete=true.PENDING_WITHDRAWS.complete=trueand does not rebuild.Operator/canceler then query
active_withdrawalssuccessfully (no fallback) and omit withdrawals submitted during the v2.0 window. That is missed approvals / missed cancel-window coverage, not an extra payout. Fail-closed for those hashes is still “user funds wait forever until someone notices”.Additionally, reconstruction is insert-only. Terminal rows that stayed in the index while v2.0 executed them are not removed; they become orphans that inflate
ACTIVE_WITHDRAW_COUNTand query scan cost.Fix: If previous
CONTRACT_VERSIONis not2.1.0(or whenever migrating from 2.0.x), resetACTIVE_INDEX_MIGRATION(complete=false,last_key=None) and either clear the index or, on each scanned canonical row,remove_activewhen!is_active. Do not treat a leftovercomplete=trueas valid across a version downgrade.Medium: repeat-migrate on columbus-5
Docs say to re-run
terrad tx wasm migrate $BRIDGE_ADDRESS $NEW_CODE_IDuntil complete. Confirm wasmd on Terra Classic allows migrate to the same code_id. If it does not, addExecuteMsg::ContinueActiveIndexMigrate { limit }(admin-only) instead of requiring dummy code stores.Medium: unbounded skip loop
query_active_withdrawalsbreaks whenwithdrawals.len() >= limit, but inconsistent keys do not count toward that limit. A polluted index can make one query walk the entire map (gas griefing / LCD timeout). Cap total range iterations (e.g.limit + MAX_SKIPor a hard max) and document that a short page may need another cursor.#139 acceptance criteria
save_pending_and_sync_indexwritesPENDING_WITHDRAWSin contract codepending_withdrawunchangedpending_withdrawalsall-statuscomplete; rollback breaks the assumptionTesting
Strong: index insert/remove, batched migrate, orphan skip, incomplete query error, proptest INV-TC-AW1, withdraw-flow integration.
Missing vs issue verification:
PENDING_WITHDRAWS(explicitly unchecked).pending_withdrawals(INV-FE-TC-AW1 is coded; manual unchecked).Gas: batch 50 / cap 100 is a reasonable bound; still needs a real LCD measurement before mainnet migrate. Unapproved-spam growth of the active set remains an economic/DoS residual (#138).
mentioned in merge request !160
Follow-up (second pass): still RECOMMEND: FIX. Additional items not in the first note:
Canceler fallback does not reset
all_approvals. Inpackages/canceler/src/watcher.rs, a failedactive_withdrawalspage resets cursor /pages_fetched/total_seenbut not the accumulatedall_approvalsvec. If page 1 of the active query succeeds and a later page fails, fallback re-walkspending_withdrawalsand appends, duplicating cancel candidates. Re-cancel is mostly a no-op on-chain, but it is extra verify/cancel work. Clearall_approvals(andinconsistent_skipped) on fallback. Operator processes pages immediately so this is less severe there; still worth resetting counters on fallback.No
contract::migrateentry-point test. Resume/idempotency tests callmigrate_active_index_batchon mock storage only. Add a cw-multi-test that instantiates v2.0-shaped state and runscontract::migratewithMigrateMsg { active_index_batch_limit }: empty, mixed, resume, idempotent second call,active_withdrawalserrors while incomplete, OK when complete. A wiring regression incontract.rsmigrate would not be caught today.Please do not
Closes #139until migrate gas is measured on realisticPENDING_WITHDRAWSsize. Batching (50/100) exists; issue AC required “within tested gas limits.” Keep #139 open (or a follow-up) for gas + soak + frontend historical-status. Unapproved-spam bounding stays #138.Agree with documenting an emergency rebuild (
complete=falsein a follow-up wasm) in addition to the rollback+re-upgrade reset requested in the first note. Canonical-active without an index key is invisible toactive_withdrawals(orphans are skipped; holes omit operator work).added 1 commit
d5aeda49- fix(terraclassic): rebuild active index after 2.0 rollback and bound queries (#139)Compare with previous version
Review follow-up (
d5aeda4)Addressed the two review notes on this MR (RECOMMEND: FIX). Work was done in the existing
fix/gl-139-terra-active-withdrawalsworktree, implemented, tested, retested, then pushed.Completed from reviews
migratereads cw2 before overwrite. If the previous version does not maintain the index (< 2.1.0), reconstruction is reset (complete=false,last_key=None) even when leftovercomplete=trueremains. Scan usessync_active_index(insert active, remove terminal leftovers).code_idmigrate on columbus-5ExecuteMsg::ContinueActiveIndexMigrate { limit, rebuild }. Userebuild: falseto continue batches if wasm migrate to the same code_id is rejected. Does not delete canonical rows.query_active_withdrawalsvisits at mostlimit + MAX_ACTIVE_QUERY_SKIPS(64) keys. Short/empty pages returnnext_start_after; operator/canceler continue on that cursor and stop onnull.all_approvalsall_approvals, skip counters, page counters, and cursor before the legacy walk. Operator also resets poll counters on fallback.contract::migrateentry-point testtests/test_active_index_migrate.rs: empty, mixed resume, idempotent 2.1 continue, stale complete after 2.0, terminal leftover removal, cw-multi-test.with_migratewiring. Plustest_continue_active_index_migrate_admin_only_rebuild_and_resume.Closes #139until gas is measuredContinueActiveIndexMigrate { rebuild: true }once, thenrebuild: falseuntil complete. INV-TC-AW3, deployment notes, OPERATIONAL_NOTES, skill.Invariants INV-TC-AW1–AW4 and
skills/agent-terraclassic-active-withdrawals.mdare updated and cross-linked with deployment +docs/contracts-terraclassic.md.Not done (out of scope / needs deployed state)
PENDING_WITHDRAWS(issue AC “within tested gas limits”).pending_withdrawals(INV-FE-TC-AW1 is coded; not exercised in a browser here).code_idwasm migrate (execute path covers both).active_withdrawals(would hide completed transfers; still must not).Tests run (then retested)
packages/contracts-terraclassic: fullcargo test -p bridge(lib, integration, proptest, withdraw flow, new migrate tests).--lib+test_active_index_migrate+test_withdraw_flow+proptest_active_withdraw.packages/operator:cargo check --bins;cargo test terra.packages/canceler:cargo check --bins;cargo test --lib.packages/multichain-rs:cargo test --lib terra.cargo clippy -p bridge -- -D warnings(lib).Checklist to verify on review / staging
2.0.0) → live submit/approve/execute → re-upgrade to 2.1. First migrate attributes includeactive_index_reset=true. New in-flight hashes appear inactive_withdrawals; hashes executed during the 2.0 window are absent from the index and still present inpending_withdraw.{"continue_active_index_migrate":{"limit":50,"rebuild":false}}until{"active_withdraw_index":{}}showsmigration_complete: true.rebuild: trueonce only, thenrebuild: false. Canonical rows unchanged.active_withdrawalserrors (clients fall back) until complete.ContinueActiveIndexMigratefails; index unchanged.next_start_afterset; operator/canceler keep paging instead of treating it as exhausted.pending_withdrawals+pending_withdraw(do not point the monitor atactive_withdrawals).(src_chain, nonce)still rejected.changed the description
added 7 commits
main9518fabf- chore: merge main into fix/gl-139-terra-active-withdrawals (active index + writer isolation)Compare with previous version
added 4 commits
main0a2c0167- chore: merge main into fix/gl-139-terra-active-withdrawals (token-rank docs)Compare with previous version
mentioned in commit
9e820004dcmentioned in issue #139
mentioned in merge request !163