lighstorm 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby-rspec-tests.yml +39 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +5 -5
  5. data/adapters/connections/channel_node/policy.rb +61 -0
  6. data/adapters/connections/channel_node.rb +40 -18
  7. data/adapters/edges/channel.rb +23 -7
  8. data/adapters/edges/forward.rb +1 -2
  9. data/adapters/edges/payment/purpose.rb +0 -2
  10. data/adapters/edges/payment.rb +2 -5
  11. data/adapters/invoice.rb +2 -3
  12. data/adapters/nodes/node.rb +39 -5
  13. data/adapters/payment_request.rb +0 -1
  14. data/controllers/channel/actions/apply_gossip.rb +194 -0
  15. data/controllers/channel/find_by_id.rb +1 -1
  16. data/controllers/channel/mine.rb +1 -1
  17. data/controllers/channel.rb +4 -0
  18. data/controllers/node/actions/apply_gossip.rb +112 -0
  19. data/controllers/node.rb +4 -0
  20. data/controllers/payment/all.rb +52 -36
  21. data/controllers/payment.rb +6 -6
  22. data/docs/README.md +89 -13
  23. data/docs/_coverpage.md +1 -1
  24. data/docs/index.html +1 -1
  25. data/models/concerns/protectable.rb +23 -0
  26. data/models/connections/channel_node/accounting.rb +4 -0
  27. data/models/connections/channel_node/fee.rb +27 -18
  28. data/models/connections/channel_node/htlc/blocks/delta.rb +39 -0
  29. data/models/connections/channel_node/htlc.rb +48 -14
  30. data/models/connections/channel_node/policy.rb +11 -2
  31. data/models/connections/channel_node.rb +33 -1
  32. data/models/edges/channel/accounting.rb +17 -0
  33. data/models/edges/channel.rb +39 -5
  34. data/models/edges/forward.rb +0 -1
  35. data/models/edges/payment.rb +0 -1
  36. data/models/errors.rb +3 -0
  37. data/models/nodes/node/lightning.rb +6 -0
  38. data/models/nodes/node/platform.rb +6 -0
  39. data/models/nodes/node.rb +51 -0
  40. data/models/payment_request.rb +6 -3
  41. data/ports/grpc/session.rb +26 -0
  42. data/ports/grpc.rb +23 -5
  43. data/static/spec.rb +1 -1
  44. metadata +9 -2
@@ -4,6 +4,7 @@ require 'securerandom'
4
4
 
5
5
  require_relative '../../satoshis'
6
6
  require_relative '../../rate'
7
+ require_relative '../../concerns/protectable'
7
8
 
8
9
  require_relative '../../../components/lnd'
9
10
  require_relative '../../../controllers/channel/actions/update_fee'
@@ -11,24 +12,38 @@ require_relative '../../../controllers/channel/actions/update_fee'
11
12
  module Lighstorm
12
13
  module Models
13
14
  class Fee
15
+ include Protectable
16
+
14
17
  def initialize(policy, data)
15
18
  @policy = policy
16
19
  @data = data
17
20
  end
18
21
 
19
22
  def base
23
+ return nil unless @data[:base]
24
+
20
25
  @base ||= Satoshis.new(milisatoshis: @data[:base][:milisatoshis])
21
26
  end
22
27
 
23
28
  def rate
29
+ return nil unless @data[:rate]
30
+
24
31
  @rate ||= Rate.new(parts_per_million: @data[:rate][:parts_per_million])
25
32
  end
26
33
 
27
34
  def to_h
28
- {
29
- base: base.to_h,
30
- rate: rate.to_h
31
- }
35
+ result = {}
36
+
37
+ result[:base] = base.to_h if @data[:base]
38
+ result[:rate] = rate.to_h if @data[:rate]
39
+
40
+ return nil if result.empty?
41
+
42
+ result
43
+ end
44
+
45
+ def dump
46
+ Marshal.load(Marshal.dump(@data))
32
47
  end
33
48
 
34
49
  def update(params, preview: false, fake: false)
@@ -38,29 +53,23 @@ module Lighstorm
38
53
  end
39
54
 
40
55
  def base=(value)
41
- validate_token!(value)
56
+ protect!(value)
42
57
 
43
58
  @base = value[:value]
44
- end
45
59
 
46
- def rate=(value)
47
- validate_token!(value)
60
+ @data[:base] = { milisatoshis: @base.milisatoshis }
48
61
 
49
- @rate = value[:value]
62
+ base
50
63
  end
51
64
 
52
- def prepare_token!(token)
53
- @token = token
54
- end
55
-
56
- private
65
+ def rate=(value)
66
+ protect!(value)
57
67
 
58
- def validate_token!(value)
59
- token = value.is_a?(Hash) ? value[:token] : nil
68
+ @rate = value[:value]
60
69
 
61
- raise OperationNotAllowedError if token.nil? || @token.nil? || token != @token
70
+ @data[:rate] = { parts_per_million: @rate.parts_per_million }
62
71
 
63
- @token = nil
72
+ rate
64
73
  end
65
74
  end
66
75
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../../concerns/protectable'
4
+
5
+ module Lighstorm
6
+ module Models
7
+ class BlocksDelta
8
+ include Protectable
9
+
10
+ def initialize(data)
11
+ @data = data
12
+ end
13
+
14
+ def minimum
15
+ @minimum ||= @data[:minimum]
16
+ end
17
+
18
+ def to_h
19
+ {
20
+ minimum: minimum
21
+ }
22
+ end
23
+
24
+ def dump
25
+ Marshal.load(Marshal.dump(@data))
26
+ end
27
+
28
+ def minimum=(value)
29
+ protect!(value)
30
+
31
+ @minimum = value[:value]
32
+
33
+ @data[:minimum] = @minimum
34
+
35
+ minimum
36
+ end
37
+ end
38
+ end
39
+ end
@@ -2,42 +2,48 @@
2
2
 
3
3
  require_relative '../../satoshis'
4
4
  require_relative '../../rate'
5
-
6
- require_relative '../../../components/lnd'
5
+ require_relative '../../concerns/protectable'
6
+ require_relative 'htlc/blocks/delta'
7
7
 
8
8
  module Lighstorm
9
9
  module Models
10
10
  class HTLC
11
+ include Protectable
12
+
11
13
  def initialize(data)
12
14
  @data = data
13
15
  end
14
16
 
15
17
  def minimum
16
- @minimum ||= Satoshis.new(milisatoshis: @data[:minimum][:milisatoshis])
18
+ @minimum ||= if @data[:minimum]
19
+ Satoshis.new(
20
+ milisatoshis: @data[:minimum][:milisatoshis]
21
+ )
22
+ end
17
23
  end
18
24
 
19
25
  def maximum
20
- @maximum ||= Satoshis.new(milisatoshis: @data[:maximum][:milisatoshis])
26
+ @maximum ||= if @data[:maximum]
27
+ Satoshis.new(
28
+ milisatoshis: @data[:maximum][:milisatoshis]
29
+ )
30
+ end
21
31
  end
22
32
 
23
33
  def blocks
24
34
  @blocks ||= Struct.new(:data) do
25
35
  def delta
26
- Struct.new(:data) do
27
- def minimum
28
- data[:minimum]
29
- end
30
-
31
- def to_h
32
- { minimum: minimum }
33
- end
34
- end.new(data[:delta])
36
+ @delta ||= BlocksDelta.new(data[:delta] || {})
37
+ end
38
+
39
+ def dump
40
+ { delta: delta.dump }
35
41
  end
36
42
 
37
43
  def to_h
38
44
  { delta: delta.to_h }
39
45
  end
40
- end.new(@data[:blocks])
46
+ end.new(@data[:blocks] || {})
41
47
  end
42
48
 
43
49
  def to_h
@@ -47,6 +53,34 @@ module Lighstorm
47
53
  blocks: blocks.to_h
48
54
  }
49
55
  end
56
+
57
+ def dump
58
+ result = Marshal.load(Marshal.dump(@data))
59
+
60
+ result = result.merge({ blocks: blocks.dump }) if @data[:blocks]
61
+
62
+ result
63
+ end
64
+
65
+ def minimum=(value)
66
+ protect!(value)
67
+
68
+ @minimum = value[:value]
69
+
70
+ @data[:minimum] = { milisatoshis: @minimum.milisatoshis }
71
+
72
+ minimum
73
+ end
74
+
75
+ def maximum=(value)
76
+ protect!(value)
77
+
78
+ @maximum = value[:value]
79
+
80
+ @data[:maximum] = { milisatoshis: @maximum.milisatoshis }
81
+
82
+ maximum
83
+ end
50
84
  end
51
85
  end
52
86
  end
@@ -14,16 +14,25 @@ module Lighstorm
14
14
  end
15
15
 
16
16
  def fee
17
- @fee ||= @data ? Fee.new(self, @data[:fee]) : nil
17
+ @fee ||= Fee.new(self, @data ? @data[:fee] : {})
18
18
  end
19
19
 
20
20
  def htlc
21
- @htlc ||= @data ? HTLC.new(@data[:htlc]) : nil
21
+ @htlc ||= HTLC.new(@data ? @data[:htlc] : {})
22
22
  end
23
23
 
24
24
  def to_h
25
25
  { fee: fee.to_h, htlc: htlc.to_h }
26
26
  end
27
+
28
+ def dump
29
+ result = Marshal.load(Marshal.dump(@data))
30
+
31
+ result = result.merge({ fee: fee.dump }) if @data && @data[:fee]
32
+ result = result.merge({ htlc: htlc.dump }) if @data && @data[:htlc]
33
+
34
+ result
35
+ end
27
36
  end
28
37
  end
29
38
  end
@@ -9,12 +9,21 @@ require_relative '../errors'
9
9
  module Lighstorm
10
10
  module Models
11
11
  class ChannelNode
12
+ include Protectable
13
+
14
+ attr_reader :state
15
+
12
16
  def initialize(data, is_mine, transaction)
13
17
  @data = data
18
+ @state = data[:state]
14
19
  @is_mine = is_mine
15
20
  @transaction = transaction
16
21
  end
17
22
 
23
+ def active?
24
+ state == 'active'
25
+ end
26
+
18
27
  def node
19
28
  @node ||= Node.new(@data[:node])
20
29
  end
@@ -30,13 +39,36 @@ module Lighstorm
30
39
  end
31
40
 
32
41
  def to_h
33
- restult = { node: node.to_h }
42
+ restult = { state: state, node: node.to_h }
34
43
 
35
44
  restult[:accounting] = accounting.to_h if @is_mine
36
45
  restult[:policy] = policy.to_h if @data[:policy]
37
46
 
38
47
  restult
39
48
  end
49
+
50
+ def dump
51
+ result = Marshal.load(Marshal.dump(@data)).merge(
52
+ {
53
+ node: node.dump,
54
+ policy: policy.dump
55
+ }
56
+ )
57
+
58
+ result[:accounting] = accounting.dump if @is_mine
59
+ result.delete(:policy) if result[:policy].nil?
60
+
61
+ result
62
+ end
63
+
64
+ def state=(value)
65
+ protect!(value)
66
+
67
+ @state = value[:value]
68
+ @data[:state] = @state
69
+
70
+ state
71
+ end
40
72
  end
41
73
  end
42
74
  end
@@ -2,10 +2,13 @@
2
2
 
3
3
  require_relative '../../satoshis'
4
4
  require_relative '../../errors'
5
+ require_relative '../../concerns/protectable'
5
6
 
6
7
  module Lighstorm
7
8
  module Models
8
9
  class ChannelAccounting
10
+ include Protectable
11
+
9
12
  def initialize(data, is_mine)
10
13
  @data = data
11
14
  @is_mine = is_mine
@@ -63,6 +66,20 @@ module Lighstorm
63
66
  }
64
67
  end
65
68
  end
69
+
70
+ def dump
71
+ Marshal.load(Marshal.dump(@data))
72
+ end
73
+
74
+ def capacity=(value)
75
+ protect!(value)
76
+
77
+ @capacity = value[:value]
78
+
79
+ @data[:capacity][:milisatoshis] = @capacity.milisatoshis
80
+
81
+ capacity
82
+ end
66
83
  end
67
84
  end
68
85
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'time'
4
- require 'date'
5
4
 
6
5
  require_relative '../../ports/grpc'
7
6
  require_relative '../../adapters/edges/channel'
@@ -12,6 +11,7 @@ require_relative '../../components/cache'
12
11
  require_relative '../nodes/node'
13
12
  require_relative 'channel/accounting'
14
13
 
14
+ require_relative '../../controllers/channel/actions/apply_gossip'
15
15
  require_relative '../connections/channel_node'
16
16
  require_relative '../satoshis'
17
17
 
@@ -20,7 +20,7 @@ require_relative '../errors'
20
20
  module Lighstorm
21
21
  module Models
22
22
  class Channel
23
- attr_reader :data, :_key, :id, :opened_at, :up_at, :active, :exposure
23
+ attr_reader :data, :_key, :id
24
24
 
25
25
  def initialize(data)
26
26
  @data = data
@@ -57,10 +57,16 @@ module Lighstorm
57
57
  @data[:up_at]
58
58
  end
59
59
 
60
- def active
60
+ def active?
61
61
  ensure_mine!
62
62
 
63
- @data[:active]
63
+ @data[:state] == 'active'
64
+ end
65
+
66
+ def state
67
+ ensure_mine!
68
+
69
+ @data[:state]
64
70
  end
65
71
 
66
72
  def accounting
@@ -126,7 +132,7 @@ module Lighstorm
126
132
  id: id,
127
133
  opened_at: opened_at,
128
134
  up_at: up_at,
129
- active: active,
135
+ state: state,
130
136
  exposure: exposure,
131
137
  accounting: accounting.to_h,
132
138
  partner: partner.to_h,
@@ -148,6 +154,34 @@ module Lighstorm
148
154
  end
149
155
  end
150
156
 
157
+ def dump
158
+ result = Marshal.load(Marshal.dump(@data)).merge(
159
+ { partners: partners.map(&:dump) }
160
+ )
161
+
162
+ result[:accounting] = accounting.dump if known?
163
+
164
+ result
165
+ end
166
+
167
+ def self.adapt(gossip: nil, dump: nil)
168
+ raise TooManyArgumentsError, 'you need to pass gossip: or dump:, not both' if !gossip.nil? && !dump.nil?
169
+
170
+ raise ArgumentError, 'missing gossip: or dump:' if gossip.nil? && dump.nil?
171
+
172
+ if !gossip.nil?
173
+ new(Adapter::Channel.subscribe_channel_graph(gossip))
174
+ elsif !dump.nil?
175
+ new(dump)
176
+ end
177
+ end
178
+
179
+ def apply!(gossip:)
180
+ Controllers::Channel::ApplyGossip.perform(
181
+ self, gossip
182
+ )
183
+ end
184
+
151
185
  private
152
186
 
153
187
  def ensure_known!
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'time'
4
- require 'date'
5
4
 
6
5
  require_relative '../satoshis'
7
6
 
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'time'
4
- require 'date'
5
4
 
6
5
  require_relative '../satoshis'
7
6
 
data/models/errors.rb CHANGED
@@ -6,6 +6,9 @@ module Lighstorm
6
6
 
7
7
  class ToDoError < LighstormError; end
8
8
 
9
+ class TooManyArgumentsError < LighstormError; end
10
+ class IncoherentGossipError < LighstormError; end
11
+ class MissingGossipHandlerError < LighstormError; end
9
12
  class MissingCredentialsError < LighstormError; end
10
13
  class MissingMilisatoshisError < LighstormError; end
11
14
  class MissingPartsPerMillionError < LighstormError; end
@@ -20,6 +20,12 @@ module Lighstorm
20
20
  version: version
21
21
  }
22
22
  end
23
+
24
+ def dump
25
+ Marshal.load(
26
+ Marshal.dump({ implementation: implementation, version: version })
27
+ )
28
+ end
23
29
  end
24
30
  end
25
31
  end
@@ -37,6 +37,12 @@ module Lighstorm
37
37
 
38
38
  response
39
39
  end
40
+
41
+ def dump
42
+ data = Marshal.load(Marshal.dump(@data))
43
+ data.merge(lightning: lightning.dump) if @node.myself?
44
+ data
45
+ end
40
46
  end
41
47
  end
42
48
  end
data/models/nodes/node.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../controllers/node/actions/apply_gossip'
3
4
  require_relative '../../controllers/channel'
5
+ require_relative '../../adapters/nodes/node'
6
+ require_relative '../concerns/protectable'
4
7
  require_relative '../errors'
5
8
 
6
9
  require_relative 'node/platform'
@@ -8,6 +11,8 @@ require_relative 'node/platform'
8
11
  module Lighstorm
9
12
  module Models
10
13
  class Node
14
+ include Protectable
15
+
11
16
  attr_reader :data, :_key, :alias, :public_key, :color
12
17
 
13
18
  def initialize(data)
@@ -46,6 +51,52 @@ module Lighstorm
46
51
 
47
52
  result
48
53
  end
54
+
55
+ def dump
56
+ result = Marshal.load(Marshal.dump(@data))
57
+
58
+ result[:platform] = platform.dump if @data[:platform]
59
+
60
+ result
61
+ end
62
+
63
+ def alias=(value)
64
+ protect!(value)
65
+
66
+ @alias = value[:value]
67
+
68
+ @data[:alias] = @alias
69
+
70
+ self.alias
71
+ end
72
+
73
+ def color=(value)
74
+ protect!(value)
75
+
76
+ @color = value[:value]
77
+
78
+ @data[:color] = @color
79
+
80
+ color
81
+ end
82
+
83
+ def self.adapt(gossip: nil, dump: nil)
84
+ raise TooManyArgumentsError, 'you need to pass gossip: or dump:, not both' if !gossip.nil? && !dump.nil?
85
+
86
+ raise ArgumentError, 'missing gossip: or dump:' if gossip.nil? && dump.nil?
87
+
88
+ if !gossip.nil?
89
+ new(Adapter::Node.subscribe_channel_graph(gossip))
90
+ elsif !dump.nil?
91
+ new(dump)
92
+ end
93
+ end
94
+
95
+ def apply!(gossip:)
96
+ Controllers::Node::ApplyGossip.perform(
97
+ self, gossip
98
+ )
99
+ end
49
100
  end
50
101
  end
51
102
  end
@@ -11,7 +11,10 @@ module Lighstorm
11
11
  def initialize(data)
12
12
  @data = data
13
13
 
14
- @_key = Digest::SHA256.hexdigest(data[:code])
14
+ @_key = data[:_key] || Digest::SHA256.hexdigest(
15
+ data[:code] || "#{data[:code][:amount][:milisatoshis]}#{Time.now}"
16
+ )
17
+
15
18
  @code = data[:code]
16
19
 
17
20
  @address = data[:address]
@@ -34,7 +37,7 @@ module Lighstorm
34
37
  def to_h
35
38
  { memo: memo, hash: hash }
36
39
  end
37
- end.new(@data[:description])
40
+ end.new(@data[:description] || {})
38
41
  end
39
42
 
40
43
  def secret
@@ -51,7 +54,7 @@ module Lighstorm
51
54
  # Don't expose 'secret' by default: Security
52
55
  { hash: hash }
53
56
  end
54
- end.new(@data[:secret])
57
+ end.new(@data[:secret] || {})
55
58
  end
56
59
 
57
60
  def to_h
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lighstorm
4
+ module Ports
5
+ class GRPCSession
6
+ attr_reader :calls
7
+
8
+ def initialize(grpc)
9
+ @grpc = grpc
10
+ @calls = {}
11
+ end
12
+
13
+ def handler(key, &block)
14
+ @calls[key] = 0 unless @calls.key?(key)
15
+ @calls[key] += 1
16
+ block.call
17
+ end
18
+
19
+ def method_missing(method_name, *args)
20
+ @grpc.send(method_name, *args) do |key, &block|
21
+ handler(key, &block)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
data/ports/grpc.rb CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  require_relative '../components/cache'
4
4
  require_relative '../components/lnd'
5
+ require_relative 'grpc/session'
5
6
 
6
7
  module Lighstorm
7
8
  module Ports
8
9
  class GRPC
9
- def initialize(service, service_key)
10
+ def initialize(service, service_key, &handler)
10
11
  @service = service
11
12
  @service_key = service_key
13
+ @handler = handler
12
14
  end
13
15
 
14
16
  def call!(call_key, *args, &block)
@@ -17,12 +19,24 @@ module Lighstorm
17
19
  if block.nil?
18
20
  response = Cache.for(key, params: args&.first) do
19
21
  LND.instance.middleware(key) do
20
- @service.send(call_key, *args, &block)
22
+ if @handler
23
+ @handler.call(call_key) do
24
+ @service.send(call_key, *args, &block)
25
+ end
26
+ else
27
+ @service.send(call_key, *args, &block)
28
+ end
21
29
  end
22
30
  end
23
31
  else
24
32
  LND.instance.middleware(key) do
25
- @service.send(call_key, *args, &block)
33
+ if @handler
34
+ @handler.call(call_key) do
35
+ @service.send(call_key, *args, &block)
36
+ end
37
+ else
38
+ @service.send(call_key, *args, &block)
39
+ end
26
40
  end
27
41
  end
28
42
  end
@@ -41,7 +55,11 @@ module Lighstorm
41
55
  @service.respond_to?(call_key) || super
42
56
  end
43
57
 
44
- def self.method_missing(method_name, *_args)
58
+ def self.session
59
+ GRPCSession.new(self)
60
+ end
61
+
62
+ def self.method_missing(method_name, *_args, &block)
45
63
  service_key = method_name.to_sym
46
64
 
47
65
  unless LND.instance.client.respond_to?(service_key)
@@ -49,7 +67,7 @@ module Lighstorm
49
67
  "Method `#{method_name}` doesn't exist."
50
68
  end
51
69
 
52
- new(LND.instance.client.send(service_key), service_key)
70
+ new(LND.instance.client.send(service_key), service_key, &block)
53
71
  end
54
72
 
55
73
  def self.respond_to_missing?(method_name, include_private = false)
data/static/spec.rb CHANGED
@@ -4,7 +4,7 @@ module Lighstorm
4
4
  module Static
5
5
  SPEC = {
6
6
  name: 'lighstorm',
7
- version: '0.0.4',
7
+ version: '0.0.5',
8
8
  author: 'icebaker',
9
9
  summary: 'API for interacting with a Lightning Node.',
10
10
  description: 'Lighstorm is an opinionated abstraction layer on top of the lnd-client for interacting with a Lightning Node.',