Skip to content

Enforce swap consensus rules during block validation#71

Open
1440000bytes wants to merge 2 commits into
LayerTwo-Labs:mainfrom
1440000bytes:fix/block-swap-validation
Open

Enforce swap consensus rules during block validation#71
1440000bytes wants to merge 2 commits into
LayerTwo-Labs:mainfrom
1440000bytes:fix/block-swap-validation

Conversation

@1440000bytes

@1440000bytes 1440000bytes commented Jun 9, 2026

Copy link
Copy Markdown

The three swap validators in lib/state/swap.rs are only reached from the mempool path, State::validate_transaction (lib state/mod.rs). The block-application path used for every connected block — prevalidate - connect_prevalidated (lib state/block.rs, called from connect_tip_ in lib/node/net_task.rs) — only checks value conservation, utreexo proofs, sigops, size, the merkle root, double-spends, and signatures. It never calls validate_swap_create, validate_swap_claim, or validate_no_locked_outputs.

  • A swap-locked SwapPending output can be spent in a regular transaction, bypassing the swap entirely (the spender double-spends a counterparty who already paid the L1 side).
  • A SwapClaim can be included that pays the attacker rather than the recipient fixed at swap creation, since the address binding check on SwapPending inputs is intentionally skipped for claims.

Fix

Add swap::validate_block_transaction, invoked per transaction in both prevalidate and validate:

  • SwapCreate - existing validate_swap_create
  • Regular - existing validate_no_locked_outputs
  • SwapClaim - new validate_swap_claim_consensus

A swap's state (Pending - WaitingConfirmations - ReadyToClaim) is set by each node's own parent-chain monitoring (query_and_update_swap in lib/state/two_way_peg_data.rs, plus manual RPC/GUI updates). It is not deterministic across nodes — which is exactly why connect trusts the block and force-advances local state. Calling the full validate_swap_claim here would let a node that hasn't yet observed the L1 fill reject an otherwise valid block and split consensus.

So validate_swap_claim_consensus enforces only the rules that follow deterministically from on-chain data:

  • the claim spends at least one output locked to its swap, and nothing locked to a different swap;
  • for pre-specified swaps, an output pays the recipient fixed at creation.

Test

New unit tests in lib/state/swap.rs

  • block_validation_rejects_spending_locked_output — a regular tx spending a swap-locked output is rejected
  • block_validation_rejects_claim_to_wrong_recipient — a pre-specified claim paying someone else is rejected
  • block_validation_accepts_claim_to_recipient — a legitimate claim still passes (no false rejection)

@rsantacroce

Copy link
Copy Markdown
Collaborator

Thanks for the contribution! There are a few CI failures to address — mostly formatting and similar minor issues. Once those are fixed, I'll go ahead and merge.

@1440000bytes 1440000bytes force-pushed the fix/block-swap-validation branch from 5aa62b7 to 41ae51e Compare June 13, 2026 10:48
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.

2 participants