htlcswitch+router: add onion error obfuscation

Within the network, it's important that when an HTLC forwarding failure
occurs, the recipient is notified in a timely manner in order to ensure
that errors are graceful and not unknown. For that reason with
accordance to BOLT №4 onion failure obfuscation have been added.
This commit is contained in:
Andrey Samokhvalov 2017-06-29 16:40:45 +03:00 committed by Olaoluwa Osuntokun
parent ef73062c14
commit 2d378b3280
13 changed files with 639 additions and 212 deletions

View File

@ -38,17 +38,23 @@ type paymentCircuit struct {
// request back. // request back.
Dest lnwire.ShortChannelID Dest lnwire.ShortChannelID
// Obfuscator is used to obfuscate the onion failure before sending it
// back to the originator of the payment.
Obfuscator Obfuscator
// RefCount is used to count the circuits with the same circuit key. // RefCount is used to count the circuits with the same circuit key.
RefCount int RefCount int
} }
// newPaymentCircuit creates new payment circuit instance. // newPaymentCircuit creates new payment circuit instance.
func newPaymentCircuit(src, dest lnwire.ShortChannelID, key circuitKey) *paymentCircuit { func newPaymentCircuit(src, dest lnwire.ShortChannelID, key circuitKey,
obfuscator Obfuscator) *paymentCircuit {
return &paymentCircuit{ return &paymentCircuit{
Src: src, Src: src,
Dest: dest, Dest: dest,
PaymentHash: key, PaymentHash: key,
RefCount: 1, RefCount: 1,
Obfuscator: obfuscator,
} }
} }

87
htlcswitch/failure.go Normal file
View File

@ -0,0 +1,87 @@
package htlcswitch
import (
"bytes"
"github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
)
// Deobfuscator entity which is used to de-obfuscate the onion opaque reason and
// extract failure.
type Deobfuscator interface {
// Deobfuscate function decodes the onion error failure.
Deobfuscate(lnwire.OpaqueReason) (lnwire.FailureMessage, error)
}
// Obfuscator entity which is used to do the initial and backward onion
// failure obfuscation.
type Obfuscator interface {
// InitialObfuscate is used to convert the failure into opaque
// reason.
InitialObfuscate(lnwire.FailureMessage) (lnwire.OpaqueReason, error)
// BackwardObfuscate is used to make the processing over onion error
// when it moves backward to the htlc sender.
BackwardObfuscate(lnwire.OpaqueReason) lnwire.OpaqueReason
}
// FailureObfuscator is used to obfuscate the onion failure.
type FailureObfuscator struct {
*sphinx.OnionObfuscator
}
// InitialObfuscate is used by the failure sender to decode the failure and
// make the initial failure obfuscation with addition of the failure data hmac.
//
// NOTE: Part of the Obfuscator interface.
func (o *FailureObfuscator) InitialObfuscate(failure lnwire.FailureMessage) (
lnwire.OpaqueReason, error) {
var b bytes.Buffer
if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
return nil, err
}
// Make the initial obfuscation with appending hmac.
return o.OnionObfuscator.Obfuscate(true, b.Bytes()), nil
}
// BackwardObfuscate is used by the forwarding nodes in order to obfuscate the
// already obfuscated onion failure blob with the stream which have been
// generated with our shared secret. The reason we re-encrypt the message on the
// backwards path is to ensure that the error is computationally
// indistinguishable from any other error seen.
//
// NOTE: Part of the Obfuscator interface.
func (o *FailureObfuscator) BackwardObfuscate(
reason lnwire.OpaqueReason) lnwire.OpaqueReason {
return o.OnionObfuscator.Obfuscate(false, reason)
}
// A compile time check to ensure FailureObfuscator implements the
// Obfuscator interface.
var _ Obfuscator = (*FailureObfuscator)(nil)
// FailureDeobfuscator wraps the sphinx data obfuscator and adds awareness of
// the lnwire onion failure messages to it.
type FailureDeobfuscator struct {
*sphinx.OnionDeobfuscator
}
// Deobfuscate decodes the obfuscated onion failure.
//
// NOTE: Part of the Obfuscator interface.
func (o *FailureDeobfuscator) Deobfuscate(reason lnwire.OpaqueReason) (lnwire.FailureMessage,
error) {
_, failureData, err := o.OnionDeobfuscator.Deobfuscate(reason)
if err != nil {
return nil, err
}
r := bytes.NewReader(failureData)
return lnwire.DecodeFailure(r, 0)
}
// A compile time check to ensure FailureDeobfuscator implements the
// Deobfuscator interface.
var _ Deobfuscator = (*FailureDeobfuscator)(nil)

View File

@ -4,7 +4,6 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcutil" "github.com/roasbeef/btcutil"
@ -139,7 +138,7 @@ func (r *sphinxHopIterator) ForwardingInstructions() ForwardingInfo {
} }
} }
// SphinxDecoder is responsible for keeping all sphinx dependent parts inside // OnionProcessor is responsible for keeping all sphinx dependent parts inside
// and expose only decoding function. With such approach we give freedom for // and expose only decoding function. With such approach we give freedom for
// subsystems which wants to decode sphinx path to not be dependable from // subsystems which wants to decode sphinx path to not be dependable from
// sphinx at all. // sphinx at all.
@ -148,25 +147,23 @@ func (r *sphinxHopIterator) ForwardingInstructions() ForwardingInfo {
// maintain the hop iterator abstraction. Without it the structures which using // maintain the hop iterator abstraction. Without it the structures which using
// the hop iterator should contain sphinx router which makes their creations in // the hop iterator should contain sphinx router which makes their creations in
// tests dependent from the sphinx internal parts. // tests dependent from the sphinx internal parts.
type SphinxDecoder struct { type OnionProcessor struct {
router *sphinx.Router router *sphinx.Router
} }
// NewSphinxDecoder creates new instance of decoder. // NewOnionProcessor creates new instance of decoder.
func NewSphinxDecoder(router *sphinx.Router) *SphinxDecoder { func NewOnionProcessor(router *sphinx.Router) *OnionProcessor {
return &SphinxDecoder{router} return &OnionProcessor{router}
} }
// Decode attempts to decode a valid sphinx packet from the passed io.Reader // DecodeHopIterator attempts to decode a valid sphinx packet from the passed io.Reader
// instance using the rHash as the associated data when checking the relevant // instance using the rHash as the associated data when checking the relevant
// MACs during the decoding process. // MACs during the decoding process.
func (p *SphinxDecoder) Decode(r io.Reader, rHash []byte) (HopIterator, error) { func (p *OnionProcessor) DecodeHopIterator(r io.Reader, rHash []byte) (HopIterator,
// Before adding the new HTLC to the state machine, parse the onion lnwire.FailCode) {
// object in order to obtain the routing information.
onionPkt := &sphinx.OnionPacket{} onionPkt := &sphinx.OnionPacket{}
if err := onionPkt.Decode(r); err != nil { if err := onionPkt.Decode(r); err != nil {
return nil, errors.Errorf("unable to decode onion pkt: %v", return nil, lnwire.CodeTemporaryChannelFailure
err)
} }
// Attempt to process the Sphinx packet. We include the payment hash of // Attempt to process the Sphinx packet. We include the payment hash of
@ -176,12 +173,49 @@ func (p *SphinxDecoder) Decode(r io.Reader, rHash []byte) (HopIterator, error) {
// hash twice, thereby losing their money entirely. // hash twice, thereby losing their money entirely.
sphinxPacket, err := p.router.ProcessOnionPacket(onionPkt, rHash) sphinxPacket, err := p.router.ProcessOnionPacket(onionPkt, rHash)
if err != nil { if err != nil {
return nil, errors.Errorf("unable to process onion pkt: "+ switch err {
"%v", err) case sphinx.ErrInvalidOnionVersion:
return nil, lnwire.CodeInvalidOnionVersion
case sphinx.ErrInvalidOnionHMAC:
return nil, lnwire.CodeInvalidOnionHmac
case sphinx.ErrInvalidOnionKey:
return nil, lnwire.CodeInvalidOnionKey
default:
return nil, lnwire.CodeTemporaryChannelFailure
}
} }
return &sphinxHopIterator{ return &sphinxHopIterator{
nextPacket: sphinxPacket.NextPacket, nextPacket: sphinxPacket.NextPacket,
processedPacket: sphinxPacket, processedPacket: sphinxPacket,
}, nil }, lnwire.CodeNone
}
// DecodeOnionObfuscator takes the onion blob as input extract the shared secret
// and return the entity which is able to obfuscate failure data.
func (p *OnionProcessor) DecodeOnionObfuscator(r io.Reader) (Obfuscator,
lnwire.FailCode) {
onionPkt := &sphinx.OnionPacket{}
if err := onionPkt.Decode(r); err != nil {
return nil, lnwire.CodeTemporaryChannelFailure
}
onionObfuscator, err := sphinx.NewOnionObfuscator(p.router,
onionPkt.EphemeralKey)
if err != nil {
switch err {
case sphinx.ErrInvalidOnionVersion:
return nil, lnwire.CodeInvalidOnionVersion
case sphinx.ErrInvalidOnionHMAC:
return nil, lnwire.CodeInvalidOnionHmac
case sphinx.ErrInvalidOnionKey:
return nil, lnwire.CodeInvalidOnionKey
default:
return nil, lnwire.CodeTemporaryChannelFailure
}
}
return &FailureObfuscator{
OnionObfuscator: onionObfuscator,
}, lnwire.CodeNone
} }

View File

@ -9,6 +9,9 @@ import (
"io" "io"
"crypto/sha256"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
@ -80,10 +83,18 @@ type ChannelLinkConfig struct {
// contained in the forwarding blob within each HTLC. // contained in the forwarding blob within each HTLC.
Switch *Switch Switch *Switch
// DecodeOnion function responsible for decoding htlc Sphinx onion // DecodeHopIterator function is responsible for decoding htlc Sphinx onion
// blob, and creating hop iterator which will give us next destination // blob, and creating hop iterator which will give us next destination
// of htlc. // of htlc.
DecodeOnion func(r io.Reader, meta []byte) (HopIterator, error) DecodeHopIterator func(r io.Reader, rHash []byte) (HopIterator, lnwire.FailCode)
// DecodeOnionObfuscator function is responsible for decoding htlc Sphinx
// onion blob, and creating onion failure obfuscator.
DecodeOnionObfuscator func(r io.Reader) (Obfuscator, lnwire.FailCode)
// GetLastChannelUpdate retrieve the topology info about the channel in
// order to create the channel update.
GetLastChannelUpdate func() (*lnwire.ChannelUpdate, error)
// Peer is a lightning network node with which we have the channel link // Peer is a lightning network node with which we have the channel link
// opened. // opened.
@ -406,32 +417,57 @@ func (l *channelLink) handleDownStreamPkt(pkt *htlcPacket) {
// commitment chains. // commitment chains.
htlc.ChanID = l.ChanID() htlc.ChanID = l.ChanID()
index, err := l.channel.AddHTLC(htlc) index, err := l.channel.AddHTLC(htlc)
if err == lnwallet.ErrMaxHTLCNumber { if err != nil {
log.Infof("Downstream htlc add update with "+ switch err {
"payment hash(%x) have been added to "+ case lnwallet.ErrMaxHTLCNumber:
"reprocessing queue, batch: %v", log.Infof("Downstream htlc add update with "+
htlc.PaymentHash[:], "payment hash(%x) have been added to "+
l.batchCounter) "reprocessing queue, batch: %v",
l.overflowQueue.consume(pkt) htlc.PaymentHash[:],
return l.batchCounter)
} else if err != nil { l.overflowQueue.consume(pkt)
failPacket := newFailPacket( return
l.ShortChanID(),
&lnwire.UpdateFailHTLC{
Reason: []byte{byte(0)},
},
htlc.PaymentHash,
htlc.Amount,
)
// The HTLC was unable to be added to the state default:
// machine, as a result, we'll signal the switch to // The HTLC was unable to be added to the state
// cancel the pending payment. // machine, as a result, we'll signal the switch to
go l.cfg.Switch.forward(failPacket) // cancel the pending payment.
var (
isObfuscated bool
reason lnwire.OpaqueReason
)
log.Errorf("unable to handle downstream add HTLC: %v", failure := lnwire.NewTemporaryChannelFailure(nil)
err) onionReader := bytes.NewReader(htlc.OnionBlob[:])
return obfuscator, failCode := l.cfg.DecodeOnionObfuscator(onionReader)
if failCode != lnwire.CodeNone {
var b bytes.Buffer
err := lnwire.EncodeFailure(&b, failure, 0)
if err != nil {
log.Errorf("unable to encode failure: %v", err)
return
}
reason = lnwire.OpaqueReason(b.Bytes())
isObfuscated = false
} else {
reason, err = obfuscator.InitialObfuscate(failure)
if err != nil {
log.Errorf("unable to obfuscate error: %v", err)
return
}
isObfuscated = true
}
go l.cfg.Switch.forward(newFailPacket(
l.ShortChanID(),
&lnwire.UpdateFailHTLC{
Reason: reason,
}, htlc.PaymentHash, htlc.Amount, isObfuscated,
))
log.Infof("Unable to handle downstream add HTLC: %v", err)
return
}
} }
log.Tracef("Received downstream htlc: payment_hash=%x, "+ log.Tracef("Received downstream htlc: payment_hash=%x, "+
@ -439,7 +475,6 @@ func (l *channelLink) handleDownStreamPkt(pkt *htlcPacket) {
htlc.PaymentHash[:], index, l.batchCounter+1) htlc.PaymentHash[:], index, l.batchCounter+1)
htlc.ID = index htlc.ID = index
l.cfg.Peer.SendMessage(htlc) l.cfg.Peer.SendMessage(htlc)
case *lnwire.UpdateFufillHTLC: case *lnwire.UpdateFufillHTLC:
@ -516,9 +551,6 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
log.Tracef("Receive upstream htlc with payment hash(%x), "+ log.Tracef("Receive upstream htlc with payment hash(%x), "+
"assigning index: %v", msg.PaymentHash[:], index) "assigning index: %v", msg.PaymentHash[:], index)
// TODO(roasbeef): perform sanity checks on per-hop payload
// * time-lock is sane, fee, chain, etc
// Store the onion blob which encapsulate the htlc route and // Store the onion blob which encapsulate the htlc route and
// use in on stage of HTLC inclusion to retrieve the next hop // use in on stage of HTLC inclusion to retrieve the next hop
// and propagate the HTLC along the remaining route. // and propagate the HTLC along the remaining route.
@ -535,6 +567,46 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
// TODO(roasbeef): add preimage to DB in order to swipe // TODO(roasbeef): add preimage to DB in order to swipe
// repeated r-values // repeated r-values
// If remote side have been unable to parse the onion blob we have sent
// to it, than we should transform the malformed notification to the the
// usual htlc fail message.
case *lnwire.UpdateFailMalformedHTLC:
idx := msg.ID
if err := l.channel.ReceiveFailHTLC(idx); err != nil {
l.fail("unable to handle upstream fail HTLC: %v", err)
return
}
var failure lnwire.FailureMessage
switch msg.FailureCode {
case lnwire.CodeInvalidOnionVersion:
failure = &lnwire.FailInvalidOnionVersion{
OnionSHA256: msg.ShaOnionBlob,
}
case lnwire.CodeInvalidOnionHmac:
failure = &lnwire.FailInvalidOnionHmac{
OnionSHA256: msg.ShaOnionBlob,
}
case lnwire.CodeInvalidOnionKey:
failure = &lnwire.FailInvalidOnionKey{
OnionSHA256: msg.ShaOnionBlob,
}
default:
log.Errorf("unable to understand code of received " +
"malformed error")
return
}
var b bytes.Buffer
if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
log.Errorf("unable to encode malformed error: %v", err)
return
}
l.cancelReasons[idx] = lnwire.OpaqueReason(b.Bytes())
case *lnwire.UpdateFailHTLC: case *lnwire.UpdateFailHTLC:
idx := msg.ID idx := msg.ID
if err := l.channel.ReceiveFailHTLC(idx); err != nil { if err := l.channel.ReceiveFailHTLC(idx); err != nil {
@ -847,7 +919,7 @@ func (l *channelLink) processLockedInHtlcs(
packetsToForward = append(packetsToForward, settlePacket) packetsToForward = append(packetsToForward, settlePacket)
l.overflowQueue.release() l.overflowQueue.release()
// A failure message for a previously forwarded HTLC has been // A failureCode message for a previously forwarded HTLC has been
// received. As a result a new slot will be freed up in our // received. As a result a new slot will be freed up in our
// commitment state, so we'll forward this to the switch so the // commitment state, so we'll forward this to the switch so the
// backwards undo can continue. // backwards undo can continue.
@ -861,7 +933,7 @@ func (l *channelLink) processLockedInHtlcs(
ChanID: l.ChanID(), ChanID: l.ChanID(),
} }
failPacket := newFailPacket(l.ShortChanID(), failUpdate, failPacket := newFailPacket(l.ShortChanID(), failUpdate,
pd.RHash, pd.Amount) pd.RHash, pd.Amount, false)
// Add the packet to the batch to be forwarded, and // Add the packet to the batch to be forwarded, and
// notify the overflow queue that a spare spot has been // notify the overflow queue that a spare spot has been
@ -880,11 +952,22 @@ func (l *channelLink) processLockedInHtlcs(
onionBlob := l.clearedOnionBlobs[pd.Index] onionBlob := l.clearedOnionBlobs[pd.Index]
delete(l.clearedOnionBlobs, pd.Index) delete(l.clearedOnionBlobs, pd.Index)
// Retrieve onion obfuscator from onion blob in order to produce
// initial obfuscation of the onion failureCode.
onionReader := bytes.NewReader(onionBlob[:]) onionReader := bytes.NewReader(onionBlob[:])
obfuscator, failureCode := l.cfg.DecodeOnionObfuscator(onionReader)
if failureCode != lnwire.CodeNone {
log.Error("unable to decode onion obfuscator")
// If we unable to process the onion blob than we should send
// the malformed htlc error to payment sender.
l.sendMalformedHTLCError(pd.RHash, failureCode, onionBlob[:])
needUpdate = true
continue
}
// Before adding the new htlc to the state machine, // Before adding the new htlc to the state machine,
// parse the onion object in order to obtain the // parse the onion object in order to obtain the
// routing information with DecodeOnion function which // routing information with DecodeHopIterator function which
// process the Sphinx packet. // process the Sphinx packet.
// //
// We include the payment hash of the htlc as it's // We include the payment hash of the htlc as it's
@ -893,20 +976,18 @@ func (l *channelLink) processLockedInHtlcs(
// attacks. In the case of a replay, an attacker is // attacks. In the case of a replay, an attacker is
// *forced* to use the same payment hash twice, thereby // *forced* to use the same payment hash twice, thereby
// losing their money entirely. // losing their money entirely.
chanIterator, err := l.cfg.DecodeOnion(onionReader, onionReader = bytes.NewReader(onionBlob[:])
pd.RHash[:]) chanIterator, failureCode := l.cfg.DecodeHopIterator(onionReader, pd.RHash[:])
if err != nil { if failureCode != lnwire.CodeNone {
// If we're unable to parse the Sphinx packet, log.Error("unable to decode onion hop iterator")
// then we'll cancel the htlc. // If we unable to process the onion blob than we should send
log.Errorf("unable to get the next hop: %v", err) // the malformed htlc error to payment sender.
reason := []byte{byte(lnwire.SphinxParseError)} l.sendMalformedHTLCError(pd.RHash, failureCode, onionBlob[:])
l.sendHTLCError(pd.RHash, reason)
needUpdate = true needUpdate = true
continue continue
} }
fwdInfo := chanIterator.ForwardingInstructions() fwdInfo := chanIterator.ForwardingInstructions()
switch fwdInfo.NextHop { switch fwdInfo.NextHop {
case exitHop: case exitHop:
// We're the designated payment destination. // We're the designated payment destination.
@ -918,8 +999,8 @@ func (l *channelLink) processLockedInHtlcs(
if err != nil { if err != nil {
log.Errorf("unable to query to locate:"+ log.Errorf("unable to query to locate:"+
" %v", err) " %v", err)
reason := []byte{byte(lnwire.UnknownPaymentHash)} failure := lnwire.FailUnknownPaymentHash{}
l.sendHTLCError(pd.RHash, reason) l.sendHTLCError(pd.RHash, failure, obfuscator)
needUpdate = true needUpdate = true
continue continue
} }
@ -940,8 +1021,8 @@ func (l *channelLink) processLockedInHtlcs(
invoice.Terms.Value, invoice.Terms.Value,
fwdInfo.AmountToForward) fwdInfo.AmountToForward)
reason := []byte{byte(lnwire.IncorrectValue)} failure := lnwire.FailIncorrectPaymentAmount{}
l.sendHTLCError(pd.RHash, reason) l.sendHTLCError(pd.RHash, failure, obfuscator)
needUpdate = true needUpdate = true
continue continue
} }
@ -954,8 +1035,8 @@ func (l *channelLink) processLockedInHtlcs(
pd.RHash[:], l.cfg.FwrdingPolicy.TimeLockDelta, pd.RHash[:], l.cfg.FwrdingPolicy.TimeLockDelta,
fwdInfo.OutgoingCTLV) fwdInfo.OutgoingCTLV)
reason := []byte{byte(lnwire.UpstreamTimeout)} failure := lnwire.NewFinalIncorrectCltvExpiry(fwdInfo.OutgoingCTLV)
l.sendHTLCError(pd.RHash, reason) l.sendHTLCError(pd.RHash, failure, obfuscator)
needUpdate = true needUpdate = true
continue continue
} }
@ -970,9 +1051,8 @@ func (l *channelLink) processLockedInHtlcs(
log.Errorf("rejecting htlc due to incorrect "+ log.Errorf("rejecting htlc due to incorrect "+
"amount: expected %v, received %v", "amount: expected %v, received %v",
invoice.Terms.Value, pd.Amount) invoice.Terms.Value, pd.Amount)
failure := lnwire.FailIncorrectPaymentAmount{}
reason := []byte{byte(lnwire.IncorrectValue)} l.sendHTLCError(pd.RHash, failure, obfuscator)
l.sendHTLCError(pd.RHash, reason)
needUpdate = true needUpdate = true
continue continue
} }
@ -1016,8 +1096,16 @@ func (l *channelLink) processLockedInHtlcs(
pd.RHash[:], l.cfg.FwrdingPolicy.MinHTLC, pd.RHash[:], l.cfg.FwrdingPolicy.MinHTLC,
pd.Amount) pd.Amount)
reason := []byte{byte(lnwire.IncorrectValue)} var failure lnwire.FailureMessage
l.sendHTLCError(pd.RHash, reason) update, err := l.cfg.GetLastChannelUpdate()
if err != nil {
failure = lnwire.NewTemporaryChannelFailure(nil)
} else {
failure = lnwire.NewAmountBelowMinimum(
pd.Amount, *update)
}
l.sendHTLCError(pd.RHash, failure, obfuscator)
needUpdate = true needUpdate = true
continue continue
} }
@ -1046,9 +1134,16 @@ func (l *channelLink) processLockedInHtlcs(
btcutil.Amount(pd.Amount-fwdInfo.AmountToForward), btcutil.Amount(pd.Amount-fwdInfo.AmountToForward),
btcutil.Amount(expectedFee)) btcutil.Amount(expectedFee))
// TODO(andrew.shvv): send proper back error var failure lnwire.FailureMessage
reason := []byte{byte(lnwire.IncorrectValue)} update, err := l.cfg.GetLastChannelUpdate()
l.sendHTLCError(pd.RHash, reason) if err != nil {
failure = lnwire.NewTemporaryChannelFailure(nil)
} else {
failure = lnwire.NewFeeInsufficient(pd.Amount,
*update)
}
l.sendHTLCError(pd.RHash, failure, obfuscator)
needUpdate = true needUpdate = true
continue continue
} }
@ -1062,16 +1157,22 @@ func (l *channelLink) processLockedInHtlcs(
// with the HTLC. // with the HTLC.
timeDelta := l.cfg.FwrdingPolicy.TimeLockDelta timeDelta := l.cfg.FwrdingPolicy.TimeLockDelta
if pd.Timeout-timeDelta != fwdInfo.OutgoingCTLV { if pd.Timeout-timeDelta != fwdInfo.OutgoingCTLV {
// TODO(andrew.shvv): send proper back error
log.Errorf("Incoming htlc(%x) has "+ log.Errorf("Incoming htlc(%x) has "+
"incorrect time-lock value: expected "+ "incorrect time-lock value: expected "+
"%v blocks, got %v blocks", "%v blocks, got %v blocks",
pd.RHash[:], pd.Timeout-timeDelta, pd.RHash[:], pd.Timeout-timeDelta,
fwdInfo.OutgoingCTLV) fwdInfo.OutgoingCTLV)
// TODO(andrew.shvv): send proper back error update, err := l.cfg.GetLastChannelUpdate()
reason := []byte{byte(lnwire.UpstreamTimeout)} if err != nil {
l.sendHTLCError(pd.RHash, reason) l.fail("unable to create channel update "+
"while handling the error: %v", err)
return nil
}
failure := lnwire.NewIncorrectCltvExpiry(
pd.Timeout, *update)
l.sendHTLCError(pd.RHash, failure, obfuscator)
needUpdate = true needUpdate = true
continue continue
} }
@ -1094,14 +1195,15 @@ func (l *channelLink) processLockedInHtlcs(
if err != nil { if err != nil {
log.Errorf("unable to encode the "+ log.Errorf("unable to encode the "+
"remaining route %v", err) "remaining route %v", err)
reason := []byte{byte(lnwire.UnknownError)}
l.sendHTLCError(pd.RHash, reason) failure := lnwire.NewTemporaryChannelFailure(nil)
l.sendHTLCError(pd.RHash, failure, obfuscator)
needUpdate = true needUpdate = true
continue continue
} }
updatePacket := newAddPacket(l.ShortChanID(), updatePacket := newAddPacket(l.ShortChanID(),
fwdInfo.NextHop, addMsg) fwdInfo.NextHop, addMsg, obfuscator)
packetsToForward = append(packetsToForward, updatePacket) packetsToForward = append(packetsToForward, updatePacket)
} }
} }
@ -1122,8 +1224,13 @@ func (l *channelLink) processLockedInHtlcs(
// sendHTLCError functions cancels htlc and send cancel message back to the // sendHTLCError functions cancels htlc and send cancel message back to the
// peer from which htlc was received. // peer from which htlc was received.
func (l *channelLink) sendHTLCError(rHash [32]byte, func (l *channelLink) sendHTLCError(rHash [32]byte, failure lnwire.FailureMessage,
reason lnwire.OpaqueReason) { obfuscator Obfuscator) {
reason, err := obfuscator.InitialObfuscate(failure)
if err != nil {
log.Errorf("unable to obfuscate error: %v", err)
return
}
index, err := l.channel.FailHTLC(rHash) index, err := l.channel.FailHTLC(rHash)
if err != nil { if err != nil {
@ -1138,10 +1245,28 @@ func (l *channelLink) sendHTLCError(rHash [32]byte,
}) })
} }
// sendMalformedHTLCError helper function which sends the malformed htlc update
// to the payment sender.
func (l *channelLink) sendMalformedHTLCError(rHash [32]byte, code lnwire.FailCode,
onionBlob []byte) {
index, err := l.channel.FailHTLC(rHash)
if err != nil {
log.Errorf("unable cancel htlc: %v", err)
return
}
l.cfg.Peer.SendMessage(&lnwire.UpdateFailMalformedHTLC{
ChanID: l.ChanID(),
ID: index,
ShaOnionBlob: sha256.Sum256(onionBlob),
FailureCode: code,
})
}
// fail helper function which is used to encapsulate the action neccessary // fail helper function which is used to encapsulate the action neccessary
// for proper disconnect. // for proper disconnect.
func (l *channelLink) fail(format string, a ...interface{}) { func (l *channelLink) fail(format string, a ...interface{}) {
reason := errors.Errorf(format, a...) reason := errors.Errorf(format, a...)
log.Error(reason) log.Error(reason)
l.cfg.Peer.Disconnect(reason) l.cfg.Peer.Disconnect(reason)
} }

View File

@ -376,10 +376,10 @@ func TestExitNodeTimelockPayloadMismatch(t *testing.T) {
n.bobServer.PubKey(), hops, amount, htlcAmt, htlcExpiry) n.bobServer.PubKey(), hops, amount, htlcAmt, htlcExpiry)
if err == nil { if err == nil {
t.Fatalf("payment should have failed but didn't") t.Fatalf("payment should have failed but didn't")
} else if err.Error() != lnwire.UpstreamTimeout.String() { } else if err.Error() != lnwire.CodeFinalIncorrectCltvExpiry.String() {
// TODO(roasbeef): use proper error after error propagation is // TODO(roasbeef): use proper error after error propagation is
// in // in
t.Fatalf("incorrect error, expected insufficient value, "+ t.Fatalf("incorrect error, expected incorrect cltv expiry, "+
"instead have: %v", err) "instead have: %v", err)
} }
} }
@ -413,7 +413,7 @@ func TestExitNodeAmountPayloadMismatch(t *testing.T) {
n.bobServer.PubKey(), hops, amount, htlcAmt, htlcExpiry) n.bobServer.PubKey(), hops, amount, htlcAmt, htlcExpiry)
if err == nil { if err == nil {
t.Fatalf("payment should have failed but didn't") t.Fatalf("payment should have failed but didn't")
} else if err.Error() != lnwire.IncorrectValue.String() { } else if err.Error() != lnwire.CodeIncorrectPaymentAmount.String() {
// TODO(roasbeef): use proper error after error propagation is // TODO(roasbeef): use proper error after error propagation is
// in // in
t.Fatalf("incorrect error, expected insufficient value, "+ t.Fatalf("incorrect error, expected insufficient value, "+
@ -455,10 +455,8 @@ func TestLinkForwardTimelockPolicyMismatch(t *testing.T) {
// should be rejected due to a policy violation. // should be rejected due to a policy violation.
if err == nil { if err == nil {
t.Fatalf("payment should have failed but didn't") t.Fatalf("payment should have failed but didn't")
} else if err.Error() != lnwire.UpstreamTimeout.String() { } else if err.Error() != lnwire.CodeIncorrectCltvExpiry.String() {
// TODO(roasbeef): use proper error after error propagation is t.Fatalf("incorrect error, expected incorrect cltv expiry, "+
// in
t.Fatalf("incorrect error, expected insufficient value, "+
"instead have: %v", err) "instead have: %v", err)
} }
} }
@ -498,10 +496,10 @@ func TestLinkForwardFeePolicyMismatch(t *testing.T) {
// should be rejected due to a policy violation. // should be rejected due to a policy violation.
if err == nil { if err == nil {
t.Fatalf("payment should have failed but didn't") t.Fatalf("payment should have failed but didn't")
} else if err.Error() != lnwire.IncorrectValue.String() { } else if err.Error() != lnwire.CodeFeeInsufficient.String() {
// TODO(roasbeef): use proper error after error propagation is // TODO(roasbeef): use proper error after error propagation is
// in // in
t.Fatalf("incorrect error, expected insufficient value, "+ t.Fatalf("incorrect error, expected fee insufficient, "+
"instead have: %v", err) "instead have: %v", err)
} }
} }
@ -541,10 +539,10 @@ func TestLinkForwardMinHTLCPolicyMismatch(t *testing.T) {
// should be rejected due to a policy violation (below min HTLC). // should be rejected due to a policy violation (below min HTLC).
if err == nil { if err == nil {
t.Fatalf("payment should have failed but didn't") t.Fatalf("payment should have failed but didn't")
} else if err.Error() != lnwire.IncorrectValue.String() { } else if err.Error() != lnwire.CodeAmountBelowMinimum.String() {
// TODO(roasbeef): use proper error after error propagation is // TODO(roasbeef): use proper error after error propagation is
// in // in
t.Fatalf("incorrect error, expected insufficient value, "+ t.Fatalf("incorrect error, expected amount below minimum, "+
"instead have: %v", err) "instead have: %v", err)
} }
} }
@ -633,7 +631,7 @@ func TestChannelLinkMultiHopInsufficientPayment(t *testing.T) {
btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5,
) )
if err := n.start(); err != nil { if err := n.start(); err != nil {
t.Fatalf("can't start three hop network: %v", err) t.Fatalf("unable to start three hop network: %v", err)
} }
defer n.stop() defer n.stop()
@ -656,7 +654,7 @@ func TestChannelLinkMultiHopInsufficientPayment(t *testing.T) {
n.bobServer.PubKey(), hops, amount, htlcAmt, totalTimelock) n.bobServer.PubKey(), hops, amount, htlcAmt, totalTimelock)
if err == nil { if err == nil {
t.Fatal("error haven't been received") t.Fatal("error haven't been received")
} else if err.Error() != errors.New(lnwire.InsufficientCapacity).Error() { } else if err.Error() != errors.New(lnwire.CodeTemporaryChannelFailure).Error() {
t.Fatalf("wrong error have been received: %v", err) t.Fatalf("wrong error have been received: %v", err)
} }
@ -702,7 +700,7 @@ func TestChannelLinkMultiHopUnknownPaymentHash(t *testing.T) {
btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5,
) )
if err := n.start(); err != nil { if err := n.start(); err != nil {
t.Fatalf("can't start three hop network: %v", err) t.Fatalf("unable to start three hop network: %v", err)
} }
defer n.stop() defer n.stop()
@ -733,13 +731,14 @@ func TestChannelLinkMultiHopUnknownPaymentHash(t *testing.T) {
// Check who is last in the route and add invoice to server registry. // Check who is last in the route and add invoice to server registry.
if err := n.carolServer.registry.AddInvoice(invoice); err != nil { if err := n.carolServer.registry.AddInvoice(invoice); err != nil {
t.Fatalf("can't add invoice in carol registry: %v", err) t.Fatalf("unable to add invoice in carol registry: %v", err)
} }
// Send payment and expose err channel. // Send payment and expose err channel.
_, err = n.aliceServer.htlcSwitch.SendHTLC(n.bobServer.PubKey(), htlc) _, err = n.aliceServer.htlcSwitch.SendHTLC(n.bobServer.PubKey(), htlc,
if err == nil { newMockDeobfuscator())
t.Fatal("error wasn't received") if err.Error() != lnwire.CodeUnknownPaymentHash.String() {
t.Fatal("error haven't been received")
} }
// Wait for Alice to receive the revocation. // Wait for Alice to receive the revocation.
@ -802,7 +801,7 @@ func TestChannelLinkMultiHopUnknownNextHop(t *testing.T) {
amount, htlcAmt, totalTimelock) amount, htlcAmt, totalTimelock)
if err == nil { if err == nil {
t.Fatal("error haven't been received") t.Fatal("error haven't been received")
} else if err.Error() != errors.New(lnwire.UnknownDestination).Error() { } else if err.Error() != lnwire.CodeUnknownNextPeer.String() {
t.Fatalf("wrong error have been received: %v", err) t.Fatalf("wrong error have been received: %v", err)
} }
@ -848,14 +847,14 @@ func TestChannelLinkMultiHopDecodeError(t *testing.T) {
btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5,
) )
if err := n.start(); err != nil { if err := n.start(); err != nil {
t.Fatalf("can't start three hop network: %v", err) t.Fatalf("unable to start three hop network: %v", err)
} }
defer n.stop() defer n.stop()
// Replace decode function with another which throws an error. // Replace decode function with another which throws an error.
n.carolChannelLink.cfg.DecodeOnion = func(r io.Reader, meta []byte) ( n.carolChannelLink.cfg.DecodeOnionObfuscator = func(
HopIterator, error) { r io.Reader) (Obfuscator, lnwire.FailCode) {
return nil, errors.New("some sphinx decode error") return nil, lnwire.CodeInvalidOnionVersion
} }
carolBandwidthBefore := n.carolChannelLink.Bandwidth() carolBandwidthBefore := n.carolChannelLink.Bandwidth()
@ -871,7 +870,7 @@ func TestChannelLinkMultiHopDecodeError(t *testing.T) {
n.bobServer.PubKey(), hops, amount, htlcAmt, totalTimelock) n.bobServer.PubKey(), hops, amount, htlcAmt, totalTimelock)
if err == nil { if err == nil {
t.Fatal("error haven't been received") t.Fatal("error haven't been received")
} else if err.Error() != errors.New(lnwire.SphinxParseError).Error() { } else if err.Error() != lnwire.CodeInvalidOnionVersion.String() {
t.Fatalf("wrong error have been received: %v", err) t.Fatalf("wrong error have been received: %v", err)
} }
@ -994,7 +993,7 @@ func TestChannelLinkSingleHopMessageOrdering(t *testing.T) {
}) })
if err := n.start(); err != nil { if err := n.start(); err != nil {
t.Fatalf("can't start three hop network: %v", err) t.Fatalf("unable to start three hop network: %v", err)
} }
defer n.stop() defer n.stop()

View File

@ -9,6 +9,8 @@ import (
"io" "io"
"sync/atomic" "sync/atomic"
"bytes"
"github.com/btcsuite/fastsha256" "github.com/btcsuite/fastsha256"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
@ -147,15 +149,60 @@ func (f *ForwardingInfo) encode(w io.Writer) error {
var _ HopIterator = (*mockHopIterator)(nil) var _ HopIterator = (*mockHopIterator)(nil)
// mockObfuscator mock implementation of the failure obfuscator which only
// encodes the failure and do not makes any onion obfuscation.
type mockObfuscator struct{}
func newMockObfuscator() Obfuscator {
return &mockObfuscator{}
}
func (o *mockObfuscator) InitialObfuscate(failure lnwire.FailureMessage) (
lnwire.OpaqueReason, error) {
var b bytes.Buffer
if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (o *mockObfuscator) BackwardObfuscate(reason lnwire.OpaqueReason) lnwire.OpaqueReason {
return reason
}
// mockDeobfuscator mock implementation of the failure deobfuscator which
// only decodes the failure do not makes any onion obfuscation.
type mockDeobfuscator struct{}
func newMockDeobfuscator() Deobfuscator {
return &mockDeobfuscator{}
}
func (o *mockDeobfuscator) Deobfuscate(reason lnwire.OpaqueReason) (lnwire.FailureMessage,
error) {
r := bytes.NewReader(reason)
failure, err := lnwire.DecodeFailure(r, 0)
if err != nil {
return nil, err
}
return failure, nil
}
var _ Deobfuscator = (*mockDeobfuscator)(nil)
// mockIteratorDecoder test version of hop iterator decoder which decodes the // mockIteratorDecoder test version of hop iterator decoder which decodes the
// encoded array of hops. // encoded array of hops.
type mockIteratorDecoder struct{} type mockIteratorDecoder struct{}
func (p *mockIteratorDecoder) Decode(r io.Reader, meta []byte) (HopIterator, error) { func (p *mockIteratorDecoder) DecodeHopIterator(r io.Reader, meta []byte) (
HopIterator, lnwire.FailCode) {
var b [4]byte var b [4]byte
_, err := r.Read(b[:]) _, err := r.Read(b[:])
if err != nil { if err != nil {
return nil, err return nil, lnwire.CodeTemporaryChannelFailure
} }
hopLength := binary.BigEndian.Uint32(b[:]) hopLength := binary.BigEndian.Uint32(b[:])
@ -163,13 +210,13 @@ func (p *mockIteratorDecoder) Decode(r io.Reader, meta []byte) (HopIterator, err
for i := uint32(0); i < hopLength; i++ { for i := uint32(0); i < hopLength; i++ {
f := &ForwardingInfo{} f := &ForwardingInfo{}
if err := f.decode(r); err != nil { if err := f.decode(r); err != nil {
return nil, err return nil, lnwire.CodeTemporaryChannelFailure
} }
hops[i] = *f hops[i] = *f
} }
return newMockHopIterator(hops...), nil return newMockHopIterator(hops...), lnwire.CodeNone
} }
func (f *ForwardingInfo) decode(r io.Reader) error { func (f *ForwardingInfo) decode(r io.Reader) error {
@ -223,6 +270,8 @@ func (s *mockServer) readHandler(message lnwire.Message) error {
targetChan = msg.ChanID targetChan = msg.ChanID
case *lnwire.UpdateFailHTLC: case *lnwire.UpdateFailHTLC:
targetChan = msg.ChanID targetChan = msg.ChanID
case *lnwire.UpdateFailMalformedHTLC:
targetChan = msg.ChanID
case *lnwire.RevokeAndAck: case *lnwire.RevokeAndAck:
targetChan = msg.ChanID targetChan = msg.ChanID
case *lnwire.CommitSig: case *lnwire.CommitSig:

View File

@ -29,12 +29,27 @@ type htlcPacket struct {
src lnwire.ShortChannelID src lnwire.ShortChannelID
// amount is the value of the HTLC that is being created or modified. // amount is the value of the HTLC that is being created or modified.
//
// TODO(andrew.shvv) should be removed after introducing sphinx payment. // TODO(andrew.shvv) should be removed after introducing sphinx payment.
amount btcutil.Amount amount btcutil.Amount
// htlc lnwire message type of which depends on switch request type. // htlc lnwire message type of which depends on switch request type.
htlc lnwire.Message htlc lnwire.Message
// obfuscator is entity which is needed to make the obfuscation of the
// onion failure, it is carried inside the packet from channel
// link to the switch because we have to create onion error inside the
// switch to, but we unable to restore obfuscator from the onion, because
// on stage of forwarding onion inside payment belongs to the remote node.
// TODO(andrew.shvv) revisit after refactoring the way of returning errors
// inside the htlcswitch packet.
obfuscator Obfuscator
// isObfuscated is used in case if switch sent the packet to the link,
// but error have occurred locally, in this case we shouldn't obfuscate
// it again.
// TODO(andrew.shvv) revisit after refactoring the way of returning errors
// inside the htlcswitch packet.
isObfuscated bool
} }
// newInitPacket creates htlc switch add packet which encapsulates the add htlc // newInitPacket creates htlc switch add packet which encapsulates the add htlc
@ -49,12 +64,13 @@ func newInitPacket(destNode [33]byte, htlc *lnwire.UpdateAddHTLC) *htlcPacket {
// newAddPacket creates htlc switch add packet which encapsulates the add htlc // newAddPacket creates htlc switch add packet which encapsulates the add htlc
// request and additional information for proper forwarding over htlc switch. // request and additional information for proper forwarding over htlc switch.
func newAddPacket(src, dest lnwire.ShortChannelID, func newAddPacket(src, dest lnwire.ShortChannelID,
htlc *lnwire.UpdateAddHTLC) *htlcPacket { htlc *lnwire.UpdateAddHTLC, obfuscator Obfuscator) *htlcPacket {
return &htlcPacket{ return &htlcPacket{
dest: dest, dest: dest,
src: src, src: src,
htlc: htlc, htlc: htlc,
obfuscator: obfuscator,
} }
} }
@ -77,11 +93,12 @@ func newSettlePacket(src lnwire.ShortChannelID, htlc *lnwire.UpdateFufillHTLC,
// add request if something wrong happened on the path to the final // add request if something wrong happened on the path to the final
// destination. // destination.
func newFailPacket(src lnwire.ShortChannelID, htlc *lnwire.UpdateFailHTLC, func newFailPacket(src lnwire.ShortChannelID, htlc *lnwire.UpdateFailHTLC,
payHash [sha256.Size]byte, amount btcutil.Amount) *htlcPacket { payHash [sha256.Size]byte, amount btcutil.Amount, isObfuscated bool) *htlcPacket {
return &htlcPacket{ return &htlcPacket{
src: src, src: src,
payHash: payHash, payHash: payHash,
htlc: htlc, htlc: htlc,
amount: amount, amount: amount,
isObfuscated: isObfuscated,
} }
} }

View File

@ -8,6 +8,7 @@ import (
"crypto/sha256" "crypto/sha256"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
@ -34,6 +35,11 @@ type pendingPayment struct {
preimage chan [sha256.Size]byte preimage chan [sha256.Size]byte
err chan error err chan error
// deobfuscator is an serializable entity which is used if we received
// an error, it deobfuscates the onion failure blob, and extracts the
// exact error from it.
deobfuscator Deobfuscator
} }
// plexPacket encapsulates switch packet and adds error channel to receive // plexPacket encapsulates switch packet and adds error channel to receive
@ -162,18 +168,17 @@ func New(cfg Config) *Switch {
// SendHTLC is used by other subsystems which aren't belong to htlc switch // SendHTLC is used by other subsystems which aren't belong to htlc switch
// package in order to send the htlc update. // package in order to send the htlc update.
func (s *Switch) SendHTLC(nextNode [33]byte, update lnwire.Message) ( func (s *Switch) SendHTLC(nextNode [33]byte, htlc *lnwire.UpdateAddHTLC,
[sha256.Size]byte, error) { deobfuscator Deobfuscator) ([sha256.Size]byte, error) {
htlc := update.(*lnwire.UpdateAddHTLC)
// Create payment and add to the map of payment in order later to be // Create payment and add to the map of payment in order later to be
// able to retrieve it and return response to the user. // able to retrieve it and return response to the user.
payment := &pendingPayment{ payment := &pendingPayment{
err: make(chan error, 1), err: make(chan error, 1),
preimage: make(chan [sha256.Size]byte, 1), preimage: make(chan [sha256.Size]byte, 1),
paymentHash: htlc.PaymentHash, paymentHash: htlc.PaymentHash,
amount: htlc.Amount, amount: htlc.Amount,
deobfuscator: deobfuscator,
} }
s.pendingMutex.Lock() s.pendingMutex.Lock()
@ -259,7 +264,7 @@ func (s *Switch) handleLocalDispatch(payment *pendingPayment, packet *htlcPacket
if err != nil { if err != nil {
log.Errorf("unable to find links by "+ log.Errorf("unable to find links by "+
"destination %v", err) "destination %v", err)
return errors.New(lnwire.UnknownDestination) return errors.New(lnwire.CodeUnknownNextPeer)
} }
// Try to find destination channel link with appropriate // Try to find destination channel link with appropriate
@ -278,7 +283,7 @@ func (s *Switch) handleLocalDispatch(payment *pendingPayment, packet *htlcPacket
if destination == nil { if destination == nil {
log.Errorf("unable to find appropriate channel link "+ log.Errorf("unable to find appropriate channel link "+
"insufficient capacity, need %v", htlc.Amount) "insufficient capacity, need %v", htlc.Amount)
return errors.New(lnwire.InsufficientCapacity) return errors.New(lnwire.CodeTemporaryChannelFailure)
} }
// Send the packet to the destination channel link which // Send the packet to the destination channel link which
@ -300,11 +305,15 @@ func (s *Switch) handleLocalDispatch(payment *pendingPayment, packet *htlcPacket
case *lnwire.UpdateFailHTLC: case *lnwire.UpdateFailHTLC:
// Retrieving the fail code from byte representation of error. // Retrieving the fail code from byte representation of error.
var userErr error var userErr error
if code, err := htlc.Reason.ToFailCode(); err != nil {
userErr = errors.Errorf("can't decode fail code id"+ failure, err := payment.deobfuscator.Deobfuscate(htlc.Reason)
"(%v): %v", htlc.ID, err) if err != nil {
userErr = errors.Errorf("unable to de-obfuscate "+
"onion failure, htlc with hash(%v): %v", payment.paymentHash[:],
err)
log.Error(userErr)
} else { } else {
userErr = errors.New(code) userErr = errors.New(failure.Code())
} }
// Notify user that his payment was discarded. // Notify user that his payment was discarded.
@ -342,15 +351,23 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
// If packet was forwarded from another channel link // If packet was forwarded from another channel link
// than we should notify this link that some error // than we should notify this link that some error
// occurred. // occurred.
reason := []byte{byte(lnwire.UnknownDestination)} failure := lnwire.FailUnknownNextPeer{}
reason, err := packet.obfuscator.InitialObfuscate(failure)
if err != nil {
err := errors.Errorf("unable to obfuscate "+
"error: %v", err)
log.Error(err)
return err
}
go source.HandleSwitchPacket(newFailPacket( go source.HandleSwitchPacket(newFailPacket(
packet.src, packet.src,
&lnwire.UpdateFailHTLC{ &lnwire.UpdateFailHTLC{
Reason: reason, Reason: reason,
}, },
htlc.PaymentHash, 0, htlc.PaymentHash, 0, true,
)) ))
err := errors.Errorf("unable to find link with "+ err = errors.Errorf("unable to find link with "+
"destination %v", packet.dest) "destination %v", packet.dest)
log.Error(err) log.Error(err)
return err return err
@ -371,20 +388,28 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
// over has insufficient capacity, then we'll cancel the htlc // over has insufficient capacity, then we'll cancel the htlc
// as the payment cannot succeed. // as the payment cannot succeed.
if destination == nil { if destination == nil {
// If packet was forwarded from another channel link // If packet was forwarded from another
// than we should notify this link that some error // channel link than we should notify this
// occurred. // link that some error occurred.
reason := []byte{byte(lnwire.InsufficientCapacity)} failure := lnwire.NewTemporaryChannelFailure(nil)
reason, err := packet.obfuscator.InitialObfuscate(failure)
if err != nil {
err := errors.Errorf("unable to obfuscate "+
"error: %v", err)
log.Error(err)
return err
}
go source.HandleSwitchPacket(newFailPacket( go source.HandleSwitchPacket(newFailPacket(
packet.src, packet.src,
&lnwire.UpdateFailHTLC{ &lnwire.UpdateFailHTLC{
Reason: reason, Reason: reason,
}, },
htlc.PaymentHash, htlc.PaymentHash,
0, 0, true,
)) ))
err := errors.Errorf("unable to find appropriate "+ err = errors.Errorf("unable to find appropriate "+
"channel link insufficient capacity, need "+ "channel link insufficient capacity, need "+
"%v", htlc.Amount) "%v", htlc.Amount)
log.Error(err) log.Error(err)
@ -398,17 +423,25 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
source.ShortChanID(), source.ShortChanID(),
destination.ShortChanID(), destination.ShortChanID(),
htlc.PaymentHash, htlc.PaymentHash,
packet.obfuscator,
)); err != nil { )); err != nil {
reason := []byte{byte(lnwire.UnknownError)} failure := lnwire.NewTemporaryChannelFailure(nil)
reason, err := packet.obfuscator.InitialObfuscate(failure)
if err != nil {
err := errors.Errorf("unable to obfuscate "+
"error: %v", err)
log.Error(err)
return err
}
go source.HandleSwitchPacket(newFailPacket( go source.HandleSwitchPacket(newFailPacket(
packet.src, packet.src,
&lnwire.UpdateFailHTLC{ &lnwire.UpdateFailHTLC{
Reason: reason, Reason: reason,
}, },
htlc.PaymentHash, htlc.PaymentHash, 0, true,
0,
)) ))
err := errors.Errorf("unable to add circuit: "+ err = errors.Errorf("unable to add circuit: "+
"%v", err) "%v", err)
log.Error(err) log.Error(err)
return err return err
@ -433,6 +466,11 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
return err return err
} }
// If this is failure than we need to obfuscate the error.
if htlc, ok := htlc.(*lnwire.UpdateFailHTLC); ok && !packet.isObfuscated {
htlc.Reason = circuit.Obfuscator.BackwardObfuscate(htlc.Reason)
}
// Propagating settle/fail htlc back to src of add htlc packet. // Propagating settle/fail htlc back to src of add htlc packet.
source, err := s.getLinkByShortID(circuit.Src) source, err := s.getLinkByShortID(circuit.Src)
if err != nil { if err != nil {

View File

@ -59,7 +59,7 @@ func TestSwitchForward(t *testing.T) {
&lnwire.UpdateAddHTLC{ &lnwire.UpdateAddHTLC{
PaymentHash: rhash, PaymentHash: rhash,
Amount: 1, Amount: 1,
}, }, newMockObfuscator(),
) )
// Handle the request and checks that bob channel link received it. // Handle the request and checks that bob channel link received it.
@ -137,7 +137,7 @@ func TestSwitchCancel(t *testing.T) {
&lnwire.UpdateAddHTLC{ &lnwire.UpdateAddHTLC{
PaymentHash: rhash, PaymentHash: rhash,
Amount: 1, Amount: 1,
}, }, newMockObfuscator(),
) )
// Handle the request and checks that bob channel link received it. // Handle the request and checks that bob channel link received it.
@ -162,7 +162,7 @@ func TestSwitchCancel(t *testing.T) {
request = newFailPacket( request = newFailPacket(
bobChannelLink.ShortChanID(), bobChannelLink.ShortChanID(),
&lnwire.UpdateFailHTLC{}, &lnwire.UpdateFailHTLC{},
rhash, 1) rhash, 1, true)
// Handle the request and checks that payment circuit works properly. // Handle the request and checks that payment circuit works properly.
if err := s.forward(request); err != nil { if err := s.forward(request); err != nil {
@ -213,7 +213,7 @@ func TestSwitchAddSamePayment(t *testing.T) {
&lnwire.UpdateAddHTLC{ &lnwire.UpdateAddHTLC{
PaymentHash: rhash, PaymentHash: rhash,
Amount: 1, Amount: 1,
}, }, newMockObfuscator(),
) )
// Handle the request and checks that bob channel link received it. // Handle the request and checks that bob channel link received it.
@ -247,7 +247,7 @@ func TestSwitchAddSamePayment(t *testing.T) {
request = newFailPacket( request = newFailPacket(
bobChannelLink.ShortChanID(), bobChannelLink.ShortChanID(),
&lnwire.UpdateFailHTLC{}, &lnwire.UpdateFailHTLC{},
rhash, 1) rhash, 1, true)
// Handle the request and checks that payment circuit works properly. // Handle the request and checks that payment circuit works properly.
if err := s.forward(request); err != nil { if err := s.forward(request); err != nil {
@ -308,14 +308,16 @@ func TestSwitchSendPayment(t *testing.T) {
// Handle the request and checks that bob channel link received it. // Handle the request and checks that bob channel link received it.
errChan := make(chan error) errChan := make(chan error)
go func() { go func() {
_, err := s.SendHTLC(aliceChannelLink.Peer().PubKey(), update) _, err := s.SendHTLC(aliceChannelLink.Peer().PubKey(), update,
newMockDeobfuscator())
errChan <- err errChan <- err
}() }()
go func() { go func() {
// Send the payment with the same payment hash and same // Send the payment with the same payment hash and same
// amount and check that it will be propagated successfully // amount and check that it will be propagated successfully
_, err := s.SendHTLC(aliceChannelLink.Peer().PubKey(), update) _, err := s.SendHTLC(aliceChannelLink.Peer().PubKey(), update,
newMockDeobfuscator())
errChan <- err errChan <- err
}() }()
@ -345,18 +347,22 @@ func TestSwitchSendPayment(t *testing.T) {
t.Fatal("wrong amount of circuits") t.Fatal("wrong amount of circuits")
} }
// Create fail request pretending that bob channel link handled the add // Create fail request pretending that bob channel link handled
// htlc request with error and sent the htlc fail request back. This // the add htlc request with error and sent the htlc fail request
// request should be forwarder back to alice channel link. // back. This request should be forwarded back to alice channel link.
packet := newFailPacket( obfuscator := newMockObfuscator()
aliceChannelLink.ShortChanID(), failure := lnwire.FailIncorrectPaymentAmount{}
reason, err := obfuscator.InitialObfuscate(failure)
if err != nil {
t.Fatalf("unable obfuscate failure: %v", err)
}
packet := newFailPacket(aliceChannelLink.ShortChanID(),
&lnwire.UpdateFailHTLC{ &lnwire.UpdateFailHTLC{
Reason: []byte{byte(lnwire.IncorrectValue)}, Reason: reason,
ID: 1, ID: 1,
}, },
rhash, rhash, 1, true)
1,
)
if err := s.forward(packet); err != nil { if err := s.forward(packet); err != nil {
t.Fatalf("can't forward htlc packet: %v", err) t.Fatalf("can't forward htlc packet: %v", err)
@ -364,7 +370,7 @@ func TestSwitchSendPayment(t *testing.T) {
select { select {
case err := <-errChan: case err := <-errChan:
if err.Error() != errors.New(lnwire.IncorrectValue).Error() { if err.Error() != errors.New(lnwire.CodeIncorrectPaymentAmount).Error() {
t.Fatal("err wasn't received") t.Fatal("err wasn't received")
} }
case <-time.After(time.Second): case <-time.After(time.Second):
@ -379,7 +385,7 @@ func TestSwitchSendPayment(t *testing.T) {
select { select {
case err := <-errChan: case err := <-errChan:
if err.Error() != errors.New(lnwire.IncorrectValue).Error() { if err.Error() != errors.New(lnwire.CodeIncorrectPaymentAmount).Error() {
t.Fatal("err wasn't received") t.Fatal("err wasn't received")
} }
case <-time.After(time.Second): case <-time.After(time.Second):

View File

@ -10,6 +10,10 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"io"
"math/big"
"github.com/btcsuite/fastsha256" "github.com/btcsuite/fastsha256"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
@ -26,8 +30,34 @@ var (
alicePrivKey = []byte("alice priv key") alicePrivKey = []byte("alice priv key")
bobPrivKey = []byte("bob priv key") bobPrivKey = []byte("bob priv key")
carolPrivKey = []byte("carol priv key") carolPrivKey = []byte("carol priv key")
testPrivKey = []byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
}
_, testPubKey = btcec.PrivKeyFromBytes(btcec.S256(), testPrivKey)
testSig = &btcec.Signature{
R: new(big.Int),
S: new(big.Int),
}
_, _ = testSig.R.SetString("6372440660162918006277497454296753625158993"+
"5445068131219452686511677818569431", 10)
_, _ = testSig.S.SetString("1880105606924982582529128710493133386286603"+
"3135609736119018462340006816851118", 10)
) )
// mockGetChanUpdateMessage helper function which returns topology update
// of the channel
func mockGetChanUpdateMessage() (*lnwire.ChannelUpdate, error) {
return &lnwire.ChannelUpdate{
Signature: testSig,
}, nil
}
// generateRandomBytes returns securely generated random bytes. // generateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random // It will return an error if the system's secure random
// number generator fails to function correctly, in which // number generator fails to function correctly, in which
@ -423,7 +453,8 @@ func (n *threeHopNetwork) makePayment(sendingPeer, receivingPeer Peer,
// Send payment and expose err channel. // Send payment and expose err channel.
errChan := make(chan error) errChan := make(chan error)
go func() { go func() {
_, err := sender.htlcSwitch.SendHTLC(firstHopPub, htlc) _, err := sender.htlcSwitch.SendHTLC(firstHopPub, htlc,
newMockDeobfuscator())
errChan <- err errChan <- err
}() }()
@ -523,14 +554,19 @@ func newThreeHopNetwork(t *testing.T, aliceToBob,
BaseFee: btcutil.Amount(1), BaseFee: btcutil.Amount(1),
TimeLockDelta: 1, TimeLockDelta: 1,
} }
obfuscator := newMockObfuscator()
aliceChannelLink := NewChannelLink( aliceChannelLink := NewChannelLink(
ChannelLinkConfig{ ChannelLinkConfig{
FwrdingPolicy: globalPolicy, FwrdingPolicy: globalPolicy,
Peer: bobServer, Peer: bobServer,
Switch: aliceServer.htlcSwitch, Switch: aliceServer.htlcSwitch,
DecodeOnion: decoder.Decode, DecodeHopIterator: decoder.DecodeHopIterator,
Registry: aliceServer.registry, DecodeOnionObfuscator: func(io.Reader) (Obfuscator,
lnwire.FailCode) {
return obfuscator, lnwire.CodeNone
},
GetLastChannelUpdate: mockGetChanUpdateMessage,
Registry: aliceServer.registry,
}, },
aliceChannel, aliceChannel,
) )
@ -540,11 +576,16 @@ func newThreeHopNetwork(t *testing.T, aliceToBob,
firstBobChannelLink := NewChannelLink( firstBobChannelLink := NewChannelLink(
ChannelLinkConfig{ ChannelLinkConfig{
FwrdingPolicy: globalPolicy, FwrdingPolicy: globalPolicy,
Peer: aliceServer, Peer: aliceServer,
Switch: bobServer.htlcSwitch, Switch: bobServer.htlcSwitch,
DecodeOnion: decoder.Decode, DecodeHopIterator: decoder.DecodeHopIterator,
Registry: bobServer.registry, DecodeOnionObfuscator: func(io.Reader) (Obfuscator,
lnwire.FailCode) {
return obfuscator, lnwire.CodeNone
},
GetLastChannelUpdate: mockGetChanUpdateMessage,
Registry: bobServer.registry,
}, },
firstBobChannel, firstBobChannel,
) )
@ -554,11 +595,16 @@ func newThreeHopNetwork(t *testing.T, aliceToBob,
secondBobChannelLink := NewChannelLink( secondBobChannelLink := NewChannelLink(
ChannelLinkConfig{ ChannelLinkConfig{
FwrdingPolicy: globalPolicy, FwrdingPolicy: globalPolicy,
Peer: carolServer, Peer: carolServer,
Switch: bobServer.htlcSwitch, Switch: bobServer.htlcSwitch,
DecodeOnion: decoder.Decode, DecodeHopIterator: decoder.DecodeHopIterator,
Registry: bobServer.registry, DecodeOnionObfuscator: func(io.Reader) (Obfuscator,
lnwire.FailCode) {
return obfuscator, lnwire.CodeNone
},
GetLastChannelUpdate: mockGetChanUpdateMessage,
Registry: bobServer.registry,
}, },
secondBobChannel, secondBobChannel,
) )
@ -568,11 +614,16 @@ func newThreeHopNetwork(t *testing.T, aliceToBob,
carolChannelLink := NewChannelLink( carolChannelLink := NewChannelLink(
ChannelLinkConfig{ ChannelLinkConfig{
FwrdingPolicy: globalPolicy, FwrdingPolicy: globalPolicy,
Peer: bobServer, Peer: bobServer,
Switch: carolServer.htlcSwitch, Switch: carolServer.htlcSwitch,
DecodeOnion: decoder.Decode, DecodeHopIterator: decoder.DecodeHopIterator,
Registry: carolServer.registry, DecodeOnionObfuscator: func(io.Reader) (Obfuscator,
lnwire.FailCode) {
return obfuscator, lnwire.CodeNone
},
GetLastChannelUpdate: mockGetChanUpdateMessage,
Registry: carolServer.registry,
}, },
carolChannel, carolChannel,
) )

View File

@ -2030,10 +2030,11 @@ out:
} else if resp.PaymentError == "" { } else if resp.PaymentError == "" {
t.Fatalf("payment should have been rejected due to invalid " + t.Fatalf("payment should have been rejected due to invalid " +
"payment hash") "payment hash")
} else if !strings.Contains(resp.PaymentError, "preimage") { } else if !strings.Contains(resp.PaymentError,
lnwire.CodeUnknownPaymentHash.String()) {
// TODO(roasbeef): make into proper gRPC error code // TODO(roasbeef): make into proper gRPC error code
t.Fatalf("payment should have failed due to unknown preimage, "+ t.Fatalf("payment should have failed due to unknown payment hash, "+
"instead failed due to : %v", err) "instead failed due to: %v", resp.PaymentError)
} }
// The balances of all parties should be the same as initially since // The balances of all parties should be the same as initially since
@ -2066,9 +2067,10 @@ out:
} else if resp.PaymentError == "" { } else if resp.PaymentError == "" {
t.Fatalf("payment should have been rejected due to wrong " + t.Fatalf("payment should have been rejected due to wrong " +
"HTLC amount") "HTLC amount")
} else if !strings.Contains(resp.PaymentError, "htlc value") { } else if !strings.Contains(resp.PaymentError,
lnwire.CodeIncorrectPaymentAmount.String()) {
t.Fatalf("payment should have failed due to wrong amount, "+ t.Fatalf("payment should have failed due to wrong amount, "+
"instead failed due to: %v", err) "instead failed due to: %v", resp.PaymentError)
} }
// The balances of all parties should be the same as initially since // The balances of all parties should be the same as initially since
@ -2129,9 +2131,10 @@ out:
} else if resp.PaymentError == "" { } else if resp.PaymentError == "" {
t.Fatalf("payment should fail due to insufficient "+ t.Fatalf("payment should fail due to insufficient "+
"capacity: %v", err) "capacity: %v", err)
} else if !strings.Contains(resp.PaymentError, "capacity") { } else if !strings.Contains(resp.PaymentError,
lnwire.CodeTemporaryChannelFailure.String()) {
t.Fatalf("payment should fail due to insufficient capacity, "+ t.Fatalf("payment should fail due to insufficient capacity, "+
"instead: %v", err) "instead: %v", resp.PaymentError)
} }
// For our final test, we'll ensure that if a target link isn't // For our final test, we'll ensure that if a target link isn't
@ -2158,9 +2161,10 @@ out:
t.Fatalf("payment stream has been closed: %v", err) t.Fatalf("payment stream has been closed: %v", err)
} else if resp.PaymentError == "" { } else if resp.PaymentError == "" {
t.Fatalf("payment should have failed") t.Fatalf("payment should have failed")
} else if !strings.Contains(resp.PaymentError, "hop unknown") { } else if !strings.Contains(resp.PaymentError,
lnwire.CodeUnknownNextPeer.String()) {
t.Fatalf("payment should fail due to unknown hop, instead: %v", t.Fatalf("payment should fail due to unknown hop, instead: %v",
err) resp.PaymentError)
} }
// Finally, immediately close the channel. This function will also // Finally, immediately close the channel. This function will also
@ -2575,17 +2579,19 @@ func testAsyncPayments(net *networkHarness, t *harnessTest) {
if err := alicePayStream.Send(sendReq); err != nil { if err := alicePayStream.Send(sendReq); err != nil {
t.Fatalf("unable to send payment: "+ t.Fatalf("unable to send payment: "+
"%v", err) "stream has been closed: %v", err)
} }
} }
// We should receive one insufficient capacity error, because we are
// sending on one invoice bigger.
errorReceived := false errorReceived := false
for i := 0; i < numInvoices; i++ { for i := 0; i < numInvoices; i++ {
if resp, err := alicePayStream.Recv(); err != nil { if resp, err := alicePayStream.Recv(); err != nil {
t.Fatalf("payment stream have been closed: %v", err) t.Fatalf("payment stream have been closed: %v", err)
} else if resp.PaymentError != "" { } else if resp.PaymentError != "" {
if strings.Contains(resp.PaymentError, if strings.Contains(resp.PaymentError,
"Insufficient") { lnwire.CodeTemporaryChannelFailure.String()) {
if errorReceived { if errorReceived {
t.Fatalf("redundant payment "+ t.Fatalf("redundant payment "+
"error: %v", resp.PaymentError) "error: %v", resp.PaymentError)
@ -2595,7 +2601,7 @@ func testAsyncPayments(net *networkHarness, t *harnessTest) {
continue continue
} }
t.Fatalf("unable to send payment: %v", err) t.Fatalf("unable to send payment: %v", resp.PaymentError)
} }
} }

View File

@ -17,6 +17,8 @@ import (
"github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil" "github.com/roasbeef/btcutil"
"crypto/sha256"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lightning-onion"
) )
@ -111,8 +113,8 @@ type Config struct {
// forward a fully encoded payment to the first hop in the route // forward a fully encoded payment to the first hop in the route
// denoted by its public key. A non-nil error is to be returned if the // denoted by its public key. A non-nil error is to be returned if the
// payment was unsuccessful. // payment was unsuccessful.
SendToSwitch func(firstHop *btcec.PublicKey, SendToSwitch func(firstHop *btcec.PublicKey, htlcAdd *lnwire.UpdateAddHTLC,
htlcAdd *lnwire.UpdateAddHTLC) ([32]byte, error) circuit *sphinx.Circuit) ([sha256.Size]byte, error)
} }
// routeTuple is an entry within the ChannelRouter's route cache. We cache // routeTuple is an entry within the ChannelRouter's route cache. We cache
@ -851,7 +853,8 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey, amt btcutil.Amount)
// the onion route specified by the passed layer 3 route. The blob returned // the onion route specified by the passed layer 3 route. The blob returned
// from this function can immediately be included within an HTLC add packet to // from this function can immediately be included within an HTLC add packet to
// be sent to the first hop within the route. // be sent to the first hop within the route.
func generateSphinxPacket(route *Route, paymentHash []byte) ([]byte, error) { func generateSphinxPacket(route *Route, paymentHash []byte) ([]byte,
*sphinx.Circuit, error) {
// First obtain all the public keys along the route which are contained // First obtain all the public keys along the route which are contained
// in each hop. // in each hop.
nodes := make([]*btcec.PublicKey, len(route.Hops)) nodes := make([]*btcec.PublicKey, len(route.Hops))
@ -877,7 +880,7 @@ func generateSphinxPacket(route *Route, paymentHash []byte) ([]byte, error) {
sessionKey, err := btcec.NewPrivateKey(btcec.S256()) sessionKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// Next generate the onion routing packet which allows us to perform // Next generate the onion routing packet which allows us to perform
@ -885,14 +888,14 @@ func generateSphinxPacket(route *Route, paymentHash []byte) ([]byte, error) {
sphinxPacket, err := sphinx.NewOnionPacket(nodes, sessionKey, sphinxPacket, err := sphinx.NewOnionPacket(nodes, sessionKey,
hopPayloads, paymentHash) hopPayloads, paymentHash)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// Finally, encode Sphinx packet using it's wire representation to be // Finally, encode Sphinx packet using it's wire representation to be
// included within the HTLC add packet. // included within the HTLC add packet.
var onionBlob bytes.Buffer var onionBlob bytes.Buffer
if err := sphinxPacket.Encode(&onionBlob); err != nil { if err := sphinxPacket.Encode(&onionBlob); err != nil {
return nil, err return nil, nil, err
} }
log.Tracef("Generated sphinx packet: %v", log.Tracef("Generated sphinx packet: %v",
@ -904,7 +907,10 @@ func generateSphinxPacket(route *Route, paymentHash []byte) ([]byte, error) {
}), }),
) )
return onionBlob.Bytes(), nil return onionBlob.Bytes(), &sphinx.Circuit{
SessionKey: sessionKey,
PaymentPath: nodes,
}, nil
} }
// LightningPayment describes a payment to be sent through the network to the // LightningPayment describes a payment to be sent through the network to the
@ -989,7 +995,8 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, *Route
// Generate the raw encoded sphinx packet to be included along // Generate the raw encoded sphinx packet to be included along
// with the htlcAdd message that we send directly to the // with the htlcAdd message that we send directly to the
// switch. // switch.
sphinxPacket, err := generateSphinxPacket(route, payment.PaymentHash[:]) onionBlob, circuit, err := generateSphinxPacket(route,
payment.PaymentHash[:])
if err != nil { if err != nil {
return preImage, nil, err return preImage, nil, err
} }
@ -1002,13 +1009,14 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, *Route
Expiry: route.TotalTimeLock, Expiry: route.TotalTimeLock,
PaymentHash: payment.PaymentHash, PaymentHash: payment.PaymentHash,
} }
copy(htlcAdd.OnionBlob[:], sphinxPacket) copy(htlcAdd.OnionBlob[:], onionBlob)
// Attempt to send this payment through the network to complete // Attempt to send this payment through the network to complete
// the payment. If this attempt fails, then we'll continue on // the payment. If this attempt fails, then we'll continue on
// to the next available route. // to the next available route.
firstHop := route.Hops[0].Channel.Node.PubKey firstHop := route.Hops[0].Channel.Node.PubKey
preImage, sendError = r.cfg.SendToSwitch(firstHop, htlcAdd) preImage, sendError = r.cfg.SendToSwitch(firstHop, htlcAdd,
circuit)
if sendError != nil { if sendError != nil {
log.Errorf("Attempt to send payment %x failed: %v", log.Errorf("Attempt to send payment %x failed: %v",
payment.PaymentHash, sendError) payment.PaymentHash, sendError)

View File

@ -10,6 +10,7 @@ import (
"github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcd/wire"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcutil" "github.com/roasbeef/btcutil"
@ -80,7 +81,7 @@ func createTestCtx(startingHeight uint32, testGraph ...string) (*testCtx, func()
Chain: chain, Chain: chain,
ChainView: chainView, ChainView: chainView,
SendToSwitch: func(_ *btcec.PublicKey, SendToSwitch: func(_ *btcec.PublicKey,
_ *lnwire.UpdateAddHTLC) ([32]byte, error) { _ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
return [32]byte{}, nil return [32]byte{}, nil
}, },
}) })
@ -175,7 +176,7 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {
// first hop. This should force the router to instead take the // first hop. This should force the router to instead take the
// available two hop path (through satoshi). // available two hop path (through satoshi).
ctx.router.cfg.SendToSwitch = func(n *btcec.PublicKey, ctx.router.cfg.SendToSwitch = func(n *btcec.PublicKey,
_ *lnwire.UpdateAddHTLC) ([32]byte, error) { _ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
if ctx.aliases["luoji"].IsEqual(n) { if ctx.aliases["luoji"].IsEqual(n) {
return [32]byte{}, errors.New("send error") return [32]byte{}, errors.New("send error")