Merge pull request #2051 from joostjager/cltv-too-far
htlcswitch+routing: implement expiry_too_far failure
This commit is contained in:
commit
ab12184544
@ -36,6 +36,17 @@ const (
|
||||
// TODO(roasbeef): must be < default delta
|
||||
expiryGraceDelta = 2
|
||||
|
||||
// maxCltvExpiry is the maximum outgoing time lock that the node accepts
|
||||
// for forwarded payments. The value is relative to the current block
|
||||
// height. The reason to have a maximum is to prevent funds getting
|
||||
// locked up unreasonably long. Otherwise, an attacker willing to lock
|
||||
// its own funds too, could force the funds of this node to be locked up
|
||||
// for an indefinite (max int32) number of blocks.
|
||||
//
|
||||
// The value 5000 is based on the maximum number of hops (20), the
|
||||
// default cltv delta (144) and some extra margin.
|
||||
maxCltvExpiry = 5000
|
||||
|
||||
// DefaultMinLinkFeeUpdateTimeout represents the minimum interval in
|
||||
// which a link should propose to update its commitment fee rate.
|
||||
DefaultMinLinkFeeUpdateTimeout = 10 * time.Minute
|
||||
@ -1950,6 +1961,14 @@ func (l *channelLink) HtlcSatifiesPolicy(payHash [32]byte,
|
||||
return failure
|
||||
}
|
||||
|
||||
if outgoingTimeout-heightNow > maxCltvExpiry {
|
||||
l.errorf("outgoing htlc(%x) has a time lock too far in the "+
|
||||
"future: got %v, but maximum is %v", payHash[:],
|
||||
outgoingTimeout-heightNow, maxCltvExpiry)
|
||||
|
||||
return &lnwire.FailExpiryTooFar{}
|
||||
}
|
||||
|
||||
// Finally, we'll ensure that the time-lock on the outgoing HTLC meets
|
||||
// the following constraint: the incoming time-lock minus our time-lock
|
||||
// delta should equal the outgoing time lock. Otherwise, whether the
|
||||
|
@ -5119,3 +5119,77 @@ func TestForwardingAsymmetricTimeLockPolicies(t *testing.T) {
|
||||
t.Fatalf("unable to send payment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHtlcSatisfyPolicy tests that a link is properly enforcing the HTLC
|
||||
// forwarding policy.
|
||||
func TestHtlcSatisfyPolicy(t *testing.T) {
|
||||
|
||||
fetchLastChannelUpdate := func(lnwire.ShortChannelID) (
|
||||
*lnwire.ChannelUpdate, error) {
|
||||
|
||||
return &lnwire.ChannelUpdate{}, nil
|
||||
}
|
||||
|
||||
link := channelLink{
|
||||
cfg: ChannelLinkConfig{
|
||||
FwrdingPolicy: ForwardingPolicy{
|
||||
TimeLockDelta: 20,
|
||||
MinHTLC: 500,
|
||||
BaseFee: 10,
|
||||
},
|
||||
FetchLastChannelUpdate: fetchLastChannelUpdate,
|
||||
},
|
||||
}
|
||||
|
||||
var hash [32]byte
|
||||
|
||||
t.Run("satisfied", func(t *testing.T) {
|
||||
result := link.HtlcSatifiesPolicy(hash, 1500, 1000,
|
||||
200, 150, 0)
|
||||
if result != nil {
|
||||
t.Fatalf("expected policy to be satisfied")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("below minhtlc", func(t *testing.T) {
|
||||
result := link.HtlcSatifiesPolicy(hash, 100, 50,
|
||||
200, 150, 0)
|
||||
if _, ok := result.(*lnwire.FailAmountBelowMinimum); !ok {
|
||||
t.Fatalf("expected FailAmountBelowMinimum failure code")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("insufficient fee", func(t *testing.T) {
|
||||
result := link.HtlcSatifiesPolicy(hash, 1005, 1000,
|
||||
200, 150, 0)
|
||||
if _, ok := result.(*lnwire.FailFeeInsufficient); !ok {
|
||||
t.Fatalf("expected FailFeeInsufficient failure code")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("expiry too soon", func(t *testing.T) {
|
||||
result := link.HtlcSatifiesPolicy(hash, 1500, 1000,
|
||||
200, 150, 190)
|
||||
if _, ok := result.(*lnwire.FailExpiryTooSoon); !ok {
|
||||
t.Fatalf("expected FailExpiryTooSoon failure code")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("incorrect cltv expiry", func(t *testing.T) {
|
||||
result := link.HtlcSatifiesPolicy(hash, 1500, 1000,
|
||||
200, 190, 0)
|
||||
if _, ok := result.(*lnwire.FailIncorrectCltvExpiry); !ok {
|
||||
t.Fatalf("expected FailIncorrectCltvExpiry failure code")
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("cltv expiry too far in the future", func(t *testing.T) {
|
||||
// Check that expiry isn't too far in the future.
|
||||
result := link.HtlcSatifiesPolicy(hash, 1500, 1000,
|
||||
10200, 10100, 0)
|
||||
if _, ok := result.(*lnwire.FailExpiryTooFar); !ok {
|
||||
t.Fatalf("expected FailExpiryTooFar failure code")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ const (
|
||||
CodeFinalExpiryTooSoon FailCode = 17
|
||||
CodeFinalIncorrectCltvExpiry FailCode = 18
|
||||
CodeFinalIncorrectHtlcAmount FailCode = 19
|
||||
CodeExpiryTooFar FailCode = 21
|
||||
)
|
||||
|
||||
// String returns the string representation of the failure code.
|
||||
@ -145,6 +146,9 @@ func (c FailCode) String() string {
|
||||
case CodeFinalIncorrectHtlcAmount:
|
||||
return "FinalIncorrectHtlcAmount"
|
||||
|
||||
case CodeExpiryTooFar:
|
||||
return "ExpiryTooFar"
|
||||
|
||||
default:
|
||||
return "<unknown>"
|
||||
}
|
||||
@ -1038,6 +1042,26 @@ func (f *FailFinalIncorrectHtlcAmount) Encode(w io.Writer, pver uint32) error {
|
||||
return writeElement(w, f.IncomingHTLCAmount)
|
||||
}
|
||||
|
||||
// FailExpiryTooFar is returned if the CLTV expiry in the HTLC is too far in the
|
||||
// future.
|
||||
//
|
||||
// NOTE: May be returned by any node in the payment route.
|
||||
type FailExpiryTooFar struct{}
|
||||
|
||||
// Code returns the failure unique code.
|
||||
//
|
||||
// NOTE: Part of the FailureMessage interface.
|
||||
func (f FailExpiryTooFar) Code() FailCode {
|
||||
return CodeExpiryTooFar
|
||||
}
|
||||
|
||||
// Returns a human readable string describing the target FailureMessage.
|
||||
//
|
||||
// NOTE: Implements the error interface.
|
||||
func (f FailExpiryTooFar) Error() string {
|
||||
return f.Code().String()
|
||||
}
|
||||
|
||||
// DecodeFailure decodes, validates, and parses the lnwire onion failure, for
|
||||
// the provided protocol version.
|
||||
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
|
||||
@ -1199,6 +1223,10 @@ func makeEmptyOnionError(code FailCode) (FailureMessage, error) {
|
||||
|
||||
case CodeFinalIncorrectHtlcAmount:
|
||||
return &FailFinalIncorrectHtlcAmount{}, nil
|
||||
|
||||
case CodeExpiryTooFar:
|
||||
return &FailExpiryTooFar{}, nil
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("unknown error code: %v", code)
|
||||
}
|
||||
|
@ -1957,6 +1957,21 @@ func (r *ChannelRouter) sendPayment(payment *LightningPayment,
|
||||
)
|
||||
continue
|
||||
|
||||
// If we crafted a route that contains a too long time
|
||||
// lock for an intermediate node, we'll prune the node.
|
||||
// As there currently is no way of knowing that node's
|
||||
// maximum acceptable cltv, we cannot take this
|
||||
// constraint into account during routing.
|
||||
//
|
||||
// TODO(joostjager): Record the rejected cltv and use
|
||||
// that as a hint during future path finding through
|
||||
// that node.
|
||||
case *lnwire.FailExpiryTooFar:
|
||||
pruneVertexFailure(
|
||||
paySession, route, errSource, false,
|
||||
)
|
||||
continue
|
||||
|
||||
// If we get a permanent channel or node failure, then
|
||||
// we'll note this (exclude the vertex/edge), and
|
||||
// continue with the rest of the routes.
|
||||
|
Loading…
Reference in New Issue
Block a user