From a5a90c4d834e5db9f694b7c2d2dd8530e97f64fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 22 Dec 2022 19:58:45 +0700 Subject: [PATCH 1/5] Add support and migration for lndhub.go Slightly WIP --- .env.production | 5 ++- app/services/lndhub.rb | 8 ++-- app/services/lndhub_v2.rb | 81 ++++++++++++++++++++++++++++++++++++++ config/credentials.yml.enc | 2 +- lib/tasks/lndhub.rake | 27 +++++++++++++ 5 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 app/services/lndhub_v2.rb diff --git a/.env.production b/.env.production index 228cf57..2f31371 100644 --- a/.env.production +++ b/.env.production @@ -1,4 +1,7 @@ EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api' + BTCPAY_API_URL='http://10.1.1.163:23001/api/v1' -LNDHUB_API_URL='http://10.1.1.163:3023' + +LNDHUB_LEGACY_API_URL='http://10.1.1.163:3023' +LNDHUB_API_URL='http://10.1.1.163:3026' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' diff --git a/app/services/lndhub.rb b/app/services/lndhub.rb index bd3670a..68f10be 100644 --- a/app/services/lndhub.rb +++ b/app/services/lndhub.rb @@ -2,7 +2,7 @@ class Lndhub attr_accessor :auth_token def initialize - @base_url = ENV["LNDHUB_API_URL"] + @base_url = ENV["LNDHUB_LEGACY_API_URL"] end def post(endpoint, payload) @@ -42,15 +42,15 @@ class Lndhub self.auth_token end - def balance(user_token) + def balance(user_token=nil) get "balance", user_token || auth_token end - def gettxs(user_token) + def gettxs(user_token=nil) get "gettxs", user_token || auth_token end - def getuserinvoices(user_token) + def getuserinvoices(user_token=nil) get "getuserinvoices", user_token || auth_token end diff --git a/app/services/lndhub_v2.rb b/app/services/lndhub_v2.rb new file mode 100644 index 0000000..af5f3a9 --- /dev/null +++ b/app/services/lndhub_v2.rb @@ -0,0 +1,81 @@ +class LndhubV2 + attr_accessor :auth_token + + def initialize + @base_url = ENV["LNDHUB_API_URL"] + end + + def post(endpoint, payload, options={}) + headers = { "Content-Type" => "application/json" } + if auth_token + headers.merge!({ "Authorization" => "Bearer #{auth_token}" }) + elsif options[:admin_token] + headers.merge!({ "Authorization" => "Bearer #{options[:admin_token]}" }) + end + + res = Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, headers + + if res.status != 200 + Rails.logger.error "[lndhub] API request failed:" + Rails.logger.error res.body + #TODO add some kind of exception tracking/notifications + end + + JSON.parse(res.body) + end + + def get(endpoint, auth_token) + res = Faraday.get("#{@base_url}/#{endpoint}", {}, { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => "Bearer #{auth_token}" + }) + + JSON.parse(res.body) + end + + def create(payload) + post "create", payload + end + + def authenticate(user) + credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password } + self.auth_token = credentials["access_token"] + self.auth_token + end + + def balance(user_token=nil) + get "balance", user_token || auth_token + end + + def gettxs(user_token) + get "gettxs", user_token || auth_token + end + + def getuserinvoices(user_token) + get "getuserinvoices", user_token || auth_token + end + + def addinvoice(payload) + invoice = post "addinvoice", { + amt: payload[:amount], + memo: payload[:memo], + description_hash: payload[:description_hash] + } + + invoice["payment_request"] + end + + # + # V2 + # + + def create_account(payload) + post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token] + end + + def create_invoice(payload) + # Payload: { amount: 1000, description: "", description_hash: "" } + post "v2/invoices", payload + end +end diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index c3dc2e3..978af61 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -oDf4FcihlyfHQuf9SUwfr+UVA0kXoECtHV3vEwtBp2TktCWkWz7SPbSZ2cLT0+EknOKhuI9xutrs311YDU2EzqZba4fZ0+a6/ohVH4jUbk1XfiHZWBp4zh+9TZ5m5Tp2RxcXpdcWPY38mP4zHWFzobTOR/brLjuemozvh8MiBSPY4NN5NR6rbFo87auK6fvYO8ik/1Qwf6pQMoVDjcTh2983po1RU/gSevUmaYsmTTHcQ5T9O9wMIBc101iZyKhkAZG46a6eNYok8yqRm18AHWr+De2j7LlBaqYSz/BZA385RWhhqoeONArwyo3Az30Bv74VttUSJAPurkRg2wDF9t8A+cvf8CeYkZ6u398JLSJbZZ6YdQS5T4IcrnONRXtp3d/m+yw+XEzpluK3MvFbV02AhZk/xzkGK6xonhaTSh1ek9hXoYrUTBzu8HBzXwKjJMnvrAodldu++/rMwLsgVmFHqXC3dydVogatLev8z6ziuGkmeMAR1d9kzGBHM3FWgsWLD7j0Ug7MTMyNWioI3r6J2QTnxkyJGh3pKBlq8Fb/Q0ypERxOfSZVQQh2gB79RMEDIemdCmN5mCU3ojsxqAAip4v9C1BZMWPtom1sHLYQSd9Bh6i0nncrNEtZXcxe5Z8JCWQolHvvfoIF2rfJh2oXYLxNx5n/1fzaoSqBdLBgvsAMA0ZWfV1wa/5V/DCa9vaJjumzHYKcfCCYbVz9PjN9OUSfwrE3nWZu8Y0awsNgmeRQwI10j2+oYYSBp040I05Vj5FM7nCgHLpdupctwaH2/VlIq83OVI6VlbzxYEay7+R5ANmpiJ+vC466DzLv8LAeFOqh/XeNeOITUm9EzhGSgf98yuDc7vsi3gmc2g7atgQ3gpSje6vEfhigm3ukydaGfA==--IYQBMD+Tar1g+srm--01E5mujYFWvcpT8Qd9HxiA== \ No newline at end of file +yEs5CyuAbqphlDWgtw/YQvkPn+EN4ecen2dAjs7zvYErkRRWp99FinGlQIMe6NRkMLLLSIj2BwR/wlscn1kLpIfwGpxfSZ89srK3do6Mb5QogpxdUsnQB8qv5PTGRQFBcjM47s1Q5m0t+OKxGvOnLyKnQp+cVS2KFJMbSzQarW8wIZSz2gKArn9Ttk0kqUHMlJWNY7Yh6xIrrxlEalaTOVzPdtnF7u8Tobminu15eeWHMormMRz4dYSaDc6hUtfpdy1NzOHaeXIU9A9RY/iytxuIQNgcMAlcWbPe//rVk/unH2F8xqSOfed4h/nC08F/qq4z8va3kEXBSdW/G91aIDMu1mo0kX3YNibq8s25C/CfGpzw39ozJ9erTBH7hy6nfmxU6qZuWcTGDj3NOfKe/XIfDcpOjsqkT2IOFARrYodb67q23IuOufraK1/FD4LXu8l0S8/Oi0cqMjtPPs7tS0M1C3DrbmlEzGKETrHpmoKHqjA0rgOmK4ZZM9LeI+l8Z+fDpYcCak9fLGGxnjf+nKiYMSUtm9+1dwycG2lpBV6fbmIKHJWngO2jVGcycODkc525oUaAO4hdPMqrz1AdU3AzYmLJTxW3aZ4uL5NyEJ7TbUBC0HT7h2gEi/tUry4cfD2EsM9bCrCUNuMBrnPqd4r8AvORoqqYIw1IEsP0RgWa2+hfeG1QCjBRPFHQOcqo+W25CelivMe79qI08w0iC8S4hfOQO4QrmMgtd1BhcR+wVpVE3X9EJZi3Hl7z14hXcSic+gkswJMtVZcnJL4rmZ0iEW1mpqUuegsX5vB/4qPxiQyeB80pg8Q33shvUbixzSBkl6znmLSiIffsiDsGOsnuzfl/MUT+JBs3UswNt4tSp7nEwhUjKFHrZHrAJiGCdtIS6yDPGe3HfQv1JkQ+9A8zv88hRmzeIx2JyT/shtIqGo+4ZTJd5cma--Lij/n0+cpstyZD28--FOUhwW3y+0jdaYkKvG2xrg== \ No newline at end of file diff --git a/lib/tasks/lndhub.rake b/lib/tasks/lndhub.rake index 54d8a05..04e7aac 100644 --- a/lib/tasks/lndhub.rake +++ b/lib/tasks/lndhub.rake @@ -21,4 +21,31 @@ namespace :lndhub do end puts "--\nSum of user balances: #{sum} sats" end + + desc "Migrate existing accounts to lndhub.go" + task :migrate => :environment do |t, args| + # user = User.find_by cn: "jimmy" + User.all.each do |user| + puts "Migrating #{user.cn}" + puts "Creating account..." + lndhub_v2 = LndhubV2.new + res = lndhub_v2.create_account login: user.ln_login, password: user.ln_password + puts res.inspect + + lndhub = Lndhub.new + lndhub.authenticate(user) + data = lndhub.balance + balance = data["BTC"]["AvailableBalance"] rescue 0 + + if balance > 0 + lndhub_v2.authenticate(user) + desc = "Balance migration from old Kosmos Lightning back-end" + res = lndhub_v2.create_invoice amount: balance, description: desc + puts "Payment request for #{user.cn} (#{balance} sats):" + puts res["payment_request"] + end + + puts "---" + end + end end From 6df3d5933c77f6a96a0e94f2af2c69c6165741af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Thu, 22 Dec 2022 20:11:38 +0700 Subject: [PATCH 2/5] Update test env --- .env.test | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.test b/.env.test index 103f43d..ce5e1f7 100644 --- a/.env.test +++ b/.env.test @@ -1,4 +1,5 @@ EJABBERD_API_URL='http://xmpp.example.com/api' BTCPAY_API_URL='http://btcpay.example.com/api/v1' -LNDHUB_API_URL='http://localhost:3023' +LNDHUB_LEGACY_API_URL='http://localhost:3023' +LNDHUB_API_URL='http://localhost:3026' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' From e62bf672625c7c432fc4b6e09d41e3ca53d74c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Fri, 23 Dec 2022 12:22:45 +0700 Subject: [PATCH 3/5] Use v2 API for creating new lndhub accounts --- ..._lndhub_wallet_job.rb => create_lndhub_account_job.rb} | 6 +++--- app/services/create_account.rb | 6 +++--- app/services/lndhub_v2.rb | 2 +- config/credentials/test.yml.enc | 2 +- ...llet_job_spec.rb => create_lndhub_account_job_spec.rb} | 8 ++++---- spec/services/create_account_spec.rb | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) rename app/jobs/{create_lndhub_wallet_job.rb => create_lndhub_account_job.rb} (60%) rename spec/jobs/{create_lndhub_wallet_job_spec.rb => create_lndhub_account_job_spec.rb} (83%) diff --git a/app/jobs/create_lndhub_wallet_job.rb b/app/jobs/create_lndhub_account_job.rb similarity index 60% rename from app/jobs/create_lndhub_wallet_job.rb rename to app/jobs/create_lndhub_account_job.rb index 30b4778..9cf33b1 100644 --- a/app/jobs/create_lndhub_wallet_job.rb +++ b/app/jobs/create_lndhub_account_job.rb @@ -1,11 +1,11 @@ -class CreateLndhubWalletJob < ApplicationJob +class CreateLndhubAccountJob < ApplicationJob queue_as :default def perform(user) return if user.ln_login.present? && user.ln_password.present? - lndhub = Lndhub.new - credentials = lndhub.create({ partnerid: user.ou, accounttype: "user" }) + lndhub = LndhubV2.new + credentials = lndhub.create_account user.update! ln_login: credentials["login"], ln_password: credentials["password"] diff --git a/app/services/create_account.rb b/app/services/create_account.rb index fac6442..f332a53 100644 --- a/app/services/create_account.rb +++ b/app/services/create_account.rb @@ -11,7 +11,7 @@ class CreateAccount < ApplicationService def call user = create_user_in_database add_ldap_document - create_lndhub_wallet(user) + create_lndhub_account(user) if @invitation.present? update_invitation(user.id) @@ -49,9 +49,9 @@ class CreateAccount < ApplicationService ExchangeXmppContactsJob.perform_later(@invitation.user, @username, @domain) end - def create_lndhub_wallet(user) + def create_lndhub_account(user) #TODO enable in development when we have a local lndhub (mock?) API return if Rails.env.development? - CreateLndhubWalletJob.perform_later(user) + CreateLndhubAccountJob.perform_later(user) end end diff --git a/app/services/lndhub_v2.rb b/app/services/lndhub_v2.rb index af5f3a9..bb4c4b9 100644 --- a/app/services/lndhub_v2.rb +++ b/app/services/lndhub_v2.rb @@ -70,7 +70,7 @@ class LndhubV2 # V2 # - def create_account(payload) + def create_account(payload={}) post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token] end diff --git a/config/credentials/test.yml.enc b/config/credentials/test.yml.enc index 5a1530a..dd9d846 100644 --- a/config/credentials/test.yml.enc +++ b/config/credentials/test.yml.enc @@ -1 +1 @@ -IIjYiPSeZeMFhH8i8v8akXN4JrtGU+OsMQ8GAao/gVdesggriCBAQ8z+Vd0cmTf1SKYeT3OQDgygEekupr325P4eD9fZ+yi56EA/UMXQXMDVZAvZw7iwvKaOXpqisbWdJnomr1GXrHyR415Ce/Fxft3fgXDwMHJW2u+dDJgpE09uORnB9GXycFwHQmoIdXo=--iQ/Vcm0VcwHgUkwQ--tKHQW/45gM/s/NplqGPaxw== \ No newline at end of file +vqH5By5qFLImVjdlWj+7FwGg8APKnr/AEd7WqekG7L0vNA32WGBpwS1uGzs02LIcATRwGj8DyJxiBOB/w9z8cwoO+t6Woi5hAnOSCQwFWKLT0dZq7jgtT8pxK0Yu/Nf91PEFN1rc/8ZFy2KKVpbtMbMPyivT38e/ctBZD/lHrWkndvLXYvFVhqWjUnDOGbhwl/U0RZgqBBjvlm3B0JkQfiN8VXPlCJL2Cd8kd0+MpRCRTgtcxA==--OdVXnDP7OhzJxCsP--+8SI6IFIeXyDxXb+WpqhIQ== \ No newline at end of file diff --git a/spec/jobs/create_lndhub_wallet_job_spec.rb b/spec/jobs/create_lndhub_account_job_spec.rb similarity index 83% rename from spec/jobs/create_lndhub_wallet_job_spec.rb rename to spec/jobs/create_lndhub_account_job_spec.rb index 4b64ba1..aa2d179 100644 --- a/spec/jobs/create_lndhub_wallet_job_spec.rb +++ b/spec/jobs/create_lndhub_account_job_spec.rb @@ -1,13 +1,13 @@ require 'rails_helper' require 'webmock/rspec' -RSpec.describe CreateLndhubWalletJob, type: :job do +RSpec.describe CreateLndhubAccountJob, type: :job do let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" } subject(:job) { described_class.perform_later(user) } before do - stub_request(:post, "http://localhost:3023/create") + stub_request(:post, "http://localhost:3026/v2/users") .to_return(status: 200, headers: {}, body: { login: "abc123", password: "def456" }.to_json) end @@ -15,8 +15,8 @@ RSpec.describe CreateLndhubWalletJob, type: :job do it "creates a new LndHub account" do perform_enqueued_jobs { job } - expect(WebMock).to have_requested(:post, "http://localhost:3023/create") - .with { |req| req.body == '{"partnerid":"kosmos.org","accounttype":"user"}' } + expect(WebMock).to have_requested(:post, "http://localhost:3026/v2/users") + .with { |req| req.body == '{}' } user.reload expect(user.ln_login).to eq("abc123") diff --git a/spec/services/create_account_spec.rb b/spec/services/create_account_spec.rb index 0180b0d..9e35a2f 100644 --- a/spec/services/create_account_spec.rb +++ b/spec/services/create_account_spec.rb @@ -93,7 +93,7 @@ RSpec.describe CreateAccount, type: :model do end end - describe "#create_lndhub_wallet" do + describe "#create_lndhub_account" do include ActiveJob::TestHelper let(:service) { CreateAccount.new( @@ -102,8 +102,8 @@ RSpec.describe CreateAccount, type: :model do )} let(:new_user) { create :user, cn: "halfinney", ou: "kosmos.org" } - it "enqueues a job to create an LndHub wallet" do - service.send(:create_lndhub_wallet, new_user) + it "enqueues a job to create an LndHub account" do + service.send(:create_lndhub_account, new_user) expect(enqueued_jobs.size).to eq(1) From e1aaa2c43403cc0b1a4d3f04a5f27aa7cb740032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Fri, 23 Dec 2022 15:14:24 +0700 Subject: [PATCH 4/5] Re-authorize when token is invalid --- app/controllers/wallet_controller.rb | 19 ++++++++++++++----- app/services/lndhub.rb | 7 ++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/controllers/wallet_controller.rb b/app/controllers/wallet_controller.rb index 4990894..535eed1 100644 --- a/app/controllers/wallet_controller.rb +++ b/app/controllers/wallet_controller.rb @@ -28,13 +28,13 @@ class WalletController < ApplicationController private - def authenticate_with_lndhub - if session["ln_auth_token"].present? - @ln_auth_token = session["ln_auth_token"] + def authenticate_with_lndhub(options={}) + if session[:ln_auth_token].present? && !options[:force_reauth] + @ln_auth_token = session[:ln_auth_token] else lndhub = Lndhub.new auth_token = lndhub.authenticate(current_user) - session["ln_auth_token"] = auth_token + session[:ln_auth_token] = auth_token @ln_auth_token = auth_token end rescue @@ -49,14 +49,23 @@ class WalletController < ApplicationController lndhub = Lndhub.new data = lndhub.balance @ln_auth_token @balance = data["BTC"]["AvailableBalance"] rescue nil + rescue + authenticate_with_lndhub(force_reauth: true) + return nil if @fetch_balance_retried + @fetch_balance_retried = true + fetch_balance end def fetch_transactions lndhub = Lndhub.new txs = lndhub.gettxs @ln_auth_token invoices = lndhub.getuserinvoices(@ln_auth_token).select{|i| i["ispaid"]} - process_transactions(txs + invoices) + rescue + authenticate_with_lndhub(force_reauth: true) + return [] if @fetch_transactions_retried + @fetch_transactions_retried = true + fetch_transactions end def process_transactions(txs) diff --git a/app/services/lndhub.rb b/app/services/lndhub.rb index 68f10be..c9547d3 100644 --- a/app/services/lndhub.rb +++ b/app/services/lndhub.rb @@ -28,8 +28,13 @@ class Lndhub "Accept" => "application/json", "Authorization" => "Bearer #{auth_token}" }) + data = JSON.parse(res.body) - JSON.parse(res.body) + if data.is_a?(Hash) && data["error"] && data["message"] == "bad auth" + raise "BAD_AUTH" + else + data + end end def create(payload) From f2a22adf6b92aaf7f1145df9a09113a83517487c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Fri, 23 Dec 2022 15:17:01 +0700 Subject: [PATCH 5/5] Switch legacy to lndhub.go Temporary fix --- .env.production | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.production b/.env.production index 2f31371..243f0e4 100644 --- a/.env.production +++ b/.env.production @@ -2,6 +2,6 @@ EJABBERD_API_URL='https://xmpp.kosmos.org:5443/api' BTCPAY_API_URL='http://10.1.1.163:23001/api/v1' -LNDHUB_LEGACY_API_URL='http://10.1.1.163:3023' +LNDHUB_LEGACY_API_URL='http://10.1.1.163:3026' LNDHUB_API_URL='http://10.1.1.163:3026' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'