diff --git a/routing/router.go b/routing/router.go index 0d67869e..c6f9be39 100644 --- a/routing/router.go +++ b/routing/router.go @@ -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... diff --git a/rpcserver.go b/rpcserver.go index 254aa13a..6c27d195 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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. diff --git a/server.go b/server.go index 464e7c70..6004dbf8 100644 --- a/server.go +++ b/server.go @@ -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,