Skip to content

Commit b99f132

Browse files
committed
adding multiclient support
1 parent d17b4d4 commit b99f132

File tree

30 files changed

+710
-255
lines changed

30 files changed

+710
-255
lines changed

README.md

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# lnd-client
1+
# lnd-client [![Gem Version](https://badge.fury.io/rb/lnd-client.svg)](https://badge.fury.io/rb/lnd-client) ![RSpec Tests Status](https://github.com/icebaker/lnd-client/actions/workflows/ruby-rspec-tests.yml/badge.svg)
22

33
Ruby Lightning Network Daemon Client: Straightforward access to [lnd](https://github.com/lightningnetwork/lnd) [gRPC API](https://lightning.engineering/api-docs/api/lnd/#grpc)
44

@@ -25,20 +25,21 @@ require 'lnd-client'
2525
puts LNDClient.version # => 0.0.5
2626

2727
client = LNDClient.new(
28-
certificate_path: '/lnd/tls.cert',
29-
macaroon_path: '/lnd/data/chain/bitcoin/mainnet/admin.macaroon',
30-
socket_address: '127.0.0.1:10009'
28+
'lndconnect://127.0.0.1:10009?cert=MIICJz...JBEERQ&macaroon=AgEDbG...45ukJ4'
3129
)
3230

3331
client.lightning.wallet_balance.total_balance # => 101527
3432

3533
client.lightning.wallet_balance.to_h # =>
36-
# {:total_balance=>101527,
37-
# :confirmed_balance=>101527,
38-
# :unconfirmed_balance=>0,
39-
# :locked_balance=>0,
40-
# :reserved_balance_anchor_chan=>20000,
41-
# :account_balance=>{"default"=>{:confirmed_balance=>101527, :unconfirmed_balance=>0}}}
34+
# { total_balance: 101_527,
35+
# confirmed_balance: 101_527,
36+
# unconfirmed_balance: 0,
37+
# locked_balance: 0,
38+
# reserved_balance_anchor_chan: 20_000,
39+
# account_balance: {
40+
# 'default' => {
41+
# confirmed_balance: 101_527,
42+
# unconfirmed_balance: 0 } } }
4243

4344
client.lightning.get_node_info(
4445
pub_key: '02d3c80335a8ccb2ed364c06875f32240f36f7edb37d80f8dbe321b4c364b6e997'

components/connector.rb

-76
This file was deleted.

controllers/client.rb

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require_relative '../components/grpc'
4+
45
require_relative 'service'
56

67
module LNDClientInternal
@@ -13,6 +14,8 @@ def initialize(connection)
1314

1415
doc = Struct.new(:services)
1516
@doc = doc.new(LNDClientInternal::GRPC::SERVICES.keys.map(&:to_s))
17+
18+
lightning(**connection[:lightning]) if connection[:lightning]
1619
end
1720

1821
def respond_to_missing?(method_name, include_private = false)

controllers/connection.rb

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# frozen_string_literal: true
2+
3+
require 'uri'
4+
require 'base64'
5+
require 'singleton'
6+
7+
require 'grpc'
8+
9+
require_relative '../models/errors'
10+
11+
module LNDClientInternal
12+
class Connection
13+
def self.validate_params!(connection)
14+
raise LNDClient::Errors::TooManyArgumentsError if connection.key?(:address) && connection.key?(:host)
15+
raise LNDClient::Errors::TooManyArgumentsError if connection.key?(:address) && connection.key?(:port)
16+
if connection.key?(:certificate_path) && connection.key?(:certificate)
17+
raise LNDClient::Errors::TooManyArgumentsError
18+
end
19+
raise LNDClient::Errors::TooManyArgumentsError if connection.key?(:macaroon_path) && connection.key?(:macaroon)
20+
end
21+
22+
def self.expand(*params, &vcr)
23+
connection = if is_lndconnect?(*params)
24+
exapand_lndconnect(*params)
25+
else
26+
params.first
27+
end
28+
29+
validate_params!(connection)
30+
31+
connection[:certificate] = load_file(connection[:certificate_path], &vcr) if connection[:certificate_path]
32+
33+
connection[:macaroon] = load_file(connection[:macaroon_path], &vcr) if connection[:macaroon_path]
34+
35+
if !connection.key?(:address) && connection.key?(:host) && connection.key?(:port)
36+
connection[:address] = "#{connection[:host]}:#{connection[:port]}"
37+
elsif !connection.key?(:address)
38+
raise LNDClient::Errors::ArgumentError, 'missing :address or :host + :port'
39+
end
40+
41+
connection[:certificate] = decode_certificate(connection[:certificate])
42+
connection[:macaroon] = decode_macaroon(connection[:macaroon])
43+
44+
if params.is_a?(Array)
45+
params.each do |param|
46+
connection[:lightning] = param[:lightning] if param.is_a?(Hash) && param.key?(:lightning)
47+
end
48+
end
49+
50+
connection
51+
end
52+
53+
def self.decode_macaroon(macaroon)
54+
if !macaroon.dup.force_encoding(Encoding::UTF_8).valid_encoding?
55+
macaroon.unpack1('H*')
56+
elsif hex?(macaroon)
57+
macaroon
58+
elsif base64?(macaroon)
59+
Base64.decode64(macaroon).unpack1('H*')
60+
end
61+
end
62+
63+
def self.decode_certificate(certificate)
64+
if hex?(certificate)
65+
[certificate].pack('H*')
66+
elsif base64?(certificate)
67+
Base64.decode64(certificate)
68+
else
69+
certificate
70+
end
71+
end
72+
73+
def self.base64?(value)
74+
(value.length % 4).zero? && value.match(%r{\A[A-Za-z0-9+/]+={0,3}\z})
75+
end
76+
77+
def self.hex?(value)
78+
value.match(/\A[\da-fA-F]+\z/)
79+
end
80+
81+
def self.exapand_lndconnect(*params)
82+
parsed_uri = URI.parse(params.first)
83+
host = parsed_uri.host
84+
port = parsed_uri.port || 10_009
85+
86+
params = URI.decode_www_form(parsed_uri.query).to_h
87+
certificate = params['cert'].tr('-_', '+/')
88+
macaroon = Base64.urlsafe_decode64(params['macaroon'])
89+
90+
certificate = "-----BEGIN CERTIFICATE-----\n#{certificate.gsub(/(.{64})/, "\\1\n")}\n-----END CERTIFICATE-----\n"
91+
92+
{
93+
host: host,
94+
port: port,
95+
certificate: certificate,
96+
macaroon: macaroon
97+
}
98+
end
99+
100+
def self.is_lndconnect?(*params)
101+
params.is_a?(Array) &&
102+
params.first.is_a?(String) &&
103+
params.first.start_with?('lndconnect://')
104+
end
105+
106+
def self.standalone(*params, &vcr)
107+
connection = Connection.expand(*params, &vcr)
108+
connection[:credentials] = ::GRPC::Core::ChannelCredentials.new(connection[:certificate])
109+
connection
110+
end
111+
112+
def self.load_file(path, &vcr)
113+
vcr.nil? ? File.read(path) : vcr.call(-> { File.read(path) }, path)
114+
end
115+
end
116+
end

controllers/multiclient.rb

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
require 'singleton'
4+
5+
require_relative 'connection'
6+
7+
require_relative 'client'
8+
9+
module LNDClientInternal
10+
class Multiclient
11+
include Singleton
12+
13+
def initialize
14+
@clients = {}
15+
end
16+
17+
def add_connection!(id, *params)
18+
@clients[id] = ClientController.new(Connection.standalone(*params))
19+
end
20+
21+
def remove_connection!(id)
22+
@clients.delete(id)
23+
end
24+
25+
def as(id)
26+
@clients[id]
27+
end
28+
29+
def clear!
30+
@clients = {}
31+
end
32+
33+
def connections
34+
@clients.keys
35+
end
36+
end
37+
end

controllers/profile.rb

-28
This file was deleted.

controllers/service.rb

-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ def initialize(client, rpc, params = {})
1010
@client = client
1111
@rpc = rpc
1212
@service = rpc.const_get(:Service)
13-
# require 'pry'; binding.pry
1413
@stub = rpc.const_get(:Stub).new(
1514
client.connection[:address],
1615
client.connection[:credentials],

0 commit comments

Comments
 (0)