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
|
// TODO(roasbeef): must be < default delta
|
||||||
expiryGraceDelta = 2
|
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
|
// DefaultMinLinkFeeUpdateTimeout represents the minimum interval in
|
||||||
// which a link should propose to update its commitment fee rate.
|
// which a link should propose to update its commitment fee rate.
|
||||||
DefaultMinLinkFeeUpdateTimeout = 10 * time.Minute
|
DefaultMinLinkFeeUpdateTimeout = 10 * time.Minute
|
||||||
@ -1950,6 +1961,14 @@ func (l *channelLink) HtlcSatifiesPolicy(payHash [32]byte,
|
|||||||
return failure
|
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
|
// Finally, we'll ensure that the time-lock on the outgoing HTLC meets
|
||||||
// the following constraint: the incoming time-lock minus our time-lock
|
// the following constraint: the incoming time-lock minus our time-lock
|
||||||
// delta should equal the outgoing time lock. Otherwise, whether the
|
// 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)
|
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
|
CodeFinalExpiryTooSoon FailCode = 17
|
||||||
CodeFinalIncorrectCltvExpiry FailCode = 18
|
CodeFinalIncorrectCltvExpiry FailCode = 18
|
||||||
CodeFinalIncorrectHtlcAmount FailCode = 19
|
CodeFinalIncorrectHtlcAmount FailCode = 19
|
||||||
|
CodeExpiryTooFar FailCode = 21
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns the string representation of the failure code.
|
// String returns the string representation of the failure code.
|
||||||
@ -145,6 +146,9 @@ func (c FailCode) String() string {
|
|||||||
case CodeFinalIncorrectHtlcAmount:
|
case CodeFinalIncorrectHtlcAmount:
|
||||||
return "FinalIncorrectHtlcAmount"
|
return "FinalIncorrectHtlcAmount"
|
||||||
|
|
||||||
|
case CodeExpiryTooFar:
|
||||||
|
return "ExpiryTooFar"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "<unknown>"
|
return "<unknown>"
|
||||||
}
|
}
|
||||||
@ -1038,6 +1042,26 @@ func (f *FailFinalIncorrectHtlcAmount) Encode(w io.Writer, pver uint32) error {
|
|||||||
return writeElement(w, f.IncomingHTLCAmount)
|
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
|
// DecodeFailure decodes, validates, and parses the lnwire onion failure, for
|
||||||
// the provided protocol version.
|
// the provided protocol version.
|
||||||
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
|
func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
|
||||||
@ -1199,6 +1223,10 @@ func makeEmptyOnionError(code FailCode) (FailureMessage, error) {
|
|||||||
|
|
||||||
case CodeFinalIncorrectHtlcAmount:
|
case CodeFinalIncorrectHtlcAmount:
|
||||||
return &FailFinalIncorrectHtlcAmount{}, nil
|
return &FailFinalIncorrectHtlcAmount{}, nil
|
||||||
|
|
||||||
|
case CodeExpiryTooFar:
|
||||||
|
return &FailExpiryTooFar{}, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("unknown error code: %v", code)
|
return nil, errors.Errorf("unknown error code: %v", code)
|
||||||
}
|
}
|
||||||
|
@ -1957,6 +1957,21 @@ func (r *ChannelRouter) sendPayment(payment *LightningPayment,
|
|||||||
)
|
)
|
||||||
continue
|
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
|
// If we get a permanent channel or node failure, then
|
||||||
// we'll note this (exclude the vertex/edge), and
|
// we'll note this (exclude the vertex/edge), and
|
||||||
// continue with the rest of the routes.
|
// continue with the rest of the routes.
|
||||||
|
Loading…
Reference in New Issue
Block a user