lighstorm 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +5 -0
  3. data/.gitignore +1 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +10 -0
  6. data/Gemfile +4 -0
  7. data/Gemfile.lock +26 -2
  8. data/README.md +29 -370
  9. data/adapters/connections/channel_node/fee.rb +26 -0
  10. data/adapters/connections/channel_node.rb +52 -0
  11. data/adapters/connections/payment_channel.rb +28 -0
  12. data/adapters/edges/channel.rb +80 -0
  13. data/adapters/edges/forward.rb +41 -0
  14. data/adapters/edges/payment/purpose.rb +34 -0
  15. data/adapters/edges/payment.rb +53 -0
  16. data/adapters/invoice.rb +50 -0
  17. data/adapters/nodes/node.rb +77 -0
  18. data/adapters/payment_request.rb +77 -0
  19. data/components/cache.rb +3 -2
  20. data/components/lnd.rb +5 -1
  21. data/controllers/channel/actions/update_fee.rb +76 -0
  22. data/controllers/channel/all.rb +79 -0
  23. data/controllers/channel/find_by_id.rb +153 -0
  24. data/controllers/channel/mine.rb +114 -0
  25. data/controllers/channel.rb +23 -0
  26. data/controllers/forward/all.rb +244 -0
  27. data/controllers/forward/group_by_channel.rb +89 -0
  28. data/controllers/forward.rb +28 -0
  29. data/controllers/invoice/actions/create.rb +36 -0
  30. data/controllers/invoice/actions/pay.rb +28 -0
  31. data/controllers/invoice/actions/pay_through_route.rb +71 -0
  32. data/controllers/invoice/all.rb +70 -0
  33. data/controllers/invoice/find_by_secret_hash.rb +42 -0
  34. data/controllers/invoice.rb +36 -0
  35. data/controllers/node/all.rb +63 -0
  36. data/controllers/node/find_by_public_key.rb +49 -0
  37. data/controllers/node/myself.rb +34 -0
  38. data/controllers/node.rb +23 -0
  39. data/controllers/payment/all.rb +352 -0
  40. data/controllers/payment.rb +21 -0
  41. data/docs/.nojekyll +0 -0
  42. data/docs/README.md +655 -0
  43. data/docs/_coverpage.md +12 -0
  44. data/docs/index.html +26 -0
  45. data/docs/vendor/docsify/docsify@4.js +1 -0
  46. data/docs/vendor/docsify-themeable/theme-simple-dark.css +3 -0
  47. data/docs/vendor/docsify-themeable/theme-simple-dark.css.map +1 -0
  48. data/docs/vendor/prismjs/prism-bash.min.js +1 -0
  49. data/docs/vendor/prismjs/prism-ruby.min.js +1 -0
  50. data/docs/vendor/prismjs/prism-tomorrow.min.css +1 -0
  51. data/lighstorm.gemspec +3 -1
  52. data/models/connections/channel_node/accounting.rb +3 -14
  53. data/models/connections/channel_node/fee.rb +39 -77
  54. data/models/connections/channel_node/htlc.rb +30 -10
  55. data/models/connections/channel_node/policy.rb +6 -18
  56. data/models/connections/channel_node.rb +20 -26
  57. data/models/connections/forward_channel.rb +8 -43
  58. data/models/connections/payment_channel.rb +18 -39
  59. data/models/edges/channel/accounting.rb +28 -20
  60. data/models/edges/channel/hop.rb +65 -0
  61. data/models/edges/channel.rb +80 -172
  62. data/models/edges/forward.rb +9 -118
  63. data/models/edges/groups/{analysis.rb → channel_forwards/analysis.rb} +9 -9
  64. data/models/edges/groups/channel_forwards.rb +10 -40
  65. data/models/edges/payment.rb +29 -214
  66. data/models/errors.rb +29 -0
  67. data/models/invoice.rb +49 -0
  68. data/models/nodes/node/lightning.rb +5 -16
  69. data/models/nodes/node/platform.rb +7 -13
  70. data/models/nodes/node.rb +20 -115
  71. data/models/payment_request.rb +69 -0
  72. data/models/rate.rb +11 -1
  73. data/models/satoshis.rb +5 -6
  74. data/ports/dsl/lighstorm/errors.rb +5 -0
  75. data/ports/dsl/lighstorm.rb +11 -9
  76. data/ports/grpc.rb +62 -0
  77. data/static/cache.rb +12 -0
  78. data/static/spec.rb +3 -1
  79. metadata +55 -6
  80. data/models/connections/channel_node/constraints.rb +0 -24
@@ -5,58 +5,23 @@ require_relative '../edges/channel'
5
5
  module Lighstorm
6
6
  module Models
7
7
  class ForwardChannel
8
- KIND = :connection
9
-
10
- def initialize(direction, forward, respond_info: true)
11
- @respond_info = respond_info
12
- @direction = direction
13
- @forward = forward
8
+ def initialize(data)
9
+ @data = data
14
10
  end
15
11
 
16
- def channel
17
- @channel ||= Channel.find_by_id(channel_id)
12
+ def amount
13
+ @amount ||= Satoshis.new(milisatoshis: @data[:amount][:milisatoshis])
18
14
  end
19
15
 
20
- def amount
21
- @amount ||= if @direction == :in
22
- Satoshis.new(milisatoshis:
23
- @forward.data[:forwarding_history][:forwarding_events].first.amt_in_msat)
24
- else
25
- Satoshis.new(milisatoshis:
26
- @forward.data[:forwarding_history][:forwarding_events].first.amt_out_msat)
27
- end
16
+ def channel
17
+ @channel ||= Channel.new(@data[:channel])
28
18
  end
29
19
 
30
20
  def to_h
31
- response = {
21
+ {
32
22
  amount: amount.to_h,
33
- channel: { id: channel_id }
34
- }
35
-
36
- return response unless @respond_info
37
-
38
- response[:channel] = {
39
- id: channel.id,
40
- partner: {
41
- node: {
42
- alias: channel.partner&.node&.alias,
43
- public_key: channel.partner&.node&.public_key,
44
- color: channel.partner&.node&.color
45
- }
46
- }
23
+ channel: channel.to_h
47
24
  }
48
-
49
- response
50
- end
51
-
52
- private
53
-
54
- def channel_id
55
- if @direction == :in
56
- @forward.data[:forwarding_history][:forwarding_events].first.chan_id_in.to_s
57
- else
58
- @forward.data[:forwarding_history][:forwarding_events].first.chan_id_out.to_s
59
- end
60
25
  end
61
26
  end
62
27
  end
@@ -1,71 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../edges/channel'
3
+ require_relative '../edges/channel/hop'
4
4
  require_relative '../nodes/node'
5
5
 
6
6
  module Lighstorm
7
7
  module Models
8
8
  class PaymentChannel
9
- KIND = :connection
9
+ attr_reader :hop
10
+
11
+ def initialize(data, payment)
12
+ @data = data
10
13
 
11
- def initialize(raw_hop, hop_index, respond_info: true)
12
- @respond_info = respond_info
13
- @raw_hop = raw_hop
14
- @hop = hop_index
14
+ @hop = data[:hop]
15
+ @payment = payment
15
16
  end
16
17
 
17
- attr_reader :hop
18
+ def first?
19
+ @hop == 1
20
+ end
18
21
 
19
- def channel
20
- Channel.find_by_id(@raw_hop.chan_id)
22
+ def last?
23
+ @data[:is_last] == true
21
24
  end
22
25
 
23
26
  def amount
24
- @amount ||= Satoshis.new(milisatoshis: @raw_hop.amt_to_forward_msat)
27
+ @amount ||= Satoshis.new(milisatoshis: @data[:amount][:milisatoshis])
25
28
  end
26
29
 
27
30
  def fee
28
- @fee ||= Satoshis.new(milisatoshis: @raw_hop.fee_msat)
29
- end
30
-
31
- def raw
32
- @raw_hop
31
+ @fee ||= Satoshis.new(milisatoshis: @data[:fee][:milisatoshis])
33
32
  end
34
33
 
35
- def partner_node
36
- Node.find_by_public_key(@raw_hop.pub_key)
34
+ def channel
35
+ @channel ||= HopChannel.new(@data, @payment)
37
36
  end
38
37
 
39
38
  def to_h
40
- response = {
39
+ {
41
40
  hop: hop,
42
41
  amount: amount.to_h,
43
42
  fee: {
44
43
  milisatoshis: fee.milisatoshis,
45
44
  parts_per_million: fee.parts_per_million(amount.milisatoshis)
46
45
  },
47
- channel: {
48
- id: @raw_hop.chan_id.to_s,
49
- node: {
50
- public_key: @raw_hop.pub_key
51
- }
52
- }
53
- }
54
-
55
- return response unless @respond_info
56
-
57
- response[:channel] = {
58
- id: channel.id,
59
- partner: {
60
- node: {
61
- alias: partner_node&.alias,
62
- public_key: partner_node&.public_key,
63
- color: partner_node&.color
64
- }
65
- }
46
+ channel: channel.to_h
66
47
  }
67
-
68
- response
69
48
  end
70
49
  end
71
50
  end
@@ -1,48 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../satoshis'
4
+ require_relative '../../errors'
4
5
 
5
6
  module Lighstorm
6
7
  module Models
7
8
  class ChannelAccounting
8
- def initialize(channel)
9
- @channel = channel
9
+ def initialize(data, is_mine)
10
+ @data = data
11
+ @is_mine = is_mine
10
12
  end
11
13
 
12
14
  def capacity
13
- if @channel.data[:get_chan_info]
14
- @capacity ||= Satoshis.new(milisatoshis: @channel.data[:get_chan_info].capacity * 1000)
15
- elsif @channel.data[:describe_graph]
16
- @capacity ||= Satoshis.new(milisatoshis: @channel.data[:describe_graph].capacity * 1000)
17
- end
15
+ @capacity ||= if @data[:capacity]
16
+ Satoshis.new(
17
+ milisatoshis: @data[:capacity][:milisatoshis]
18
+ )
19
+ end
18
20
  end
19
21
 
20
22
  def sent
21
- return nil unless @channel.data[:list_channels]
23
+ raise Errors::NotYourChannelError unless @is_mine
22
24
 
23
- @sent ||= Satoshis.new(milisatoshis: (
24
- @channel.data[:list_channels][:channels].first.total_satoshis_sent.to_f * 1000.0
25
- ))
25
+ @sent ||= if @data[:sent]
26
+ Satoshis.new(
27
+ milisatoshis: @data[:sent][:milisatoshis]
28
+ )
29
+ end
26
30
  end
27
31
 
28
32
  def received
29
- return nil unless @channel.data[:list_channels]
33
+ raise Errors::NotYourChannelError unless @is_mine
30
34
 
31
- @received ||= Satoshis.new(milisatoshis: (
32
- @channel.data[:list_channels][:channels].first.total_satoshis_received.to_f * 1000.0
33
- ))
35
+ @received ||= if @data[:received]
36
+ Satoshis.new(
37
+ milisatoshis: @data[:received][:milisatoshis]
38
+ )
39
+ end
34
40
  end
35
41
 
36
42
  def unsettled
37
- return nil unless @channel.data[:list_channels]
43
+ raise Errors::NotYourChannelError unless @is_mine
38
44
 
39
- @unsettled ||= Satoshis.new(milisatoshis: (
40
- @channel.data[:list_channels][:channels].first.unsettled_balance.to_f * 1000.0
41
- ))
45
+ @unsettled ||= if @data[:unsettled]
46
+ Satoshis.new(
47
+ milisatoshis: @data[:unsettled][:milisatoshis]
48
+ )
49
+ end
42
50
  end
43
51
 
44
52
  def to_h
45
- if @channel.data[:get_chan_info]
53
+ if @is_mine
46
54
  {
47
55
  capacity: capacity.to_h,
48
56
  sent: sent.to_h,
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../channel'
4
+
5
+ module Lighstorm
6
+ module Models
7
+ class HopChannel < Channel
8
+ def initialize(data, payment)
9
+ @hop_data = data
10
+ @payment = payment
11
+ super(data[:channel])
12
+ end
13
+
14
+ def target
15
+ partners.find do |partner|
16
+ partner.node.public_key == @hop_data[:channel][:target][:public_key]
17
+ end.node
18
+ end
19
+
20
+ def exit
21
+ @exit ||= if include_myself? && @hop_data[:hop] == 1
22
+ partners.reverse.find do |partner|
23
+ !partner.node.myself?
24
+ end.node
25
+ elsif @hop_data[:hop] == 1
26
+ target
27
+ end
28
+ end
29
+
30
+ def entry
31
+ return nil if @hop_data[:is_last] && @hop_data[:hop] == 1
32
+
33
+ @entry ||= if include_myself? && @hop_data[:is_last]
34
+ if partners.size > 1
35
+ partners.reverse.find do |partner|
36
+ !partner.node.myself?
37
+ end.node
38
+ else
39
+ @payment.hops[@payment.hops.size - 2].channel.target
40
+ end
41
+ end
42
+ end
43
+
44
+ def include_myself?
45
+ !partners.find { |partner| partner.node.myself? }.nil?
46
+ end
47
+
48
+ def to_h
49
+ if !known? && !partners.size.positive?
50
+ { _key: _key, id: id }
51
+ else
52
+ target_hash = target.to_h
53
+ target_hash.delete(:platform)
54
+
55
+ result = { _key: _key, id: id, target: target_hash }
56
+
57
+ result[:entry] = entry.to_h if entry
58
+ result[:exit] = self.exit.to_h if self.exit
59
+
60
+ result
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -3,6 +3,9 @@
3
3
  require 'time'
4
4
  require 'date'
5
5
 
6
+ require_relative '../../ports/grpc'
7
+ require_relative '../../adapters/edges/channel'
8
+
6
9
  require_relative '../../components/lnd'
7
10
  require_relative '../../components/cache'
8
11
 
@@ -12,189 +15,114 @@ require_relative 'channel/accounting'
12
15
  require_relative '../connections/channel_node'
13
16
  require_relative '../satoshis'
14
17
 
18
+ require_relative '../errors'
19
+
15
20
  module Lighstorm
16
21
  module Models
17
22
  class Channel
18
- KIND = :edge
23
+ attr_reader :data, :_key, :id, :opened_at, :up_at, :active, :exposure
19
24
 
20
- attr_reader :data
21
-
22
- def self.all
23
- response = LND.instance.middleware('lightning.describe_graph') do
24
- LND.instance.client.lightning.describe_graph
25
- end
25
+ def initialize(data)
26
+ @data = data
26
27
 
27
- response.edges.map do |raw_channel|
28
- Channel.new({ describe_graph: raw_channel })
29
- end
28
+ @_key = data[:_key]
29
+ @id = data[:id]
30
30
  end
31
31
 
32
- def self.mine
33
- response = Cache.for('lightning.list_channels') do
34
- LND.instance.middleware('lightning.list_channels') do
35
- LND.instance.client.lightning.list_channels
36
- end
37
- end
38
-
39
- response.channels.map do |channel|
40
- Channel.find_by_id(channel.chan_id.to_s)
41
- end
32
+ def known?
33
+ @data[:known] == true
42
34
  end
43
35
 
44
- def self.find_by_id(id)
45
- Channel.new({ id: id })
46
- end
36
+ def mine?
37
+ ensure_known!
47
38
 
48
- def id
49
- # Standard JSON don't support BigInt, so, a String is safer.
50
- @id.to_s
39
+ @data[:mine]
51
40
  end
52
41
 
53
- def initialize(params)
54
- if params[:id]
55
- begin
56
- response = Cache.for('lightning.get_chan_info', params: { chan_id: params[:id].to_i }) do
57
- LND.instance.middleware('lightning.get_chan_info') do
58
- LND.instance.client.lightning.get_chan_info(chan_id: params[:id].to_i)
59
- end
60
- end
61
-
62
- @data = { get_chan_info: response }
63
- @id = @data[:get_chan_info].channel_id
64
- rescue StandardError => e
65
- @data = { get_chan_info: nil, error: e }
66
- @id = params[:id]
67
- end
68
- elsif params[:describe_graph]
69
- @data = { describe_graph: params[:describe_graph] }
70
- @id = @data[:describe_graph].channel_id
71
- end
72
-
73
- fetch_from_fee_report!
74
-
75
- fetch_from_list_channels!
76
- calculate_times_after_list_channels!
77
- end
42
+ def exposure
43
+ ensure_known!
78
44
 
79
- def error?
80
- !@data[:error].nil?
45
+ @data[:exposure]
81
46
  end
82
47
 
83
- def error
84
- @data[:error]
85
- end
48
+ def opened_at
49
+ ensure_mine!
86
50
 
87
- def active
88
- @data[:list_channels] ? @data[:list_channels][:channels].first.active : nil
51
+ @data[:opened_at]
89
52
  end
90
53
 
91
- def exposure
92
- return 'public' if @data[:describe_graph]
93
-
94
- return unless @data[:list_channels]
54
+ def up_at
55
+ ensure_mine!
95
56
 
96
- @data[:list_channels][:channels].first.private ? 'private' : 'public'
57
+ @data[:up_at]
97
58
  end
98
59
 
99
- def opened_at
100
- @opened_at ||= if @data[:list_channels]
101
- DateTime.parse(
102
- (Time.now - @data[:list_channels][:channels].first.lifetime).to_s
103
- )
104
- end
105
- end
60
+ def active
61
+ ensure_mine!
106
62
 
107
- def up_at
108
- @up_at ||= if @data[:list_channels]
109
- DateTime.parse(
110
- (Time.now - @data[:list_channels][:channels].first.uptime).to_s
111
- )
112
- end
63
+ @data[:active]
113
64
  end
114
65
 
115
66
  def accounting
116
- @accounting ||= ChannelAccounting.new(self)
67
+ ensure_known!
68
+
69
+ @accounting ||= @data[:accounting] ? ChannelAccounting.new(@data[:accounting], mine?) : nil
117
70
  end
118
71
 
119
- def partners(fetch: false)
120
- @partners ||= if mine?
121
- [myself, partner]
122
- elsif @data[:describe_graph]
123
- [
124
- ChannelNode.new(
125
- self,
126
- Node.new({ public_key: @data[:describe_graph].node1_pub }, fetch: fetch)
127
- ),
128
- ChannelNode.new(
129
- self,
130
- Node.new({ public_key: @data[:describe_graph].node2_pub }, fetch: fetch)
131
- )
132
- ]
133
- elsif @data[:get_chan_info]
134
- [
135
- ChannelNode.new(
136
- self,
137
- Node.new({ public_key: @data[:get_chan_info].node1_pub }, fetch: fetch)
138
- ),
139
- ChannelNode.new(
140
- self,
141
- Node.new({ public_key: @data[:get_chan_info].node2_pub }, fetch: fetch)
142
- )
143
- ]
72
+ def partners
73
+ @partners ||= if @data[:partners]
74
+ @data[:partners].map do |data|
75
+ ChannelNode.new(data, known? ? mine? : nil, transaction)
76
+ end
144
77
  else
145
- raise 'missing data'
78
+ []
146
79
  end
147
80
  end
148
81
 
149
- def mine?
150
- my_node = Node.myself.public_key
151
-
152
- if @data[:get_chan_info]
153
- (
154
- @data[:get_chan_info].node1_pub == my_node || @data[:get_chan_info].node2_pub == my_node
155
- )
156
- elsif @data[:describe_graph]
157
- (
158
- @data[:describe_graph].node1_pub == my_node || @data[:describe_graph].node2_pub == my_node
159
- )
160
- else
161
- false
162
- end
163
- end
164
-
165
82
  def myself
166
- raise 'not your channel' unless mine?
83
+ ensure_mine!
167
84
 
168
- @myself ||= ChannelNode.new(self, Node.myself)
85
+ @myself ||= partners.find { |partner| partner.node.myself? }
169
86
  end
170
87
 
171
88
  def partner
172
- raise 'not your channel' unless mine?
89
+ ensure_mine!
173
90
 
174
- key = @data[:get_chan_info] ? :get_chan_info : :describe_graph
91
+ @partner ||= partners.find { |partner| !partner.node.myself? }
92
+ end
175
93
 
176
- public_key = if @data[key].node1_pub == myself.node.public_key
177
- @data[key].node2_pub
178
- else
179
- @data[key].node1_pub
180
- end
94
+ def transaction
95
+ Struct.new(:data) do
96
+ def funding
97
+ Struct.new(:data) do
98
+ def id
99
+ data[:id]
100
+ end
181
101
 
182
- @partner ||= ChannelNode.new(self, Node.find_by_public_key(public_key))
183
- end
102
+ def index
103
+ data[:index]
104
+ end
184
105
 
185
- def raw
186
- {
187
- get_chan_info: @data[:get_chan_info].to_h,
188
- describe_graph: @data[:describe_graph].to_h,
189
- list_channels: {
190
- channels: @data[:list_channels] ? @data[:list_channels][:channels].map(&:to_h) : nil
191
- }
192
- }
106
+ def to_h
107
+ { id: id, index: index }
108
+ end
109
+ end.new(data[:funding])
110
+ end
111
+
112
+ def to_h
113
+ { funding: funding.to_h }
114
+ end
115
+ end.new(@data[:transaction])
193
116
  end
194
117
 
195
118
  def to_h
196
- if @data[:get_chan_info]
119
+ if !known? && partners.size.positive?
120
+ { _key: _key, id: id, partners: partners.map(&:to_h) }
121
+ elsif !known?
122
+ { _key: _key, id: id }
123
+ elsif mine?
197
124
  {
125
+ _key: _key,
198
126
  id: id,
199
127
  opened_at: opened_at,
200
128
  up_at: up_at,
@@ -204,51 +132,31 @@ module Lighstorm
204
132
  partner: partner.to_h,
205
133
  myself: myself.to_h
206
134
  }
207
- else
135
+ elsif @data[:accounting]
208
136
  {
137
+ _key: _key,
209
138
  id: id,
210
139
  accounting: accounting.to_h,
211
140
  partners: partners.map(&:to_h)
212
141
  }
142
+ else
143
+ {
144
+ _key: _key,
145
+ id: id,
146
+ partners: partners.map(&:to_h)
147
+ }
213
148
  end
214
149
  end
215
150
 
216
151
  private
217
152
 
218
- # Ensure that we are getting fresh up-date data about our own fees.
219
- def fetch_from_fee_report!
220
- response = Cache.for('lightning.fee_report') do
221
- LND.instance.middleware('lightning.fee_report') do
222
- LND.instance.client.lightning.fee_report
223
- end
224
- end
225
-
226
- response.channel_fees.map do |channel|
227
- if channel.chan_id == @id
228
- @data[:fee_report] = { channel_fees: [channel] }
229
- break
230
- end
231
- end
232
- end
233
-
234
- def fetch_from_list_channels!
235
- response = Cache.for('lightning.list_channels') do
236
- LND.instance.middleware('lightning.list_channels') do
237
- LND.instance.client.lightning.list_channels
238
- end
239
- end
240
-
241
- response.channels.map do |channel|
242
- if channel.chan_id == @id
243
- @data[:list_channels] = { channels: [channel] }
244
- break
245
- end
246
- end
153
+ def ensure_known!
154
+ raise Errors::UnknownChannelError unless known?
247
155
  end
248
156
 
249
- def calculate_times_after_list_channels!
250
- opened_at
251
- up_at
157
+ def ensure_mine!
158
+ ensure_known!
159
+ raise Errors::NotYourChannelError if @data[:mine] == false
252
160
  end
253
161
  end
254
162
  end