Skip to content

Reject spending withdrawal outputs in transactions#103

Open
1440000bytes wants to merge 2 commits into
LayerTwo-Labs:masterfrom
1440000bytes:fix-reject-spending-withdrawal-output
Open

Reject spending withdrawal outputs in transactions#103
1440000bytes wants to merge 2 commits into
LayerTwo-Labs:masterfrom
1440000bytes:fix-reject-spending-withdrawal-output

Conversation

@1440000bytes

Copy link
Copy Markdown
Contributor

A withdrawal output (OutputContent::Withdrawal) is created with the withdrawing user's own address and inserted into state.utxos + the utreexo accumulator like any output. When a withdrawal bundle is collected, collect_withdrawal_bundle only reads those UTXOs (via a read-only txn) to build the M6, stores the bundle as Pending, and the node broadcasts the M6 to the mainchain but the UTXOs are not removed from the set or the accumulator until the later Submitted event.

During that Pending - Submitted window the owner can spend the withdrawal output back to a Value output. Every consensus check passes: the utreexo leaf is still live, validate_utxo_hashes matches the real output's hash, validate_filled_transaction sees value_in == value_out, and authorization succeeds with the owner's key. Nothing rejected spending a Withdrawal-content output (is_withdrawal() was only used in wallet coin-selection).

Impact:

  • Double-spend / peg insolvency: the M6 pays the value out on the mainchain treasury while the same value remains spendable on the sidechain.
  • Deterministic halt: when the Submitted event later runs if !state.utxos.delete(...) { return Err(NoUtxo) }, the UTXO is already gone, so block connection fails for every node on deterministic 2WPD data.

Fix

validate_filled_transaction now rejects any transaction input whose spent output is Withdrawal-content (Error::SpendWithdrawalOutput). A withdrawal output is committed to a bundle and can only be consumed by the bundle mechanism, never by a transaction which also guarantees the Submitted delete succeeds.

@1440000bytes

Copy link
Copy Markdown
Contributor Author
running 2 tests

[demo] outpoint regular 0101010101010101010101010101010101010101010101010101010101010101 0 is a Withdrawal output (value 1000 + main_fee 300)
[demo] re-spending it to a 1300-sat Value output: validate_filled_transaction => Ok(0 SAT)
[demo] ACCEPTED: the in-flight withdrawal is double-spendable on the sidechain

[demo] pending bundle b1f9d1947f6d43a637a6421a47afc45800dc3685c39b9b0d00fc0bb41762d335 commits outpoint regular 0202020202020202020202020202020202020202020202020202020202020202 0, but it was already spent (not in state.utxos)
[demo] delivering the Submitted event: connect_withdrawal_bundle_submitted => Err(NoUtxo(NoUtxo { outpoint: Regular { txid: 0202...0202, vout: 0 } }))
[demo] HALT: block connection fails deterministically for every node

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 10 filtered out; finished in 0.01s

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