stellar_spectrum 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +10 -7
- data/README.md +8 -0
- data/lib/stellar_spectrum/client.rb +20 -130
- data/lib/stellar_spectrum/services/get_available_channels.rb +9 -4
- data/lib/stellar_spectrum/services/get_channel_account_info.rb +23 -0
- data/lib/stellar_spectrum/services/get_channel_accounts.rb +15 -0
- data/lib/stellar_spectrum/services/get_locked_accounts.rb +9 -2
- data/lib/stellar_spectrum/services/get_sequence_number.rb +17 -0
- data/lib/stellar_spectrum/services/increment_tries.rb +13 -0
- data/lib/stellar_spectrum/services/init_redis.rb +6 -2
- data/lib/stellar_spectrum/services/init_stellar_client.rb +13 -0
- data/lib/stellar_spectrum/services/lock_channel.rb +25 -0
- data/lib/stellar_spectrum/services/pick_channel.rb +13 -0
- data/lib/stellar_spectrum/services/send_payment.rb +64 -0
- data/lib/stellar_spectrum/services/sending_payment/attempt_release_lock.rb +18 -0
- data/lib/stellar_spectrum/services/sending_payment/retry.rb +45 -0
- data/lib/stellar_spectrum/services/sending_payment/send_asset.rb +57 -0
- data/lib/stellar_spectrum/services/unlocking/attempt_release.rb +37 -0
- data/lib/stellar_spectrum/services/unlocking/check_sequence_number.rb +25 -0
- data/lib/stellar_spectrum/services/unlocking/get_account_to_unlock.rb +23 -0
- data/lib/stellar_spectrum/services/unlocking/unlock.rb +16 -0
- data/lib/stellar_spectrum/version.rb +1 -1
- data/lib/stellar_spectrum.rb +16 -0
- data/stellar_spectrum.gemspec +1 -0
- metadata +33 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82171102575548c02b4566a049a7959544daea5bb1e4509df7a5ce1e78dd1a2e
|
4
|
+
data.tar.gz: 88a64d56566dacc96206c78be2fb0f4aa37ba36a0565009c9db048f55feaa3f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 548ca98342523d4b589461d1a8f5ea210778a0d97acc6e0844b83c38d9ca36c9d250ef04e73b9dd5fa906ee063f5f103ac409f4f7dbfaeb1fa496e4e80260e38
|
7
|
+
data.tar.gz: 398063e01bf565768e527af8eba2dce1dae26d0c5f914363a76de8a7a3bde7398d1581315e032647ea33f991610cbee71892ffe3104d3e261776b26fed4ced46
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.5.3
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## [1.1.0] - 2018-12-12
|
8
|
+
### Added
|
9
|
+
- Retry when timeout is encountered [#6](https://github.com/bloom-solutions/stellar_spectrum-ruby/pull/6)
|
10
|
+
|
7
11
|
## [1.0.0] - 2018-11-30
|
8
12
|
### Changed
|
9
13
|
- Use stellar-sdk => 0.6.0
|
data/Gemfile.lock
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
stellar_spectrum (1.
|
4
|
+
stellar_spectrum (1.1.0)
|
5
5
|
activesupport
|
6
6
|
gem_config
|
7
|
+
light-service
|
7
8
|
redis
|
8
9
|
stellar-sdk (>= 0.6.0)
|
9
10
|
|
10
11
|
GEM
|
11
12
|
remote: https://rubygems.org/
|
12
13
|
specs:
|
13
|
-
activemodel (5.2.
|
14
|
-
activesupport (= 5.2.
|
15
|
-
activesupport (5.2.
|
14
|
+
activemodel (5.2.2)
|
15
|
+
activesupport (= 5.2.2)
|
16
|
+
activesupport (5.2.2)
|
16
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
18
|
i18n (>= 0.7, < 2)
|
18
19
|
minitest (~> 5.1)
|
@@ -49,13 +50,15 @@ GEM
|
|
49
50
|
faraday_middleware
|
50
51
|
net-http-digest_auth
|
51
52
|
uri_template
|
52
|
-
i18n (1.
|
53
|
+
i18n (1.2.0)
|
53
54
|
concurrent-ruby (~> 1.0)
|
54
|
-
|
55
|
+
light-service (0.11.0)
|
56
|
+
activesupport (>= 3.0)
|
57
|
+
method_source (0.9.2)
|
55
58
|
minitest (5.11.3)
|
56
59
|
multipart-post (2.0.0)
|
57
60
|
net-http-digest_auth (1.4.1)
|
58
|
-
pry (0.
|
61
|
+
pry (0.12.2)
|
59
62
|
coderay (~> 1.1.0)
|
60
63
|
method_source (~> 0.9.0)
|
61
64
|
pry-byebug (3.6.0)
|
data/README.md
CHANGED
@@ -61,6 +61,14 @@ When another transaction is created, an available (unlocked) payment channel wil
|
|
61
61
|
|
62
62
|
If all payment channels are unavailable (locked), then the call will block the Ruby process/thread until a channel is available. Therefore, unless you have way more channels than you will ever consume, we strongly recommend you send transactions in a background worker like Sidekiq.
|
63
63
|
|
64
|
+
#### Retries
|
65
|
+
|
66
|
+
When the call to Horizon [times out](https://www.stellar.org/developers/horizon/reference/errors/timeout.html), we do not know whether or not the asset was sent.
|
67
|
+
|
68
|
+
Because there are many payment channels to choose from, and each channel has their own sequence number, it would be complicated to control it from outside the gem. Therefore, this gem will retry for you. When a timeout is encountered the same channel and the same sequence number will be used to retry, as suggested in the Stellar docs.
|
69
|
+
|
70
|
+
If the response is successful, the gem will mark the request as successful, and return the response.
|
71
|
+
|
64
72
|
## Development
|
65
73
|
|
66
74
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -21,143 +21,33 @@ module StellarSpectrum
|
|
21
21
|
self.logger = logger
|
22
22
|
end
|
23
23
|
|
24
|
-
def send_payment(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
attempt_release_lock!
|
35
|
-
|
36
|
-
return send_payment(
|
37
|
-
from: from,
|
38
|
-
to: to,
|
39
|
-
amount: amount,
|
40
|
-
memo: memo,
|
41
|
-
tries: tries,
|
42
|
-
)
|
43
|
-
end
|
44
|
-
|
45
|
-
log "Sending payment. #{available_channels.count} available channels: " \
|
46
|
-
"#{available_channels.map(&:address).inspect}"
|
47
|
-
|
48
|
-
channel_account = available_channels.first
|
49
|
-
|
50
|
-
next_sequence_number = next_sequence_number_for(channel_account)
|
51
|
-
|
52
|
-
successfully_locked = lock!(channel_account.address, {
|
53
|
-
sequence_number: next_sequence_number,
|
54
|
-
})
|
55
|
-
|
56
|
-
if !successfully_locked
|
57
|
-
log "Unable to lock #{channel_account.address}, " \
|
58
|
-
"retry for the #{tries.ordinalize} time..."
|
59
|
-
|
60
|
-
attempt_release_lock!
|
61
|
-
|
62
|
-
return send_payment(
|
63
|
-
from: from,
|
64
|
-
to: to,
|
65
|
-
amount: amount,
|
66
|
-
memo: memo,
|
67
|
-
tries: tries,
|
68
|
-
)
|
69
|
-
end
|
70
|
-
|
71
|
-
stellar_client.send_payment(
|
24
|
+
def send_payment(
|
25
|
+
from:,
|
26
|
+
to:,
|
27
|
+
amount:,
|
28
|
+
memo: nil,
|
29
|
+
tries: 0,
|
30
|
+
transaction_source: nil,
|
31
|
+
sequence: nil
|
32
|
+
)
|
33
|
+
result = SendPayment.(
|
72
34
|
from: from,
|
73
35
|
to: to,
|
74
36
|
amount: amount,
|
75
37
|
memo: memo,
|
76
|
-
|
77
|
-
|
38
|
+
seeds: seeds,
|
39
|
+
redis_url: redis_url,
|
40
|
+
horizon_url: horizon_url,
|
41
|
+
force_transaction_source: transaction_source,
|
42
|
+
force_sequence_number: sequence,
|
43
|
+
force_lock: false,
|
78
44
|
)
|
79
|
-
end
|
80
|
-
|
81
|
-
def attempt_release_lock!(except_address: nil)
|
82
|
-
if logger
|
83
|
-
log_message = "Attempting to release a lock"
|
84
|
-
if except_address.present?
|
85
|
-
log_message << "(except for address #{except_address})"
|
86
|
-
end
|
87
|
-
log log_message
|
88
|
-
end
|
89
|
-
|
90
|
-
address, address_info = locked_accounts.except(except_address).
|
91
|
-
sort_by {|address, info| info[:pttl]}.last
|
92
|
-
|
93
|
-
return false if address.nil?
|
94
|
-
|
95
|
-
current_sequence_number = current_sequence_number_for(address)
|
96
|
-
address_info = locked_accounts[address]
|
97
|
-
|
98
|
-
# Someone else may have unlocked it while we were taking our sweet
|
99
|
-
# time fetching the sequence number
|
100
|
-
return false if address_info.nil?
|
101
|
-
|
102
|
-
address_sequence_number = address_info[:sequence_number].to_i
|
103
|
-
if current_sequence_number < address_sequence_number
|
104
|
-
log "Not releasing #{address}: sequence number locked at " \
|
105
|
-
"#{address_sequence_number} is > current sequence number " \
|
106
|
-
"#{current_sequence_number}"
|
107
|
-
return false
|
108
|
-
end
|
109
45
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
unlock!(address)
|
114
|
-
end
|
115
|
-
|
116
|
-
def channel_accounts
|
117
|
-
@channel_accounts ||= seeds.map { |s| Stellar::Account.from_seed(s) }
|
118
|
-
end
|
119
|
-
|
120
|
-
def locked_accounts
|
121
|
-
GetLockedAccounts.execute(redis: redis, channel_accounts: channel_accounts)
|
122
|
-
end
|
46
|
+
# if result.failure?
|
47
|
+
# return false
|
48
|
+
# end
|
123
49
|
|
124
|
-
|
125
|
-
@stellar_client ||= Stellar::Client.new(horizon: self.horizon_url)
|
50
|
+
result.send_asset_response
|
126
51
|
end
|
127
|
-
|
128
|
-
def redis
|
129
|
-
@redis ||= InitRedis.execute(redis_url: self.redis_url)
|
130
|
-
end
|
131
|
-
|
132
|
-
def lock!(address, sequence_number:)
|
133
|
-
redis.set(GetKeyForAddress.execute(address), sequence_number, {
|
134
|
-
nx: true,
|
135
|
-
ex: MAX_LOCK_TIME_IN_SECONDS,
|
136
|
-
})
|
137
|
-
end
|
138
|
-
|
139
|
-
def unlock!(address)
|
140
|
-
log "Unlocking address #{address}"
|
141
|
-
redis.del(GetKeyForAddress.execute(address))
|
142
|
-
end
|
143
|
-
|
144
|
-
def next_sequence_number_for(address_or_account)
|
145
|
-
current_sequence_number_for(address_or_account) + 1
|
146
|
-
end
|
147
|
-
|
148
|
-
def current_sequence_number_for(address_or_account)
|
149
|
-
account = address_or_account
|
150
|
-
if address_or_account.is_a?(String)
|
151
|
-
account = Stellar::Account.from_address(address_or_account)
|
152
|
-
end
|
153
|
-
|
154
|
-
stellar_client.account_info(account).sequence.to_i
|
155
|
-
end
|
156
|
-
|
157
|
-
def log(message)
|
158
|
-
return if logger.nil?
|
159
|
-
logger.info [LOG_TAG, message].join(" ")
|
160
|
-
end
|
161
|
-
|
162
52
|
end
|
163
53
|
end
|
@@ -1,11 +1,16 @@
|
|
1
1
|
module StellarSpectrum
|
2
2
|
class GetAvailableChannels
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
extend LightService::Action
|
5
|
+
expects :redis, :locked_accounts, :channel_accounts
|
6
|
+
promises :available_channels
|
7
|
+
|
8
|
+
executed do |c|
|
9
|
+
locked_addresses = c.locked_accounts.keys
|
10
|
+
|
11
|
+
c.available_channels = c.channel_accounts.each_with_object([]) do |account, channels|
|
7
12
|
next if locked_addresses.include?(account.address)
|
8
|
-
|
13
|
+
channels << account
|
9
14
|
end
|
10
15
|
end
|
11
16
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
class GetChannelAccountInfo
|
3
|
+
|
4
|
+
extend LightService::Action
|
5
|
+
expects :locked_accounts, :channel_account
|
6
|
+
promises :channel_account_info, :unlock_response
|
7
|
+
|
8
|
+
executed do |c|
|
9
|
+
c.channel_account_info = c.locked_accounts[c.channel_account.address]
|
10
|
+
|
11
|
+
c.unlock_response = nil
|
12
|
+
next c if c.channel_account_info.present?
|
13
|
+
|
14
|
+
c.unlock_response = true
|
15
|
+
|
16
|
+
message = "#{c.channel_account.address} has been unlocked by some " \
|
17
|
+
"other mechanism like ttl expiration or by something else, " \
|
18
|
+
"so this is a success"
|
19
|
+
c.skip_remaining!(message)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
class GetChannelAccounts
|
3
|
+
|
4
|
+
extend LightService::Action
|
5
|
+
expects :seeds
|
6
|
+
promises :channel_accounts
|
7
|
+
|
8
|
+
executed do |c|
|
9
|
+
c.channel_accounts = c.seeds.map do |seed|
|
10
|
+
Stellar::Account.from_seed(seed)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -1,9 +1,16 @@
|
|
1
1
|
module StellarSpectrum
|
2
2
|
class GetLockedAccounts
|
3
3
|
|
4
|
+
extend LightService::Action
|
5
|
+
expects :redis, :channel_accounts
|
6
|
+
promises :locked_accounts
|
7
|
+
|
4
8
|
REDIS_PTTL_KEY_NON_PRESENT = -2
|
5
9
|
|
6
|
-
|
10
|
+
executed do |c|
|
11
|
+
redis = c.redis
|
12
|
+
channel_accounts = c.channel_accounts
|
13
|
+
|
7
14
|
addresses = channel_accounts.map(&:address)
|
8
15
|
address_keys = addresses.map {|a| GetKeyForAddress.execute(a) }
|
9
16
|
redis_response = redis.multi do
|
@@ -15,7 +22,7 @@ module StellarSpectrum
|
|
15
22
|
address_sequence_numbers = redis_response[0]
|
16
23
|
address_pttls = redis_response[1..-1]
|
17
24
|
|
18
|
-
addresses.each_with_object({}).with_index do |(address, hash), i|
|
25
|
+
c.locked_accounts = addresses.each_with_object({}).with_index do |(address, hash), i|
|
19
26
|
pttl = address_pttls[i]
|
20
27
|
|
21
28
|
next if pttl == REDIS_PTTL_KEY_NON_PRESENT
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
class GetSequenceNumber
|
3
|
+
|
4
|
+
extend LightService::Action
|
5
|
+
expects :stellar_client, :channel_account
|
6
|
+
promises :current_sequence_number, :next_sequence_number
|
7
|
+
|
8
|
+
executed do |c|
|
9
|
+
account_info = c.stellar_client.account_info(c.channel_account)
|
10
|
+
c.current_sequence_number = account_info.sequence.to_i
|
11
|
+
|
12
|
+
c.next_sequence_number = c[:force_sequence_number] ||
|
13
|
+
c.current_sequence_number + 1
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
module StellarSpectrum
|
2
2
|
class InitRedis
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
extend LightService::Action
|
5
|
+
expects :redis_url
|
6
|
+
promises :redis
|
7
|
+
|
8
|
+
executed do |c|
|
9
|
+
c.redis = Redis.new(redis_url: c.redis_url)
|
6
10
|
end
|
7
11
|
|
8
12
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
class LockChannel
|
3
|
+
|
4
|
+
extend LightService::Action
|
5
|
+
expects :channel_account, :next_sequence_number, :redis, :force_lock
|
6
|
+
promises :successfully_locked
|
7
|
+
|
8
|
+
SUCCESS_NON_NX_RESULT = "OK".freeze
|
9
|
+
|
10
|
+
executed do |c|
|
11
|
+
address = c.channel_account.address
|
12
|
+
address_key = GetKeyForAddress.execute(address)
|
13
|
+
|
14
|
+
c.successfully_locked = c.redis.set(address_key, c.next_sequence_number, {
|
15
|
+
nx: !c.force_lock,
|
16
|
+
ex: Client::MAX_LOCK_TIME_IN_SECONDS,
|
17
|
+
})
|
18
|
+
|
19
|
+
if c.successfully_locked == SUCCESS_NON_NX_RESULT
|
20
|
+
c.successfully_locked = true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
class SendPayment
|
3
|
+
|
4
|
+
extend LightService::Organizer
|
5
|
+
|
6
|
+
def self.call(
|
7
|
+
from:,
|
8
|
+
to:,
|
9
|
+
amount:,
|
10
|
+
memo: nil,
|
11
|
+
tries: 0,
|
12
|
+
seeds:,
|
13
|
+
redis_url:,
|
14
|
+
horizon_url:,
|
15
|
+
force_transaction_source: nil,
|
16
|
+
force_sequence_number: nil,
|
17
|
+
force_lock: false
|
18
|
+
)
|
19
|
+
with(
|
20
|
+
from: from,
|
21
|
+
to: to,
|
22
|
+
amount: amount,
|
23
|
+
memo: memo,
|
24
|
+
tries: tries,
|
25
|
+
seeds: seeds,
|
26
|
+
redis_url: redis_url,
|
27
|
+
horizon_url: horizon_url,
|
28
|
+
force_transaction_source: force_transaction_source,
|
29
|
+
force_sequence_number: force_sequence_number,
|
30
|
+
force_lock: force_lock,
|
31
|
+
).reduce(actions)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.actions
|
35
|
+
[
|
36
|
+
IncrementTries,
|
37
|
+
InitStellarClient,
|
38
|
+
InitRedis,
|
39
|
+
GetChannelAccounts,
|
40
|
+
reduce_if(->(c) {c[:force_transaction_source].nil?}, [
|
41
|
+
GetLockedAccounts,
|
42
|
+
GetAvailableChannels,
|
43
|
+
reduce_if(->(c) { c.available_channels.empty? }, retry_actions),
|
44
|
+
PickChannel,
|
45
|
+
]),
|
46
|
+
reduce_if(->(c) {c[:force_transaction_source].present?}, [
|
47
|
+
execute(->(c) {c[:channel_account] = c[:force_transaction_source]}),
|
48
|
+
]),
|
49
|
+
GetSequenceNumber,
|
50
|
+
LockChannel,
|
51
|
+
reduce_if(->(c) {!c.successfully_locked}, retry_actions),
|
52
|
+
SendingPayment::SendAsset,
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.retry_actions
|
57
|
+
[
|
58
|
+
SendingPayment::AttemptReleaseLock,
|
59
|
+
SendingPayment::Retry,
|
60
|
+
]
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
module SendingPayment
|
3
|
+
class AttemptReleaseLock
|
4
|
+
|
5
|
+
extend LightService::Action
|
6
|
+
expects :stellar_client, :redis, :channel_accounts
|
7
|
+
|
8
|
+
executed do |c|
|
9
|
+
Unlocking::AttemptRelease.(
|
10
|
+
stellar_client: c.stellar_client,
|
11
|
+
redis: c.redis,
|
12
|
+
channel_accounts: c.channel_accounts,
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
module SendingPayment
|
3
|
+
class Retry
|
4
|
+
|
5
|
+
extend LightService::Action
|
6
|
+
EXPECTS = %i[
|
7
|
+
from
|
8
|
+
to
|
9
|
+
amount
|
10
|
+
memo
|
11
|
+
tries
|
12
|
+
seeds
|
13
|
+
horizon_url
|
14
|
+
redis_url
|
15
|
+
force_transaction_source
|
16
|
+
force_sequence_number
|
17
|
+
force_lock
|
18
|
+
]
|
19
|
+
expects *EXPECTS
|
20
|
+
promises :send_asset_response
|
21
|
+
WAIT_TIME_IN_SECONDS = 5
|
22
|
+
|
23
|
+
executed do |c|
|
24
|
+
sleep WAIT_TIME_IN_SECONDS
|
25
|
+
|
26
|
+
args = EXPECTS.each_with_object({}) do |attr, hash|
|
27
|
+
hash[attr] = c.send(attr)
|
28
|
+
end
|
29
|
+
|
30
|
+
c.send_asset_response = SendPayment.(args).send_asset_response
|
31
|
+
|
32
|
+
# THIS IS IMPORTANT or else it will continue with the rest of the
|
33
|
+
# actions and possibly send out the asset just as many times as `tries`
|
34
|
+
# https://github.com/adomokos/light-service/pull/164
|
35
|
+
# fail_and_return for now until we figure out a way to stop all actions
|
36
|
+
# from reduce_if
|
37
|
+
# See https://github.com/adomokos/light-service/pull/164
|
38
|
+
message = "Closing try #{c.tries}"
|
39
|
+
c.fail_and_return! message
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
module SendingPayment
|
3
|
+
class SendAsset
|
4
|
+
|
5
|
+
extend LightService::Action
|
6
|
+
TIMEOUT_CODE = 504.freeze
|
7
|
+
|
8
|
+
expects *%i[
|
9
|
+
from
|
10
|
+
to
|
11
|
+
amount
|
12
|
+
memo
|
13
|
+
channel_account
|
14
|
+
next_sequence_number
|
15
|
+
stellar_client
|
16
|
+
tries
|
17
|
+
seeds
|
18
|
+
horizon_url
|
19
|
+
redis_url
|
20
|
+
]
|
21
|
+
|
22
|
+
promises :send_asset_response
|
23
|
+
|
24
|
+
executed do |c|
|
25
|
+
c.send_asset_response = c.stellar_client.send_payment(
|
26
|
+
from: c.from,
|
27
|
+
to: c.to,
|
28
|
+
amount: c.amount,
|
29
|
+
memo: c.memo,
|
30
|
+
transaction_source: c.channel_account,
|
31
|
+
sequence: c.next_sequence_number,
|
32
|
+
)
|
33
|
+
rescue Faraday::ClientError => e
|
34
|
+
if e.response[:status] == TIMEOUT_CODE
|
35
|
+
retry_result = Retry.execute(
|
36
|
+
stellar_client: c.stellar_client,
|
37
|
+
from: c.from,
|
38
|
+
to: c.to,
|
39
|
+
amount: c.amount,
|
40
|
+
memo: c.memo,
|
41
|
+
tries: c.tries,
|
42
|
+
seeds: c.seeds,
|
43
|
+
horizon_url: c.horizon_url,
|
44
|
+
redis_url: c.redis_url,
|
45
|
+
force_transaction_source: c.channel_account,
|
46
|
+
force_sequence_number: c.next_sequence_number,
|
47
|
+
force_lock: true,
|
48
|
+
)
|
49
|
+
c.send_asset_response = retry_result[:send_asset_response]
|
50
|
+
else
|
51
|
+
fail
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
module Unlocking
|
3
|
+
class AttemptRelease
|
4
|
+
|
5
|
+
extend LightService::Organizer
|
6
|
+
def self.call(redis:, channel_accounts:, stellar_client:)
|
7
|
+
result = with(
|
8
|
+
stellar_client: stellar_client,
|
9
|
+
redis: redis,
|
10
|
+
channel_accounts: channel_accounts,
|
11
|
+
).reduce(actions)
|
12
|
+
|
13
|
+
if result.failure?
|
14
|
+
result[:unlock_response] = false
|
15
|
+
end
|
16
|
+
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.actions
|
21
|
+
[
|
22
|
+
GetLockedAccounts,
|
23
|
+
GetAccountToUnlock,
|
24
|
+
GetSequenceNumber,
|
25
|
+
# Someone else may have unlocked it while we were taking
|
26
|
+
# our sweet time fetching the sequence number, so fetch
|
27
|
+
# the locked accounts again:
|
28
|
+
GetLockedAccounts,
|
29
|
+
GetChannelAccountInfo,
|
30
|
+
CheckSequenceNumber,
|
31
|
+
Unlock,
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
module Unlocking
|
3
|
+
class CheckSequenceNumber
|
4
|
+
|
5
|
+
extend LightService::Action
|
6
|
+
expects :current_sequence_number, :channel_account, :channel_account_info
|
7
|
+
|
8
|
+
executed do |c|
|
9
|
+
address_sequence_number = c.channel_account_info[:sequence_number].to_i
|
10
|
+
current_sequence_number = c.current_sequence_number
|
11
|
+
|
12
|
+
next c if current_sequence_number >= address_sequence_number
|
13
|
+
|
14
|
+
address = c.channel_account.address
|
15
|
+
|
16
|
+
message = "Not releasing #{address}: sequence number locked at " \
|
17
|
+
"#{address_sequence_number} is > current sequence number " \
|
18
|
+
"#{current_sequence_number}"
|
19
|
+
|
20
|
+
c.fail_and_return! message
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
module Unlocking
|
3
|
+
class GetAccountToUnlock
|
4
|
+
|
5
|
+
extend LightService::Action
|
6
|
+
expects :locked_accounts
|
7
|
+
promises :channel_account
|
8
|
+
|
9
|
+
executed do |c|
|
10
|
+
address, info = c.locked_accounts.except(c[:except_address])
|
11
|
+
.sort_by {|address, info| info[:pttl]}.last
|
12
|
+
|
13
|
+
if address.present?
|
14
|
+
c.channel_account = Stellar::Account.from_address(address)
|
15
|
+
else
|
16
|
+
c.channel_account = nil
|
17
|
+
c.fail_and_return! "No addresses to unlock"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module StellarSpectrum
|
2
|
+
module Unlocking
|
3
|
+
class Unlock
|
4
|
+
|
5
|
+
extend LightService::Action
|
6
|
+
expects :redis, :channel_account
|
7
|
+
promises :unlock_response
|
8
|
+
|
9
|
+
executed do |c|
|
10
|
+
address_key = GetKeyForAddress.execute(c.channel_account.address)
|
11
|
+
c.unlock_response = c.redis.del(address_key)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/stellar_spectrum.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "gem_config"
|
2
|
+
require "light-service"
|
2
3
|
require "stellar_spectrum/version"
|
3
4
|
require "stellar_spectrum/client"
|
4
5
|
require "stellar-sdk"
|
@@ -6,10 +7,25 @@ require "redis"
|
|
6
7
|
require "active_support/core_ext/hash/except"
|
7
8
|
require "active_support/core_ext/object/blank"
|
8
9
|
require 'active_support/core_ext/integer/inflections'
|
10
|
+
require "stellar_spectrum/services/get_channel_account_info"
|
9
11
|
require "stellar_spectrum/services/get_available_channels"
|
12
|
+
require "stellar_spectrum/services/get_channel_accounts"
|
10
13
|
require "stellar_spectrum/services/get_key_for_address"
|
11
14
|
require "stellar_spectrum/services/get_locked_accounts"
|
15
|
+
require "stellar_spectrum/services/get_sequence_number"
|
16
|
+
require "stellar_spectrum/services/increment_tries"
|
12
17
|
require "stellar_spectrum/services/init_redis"
|
18
|
+
require "stellar_spectrum/services/init_stellar_client"
|
19
|
+
require "stellar_spectrum/services/lock_channel"
|
20
|
+
require "stellar_spectrum/services/pick_channel"
|
21
|
+
require "stellar_spectrum/services/send_payment"
|
22
|
+
require "stellar_spectrum/services/sending_payment/attempt_release_lock"
|
23
|
+
require "stellar_spectrum/services/sending_payment/retry"
|
24
|
+
require "stellar_spectrum/services/sending_payment/send_asset"
|
25
|
+
require "stellar_spectrum/services/unlocking/attempt_release"
|
26
|
+
require "stellar_spectrum/services/unlocking/check_sequence_number"
|
27
|
+
require "stellar_spectrum/services/unlocking/get_account_to_unlock"
|
28
|
+
require "stellar_spectrum/services/unlocking/unlock"
|
13
29
|
|
14
30
|
module StellarSpectrum
|
15
31
|
|
data/stellar_spectrum.gemspec
CHANGED
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.add_dependency "gem_config"
|
40
40
|
spec.add_dependency "redis"
|
41
41
|
spec.add_dependency "stellar-sdk", ">= 0.6.0"
|
42
|
+
spec.add_dependency "light-service"
|
42
43
|
|
43
44
|
spec.add_development_dependency "bundler", "~> 1.16"
|
44
45
|
spec.add_development_dependency "rake", "~> 10.0"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stellar_spectrum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ramon Tayag
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-
|
12
|
+
date: 2018-12-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -67,6 +67,20 @@ dependencies:
|
|
67
67
|
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: 0.6.0
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: light-service
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
70
84
|
- !ruby/object:Gem::Dependency
|
71
85
|
name: bundler
|
72
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -147,6 +161,7 @@ extra_rdoc_files: []
|
|
147
161
|
files:
|
148
162
|
- ".gitignore"
|
149
163
|
- ".rspec"
|
164
|
+
- ".ruby-version"
|
150
165
|
- ".travis.yml"
|
151
166
|
- CHANGELOG.md
|
152
167
|
- CODE_OF_CONDUCT.md
|
@@ -161,9 +176,24 @@ files:
|
|
161
176
|
- lib/stellar_spectrum.rb
|
162
177
|
- lib/stellar_spectrum/client.rb
|
163
178
|
- lib/stellar_spectrum/services/get_available_channels.rb
|
179
|
+
- lib/stellar_spectrum/services/get_channel_account_info.rb
|
180
|
+
- lib/stellar_spectrum/services/get_channel_accounts.rb
|
164
181
|
- lib/stellar_spectrum/services/get_key_for_address.rb
|
165
182
|
- lib/stellar_spectrum/services/get_locked_accounts.rb
|
183
|
+
- lib/stellar_spectrum/services/get_sequence_number.rb
|
184
|
+
- lib/stellar_spectrum/services/increment_tries.rb
|
166
185
|
- lib/stellar_spectrum/services/init_redis.rb
|
186
|
+
- lib/stellar_spectrum/services/init_stellar_client.rb
|
187
|
+
- lib/stellar_spectrum/services/lock_channel.rb
|
188
|
+
- lib/stellar_spectrum/services/pick_channel.rb
|
189
|
+
- lib/stellar_spectrum/services/send_payment.rb
|
190
|
+
- lib/stellar_spectrum/services/sending_payment/attempt_release_lock.rb
|
191
|
+
- lib/stellar_spectrum/services/sending_payment/retry.rb
|
192
|
+
- lib/stellar_spectrum/services/sending_payment/send_asset.rb
|
193
|
+
- lib/stellar_spectrum/services/unlocking/attempt_release.rb
|
194
|
+
- lib/stellar_spectrum/services/unlocking/check_sequence_number.rb
|
195
|
+
- lib/stellar_spectrum/services/unlocking/get_account_to_unlock.rb
|
196
|
+
- lib/stellar_spectrum/services/unlocking/unlock.rb
|
167
197
|
- lib/stellar_spectrum/version.rb
|
168
198
|
- stellar_spectrum.gemspec
|
169
199
|
homepage: https://github.com/bloom-solutions/stellar_spectrum-ruby
|
@@ -190,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
190
220
|
version: '0'
|
191
221
|
requirements: []
|
192
222
|
rubyforge_project:
|
193
|
-
rubygems_version: 2.7.
|
223
|
+
rubygems_version: 2.7.8
|
194
224
|
signing_key:
|
195
225
|
specification_version: 4
|
196
226
|
summary: Use Stellar payment channels in Ruby with ease
|