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()
|
fwdInfo := chanIterator.ForwardingInstructions()
|
||||||
switch fwdInfo.NextHop {
|
switch fwdInfo.NextHop {
|
||||||
case exitHop:
|
case exitHop:
|
||||||
// If hodl.ExitSettle is requested, we will not validate
|
updated, err := l.processExitHop(
|
||||||
// the final hop's ADD, nor will we settle the
|
pd, obfuscator, fwdInfo, heightNow,
|
||||||
// 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,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.fail(LinkFailureError{code: ErrInternalError},
|
l.fail(LinkFailureError{code: ErrInternalError},
|
||||||
"unable to settle htlc: %v", err)
|
err.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if updated {
|
||||||
// Notify the invoiceRegistry of the invoices we just
|
needUpdate = true
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// There are additional channels left within this route. So
|
||||||
// we'll simply do some forwarding package book-keeping.
|
// we'll simply do some forwarding package book-keeping.
|
||||||
default:
|
default:
|
||||||
@ -2733,6 +2525,178 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||||||
return needUpdate
|
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
|
// 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
|
// 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.
|
// as a goroutine so the responses can be handled in the background.
|
||||||
|
Loading…
Reference in New Issue
Block a user