htlcswitch: extract exit hop processing to method
This commit is contained in:
parent
9643b45dbc
commit
eb598ec7a4
@ -2354,228 +2354,20 @@ 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)
|
||||
err.Error(),
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
if err != nil {
|
||||
l.fail(LinkFailureError{code: ErrInternalError},
|
||||
"unable to settle invoice: %v", err)
|
||||
return false
|
||||
if updated {
|
||||
needUpdate = true
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
needUpdate = true
|
||||
|
||||
// There are additional channels left within this route. So
|
||||
// we'll simply do some forwarding package book-keeping.
|
||||
default:
|
||||
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user