routing+rpcserver: move route querying+sending to SendPayment

This commit moves much of the logic for querying for a potential route,
constructing the HTLC including the Sphinx packet, and sending the
ultimate payment from the rpcServer to the ChannelRouter.

This movement paves the way for muilt-path path finding as well as
adding automatic retry logic to the ChannelRouter. Additionally, by
having the ChannelRouter construct the Sphinx packet, we’ll be able to
also include the proper time-lock and general per-hop-payload
information properly in the future.
This commit is contained in:
Olaoluwa Osuntokun 2017-02-01 18:29:46 -08:00
parent 4eebc7c994
commit 08f0d0fbea
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
3 changed files with 183 additions and 157 deletions

View File

@ -15,6 +15,8 @@ import (
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"github.com/lightningnetwork/lightning-onion"
)
// FeeSchema is the set fee configuration for a Lighting Node on the network.
@ -74,9 +76,12 @@ type Config struct {
// key.
SendMessages func(target *btcec.PublicKey, msg ...lnwire.Message) error
// TODO(roasbeef): need a SendToSwitch func
// * possibly lift switch into package?
// *
// SendToSwitch 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.
SendToSwitch func(firstHop *btcec.PublicKey,
htlcAdd *lnwire.HTLCAddRequest) error
}
// ChannelRouter is the layer 3 router within the Lightning stack. Below the
@ -540,6 +545,11 @@ func (r *ChannelRouter) processNetworkAnnouncement(msg lnwire.Message) bool {
return false
}
// TODO(roasbeef): check if height > then our known height and
// wait for next epoch based on that?
// * or add to "premature" announcement bucket
// * bucket gets checked on each new incoming block
// Before we can add the channel to the channel graph, we need
// to obtain the full funding outpoint that's encoded within
// the channel ID.
@ -911,13 +921,130 @@ func (r *ChannelRouter) FindRoute(target *btcec.PublicKey, amt btcutil.Amount) (
return route, nil
}
// SendPayment...
// generateSphinxPacket generates then encodes a sphinx packet which encodes
// 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
// be sent to the first hop within the route.
//
// TODO(roasbeef): pipe through the htlcSwitch, move the payment storage info
// to the router, add interface for payment storage
// TODO(roasbeef): add version that takes a route object
func (r *ChannelRouter) SendPayment() error {
return nil
// TODO(roasbeef): add params for the per-hop payloads
func generateSphinxPacket(route *Route, paymentHash []byte) ([]byte, error) {
// First obtain all the public keys along the route which are contained
// in each hop.
nodes := make([]*btcec.PublicKey, len(route.Hops))
for i, hop := range route.Hops {
// We create a new instance of the public key to avoid possibly
// mutating the curve parameters, which are unset in a higher
// level in order to avoid spamming the logs.
pub := btcec.PublicKey{
btcec.S256(),
hop.Channel.Node.PubKey.X,
hop.Channel.Node.PubKey.Y,
}
nodes[i] = &pub
}
// Next we generate the per-hop payload which gives each node within
// the route the necessary information (fees, CLTV value, etc) to
// properly forward the payment.
// TODO(roasbeef): properly set CLTV value, payment amount, and chain
// within hop payloads.
var hopPayloads [][]byte
for i := 0; i < len(route.Hops); i++ {
payload := bytes.Repeat([]byte{byte('A' + i)},
sphinx.HopPayloadSize)
hopPayloads = append(hopPayloads, payload)
}
sessionKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return nil, err
}
// Next generate the onion routing packet which allows us to perform
// privacy preserving source routing across the network.
sphinxPacket, err := sphinx.NewOnionPacket(nodes, sessionKey,
hopPayloads, paymentHash)
if err != nil {
return nil, err
}
// Finally, encode Sphinx packet using it's wire representation to be
// included within the HTLC add packet.
var onionBlob bytes.Buffer
if err := sphinxPacket.Encode(&onionBlob); err != nil {
return nil, err
}
log.Tracef("Generated sphinx packet: %v",
newLogClosure(func() string {
// We unset the internal curve here in order to keep
// the logs from getting noisy.
sphinxPacket.Header.EphemeralKey.Curve = nil
return spew.Sdump(sphinxPacket)
}),
)
return onionBlob.Bytes(), nil
}
// LightningPayment describes a payment to be sent through the network to the
// final destination.
type LightningPayment struct {
// Target is the node in which the payment should be routed towards.
Target *btcec.PublicKey
// Amount is the value of the payment to send throuhg the network in
// satoshis.
// TODO(roasbeef): this should be milli satoshis
Amount btcutil.Amount
// PaymentHash is the r-hash value to use within the HTLC extended to
// the first hop.
PaymentHash [32]byte
// TODO(roasbeef): add message?
}
// SendPayment attempts to send a payment as described within the passed
// LightningPayment. This function is blocking and will return either: when the
// payment is successful, or all candidates routes have been attempted and
// resulted in a failed payment. If the payment succeeds, then a non-nil Route
// will be returned which describes the path the successful payment traversed
// within the network to reach the destination.
func (r *ChannelRouter) SendPayment(payment *LightningPayment) (*Route, error) {
// Query the graph for a potential path to the destination node that
// can support our payment amount. If a path is ultimately unavailable,
// then an error will be returned.
route, err := r.FindRoute(payment.Target, payment.Amount)
if err != nil {
return nil, err
}
log.Tracef("Selected route for payment: %#v", route)
// Generate the raw encoded sphinx packet to be included along with the
// htlcAdd message that we send directly to the switch.
sphinxPacket, err := generateSphinxPacket(route, payment.PaymentHash[:])
if err != nil {
return nil, err
}
// Craft an HTLC packet to send to the layer 2 switch. The metadata
// within this packet will be used to route the payment through the
// network, starting with the first-hop.
htlcAdd := &lnwire.HTLCAddRequest{
Amount: route.TotalAmount,
RedemptionHashes: [][32]byte{payment.PaymentHash},
OnionBlob: sphinxPacket,
}
// Attempt to send this payment through the network to complete the
// payment. If this attempt fails, then we'll bail our early.
firstHop := route.Hops[0].Channel.Node.PubKey
if err := r.cfg.SendToSwitch(firstHop, htlcAdd); err != nil {
return nil, err
}
return route, nil
}
// TopologyClient...

View File

@ -1,7 +1,6 @@
package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
@ -17,7 +16,6 @@ import (
"github.com/btcsuite/fastsha256"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
@ -916,10 +914,10 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
return err
}
// If we're in debug HTLC mode, then all outgoing
// HTLCs will pay to the same debug rHash. Otherwise,
// we pay to the rHash specified within the RPC
// request.
// If we're in debug HTLC mode, then all outgoing HTLCs
// will pay to the same debug rHash. Otherwise, we pay
// to the rHash specified within the RPC request.
var rHash [32]byte
if cfg.DebugHTLC && len(nextPayment.PaymentHash) == 0 {
rHash = debugHash
@ -927,15 +925,6 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
copy(rHash[:], nextPayment.PaymentHash)
}
// Construct and HTLC packet which a payment route (if
// one is found) to the destination using a Sphinx
// onion packet to encode the route.
htlcPkt, route, err := r.constructPaymentRoute(destNode, amt,
rHash)
if err != nil {
return err
}
// We launch a new goroutine to execute the current
// payment so we can continue to serve requests while
// this payment is being dispatched.
@ -943,10 +932,18 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
// TODO(roasbeef): semaphore to limit num outstanding
// goroutines.
go func() {
// Finally, send this next packet to the
// routing layer in order to complete the next
// payment.
if err := r.server.htlcSwitch.SendHTLC(htlcPkt); err != nil {
// Construct a payment request to send to the
// channel router. If the payment is
// successful, the the route chosen will be
// returned. Otherwise, we'll get a non-nil
// error.
payment := &routing.LightningPayment{
Target: destNode,
Amount: amt,
PaymentHash: rHash,
}
route, err := r.server.chanRouter.SendPayment(payment)
if err != nil {
errChan <- err
return
}
@ -958,11 +955,11 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
return
}
// TODO(roasbeef): proper responses
resp := &lnrpc.SendResponse{
// TODO(roasbeef): tack on payment hash also?
err = paymentStream.Send(&lnrpc.SendResponse{
PaymentRoute: marshalRoute(route),
}
if err := paymentStream.Send(resp); err != nil {
})
if err != nil {
errChan <- err
return
}
@ -997,12 +994,12 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
amt = payReq.Amount
rHash = payReq.PaymentHash
// Otherwise, the payment conditions have been manually specified in
// the proto.
// Otherwise, the payment conditions have been manually
// specified in the proto.
} else {
// If we're in debug HTLC mode, then all outgoing HTLCs will pay to
// the same debug rHash. Otherwise, we pay to the rHash specified
// within the RPC request.
// If we're in debug HTLC mode, then all outgoing HTLCs will
// pay to the same debug rHash. Otherwise, we pay to the rHash
// specified within the RPC request.
if cfg.DebugHTLC && nextPayment.PaymentHashString == "" {
rHash = debugHash
} else {
@ -1026,20 +1023,18 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
amt = btcutil.Amount(nextPayment.Amt)
}
// Construct and HTLC packet which a payment route (if
// one is found) to the destination using a Sphinx
// onoin packet to encode the route.
htlcPkt, route, err := r.constructPaymentRoute(destPub, amt, rHash)
// Finally, send a payment request to the channel router. If the
// payment succeeds, then the returned route will be that was used
// successfully within the payment.
route, err := r.server.chanRouter.SendPayment(&routing.LightningPayment{
Target: destPub,
Amount: amt,
PaymentHash: rHash,
})
if err != nil {
return nil, err
}
// Next, send this next packet to the routing layer in order to
// complete the next payment.
if err := r.server.htlcSwitch.SendHTLC(htlcPkt); err != nil {
return nil, err
}
// With the payment completed successfully, we now ave the details of
// the completed payment to the databse for historical record keeping.
if err := r.savePayment(route, amt, rHash[:]); err != nil {
@ -1049,114 +1044,6 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
return &lnrpc.SendResponse{}, nil
}
// constructPaymentRoute attempts to construct a complete HTLC packet which
// encapsulates a Sphinx onion packet that encodes the end-to-end route any
// payment instructions necessary to complete an HTLC. If a route is unable to
// be located, then an error is returned indicating as much.
func (r *rpcServer) constructPaymentRoute(destNode *btcec.PublicKey,
amt btcutil.Amount, rHash [32]byte) (*htlcPacket, *routing.Route, error) {
const queryTimeout = time.Duration(time.Second * 10)
// Query the channel router for a potential path to the destination
// node that can support our payment amount. If a path is ultimately
// unavailable, then an error will be returned.
route, err := r.server.chanRouter.FindRoute(destNode, amt)
if err != nil {
return nil, nil, err
}
rpcsLog.Tracef("[sendpayment] selected route: %#v", route)
// Generate the raw encoded sphinx packet to be included along with the
// HTLC add message. We snip off the first hop from the path as within
// the routing table's star graph, we're always the first hop.
sphinxPacket, err := generateSphinxPacket(route, rHash[:])
if err != nil {
return nil, nil, err
}
// Craft an HTLC packet to send to the routing subsystem. The
// metadata within this packet will be used to route the payment
// through the network.
htlcAdd := &lnwire.HTLCAddRequest{
Amount: route.TotalAmount,
RedemptionHashes: [][32]byte{rHash},
OnionBlob: sphinxPacket,
}
firstHopPub := route.Hops[0].Channel.Node.PubKey.SerializeCompressed()
destInterface := chainhash.Hash(fastsha256.Sum256(firstHopPub))
return &htlcPacket{
dest: destInterface,
msg: htlcAdd,
}, route, nil
}
// generateSphinxPacket generates then encodes a sphinx packet which encodes
// 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
// be sent to the first hop within the route.
func generateSphinxPacket(route *routing.Route, paymentHash []byte) ([]byte, error) {
// First obtain all the public keys along the route which are contained
// in each hop.
nodes := make([]*btcec.PublicKey, len(route.Hops))
for i, hop := range route.Hops {
// We create a new instance of the public key to avoid possibly
// mutating the curve parameters, which are unset in a higher
// level in order to avoid spamming the logs.
pub := btcec.PublicKey{
btcec.S256(),
hop.Channel.Node.PubKey.X,
hop.Channel.Node.PubKey.Y,
}
nodes[i] = &pub
}
// Next we generate the per-hop payload which gives each node within
// the route the necessary information (fees, CLTV value, etc) to
// properly forward the payment.
// TODO(roasbeef): properly set CLTV value, payment amount, and chain
// within hop paylods.
var hopPayloads [][]byte
for i := 0; i < len(route.Hops); i++ {
payload := bytes.Repeat([]byte{byte('A' + i)},
sphinx.HopPayloadSize)
hopPayloads = append(hopPayloads, payload)
}
sessionKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return nil, err
}
// Next generate the onion routing packet which allows
// us to perform privacy preserving source routing
// across the network.
sphinxPacket, err := sphinx.NewOnionPacket(nodes, sessionKey,
hopPayloads, paymentHash)
if err != nil {
return nil, err
}
// Finally, encode Sphinx packet using it's wire representation to be
// included within the HTLC add packet.
var onionBlob bytes.Buffer
if err := sphinxPacket.Encode(&onionBlob); err != nil {
return nil, err
}
rpcsLog.Tracef("[sendpayment] generated sphinx packet: %v",
newLogClosure(func() string {
// We unset the internal curve here in order to keep
// the logs from getting noisy.
sphinxPacket.Header.EphemeralKey.Curve = nil
return spew.Sdump(sphinxPacket)
}))
return onionBlob.Bytes(), nil
}
// AddInvoice attempts to add a new invoice to the invoice database. Any
// duplicated invoices are rejected, therefore all invoices *must* have a
// unique payment preimage.

View File

@ -17,11 +17,11 @@ import (
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/connmgr"
"github.com/roasbeef/btcutil"
"github.com/lightningnetwork/lnd/routing"
)
// server is the main server of the Lightning Network Daemon. The server houses
@ -167,6 +167,17 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
Notifier: notifier,
Broadcast: s.broadcastMessage,
SendMessages: s.sendToPeer,
SendToSwitch: func(firstHop *btcec.PublicKey,
htlcAdd *lnwire.HTLCAddRequest) error {
firstHopPub := firstHop.SerializeCompressed()
destInterface := chainhash.Hash(fastsha256.Sum256(firstHopPub))
return s.htlcSwitch.SendHTLC(&htlcPacket{
dest: destInterface,
msg: htlcAdd,
})
},
})
if err != nil {
return nil, err
@ -217,6 +228,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
// Send the persistent connection request to the connection
// manager, saving the request itself so we can cancel/restart
// the process as needed.
// TODO(roasbeef): use default addr
connReq := &connmgr.ConnReq{
Addr: lnAddr,
Permanent: true,