diff --git a/witness_beacon.go b/witness_beacon.go index 1c2e78cf1..cefeb2fd3 100644 --- a/witness_beacon.go +++ b/witness_beacon.go @@ -107,14 +107,26 @@ func (p *preimageBeacon) SubscribeUpdates( }, } + // Report the forwarding next hop to the interceptor. A channel-ID next + // hop is reported directly; a node-ID next hop has no outgoing channel + // of its own, so outgoingChanID is hop.Exit and the requested node ID + // is exposed separately, exactly as the off-chain interceptor does. + // This is the requested next hop, not the channel that non-strict + // forwarding eventually selects, so we deliberately do not resolve it + // against the circuit map. The RPC boundary maps a node-ID hop to the + // NodeIDForwardSCID sentinel for the client. + // // Notify the htlc interceptor. There may be a client connected // and willing to supply a preimage. packet := &htlcswitch.InterceptedPacket{ - Hash: htlc.RHash, - IncomingExpiry: htlc.RefundTimeout, - IncomingAmount: htlc.Amt, - IncomingCircuit: inKey, - OutgoingChanID: payload.FwdInfo.NextHopChannel().UnwrapOr(hop.Exit), + Hash: htlc.RHash, + IncomingExpiry: htlc.RefundTimeout, + IncomingAmount: htlc.Amt, + IncomingCircuit: inKey, + OutgoingChanID: payload.FwdInfo.NextHopChannel().UnwrapOr( + hop.Exit, + ), + OutgoingNodeID: payload.FwdInfo.NextHopNode(), OutgoingExpiry: payload.FwdInfo.OutgoingCLTV, OutgoingAmount: payload.FwdInfo.AmountToForward, InOnionCustomRecords: payload.CustomRecords(), diff --git a/witness_beacon_test.go b/witness_beacon_test.go index 7ba22db55..dc3e0ddb5 100644 --- a/witness_beacon_test.go +++ b/witness_beacon_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/lightningnetwork/lnd/chanstate" + "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/htlcswitch/hop" @@ -97,6 +98,47 @@ func TestWitnessBeaconInterceptErrorCancels(t *testing.T) { p.RUnlock() } +// TestWitnessBeaconInterceptNodeID asserts that for a node-ID next hop the +// on-chain interceptor reports the exit-hop SCID (hop.Exit) together with the +// requested next node's public key, matching the off-chain interceptor. The +// next hop is not resolved against the circuit map; the RPC boundary maps +// hop.Exit to the sentinel. +func TestWitnessBeaconInterceptNodeID(t *testing.T) { + var interceptedFwd htlcswitch.InterceptedForward + interceptor := func(fwd htlcswitch.InterceptedForward) error { + interceptedFwd = fwd + + return nil + } + + p := newPreimageBeacon( + &mockWitnessCache{}, interceptor, + func(models.CircuitKey) error { + return nil + }, + ) + + var nodeID [33]byte + nodeID[0] = 0x02 + + payload := &hop.Payload{ + FwdInfo: hop.ForwardingInfo{ + NextHop: hop.NewNodeNextHop(nodeID), + }, + } + + _, err := p.SubscribeUpdates( + lnwire.NewShortChanIDFromInt(1), + &chanstate.HTLC{RHash: lntypes.Hash{1}}, + payload, []byte{2}, + ) + require.NoError(t, err) + + packet := interceptedFwd.Packet() + require.Equal(t, hop.Exit, packet.OutgoingChanID) + require.Equal(t, fn.Some(nodeID), packet.OutgoingNodeID) +} + type mockWitnessCache struct { witnessCache }