Skip to content

Commit bb4904f

Browse files
committed
Update EventHandler to create inbound PaymentStore entries on demand
With receive-side pre-creation removed, the event handler must now create `PaymentStore` entries when it first encounters an inbound payment. In the `PaymentClaimable` handler, when a `Bolt11InvoicePayment` is not found in the store: - Manual-claim path (`preimage == None`): check the metadata store for `LSPFeeLimits`, validate counterparty-skimmed fees, create a `Bolt11Jit` or `Bolt11` entry, and emit `PaymentClaimable`. - Auto-claim path (`preimage == Some`): same fee-limit check and entry creation, then fall through to `claim_funds`. In the `PaymentClaimed` handler, when the update returns `NotFound`, insert a new entry using the payment purpose to determine the kind. Outbound payment handlers (`PaymentSent`, `PaymentFailed`) are unchanged since entries are still pre-created by `send()` / `send_using_amount()`. Generated with the assistance of AI tools. Co-Authored-By: HAL 9000
1 parent 8947331 commit bb4904f

File tree

2 files changed

+273
-33
lines changed

2 files changed

+273
-33
lines changed

src/event.rs

Lines changed: 264 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,184 @@ where
821821
amount_msat,
822822
);
823823
let payment_preimage = match purpose {
824-
PaymentPurpose::Bolt11InvoicePayment { payment_preimage, .. } => {
824+
PaymentPurpose::Bolt11InvoicePayment {
825+
payment_preimage,
826+
payment_secret,
827+
..
828+
} => {
829+
if payment_preimage.is_none() {
830+
// This is a manual-claim (`_for_hash`) payment that was not
831+
// pre-registered in the payment store. Check metadata store for
832+
// LSP fee limits and create the store entry.
833+
let lsp_fee_limits = self
834+
.payment_metadata_store
835+
.get_lsp_fee_limits_for_payment_id(&payment_id);
836+
837+
if let Some(ref limits) = lsp_fee_limits {
838+
let max_total_opening_fee_msat = limits
839+
.max_total_opening_fee_msat
840+
.or_else(|| {
841+
limits.max_proportional_opening_fee_ppm_msat.and_then(
842+
|max_prop_fee| {
843+
compute_opening_fee(amount_msat, 0, max_prop_fee)
844+
},
845+
)
846+
})
847+
.unwrap_or(0);
848+
849+
if counterparty_skimmed_fee_msat > max_total_opening_fee_msat {
850+
log_info!(
851+
self.logger,
852+
"Refusing inbound payment with hash {} as the counterparty-withheld fee of {}msat exceeds our limit of {}msat",
853+
hex_utils::to_string(&payment_hash.0),
854+
counterparty_skimmed_fee_msat,
855+
max_total_opening_fee_msat,
856+
);
857+
self.channel_manager.fail_htlc_backwards(&payment_hash);
858+
return Ok(());
859+
}
860+
}
861+
862+
let kind = if lsp_fee_limits.is_some() {
863+
PaymentKind::Bolt11Jit {
864+
hash: payment_hash,
865+
preimage: None,
866+
secret: Some(payment_secret),
867+
counterparty_skimmed_fee_msat: if counterparty_skimmed_fee_msat
868+
> 0
869+
{
870+
Some(counterparty_skimmed_fee_msat)
871+
} else {
872+
None
873+
},
874+
lsp_fee_limits: lsp_fee_limits.unwrap(),
875+
}
876+
} else {
877+
PaymentKind::Bolt11 {
878+
hash: payment_hash,
879+
preimage: None,
880+
secret: Some(payment_secret),
881+
}
882+
};
883+
884+
let payment = PaymentDetails::new(
885+
payment_id,
886+
kind,
887+
Some(amount_msat),
888+
None,
889+
PaymentDirection::Inbound,
890+
PaymentStatus::Pending,
891+
);
892+
893+
match self.payment_store.insert(payment) {
894+
Ok(_) => {},
895+
Err(e) => {
896+
log_error!(
897+
self.logger,
898+
"Failed to insert payment with ID {}: {}",
899+
payment_id,
900+
e
901+
);
902+
return Err(ReplayEvent());
903+
},
904+
}
905+
906+
let custom_records = onion_fields
907+
.map(|cf| {
908+
cf.custom_tlvs().into_iter().map(|tlv| tlv.into()).collect()
909+
})
910+
.unwrap_or_default();
911+
let event = Event::PaymentClaimable {
912+
payment_id,
913+
payment_hash,
914+
claimable_amount_msat: amount_msat,
915+
claim_deadline,
916+
custom_records,
917+
};
918+
match self.event_queue.add_event(event).await {
919+
Ok(_) => return Ok(()),
920+
Err(e) => {
921+
log_error!(self.logger, "Failed to push to event queue: {}", e);
922+
return Err(ReplayEvent());
923+
},
924+
};
925+
} else {
926+
// Auto-claim path: payment has a preimage but was not
927+
// pre-registered in the store. Check metadata store for
928+
// LSP fee limits and create the store entry before claiming.
929+
let lsp_fee_limits = self
930+
.payment_metadata_store
931+
.get_lsp_fee_limits_for_payment_id(&payment_id);
932+
933+
if let Some(ref limits) = lsp_fee_limits {
934+
let max_total_opening_fee_msat = limits
935+
.max_total_opening_fee_msat
936+
.or_else(|| {
937+
limits.max_proportional_opening_fee_ppm_msat.and_then(
938+
|max_prop_fee| {
939+
compute_opening_fee(amount_msat, 0, max_prop_fee)
940+
},
941+
)
942+
})
943+
.unwrap_or(0);
944+
945+
if counterparty_skimmed_fee_msat > max_total_opening_fee_msat {
946+
log_info!(
947+
self.logger,
948+
"Refusing inbound payment with hash {} as the counterparty-withheld fee of {}msat exceeds our limit of {}msat",
949+
hex_utils::to_string(&payment_hash.0),
950+
counterparty_skimmed_fee_msat,
951+
max_total_opening_fee_msat,
952+
);
953+
self.channel_manager.fail_htlc_backwards(&payment_hash);
954+
return Ok(());
955+
}
956+
}
957+
958+
let kind = if lsp_fee_limits.is_some() {
959+
PaymentKind::Bolt11Jit {
960+
hash: payment_hash,
961+
preimage: payment_preimage,
962+
secret: Some(payment_secret),
963+
counterparty_skimmed_fee_msat: if counterparty_skimmed_fee_msat
964+
> 0
965+
{
966+
Some(counterparty_skimmed_fee_msat)
967+
} else {
968+
None
969+
},
970+
lsp_fee_limits: lsp_fee_limits.unwrap(),
971+
}
972+
} else {
973+
PaymentKind::Bolt11 {
974+
hash: payment_hash,
975+
preimage: payment_preimage,
976+
secret: Some(payment_secret),
977+
}
978+
};
979+
980+
let payment = PaymentDetails::new(
981+
payment_id,
982+
kind,
983+
Some(amount_msat),
984+
None,
985+
PaymentDirection::Inbound,
986+
PaymentStatus::Pending,
987+
);
988+
989+
match self.payment_store.insert(payment) {
990+
Ok(_) => {},
991+
Err(e) => {
992+
log_error!(
993+
self.logger,
994+
"Failed to insert payment with ID {}: {}",
995+
payment_id,
996+
e
997+
);
998+
return Err(ReplayEvent());
999+
},
1000+
}
1001+
}
8251002
payment_preimage
8261003
},
8271004
PaymentPurpose::Bolt12OfferPayment {
@@ -960,43 +1137,85 @@ where
9601137
amount_msat,
9611138
);
9621139

963-
let update = match purpose {
1140+
let (update, kind_for_insert) = match purpose {
9641141
PaymentPurpose::Bolt11InvoicePayment {
9651142
payment_preimage,
9661143
payment_secret,
9671144
..
968-
} => PaymentDetailsUpdate {
969-
preimage: Some(payment_preimage),
970-
secret: Some(Some(payment_secret)),
971-
amount_msat: Some(Some(amount_msat)),
972-
status: Some(PaymentStatus::Succeeded),
973-
..PaymentDetailsUpdate::new(payment_id)
1145+
} => {
1146+
let kind = PaymentKind::Bolt11 {
1147+
hash: payment_hash,
1148+
preimage: payment_preimage,
1149+
secret: Some(payment_secret.clone()),
1150+
};
1151+
let update = PaymentDetailsUpdate {
1152+
preimage: Some(payment_preimage),
1153+
secret: Some(Some(payment_secret)),
1154+
amount_msat: Some(Some(amount_msat)),
1155+
status: Some(PaymentStatus::Succeeded),
1156+
..PaymentDetailsUpdate::new(payment_id)
1157+
};
1158+
(update, kind)
9741159
},
9751160
PaymentPurpose::Bolt12OfferPayment {
976-
payment_preimage, payment_secret, ..
977-
} => PaymentDetailsUpdate {
978-
preimage: Some(payment_preimage),
979-
secret: Some(Some(payment_secret)),
980-
amount_msat: Some(Some(amount_msat)),
981-
status: Some(PaymentStatus::Succeeded),
982-
..PaymentDetailsUpdate::new(payment_id)
1161+
payment_preimage,
1162+
payment_secret,
1163+
payment_context,
1164+
..
1165+
} => {
1166+
let payer_note = payment_context.invoice_request.payer_note_truncated;
1167+
let offer_id = payment_context.offer_id;
1168+
let quantity = payment_context.invoice_request.quantity;
1169+
let kind = PaymentKind::Bolt12Offer {
1170+
hash: Some(payment_hash),
1171+
preimage: payment_preimage,
1172+
secret: Some(payment_secret.clone()),
1173+
offer_id,
1174+
payer_note,
1175+
quantity,
1176+
};
1177+
let update = PaymentDetailsUpdate {
1178+
preimage: Some(payment_preimage),
1179+
secret: Some(Some(payment_secret)),
1180+
amount_msat: Some(Some(amount_msat)),
1181+
status: Some(PaymentStatus::Succeeded),
1182+
..PaymentDetailsUpdate::new(payment_id)
1183+
};
1184+
(update, kind)
9831185
},
9841186
PaymentPurpose::Bolt12RefundPayment {
9851187
payment_preimage,
9861188
payment_secret,
9871189
..
988-
} => PaymentDetailsUpdate {
989-
preimage: Some(payment_preimage),
990-
secret: Some(Some(payment_secret)),
991-
amount_msat: Some(Some(amount_msat)),
992-
status: Some(PaymentStatus::Succeeded),
993-
..PaymentDetailsUpdate::new(payment_id)
1190+
} => {
1191+
let kind = PaymentKind::Bolt12Refund {
1192+
hash: Some(payment_hash),
1193+
preimage: payment_preimage,
1194+
secret: Some(payment_secret.clone()),
1195+
payer_note: None,
1196+
quantity: None,
1197+
};
1198+
let update = PaymentDetailsUpdate {
1199+
preimage: Some(payment_preimage),
1200+
secret: Some(Some(payment_secret)),
1201+
amount_msat: Some(Some(amount_msat)),
1202+
status: Some(PaymentStatus::Succeeded),
1203+
..PaymentDetailsUpdate::new(payment_id)
1204+
};
1205+
(update, kind)
9941206
},
995-
PaymentPurpose::SpontaneousPayment(preimage) => PaymentDetailsUpdate {
996-
preimage: Some(Some(preimage)),
997-
amount_msat: Some(Some(amount_msat)),
998-
status: Some(PaymentStatus::Succeeded),
999-
..PaymentDetailsUpdate::new(payment_id)
1207+
PaymentPurpose::SpontaneousPayment(preimage) => {
1208+
let kind = PaymentKind::Spontaneous {
1209+
hash: payment_hash,
1210+
preimage: Some(preimage),
1211+
};
1212+
let update = PaymentDetailsUpdate {
1213+
preimage: Some(Some(preimage)),
1214+
amount_msat: Some(Some(amount_msat)),
1215+
status: Some(PaymentStatus::Succeeded),
1216+
..PaymentDetailsUpdate::new(payment_id)
1217+
};
1218+
(update, kind)
10001219
},
10011220
};
10021221

@@ -1006,11 +1225,27 @@ where
10061225
// be the result of a replayed event.
10071226
),
10081227
Ok(DataStoreUpdateResult::NotFound) => {
1009-
log_error!(
1010-
self.logger,
1011-
"Claimed payment with ID {} couldn't be found in store",
1228+
// Payment was auto-claimed without a prior store entry.
1229+
let payment = PaymentDetails::new(
10121230
payment_id,
1231+
kind_for_insert,
1232+
Some(amount_msat),
1233+
None,
1234+
PaymentDirection::Inbound,
1235+
PaymentStatus::Succeeded,
10131236
);
1237+
match self.payment_store.insert(payment) {
1238+
Ok(_) => (),
1239+
Err(e) => {
1240+
log_error!(
1241+
self.logger,
1242+
"Failed to insert payment with ID {}: {}",
1243+
payment_id,
1244+
e
1245+
);
1246+
return Err(ReplayEvent());
1247+
},
1248+
}
10141249
},
10151250
Err(e) => {
10161251
log_error!(

tests/common/mod.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -905,13 +905,15 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
905905
});
906906
assert_eq!(outbound_payments_b.len(), 0);
907907

908+
// Wait for events before checking inbound payments, as they are now created on demand
909+
// by the event handler.
910+
expect_event!(node_a, PaymentSuccessful);
911+
expect_event!(node_b, PaymentReceived);
912+
908913
let inbound_payments_b = node_b.list_payments_with_filter(|p| {
909914
p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. })
910915
});
911916
assert_eq!(inbound_payments_b.len(), 1);
912-
913-
expect_event!(node_a, PaymentSuccessful);
914-
expect_event!(node_b, PaymentReceived);
915917
assert_eq!(node_a.payment(&payment_id).unwrap().status, PaymentStatus::Succeeded);
916918
assert_eq!(node_a.payment(&payment_id).unwrap().direction, PaymentDirection::Outbound);
917919
assert_eq!(node_a.payment(&payment_id).unwrap().amount_msat, Some(invoice_amount_1_msat));
@@ -1145,9 +1147,12 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
11451147
node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(),
11461148
5
11471149
);
1150+
// Note: node_b has 5 (not 6) Bolt11 payments because the receive() invoice used only for the
1151+
// underpaid attempt (which fails with InvalidAmount) no longer creates a store entry. Only
1152+
// invoices that result in actual payment events are tracked.
11481153
assert_eq!(
11491154
node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(),
1150-
6
1155+
5
11511156
);
11521157
assert_eq!(
11531158
node_a

0 commit comments

Comments
 (0)