@@ -72,18 +72,23 @@ impl OnchainEventEntry {
72
72
}
73
73
}
74
74
75
- /// Upon discovering of some classes of onchain tx by ChannelMonitor, we may have to take actions on it
76
- /// once they mature to enough confirmations (ANTI_REORG_DELAY)
75
+ /// Events for claims the [`OnchainTxHandler`] has generated. Once the events are considered safe
76
+ /// from a chain reorg, the [`OnchainTxHandler`] will act accordingly.
77
77
#[ derive( PartialEq , Eq ) ]
78
78
enum OnchainEvent {
79
- /// Outpoint under claim process by our own tx, once this one get enough confirmations, we remove it from
80
- /// bump-txn candidate buffer.
79
+ /// A pending request has been claimed by a transaction spending the exact same set of outpoints
80
+ /// as the request. This claim can either be ours or from the counterparty. Once the claiming
81
+ /// transaction has met [`ANTI_REORG_DELAY`] confirmations, we consider it final and remove the
82
+ /// pending request.
81
83
Claim {
82
84
package_id : PackageID ,
83
85
} ,
84
- /// Claim tx aggregate multiple claimable outpoints. One of the outpoint may be claimed by a counterparty party tx.
85
- /// In this case, we need to drop the outpoint and regenerate a new claim tx. By safety, we keep tracking
86
- /// the outpoint to be sure to resurect it back to the claim tx if reorgs happen.
86
+ /// The counterparty has claimed an outpoint from one of our pending requests through a
87
+ /// different transaction than ours. If our transaction was attempting to claim multiple
88
+ /// outputs, we need to drop the outpoint claimed by the counterparty and regenerate a new claim
89
+ /// transaction for ourselves. We keep tracking, separately, the outpoint claimed by the
90
+ /// counterparty up to [`ANTI_REORG_DELAY`] confirmations to ensure we attempt to re-claim it
91
+ /// if the counterparty's claim is reorged from the chain.
87
92
ContentiousOutpoint {
88
93
package : PackageTemplate ,
89
94
}
@@ -215,7 +220,6 @@ type PackageID = [u8; 32];
215
220
216
221
/// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and
217
222
/// do RBF bumping if possible.
218
- #[ derive( PartialEq ) ]
219
223
pub struct OnchainTxHandler < ChannelSigner : WriteableEcdsaChannelSigner > {
220
224
destination_script : Script ,
221
225
holder_commitment : HolderCommitmentTransaction ,
@@ -244,15 +248,26 @@ pub struct OnchainTxHandler<ChannelSigner: WriteableEcdsaChannelSigner> {
244
248
pub ( crate ) pending_claim_requests : HashMap < PackageID , PackageTemplate > ,
245
249
#[ cfg( not( test) ) ]
246
250
pending_claim_requests : HashMap < PackageID , PackageTemplate > ,
251
+
252
+ // Used to track external events that need to be forwarded to the `ChainMonitor`. This `Vec`
253
+ // essentially acts as an insertion-ordered `HashMap` – there should only ever be one occurrence
254
+ // of a `PackageID`, which tracks its latest `ClaimEvent`, i.e., if a pending claim exists, and
255
+ // a new block has been connected, resulting in a new claim, the previous will be replaced with
256
+ // the new.
257
+ //
258
+ // These external events may be generated in the following cases:
259
+ // - A channel has been force closed by broadcasting the holder's latest commitment transaction
260
+ // - A block being connected/disconnected
261
+ // - Learning the preimage for an HTLC we can claim onchain
247
262
#[ cfg( anchors) ]
248
- pending_claim_events : HashMap < PackageID , ClaimEvent > ,
249
-
250
- // Used to link outpoints claimed in a connected block to a pending claim request.
251
- // Key is outpoint than monitor parsing has detected we have keys/scripts to claim
252
- // Value is ( pending claim request identifier, confirmation_block), identifier
253
- // is txid of the initial claiming transaction and is immutable until outpoint is
254
- // post-anti-reorg-delay solved, confirmaiton_block is used to erase entry if
255
- // block with output gets disconnected.
263
+ pending_claim_events : Vec < ( PackageID , ClaimEvent ) > ,
264
+
265
+ // Used to link outpoints claimed in a connected block to a pending claim request. The keys
266
+ // represent the outpoints that our `ChannelMonitor` has detected we have keys/scripts to
267
+ // claim. The values track the pending claim request identifier and the initial confirmation
268
+ // block height, and are immutable until the outpoint has enough confirmations to meet our
269
+ // [`ANTI_REORG_DELAY`]. The initial confirmation block height is used to remove the entry if
270
+ // the block gets disconnected.
256
271
#[ cfg( test) ] // Used in functional_test to verify sanitization
257
272
pub claimable_outpoints : HashMap < BitcoinOutPoint , ( PackageID , u32 ) > ,
258
273
#[ cfg( not( test) ) ]
@@ -265,6 +280,22 @@ pub struct OnchainTxHandler<ChannelSigner: WriteableEcdsaChannelSigner> {
265
280
pub ( super ) secp_ctx : Secp256k1 < secp256k1:: All > ,
266
281
}
267
282
283
+ impl < ChannelSigner : WriteableEcdsaChannelSigner > PartialEq for OnchainTxHandler < ChannelSigner > {
284
+ fn eq ( & self , other : & Self ) -> bool {
285
+ // `signer`, `secp_ctx`, and `pending_claim_events` are excluded on purpose.
286
+ self . destination_script == other. destination_script &&
287
+ self . holder_commitment == other. holder_commitment &&
288
+ self . holder_htlc_sigs == other. holder_htlc_sigs &&
289
+ self . prev_holder_commitment == other. prev_holder_commitment &&
290
+ self . prev_holder_htlc_sigs == other. prev_holder_htlc_sigs &&
291
+ self . channel_transaction_parameters == other. channel_transaction_parameters &&
292
+ self . pending_claim_requests == other. pending_claim_requests &&
293
+ self . claimable_outpoints == other. claimable_outpoints &&
294
+ self . locktimed_packages == other. locktimed_packages &&
295
+ self . onchain_events_awaiting_threshold_conf == other. onchain_events_awaiting_threshold_conf
296
+ }
297
+ }
298
+
268
299
const SERIALIZATION_VERSION : u8 = 1 ;
269
300
const MIN_SERIALIZATION_VERSION : u8 = 1 ;
270
301
@@ -406,7 +437,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
406
437
pending_claim_requests,
407
438
onchain_events_awaiting_threshold_conf,
408
439
#[ cfg( anchors) ]
409
- pending_claim_events : HashMap :: new ( ) ,
440
+ pending_claim_events : Vec :: new ( ) ,
410
441
secp_ctx,
411
442
} )
412
443
}
@@ -427,8 +458,7 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
427
458
locktimed_packages : BTreeMap :: new ( ) ,
428
459
onchain_events_awaiting_threshold_conf : Vec :: new ( ) ,
429
460
#[ cfg( anchors) ]
430
- pending_claim_events : HashMap :: new ( ) ,
431
-
461
+ pending_claim_events : Vec :: new ( ) ,
432
462
secp_ctx,
433
463
}
434
464
}
@@ -443,9 +473,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
443
473
444
474
#[ cfg( anchors) ]
445
475
pub ( crate ) fn get_and_clear_pending_claim_events ( & mut self ) -> Vec < ClaimEvent > {
446
- let mut ret = HashMap :: new ( ) ;
447
- swap ( & mut ret , & mut self . pending_claim_events ) ;
448
- ret . into_iter ( ) . map ( |( _, event) | event) . collect :: < Vec < _ > > ( )
476
+ let mut events = Vec :: new ( ) ;
477
+ swap ( & mut events , & mut self . pending_claim_events ) ;
478
+ events . into_iter ( ) . map ( |( _, event) | event) . collect ( )
449
479
}
450
480
451
481
/// Lightning security model (i.e being able to redeem/timeout HTLC or penalize counterparty
@@ -474,12 +504,12 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
474
504
// transaction is reorged out.
475
505
let mut all_inputs_have_confirmed_spend = true ;
476
506
for outpoint in request_outpoints. iter ( ) {
477
- if let Some ( first_claim_txid_height ) = self . claimable_outpoints . get ( * outpoint) {
507
+ if let Some ( ( request_package_id , _ ) ) = self . claimable_outpoints . get ( * outpoint) {
478
508
// We check for outpoint spends within claims individually rather than as a set
479
509
// since requests can have outpoints split off.
480
510
if !self . onchain_events_awaiting_threshold_conf . iter ( )
481
511
. any ( |event_entry| if let OnchainEvent :: Claim { package_id } = event_entry. event {
482
- first_claim_txid_height . 0 == package_id
512
+ * request_package_id == package_id
483
513
} else {
484
514
// The onchain event is not a claim, keep seeking until we find one.
485
515
false
@@ -689,7 +719,8 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
689
719
package_id
690
720
} ,
691
721
} ;
692
- self . pending_claim_events . insert ( package_id, claim_event) ;
722
+ debug_assert_eq ! ( self . pending_claim_events. iter( ) . filter( |entry| entry. 0 == package_id) . count( ) , 0 ) ;
723
+ self . pending_claim_events . push ( ( package_id, claim_event) ) ;
693
724
package_id
694
725
} ,
695
726
} ;
@@ -724,9 +755,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
724
755
// Scan all input to verify is one of the outpoint spent is of interest for us
725
756
let mut claimed_outputs_material = Vec :: new ( ) ;
726
757
for inp in & tx. input {
727
- if let Some ( first_claim_txid_height ) = self . claimable_outpoints . get ( & inp. previous_output ) {
758
+ if let Some ( ( package_id , _ ) ) = self . claimable_outpoints . get ( & inp. previous_output ) {
728
759
// If outpoint has claim request pending on it...
729
- if let Some ( request) = self . pending_claim_requests . get_mut ( & first_claim_txid_height . 0 ) {
760
+ if let Some ( request) = self . pending_claim_requests . get_mut ( package_id ) {
730
761
//... we need to verify equality between transaction outpoints and claim request
731
762
// outpoints to know if transaction is the original claim or a bumped one issued
732
763
// by us.
@@ -746,7 +777,7 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
746
777
txid: tx. txid( ) ,
747
778
height: conf_height,
748
779
block_hash: Some ( conf_hash) ,
749
- event: OnchainEvent :: Claim { package_id: first_claim_txid_height . 0 }
780
+ event: OnchainEvent :: Claim { package_id: * package_id }
750
781
} ;
751
782
if !self . onchain_events_awaiting_threshold_conf. contains( & entry) {
752
783
self . onchain_events_awaiting_threshold_conf. push( entry) ;
@@ -773,7 +804,21 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
773
804
}
774
805
//TODO: recompute soonest_timelock to avoid wasting a bit on fees
775
806
if at_least_one_drop {
776
- bump_candidates. insert ( first_claim_txid_height. 0 . clone ( ) , request. clone ( ) ) ;
807
+ bump_candidates. insert ( * package_id, request. clone ( ) ) ;
808
+ // If we have any pending claim events for the request being updated
809
+ // that have yet to be consumed, we'll remove them since they will
810
+ // end up producing an invalid transaction by double spending
811
+ // input(s) that already have a confirmed spend. If such spend is
812
+ // reorged out of the chain, then we'll attempt to re-spend the
813
+ // inputs once we see it.
814
+ #[ cfg( anchors) ] {
815
+ #[ cfg( debug_assertions) ] {
816
+ let existing = self . pending_claim_events . iter ( )
817
+ . filter ( |entry| entry. 0 == * package_id) . count ( ) ;
818
+ assert ! ( existing == 0 || existing == 1 ) ;
819
+ }
820
+ self . pending_claim_events . retain ( |entry| entry. 0 != * package_id) ;
821
+ }
777
822
}
778
823
}
779
824
break ; //No need to iterate further, either tx is our or their
@@ -809,8 +854,14 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
809
854
log_debug ! ( logger, "Removing claim tracking for {} due to maturation of claim package {}." ,
810
855
outpoint, log_bytes!( package_id) ) ;
811
856
self . claimable_outpoints . remove ( outpoint) ;
812
- #[ cfg( anchors) ]
813
- self . pending_claim_events . remove ( & package_id) ;
857
+ }
858
+ #[ cfg( anchors) ] {
859
+ #[ cfg( debug_assertions) ] {
860
+ let num_existing = self . pending_claim_events . iter ( )
861
+ . filter ( |entry| entry. 0 == package_id) . count ( ) ;
862
+ assert ! ( num_existing == 0 || num_existing == 1 ) ;
863
+ }
864
+ self . pending_claim_events . retain ( |( id, _) | * id != package_id) ;
814
865
}
815
866
}
816
867
} ,
@@ -826,17 +877,17 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
826
877
}
827
878
828
879
// Check if any pending claim request must be rescheduled
829
- for ( first_claim_txid , ref request) in self . pending_claim_requests . iter ( ) {
880
+ for ( package_id , request) in self . pending_claim_requests . iter ( ) {
830
881
if let Some ( h) = request. timer ( ) {
831
882
if cur_height >= h {
832
- bump_candidates. insert ( * first_claim_txid , ( * request) . clone ( ) ) ;
883
+ bump_candidates. insert ( * package_id , request. clone ( ) ) ;
833
884
}
834
885
}
835
886
}
836
887
837
888
// Build, bump and rebroadcast tx accordingly
838
889
log_trace ! ( logger, "Bumping {} candidates" , bump_candidates. len( ) ) ;
839
- for ( first_claim_txid , request) in bump_candidates. iter ( ) {
890
+ for ( package_id , request) in bump_candidates. iter ( ) {
840
891
if let Some ( ( new_timer, new_feerate, bump_claim) ) = self . generate_claim ( cur_height, & request, & * fee_estimator, & * logger) {
841
892
match bump_claim {
842
893
OnchainClaim :: Tx ( bump_tx) => {
@@ -846,10 +897,16 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
846
897
#[ cfg( anchors) ]
847
898
OnchainClaim :: Event ( claim_event) => {
848
899
log_info ! ( logger, "Yielding RBF-bumped onchain event to spend inputs {:?}" , request. outpoints( ) ) ;
849
- self . pending_claim_events . insert ( * first_claim_txid, claim_event) ;
900
+ #[ cfg( debug_assertions) ] {
901
+ let num_existing = self . pending_claim_events . iter ( ) .
902
+ filter ( |entry| entry. 0 == * package_id) . count ( ) ;
903
+ assert ! ( num_existing == 0 || num_existing == 1 ) ;
904
+ }
905
+ self . pending_claim_events . retain ( |event| event. 0 != * package_id) ;
906
+ self . pending_claim_events . push ( ( * package_id, claim_event) ) ;
850
907
} ,
851
908
}
852
- if let Some ( request) = self . pending_claim_requests . get_mut ( first_claim_txid ) {
909
+ if let Some ( request) = self . pending_claim_requests . get_mut ( package_id ) {
853
910
request. set_timer ( new_timer) ;
854
911
request. set_feerate ( new_feerate) ;
855
912
}
@@ -895,12 +952,12 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
895
952
//- resurect outpoint back in its claimable set and regenerate tx
896
953
match entry. event {
897
954
OnchainEvent :: ContentiousOutpoint { package } => {
898
- if let Some ( ancestor_claimable_txid ) = self . claimable_outpoints . get ( package. outpoints ( ) [ 0 ] ) {
899
- if let Some ( request) = self . pending_claim_requests . get_mut ( & ancestor_claimable_txid . 0 ) {
955
+ if let Some ( pending_claim ) = self . claimable_outpoints . get ( package. outpoints ( ) [ 0 ] ) {
956
+ if let Some ( request) = self . pending_claim_requests . get_mut ( & pending_claim . 0 ) {
900
957
request. merge_package ( package) ;
901
958
// Using a HashMap guarantee us than if we have multiple outpoints getting
902
959
// resurrected only one bump claim tx is going to be broadcast
903
- bump_candidates. insert ( ancestor_claimable_txid . clone ( ) , request. clone ( ) ) ;
960
+ bump_candidates. insert ( pending_claim . clone ( ) , request. clone ( ) ) ;
904
961
}
905
962
}
906
963
} ,
@@ -910,7 +967,7 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
910
967
self . onchain_events_awaiting_threshold_conf . push ( entry) ;
911
968
}
912
969
}
913
- for ( _first_claim_txid_height , request) in bump_candidates. iter_mut ( ) {
970
+ for ( ( _package_id , _ ) , ref mut request) in bump_candidates. iter_mut ( ) {
914
971
if let Some ( ( new_timer, new_feerate, bump_claim) ) = self . generate_claim ( height, & request, fee_estimator, & & * logger) {
915
972
request. set_timer ( new_timer) ;
916
973
request. set_feerate ( new_feerate) ;
@@ -922,7 +979,13 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
922
979
#[ cfg( anchors) ]
923
980
OnchainClaim :: Event ( claim_event) => {
924
981
log_info ! ( logger, "Yielding onchain event after reorg to spend inputs {:?}" , request. outpoints( ) ) ;
925
- self . pending_claim_events . insert ( _first_claim_txid_height. 0 , claim_event) ;
982
+ #[ cfg( debug_assertions) ] {
983
+ let num_existing = self . pending_claim_events . iter ( )
984
+ . filter ( |entry| entry. 0 == * _package_id) . count ( ) ;
985
+ assert ! ( num_existing == 0 || num_existing == 1 ) ;
986
+ }
987
+ self . pending_claim_events . retain ( |event| event. 0 != * _package_id) ;
988
+ self . pending_claim_events . push ( ( * _package_id, claim_event) ) ;
926
989
} ,
927
990
}
928
991
}
0 commit comments