mirror of https://github.com/lightninglabs/loop
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.pull/60/head
parent
dc2baf582f
commit
f559120565
@ -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
|
||||
}
|
@ -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