config: check cltv expiry policy range

Check configured and advertised forwarding CLTV deltas against max-cltv-expiry
so local configuration and advertised channel policy stay within the same
supported range.

(cherry picked from commit b8e861fe6b)
This commit is contained in:
ziggie 2026-06-24 09:36:26 -03:00 committed by github-actions[bot]
parent 0981974786
commit 056b526e61
3 changed files with 123 additions and 8 deletions

View file

@ -264,6 +264,58 @@ const (
defaultNoDisconnectOnPongFailure = false
)
// validateMaxOutgoingCltvExpiry validates the configured maximum outgoing CLTV
// expiry against the node's default time lock delta.
func validateMaxOutgoingCltvExpiry(maxCltvExpiry, timeLockDelta uint32) error {
if maxCltvExpiry < timeLockDelta {
return fmt.Errorf(
"max-cltv-expiry must be at least %v", timeLockDelta,
)
}
if maxCltvExpiry > MaxTimeLockDelta {
return fmt.Errorf(
"max-cltv-expiry must be at most %v", MaxTimeLockDelta,
)
}
return nil
}
// validateCltvDeltaBounds validates a CLTV delta against LND's absolute
// supported bounds.
func validateCltvDeltaBounds(delta uint32) error {
if delta < minTimeLockDelta {
return fmt.Errorf("time lock delta of %v is too small, "+
"minimum supported is %v", delta, minTimeLockDelta)
}
if delta > MaxTimeLockDelta {
return fmt.Errorf("time lock delta of %v is too big, "+
"maximum supported is %v", delta, MaxTimeLockDelta)
}
return nil
}
// validateChannelPolicyTimeLockDelta validates an advertised channel policy
// time lock delta against the node's supported forwarding bounds.
func validateChannelPolicyTimeLockDelta(timeLockDelta,
maxOutgoingCltvExpiry uint32) error {
if err := validateCltvDeltaBounds(timeLockDelta); err != nil {
return err
}
if timeLockDelta > maxOutgoingCltvExpiry {
return fmt.Errorf("time lock delta of %v exceeds "+
"max-cltv-expiry of %v", timeLockDelta,
maxOutgoingCltvExpiry)
}
return nil
}
var (
// DefaultLndDir is the default directory where lnd tries to find its
// configuration file and store its data. This is a directory in the
@ -1198,6 +1250,12 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
cfg.MaxCommitFeeRateAnchors)
}
if err := validateMaxOutgoingCltvExpiry(
cfg.MaxOutgoingCltvExpiry, cfg.Bitcoin.TimeLockDelta,
); err != nil {
return nil, mkErr("%v", err)
}
// Validate the Tor config parameters.
socks, err := lncfg.ParseAddressString(
cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort),

View file

@ -5,6 +5,7 @@ import (
"testing"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/routing"
"github.com/stretchr/testify/require"
)
@ -171,3 +172,63 @@ func TestValidateConfigTrickleDelay(t *testing.T) {
})
}
}
// TestValidateMaxOutgoingCltvExpiry asserts that max-cltv-expiry accepts
// values within its supported bounds and rejects values outside them.
func TestValidateMaxOutgoingCltvExpiry(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
require.NoError(
t, validateMaxOutgoingCltvExpiry(
htlcswitch.DefaultMaxOutgoingCltvExpiry,
cfg.Bitcoin.TimeLockDelta,
),
)
require.NoError(t, validateMaxOutgoingCltvExpiry(
MaxTimeLockDelta, MaxTimeLockDelta,
))
err := validateMaxOutgoingCltvExpiry(
cfg.Bitcoin.TimeLockDelta-1,
cfg.Bitcoin.TimeLockDelta,
)
require.ErrorContains(t, err, "max-cltv-expiry must be at least")
err = validateMaxOutgoingCltvExpiry(
MaxTimeLockDelta+1, cfg.Bitcoin.TimeLockDelta,
)
require.ErrorContains(t, err, "max-cltv-expiry must be at most")
}
// TestValidateChannelPolicyTimeLockDelta asserts that advertised channel
// policy CLTV deltas stay within the node's supported forwarding bounds.
func TestValidateChannelPolicyTimeLockDelta(t *testing.T) {
t.Parallel()
cfg := DefaultConfig()
require.NoError(t, validateChannelPolicyTimeLockDelta(
cfg.Bitcoin.TimeLockDelta, cfg.MaxOutgoingCltvExpiry,
))
require.NoError(t, validateChannelPolicyTimeLockDelta(
cfg.MaxOutgoingCltvExpiry, cfg.MaxOutgoingCltvExpiry,
))
err := validateChannelPolicyTimeLockDelta(
minTimeLockDelta-1, cfg.MaxOutgoingCltvExpiry,
)
require.ErrorContains(t, err, "time lock delta of")
require.ErrorContains(t, err, "is too small")
err = validateChannelPolicyTimeLockDelta(
MaxTimeLockDelta+1, MaxTimeLockDelta,
)
require.ErrorContains(t, err, "is too big")
err = validateChannelPolicyTimeLockDelta(
cfg.MaxOutgoingCltvExpiry+1, cfg.MaxOutgoingCltvExpiry,
)
require.ErrorContains(t, err, "exceeds max-cltv-expiry")
}

View file

@ -7487,14 +7487,10 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context,
// We'll also ensure that the user isn't setting a CLTV delta that
// won't give outgoing HTLCs enough time to fully resolve if needed.
if req.TimeLockDelta < minTimeLockDelta {
return nil, fmt.Errorf("time lock delta of %v is too small, "+
"minimum supported is %v", req.TimeLockDelta,
minTimeLockDelta)
} else if req.TimeLockDelta > uint32(MaxTimeLockDelta) {
return nil, fmt.Errorf("time lock delta of %v is too big, "+
"maximum supported is %v", req.TimeLockDelta,
MaxTimeLockDelta)
if err := validateChannelPolicyTimeLockDelta(
req.TimeLockDelta, r.cfg.MaxOutgoingCltvExpiry,
); err != nil {
return nil, err
}
// By default, positive inbound fees are rejected.