feat(bridge): two-phase authorize-then-prove reservation settlement - #1091
Draft
mswilkison wants to merge 5 commits into
Draft
feat(bridge): two-phase authorize-then-prove reservation settlement#1091mswilkison wants to merge 5 commits into
mswilkison wants to merge 5 commits into
Conversation
Replace the single-phase reservation lifecycle (request -> prove, with all validity checked at proof time against live state) with the two-phase, nonce-bound state machine specified by RFC 13. Every Bitcoin action of a reservation's life — acceptance anchor, in-kind redemption, re-anchor, dissolution — is now an explicitly requested *generation*: - The request increments the position's monotonic request nonce, performs and RESERVES every capacity/lifecycle check (global total, per-wallet count, target-wallet liveness, the per-wallet dissolution lock), and snapshots every proof- and settlement-critical parameter into a ReservationAction record keyed by (reservationKey, nonce). A wallet that signs an authorized action can never find it unprovable because state moved after signing. - The SPV proof names its generation and validates exclusively against the record's snapshots. Reserved-redemption proofs additionally enforce the watchtower delay of the generation on-chain — an early-broadcasting wallet cannot finalize before the guardians' window closes, and a vetoed generation never settles (the unauthorized signature stays exposed to the fraud machinery). - Timeouts write terminal, late-proof-accepting records: a Bitcoin spend that confirmed before its proof landed still settles — consumed outpoints are marked honestly spent (defeating fraud challenges against the honest-but-late signature) and the anchor lineage closes — but no Bank movement is repeated: the timeout already refunded the escrow and slashed the wallet. If the position's current pending redemption also matches the transaction, the proof must settle that generation instead (burning the escrow, the correct accounting); a non-matching pending generation is unwound (Superseded) with its escrow refunded, because its anchor provably no longer exists. This closes the claim double-spend race, where a timeout/veto racing a confirmed redemption both refunded the claim on Ethereum and paid the BTC, with the late proof reverting. - Veto and objection state is keyed per generation, so objections never accumulate across generations and an unbanned owner's reservation is always processable again; the request path uses a reservation-scoped safety check (ban state only). - Re-anchor and dissolution gain explicit on-chain authorization: source wallet in MovingFunds (permissionless) or Live with governance consent; target Live, distinct, capacity-reserved; the proof requires the output to pay the recorded target — a same-wallet re-anchor no longer exists, removing the re-anchor/dissolution byte-ambiguity. Dissolutions take a per-wallet main-UTXO action lock and snapshot the expected main UTXO; registry drift (a sweep landing before a no-main-UTXO dissolution's proof) re-registers the output for the moved-funds sweep machinery. - Redemption requests are gated on a Live/MovingFunds wallet and the (snapshotted) grace bound; after grace only dissolution can be requested, so request/timeout cycling cannot defeat the stranding bound. A fee-paid generation that times out mints a single-use retry entitlement consumed by the fee-free retry path. A wallet with reservation anchors can no longer begin closing: with no main UTXO it enters MovingFunds to drain its anchors, and both redemption and dissolution timeouts slash like a pooled redemption timeout. The settlement side lives in a new external library, ReservationProofs, reached through Reservation so the ReservationRouter links exactly one library. Storage appends: reservationActionTimeout, reservationActions, walletPendingDissolution (gap 42 -> 39). Sizes: router 6,007 B, Reservation 14,760 B, ReservationProofs 19,416 B, Bridge unchanged at 22,403 B.
…validation @openzeppelin/upgrades-core@1.20.0's getUnlinkedBytecode probes every linkable contract by splicing its library-link placeholders into the bytecode being proxy-deployed. When a foreign contract's link position lands inside that bytecode's metadata-trim boundary, the truncated placeholder makes getVersion throw 'Bytecode is not a valid hex string' and the whole proxy deployment fails — observed for unrelated proxy deployments once the ReservationRouter's link references entered the compilation set. Newer upgrades-core versions wrap each probe in try/catch; this config-time shim applies the same per-candidate tolerance without a dependency bump and can be dropped when the plugin is upgraded.
Adapt the reservation suite to the authorize-then-prove flows (explicit acceptance/re-anchor/dissolution requests, generation-named proofs, unified action timeout, per-generation veto keying, retry entitlement) and add an adversarial settlement suite covering: the claim double-spend regression (timeout racing a confirmed redemption settles late with no second refund and fraud-defeat registration), the late-proof matrix against newer pending generations (matching generation must settle, non-matching is unwound with its escrow refunded), on-chain watchtower delay enforcement and the never-settling vetoed generation, the per-wallet dissolution lock with serialized no-main-UTXO dissolutions and the registry-drift fallback, capacity-reserved-before-signing (fill-then-prove), acceptance authorization timeout with late anchor settlement, the post-grace stranding bound, and wallet lifecycle integration (reservation-holding wallets retire through MovingFunds). The pooled WalletProposalValidator suite now fakes the Bridge from the merged Bridge+ReservationRouter ABI (router-resident views must return zeroed defaults rather than empty revert data), and the TIP-109 deployment tests expect the 6-library Bridge link map (Reservation moved behind the router).
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The CI slither (0.9.0) crashes with an internal AssertionError while generating slithir for qualified cross-library event emissions (emit Reservation.X(...) from ReservationProofs) — reproduced locally against the pinned version. Declare the settlement-side events in the library that emits them instead; the router keeps re-declaring the full set for the ABI, so nothing changes for off-chain consumers. Annotate the new reentrancy-events findings (all behind trusted-Bank refunds in the late-settlement unwind path) and the deliberate enabled-timestamp strict-equality check, matching the repository's existing conventions. Also make the upgrades-core validation shim loop-free (the repo's eslint config forbids for-of) and fix a template-literal-without- interpolation lint error in the router test.
This was referenced Aug 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1090 (router). Implements the settlement state machine of RFC 13 — the shared fix for the review's Critical and most High findings, whose root cause was a single-phase lifecycle racing timeout/veto against confirmed Bitcoin spends.
The machine
Every reservation Bitcoin action (acceptance anchor, in-kind redemption, re-anchor, dissolution) is now an explicitly requested generation keyed by
(reservationKey, requestNonce):ReservationActionrecord. A signed authorized action can never become unprovable because state moved later (H-02).timedOutRedemptions). A late proof settles: consumed outpoints are marked inspentMainUTXOs(honest-but-late signature defeats fraud challenges), the lineage closes, and no Bank movement repeats — no double refund, no double burn (C-01). If the current pending redemption also matches the tx, the proof must settle it (escrow burned — the correct accounting); a non-matching pending generation is unwound (Superseded, escrow refunded) since its anchor provably no longer exists.Closes
C-01, H-01, H-02, H-03, H-05 (request side), H-07, M-01, M-02, M-03. (H-06 termination stranding, M-04/M-05 reveal-side guards, and the H-04/M-06 backing model land in the next PRs of the stack.)
Shape
The settlement side is a new external library
ReservationProofs, reached throughReservationso the router links exactly one library. Storage appends:reservationActionTimeout,reservationActions,walletPendingDissolution(gap 42→39, append-only). Sizes: router 6,007 B / Reservation 14,760 B / ReservationProofs 19,416 B / Bridge unchanged 22,403 B — all with ≥5 KB EIP-170 margin.Includes a config-time backport of the upstream
@openzeppelin/upgrades-corefix for foreign link-reference splices crashing unrelated proxy validations (droppable on plugin upgrade).Tests
Full suite 3068 passing / 0 failing. New adversarial settlement suite: C-01 late-proof regression (no second refund, fraud-defeat registration, no double-settle), late-proof matrix vs newer pending generations, H-03 delay + vetoed-generation permanence, H-07 serialized dissolutions + drift fallback, H-02 fill-then-prove, acceptance-authorization timeout with late anchor settlement, post-grace stranding bound, wallet retirement through MovingFunds.
keep-core follow-up (flagged)
Proposal structs gained
requestNonceand proofs now name their generation — threshold-network/keep-core#4238's coordination/assemblers must carry the authorized nonce and consult action records (gated on this ABI publishing), including the newrequestReservationAcceptance/requestReservationDissolution/notifyReservationActionTimeoutentry points.