From dc2baf582f4789ab41eb536752f25699e111306b Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 6 Jun 2019 11:30:32 +0200 Subject: [PATCH 1/2] lndclient: use SubscribeSingleInvoiceRequest message --- go.mod | 6 +++--- go.sum | 29 +++++++++++++++++------------ lndclient/invoices_client.go | 2 +- lndclient/lightning_client.go | 7 ++++--- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 6e7679f..2c40f84 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,15 @@ module github.com/lightninglabs/loop require ( - github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c + github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f - github.com/btcsuite/btcutil v0.0.0-20190316010144-3ac1210f4b38 + github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d github.com/coreos/bbolt v1.3.2 github.com/fortytw2/leaktest v1.3.0 github.com/golang/protobuf v1.3.1 github.com/grpc-ecosystem/grpc-gateway v1.8.5 github.com/jessevdk/go-flags v1.4.0 - github.com/lightningnetwork/lnd v0.5.1-beta.0.20190322220528-6ad8be25e1aa + github.com/lightningnetwork/lnd v0.6.1-beta.0.20190605130338-880279b266e9 github.com/lightningnetwork/lnd/queue v1.0.1 github.com/urfave/cli v1.20.0 golang.org/x/net v0.0.0-20190313220215-9f648a60d977 diff --git a/go.sum b/go.sum index e4f594b..0ef6949 100644 --- a/go.sum +++ b/go.sum @@ -15,20 +15,24 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/btcsuite/btcd v0.0.0-20180823030728-d81d8877b8f3/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= +github.com/btcsuite/btcd v0.0.0-20181130015935-7d2daa5bfef2/go.mod h1:Jr9bmNVGZ7TH2Ux1QuP0ec+yGgh0gE9FIlkzQiI5bR0= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= -github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c h1:5N/b57wo2KfeHCGGdcXtOPsHqkPD+veLZhK/bMg2anQ= -github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= +github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50 h1:4i3KsuVA0o0KoBxAC5x+MY7RbteiMK1V7gf/G08NGIQ= +github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803 h1:j3AgPKKZtZStM2nyhrDSLSYgT7YHrZKdSkq1OYeLjvM= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v0.0.0-20190316010144-3ac1210f4b38 h1:GbQHMJ2u/geMPV1tbN7i7zARSoPAPuXWa44V0KYvJXU= -github.com/btcsuite/btcutil v0.0.0-20190316010144-3ac1210f4b38/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcwallet v0.0.0-20180904010540-284e2e0e696e33d5be388f7f3d9a26db703e0c06/go.mod h1:/d7QHZsfUAruXuBhyPITqoYOmJ+nq35qPsJjz/aSpCg= github.com/btcsuite/btcwallet v0.0.0-20190313032608-acf3b04b0273/go.mod h1:mkOYY8/psBiL5E+Wb0V7M0o+N7NXi2SZJz6+RKkncIc= github.com/btcsuite/btcwallet v0.0.0-20190319010515-89ab2044f962 h1:/6EtNbubaGgo10bFyYyE6S6yXfxtc3dxKpvLHxMt5rg= github.com/btcsuite/btcwallet v0.0.0-20190319010515-89ab2044f962/go.mod h1:qMi4jGpAO6YRsd81RYDG7o5pBIGqN9faCioJdagLu64= +github.com/btcsuite/btcwallet v0.0.0-20190524003533-2c05240dff28 h1:jn9dKmISiudJAihPQ2XRQenffPkhQeoBh3SCiZSfFXE= +github.com/btcsuite/btcwallet v0.0.0-20190524003533-2c05240dff28/go.mod h1:GlcKHrCBxtujd/6coLUvczN68EkaBezgyN+JnEGVDUY= github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 h1:kij1x2aL7VE6gtx8KMIt8PGPgI5GV9LgtHFG5KaEMPY= github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:QcFA8DZHtuIAdYKCq/BzELOaznRsCvwf4zTPmaYwaig= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= @@ -76,8 +80,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJ github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jackpal/gateway v1.0.4 h1:LS5EHkLuQ6jzaHwULi0vL+JO0mU/n4yUtK8oUjHHOlM= -github.com/jackpal/gateway v1.0.4/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= +github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc= github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -114,12 +117,12 @@ github.com/lightninglabs/gozmq v0.0.0-20180324010646-462a8a753885/go.mod h1:KUh1 github.com/lightninglabs/neutrino v0.0.0-20181017011010-4d6069299130/go.mod h1:KJq43Fu9ceitbJsSXMILcT4mGDNI/crKmPIkDOZXFyM= github.com/lightninglabs/neutrino v0.0.0-20190213031021-ae4583a89cfb/go.mod h1:g6cMQd+hfAU8pQTJAdjm6/EQREhupyd22f+CL0qYFOE= github.com/lightninglabs/neutrino v0.0.0-20190313035638-e1ad4c33fb18/go.mod h1:v6tz6jbuAubTrRpX8ke2KH9sJxml8KlPQTKgo9mAp1Q= -github.com/lightninglabs/neutrino v0.0.0-20190321023416-6dac90b98052 h1:YVi5x7xCumfIVtEejy3zBAej+NqY9EONy/fRim2+azQ= -github.com/lightninglabs/neutrino v0.0.0-20190321023416-6dac90b98052/go.mod h1:IcX2/lKnXpLkF4XzefhiJOPn5he2+kZyJE32dxX4F4E= -github.com/lightningnetwork/lightning-onion v0.0.0-20180605012408-ac4d9da8f1d6 h1:ONLGrYJVQdbtP6CE/ff1KNWZtygRGEh12RzonTiCzPs= -github.com/lightningnetwork/lightning-onion v0.0.0-20180605012408-ac4d9da8f1d6/go.mod h1:8EgEt4a/NUOVQd+3kk6n9aZCJ1Ssj96Pb6lCrci+6oc= -github.com/lightningnetwork/lnd v0.5.1-beta.0.20190322220528-6ad8be25e1aa h1:nYDuZ0vMAZ2DVIMmc/B8dNrmZ3YGxe7pwD/4DxG4rxQ= -github.com/lightningnetwork/lnd v0.5.1-beta.0.20190322220528-6ad8be25e1aa/go.mod h1:Xwk2vsL5uUgfMDQ7oHfltFZIYX5T74Htk0AA8NPzXlE= +github.com/lightninglabs/neutrino v0.0.0-20190521105038-03deaa76ddc2 h1:309vLN4dOi1CTqylpb8CsCWo1nT59rMrluZUF2228e0= +github.com/lightninglabs/neutrino v0.0.0-20190521105038-03deaa76ddc2/go.mod h1:/XWY/6/btfsknUpLPV8vvIZyhod61zYaUJiE8HxsFUs= +github.com/lightningnetwork/lightning-onion v0.0.0-20190430041606-751fb4dd8b72 h1:KgmypyQfJnEf2vhwboKCtTp4mHxIcLeXPBPWDbPuzFQ= +github.com/lightningnetwork/lightning-onion v0.0.0-20190430041606-751fb4dd8b72/go.mod h1:Sooe/CoCqa85JxqHV+IBR2HW+6t2Cv+36awSmoccswM= +github.com/lightningnetwork/lnd v0.6.1-beta.0.20190605130338-880279b266e9 h1:zuA8x6yj68cNRqVxX4l/jQdE+AhbK4csA6izqzrupsg= +github.com/lightningnetwork/lnd v0.6.1-beta.0.20190605130338-880279b266e9/go.mod h1:bzsQRx/1YhdXEk4TMWjKXUafgVrhcbxlDc9LKpY0VG8= github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU= @@ -147,6 +150,7 @@ go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -174,6 +178,7 @@ golang.org/x/sys v0.0.0-20180821140842-3b58ed4ad339/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/lndclient/invoices_client.go b/lndclient/invoices_client.go index 60663ba..61e6de1 100644 --- a/lndclient/invoices_client.go +++ b/lndclient/invoices_client.go @@ -83,7 +83,7 @@ func (s *invoicesClient) SubscribeSingleInvoice(ctx context.Context, invoiceStream, err := s.client.SubscribeSingleInvoice( s.invoiceMac.WithMacaroonAuth(ctx), - &lnrpc.PaymentHash{ + &invoicesrpc.SubscribeSingleInvoiceRequest{ RHash: hash[:], }, ) diff --git a/lndclient/lightning_client.go b/lndclient/lightning_client.go index 23d7456..c779ac8 100644 --- a/lndclient/lightning_client.go +++ b/lndclient/lightning_client.go @@ -10,11 +10,12 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcutil" - "github.com/lightningnetwork/lnd/htlcswitch" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/zpay32" + "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -67,12 +68,12 @@ var ( // PaymentResultAlreadyPaid is the string result returned by SendPayment // when the payment was already completed in a previous SendPayment // call. - PaymentResultAlreadyPaid = htlcswitch.ErrAlreadyPaid.Error() + PaymentResultAlreadyPaid = channeldb.ErrAlreadyPaid.Error() // PaymentResultInFlight is the string result returned by SendPayment // when the payment was initiated in a previous SendPayment call and // still in flight. - PaymentResultInFlight = htlcswitch.ErrPaymentInFlight.Error() + PaymentResultInFlight = channeldb.ErrPaymentInFlight.Error() paymentPollInterval = 3 * time.Second ) From f559120565885e1aeae4d3f88c363658c8779265 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 25 Mar 2019 10:31:03 +0100 Subject: [PATCH 2/2] 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. --- lndclient/lnd_services.go | 4 + lndclient/macaroon_pouch.go | 10 ++ lndclient/router_client.go | 238 ++++++++++++++++++++++++++++++++++++ test/lnd_services_mock.go | 24 ++++ test/router_mock.go | 43 +++++++ 5 files changed, 319 insertions(+) create mode 100644 lndclient/router_client.go create mode 100644 test/router_mock.go diff --git a/lndclient/lnd_services.go b/lndclient/lnd_services.go index 5108b10..a6372b1 100644 --- a/lndclient/lnd_services.go +++ b/lndclient/lnd_services.go @@ -24,6 +24,7 @@ type LndServices struct { ChainNotifier ChainNotifierClient Signer SignerClient Invoices InvoicesClient + Router RouterClient ChainParams *chaincfg.Params @@ -121,6 +122,7 @@ func NewLndServices(lndAddress, application, network, macaroonDir, signerClient := newSignerClient(conn, macaroons.signerMac) walletKitClient := newWalletKitClient(conn, macaroons.walletKitMac) invoicesClient := newInvoicesClient(conn, macaroons.invoiceMac) + routerClient := newRouterClient(conn, macaroons.routerMac) cleanup := func() { logger.Debugf("Closing lnd connection") @@ -145,6 +147,7 @@ func NewLndServices(lndAddress, application, network, macaroonDir, ChainNotifier: notifierClient, Signer: signerClient, Invoices: invoicesClient, + Router: routerClient, ChainParams: chainParams, macaroons: macaroons, }, @@ -178,6 +181,7 @@ var ( defaultInvoiceMacaroonFilename = "invoices.macaroon" defaultChainMacaroonFilename = "chainnotifier.macaroon" defaultWalletKitMacaroonFilename = "walletkit.macaroon" + defaultRouterMacaroonFilename = "router.macaroon" defaultSignerFilename = "signer.macaroon" // maxMsgRecvSize is the largest gRPC message our client will receive. diff --git a/lndclient/macaroon_pouch.go b/lndclient/macaroon_pouch.go index 350224b..2352de0 100644 --- a/lndclient/macaroon_pouch.go +++ b/lndclient/macaroon_pouch.go @@ -48,6 +48,9 @@ type macaroonPouch struct { // walletKitMac is the macaroon for the WalletKit sub-server. walletKitMac serializedMacaroon + // routerMac is the macaroon for the router sub-server. + routerMac serializedMacaroon + // adminMac is the primary admin macaroon for lnd. adminMac serializedMacaroon } @@ -87,6 +90,13 @@ func newMacaroonPouch(macaroonDir string) (*macaroonPouch, error) { return nil, err } + m.routerMac, err = newSerializedMacaroon( + filepath.Join(macaroonDir, defaultRouterMacaroonFilename), + ) + if err != nil { + return nil, err + } + m.adminMac, err = newSerializedMacaroon( filepath.Join(macaroonDir, defaultAdminMacaroonFilename), ) diff --git a/lndclient/router_client.go b/lndclient/router_client.go new file mode 100644 index 0000000..7a33168 --- /dev/null +++ b/lndclient/router_client.go @@ -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 +} diff --git a/test/lnd_services_mock.go b/test/lnd_services_mock.go index 6946853..b085c4f 100644 --- a/test/lnd_services_mock.go +++ b/test/lnd_services_mock.go @@ -24,6 +24,7 @@ func NewMockLnd() *LndMockServices { chainNotifier := &mockChainNotifier{} signer := &mockSigner{} invoices := &mockInvoices{} + router := &mockRouter{} lnd := LndMockServices{ LndServices: lndclient.LndServices{ @@ -32,6 +33,7 @@ func NewMockLnd() *LndMockServices { ChainNotifier: chainNotifier, Signer: signer, Invoices: invoices, + Router: router, ChainParams: &chaincfg.TestNet3Params, }, SendPaymentChannel: make(chan PaymentChannelMessage), @@ -44,6 +46,9 @@ func NewMockLnd() *LndMockServices { SettleInvoiceChannel: make(chan lntypes.Preimage), SingleInvoiceSubcribeChannel: make(chan *SingleInvoiceSubscription), + RouterSendPaymentChannel: make(chan RouterPaymentChannelMessage), + TrackPaymentChannel: make(chan TrackPaymentMessage), + FailInvoiceChannel: make(chan lntypes.Hash, 2), epochChannel: make(chan int32), Height: testStartingHeight, @@ -53,6 +58,7 @@ func NewMockLnd() *LndMockServices { chainNotifier.lnd = &lnd walletKit.lnd = &lnd invoices.lnd = &lnd + router.lnd = &lnd lnd.WaitForFinished = func() { chainNotifier.WaitForFinished() @@ -69,6 +75,21 @@ type PaymentChannelMessage struct { 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 type SingleInvoiceSubscription struct { Hash lntypes.Hash @@ -94,6 +115,9 @@ type LndMockServices struct { SingleInvoiceSubcribeChannel chan *SingleInvoiceSubscription + RouterSendPaymentChannel chan RouterPaymentChannelMessage + TrackPaymentChannel chan TrackPaymentMessage + Height int32 WaitForFinished func() diff --git a/test/router_mock.go b/test/router_mock.go new file mode 100644 index 0000000..706e0af --- /dev/null +++ b/test/router_mock.go @@ -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 +}