Skip to content

Commit efdcd2e

Browse files
committed
Maintain order of yielded claim events
Since the claim events are stored internally within a HashMap, they will be yielded in a random order once dispatched. Claim events may be invalidated if a conflicting claim has confirmed on-chain and we need to generate a new claim event; the randomized order could result in the new claim event being handled prior to the previous. To maintain the order in which the claim events are generated, we track them in a Vec instead and ensure only one instance of a PackageId only ever exists within it. This would have certain performance implications, but since we're bounded by the total number of HTLCs in a commitment anyway, we're comfortable with taking the cost.
1 parent 6c4e982 commit efdcd2e

File tree

1 file changed

+42
-13
lines changed

1 file changed

+42
-13
lines changed

lightning/src/chain/onchaintx.rs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,19 @@ pub struct OnchainTxHandler<ChannelSigner: WriteableEcdsaChannelSigner> {
248248
pub(crate) pending_claim_requests: HashMap<PackageID, PackageTemplate>,
249249
#[cfg(not(test))]
250250
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
251262
#[cfg(anchors)]
252-
pending_claim_events: HashMap<PackageID, ClaimEvent>,
263+
pending_claim_events: Vec<(PackageID, ClaimEvent)>,
253264

254265
// Used to link outpoints claimed in a connected block to a pending claim request. The keys
255266
// represent the outpoints that our `ChannelMonitor` has detected we have keys/scripts to
@@ -426,7 +437,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
426437
pending_claim_requests,
427438
onchain_events_awaiting_threshold_conf,
428439
#[cfg(anchors)]
429-
pending_claim_events: HashMap::new(),
440+
pending_claim_events: Vec::new(),
430441
secp_ctx,
431442
})
432443
}
@@ -447,8 +458,7 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
447458
locktimed_packages: BTreeMap::new(),
448459
onchain_events_awaiting_threshold_conf: Vec::new(),
449460
#[cfg(anchors)]
450-
pending_claim_events: HashMap::new(),
451-
461+
pending_claim_events: Vec::new(),
452462
secp_ctx,
453463
}
454464
}
@@ -463,9 +473,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
463473

464474
#[cfg(anchors)]
465475
pub(crate) fn get_and_clear_pending_claim_events(&mut self) -> Vec<ClaimEvent> {
466-
let mut ret = HashMap::new();
467-
swap(&mut ret, &mut self.pending_claim_events);
468-
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()
469479
}
470480

471481
/// Lightning security model (i.e being able to redeem/timeout HTLC or penalize counterparty
@@ -709,7 +719,8 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
709719
package_id
710720
},
711721
};
712-
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));
713724
package_id
714725
},
715726
};
@@ -794,6 +805,14 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
794805
//TODO: recompute soonest_timelock to avoid wasting a bit on fees
795806
if at_least_one_drop {
796807
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+
self.pending_claim_events.retain(|entry| entry.0 != *package_id);
797816
}
798817
}
799818
break; //No need to iterate further, either tx is our or their
@@ -829,9 +848,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
829848
log_debug!(logger, "Removing claim tracking for {} due to maturation of claim package {}.",
830849
outpoint, log_bytes!(package_id));
831850
self.claimable_outpoints.remove(outpoint);
832-
#[cfg(anchors)]
833-
self.pending_claim_events.remove(&package_id);
834851
}
852+
#[cfg(anchors)]
853+
self.pending_claim_events.retain(|(id, _)| *id != package_id);
835854
}
836855
},
837856
OnchainEvent::ContentiousOutpoint { package } => {
@@ -866,7 +885,12 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
866885
#[cfg(anchors)]
867886
OnchainClaim::Event(claim_event) => {
868887
log_info!(logger, "Yielding RBF-bumped onchain event to spend inputs {:?}", request.outpoints());
869-
self.pending_claim_events.insert(*package_id, claim_event);
888+
if let Some((existing_claim_idx, _)) = self.pending_claim_events.iter().enumerate()
889+
.find(|(_, entry)| entry.0 == *package_id)
890+
{
891+
self.pending_claim_events.remove(existing_claim_idx);
892+
}
893+
self.pending_claim_events.push((*package_id, claim_event));
870894
},
871895
}
872896
if let Some(request) = self.pending_claim_requests.get_mut(package_id) {
@@ -930,7 +954,7 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
930954
self.onchain_events_awaiting_threshold_conf.push(entry);
931955
}
932956
}
933-
for ((_package_id, _), request) in bump_candidates.iter_mut() {
957+
for ((_package_id, _), ref mut request) in bump_candidates.iter_mut() {
934958
if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(height, &request, fee_estimator, &&*logger) {
935959
request.set_timer(new_timer);
936960
request.set_feerate(new_feerate);
@@ -942,7 +966,12 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
942966
#[cfg(anchors)]
943967
OnchainClaim::Event(claim_event) => {
944968
log_info!(logger, "Yielding onchain event after reorg to spend inputs {:?}", request.outpoints());
945-
self.pending_claim_events.insert(_package_id, claim_event);
969+
if let Some((existing_claim_idx, _)) = self.pending_claim_events.iter().enumerate()
970+
.find(|(_, entry)| entry.0 == *_package_id)
971+
{
972+
self.pending_claim_events.remove(existing_claim_idx);
973+
}
974+
self.pending_claim_events.push((*_package_id, claim_event));
946975
},
947976
}
948977
}

0 commit comments

Comments
 (0)