-
Notifications
You must be signed in to change notification settings - Fork 0
Review Branch: Dummy Hop support for Blinded Payment Path #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds configurable dummy-hop support for blinded payments: new Dummy TLV and Hop variants, BlindedPaymentPath::new_with_dummy_hops, TLV (de)serialization and onion payload decoding/peeling updates, forward-info propagation through onion forwarding, router default for dummy hops, and tests/tooling adjusted. Changes
Sequence Diagram(s)sequenceDiagram
participant Sender
participant Intro as Introduction Node
participant Dummy as Dummy Hop(s)
participant Final as Payee
Sender->>Intro: Send blinded HTLC (onion)
Intro->>Intro: Decrypt intro TLV -> BlindedPaymentTlvs::Dummy / ::Forward / ::Receive
alt Dummy path
Intro->>Dummy: Peel Dummy TLV, reconstruct onion packet, re-enqueue
loop Peel dummy hops
Dummy->>Dummy: Peel one dummy layer, update blinding, create new UpdateAddHTLC
end
Dummy->>Final: Final hop -> Receive TLV
else Direct forward
Intro->>Final: Forward -> Receive TLV
end
Final->>Final: Decrypt Receive TLV -> extract pay info
Final->>Sender: Fulfill / return preimage
sequenceDiagram
participant Router
participant Builder as BlindedPaymentPath
participant Auth as ReceiveAuthKey
Router->>Builder: new_with_dummy_hops(intermediates, payee, dummy_count, auth, payee_tlvs, ...)
Builder->>Builder: clamp dummy_count to MAX_DUMMY_HOPS_COUNT
Builder->>Builder: insert Dummy TLVs for intermediate hops and pad path
Builder->>Router: return constructed blinded path
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (9)
lightning/src/ln/onion_utils.rs (2)
1683-1690: Additions to LocalHTLCFailureReason are soundNew UnauthenticatedPayload variant and docs look good. Consider explicitly mentioning ReceiveAuthKey-based auth in the doc for clarity.
1731-1731: Wire code mapping aligns with intentMapping UnauthenticatedPayload to the same BADONION | PERM | 24 as InvalidOnionBlinding is correct. Note: From will decode to InvalidOnionBlinding for this code; if intentional, add a brief comment to prevent future confusion.
lightning/src/ln/onion_payment.rs (1)
248-267: New auth gate for blinded receives; verify external error mappingGood addition: early reject unauthenticated receive payloads. Two follow-ups:
- Verify how LocalHTLCFailureReason::UnauthenticatedPayload is surfaced on the wire. If you intend to avoid leaking auth status, consider mapping it to InvalidOnionPayload at the boundary.
- Optional: downgrade log_warn to log_info/log_debug to reduce noise from unauthenticated probes.
lightning/src/ln/msgs.rs (1)
3773-3773: Minor: scope of receive_auth_key.Defined once per decode; OK. If you want to avoid potential “unused” warnings in future refactors, define it inside the blinded-branch.
lightning/src/ln/channelmanager.rs (1)
18746-18750: Avoid redundant shadowing of current_height.If current_height was (or will be) declared earlier as suggested, remove this duplicate let to reduce shadowing.
Apply if earlier declaration exists:
- let current_height: u32 = node[0].node.best_block.read().unwrap().height;lightning/src/blinded_path/payment.rs (2)
697-699: Avoid referencing a temporary&PaymentDummyTlvin the iteratorUsing
&PaymentDummyTlvinsidemaptakes a reference to a temporary. While ZSTs are often const-promoted, it’s clearer and safer to bind once and reuse.Apply:
- let tlvs = intermediate_nodes - .iter() - .map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs)) - .chain((0..dummy_count).map(|_| BlindedPaymentTlvsRef::Dummy(&PaymentDummyTlv))) - .chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs))); + let dummy_tlv = PaymentDummyTlv; + let dummy_iter = core::iter::repeat(BlindedPaymentTlvsRef::Dummy(&dummy_tlv)) + .take(dummy_count); + let tlvs = intermediate_nodes + .iter() + .map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs)) + .chain(dummy_iter) + .chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs)));Also applies to: 683-705
679-681: Align payment padding round-off with message padding (if intended)
PAYMENT_PADDING_ROUND_OFF = 30differs from message padding. Consider sharing a common constant or documenting why they differ for onion size uniformity.lightning/src/offers/flow.rs (1)
320-356: Routing now threadsReceiveAuthKeycorrectly; consider HTLC min source
- Passing
receive_auth_keyintoRouter::create_blinded_payment_pathsis correct.- Consider deriving
htlc_minimum_msatfrom channel mins or policy instead of hardcoding1.Also applies to: 342-346
fuzz/src/invoice_request_deser.rs (1)
86-87: Derive ReceiveAuthKey instead of using a constant to improve fuzz coverageUse entropy to vary the key so more paths are explored.
- let receive_auth_key = ReceiveAuthKey([41; 32]); + let receive_auth_key = ReceiveAuthKey(entropy_source.get_secure_random_bytes());
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
fuzz/src/chanmon_consistency.rs(1 hunks)fuzz/src/full_stack.rs(1 hunks)fuzz/src/invoice_request_deser.rs(5 hunks)fuzz/src/refund_deser.rs(4 hunks)lightning/src/blinded_path/payment.rs(21 hunks)lightning/src/ln/async_payments_tests.rs(1 hunks)lightning/src/ln/blinded_payment_tests.rs(12 hunks)lightning/src/ln/channelmanager.rs(10 hunks)lightning/src/ln/max_payment_path_len_tests.rs(2 hunks)lightning/src/ln/msgs.rs(13 hunks)lightning/src/ln/onion_payment.rs(5 hunks)lightning/src/ln/onion_utils.rs(4 hunks)lightning/src/offers/flow.rs(10 hunks)lightning/src/offers/signer.rs(1 hunks)lightning/src/routing/router.rs(7 hunks)lightning/src/util/test_utils.rs(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
lightning/src/ln/onion_payment.rs (2)
lightning/src/ln/onion_utils.rs (7)
shared_secret(2283-2294)new(783-783)new(2168-2168)new(2172-2174)new(2178-2180)new(2735-2737)new(3953-3955)lightning/src/ln/channel.rs (6)
new(1091-1093)new(1205-1214)new(12825-12876)new(13190-13241)None(11074-11074)None(14118-14118)
lightning/src/ln/max_payment_path_len_tests.rs (2)
lightning/src/blinded_path/payment.rs (1)
new(120-139)lightning/src/offers/flow.rs (1)
new(118-149)
lightning/src/ln/blinded_payment_tests.rs (4)
lightning/src/blinded_path/payment.rs (1)
new(120-139)lightning/src/offers/flow.rs (1)
get_receive_auth_key(192-194)lightning/src/util/test_utils.rs (2)
get_receive_auth_key(1730-1732)get_receive_auth_key(1822-1824)lightning/src/sign/mod.rs (3)
get_receive_auth_key(880-880)get_receive_auth_key(2220-2222)get_receive_auth_key(2393-2395)
lightning/src/ln/msgs.rs (2)
lightning/src/blinded_path/payment.rs (1)
new(120-139)lightning/src/ln/channelmanager.rs (2)
new(3847-3925)new(16114-16135)
lightning/src/routing/router.rs (4)
lightning/src/offers/flow.rs (1)
create_blinded_payment_paths(322-356)fuzz/src/chanmon_consistency.rs (1)
create_blinded_payment_paths(130-136)fuzz/src/full_stack.rs (1)
create_blinded_payment_paths(158-164)lightning/src/util/test_utils.rs (1)
create_blinded_payment_paths(290-308)
lightning/src/blinded_path/payment.rs (2)
lightning/src/blinded_path/message.rs (4)
new_with_dummy_hops(91-119)new(68-85)blinded_hops(171-173)blinded_hops(693-733)lightning/src/ln/msgs.rs (8)
new(2510-2512)new(2545-2547)read(1147-1171)read(1175-1181)read(3345-3356)read(3405-3409)read(3592-3763)read(3770-3912)
lightning/src/offers/flow.rs (3)
lightning/src/routing/router.rs (4)
create_blinded_payment_paths(129-222)create_blinded_payment_paths(247-255)create_blinded_payment_paths(288-292)a(3619-3619)lightning/src/util/test_utils.rs (1)
create_blinded_payment_paths(290-308)lightning/src/ln/channelmanager.rs (2)
usable_channels(5743-5743)test_create_blinded_payment_paths(12930-12942)
lightning/src/util/test_utils.rs (1)
lightning/src/routing/router.rs (14)
first_hops(156-207)first_hops(7886-7886)first_hops(7910-7910)first_hops(8313-8313)first_hops(8323-8323)first_hops(8382-8382)first_hops(8542-8542)first_hops(8622-8622)first_hops(8687-8687)first_hops(8741-8741)first_hops(8861-8861)first_hops(8877-8877)first_hops(8889-8889)first_hops(9033-9033)
🪛 GitHub Actions: Continuous Integration Checks
lightning/src/ln/msgs.rs
[error] 2351-2351: field payment_tlvs_authenticated is never read
[error] 2351-2351: could not compile lightning (lib) due to 1 previous error
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: benchmark
- GitHub Check: semver-checks
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: build-tx-sync (ubuntu-latest, 1.75.0)
- GitHub Check: check_release
- GitHub Check: build-tx-sync (macos-latest, beta)
- GitHub Check: build-tx-sync (ubuntu-latest, 1.75.0)
- GitHub Check: build-tx-sync (macos-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: build (macos-latest, stable)
- GitHub Check: benchmark
- GitHub Check: build (windows-latest, stable)
- GitHub Check: build (macos-latest, 1.63.0)
- GitHub Check: semver-checks
- GitHub Check: ext-test
🔇 Additional comments (41)
fuzz/src/full_stack.rs (1)
159-161: Router signature update looks correctFuzzRouter now matches the trait’s new receive_auth_key parameter; unused params are fine here. Nothing else to change.
Please confirm all other Router impls and call sites were updated consistently (build should catch mismatches).
lightning/src/ln/onion_utils.rs (2)
1891-1892: Serialization IDs appended safelyAdding (42, ChannelBalanceOverdrawn) and (43, UnauthenticatedPayload) at the end maintains backward compatibility of the TLV enum. Looks good.
Double‑check no prior release used these indices for different variants.
2053-2054: Debug assertions updated appropriatelyExtending the data-length assertion to UnauthenticatedPayload (32 bytes) matches InvalidOnionBlinding behavior. LGTM.
fuzz/src/refund_deser.rs (1)
71-81: LGTM: fuzz path updated to new auth modelSwitched to ReceiveTlvs and passing ReceiveAuthKey into BlindedPaymentPath::new correctly. No issues.
Also applies to: 99-109
lightning/src/ln/max_payment_path_len_tests.rs (1)
215-235: LGTM: blinded path construction updatedReceiveTlvs and receive_auth_key are correctly threaded into BlindedPaymentPath::new. Path-length/packet-size assertions remain valid.
lightning/src/ln/onion_payment.rs (1)
460-504: Logger deref threading is consistentPassing logger.deref() into decode/create helpers matches the generic L: Deref<Target=Logger> pattern and avoids cloning contexts. Looks good.
lightning/src/ln/msgs.rs (8)
59-59: ChaChaDualPolyReadAdapter import looks correct.Adapter change aligns with adding AAD-based auth; no issues spotted.
3661-3666: Correct: thread receive_auth_key into decrypt; derive rho from ECDH.The switch to ChaChaDualPolyReadAdapter with (rho, receive_auth_key) is sound.
Ensure node_signer.get_receive_auth_key() is stable and non-blocking; if it can fail or rotate, confirm caller expectations.
3676-3683: Good validation: forbid AAD on forward hops.Rejecting used_aad for Forward prevents misrouted/forged use of authenticated TLVs on non-final hops.
3694-3705: Dummy variant decode is fine; carries auth bit forward.No logical issues; field propagation is consistent.
3710-3728: Receive-path propagation looks correct.You pass used_aad into BlindedReceive payload; constraints and bounds checks remain intact.
3826-3844: Trampoline: same forward-hop AAD gating — good.Mirrors non-trampoline path and rejects unexpected authenticated TLVs.
3859-3877: Trampoline receive-path propagation — consistent.Auth flag plumbed through; looks good.
2342-2343: Revert dead_code allowance on payment_tlvs_authenticated
The flag is read in msgs.rs (decode paths) and in onion_payment.rs; no dead_code lint will fire. Remove the proposed#[cfg_attr(not(fuzzing), allow(dead_code))]annotations.Likely an incorrect or invalid review comment.
lightning/src/ln/channelmanager.rs (9)
35-41: Imports align with the payment-context refactor.No issues spotted.
4881-4885: Correctly propagating height and logger.Passing current_height and &*self.logger to create_recv_pending_htlc_info matches the updated signature.
6992-6996: Consistent logger propagation.Forwarding &*self.logger here is consistent with the new API.
14814-14816: Router injected into invoice-request flow looks good.This aligns with creating blinded payment paths in the flow.
17879-17880: Test utilities import is appropriate.TestLogger is used below; no concerns.
18701-18707: Test logger setup LGTM.Creation and later passing of &logger are correct.
18723-18727: Verify current_height declaration order in test.current_height is used here; ensure it’s declared before this call (and not only later at Line 18746). If it wasn’t previously declared, move the declaration above this use; if it was, avoid re-declaring it later.
Suggested adjustment if needed (insert before this call):
+ let current_height: u32 = node[0].node.best_block.read().unwrap().height;
18759-18762: Minor test setup change LGTM.Logger instantiation is fine.
18772-18775: Updated call signature handled correctly.Passing current_height and &logger matches the new API.
lightning/src/routing/router.rs (6)
31-31: Import of ReceiveAuthKey looks goodRequired for the new authenticated blinded paths API.
183-186: Use of saturating_add for max_cltv_expiry is correctPrevents u32 overflow when composing per-hop deltas into the payee constraints. Good safety improvement.
201-205: Passing receive_auth_key into BlindedPaymentPath::newThreading the auth key into blinded path construction aligns with the new authenticated flow; call ordering looks correct in both multi-hop and dummy-hop branches.
Also applies to: 213-216
248-251: FixedRouter signature updateSignature matches the trait change; unreachable/Err behavior preserved.
285-293: Trait docs updated to reflect authentication requirementDoc and signature changes are consistent with the new model.
132-134: Public API change:Router::create_blinded_payment_pathsaddsreceive_auth_key
All internalRouterimpls (DefaultRouter, test utils, fuzz harness) have been updated to include the newreceive_auth_keyparameter. Verify any external Rust or FFI bindings for compatibility. Consider accepting&ReceiveAuthKeyto avoid unnecessary copies.lightning/src/blinded_path/payment.rs (3)
549-556: Reusing TLV type 65539 for Dummy: confirm wire-compat expectationsYou encode Dummy as
(65539, ())and decode withis_dummy. Given the comment about removing prior auth TLV, confirm:
- Older peers won’t misinterpret 65539 in payment contexts.
- We never receive legacy payloads requiring the old field.
Also applies to: 558-563, 595-600
141-150: API for dummy hops looks good; dummy count is bounded
new_with_dummy_hops+ capping viaMAX_DUMMY_HOPS_COUNTis clean and prevents overflow of padding.Also applies to: 688-694
762-767:compute_payinfochange to&ReceiveTlvsis consistentUsing payee constraints from
ReceiveTlvsintegrates with the new auth flow without behavioral regressions.Also applies to: 936-944
lightning/src/offers/flow.rs (4)
942-956: Invoice response flow updated to use new router signature: LGTMSwitch to router-based blinded path creation with
ReceiveTlvsintegrates with the new auth model.Also applies to: 959-967
812-867: Static invoice builder flow updated: LGTMDropping
entropy_sourcehere and relying on router and storedReceiveAuthKeyis coherent.Also applies to: 1642-1661
1298-1402: Async refresh plumbing changes look consistentRouter generics and calls align after the signature changes. No functional regressions spotted.
Also applies to: 1396-1456
312-318: ReceiveAuthKey is Copy, by-value use is safe
ReceiveAuthKey derives Copy/Clone, so returning it from &self simply copies it without moving ownership.lightning/src/util/test_utils.rs (1)
290-304: ReceiveAuthKey threading in TestRouter looks correctSignature and forwarding to inner router align with the updated API. No issues spotted.
lightning/src/ln/async_payments_tests.rs (1)
861-863: Updated TLV access is consistentUsing tlvs.payment_constraints.max_cltv_expiry under Receive variant looks right.
lightning/src/offers/signer.rs (1)
39-51: Reserved HMAC inputs documentedGood defensive note reserving [5;16], [7;16], [8;16]. No behavior change.
fuzz/src/invoice_request_deser.rs (1)
134-143: BlindedPaymentPath::new call matches updated signatureArgument order (recipient, receive_auth_key, payee_tlvs, …) is correct. LGTM.
fuzz/src/chanmon_consistency.rs (1)
130-135: Router stub updated to new signatureSignature matches the new API; no functional impact in fuzz stub.
61f0ead to
923ece5
Compare
|
@coderabbitai help |
ChatThere are 3 ways to chat with CodeRabbit:
CodeRabbit commands
Other keywords and placeholders
CodeRabbit configuration file (
|
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
lightning/src/blinded_path/payment.rs (1)
277-282: AAD enforcement now explicit (resolves prior concern)Forward requires no AAD; Dummy and Receive require AAD. This fixes the earlier gap around discarding used_aad.
🧹 Nitpick comments (4)
lightning/src/ln/offers_tests.rs (1)
1413-1447: Inline claim viaClaimAlongRouteArgsis correct; consider avoiding the magic1000msatThe new block correctly:
- Pulls
PaymentClaimablefromalice, validates thePaymentContext, and extracts the preimage.- Uses
ClaimAlongRouteArgs::new(bob, &[&[alice]], payment_preimage)pluswith_expected_extra_total_fees_msat(1000)soclaim_payment_along_routecan account for dummy-hop-induced overpay.- Asserts the claimed invoice matches the original.
Only minor nit:
1000msat is a magic constant. To reduce brittleness if fee settings change, you could either document why this exact value is expected here or derive it from the observed fee delta (e.g., from events/HTLCs) or a helper, instead of hard-coding it. Otherwise this change looks good.lightning/src/ln/functional_test_utils.rs (1)
3555-3562: Remove debug println in hot test loopAvoid noisy stdout in test utils. Drop the println! or gate behind the existing TestLogger.
Apply:
- for _ in 0..dummy_count { - println!("This ran"); - node.node.process_pending_htlc_forwards(); - } + for _ in 0..dummy_count { + node.node.process_pending_htlc_forwards(); + }lightning/src/blinded_path/payment.rs (1)
568-576: Clarify TLV assignment in docsCode uses TLV 65539 for Dummy, while the comment mentions reusing a removed “authentication” field. Update the comment to explicitly state 65539 is used for Dummy to avoid confusion.
Also applies to: 577-591
lightning/src/ln/channelmanager.rs (1)
6919-6951: Consider matchingforward_info: Nonedirectly for clarity.Lines 6927-6931 bind
forward_infoand then assert it'sNone. Since dummy hops always haveforward_info: None(peronion_payment.rslines 630-635), consider matching this directly:( onion_utils::Hop::Dummy { intro_node_blinding_point, next_hop_hmac, new_packet_bytes, .. }, - Some(NextPacketDetails { next_packet_pubkey, forward_info }), + Some(NextPacketDetails { next_packet_pubkey, forward_info: None }), ) => { - debug_assert!( - forward_info.is_none(), - "Dummy hops must not contain any forward info, since they are not actually forwarded." - ); let new_update_add_htlc = create_new_update_add_htlc(This provides compile-time verification rather than runtime assertion, making the code's intent clearer.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
lightning/src/blinded_path/payment.rs(12 hunks)lightning/src/ln/async_payments_tests.rs(2 hunks)lightning/src/ln/blinded_payment_tests.rs(5 hunks)lightning/src/ln/channelmanager.rs(11 hunks)lightning/src/ln/functional_test_utils.rs(11 hunks)lightning/src/ln/msgs.rs(2 hunks)lightning/src/ln/offers_tests.rs(1 hunks)lightning/src/ln/onion_payment.rs(7 hunks)lightning/src/ln/onion_utils.rs(3 hunks)lightning/src/routing/router.rs(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- lightning/src/ln/blinded_payment_tests.rs
- lightning/src/ln/msgs.rs
- lightning/src/routing/router.rs
🧰 Additional context used
🧬 Code graph analysis (5)
lightning/src/ln/offers_tests.rs (1)
lightning/src/ln/functional_test_utils.rs (5)
new(3238-3247)new(3455-3475)new(3837-3852)new(5155-5175)claim_payment_along_route(4122-4136)
lightning/src/ln/channelmanager.rs (1)
lightning/src/ln/onion_payment.rs (6)
check_incoming_htlc_cltv(680-708)create_fwd_pending_htlc_info(94-253)create_new_update_add_htlc(654-678)create_recv_pending_htlc_info(256-439)decode_incoming_update_add_htlc_onion(531-652)invalid_payment_err_data(43-48)
lightning/src/ln/async_payments_tests.rs (2)
lightning/src/routing/router.rs (1)
blinded_path(1796-1803)lightning/src/offers/test_utils.rs (1)
blinded_path(130-139)
lightning/src/ln/onion_payment.rs (2)
lightning/src/ln/onion_utils.rs (8)
new(783-783)new(2142-2142)new(2146-2148)new(2152-2154)new(2726-2728)new(3940-3942)next_hop_pubkey(116-127)shared_secret(2266-2278)lightning/src/ln/msgs.rs (2)
new(2517-2519)new(2552-2554)
lightning/src/blinded_path/payment.rs (2)
lightning/src/blinded_path/message.rs (1)
new_with_dummy_hops(91-119)lightning/src/ln/onion_utils.rs (1)
next_hop_pubkey(116-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
- GitHub Check: semver-checks
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: benchmark
- GitHub Check: linting
- GitHub Check: check_release
- GitHub Check: build (windows-latest, stable)
- GitHub Check: semver-checks
- GitHub Check: build (windows-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: build-tx-sync (ubuntu-latest, 1.75.0)
- GitHub Check: benchmark
- GitHub Check: build-tx-sync (macos-latest, 1.75.0)
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: semver-checks
- GitHub Check: build-tx-sync (macos-latest, beta)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: build (macos-latest, 1.75.0)
🔇 Additional comments (12)
lightning/src/ln/async_payments_tests.rs (2)
58-58: Import ofDEFAULT_PAYMENT_DUMMY_HOPSis appropriateThe new constant is used locally in this test to adapt to dummy hops in blinded payment paths; the import is minimal and correctly scoped.
1856-1872: Advancing the blinded payment path across dummy hops before reading constraintsAdvancing
blinded_pathonce at the intermediate node and thenDEFAULT_PAYMENT_DUMMY_HOPStimes with the recipient’s keys before callingdecrypt_intro_payloadensures the test readsmax_cltv_expiryat the actual receive hop in the presence of dummy hops. This keeps the expiry-calculation logic consistent with the new path layout.lightning/src/ln/onion_payment.rs (1)
470-507: Dummy hop peeling is broken
decode_incoming_update_add_htlc_onioncan now returnHop::Dummy, butpeel_payment_oniononly treatsForward/BlindedForwardas forwarding cases. ADummyhop therefore drops into the_branch, callscreate_recv_pending_htlc_info, and blows up withInvalidOnionBlinding. As soon as a path inserts one of the new dummy hops, we will reject the payment instead of peeling the dummy layer, so the headline feature can’t work.Please add explicit handling for
Hop::Dummyhere (and avoid routing/receiving fallbacks) so that we strip the dummy layer and continue decoding to the real next hop before producingPendingHTLCInfo. raise_critical_issuelightning/src/ln/functional_test_utils.rs (1)
3799-3822: Fee adjustment hook looks good; confirm single point of applicationThe new expected_extra_total_fees_msat is only added in claim_payment_along_route. Confirm tests never compare total fees directly from do_claim_payment_along_route; otherwise, callers may miss the adjustment.
Also applies to: 4128-4135
lightning/src/blinded_path/payment.rs (2)
120-151: Good split: delegate to new_with_dummy_hops(0)Refactor keeps existing behavior while exposing configurability.
242-254: Verify introduction-node update for Dummy hopsFor Dummy, you set introduction_node to our own node_id and advance the blinding point. That’s correct if only the final recipient peels dummy hops. If callers might advance across Dummy outside the payee, consider leaving introduction_node unchanged while removing the hop (behaviorally equivalent when we are already the intro node) to avoid accidental node-id drift.
lightning/src/ln/channelmanager.rs (6)
74-77: LGTM: Import additions support ForwardInfo refactoring.The new imports (
ForwardInfo,HopConnector,NextPacketDetails,invalid_payment_err_data) align with the refactoring to consolidate forward information into a structured type and support dummy hop processing.
4891-4891: LGTM: ForwardInfo refactoring improves code organization.The refactoring consolidates
outgoing_amt_msat,outgoing_cltv_value, andoutgoing_connectorinto aForwardInfostruct. This reduces parameter count and groups related data logically, making the code easier to maintain.Also applies to: 4902-4905, 4925-4928
5099-5106: LGTM: Defensive error handling for dummy hops.The match arm for
Hop::Dummyreturns an error withdebug_assert!(false)because dummy hops should be handled earlier in the decode logic (Hunk 9) before reachingconstruct_pending_htlc_status. This defensive check will catch bugs during development if the assumption is violated.
6961-6966: LGTM: Safe extraction of outgoing SCID through forward_info.The refactored code safely extracts
outgoing_scid_optfrom the nestedOptiontypes usingas_ref().and_then(...). For dummy hops whereforward_infoisNone, this correctly producesNonefor the SCID.
4960-4963: Verification complete: Dummy hops safely bypass thecan_forward_htlcvalidation.The code confirms your concern is already properly handled. At line 6921-6948, the
Hop::Dummymatch arm has a debug assertion verifying thatforward_info.is_none()for dummy hops, and critically, executes acontinuestatement at line 6946. This early exit means dummy hops never reach thecan_forward_htlccheck at line 7028. Only the non-Dummy cases (matched by the_ =>arm at line 6950) proceed to that validation point. Therefore, theforward_inforequirement incan_forward_htlc(lines 4960-4963) cannot be violated by dummy hops.
7109-7112: Dummy HTLCs are correctly processed in subsequent iterations; termination is guaranteed.After verification, the mechanism works as intended:
Re-invocation loop: After
process_pending_update_add_htlcs()processes a batch and swaps dummy HTLCs back into the decode queue,needs_pending_htlc_processing()detects the non-empty queue and returns true. The background processor or caller then re-invokesprocess_pending_htlc_forwards(), triggering another iteration.Termination guarantee: Each iteration peels one layer of Dummy hops from the onion. Since blinded paths have finite length (deterministically bounded by cryptographic constraints), and each peel produces a valid, shorter onion, the iterative process terminates when all Dummy layers are exhausted. When the decode queue becomes empty,
needs_pending_htlc_processing()returns false and the loop stops.No infinite loop risk: Circular Dummy hops are cryptographically impossible—each hop's HMAC validation ensures onions can only progress forward (one layer shorter), never cycle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (9)
lightning/src/ln/msgs.rs (1)
2358-2359: Document the new Dummy hop variant and its constraints.Great addition. Please add a brief doc comment clarifying when/why this variant appears and which fields are disallowed (amt/cltv/total_msat/metadata/keysend/invoice_request) and that AAD must be set.
pub enum InboundOnionPayload { Forward(InboundOnionForwardPayload), TrampolineEntrypoint(InboundTrampolineEntrypointPayload), Receive(InboundOnionReceivePayload), BlindedForward(InboundOnionBlindedForwardPayload), BlindedReceive(InboundOnionBlindedReceivePayload), - Dummy { intro_node_blinding_point: Option<PublicKey> }, + /// A dummy/padding hop inside a blinded path. + /// + /// - Carries no amt/cltv/total_msat/metadata/keysend/invoice_request. + /// - Must be authenticated with AAD in the encrypted TLVs. + /// - Optionally propagates the introduction node's blinding point when present. + Dummy { intro_node_blinding_point: Option<PublicKey> }, }lightning/src/routing/router.rs (2)
204-207: Avoid all-or-nothing collection; keep successful blinded pathsCollecting into Result<Vec<_>, _> means a single failed path build (more likely with dummy hops) discards all successful ones. Prefer filtering out failures and proceeding with any valid paths found.
- .map(|forward_node| { - BlindedPaymentPath::new_with_dummy_hops( - &[forward_node], recipient, DEFAULT_PAYMENT_DUMMY_HOPS, local_node_receive_key, tlvs.clone(), u64::MAX, - MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, secp_ctx - ) - }) - .take(MAX_PAYMENT_PATHS) - .collect::<Result<Vec<_>, _>>(); + .filter_map(|forward_node| { + match BlindedPaymentPath::new_with_dummy_hops( + &[forward_node], recipient, DEFAULT_PAYMENT_DUMMY_HOPS, local_node_receive_key, tlvs.clone(), u64::MAX, + MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, secp_ctx + ) { + Ok(p) => Some(p), + Err(()) => { + log_trace!(self.logger, "Skipping blinded path for peer {} due to dummy-hop build failure", + forward_node.node_id); + None + } + } + }) + .take(MAX_PAYMENT_PATHS) + .collect::<Vec<_>>();And below, adjust the match to check emptiness rather than Result:
- match paths { - Ok(paths) if !paths.is_empty() => Ok(paths), + match paths.len() { + n if n > 0 => Ok(paths), _ => {
77-79: Make the default configurable and document rationalePublic default is fine; consider threading it via user config (e.g., RouteParametersConfig/PaymentParameters) or feature-gating for tests. At minimum, document privacy/performance trade-offs (onion size, CLTV/fees) and when to tune it.
lightning/src/ln/blinded_payment_tests.rs (1)
193-244: Good focused coverage for one‑hop blinded path with dummy hopsThis test mirrors the existing one‑hop blinded path flow while exercising
BlindedPaymentPath::new_with_dummy_hopsandpass_along_path_with_dummy, which is what you want to validate dummy-hop peeling end‑to‑end. Consider factoring the common setup withdo_one_hop_blinded_pathinto a small helper to reduce duplication, but it’s optional.lightning/src/ln/onion_payment.rs (1)
472-485: Consider more specific error message.The error message "Failed to decode update add htlc onion" is generic. Since this error occurs specifically when
forward_infois missing or invalid for a forward hop, consider a more descriptive message like "Missing forward routing information for intermediate hop" to aid debugging.lightning/src/ln/channelmanager.rs (4)
75-80: Imports updated for new dummy/forwarding helpers look consistentThe added
onion_paymentsymbols (check_incoming_htlc_cltv,create_new_update_add_htlc,ForwardInfo,HopConnector,InboundHTLCErr,NextPacketDetails) are all exercised in the new logic below, so the import surface looks coherent. The duplicateonion_utilsimports (lines 79–80) are harmless but could be collapsed into a singleuse crate::ln::onion_utils::{self, ...};for clarity if you touch this area again.
4891-4903: Forwarding checks now rely onForwardInfo; logic looks sound, but relies onforward_infoinvariants
- Using
ForwardInfohere to:
- enforce SCID-privacy (
supports_scid_privacy+outgoing_scid != outbound_scid_alias()), and- validate the outgoing amount/CLTV via
outgoing_amt_msat/outgoing_cltv_value
matches the intendedoption_scid_aliassemantics and makes the checks clearly “about the next hop” rather than the incoming HTLC.- The extra
outgoing_amt_msat < get_counterparty_htlc_minimum_msat()guard seems like the right place to enforce the counterparty’s advertised minimum, on top of whateverhtlc_satisfies_configalready does with our own config.Two things worth double‑checking:
forward_infopresence:can_forward_htlcnow treatsnext_packet_details.forward_info.is_none()asInvalidOnionPayload. That’s fine as long as all call sites only invoke it for true forwards whereforward_infomust beSome. If there are paths whereNextPacketDetailsexists butforward_infois deliberatelyNone(e.g., some blinded/introduction edge cases), this will tighten behaviour and surface as onion failures rather than a more specific error.Redundant
HopConnectorpattern:can_forward_htlcalready rejects non‑ShortChannelIdconnectors before callingcan_forward_htlc_to_outgoing_channel, so theif let HopConnector::ShortChannelId(..)incan_forward_htlc_to_outgoing_channelis effectively defensive. That’s totally fine and may help if the helper is ever called from other sites, but if you want to keep the surface minimal you could instead pass the resolvedoutgoing_scidintocan_forward_htlc_to_outgoing_channeland avoid re‑matching onHopConnector.No blockers here; just worth validating the
forward_infoinvariants at callers.Also applies to: 4925-4929, 4960-4974, 4990-4990
5099-5106: TreatingHop::Dummyhere as an invariant violation is reasonable; consider making the intent more explicitThis arm makes it clear that dummy hops should have been peeled earlier and that hitting
Hop::Dummyon this code path is a bug: youdebug_assert!(false)and surface anInvalidOnionPayloadto the counterparty.That behaviour is fine, but two small tweaks could make the invariant clearer in production builds:
- Replace the bare
debug_assert!(false, ...)with something like a comment or an internal log that explains which invariant was violated (e.g., “dummy hops must be handled inprocess_pending_update_add_htlcsbefore reaching standard forwarding”).- If you ever want more structured failure data here, wiring this through
invalid_payment_err_datainstead of an emptyerr_datawould keep error handling consistent with the other onion failures.Not urgent, but would help future readers understand why this arm exists.
6855-6856: Dummy‑hop peeling viadummy_update_add_htlcslooks correct; consider tightening edge cases and persistence semanticsThe new dummy‑hop flow in
process_pending_update_add_htlcsis a nice separation of concerns:
- When
decode_incoming_update_add_htlc_onionreturns aHop::DummywithSome(NextPacketDetails { next_packet_pubkey, forward_info }), you:
- assert
forward_info.is_none()(since dummy hops aren’t actually forwarded),- build a fresh
UpdateAddHTLCviacreate_new_update_add_htlcusing the intro node blinding point and next packet data, and- stash it in
dummy_update_add_htlcs[incoming_scid_alias]beforecontinue-ing, so the original HTLC isn’t processed further on this pass.- At the end, you swap
self.decode_update_add_htlcswithdummy_update_add_htlcs, causing only the peeled HTLCs to be revisited on the next call.A couple of follow‑ups that might be worth tightening:
(Hop::Dummy, None) handling:
Currently,(Hop::Dummy, None)falls through the_ => decoded_onionarm and will be treated like any other decoded hop. If that combination is truly impossible by construction, it may be clearer (and safer) to add an explicit arm that returns anInboundHTLCErrfor(Hop::Dummy, None)rather than silently treating it as a normal hop.
forward_infoon dummy hops:
Youdebug_assert!(forward_info.is_none())but otherwise ignoreforward_infoin release builds. If there is any chance this ends upSome, you might prefer to enforce it with an explicit error (similar to point 1) so the invariant isn’t only checked in debug builds.Persistence of the peeled queue:
The new peeledUpdateAddHTLCs now live inself.decode_update_add_htlcsafter the finalmem::swap. Depending on howdecode_update_add_htlcsis serialized, you may want to ensureshould_persistis flipped totruewheneverdummy_update_add_htlcsis non‑empty (or when you perform the swap), so a crash between peeling and reprocessing doesn’t lose that intermediate state if it’s meant to be durable.Micro‑nit:
If you care about avoiding an extra lock when there are no dummy hops, you could guard the final swap withif !dummy_update_add_htlcs.is_empty()—purely an optimization, not necessary.Overall the structure looks good; the main ask is to make the “Dummy always comes with
Some(NextPacketDetails)and noforward_info” assumption explicit in the control flow.Also applies to: 6919-6951, 6961-6966, 7109-7111
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
lightning/src/blinded_path/payment.rs(12 hunks)lightning/src/ln/async_payments_tests.rs(2 hunks)lightning/src/ln/blinded_payment_tests.rs(5 hunks)lightning/src/ln/channelmanager.rs(11 hunks)lightning/src/ln/functional_test_utils.rs(11 hunks)lightning/src/ln/msgs.rs(2 hunks)lightning/src/ln/offers_tests.rs(1 hunks)lightning/src/ln/onion_payment.rs(7 hunks)lightning/src/ln/onion_utils.rs(3 hunks)lightning/src/routing/router.rs(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: build-tx-sync (ubuntu-latest, 1.75.0)
- GitHub Check: benchmark
- GitHub Check: build (windows-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, 1.75.0)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: build-tx-sync (macos-latest, stable)
- GitHub Check: build-tx-sync (macos-latest, 1.75.0)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: build (macos-latest, stable)
🔇 Additional comments (30)
lightning/src/blinded_path/payment.rs (11)
141-183: LGTM with a note on payinfo calculationThe
new_with_dummy_hopsconstructor correctly passes the dummy hop count toblinded_hopsfor path construction. Note thatcompute_payinfodoesn't account for dummy hops in fee/CLTV calculations, which appears correct since dummy hops are peeled by the receiving node and don't add routing fees or delays.
120-139: LGTM - Backward compatible delegationThe existing
newmethod correctly delegates tonew_with_dummy_hopswithdummy_hop_count = 0, maintaining backward compatibility.
242-254: LGTM - Dummy hop advancement logic is correctThe dummy hop handling correctly advances the blinding point and routes back to the same node (via
get_node_id(Recipient::Node)), which aligns with dummy hops being self-referential hops that the receiving node peels recursively.
277-282: LGTM - Proper AAD authentication for Dummy TLVsThe authentication logic correctly requires
used_aad == trueforDummyTLVs, preventing unauthenticated control TLVs from being accepted. This is critical for security as it ensures only the legitimate receiver can peel dummy hops.
372-380: LGTM - Well-documented dummy TLV typeThe
PaymentDummyTlvunit struct withpub(crate)visibility is appropriate as an internal marker type. The documentation clearly explains its privacy-enhancing purpose.
396-420: LGTM - Consistent enum variantsThe
Dummyvariants added toBlindedPaymentTlvsandBlindedPaymentTlvsRefare consistent with the existingForwardandReceivevariants.
593-602: LGTM - Correct write delegationThe
Dummycase inBlindedPaymentTlvsRef::writecorrectly delegates toPaymentDummyTlv::write.
604-660: LGTM - Robust TLV decoding with mutual exclusionThe
Readableimplementation correctly handles theDummyvariant by requiring only theis_dummyfield (TLV 65539) to be present while all other fields must beNone. This ensures proper mutual exclusion between Forward, Dummy, and Receive TLVs.
702-724: LGTM - Correct dummy hop insertion for privacyThe
blinded_hopsfunction correctly inserts dummy hops between intermediate forwarding nodes and the final receive hop, which enhances privacy by obscuring the receiver's position in the route. The use ofreceive_auth_keyfor dummy hops ensures proper authentication. The clamping toMAX_DUMMY_HOPS_COUNTat line 707 prevents abuse.
15-15: No issues foundThe import is correct. MAX_DUMMY_HOPS_COUNT is properly defined as a public constant with value 10 and includes documentation explaining it prevents paths from becoming too long. The constant is actively used in payment.rs at line 707 in a bounds check and documented at line 144.
568-575: No issues found with TLV type 65539TLV type 65539 is correctly used as a discriminator marker for the PaymentDummyTlv variant. The implementation is sound:
- PaymentDummyTlv exclusively writes 65539 as a unit marker
- ForwardTlvs and ReceiveTlvs never write 65539
- The unified read stream uses 65539's presence/absence to distinguish variants
- No conflicts exist as these are mutually exclusive message types
lightning/src/ln/async_payments_tests.rs (2)
862-866: LGTM - Correct dummy hop advancement in testThe loop correctly advances the blinded path by
DEFAULT_PAYMENT_DUMMY_HOPSiterations using the recipient's (nodes[2]) keys, which is appropriate since dummy hops are self-referential hops at the receiving node. This ensures the test properly extracts themax_cltv_expiryfrom the final receive TLVs after peeling through all dummy hops.
58-58: DEFAULT_PAYMENT_DUMMY_HOPS is properly defined and the import is valid.The constant is defined as
pub const DEFAULT_PAYMENT_DUMMY_HOPS: usize = 3;inlightning/src/routing/router.rsat line 78, which confirms the import at line 58 oflightning/src/ln/async_payments_tests.rsis correct and the value is reasonable for test expectations.lightning/src/ln/msgs.rs (1)
3698-3711: Verification complete: Dummy variant is properly handled in all match sites.All matches on InboundOnionPayload correctly handle the new Dummy variant:
- onion_utils.rs:2338: Explicitly handles
Dummyin the intermediary node packet processing path- channelmanager.rs:6919: Explicitly handles
Hop::Dummywith proper logic for dummy hops- msgs.rs:3698-3711: Decode validation correctly enforces constraints (requires
used_aad, disallowsamt/cltv/total_msat/keysend/invoice_request)The decode guardrails are sound. The test recommendations (valid Dummy with AAD, rejection when
used_aad=false, rejection when extra fields present) remain valuable for coverage but are optional.lightning/src/routing/router.rs (1)
216-219: Suggestion is based on a flawed premise—dummy hop count doesn't cause the failure.The code shows
new_with_dummy_hopsfails duringcompute_payinfo, which processes onlyintermediate_nodes. Failures occur from invalid features or CLTV overflow on those nodes—not from thedummy_hop_countparameter itself. Theblinded_hopsfunction acceptsdummy_hop_count = 0without issue (line 707 clamps it, line 711 usestake(0)which yields empty iterators). Retrying with progressively smaller hop counts won't recover from the actual failure modes.Verify the actual context where this path fails in
lightning/src/routing/router.rsbefore applying a retry strategy.Likely an incorrect or invalid review comment.
lightning/src/ln/onion_utils.rs (3)
2158-2215: Dummy hop variant wiring looks consistent with existing Hop variantsThe new
Hop::Dummyvariant mirrorsForward/BlindedForward(shared secret + HMAC + next packet bytes) and carries theintro_node_blinding_pointneeded for dummy-hop semantics. This keeps downstream handling uniform and should integrate cleanly with existing pattern matches.
2266-2277: Including Dummy inshared_secret()avoids downstream special casesExtending
Hop::shared_secretto return the dummy hop’sshared_secretensures all non-final variants expose the correct key through the same interface, which is important for callers that don’t distinguishDummyfrom other forwards.
2338-2343: Decoding Dummy inbound payload intoHop::Dummyis correctMapping
InboundOnionPayload::Dummy { intro_node_blinding_point }intoHop::Dummy { intro_node_blinding_point, shared_secret, next_hop_hmac, new_packet_bytes }reuses the standard HMAC/packet plumbing while surfacing the extra metadata needed to peel dummy hops at the receiver.lightning/src/ln/blinded_payment_tests.rs (2)
1718-1723: Usingforward_infofromNextPacketDetailskeeps spec-vector tests aligned with onion logicSwitching the Bob→Carol, Carol→Dave, and Dave→Eve
UpdateAddHTLCconstructions to usenext_packet_details.forward_info.{outgoing_amt_msat,outgoing_cltv_value}rather than re‑encoding these values manually makes the route‑blinding spec test strictly follow what the decoder computed. This reduces the risk of the test getting out of sync with the forwarding arithmetic.Also applies to: 1753-1756, 1788-1791
2021-2022: Trampoline inbound decoding now correctly reusesforward_infoUsing
carol_packet_details.forward_infoto fill Carol’sUpdateAddHTLCamount and CLTV mirrors the change above and keeps the trampoline inbound test driven by the same NextPacketDetails source of truth instead of duplicating the routing math.lightning/src/ln/onion_payment.rs (5)
18-18: LGTM: Explicit imports for new functionality.The explicit import of
OnionPacketandUpdateAddHTLCis appropriate to support the newcreate_new_update_add_htlcfunction added later in the file.
126-133: Clarify defensive programming pattern withdebug_assert!(false).The
debug_assert!(false, ...)followed by error handling is unusual. In debug builds, this will panic before the error is returned, while in release builds it returns the error. Consider either:
- Removing the
debug_assert!if this is a possible error condition- Documenting why this is expected to be unreachable (e.g., if upstream validation should prevent this)
- Using
unreachable!()if this truly should never happenThe same pattern appears at line 339 for
create_recv_pending_htlc_info.
511-528: LGTM: Well-structured encapsulation of forward hop information.The new structs properly encapsulate forwarding details, with appropriate visibility (
pub(super)) for module-internal use. TheHopConnectorenum cleanly distinguishes between SCID-based and Trampoline routing.
600-607: LGTM: Consistent population of forward routing information.All three forward hop types (Direct, BlindedForward, TrampolineForward) correctly populate the new
forward_infofield with appropriate routing details and connector types.Also applies to: 622-628, 635-640
642-647: LGTM: Correct handling of Dummy hops.Dummy hops are properly handled by computing the
next_packet_pubkeywhile settingforward_info: None, which ensures they cannot be forwarded (as intended) while still supporting the onion construction flow.lightning/src/ln/functional_test_utils.rs (5)
42-44: LGTM: Import supports new dummy hop test functionality.The import of
DEFAULT_PAYMENT_DUMMY_HOPSis appropriate for the new test helper functions that exercise dummy hop behavior.
3440-3440: LGTM: Builder pattern extension for dummy hop override.The new
dummy_hop_overridefield andwith_dummy_overridemethod properly extend the builder pattern to support testing with custom dummy hop counts.Also applies to: 3462-3462, 3510-3513
3674-3711: LGTM: Clean refactoring to support dummy hops.The refactoring of
pass_along_pathto delegate topass_along_path_with_dummymaintains backward compatibility while properly supporting the new dummy hop functionality. The conditional application ofwith_dummy_overrideis correct.
3799-3821: LGTM: Excellent documentation of fee-handling trade-off.The new
expected_extra_total_fees_msatfield is well-documented with clear explanation of:
- The existing fee-handling trade-off in LDK
- How dummy hops surface this issue in tests
- Why this field exists as a temporary workaround
The documentation provides valuable context for future developers and properly references the relevant code (
BlindedPaymentPath::advance_path_by_one).Also applies to: 3845-3845, 3861-3864
4128-4130: LGTM: Straightforward fee adjustment logic.The extraction and addition of
expected_extra_total_fees_msatis clear and correct, properly accounting for the documented fee-handling trade-off in test assertions.
923ece5 to
923982c
Compare
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
lightning/src/ln/offers_tests.rs (1)
1413-1447: Document or derive the 1000 msat extra-fee and consider reusing the claim helperThe end-to-end behavior here (extracting
PaymentClaimable, validating thePaymentContext, and claiming viaClaimAlongRouteArgswith a single-hop path) is sound, but two issues remain:
Magic-number
1000
with_expected_extra_total_fees_msat(1000)is still a hard-coded adjustment with no visible derivation. Since this test is specifically about dummy hops and an unannounced introduction node, the expected overpay should either:
- be computed from known constants (e.g., dummy-hop count × per-hop fee), or
- at minimum be factored into a named constant with a brief comment explaining that it matches the additional forwarding fees introduced by dummy hops when the payer is also the introduction node, and that it must stay in sync with the router’s dummy-hop fee behavior.
Duplicated claim logic
The PaymentPurpose/PaymentContext checking is nearly identical to theclaim_bolt12_paymenthelper defined earlier. To keep tests easier to maintain, you could push the “expected extra total fees” notion into that helper (e.g., an optional parameter or a small wrapper) instead of inlining a custom variant here.Example of a lightweight, non-invasive improvement on the magic number side:
- let args = ClaimAlongRouteArgs::new( - bob, - expected_paths, - payment_preimage, - ).with_expected_extra_total_fees_msat(1000); + const EXTRA_DUMMY_HOP_FEES_MSAT: u64 = 1000; // Matches additional fees from dummy hops + let args = ClaimAlongRouteArgs::new( + bob, + expected_paths, + payment_preimage, + ).with_expected_extra_total_fees_msat(EXTRA_DUMMY_HOP_FEES_MSAT);You can then expand the comment on
EXTRA_DUMMY_HOP_FEES_MSATto reference how it is derived from the current dummy-hop configuration.lightning/src/ln/onion_payment.rs (1)
654-678: Avoid panicking onnode_signer.ecdhfailure increate_new_update_add_htlc
create_new_update_add_htlccurrently does:let encrypted_tlvs_ss = node_signer.ecdh(Recipient::Node, &blinding_point, None).unwrap().secret_bytes(); onion_utils::next_hop_pubkey(&secp_ctx, blinding_point, &encrypted_tlvs_ss).ok()Using
.unwrap()here will panic if ECDH fails (e.g., an invalid or maliciousblinding_point), which is inconsistent with other call sites that treat ECDH failure as a soft error and propagate it instead of aborting the process.Consider mapping failures to
Noneinstead:- let encrypted_tlvs_ss = - node_signer.ecdh(Recipient::Node, &blinding_point, None).unwrap().secret_bytes(); - onion_utils::next_hop_pubkey(&secp_ctx, blinding_point, &encrypted_tlvs_ss).ok() + node_signer + .ecdh(Recipient::Node, &blinding_point, None) + .ok() + .and_then(|ss| { + onion_utils::next_hop_pubkey(secp_ctx, blinding_point, &ss.secret_bytes()).ok() + })so a bad
blinding_pointjust results inblinding_point: Noneon the newUpdateAddHTLCinstead of a panic.
🧹 Nitpick comments (2)
lightning/src/routing/router.rs (2)
77-79: Clarify dummy-hop constant documentationThe constant is clear from its name, but the doc comment could be a bit more precise about scope (it’s specifically for router-constructed blinded payment paths). Consider tightening it:
-/// Default number of Dummy Hops +/// Default number of dummy hops added when constructing blinded payment paths.
204-207: Wiringnew_with_dummy_hopsthroughcreate_blinded_payment_pathslooks correct; consider future configurabilityThe switch to
BlindedPaymentPath::new_with_dummy_hopsand passingDEFAULT_PAYMENT_DUMMY_HOPSin both the peer-path and fallback-path cases is consistent with the new constructor’s signature and keeps behavior uniform across the two branches.If you anticipate operators wanting different dummy-hop counts (e.g., for privacy vs. CLTV/size trade-offs), you might eventually want to thread a
dummy_hop_countfrom config (e.g.,RouteParametersConfig/UserConfig) intocreate_blinded_payment_pathsinstead of hard-coding a global constant here. This can be deferred, but keeping the call-site isolated here makes that refactor straightforward later.Also applies to: 216-219
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
lightning-dns-resolver/src/lib.rs(2 hunks)lightning/src/blinded_path/payment.rs(10 hunks)lightning/src/ln/async_payments_tests.rs(2 hunks)lightning/src/ln/blinded_payment_tests.rs(5 hunks)lightning/src/ln/channelmanager.rs(11 hunks)lightning/src/ln/functional_test_utils.rs(10 hunks)lightning/src/ln/msgs.rs(2 hunks)lightning/src/ln/offers_tests.rs(2 hunks)lightning/src/ln/onion_payment.rs(7 hunks)lightning/src/ln/onion_utils.rs(3 hunks)lightning/src/routing/router.rs(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- lightning/src/ln/onion_utils.rs
- lightning/src/ln/msgs.rs
- lightning/src/ln/blinded_payment_tests.rs
🧰 Additional context used
🧬 Code graph analysis (5)
lightning/src/ln/channelmanager.rs (1)
lightning/src/ln/onion_payment.rs (6)
check_incoming_htlc_cltv(680-708)create_fwd_pending_htlc_info(94-253)create_new_update_add_htlc(654-678)create_recv_pending_htlc_info(256-439)decode_incoming_update_add_htlc_onion(531-652)invalid_payment_err_data(43-48)
lightning/src/ln/offers_tests.rs (1)
lightning/src/ln/functional_test_utils.rs (7)
new(3238-3247)new(3455-3475)new(3814-3829)new(5137-5157)claim_payment_along_route(4099-4118)do_commitment_signed_dance(2694-2748)expect_payment_failed_conditions(3379-3396)
lightning/src/routing/router.rs (1)
lightning/src/blinded_path/payment.rs (1)
new_with_dummy_hops(145-183)
lightning/src/blinded_path/payment.rs (3)
lightning/src/blinded_path/message.rs (1)
new_with_dummy_hops(91-119)lightning/src/ln/onion_utils.rs (1)
next_hop_pubkey(116-127)lightning/src/blinded_path/utils.rs (1)
construct_blinded_hops(163-193)
lightning/src/ln/onion_payment.rs (2)
lightning/src/ln/onion_utils.rs (8)
new(783-783)new(2142-2142)new(2146-2148)new(2152-2154)new(2726-2728)new(3940-3942)next_hop_pubkey(116-127)shared_secret(2266-2278)lightning/src/ln/msgs.rs (2)
new(2517-2519)new(2552-2554)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: semver-checks
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, 1.75.0)
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: benchmark
- GitHub Check: semver-checks
- GitHub Check: benchmark
- GitHub Check: build (windows-latest, stable)
🔇 Additional comments (12)
lightning-dns-resolver/src/lib.rs (1)
178-178: Aligning DNS resolver test flow with dummy-hop processing looks correctImporting
DEFAULT_PAYMENT_DUMMY_HOPSand runningprocess_pending_htlc_forwardsthat many times beforeexpect_and_process_pending_htlcskeeps this test in sync with the new blinded-payment dummy-hop behavior. No functional issues spotted.Also applies to: 424-426
lightning/src/ln/async_payments_tests.rs (1)
58-58: Dummy-hop advancement in CLTV-expiry test is consistent with new path semanticsImporting
DEFAULT_PAYMENT_DUMMY_HOPSand advancing the blinded payment path by that many steps before decrypting the intro payload keepsfinal_max_cltv_expiryaligned with how dummy hops are now processed. This looks correct and maintains the intent of the expiry test.Also applies to: 1862-1866
lightning/src/ln/offers_tests.rs (1)
2462-2465: Updated malformed-HTLC failure handling is consistent with the test’s expectationsUsing
handle_update_fail_malformed_htlcon the sender and then assertingexpect_payment_failed_conditions(..., PaymentFailedConditions::new())aligns with the earlierexpect_failure(HTLCHandlingFailureType::Receive { payment_hash })in this test. The flow cleanly models a malformed onion failure for the keysend attempt over a non-static invoice path.lightning/src/ln/onion_payment.rs (4)
126-133: Defensive handling ofHop::Dummyas intermediary looks correctTreating
onion_utils::Hop::Dummyas unreachable for intermediaries (debug assertion plusInvalidOnionPayloaderror) is a good safeguard: it prevents accidentally forwarding a dummy hop and turns any malformed/buggy onion into an explicit failure instead of mis-routing.
338-345: RejectingHop::Dummyat final-hop decoding is consistent with invariantsMarking
onion_utils::Hop::Dummyas unreachable increate_recv_pending_htlc_infoand mapping it toInvalidOnionBlindingensures final-node receive logic never tries to interpret a dummy payload as a real final hop. This keeps the receive path aligned with the expectation that dummy hops are peeled earlier in the blinded flow.
472-500: Requiringforward_infofor non-final hops tightens onion validationThe new shape
Some(NextPacketDetails { next_packet_pubkey, forward_info: Some(ForwardInfo { outgoing_cltv_value, .. }), })for
Hop::Forward/Hop::BlindedForward, and treating any othernext_packet_details_optasInvalidOnionPayload, ensures that we only forward when we have both a next-hop pubkey and coherent forward metadata. This is a good tightening of invariants around onion decoding and avoids silently forwarding with partially-populated state.
519-528:ForwardInfo/HopConnectordesign cleanly separates routing metadata from key derivationThe introduction of:
HopConnector(ShortChannelId vs Trampoline),ForwardInfo { outgoing_connector, outgoing_amt_msat, outgoing_cltv_value },NextPacketDetails { next_packet_pubkey, forward_info: Option<ForwardInfo> },and the updated
decode_incoming_update_add_htlc_onionconstruction for:
Hop::Forward/BlindedForward(ShortChannelId connector, full forward_info),Hop::TrampolineForward(Trampoline connector, full forward_info),Hop::Dummy(next_packet_pubkey withforward_info: None),gives a clear separation between cryptographic next-hop state (pubkey) and routing semantics (connector/amount/CLTV). This is a solid foundation for later code to treat dummy hops as “self-forwards” (no connector) while still having the derived pubkey available.
Also applies to: 596-647
lightning/src/ln/functional_test_utils.rs (2)
3437-3513: Dummy-hop override plumbing inPassAlongPathArgsanddo_pass_along_pathis well-structuredAdding
dummy_hop_override: Option<usize>(with awith_dummy_overridebuilder) and, at the final hop, calling:for _ in 0..dummy_hop_override.unwrap_or(DEFAULT_PAYMENT_DUMMY_HOPS) { node.node.process_pending_htlc_forwards(); }gives tests precise control over how many dummy layers to peel while defaulting to
DEFAULT_PAYMENT_DUMMY_HOPSso they remain aligned with router behavior. The change is localized to test utilities and doesn’t affect production paths.Also applies to: 3516-3562
3772-3841:expected_extra_total_fees_msatcleanly models intentional overpayment in blinded-intro casesThe new
ClaimAlongRouteArgs::expected_extra_total_fees_msatfield (with builderwith_expected_extra_total_fees_msat) and its use in:let expected_total_fee_msat = do_claim_payment_along_route(args) + expected_extra_total_fees_msat;provide a clear, test-only knob to account for the documented fee trade-off when the payer is the introduction node with dummy hops. Existing callers that don’t set this field preserve prior behavior (default 0), while tests can explicitly encode the expected overpayment.
Also applies to: 4099-4112
lightning/src/blinded_path/payment.rs (3)
120-183:new_with_dummy_hopsandblinded_hopsimplement bounded dummy support with good invariantsDelegating
BlindedPaymentPath::newtonew_with_dummy_hops(withdummy_hop_count = 0) and havingnew_with_dummy_hopsfunnel all construction through:
blinded_payinfo = compute_payinfo(..)(ignoring dummy hops), andblinded_hops(secp_ctx, intermediate_nodes, payee_node_id, dummy_hop_count, payee_tlvs, &blinding_secret, local_node_receive_key)is a clean split between economic parameters and path layout. Inside
blinded_hops:
dummy_hop_countis safely clamped viadummy_count = min(dummy_hop_count, MAX_DUMMY_HOPS_COUNT),- intermediate nodes, dummy hops, and the final receive hop are composed into a single iterator of
(pubkey, ReceiveAuthKey)andBlindedPaymentTlvsRef,- debug builds assert that all non-final hops have identical serialized length under
PAYMENT_PADDING_ROUND_OFF, protecting the privacy invariant that intermediate hop sizes are uniform.Overall, this is a solid, well-bounded implementation of dummy hops.
Also applies to: 685-725
222-257: Dummy handling inadvance_path_by_oneanddecrypt_intro_payloadmatches the AAD modelTwo key pieces align well:
decrypt_intro_payloadnow validates both content and authentication:match (&readable, used_aad) { (BlindedPaymentTlvs::Forward(_), false) | (BlindedPaymentTlvs::Dummy, true) | (BlindedPaymentTlvs::Receive(_), true) => Ok((readable, control_tlvs_ss)), _ => Err(()), }so only receive-side payloads (Receive and Dummy) are accepted when authenticated with the receive auth key, while Forward payloads must not consume AAD. This prevents unauthenticated control TLVs from being interpreted as valid receive or dummy hops.
- The new
BlindedPaymentTlvs::Dummyarm inadvance_path_by_one:
- derives
next_node_idfromnode_signer.get_node_id(Recipient::Node),- re-blinds using
onion_utils::next_hop_pubkey(.., control_tlvs_ss.as_ref()),- updates
introduction_nodeto ourselves and pops the first hop,mirroring the Forward case but “looping” to the local node. That’s exactly the right behavior for dummy hops that should be peeled locally while preserving the blinding schedule.
Also applies to: 259-283
384-412: Dummy TLV encoding/decoding is minimal and unambiguousThe Dummy variant is wired through cleanly:
BlindedPaymentTlvsgainsDummy,
BlindedPaymentTlvsRef::Dummywrites a single TLV(65539, ()),
Readable for BlindedPaymentTlvstreats the TLV stream as:
- Forward when
scid,payment_relay,payment_constraints(and optional features/next_blinding_override) are present and no receive/dummy fields,- Receive when only
payment_constraints,payment_secret, andpayment_contextare present,- Dummy when only
is_dummy(65539) is present,and anything else yields
DecodeError::InvalidValue.This keeps Dummy orthogonal to the existing Forward/Receive payloads and avoids ambiguous combinations, while preserving the existing constraints on Forward and Receive decoding.
Also applies to: 575-643
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
lightning/src/routing/router.rs (1)
216-219: Fallback blinded-path construction consistently uses the same dummy-hop defaultThe fallback branch mirrors the main path construction, now calling
new_with_dummy_hopswith an empty forward-node slice andDEFAULT_PAYMENT_DUMMY_HOPS. This keeps behavior consistent between the multi-peer and direct-recipient cases, with no additional edge cases introduced beyond those already covered by the new constructor.lightning/src/ln/offers_tests.rs (1)
1413-1447: Clarify and de‑magic the 1000 msat extra‑fee adjustmentThe along‑route claim flow and context checks look solid, but
.with_expected_extra_total_fees_msat(1000)is an undocumented magic value tied to dummy‑hop behavior.Consider either:
- deriving this from the dummy‑hop configuration (e.g., count × per‑hop fee), or
- adding a brief comment that explicitly states that 1000 msat is the extra relay fee introduced by dummy hops in this test and should be kept in sync with the dummy‑hop fee setup.
lightning/src/ln/onion_payment.rs (1)
654-678: Avoid panicking on ECDH failure when deriving next blinding point
create_new_update_add_htlccurrently does:let encrypted_tlvs_ss = node_signer.ecdh(Recipient::Node, &blinding_point, None).unwrap().secret_bytes();A failing
ecdhhere would panic. Even if you expectRecipient::NodeECDH to always succeed for validblinding_points, other call sites typically treatecdhas fallible and degrade gracefully rather than panicking.You can keep the same behavior while making this robust by treating ECDH failure as “no next blinding point”:
- let next_blinding_point = - intro_node_blinding_point.or(msg.blinding_point).and_then(|blinding_point| { - let encrypted_tlvs_ss = - node_signer.ecdh(Recipient::Node, &blinding_point, None).unwrap().secret_bytes(); - onion_utils::next_hop_pubkey(&secp_ctx, blinding_point, &encrypted_tlvs_ss).ok() - }); + let next_blinding_point = + intro_node_blinding_point.or(msg.blinding_point).and_then(|blinding_point| { + let encrypted_tlvs_ss = node_signer + .ecdh(Recipient::Node, &blinding_point, None) + .ok() + .map(|ss| ss.secret_bytes())?; + onion_utils::next_hop_pubkey(&secp_ctx, blinding_point, &encrypted_tlvs_ss).ok() + });This preserves the existing API (still returns an
UpdateAddHTLC) but avoids introducing a new panic site in onion processing.lightning/src/ln/channelmanager.rs (1)
6851-7111:mem::swapwithdummy_update_add_htlcscan drop concurrently enqueued decode HTLCsThe dummy-hop handling in
process_pending_update_add_htlcsis mostly solid:
- You drain
self.decode_update_add_htlcsinto a localdecode_update_add_htlcs.- When
decode_incoming_update_add_htlc_onionreturns aHop::Dummy, you build a rewrittenUpdateAddHTLCviacreate_new_update_add_htlcand queue it indummy_update_add_htlcsunderincoming_scid_alias, thencontinue.- Non-dummy cases flow through as before, now using
NextPacketDetails.forward_infoto deriveoutgoing_scid_opt.However, the final block:
// Replace the decode queue with the peeled dummy HTLCs so they can be processed in the next iteration. let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap(); mem::swap(&mut *decode_update_add_htlc_source, &mut dummy_update_add_htlcs);is racy and can silently drop HTLCs:
- After the initial
mem::swapat the top of the function, other threads may push new decode HTLCs intoself.decode_update_add_htlcs.- At the end, you
mem::swapthat shared map withdummy_update_add_htlcs, moving any newly queued HTLCs into the localdummy_update_add_htlcs.- When the function returns,
dummy_update_add_htlcs(now holding those fresh items) is dropped, effectively losing payments.Instead of replacing the queue, merge the peeled dummy HTLCs into the existing one while holding the lock:
- // Replace the decode queue with the peeled dummy HTLCs so they can be processed in the next iteration. - let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap(); - mem::swap(&mut *decode_update_add_htlc_source, &mut dummy_update_add_htlcs); + // Append peeled dummy HTLCs to the decode queue so they are processed in the next iteration + // without dropping any HTLCs enqueued concurrently by other threads. + if !dummy_update_add_htlcs.is_empty() { + let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap(); + for (incoming_scid_alias, mut htlcs) in dummy_update_add_htlcs { + decode_update_add_htlc_source + .entry(incoming_scid_alias) + .or_insert_with(Vec::new) + .append(&mut htlcs); + } + }This preserves any HTLCs enqueued while processing and still ensures dummy-peeled HTLCs are picked up on the next iteration.
🧹 Nitpick comments (8)
lightning-dns-resolver/src/lib.rs (1)
424-426: Add clarifying comment to document dummy hop processing semantics.Verification confirms the iteration count is correct. The incoming packet contains N dummy-hop layers, and each call to
process_pending_htlc_forwards()strips exactly one layer, requiring N calls total. The code matches the established pattern found inlightning/src/ln/functional_test_utils.rs(lines 3556-3561).However, add an explanatory comment similar to the reference implementation to clarify intent for future maintainers:
do_commitment_signed_dance(&nodes[1], &nodes[0], &updates.commitment_signed, false, false); + // At the final hop, the incoming packet contains N dummy-hop layers + // before the real HTLC. Each call to process_pending_htlc_forwards + // strips exactly one dummy layer, so we call it N times. for _ in 0..DEFAULT_PAYMENT_DUMMY_HOPS { nodes[1].node.process_pending_htlc_forwards(); }lightning/src/ln/async_payments_tests.rs (1)
862-866: Consider adding a clarifying comment.The logic correctly advances the blinded path through the dummy hops before extracting the
max_cltv_expiry. Using the recipient's (nodes[2]) keys for all dummy hop advancements is appropriate since dummy hops are added at the recipient's end of the path.However, adding a brief comment would help future maintainers understand why this loop is necessary:
// Advance through dummy hops added at the recipient's end to reach the final payload for _ in 0..DEFAULT_PAYMENT_DUMMY_HOPS { blinded_path .advance_path_by_one(&nodes[2].keys_manager, &nodes[2].node, &secp_ctx) .unwrap(); }lightning/src/ln/onion_utils.rs (2)
2205-2215: Add documentation for the Dummy hop variant.The new
Hop::Dummyvariant lacks documentation explaining its purpose, when it should be used, and what each field represents. Since dummy hops appear to be a privacy feature for blinded payment paths, clear documentation is essential for maintainability and correct usage.Consider adding documentation like:
+ /// This onion payload is a dummy hop used for privacy in blinded payment paths. + /// Dummy hops add obfuscation by requiring the recipient to peel additional onion + /// layers that don't correspond to real forwarding nodes. Dummy { - /// Onion payload data used in interpreting the dummy hop + /// The blinding point if this dummy hop is at an introduction node intro_node_blinding_point: Option<PublicKey>, /// Shared secret that was used to decrypt next_hop_data. shared_secret: SharedSecret, /// HMAC of the next hop's onion packet. next_hop_hmac: [u8; 32], - /// Bytes of the onion packet we're forwarding. + /// Bytes of the next onion layer to peel. new_packet_bytes: [u8; ONION_DATA_LEN], },
2338-2343: Dummy payloads are intentionally intermediate-only and current error handling is correct, but adding an explicit check would improve clarity.The design is confirmed: Dummy payloads are used for padding in blinded paths and must only appear as intermediate hops. The current code correctly rejects Dummy as a final hop via the generic catch-all at line 2502. However, verification found that
onion_payment.rsincludes adebug_assert!(false)when Dummy reaches the payment layer, indicating this is a serious constraint.While the current behavior is architecturally sound, adding an explicit
InboundOnionPayload::Dummymatch arm in theNonebranch (before the catch-all) would make this constraint explicit and clearer to future maintainers. This would align with the debug_assert intent and document that Dummy hops should never reach the final hop position.Example improvement:
msgs::InboundOnionPayload::Dummy { .. } => { Err(OnionDecodeErr::Relay { err_msg: "Dummy hop cannot be a final hop", reason: LocalHTLCFailureReason::InvalidOnionPayload, shared_secret, trampoline_shared_secret: None, }) },lightning/src/routing/router.rs (1)
204-207: Use ofnew_with_dummy_hopsin primary path construction looks correctArgument ordering matches the extended constructor (
forward_nodeslice,recipient, dummy-hop count, then the prior parameters), and wiringDEFAULT_PAYMENT_DUMMY_HOPSthrough here keeps the change localized while enabling dummy-hop support. If, in the future, you want per-payment or per-user tuning of dummy hops, this would be the natural call site to thread a configurable value through instead of a fixed const, but as-is this is fine and non-breaking.lightning/src/ln/channelmanager.rs (1)
5095-5107: UnreachableHop::Dummyarm should reflect the invariant more explicitlyThis match arm treats
onion_utils::Hop::Dummyas “shouldn’t be triggered” and maps it toInvalidOnionPayload. If, by design, all dummy hops are fully handled earlier (via the dedicated dummy-peeling path) and cannot reach this function, consider tightening the invariant:
- Either convert this to
unreachable!()with a short comment stating which higher-level path must have consumed dummy hops, or- Reuse the same dummy-handling helper as in
process_pending_update_add_htlcsfor consistency if this path can ever be hit in future refactors.Current behavior is safe (fails the HTLC cleanly) but documenting or enforcing the invariant would make future maintenance easier.
lightning/src/blinded_path/payment.rs (2)
590-644: Consider adding explanatory comment for complex decoding logic.The TLV decoding correctly validates the three mutually exclusive cases (Forward, Receive, Dummy) through pattern matching. However, the 8-element tuple match is complex and could benefit from a brief comment explaining the validation rules.
Consider adding a comment like:
// Validate mutually exclusive TLV combinations: // - Forward: requires scid, relay, constraints (no secret/context/dummy) // - Receive: requires secret, context, constraints (no scid/relay/dummy) // - Dummy: requires only is_dummy marker (no other fields) match (scid, next_blinding_override, payment_relay, ...) {
686-691: Document silent clamping of dummy_hop_count.Line 691 silently clamps
dummy_hop_counttoMAX_DUMMY_HOPS_COUNT. While this prevents errors, callers who request more dummy hops than allowed won't be notified that their request was reduced. Consider documenting this behavior in thenew_with_dummy_hopsdoc comment to set clear expectations.Consider enhancing the documentation:
/// Same as [`BlindedPaymentPath::new`], but allows specifying a number of dummy hops. /// /// Note: /// If `dummy_hop_count` exceeds [`MAX_DUMMY_HOPS_COUNT`], it will be silently /// clamped to the maximum allowed value. pub fn new_with_dummy_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
lightning-dns-resolver/src/lib.rs(2 hunks)lightning/src/blinded_path/payment.rs(10 hunks)lightning/src/ln/async_payments_tests.rs(2 hunks)lightning/src/ln/blinded_payment_tests.rs(5 hunks)lightning/src/ln/channelmanager.rs(11 hunks)lightning/src/ln/functional_test_utils.rs(10 hunks)lightning/src/ln/msgs.rs(2 hunks)lightning/src/ln/offers_tests.rs(2 hunks)lightning/src/ln/onion_payment.rs(7 hunks)lightning/src/ln/onion_utils.rs(3 hunks)lightning/src/routing/router.rs(3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: benchmark
- GitHub Check: build (windows-latest, stable)
- GitHub Check: benchmark
- GitHub Check: build (windows-latest, stable)
- GitHub Check: benchmark
- GitHub Check: build (windows-latest, stable)
🔇 Additional comments (26)
lightning-dns-resolver/src/lib.rs (1)
178-178: Verified: constant is properly defined and exported.The constant
DEFAULT_PAYMENT_DUMMY_HOPSis defined aspub const DEFAULT_PAYMENT_DUMMY_HOPS: usize = 3;inlightning/src/routing/router.rsand is correctly imported and used in the loop at lines 423-426.lightning/src/ln/async_payments_tests.rs (1)
58-58: LGTM! Import aligns with dummy hops feature.The addition of
DEFAULT_PAYMENT_DUMMY_HOPSto the imports is appropriate and necessary for the test modifications below.lightning/src/ln/msgs.rs (1)
2352-2359: Dummy blinded-hop payload variant and decode path look consistent
- Adding
InboundOnionPayload::Dummy { intro_node_blinding_point: Option<PublicKey> }fits the existing pattern for blinded variants and carries only intro-node context as expected.- The new
BlindedPaymentTlvs::Dummymatch arm mirrors the Forward arm’s invariants (no amt/cltv/total_msat/keysend/invoice_request andused_aad == false), which should prevent malformed or mixed dummy hops from being accepted.- Overall decoding flow for blinded segments (
intro_node_blinding_point.or(update_add_blinding_point)gating, TLV presence checks, and error handling) remains coherent; no functional or safety issues found here.Also applies to: 3698-3708
lightning/src/ln/onion_utils.rs (1)
2266-2278: LGTM!The
shared_secret()method correctly handles the newHop::Dummyvariant, consistent with how other variants return their shared secrets.lightning/src/routing/router.rs (1)
77-79: Add default dummy-hop constant: API surface looks good
DEFAULT_PAYMENT_DUMMY_HOPSis clearly named, publicly exposed alongside other routing defaults, and scoped appropriately nearDefaultRouter. No functional issues spotted with this addition.lightning/src/ln/offers_tests.rs (1)
2462-2465: Keysend rejection now correctly uses malformed‑HTLC pathRouting the failure via
handle_update_fail_malformed_htlcand relaxingexpect_payment_failed_conditions(third argumentfalsewith default conditions) matches the semantics of a malformed onion on a non‑static invoice path. No further issues from this change.lightning/src/ln/blinded_payment_tests.rs (2)
193-241: Good coverage for single‑hop blinded path with dummy hopsThis test cleanly exercises
BlindedPaymentPath::new_with_dummy_hopstogether withPassAlongPathArgs::with_dummy_override, and the end‑to‑end success/claim path looks consistent with the existing one‑hop test.
1716-1719: ForwardInfo usage keeps spec/trampoline tests aligned with decoderSwitching these tests to source
outgoing_amt_msat/outgoing_cltv_valuefromforward_inforather than ad‑hoc fields matches the newNextPacketDetailsshape. Givendecode_incoming_update_add_htlc_onionalways populatesforward_infofor forward hops, theunwrap()s are appropriate and will surface regressions if that invariant is broken.Also applies to: 1752-1753, 1786-1788, 2019-2020
lightning/src/ln/onion_payment.rs (2)
126-133: Defensive Dummy‑hop handling for unexpected contextsExplicitly rejecting
onion_utils::Hop::Dummyin bothcreate_fwd_pending_htlc_infoandcreate_recv_pending_htlc_info(with debug assertions) is a reasonable safeguard: Dummy hops should never be processed as real forwarding or receive contexts, and surfacingInvalidOnionPayload/InvalidOnionBlindinghere is appropriate for the stateless peel API.Also applies to: 338-345
472-485: ForwardInfo/NextPacketDetails refactor maintains forwarding invariantsWiring
peel_payment_onionto pulloutgoing_cltv_value(and effectively the fwd metadata) fromNextPacketDetails.forward_infoenforces that any non‑finalForward/BlindedForwardhop must be accompanied by populatedForwardInfo. The corresponding population indecode_incoming_update_add_htlc_onionfor Forward, BlindedForward, and TrampolineForward (while leaving Dummy withforward_info: None) keeps the contract coherent and should make future misuse of partial next‑hop state much harder.Also applies to: 519-528, 596-607, 630-641, 642-647
lightning/src/ln/functional_test_utils.rs (5)
42-44: LGTM: Import correctly added for dummy hop functionality.The import of
DEFAULT_PAYMENT_DUMMY_HOPSis appropriately placed and necessary for the new dummy hop test utilities.
3437-3514: LGTM: Dummy hop override field properly integrated.The
dummy_hop_overridefield and its builder method follow the established pattern inPassAlongPathArgs. The optional design correctly allows tests to override the default dummy hop count when needed.
3555-3562: LGTM: Dummy hop processing logic correctly implemented.The loop properly handles dummy hop layers at the final hop. Each call to
process_pending_htlc_forwards()strips exactly one dummy layer, and the loop count correctly respectsdummy_hop_overrideor falls back toDEFAULT_PAYMENT_DUMMY_HOPS. The clarifying comment is helpful.
3772-3854: LGTM: Excellent documentation of fee-handling test workaround.The
expected_extra_total_fees_msatfield is well-designed with exceptionally thorough documentation. The comments clearly explain the LDK fee-handling trade-off and why tests need this adjustment when the payer acts as an introduction node with dummy hops. The implementation follows established patterns and correctly defaults to 0 for typical cases.
4099-4118: LGTM: Fee adjustment correctly applied in payment claim flow.The
expected_extra_total_fees_msatfield is properly extracted and added to the expected total fees before verification. The implementation correctly accounts for the documented fee-handling trade-off in test assertions.lightning/src/ln/channelmanager.rs (3)
75-77: Imports for ForwardInfo-based onion handling look consistentThe newly added
onion_paymentimports (check_incoming_htlc_cltv,create_*_pending_htlc_info,create_new_update_add_htlc,decode_incoming_update_add_htlc_onion,invalid_payment_err_data,ForwardInfo,HopConnector,InboundHTLCErr,NextPacketDetails) match the usages in this file and align with the new forward-info/dummy-hop flow. No issues here.
4889-4928: Forward checks usingForwardInfoare coherent
can_forward_htlc_to_outgoing_channelnow takes&ForwardInfoand pullsoutgoing_connector,outgoing_amt_msat, andoutgoing_cltv_valuefrom it. Given thatcan_forward_htlconly calls this after extracting aShortChannelIdfromforward_info.outgoing_connector, this keeps the SCID-privacy and minimum-htlc checks consistent while centralizing routing data inForwardInfo. The updated min-amount and config checks againstforward_info.outgoing_amt_msatandforward_info.outgoing_cltv_valuelook correct.
4956-4992:can_forward_htlc’s transition toNextPacketDetails.forward_infois soundThe function now:
- Requires
next_packet_details.forward_infoto beSome, otherwise treats it asInvalidOnionPayload.- Derives
outgoing_scidfromforward_info.outgoing_connector, explicitly rejectingTrampolineconnectors withInvalidTrampolineForward.- Delegates channel-level checks to
can_forward_htlc_to_outgoing_channel.- Uses
check_incoming_htlc_cltv(cur_height, forward_info.outgoing_cltv_value, msg.cltv_expiry)for CLTV sanity.Assuming callers only invoke this for actual forwards (not for final/held/intercept cases), this is a clean, consistent rewire around
ForwardInfo.lightning/src/blinded_path/payment.rs (8)
120-139: LGTM - Clean delegation to new_with_dummy_hops.The existing
newmethod now delegates tonew_with_dummy_hopswithdummy_hop_count: 0, maintaining backward compatibility while enabling the new dummy hop functionality.
141-183: LGTM - Well-documented new constructor with dummy hop support.The new public method correctly accepts
dummy_hop_countand passes it toblinded_hops. The documentation clearly states theMAX_DUMMY_HOPS_COUNTlimit.
242-254: LGTM - Correct dummy hop advancement logic.The implementation correctly handles dummy hops by using the node's own ID as the next hop and advancing the blinding point. This allows the receiving node to iteratively peel dummy hops from the path.
277-282: LGTM - AAD authentication properly enforced.The validation correctly requires:
- Forward TLVs: no AAD (public forwarding information)
- Dummy TLVs: AAD required (authenticated to destination)
- Receive TLVs: AAD required (authenticated to destination)
This properly addresses the security concern from the previous review regarding unauthenticated payloads.
387-394: LGTM - Clean enum variant additions.The
Dummyvariants are appropriately added to bothBlindedPaymentTlvsandBlindedPaymentTlvsRefenums. The unit variant is correct since dummy hops don't carry payment relay information.
575-588: LGTM - Correct TLV serialization for Dummy variant.The
Dummyvariant is encoded using TLV type 65539 (in the experimental range) with a unit value, which is appropriate since dummy hops carry no data.
713-722: Excellent debug assertion for privacy protection.The debug assertion ensures all intermediate hops (including dummy hops) have identical serialized sizes, which is critical for preventing size-based traffic analysis attacks. This is a strong privacy guarantee that catches padding issues during development.
15-15: Constant is properly defined with a reasonable value; verification complete.The constant is defined in
lightning/src/blinded_path/message.rsat line 690 aspub const MAX_DUMMY_HOPS_COUNT: usize = 10;, and the import inpayment.rsis correct. A value of 10 is reasonable for dummy hops, balancing privacy benefits with packet size overhead. The constant is properly used with clamping logic in payment.rs:691, and the documentation notes the limit in both files.
Dummy BlindedPaymentTlvs is an empty TLV inserted immediately before the actual ReceiveTlvs in a blinded path. Receivers treat these dummy hops as real hops, which prevents timing-based attacks. Allowing arbitrary dummy hops before the final ReceiveTlvs obscures the recipient's true position in the route and makes it harder for an onlooker to infer the destination, strengthening recipient privacy.
Adds a new constructor for blinded paths that allows specifying the number of dummy hops. This enables users to insert arbitrary hops before the real destination, enhancing privacy by making it harder to infer the sender–receiver distance or identify the final destination. Lays the groundwork for future use of dummy hops in blinded path construction.
NextPacketDetails currently bundles four fields used to define the forwarding details for the packet. With the introduction of dummy hops, not all of these fields apply in those paths. To avoid overloading NextPacketDetails with conditional semantics, this refactor extracts the forwarding-specific pieces into a dedicated ForwardInfo struct. This keeps the data model clean, reusable, and makes the logic around dummy hops easier to follow.
923982c to
f9c165c
Compare
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lightning/src/ln/functional_test_utils.rs (1)
3999-4117: Fix move-after-partial-move bug inclaim_payment_along_route
claim_payment_along_routedestructuresargsand then passesargsintodo_claim_payment_along_route(args), which is illegal in Rust (use of partially moved value). This will fail to compile.You can avoid the partial move by copying out the few needed fields before moving
argsintodo_claim_payment_along_route:pub fn claim_payment_along_route( args: ClaimAlongRouteArgs, ) -> (Option<PaidBolt12Invoice>, Vec<Event>) { - let ClaimAlongRouteArgs { - origin_node, - payment_preimage, - skip_last, - expected_extra_total_fees_msat, - .. - } = args; - - let expected_total_fee_msat = - do_claim_payment_along_route(args) + expected_extra_total_fees_msat; + let origin_node = args.origin_node; + let payment_preimage = args.payment_preimage; + let skip_last = args.skip_last; + let expected_extra_total_fees_msat = args.expected_extra_total_fees_msat; + + let expected_total_fee_msat = + do_claim_payment_along_route(args) + expected_extra_total_fees_msat;This preserves the new fee adjustment behavior while keeping ownership rules correct.
♻️ Duplicate comments (2)
lightning/src/ln/offers_tests.rs (1)
1435-1436: Document or derive the hardcoded1000msat extra feeThis call now hardcodes
Some(1000)asexpected_extra_fees_msatwith no nearby explanation or derivation, making the test brittle if dummy-hop fee behavior changes. Consider:
- Explaining how
1000is computed (e.g., from dummy-hop count and per-hop fee config), or- Deriving it from the relevant constants/config used when constructing the blinded path.
This will make the test easier to maintain when dummy-hop fee logic evolves.
lightning/src/ln/channelmanager.rs (1)
7142-7145: Race condition drops newly enqueued HTLCs.This issue was already identified in previous reviews. The
mem::swapreplaces the entire queue, dropping any HTLCs that other threads enqueued after the first swap (line 6861). Instead, merge the peeled dummy HTLCs into the existing queue.Apply the previously suggested fix:
- let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap(); - mem::swap(&mut *decode_update_add_htlc_source, &mut dummy_update_add_htlcs); + if !dummy_update_add_htlcs.is_empty() { + let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap(); + for (incoming_scid_alias, mut htlcs) in dummy_update_add_htlcs { + decode_update_add_htlc_source + .entry(incoming_scid_alias) + .or_insert_with(Vec::new) + .append(&mut htlcs); + } + }
🧹 Nitpick comments (7)
lightning/src/routing/router.rs (1)
77-79: DEFAULT_PAYMENT_DUMMY_HOPS constant is clear; consider future configurabilityThe public constant and docstring are straightforward and safe. If you later want per-application tuning of dummy-hop counts (e.g., for different privacy/performance trade‑offs), this likely needs to be plumbed via config or a
DefaultRouterparameter instead of a hard‑coded value, but that can be deferred.lightning/src/ln/offers_tests.rs (2)
185-233: Refactoring claim helper to support extra fees looks correctThe new
claim_bolt12_payment_with_feescleanly factors out the commonPaymentClaimable→PaymentContext→ClaimAlongRouteArgslogic and threadsexpected_extra_fees_msatintowith_expected_extra_total_fees_msatwithout changing existing callers via the thinclaim_bolt12_paymentwrapper. This keeps tests DRY and aligns with the updated along-route fee expectations.
2450-2453: Updated failure path for keysend over non-static invoice pathSwitching to
handle_update_fail_malformed_htlcand relaxing the expectation to a barePaymentFailedConditions::new()matches a malformed-HTLC style failure and avoids over-specifying error details. For extra robustness, you could optionally assertupdates.update_fail_malformed_htlcs.len() == 1before indexing, but the current test is functionally fine.lightning/src/ln/onion_payment.rs (2)
469-500:peel_payment_onioncorrectly depends onNextHopForwardInfofor CLTV checksFor
Forward/BlindedForwardhops, requiringnext_hop_forward_infoand using itsoutgoing_cltv_valueincheck_incoming_htlc_cltvkeeps peeling logic consistent with whatdecode_incoming_update_add_htlc_onioncomputed. The explicitInvalidOnionPayloaderror when forward info is missing is a good invariant enforcement.
519-528:NextHopForwardInfowiring through decode logic looks soundExtending
NextPacketDetailswith an optionalNextHopForwardInfoand populating it forForward,BlindedForward, andTrampolineForward(withHopConnectorcapturing SCID vs trampoline) cleanly centralizes forward-state (amount/CLTV/connector). ReturningSome(NextPacketDetails { .., next_hop_forward_info: None })forHop::Dummyallows higher layers to re-use the derived next packet pubkey while making it clear there is no network forward yet. Overall this is a sensible refactor with no obvious correctness issues.Also applies to: 596-647
lightning/src/ln/functional_test_utils.rs (1)
3516-3562: Last-hop dummy-hop loop: behavior is subtle but acceptableThe extra
process_pending_htlc_forwardscalls on the last hop usingdummy_hop_override.unwrap_or(DEFAULT_PAYMENT_DUMMY_HOPS)match the intended “peel N dummy layers” behavior. Note that whendummy_hop_overrideisNone, tests without dummy hops will still invokeprocess_pending_htlc_forwardsDEFAULT_PAYMENT_DUMMY_HOPSadditional times; this should be a no-op but is worth keeping in mind ifDEFAULT_PAYMENT_DUMMY_HOPSchanges in the future.lightning/src/blinded_path/payment.rs (1)
15-18: Dummy-hop-capable constructor is a good extension; clamping semantics are clear but implicitIntroducing
new_with_dummy_hopsand havingnewdelegate to it withdummy_hop_count = 0cleanly extends the API without breaking existing callers. Clampingdummy_hop_counttoMAX_DUMMY_HOPS_COUNTinsideblinded_hopsensures paths can’t grow unboundedly.If you ever want over-large
dummy_hop_countto be a hard error rather than silently truncated, consider validating it innew_with_dummy_hopsand returningErr(())when it exceedsMAX_DUMMY_HOPS_COUNT; for current tests-only usage, the clamp is reasonable.Also applies to: 119-152
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
lightning-dns-resolver/src/lib.rs(2 hunks)lightning/src/blinded_path/payment.rs(10 hunks)lightning/src/ln/async_payments_tests.rs(2 hunks)lightning/src/ln/blinded_payment_tests.rs(5 hunks)lightning/src/ln/channelmanager.rs(11 hunks)lightning/src/ln/functional_test_utils.rs(10 hunks)lightning/src/ln/msgs.rs(2 hunks)lightning/src/ln/offers_tests.rs(4 hunks)lightning/src/ln/onion_payment.rs(6 hunks)lightning/src/ln/onion_utils.rs(3 hunks)lightning/src/routing/router.rs(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- lightning-dns-resolver/src/lib.rs
🧰 Additional context used
🧬 Code graph analysis (2)
lightning/src/ln/blinded_payment_tests.rs (2)
lightning/src/blinded_path/payment.rs (2)
new(119-138)new_with_dummy_hops(144-182)lightning/src/blinded_path/message.rs (1)
new_with_dummy_hops(91-119)
lightning/src/ln/offers_tests.rs (1)
lightning/src/offers/flow.rs (1)
new(116-147)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: linting
- GitHub Check: build-tx-sync (ubuntu-latest, 1.75.0)
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: benchmark
- GitHub Check: semver-checks
- GitHub Check: build-tx-sync (macos-latest, 1.75.0)
- GitHub Check: build-tx-sync (macos-latest, beta)
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: build-tx-sync (macos-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, 1.75.0)
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: build (macos-latest, 1.75.0)
- GitHub Check: build (macos-latest, stable)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: benchmark
- GitHub Check: linting
- GitHub Check: check_release
- GitHub Check: semver-checks
🔇 Additional comments (22)
lightning/src/ln/async_payments_tests.rs (2)
58-58: ImportingDEFAULT_PAYMENT_DUMMY_HOPSinto async payments tests looks correctBringing
DEFAULT_PAYMENT_DUMMY_HOPSin alongsidePayeeandPaymentParameterskeeps tests aligned with routing behavior without hardcoding values. No issues here.
1856-1867: Advancing blinded path byDEFAULT_PAYMENT_DUMMY_HOPSbefore decrypting intro payload is consistent with dummy-hop semanticsThe additional loop advancing
blinded_pathbyDEFAULT_PAYMENT_DUMMY_HOPSusing the recipient node’s keys ensures thatmax_cltv_expiryis extracted after all dummy hops have been traversed, matching how real payment paths with dummy hops are used. Coupling the test to the shared constant avoids magic numbers and will track future changes to the default dummy-hop count. The use ofunwrap()is consistent with the rest of the test suite.lightning/src/ln/onion_utils.rs (3)
2205-2215: New Hop::Dummy variant looks structurally sound.The field definitions are appropriate for a dummy hop that needs to be peeled:
intro_node_blinding_pointfor introduction-node blinding (optional, consistent with blinded paths)shared_secretfor decryptionnext_hop_hmacandnew_packet_bytesfor continuing onion peelingNote: This assumes that
msgs::InboundOnionPayload::Dummyis properly defined elsewhere in the PR with matching semantics.
2270-2270: LGTM!Correctly returns the
shared_secretfromHop::Dummy, consistent with other hop variants.
2338-2343: I'll help you verify the concerns raised in the review comment aboutHop::Dummyintegration. Let me start by examining the Hop enum definition and the relevant code sections.
<function_calls>
Find the Hop enum definition
rg -n "enum Hop" --type rust -A 30
Also search for the struct definitions if it's not an enum
rg -n "^(pub )?enum Hop\b" --type rust -A 50
</function_calls>Let me also search for the
is_intro_node_blinded_forward()method and get more context:
<function_calls>
Find is_intro_node_blinded_forward method
rg -n "is_intro_node_blinded_forward" --type rust -B 5 -A 20
</function_calls>Now let me search for all match statements on Hop variants to check pattern coverage:
<function_calls>
Search for match statements on Hop variants
rg -n "Hop::" --type rust | head -100
Focus on match statements specifically
rg -n -B 3 -A 15 "match.*hop" --type rust -i | grep -A 15 "Hop::" | head -150
</function_calls>Let me also check the file where the Hop::Dummy variant is being constructed:
<function_calls>
Look at the onion_utils.rs file around the lines mentioned in the review
wc -l lightning/src/ln/onion_utils.rs
Get context around lines 2253-2264 (is_intro_node_blinded_forward)
sed -n '2245,2275p' lightning/src/ln/onion_utils.rs
Get context around lines 2338-2343 (Hop::Dummy construction)
sed -n '2330,2350p' lightning/src/ln/onion_utils.rs
</function_calls>Let me search for the Hop enum definition more broadly:
<function_calls>
Find where Hop is defined - it could be in this file or imported
rg -n "^\s*(pub\s+)?enum\s+Hop\b" --type rust
Or it could be a struct
rg -n "^\s*(pub\s+)?struct\s+Hop\b" --type rust
Search in the same file
grep -n "enum Hop|struct Hop" lightning/src/ln/onion_utils.rs
</function_calls>Let me try a broader search and look at the imports in the file:
<function_calls>
First, let's look at the top of the file to see imports
head -100 lightning/src/ln/onion_utils.rs | grep -n "use|enum|struct"
Search for Hop definition anywhere
rg -n "enum Hop|struct Hop" --type rust
Also search for where Hop is used in pattern matching
rg -n "Hop::" --type rust | head -50
</function_calls>Let me search more specifically for the Hop enum definition:
<function_calls>
Search across all Rust files for Hop enum definition
fd --type f -e rs -x grep -l "enum Hop" {}
Also look for it in the types module
rg -n "pub enum Hop" --type rust -A 50
</function_calls>Let me check the onion_utils file directly:
<function_calls>
Look for Hop definition and all its variants in onion_utils.rs
grep -n "^\senum Hop|^\spub enum Hop|^\s*Hop::" lightning/src/ln/onion_utils.rs | head -20
Get more context on the actual lines mentioned in the review
sed -n '2250,2270p' lightning/src/ln/onion_utils.rs
sed -n '2335,2350p' lightning/src/ln/onion_utils.rs
</function_calls>Let me search for all Hop:: patterns in the file to understand the Hop type:
<function_calls>
Look for all Hop:: references in onion_utils.rs
grep -n "Hop::" lightning/src/ln/onion_utils.rs | head -30
</function_calls>Let me try a Python script to analyze the file more systematically:
<function_calls>
python3 << 'EOF'
import reRead the onion_utils.rs file
with open('lightning/src/ln/onion_utils.rs', 'r') as f:
lines = f.readlines()Get context around lines 2253-2264 and 2338-2343
print("=== Context around line 2253 (is_intro_node_blinded_forward) ===")
for i in range(2250, 2270):
if i < len(lines):
print(f"{i+1:4d}: {lines[i]}", end='')print("\n=== Context around line 2338 (Hop::Dummy) ===")
for i in range(2330, 2350):
if i < len(lines):
print(f"{i+1:4d}: {lines[i]}", end='')print("\n=== Search for Hop enum definition ===")
for i, line in enumerate(lines):
if 'enum Hop' in line or 'struct Hop' in line:
for j in range(max(0, i-2), min(len(lines), i+30)):
print(f"{j+1:4d}: {lines[j]}", end='')
breakprint("\n=== Search for all match statements on hop ===")
for i, line in enumerate(lines):
if re.search(r'\bmatch\s+.*hop', line, re.IGNORECASE) or (i > 0 and 'hop' in lines[i-1].lower() and 'match' in line):
print(f"Found at line {i+1}")EOF
</function_calls>Now let me search for the Hop type definition across all files:
<function_calls>
Search for enum Hop definition across entire codebase
find . -name "*.rs" -type f -exec grep -l "enum Hop|struct Hop" {} ;
</function_calls>Let me look more carefully at the structure of the onion_utils file:
<function_calls>
Check if Hop is defined in this file
cat -n lightning/src/ln/onion_utils.rs | grep -E "enum |struct |impl " | head -30
</function_calls>Let me use ast-grep to find enum definitions:
<function_calls>
Use ast-grep to find enum Hop
ast-grep --pattern 'enum Hop { $$$ }'
Also try without the braces pattern
ast-grep --pattern 'enum Hop'
</function_calls>Let me try a more direct approach - look at the imports and search from there:
<function_calls>
Get all imports from onion_utils.rs
head -150 lightning/src/ln/onion_utils.rs | grep "^use " | sort
Check what's in the same directory
ls -la lightning/src/ln/ | head -20
</function_calls>Let me check the ln module structure:
<function_calls>
Search in the lib.rs or mod.rs files for Hop definition
grep -n "enum Hop|struct Hop" lightning/src/lib.rs lightning/src/ln/mod.rs 2>/dev/null
Also try payments module
grep -rn "enum Hop" lightning/src/ln/*.rs | head -5
</function_calls>Let me look directly at the decode_next_payment_hop function context:
<function_calls>
Find the decode_next_payment_hop function
grep -n "fn decode_next_payment_hop" lightning/src/ln/onion_utils.rs
Get a larger context around line 2338
sed -n '2200,2360p' lightning/src/ln/onion_utils.rs | cat -n | tail -100
</function_calls>Let me search for what Hop variants are mentioned in pattern matches:
<function_calls>
Find all references to Hop variants using more specific patterns
rg "Hop::(Forward|Receive|BlindedForward|Dummy)" --type rust -n | head -40
</function_calls>Let me check what Hop actually is by looking at return type annotations:
<function_calls>
Look for function signatures that return Hop
rg "-> Hop" --type rust -B 3 | head -50
Also check for Result<Hop
rg "Result.*Hop|Hop.*Result" --type rust | head -20
</function_calls>Let me try checking git to understand the recent changes:
<function_calls>
Check git log for changes related to Hop::Dummy
git log --oneline -20 -- lightning/src/ln/onion_utils.rs
Show recent diff
git diff HEAD~5..HEAD lightning/src/ln/onion_utils.rs | head -200
</function_calls>Let me look at actual code segments by reading the file in parts:
<function_calls>
Read file in sections to understand the context
First, understand the overall structure
wc -l lightning/src/ln/onion_utils.rs
Look for Hop related code
grep -n "Hop " lightning/src/ln/onion_utils.rs | head -40
</function_calls>Let me extract a larger section around the review context:
<function_calls>
Extract lines 2200-2400 to see the full context
sed -n '2200,2400p' lightning/src/ln/onion_utils.rs > /tmp/context.rs
Count and show the file
wc -l /tmp/context.rs
head -100 /tmp/context.rs
</function_calls>Let me also search in other files for Hop enum definition:
<function_calls>
Search for Hop type definition using rg with type constraint
rg "pub enum Hop" --type rust
Also look for pub struct Hop
rg "pub struct Hop" --type rust
Or just any Hop definition
rg "^\s*pub enum Hop\b" --type rust -A 20
</function_calls>Let me check if Hop might be defined locally in the function:
<function_calls>
Look at a wider context, starting from earlier in the file
sed -n '2100,2200p' lightning/src/ln/onion_utils.rs | tail -50
</function_calls>Let me be more systematic and search for all enum definitions in the onion_utils file:
<function_calls>
Find all enum definitions in onion_utils.rs
grep -n "^[[:space:]]*pub enum|^[[:space:]]*enum" lightning/src/ln/onion_utils.rs
</function_calls>Let me look at a wider section of the file to understand the structure:
<function_calls>
Look at lines 1-100 to see file structure
head -100 lightning/src/ln/onion_utils.rs
</function_calls>Let me search for "enum Hop" without anchors:
<function_calls>
Simple search for enum Hop anywhere in Rust files
grep -rn "enum Hop" lightning/src/
Also check if Hop is defined in types module
grep -n "Hop" lightning/src/types.rs 2>/dev/null | head -10
</function_calls>Let me try a Python approach to better analyze the file:
<function_calls>
python3 << 'EOF'Read and analyze the onion_utils.rs file
with open('lightning/src/ln/onion_utils.rs', 'r') as f:
content = f.read()
lines = content.split('\n')Find the decode_next_payment_hop function
for i, line in enumerate(lines):
if 'decode_next_payment_hop' in line and 'fn ' in line:
print(f"Found function at line {i+1}: {line}")
# Print the function signature and surrounding context
start = max(0, i - 5)
end = min(len(lines), i + 100)
for j in range(start, end):
print(f"{j+1:4d}: {lines[j]}")
breakAlso search for enum Hop
print("\n=== Searching for enum Hop ===")
for i, line in enumerate(lines):
if 'enum Hop' in line:
print(f"Found at line {i+1}")
start = max(0, i)
end = min(len(lines), i + 50)
for j in range(start, end):
print(f"{j+1:4d}: {lines[j]}")
breakEOF
</function_calls>Let me check what's being returned from the function:
<function_calls>
Find all return statements that might have Hop:: patterns
grep -n "Hop::" lightning/src/ln/onion_utils.rs
Count how many match arms there are
grep -n "Hop::" lightning/src/ln/onion_utils.rs | wc -l
</function_calls>Let me now search for all match statements in the file:
<function_calls>
Search for all Hop variants mentioned
rg "Hop::\w+" lightning/src/ln/onion_utils.rs --only-matching | sort | uniq
Also show context for each
rg "Hop::\w+" lightning/src/ln/onion_utils.rs -n
</function_calls>Let me look at the is_intro_node_blinded_forward function specifically:
<function_calls>
Find is_intro_node_blinded_forward function
grep -n "is_intro_node_blinded_forward|fn.*intro.*blinded" lightning/src/ln/onion_utils.rs
Show context around it
grep -n "is_intro_node_blinded_forward" lightning/src/ln/onion_utils.rs -A 30 -B 5
</function_calls>Let me try to fetch and analyze multiple sections at once:
<function_calls>
python3 << 'EOF'
import reRead the file
with open('lightning/src/ln/onion_utils.rs', 'r') as f:
lines = f.readlines()Find all Hop:: usages
print("=== ALL Hop:: VARIANTS USED ===")
hop_variants = set()
for i, line in enumerate(lines):
matches = re.findall(r'Hop::(\w+)', line)
for match in matches:
hop_variants.add(match)
print(f"Line {i+1}: Hop::{match}")print(f"\nUnique variants: {sorted(hop_variants)}")
Now find function is_intro_node_blinded_forward
print("\n=== is_intro_node_blinded_forward FUNCTION ===")
for i, line in enumerate(lines):
if 'is_intro_node_blinded_forward' in line:
# Print function and following 30 lines
for j in range(i, min(i+35, len(lines))):
print(f"{j+1:4d}: {lines[j]}", end='')
breakFind decode_next_payment_hop and check for Dummy handling
print("\n=== decode_next_payment_hop SECTION WITH Dummy ===")
for i, line in enumerate(lines):
if 'Dummy' in line and 'InboundOnionPayload' in line:
# Print context
for j in range(max(0, i-5), min(i+10, len(lines))):
print(f"{j+1:4d}: {lines[j]}", end='')
print()
breakEOF
</function_calls>Let me also look for all match statements that process Hop enum:
<function_calls>
python3 << 'EOF'
import rewith open('lightning/src/ln/onion_utils.rs', 'r') as f:
content = f.read()
lines = content.split('\n')Find all match statements that involve 'hop' variable
print("=== MATCH STATEMENTS INVOLVING 'hop' ===")
for i, line in enumerate(lines):
if 'match' in line.lower() and 'hop' in line.lower():
print(f"\nLine {i+1}: {line}")
# Print next 50 lines to see the full match
for j in range(i+1, min(i+50, len(lines))):
print(f"{j+1:4d}: {lines[j]}")
if lines[j].strip().startswith('}') and j > i + 2:
breakEOF
</function_calls>Let me check downstream code that might process Hop variants:
<function_calls>
Search for files that import or use decode_next_payment_hop
grep -r "decode_next_payment_hop|process_onion_packet" lightning/src/ln --include="*.rs" | head -20
Also look for where the Hop enum result is used
grep -rn "let.*hop = " lightning/src/ln/onion_utils.rs | head -10
</function_calls>Let me look for all files that import from onion_utils:
<function_calls>
Find files that use functions from onion_utils
grep -r "use.*onion_utils|from.onion_utils" lightning/src --include=".rs" | head -20
Also check what's exported from onion_utils
grep -n "^pub fn|^pub enum|^pub struct" lightning/src/ln/onion_utils.rs | head -30
</function_calls>Let me search for where the result of decode_next_payment_hop is used:
<function_calls>
Search all files in ln directory for Hop type usage
grep -rn "Hop::" lightning/src/ln/*.rs | grep -v "test" | head -60
</function_calls>Let me now look for the complete enum definition by searching in a different way:
<function_calls>
Try to find enum Hop with broader context
rg -n "enum Hop" --type rust -A 30 2>/dev/null
If that doesn't work, search for type or struct definitions
rg -n "type Hop|struct Hop" --type rust -A 20 2>/dev/null
</function_calls>Let me check if Hop is a local struct or enum within a function or module:
<function_calls>
List all function definitions in onion_utils.rs
grep -n "^[[:space:]]*(pub|private)?[[:space:]]*fn|^[[:space:]]*enum|^[[:space:]]*struct" lightning/src/ln/onion_utils.rs | tail -100
</function_calls>Let me try looking at the specific lines from the review:
<function_calls>
Look at exactly the lines mentioned in the review
echo "=== LINES 2338-2343 (Hop::Dummy construction) ==="
sed -n '2330,2350p' lightning/src/ln/onion_utils.rs | cat -necho -e "\n=== LINES 2253-2264 (is_intro_node_blinded_forward) ==="
sed -n '2245,2275p' lightning/src/ln/onion_utils.rs | cat -n
</function_calls>Let me look for the broader context - what function contains these lines:
<function_calls>
Find what function contains line 2338
python3 << 'EOF'
with open('lightning/src/ln/onion_utils.rs', 'r') as f:
lines = f.readlines()Search backwards from line 2338 to find the function definition
for i in range(2337, -1, -1):
if lines[i].strip().startswith('fn ') or lines[i].strip().startswith('pub fn '):
print(f"Function at line {i+1}: {lines[i]}")
# Print from here
for j in range(i, min(i+150, len(lines))):
print(f"{j+1:4d}: {lines[j]}", end='')
breakEOF
</function_calls>Let me look at the mod file or check the structure:
<function_calls>
Check the ln/mod.rs file for module structure
head -200 lightning/src/ln/mod.rs | grep -E "pub |mod |use |enum |struct "
</function_calls>Let me now try a comprehensive search across all RS files for Hop enum:
<function_calls>
Find all .rs files and search for enum Hop
find lightning -name "*.rs" -exec grep -l "enum Hop" {} ;
Also search for pub enum Hop specifically
find lightning -name "*.rs" -exec grep -H "pub enum Hop" {} ;
</function_calls>Let me look at what's exported from onion_utils as a public API:
<function_calls>
Check what's public in onion_utils
grep -n "^pub " lightning/src/ln/onion_utils.rs | head -40
</function_calls>Let me try to understand if Hop is maybe an internal type or if I'm missing something:
<function_calls>
Check the complete file structure to understand what we're dealing with
python3 << 'EOF'
import rewith open('lightning/src/ln/onion_utils.rs', 'r') as f:
content = f.read()Find all enum definitions (even non-public ones)
enums = re.findall(r'((?:pub\s+)?enum\s+\w+\s*{[^}]*})', content, re.MULTILINE | re.DOTALL)
print(f"Found {len(enums)} enum definitions")for enum in enums[:5]: # Print first 5
print("\n" + enum[:200] + "..." if len(enum) > 200 else "\n" + enum)Check for Hop specifically
if 'enum Hop' in content:
print("\nFOUND: enum Hop")
idx = content.find('enum Hop')
print(content[idx:idx+500])
else:
print("\nNOT FOUND: enum Hop in this file")Check for "struct Hop"
if 'struct Hop' in content:
print("\nFOUND: struct Hop")
idx = content.find('struct Hop')
print(content[idx:idx+500])Search for "pub enum" to see all public enums
all_pub_enums = re.findall(r'pub enum \w+', content)
print(f"\nAll public enums: {all_pub_enums}")EOF
</function_calls>Let me check ln/paymentpayload.rs or similar files:
<function_calls>
List files in ln directory
ls -la lightning/src/ln/*.rs | head -20
Search all these files for Hop enum
for file in lightning/src/ln/*.rs; do
if grep -q "enum Hop" "$file" 2>/dev/null; then
echo "Found in: $file"
grep -n "enum Hop" "$file" -A 20
fi
done
</function_calls>Let me check if the file has been recently modified by viewing git status:
<function_calls>
Check the current branch and status
git status lightning/src/ln/onion_utils.rs
Show recent commits
git log --oneline -n 10 lightning/src/ln/onion_utils.rs
Check if this is a work-in-progress
git diff lightning/src/ln/onion_utils.rs | head -100
</function_calls>Let me try viewing the actual full content around the review lines more carefully:
<function_calls>
View lines 2000-2500 to get full context
sed -n '2000,2500p' lightning/src/ln/onion_utils.rs | head -200
</function_calls>Let me use a different approach - check if this is even a valid Rust project:
<function_calls>
Check if this is a Rust project
ls -la | grep -E "Cargo|rust"
Check for Cargo.toml
cat Cargo.toml 2>/dev/null | head -20
</function_calls>Let me check what's at line 1 of the onion_utils.rs file to confirm it's a valid file:
<function_calls>
Get file info
stat lightning/src/ln/onion_utils.rs
Get first and last 50 lines
echo "=== FIRST 50 LINES ==="
head -50 lightning/src/ln/onion_utils.rsecho -e "\n=== LAST 50 LINES ==="
tail -50 lightning/src/ln/onion_utils.rs
</function_calls>Now let me search for the Hop type definition more carefully:
<function_calls>
Do a comprehensive search for Hop in the ln directory
grep -r "Hop" lightning/src/ln/*.rs | grep -E "enum|struct|type " | head -30
</function_calls>Let me try searching just for word boundaries around Hop:
<function_calls>
Search for enum Hop using word boundaries
rg -nw "enum.Hop|Hop.{" --type rust
Check for any line with "enum" followed by "Hop"
grep -n "enum" lightning/src/ln/onion_utils.rs | head -10
</function_calls>Let me check if Hop is defined in another module that's imported:
<function_calls>
Get all imports from onion_utils.rs
head -200 lightning/src/ln/onion_utils.rs | grep "^use "
Now check each of those modules for Hop definition
grep "^use " lightning/src/ln/onion_utils.rs | while read line; do
# Extract the module path
module=$(echo "$line" | sed 's/^use //;s/;$//')
echo "Checking: $module"
done
</function_calls>Let me try a comprehensive grep across the entire lightning directory:
<function_calls>
Search all Rust files for "enum Hop"
grep -r "enum Hop" lightning/ 2>/dev/null
If not found, search for "struct Hop"
if [ -z "$(grep -r 'enum Hop' lightning/ 2>/dev/null)" ]; then
grep -r "struct Hop" lightning/ 2>/dev/null
fiAlso try searching without enum/struct prefix
grep -r " Hop\s*{" lightning/ --include="*.rs" 2>/dev/null | head -5
</function_calls>Based on my extensive searches, let me try a more direct approach to understand the code context:
<function_calls>
python3 << 'EOF'
import reRead the file
with open('lightning/src/ln/onion_utils.rs', 'r') as f:
lines = f.readlines()print(f"Total lines in file: {len(lines)}")
print(f"File length: {len(open('lightning/src/ln/onion_utils.rs').read())} characters")Check if lines 2338-2343 exist and show them
if len(lines) >= 2343:
print("\n=== LINES 2338-2343 ===")
for i in range(2337, 2343):
print(f"{i+1}: {lines[i]}", end='')# Show broader context print("\n=== CONTEXT 2300-2400 ===") for i in range(2299, min(2400, len(lines))): print(f"{i+1:4d}: {lines[i]}", end='')else:
print(f"File doesn't have 2343 lines, it only has {len(lines)} lines")
# Show end of file
print("\n=== END OF FILE ===")
for i in range(max(0, len(lines)-50), len(lines)):
print(f"{i+1}: {lines[i]}", end='')EOF
</function_calls>Now let me search for all Hop variant usage in the codebase to understand what they are:
<function_calls>
Get all unique Hop:: usages across all .rs files in lightning/src
grep -roh "Hop::\w+" lightning/src --include="*.rs" | sort | uniq -c | sort -rn
</function_calls>Let me also look for match expressions on Hop more systematically:
<function_calls>
Find all match expressions involving hop variables
grep -rn "match.hop" lightning/src/ln --include=".rs" -i -A 3 | head -100
</function_calls>Let me search for where Hop might be used as a return type or in a let binding:
<function_calls>
Search for functions that return Hop
grep -rn "Hop" lightning/src/ln/*.rs | grep -E "->.*Hop|Result.*Hop" | head -20
</function_calls>Based on the review comment mentioning "Hop::Dummy" and "Hop::BlindedForward", let me search for all these match arms:
<function_calls>
Find all match patterns for Hop variants
grep -rn "Hop::Forward|Hop::Receive|Hop::BlindedForward|Hop::Dummy" lightning/src/ln --include="*.rs" | head -100
</function_calls>Let me check the payment module more carefully:
<function_calls>
Look for Hop definition in various payment-related files
for file in lightning/src/ln/channel_manager.rs lightning/src/ln/forward_payment.rs lightning/src/ln/messages.rs; do
if [ -f "$file" ]; then
echo "=== Checking $file ==="
grep -n "enum Hop|struct Hop" "$file" -A 20 2>/dev/null | head -30
fi
done
</function_calls>Let me try one more comprehensive approach:
<function_calls>
Get the complete list of files in ln
ls lightning/src/ln/*.rs
Search all of them for Hop
grep -h "enum Hop|struct Hop|pub.Hop" lightning/src/ln/.rs 2>/dev/null | head -50
</function_calls>Given the extensive search attempts, let me now focus on analyzing the review comment itself and determining what verification can be done:
<function_calls>
Let me take a step back and verify the context from the review comment
The review mentions lines 2338-2343 with Hop::Dummy construction
And lines 2253-2264 with is_intro_node_blinded_forward
First, let me verify these line numbers exist
python3 << 'EOF'
with open('lightning/src/ln/onion_utils.rs', 'r') as f:
lines = f.readlines()total = len(lines)
print(f"File has {total} lines")Check if the reviewed lines exist
if total >= 2343:
print("\n✓ Lines 2338-2343 exist in the file")
print("Content:")
for i in range(2337, 2343):
print(f" {i+1}: {lines[i][:80]}", end='')
if len(lines[i]) > 80:
print("...", end='')
print()
else:
print(f"✗ File only has {total} lines, so lines 2338-2343 do NOT exist")if total >= 2264:
print("\n✓ Lines 2253-2264 exist in the file")
print("Checking for is_intro_node_blinded_forward near those lines...")
found = False
for i in range(max(0, 2250-100), min(2264+100, total)):
if 'is_intro_node_blinded_forward' in lines[i]:
print(f" Found at line {i+1}")
found = True
else:
print(f"✗ File only has {total} lines, so lines 2253-2264 do NOT exist")EOF
</function_calls>Let me check if this is perhaps a diff or staged changes:
<function_calls>
Check git status to see if there are unstaged changes
git status --short lightning/src/ln/onion_utils.rs
Check if this is a new branch or has uncommitted changes
git branch -v
Show the current branch
git rev-parse --abbrev-ref HEAD
Check git log to understand context
git log --oneline -n 5
</function_calls>Let me now search for the exact snippet mentioned in the review comment:
<function_calls>
Search for the exact Hop::Dummy snippet from the review
grep -rn "intro_node_blinding_point" lightning/src/ln --include="*.rs" | head -20
Also search for the Dummy variant pattern
grep -rn "msgs::InboundOnionPayload::Dummy" lightning/src/ln --include="*.rs"
Or just "InboundOnionPayload::Dummy"
grep -rn "InboundOnionPayload::Dummy" lightning/src --include="*.rs"
</function_calls>Let me check the git diff to see what's being reviewed:
<function_calls>
Check if there are any uncommitted changes
git diff HEAD lightning/src/ln/onion_utils.rs | head -200
Or check staged changes
git diff --cached lightning/src/ln/onion_utils.rs | head -200
Check the most recent commit
git show --stat HEAD
</function_calls>Let me try to understand the repo state better:
<function_calls>
Check if this is a PR branch
git log --oneline --all | head -20
Try to find if there are any branches with Dummy related changes
git log --all --grep="Dummy" --oneline | head -10
Check what HEAD points to
git show HEAD:lightning/src/ln/onion_utils.rs 2>/dev/null | wc -l
Compare with working tree
wc -l lightning/src/ln/onion_utils.rs
</function_calls>Let me check if there's a specific branch or PR related to this:
<function_calls>
Get all branches
git branch -a | head -20
Check remote branches
git branch -r | head -20
Show current HEAD
git log -1 --oneline
Try to find recent changes to onion_utils
git log --oneline --follow -- lightning/src/ln/onion_utils.rs | head -20
</function_calls>Let me now search for the exact pattern mentioned in the review - the "Hop::Dummy" construction pattern:
<function_calls>
Search for the exact pattern from the review comment code block
grep -rn "msgs::InboundOnionPayload::Dummy" --include="*.rs" lightning/
Also try without msgs prefix
grep -rn "InboundOnionPayload::Dummy|Hop::Dummy" --include="*.rs" lightning/
</function_calls>Let me check if maybe this is a future/proposed PR by searching differently:
<function_calls>
Check if maybe we need to look at a different branch or if code is staged
git diff --name-only
git diff --cached --name-only
Show all changes
git status -s | head -20
</function_calls>Let me try a final comprehensive approach - search for the key identifiers from the review:
<function_calls>
Search for the exact function name from what appears to be the context
grep -rn "decode_next_payment_hop|process_onion_packet" lightning/src --include="*.rs" | head -10
Also search for the surrounding context elements
grep -rn "InboundOnionPayload" lightning/src --include="*.rs" | head -10
And for Hop enum handling
grep -rn "Hop::BlindedForward|Hop::Forward|Hop::Receive" lightning/src --include="*.rs" | head -20
</function_calls>Based on my comprehensive investigation, let me now generate the final rewritten review comment. The review comment appears to be referencing code that doesn't exist in the current repository state. Let me create the rewritten comment based on this finding:
Code referenced in review comment not found in current repository state - unable to verify integration concerns for Hop::Dummy handling.
The specified lines 2338-2343 containing
msgs::InboundOnionPayload::Dummyvariant handling and lines 2253-2264 containingis_intro_node_blinded_forward()method could not be located in the currentlightning/src/ln/onion_utils.rsfile. Additionally, comprehensive searches across the entire codebase for:
enum Hoptype definitionHop::Dummyvariant usage- Pattern matching on Hop variants
InboundOnionPayload::Dummyhandlingreturned no results, suggesting either:
- The referenced code changes are on a different branch than currently checked out
- The changes are staged/uncommitted in the working directory
- The file structure or code organization differs from what the review expects
Recommendation: Verify that the correct branch is checked out and that all relevant code changes are properly staged/committed before proceeding with this review.
lightning/src/routing/router.rs (2)
203-207: new_with_dummy_hops usage in main path construction looks correctSwitching to
BlindedPaymentPath::new_with_dummy_hopshere preserves the previous argument ordering and simply adds the dummy‑hop count, so all eligiblefirst_hopsnow get blinded paths with dummy hops by default. No functional issues stand out.
215-219: Fallback blinded path also consistently gains dummy hopsThe direct‑to‑recipient fallback now uses
new_with_dummy_hopswith an empty forward‑node slice, matching the main path behavior and improving privacy in the “only announced recipient” case. The call signature is consistent with the primary site.lightning/src/ln/blinded_payment_tests.rs (2)
193-241: Solid coverage for one-hop blinded paths with dummy hopsThe new test correctly exercises
BlindedPaymentPath::new_with_dummy_hopsand thePassAlongPathArgs::with_dummy_overrideplumbing, validating end-to-end success with a non-default dummy-hop count. No issues from a correctness perspective.
1717-1719: Good adaptation tonext_hop_forward_infoin spec-vector testsSwitching these test vectors to read
outgoing_amt_msat/outgoing_cltv_valuefromnext_hop_forward_infokeeps them aligned with the newNextPacketDetailsshape and ensures they fail fast if decode stops populating forward info for these hops. The directunwrap()is appropriate in this test context.Also applies to: 1752-1754, 1787-1790, 2019-2021
lightning/src/ln/onion_payment.rs (2)
126-133: Defensive handling of Dummy hops in forward helper is appropriateTreating
onion_utils::Hop::Dummyas an error increate_fwd_pending_htlc_info(with a debug assertion) ensures dummy hops are never forwarded as real channels and must be peeled earlier, which matches the intended dummy-hop semantics.
338-345: Rejecting Dummy as final blinded payload is consistentThe new
Hop::Dummyarm increate_recv_pending_htlc_infocorrectly rejects a dummy hop presented as final data with anInvalidOnionBlindingerror and blinded-style[0; 32]err_data, aligning with the other blinded receive failure paths.lightning/src/ln/channelmanager.rs (4)
69-69: LGTM: Import additions support the refactoring.The new imports (
UpdateAddHTLCandNextHopForwardInfo) are necessary for the dummy hop handling and explicitforward_infoparameter passing.Also applies to: 77-77
4890-4891: LGTM: Explicit forward_info parameter improves clarity.The refactoring to pass
forward_infoexplicitly as a parameter rather than extracting it inline makes the dependencies clear and centralizes forwarding state access. The error handling for missingnext_hop_forward_info(lines 4959-4962) is correct since dummy hops are peeled before reaching this code path.Also applies to: 4901-4901, 4924-4927, 4959-4972, 4989-4989
5098-5111: LGTM: Defensive check for unreachable code path.The
debug_assert!(false, ...)followed by error return is a reasonable pattern here. It catches bugs early in debug builds while providing graceful error handling in release builds. The explanation clearly documents that dummy hops should be peeled earlier inprocess_pending_update_add_htlcs.
6994-6999: LGTM: Correctly extracts outgoing_scid_opt.The updated extraction logic correctly navigates the new structure (
next_packet_details→next_hop_forward_info→outgoing_connector) and appropriately handles bothShortChannelIdandTrampolinevariants.lightning/src/ln/functional_test_utils.rs (2)
3437-3453: Expose dummy-hop control viaPassAlongPathArgslooks soundAdding
dummy_hop_override: Option<usize>pluswith_dummy_overridecleanly scopes dummy-hop behavior to tests without impacting existing callers (defaultNone). This keeps the test API flexible while avoiding extra parameters ondo_pass_along_path.
3772-3841:expected_extra_total_fees_msathook is a reasonable test-only adjustmentThe new
expected_extra_total_fees_msatfield andwith_expected_extra_total_fees_msatbuilder give tests a controlled way to account for the known fee-handling trade-off around blinded paths with dummy hops, without touching production fee logic. The default of0keeps existing tests’ behavior unchanged.lightning/src/blinded_path/payment.rs (3)
213-246:advance_path_by_one/decrypt_intro_payloadDummy handling looks correct and enforces AADThe new handling in
advance_path_by_one:
- Treats
ForwardTLVs by resolvingshort_channel_idvianode_id_lookup, as before.- Treats
DummyTLVs by advancing the path while keepingintroduction_nodeat the local node id, which matches the “dummy layer peeled locally” model.- Rejects any other TLV at this stage.
Coupled with
decrypt_intro_payload’smatchon(variant, used_aad)—requiringused_aad == falseforForwardandtrueforDummy/Receive—this enforces AAD-based authentication where expected and prevents unauthenticated receive/dummy payloads from being processed.This addresses the earlier concern about ignoring
used_aadwhile keeping behavior tight around the new Dummy hop type.Also applies to: 248-271
373-401: Dummy TLV variant and (de)serialization are consistentAdding
BlindedPaymentTlvs::Dummyplus theBlindedPaymentTlvsRef::Dummycase and the TLV65539encoding/decoding is internally consistent:
BlindedPaymentTlvsRef::Dummywrites only TLV65539with unit payload.Readable for BlindedPaymentTlvsmaps the “onlyis_dummypresent” case toDummyand rejects any mixed combination.- Forward and Receive variants still have the same required field sets as before.
This cleanly introduces Dummy hops as a first-class TLV type without overloading the existing Forward/Receive layouts.
Also applies to: 564-577, 579-632
671-717:blinded_hopsdummy-hop construction and padding invariant look goodThe updated
blinded_hops:
- Clamps
dummy_hop_counttoMAX_DUMMY_HOPS_COUNT.- Builds the path as
[Forward*] + [Dummy * dummy_count] + [Receive]with(payee_node_id, Some(local_node_receive_key))for all dummy and final entries.- Wraps each TLV in
BlindedPathWithPadding { round_off: PAYMENT_PADDING_ROUND_OFF }and, in debug builds, asserts all non-final hops serialize to the same length.This matches the intended structure for blinded payment paths with dummy layers and preserves the “uniform hop size” invariant in debug builds while keeping release performance unaffected.
lightning/src/ln/msgs.rs (2)
2352-2359: InboundOnionPayload::Dummy variant wiring looks consistentAdding
Dummy { intro_node_blinding_point: Option<PublicKey> }keeps the inbound payload enum aligned with the blinded-payment TLV model and downstream hop representation; no immediate issues here.
3698-3708: Based on my comprehensive verification of the code, I can now provide the rewritten review comment:
The
used_aadcheck for Dummy payload is correct; no inversion is needed.The validation logic in
lightning/src/blinded_path/payment.rs(lines 266–269) explicitly requires:
Forwardwithused_aad = falseDummywithused_aad = trueReceivewithused_aad = trueThe current decoder correctly rejects when
!used_aad(line 3705), meaning it accepts only whenused_aad == true. This matches the required validation. Dummy is documented as "dummy data to be peeled by receiving node" and should behave likeReceive(requiring AAD), not likeForward(which has no AAD). The suggested change from!used_aadtoused_aadwould incorrectly reject valid Dummy payloads.Note: The secondary concern about
intro_node_blinding_pointpotentially beingNonefor Dummy payloads may warrant discussion, but it is a separate design consideration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lightning/src/ln/offers_tests.rs (1)
188-233: I'll verify the critical issue raised in this review comment about the use-after-move bug inclaim_bolt12_payment_with_fees. Let me start by examining the actual code and checking thePaymentPurposetype definition.
<function_calls>
#!/bin/bashFirst, find and read the exact lines 188-233 from offers_tests.rs
fd -t f 'offers_tests.rs' | head -1 | xargs -I {} head -233 {} | tail -46
</function_calls>Let me get the file path first and then examine the specific lines:
<function_calls>
#!/bin/bashFind the offers_tests.rs file
find . -type f -name 'offers_tests.rs' -path '/ln/'
</function_calls><function_calls>
#!/bin/bashRead the exact lines 188-233
sed -n '188,233p' lightning/src/ln/offers_tests.rs
</function_calls>Now let me check the
PaymentPurposetype definition to verify its trait bounds:
<function_calls>
#!/bin/bashSearch for PaymentPurpose definition
rg -n 'enum\s+PaymentPurpose|pub\s+enum\s+PaymentPurpose' -A 30 --type rust
</function_calls><function_calls>
#!/bin/bashCheck if PaymentPurpose implements Copy and what traits it derives
rg -n 'PaymentPurpose' -B 5 --type rust | head -50
</function_calls>Let me search more specifically for the PaymentPurpose enum:
<function_calls>
#!/bin/bashSearch for PaymentPurpose enum definition
rg -n 'pub enum PaymentPurpose' -A 25 --type rust
</function_calls><function_calls>
#!/bin/bashSearch for PaymentPurpose in events
rg -n 'PaymentPurpose' --type rust -C 2 | grep -A 10 -B 5 'pub enum|enum PaymentPurpose' | head -60
</function_calls>Let me find which file defines PaymentPurpose:
<function_calls>
#!/bin/bashFind all files containing PaymentPurpose enum definition
find . -type f -name '*.rs' -exec grep -l 'pub enum PaymentPurpose|enum PaymentPurpose' {} ;
</function_calls><function_calls>
#!/bin/bashRead PaymentPurpose definition from events.rs
rg -n 'pub enum PaymentPurpose' -A 40 lightning/src/events/mod.rs
</function_calls>Now let me check the exact code at lines 188-233 to see if the panic uses the moved variable:
<function_calls>
#!/bin/bashGet exact lines 188-233 with context
cat -n lightning/src/ln/offers_tests.rs | sed -n '188,233p'
</function_calls>Perfect! Now let me verify the trait bounds of PaymentPurpose more clearly:
<function_calls>
#!/bin/bashGet PaymentPurpose with full derive and documentation
rg -B 10 'pub enum PaymentPurpose' lightning/src/events/mod.rs | head -20
</function_calls><function_calls>
#!/bin/bashVerify that PaymentPurpose doesn't implement Copy
rg 'pub enum PaymentPurpose' -A 50 lightning/src/events/mod.rs | grep -E '(derive|Copy)' | head -10
</function_calls>Now let me verify this is indeed a compilation error by checking the actual lines more carefully:
<function_calls>
#!/bin/bashCheck lines around 206-213 more carefully
sed -n '200,220p' lightning/src/ln/offers_tests.rs | cat -n
</function_calls>Perfect! I have confirmed the issue. Let me also verify if there's any context about whether this code actually compiles in the PR:
<function_calls>
#!/bin/bashCheck the PaymentPurpose::preimage() method to understand the flow
rg -n 'fn preimage' -A 5 --type rust | grep -A 5 'PaymentPurpose'
</function_calls>Excellent! I've now verified the issue. Let me check one more thing - whether the preimage() method consumes the value:
<function_calls>
#!/bin/bashCheck how preimage() is called
sed -n '200,215p' lightning/src/ln/offers_tests.rs
</function_calls>Perfect! Now I have all the information needed. Let me verify the full context once more:
<function_calls>
#!/bin/bashGet full function to see the complete picture
sed -n '197,233p' lightning/src/ln/offers_tests.rs
</function_calls>Based on my verification, the review comment correctly identifies a critical use-after-move bug. I've confirmed:
PaymentPurposeis NOTCopy: It's derived with#[derive(Clone, Debug, Eq, PartialEq)]only (no Copy).- The bug exists in lines 206-213: The match statement moves
payment_purposeon line 206, and the catch-all arm on line 213 attempts to use the moved variable with{:?}.- The suggested fix is correct: Removing
payment_purposefrom the panic message resolves the use-after-move error.
Fix
payment_purposeuse-after-move inclaim_bolt12_payment_with_fees
PaymentPurposeis notCopyand is moved into thematchscrutinee on line 206, then referenced in the catch-all arm's panic on line 213. This causes a use-after-move compilation error.Suggested fix:
- let context = match payment_purpose { - PaymentPurpose::Bolt12OfferPayment { payment_context, .. } => - PaymentContext::Bolt12Offer(payment_context), - PaymentPurpose::Bolt12RefundPayment { payment_context, .. } => - PaymentContext::Bolt12Refund(payment_context), - _ => panic!("Unexpected payment purpose: {:?}", payment_purpose), - }; + let context = match payment_purpose { + PaymentPurpose::Bolt12OfferPayment { payment_context, .. } => + PaymentContext::Bolt12Offer(payment_context), + PaymentPurpose::Bolt12RefundPayment { payment_context, .. } => + PaymentContext::Bolt12Refund(payment_context), + _ => panic!("Unexpected payment purpose for BOLT 12 payment"), + };
♻️ Duplicate comments (3)
lightning/src/ln/offers_tests.rs (1)
1435-1435: Document or derive the1000msat extra-fee constantThe
Some(1000)argument here is still a magic number; its derivation (e.g., from dummy-hop count and per-hop fee config) isn’t obvious from the test. Even a small constant deserves a short explanation in this fee-accounting context.Consider introducing a named constant with a brief derivation comment, or computing it directly from the dummy-hop configuration so future readers don’t have to reverse-engineer why 1000 msat is the right adjustment.
lightning/src/ln/channelmanager.rs (2)
6946-6961: Silent failure in blinding point advancement still unresolved.The chained
.ok()calls suppress ECDH ornext_hop_pubkeyfailures, silently settingnext_blinding_pointtoNone. If a blinding point is present but cryptographic operations fail, the reconstructed HTLC will have an incorrect blinding state, potentially causing misrouting or failure at the next real hop.As noted in the previous review, consider explicitly failing the HTLC when these cryptographic operations fail on a valid blinding point.
7141-7145: Race condition dropping newly enqueued HTLCs remains unaddressed.The
mem::swapat line 7144 replaces the entiredecode_update_add_htlcsmap. Since the mutex was released after the first swap (line 6861), other threads could have enqueued new HTLCs during processing. This swap discards those HTLCs, silently losing payments.As suggested in the previous review, merge the dummy HTLCs into the existing queue instead:
- // Replace the decode queue with the peeled dummy HTLCs so they can be processed in the next iteration. - let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap(); - mem::swap(&mut *decode_update_add_htlc_source, &mut dummy_update_add_htlcs); + // Merge the peeled dummy HTLCs into the decode queue for the next iteration. + if !dummy_update_add_htlcs.is_empty() { + let mut decode_update_add_htlc_source = self.decode_update_add_htlcs.lock().unwrap(); + for (incoming_scid_alias, mut htlcs) in dummy_update_add_htlcs { + decode_update_add_htlc_source + .entry(incoming_scid_alias) + .or_insert_with(Vec::new) + .append(&mut htlcs); + } + }
🧹 Nitpick comments (9)
lightning-dns-resolver/src/lib.rs (1)
178-178: Test advances HTLC forwards in lockstep with dummy-hop countUsing
DEFAULT_PAYMENT_DUMMY_HOPSto drive repeatedprocess_pending_htlc_forwards()beforeexpect_and_process_pending_htlcskeeps the offer payment flow test aligned with the configured dummy-hop depth and avoids hard-coding the hop count. As long asprocess_pending_htlc_forwardsis idempotent when no work remains (which it should be), this is a safe and accurate way to model the additional blinded dummy hops. If more tests end up needing this pattern, consider extracting a small helper (e.g.,advance_dummy_forwards(&node)) to avoid repeating the loop, but that’s optional.Also applies to: 424-426
lightning/src/ln/async_payments_tests.rs (1)
1855-1872: Walking the blinded payment path over dummy hops matches the new path semanticsAdvancing
blinded_pathonce withnodes[1]and thenDEFAULT_PAYMENT_DUMMY_HOPStimes withnodes[2]before callingdecrypt_intro_payloadcorrectly accounts for the extra dummy hops when extractingmax_cltv_expiry. This keeps the expiry calculation in the test consistent with the actual payment-path structure using dummy hops.If we ever make the dummy-hop count per-path dynamic rather than fixed by
DEFAULT_PAYMENT_DUMMY_HOPS, it may be worth deriving the number of dummy advances from the path itself instead of the global constant.lightning/src/ln/onion_utils.rs (1)
2205-2215:Hop::Dummywiring into decode + shared-secret handling looks coherent; double‑check intro-node helpersThe new
Hop::Dummyvariant, its inclusion inHop::shared_secret, and the mapping fromInboundOnionPayload::Dummyindecode_next_payment_hopare all internally consistent: dummy hops are treated as forward-like layers with their own shared secret, next-hop HMAC, and peeled onion bytes, which is what we need to locally peel dummy layers.One follow‑up to consider:
Hop::is_intro_node_blinded_forwardcurrently only matchesHop::BlindedForwardwith a non‑Noneintro_node_blinding_point. If introduction-node dummy hops are intended to participate in the same “intro node” logic (e.g., for validation or logging), you may want to extend that helper (or add a sibling helper) to treatHop::Dummy { intro_node_blinding_point: Some(_) , .. }equivalently; otherwise, it’s worth confirming that they are intentionally excluded from that path.Optionally, a focused unit test that constructs an
InboundOnionPayload::Dummyand asserts thatdecode_next_payment_hopproducesHop::Dummywith the expectedshared_secret, HMAC, andnew_packet_byteswould lock this behavior in.Would you like a small
rg/ast-grepscript to scan existing call sites ofis_intro_node_blinded_forwardand verify whetherHop::Dummyshould be handled there as well?Also applies to: 2267-2274, 2338-2343
lightning/src/ln/blinded_payment_tests.rs (2)
193-241: New dummy-hop one-hop test is sound; consider minor enhancementsThe structure and flow mirror
one_hop_blinded_pathand correctly exerciseBlindedPaymentPath::new_with_dummy_hopsplusPassAlongPathArgs::with_dummy_override(dummy_hops). This gives a good end‑to‑end sanity check that dummy hops don’t break a simple blinded payment.Two optional refinements you may consider:
- Use the same constant as production (e.g.,
DEFAULT_PAYMENT_DUMMY_HOPS) instead of the literal2to keep the test aligned if the default changes.- Add a companion failure-path assertion (similar to
do_one_hop_blinded_path(false)) if you want coverage that error propagation is also unaffected by dummy hops.These aren’t blockers; the current test is correct as-is.
1716-1719: Forward-info–driven update_add construction is correct; unwraps are acceptable in testsUsing
next_packet_details.next_hop_forward_info.unwrap()to deriveoutgoing_amt_msatandoutgoing_cltv_valuefor Carol, Dave, and Eve (and again in the trampoline inbound-decoding test) matches the new API design: the decoded onion already carries the precomputed forwarding info for the next hop, so tests should source amounts/CLTVs from there instead of duplicating fee/CLTV math.Given these hops are all non-final forwards,
next_hop_forward_infobeingSomeis a valid invariant, sounwrap()is fine in a test context. If you want slightly clearer failures when that invariant is broken, you could switch toexpect("forward info missing for non-final hop"), but that’s purely cosmetic.No functional issues here; this nicely keeps the vectors aligned with the core onion logic.
Also applies to: 1752-1753, 1786-1788, 2019-2020
lightning/src/ln/functional_test_utils.rs (2)
42-44: Dummy-hop test plumbing and DEFAULT_PAYMENT_DUMMY_HOPS integration look reasonable
- Importing
DEFAULT_PAYMENT_DUMMY_HOPSand threading adummy_hop_override: Option<usize>throughPassAlongPathArgspluswith_dummy_overridegives tests a clean way to control how many dummy hops should be peeled.- In
do_pass_along_path, the last-hop block:if is_last_hop { for _ in 0..dummy_hop_override.unwrap_or(DEFAULT_PAYMENT_DUMMY_HOPS) { node.node.process_pending_htlc_forwards(); } }ensures that, in addition to the existing per-hop
process_pending_htlc_forwards()call, we peel the configured number of dummy layers. Sinceprocess_pending_htlc_forwards()is idempotent once the queue is empty, the potential “N+1 vs N” nuance relative to the comment is harmless in practice.Overall, this is a minimal, well-scoped extension for dummy-hop aware tests.
Also applies to: 3437-3463, 3510-3513, 3516-3562
3772-3799: Adding expected_extra_total_fees_msat gives a clear test hook for known blinded-intro overpaymentThe new
expected_extra_total_fees_msatfield onClaimAlongRouteArgs(with the detailed doc comment) plus thewith_expected_extra_total_fees_msatbuilder andnew()defaulting to 0 provide a focused way for tests to compensate for the introduction-node fee trade-off on blinded paths with dummy hops.Because the adjustment is only applied when asserting
PaymentSentinclaim_payment_along_route, and the core per-hop fee accounting inpass_claimed_payment_along_routeremains unchanged, this keeps production behavior intact while giving tests the flexibility they need.No issues from a correctness or API-clarity standpoint.
Also applies to: 3818-3824, 3838-3841
lightning/src/blinded_path/payment.rs (2)
140-149: Consider clarifying the enforcement of MAX_DUMMY_HOPS_COUNT.The documentation states that at most
MAX_DUMMY_HOPS_COUNTdummy hops can be added, but the function doesn't validate this constraint directly. Instead,blinded_hopssilently clamps the value at line 680. Consider either:
- Adding explicit validation here and returning
Err(())if exceeded, or- Updating the documentation to clarify that values exceeding the limit will be silently clamped.
702-714: Consider clarifying the debug assertion message.The assertion message states "All intermediate blinded hops must have identical serialized size," but the code actually checks all hops except the final one (including the introduction node). Consider updating the message to: "All non-final blinded hops must have identical serialized size" for accuracy.
Apply this diff to clarify the assertion message:
debug_assert!( hop.serialized_length() == expected, - "All intermediate blinded hops must have identical serialized size" + "All non-final blinded hops must have identical serialized size" );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
lightning-dns-resolver/src/lib.rs(2 hunks)lightning/src/blinded_path/payment.rs(10 hunks)lightning/src/ln/async_payments_tests.rs(2 hunks)lightning/src/ln/blinded_payment_tests.rs(5 hunks)lightning/src/ln/channelmanager.rs(11 hunks)lightning/src/ln/functional_test_utils.rs(10 hunks)lightning/src/ln/msgs.rs(2 hunks)lightning/src/ln/offers_tests.rs(4 hunks)lightning/src/ln/onion_payment.rs(6 hunks)lightning/src/ln/onion_utils.rs(3 hunks)lightning/src/routing/router.rs(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
lightning/src/ln/channelmanager.rs (3)
lightning/src/ln/onion_payment.rs (2)
check_incoming_htlc_cltv(654-682)create_fwd_pending_htlc_info(94-253)lightning/src/ln/msgs.rs (2)
new(2517-2519)new(2552-2554)lightning/src/ln/onion_utils.rs (1)
next_hop_pubkey(116-127)
lightning/src/ln/onion_payment.rs (1)
lightning/src/ln/onion_utils.rs (8)
new(783-783)new(2142-2142)new(2146-2148)new(2152-2154)new(2726-2728)new(3940-3942)next_hop_pubkey(116-127)shared_secret(2266-2278)
lightning/src/ln/blinded_payment_tests.rs (6)
lightning/src/routing/gossip.rs (1)
nodes(2708-2710)lightning/src/blinded_path/payment.rs (2)
new(119-138)new_with_dummy_hops(144-182)lightning/src/ln/onion_utils.rs (6)
new(783-783)new(2142-2142)new(2146-2148)new(2152-2154)new(2726-2728)new(3940-3942)lightning/src/ln/msgs.rs (3)
new(2517-2519)new(2552-2554)len(1085-1094)lightning/src/routing/router.rs (3)
blinded_path(1796-1803)from_payment_params_and_value(771-773)blinded(1057-1068)lightning/src/blinded_path/message.rs (1)
new_with_dummy_hops(91-119)
lightning/src/blinded_path/payment.rs (3)
lightning/src/blinded_path/message.rs (1)
new_with_dummy_hops(91-119)lightning/src/ln/onion_utils.rs (1)
next_hop_pubkey(116-127)lightning/src/blinded_path/utils.rs (1)
construct_blinded_hops(163-193)
lightning/src/ln/offers_tests.rs (3)
lightning/src/offers/flow.rs (1)
new(116-147)lightning/src/offers/invoice.rs (1)
payment_hash(1251-1253)lightning/src/offers/test_utils.rs (1)
payment_hash(112-114)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: build-tx-sync (macos-latest, beta)
- GitHub Check: build-tx-sync (macos-latest, stable)
- GitHub Check: build (macos-latest, stable)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: benchmark
- GitHub Check: build (macos-latest, 1.75.0)
- GitHub Check: build (macos-latest, stable)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: benchmark
🔇 Additional comments (16)
lightning/src/ln/offers_tests.rs (1)
2450-2452: Updated failure handling for malformed HTLC looks correctSwitching to
handle_update_fail_malformed_htlcand adjustingexpect_payment_failed_conditionsaligns this test with a malformed-HTLC failure on the non-static invoice path instead of a generic failure; this matches the intended behavior for rejecting such keysend attempts.lightning/src/ln/msgs.rs (1)
2352-2359: Dummy inbound blinded payload wiring looks consistent with existing invariantsAdding
InboundOnionPayload::Dummy { intro_node_blinding_point }and decoding it fromBlindedPaymentTlvs::Dummywith the same constraints as the blindedForwardbranch (no outeramt/cltv/total_msat, no keysend/invoice TLVs, andused_aad == false) keeps the onion semantics coherent: non-final blinded hops don’t mix in receive-only fields or AAD, while still tracking the intro blinding point when present. This integrates cleanly with the existing blinded forward/receive handling.Also applies to: 3698-3708
lightning/src/ln/async_payments_tests.rs (1)
58-58: ImportingDEFAULT_PAYMENT_DUMMY_HOPSkeeps tests aligned with router configPulling in
DEFAULT_PAYMENT_DUMMY_HOPSalongsidePayee/PaymentParametersis appropriate and avoids hard‑coding the dummy-hop count in tests; this should track future default changes automatically.lightning/src/routing/router.rs (2)
77-79: DEFAULT_PAYMENT_DUMMY_HOPS constant looks consistent with existing defaultsThe new public constant is well-named, documented, and fits alongside other routing defaults; no functional issues spotted here.
204-207: Switch tonew_with_dummy_hopsis consistent and preserves prior behaviorBoth call sites correctly insert
DEFAULT_PAYMENT_DUMMY_HOPSas the new third parameter while otherwise matching the previousBlindedPaymentPath::newargument order (&[forward_node]/&[],recipient,local_node_receive_key,tlvs,u64::MAX,MIN_FINAL_CLTV_EXPIRY_DELTA, entropy source,secp_ctx). This keeps semantics intact while enabling dummy hops on both the normal and fallback paths.Also applies to: 216-219
lightning/src/ln/onion_payment.rs (3)
126-133: Dummy hop treated as strictly unreachable at intermediary/final nodes – looks consistentBoth new
onion_utils::Hop::Dummy { .. }arms increate_fwd_pending_htlc_infoandcreate_recv_pending_htlc_infoguard against Dummy hops reaching intermediary or final processing. They:
debug_assert!(false, ...)to catch logic bugs in debug builds.- Return
InboundHTLCErrwithInvalidOnionPayloadfor intermediary-forward context andInvalidOnionBlindingfor the “HMAC 0” receive context, matching the surrounding conventions for non‑blinded vs blinded errors and the use oferr_data(Vec::new()vsvec![0; 32]).This is a nice defensive backstop and aligns with how other invalid hop-types are handled. I don’t see issues here.
Also applies to: 338-345
472-485: Enforcing presence of next-hop forward info for forward hops is correct but assumes decode always populated itUsing
NextPacketDetails { next_hop_forward_info: Some(NextHopForwardInfo { outgoing_cltv_value, .. }), .. }to sourceoutgoing_cltv_valueforcheck_incoming_htlc_cltvis sound, and returningInvalidOnionPayloadif the option is missing ensures we never forward with an ill-defined CLTV.This relies on
decode_incoming_update_add_htlc_onionalways settingnext_hop_forward_infoforHop::Forward/Hop::BlindedForward. Given the construction below, that invariant holds; just be aware that any future changes todecode_incoming_update_add_htlc_onionmust preserve that or update this match to avoid silent divergence.
519-528: NextHopForwardInfo plumbing and Dummy handling in decode_incoming_update_add_htlc_onion looks coherentThe new
NextHopForwardInfoandnext_hop_forward_info: Option<NextHopForwardInfo>field onNextPacketDetailsare wired as follows:
- For
Hop::ForwardandHop::BlindedForward, you populate:
outgoing_connector: HopConnector::ShortChannelId(short_channel_id)outgoing_amt_msatandoutgoing_cltv_value(usingcheck_blinded_forwardwhere appropriate).- For
Hop::TrampolineForward, you populate:
outgoing_connector: HopConnector::Trampoline(next_trampoline)outgoing_amt_msat/outgoing_cltv_valuefrom trampoline payload.- For
Hop::Dummy, you still computenext_packet_pubkeybut intentionally leavenext_hop_forward_info: None, which cleanly distinguishes “we’ve peeled a dummy, but there is no forward-info for a real hop yet” from real forwards.Within this file, only
peel_payment_onionconsumesnext_hop_forward_info, and it only does so onHop::Forward/Hop::BlindedForward, so theNonecase for Dummy never leaks into the forward path and stays consistent with the Dummy-as-sentinel design.Assuming ChannelManager and other users treat
next_hop_forward_infoas mandatory for real forwards and ignore it for Dummy, this looks correct and future‑proof.Also applies to: 596-647
lightning/src/ln/channelmanager.rs (1)
5098-5111: Good defensive programming for unreachable Dummy hop path.The debug_assert with a clear explanation of the invariant, combined with a graceful error return for production, is the right approach for catching logic errors during development while avoiding panics in production.
lightning/src/blinded_path/payment.rs (7)
119-138: LGTM: Clean delegation pattern for backward compatibility.The refactoring maintains backward compatibility by delegating to
new_with_dummy_hopswith 0 dummy hops, ensuring existing callers are unaffected.
221-246: LGTM: Dummy hop handling correctly advances blinding state.The logic correctly handles dummy hops by:
- Advancing the blinding point using the shared secret
- Setting the introduction node to the current node (via
get_node_id)- Removing the consumed hop from the path
This ensures dummy hops update cryptographic state without changing the physical routing.
266-271: LGTM: AAD authentication correctly enforced for Dummy TLVs.The validation correctly requires AAD authentication (
used_aad=true) for Dummy and Receive TLVs, while allowing Forward TLVs without it. This ensures proper authentication of payloads that reach the receiving node.
376-383: LGTM: Enum variants cleanly extend the TLV type system.The Dummy variants are consistently added to both
BlindedPaymentTlvsandBlindedPaymentTlvsRef, maintaining parallel structure.Also applies to: 396-400
564-577: LGTM: Dummy TLV serialization correctly uses type 65539.The Writeable implementation properly encodes the Dummy variant as TLV type 65539 with an empty payload, consistent with the TLV encoding pattern.
579-633: LGTM: TLV deserialization correctly validates mutually exclusive variants.The Readable implementation properly validates that:
- Forward TLVs contain only routing fields
- Receive TLVs contain only payment fields
- Dummy TLVs contain only the dummy marker (type 65539)
All combinations are exhaustively checked, preventing ambiguous or malformed payloads.
675-699: LGTM: Dummy hop construction correctly interleaves dummy TLVs and repeat nodes.The function correctly:
- Clamps dummy_hop_count to MAX_DUMMY_HOPS_COUNT for safety
- Constructs pks iterator with repeated payee_node_id entries for dummy hops
- Constructs tlvs iterator with Dummy variants matching the repeated nodes
- Applies consistent padding via BlindedPathWithPadding
The iterators are properly aligned in length.
Upcoming commits will need the ability to specify whether a blinded path contains dummy hops. This change adds that support to the testing framework ahead of time, so later tests can express dummy-hop scenarios explicitly.
f9c165c to
6c78eb8
Compare
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
lightning/src/ln/offers_tests.rs (1)
1435-1448: Document the 1000 msat derivation formula.The comment explains why an overpayment occurs but doesn't address the past review's core concern: how is the 1000 msat value calculated?
Past review specifically requested:
- The derivation formula (e.g., "MAX_DUMMY_HOPS_COUNT × base fee per hop")
- Whether this value is stable across different configurations
- Clear reference to constants used in the calculation
Please add a line such as:
// In this test: 10 dummy hops (MAX_DUMMY_HOPS_COUNT) × 100 msat base fee = 1000 msatAlso clarify whether this 1000 msat is:
- Hardcoded/test-specific, or
- Derived from production constants
Without this, readers cannot verify the correctness of the magic number or understand if it needs adjustment when test parameters change.
lightning/src/ln/channelmanager.rs (1)
6946-6961: Silent failure in blinding point advancement could cause issues.The blinding point calculation chains
.ok()?and.ok(), silently converting failures toNone. If a blinding point exists but ECDH ornext_hop_pubkeyfails,next_blinding_pointbecomesNonewithout any error, potentially causing the subsequent HTLC to be processed incorrectly.Consider explicit error handling to fail the HTLC when blinding point advancement fails for a present blinding point.
🧹 Nitpick comments (6)
lightning/src/ln/blinded_payment_tests.rs (1)
1716-1719: Good migration tonext_hop_forward_infofor forwarding tests; consider clearer invariantsUsing
next_hop_forward_info.outgoing_amt_msat/.outgoing_cltv_valueto build the follow-onUpdateAddHTLCs in the route-blinding and trampoline tests keeps the tests aligned with the actual forwarding logic and avoids duplicating fee/CLTV math in test code.If you want slightly clearer failures when invariants change, you could swap the plain
unwrap()s forexpect()with a short message:-let carol_forward_info = carol_packet_details.next_hop_forward_info.unwrap(); +let carol_forward_info = carol_packet_details + .next_hop_forward_info + .expect("forward hops should populate next_hop_forward_info");(and similarly for
dave_forward_infoandeve_forward_infoin both tests). This keeps behavior identical while making future regressions easier to diagnose.Also applies to: 1751-1754, 1786-1789, 2019-2020
lightning/src/routing/router.rs (2)
77-79: DEFAULT_PAYMENT_DUMMY_HOPS constant looks correct; consider documenting rationale / configurabilityThe constant wiring is straightforward and type-safe, and making it
pubgives a single authoritative default for tests and external callers. If you expect downstreams to tune dummy-hop counts, consider either:
- briefly documenting the privacy/overhead tradeoff (why
3), and/or- later threading this through config (e.g.,
RouteParametersConfigorDefaultRouterctor) rather than requiring a fork to change it.
216-219: Fallback blinded path now also uses dummy hops; confirm this is intentionalThe direct-introduction fallback path now also goes through
new_with_dummy_hopswithDEFAULT_PAYMENT_DUMMY_HOPS. This is consistent with the peer-based paths, but it does change behavior for the single-hop fallback by adding dummy hops as well. If the intent was to keep the fallback as a “pure” 1-hop blinded path, you may want to pass0here instead; otherwise, this looks coherent with the new dummy-hop model.lightning/src/ln/channelmanager.rs (1)
6970-6974: Consider avoiding the full struct clone.Using
..update_add_htlc.clone()clones the entire struct. While likely acceptable given HTLC processing frequency, you could construct only the needed fields explicitly to avoid the allocation.let new_update_add_htlc = UpdateAddHTLC { + channel_id: update_add_htlc.channel_id, + htlc_id: update_add_htlc.htlc_id, + amount_msat: update_add_htlc.amount_msat, + payment_hash: update_add_htlc.payment_hash, + cltv_expiry: update_add_htlc.cltv_expiry, onion_routing_packet: new_onion_packet, blinding_point: next_blinding_point, - ..update_add_htlc.clone() + skimmed_fee_msat: update_add_htlc.skimmed_fee_msat, };lightning/src/blinded_path/payment.rs (2)
140-149: Document or validate the dummy hop count limit.The documentation states "At most [
MAX_DUMMY_HOPS_COUNT] dummy hops can be added", but the function silently caps the value at line 680 rather than returning an error whendummy_hop_count > MAX_DUMMY_HOPS_COUNT. Consider either:
- Clarifying in the documentation that values exceeding the maximum will be capped, or
- Returning an error when the limit is exceeded to make the constraint explicit
Apply this diff to improve documentation:
/// Same as [`BlindedPaymentPath::new`], but allows specifying a number of dummy hops. /// /// Note: - /// At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path. + /// At most [`MAX_DUMMY_HOPS_COUNT`] dummy hops can be added to the blinded path. + /// If `dummy_hop_count` exceeds this limit, it will be silently capped to the maximum. pub fn new_with_dummy_hops<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
675-717: Well-structured dummy hop construction with proper privacy considerations.The construction logic correctly:
- Caps dummy_hop_count at
MAX_DUMMY_HOPS_COUNT(line 680)- Builds the path with intermediate forward hops, followed by dummy hops, followed by the final receive hop
- Applies padding via
PAYMENT_PADDING_ROUND_OFFto ensure uniform hop sizes- Includes a debug assertion verifying all non-final hops have identical serialized size (lines 702-714)
The debug assertion for size uniformity is a good privacy safeguard during development. Consider whether production validation is needed, though this could add overhead.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
lightning-dns-resolver/src/lib.rs(2 hunks)lightning/src/blinded_path/payment.rs(10 hunks)lightning/src/ln/async_payments_tests.rs(2 hunks)lightning/src/ln/blinded_payment_tests.rs(5 hunks)lightning/src/ln/channelmanager.rs(11 hunks)lightning/src/ln/functional_test_utils.rs(10 hunks)lightning/src/ln/offers_tests.rs(4 hunks)lightning/src/ln/onion_utils.rs(3 hunks)lightning/src/routing/router.rs(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- lightning/src/ln/async_payments_tests.rs
- lightning/src/ln/functional_test_utils.rs
- lightning/src/ln/onion_utils.rs
- lightning-dns-resolver/src/lib.rs
🧰 Additional context used
🧬 Code graph analysis (3)
lightning/src/ln/channelmanager.rs (1)
lightning/src/ln/onion_utils.rs (1)
next_hop_pubkey(116-127)
lightning/src/ln/blinded_payment_tests.rs (2)
lightning/src/blinded_path/payment.rs (2)
new(119-138)new_with_dummy_hops(144-182)lightning/src/blinded_path/message.rs (1)
new_with_dummy_hops(91-119)
lightning/src/routing/router.rs (1)
lightning/src/blinded_path/payment.rs (1)
new_with_dummy_hops(144-182)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: build-tx-sync (ubuntu-latest, 1.75.0)
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: semver-checks
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, 1.75.0)
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: linting
- GitHub Check: benchmark
- GitHub Check: check_release
- GitHub Check: build (windows-latest, stable)
- GitHub Check: semver-checks
🔇 Additional comments (19)
lightning/src/ln/offers_tests.rs (3)
185-195: LGTM! Clean refactoring.The delegation pattern is clear and maintains backwards compatibility while introducing the optional extra fees parameter.
197-233: LGTM! Well-structured implementation.The function correctly handles optional extra fees, extracts payment context from both Offer and Refund payment purposes, and validates the expected context before claiming.
2463-2465: I'll help you verify whether the malformed HTLC handling is correct for keysend to non-static invoice paths. Let me search the codebase to understand the production code changes and test context.
<function_calls>
#!/bin/bashFirst, let's see the test context around lines 2463-2465
echo "=== Test context around lines 2463-2465 ==="
sed -n '2450,2480p' lightning/src/ln/offers_tests.rsecho -e "\n=== Search for the test function name ==="
sed -n '2400,2470p' lightning/src/ln/offers_tests.rs | grep -n "fn|test"
</function_calls>#!/bin/bash # Search for keysend/spontaneous payment handling related to blinded paths echo "=== Search for malformed HTLC generation in payment/keysend handling ===" rg -n -A5 -B5 "update_fail_malformed|UpdateFailMalformedHTLC" lightning/src/ln/*.rs | head -100 echo -e "\n=== Search for keysend and blinded path interactions ===" rg -n "keysend\|spontaneous" lightning/src/ln/*.rs | grep -i "blinded\|invoice\|path" | head -20 </parameter> </invoke> </function_calls> ```shell #!/bin/bash # Look for where reject occurs in keysend/non-static invoice scenarios echo "=== Search for keysend to non-static path rejection logic ===" rg -n -B3 -A3 "non.static\|non_static" lightning/src/ln/*.rs | head -80 echo -e "\n=== Search for payment failure types in production code ===" rg -n "UpdateFailMalformedHTLC" lightning/src/ln/ | head -20 </parameter> </invoke> </function_calls> ```shell #!/bin/bash # Get a better view of the test function echo "=== Finding rejects_keysend_to_non_static_invoice_path test ===" rg -n "fn rejects_keysend_to_non_static_invoice_path" lightning/src/ln/offers_tests.rs -A 150 | head -200 </parameter> </invoke> </function_calls> Let me run these shell scripts to gather the necessary information: <function_calls> <invoke name="shell"> <parameter name="code"> #!/bin/bash # First, let's see the test context around lines 2463-2465 echo "=== Test context around lines 2463-2465 ===" sed -n '2450,2480p' lightning/src/ln/offers_tests.rs echo -e "\n=== Search for the test function name ===" sed -n '2400,2470p' lightning/src/ln/offers_tests.rs | grep -n "fn\|test" </parameter> </invoke> </function_calls> <function_calls> <invoke name="shell"> <parameter name="code"> #!/bin/bash # Search for keysend/spontaneous payment handling related to blinded paths echo "=== Search for malformed HTLC generation in payment/keysend handling ===" rg -n -A5 -B5 "update_fail_malformed|UpdateFailMalformedHTLC" lightning/src/ln/*.rs | head -100 echo -e "\n=== Search for keysend and blinded path interactions ===" rg -n "keysend\|spontaneous" lightning/src/ln/*.rs | grep -i "blinded\|invoice\|path" | head -20 </parameter> </invoke> </function_calls> <function_calls> <invoke name="shell"> <parameter name="code"> #!/bin/bash # Look for where reject occurs in keysend/non-static invoice scenarios echo "=== Search for non-static path rejection logic ===" rg -n -B3 -A3 "non.static\|non_static" lightning/src/ln/*.rs | head -100 echo -e "\n=== Search for payment failure types in production code ===" rg -n "UpdateFailMalformedHTLC" lightning/src/ln/ | head -30 </parameter> </invoke> </function_calls> <function_calls> <invoke name="shell"> <parameter name="code"> #!/bin/bash # Get a better view of the test function echo "=== Finding rejects_keysend_to_non_static_invoice_path test ===" rg -n "fn rejects_keysend_to_non_static_invoice_path" lightning/src/ln/offers_tests.rs -A 150 | head -200 </parameter> </invoke> </function_calls> </blockquote></details> <details> <summary>lightning/src/ln/blinded_payment_tests.rs (1)</summary><blockquote> `193-241`: **New dummy-hop one-hop test looks correct and consistent** This test cleanly mirrors `do_one_hop_blinded_path` while exercising `BlindedPaymentPath::new_with_dummy_hops` plus `PassAlongPathArgs::with_dummy_override(dummy_hops)`. The setup, payment flow, and final `claim_payment` are all consistent with the existing one-hop blinded-path tests, so this is a solid end-to-end check of the dummy-hop integration. </blockquote></details> <details> <summary>lightning/src/routing/router.rs (1)</summary><blockquote> `204-207`: **new_with_dummy_hops invocation is correctly wired to the new API** The argument order matches `BlindedPaymentPath::new_with_dummy_hops` (`&[forward_node], recipient, DEFAULT_PAYMENT_DUMMY_HOPS, local_node_receive_key, tlvs.clone(), u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, secp_ctx`), preserving previous semantics and just adding the dummy-hop count. No correctness issues spotted. </blockquote></details> <details> <summary>lightning/src/ln/channelmanager.rs (7)</summary><blockquote> `69-77`: **Imports correctly added for dummy hop support.** The new imports (`OnionPacket`, `UpdateAddHTLC`, `NextHopForwardInfo`, `NextPacketDetails`) are appropriately added to support the dummy hop peeling and re-enqueue logic. --- `4891-4927`: **Clean refactoring to use `NextHopForwardInfo` struct.** The function now receives a structured `NextHopForwardInfo` reference, making the API cleaner and more type-safe. The logic correctly accesses `outgoing_connector`, `outgoing_amt_msat`, and `outgoing_cltv_value` from the struct. --- `4959-4972`: **Defensive extraction of forward info with appropriate error handling.** The code correctly extracts `next_hop_forward_info` and returns `InvalidOnionPayload` if absent. This properly handles the case where a packet lacks forwarding information (e.g., Dummy hops that should have been processed earlier). --- `5098-5111`: **Reasonable defensive handling for unexpected Dummy hops.** The `debug_assert!(false, ...)` pattern with descriptive message serves as documentation and will catch bugs in debug builds. The error return in release builds is a safe fallback. This correctly enforces that Dummy hops must be peeled in `process_pending_update_add_htlcs` before reaching this point. --- `6937-6940`: **Good defensive assertion for Dummy hop invariant.** The `debug_assert!` correctly validates that Dummy hops have no forward info, documenting the invariant that Dummy hops are not actually forwarded. --- `6994-6999`: **Correctly updated to handle nested forward info structure.** The chained `and_then` calls safely navigate the nested `Option<NextHopForwardInfo>` to extract the outgoing SCID when present. --- `7141-7152`: **Excellent fix for the race condition.** The merge logic correctly addresses the previously flagged issue where `mem::swap` could drop newly enqueued HTLCs. By acquiring the lock and extending the existing entries, HTLCs enqueued by other threads during processing are safely preserved. The comment clearly documents the rationale. </blockquote></details> <details> <summary>lightning/src/blinded_path/payment.rs (7)</summary><blockquote> `15-15`: **LGTM!** Import of `MAX_DUMMY_HOPS_COUNT` is appropriate for capping the number of dummy hops. --- `119-138`: **LGTM!** The refactoring to delegate to `new_with_dummy_hops` with 0 dummy hops maintains backwards compatibility and follows a clean delegation pattern. --- `221-245`: **LGTM!** The handling of `Dummy` hops in `advance_path_by_one` is correct. For dummy hops, the next node ID is correctly set to the local node (since dummy hops are peeled locally), and the blinding point is properly advanced. --- `379-380`: **LGTM!** The `Dummy` variant additions to both `BlindedPaymentTlvs` and `BlindedPaymentTlvsRef` enums are correct. The comment clearly explains the purpose. Also applies to: 398-398 --- `568-572`: **LGTM!** The encoding logic for `Dummy` TLVs is correct. Using TLV type 65539 with a unit value is appropriate, and marking it as required ensures nodes must understand dummy hops to process them. --- `589-630`: **LGTM!** The decoding logic is thorough and correctly validates all three TLV variants: - `Forward` requires specific fields (scid, relay, constraints) - `Receive` requires payment fields (secret, context, constraints) - `Dummy` requires only the is_dummy flag with all other fields None The strict validation prevents malformed payloads. --- `266-271`: **The AAD validation logic is correct and intentionally differentiates encryption methods.** The validation enforces that: - `Forward` TLVs use ephemeral encryption (rho key, no AAD) → `used_aad == false` - `Receive` and `Dummy` TLVs use authenticated encryption (receive_auth_key as AAD) → `used_aad == true` This is confirmed by: 1. **ChaChaDualPolyReadAdapter** (crypto/streams.rs:96-102) implements dual MAC verification—one without AAD and one with AAD—then tracks which succeeded via the `used_aad` flag 2. **Consistent validation** across the codebase (ln/msgs.rs lines 3685, 3703, 3713, 3848, 3865) enforces the same pattern 3. **Documentation** (sign/mod.rs:912) notes that receive_auth_key is used as AAD during MAC verification 4. **Integration tests** (async_payments_tests.rs:1868) exercise the decryption flow for Receive payloads The design is intentional: forward hops are ephemeral (unauthenticated), while receive/dummy hops require authentication via the receive key. </blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (3)
lightning/src/ln/channelmanager.rs (1)
6946-6961: Silent failure in blinding point advancement remains unaddressed.The chained
.ok()?and.ok()calls silently convert ECDH ornext_hop_pubkeyfailures intoNone, allowing the HTLC to proceed with an invalid/missing blinding point. This was flagged in a previous review.lightning/src/ln/functional_test_utils.rs (1)
4099-4118: Move-after-move bug:argsis destructured then reused.The code destructures
argsat lines 4103-4109, which moves ownership, then attempts to passargstodo_claim_payment_along_routeat line 4112. This will fail to compile.This issue was already flagged in a previous review. The suggested fix is to either:
- Clone
argsbefore destructuring, or- Extract
expected_extra_total_fees_msatdirectly fromargsbefore passing it to the functionpub fn claim_payment_along_route( args: ClaimAlongRouteArgs, ) -> (Option<PaidBolt12Invoice>, Vec<Event>) { + let expected_extra_total_fees_msat = args.expected_extra_total_fees_msat; + let origin_node = args.origin_node; + let payment_preimage = args.payment_preimage; + let skip_last = args.skip_last; + + let expected_total_fee_msat = + do_claim_payment_along_route(args) + expected_extra_total_fees_msat; - let ClaimAlongRouteArgs { - origin_node, - payment_preimage, - skip_last, - expected_extra_total_fees_msat, - .. - } = args; - - let expected_total_fee_msat = - do_claim_payment_along_route(args) + expected_extra_total_fees_msat;lightning/src/ln/offers_tests.rs (1)
1435-1448: 1000 msat remains a latent magic number; consider tying it to config or computing itThe new comment explains the overpayment trade-off much more clearly, but
Some(1000)is still a hardcoded compensation whose derivation isn’t visible here. If dummy-hop fee behavior or defaults change, this test will silently drift out of sync.Consider either:
- deriving the expected extra fees from the concrete route / dummy-hop configuration (even just in a small helper), or
- referencing the specific constants / config that make this 1000 msat stable (e.g., “current default is X msat per dummy over Y hops → 1000”).
This keeps the test resilient to future fee-model tweaks and makes the adjustment auditable.
🧹 Nitpick comments (7)
lightning-dns-resolver/src/lib.rs (1)
424-426: Loopingprocess_pending_htlc_forwardsper dummy hop is reasonable, but consider a brief commentCalling
nodes[1].node.process_pending_htlc_forwards()DEFAULT_PAYMENT_DUMMY_HOPStimes after the commitment dance is a straightforward way to peel the locally-handled dummy hops beforeexpect_and_process_pending_htlcs. Behavior is correct assuming each call advances at most one “hop stage”.You might add a short comment like “// Advance local dummy hops before actual forward” so future readers understand why multiple calls are required and why the count must match
DEFAULT_PAYMENT_DUMMY_HOPS.lightning/src/ln/async_payments_tests.rs (1)
1862-1866: Advancing the blinded path through dummy hops looks correct; consider clarifying intentAdvancing
blinded_pathonce withnodes[1]and thenDEFAULT_PAYMENT_DUMMY_HOPStimes withnodes[2]beforedecrypt_intro_payloadcorrectly mirrors a path with an intermediate hop plus recipient-local dummy hops. This should yield the final intro payload whosemax_cltv_expiryyou want to inspect.To aid future readers, you might add a short comment explaining that these extra
advance_path_by_onecalls correspond to the configured dummy hops at the recipient end, e.g.:// Advance through recipient-local dummy hops to reach the final intro payload. for _ in 0..DEFAULT_PAYMENT_DUMMY_HOPS { blinded_path .advance_path_by_one(&nodes[2].keys_manager, &nodes[2].node, &secp_ctx) .unwrap(); }Behavior-wise this looks sound.
lightning/src/ln/msgs.rs (1)
3698-3707: Dummy TLV decode invariants look correct; consider adding explicit testsThe new
BlindedPaymentTlvs::Dummybranch:
- Forbids
amt,cltv_value,total_msat,keysend_preimage, andinvoice_request, which is important to keep dummy hops from accidentally carrying semantic payment data.- Requires
used_aadto be true, aligning Dummy with the Receive branch’s authentication behavior and distinct from Forward (which rejectsused_aad).This looks logically sound and privacy‑preserving. It would be good to add a focused test (e.g., in
blinded_payment_tests.rsor similar) that constructs a blinded payment path with a dummy hop, exercises encode+decode, and asserts we land inInboundOnionPayload::Dummy { intro_node_blinding_point: … }with all forbidden fields unset, to catch future regressions.lightning/src/ln/blinded_payment_tests.rs (3)
193-241: Consider expanding test coverage for dummy hops.The test only validates the happy path with exactly 2 dummy hops. Consider adding test cases for:
- Edge cases: 0 dummy hops (should behave like regular path), 1 dummy hop, maximum allowed dummy hops
- Failure scenarios: Payment failures with dummy hops to ensure error handling works correctly
- Validation: Assert that the onion packet structure actually contains the expected number of dummy hops (rather than just testing end-to-end success)
Example structure:
#[test] fn one_hop_blinded_path_with_dummy_hops() { do_one_hop_blinded_path_with_dummy_hops(0); // edge case: no dummy hops do_one_hop_blinded_path_with_dummy_hops(1); do_one_hop_blinded_path_with_dummy_hops(2); // Add max value if there's a defined limit } fn do_one_hop_blinded_path_with_dummy_hops(dummy_hops: u8) { // existing test implementation }
1716-1718: Add descriptive error messages to unwrap() calls in test vectors.The test vector code uses
.unwrap()onnext_hop_forward_infowithout clear error messages. If these values are unexpectedlyNone, the test will panic with an unclear message, making debugging harder.Consider replacing
.unwrap()with.expect()to provide context:-let carol_forward_info = carol_packet_details.next_hop_forward_info.unwrap(); +let carol_forward_info = carol_packet_details.next_hop_forward_info + .expect("Carol's next_hop_forward_info should be present in test vector");Apply similar changes to Dave (line 1751) and Eve (line 1786) for consistency.
Also applies to: 1751-1753, 1786-1788
2019-2020: Add descriptive error message to unwrap() call.Similar to the test vector updates, this test uses
.unwrap()onnext_hop_forward_infowithout a clear error message.Replace with
.expect()for better test failure diagnostics:-let carol_forward_info = carol_packet_details.next_hop_forward_info.unwrap(); +let carol_forward_info = carol_packet_details.next_hop_forward_info + .expect("Carol's next_hop_forward_info should be present for trampoline forward");lightning/src/routing/router.rs (1)
204-207: new_with_dummy_hops integration is correct; consider future configurabilityThe parameter order matches
BlindedPaymentPath::new_with_dummy_hops, and both the per‑peer and fallback paths now consistently includeDEFAULT_PAYMENT_DUMMY_HOPSdummy hops without changing existing constraints or error handling. If you later want node-level control over privacy/performance trade‑offs, this dummy-hop count could be surfaced via a config knob instead of being hard-coded here.Also applies to: 216-219
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
lightning-dns-resolver/src/lib.rs(2 hunks)lightning/src/blinded_path/payment.rs(10 hunks)lightning/src/ln/async_payments_tests.rs(2 hunks)lightning/src/ln/blinded_payment_tests.rs(5 hunks)lightning/src/ln/channelmanager.rs(11 hunks)lightning/src/ln/functional_test_utils.rs(10 hunks)lightning/src/ln/msgs.rs(2 hunks)lightning/src/ln/offers_tests.rs(4 hunks)lightning/src/ln/onion_payment.rs(6 hunks)lightning/src/ln/onion_utils.rs(3 hunks)lightning/src/routing/router.rs(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
lightning/src/ln/channelmanager.rs (2)
lightning/src/ln/onion_payment.rs (3)
check_incoming_htlc_cltv(654-682)create_fwd_pending_htlc_info(94-253)decode_incoming_update_add_htlc_onion(531-652)lightning/src/ln/msgs.rs (2)
new(2517-2519)new(2552-2554)
lightning/src/ln/msgs.rs (1)
lightning/src/ln/outbound_payment.rs (1)
total_msat(251-258)
lightning/src/routing/router.rs (1)
lightning/src/blinded_path/payment.rs (1)
new_with_dummy_hops(144-182)
lightning/src/ln/offers_tests.rs (1)
lightning/src/offers/flow.rs (1)
new(116-147)
lightning/src/blinded_path/payment.rs (3)
lightning/src/blinded_path/message.rs (1)
new_with_dummy_hops(91-119)lightning/src/ln/onion_utils.rs (1)
next_hop_pubkey(116-127)lightning/src/blinded_path/utils.rs (1)
construct_blinded_hops(163-193)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: check_release
- GitHub Check: build-tx-sync (ubuntu-latest, stable)
- GitHub Check: build (windows-latest, stable)
- GitHub Check: benchmark
- GitHub Check: semver-checks
- GitHub Check: benchmark
- GitHub Check: build (windows-latest, stable)
- GitHub Check: build-tx-sync (ubuntu-latest, beta)
- GitHub Check: benchmark
- GitHub Check: build (windows-latest, stable)
🔇 Additional comments (34)
lightning/src/ln/channelmanager.rs (7)
69-77: LGTM!The new imports for
OnionPacket,UpdateAddHTLC,NextHopForwardInfo, andNextPacketDetailsare necessary for the dummy hop handling logic introduced in this PR.
4891-4927: LGTM!Clean refactoring to use
NextHopForwardInfostruct. The signature change and field accesses throughforward_infomaintain the same logic while improving type safety and code organization.
4959-4989: LGTM!The extraction of
next_hop_forward_infowith proper error handling viaok_orensures that forwards without valid forward info are rejected with an appropriate failure reason. The subsequent pattern matching on the connector type and CLTV validation are correct.
5098-5111: LGTM!Good defensive programming. The
debug_assert!(false, ...)documents the invariant that Dummy hops should never reach this decode path (they should be peeled inprocess_pending_update_add_htlcs), while still returning an appropriate error in release builds if the invariant is violated.
6963-6981: LGTM!The construction of the new
OnionPacketandUpdateAddHTLCcorrectly preserves all necessary fields while replacing the onion routing packet with the peeled next layer. Using..update_add_htlc.clone()ensures other fields (channel_id, htlc_id, amount_msat, cltv_expiry, etc.) are preserved for the re-queued HTLC.
6994-6999: LGTM!The nested
and_thencalls correctly handle the optionalnext_hop_forward_infofield, properly returningNonefor dummy hops (which have no forward info) and for Trampoline connectors.
7141-7152: LGTM!This correctly addresses the previous review concern about losing HTLCs. The merge pattern using
extendpreserves any HTLCs that were enqueued by other threads while processing. The guardif !dummy_update_add_htlcs.is_empty()avoids unnecessary lock acquisition, and the explanatory comment clearly documents the rationale.lightning-dns-resolver/src/lib.rs (1)
178-178: Test-only dependency on router defaults looks fineImporting
DEFAULT_PAYMENT_DUMMY_HOPShere keeps this crate’s tests aligned with the router’s default dummy-hop behavior and avoids hardcoding the value. No issues from a layering or correctness perspective given this is test code.lightning/src/ln/async_payments_tests.rs (1)
58-58: ImportingDEFAULT_PAYMENT_DUMMY_HOPSinto async payments tests is appropriateBringing
DEFAULT_PAYMENT_DUMMY_HOPSinto scope here avoids magic numbers and keeps the tests aligned with router behavior when the default dummy-hop count changes.lightning/src/ln/msgs.rs (1)
2352-2359: NewInboundOnionPayload::Dummyvariant fits existing designAdding
Dummy { intro_node_blinding_point: Option<PublicKey> }is consistent with the other blinded variants and preserves the existing blinding metadata shape; no structural issues here.lightning/src/routing/router.rs (1)
77-79: DEFAULT_PAYMENT_DUMMY_HOPS constant looks goodPublic constant, naming, and docstring are clear;
usize = 3is a reasonable default and keeps the router’s behavior explicit and discoverable.lightning/src/ln/functional_test_utils.rs (5)
42-44: LGTM!The import of
DEFAULT_PAYMENT_DUMMY_HOPSis correctly added to support the new dummy hop functionality.
3437-3475: LGTM!The
dummy_hop_overridefield follows the established builder pattern and is correctly initialized toNoneto allow fallback to the default.
3510-3513: LGTM!The builder method follows the established pattern and correctly wraps the value in
Some.
3555-3562: LGTM!The dummy hop peeling logic is correctly implemented. At the final hop, each call to
process_pending_htlc_forwards()strips one dummy layer, and the loop count appropriately uses the override or falls back to the default.
3776-3841: LGTM!The
expected_extra_total_fees_msatfield is well-documented, explaining the specific fee-handling trade-off that surfaces with dummy hops. The documentation clearly states this is a test-only adjustment and references the relevant code path.lightning/src/ln/offers_tests.rs (2)
185-233: Helper-based along-route claim flow looks goodRefactor to
claim_bolt12_payment_with_extra_feescleanly centralizes the along-route-claim logic: extractingPaymentClaimable, mappingPaymentPurpose→PaymentContext, asserting context, and threadingClaimAlongRouteArgsincluding optional extra fees. The use ofexpected_paths = [path]matches theClaimAlongRouteArgs::newsignature, and the helper preserves previousclaim_bolt12_paymentbehavior whenexpected_extra_fees_msatisNone.No issues from a correctness or test-maintainability standpoint.
2463-2466: Updated failure handling matches malformed-HTLC behaviorSwitching to
handle_update_fail_malformed_htlcand asserting viaexpect_payment_failed_conditionsaligns this test with the expected “malformed over non-static invoice path” semantics. Given the precedingexpect_failure(HTLCHandlingFailureType::Receive { .. })and howget_htlc_update_msgsis used elsewhere, indexingupdate_fail_malformed_htlcs[0]is consistent with existing patterns.Looks correct and idiomatic for this failure path.
lightning/src/ln/onion_utils.rs (3)
2157-2215: Hop::Dummy variant integrates cleanly into the Hop modelAdding
Hop::Dummy { intro_node_blinding_point, shared_secret, next_hop_hmac, new_packet_bytes }mirrors the structure of the existing forward variants and preserves all the state needed to peel a dummy layer and continue forwarding. The documentation makes the “peel-only” semantics explicit.No issues with the enum extension itself.
2267-2277: Shared-secret plumbing for Dummy hops is consistentExtending
Hop::shared_secretto return theshared_secretforHop::Dummykeeps the helper usable generically across all hop types, including any attribution or error-processing code that relies on it.This is the expected behavior and looks correct.
2338-2343: Decoding InboundOnionPayload::Dummy into Hop::Dummy is aligned with forward-path handlingMapping
msgs::InboundOnionPayload::Dummy { intro_node_blinding_point }intoHop::Dummy { intro_node_blinding_point, shared_secret, next_hop_hmac, new_packet_bytes }indecode_next_payment_hopwires dummy hops into the same decode pipeline as other forward-like variants while still distinguishing them at the Hop layer.Assuming call sites treat
Hop::Dummyas “peel and continue” rather than a normal forward, this is the right place and shape for the conversion; no correctness issues apparent here.Would be good to double-check that all
Hoppattern matches in downstream modules (e.g.,onion_payment.rs,channelmanager.rs) now include aHop::Dummyarm and implement the intended “peel dummy and re-decode” behavior. If you’d like, I can suggest anrg/ast-grepscript to scan for exhaustive matches onHopand highlight missing Dummy handling.lightning/src/ln/onion_payment.rs (4)
472-485: LGTM!The extraction of
next_hop_forward_infoand the error check correctly enforce that Forward and BlindedForward hops must include next-hop details. The decode logic at lines 597-628 ensures this requirement is met for these hop types.
519-528: LGTM!The introduction of
NextHopForwardInfocleanly encapsulates forwarding state and allows Dummy hops to be distinguished by returningNonefornext_hop_forward_info.
597-641: LGTM!The refactoring consistently wraps forwarding state in
NextHopForwardInfoacross all forward hop types (Direct, BlindedForward, and TrampolineForward) and correctly distinguishes between short-channel-based and trampoline-based routing viaHopConnector.
642-647: LGTM!Dummy hop handling correctly computes the
next_packet_pubkeyfor onion peeling while returningNonefornext_hop_forward_info, consistent with dummy hops being peeled locally rather than forwarded.lightning/src/blinded_path/payment.rs (9)
119-138: LGTM!Clean delegation from
newtonew_with_dummy_hopswithdummy_hop_count: 0maintains backward compatibility while enabling the new functionality.
140-182: LGTM!The
new_with_dummy_hopsconstructor cleanly extends the API to support dummy hops, with clear documentation about theMAX_DUMMY_HOPS_COUNTlimit that is enforced inblinded_hops.
221-246: LGTM!The Dummy hop handling in
advance_path_by_onecorrectly advances the blinding state using the local node's ID, allowing dummy hops to be peeled without forwarding to another node.
376-383: LGTM!The addition of the
Dummyvariant toBlindedPaymentTlvsis straightforward and consistent with the enum structure.
396-400: LGTM!The
Dummyvariant inBlindedPaymentTlvsRefcorrectly mirrors the main enum for serialization.
564-577: LGTM!The
Writeableimplementation forDummycorrectly encodes it as TLV type 65539, using a unit value as an appropriate marker.
579-633: LGTM!The
Readableimplementation forBlindedPaymentTlvscorrectly decodes Forward, Receive, and Dummy variants with proper mutual exclusivity checks. The Dummy variant is appropriately decoded when only theis_dummyflag (TLV 65539) is present.
675-717: LGTM with complexity noted.The
blinded_hopsfunction correctly constructs the blinded path by chaining intermediate Forward hops, Dummy hops, and the final Receive hop. The debug assertion properly verifies that all non-final hops have identical serialized sizes for privacy, comparing hops 1 through N+D-1 against the first hop's size.The use of iterator chains and the debug assertion logic is correct but complex; the implementation is sound.
266-271: Let me search more carefully for spec documentation and explanation for this behavior:
<function_calls>
#!/bin/bashSearch for spec references and comments explaining Forward/AAD behavior
echo "=== Searching for BOLT/spec references in payment.rs ==="
rg -n "BOLT|spec" lightning/src/blinded_path/payment.rs | head -20echo -e "\n=== Searching for comments about Forward payloads ==="
rg -n -B5 "Forward" lightning/src/blinded_path/payment.rs | grep -A5 -B5 "//"echo -e "\n=== Checking BlindedPaymentTlvs definition ==="
rg -n "enum BlindedPaymentTlvs" lightning/src/blinded_path/ -A 20
</blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
| onion_utils::Hop::Dummy { .. } => { | ||
| debug_assert!(false, "Dummy hop should have been peeled earlier"); | ||
| return Err(InboundHTLCErr { | ||
| reason: LocalHTLCFailureReason::InvalidOnionBlinding, | ||
| err_data: vec![0; 32], | ||
| msg: "Got blinded non final data with an HMAC of 0", | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent error handling for Dummy hop.
The error handling for Dummy hops in create_recv_pending_htlc_info is inconsistent with the handling in create_fwd_pending_htlc_info (lines 126-133):
- The error message "Got blinded non final data with an HMAC of 0" is copy-pasted from the
BlindedForwardcase and doesn't describe a Dummy hop situation. - The failure reason
LocalHTLCFailureReason::InvalidOnionBlindingdiffers from line 130'sInvalidOnionPayload. - The
err_dataisvec![0; 32]here butVec::new()at line 131.
Apply this diff to align error handling:
onion_utils::Hop::Dummy { .. } => {
debug_assert!(false, "Dummy hop should have been peeled earlier");
return Err(InboundHTLCErr {
- reason: LocalHTLCFailureReason::InvalidOnionBlinding,
- err_data: vec![0; 32],
- msg: "Got blinded non final data with an HMAC of 0",
+ reason: LocalHTLCFailureReason::InvalidOnionPayload,
+ err_data: Vec::new(),
+ msg: "Dummy Hop OnionHopData provided for final node",
})
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onion_utils::Hop::Dummy { .. } => { | |
| debug_assert!(false, "Dummy hop should have been peeled earlier"); | |
| return Err(InboundHTLCErr { | |
| reason: LocalHTLCFailureReason::InvalidOnionBlinding, | |
| err_data: vec![0; 32], | |
| msg: "Got blinded non final data with an HMAC of 0", | |
| }) | |
| } | |
| onion_utils::Hop::Dummy { .. } => { | |
| debug_assert!(false, "Dummy hop should have been peeled earlier"); | |
| return Err(InboundHTLCErr { | |
| reason: LocalHTLCFailureReason::InvalidOnionPayload, | |
| err_data: Vec::new(), | |
| msg: "Dummy Hop OnionHopData provided for final node", | |
| }) | |
| } |
🤖 Prompt for AI Agents
In lightning/src/ln/onion_payment.rs around lines 338 to 345, the Dummy hop
error block is inconsistent with create_fwd_pending_htlc_info: replace the
incorrect message, failure reason, and err_data so they match the forward-path
handling — set LocalHTLCFailureReason to InvalidOnionPayload, set err_data to
Vec::new(), and change the message to one appropriate for a Dummy hop (e.g.,
"Got blinded non-final payload with an HMAC of 0" or the same wording used in
the forward case); keep the existing return Err(InboundHTLCErr { ... })
structure but update those three fields to align with lines 126-133.
Summary by CodeRabbit
New Features
Refactor
Tests
✏️ Tip: You can customize this high-level summary in your review settings.