Merge pull request #3063 from halseth/router-error-deobfuscation
[reliable payments] Move payment error deobfuscation to router
This commit is contained in:
commit
30cb667ee2
@ -54,6 +54,8 @@ func newConcurrentTester(t *testing.T) *concurrentTester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *concurrentTester) Fatalf(format string, args ...interface{}) {
|
func (c *concurrentTester) Fatalf(format string, args ...interface{}) {
|
||||||
|
c.T.Helper()
|
||||||
|
|
||||||
c.mtx.Lock()
|
c.mtx.Lock()
|
||||||
defer c.mtx.Unlock()
|
defer c.mtx.Unlock()
|
||||||
|
|
||||||
@ -1100,20 +1102,43 @@ func TestChannelLinkMultiHopUnknownPaymentHash(t *testing.T) {
|
|||||||
// Generate payment invoice and htlc, but don't add this invoice to the
|
// Generate payment invoice and htlc, but don't add this invoice to the
|
||||||
// receiver registry. This should trigger an unknown payment hash
|
// receiver registry. This should trigger an unknown payment hash
|
||||||
// failure.
|
// failure.
|
||||||
_, htlc, err := generatePayment(amount, htlcAmt, totalTimelock,
|
_, htlc, pid, err := generatePayment(
|
||||||
blob)
|
amount, htlcAmt, totalTimelock, blob,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send payment and expose err channel.
|
// Send payment and expose err channel.
|
||||||
_, err = n.aliceServer.htlcSwitch.SendHTLC(
|
err = n.aliceServer.htlcSwitch.SendHTLC(
|
||||||
n.firstBobChannelLink.ShortChanID(), htlc,
|
n.firstBobChannelLink.ShortChanID(), pid, htlc,
|
||||||
newMockDeobfuscator(),
|
|
||||||
)
|
)
|
||||||
if !strings.Contains(err.Error(), lnwire.CodeUnknownPaymentHash.String()) {
|
if err != nil {
|
||||||
t.Fatalf("expected %v got %v", err,
|
t.Fatalf("unable to get send payment: %v", err)
|
||||||
lnwire.CodeUnknownPaymentHash)
|
}
|
||||||
|
|
||||||
|
resultChan, err := n.aliceServer.htlcSwitch.GetPaymentResult(
|
||||||
|
pid, newMockDeobfuscator(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to get payment result: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *PaymentResult
|
||||||
|
var ok bool
|
||||||
|
select {
|
||||||
|
|
||||||
|
case result, ok = <-resultChan:
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unexpected shutdown")
|
||||||
|
}
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatalf("no result arrive")
|
||||||
|
}
|
||||||
|
|
||||||
|
fErr := result.Error
|
||||||
|
if !strings.Contains(fErr.Error(), lnwire.CodeUnknownPaymentHash.String()) {
|
||||||
|
t.Fatalf("expected %v got %v", lnwire.CodeUnknownPaymentHash, fErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for Alice to receive the revocation.
|
// Wait for Alice to receive the revocation.
|
||||||
@ -1909,7 +1934,9 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) {
|
|||||||
// a switch initiated payment. The resulting bandwidth should
|
// a switch initiated payment. The resulting bandwidth should
|
||||||
// now be decremented to reflect the new HTLC.
|
// now be decremented to reflect the new HTLC.
|
||||||
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
||||||
invoice, htlc, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob)
|
invoice, htlc, _, err := generatePayment(
|
||||||
|
htlcAmt, htlcAmt, 5, mockBlob,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create payment: %v", err)
|
t.Fatalf("unable to create payment: %v", err)
|
||||||
}
|
}
|
||||||
@ -1989,7 +2016,7 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) {
|
|||||||
|
|
||||||
// Next, we'll add another HTLC initiated by the switch (of the same
|
// Next, we'll add another HTLC initiated by the switch (of the same
|
||||||
// amount as the prior one).
|
// amount as the prior one).
|
||||||
invoice, htlc, err = generatePayment(htlcAmt, htlcAmt, 5, mockBlob)
|
invoice, htlc, _, err = generatePayment(htlcAmt, htlcAmt, 5, mockBlob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create payment: %v", err)
|
t.Fatalf("unable to create payment: %v", err)
|
||||||
}
|
}
|
||||||
@ -2075,8 +2102,9 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to gen route: %v", err)
|
t.Fatalf("unable to gen route: %v", err)
|
||||||
}
|
}
|
||||||
invoice, htlc, err = generatePayment(htlcAmt, htlcAmt,
|
invoice, htlc, _, err = generatePayment(
|
||||||
totalTimelock, blob)
|
htlcAmt, htlcAmt, totalTimelock, blob,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create payment: %v", err)
|
t.Fatalf("unable to create payment: %v", err)
|
||||||
}
|
}
|
||||||
@ -2183,7 +2211,9 @@ func TestChannelLinkBandwidthConsistency(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to gen route: %v", err)
|
t.Fatalf("unable to gen route: %v", err)
|
||||||
}
|
}
|
||||||
invoice, htlc, err = generatePayment(htlcAmt, htlcAmt, totalTimelock, blob)
|
invoice, htlc, _, err = generatePayment(
|
||||||
|
htlcAmt, htlcAmt, totalTimelock, blob,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create payment: %v", err)
|
t.Fatalf("unable to create payment: %v", err)
|
||||||
}
|
}
|
||||||
@ -2314,7 +2344,9 @@ func TestChannelLinkBandwidthConsistencyOverflow(t *testing.T) {
|
|||||||
|
|
||||||
var htlcID uint64
|
var htlcID uint64
|
||||||
addLinkHTLC := func(id uint64, amt lnwire.MilliSatoshi) [32]byte {
|
addLinkHTLC := func(id uint64, amt lnwire.MilliSatoshi) [32]byte {
|
||||||
invoice, htlc, err := generatePayment(amt, amt, 5, mockBlob)
|
invoice, htlc, _, err := generatePayment(
|
||||||
|
amt, amt, 5, mockBlob,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create payment: %v", err)
|
t.Fatalf("unable to create payment: %v", err)
|
||||||
}
|
}
|
||||||
@ -2580,7 +2612,7 @@ func TestChannelLinkTrimCircuitsPending(t *testing.T) {
|
|||||||
// message for the test.
|
// message for the test.
|
||||||
var mockBlob [lnwire.OnionPacketSize]byte
|
var mockBlob [lnwire.OnionPacketSize]byte
|
||||||
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
||||||
_, htlc, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob)
|
_, htlc, _, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create payment: %v", err)
|
t.Fatalf("unable to create payment: %v", err)
|
||||||
}
|
}
|
||||||
@ -2860,7 +2892,7 @@ func TestChannelLinkTrimCircuitsNoCommit(t *testing.T) {
|
|||||||
// message for the test.
|
// message for the test.
|
||||||
var mockBlob [lnwire.OnionPacketSize]byte
|
var mockBlob [lnwire.OnionPacketSize]byte
|
||||||
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
||||||
_, htlc, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob)
|
_, htlc, _, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create payment: %v", err)
|
t.Fatalf("unable to create payment: %v", err)
|
||||||
}
|
}
|
||||||
@ -3113,7 +3145,7 @@ func TestChannelLinkBandwidthChanReserve(t *testing.T) {
|
|||||||
// a switch initiated payment. The resulting bandwidth should
|
// a switch initiated payment. The resulting bandwidth should
|
||||||
// now be decremented to reflect the new HTLC.
|
// now be decremented to reflect the new HTLC.
|
||||||
htlcAmt := lnwire.NewMSatFromSatoshis(3 * btcutil.SatoshiPerBitcoin)
|
htlcAmt := lnwire.NewMSatFromSatoshis(3 * btcutil.SatoshiPerBitcoin)
|
||||||
invoice, htlc, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob)
|
invoice, htlc, _, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create payment: %v", err)
|
t.Fatalf("unable to create payment: %v", err)
|
||||||
}
|
}
|
||||||
@ -3844,8 +3876,9 @@ func TestChannelLinkAcceptDuplicatePayment(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
invoice, htlc, err := generatePayment(amount, htlcAmt, totalTimelock,
|
invoice, htlc, pid, err := generatePayment(
|
||||||
blob)
|
amount, htlcAmt, totalTimelock, blob,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -3858,19 +3891,37 @@ func TestChannelLinkAcceptDuplicatePayment(t *testing.T) {
|
|||||||
// With the invoice now added to Carol's registry, we'll send the
|
// With the invoice now added to Carol's registry, we'll send the
|
||||||
// payment. It should succeed w/o any issues as it has been crafted
|
// payment. It should succeed w/o any issues as it has been crafted
|
||||||
// properly.
|
// properly.
|
||||||
_, err = n.aliceServer.htlcSwitch.SendHTLC(
|
err = n.aliceServer.htlcSwitch.SendHTLC(
|
||||||
n.firstBobChannelLink.ShortChanID(), htlc,
|
n.firstBobChannelLink.ShortChanID(), pid, htlc,
|
||||||
newMockDeobfuscator(),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to send payment to carol: %v", err)
|
t.Fatalf("unable to send payment to carol: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resultChan, err := n.aliceServer.htlcSwitch.GetPaymentResult(
|
||||||
|
pid, newMockDeobfuscator(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to get payment result: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result, ok := <-resultChan:
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unexpected shutdown")
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("payment failed: %v", result.Error)
|
||||||
|
}
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatalf("payment result did not arrive")
|
||||||
|
}
|
||||||
|
|
||||||
// Now, if we attempt to send the payment *again* it should be rejected
|
// Now, if we attempt to send the payment *again* it should be rejected
|
||||||
// as it's a duplicate request.
|
// as it's a duplicate request.
|
||||||
_, err = n.aliceServer.htlcSwitch.SendHTLC(
|
err = n.aliceServer.htlcSwitch.SendHTLC(
|
||||||
n.firstBobChannelLink.ShortChanID(), htlc,
|
n.firstBobChannelLink.ShortChanID(), pid, htlc,
|
||||||
newMockDeobfuscator(),
|
|
||||||
)
|
)
|
||||||
if err != ErrAlreadyPaid {
|
if err != ErrAlreadyPaid {
|
||||||
t.Fatalf("ErrAlreadyPaid should have been received got: %v", err)
|
t.Fatalf("ErrAlreadyPaid should have been received got: %v", err)
|
||||||
@ -4255,7 +4306,7 @@ func generateHtlcAndInvoice(t *testing.T,
|
|||||||
t.Fatalf("unable to generate route: %v", err)
|
t.Fatalf("unable to generate route: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
invoice, htlc, err := generatePayment(
|
invoice, htlc, _, err := generatePayment(
|
||||||
htlcAmt, htlcAmt, uint32(htlcExpiry), blob,
|
htlcAmt, htlcAmt, uint32(htlcExpiry), blob,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
48
htlcswitch/payment_result.go
Normal file
48
htlcswitch/payment_result.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package htlcswitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPaymentIDNotFound is an error returned if the given paymentID is
|
||||||
|
// not found.
|
||||||
|
ErrPaymentIDNotFound = errors.New("paymentID not found")
|
||||||
|
|
||||||
|
// ErrPaymentIDAlreadyExists is returned if we try to write a pending
|
||||||
|
// payment whose paymentID already exists.
|
||||||
|
ErrPaymentIDAlreadyExists = errors.New("paymentID already exists")
|
||||||
|
)
|
||||||
|
|
||||||
|
// PaymentResult wraps a decoded result received from the network after a
|
||||||
|
// payment attempt was made. This is what is eventually handed to the router
|
||||||
|
// for processing.
|
||||||
|
type PaymentResult struct {
|
||||||
|
// Preimage is set by the switch in case a sent HTLC was settled.
|
||||||
|
Preimage [32]byte
|
||||||
|
|
||||||
|
// Error is non-nil in case a HTLC send failed, and the HTLC is now
|
||||||
|
// irrevocably cancelled. If the payment failed during forwarding, this
|
||||||
|
// error will be a *ForwardingError.
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
// networkResult is the raw result received from the network after a payment
|
||||||
|
// attempt has been made. Since the switch doesn't always have the necessary
|
||||||
|
// data to decode the raw message, we store it together with some meta data,
|
||||||
|
// and decode it when the router query for the final result.
|
||||||
|
type networkResult struct {
|
||||||
|
// msg is the received result. This should be of type UpdateFulfillHTLC
|
||||||
|
// or UpdateFailHTLC.
|
||||||
|
msg lnwire.Message
|
||||||
|
|
||||||
|
// unencrypted indicates whether the failure encoded in the message is
|
||||||
|
// unencrypted, and hence doesn't need to be decrypted.
|
||||||
|
unencrypted bool
|
||||||
|
|
||||||
|
// isResolution indicates whether this is a resolution message, in
|
||||||
|
// which the failure reason might not be included.
|
||||||
|
isResolution bool
|
||||||
|
}
|
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/ticker"
|
"github.com/lightningnetwork/lnd/ticker"
|
||||||
@ -67,16 +68,10 @@ var (
|
|||||||
// updates to be received whether the payment has been rejected or proceed
|
// updates to be received whether the payment has been rejected or proceed
|
||||||
// successfully.
|
// successfully.
|
||||||
type pendingPayment struct {
|
type pendingPayment struct {
|
||||||
paymentHash lnwallet.PaymentHash
|
paymentHash lntypes.Hash
|
||||||
amount lnwire.MilliSatoshi
|
amount lnwire.MilliSatoshi
|
||||||
|
|
||||||
preimage chan [sha256.Size]byte
|
resultChan chan *networkResult
|
||||||
err chan error
|
|
||||||
|
|
||||||
// deobfuscator is a serializable entity which is used if we received
|
|
||||||
// an error, it deobfuscates the onion failure blob, and extracts the
|
|
||||||
// exact error from it.
|
|
||||||
deobfuscator ErrorDecrypter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// plexPacket encapsulates switch packet and adds error channel to receive
|
// plexPacket encapsulates switch packet and adds error channel to receive
|
||||||
@ -213,8 +208,6 @@ type Switch struct {
|
|||||||
pendingPayments map[uint64]*pendingPayment
|
pendingPayments map[uint64]*pendingPayment
|
||||||
pendingMutex sync.RWMutex
|
pendingMutex sync.RWMutex
|
||||||
|
|
||||||
paymentSequencer Sequencer
|
|
||||||
|
|
||||||
// control provides verification of sending htlc mesages
|
// control provides verification of sending htlc mesages
|
||||||
control ControlTower
|
control ControlTower
|
||||||
|
|
||||||
@ -293,16 +286,10 @@ func New(cfg Config, currentHeight uint32) (*Switch, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sequencer, err := NewPersistentSequencer(cfg.DB)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Switch{
|
return &Switch{
|
||||||
bestHeight: currentHeight,
|
bestHeight: currentHeight,
|
||||||
cfg: &cfg,
|
cfg: &cfg,
|
||||||
circuits: circuitMap,
|
circuits: circuitMap,
|
||||||
paymentSequencer: sequencer,
|
|
||||||
control: NewPaymentControl(false, cfg.DB),
|
control: NewPaymentControl(false, cfg.DB),
|
||||||
linkIndex: make(map[lnwire.ChannelID]ChannelLink),
|
linkIndex: make(map[lnwire.ChannelID]ChannelLink),
|
||||||
mailOrchestrator: newMailOrchestrator(),
|
mailOrchestrator: newMailOrchestrator(),
|
||||||
@ -353,35 +340,90 @@ func (s *Switch) ProcessContractResolution(msg contractcourt.ResolutionMsg) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPaymentResult returns the the result of the payment attempt with the
|
||||||
|
// given paymentID. The method returns a channel where the payment result will
|
||||||
|
// be sent when available, or an error is encountered during forwarding. When a
|
||||||
|
// result is received on the channel, the HTLC is guaranteed to no longer be in
|
||||||
|
// flight. The switch shutting down is signaled by closing the channel. If the
|
||||||
|
// paymentID is unknown, ErrPaymentIDNotFound will be returned.
|
||||||
|
func (s *Switch) GetPaymentResult(paymentID uint64,
|
||||||
|
deobfuscator ErrorDecrypter) (<-chan *PaymentResult, error) {
|
||||||
|
|
||||||
|
s.pendingMutex.Lock()
|
||||||
|
payment, ok := s.pendingPayments[paymentID]
|
||||||
|
s.pendingMutex.Unlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrPaymentIDNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
resultChan := make(chan *PaymentResult, 1)
|
||||||
|
|
||||||
|
// Since the payment was known, we can start a goroutine that can
|
||||||
|
// extract the result when it is available, and pass it on to the
|
||||||
|
// caller.
|
||||||
|
s.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer s.wg.Done()
|
||||||
|
|
||||||
|
var n *networkResult
|
||||||
|
select {
|
||||||
|
case n = <-payment.resultChan:
|
||||||
|
case <-s.quit:
|
||||||
|
// We close the result channel to signal a shutdown. We
|
||||||
|
// don't send any result in this case since the HTLC is
|
||||||
|
// still in flight.
|
||||||
|
close(resultChan)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the result and pass it to the result channel.
|
||||||
|
result, err := s.extractResult(
|
||||||
|
deobfuscator, n, paymentID, payment.paymentHash,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
e := fmt.Errorf("Unable to extract result: %v", err)
|
||||||
|
log.Error(e)
|
||||||
|
resultChan <- &PaymentResult{
|
||||||
|
Error: e,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultChan <- result
|
||||||
|
}()
|
||||||
|
|
||||||
|
return resultChan, nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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. The paymentID used MUST be unique
|
||||||
func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID,
|
// for this HTLC, and MUST be used only once, otherwise the switch might reject
|
||||||
htlc *lnwire.UpdateAddHTLC,
|
// it.
|
||||||
deobfuscator ErrorDecrypter) ([sha256.Size]byte, error) {
|
func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID, paymentID uint64,
|
||||||
|
htlc *lnwire.UpdateAddHTLC) error {
|
||||||
|
|
||||||
// Before sending, double check that we don't already have 1) an
|
// Before sending, double check that we don't already have 1) an
|
||||||
// in-flight payment to this payment hash, or 2) a complete payment for
|
// in-flight payment to this payment hash, or 2) a complete payment for
|
||||||
// the same hash.
|
// the same hash.
|
||||||
if err := s.control.ClearForTakeoff(htlc); err != nil {
|
if err := s.control.ClearForTakeoff(htlc); err != nil {
|
||||||
return zeroPreimage, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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),
|
resultChan: make(chan *networkResult, 1),
|
||||||
preimage: make(chan [sha256.Size]byte, 1),
|
|
||||||
paymentHash: htlc.PaymentHash,
|
paymentHash: htlc.PaymentHash,
|
||||||
amount: htlc.Amount,
|
amount: htlc.Amount,
|
||||||
deobfuscator: deobfuscator,
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentID, err := s.paymentSequencer.NextID()
|
|
||||||
if err != nil {
|
|
||||||
return zeroPreimage, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.pendingMutex.Lock()
|
s.pendingMutex.Lock()
|
||||||
|
if _, ok := s.pendingPayments[paymentID]; ok {
|
||||||
|
s.pendingMutex.Unlock()
|
||||||
|
|
||||||
|
return ErrPaymentIDAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
s.pendingPayments[paymentID] = payment
|
s.pendingPayments[paymentID] = payment
|
||||||
s.pendingMutex.Unlock()
|
s.pendingMutex.Unlock()
|
||||||
|
|
||||||
@ -398,31 +440,13 @@ func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID,
|
|||||||
if err := s.forward(packet); err != nil {
|
if err := s.forward(packet); err != nil {
|
||||||
s.removePendingPayment(paymentID)
|
s.removePendingPayment(paymentID)
|
||||||
if err := s.control.Fail(htlc.PaymentHash); err != nil {
|
if err := s.control.Fail(htlc.PaymentHash); err != nil {
|
||||||
return zeroPreimage, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return zeroPreimage, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns channels so that other subsystem might wait/skip the
|
return nil
|
||||||
// waiting of handling of payment.
|
|
||||||
var preimage [sha256.Size]byte
|
|
||||||
|
|
||||||
select {
|
|
||||||
case e := <-payment.err:
|
|
||||||
err = e
|
|
||||||
case <-s.quit:
|
|
||||||
return zeroPreimage, ErrSwitchExiting
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case p := <-payment.preimage:
|
|
||||||
preimage = p
|
|
||||||
case <-s.quit:
|
|
||||||
return zeroPreimage, ErrSwitchExiting
|
|
||||||
}
|
|
||||||
|
|
||||||
return preimage, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateForwardingPolicies sends a message to the switch to update the
|
// UpdateForwardingPolicies sends a message to the switch to update the
|
||||||
@ -889,12 +913,28 @@ func (s *Switch) handleLocalResponse(pkt *htlcPacket) {
|
|||||||
// has been restarted since sending the payment.
|
// has been restarted since sending the payment.
|
||||||
payment := s.findPayment(pkt.incomingHTLCID)
|
payment := s.findPayment(pkt.incomingHTLCID)
|
||||||
|
|
||||||
var (
|
// The error reason will be unencypted in case this a local
|
||||||
preimage [32]byte
|
// failure or a converted error.
|
||||||
paymentErr error
|
unencrypted := pkt.localFailure || pkt.convertedError
|
||||||
)
|
n := &networkResult{
|
||||||
|
msg: pkt.htlc,
|
||||||
|
unencrypted: unencrypted,
|
||||||
|
isResolution: pkt.isResolution,
|
||||||
|
}
|
||||||
|
|
||||||
switch htlc := pkt.htlc.(type) {
|
// Deliver the payment error and preimage to the application, if it is
|
||||||
|
// waiting for a response.
|
||||||
|
if payment != nil {
|
||||||
|
payment.resultChan <- n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractResult uses the given deobfuscator to extract the payment result from
|
||||||
|
// the given network message.
|
||||||
|
func (s *Switch) extractResult(deobfuscator ErrorDecrypter, n *networkResult,
|
||||||
|
paymentID uint64, paymentHash lntypes.Hash) (*PaymentResult, error) {
|
||||||
|
|
||||||
|
switch htlc := n.msg.(type) {
|
||||||
|
|
||||||
// We've received a settle update which means we can finalize the user
|
// We've received a settle update which means we can finalize the user
|
||||||
// payment and return successful response.
|
// payment and return successful response.
|
||||||
@ -902,52 +942,52 @@ func (s *Switch) handleLocalResponse(pkt *htlcPacket) {
|
|||||||
// Persistently mark that a payment to this payment hash
|
// Persistently mark that a payment to this payment hash
|
||||||
// succeeded. This will prevent us from ever making another
|
// succeeded. This will prevent us from ever making another
|
||||||
// payment to this hash.
|
// payment to this hash.
|
||||||
err := s.control.Success(pkt.circuit.PaymentHash)
|
err := s.control.Success(paymentHash)
|
||||||
if err != nil && err != ErrPaymentAlreadyCompleted {
|
if err != nil && err != ErrPaymentAlreadyCompleted {
|
||||||
log.Warnf("Unable to mark completed payment %x: %v",
|
return nil, fmt.Errorf("Unable to mark completed "+
|
||||||
pkt.circuit.PaymentHash, err)
|
"payment %x: %v", paymentHash, err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preimage = htlc.PaymentPreimage
|
return &PaymentResult{
|
||||||
|
Preimage: htlc.PaymentPreimage,
|
||||||
|
}, nil
|
||||||
|
|
||||||
// We've received a fail update which means we can finalize the user
|
// We've received a fail update which means we can finalize the
|
||||||
// payment and return fail response.
|
// user payment and return fail response.
|
||||||
case *lnwire.UpdateFailHTLC:
|
case *lnwire.UpdateFailHTLC:
|
||||||
// Persistently mark that a payment to this payment hash failed.
|
// Persistently mark that a payment to this payment hash
|
||||||
// This will permit us to make another attempt at a successful
|
// failed. This will permit us to make another attempt at a
|
||||||
// payment.
|
// successful payment.
|
||||||
err := s.control.Fail(pkt.circuit.PaymentHash)
|
err := s.control.Fail(paymentHash)
|
||||||
if err != nil && err != ErrPaymentAlreadyCompleted {
|
if err != nil && err != ErrPaymentAlreadyCompleted {
|
||||||
log.Warnf("Unable to ground payment %x: %v",
|
return nil, fmt.Errorf("Unable to ground payment "+
|
||||||
pkt.circuit.PaymentHash, err)
|
"%x: %v", paymentHash, err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
paymentErr := s.parseFailedPayment(
|
||||||
|
deobfuscator, paymentID, paymentHash, n.unencrypted,
|
||||||
|
n.isResolution, htlc,
|
||||||
|
)
|
||||||
|
|
||||||
paymentErr = s.parseFailedPayment(payment, pkt, htlc)
|
return &PaymentResult{
|
||||||
|
Error: paymentErr,
|
||||||
|
}, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Warnf("Received unknown response type: %T", pkt.htlc)
|
return nil, fmt.Errorf("Received unknown response type: %T",
|
||||||
return
|
htlc)
|
||||||
}
|
|
||||||
|
|
||||||
// Deliver the payment error and preimage to the application, if it is
|
|
||||||
// waiting for a response.
|
|
||||||
if payment != nil {
|
|
||||||
payment.err <- paymentErr
|
|
||||||
payment.preimage <- preimage
|
|
||||||
s.removePendingPayment(pkt.incomingHTLCID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseFailedPayment determines the appropriate failure message to return to
|
// parseFailedPayment determines the appropriate failure message to return to
|
||||||
// a user initiated payment. The three cases handled are:
|
// a user initiated payment. The three cases handled are:
|
||||||
// 1) A local failure, which should already plaintext.
|
// 1) An unencrypted failure, which should already plaintext.
|
||||||
// 2) A resolution from the chain arbitrator,
|
// 2) A resolution from the chain arbitrator, which possibly has no failure
|
||||||
// 3) A failure from the remote party, which will need to be decrypted using the
|
// reason attached.
|
||||||
// payment deobfuscator.
|
// 3) A failure from the remote party, which will need to be decrypted using
|
||||||
func (s *Switch) parseFailedPayment(payment *pendingPayment, pkt *htlcPacket,
|
// the payment deobfuscator.
|
||||||
htlc *lnwire.UpdateFailHTLC) *ForwardingError {
|
func (s *Switch) parseFailedPayment(deobfuscator ErrorDecrypter,
|
||||||
|
paymentID uint64, paymentHash lntypes.Hash, unencrypted,
|
||||||
|
isResolution bool, htlc *lnwire.UpdateFailHTLC) *ForwardingError {
|
||||||
|
|
||||||
var failure *ForwardingError
|
var failure *ForwardingError
|
||||||
|
|
||||||
@ -956,14 +996,14 @@ func (s *Switch) parseFailedPayment(payment *pendingPayment, pkt *htlcPacket,
|
|||||||
// The payment never cleared the link, so we don't need to
|
// The payment never cleared the link, so we don't need to
|
||||||
// decrypt the error, simply decode it them report back to the
|
// decrypt the error, simply decode it them report back to the
|
||||||
// user.
|
// user.
|
||||||
case pkt.localFailure || pkt.convertedError:
|
case unencrypted:
|
||||||
var userErr string
|
var userErr string
|
||||||
r := bytes.NewReader(htlc.Reason)
|
r := bytes.NewReader(htlc.Reason)
|
||||||
failureMsg, err := lnwire.DecodeFailure(r, 0)
|
failureMsg, err := lnwire.DecodeFailure(r, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
userErr = fmt.Sprintf("unable to decode onion failure, "+
|
userErr = fmt.Sprintf("unable to decode onion "+
|
||||||
"htlc with hash(%x): %v",
|
"failure (hash=%v, pid=%d): %v",
|
||||||
pkt.circuit.PaymentHash[:], err)
|
paymentHash, paymentID, err)
|
||||||
log.Error(userErr)
|
log.Error(userErr)
|
||||||
|
|
||||||
// As this didn't even clear the link, we don't need to
|
// As this didn't even clear the link, we don't need to
|
||||||
@ -981,38 +1021,27 @@ func (s *Switch) parseFailedPayment(payment *pendingPayment, pkt *htlcPacket,
|
|||||||
// the first hop. In this case, we'll report a permanent
|
// the first hop. In this case, we'll report a permanent
|
||||||
// channel failure as this means us, or the remote party had to
|
// channel failure as this means us, or the remote party had to
|
||||||
// go on chain.
|
// go on chain.
|
||||||
case pkt.isResolution && htlc.Reason == nil:
|
case isResolution && htlc.Reason == nil:
|
||||||
userErr := fmt.Sprintf("payment was resolved " +
|
userErr := fmt.Sprintf("payment was resolved "+
|
||||||
"on-chain, then cancelled back")
|
"on-chain, then cancelled back (hash=%v, pid=%d)",
|
||||||
|
paymentHash, paymentID)
|
||||||
failure = &ForwardingError{
|
failure = &ForwardingError{
|
||||||
ErrorSource: s.cfg.SelfKey,
|
ErrorSource: s.cfg.SelfKey,
|
||||||
ExtraMsg: userErr,
|
ExtraMsg: userErr,
|
||||||
FailureMessage: lnwire.FailPermanentChannelFailure{},
|
FailureMessage: lnwire.FailPermanentChannelFailure{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the provided payment is nil, we have discarded the error decryptor
|
|
||||||
// due to a restart. We'll return a fixed error and signal a temporary
|
|
||||||
// channel failure to the router.
|
|
||||||
case payment == nil:
|
|
||||||
userErr := fmt.Sprintf("error decryptor for payment " +
|
|
||||||
"could not be located, likely due to restart")
|
|
||||||
failure = &ForwardingError{
|
|
||||||
ErrorSource: s.cfg.SelfKey,
|
|
||||||
ExtraMsg: userErr,
|
|
||||||
FailureMessage: lnwire.NewTemporaryChannelFailure(nil),
|
|
||||||
}
|
|
||||||
|
|
||||||
// A regular multi-hop payment error that we'll need to
|
// A regular multi-hop payment error that we'll need to
|
||||||
// decrypt.
|
// decrypt.
|
||||||
default:
|
default:
|
||||||
var err error
|
var err error
|
||||||
// We'll attempt to fully decrypt the onion encrypted
|
// We'll attempt to fully decrypt the onion encrypted
|
||||||
// error. If we're unable to then we'll bail early.
|
// error. If we're unable to then we'll bail early.
|
||||||
failure, err = payment.deobfuscator.DecryptError(htlc.Reason)
|
failure, err = deobfuscator.DecryptError(htlc.Reason)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
userErr := fmt.Sprintf("unable to de-obfuscate onion "+
|
userErr := fmt.Sprintf("unable to de-obfuscate "+
|
||||||
"failure, htlc with hash(%x): %v",
|
"onion failure (hash=%v, pid=%d): %v",
|
||||||
pkt.circuit.PaymentHash[:], err)
|
paymentHash, paymentID, err)
|
||||||
log.Error(userErr)
|
log.Error(userErr)
|
||||||
failure = &ForwardingError{
|
failure = &ForwardingError{
|
||||||
ErrorSource: s.cfg.SelfKey,
|
ErrorSource: s.cfg.SelfKey,
|
||||||
@ -2206,15 +2235,6 @@ func (s *Switch) CircuitModifier() CircuitModifier {
|
|||||||
return s.circuits
|
return s.circuits
|
||||||
}
|
}
|
||||||
|
|
||||||
// numPendingPayments is helper function which returns the overall number of
|
|
||||||
// pending user payments.
|
|
||||||
func (s *Switch) numPendingPayments() int {
|
|
||||||
s.pendingMutex.RLock()
|
|
||||||
defer s.pendingMutex.RUnlock()
|
|
||||||
|
|
||||||
return len(s.pendingPayments)
|
|
||||||
}
|
|
||||||
|
|
||||||
// commitCircuits persistently adds a circuit to the switch's circuit map.
|
// commitCircuits persistently adds a circuit to the switch's circuit map.
|
||||||
func (s *Switch) commitCircuits(circuits ...*PaymentCircuit) (
|
func (s *Switch) commitCircuits(circuits ...*PaymentCircuit) (
|
||||||
*CircuitFwdActions, error) {
|
*CircuitFwdActions, error) {
|
||||||
|
@ -1417,7 +1417,7 @@ func testSkipLinkLocalForward(t *testing.T, eligible bool,
|
|||||||
// We'll attempt to send out a new HTLC that has Alice as the first
|
// We'll attempt to send out a new HTLC that has Alice as the first
|
||||||
// outgoing link. This should fail as Alice isn't yet able to forward
|
// outgoing link. This should fail as Alice isn't yet able to forward
|
||||||
// any active HTLC's.
|
// any active HTLC's.
|
||||||
_, err = s.SendHTLC(aliceChannelLink.ShortChanID(), addMsg, nil)
|
err = s.SendHTLC(aliceChannelLink.ShortChanID(), 0, addMsg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("local forward should fail due to inactive link")
|
t.Fatalf("local forward should fail due to inactive link")
|
||||||
}
|
}
|
||||||
@ -1738,24 +1738,47 @@ func TestSwitchSendPayment(t *testing.T) {
|
|||||||
PaymentHash: rhash,
|
PaymentHash: rhash,
|
||||||
Amount: 1,
|
Amount: 1,
|
||||||
}
|
}
|
||||||
|
paymentID := uint64(123)
|
||||||
|
|
||||||
|
// First check that the switch will correctly respond that this payment
|
||||||
|
// ID is unknown.
|
||||||
|
_, err = s.GetPaymentResult(
|
||||||
|
paymentID, newMockDeobfuscator(),
|
||||||
|
)
|
||||||
|
if err != ErrPaymentIDNotFound {
|
||||||
|
t.Fatalf("expected ErrPaymentIDNotFound, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 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(
|
err := s.SendHTLC(
|
||||||
aliceChannelLink.ShortChanID(), update,
|
aliceChannelLink.ShortChanID(), paymentID, update,
|
||||||
newMockDeobfuscator())
|
|
||||||
errChan <- err
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
// Send the payment with the same payment hash and same
|
|
||||||
// amount and check that it will be propagated successfully
|
|
||||||
_, err := s.SendHTLC(
|
|
||||||
aliceChannelLink.ShortChanID(), update,
|
|
||||||
newMockDeobfuscator(),
|
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resultChan, err := s.GetPaymentResult(
|
||||||
|
paymentID, newMockDeobfuscator(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := <-resultChan
|
||||||
|
if !ok {
|
||||||
|
errChan <- fmt.Errorf("shutting down")
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
errChan <- result.Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errChan <- nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@ -1765,29 +1788,13 @@ func TestSwitchSendPayment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
if err != ErrPaymentInFlight {
|
if err != nil {
|
||||||
t.Fatalf("unable to send payment: %v", err)
|
t.Fatalf("unable to send payment: %v", err)
|
||||||
}
|
}
|
||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
t.Fatal("request was not propagated to destination")
|
t.Fatal("request was not propagated to destination")
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
|
||||||
case packet := <-aliceChannelLink.packets:
|
|
||||||
if err := aliceChannelLink.completeCircuit(packet); err != nil {
|
|
||||||
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
case err := <-errChan:
|
|
||||||
t.Fatalf("unable to send payment: %v", err)
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatal("request was not propagated to destination")
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.numPendingPayments() != 1 {
|
|
||||||
t.Fatal("wrong amount of pending payments")
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.circuits.NumOpen() != 1 {
|
if s.circuits.NumOpen() != 1 {
|
||||||
t.Fatal("wrong amount of circuits")
|
t.Fatal("wrong amount of circuits")
|
||||||
}
|
}
|
||||||
@ -1824,10 +1831,6 @@ func TestSwitchSendPayment(t *testing.T) {
|
|||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
t.Fatal("err wasn't received")
|
t.Fatal("err wasn't received")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.numPendingPayments() != 0 {
|
|
||||||
t.Fatal("wrong amount of pending payments")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLocalPaymentNoForwardingEvents tests that if we send a series of locally
|
// TestLocalPaymentNoForwardingEvents tests that if we send a series of locally
|
||||||
|
@ -543,7 +543,7 @@ func getChanID(msg lnwire.Message) (lnwire.ChannelID, error) {
|
|||||||
func generatePaymentWithPreimage(invoiceAmt, htlcAmt lnwire.MilliSatoshi,
|
func generatePaymentWithPreimage(invoiceAmt, htlcAmt lnwire.MilliSatoshi,
|
||||||
timelock uint32, blob [lnwire.OnionPacketSize]byte,
|
timelock uint32, blob [lnwire.OnionPacketSize]byte,
|
||||||
preimage, rhash [32]byte) (*channeldb.Invoice, *lnwire.UpdateAddHTLC,
|
preimage, rhash [32]byte) (*channeldb.Invoice, *lnwire.UpdateAddHTLC,
|
||||||
error) {
|
uint64, error) {
|
||||||
|
|
||||||
// Create the db invoice. Normally the payment requests needs to be set,
|
// Create the db invoice. Normally the payment requests needs to be set,
|
||||||
// because it is decoded in InvoiceRegistry to obtain the cltv expiry.
|
// because it is decoded in InvoiceRegistry to obtain the cltv expiry.
|
||||||
@ -566,18 +566,25 @@ func generatePaymentWithPreimage(invoiceAmt, htlcAmt lnwire.MilliSatoshi,
|
|||||||
OnionBlob: blob,
|
OnionBlob: blob,
|
||||||
}
|
}
|
||||||
|
|
||||||
return invoice, htlc, nil
|
pid, err := generateRandomBytes(8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, 0, err
|
||||||
|
}
|
||||||
|
paymentID := binary.BigEndian.Uint64(pid)
|
||||||
|
|
||||||
|
return invoice, htlc, paymentID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generatePayment generates the htlc add request by given path blob and
|
// generatePayment generates the htlc add request by given path blob and
|
||||||
// invoice which should be added by destination peer.
|
// invoice which should be added by destination peer.
|
||||||
func generatePayment(invoiceAmt, htlcAmt lnwire.MilliSatoshi, timelock uint32,
|
func generatePayment(invoiceAmt, htlcAmt lnwire.MilliSatoshi, timelock uint32,
|
||||||
blob [lnwire.OnionPacketSize]byte) (*channeldb.Invoice, *lnwire.UpdateAddHTLC, error) {
|
blob [lnwire.OnionPacketSize]byte) (*channeldb.Invoice,
|
||||||
|
*lnwire.UpdateAddHTLC, uint64, error) {
|
||||||
|
|
||||||
var preimage [sha256.Size]byte
|
var preimage [sha256.Size]byte
|
||||||
r, err := generateRandomBytes(sha256.Size)
|
r, err := generateRandomBytes(sha256.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
copy(preimage[:], r)
|
copy(preimage[:], r)
|
||||||
|
|
||||||
@ -772,7 +779,9 @@ func preparePayment(sendingPeer, receivingPeer lnpeer.Peer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate payment: invoice and htlc.
|
// Generate payment: invoice and htlc.
|
||||||
invoice, htlc, err := generatePayment(invoiceAmt, htlcAmt, timelock, blob)
|
invoice, htlc, pid, err := generatePayment(
|
||||||
|
invoiceAmt, htlcAmt, timelock, blob,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -785,10 +794,29 @@ func preparePayment(sendingPeer, receivingPeer lnpeer.Peer,
|
|||||||
|
|
||||||
// Send payment and expose err channel.
|
// Send payment and expose err channel.
|
||||||
return invoice, func() error {
|
return invoice, func() error {
|
||||||
_, err := sender.htlcSwitch.SendHTLC(
|
err := sender.htlcSwitch.SendHTLC(
|
||||||
firstHop, htlc, newMockDeobfuscator(),
|
firstHop, pid, htlc,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
resultChan, err := sender.htlcSwitch.GetPaymentResult(
|
||||||
|
pid, newMockDeobfuscator(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := <-resultChan
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("shutting down")
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1235,8 +1263,10 @@ func (n *twoHopNetwork) makeHoldPayment(sendingPeer, receivingPeer lnpeer.Peer,
|
|||||||
rhash := preimage.Hash()
|
rhash := preimage.Hash()
|
||||||
|
|
||||||
// Generate payment: invoice and htlc.
|
// Generate payment: invoice and htlc.
|
||||||
invoice, htlc, err := generatePaymentWithPreimage(invoiceAmt, htlcAmt, timelock, blob,
|
invoice, htlc, pid, err := generatePaymentWithPreimage(
|
||||||
channeldb.UnknownPreimage, rhash)
|
invoiceAmt, htlcAmt, timelock, blob,
|
||||||
|
channeldb.UnknownPreimage, rhash,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
paymentErr <- err
|
paymentErr <- err
|
||||||
return paymentErr
|
return paymentErr
|
||||||
@ -1250,10 +1280,32 @@ func (n *twoHopNetwork) makeHoldPayment(sendingPeer, receivingPeer lnpeer.Peer,
|
|||||||
|
|
||||||
// Send payment and expose err channel.
|
// Send payment and expose err channel.
|
||||||
go func() {
|
go func() {
|
||||||
_, err := sender.htlcSwitch.SendHTLC(
|
err := sender.htlcSwitch.SendHTLC(
|
||||||
firstHop, htlc, newMockDeobfuscator(),
|
firstHop, pid, htlc,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
paymentErr <- err
|
paymentErr <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resultChan, err := sender.htlcSwitch.GetPaymentResult(
|
||||||
|
pid, newMockDeobfuscator(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
paymentErr <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := <-resultChan
|
||||||
|
if !ok {
|
||||||
|
paymentErr <- fmt.Errorf("shutting down")
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
paymentErr <- result.Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
paymentErr <- nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return paymentErr
|
return paymentErr
|
||||||
|
64
routing/mock_test.go
Normal file
64
routing/mock_test.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package routing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockPaymentAttemptDispatcher struct {
|
||||||
|
onPayment func(firstHop lnwire.ShortChannelID) ([32]byte, error)
|
||||||
|
results map[uint64]*htlcswitch.PaymentResult
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PaymentAttemptDispatcher = (*mockPaymentAttemptDispatcher)(nil)
|
||||||
|
|
||||||
|
func (m *mockPaymentAttemptDispatcher) SendHTLC(firstHop lnwire.ShortChannelID,
|
||||||
|
pid uint64,
|
||||||
|
_ *lnwire.UpdateAddHTLC) error {
|
||||||
|
|
||||||
|
if m.onPayment == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.results == nil {
|
||||||
|
m.results = make(map[uint64]*htlcswitch.PaymentResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *htlcswitch.PaymentResult
|
||||||
|
preimage, err := m.onPayment(firstHop)
|
||||||
|
if err != nil {
|
||||||
|
fwdErr, ok := err.(*htlcswitch.ForwardingError)
|
||||||
|
if !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result = &htlcswitch.PaymentResult{
|
||||||
|
Error: fwdErr,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = &htlcswitch.PaymentResult{Preimage: preimage}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.results[pid] = result
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPaymentAttemptDispatcher) GetPaymentResult(paymentID uint64,
|
||||||
|
_ htlcswitch.ErrorDecrypter) (<-chan *htlcswitch.PaymentResult, error) {
|
||||||
|
|
||||||
|
c := make(chan *htlcswitch.PaymentResult, 1)
|
||||||
|
res, ok := m.results[paymentID]
|
||||||
|
if !ok {
|
||||||
|
return nil, htlcswitch.ErrPaymentIDNotFound
|
||||||
|
}
|
||||||
|
c <- res
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockPaymentAttemptDispatcher) setPaymentResult(
|
||||||
|
f func(firstHop lnwire.ShortChannelID) ([32]byte, error)) {
|
||||||
|
|
||||||
|
m.onPayment = f
|
||||||
|
}
|
@ -2,7 +2,6 @@ package routing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
@ -126,6 +125,28 @@ type ChannelGraphSource interface {
|
|||||||
e1, e2 *channeldb.ChannelEdgePolicy) error) error
|
e1, e2 *channeldb.ChannelEdgePolicy) error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PaymentAttemptDispatcher is used by the router to send payment attempts onto
|
||||||
|
// the network, and receive their results.
|
||||||
|
type PaymentAttemptDispatcher interface {
|
||||||
|
// SendHTLC is a function that directs a link-layer switch to
|
||||||
|
// 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
|
||||||
|
// payment was unsuccessful.
|
||||||
|
SendHTLC(firstHop lnwire.ShortChannelID,
|
||||||
|
paymentID uint64,
|
||||||
|
htlcAdd *lnwire.UpdateAddHTLC) error
|
||||||
|
|
||||||
|
// GetPaymentResult returns the the result of the payment attempt with
|
||||||
|
// the given paymentID. The method returns a channel where the payment
|
||||||
|
// result will be sent when available, or an error is encountered
|
||||||
|
// during forwarding. When a result is received on the channel, the
|
||||||
|
// HTLC is guaranteed to no longer be in flight. The switch shutting
|
||||||
|
// down is signaled by closing the channel. If the paymentID is
|
||||||
|
// unknown, ErrPaymentIDNotFound will be returned.
|
||||||
|
GetPaymentResult(paymentID uint64, deobfuscator htlcswitch.ErrorDecrypter) (
|
||||||
|
<-chan *htlcswitch.PaymentResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
// FeeSchema is the set fee configuration for a Lightning Node on the network.
|
// FeeSchema is the set fee configuration for a Lightning Node on the network.
|
||||||
// Using the coefficients described within the schema, the required fee to
|
// Using the coefficients described within the schema, the required fee to
|
||||||
// forward outgoing payments can be derived.
|
// forward outgoing payments can be derived.
|
||||||
@ -173,13 +194,10 @@ type Config struct {
|
|||||||
// we need in order to properly maintain the channel graph.
|
// we need in order to properly maintain the channel graph.
|
||||||
ChainView chainview.FilteredChainView
|
ChainView chainview.FilteredChainView
|
||||||
|
|
||||||
// SendToSwitch is a function that directs a link-layer switch to
|
// Payer is an instance of a PaymentAttemptDispatcher and is used by
|
||||||
// forward a fully encoded payment to the first hop in the route
|
// the router to send payment attempts onto the network, and receive
|
||||||
// denoted by its public key. A non-nil error is to be returned if the
|
// their results.
|
||||||
// payment was unsuccessful.
|
Payer PaymentAttemptDispatcher
|
||||||
SendToSwitch func(firstHop lnwire.ShortChannelID,
|
|
||||||
htlcAdd *lnwire.UpdateAddHTLC,
|
|
||||||
circuit *sphinx.Circuit) ([sha256.Size]byte, error)
|
|
||||||
|
|
||||||
// ChannelPruneExpiry is the duration used to determine if a channel
|
// ChannelPruneExpiry is the duration used to determine if a channel
|
||||||
// should be pruned or not. If the delta between now and when the
|
// should be pruned or not. If the delta between now and when the
|
||||||
@ -199,6 +217,12 @@ type Config struct {
|
|||||||
// returned.
|
// returned.
|
||||||
QueryBandwidth func(edge *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi
|
QueryBandwidth func(edge *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// NextPaymentID is a method that guarantees to return a new, unique ID
|
||||||
|
// each time it is called. This is used by the router to generate a
|
||||||
|
// unique payment ID for each payment it attempts to send, such that
|
||||||
|
// the switch can properly handle the HTLC.
|
||||||
|
NextPaymentID func() (uint64, error)
|
||||||
|
|
||||||
// AssumeChannelValid toggles whether or not the router will check for
|
// AssumeChannelValid toggles whether or not the router will check for
|
||||||
// spentness of channel outpoints. For neutrino, this saves long rescans
|
// spentness of channel outpoints. For neutrino, this saves long rescans
|
||||||
// from blocking initial usage of the daemon.
|
// from blocking initial usage of the daemon.
|
||||||
@ -1381,12 +1405,22 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex,
|
|||||||
return route, nil
|
return route, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generateNewSessionKey generates a new ephemeral private key to be used for a
|
||||||
|
// payment attempt.
|
||||||
|
func generateNewSessionKey() (*btcec.PrivateKey, error) {
|
||||||
|
// Generate a new random session key to ensure that we don't trigger
|
||||||
|
// any replay.
|
||||||
|
//
|
||||||
|
// TODO(roasbeef): add more sources of randomness?
|
||||||
|
return btcec.NewPrivateKey(btcec.S256())
|
||||||
|
}
|
||||||
|
|
||||||
// generateSphinxPacket generates then encodes a sphinx packet which encodes
|
// generateSphinxPacket generates then encodes a sphinx packet which encodes
|
||||||
// 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(rt *route.Route, paymentHash []byte) ([]byte,
|
func generateSphinxPacket(rt *route.Route, paymentHash []byte,
|
||||||
*sphinx.Circuit, error) {
|
sessionKey *btcec.PrivateKey) ([]byte, *sphinx.Circuit, error) {
|
||||||
|
|
||||||
// As a sanity check, we'll ensure that the set of hops has been
|
// As a sanity check, we'll ensure that the set of hops has been
|
||||||
// properly filled in, otherwise, we won't actually be able to
|
// properly filled in, otherwise, we won't actually be able to
|
||||||
@ -1410,15 +1444,6 @@ func generateSphinxPacket(rt *route.Route, paymentHash []byte) ([]byte,
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Generate a new random session key to ensure that we don't trigger
|
|
||||||
// any replay.
|
|
||||||
//
|
|
||||||
// TODO(roasbeef): add more sources of randomness?
|
|
||||||
sessionKey, err := btcec.NewPrivateKey(btcec.S256())
|
|
||||||
if err != nil {
|
|
||||||
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
|
||||||
// privacy preserving source routing across the network.
|
// privacy preserving source routing across the network.
|
||||||
sphinxPacket, err := sphinx.NewOnionPacket(
|
sphinxPacket, err := sphinx.NewOnionPacket(
|
||||||
@ -1654,32 +1679,19 @@ func (r *ChannelRouter) sendPaymentAttempt(paySession *paymentSession,
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
preimage, err := r.sendToSwitch(route, paymentHash)
|
// Generate a new key to be used for this attempt.
|
||||||
if err == nil {
|
sessionKey, err := generateNewSessionKey()
|
||||||
return preimage, true, nil
|
if err != nil {
|
||||||
|
return [32]byte{}, true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Errorf("Attempt to send payment %x failed: %v",
|
|
||||||
paymentHash, err)
|
|
||||||
|
|
||||||
finalOutcome := r.processSendError(paySession, route, err)
|
|
||||||
|
|
||||||
return [32]byte{}, finalOutcome, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendToSwitch sends a payment along the specified route and returns the
|
|
||||||
// obtained preimage.
|
|
||||||
func (r *ChannelRouter) sendToSwitch(route *route.Route, paymentHash [32]byte) (
|
|
||||||
[32]byte, error) {
|
|
||||||
|
|
||||||
// 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.
|
||||||
onionBlob, circuit, err := generateSphinxPacket(
|
onionBlob, circuit, err := generateSphinxPacket(
|
||||||
route, paymentHash[:],
|
route, paymentHash[:], sessionKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return [32]byte{}, err
|
return [32]byte{}, true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Craft an HTLC packet to send to the layer 2 switch. The
|
// Craft an HTLC packet to send to the layer 2 switch. The
|
||||||
@ -1698,9 +1710,74 @@ func (r *ChannelRouter) sendToSwitch(route *route.Route, paymentHash [32]byte) (
|
|||||||
firstHop := lnwire.NewShortChanIDFromInt(
|
firstHop := lnwire.NewShortChanIDFromInt(
|
||||||
route.Hops[0].ChannelID,
|
route.Hops[0].ChannelID,
|
||||||
)
|
)
|
||||||
return r.cfg.SendToSwitch(
|
|
||||||
firstHop, htlcAdd, circuit,
|
// We generate a new, unique payment ID that we will use for
|
||||||
|
// this HTLC.
|
||||||
|
paymentID, err := r.cfg.NextPaymentID()
|
||||||
|
if err != nil {
|
||||||
|
return [32]byte{}, true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.cfg.Payer.SendHTLC(
|
||||||
|
firstHop, paymentID, htlcAdd,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed sending attempt %d for payment %x to "+
|
||||||
|
"switch: %v", paymentID, paymentHash, err)
|
||||||
|
|
||||||
|
// We must inspect the error to know whether it was critical or
|
||||||
|
// not, to decide whether we should continue trying.
|
||||||
|
finalOutcome := r.processSendError(
|
||||||
|
paySession, route, err,
|
||||||
|
)
|
||||||
|
|
||||||
|
return [32]byte{}, finalOutcome, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using the created circuit, initialize the error decrypter so we can
|
||||||
|
// parse+decode any failures incurred by this payment within the
|
||||||
|
// switch.
|
||||||
|
errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
|
||||||
|
OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now ask the switch to return the result of the payment when
|
||||||
|
// available.
|
||||||
|
resultChan, err := r.cfg.Payer.GetPaymentResult(
|
||||||
|
paymentID, errorDecryptor,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed getting result for paymentID %d "+
|
||||||
|
"from switch: %v", paymentID, err)
|
||||||
|
return [32]byte{}, true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
result *htlcswitch.PaymentResult
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
select {
|
||||||
|
case result, ok = <-resultChan:
|
||||||
|
if !ok {
|
||||||
|
return [32]byte{}, true, htlcswitch.ErrSwitchExiting
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-r.quit:
|
||||||
|
return [32]byte{}, true, ErrRouterShuttingDown
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
log.Errorf("Attempt to send payment %x failed: %v",
|
||||||
|
paymentHash, result.Error)
|
||||||
|
|
||||||
|
finalOutcome := r.processSendError(
|
||||||
|
paySession, route, result.Error,
|
||||||
|
)
|
||||||
|
|
||||||
|
return [32]byte{}, finalOutcome, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Preimage, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// processSendError analyzes the error for the payment attempt received from the
|
// processSendError analyzes the error for the payment attempt received from the
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -15,7 +16,6 @@ import (
|
|||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
||||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
@ -24,6 +24,8 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/zpay32"
|
"github.com/lightningnetwork/lnd/zpay32"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var uniquePaymentID uint64 = 1 // to be used atomically
|
||||||
|
|
||||||
type testCtx struct {
|
type testCtx struct {
|
||||||
router *ChannelRouter
|
router *ChannelRouter
|
||||||
|
|
||||||
@ -47,10 +49,7 @@ func (c *testCtx) RestartRouter() error {
|
|||||||
Graph: c.graph,
|
Graph: c.graph,
|
||||||
Chain: c.chain,
|
Chain: c.chain,
|
||||||
ChainView: c.chainView,
|
ChainView: c.chainView,
|
||||||
SendToSwitch: func(_ lnwire.ShortChannelID,
|
Payer: &mockPaymentAttemptDispatcher{},
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
|
||||||
return [32]byte{}, nil
|
|
||||||
},
|
|
||||||
ChannelPruneExpiry: time.Hour * 24,
|
ChannelPruneExpiry: time.Hour * 24,
|
||||||
GraphPruneInterval: time.Hour * 2,
|
GraphPruneInterval: time.Hour * 2,
|
||||||
})
|
})
|
||||||
@ -88,16 +87,16 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr
|
|||||||
Graph: graphInstance.graph,
|
Graph: graphInstance.graph,
|
||||||
Chain: chain,
|
Chain: chain,
|
||||||
ChainView: chainView,
|
ChainView: chainView,
|
||||||
SendToSwitch: func(_ lnwire.ShortChannelID,
|
Payer: &mockPaymentAttemptDispatcher{},
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
|
||||||
|
|
||||||
return [32]byte{}, nil
|
|
||||||
},
|
|
||||||
ChannelPruneExpiry: time.Hour * 24,
|
ChannelPruneExpiry: time.Hour * 24,
|
||||||
GraphPruneInterval: time.Hour * 2,
|
GraphPruneInterval: time.Hour * 2,
|
||||||
QueryBandwidth: func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
|
QueryBandwidth: func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
|
||||||
return lnwire.NewMSatFromSatoshis(e.Capacity)
|
return lnwire.NewMSatFromSatoshis(e.Capacity)
|
||||||
},
|
},
|
||||||
|
NextPaymentID: func() (uint64, error) {
|
||||||
|
next := atomic.AddUint64(&uniquePaymentID, 1)
|
||||||
|
return next, nil
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to create router %v", err)
|
return nil, nil, fmt.Errorf("unable to create router %v", err)
|
||||||
@ -250,8 +249,8 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {
|
|||||||
// router's configuration to ignore the path that has luo ji as the
|
// router's configuration to ignore the path that has luo ji as the
|
||||||
// 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(firstHop lnwire.ShortChannelID,
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
||||||
|
|
||||||
roasbeefLuoji := lnwire.NewShortChanIDFromInt(689530843)
|
roasbeefLuoji := lnwire.NewShortChanIDFromInt(689530843)
|
||||||
if firstHop == roasbeefLuoji {
|
if firstHop == roasbeefLuoji {
|
||||||
@ -267,7 +266,7 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return preImage, nil
|
return preImage, nil
|
||||||
}
|
})
|
||||||
|
|
||||||
// Send off the payment request to the router, route through satoshi
|
// Send off the payment request to the router, route through satoshi
|
||||||
// should've been selected as a fall back and succeeded correctly.
|
// should've been selected as a fall back and succeeded correctly.
|
||||||
@ -387,8 +386,8 @@ func TestChannelUpdateValidation(t *testing.T) {
|
|||||||
// We'll modify the SendToSwitch method so that it simulates a failed
|
// We'll modify the SendToSwitch method so that it simulates a failed
|
||||||
// payment with an error originating from the first hop of the route.
|
// payment with an error originating from the first hop of the route.
|
||||||
// The unsigned channel update is attached to the failure message.
|
// The unsigned channel update is attached to the failure message.
|
||||||
ctx.router.cfg.SendToSwitch = func(firstHop lnwire.ShortChannelID,
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
||||||
|
|
||||||
v := ctx.aliases["b"]
|
v := ctx.aliases["b"]
|
||||||
source, err := btcec.ParsePubKey(
|
source, err := btcec.ParsePubKey(
|
||||||
@ -404,7 +403,7 @@ func TestChannelUpdateValidation(t *testing.T) {
|
|||||||
Update: errChanUpdate,
|
Update: errChanUpdate,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
// The payment parameter is mostly redundant in SendToRoute. Can be left
|
// The payment parameter is mostly redundant in SendToRoute. Can be left
|
||||||
// empty for this test.
|
// empty for this test.
|
||||||
@ -518,8 +517,8 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
|
|||||||
// We'll now modify the SendToSwitch method to return an error for the
|
// We'll now modify the SendToSwitch method to return an error for the
|
||||||
// outgoing channel to Son goku. This will be a fee related error, so
|
// outgoing channel to Son goku. This will be a fee related error, so
|
||||||
// it should only cause the edge to be pruned after the second attempt.
|
// it should only cause the edge to be pruned after the second attempt.
|
||||||
ctx.router.cfg.SendToSwitch = func(firstHop lnwire.ShortChannelID,
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
||||||
|
|
||||||
roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID)
|
roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID)
|
||||||
if firstHop == roasbeefSongoku {
|
if firstHop == roasbeefSongoku {
|
||||||
@ -543,7 +542,7 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return preImage, nil
|
return preImage, nil
|
||||||
}
|
})
|
||||||
|
|
||||||
// Send off the payment request to the router, route through satoshi
|
// Send off the payment request to the router, route through satoshi
|
||||||
// should've been selected as a fall back and succeeded correctly.
|
// should've been selected as a fall back and succeeded correctly.
|
||||||
@ -633,8 +632,8 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
|
|||||||
// outgoing channel to son goku. Since this is a time lock related
|
// outgoing channel to son goku. Since this is a time lock related
|
||||||
// error, we should fail the payment flow all together, as Goku is the
|
// error, we should fail the payment flow all together, as Goku is the
|
||||||
// only channel to Sophon.
|
// only channel to Sophon.
|
||||||
ctx.router.cfg.SendToSwitch = func(firstHop lnwire.ShortChannelID,
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
||||||
|
|
||||||
if firstHop == roasbeefSongoku {
|
if firstHop == roasbeefSongoku {
|
||||||
sourceKey, err := btcec.ParsePubKey(
|
sourceKey, err := btcec.ParsePubKey(
|
||||||
@ -653,7 +652,7 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return preImage, nil
|
return preImage, nil
|
||||||
}
|
})
|
||||||
|
|
||||||
// assertExpectedPath is a helper function that asserts the returned
|
// assertExpectedPath is a helper function that asserts the returned
|
||||||
// route properly routes around the failure we've introduced in the
|
// route properly routes around the failure we've introduced in the
|
||||||
@ -694,8 +693,8 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
|
|||||||
// We'll now modify the error return an IncorrectCltvExpiry error
|
// We'll now modify the error return an IncorrectCltvExpiry error
|
||||||
// instead, this should result in the same behavior of roasbeef routing
|
// instead, this should result in the same behavior of roasbeef routing
|
||||||
// around the faulty Son Goku node.
|
// around the faulty Son Goku node.
|
||||||
ctx.router.cfg.SendToSwitch = func(firstHop lnwire.ShortChannelID,
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
||||||
|
|
||||||
if firstHop == roasbeefSongoku {
|
if firstHop == roasbeefSongoku {
|
||||||
sourceKey, err := btcec.ParsePubKey(
|
sourceKey, err := btcec.ParsePubKey(
|
||||||
@ -714,7 +713,7 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return preImage, nil
|
return preImage, nil
|
||||||
}
|
})
|
||||||
|
|
||||||
// Once again, Roasbeef should route around Goku since they disagree
|
// Once again, Roasbeef should route around Goku since they disagree
|
||||||
// w.r.t to the block height, and instead go through Pham Nuwen.
|
// w.r.t to the block height, and instead go through Pham Nuwen.
|
||||||
@ -771,8 +770,8 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// TODO(roasbeef): filtering should be intelligent enough so just not
|
// TODO(roasbeef): filtering should be intelligent enough so just not
|
||||||
// go through satoshi at all at this point.
|
// go through satoshi at all at this point.
|
||||||
ctx.router.cfg.SendToSwitch = func(firstHop lnwire.ShortChannelID,
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
||||||
|
|
||||||
if firstHop == roasbeefLuoji {
|
if firstHop == roasbeefLuoji {
|
||||||
// We'll first simulate an error from the first
|
// We'll first simulate an error from the first
|
||||||
@ -804,7 +803,7 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return preImage, nil
|
return preImage, nil
|
||||||
}
|
})
|
||||||
|
|
||||||
ctx.router.missionControl.ResetHistory()
|
ctx.router.missionControl.ResetHistory()
|
||||||
|
|
||||||
@ -826,8 +825,8 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
|||||||
// Next, we'll modify the SendToSwitch method to indicate that luo ji
|
// Next, we'll modify the SendToSwitch method to indicate that luo ji
|
||||||
// wasn't originally online. This should also halt the send all
|
// wasn't originally online. This should also halt the send all
|
||||||
// together as all paths contain luoji and he can't be reached.
|
// together as all paths contain luoji and he can't be reached.
|
||||||
ctx.router.cfg.SendToSwitch = func(firstHop lnwire.ShortChannelID,
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
||||||
|
|
||||||
if firstHop == roasbeefLuoji {
|
if firstHop == roasbeefLuoji {
|
||||||
return [32]byte{}, &htlcswitch.ForwardingError{
|
return [32]byte{}, &htlcswitch.ForwardingError{
|
||||||
@ -837,7 +836,7 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return preImage, nil
|
return preImage, nil
|
||||||
}
|
})
|
||||||
|
|
||||||
// This shouldn't return an error, as we'll make a payment attempt via
|
// This shouldn't return an error, as we'll make a payment attempt via
|
||||||
// the satoshi channel based on the assumption that there might be an
|
// the satoshi channel based on the assumption that there might be an
|
||||||
@ -869,8 +868,8 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
|||||||
// Finally, we'll modify the SendToSwitch function to indicate that the
|
// Finally, we'll modify the SendToSwitch function to indicate that the
|
||||||
// roasbeef -> luoji channel has insufficient capacity. This should
|
// roasbeef -> luoji channel has insufficient capacity. This should
|
||||||
// again cause us to instead go via the satoshi route.
|
// again cause us to instead go via the satoshi route.
|
||||||
ctx.router.cfg.SendToSwitch = func(firstHop lnwire.ShortChannelID,
|
ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult(
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
func(firstHop lnwire.ShortChannelID) ([32]byte, error) {
|
||||||
|
|
||||||
if firstHop == roasbeefLuoji {
|
if firstHop == roasbeefLuoji {
|
||||||
// We'll first simulate an error from the first
|
// We'll first simulate an error from the first
|
||||||
@ -882,7 +881,7 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return preImage, nil
|
return preImage, nil
|
||||||
}
|
})
|
||||||
|
|
||||||
paymentPreImage, rt, err = ctx.router.SendPayment(&payment)
|
paymentPreImage, rt, err = ctx.router.SendPayment(&payment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1528,10 +1527,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) {
|
|||||||
Graph: ctx.graph,
|
Graph: ctx.graph,
|
||||||
Chain: ctx.chain,
|
Chain: ctx.chain,
|
||||||
ChainView: ctx.chainView,
|
ChainView: ctx.chainView,
|
||||||
SendToSwitch: func(_ lnwire.ShortChannelID,
|
Payer: &mockPaymentAttemptDispatcher{},
|
||||||
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
|
|
||||||
return [32]byte{}, nil
|
|
||||||
},
|
|
||||||
ChannelPruneExpiry: time.Hour * 24,
|
ChannelPruneExpiry: time.Hour * 24,
|
||||||
GraphPruneInterval: time.Hour * 2,
|
GraphPruneInterval: time.Hour * 2,
|
||||||
})
|
})
|
||||||
@ -2446,8 +2442,9 @@ func TestIsStaleEdgePolicy(t *testing.T) {
|
|||||||
func TestEmptyRoutesGenerateSphinxPacket(t *testing.T) {
|
func TestEmptyRoutesGenerateSphinxPacket(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
sessionKey, _ := btcec.NewPrivateKey(btcec.S256())
|
||||||
emptyRoute := &route.Route{}
|
emptyRoute := &route.Route{}
|
||||||
_, _, err := generateSphinxPacket(emptyRoute, testHash[:])
|
_, _, err := generateSphinxPacket(emptyRoute, testHash[:], sessionKey)
|
||||||
if err != route.ErrNoRouteHopsProvided {
|
if err != route.ErrNoRouteHopsProvided {
|
||||||
t.Fatalf("expected empty hops error: instead got: %v", err)
|
t.Fatalf("expected empty hops error: instead got: %v", err)
|
||||||
}
|
}
|
||||||
|
24
server.go
24
server.go
@ -609,25 +609,18 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
}
|
}
|
||||||
s.currentNodeAnn = nodeAnn
|
s.currentNodeAnn = nodeAnn
|
||||||
|
|
||||||
|
// The router will get access to the payment ID sequencer, such that it
|
||||||
|
// can generate unique payment IDs.
|
||||||
|
sequencer, err := htlcswitch.NewPersistentSequencer(chanDB)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
s.chanRouter, err = routing.New(routing.Config{
|
s.chanRouter, err = routing.New(routing.Config{
|
||||||
Graph: chanGraph,
|
Graph: chanGraph,
|
||||||
Chain: cc.chainIO,
|
Chain: cc.chainIO,
|
||||||
ChainView: cc.chainView,
|
ChainView: cc.chainView,
|
||||||
SendToSwitch: func(firstHop lnwire.ShortChannelID,
|
Payer: s.htlcSwitch,
|
||||||
htlcAdd *lnwire.UpdateAddHTLC,
|
|
||||||
circuit *sphinx.Circuit) ([32]byte, error) {
|
|
||||||
|
|
||||||
// Using the created circuit, initialize the error
|
|
||||||
// decrypter so we can parse+decode any failures
|
|
||||||
// incurred by this payment within the switch.
|
|
||||||
errorDecryptor := &htlcswitch.SphinxErrorDecrypter{
|
|
||||||
OnionErrorDecrypter: sphinx.NewOnionErrorDecrypter(circuit),
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.htlcSwitch.SendHTLC(
|
|
||||||
firstHop, htlcAdd, errorDecryptor,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
ChannelPruneExpiry: routing.DefaultChannelPruneExpiry,
|
ChannelPruneExpiry: routing.DefaultChannelPruneExpiry,
|
||||||
GraphPruneInterval: time.Duration(time.Hour),
|
GraphPruneInterval: time.Duration(time.Hour),
|
||||||
QueryBandwidth: func(edge *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
|
QueryBandwidth: func(edge *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi {
|
||||||
@ -660,6 +653,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
|||||||
return link.Bandwidth()
|
return link.Bandwidth()
|
||||||
},
|
},
|
||||||
AssumeChannelValid: cfg.Routing.UseAssumeChannelValid(),
|
AssumeChannelValid: cfg.Routing.UseAssumeChannelValid(),
|
||||||
|
NextPaymentID: sequencer.NextID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't create router: %v", err)
|
return nil, fmt.Errorf("can't create router: %v", err)
|
||||||
|
Loading…
Reference in New Issue
Block a user