mirror of
https://github.com/lightninglabs/loop
synced 2024-11-11 13:11:12 +00:00
lndclient: add router sub server
This commit exposes router sub server functionality to loop. This is a preparation for using reliable payments in loop out.
This commit is contained in:
parent
dc2baf582f
commit
f559120565
@ -24,6 +24,7 @@ type LndServices struct {
|
|||||||
ChainNotifier ChainNotifierClient
|
ChainNotifier ChainNotifierClient
|
||||||
Signer SignerClient
|
Signer SignerClient
|
||||||
Invoices InvoicesClient
|
Invoices InvoicesClient
|
||||||
|
Router RouterClient
|
||||||
|
|
||||||
ChainParams *chaincfg.Params
|
ChainParams *chaincfg.Params
|
||||||
|
|
||||||
@ -121,6 +122,7 @@ func NewLndServices(lndAddress, application, network, macaroonDir,
|
|||||||
signerClient := newSignerClient(conn, macaroons.signerMac)
|
signerClient := newSignerClient(conn, macaroons.signerMac)
|
||||||
walletKitClient := newWalletKitClient(conn, macaroons.walletKitMac)
|
walletKitClient := newWalletKitClient(conn, macaroons.walletKitMac)
|
||||||
invoicesClient := newInvoicesClient(conn, macaroons.invoiceMac)
|
invoicesClient := newInvoicesClient(conn, macaroons.invoiceMac)
|
||||||
|
routerClient := newRouterClient(conn, macaroons.routerMac)
|
||||||
|
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
logger.Debugf("Closing lnd connection")
|
logger.Debugf("Closing lnd connection")
|
||||||
@ -145,6 +147,7 @@ func NewLndServices(lndAddress, application, network, macaroonDir,
|
|||||||
ChainNotifier: notifierClient,
|
ChainNotifier: notifierClient,
|
||||||
Signer: signerClient,
|
Signer: signerClient,
|
||||||
Invoices: invoicesClient,
|
Invoices: invoicesClient,
|
||||||
|
Router: routerClient,
|
||||||
ChainParams: chainParams,
|
ChainParams: chainParams,
|
||||||
macaroons: macaroons,
|
macaroons: macaroons,
|
||||||
},
|
},
|
||||||
@ -178,6 +181,7 @@ var (
|
|||||||
defaultInvoiceMacaroonFilename = "invoices.macaroon"
|
defaultInvoiceMacaroonFilename = "invoices.macaroon"
|
||||||
defaultChainMacaroonFilename = "chainnotifier.macaroon"
|
defaultChainMacaroonFilename = "chainnotifier.macaroon"
|
||||||
defaultWalletKitMacaroonFilename = "walletkit.macaroon"
|
defaultWalletKitMacaroonFilename = "walletkit.macaroon"
|
||||||
|
defaultRouterMacaroonFilename = "router.macaroon"
|
||||||
defaultSignerFilename = "signer.macaroon"
|
defaultSignerFilename = "signer.macaroon"
|
||||||
|
|
||||||
// maxMsgRecvSize is the largest gRPC message our client will receive.
|
// maxMsgRecvSize is the largest gRPC message our client will receive.
|
||||||
|
@ -48,6 +48,9 @@ type macaroonPouch struct {
|
|||||||
// walletKitMac is the macaroon for the WalletKit sub-server.
|
// walletKitMac is the macaroon for the WalletKit sub-server.
|
||||||
walletKitMac serializedMacaroon
|
walletKitMac serializedMacaroon
|
||||||
|
|
||||||
|
// routerMac is the macaroon for the router sub-server.
|
||||||
|
routerMac serializedMacaroon
|
||||||
|
|
||||||
// adminMac is the primary admin macaroon for lnd.
|
// adminMac is the primary admin macaroon for lnd.
|
||||||
adminMac serializedMacaroon
|
adminMac serializedMacaroon
|
||||||
}
|
}
|
||||||
@ -87,6 +90,13 @@ func newMacaroonPouch(macaroonDir string) (*macaroonPouch, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.routerMac, err = newSerializedMacaroon(
|
||||||
|
filepath.Join(macaroonDir, defaultRouterMacaroonFilename),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
m.adminMac, err = newSerializedMacaroon(
|
m.adminMac, err = newSerializedMacaroon(
|
||||||
filepath.Join(macaroonDir, defaultAdminMacaroonFilename),
|
filepath.Join(macaroonDir, defaultAdminMacaroonFilename),
|
||||||
)
|
)
|
||||||
|
238
lndclient/router_client.go
Normal file
238
lndclient/router_client.go
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
package lndclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RouterClient exposes payment functionality.
|
||||||
|
type RouterClient interface {
|
||||||
|
// SendPayment attempts to route a payment to the final destination. The
|
||||||
|
// call returns a payment update stream and an error stream.
|
||||||
|
SendPayment(ctx context.Context, request SendPaymentRequest) (
|
||||||
|
chan PaymentStatus, chan error, error)
|
||||||
|
|
||||||
|
// TrackPayment picks up a previously started payment and returns a
|
||||||
|
// payment update stream and an error stream.
|
||||||
|
TrackPayment(ctx context.Context, hash lntypes.Hash) (
|
||||||
|
chan PaymentStatus, chan error, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaymentStatus describe the state of a payment.
|
||||||
|
type PaymentStatus struct {
|
||||||
|
State routerrpc.PaymentState
|
||||||
|
Preimage lntypes.Preimage
|
||||||
|
Fee lnwire.MilliSatoshi
|
||||||
|
Route *route.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendPaymentRequest defines the payment parameters for a new payment.
|
||||||
|
type SendPaymentRequest struct {
|
||||||
|
Invoice string
|
||||||
|
MaxFee btcutil.Amount
|
||||||
|
MaxCltv *int32
|
||||||
|
OutgoingChannel *uint64
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// routerClient is a wrapper around the generated routerrpc proxy.
|
||||||
|
type routerClient struct {
|
||||||
|
client routerrpc.RouterClient
|
||||||
|
routerKitMac serializedMacaroon
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRouterClient(conn *grpc.ClientConn,
|
||||||
|
routerKitMac serializedMacaroon) *routerClient {
|
||||||
|
|
||||||
|
return &routerClient{
|
||||||
|
client: routerrpc.NewRouterClient(conn),
|
||||||
|
routerKitMac: routerKitMac,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendPayment attempts to route a payment to the final destination. The call
|
||||||
|
// returns a payment update stream and an error stream.
|
||||||
|
func (r *routerClient) SendPayment(ctx context.Context,
|
||||||
|
request SendPaymentRequest) (chan PaymentStatus, chan error, error) {
|
||||||
|
|
||||||
|
rpcCtx := r.routerKitMac.WithMacaroonAuth(ctx)
|
||||||
|
rpcReq := &routerrpc.SendPaymentRequest{
|
||||||
|
FeeLimitSat: int64(request.MaxFee),
|
||||||
|
PaymentRequest: request.Invoice,
|
||||||
|
TimeoutSeconds: int32(request.Timeout.Seconds()),
|
||||||
|
}
|
||||||
|
if request.MaxCltv != nil {
|
||||||
|
rpcReq.CltvLimit = *request.MaxCltv
|
||||||
|
}
|
||||||
|
if request.OutgoingChannel != nil {
|
||||||
|
rpcReq.OutgoingChanId = *request.OutgoingChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := r.client.SendPayment(rpcCtx, rpcReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.trackPayment(ctx, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackPayment picks up a previously started payment and returns a payment
|
||||||
|
// update stream and an error stream.
|
||||||
|
func (r *routerClient) TrackPayment(ctx context.Context,
|
||||||
|
hash lntypes.Hash) (chan PaymentStatus, chan error, error) {
|
||||||
|
|
||||||
|
ctx = r.routerKitMac.WithMacaroonAuth(ctx)
|
||||||
|
stream, err := r.client.TrackPayment(
|
||||||
|
ctx, &routerrpc.TrackPaymentRequest{
|
||||||
|
PaymentHash: hash[:],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.trackPayment(ctx, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
// trackPayment takes an update stream from either a SendPayment or a
|
||||||
|
// TrackPayment rpc call and converts it into distinct update and error streams.
|
||||||
|
func (r *routerClient) trackPayment(ctx context.Context,
|
||||||
|
stream routerrpc.Router_TrackPaymentClient) (chan PaymentStatus,
|
||||||
|
chan error, error) {
|
||||||
|
|
||||||
|
statusChan := make(chan PaymentStatus)
|
||||||
|
errorChan := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
rpcStatus, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
switch status.Convert(err).Code() {
|
||||||
|
|
||||||
|
// NotFound is only expected as a response to
|
||||||
|
// TrackPayment.
|
||||||
|
case codes.NotFound:
|
||||||
|
err = channeldb.ErrPaymentNotInitiated
|
||||||
|
|
||||||
|
// NotFound is only expected as a response to
|
||||||
|
// SendPayment.
|
||||||
|
case codes.AlreadyExists:
|
||||||
|
err = channeldb.ErrAlreadyPaid
|
||||||
|
}
|
||||||
|
|
||||||
|
errorChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := unmarshallPaymentStatus(rpcStatus)
|
||||||
|
if err != nil {
|
||||||
|
errorChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case statusChan <- *status:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return statusChan, errorChan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshallPaymentStatus converts an rpc status update to the PaymentStatus
|
||||||
|
// type that is used throughout the application.
|
||||||
|
func unmarshallPaymentStatus(rpcStatus *routerrpc.PaymentStatus) (
|
||||||
|
*PaymentStatus, error) {
|
||||||
|
|
||||||
|
status := PaymentStatus{
|
||||||
|
State: rpcStatus.State,
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.State == routerrpc.PaymentState_SUCCEEDED {
|
||||||
|
preimage, err := lntypes.MakePreimage(
|
||||||
|
rpcStatus.Preimage,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
status.Preimage = preimage
|
||||||
|
|
||||||
|
status.Fee = lnwire.MilliSatoshi(
|
||||||
|
rpcStatus.Route.TotalFeesMsat,
|
||||||
|
)
|
||||||
|
|
||||||
|
if rpcStatus.Route != nil {
|
||||||
|
route, err := unmarshallRoute(rpcStatus.Route)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
status.Route = route
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshallRoute unmarshalls an rpc route.
|
||||||
|
func unmarshallRoute(rpcroute *lnrpc.Route) (
|
||||||
|
*route.Route, error) {
|
||||||
|
|
||||||
|
hops := make([]*route.Hop, len(rpcroute.Hops))
|
||||||
|
for i, hop := range rpcroute.Hops {
|
||||||
|
routeHop, err := unmarshallHop(hop)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hops[i] = routeHop
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(joostjager): Fetch self node from lnd.
|
||||||
|
selfNode := route.Vertex{}
|
||||||
|
|
||||||
|
route, err := route.NewRouteFromHops(
|
||||||
|
lnwire.MilliSatoshi(rpcroute.TotalAmtMsat),
|
||||||
|
rpcroute.TotalTimeLock,
|
||||||
|
selfNode,
|
||||||
|
hops,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return route, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshallKnownPubkeyHop unmarshalls an rpc hop.
|
||||||
|
func unmarshallHop(hop *lnrpc.Hop) (*route.Hop, error) {
|
||||||
|
pubKey, err := hex.DecodeString(hop.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot decode pubkey %s", hop.PubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubKeyBytes [33]byte
|
||||||
|
copy(pubKeyBytes[:], pubKey)
|
||||||
|
|
||||||
|
return &route.Hop{
|
||||||
|
OutgoingTimeLock: hop.Expiry,
|
||||||
|
AmtToForward: lnwire.MilliSatoshi(hop.AmtToForwardMsat),
|
||||||
|
PubKeyBytes: pubKeyBytes,
|
||||||
|
ChannelID: hop.ChanId,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -24,6 +24,7 @@ func NewMockLnd() *LndMockServices {
|
|||||||
chainNotifier := &mockChainNotifier{}
|
chainNotifier := &mockChainNotifier{}
|
||||||
signer := &mockSigner{}
|
signer := &mockSigner{}
|
||||||
invoices := &mockInvoices{}
|
invoices := &mockInvoices{}
|
||||||
|
router := &mockRouter{}
|
||||||
|
|
||||||
lnd := LndMockServices{
|
lnd := LndMockServices{
|
||||||
LndServices: lndclient.LndServices{
|
LndServices: lndclient.LndServices{
|
||||||
@ -32,6 +33,7 @@ func NewMockLnd() *LndMockServices {
|
|||||||
ChainNotifier: chainNotifier,
|
ChainNotifier: chainNotifier,
|
||||||
Signer: signer,
|
Signer: signer,
|
||||||
Invoices: invoices,
|
Invoices: invoices,
|
||||||
|
Router: router,
|
||||||
ChainParams: &chaincfg.TestNet3Params,
|
ChainParams: &chaincfg.TestNet3Params,
|
||||||
},
|
},
|
||||||
SendPaymentChannel: make(chan PaymentChannelMessage),
|
SendPaymentChannel: make(chan PaymentChannelMessage),
|
||||||
@ -44,6 +46,9 @@ func NewMockLnd() *LndMockServices {
|
|||||||
SettleInvoiceChannel: make(chan lntypes.Preimage),
|
SettleInvoiceChannel: make(chan lntypes.Preimage),
|
||||||
SingleInvoiceSubcribeChannel: make(chan *SingleInvoiceSubscription),
|
SingleInvoiceSubcribeChannel: make(chan *SingleInvoiceSubscription),
|
||||||
|
|
||||||
|
RouterSendPaymentChannel: make(chan RouterPaymentChannelMessage),
|
||||||
|
TrackPaymentChannel: make(chan TrackPaymentMessage),
|
||||||
|
|
||||||
FailInvoiceChannel: make(chan lntypes.Hash, 2),
|
FailInvoiceChannel: make(chan lntypes.Hash, 2),
|
||||||
epochChannel: make(chan int32),
|
epochChannel: make(chan int32),
|
||||||
Height: testStartingHeight,
|
Height: testStartingHeight,
|
||||||
@ -53,6 +58,7 @@ func NewMockLnd() *LndMockServices {
|
|||||||
chainNotifier.lnd = &lnd
|
chainNotifier.lnd = &lnd
|
||||||
walletKit.lnd = &lnd
|
walletKit.lnd = &lnd
|
||||||
invoices.lnd = &lnd
|
invoices.lnd = &lnd
|
||||||
|
router.lnd = &lnd
|
||||||
|
|
||||||
lnd.WaitForFinished = func() {
|
lnd.WaitForFinished = func() {
|
||||||
chainNotifier.WaitForFinished()
|
chainNotifier.WaitForFinished()
|
||||||
@ -69,6 +75,21 @@ type PaymentChannelMessage struct {
|
|||||||
Done chan lndclient.PaymentResult
|
Done chan lndclient.PaymentResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrackPaymentMessage is the data that passed through TrackPaymentChannel.
|
||||||
|
type TrackPaymentMessage struct {
|
||||||
|
Hash lntypes.Hash
|
||||||
|
|
||||||
|
Updates chan lndclient.PaymentStatus
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouterPaymentChannelMessage is the data that passed through RouterSendPaymentChannel.
|
||||||
|
type RouterPaymentChannelMessage struct {
|
||||||
|
lndclient.SendPaymentRequest
|
||||||
|
|
||||||
|
TrackPaymentMessage
|
||||||
|
}
|
||||||
|
|
||||||
// SingleInvoiceSubscription contains the single invoice subscribers
|
// SingleInvoiceSubscription contains the single invoice subscribers
|
||||||
type SingleInvoiceSubscription struct {
|
type SingleInvoiceSubscription struct {
|
||||||
Hash lntypes.Hash
|
Hash lntypes.Hash
|
||||||
@ -94,6 +115,9 @@ type LndMockServices struct {
|
|||||||
|
|
||||||
SingleInvoiceSubcribeChannel chan *SingleInvoiceSubscription
|
SingleInvoiceSubcribeChannel chan *SingleInvoiceSubscription
|
||||||
|
|
||||||
|
RouterSendPaymentChannel chan RouterPaymentChannelMessage
|
||||||
|
TrackPaymentChannel chan TrackPaymentMessage
|
||||||
|
|
||||||
Height int32
|
Height int32
|
||||||
|
|
||||||
WaitForFinished func()
|
WaitForFinished func()
|
||||||
|
43
test/router_mock.go
Normal file
43
test/router_mock.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/lightninglabs/loop/lndclient"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockRouter struct {
|
||||||
|
lnd *LndMockServices
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mockRouter) SendPayment(ctx context.Context,
|
||||||
|
request lndclient.SendPaymentRequest) (chan lndclient.PaymentStatus,
|
||||||
|
chan error, error) {
|
||||||
|
|
||||||
|
statusChan := make(chan lndclient.PaymentStatus)
|
||||||
|
errorChan := make(chan error)
|
||||||
|
|
||||||
|
r.lnd.RouterSendPaymentChannel <- RouterPaymentChannelMessage{
|
||||||
|
SendPaymentRequest: request,
|
||||||
|
TrackPaymentMessage: TrackPaymentMessage{
|
||||||
|
Updates: statusChan,
|
||||||
|
Errors: errorChan,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusChan, errorChan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mockRouter) TrackPayment(ctx context.Context,
|
||||||
|
hash lntypes.Hash) (chan lndclient.PaymentStatus, chan error, error) {
|
||||||
|
|
||||||
|
statusChan := make(chan lndclient.PaymentStatus)
|
||||||
|
errorChan := make(chan error)
|
||||||
|
r.lnd.TrackPaymentChannel <- TrackPaymentMessage{
|
||||||
|
Hash: hash,
|
||||||
|
Updates: statusChan,
|
||||||
|
Errors: errorChan,
|
||||||
|
}
|
||||||
|
|
||||||
|
return statusChan, errorChan, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user