From c72639aa68728cdca76cc7b959f8346dd83a68a8 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 3 Feb 2022 14:53:12 +0100 Subject: [PATCH 01/40] feature: pluggable ln backend --- go.mod | 2 ++ go.sum | 49 ++++++++++++++++++++++++++++++++++++++++++ lib/service/service.go | 4 ++-- lnd/c-lightning.go | 37 +++++++++++++++++++++++++++++++ lnd/interface.go | 16 ++++++++++++++ 5 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 lnd/c-lightning.go create mode 100644 lnd/interface.go diff --git a/go.mod b/go.mod index 102b9102..0a0cf1fc 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/getAlby/lndhub.go go 1.17 + // +heroku goVersion go1.17 require ( @@ -51,6 +52,7 @@ require ( github.com/dustin/go-humanize v1.0.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fergusstrange/embedded-postgres v1.10.0 // indirect + github.com/fiatjaf/lightningd-gjson-rpc v1.4.1 github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/go-playground/locales v0.14.0 // indirect diff --git a/go.sum b/go.sum index 3beba425..3383dd4a 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,7 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ= github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -79,6 +80,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.20.1-beta.0.20200513120220-b470eee47728/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46/go.mod h1:Yktc19YNjh/Iz2//CX0vfRTS4IJKM/RKO5YZ9Fn+Pgo= github.com/btcsuite/btcd v0.21.0-beta.0.20201208033208-6bd4c64a54fa/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39 h1:o6qacOzpKubr16y0RrE2fBauRZN1rDZ1YsE26ixCgQ0= @@ -90,9 +93,11 @@ github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3L github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 h1:9aGy5p7oXRUB4MCTmWm0+jzuh79GpjPIfv1leA5POD4= github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/btcutil/psbt v1.0.2/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 h1:0xUNvvwJ7RjzBs4nCF+YrK28S5P/b4uHkpPxY1ovGY4= github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= +github.com/btcsuite/btcwallet v0.11.1-0.20200515224913-e0e62245ecbe/go.mod h1:9+AH3V5mcTtNXTKe+fe63fDLKGOwQbZqmvOVUef+JFE= github.com/btcsuite/btcwallet v0.13.0 h1:gtLWwueRm27KQiHJpycybv3uMdK1eo87JexfTfzvEhk= github.com/btcsuite/btcwallet v0.13.0/go.mod h1:iLN1lG1MW0eREm+SikmPO8AZPz5NglBTEK/ErqkjGpo= github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= @@ -104,13 +109,19 @@ github.com/btcsuite/btcwallet/wallet/txrules v1.1.0/go.mod h1:Zn9UTqpiTH+HOd5BLz github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8= github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= +github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk= +github.com/btcsuite/btcwallet/walletdb v1.2.0/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc= +github.com/btcsuite/btcwallet/walletdb v1.3.1/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc= github.com/btcsuite/btcwallet/walletdb v1.3.4/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec h1:zcAU3Ij8SmqaE+ITtS76fua2Niq7DRNp46sJRhi8PiI= github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= +github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY= +github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe/go.mod h1:OwC0W0HhUszbWdvJvH6xvgabKSJ0lXl11YbmmqF9YXQ= github.com/btcsuite/btcwallet/wtxmgr v1.3.0/go.mod h1:awQsh1n/0ZrEQ+JZgWvHeo153ubzEisf/FyNtwI0dDk= github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c h1:owWPexGfK4eSK4/Zy+XK2lET5qsnW7FRAc8OCOdD0Fg= github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c/go.mod h1:UM38ixX8VwJ9qey4umf//0H3ndn5kSImFZ46V54Nd5Q= +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= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= @@ -152,6 +163,7 @@ github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOi github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -185,6 +197,7 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -201,6 +214,11 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fergusstrange/embedded-postgres v1.10.0 h1:YnwF6xAQYmKLAXXrrRx4rHDLih47YJwVPvg8jeKfdNg= github.com/fergusstrange/embedded-postgres v1.10.0/go.mod h1:a008U8/Rws5FtIOTGYDYa7beVWsT3qVKyqExqYYjL+c= +github.com/fiatjaf/go-lnurl v1.0.0/go.mod h1:BqA8WXAOzntF7Z3EkVO7DfP4y5rhWUmJ/Bu9KBke+rs= +github.com/fiatjaf/lightningd-gjson-rpc v1.4.1 h1:J6TgNXO18Xc6udxj7/a6RhuusJR0lUiDlKJkJ/EdnNs= +github.com/fiatjaf/lightningd-gjson-rpc v1.4.1/go.mod h1:SQGA0qcY2qypaMXDQlE5V5+2MnLZzQ7NzfRsScliFeE= +github.com/fiatjaf/ln-decodepay v1.0.0 h1:1YUMjvLock+BicMNwoZ/OA3oG2ZYEaJ8AzdS6EGVMTQ= +github.com/fiatjaf/ln-decodepay v1.0.0/go.mod h1:/LWK+ZUa3i8MqbRjIMAiVQS2+NbhwKWlwib2n446cMQ= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= @@ -239,6 +257,7 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= @@ -326,6 +345,8 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -334,11 +355,13 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaW github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 h1:ajue7SzQMywqRjg2fK7dcpc0QhFGpTR2plWfV4EZWR4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0/go.mod h1:r1hZAcvfFXuYmcKyCJI9wlyOPIZUJl6FCB8Cpca/NLE= +github.com/guiguan/caster v0.0.0-20191104051807-3736c4464f38/go.mod h1:giU/iWwQIOg/ND1ecR8raoyROxojrXL9osppnuI7MRY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -514,14 +537,20 @@ github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= +github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg= +github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0= github.com/lightninglabs/neutrino v0.12.1/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= github.com/lightninglabs/neutrino v0.13.0 h1:j3PKWEJCwqwMn/qLASz2j0IuCF6AumS9DaM0i0pM/nY= github.com/lightninglabs/neutrino v0.13.0/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= +github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI= github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display/go.mod h1:2oKOBU042GKFHrdbgGiKax4xVrFiZu51lhacUZQ9MnE= +github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1 h1:h1BsjPzWea790mAXISoiT/qr0JRcixTCDNLmjsDThSw= github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= +github.com/lightningnetwork/lnd v0.10.1-beta/go.mod h1:F9er1DrpOHdQVQBqYqyBqIFyl6q16xgBM8yTioHj2Cg= github.com/lightningnetwork/lnd v0.14.1-beta h1:v9hOlJ1xYivYQ634Nt71up5QDh8mXVpYh4MXUbbWTRw= github.com/lightningnetwork/lnd v0.14.1-beta/go.mod h1:o7zDwjZXm/bPP48qjwsqnZvvITyQl+fUv6UVoV4o+J8= +github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo= github.com/lightningnetwork/lnd/cert v1.1.0/go.mod h1:3MWXVLLPI0Mg0XETm9fT4N9Vyy/8qQLmaM5589bEggM= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= github.com/lightningnetwork/lnd/clock v1.1.0 h1:/yfVAwtPmdx45aQBoXQImeY7sOIEr7IXlImRMBOZ7GQ= @@ -532,6 +561,7 @@ github.com/lightningnetwork/lnd/healthcheck v1.2.0/go.mod h1:WSz3lsUjErJQZ3gb+zW github.com/lightningnetwork/lnd/kvdb v1.2.1 h1:QevYLPh6bh1SLIvlUaPbrpX/YMKzSPBfmGVvn2i8IlU= github.com/lightningnetwork/lnd/kvdb v1.2.1/go.mod h1:x+IpsuDynubjokUofavLXroeGfS/WrqUXXTK6vN/gp4= github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= +github.com/lightningnetwork/lnd/queue v1.0.3/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg= github.com/lightningnetwork/lnd/queue v1.1.0 h1:YpCJjlIvVxN/R7ww2aNiY8ex7U2fucZDLJ67tI3HFx8= github.com/lightningnetwork/lnd/queue v1.1.0/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg= github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= @@ -540,6 +570,7 @@ github.com/lightningnetwork/lnd/ticker v1.1.0/go.mod h1:ubqbSVCn6RlE0LazXuBr7/Zi github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY= github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= +github.com/lucsky/cuid v1.0.2/go.mod h1:QaaJqckboimOmhRSJXSx/+IT+VTfxfPGSo/6mfgUfmE= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -570,6 +601,7 @@ github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE= github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= @@ -653,6 +685,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= @@ -707,7 +740,19 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= +github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= +github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI= +github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= +github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= +github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao= +github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -734,6 +779,7 @@ github.com/uptrace/bun/driver/sqliteshim v1.0.21 h1:9cBMi0TosIeomB+N35mXk1Dh8cSK github.com/uptrace/bun/driver/sqliteshim v1.0.21/go.mod h1:xXz8OSqmYnRtxbEktkTjpmEZy7YDroVnX0RVpEs+5zE= github.com/uptrace/bun/extra/bundebug v1.0.21 h1:ILYJLqhx97I9WM+GbCYnF1QjNN63gpI2fvoj04atf8s= github.com/uptrace/bun/extra/bundebug v1.0.21/go.mod h1:n/2QqdhgXrLHDOYqezHmJzBNKf/2NNjE5xMi2fZ38iY= +github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -770,6 +816,7 @@ github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxt github.com/ziflex/lecho/v3 v3.1.0 h1:65bSzSc0yw7EEhi44lMnkOI877ZzbE7tGDWfYCQXZwI= github.com/ziflex/lecho/v3 v3.1.0/go.mod h1:dwQ6xCAKmSBHhwZ6XmiAiDptD7iklVkW7xQYGUncX0Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= @@ -1050,6 +1097,7 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1219,6 +1267,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/antage/eventsource.v1 v1.0.0-20150318155416-803f4c5af225/go.mod h1:SiXNRpUllqhl+GIw2V/BtKI7BUlz+uxov9vBFtXHqh8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/lib/service/service.go b/lib/service/service.go index 4af9a3d4..33dd0fe9 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -8,8 +8,8 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/getAlby/lndhub.go/db/models" "github.com/getAlby/lndhub.go/lib/tokens" + "github.com/getAlby/lndhub.go/lnd" "github.com/labstack/gommon/random" - "github.com/lightningnetwork/lnd/lnrpc" "github.com/uptrace/bun" "github.com/ziflex/lecho/v3" "golang.org/x/crypto/bcrypt" @@ -20,7 +20,7 @@ const alphaNumBytes = random.Alphanumeric type LndhubService struct { Config *Config DB *bun.DB - LndClient lnrpc.LightningClient + LndClient lnd.LightningClientWrapper Logger *lecho.Logger IdentityPubkey *btcec.PublicKey } diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go new file mode 100644 index 00000000..aea725e9 --- /dev/null +++ b/lnd/c-lightning.go @@ -0,0 +1,37 @@ +package lnd + +import ( + "context" + + cln "github.com/fiatjaf/lightningd-gjson-rpc" + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/grpc" +) + +type CLNClient struct { + client *cln.Client +} + +func (cl *CLNClient) ListChannels(ctx context.Context, req *lnrpc.ListChannelsRequest, options ...grpc.CallOption) (*lnrpc.ListChannelsResponse, error) { + panic("not implemented") // TODO: Implement +} + +func (cl *CLNClient) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest, options ...grpc.CallOption) (*lnrpc.SendResponse, error) { + panic("not implemented") // TODO: Implement +} + +func (cl *CLNClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) { + panic("not implemented") // TODO: Implement +} + +// Todo here: make CLNClient implement the interface (Recv()) +// This method will read from a channel or block +// The handler function publishes on the channel on a received invoice +// set the client's invoice index to the one from req +func (cl *CLNClient) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (lnrpc.Lightning_SubscribeInvoicesClient, error) { + panic("not implemented") // TODO: Implement +} + +func (cl *CLNClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) { + panic("not implemented") // TODO: Implement +} diff --git a/lnd/interface.go b/lnd/interface.go new file mode 100644 index 00000000..d5dbf6b2 --- /dev/null +++ b/lnd/interface.go @@ -0,0 +1,16 @@ +package lnd + +import ( + "context" + + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/grpc" +) + +type LightningClientWrapper interface { + ListChannels(ctx context.Context, req *lnrpc.ListChannelsRequest, options ...grpc.CallOption) (*lnrpc.ListChannelsResponse, error) + SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest, options ...grpc.CallOption) (*lnrpc.SendResponse, error) + AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) + SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (lnrpc.Lightning_SubscribeInvoicesClient, error) + GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) +} From 40e41ab8797a4c60353cef2a106b49acd10f6ab0 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 3 Feb 2022 17:22:00 +0100 Subject: [PATCH 02/40] implement more methods --- go.mod | 144 +-------------------------------------------- lnd/c-lightning.go | 129 ++++++++++++++++++++++++++++++++++++++-- main.go | 18 ++++-- 3 files changed, 140 insertions(+), 151 deletions(-) diff --git a/go.mod b/go.mod index 0a0cf1fc..05e5b576 100644 --- a/go.mod +++ b/go.mod @@ -28,150 +28,10 @@ require ( ) require ( - github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect - github.com/aead/siphash v1.0.1 // indirect - github.com/andybalholm/brotli v1.0.3 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect - github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // indirect - github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 // indirect - github.com/btcsuite/btcwallet v0.13.0 // indirect - github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 // indirect - github.com/btcsuite/btcwallet/wallet/txrules v1.1.0 // indirect - github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect - github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec // indirect - github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c // indirect - github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect - github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.3.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/lru v1.0.0 // indirect - github.com/dsnet/compress v0.0.1 // indirect - github.com/dustin/go-humanize v1.0.0 // indirect - github.com/fatih/color v1.13.0 // indirect - github.com/fergusstrange/embedded-postgres v1.10.0 // indirect github.com/fiatjaf/lightningd-gjson-rpc v1.4.1 - github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect - github.com/go-errors/errors v1.0.1 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/gorilla/websocket v1.4.2 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.10.0 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.1.1 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.8.1 // indirect - github.com/jackc/pgx/v4 v4.13.0 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jonboulle/clockwork v0.2.2 // indirect - github.com/jrick/logrotate v1.0.0 // indirect - github.com/json-iterator/go v1.1.11 // indirect - github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/kkdai/bstream v1.0.0 // indirect - github.com/klauspost/compress v1.13.6 // indirect - github.com/klauspost/pgzip v1.2.5 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/lib/pq v1.10.3 // indirect - github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect - github.com/lightninglabs/neutrino v0.13.0 // indirect - github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1 // indirect - github.com/lightningnetwork/lnd/clock v1.1.0 // indirect - github.com/lightningnetwork/lnd/healthcheck v1.2.0 // indirect - github.com/lightningnetwork/lnd/kvdb v1.2.1 // indirect - github.com/lightningnetwork/lnd/queue v1.1.0 // indirect - github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect - github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect - github.com/mattn/go-sqlite3 v1.14.10 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mholt/archiver/v3 v3.5.0 // indirect - github.com/miekg/dns v1.1.43 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/nwaples/rardecode v1.1.2 // indirect - github.com/pierrec/lz4/v4 v4.1.8 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.26.0 // indirect - github.com/prometheus/procfs v0.6.0 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect - github.com/rogpeppe/fastuuid v1.2.0 // indirect - github.com/rs/zerolog v1.26.0 // indirect - github.com/sirupsen/logrus v1.7.0 // indirect - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect - github.com/soheilhy/cmux v0.1.5 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect - github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect - github.com/ulikunitz/xz v0.5.10 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.1 // indirect - github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - go.etcd.io/bbolt v1.3.6 // indirect - go.etcd.io/etcd/api/v3 v3.5.0 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect - go.etcd.io/etcd/client/v2 v2.305.0 // indirect - go.etcd.io/etcd/client/v3 v3.5.0 // indirect - go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect - go.etcd.io/etcd/raft/v3 v3.5.0 // indirect - go.etcd.io/etcd/server/v3 v3.5.0 // indirect - go.opentelemetry.io/contrib v0.20.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect - go.opentelemetry.io/otel v0.20.0 // indirect - go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect - go.opentelemetry.io/otel/metric v0.20.0 // indirect - go.opentelemetry.io/otel/sdk v0.20.0 // indirect - go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect - go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect - go.opentelemetry.io/otel/trace v0.20.0 // indirect - go.opentelemetry.io/proto/otlp v0.7.0 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.17.0 // indirect - golang.org/x/mod v0.5.1 // indirect + github.com/gofrs/uuid v4.0.0+incompatible + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect - golang.org/x/tools v0.1.8 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0 // indirect - google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/errgo.v1 v1.0.1 // indirect - gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - lukechampine.com/uint128 v1.1.1 // indirect - mellium.im/sasl v0.2.1 // indirect - modernc.org/cc/v3 v3.35.22 // indirect - modernc.org/ccgo/v3 v3.14.0 // indirect - modernc.org/libc v1.13.2 // indirect - modernc.org/mathutil v1.4.1 // indirect - modernc.org/memory v1.0.5 // indirect - modernc.org/opt v0.1.1 // indirect - modernc.org/sqlite v1.14.3 // indirect - modernc.org/strutil v1.1.1 // indirect - modernc.org/token v1.0.0 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index aea725e9..e2b80277 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -2,26 +2,117 @@ package lnd import ( "context" + "fmt" cln "github.com/fiatjaf/lightningd-gjson-rpc" + "github.com/gofrs/uuid" "github.com/lightningnetwork/lnd/lnrpc" "google.golang.org/grpc" ) +const ( + MSAT_PER_SAT = 1000 +) + type CLNClient struct { client *cln.Client } +type CLNClientOptions struct { + SparkUrl string + SparkToken string +} + +func NewCLNClient(options CLNClientOptions) (*CLNClient, error) { + return &CLNClient{ + client: &cln.Client{ + //PaymentHandler: func(gjson.Result) { + //}, + //CallTimeout: 0, + //Path: "", + //LightningDir: "", + SparkURL: options.SparkUrl, + SparkToken: options.SparkToken, + }, + }, nil +} + func (cl *CLNClient) ListChannels(ctx context.Context, req *lnrpc.ListChannelsRequest, options ...grpc.CallOption) (*lnrpc.ListChannelsResponse, error) { - panic("not implemented") // TODO: Implement + result, err := cl.client.Call("listpeers") + if err != nil { + return nil, err + } + channels := []*lnrpc.Channel{} + for _, peer := range result.Get("peers").Array() { + for _, ch := range peer.Get("channels").Array() { + //todo fill in missing fields + channels = append(channels, &lnrpc.Channel{ + Active: ch.Get("state").String() == "CHANNELD_NORMAL", + RemotePubkey: peer.Get("id").String(), + ChannelPoint: "", + ChanId: 0, + Capacity: ch.Get("msatoshi_total").Int() / MSAT_PER_SAT, + LocalBalance: ch.Get("msatoshi_to_us").Int() / MSAT_PER_SAT, + RemoteBalance: ch.Get("receivable_msatoshi").Int() / MSAT_PER_SAT, + CommitFee: 0, + CommitWeight: 0, + FeePerKw: 0, + UnsettledBalance: 0, + TotalSatoshisSent: 0, + TotalSatoshisReceived: 0, + NumUpdates: 0, + PendingHtlcs: []*lnrpc.HTLC{}, + CsvDelay: 0, + Private: false, + Initiator: false, + ChanStatusFlags: "", + LocalChanReserveSat: 0, + RemoteChanReserveSat: 0, + StaticRemoteKey: false, + CommitmentType: 0, + Lifetime: 0, + Uptime: 0, + CloseAddress: "", + PushAmountSat: 0, + ThawHeight: 0, + LocalConstraints: &lnrpc.ChannelConstraints{}, + RemoteConstraints: &lnrpc.ChannelConstraints{}, + }) + } + } + return &lnrpc.ListChannelsResponse{ + Channels: channels, + }, nil } func (cl *CLNClient) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest, options ...grpc.CallOption) (*lnrpc.SendResponse, error) { - panic("not implemented") // TODO: Implement + //todo add other options + result, err := cl.client.Call("pay", req.PaymentRequest) + if err != nil { + return nil, err + } + //todo failure modes + return &lnrpc.SendResponse{ + PaymentError: "", + PaymentPreimage: []byte(result.Get("payment_preimage").String()), + PaymentHash: []byte(result.Get("payment_hash").String()), + }, nil } func (cl *CLNClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) { - panic("not implemented") // TODO: Implement + uuid, err := uuid.NewV4() + if err != nil { + return nil, err + } + mSatAmt := MSAT_PER_SAT * req.Value + res, err := cl.client.Call("invoicewithdescriptionhash", mSatAmt, uuid.String(), req.DescriptionHash) + if err != nil { + return nil, err + } + return &lnrpc.AddInvoiceResponse{ + RHash: []byte(res.Get("payment_hash").String()), + PaymentRequest: res.Get("bolt11").String(), + }, nil } // Todo here: make CLNClient implement the interface (Recv()) @@ -33,5 +124,35 @@ func (cl *CLNClient) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSu } func (cl *CLNClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) { - panic("not implemented") // TODO: Implement + result, err := cl.client.Call("getinfo") + if err != nil { + return nil, err + } + uris := []string{} + for _, addr := range result.Get("address").Array() { + uris = append(uris, fmt.Sprintf("%s@%s:%s", result.Get("id").String(), addr.Get("address").String(), addr.Get("port").String())) + } + + return &lnrpc.GetInfoResponse{ + Version: result.Get("version").String(), + IdentityPubkey: result.Get("id").String(), + Alias: result.Get("alias").String(), + Color: result.Get("color").String(), + NumPendingChannels: uint32(result.Get("num_pending_channels").Int()), + NumActiveChannels: uint32(result.Get("num_active_channels").Int()), + NumInactiveChannels: uint32(result.Get("num_inactive_channels").Int()), + NumPeers: uint32(result.Get("num_peers").Int()), + BlockHeight: uint32(result.Get("blockheight").Int()), + // workaround + SyncedToChain: true, + SyncedToGraph: true, + Testnet: false, + Chains: []*lnrpc.Chain{ + { + Chain: "bitcoin", + Network: result.Get("network").String(), + }, + }, + Uris: uris, + }, nil } diff --git a/main.go b/main.go index af659fc9..fa82c770 100644 --- a/main.go +++ b/main.go @@ -101,10 +101,17 @@ func main() { } // Init new LND client - lndClient, err := lnd.NewLNDclient(lnd.LNDoptions{ - Address: c.LNDAddress, - MacaroonHex: c.LNDMacaroonHex, - CertHex: c.LNDCertHex, + //lndClient, err := lnd.NewLNDclient(lnd.LNDoptions{ + // Address: c.LNDAddress, + // MacaroonHex: c.LNDMacaroonHex, + // CertHex: c.LNDCertHex, + //}) + + //Init new CLN client + //re-use other config to not make things overcomplicated + lndClient, err := lnd.NewCLNClient(lnd.CLNClientOptions{ + SparkUrl: c.LNDAddress, + SparkToken: c.LNDMacaroonHex, }) if err != nil { e.Logger.Fatalf("Error initializing the LND connection: %v", err) @@ -159,7 +166,8 @@ func main() { e.GET("/static/img/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) // Subscribe to LND invoice updates in the background - go svc.InvoiceUpdateSubscription(context.Background()) + // CLN: todo: re-write logic + //go svc.InvoiceUpdateSubscription(context.Background()) // Start server go func() { From ad13bb22fca9c96f6f32084ff8a26914cb8716c8 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 3 Feb 2022 17:42:35 +0100 Subject: [PATCH 03/40] more work on invoice subscription --- go.mod | 1 + lib/service/invoicesubscription.go | 3 ++- lnd/c-lightning.go | 25 ++++++++++++++++++++----- lnd/interface.go | 6 +++++- main.go | 2 +- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 05e5b576..eddae364 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/fiatjaf/lightningd-gjson-rpc v1.4.1 github.com/gofrs/uuid v4.0.0+incompatible github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + github.com/tidwall/gjson v1.6.0 golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0 // indirect diff --git a/lib/service/invoicesubscription.go b/lib/service/invoicesubscription.go index ec26c4bb..c704c489 100644 --- a/lib/service/invoicesubscription.go +++ b/lib/service/invoicesubscription.go @@ -8,6 +8,7 @@ import ( "time" "github.com/getAlby/lndhub.go/db/models" + "github.com/getAlby/lndhub.go/lnd" "github.com/getsentry/sentry-go" "github.com/lightningnetwork/lnd/lnrpc" "github.com/uptrace/bun" @@ -95,7 +96,7 @@ func (svc *LndhubService) ProcessInvoiceUpdate(ctx context.Context, rawInvoice * return nil } -func (svc *LndhubService) ConnectInvoiceSubscription(ctx context.Context) (lnrpc.Lightning_SubscribeInvoicesClient, error) { +func (svc *LndhubService) ConnectInvoiceSubscription(ctx context.Context) (lnd.SubscribeInvoicesWrapper, error) { var invoice models.Invoice invoiceSubscriptionOptions := lnrpc.InvoiceSubscription{} // Find the oldest NOT settled invoice with an add_index diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index e2b80277..df2156e3 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -7,6 +7,7 @@ import ( cln "github.com/fiatjaf/lightningd-gjson-rpc" "github.com/gofrs/uuid" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/tidwall/gjson" "google.golang.org/grpc" ) @@ -15,19 +16,24 @@ const ( ) type CLNClient struct { - client *cln.Client + client *cln.Client + handler *InvoiceHandler } +type InvoiceHandler struct { + invoiceChan chan (*lnrpc.Invoice) +} type CLNClientOptions struct { SparkUrl string SparkToken string } func NewCLNClient(options CLNClientOptions) (*CLNClient, error) { + handler := &InvoiceHandler{} return &CLNClient{ + handler: handler, client: &cln.Client{ - //PaymentHandler: func(gjson.Result) { - //}, + PaymentHandler: handler.Handle, //CallTimeout: 0, //Path: "", //LightningDir: "", @@ -37,6 +43,15 @@ func NewCLNClient(options CLNClientOptions) (*CLNClient, error) { }, nil } +func (cln *CLNClient) Recv() (invoice *lnrpc.Invoice, err error) { + return nil, nil +} + +func (handler *InvoiceHandler) Handle(gjson.Result) { + invoice := &lnrpc.Invoice{} + handler.invoiceChan <- invoice +} + func (cl *CLNClient) ListChannels(ctx context.Context, req *lnrpc.ListChannelsRequest, options ...grpc.CallOption) (*lnrpc.ListChannelsResponse, error) { result, err := cl.client.Call("listpeers") if err != nil { @@ -119,8 +134,8 @@ func (cl *CLNClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options // This method will read from a channel or block // The handler function publishes on the channel on a received invoice // set the client's invoice index to the one from req -func (cl *CLNClient) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (lnrpc.Lightning_SubscribeInvoicesClient, error) { - panic("not implemented") // TODO: Implement +func (cl *CLNClient) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) { + return cl, nil } func (cl *CLNClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) { diff --git a/lnd/interface.go b/lnd/interface.go index d5dbf6b2..c5a39a7d 100644 --- a/lnd/interface.go +++ b/lnd/interface.go @@ -11,6 +11,10 @@ type LightningClientWrapper interface { ListChannels(ctx context.Context, req *lnrpc.ListChannelsRequest, options ...grpc.CallOption) (*lnrpc.ListChannelsResponse, error) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest, options ...grpc.CallOption) (*lnrpc.SendResponse, error) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) - SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (lnrpc.Lightning_SubscribeInvoicesClient, error) + SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) } + +type SubscribeInvoicesWrapper interface { + Recv() (*lnrpc.Invoice, error) +} diff --git a/main.go b/main.go index fa82c770..e2cc4299 100644 --- a/main.go +++ b/main.go @@ -167,7 +167,7 @@ func main() { // Subscribe to LND invoice updates in the background // CLN: todo: re-write logic - //go svc.InvoiceUpdateSubscription(context.Background()) + go svc.InvoiceUpdateSubscription(context.Background()) // Start server go func() { From 856c4b5a1d7e1452fcbc8018d82dd4eac633b5bf Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 4 Feb 2022 11:00:26 +0100 Subject: [PATCH 04/40] add invoice handling --- lnd/c-lightning.go | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index df2156e3..d71de380 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -43,12 +43,42 @@ func NewCLNClient(options CLNClientOptions) (*CLNClient, error) { }, nil } +//todo handle errors? func (cln *CLNClient) Recv() (invoice *lnrpc.Invoice, err error) { - return nil, nil + return <-cln.handler.invoiceChan, nil } -func (handler *InvoiceHandler) Handle(gjson.Result) { - invoice := &lnrpc.Invoice{} +func (handler *InvoiceHandler) Handle(res gjson.Result) { + //todo missing or wrong fields + invoice := &lnrpc.Invoice{ + Memo: res.Get("description").String(), + RPreimage: []byte(res.Get("payment_preimage").String()), + RHash: []byte(res.Get("payment_hash").String()), + Value: res.Get("amount_msat").Int() / MSAT_PER_SAT, + ValueMsat: res.Get("amount_msat").Int(), + Settled: true, + CreationDate: 0, + SettleDate: res.Get("paid_at").Int(), + PaymentRequest: res.Get("bolt11").String(), + DescriptionHash: []byte{}, + Expiry: 0, + FallbackAddr: "", + CltvExpiry: 0, + RouteHints: []*lnrpc.RouteHint{}, + Private: false, + AddIndex: 0, + SettleIndex: 0, + AmtPaid: res.Get("amount_msat").Int() / MSAT_PER_SAT, + AmtPaidSat: res.Get("amount_msat").Int() / MSAT_PER_SAT, + AmtPaidMsat: res.Get("amount_msat").Int(), + State: lnrpc.Invoice_SETTLED, + Htlcs: []*lnrpc.InvoiceHTLC{}, + Features: map[uint32]*lnrpc.Feature{}, + IsKeysend: false, + PaymentAddr: []byte{}, + IsAmp: false, + AmpInvoiceState: map[string]*lnrpc.AMPInvoiceState{}, + } handler.invoiceChan <- invoice } From b9b5be9fd83cd4e7ad15aaa31e52eda8ad67a408 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 4 Feb 2022 11:01:37 +0100 Subject: [PATCH 05/40] init channel --- lnd/c-lightning.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index d71de380..45919ada 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -29,7 +29,9 @@ type CLNClientOptions struct { } func NewCLNClient(options CLNClientOptions) (*CLNClient, error) { - handler := &InvoiceHandler{} + handler := &InvoiceHandler{ + invoiceChan: make(chan *lnrpc.Invoice), + } return &CLNClient{ handler: handler, client: &cln.Client{ From 8d37b5f02fd80ab68b169ddb32907934745b8bfa Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 4 Feb 2022 11:27:55 +0100 Subject: [PATCH 06/40] send + receive working on regtest --- lnd/c-lightning.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index 45919ada..869d04ec 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -3,6 +3,7 @@ package lnd import ( "context" "fmt" + "reflect" cln "github.com/fiatjaf/lightningd-gjson-rpc" "github.com/gofrs/uuid" @@ -143,6 +144,10 @@ func (cl *CLNClient) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest PaymentError: "", PaymentPreimage: []byte(result.Get("payment_preimage").String()), PaymentHash: []byte(result.Get("payment_hash").String()), + PaymentRoute: &lnrpc.Route{ + TotalFees: result.Get("amount_sent_msat").Int() - result.Get("amount_msat").Int(), + TotalAmt: result.Get("amount_sent_msat").Int(), + }, }, nil } @@ -152,7 +157,13 @@ func (cl *CLNClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options return nil, err } mSatAmt := MSAT_PER_SAT * req.Value - res, err := cl.client.Call("invoicewithdescriptionhash", mSatAmt, uuid.String(), req.DescriptionHash) + methodToCall := "invoice" + arg := req.Memo + if !reflect.DeepEqual(req.DescriptionHash, []byte("")) { + methodToCall = "invoicewithdescriptionhash" + arg = string(req.DescriptionHash) + } + res, err := cl.client.Call(methodToCall, mSatAmt, uuid.String(), arg) if err != nil { return nil, err } @@ -167,6 +178,8 @@ func (cl *CLNClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options // The handler function publishes on the channel on a received invoice // set the client's invoice index to the one from req func (cl *CLNClient) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) { + cl.client.LastInvoiceIndex = int(req.AddIndex) + cl.client.ListenForInvoices() return cl, nil } From b172d7c6eba5dde9662ec37ac1a69f0591f2b052 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 4 Feb 2022 12:02:29 +0100 Subject: [PATCH 07/40] add pay index to updated invoice --- lnd/c-lightning.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index 869d04ec..579be3f3 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -69,7 +69,7 @@ func (handler *InvoiceHandler) Handle(res gjson.Result) { CltvExpiry: 0, RouteHints: []*lnrpc.RouteHint{}, Private: false, - AddIndex: 0, + AddIndex: res.Get("pay_index").Uint(), SettleIndex: 0, AmtPaid: res.Get("amount_msat").Int() / MSAT_PER_SAT, AmtPaidSat: res.Get("amount_msat").Int() / MSAT_PER_SAT, From 514d33a0d112342330701bbf988740c5257c8f04 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 4 Feb 2022 13:31:16 +0100 Subject: [PATCH 08/40] feature: bolt12 --- controllers/bolt12.ctrl.go | 28 ++++++++++++++++++++++++++++ lnd/c-lightning.go | 21 +++++++++++++++++++++ lnd/interface.go | 11 +++++++++++ main.go | 1 + 4 files changed, 61 insertions(+) create mode 100644 controllers/bolt12.ctrl.go diff --git a/controllers/bolt12.ctrl.go b/controllers/bolt12.ctrl.go new file mode 100644 index 00000000..630d11e8 --- /dev/null +++ b/controllers/bolt12.ctrl.go @@ -0,0 +1,28 @@ +package controllers + +import ( + "context" + "net/http" + + "github.com/getAlby/lndhub.go/lib/service" + "github.com/labstack/echo/v4" +) + +// Bolt12Controller : Bolt12Controller struct +type Bolt12Controller struct { + svc *service.LndhubService +} + +func NewBolt12Controller(svc *service.LndhubService) *Bolt12Controller { + return &Bolt12Controller{svc: svc} +} + +// Decode : Decode handler +func (controller *Bolt12Controller) Decode(c echo.Context) error { + offer := c.Param("offer") + decoded, err := controller.svc.LndClient.DecodeOffer(context.TODO(), offer) + if err != nil { + return err + } + return c.JSON(http.StatusOK, decoded) +} diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index 579be3f3..da85108c 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -216,3 +216,24 @@ func (cl *CLNClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, opt Uris: uris, }, nil } + +func (cl *CLNClient) DecodeOffer(ctx context.Context, offer string) (*Offer, error) { + result, err := cl.client.Call("decode", offer) + if err != nil { + return nil, err + } + chains := []string{} + for _, ch := range result.Get("chains").Array() { + chains = append(chains, ch.String()) + } + return &Offer{ + Type: result.Get("type").String(), + OfferID: result.Get("offer_id").String(), + Description: result.Get("description").String(), + NodeID: result.Get("node_id").String(), + Signature: result.Get("signature").String(), + Vendor: result.Get("vendor").String(), + Chains: chains, + Valid: result.Get("valid").Bool(), + }, nil +} diff --git a/lnd/interface.go b/lnd/interface.go index c5a39a7d..a76828a5 100644 --- a/lnd/interface.go +++ b/lnd/interface.go @@ -13,8 +13,19 @@ type LightningClientWrapper interface { AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) + DecodeOffer(ctx context.Context, offer string) (*Offer, error) } type SubscribeInvoicesWrapper interface { Recv() (*lnrpc.Invoice, error) } +type Offer struct { + Type string `json:"type"` + OfferID string `json:"offer_id"` + Chains []string `json:"chains"` + Description string `json:"description"` + NodeID string `json:"node_id"` + Signature string `json:"signature"` + Vendor string `json:"vendor"` + Valid bool `json:"valid"` +} diff --git a/main.go b/main.go index e2cc4299..c1c65381 100644 --- a/main.go +++ b/main.go @@ -151,6 +151,7 @@ func main() { secured.GET("/checkpayment/:payment_hash", controllers.NewCheckPaymentController(svc).CheckPayment) secured.GET("/balance", controllers.NewBalanceController(svc).Balance) secured.GET("/getinfo", controllers.NewGetInfoController(svc).GetInfo) + secured.GET("/bolt12/decode/:offer", controllers.NewBolt12Controller(svc).Decode) // These endpoints are currently not supported and we return a blank response for backwards compatibility blankController := controllers.NewBlankController(svc) From 73aa3f4e97a6a2ab45999b57cc2ede5f9e32c5e6 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Sun, 6 Feb 2022 15:20:36 +0100 Subject: [PATCH 09/40] set up scaffolding --- controllers/bolt12.ctrl.go | 52 ++++++++++++++++++++++++++++++++++++++ lib/service/bolt12.go | 16 ++++++++++++ lnd/c-lightning.go | 4 +-- lnd/interface.go | 4 +++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 lib/service/bolt12.go diff --git a/controllers/bolt12.ctrl.go b/controllers/bolt12.ctrl.go index 630d11e8..7120a9a4 100644 --- a/controllers/bolt12.ctrl.go +++ b/controllers/bolt12.ctrl.go @@ -4,6 +4,7 @@ import ( "context" "net/http" + "github.com/getAlby/lndhub.go/lib/responses" "github.com/getAlby/lndhub.go/lib/service" "github.com/labstack/echo/v4" ) @@ -12,6 +13,11 @@ import ( type Bolt12Controller struct { svc *service.LndhubService } +type FetchInvoiceRequestBody struct { + Amount int64 `json:"amt"` // amount in Satoshi + Memo string `json:"memo"` + Offer string `json:"offer"` +} func NewBolt12Controller(svc *service.LndhubService) *Bolt12Controller { return &Bolt12Controller{svc: svc} @@ -26,3 +32,49 @@ func (controller *Bolt12Controller) Decode(c echo.Context) error { } return c.JSON(http.StatusOK, decoded) } + +// FetchInvoice: fetches an invoice from a bolt12 offer for a certain amount +func (controller *Bolt12Controller) FetchInvoice(c echo.Context) error { + var body FetchInvoiceRequestBody + + if err := c.Bind(&body); err != nil { + c.Logger().Errorf("Failed to load fetchinvoice request body: %v", err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + if err := c.Validate(&body); err != nil { + c.Logger().Errorf("Invalid fetchinvoice request body: %v", err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + invoice, err := controller.svc.FetchBolt12Invoice(context.TODO(), body.Offer, body.Memo, body.Amount) + if err != nil { + return err + } + return c.JSON(http.StatusOK, invoice) +} + +// PayOffer: fetches an invoice from a bolt12 offer for a certain amount, and pays it +func (controller *Bolt12Controller) PayOffer(c echo.Context) error { + var body FetchInvoiceRequestBody + + if err := c.Bind(&body); err != nil { + c.Logger().Errorf("Failed to load fetchinvoice request body: %v", err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + if err := c.Validate(&body); err != nil { + c.Logger().Errorf("Invalid fetchinvoice request body: %v", err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + invoice, err := controller.svc.FetchBolt12Invoice(context.TODO(), body.Offer, body.Memo, body.Amount) + if err != nil { + return err + } + result, err := controller.svc.PayBolt12Invoice(context.TODO(), invoice.Invoice) + if err != nil { + return err + } + return c.JSON(http.StatusOK, result) +} diff --git a/lib/service/bolt12.go b/lib/service/bolt12.go new file mode 100644 index 00000000..94707315 --- /dev/null +++ b/lib/service/bolt12.go @@ -0,0 +1,16 @@ +package service + +import ( + "context" + + "github.com/getAlby/lndhub.go/lnd" + "github.com/lightningnetwork/lnd/lnrpc" +) + +func (svc *LndhubService) FetchBolt12Invoice(ctx context.Context, offer, memo string, amt int64) (result *lnd.Bolt12Invoice, err error) { + return nil, err +} + +func (svc *LndhubService) PayBolt12Invoice(ctx context.Context, invoice string) (result *lnrpc.SendResponse, err error) { + return nil, err +} diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index da85108c..be2bd6ec 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -145,8 +145,8 @@ func (cl *CLNClient) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest PaymentPreimage: []byte(result.Get("payment_preimage").String()), PaymentHash: []byte(result.Get("payment_hash").String()), PaymentRoute: &lnrpc.Route{ - TotalFees: result.Get("amount_sent_msat").Int() - result.Get("amount_msat").Int(), - TotalAmt: result.Get("amount_sent_msat").Int(), + TotalFees: result.Get("msatoshi_sent").Int()/MSAT_PER_SAT - result.Get("msatoshi").Int()/MSAT_PER_SAT, + TotalAmt: result.Get("msatoshi_sent").Int() / MSAT_PER_SAT, }, }, nil } diff --git a/lnd/interface.go b/lnd/interface.go index a76828a5..0f155c66 100644 --- a/lnd/interface.go +++ b/lnd/interface.go @@ -29,3 +29,7 @@ type Offer struct { Vendor string `json:"vendor"` Valid bool `json:"valid"` } + +type Bolt12Invoice struct { + Invoice string `json:"invoice"` +} From c3f22ab166bea40a99934e03661abcc261c8a3b7 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Sun, 6 Feb 2022 15:31:42 +0100 Subject: [PATCH 10/40] everything ok except payment --- lib/service/bolt12.go | 2 +- lnd/c-lightning.go | 10 ++++++++++ lnd/interface.go | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/service/bolt12.go b/lib/service/bolt12.go index 94707315..2bf325b0 100644 --- a/lib/service/bolt12.go +++ b/lib/service/bolt12.go @@ -8,7 +8,7 @@ import ( ) func (svc *LndhubService) FetchBolt12Invoice(ctx context.Context, offer, memo string, amt int64) (result *lnd.Bolt12Invoice, err error) { - return nil, err + return svc.LndClient.FetchBolt12Invoice(ctx, offer, memo, amt) } func (svc *LndhubService) PayBolt12Invoice(ctx context.Context, invoice string) (result *lnrpc.SendResponse, err error) { diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index be2bd6ec..00551061 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -151,6 +151,16 @@ func (cl *CLNClient) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest }, nil } +func (cl *CLNClient) FetchBolt12Invoice(ctx context.Context, offer, memo string, amount int64) (*Bolt12Invoice, error) { + res, err := cl.client.CallNamed("fetchinvoice", "offer", offer, "msatoshi", amount*MSAT_PER_SAT, "payer_note", memo) + if err != nil { + return nil, err + } + return &Bolt12Invoice{ + Invoice: res.Get("invoice").String(), + }, nil +} + func (cl *CLNClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) { uuid, err := uuid.NewV4() if err != nil { diff --git a/lnd/interface.go b/lnd/interface.go index 0f155c66..bf1335d1 100644 --- a/lnd/interface.go +++ b/lnd/interface.go @@ -14,6 +14,7 @@ type LightningClientWrapper interface { SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) DecodeOffer(ctx context.Context, offer string) (*Offer, error) + FetchBolt12Invoice(ctx context.Context, offer, memo string, amount int64) (*Bolt12Invoice, error) } type SubscribeInvoicesWrapper interface { From 94fd1fe82e3a77a6ca79920bd6a63feef5afef89 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 9 Feb 2022 12:47:02 +0100 Subject: [PATCH 11/40] fetch invoice --- controllers/bolt12.ctrl.go | 8 ++++---- lib/service/bolt12.go | 4 ++-- lnd/c-lightning.go | 39 +++++++++++++++++++++++--------------- lnd/interface.go | 38 +++++++++++++++++++++++-------------- main.go | 1 + 5 files changed, 55 insertions(+), 35 deletions(-) diff --git a/controllers/bolt12.ctrl.go b/controllers/bolt12.ctrl.go index 7120a9a4..6adbbb53 100644 --- a/controllers/bolt12.ctrl.go +++ b/controllers/bolt12.ctrl.go @@ -14,9 +14,9 @@ type Bolt12Controller struct { svc *service.LndhubService } type FetchInvoiceRequestBody struct { - Amount int64 `json:"amt"` // amount in Satoshi + Amount int64 `json:"amt" validate:"required"` // todo: validate properly, amount not strictly needed always amount in Satoshi Memo string `json:"memo"` - Offer string `json:"offer"` + Offer string `json:"offer" validate:"required"` } func NewBolt12Controller(svc *service.LndhubService) *Bolt12Controller { @@ -26,7 +26,7 @@ func NewBolt12Controller(svc *service.LndhubService) *Bolt12Controller { // Decode : Decode handler func (controller *Bolt12Controller) Decode(c echo.Context) error { offer := c.Param("offer") - decoded, err := controller.svc.LndClient.DecodeOffer(context.TODO(), offer) + decoded, err := controller.svc.LndClient.DecodeBolt12(context.TODO(), offer) if err != nil { return err } @@ -72,7 +72,7 @@ func (controller *Bolt12Controller) PayOffer(c echo.Context) error { if err != nil { return err } - result, err := controller.svc.PayBolt12Invoice(context.TODO(), invoice.Invoice) + result, err := controller.svc.PayBolt12Invoice(context.TODO(), invoice) if err != nil { return err } diff --git a/lib/service/bolt12.go b/lib/service/bolt12.go index 2bf325b0..af059cb8 100644 --- a/lib/service/bolt12.go +++ b/lib/service/bolt12.go @@ -7,10 +7,10 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" ) -func (svc *LndhubService) FetchBolt12Invoice(ctx context.Context, offer, memo string, amt int64) (result *lnd.Bolt12Invoice, err error) { +func (svc *LndhubService) FetchBolt12Invoice(ctx context.Context, offer, memo string, amt int64) (result *lnd.Bolt12, err error) { return svc.LndClient.FetchBolt12Invoice(ctx, offer, memo, amt) } -func (svc *LndhubService) PayBolt12Invoice(ctx context.Context, invoice string) (result *lnrpc.SendResponse, err error) { +func (svc *LndhubService) PayBolt12Invoice(ctx context.Context, invoice *lnd.Bolt12) (result *lnrpc.SendResponse, err error) { return nil, err } diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index 00551061..0d346a30 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -151,14 +151,12 @@ func (cl *CLNClient) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest }, nil } -func (cl *CLNClient) FetchBolt12Invoice(ctx context.Context, offer, memo string, amount int64) (*Bolt12Invoice, error) { +func (cl *CLNClient) FetchBolt12Invoice(ctx context.Context, offer, memo string, amount int64) (result *Bolt12, err error) { res, err := cl.client.CallNamed("fetchinvoice", "offer", offer, "msatoshi", amount*MSAT_PER_SAT, "payer_note", memo) if err != nil { return nil, err } - return &Bolt12Invoice{ - Invoice: res.Get("invoice").String(), - }, nil + return cl.DecodeBolt12(ctx, res.Get("invoice").String()) } func (cl *CLNClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) { @@ -227,8 +225,8 @@ func (cl *CLNClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, opt }, nil } -func (cl *CLNClient) DecodeOffer(ctx context.Context, offer string) (*Offer, error) { - result, err := cl.client.Call("decode", offer) +func (cl *CLNClient) DecodeBolt12(ctx context.Context, bolt12 string) (decoded *Bolt12, err error) { + result, err := cl.client.Call("decode", bolt12) if err != nil { return nil, err } @@ -236,14 +234,25 @@ func (cl *CLNClient) DecodeOffer(ctx context.Context, offer string) (*Offer, err for _, ch := range result.Get("chains").Array() { chains = append(chains, ch.String()) } - return &Offer{ - Type: result.Get("type").String(), - OfferID: result.Get("offer_id").String(), - Description: result.Get("description").String(), - NodeID: result.Get("node_id").String(), - Signature: result.Get("signature").String(), - Vendor: result.Get("vendor").String(), - Chains: chains, - Valid: result.Get("valid").Bool(), + return &Bolt12{ + Type: result.Get("type").String(), + OfferID: result.Get("offer_id").String(), + Chains: chains, + Description: result.Get("description").String(), + NodeID: result.Get("node_id").String(), + Signature: result.Get("signature").String(), + Vendor: result.Get("vendor").String(), + Valid: result.Get("valid").Bool(), + AmountMsat: result.Get("amount_msat").String(), + Features: result.Get("features").String(), + PayerKey: result.Get("payer_key").String(), + PayerInfo: result.Get("payer_info").String(), + PayerNote: result.Get("payer_note").String(), + Timestamp: result.Get("timestamp").Int(), + CreatedAt: result.Get("created_at").Int(), + PaymentHash: result.Get("payment_hash").String(), + RelativeExpiry: result.Get("relative_expiry").Int(), + MinFinalCltvExpiry: result.Get("min_final_cltv_expiry").Int(), + Encoded: bolt12, }, nil } diff --git a/lnd/interface.go b/lnd/interface.go index bf1335d1..19d0a9d3 100644 --- a/lnd/interface.go +++ b/lnd/interface.go @@ -13,24 +13,34 @@ type LightningClientWrapper interface { AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) - DecodeOffer(ctx context.Context, offer string) (*Offer, error) - FetchBolt12Invoice(ctx context.Context, offer, memo string, amount int64) (*Bolt12Invoice, error) + DecodeBolt12(ctx context.Context, bolt12 string) (*Bolt12, error) + FetchBolt12Invoice(ctx context.Context, offer, memo string, amount int64) (*Bolt12, error) } type SubscribeInvoicesWrapper interface { Recv() (*lnrpc.Invoice, error) } -type Offer struct { - Type string `json:"type"` - OfferID string `json:"offer_id"` - Chains []string `json:"chains"` - Description string `json:"description"` - NodeID string `json:"node_id"` - Signature string `json:"signature"` - Vendor string `json:"vendor"` - Valid bool `json:"valid"` -} -type Bolt12Invoice struct { - Invoice string `json:"invoice"` +//Bolt12 can be both an offer or an invoice +//depending on Type +type Bolt12 struct { + Type string `json:"type"` + OfferID string `json:"offer_id"` + Chains []string `json:"chains"` + Description string `json:"description"` + NodeID string `json:"node_id"` + Signature string `json:"signature"` + Vendor string `json:"vendor"` + Valid bool `json:"valid"` + AmountMsat string `json:"amount_msat"` + Features string `json:"features"` + PayerKey string `json:"payer_key"` + PayerInfo string `json:"payer_info"` + PayerNote string `json:"payer_note"` + Timestamp int64 `json:"timestamp"` + CreatedAt int64 `json:"created_at"` + PaymentHash string `json:"payment_hash"` + RelativeExpiry int64 `json:"relative_expiry"` + MinFinalCltvExpiry int64 `json:"min_final_cltv_expiry"` + Encoded string `json:"encoded"` } diff --git a/main.go b/main.go index c1c65381..85f8cd5f 100644 --- a/main.go +++ b/main.go @@ -152,6 +152,7 @@ func main() { secured.GET("/balance", controllers.NewBalanceController(svc).Balance) secured.GET("/getinfo", controllers.NewGetInfoController(svc).GetInfo) secured.GET("/bolt12/decode/:offer", controllers.NewBolt12Controller(svc).Decode) + secured.POST("/bolt12/fetchinvoice", controllers.NewBolt12Controller(svc).FetchInvoice) // These endpoints are currently not supported and we return a blank response for backwards compatibility blankController := controllers.NewBlankController(svc) From 4d5c8c12d1557114909e9ec79c16a7de8270d7ec Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 10 Feb 2022 10:30:13 +0100 Subject: [PATCH 12/40] pay command working --- controllers/bolt12.ctrl.go | 62 +++++++++++++++++++++++++++++++++++--- lib/service/invoices.go | 34 +++++++++++++++++++++ main.go | 1 + 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/controllers/bolt12.ctrl.go b/controllers/bolt12.ctrl.go index 6adbbb53..41fd6710 100644 --- a/controllers/bolt12.ctrl.go +++ b/controllers/bolt12.ctrl.go @@ -2,10 +2,13 @@ package controllers import ( "context" + "fmt" "net/http" + "github.com/getAlby/lndhub.go/lib" "github.com/getAlby/lndhub.go/lib/responses" "github.com/getAlby/lndhub.go/lib/service" + "github.com/getsentry/sentry-go" "github.com/labstack/echo/v4" ) @@ -55,7 +58,8 @@ func (controller *Bolt12Controller) FetchInvoice(c echo.Context) error { } // PayOffer: fetches an invoice from a bolt12 offer for a certain amount, and pays it -func (controller *Bolt12Controller) PayOffer(c echo.Context) error { +func (controller *Bolt12Controller) PayBolt12(c echo.Context) error { + userID := c.Get("UserID").(int64) var body FetchInvoiceRequestBody if err := c.Bind(&body); err != nil { @@ -68,13 +72,63 @@ func (controller *Bolt12Controller) PayOffer(c echo.Context) error { return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) } - invoice, err := controller.svc.FetchBolt12Invoice(context.TODO(), body.Offer, body.Memo, body.Amount) + bolt12, err := controller.svc.FetchBolt12Invoice(context.TODO(), body.Offer, body.Memo, body.Amount) + if err != nil { + return err + } + decodedPaymentRequest, err := controller.svc.TransformBolt12(bolt12) + if err != nil { + c.Logger().Errorf("Invalid payment request: %v", err) + sentry.CaptureException(err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + invoice, err := controller.svc.AddOutgoingInvoice(userID, bolt12.Encoded, decodedPaymentRequest) if err != nil { return err } - result, err := controller.svc.PayBolt12Invoice(context.TODO(), invoice) + + currentBalance, err := controller.svc.CurrentUserBalance(context.TODO(), userID) if err != nil { return err } - return c.JSON(http.StatusOK, result) + + if currentBalance < invoice.Amount { + c.Logger().Errorf("User does not have enough balance invoice_id=%v user_id=%v balance=%v amount=%v", invoice.ID, userID, currentBalance, invoice.Amount) + + return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) + } + + sendPaymentResponse, err := controller.svc.PayInvoice(invoice) + if err != nil { + c.Logger().Errorf("Payment failed: %v", err) + sentry.CaptureException(err) + return c.JSON(http.StatusBadRequest, echo.Map{ + "error": true, + "code": 10, + "message": fmt.Sprintf("Payment failed. Does the receiver have enough inbound capacity? (%v)", err), + }) + } + + var responseBody struct { + RHash *lib.JavaScriptBuffer `json:"payment_hash,omitempty"` + PaymentRequest string `json:"payment_request,omitempty"` + PayReq string `json:"pay_req,omitempty"` + Amount int64 `json:"num_satoshis,omitempty"` + Description string `json:"description,omitempty"` + DescriptionHashStr string `json:"description_hash,omitempty"` + PaymentError string `json:"payment_error,omitempty"` + PaymentPreimage *lib.JavaScriptBuffer `json:"payment_preimage,omitempty"` + PaymentRoute *service.Route `json:"route,omitempty"` + } + + responseBody.RHash = &lib.JavaScriptBuffer{Data: sendPaymentResponse.PaymentHash} + responseBody.PaymentRequest = bolt12.Encoded + responseBody.PayReq = bolt12.Encoded + responseBody.Description = bolt12.PayerNote + responseBody.PaymentError = sendPaymentResponse.PaymentError + responseBody.PaymentPreimage = &lib.JavaScriptBuffer{Data: sendPaymentResponse.PaymentPreimage} + responseBody.PaymentRoute = sendPaymentResponse.PaymentRoute + + return c.JSON(http.StatusOK, &responseBody) } diff --git a/lib/service/invoices.go b/lib/service/invoices.go index 5f29e346..9a98cde1 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -6,13 +6,17 @@ import ( "encoding/hex" "errors" "math/rand" + "strconv" "strings" "time" + "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/getAlby/lndhub.go/db/models" + "github.com/getAlby/lndhub.go/lnd" "github.com/labstack/gommon/random" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/zpay32" "github.com/uptrace/bun" "github.com/uptrace/bun/schema" @@ -312,6 +316,36 @@ func (svc *LndhubService) AddIncomingInvoice(userID int64, amount int64, memo, d return &invoice, nil } +func (svc *LndhubService) TransformBolt12(bolt12 *lnd.Bolt12) (result *zpay32.Invoice, err error) { + //shoehorn msat into lnwire data type + msatAmt, err := strconv.Atoi(strings.Trim(bolt12.AmountMsat, "msat")) + if err != nil { + return nil, err + } + msat := lnwire.MilliSatoshi(msatAmt) + //horrible hack that won't work in practice, this should be solved later + hexPubkey, err := hex.DecodeString("02" + bolt12.NodeID) + if err != nil { + return nil, err + } + pubkey, err := btcec.ParsePubKey(hexPubkey[:], btcec.S256()) + if err != nil { + return nil, err + } + payerNote := bolt12.PayerNote + result = &zpay32.Invoice{ + MilliSat: &msat, + Timestamp: time.Unix(bolt12.Timestamp, 0), + PaymentHash: &[32]byte{}, + Destination: pubkey, + Description: &payerNote, + } + paymentHash := [32]byte{} + copy(paymentHash[:], bolt12.PaymentHash) + result.PaymentHash = &paymentHash + return result, nil +} + func (svc *LndhubService) DecodePaymentRequest(bolt11 string) (*zpay32.Invoice, error) { return zpay32.Decode(bolt11, ChainFromCurrency(bolt11[2:])) } diff --git a/main.go b/main.go index 85f8cd5f..7edb127b 100644 --- a/main.go +++ b/main.go @@ -153,6 +153,7 @@ func main() { secured.GET("/getinfo", controllers.NewGetInfoController(svc).GetInfo) secured.GET("/bolt12/decode/:offer", controllers.NewBolt12Controller(svc).Decode) secured.POST("/bolt12/fetchinvoice", controllers.NewBolt12Controller(svc).FetchInvoice) + secured.POST("/bolt12/pay", controllers.NewBolt12Controller(svc).PayBolt12) // These endpoints are currently not supported and we return a blank response for backwards compatibility blankController := controllers.NewBlankController(svc) From 4ddf0aae3bdbb3d9e383f7301e486c358f8eb363 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 11 Feb 2022 10:05:31 +0100 Subject: [PATCH 13/40] signatures not working like this, need to boot btcec dep --- lib/service/invoices.go | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/service/invoices.go b/lib/service/invoices.go index 9a98cde1..ae71e453 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/hex" "errors" + "fmt" "math/rand" "strconv" "strings" @@ -323,12 +324,7 @@ func (svc *LndhubService) TransformBolt12(bolt12 *lnd.Bolt12) (result *zpay32.In return nil, err } msat := lnwire.MilliSatoshi(msatAmt) - //horrible hack that won't work in practice, this should be solved later - hexPubkey, err := hex.DecodeString("02" + bolt12.NodeID) - if err != nil { - return nil, err - } - pubkey, err := btcec.ParsePubKey(hexPubkey[:], btcec.S256()) + pubkey, err := constructPubkey(bolt12) if err != nil { return nil, err } @@ -352,6 +348,36 @@ func (svc *LndhubService) DecodePaymentRequest(bolt11 string) (*zpay32.Invoice, const hexBytes = random.Hex +func constructPubkey(bolt12 *lnd.Bolt12) (*btcec.PublicKey, error) { + //horrible code that should be yeeted later + hexPubkey, err := hex.DecodeString("02" + bolt12.NodeID) + if err != nil { + return nil, err + } + pubkey, err := btcec.ParsePubKey(hexPubkey[:], btcec.S256()) + if err != nil { + return nil, err + } + + sig, err := btcec.ParseDERSignature([]byte(bolt12.Signature), btcec.S256()) + if err != nil { + return nil, err + } + if !sig.Verify([]byte(bolt12.NodeID), pubkey) { + fmt.Println("should not be here") + //we made the wrong pick + hexPubkey, err = hex.DecodeString("03" + bolt12.NodeID) + if err != nil { + return nil, err + } + pubkey, err = btcec.ParsePubKey(hexPubkey[:], btcec.S256()) + if err != nil { + return nil, err + } + } + return pubkey, nil +} + func makePreimageHex() []byte { b := make([]byte, 32) for i := range b { From b63885ca4f48b290dec39edc75e29bf6c6c1442a Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 15 Feb 2022 17:54:16 +0100 Subject: [PATCH 14/40] add transform bolt12 function again --- lib/service/invoices.go | 33 --------------------------------- lib/service/ln.go | 13 ++++++++++++- 2 files changed, 12 insertions(+), 34 deletions(-) diff --git a/lib/service/invoices.go b/lib/service/invoices.go index 0b51bb07..87a7b586 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -5,14 +5,11 @@ import ( "database/sql" "encoding/hex" "errors" - "fmt" "math/rand" "time" - "github.com/btcsuite/btcd/btcec" "github.com/getAlby/lndhub.go/common" "github.com/getAlby/lndhub.go/db/models" - "github.com/getAlby/lndhub.go/lnd" "github.com/labstack/gommon/random" "github.com/lightningnetwork/lnd/lnrpc" "github.com/uptrace/bun" @@ -301,36 +298,6 @@ func (svc *LndhubService) DecodePaymentRequest(ctx context.Context, bolt11 strin const hexBytes = random.Hex -func constructPubkey(bolt12 *lnd.Bolt12) (*btcec.PublicKey, error) { - //horrible code that should be yeeted later - hexPubkey, err := hex.DecodeString("02" + bolt12.NodeID) - if err != nil { - return nil, err - } - pubkey, err := btcec.ParsePubKey(hexPubkey[:], btcec.S256()) - if err != nil { - return nil, err - } - - sig, err := btcec.ParseDERSignature([]byte(bolt12.Signature), btcec.S256()) - if err != nil { - return nil, err - } - if !sig.Verify([]byte(bolt12.NodeID), pubkey) { - fmt.Println("should not be here") - //we made the wrong pick - hexPubkey, err = hex.DecodeString("03" + bolt12.NodeID) - if err != nil { - return nil, err - } - pubkey, err = btcec.ParsePubKey(hexPubkey[:], btcec.S256()) - if err != nil { - return nil, err - } - } - return pubkey, nil -} - func makePreimageHex() []byte { b := make([]byte, 32) for i := range b { diff --git a/lib/service/ln.go b/lib/service/ln.go index 8ec2b1c3..acd34b8c 100644 --- a/lib/service/ln.go +++ b/lib/service/ln.go @@ -12,5 +12,16 @@ func (svc *LndhubService) GetInfo(ctx context.Context) (*lnrpc.GetInfoResponse, } func (svc *LndhubService) TransformBolt12(bolt12 *lnd.Bolt12) (*lnrpc.PayReq, error) { - return nil, nil + return &lnrpc.PayReq{ + Destination: bolt12.NodeID, + PaymentHash: bolt12.PaymentHash, + //todo see if CLN really can't return an int here + NumSatoshis: 0, + Timestamp: bolt12.Timestamp, + Expiry: bolt12.RelativeExpiry, //todo is this correct? + Description: bolt12.Description, + DescriptionHash: "", // not supported by bolt 12? + //todo see if CLN really can't return an int here + NumMsat: 0, + }, nil } From 7d3eefd26b9dd133ff35a766708a502209866eca Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 15 Feb 2022 18:10:14 +0100 Subject: [PATCH 15/40] should work now --- lib/service/bolt12.go | 20 ++++++++++++++++++++ lib/service/ln.go | 16 ---------------- lnd/c-lightning.go | 20 +++++++------------- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/lib/service/bolt12.go b/lib/service/bolt12.go index af059cb8..7493b0a3 100644 --- a/lib/service/bolt12.go +++ b/lib/service/bolt12.go @@ -2,6 +2,8 @@ package service import ( "context" + "strconv" + "strings" "github.com/getAlby/lndhub.go/lnd" "github.com/lightningnetwork/lnd/lnrpc" @@ -14,3 +16,21 @@ func (svc *LndhubService) FetchBolt12Invoice(ctx context.Context, offer, memo st func (svc *LndhubService) PayBolt12Invoice(ctx context.Context, invoice *lnd.Bolt12) (result *lnrpc.SendResponse, err error) { return nil, err } +func (svc *LndhubService) TransformBolt12(bolt12 *lnd.Bolt12) (*lnrpc.PayReq, error) { + + //todo see if CLN really can't return an int here + msatAmt, err := strconv.Atoi(strings.Trim(bolt12.AmountMsat, "msat")) + if err != nil { + return nil, err + } + return &lnrpc.PayReq{ + Destination: bolt12.NodeID, + PaymentHash: bolt12.PaymentHash, + NumSatoshis: int64(msatAmt / lnd.MSAT_PER_SAT), + Timestamp: bolt12.Timestamp, + Expiry: bolt12.RelativeExpiry, + Description: bolt12.Description, + DescriptionHash: "", // not supported by bolt 12? + NumMsat: int64(msatAmt), + }, nil +} diff --git a/lib/service/ln.go b/lib/service/ln.go index acd34b8c..a9f609a7 100644 --- a/lib/service/ln.go +++ b/lib/service/ln.go @@ -3,25 +3,9 @@ package service import ( "context" - "github.com/getAlby/lndhub.go/lnd" "github.com/lightningnetwork/lnd/lnrpc" ) func (svc *LndhubService) GetInfo(ctx context.Context) (*lnrpc.GetInfoResponse, error) { return svc.LndClient.GetInfo(ctx, &lnrpc.GetInfoRequest{}) } - -func (svc *LndhubService) TransformBolt12(bolt12 *lnd.Bolt12) (*lnrpc.PayReq, error) { - return &lnrpc.PayReq{ - Destination: bolt12.NodeID, - PaymentHash: bolt12.PaymentHash, - //todo see if CLN really can't return an int here - NumSatoshis: 0, - Timestamp: bolt12.Timestamp, - Expiry: bolt12.RelativeExpiry, //todo is this correct? - Description: bolt12.Description, - DescriptionHash: "", // not supported by bolt 12? - //todo see if CLN really can't return an int here - NumMsat: 0, - }, nil -} diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index e882c526..c0574793 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -226,21 +226,15 @@ func (cl *CLNClient) DecodeBolt11(ctx context.Context, bolt11 string, options .. if err != nil { return nil, err } - //todo return &lnrpc.PayReq{ - Destination: result.Get("destination").String(), + Destination: result.Get("payee").String(), PaymentHash: result.Get("payment_hash").String(), - NumSatoshis: 0, - Timestamp: 0, - Expiry: 0, - Description: "", - DescriptionHash: "", - FallbackAddr: "", - CltvExpiry: 0, - RouteHints: []*lnrpc.RouteHint{}, - PaymentAddr: []byte{}, - NumMsat: 0, - Features: map[uint32]*lnrpc.Feature{}, + NumSatoshis: result.Get("msatoshi").Int() / MSAT_PER_SAT, + Timestamp: result.Get("created_at").Time().Unix(), + Expiry: result.Get("expiry").Int(), + Description: result.Get("description").String(), + DescriptionHash: result.Get("description_hash").String(), + NumMsat: result.Get("msatoshi").Int(), }, nil } From 5f052add8468d4d484bf7f169a1e2f95c124d27b Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 23 Feb 2022 15:13:48 +0100 Subject: [PATCH 16/40] add const --- lnd/c-lightning.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index c0574793..d69d58b0 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -12,6 +12,10 @@ import ( "google.golang.org/grpc" ) +const ( + MSAT_PER_SAT = 1000 +) + type CLNClient struct { client *cln.Client handler *InvoiceHandler From d067b2c2d2bc85e83d007161e3abb4958d97fe23 Mon Sep 17 00:00:00 2001 From: kiwiidb <33457577+kiwiidb@users.noreply.github.com> Date: Sat, 26 Feb 2022 13:28:07 +0100 Subject: [PATCH 17/40] use a super high timeout --- lnd/c-lightning.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index d69d58b0..f63c1113 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "reflect" + "time" cln "github.com/fiatjaf/lightningd-gjson-rpc" "github.com/gofrs/uuid" @@ -37,7 +38,7 @@ func NewCLNClient(options CLNClientOptions) (*CLNClient, error) { handler: handler, client: &cln.Client{ PaymentHandler: handler.Handle, - //CallTimeout: 0, + CallTimeout: 24 * 3600 * time.Second, //should be infinite actually //Path: "", //LightningDir: "", SparkURL: options.SparkUrl, From b05b6d89d33924aaaeb8e29e898a451a94fe8fc1 Mon Sep 17 00:00:00 2001 From: kiwiidb <33457577+kiwiidb@users.noreply.github.com> Date: Sun, 27 Feb 2022 09:09:00 +0100 Subject: [PATCH 18/40] no auth for decoding --- main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 33440f65..e54de2c4 100644 --- a/main.go +++ b/main.go @@ -141,7 +141,6 @@ func main() { secured.GET("/checkpayment/:payment_hash", controllers.NewCheckPaymentController(svc).CheckPayment) secured.GET("/balance", controllers.NewBalanceController(svc).Balance) secured.GET("/getinfo", controllers.NewGetInfoController(svc).GetInfo) - secured.GET("/bolt12/decode/:offer", controllers.NewBolt12Controller(svc).Decode) secured.POST("/bolt12/fetchinvoice", controllers.NewBolt12Controller(svc).FetchInvoice) secured.POST("/bolt12/pay", controllers.NewBolt12Controller(svc).PayBolt12) @@ -158,6 +157,8 @@ func main() { e.GET("/static/css/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) e.GET("/static/img/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) + e.GET("/bolt12/decode/:offer", controllers.NewBolt12Controller(svc).Decode) + // Subscribe to LND invoice updates in the background // CLN: todo: re-write logic go svc.InvoiceUpdateSubscription(context.Background()) From 93ad471dac69c42682af9ebf17276f07d04a42a2 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Sun, 20 Mar 2022 13:17:38 +0100 Subject: [PATCH 19/40] feature: settled invoice stream --- controllers/invoicestream.ctrl.go | 47 ++++++++++++++++++++++++++++++ lib/service/invoicesubscription.go | 3 ++ lib/service/service.go | 11 +++---- main.go | 15 ++++++---- 4 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 controllers/invoicestream.ctrl.go diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go new file mode 100644 index 00000000..8c92ae92 --- /dev/null +++ b/controllers/invoicestream.ctrl.go @@ -0,0 +1,47 @@ +package controllers + +import ( + "encoding/json" + "net/http" + + "github.com/getAlby/lndhub.go/common" + "github.com/getAlby/lndhub.go/db/models" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/labstack/echo/v4" +) + +// GetTXSController : GetTXSController struct +type InvoiceStreamController struct { + svc *service.LndhubService +} + +func NewInvoiceStreamController(svc *service.LndhubService) *InvoiceStreamController { + return &InvoiceStreamController{svc: svc} +} + +// Stream invoices streams incoming payments to the client +func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error { + userId := c.Get("UserID").(int64) + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + c.Response().WriteHeader(http.StatusOK) + enc := json.NewEncoder(c.Response()) + invoiceChan := make(chan models.Invoice) + controller.svc.InvoiceSubscribers[userId] = invoiceChan + for invoice := range invoiceChan { + if err := enc.Encode(IncomingInvoice{ + PaymentHash: invoice.RHash, + PaymentRequest: invoice.PaymentRequest, + Description: invoice.Memo, + PayReq: invoice.PaymentRequest, + Timestamp: invoice.CreatedAt.Unix(), + Type: common.InvoiceTypeUser, + ExpireTime: 3600 * 24, + Amount: invoice.Amount, + IsPaid: invoice.State == common.InvoiceStateSettled, + }); err != nil { + return err + } + c.Response().Flush() + } + return nil +} diff --git a/lib/service/invoicesubscription.go b/lib/service/invoicesubscription.go index e809be8a..bf1c438d 100644 --- a/lib/service/invoicesubscription.go +++ b/lib/service/invoicesubscription.go @@ -97,6 +97,9 @@ func (svc *LndhubService) ProcessInvoiceUpdate(ctx context.Context, rawInvoice * svc.Logger.Errorf("Failed to commit DB transaction user_id:%v invoice_id:%v %v", invoice.UserID, invoice.ID, err) return err } + if sub, ok := svc.InvoiceSubscribers[invoice.UserID]; ok { + sub <- invoice + } return nil } diff --git a/lib/service/service.go b/lib/service/service.go index 3dba99c8..03484c0e 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -17,11 +17,12 @@ import ( const alphaNumBytes = random.Alphanumeric type LndhubService struct { - Config *Config - DB *bun.DB - LndClient lnd.LightningClientWrapper - Logger *lecho.Logger - IdentityPubkey string + Config *Config + DB *bun.DB + LndClient lnd.LightningClientWrapper + Logger *lecho.Logger + IdentityPubkey string + InvoiceSubscribers map[int64]chan models.Invoice } func (svc *LndhubService) GenerateToken(ctx context.Context, login, password, inRefreshToken string) (accessToken, refreshToken string, err error) { diff --git a/main.go b/main.go index a54f6c0b..c0aaabd2 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "github.com/getAlby/lndhub.go/controllers" "github.com/getAlby/lndhub.go/db" "github.com/getAlby/lndhub.go/db/migrations" + "github.com/getAlby/lndhub.go/db/models" "github.com/getAlby/lndhub.go/lib" "github.com/getAlby/lndhub.go/lib/responses" "github.com/getAlby/lndhub.go/lib/service" @@ -117,11 +118,12 @@ func main() { logger.Infof("Connected to LND: %s - %s", getInfo.Alias, getInfo.IdentityPubkey) svc := &service.LndhubService{ - Config: c, - DB: dbConn, - LndClient: lndClient, - Logger: logger, - IdentityPubkey: getInfo.IdentityPubkey, + Config: c, + DB: dbConn, + LndClient: lndClient, + Logger: logger, + IdentityPubkey: getInfo.IdentityPubkey, + InvoiceSubscribers: map[int64]chan models.Invoice{}, } strictRateLimitMiddleware := createRateLimitMiddleware(c.StrictRateLimit, c.BurstRateLimit) @@ -154,6 +156,9 @@ func main() { e.GET("/static/css/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) e.GET("/static/img/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) + //invoice streaming + secured.GET("/invoices/stream", controllers.NewInvoiceStreamController(svc).StreamInvoices) + // Subscribe to LND invoice updates in the background go svc.InvoiceUpdateSubscription(context.Background()) From 8e2606a2e694ed1927f350f3115eccaa26fe0631 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 21 Mar 2022 11:42:59 +0100 Subject: [PATCH 20/40] fix: graceful shutdown --- controllers/invoicestream.ctrl.go | 33 ++++++++++++++++++------------- main.go | 4 ++++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 8c92ae92..3b239e94 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -27,21 +27,26 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error enc := json.NewEncoder(c.Response()) invoiceChan := make(chan models.Invoice) controller.svc.InvoiceSubscribers[userId] = invoiceChan - for invoice := range invoiceChan { - if err := enc.Encode(IncomingInvoice{ - PaymentHash: invoice.RHash, - PaymentRequest: invoice.PaymentRequest, - Description: invoice.Memo, - PayReq: invoice.PaymentRequest, - Timestamp: invoice.CreatedAt.Unix(), - Type: common.InvoiceTypeUser, - ExpireTime: 3600 * 24, - Amount: invoice.Amount, - IsPaid: invoice.State == common.InvoiceStateSettled, - }); err != nil { - return err + ctx := c.Request().Context() + for { + select { + case <-ctx.Done(): + return nil + case invoice := <-invoiceChan: + if err := enc.Encode(IncomingInvoice{ + PaymentHash: invoice.RHash, + PaymentRequest: invoice.PaymentRequest, + Description: invoice.Memo, + PayReq: invoice.PaymentRequest, + Timestamp: invoice.CreatedAt.Unix(), + Type: common.InvoiceTypeUser, + ExpireTime: 3600 * 24, + Amount: invoice.Amount, + IsPaid: invoice.State == common.InvoiceStateSettled, + }); err != nil { + return err + } } c.Response().Flush() } - return nil } diff --git a/main.go b/main.go index c0aaabd2..4a326ce4 100644 --- a/main.go +++ b/main.go @@ -176,6 +176,10 @@ func main() { <-quit ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() + //close all channels + for _, sub := range svc.InvoiceSubscribers { + close(sub) + } if err := e.Shutdown(ctx); err != nil { e.Logger.Fatal(err) } From c91bc0c4c3204d9a087126bff7883aba79d11455 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 21 Mar 2022 11:57:55 +0100 Subject: [PATCH 21/40] go mod tidy --- go.mod | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e651ca98..4352f7fb 100644 --- a/go.mod +++ b/go.mod @@ -28,15 +28,12 @@ require ( require ( github.com/SporkHubr/echo-http-cache v0.0.0-20200706100054-1d7ae9f38029 - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect - golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect - golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba github.com/fiatjaf/lightningd-gjson-rpc v1.4.1 github.com/gofrs/uuid v4.0.0+incompatible github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/tidwall/gjson v1.6.0 golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0 // indirect ) From fcbee32d76f04fa54981e2f6d39a87053b2445e6 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 21 Mar 2022 12:01:13 +0100 Subject: [PATCH 22/40] wrap bolt12 response --- lib/service/bolt12.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/service/bolt12.go b/lib/service/bolt12.go index 7493b0a3..d58ec4d0 100644 --- a/lib/service/bolt12.go +++ b/lib/service/bolt12.go @@ -16,21 +16,24 @@ func (svc *LndhubService) FetchBolt12Invoice(ctx context.Context, offer, memo st func (svc *LndhubService) PayBolt12Invoice(ctx context.Context, invoice *lnd.Bolt12) (result *lnrpc.SendResponse, err error) { return nil, err } -func (svc *LndhubService) TransformBolt12(bolt12 *lnd.Bolt12) (*lnrpc.PayReq, error) { +func (svc *LndhubService) TransformBolt12(bolt12 *lnd.Bolt12) (*lnd.LNPayReq, error) { //todo see if CLN really can't return an int here msatAmt, err := strconv.Atoi(strings.Trim(bolt12.AmountMsat, "msat")) if err != nil { return nil, err } - return &lnrpc.PayReq{ - Destination: bolt12.NodeID, - PaymentHash: bolt12.PaymentHash, - NumSatoshis: int64(msatAmt / lnd.MSAT_PER_SAT), - Timestamp: bolt12.Timestamp, - Expiry: bolt12.RelativeExpiry, - Description: bolt12.Description, - DescriptionHash: "", // not supported by bolt 12? - NumMsat: int64(msatAmt), + return &lnd.LNPayReq{ + PayReq: &lnrpc.PayReq{ + Destination: bolt12.NodeID, + PaymentHash: bolt12.PaymentHash, + NumSatoshis: int64(msatAmt / lnd.MSAT_PER_SAT), + Timestamp: bolt12.Timestamp, + Expiry: bolt12.RelativeExpiry, + Description: bolt12.Description, + DescriptionHash: "", // not supported by bolt 12? + NumMsat: int64(msatAmt), + }, + Keysend: false, }, nil } From aa9e1bc4a4a6dfebb1477d3b63ea4de72309775f Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 21 Mar 2022 16:58:57 +0100 Subject: [PATCH 23/40] encode description hash again to hex --- lnd/c-lightning.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go index f63c1113..fd3df7b8 100644 --- a/lnd/c-lightning.go +++ b/lnd/c-lightning.go @@ -2,6 +2,7 @@ package lnd import ( "context" + "encoding/hex" "fmt" "reflect" "time" @@ -38,7 +39,7 @@ func NewCLNClient(options CLNClientOptions) (*CLNClient, error) { handler: handler, client: &cln.Client{ PaymentHandler: handler.Handle, - CallTimeout: 24 * 3600 * time.Second, //should be infinite actually + CallTimeout: 24 * 3600 * time.Second, //should be infinite actually //Path: "", //LightningDir: "", SparkURL: options.SparkUrl, @@ -170,7 +171,7 @@ func (cl *CLNClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options arg := req.Memo if !reflect.DeepEqual(req.DescriptionHash, []byte("")) { methodToCall = "invoicewithdescriptionhash" - arg = string(req.DescriptionHash) + arg = hex.EncodeToString(req.DescriptionHash) } res, err := cl.client.Call(methodToCall, mSatAmt, uuid.String(), arg) if err != nil { From edffaf454281f871e236d7c7a7bf9938a71e13fb Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 23 Mar 2022 10:20:17 +0100 Subject: [PATCH 24/40] add keepalive messages --- controllers/gettxs.ctrl.go | 4 ++-- controllers/invoicestream.ctrl.go | 38 ++++++++++++++++++++++--------- main.go | 6 ++--- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/controllers/gettxs.ctrl.go b/controllers/gettxs.ctrl.go index ad8e3f3f..4d5cd048 100644 --- a/controllers/gettxs.ctrl.go +++ b/controllers/gettxs.ctrl.go @@ -19,7 +19,7 @@ func NewGetTXSController(svc *service.LndhubService) *GetTXSController { } type OutgoingInvoice struct { - RHash interface{} `json:"r_hash"` + RHash interface{} `json:"r_hash,omitempty"` PaymentHash interface{} `json:"payment_hash"` PaymentPreimage string `json:"payment_preimage"` Value int64 `json:"value"` @@ -30,7 +30,7 @@ type OutgoingInvoice struct { } type IncomingInvoice struct { - RHash interface{} `json:"r_hash"` + RHash interface{} `json:"r_hash,omitempty"` PaymentHash interface{} `json:"payment_hash"` PaymentRequest string `json:"payment_request"` Description string `json:"description"` diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 3b239e94..94b50fe1 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -3,6 +3,7 @@ package controllers import ( "encoding/json" "net/http" + "time" "github.com/getAlby/lndhub.go/common" "github.com/getAlby/lndhub.go/db/models" @@ -19,6 +20,11 @@ func NewInvoiceStreamController(svc *service.LndhubService) *InvoiceStreamContro return &InvoiceStreamController{svc: svc} } +type InvoiceEvent struct { + Invoice *IncomingInvoice `json:"invoice,omitempty"` + Type string +} + // Stream invoices streams incoming payments to the client func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error { userId := c.Get("UserID").(int64) @@ -28,22 +34,32 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error invoiceChan := make(chan models.Invoice) controller.svc.InvoiceSubscribers[userId] = invoiceChan ctx := c.Request().Context() + ticker := time.NewTicker(30 * time.Second) for { select { + case <-ticker.C: + if err := enc.Encode( + InvoiceEvent{ + Type: "keepalive", + }); err != nil { + return err + } case <-ctx.Done(): return nil case invoice := <-invoiceChan: - if err := enc.Encode(IncomingInvoice{ - PaymentHash: invoice.RHash, - PaymentRequest: invoice.PaymentRequest, - Description: invoice.Memo, - PayReq: invoice.PaymentRequest, - Timestamp: invoice.CreatedAt.Unix(), - Type: common.InvoiceTypeUser, - ExpireTime: 3600 * 24, - Amount: invoice.Amount, - IsPaid: invoice.State == common.InvoiceStateSettled, - }); err != nil { + if err := enc.Encode( + InvoiceEvent{ + Type: "invoice", + Invoice: &IncomingInvoice{ + PaymentHash: invoice.RHash, + PaymentRequest: invoice.PaymentRequest, + Description: invoice.Memo, + PayReq: invoice.PaymentRequest, + Timestamp: invoice.CreatedAt.Unix(), + Type: common.InvoiceTypeUser, + Amount: invoice.Amount, + IsPaid: invoice.State == common.InvoiceStateSettled, + }}); err != nil { return err } } diff --git a/main.go b/main.go index 4a326ce4..d14a0b82 100644 --- a/main.go +++ b/main.go @@ -176,13 +176,13 @@ func main() { <-quit ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() + if err := e.Shutdown(ctx); err != nil { + e.Logger.Fatal(err) + } //close all channels for _, sub := range svc.InvoiceSubscribers { close(sub) } - if err := e.Shutdown(ctx); err != nil { - e.Logger.Fatal(err) - } } func createRateLimitMiddleware(seconds int, burst int) echo.MiddlewareFunc { From 0341276f4dc17414e1adbdcba25cd0b351f7a49a Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 24 Mar 2022 13:55:11 +0100 Subject: [PATCH 25/40] use websocket instead of sse because more client support --- controllers/invoicestream.ctrl.go | 65 ++++++++++++++----------------- go.mod | 1 + go.sum | 3 +- lib/tokens/jwt.go | 28 +++++++++++++ main.go | 3 +- 5 files changed, 63 insertions(+), 37 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 94b50fe1..6dd64760 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -1,13 +1,13 @@ package controllers import ( - "encoding/json" "net/http" - "time" "github.com/getAlby/lndhub.go/common" "github.com/getAlby/lndhub.go/db/models" "github.com/getAlby/lndhub.go/lib/service" + "github.com/getAlby/lndhub.go/lib/tokens" + "github.com/gorilla/websocket" "github.com/labstack/echo/v4" ) @@ -20,49 +20,44 @@ func NewInvoiceStreamController(svc *service.LndhubService) *InvoiceStreamContro return &InvoiceStreamController{svc: svc} } -type InvoiceEvent struct { - Invoice *IncomingInvoice `json:"invoice,omitempty"` - Type string -} - // Stream invoices streams incoming payments to the client func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error { - userId := c.Get("UserID").(int64) - c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - c.Response().WriteHeader(http.StatusOK) - enc := json.NewEncoder(c.Response()) + userId, err := tokens.ParseToken(controller.svc.Config.JWTSecret, (c.QueryParam("token"))) + if err != nil { + return err + } invoiceChan := make(chan models.Invoice) controller.svc.InvoiceSubscribers[userId] = invoiceChan ctx := c.Request().Context() - ticker := time.NewTicker(30 * time.Second) + upgrader := websocket.Upgrader{} + upgrader.CheckOrigin = func(r *http.Request) bool { return true } + ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) + if err != nil { + return err + } + defer ws.Close() +SocketLoop: for { select { - case <-ticker.C: - if err := enc.Encode( - InvoiceEvent{ - Type: "keepalive", - }); err != nil { - return err - } case <-ctx.Done(): - return nil + break SocketLoop case invoice := <-invoiceChan: - if err := enc.Encode( - InvoiceEvent{ - Type: "invoice", - Invoice: &IncomingInvoice{ - PaymentHash: invoice.RHash, - PaymentRequest: invoice.PaymentRequest, - Description: invoice.Memo, - PayReq: invoice.PaymentRequest, - Timestamp: invoice.CreatedAt.Unix(), - Type: common.InvoiceTypeUser, - Amount: invoice.Amount, - IsPaid: invoice.State == common.InvoiceStateSettled, - }}); err != nil { - return err + err := ws.WriteJSON( + &IncomingInvoice{ + PaymentHash: invoice.RHash, + PaymentRequest: invoice.PaymentRequest, + Description: invoice.Memo, + PayReq: invoice.PaymentRequest, + Timestamp: invoice.CreatedAt.Unix(), + Type: common.InvoiceTypeUser, + Amount: invoice.Amount, + IsPaid: invoice.State == common.InvoiceStateSettled, + }) + if err != nil { + controller.svc.Logger.Error(err) + break SocketLoop } } - c.Response().Flush() } + return nil } diff --git a/go.mod b/go.mod index af8d6e3c..b4080db7 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( require ( github.com/SporkHubr/echo-http-cache v0.0.0-20200706100054-1d7ae9f38029 + github.com/gorilla/websocket v1.5.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect diff --git a/go.sum b/go.sum index 1e937f64..f5d3a3d2 100644 --- a/go.sum +++ b/go.sum @@ -338,8 +338,9 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= diff --git a/lib/tokens/jwt.go b/lib/tokens/jwt.go index 46b7c67c..117453bf 100644 --- a/lib/tokens/jwt.go +++ b/lib/tokens/jwt.go @@ -81,6 +81,34 @@ func GenerateRefreshToken(secret []byte, expiryInSeconds int, u *models.User) (s return t, nil } +func ParseToken(secret []byte, token string) (int64, error) { + userIdClaim := "id" + claims := jwt.MapClaims{} + parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { + return secret, nil + }) + + if err != nil { + return -1, err + } + + if !parsedToken.Valid { + return -1, errors.New("Token is invalid") + } + + var userId interface{} + for k, v := range claims { + if k == userIdClaim { + userId = v.(float64) + } + } + + if userId == nil { + return -1, errors.New("User id claim not found") + } + + return int64(userId.(float64)), nil +} func GetUserIdFromToken(secret []byte, token string) (int64, error) { userIdClaim := "id" diff --git a/main.go b/main.go index d14a0b82..56bd7afd 100644 --- a/main.go +++ b/main.go @@ -157,7 +157,8 @@ func main() { e.GET("/static/img/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) //invoice streaming - secured.GET("/invoices/stream", controllers.NewInvoiceStreamController(svc).StreamInvoices) + //Authentication should be done through the query param because this is a websocket + e.GET("/invoices/stream", controllers.NewInvoiceStreamController(svc).StreamInvoices) // Subscribe to LND invoice updates in the background go svc.InvoiceUpdateSubscription(context.Background()) From 4bfbc3d25cb61ec925d9fc1c2b49516eb7efe37a Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 24 Mar 2022 15:06:55 +0100 Subject: [PATCH 26/40] add keepalive messages --- controllers/invoicestream.ctrl.go | 35 ++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 6dd64760..4532c2f7 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -2,6 +2,7 @@ package controllers import ( "net/http" + "time" "github.com/getAlby/lndhub.go/common" "github.com/getAlby/lndhub.go/db/models" @@ -16,6 +17,11 @@ type InvoiceStreamController struct { svc *service.LndhubService } +type InvoiceEventWrapper struct { + Type string `json:"type"` + Invoice *IncomingInvoice `json:"invoice, omitempty"` +} + func NewInvoiceStreamController(svc *service.LndhubService) *InvoiceStreamController { return &InvoiceStreamController{svc: svc} } @@ -31,6 +37,7 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error ctx := c.Request().Context() upgrader := websocket.Upgrader{} upgrader.CheckOrigin = func(r *http.Request) bool { return true } + ticker := time.NewTicker(30 * time.Second) ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) if err != nil { return err @@ -41,18 +48,26 @@ SocketLoop: select { case <-ctx.Done(): break SocketLoop + case <-ticker.C: + err := ws.WriteJSON(&InvoiceEventWrapper{Type: "keepalive"}) + if err != nil { + controller.svc.Logger.Error(err) + break SocketLoop + } case invoice := <-invoiceChan: err := ws.WriteJSON( - &IncomingInvoice{ - PaymentHash: invoice.RHash, - PaymentRequest: invoice.PaymentRequest, - Description: invoice.Memo, - PayReq: invoice.PaymentRequest, - Timestamp: invoice.CreatedAt.Unix(), - Type: common.InvoiceTypeUser, - Amount: invoice.Amount, - IsPaid: invoice.State == common.InvoiceStateSettled, - }) + &InvoiceEventWrapper{ + Type: "invoice", + Invoice: &IncomingInvoice{ + PaymentHash: invoice.RHash, + PaymentRequest: invoice.PaymentRequest, + Description: invoice.Memo, + PayReq: invoice.PaymentRequest, + Timestamp: invoice.CreatedAt.Unix(), + Type: common.InvoiceTypeUser, + Amount: invoice.Amount, + IsPaid: invoice.State == common.InvoiceStateSettled, + }}) if err != nil { controller.svc.Logger.Error(err) break SocketLoop From c77e7ccff8bf160eded09fbe5e0e3e47eb6e7c3a Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 24 Mar 2022 15:08:45 +0100 Subject: [PATCH 27/40] struct tags --- controllers/invoicestream.ctrl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 4532c2f7..6b307551 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -19,7 +19,7 @@ type InvoiceStreamController struct { type InvoiceEventWrapper struct { Type string `json:"type"` - Invoice *IncomingInvoice `json:"invoice, omitempty"` + Invoice *IncomingInvoice `json:"invoice,omitempty"` } func NewInvoiceStreamController(svc *service.LndhubService) *InvoiceStreamController { From 55c775d8b44baf2450109465e96cc65202164a48 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 25 Mar 2022 09:34:02 +0100 Subject: [PATCH 28/40] send keepalive msg on startup --- controllers/invoicestream.ctrl.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 6b307551..bf939f34 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -43,6 +43,12 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error return err } defer ws.Close() + //start with keepalive message + err = ws.WriteJSON(&InvoiceEventWrapper{Type: "keepalive"}) + if err != nil { + controller.svc.Logger.Error(err) + return err + } SocketLoop: for { select { From 4cb60a5c91f7b54ba29f9a2e3bf171461be3dc77 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 6 Apr 2022 16:36:06 +0200 Subject: [PATCH 29/40] clean up PR --- controllers/bolt12.ctrl.go | 134 ------------------ go.mod | 3 - go.sum | 49 ------- lib/service/bolt12.go | 39 ------ lnd/c-lightning.go | 277 ------------------------------------- lnd/interface.go | 26 ---- lnd/lnd.go | 8 -- main.go | 20 +-- 8 files changed, 4 insertions(+), 552 deletions(-) delete mode 100644 controllers/bolt12.ctrl.go delete mode 100644 lib/service/bolt12.go delete mode 100644 lnd/c-lightning.go diff --git a/controllers/bolt12.ctrl.go b/controllers/bolt12.ctrl.go deleted file mode 100644 index aa8ba8f3..00000000 --- a/controllers/bolt12.ctrl.go +++ /dev/null @@ -1,134 +0,0 @@ -package controllers - -import ( - "context" - "fmt" - "net/http" - - "github.com/getAlby/lndhub.go/lib" - "github.com/getAlby/lndhub.go/lib/responses" - "github.com/getAlby/lndhub.go/lib/service" - "github.com/getsentry/sentry-go" - "github.com/labstack/echo/v4" -) - -// Bolt12Controller : Bolt12Controller struct -type Bolt12Controller struct { - svc *service.LndhubService -} -type FetchInvoiceRequestBody struct { - Amount int64 `json:"amt" validate:"required"` // todo: validate properly, amount not strictly needed always amount in Satoshi - Memo string `json:"memo"` - Offer string `json:"offer" validate:"required"` -} - -func NewBolt12Controller(svc *service.LndhubService) *Bolt12Controller { - return &Bolt12Controller{svc: svc} -} - -// Decode : Decode handler -func (controller *Bolt12Controller) Decode(c echo.Context) error { - offer := c.Param("offer") - decoded, err := controller.svc.LndClient.DecodeBolt12(context.TODO(), offer) - if err != nil { - return err - } - return c.JSON(http.StatusOK, decoded) -} - -// FetchInvoice: fetches an invoice from a bolt12 offer for a certain amount -func (controller *Bolt12Controller) FetchInvoice(c echo.Context) error { - var body FetchInvoiceRequestBody - - if err := c.Bind(&body); err != nil { - c.Logger().Errorf("Failed to load fetchinvoice request body: %v", err) - return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) - } - - if err := c.Validate(&body); err != nil { - c.Logger().Errorf("Invalid fetchinvoice request body: %v", err) - return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) - } - - invoice, err := controller.svc.FetchBolt12Invoice(context.TODO(), body.Offer, body.Memo, body.Amount) - if err != nil { - return err - } - return c.JSON(http.StatusOK, invoice) -} - -// PayOffer: fetches an invoice from a bolt12 offer for a certain amount, and pays it -func (controller *Bolt12Controller) PayBolt12(c echo.Context) error { - userID := c.Get("UserID").(int64) - var body FetchInvoiceRequestBody - - if err := c.Bind(&body); err != nil { - c.Logger().Errorf("Failed to load fetchinvoice request body: %v", err) - return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) - } - - if err := c.Validate(&body); err != nil { - c.Logger().Errorf("Invalid fetchinvoice request body: %v", err) - return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) - } - - bolt12, err := controller.svc.FetchBolt12Invoice(c.Request().Context(), body.Offer, body.Memo, body.Amount) - if err != nil { - return err - } - decodedPaymentRequest, err := controller.svc.TransformBolt12(bolt12) - if err != nil { - c.Logger().Errorf("Invalid payment request: %v", err) - sentry.CaptureException(err) - return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) - } - - invoice, err := controller.svc.AddOutgoingInvoice(c.Request().Context(), userID, bolt12.Encoded, decodedPaymentRequest) - if err != nil { - return err - } - - currentBalance, err := controller.svc.CurrentUserBalance(c.Request().Context(), userID) - if err != nil { - return err - } - - if currentBalance < invoice.Amount { - c.Logger().Errorf("User does not have enough balance invoice_id=%v user_id=%v balance=%v amount=%v", invoice.ID, userID, currentBalance, invoice.Amount) - - return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) - } - - sendPaymentResponse, err := controller.svc.PayInvoice(c.Request().Context(), invoice) - if err != nil { - c.Logger().Errorf("Payment failed: %v", err) - sentry.CaptureException(err) - return c.JSON(http.StatusBadRequest, echo.Map{ - "error": true, - "code": 10, - "message": fmt.Sprintf("Payment failed. Does the receiver have enough inbound capacity? (%v)", err), - }) - } - - var responseBody struct { - RHash *lib.JavaScriptBuffer `json:"payment_hash,omitempty"` - PaymentRequest string `json:"payment_request,omitempty"` - PayReq string `json:"pay_req,omitempty"` - Amount int64 `json:"num_satoshis,omitempty"` - Description string `json:"description,omitempty"` - DescriptionHashStr string `json:"description_hash,omitempty"` - PaymentError string `json:"payment_error,omitempty"` - PaymentPreimage *lib.JavaScriptBuffer `json:"payment_preimage,omitempty"` - PaymentRoute *service.Route `json:"route,omitempty"` - } - - responseBody.RHash = &lib.JavaScriptBuffer{Data: sendPaymentResponse.PaymentHash} - responseBody.PaymentRequest = bolt12.Encoded - responseBody.PayReq = bolt12.Encoded - responseBody.Description = bolt12.PayerNote - responseBody.PaymentError = sendPaymentResponse.PaymentError - responseBody.PaymentPreimage = &lib.JavaScriptBuffer{Data: sendPaymentResponse.PaymentPreimage} - responseBody.PaymentRoute = sendPaymentResponse.PaymentRoute - - return c.JSON(http.StatusOK, &responseBody) -} diff --git a/go.mod b/go.mod index b320f64a..b4080db7 100644 --- a/go.mod +++ b/go.mod @@ -29,10 +29,7 @@ require ( require ( github.com/SporkHubr/echo-http-cache v0.0.0-20200706100054-1d7ae9f38029 github.com/gorilla/websocket v1.5.0 - github.com/fiatjaf/lightningd-gjson-rpc v1.4.1 - github.com/gofrs/uuid v4.0.0+incompatible github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - github.com/tidwall/gjson v1.6.0 golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba diff --git a/go.sum b/go.sum index 1fdc86fc..f5d3a3d2 100644 --- a/go.sum +++ b/go.sum @@ -44,7 +44,6 @@ github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5Db github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ= github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -86,8 +85,6 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.20.1-beta.0.20200513120220-b470eee47728/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46/go.mod h1:Yktc19YNjh/Iz2//CX0vfRTS4IJKM/RKO5YZ9Fn+Pgo= github.com/btcsuite/btcd v0.21.0-beta.0.20201208033208-6bd4c64a54fa/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= github.com/btcsuite/btcd v0.22.0-beta.0.20210803133449-f5a1fb9965e4/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39 h1:o6qacOzpKubr16y0RrE2fBauRZN1rDZ1YsE26ixCgQ0= @@ -99,11 +96,9 @@ github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3L github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 h1:9aGy5p7oXRUB4MCTmWm0+jzuh79GpjPIfv1leA5POD4= github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= -github.com/btcsuite/btcutil/psbt v1.0.2/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= github.com/btcsuite/btcutil/psbt v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 h1:0xUNvvwJ7RjzBs4nCF+YrK28S5P/b4uHkpPxY1ovGY4= github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= -github.com/btcsuite/btcwallet v0.11.1-0.20200515224913-e0e62245ecbe/go.mod h1:9+AH3V5mcTtNXTKe+fe63fDLKGOwQbZqmvOVUef+JFE= github.com/btcsuite/btcwallet v0.13.0 h1:gtLWwueRm27KQiHJpycybv3uMdK1eo87JexfTfzvEhk= github.com/btcsuite/btcwallet v0.13.0/go.mod h1:iLN1lG1MW0eREm+SikmPO8AZPz5NglBTEK/ErqkjGpo= github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= @@ -115,19 +110,13 @@ github.com/btcsuite/btcwallet/wallet/txrules v1.1.0/go.mod h1:Zn9UTqpiTH+HOd5BLz github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8= github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= -github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk= -github.com/btcsuite/btcwallet/walletdb v1.2.0/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc= -github.com/btcsuite/btcwallet/walletdb v1.3.1/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc= github.com/btcsuite/btcwallet/walletdb v1.3.4/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec h1:zcAU3Ij8SmqaE+ITtS76fua2Niq7DRNp46sJRhi8PiI= github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= -github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY= -github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe/go.mod h1:OwC0W0HhUszbWdvJvH6xvgabKSJ0lXl11YbmmqF9YXQ= github.com/btcsuite/btcwallet/wtxmgr v1.3.0/go.mod h1:awQsh1n/0ZrEQ+JZgWvHeo153ubzEisf/FyNtwI0dDk= github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c h1:owWPexGfK4eSK4/Zy+XK2lET5qsnW7FRAc8OCOdD0Fg= github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c/go.mod h1:UM38ixX8VwJ9qey4umf//0H3ndn5kSImFZ46V54Nd5Q= -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= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= @@ -169,7 +158,6 @@ github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOi github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -205,7 +193,6 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -222,11 +209,6 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fergusstrange/embedded-postgres v1.10.0 h1:YnwF6xAQYmKLAXXrrRx4rHDLih47YJwVPvg8jeKfdNg= github.com/fergusstrange/embedded-postgres v1.10.0/go.mod h1:a008U8/Rws5FtIOTGYDYa7beVWsT3qVKyqExqYYjL+c= -github.com/fiatjaf/go-lnurl v1.0.0/go.mod h1:BqA8WXAOzntF7Z3EkVO7DfP4y5rhWUmJ/Bu9KBke+rs= -github.com/fiatjaf/lightningd-gjson-rpc v1.4.1 h1:J6TgNXO18Xc6udxj7/a6RhuusJR0lUiDlKJkJ/EdnNs= -github.com/fiatjaf/lightningd-gjson-rpc v1.4.1/go.mod h1:SQGA0qcY2qypaMXDQlE5V5+2MnLZzQ7NzfRsScliFeE= -github.com/fiatjaf/ln-decodepay v1.0.0 h1:1YUMjvLock+BicMNwoZ/OA3oG2ZYEaJ8AzdS6EGVMTQ= -github.com/fiatjaf/ln-decodepay v1.0.0/go.mod h1:/LWK+ZUa3i8MqbRjIMAiVQS2+NbhwKWlwib2n446cMQ= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= @@ -268,7 +250,6 @@ github.com/go-redis/cache/v8 v8.0.0-beta.11/go.mod h1:4wxD/neK+Uw+SteOR+AXtlyQYM github.com/go-redis/redis/v8 v8.0.0-beta.2/go.mod h1:o1M7JtsgfDYyv3o+gBn/jJ1LkqpnCrmil7PSppZGBak= github.com/go-redis/redis/v8 v8.0.0-beta.5/go.mod h1:Mm9EH/5UMRx680UIryN6rd5XFn/L7zORPqLV+1D5thQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= @@ -356,8 +337,6 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -367,13 +346,11 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaW github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 h1:ajue7SzQMywqRjg2fK7dcpc0QhFGpTR2plWfV4EZWR4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0/go.mod h1:r1hZAcvfFXuYmcKyCJI9wlyOPIZUJl6FCB8Cpca/NLE= -github.com/guiguan/caster v0.0.0-20191104051807-3736c4464f38/go.mod h1:giU/iWwQIOg/ND1ecR8raoyROxojrXL9osppnuI7MRY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -551,20 +528,14 @@ github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= -github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg= -github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0= github.com/lightninglabs/neutrino v0.12.1/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= github.com/lightninglabs/neutrino v0.13.0 h1:j3PKWEJCwqwMn/qLASz2j0IuCF6AumS9DaM0i0pM/nY= github.com/lightninglabs/neutrino v0.13.0/go.mod h1:GlKninWpRBbL7b8G0oQ36/8downfnFwKsr0hbRA6E/E= -github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI= github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display/go.mod h1:2oKOBU042GKFHrdbgGiKax4xVrFiZu51lhacUZQ9MnE= -github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1 h1:h1BsjPzWea790mAXISoiT/qr0JRcixTCDNLmjsDThSw= github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= -github.com/lightningnetwork/lnd v0.10.1-beta/go.mod h1:F9er1DrpOHdQVQBqYqyBqIFyl6q16xgBM8yTioHj2Cg= github.com/lightningnetwork/lnd v0.14.1-beta h1:v9hOlJ1xYivYQ634Nt71up5QDh8mXVpYh4MXUbbWTRw= github.com/lightningnetwork/lnd v0.14.1-beta/go.mod h1:o7zDwjZXm/bPP48qjwsqnZvvITyQl+fUv6UVoV4o+J8= -github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo= github.com/lightningnetwork/lnd/cert v1.1.0/go.mod h1:3MWXVLLPI0Mg0XETm9fT4N9Vyy/8qQLmaM5589bEggM= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= github.com/lightningnetwork/lnd/clock v1.1.0 h1:/yfVAwtPmdx45aQBoXQImeY7sOIEr7IXlImRMBOZ7GQ= @@ -575,7 +546,6 @@ github.com/lightningnetwork/lnd/healthcheck v1.2.0/go.mod h1:WSz3lsUjErJQZ3gb+zW github.com/lightningnetwork/lnd/kvdb v1.2.1 h1:QevYLPh6bh1SLIvlUaPbrpX/YMKzSPBfmGVvn2i8IlU= github.com/lightningnetwork/lnd/kvdb v1.2.1/go.mod h1:x+IpsuDynubjokUofavLXroeGfS/WrqUXXTK6vN/gp4= github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= -github.com/lightningnetwork/lnd/queue v1.0.3/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg= github.com/lightningnetwork/lnd/queue v1.1.0 h1:YpCJjlIvVxN/R7ww2aNiY8ex7U2fucZDLJ67tI3HFx8= github.com/lightningnetwork/lnd/queue v1.1.0/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg= github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= @@ -584,7 +554,6 @@ github.com/lightningnetwork/lnd/ticker v1.1.0/go.mod h1:ubqbSVCn6RlE0LazXuBr7/Zi github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY= github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= -github.com/lucsky/cuid v1.0.2/go.mod h1:QaaJqckboimOmhRSJXSx/+IT+VTfxfPGSo/6mfgUfmE= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -616,7 +585,6 @@ github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE= github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= @@ -704,7 +672,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= @@ -759,19 +726,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= -github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= -github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI= -github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= -github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= -github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= -github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= -github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= -github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= -github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao= -github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -798,7 +753,6 @@ github.com/uptrace/bun/driver/sqliteshim v1.0.21 h1:9cBMi0TosIeomB+N35mXk1Dh8cSK github.com/uptrace/bun/driver/sqliteshim v1.0.21/go.mod h1:xXz8OSqmYnRtxbEktkTjpmEZy7YDroVnX0RVpEs+5zE= github.com/uptrace/bun/extra/bundebug v1.0.21 h1:ILYJLqhx97I9WM+GbCYnF1QjNN63gpI2fvoj04atf8s= github.com/uptrace/bun/extra/bundebug v1.0.21/go.mod h1:n/2QqdhgXrLHDOYqezHmJzBNKf/2NNjE5xMi2fZ38iY= -github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -843,7 +797,6 @@ github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxt github.com/ziflex/lecho/v3 v3.1.0 h1:65bSzSc0yw7EEhi44lMnkOI877ZzbE7tGDWfYCQXZwI= github.com/ziflex/lecho/v3 v3.1.0/go.mod h1:dwQ6xCAKmSBHhwZ6XmiAiDptD7iklVkW7xQYGUncX0Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= @@ -1131,7 +1084,6 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1304,7 +1256,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/antage/eventsource.v1 v1.0.0-20150318155416-803f4c5af225/go.mod h1:SiXNRpUllqhl+GIw2V/BtKI7BUlz+uxov9vBFtXHqh8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/lib/service/bolt12.go b/lib/service/bolt12.go deleted file mode 100644 index d58ec4d0..00000000 --- a/lib/service/bolt12.go +++ /dev/null @@ -1,39 +0,0 @@ -package service - -import ( - "context" - "strconv" - "strings" - - "github.com/getAlby/lndhub.go/lnd" - "github.com/lightningnetwork/lnd/lnrpc" -) - -func (svc *LndhubService) FetchBolt12Invoice(ctx context.Context, offer, memo string, amt int64) (result *lnd.Bolt12, err error) { - return svc.LndClient.FetchBolt12Invoice(ctx, offer, memo, amt) -} - -func (svc *LndhubService) PayBolt12Invoice(ctx context.Context, invoice *lnd.Bolt12) (result *lnrpc.SendResponse, err error) { - return nil, err -} -func (svc *LndhubService) TransformBolt12(bolt12 *lnd.Bolt12) (*lnd.LNPayReq, error) { - - //todo see if CLN really can't return an int here - msatAmt, err := strconv.Atoi(strings.Trim(bolt12.AmountMsat, "msat")) - if err != nil { - return nil, err - } - return &lnd.LNPayReq{ - PayReq: &lnrpc.PayReq{ - Destination: bolt12.NodeID, - PaymentHash: bolt12.PaymentHash, - NumSatoshis: int64(msatAmt / lnd.MSAT_PER_SAT), - Timestamp: bolt12.Timestamp, - Expiry: bolt12.RelativeExpiry, - Description: bolt12.Description, - DescriptionHash: "", // not supported by bolt 12? - NumMsat: int64(msatAmt), - }, - Keysend: false, - }, nil -} diff --git a/lnd/c-lightning.go b/lnd/c-lightning.go deleted file mode 100644 index fd3df7b8..00000000 --- a/lnd/c-lightning.go +++ /dev/null @@ -1,277 +0,0 @@ -package lnd - -import ( - "context" - "encoding/hex" - "fmt" - "reflect" - "time" - - cln "github.com/fiatjaf/lightningd-gjson-rpc" - "github.com/gofrs/uuid" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/tidwall/gjson" - "google.golang.org/grpc" -) - -const ( - MSAT_PER_SAT = 1000 -) - -type CLNClient struct { - client *cln.Client - handler *InvoiceHandler -} - -type InvoiceHandler struct { - invoiceChan chan (*lnrpc.Invoice) -} -type CLNClientOptions struct { - SparkUrl string - SparkToken string -} - -func NewCLNClient(options CLNClientOptions) (*CLNClient, error) { - handler := &InvoiceHandler{ - invoiceChan: make(chan *lnrpc.Invoice), - } - return &CLNClient{ - handler: handler, - client: &cln.Client{ - PaymentHandler: handler.Handle, - CallTimeout: 24 * 3600 * time.Second, //should be infinite actually - //Path: "", - //LightningDir: "", - SparkURL: options.SparkUrl, - SparkToken: options.SparkToken, - }, - }, nil -} - -//todo handle errors? -func (cln *CLNClient) Recv() (invoice *lnrpc.Invoice, err error) { - return <-cln.handler.invoiceChan, nil -} - -func (handler *InvoiceHandler) Handle(res gjson.Result) { - //todo missing or wrong fields - invoice := &lnrpc.Invoice{ - Memo: res.Get("description").String(), - RPreimage: []byte(res.Get("payment_preimage").String()), - RHash: []byte(res.Get("payment_hash").String()), - Value: res.Get("amount_msat").Int() / MSAT_PER_SAT, - ValueMsat: res.Get("amount_msat").Int(), - Settled: true, - CreationDate: 0, - SettleDate: res.Get("paid_at").Int(), - PaymentRequest: res.Get("bolt11").String(), - DescriptionHash: []byte{}, - Expiry: 0, - FallbackAddr: "", - CltvExpiry: 0, - RouteHints: []*lnrpc.RouteHint{}, - Private: false, - AddIndex: res.Get("pay_index").Uint(), - SettleIndex: 0, - AmtPaid: res.Get("amount_msat").Int() / MSAT_PER_SAT, - AmtPaidSat: res.Get("amount_msat").Int() / MSAT_PER_SAT, - AmtPaidMsat: res.Get("amount_msat").Int(), - State: lnrpc.Invoice_SETTLED, - Htlcs: []*lnrpc.InvoiceHTLC{}, - Features: map[uint32]*lnrpc.Feature{}, - IsKeysend: false, - PaymentAddr: []byte{}, - IsAmp: false, - AmpInvoiceState: map[string]*lnrpc.AMPInvoiceState{}, - } - handler.invoiceChan <- invoice -} - -func (cl *CLNClient) ListChannels(ctx context.Context, req *lnrpc.ListChannelsRequest, options ...grpc.CallOption) (*lnrpc.ListChannelsResponse, error) { - result, err := cl.client.Call("listpeers") - if err != nil { - return nil, err - } - channels := []*lnrpc.Channel{} - for _, peer := range result.Get("peers").Array() { - for _, ch := range peer.Get("channels").Array() { - //todo fill in missing fields - channels = append(channels, &lnrpc.Channel{ - Active: ch.Get("state").String() == "CHANNELD_NORMAL", - RemotePubkey: peer.Get("id").String(), - ChannelPoint: "", - ChanId: 0, - Capacity: ch.Get("msatoshi_total").Int() / MSAT_PER_SAT, - LocalBalance: ch.Get("msatoshi_to_us").Int() / MSAT_PER_SAT, - RemoteBalance: ch.Get("receivable_msatoshi").Int() / MSAT_PER_SAT, - CommitFee: 0, - CommitWeight: 0, - FeePerKw: 0, - UnsettledBalance: 0, - TotalSatoshisSent: 0, - TotalSatoshisReceived: 0, - NumUpdates: 0, - PendingHtlcs: []*lnrpc.HTLC{}, - CsvDelay: 0, - Private: false, - Initiator: false, - ChanStatusFlags: "", - LocalChanReserveSat: 0, - RemoteChanReserveSat: 0, - StaticRemoteKey: false, - CommitmentType: 0, - Lifetime: 0, - Uptime: 0, - CloseAddress: "", - PushAmountSat: 0, - ThawHeight: 0, - LocalConstraints: &lnrpc.ChannelConstraints{}, - RemoteConstraints: &lnrpc.ChannelConstraints{}, - }) - } - } - return &lnrpc.ListChannelsResponse{ - Channels: channels, - }, nil -} - -func (cl *CLNClient) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest, options ...grpc.CallOption) (*lnrpc.SendResponse, error) { - //todo add other options - result, err := cl.client.Call("pay", req.PaymentRequest) - if err != nil { - return nil, err - } - //todo failure modes - return &lnrpc.SendResponse{ - PaymentError: "", - PaymentPreimage: []byte(result.Get("payment_preimage").String()), - PaymentHash: []byte(result.Get("payment_hash").String()), - PaymentRoute: &lnrpc.Route{ - TotalFees: result.Get("msatoshi_sent").Int()/MSAT_PER_SAT - result.Get("msatoshi").Int()/MSAT_PER_SAT, - TotalAmt: result.Get("msatoshi_sent").Int() / MSAT_PER_SAT, - }, - }, nil -} - -func (cl *CLNClient) FetchBolt12Invoice(ctx context.Context, offer, memo string, amount int64) (result *Bolt12, err error) { - res, err := cl.client.CallNamed("fetchinvoice", "offer", offer, "msatoshi", amount*MSAT_PER_SAT, "payer_note", memo) - if err != nil { - return nil, err - } - return cl.DecodeBolt12(ctx, res.Get("invoice").String()) -} - -func (cl *CLNClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) { - uuid, err := uuid.NewV4() - if err != nil { - return nil, err - } - mSatAmt := MSAT_PER_SAT * req.Value - methodToCall := "invoice" - arg := req.Memo - if !reflect.DeepEqual(req.DescriptionHash, []byte("")) { - methodToCall = "invoicewithdescriptionhash" - arg = hex.EncodeToString(req.DescriptionHash) - } - res, err := cl.client.Call(methodToCall, mSatAmt, uuid.String(), arg) - if err != nil { - return nil, err - } - return &lnrpc.AddInvoiceResponse{ - RHash: []byte(res.Get("payment_hash").String()), - PaymentRequest: res.Get("bolt11").String(), - }, nil -} - -// Todo here: make CLNClient implement the interface (Recv()) -// This method will read from a channel or block -// The handler function publishes on the channel on a received invoice -// set the client's invoice index to the one from req -func (cl *CLNClient) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) { - cl.client.LastInvoiceIndex = int(req.AddIndex) - cl.client.ListenForInvoices() - return cl, nil -} - -func (cl *CLNClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) { - result, err := cl.client.Call("getinfo") - if err != nil { - return nil, err - } - uris := []string{} - for _, addr := range result.Get("address").Array() { - uris = append(uris, fmt.Sprintf("%s@%s:%s", result.Get("id").String(), addr.Get("address").String(), addr.Get("port").String())) - } - - return &lnrpc.GetInfoResponse{ - Version: result.Get("version").String(), - IdentityPubkey: result.Get("id").String(), - Alias: result.Get("alias").String(), - Color: result.Get("color").String(), - NumPendingChannels: uint32(result.Get("num_pending_channels").Int()), - NumActiveChannels: uint32(result.Get("num_active_channels").Int()), - NumInactiveChannels: uint32(result.Get("num_inactive_channels").Int()), - NumPeers: uint32(result.Get("num_peers").Int()), - BlockHeight: uint32(result.Get("blockheight").Int()), - // workaround - SyncedToChain: true, - SyncedToGraph: true, - Testnet: false, - Chains: []*lnrpc.Chain{ - { - Chain: "bitcoin", - Network: result.Get("network").String(), - }, - }, - Uris: uris, - }, nil -} - -func (cl *CLNClient) DecodeBolt11(ctx context.Context, bolt11 string, options ...grpc.CallOption) (*lnrpc.PayReq, error) { - result, err := cl.client.Call("decode", bolt11) - if err != nil { - return nil, err - } - return &lnrpc.PayReq{ - Destination: result.Get("payee").String(), - PaymentHash: result.Get("payment_hash").String(), - NumSatoshis: result.Get("msatoshi").Int() / MSAT_PER_SAT, - Timestamp: result.Get("created_at").Time().Unix(), - Expiry: result.Get("expiry").Int(), - Description: result.Get("description").String(), - DescriptionHash: result.Get("description_hash").String(), - NumMsat: result.Get("msatoshi").Int(), - }, nil -} - -func (cl *CLNClient) DecodeBolt12(ctx context.Context, bolt12 string) (decoded *Bolt12, err error) { - result, err := cl.client.Call("decode", bolt12) - if err != nil { - return nil, err - } - chains := []string{} - for _, ch := range result.Get("chains").Array() { - chains = append(chains, ch.String()) - } - return &Bolt12{ - Type: result.Get("type").String(), - OfferID: result.Get("offer_id").String(), - Chains: chains, - Description: result.Get("description").String(), - NodeID: result.Get("node_id").String(), - Signature: result.Get("signature").String(), - Vendor: result.Get("vendor").String(), - Valid: result.Get("valid").Bool(), - AmountMsat: result.Get("amount_msat").String(), - Features: result.Get("features").String(), - PayerKey: result.Get("payer_key").String(), - PayerInfo: result.Get("payer_info").String(), - PayerNote: result.Get("payer_note").String(), - Timestamp: result.Get("timestamp").Int(), - CreatedAt: result.Get("created_at").Int(), - PaymentHash: result.Get("payment_hash").String(), - RelativeExpiry: result.Get("relative_expiry").Int(), - MinFinalCltvExpiry: result.Get("min_final_cltv_expiry").Int(), - Encoded: bolt12, - }, nil -} diff --git a/lnd/interface.go b/lnd/interface.go index 8a995b19..0c3733b4 100644 --- a/lnd/interface.go +++ b/lnd/interface.go @@ -13,35 +13,9 @@ type LightningClientWrapper interface { AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) - DecodeBolt12(ctx context.Context, bolt12 string) (*Bolt12, error) - FetchBolt12Invoice(ctx context.Context, offer, memo string, amount int64) (*Bolt12, error) DecodeBolt11(ctx context.Context, bolt11 string, options ...grpc.CallOption) (*lnrpc.PayReq, error) } type SubscribeInvoicesWrapper interface { Recv() (*lnrpc.Invoice, error) } - -//Bolt12 can be both an offer or an invoice -//depending on Type -type Bolt12 struct { - Type string `json:"type"` - OfferID string `json:"offer_id"` - Chains []string `json:"chains"` - Description string `json:"description"` - NodeID string `json:"node_id"` - Signature string `json:"signature"` - Vendor string `json:"vendor"` - Valid bool `json:"valid"` - AmountMsat string `json:"amount_msat"` - Features string `json:"features"` - PayerKey string `json:"payer_key"` - PayerInfo string `json:"payer_info"` - PayerNote string `json:"payer_note"` - Timestamp int64 `json:"timestamp"` - CreatedAt int64 `json:"created_at"` - PaymentHash string `json:"payment_hash"` - RelativeExpiry int64 `json:"relative_expiry"` - MinFinalCltvExpiry int64 `json:"min_final_cltv_expiry"` - Encoded string `json:"encoded"` -} diff --git a/lnd/lnd.go b/lnd/lnd.go index 4ff95fa4..bdfe887d 100644 --- a/lnd/lnd.go +++ b/lnd/lnd.go @@ -6,7 +6,6 @@ import ( "crypto/x509" "encoding/hex" "errors" - "fmt" "io/ioutil" "github.com/lightningnetwork/lnd/lnrpc" @@ -122,10 +121,3 @@ func (wrapper *LNDWrapper) DecodeBolt11(ctx context.Context, bolt11 string, opti PayReq: bolt11, }) } - -func (wrapper *LNDWrapper) DecodeBolt12(ctx context.Context, bolt12 string) (*Bolt12, error) { - return nil, fmt.Errorf("Bolt12 is not supported yet, LL get on with it!") -} -func (wrapper *LNDWrapper) FetchBolt12Invoice(ctx context.Context, offer, memo string, amount int64) (*Bolt12, error) { - return nil, fmt.Errorf("Bolt12 is not supported yet, LL get on with it!") -} diff --git a/main.go b/main.go index 6bd859f3..91dae376 100644 --- a/main.go +++ b/main.go @@ -103,17 +103,10 @@ func main() { } // Init new LND client - //lndClient, err := lnd.NewLNDclient(lnd.LNDoptions{ - // Address: c.LNDAddress, - // MacaroonHex: c.LNDMacaroonHex, - // CertHex: c.LNDCertHex, - //}) - - //Init new CLN client - //re-use other config to not make things overcomplicated - lndClient, err := lnd.NewCLNClient(lnd.CLNClientOptions{ - SparkUrl: c.LNDAddress, - SparkToken: c.LNDMacaroonHex, + lndClient, err := lnd.NewLNDclient(lnd.LNDoptions{ + Address: c.LNDAddress, + MacaroonHex: c.LNDMacaroonHex, + CertHex: c.LNDCertHex, }) if err != nil { e.Logger.Fatalf("Error initializing the LND connection: %v", err) @@ -150,9 +143,6 @@ func main() { secured.GET("/balance", controllers.NewBalanceController(svc).Balance) secured.GET("/getinfo", controllers.NewGetInfoController(svc).GetInfo, createCacheClient().Middleware()) securedWithStrictRateLimit.POST("/keysend", controllers.NewKeySendController(svc).KeySend) - secured.GET("/getinfo", controllers.NewGetInfoController(svc).GetInfo) - secured.POST("/bolt12/fetchinvoice", controllers.NewBolt12Controller(svc).FetchInvoice) - secured.POST("/bolt12/pay", controllers.NewBolt12Controller(svc).PayBolt12) // These endpoints are currently not supported and we return a blank response for backwards compatibility blankController := controllers.NewBlankController(svc) @@ -167,13 +157,11 @@ func main() { e.GET("/static/css/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) e.GET("/static/img/*", echo.WrapHandler(http.FileServer(http.FS(staticContent)))) - e.GET("/bolt12/decode/:offer", controllers.NewBolt12Controller(svc).Decode) //invoice streaming //Authentication should be done through the query param because this is a websocket e.GET("/invoices/stream", controllers.NewInvoiceStreamController(svc).StreamInvoices) // Subscribe to LND invoice updates in the background - // CLN: todo: re-write logic go svc.InvoiceUpdateSubscription(context.Background()) // Start server From a5ae01a5f2ea39f00aa62d2f862faa55c992e5a7 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 6 Apr 2022 18:00:04 +0200 Subject: [PATCH 30/40] add proper pubsub --- controllers/invoicestream.ctrl.go | 4 ++- lib/service/invoicesubscription.go | 4 +-- lib/service/pubsub.go | 48 ++++++++++++++++++++++++++++++ lib/service/service.go | 12 ++++---- main.go | 17 +++++------ 5 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 lib/service/pubsub.go diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index bf939f34..9b4d2f04 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -33,7 +33,8 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error return err } invoiceChan := make(chan models.Invoice) - controller.svc.InvoiceSubscribers[userId] = invoiceChan + reqId := c.Response().Header().Get(echo.HeaderXRequestID) + controller.svc.InvoicePubSub.Subscribe(reqId, userId, invoiceChan) ctx := c.Request().Context() upgrader := websocket.Upgrader{} upgrader.CheckOrigin = func(r *http.Request) bool { return true } @@ -80,5 +81,6 @@ SocketLoop: } } } + controller.svc.InvoicePubSub.Unsubscribe(reqId, userId) return nil } diff --git a/lib/service/invoicesubscription.go b/lib/service/invoicesubscription.go index bf1c438d..e4ae58ba 100644 --- a/lib/service/invoicesubscription.go +++ b/lib/service/invoicesubscription.go @@ -97,9 +97,7 @@ func (svc *LndhubService) ProcessInvoiceUpdate(ctx context.Context, rawInvoice * svc.Logger.Errorf("Failed to commit DB transaction user_id:%v invoice_id:%v %v", invoice.UserID, invoice.ID, err) return err } - if sub, ok := svc.InvoiceSubscribers[invoice.UserID]; ok { - sub <- invoice - } + svc.InvoicePubSub.Publish(invoice.UserID, invoice) return nil } diff --git a/lib/service/pubsub.go b/lib/service/pubsub.go new file mode 100644 index 00000000..e38e10f6 --- /dev/null +++ b/lib/service/pubsub.go @@ -0,0 +1,48 @@ +package service + +import ( + "sync" + + "github.com/getAlby/lndhub.go/db/models" +) + +type Pubsub struct { + mu sync.RWMutex + subs map[int64]map[string]chan models.Invoice +} + +func NewPubsub() *Pubsub { + ps := &Pubsub{} + ps.subs = make(map[int64]map[string]chan models.Invoice) + return ps +} + +func (ps *Pubsub) Subscribe(id string, topic int64, ch chan models.Invoice) { + ps.mu.Lock() + defer ps.mu.Unlock() + + ps.subs[topic][id] = ch +} + +func (ps *Pubsub) Unsubscribe(id string, topic int64) { + ps.mu.Lock() + defer ps.mu.Unlock() + delete(ps.subs[topic], id) +} + +func (ps *Pubsub) Publish(topic int64, msg models.Invoice) { + ps.mu.RLock() + defer ps.mu.RUnlock() + + for _, ch := range ps.subs[topic] { + ch <- msg + } +} + +func (ps *Pubsub) CloseAll() { + for _, subs := range ps.subs { + for _, ch := range subs { + close(ch) + } + } +} diff --git a/lib/service/service.go b/lib/service/service.go index 03484c0e..06b32387 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -17,12 +17,12 @@ import ( const alphaNumBytes = random.Alphanumeric type LndhubService struct { - Config *Config - DB *bun.DB - LndClient lnd.LightningClientWrapper - Logger *lecho.Logger - IdentityPubkey string - InvoiceSubscribers map[int64]chan models.Invoice + Config *Config + DB *bun.DB + LndClient lnd.LightningClientWrapper + Logger *lecho.Logger + IdentityPubkey string + InvoicePubSub *Pubsub } func (svc *LndhubService) GenerateToken(ctx context.Context, login, password, inRefreshToken string) (accessToken, refreshToken string, err error) { diff --git a/main.go b/main.go index 2b945d84..edab4397 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,6 @@ import ( "github.com/getAlby/lndhub.go/controllers" "github.com/getAlby/lndhub.go/db" "github.com/getAlby/lndhub.go/db/migrations" - "github.com/getAlby/lndhub.go/db/models" "github.com/getAlby/lndhub.go/lib" "github.com/getAlby/lndhub.go/lib/responses" "github.com/getAlby/lndhub.go/lib/service" @@ -119,12 +118,12 @@ func main() { logger.Infof("Connected to LND: %s - %s", getInfo.Alias, getInfo.IdentityPubkey) svc := &service.LndhubService{ - Config: c, - DB: dbConn, - LndClient: lndClient, - Logger: logger, - IdentityPubkey: getInfo.IdentityPubkey, - InvoiceSubscribers: map[int64]chan models.Invoice{}, + Config: c, + DB: dbConn, + LndClient: lndClient, + Logger: logger, + IdentityPubkey: getInfo.IdentityPubkey, + InvoicePubSub: service.NewPubsub(), } strictRateLimitMiddleware := createRateLimitMiddleware(c.StrictRateLimit, c.BurstRateLimit) @@ -204,9 +203,7 @@ func main() { e.Logger.Fatal(err) } //close all channels - for _, sub := range svc.InvoiceSubscribers { - close(sub) - } + svc.InvoicePubSub.CloseAll() if echoPrometheus != nil { if err := echoPrometheus.Shutdown(ctx); err != nil { e.Logger.Fatal(err) From 55ee968174f674ae5fbd0d082bd42e3d50d4d213 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 7 Apr 2022 11:31:10 +0200 Subject: [PATCH 31/40] init subscription map if nil --- lib/service/pubsub.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/service/pubsub.go b/lib/service/pubsub.go index e38e10f6..ac43857a 100644 --- a/lib/service/pubsub.go +++ b/lib/service/pubsub.go @@ -20,7 +20,9 @@ func NewPubsub() *Pubsub { func (ps *Pubsub) Subscribe(id string, topic int64, ch chan models.Invoice) { ps.mu.Lock() defer ps.mu.Unlock() - + if ps.subs[topic] == nil { + ps.subs[topic] = make(map[string]chan models.Invoice) + } ps.subs[topic][id] = ch } From bc57cd04c2f3eb7ea62d58b3ae00a3e9a986d6ab Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 7 Apr 2022 12:09:13 +0200 Subject: [PATCH 32/40] handle close messages --- controllers/invoicestream.ctrl.go | 19 +++++++++++++++---- lib/service/pubsub.go | 12 +++--------- main.go | 2 -- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 9b4d2f04..42be2ef3 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -35,7 +35,6 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error invoiceChan := make(chan models.Invoice) reqId := c.Response().Header().Get(echo.HeaderXRequestID) controller.svc.InvoicePubSub.Subscribe(reqId, userId, invoiceChan) - ctx := c.Request().Context() upgrader := websocket.Upgrader{} upgrader.CheckOrigin = func(r *http.Request) bool { return true } ticker := time.NewTicker(30 * time.Second) @@ -44,6 +43,19 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error return err } defer ws.Close() + + //start listening for close messages + done := make(chan struct{}) + go func() { + defer close(done) + for { + _, _, err := ws.ReadMessage() + if err != nil { + return + } + } + }() + //start with keepalive message err = ws.WriteJSON(&InvoiceEventWrapper{Type: "keepalive"}) if err != nil { @@ -53,7 +65,7 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error SocketLoop: for { select { - case <-ctx.Done(): + case <-done: break SocketLoop case <-ticker.C: err := ws.WriteJSON(&InvoiceEventWrapper{Type: "keepalive"}) @@ -81,6 +93,5 @@ SocketLoop: } } } - controller.svc.InvoicePubSub.Unsubscribe(reqId, userId) - return nil + return controller.svc.InvoicePubSub.Unsubscribe(reqId, userId) } diff --git a/lib/service/pubsub.go b/lib/service/pubsub.go index ac43857a..b5faad8f 100644 --- a/lib/service/pubsub.go +++ b/lib/service/pubsub.go @@ -26,10 +26,12 @@ func (ps *Pubsub) Subscribe(id string, topic int64, ch chan models.Invoice) { ps.subs[topic][id] = ch } -func (ps *Pubsub) Unsubscribe(id string, topic int64) { +func (ps *Pubsub) Unsubscribe(id string, topic int64) error { ps.mu.Lock() defer ps.mu.Unlock() + close(ps.subs[topic][id]) delete(ps.subs[topic], id) + return nil } func (ps *Pubsub) Publish(topic int64, msg models.Invoice) { @@ -40,11 +42,3 @@ func (ps *Pubsub) Publish(topic int64, msg models.Invoice) { ch <- msg } } - -func (ps *Pubsub) CloseAll() { - for _, subs := range ps.subs { - for _, ch := range subs { - close(ch) - } - } -} diff --git a/main.go b/main.go index edab4397..ed2487b9 100644 --- a/main.go +++ b/main.go @@ -202,8 +202,6 @@ func main() { if err := e.Shutdown(ctx); err != nil { e.Logger.Fatal(err) } - //close all channels - svc.InvoicePubSub.CloseAll() if echoPrometheus != nil { if err := echoPrometheus.Shutdown(ctx); err != nil { e.Logger.Fatal(err) From 87532a00622fcc09baa4d7b8199e132926ec5a94 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Thu, 7 Apr 2022 15:22:17 +0200 Subject: [PATCH 33/40] always build docker images on every branch --- .github/workflows/workflow.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index a71b3204..c84df672 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -1,8 +1,6 @@ name: Docker build & push on: push: - branches: - - main release: types: [published] jobs: @@ -30,6 +28,7 @@ jobs: token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # Always update dev environment - name: Update dev environment + if: ${{ github.ref == 'refs/heads/main' }} uses: fjogeleit/yaml-update-action@v0.7.0 with: valueFile: 'alby-simnet-deployment/values.yaml' From a6c6a14484d609d5a1d4038602c37c8c75755247 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 12 Apr 2022 12:17:59 +0200 Subject: [PATCH 34/40] start integration tests for ws --- integration_tests/util.go | 1 + integration_tests/websocket_test.go | 124 ++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 integration_tests/websocket_test.go diff --git a/integration_tests/util.go b/integration_tests/util.go index 7763339c..33c9dc3b 100644 --- a/integration_tests/util.go +++ b/integration_tests/util.go @@ -92,6 +92,7 @@ func LndHubTestServiceInit(lndClientMock lnd.LightningClientWrapper) (svc *servi } svc.IdentityPubkey = getInfo.IdentityPubkey + svc.InvoicePubSub = service.NewPubsub() return svc, nil } diff --git a/integration_tests/websocket_test.go b/integration_tests/websocket_test.go new file mode 100644 index 00000000..5d9c051a --- /dev/null +++ b/integration_tests/websocket_test.go @@ -0,0 +1,124 @@ +package integration_tests + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/getAlby/lndhub.go/controllers" + "github.com/getAlby/lndhub.go/lib" + "github.com/getAlby/lndhub.go/lib/responses" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/getAlby/lndhub.go/lib/tokens" + "github.com/getAlby/lndhub.go/lnd" + "github.com/go-playground/validator/v10" + "github.com/gorilla/websocket" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type KeepAlive struct { + Type string +} + +type WebSocketTestSuite struct { + TestSuite + fundingClient *lnd.LNDWrapper + service *service.LndhubService + userLogin ExpectedCreateUserResponseBody + userToken string + invoiceUpdateSubCancelFn context.CancelFunc + websocketServer *httptest.Server +} +type WsHandler struct { + handler echo.HandlerFunc +} + +func (h *WsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + e := echo.New() + c := e.NewContext(r, w) + + err := h.handler(c) + if err != nil { + _, _ = w.Write([]byte(err.Error())) + } +} + +func (suite *WebSocketTestSuite) SetupSuite() { + lndClient, err := lnd.NewLNDclient(lnd.LNDoptions{ + Address: lnd2RegtestAddress, + MacaroonHex: lnd2RegtestMacaroonHex, + }) + if err != nil { + log.Fatalf("Error setting up funding client: %v", err) + } + suite.fundingClient = lndClient + + svc, err := LndHubTestServiceInit(nil) + if err != nil { + log.Fatalf("Error initializing test service: %v", err) + } + users, userTokens, err := createUsers(svc, 1) + if err != nil { + log.Fatalf("Error creating test users: %v", err) + } + // Subscribe to LND invoice updates in the background + // store cancel func to be called in tear down suite + ctx, cancel := context.WithCancel(context.Background()) + suite.invoiceUpdateSubCancelFn = cancel + go svc.InvoiceUpdateSubscription(ctx) + suite.service = svc + e := echo.New() + + e.HTTPErrorHandler = responses.HTTPErrorHandler + e.Validator = &lib.CustomValidator{Validator: validator.New()} + suite.echo = e + assert.Equal(suite.T(), 1, len(users)) + assert.Equal(suite.T(), 1, len(userTokens)) + suite.userLogin = users[0] + suite.userToken = userTokens[0] + suite.echo.Use(tokens.Middleware([]byte(suite.service.Config.JWTSecret))) + suite.echo.POST("/addinvoice", controllers.NewAddInvoiceController(suite.service).AddInvoice) + + //websocket server + h := WsHandler{handler: controllers.NewInvoiceStreamController(suite.service).StreamInvoices} + server := httptest.NewServer(http.HandlerFunc(h.ServeHTTP)) + suite.websocketServer = server +} + +func (suite *WebSocketTestSuite) TestWebSocket() { + + //start listening to websocket + wsURL := "ws" + strings.TrimPrefix(suite.websocketServer.URL, "http") + fmt.Sprintf("?token=%s", suite.userToken) + ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil) + assert.NoError(suite.T(), err, err) + //assert that there is a subscription + _, msg, err := ws.ReadMessage() + assert.NoError(suite.T(), err, err) + keepAlive := KeepAlive{} + err = json.Unmarshal([]byte(msg), &keepAlive) + assert.NoError(suite.T(), err, err) + assert.Equal(suite.T(), "keepalive", keepAlive.Type) + //assert that there are no more subscriptions + + //create subscription, create invoice, pay invoice, assert that invoice is received + //create 2nd subscription, create invoice, pay invoice, assert that invoice is received twice + //assert that there are 2 subscriptions + //close 1 subscription, assert that there is a single subscription left, assert that the existing sub still receives their invoices + //create subs for 2 different users, assert that they each get their own invoice updates +} + +func (suite *WebSocketTestSuite) TearDownSuite() { + suite.invoiceUpdateSubCancelFn() + suite.websocketServer.Close() +} + +func TestWebSocketSuite(t *testing.T) { + suite.Run(t, new(WebSocketTestSuite)) +} From 8be8471da0f2d247a44d7d8a7acb081a245c5b96 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 12 Apr 2022 15:22:24 +0200 Subject: [PATCH 35/40] add all integration tests --- controllers/invoicestream.ctrl.go | 7 +- .../expected_requests_and_responses.go | 4 + integration_tests/websocket_test.go | 118 ++++++++++++++++-- lib/service/pubsub.go | 7 +- 4 files changed, 121 insertions(+), 15 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 42be2ef3..2ec80bc7 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -33,13 +33,13 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error return err } invoiceChan := make(chan models.Invoice) - reqId := c.Response().Header().Get(echo.HeaderXRequestID) - controller.svc.InvoicePubSub.Subscribe(reqId, userId, invoiceChan) + subId := controller.svc.InvoicePubSub.Subscribe(userId, invoiceChan) upgrader := websocket.Upgrader{} upgrader.CheckOrigin = func(r *http.Request) bool { return true } ticker := time.NewTicker(30 * time.Second) ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) if err != nil { + controller.svc.InvoicePubSub.Unsubscribe(subId, userId) return err } defer ws.Close() @@ -60,6 +60,7 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error err = ws.WriteJSON(&InvoiceEventWrapper{Type: "keepalive"}) if err != nil { controller.svc.Logger.Error(err) + controller.svc.InvoicePubSub.Unsubscribe(subId, userId) return err } SocketLoop: @@ -93,5 +94,5 @@ SocketLoop: } } } - return controller.svc.InvoicePubSub.Unsubscribe(reqId, userId) + return controller.svc.InvoicePubSub.Unsubscribe(subId, userId) } diff --git a/integration_tests/expected_requests_and_responses.go b/integration_tests/expected_requests_and_responses.go index b3a61e12..5753b264 100644 --- a/integration_tests/expected_requests_and_responses.go +++ b/integration_tests/expected_requests_and_responses.go @@ -90,6 +90,10 @@ type ExpectedIncomingInvoice struct { Amount int64 `json:"amt"` IsPaid bool `json:"ispaid"` } +type ExpectedInvoiceEventWrapper struct { + Type string `json:"type"` + Invoice *ExpectedIncomingInvoice `json:"invoice,omitempty"` +} type ExpectedPayInvoiceRequestBody struct { Invoice string `json:"invoice" validate:"required"` diff --git a/integration_tests/websocket_test.go b/integration_tests/websocket_test.go index 5d9c051a..35f094c6 100644 --- a/integration_tests/websocket_test.go +++ b/integration_tests/websocket_test.go @@ -19,6 +19,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/gorilla/websocket" "github.com/labstack/echo/v4" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -33,8 +34,11 @@ type WebSocketTestSuite struct { service *service.LndhubService userLogin ExpectedCreateUserResponseBody userToken string + userToken2 string invoiceUpdateSubCancelFn context.CancelFunc websocketServer *httptest.Server + wsUrl string + wsUrl2 string } type WsHandler struct { handler echo.HandlerFunc @@ -64,7 +68,7 @@ func (suite *WebSocketTestSuite) SetupSuite() { if err != nil { log.Fatalf("Error initializing test service: %v", err) } - users, userTokens, err := createUsers(svc, 1) + users, userTokens, err := createUsers(svc, 2) if err != nil { log.Fatalf("Error creating test users: %v", err) } @@ -79,10 +83,11 @@ func (suite *WebSocketTestSuite) SetupSuite() { e.HTTPErrorHandler = responses.HTTPErrorHandler e.Validator = &lib.CustomValidator{Validator: validator.New()} suite.echo = e - assert.Equal(suite.T(), 1, len(users)) - assert.Equal(suite.T(), 1, len(userTokens)) + assert.Equal(suite.T(), 2, len(users)) + assert.Equal(suite.T(), 2, len(userTokens)) suite.userLogin = users[0] suite.userToken = userTokens[0] + suite.userToken2 = userTokens[1] suite.echo.Use(tokens.Middleware([]byte(suite.service.Config.JWTSecret))) suite.echo.POST("/addinvoice", controllers.NewAddInvoiceController(suite.service).AddInvoice) @@ -90,28 +95,121 @@ func (suite *WebSocketTestSuite) SetupSuite() { h := WsHandler{handler: controllers.NewInvoiceStreamController(suite.service).StreamInvoices} server := httptest.NewServer(http.HandlerFunc(h.ServeHTTP)) suite.websocketServer = server + suite.wsUrl = "ws" + strings.TrimPrefix(suite.websocketServer.URL, "http") + fmt.Sprintf("?token=%s", suite.userToken) + suite.wsUrl2 = "ws" + strings.TrimPrefix(suite.websocketServer.URL, "http") + fmt.Sprintf("?token=%s", suite.userToken2) } func (suite *WebSocketTestSuite) TestWebSocket() { //start listening to websocket - wsURL := "ws" + strings.TrimPrefix(suite.websocketServer.URL, "http") + fmt.Sprintf("?token=%s", suite.userToken) - ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil) + ws, _, err := websocket.DefaultDialer.Dial(suite.wsUrl, nil) assert.NoError(suite.T(), err, err) - //assert that there is a subscription _, msg, err := ws.ReadMessage() assert.NoError(suite.T(), err, err) keepAlive := KeepAlive{} err = json.Unmarshal([]byte(msg), &keepAlive) assert.NoError(suite.T(), err, err) assert.Equal(suite.T(), "keepalive", keepAlive.Type) - //assert that there are no more subscriptions - //create subscription, create invoice, pay invoice, assert that invoice is received + // create incoming invoice and fund account + invoice := suite.createAddInvoiceReq(1000, "integration test websocket 1", suite.userToken) + sendPaymentRequest := lnrpc.SendRequest{ + PaymentRequest: invoice.PayReq, + FeeLimit: nil, + } + _, err = suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest) + assert.NoError(suite.T(), err) + + _, msg, err = ws.ReadMessage() + assert.NoError(suite.T(), err, err) + event := ExpectedInvoiceEventWrapper{} + err = json.Unmarshal([]byte(msg), &event) + assert.NoError(suite.T(), err, err) + assert.Equal(suite.T(), event.Type, "invoice") + assert.Equal(suite.T(), int64(1000), event.Invoice.Amount) + assert.Equal(suite.T(), "integration test websocket 1", event.Invoice.Description) //create 2nd subscription, create invoice, pay invoice, assert that invoice is received twice - //assert that there are 2 subscriptions - //close 1 subscription, assert that there is a single subscription left, assert that the existing sub still receives their invoices + //start listening to websocket + ws2, _, err := websocket.DefaultDialer.Dial(suite.wsUrl, nil) + assert.NoError(suite.T(), err, err) + //read keepalive msg + _, _, err = ws2.ReadMessage() + assert.NoError(suite.T(), err, err) + invoice = suite.createAddInvoiceReq(1000, "integration test websocket 2", suite.userToken) + sendPaymentRequest = lnrpc.SendRequest{ + PaymentRequest: invoice.PayReq, + FeeLimit: nil, + } + _, err = suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest) + assert.NoError(suite.T(), err) + _, msg1, err := ws.ReadMessage() + assert.NoError(suite.T(), err, err) + _, msg2, err := ws2.ReadMessage() + assert.NoError(suite.T(), err) + + event1 := ExpectedInvoiceEventWrapper{} + err = json.Unmarshal([]byte(msg1), &event1) + assert.NoError(suite.T(), err) + event2 := ExpectedInvoiceEventWrapper{} + err = json.Unmarshal([]byte(msg2), &event2) + assert.NoError(suite.T(), err, err) + assert.Equal(suite.T(), "integration test websocket 2", event1.Invoice.Description) + assert.Equal(suite.T(), "integration test websocket 2", event2.Invoice.Description) + //close 1 subscription, assert that the existing sub still receives their invoices + ws.Close() + invoice = suite.createAddInvoiceReq(1000, "integration test websocket 3", suite.userToken) + sendPaymentRequest = lnrpc.SendRequest{ + PaymentRequest: invoice.PayReq, + FeeLimit: nil, + } + _, err = suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest) + assert.NoError(suite.T(), err) + _, msg2, err = ws2.ReadMessage() + assert.NoError(suite.T(), err) + event2 = ExpectedInvoiceEventWrapper{} + err = json.Unmarshal([]byte(msg2), &event2) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "integration test websocket 3", event2.Invoice.Description) //create subs for 2 different users, assert that they each get their own invoice updates + user2Ws, _, err := websocket.DefaultDialer.Dial(suite.wsUrl2, nil) + assert.NoError(suite.T(), err) + //read keepalive msg + _, _, err = user2Ws.ReadMessage() + assert.NoError(suite.T(), err) + // add invoice for user 1 + user1Invoice := suite.createAddInvoiceReq(1000, "integration test websocket user 1", suite.userToken) + sendPaymentRequestUser1 := lnrpc.SendRequest{ + PaymentRequest: user1Invoice.PayReq, + FeeLimit: nil, + } + // add invoice for user 2 + user2Invoice := suite.createAddInvoiceReq(1000, "integration test websocket user 2", suite.userToken2) + sendPaymentRequestUser2 := lnrpc.SendRequest{ + PaymentRequest: user2Invoice.PayReq, + FeeLimit: nil, + } + //pay invoices + _, err = suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequestUser1) + assert.NoError(suite.T(), err) + _, err = suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequestUser2) + assert.NoError(suite.T(), err) + //read user 1 received msg + _, user1Msg, err := ws2.ReadMessage() + assert.NoError(suite.T(), err) + //assert it's their's + eventUser1 := ExpectedInvoiceEventWrapper{} + err = json.Unmarshal([]byte(user1Msg), &eventUser1) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "integration test websocket user 1", eventUser1.Invoice.Description) + //read user 2 received msg + _, user2Msg, err := user2Ws.ReadMessage() + assert.NoError(suite.T(), err) + //assert it's their's + eventUser2 := ExpectedInvoiceEventWrapper{} + err = json.Unmarshal([]byte(user2Msg), &eventUser2) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "integration test websocket user 2", eventUser2.Invoice.Description) + } func (suite *WebSocketTestSuite) TearDownSuite() { diff --git a/lib/service/pubsub.go b/lib/service/pubsub.go index b5faad8f..f71b8536 100644 --- a/lib/service/pubsub.go +++ b/lib/service/pubsub.go @@ -17,13 +17,16 @@ func NewPubsub() *Pubsub { return ps } -func (ps *Pubsub) Subscribe(id string, topic int64, ch chan models.Invoice) { +func (ps *Pubsub) Subscribe(topic int64, ch chan models.Invoice) (subId string) { ps.mu.Lock() defer ps.mu.Unlock() if ps.subs[topic] == nil { ps.subs[topic] = make(map[string]chan models.Invoice) } - ps.subs[topic][id] = ch + //re-use preimage code for a uuid + subId = string(makePreimageHex()) + ps.subs[topic][subId] = ch + return subId } func (ps *Pubsub) Unsubscribe(id string, topic int64) error { From 455fba26a0c3c63d1ceaa21f2cf3ad0b4a38fa58 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 13 Apr 2022 13:04:29 +0200 Subject: [PATCH 36/40] implement fixes based on skosito's remarks --- controllers/invoicestream.ctrl.go | 48 +++++++++++++++++++------------ lib/service/pubsub.go | 13 +++++++-- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 2ec80bc7..a6630e7f 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -33,28 +33,13 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error return err } invoiceChan := make(chan models.Invoice) - subId := controller.svc.InvoicePubSub.Subscribe(userId, invoiceChan) - upgrader := websocket.Upgrader{} - upgrader.CheckOrigin = func(r *http.Request) bool { return true } ticker := time.NewTicker(30 * time.Second) - ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) + ws, done, err := createWebsocketUpgrader(c) if err != nil { - controller.svc.InvoicePubSub.Unsubscribe(subId, userId) return err } - defer ws.Close() - - //start listening for close messages - done := make(chan struct{}) - go func() { - defer close(done) - for { - _, _, err := ws.ReadMessage() - if err != nil { - return - } - } - }() + //start subscription + subId := controller.svc.InvoicePubSub.Subscribe(userId, invoiceChan) //start with keepalive message err = ws.WriteJSON(&InvoiceEventWrapper{Type: "keepalive"}) @@ -94,5 +79,30 @@ SocketLoop: } } } - return controller.svc.InvoicePubSub.Unsubscribe(subId, userId) + controller.svc.InvoicePubSub.Unsubscribe(subId, userId) + return nil +} + +//open the websocket and start listening for close messages in a goroutine +func createWebsocketUpgrader(c echo.Context) (conn *websocket.Conn, done chan struct{}, err error) { + upgrader := websocket.Upgrader{} + upgrader.CheckOrigin = func(r *http.Request) bool { return true } + ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) + if err != nil { + return nil, nil, err + } + defer ws.Close() + + //start listening for close messages + done = make(chan struct{}) + go func() { + defer close(done) + for { + _, _, err := ws.ReadMessage() + if err != nil { + return + } + } + }() + return ws, done, nil } diff --git a/lib/service/pubsub.go b/lib/service/pubsub.go index f71b8536..47b04866 100644 --- a/lib/service/pubsub.go +++ b/lib/service/pubsub.go @@ -29,18 +29,27 @@ func (ps *Pubsub) Subscribe(topic int64, ch chan models.Invoice) (subId string) return subId } -func (ps *Pubsub) Unsubscribe(id string, topic int64) error { +func (ps *Pubsub) Unsubscribe(id string, topic int64) { ps.mu.Lock() defer ps.mu.Unlock() + if ps.subs[topic] == nil { + return + } + if ps.subs[topic][id] == nil { + return + } close(ps.subs[topic][id]) delete(ps.subs[topic], id) - return nil } func (ps *Pubsub) Publish(topic int64, msg models.Invoice) { ps.mu.RLock() defer ps.mu.RUnlock() + if ps.subs[topic] == nil { + return + } + for _, ch := range ps.subs[topic] { ch <- msg } From a9b796a7d61ba0d56c582dc192381d9b869d672f Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 13 Apr 2022 13:19:14 +0200 Subject: [PATCH 37/40] refactor tests --- controllers/invoicestream.ctrl.go | 2 +- integration_tests/websocket_test.go | 55 +++++++++++++++++++---------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index a6630e7f..3090f7cd 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -35,6 +35,7 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error invoiceChan := make(chan models.Invoice) ticker := time.NewTicker(30 * time.Second) ws, done, err := createWebsocketUpgrader(c) + defer ws.Close() if err != nil { return err } @@ -91,7 +92,6 @@ func createWebsocketUpgrader(c echo.Context) (conn *websocket.Conn, done chan st if err != nil { return nil, nil, err } - defer ws.Close() //start listening for close messages done = make(chan struct{}) diff --git a/integration_tests/websocket_test.go b/integration_tests/websocket_test.go index 35f094c6..7714339f 100644 --- a/integration_tests/websocket_test.go +++ b/integration_tests/websocket_test.go @@ -100,15 +100,14 @@ func (suite *WebSocketTestSuite) SetupSuite() { } func (suite *WebSocketTestSuite) TestWebSocket() { - //start listening to websocket ws, _, err := websocket.DefaultDialer.Dial(suite.wsUrl, nil) - assert.NoError(suite.T(), err, err) + assert.NoError(suite.T(), err) _, msg, err := ws.ReadMessage() - assert.NoError(suite.T(), err, err) + assert.NoError(suite.T(), err) keepAlive := KeepAlive{} err = json.Unmarshal([]byte(msg), &keepAlive) - assert.NoError(suite.T(), err, err) + assert.NoError(suite.T(), err) assert.Equal(suite.T(), "keepalive", keepAlive.Type) // create incoming invoice and fund account @@ -121,29 +120,37 @@ func (suite *WebSocketTestSuite) TestWebSocket() { assert.NoError(suite.T(), err) _, msg, err = ws.ReadMessage() - assert.NoError(suite.T(), err, err) + assert.NoError(suite.T(), err) event := ExpectedInvoiceEventWrapper{} err = json.Unmarshal([]byte(msg), &event) - assert.NoError(suite.T(), err, err) + assert.NoError(suite.T(), err) assert.Equal(suite.T(), event.Type, "invoice") assert.Equal(suite.T(), int64(1000), event.Invoice.Amount) assert.Equal(suite.T(), "integration test websocket 1", event.Invoice.Description) +} + +func (suite *WebSocketTestSuite) TestWebSocketDoubeSubscription() { + //create 1st subscription + ws1, _, err := websocket.DefaultDialer.Dial(suite.wsUrl, nil) + assert.NoError(suite.T(), err) + //read keepalive msg + _, _, err = ws1.ReadMessage() //create 2nd subscription, create invoice, pay invoice, assert that invoice is received twice //start listening to websocket ws2, _, err := websocket.DefaultDialer.Dial(suite.wsUrl, nil) - assert.NoError(suite.T(), err, err) + assert.NoError(suite.T(), err) //read keepalive msg _, _, err = ws2.ReadMessage() - assert.NoError(suite.T(), err, err) - invoice = suite.createAddInvoiceReq(1000, "integration test websocket 2", suite.userToken) - sendPaymentRequest = lnrpc.SendRequest{ + assert.NoError(suite.T(), err) + invoice := suite.createAddInvoiceReq(1000, "integration test websocket 2", suite.userToken) + sendPaymentRequest := lnrpc.SendRequest{ PaymentRequest: invoice.PayReq, FeeLimit: nil, } _, err = suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest) assert.NoError(suite.T(), err) - _, msg1, err := ws.ReadMessage() - assert.NoError(suite.T(), err, err) + _, msg1, err := ws1.ReadMessage() + assert.NoError(suite.T(), err) _, msg2, err := ws2.ReadMessage() assert.NoError(suite.T(), err) @@ -152,11 +159,11 @@ func (suite *WebSocketTestSuite) TestWebSocket() { assert.NoError(suite.T(), err) event2 := ExpectedInvoiceEventWrapper{} err = json.Unmarshal([]byte(msg2), &event2) - assert.NoError(suite.T(), err, err) + assert.NoError(suite.T(), err) assert.Equal(suite.T(), "integration test websocket 2", event1.Invoice.Description) assert.Equal(suite.T(), "integration test websocket 2", event2.Invoice.Description) //close 1 subscription, assert that the existing sub still receives their invoices - ws.Close() + ws1.Close() invoice = suite.createAddInvoiceReq(1000, "integration test websocket 3", suite.userToken) sendPaymentRequest = lnrpc.SendRequest{ PaymentRequest: invoice.PayReq, @@ -164,12 +171,22 @@ func (suite *WebSocketTestSuite) TestWebSocket() { } _, err = suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest) assert.NoError(suite.T(), err) - _, msg2, err = ws2.ReadMessage() + _, msg3, err := ws2.ReadMessage() assert.NoError(suite.T(), err) - event2 = ExpectedInvoiceEventWrapper{} - err = json.Unmarshal([]byte(msg2), &event2) + event3 := ExpectedInvoiceEventWrapper{} + err = json.Unmarshal([]byte(msg3), &event3) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "integration test websocket 3", event3.Invoice.Description) + +} +func (suite *WebSocketTestSuite) TestWebSocketDoubleUser() { + + //create subs for 2 different users, assert that they each get their own invoice updates + user1Ws, _, err := websocket.DefaultDialer.Dial(suite.wsUrl, nil) + assert.NoError(suite.T(), err) + //read keepalive msg + _, _, err = user1Ws.ReadMessage() assert.NoError(suite.T(), err) - assert.Equal(suite.T(), "integration test websocket 3", event2.Invoice.Description) //create subs for 2 different users, assert that they each get their own invoice updates user2Ws, _, err := websocket.DefaultDialer.Dial(suite.wsUrl2, nil) assert.NoError(suite.T(), err) @@ -194,7 +211,7 @@ func (suite *WebSocketTestSuite) TestWebSocket() { _, err = suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequestUser2) assert.NoError(suite.T(), err) //read user 1 received msg - _, user1Msg, err := ws2.ReadMessage() + _, user1Msg, err := user1Ws.ReadMessage() assert.NoError(suite.T(), err) //assert it's their's eventUser1 := ExpectedInvoiceEventWrapper{} From c24d1599d069179145c16e09447e8e472e439b1b Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 20 Apr 2022 16:25:01 +0200 Subject: [PATCH 38/40] refactor parse token --- controllers/invoicestream.ctrl.go | 2 +- lib/tokens/jwt.go | 37 +++++-------------------------- 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 3090f7cd..9779244d 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -28,7 +28,7 @@ func NewInvoiceStreamController(svc *service.LndhubService) *InvoiceStreamContro // Stream invoices streams incoming payments to the client func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error { - userId, err := tokens.ParseToken(controller.svc.Config.JWTSecret, (c.QueryParam("token"))) + userId, err := tokens.ParseToken(controller.svc.Config.JWTSecret, (c.QueryParam("token")), false) if err != nil { return err } diff --git a/lib/tokens/jwt.go b/lib/tokens/jwt.go index 0e8d35d9..d2eb9597 100644 --- a/lib/tokens/jwt.go +++ b/lib/tokens/jwt.go @@ -81,36 +81,7 @@ func GenerateRefreshToken(secret []byte, expiryInSeconds int, u *models.User) (s return t, nil } -func ParseToken(secret []byte, token string) (int64, error) { - userIdClaim := "id" - claims := jwt.MapClaims{} - parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { - return secret, nil - }) - - if err != nil { - return -1, err - } - - if !parsedToken.Valid { - return -1, errors.New("Token is invalid") - } - - var userId interface{} - for k, v := range claims { - if k == userIdClaim { - userId = v.(float64) - } - } - - if userId == nil { - return -1, errors.New("User id claim not found") - } - - return int64(userId.(float64)), nil -} - -func GetUserIdFromToken(secret []byte, token string) (int64, error) { +func ParseToken(secret []byte, token string, mustBeRefresh bool) (int64, error) { userIdClaim := "id" isRefreshClaim := "isRefresh" claims := jwt.MapClaims{} @@ -128,7 +99,7 @@ func GetUserIdFromToken(secret []byte, token string) (int64, error) { var userId interface{} for k, v := range claims { - if k == isRefreshClaim && v.(bool) == false { + if k == isRefreshClaim && !v.(bool) && mustBeRefresh { return -1, errors.New("This is not a refresh token") } if k == userIdClaim { @@ -142,3 +113,7 @@ func GetUserIdFromToken(secret []byte, token string) (int64, error) { return int64(userId.(float64)), nil } + +func GetUserIdFromToken(secret []byte, token string) (int64, error) { + return ParseToken(secret, token, true) +} From 9180a7d79b658df7dbad31c38d6fe0ce672f1e7b Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 20 Apr 2022 16:48:16 +0200 Subject: [PATCH 39/40] add missing invoice functionality --- controllers/invoicestream.ctrl.go | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 9779244d..5f26ec6a 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -49,6 +49,15 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error controller.svc.InvoicePubSub.Unsubscribe(subId, userId) return err } + fromPaymentHash := c.QueryParam("from_payment_hash") + if fromPaymentHash != "" { + err = controller.writeMissingInvoices(c, userId, ws, fromPaymentHash) + if err != nil { + controller.svc.Logger.Error(err) + controller.svc.InvoicePubSub.Unsubscribe(subId, userId) + return err + } + } SocketLoop: for { select { @@ -106,3 +115,36 @@ func createWebsocketUpgrader(c echo.Context) (conn *websocket.Conn, done chan st }() return ws, done, nil } + +func (controller *InvoiceStreamController) writeMissingInvoices(c echo.Context, userId int64, ws *websocket.Conn, hash string) error { + invoices, err := controller.svc.InvoicesFor(c.Request().Context(), userId, common.InvoiceTypeIncoming) + if err != nil { + return err + } + for _, inv := range invoices { + //invoices are order from newest to oldest (with a maximum of 100 invoices being returned) + //so if we get a match on the hash, we have processed all missing invoices for this client + if inv.DescriptionHash == hash { + break + } + if inv.State == common.InvoiceStateSettled { + err := ws.WriteJSON( + &InvoiceEventWrapper{ + Type: "invoice", + Invoice: &IncomingInvoice{ + PaymentHash: inv.RHash, + PaymentRequest: inv.PaymentRequest, + Description: inv.Memo, + PayReq: inv.PaymentRequest, + Timestamp: inv.CreatedAt.Unix(), + Type: common.InvoiceTypeUser, + Amount: inv.Amount, + IsPaid: inv.State == common.InvoiceStateSettled, + }}) + if err != nil { + return err + } + } + } + return nil +} From d36d22a2d08de82bc023c3648643e78af1271fca Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 20 Apr 2022 16:57:12 +0200 Subject: [PATCH 40/40] add integration test for missing invoices --- controllers/invoicestream.ctrl.go | 4 +-- integration_tests/websocket_test.go | 39 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 5f26ec6a..2f1e0bda 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -49,7 +49,7 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error controller.svc.InvoicePubSub.Unsubscribe(subId, userId) return err } - fromPaymentHash := c.QueryParam("from_payment_hash") + fromPaymentHash := c.QueryParam("since_payment_hash") if fromPaymentHash != "" { err = controller.writeMissingInvoices(c, userId, ws, fromPaymentHash) if err != nil { @@ -124,7 +124,7 @@ func (controller *InvoiceStreamController) writeMissingInvoices(c echo.Context, for _, inv := range invoices { //invoices are order from newest to oldest (with a maximum of 100 invoices being returned) //so if we get a match on the hash, we have processed all missing invoices for this client - if inv.DescriptionHash == hash { + if inv.RHash == hash { break } if inv.State == common.InvoiceStateSettled { diff --git a/integration_tests/websocket_test.go b/integration_tests/websocket_test.go index 7714339f..e5d4f68f 100644 --- a/integration_tests/websocket_test.go +++ b/integration_tests/websocket_test.go @@ -228,6 +228,45 @@ func (suite *WebSocketTestSuite) TestWebSocketDoubleUser() { assert.Equal(suite.T(), "integration test websocket user 2", eventUser2.Invoice.Description) } +func (suite *WebSocketTestSuite) TestWebSocketMissingInvoice() { + // create incoming invoice and fund account + invoice1 := suite.createAddInvoiceReq(1000, "integration test websocket missing invoices", suite.userToken) + sendPaymentRequest := lnrpc.SendRequest{ + PaymentRequest: invoice1.PayReq, + FeeLimit: nil, + } + _, err := suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest) + assert.NoError(suite.T(), err) + + // create 2nd invoice and pay it as well + invoice2 := suite.createAddInvoiceReq(1000, "integration test websocket missing invoices 2nd", suite.userToken) + sendPaymentRequest = lnrpc.SendRequest{ + PaymentRequest: invoice2.PayReq, + FeeLimit: nil, + } + _, err = suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest) + assert.NoError(suite.T(), err) + + //start listening to websocket after 2nd invoice has been paid + //we should get an event for the 2nd invoice if we specify the hash as the query parameter + ws, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("%s&since_payment_hash=%s", suite.wsUrl, invoice1.RHash), nil) + assert.NoError(suite.T(), err) + _, msg, err := ws.ReadMessage() + assert.NoError(suite.T(), err) + keepAlive := KeepAlive{} + err = json.Unmarshal([]byte(msg), &keepAlive) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "keepalive", keepAlive.Type) + + _, msg, err = ws.ReadMessage() + assert.NoError(suite.T(), err) + event := ExpectedInvoiceEventWrapper{} + err = json.Unmarshal([]byte(msg), &event) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), event.Type, "invoice") + assert.Equal(suite.T(), int64(1000), event.Invoice.Amount) + assert.Equal(suite.T(), "integration test websocket missing invoices 2nd", event.Invoice.Description) +} func (suite *WebSocketTestSuite) TearDownSuite() { suite.invoiceUpdateSubCancelFn()