mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-13 12:32:48 +02:00
htlcswitch: key the aux traffic shaper on the evaluated channel
During non-strict forwarding, handlePacketAdd evaluates every candidate
channel to the next peer and calls CheckHtlcForward with the sender-requested
outgoing SCID (originalOutgoingChanID) for each candidate. That SCID flowed
through canSendHtlc into AuxTrafficShaper.ShouldHandleTraffic, so a
channel-keyed shaper was asked about the requested channel rather than the
candidate actually being evaluated. With parallel channels to a peer this
inspects the wrong channel.
Key the shaper on l.ShortChanID() (the channel under evaluation) instead.
originalScid is retained solely for createFailureWithUpdate / FailAliasUpdate,
so the alias-aware channel_update returned to the sender is unchanged and the
real SCID handed to the shaper never leaks onto the wire.
(cherry picked from commit b166780015)
This commit is contained in:
parent
c863397a4f
commit
86eeacc3ae
2 changed files with 133 additions and 1 deletions
|
|
@ -2635,7 +2635,10 @@ func (l *channelLink) canSendHtlc(policy models.ForwardingPolicy,
|
|||
htlcBlob = fn.Some(blob)
|
||||
}
|
||||
|
||||
return l.AuxBandwidth(amt, originalScid, htlcBlob, ts)
|
||||
// Check if this link can handle the traffic.
|
||||
return l.AuxBandwidth(
|
||||
amt, l.ShortChanID(), htlcBlob, ts,
|
||||
)
|
||||
},
|
||||
).Unpack()
|
||||
if externalErr != nil {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/ticker"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -6394,6 +6395,134 @@ func TestCheckHtlcForward(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// recordingAuxShaper is a minimal AuxTrafficShaper that records the channel id
|
||||
// it is asked about and declines to handle the traffic, so the normal
|
||||
// forwarding path proceeds. Only the methods reached by CheckHtlcForward are
|
||||
// implemented; the rest are inherited from the embedded (nil) interface and
|
||||
// must never be called.
|
||||
type recordingAuxShaper struct {
|
||||
AuxTrafficShaper
|
||||
|
||||
gotCID lnwire.ShortChannelID
|
||||
}
|
||||
|
||||
// ShouldHandleTraffic records the short channel ID passed to the shaper.
|
||||
func (a *recordingAuxShaper) ShouldHandleTraffic(cid lnwire.ShortChannelID,
|
||||
_, _ fn.Option[tlv.Blob]) (bool, error) {
|
||||
|
||||
a.gotCID = cid
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// IsCustomHTLC returns false as recordingAuxShaper handles standard HTLCs.
|
||||
func (a *recordingAuxShaper) IsCustomHTLC(_ lnwire.CustomRecords) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// TestCheckHtlcForwardAuxShaperChannel asserts that during non-strict
|
||||
// forwarding the aux traffic shaper is keyed on the channel actually being
|
||||
// evaluated (the link's own SCID), not the sender-requested SCID, which fixes
|
||||
// both the node-ID/blinded path (where no SCID is requested) and pre-existing
|
||||
// parallel-channel forwarding. It also asserts the real SCID handed to the
|
||||
// shaper never leaks into the sender-facing channel_update, which continues to
|
||||
// reference the requested (alias) SCID.
|
||||
func TestCheckHtlcForwardAuxShaperChannel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
chanScid = 42
|
||||
requestedScid = 99
|
||||
)
|
||||
|
||||
fetchLastChannelUpdate := func(lnwire.ShortChannelID) (
|
||||
*lnwire.ChannelUpdate1, error) {
|
||||
|
||||
return &lnwire.ChannelUpdate1{}, nil
|
||||
}
|
||||
|
||||
// Record the SCID used to build the returned channel_update on failure.
|
||||
var updateScid lnwire.ShortChannelID
|
||||
failAliasUpdate := func(sid lnwire.ShortChannelID,
|
||||
incoming bool) *lnwire.ChannelUpdate1 {
|
||||
|
||||
updateScid = sid
|
||||
|
||||
return &lnwire.ChannelUpdate1{
|
||||
ShortChannelID: sid,
|
||||
}
|
||||
}
|
||||
|
||||
testChannel, _, err := createTestChannel(
|
||||
t, alicePrivKey, bobPrivKey, 100000, 100000, 1000, 1000,
|
||||
lnwire.NewShortChanIDFromInt(chanScid),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
shaper := &recordingAuxShaper{}
|
||||
link := channelLink{
|
||||
cfg: ChannelLinkConfig{
|
||||
FwrdingPolicy: models.ForwardingPolicy{
|
||||
TimeLockDelta: 20,
|
||||
MinHTLCOut: 500,
|
||||
MaxHTLC: 1000,
|
||||
BaseFee: 10,
|
||||
},
|
||||
FetchLastChannelUpdate: fetchLastChannelUpdate,
|
||||
MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry,
|
||||
HtlcNotifier: &mockHTLCNotifier{},
|
||||
},
|
||||
log: log,
|
||||
channel: testChannel.channel,
|
||||
}
|
||||
link.cfg.AuxTrafficShaper = fn.Some[AuxTrafficShaper](shaper)
|
||||
link.attachFailAliasUpdate(failAliasUpdate)
|
||||
|
||||
require.Equal(
|
||||
t, lnwire.NewShortChanIDFromInt(chanScid), link.ShortChanID(),
|
||||
)
|
||||
|
||||
var hash [32]byte
|
||||
requested := lnwire.NewShortChanIDFromInt(requestedScid)
|
||||
|
||||
// A satisfiable forward: the shaper must be queried about the channel
|
||||
// being evaluated (the link's own SCID), not the requested SCID.
|
||||
result := link.CheckHtlcForward(
|
||||
hash, 1500, 1000, 200, 150, models.InboundFee{}, 0, requested,
|
||||
nil,
|
||||
)
|
||||
require.Nil(t, result, "expected policy to be satisfied")
|
||||
require.Equal(
|
||||
t, link.ShortChanID(), shaper.gotCID,
|
||||
"aux shaper must be keyed on the evaluated channel",
|
||||
)
|
||||
require.NotEqual(
|
||||
t, requested, shaper.gotCID,
|
||||
"aux shaper must not be keyed on the requested SCID",
|
||||
)
|
||||
|
||||
// A failing forward: the returned channel_update must reference the
|
||||
// requested (alias) SCID, never the real channel SCID handed to the
|
||||
// shaper.
|
||||
result = link.CheckHtlcForward(
|
||||
hash, 100, 50, 200, 150, models.InboundFee{}, 0, requested, nil,
|
||||
)
|
||||
require.NotNil(t, result)
|
||||
require.Equal(
|
||||
t, requested, updateScid,
|
||||
"channel_update must reference the requested SCID, not the "+
|
||||
"real channel SCID",
|
||||
)
|
||||
|
||||
wireErr := result.WireMessage()
|
||||
failAmt, ok := wireErr.(*lnwire.FailAmountBelowMinimum)
|
||||
require.True(t, ok, "expected FailAmountBelowMinimum failure")
|
||||
require.Equal(
|
||||
t, requested, failAmt.Update.ShortChannelID,
|
||||
"failure update must carry the requested SCID",
|
||||
)
|
||||
}
|
||||
|
||||
// TestChannelLinkCanceledInvoice in this test checks the interaction
|
||||
// between Alice and Bob for a canceled invoice.
|
||||
func TestChannelLinkCanceledInvoice(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue