@@ -175,7 +175,11 @@ fn connect_withdrawal_bundle_submitted(
175175 } ;
176176 return Err ( err) ;
177177 }
178- ( _, WithdrawalBundleStatus :: Submitted ) => {
178+ (
179+ _,
180+ WithdrawalBundleStatus :: Submitted
181+ | WithdrawalBundleStatus :: SubmittedUnexpected ,
182+ ) => {
179183 let err =
180184 error:: ConnectWithdrawalBundleSubmitted :: Resubmitted {
181185 event_block_hash : * event_block_hash,
@@ -242,8 +246,8 @@ fn connect_withdrawal_bundle_submitted(
242246 }
243247 }
244248 bundle_status
245- . push ( WithdrawalBundleStatus :: Submitted , block_height)
246- . expect ( "push submitted status should be valid" ) ;
249+ . push ( WithdrawalBundleStatus :: SubmittedUnexpected , block_height)
250+ . expect ( "push submitted unexpected status should be valid" ) ;
247251 state
248252 . withdrawal_bundles
249253 . put ( rwtxn, & m6id, & ( bundle, bundle_status) ) ?
@@ -283,43 +287,90 @@ fn connect_withdrawal_bundle_confirmed(
283287 // Already applied
284288 return Ok ( ( ) ) ;
285289 }
286- assert_eq ! (
290+ assert ! ( matches !(
287291 bundle_status. latest( ) . value,
288292 WithdrawalBundleStatus :: Submitted
289- ) ;
290- // If an unknown bundle is confirmed, all UTXOs older than the
291- // bundle submission are potentially spent.
292- // This is only accepted in the case that block height is 0,
293- // and so no UTXOs could possibly have been double-spent yet.
294- // In this case, ALL UTXOs are considered spent.
295- if !bundle. is_known ( ) {
296- if block_height == 0 {
297- tracing:: warn!(
298- %event_block_hash,
299- %m6id,
300- "Unknown withdrawal bundle confirmed, marking all UTXOs as spent"
301- ) ;
302- let raw_utxos: BTreeMap < OutPointKey , _ > =
303- state. utxos . iter ( rwtxn) ?. collect ( ) ?;
304- let mut utxos: BTreeMap < OutPoint , _ > = BTreeMap :: new ( ) ;
305- for ( outpoint_key, output) in & raw_utxos {
306- let outpoint = OutPoint :: from ( * outpoint_key) ;
307- let spent_output = SpentOutput {
308- output : output. clone ( ) ,
309- inpoint : InPoint :: Withdrawal { m6id } ,
310- } ;
311- state. stxos . put ( rwtxn, outpoint_key, & spent_output) ?;
312- utxos. insert ( outpoint, output. clone ( ) ) ;
313- }
314- state. utxos . clear ( rwtxn) ?;
315- bundle =
316- WithdrawalBundleInfo :: UnknownConfirmed { spend_utxos : utxos } ;
317- } else {
318- return Err ( Error :: UnknownWithdrawalBundleConfirmed {
293+ | WithdrawalBundleStatus :: SubmittedUnexpected
294+ ) ) ;
295+ match & bundle {
296+ WithdrawalBundleInfo :: UnknownConfirmed { spend_utxos : _ } => {
297+ return Err ( Error :: UnknownWithdrawalBundleReconfirmed {
319298 event_block_hash : * event_block_hash,
320299 m6id,
321300 } ) ;
322301 }
302+ WithdrawalBundleInfo :: Unknown => {
303+ // If an unknown bundle is confirmed, all UTXOs older than the
304+ // bundle submission are potentially spent.
305+ // This is only accepted in the case that block height is 0,
306+ // and so no UTXOs could possibly have been double-spent yet.
307+ // In this case, ALL UTXOs are considered spent.
308+ if block_height == 0 {
309+ tracing:: warn!(
310+ %event_block_hash,
311+ %m6id,
312+ "Unknown withdrawal bundle confirmed, marking all UTXOs as spent"
313+ ) ;
314+ let utxos: BTreeMap < OutPoint , FilledOutput > = state
315+ . utxos
316+ . iter ( rwtxn)
317+ . map_err ( Error :: from) ?
318+ . map ( |( key, output) | Ok ( ( key. into ( ) , output) ) )
319+ . collect ( ) ?;
320+ for ( outpoint, output) in & utxos {
321+ let spent_output = SpentOutput {
322+ output : output. clone ( ) ,
323+ inpoint : InPoint :: Withdrawal { m6id } ,
324+ } ;
325+ state. stxos . put (
326+ rwtxn,
327+ & OutPointKey :: from ( outpoint) ,
328+ & spent_output,
329+ ) ?;
330+ }
331+ state. utxos . clear ( rwtxn) ?;
332+ bundle = WithdrawalBundleInfo :: UnknownConfirmed {
333+ spend_utxos : utxos,
334+ } ;
335+ } else {
336+ return Err ( Error :: UnknownWithdrawalBundleConfirmed {
337+ event_block_hash : * event_block_hash,
338+ m6id,
339+ } ) ;
340+ }
341+ }
342+ WithdrawalBundleInfo :: Known ( bundle) => {
343+ if matches ! (
344+ bundle_status. latest( ) . value,
345+ WithdrawalBundleStatus :: SubmittedUnexpected
346+ ) {
347+ // If a previously dropped or failed bundle is confirmed,
348+ // then unless all of the bundle UTXOs can be spent,
349+ // the chain is insolvent, and cannot continue.
350+ tracing:: warn!(
351+ %event_block_hash,
352+ %m6id,
353+ "Unexpected withdrawal bundle confirmed, marking bundle UTXOs as spent"
354+ ) ;
355+ for ( outpoint, output) in bundle. spend_utxos ( ) {
356+ let outpoint_key = OutPointKey :: from ( outpoint) ;
357+ if !state. utxos . delete ( rwtxn, & outpoint_key) ? {
358+ return Err (
359+ Error :: UnexpectedWithdrawalBundleInsolvency {
360+ event_block_hash : * event_block_hash,
361+ m6id,
362+ outpoint : * outpoint,
363+ } ,
364+ ) ;
365+ }
366+ let spent_output = SpentOutput {
367+ output : output. clone ( ) ,
368+ inpoint : InPoint :: Withdrawal { m6id } ,
369+ } ;
370+ state. stxos . put ( rwtxn, & outpoint_key, & spent_output) ?;
371+ }
372+ }
373+ }
323374 }
324375 bundle_status
325376 . push ( WithdrawalBundleStatus :: Confirmed , block_height)
@@ -348,17 +399,21 @@ fn connect_withdrawal_bundle_failed(
348399 // Already applied
349400 return Ok ( ( ) ) ;
350401 }
351- assert_eq ! (
402+ assert ! ( matches !(
352403 bundle_status. latest( ) . value,
353404 WithdrawalBundleStatus :: Submitted
354- ) ;
355- bundle_status
356- . push ( WithdrawalBundleStatus :: Failed , block_height)
357- . expect ( "Push failed status should be valid" ) ;
405+ | WithdrawalBundleStatus :: SubmittedUnexpected
406+ ) ) ;
358407 match & bundle {
359408 WithdrawalBundleInfo :: Unknown
360409 | WithdrawalBundleInfo :: UnknownConfirmed { .. } => ( ) ,
361- WithdrawalBundleInfo :: Known ( bundle) => {
410+ WithdrawalBundleInfo :: Known ( bundle) => ' known: {
411+ if matches ! (
412+ bundle_status. latest( ) . value,
413+ WithdrawalBundleStatus :: SubmittedUnexpected
414+ ) {
415+ break ' known;
416+ }
362417 for ( outpoint, output) in bundle. spend_utxos ( ) {
363418 state. stxos . delete ( rwtxn, & OutPointKey :: from ( outpoint) ) ?;
364419 state
@@ -382,6 +437,9 @@ fn connect_withdrawal_bundle_failed(
382437 ) ?;
383438 }
384439 }
440+ bundle_status
441+ . push ( WithdrawalBundleStatus :: Failed , block_height)
442+ . expect ( "Push failed status should be valid" ) ;
385443 state
386444 . withdrawal_bundles
387445 . put ( rwtxn, & m6id, & ( bundle, bundle_status) ) ?;
@@ -569,10 +627,11 @@ fn disconnect_withdrawal_bundle_submitted(
569627 }
570628 } ;
571629 let ( bundle_status, latest_bundle_status) = bundle_status. pop ( ) ;
572- assert_eq ! (
630+ assert ! ( matches !(
573631 latest_bundle_status. value,
574632 WithdrawalBundleStatus :: Submitted
575- ) ;
633+ | WithdrawalBundleStatus :: SubmittedUnexpected
634+ ) ) ;
576635 assert_eq ! ( latest_bundle_status. height, block_height) ;
577636 match & bundle {
578637 WithdrawalBundleInfo :: Unknown
@@ -622,7 +681,11 @@ fn disconnect_withdrawal_bundle_confirmed(
622681 . try_get ( rwtxn, & m6id) ?
623682 . ok_or_else ( || Error :: UnknownWithdrawalBundle { m6id } ) ?;
624683 let ( prev_bundle_status, latest_bundle_status) = bundle_status. pop ( ) ;
625- if latest_bundle_status. value == WithdrawalBundleStatus :: Submitted {
684+ if matches ! (
685+ latest_bundle_status. value,
686+ WithdrawalBundleStatus :: Submitted
687+ | WithdrawalBundleStatus :: SubmittedUnexpected
688+ ) {
626689 // Already applied
627690 return Ok ( ( ) ) ;
628691 }
@@ -633,25 +696,41 @@ fn disconnect_withdrawal_bundle_confirmed(
633696 assert_eq ! ( latest_bundle_status. height, block_height) ;
634697 let prev_bundle_status = prev_bundle_status
635698 . expect ( "Pop confirmed bundle status should be valid" ) ;
636- assert_eq ! (
699+ assert ! ( matches !(
637700 prev_bundle_status. latest( ) . value,
638701 WithdrawalBundleStatus :: Submitted
639- ) ;
640- match bundle {
641- WithdrawalBundleInfo :: Known ( _) | WithdrawalBundleInfo :: Unknown => ( ) ,
702+ | WithdrawalBundleStatus :: SubmittedUnexpected
703+ ) ) ;
704+ match & bundle {
705+ WithdrawalBundleInfo :: Known ( bundle) => {
706+ if matches ! (
707+ prev_bundle_status. latest( ) . value,
708+ WithdrawalBundleStatus :: SubmittedUnexpected
709+ ) {
710+ for ( outpoint, output) in bundle. spend_utxos ( ) {
711+ let outpoint_key = OutPointKey :: from ( outpoint) ;
712+ state. utxos . put ( rwtxn, & outpoint_key, output) ?;
713+ if !state. stxos . delete ( rwtxn, & outpoint_key) ? {
714+ return Err ( Error :: NoStxo {
715+ outpoint : * outpoint,
716+ } ) ;
717+ } ;
718+ }
719+ }
720+ }
642721 WithdrawalBundleInfo :: UnknownConfirmed { spend_utxos } => {
643722 for ( outpoint, output) in spend_utxos {
644- state. utxos . put (
645- rwtxn,
646- & OutPointKey :: from ( & outpoint) ,
647- & output,
648- ) ?;
649- if !state. stxos . delete ( rwtxn, & OutPointKey :: from ( & outpoint) ) ? {
650- return Err ( Error :: NoStxo { outpoint } ) ;
723+ let outpoint_key = OutPointKey :: from ( outpoint) ;
724+ state. utxos . put ( rwtxn, & outpoint_key, output) ?;
725+ if !state. stxos . delete ( rwtxn, & outpoint_key) ? {
726+ return Err ( Error :: NoStxo {
727+ outpoint : * outpoint,
728+ } ) ;
651729 } ;
652730 }
653731 bundle = WithdrawalBundleInfo :: Unknown ;
654732 }
733+ WithdrawalBundleInfo :: Unknown => ( ) ,
655734 }
656735 state. withdrawal_bundles . put (
657736 rwtxn,
@@ -681,14 +760,21 @@ fn disconnect_withdrawal_bundle_failed(
681760 assert_eq ! ( latest_bundle_status. height, block_height) ;
682761 let prev_bundle_status =
683762 prev_bundle_status. expect ( "Pop failed bundle status should be valid" ) ;
684- assert_eq ! (
763+ assert ! ( matches !(
685764 prev_bundle_status. latest( ) . value,
686765 WithdrawalBundleStatus :: Submitted
687- ) ;
766+ | WithdrawalBundleStatus :: SubmittedUnexpected
767+ ) ) ;
688768 match & bundle {
689769 WithdrawalBundleInfo :: Unknown
690770 | WithdrawalBundleInfo :: UnknownConfirmed { .. } => ( ) ,
691- WithdrawalBundleInfo :: Known ( bundle) => {
771+ WithdrawalBundleInfo :: Known ( bundle) => ' known: {
772+ if matches ! (
773+ prev_bundle_status. latest( ) . value,
774+ WithdrawalBundleStatus :: SubmittedUnexpected
775+ ) {
776+ break ' known;
777+ }
692778 for ( outpoint, output) in bundle. spend_utxos ( ) . iter ( ) . rev ( ) {
693779 let spent_output = SpentOutput {
694780 output : output. clone ( ) ,
0 commit comments