lnd/witness_beacon_test.go
bitromortac 9c4b8bfec2
witness beacon: report node-ID next hop to the on-chain HTLC interceptor
Extend the on-chain interceptor path in the witness beacon to expose a
node-ID next hop, mirroring the off-chain path. A node-ID next hop has no
outgoing channel of its own, so the beacon reports hop.Exit as the outgoing
channel (via ForwardingInfo.NextHopChannel().UnwrapOr) and the requested
next node's public key. The RPC boundary maps that to the NodeIDForwardSCID
sentinel so the forward is not misread as a final receive.

This is the requested next hop, not the channel eventually selected by
non-strict forwarding, so the beacon deliberately does not resolve it
against the circuit map.
2026-07-30 08:57:19 +00:00

150 lines
3.6 KiB
Go

package lnd
import (
"errors"
"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"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// TestWitnessBeaconIntercept tests that the beacon passes on subscriptions to
// the interceptor correctly.
func TestWitnessBeaconIntercept(t *testing.T) {
var interceptedFwd htlcswitch.InterceptedForward
interceptor := func(fwd htlcswitch.InterceptedForward) error {
interceptedFwd = fwd
return nil
}
var canceledKey models.CircuitKey
cancelInterceptor := func(key models.CircuitKey) error {
canceledKey = key
return nil
}
p := newPreimageBeacon(
&mockWitnessCache{}, interceptor, cancelInterceptor,
)
preimage := lntypes.Preimage{1, 2, 3}
hash := preimage.Hash()
subscription, err := p.SubscribeUpdates(
lnwire.NewShortChanIDFromInt(1),
&chanstate.HTLC{
RHash: hash,
},
&hop.Payload{},
[]byte{2},
)
require.NoError(t, err)
require.NoError(t, interceptedFwd.Settle(preimage))
update := <-subscription.WitnessUpdates
require.Equal(t, preimage, update)
subscription.CancelSubscription()
require.Equal(t, interceptedFwd.Packet().IncomingCircuit, canceledKey)
}
// TestWitnessBeaconInterceptErrorCancels tests that a failed interceptor offer
// tears down the witness subscription and on-chain intercept handle.
func TestWitnessBeaconInterceptErrorCancels(t *testing.T) {
errInterceptor := errors.New("interceptor error")
interceptor := func(htlcswitch.InterceptedForward) error {
return errInterceptor
}
var canceledKey models.CircuitKey
cancelInterceptor := func(key models.CircuitKey) error {
canceledKey = key
return nil
}
p := newPreimageBeacon(
&mockWitnessCache{}, interceptor, cancelInterceptor,
)
chanID := lnwire.NewShortChanIDFromInt(1)
htlc := &chanstate.HTLC{
HtlcIndex: 2,
RHash: lntypes.Hash{3},
}
subscription, err := p.SubscribeUpdates(
chanID, htlc, &hop.Payload{}, []byte{2},
)
require.ErrorIs(t, err, errInterceptor)
require.Nil(t, subscription)
require.Equal(t, models.CircuitKey{
ChanID: chanID,
HtlcID: htlc.HtlcIndex,
}, canceledKey)
p.RLock()
require.Empty(t, p.subscribers)
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
}
func (w *mockWitnessCache) AddSha256Witnesses(
preimages ...lntypes.Preimage) error {
return nil
}