htlcswitch: extract exit hop processing to method

This commit is contained in:
Joost Jager 2019-02-08 10:01:54 +01:00
parent 9643b45dbc
commit eb598ec7a4
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7

@ -2354,227 +2354,19 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
fwdInfo := chanIterator.ForwardingInstructions()
switch fwdInfo.NextHop {
case exitHop:
// If hodl.ExitSettle is requested, we will not validate
// the final hop's ADD, nor will we settle the
// corresponding invoice or respond with the preimage.
if l.cfg.DebugHTLC &&
l.cfg.HodlMask.Active(hodl.ExitSettle) {
l.warnf(hodl.ExitSettle.Warning())
continue
}
// First, we'll check the expiry of the HTLC itself
// against, the current block height. If the timeout is
// too soon, then we'll reject the HTLC.
if pd.Timeout-expiryGraceDelta <= heightNow {
log.Errorf("htlc(%x) has an expiry that's too "+
"soon: expiry=%v, best_height=%v",
pd.RHash[:], pd.Timeout, heightNow)
failure := lnwire.FailFinalExpiryTooSoon{}
l.sendHTLCError(
pd.HtlcIndex, &failure, obfuscator, pd.SourceRef,
)
needUpdate = true
continue
}
// We're the designated payment destination. Therefore
// we attempt to see if we have an invoice locally
// which'll allow us to settle this htlc.
invoiceHash := lntypes.Hash(pd.RHash)
invoice, minCltvDelta, err := l.cfg.Registry.LookupInvoice(
invoiceHash,
)
if err != nil {
log.Errorf("unable to query invoice registry: "+
" %v", err)
failure := lnwire.NewFailUnknownPaymentHash(
pd.Amount,
)
l.sendHTLCError(
pd.HtlcIndex, failure, obfuscator, pd.SourceRef,
)
needUpdate = true
continue
}
// Reject htlcs for canceled invoices.
if invoice.Terms.State == channeldb.ContractCanceled {
l.errorf("Rejecting htlc due to canceled " +
"invoice")
failure := lnwire.NewFailUnknownPaymentHash(
pd.Amount,
)
l.sendHTLCError(
pd.HtlcIndex, failure, obfuscator,
pd.SourceRef,
)
needUpdate = true
continue
}
// If the invoice is already settled, we choose to
// accept the payment to simplify failure recovery.
//
// NOTE: Though our recovery and forwarding logic is
// predominately batched, settling invoices happens
// iteratively. We may reject one of two payments
// for the same rhash at first, but then restart and
// reject both after seeing that the invoice has been
// settled. Without any record of which one settles
// first, it is ambiguous as to which one actually
// settled the invoice. Thus, by accepting all
// payments, we eliminate the race condition that can
// lead to this inconsistency.
//
// TODO(conner): track ownership of settlements to
// properly recover from failures? or add batch invoice
// settlement
if invoice.Terms.State != channeldb.ContractOpen {
log.Warnf("Accepting duplicate payment for "+
"hash=%x", pd.RHash[:])
}
// If we're not currently in debug mode, and the
// extended htlc doesn't meet the value requested, then
// we'll fail the htlc. Otherwise, we settle this htlc
// within our local state update log, then send the
// update entry to the remote party.
//
// NOTE: We make an exception when the value requested
// by the invoice is zero. This means the invoice
// allows the payee to specify the amount of satoshis
// they wish to send. So since we expect the htlc to
// have a different amount, we should not fail.
if !l.cfg.DebugHTLC && invoice.Terms.Value > 0 &&
pd.Amount < invoice.Terms.Value {
log.Errorf("rejecting htlc due to incorrect "+
"amount: expected %v, received %v",
invoice.Terms.Value, pd.Amount)
failure := lnwire.NewFailUnknownPaymentHash(
pd.Amount,
)
l.sendHTLCError(
pd.HtlcIndex, failure, obfuscator, pd.SourceRef,
)
needUpdate = true
continue
}
// As we're the exit hop, we'll double check the
// hop-payload included in the HTLC to ensure that it
// was crafted correctly by the sender and matches the
// HTLC we were extended.
//
// NOTE: We make an exception when the value requested
// by the invoice is zero. This means the invoice
// allows the payee to specify the amount of satoshis
// they wish to send. So since we expect the htlc to
// have a different amount, we should not fail.
if !l.cfg.DebugHTLC && invoice.Terms.Value > 0 &&
fwdInfo.AmountToForward < invoice.Terms.Value {
log.Errorf("Onion payload of incoming htlc(%x) "+
"has incorrect value: expected %v, "+
"got %v", pd.RHash, invoice.Terms.Value,
fwdInfo.AmountToForward)
failure := lnwire.NewFailUnknownPaymentHash(
pd.Amount,
)
l.sendHTLCError(
pd.HtlcIndex, failure, obfuscator, pd.SourceRef,
)
needUpdate = true
continue
}
// We'll also ensure that our time-lock value has been
// computed correctly.
expectedHeight := heightNow + minCltvDelta
switch {
case !l.cfg.DebugHTLC && pd.Timeout < expectedHeight:
log.Errorf("Incoming htlc(%x) has an "+
"expiration that is too soon: "+
"expected at least %v, got %v",
pd.RHash[:], expectedHeight, pd.Timeout)
failure := lnwire.FailFinalExpiryTooSoon{}
l.sendHTLCError(
pd.HtlcIndex, failure, obfuscator,
pd.SourceRef,
)
needUpdate = true
continue
case !l.cfg.DebugHTLC && pd.Timeout != fwdInfo.OutgoingCTLV:
log.Errorf("HTLC(%x) has incorrect "+
"time-lock: expected %v, got %v",
pd.RHash[:], pd.Timeout,
fwdInfo.OutgoingCTLV)
failure := lnwire.NewFinalIncorrectCltvExpiry(
fwdInfo.OutgoingCTLV,
)
l.sendHTLCError(
pd.HtlcIndex, failure, obfuscator, pd.SourceRef,
)
needUpdate = true
continue
}
preimage := invoice.Terms.PaymentPreimage
err = l.channel.SettleHTLC(
preimage, pd.HtlcIndex, pd.SourceRef, nil, nil,
updated, err := l.processExitHop(
pd, obfuscator, fwdInfo, heightNow,
)
if err != nil {
l.fail(LinkFailureError{code: ErrInternalError},
"unable to settle htlc: %v", err)
return false
}
// Notify the invoiceRegistry of the invoices we just
// settled (with the amount accepted at settle time)
// with this latest commitment update.
err = l.cfg.Registry.SettleInvoice(
invoiceHash, pd.Amount,
err.Error(),
)
if err != nil {
l.fail(LinkFailureError{code: ErrInternalError},
"unable to settle invoice: %v", err)
return false
}
l.infof("settling %x as exit hop", pd.RHash)
// If the link is in hodl.BogusSettle mode, replace the
// preimage with a fake one before sending it to the
// peer.
if l.cfg.DebugHTLC &&
l.cfg.HodlMask.Active(hodl.BogusSettle) {
l.warnf(hodl.BogusSettle.Warning())
preimage = [32]byte{}
copy(preimage[:], bytes.Repeat([]byte{2}, 32))
}
// HTLC was successfully settled locally send
// notification about it remote peer.
l.cfg.Peer.SendMessage(false, &lnwire.UpdateFulfillHTLC{
ChanID: l.ChanID(),
ID: pd.HtlcIndex,
PaymentPreimage: preimage,
})
if updated {
needUpdate = true
}
// There are additional channels left within this route. So
// we'll simply do some forwarding package book-keeping.
@ -2733,6 +2525,178 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
return needUpdate
}
// processExitHop handles an htlc for which this link is the exit hop. It
// returns a boolean indicating whether the commitment tx needs an update.
func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
obfuscator ErrorEncrypter, fwdInfo ForwardingInfo, heightNow uint32) (
bool, error) {
// If hodl.ExitSettle is requested, we will not validate the final hop's
// ADD, nor will we settle the corresponding invoice or respond with the
// preimage.
if l.cfg.DebugHTLC && l.cfg.HodlMask.Active(hodl.ExitSettle) {
l.warnf(hodl.ExitSettle.Warning())
return false, nil
}
// First, we'll check the expiry of the HTLC itself against, the current
// block height. If the timeout is too soon, then we'll reject the HTLC.
if pd.Timeout-expiryGraceDelta <= heightNow {
log.Errorf("htlc(%x) has an expiry that's too soon: expiry=%v"+
", best_height=%v", pd.RHash[:], pd.Timeout, heightNow)
failure := lnwire.NewFinalExpiryTooSoon()
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
return true, nil
}
// We're the designated payment destination. Therefore we attempt to
// see if we have an invoice locally which'll allow us to settle this
// htlc.
invoiceHash := lntypes.Hash(pd.RHash)
invoice, minCltvDelta, err := l.cfg.Registry.LookupInvoice(invoiceHash)
if err != nil {
log.Errorf("unable to query invoice registry: %v", err)
failure := lnwire.NewFailUnknownPaymentHash(pd.Amount)
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
return true, nil
}
// Reject htlcs for canceled invoices.
if invoice.Terms.State == channeldb.ContractCanceled {
l.errorf("Rejecting htlc due to canceled invoice")
failure := lnwire.NewFailUnknownPaymentHash(pd.Amount)
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
return true, nil
}
// If the invoice is already settled, we choose to accept the payment to
// simplify failure recovery.
//
// NOTE: Though our recovery and forwarding logic is predominately
// batched, settling invoices happens iteratively. We may reject one of
// two payments for the same rhash at first, but then restart and reject
// both after seeing that the invoice has been settled. Without any
// record of which one settles first, it is ambiguous as to which one
// actually settled the invoice. Thus, by accepting all payments, we
// eliminate the race condition that can lead to this inconsistency.
//
// TODO(conner): track ownership of settlements to properly recover from
// failures? or add batch invoice settlement
if invoice.Terms.State != channeldb.ContractOpen {
log.Warnf("Accepting duplicate payment for hash=%x",
pd.RHash[:])
}
// If we're not currently in debug mode, and the extended htlc doesn't
// meet the value requested, then we'll fail the htlc. Otherwise, we
// settle this htlc within our local state update log, then send the
// update entry to the remote party.
//
// NOTE: We make an exception when the value requested by the invoice is
// zero. This means the invoice allows the payee to specify the amount
// of satoshis they wish to send. So since we expect the htlc to have a
// different amount, we should not fail.
if !l.cfg.DebugHTLC && invoice.Terms.Value > 0 &&
pd.Amount < invoice.Terms.Value {
log.Errorf("rejecting htlc due to incorrect amount: expected "+
"%v, received %v", invoice.Terms.Value, pd.Amount)
failure := lnwire.NewFailUnknownPaymentHash(pd.Amount)
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
return true, nil
}
// As we're the exit hop, we'll double check the hop-payload included in
// the HTLC to ensure that it was crafted correctly by the sender and
// matches the HTLC we were extended.
//
// NOTE: We make an exception when the value requested by the invoice is
// zero. This means the invoice allows the payee to specify the amount
// of satoshis they wish to send. So since we expect the htlc to have a
// different amount, we should not fail.
if !l.cfg.DebugHTLC && invoice.Terms.Value > 0 &&
fwdInfo.AmountToForward < invoice.Terms.Value {
log.Errorf("Onion payload of incoming htlc(%x) has incorrect "+
"value: expected %v, got %v", pd.RHash,
invoice.Terms.Value, fwdInfo.AmountToForward)
failure := lnwire.NewFailUnknownPaymentHash(pd.Amount)
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
return true, nil
}
// We'll also ensure that our time-lock value has been computed
// correctly.
expectedHeight := heightNow + minCltvDelta
switch {
case !l.cfg.DebugHTLC && pd.Timeout < expectedHeight:
log.Errorf("Incoming htlc(%x) has an expiration that is too "+
"soon: expected at least %v, got %v",
pd.RHash[:], expectedHeight, pd.Timeout)
failure := lnwire.FailFinalExpiryTooSoon{}
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
return true, nil
case !l.cfg.DebugHTLC && pd.Timeout != fwdInfo.OutgoingCTLV:
log.Errorf("HTLC(%x) has incorrect time-lock: expected %v, "+
"got %v", pd.RHash[:], pd.Timeout, fwdInfo.OutgoingCTLV)
failure := lnwire.NewFinalIncorrectCltvExpiry(
fwdInfo.OutgoingCTLV,
)
l.sendHTLCError(pd.HtlcIndex, failure, obfuscator, pd.SourceRef)
return true, nil
}
preimage := invoice.Terms.PaymentPreimage
err = l.channel.SettleHTLC(
preimage, pd.HtlcIndex, pd.SourceRef, nil, nil,
)
if err != nil {
return false, fmt.Errorf("unable to settle htlc: %v", err)
}
// Notify the invoiceRegistry of the invoices we just settled (with the
// amount accepted at settle time) with this latest commitment update.
err = l.cfg.Registry.SettleInvoice(invoiceHash, pd.Amount)
if err != nil {
return false, fmt.Errorf("unable to settle invoice: %v", err)
}
l.infof("settling %x as exit hop", pd.RHash)
// If the link is in hodl.BogusSettle mode, replace the preimage with a
// fake one before sending it to the peer.
if l.cfg.DebugHTLC && l.cfg.HodlMask.Active(hodl.BogusSettle) {
l.warnf(hodl.BogusSettle.Warning())
preimage = [32]byte{}
copy(preimage[:], bytes.Repeat([]byte{2}, 32))
}
// HTLC was successfully settled locally send notification about it
// remote peer.
l.cfg.Peer.SendMessage(false, &lnwire.UpdateFulfillHTLC{
ChanID: l.ChanID(),
ID: pd.HtlcIndex,
PaymentPreimage: preimage,
})
return true, nil
}
// forwardBatch forwards the given htlcPackets to the switch, and waits on the
// err chan for the individual responses. This method is intended to be spawned
// as a goroutine so the responses can be handled in the background.