pinetwork 0.1.3 → 0.1.5
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.
- checksums.yaml +4 -4
- data/Rakefile +9 -0
- data/lib/errors.rb +13 -0
- data/lib/pinetwork.rb +56 -34
- data/test/a2u_concurrency_test.rb +35 -0
- metadata +12 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a84d8ec60d970ce532eaa91719ad9489df92a4d705b0fd0e9e729ec787f2b87
|
|
4
|
+
data.tar.gz: 18f1f89e4bc966b552556d8bd6c4a1569e15b6fb29612a55f41cb8aa47284ede
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5e3149e0c3bffeaf65dd395b57c51fdb87ff3667e915aa0fe9ab0cf8e3f7ec0f9d6770d0c62cf1ca855d28ed73974d83c84e38917f4c77b85e7c0ff62f9aae46
|
|
7
|
+
data.tar.gz: 6e561e7f72fafde97d68245f01ad4296b2b9c182e497978d0899ba4e827879e5e8884b74862bbac6acdae9ba3c06f0c31d27247f71a40416b60bce55ba59f338
|
data/Rakefile
ADDED
data/lib/errors.rb
CHANGED
|
@@ -3,6 +3,7 @@ class ::PiNetwork
|
|
|
3
3
|
class APIRequestError < StandardError
|
|
4
4
|
attr_reader :response_body
|
|
5
5
|
attr_reader :response_status
|
|
6
|
+
|
|
6
7
|
def initialize(message, response_status, response_body)
|
|
7
8
|
super(message)
|
|
8
9
|
@response_status = response_status
|
|
@@ -12,6 +13,7 @@ class ::PiNetwork
|
|
|
12
13
|
|
|
13
14
|
class PaymentNotFoundError < StandardError
|
|
14
15
|
attr_reader :payment_id
|
|
16
|
+
|
|
15
17
|
def initialize(message, payment_id)
|
|
16
18
|
super(message)
|
|
17
19
|
@payment_id = payment_id
|
|
@@ -28,5 +30,16 @@ class ::PiNetwork
|
|
|
28
30
|
@txid = txid
|
|
29
31
|
end
|
|
30
32
|
end
|
|
33
|
+
|
|
34
|
+
class TxSubmissionError < StandardError
|
|
35
|
+
attr_reader :tx_error_code
|
|
36
|
+
attr_reader :op_error_codes
|
|
37
|
+
|
|
38
|
+
def initialize(tx_error_code, op_error_codes)
|
|
39
|
+
super(message)
|
|
40
|
+
@tx_error_code = tx_error_code
|
|
41
|
+
@op_error_codes = op_error_codes
|
|
42
|
+
end
|
|
43
|
+
end
|
|
31
44
|
end
|
|
32
45
|
end
|
data/lib/pinetwork.rb
CHANGED
|
@@ -11,15 +11,21 @@ class PiNetwork
|
|
|
11
11
|
attr_reader :base_url
|
|
12
12
|
attr_reader :from_address
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
BASE_URL = "https://api.minepi.com".freeze
|
|
15
|
+
MAINNET_HOST = "api.mainnet.minepi.com".freeze
|
|
16
|
+
TESTNET_HOST = "api.testnet.minepi.com".freeze
|
|
17
|
+
|
|
18
|
+
def initialize(api_key:, wallet_private_key:, faraday: Faraday.new, options: {})
|
|
15
19
|
validate_private_seed_format!(wallet_private_key)
|
|
16
20
|
@api_key = api_key
|
|
17
21
|
@account = load_account(wallet_private_key)
|
|
18
|
-
@base_url = options[:base_url] ||
|
|
19
|
-
@mainnet_host = options[:mainnet_host] ||
|
|
20
|
-
@testnet_host = options[:testnet_host] ||
|
|
22
|
+
@base_url = options[:base_url] || BASE_URL
|
|
23
|
+
@mainnet_host = options[:mainnet_host] || MAINNET_HOST
|
|
24
|
+
@testnet_host = options[:testnet_host] || TESTNET_HOST
|
|
25
|
+
@faraday = faraday
|
|
21
26
|
|
|
22
27
|
@open_payments = {}
|
|
28
|
+
@open_payments_mutex = Mutex.new
|
|
23
29
|
end
|
|
24
30
|
|
|
25
31
|
def get_payment(payment_id)
|
|
@@ -43,7 +49,7 @@ class PiNetwork
|
|
|
43
49
|
payment: payment_data,
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
response =
|
|
52
|
+
response = @faraday.post(
|
|
47
53
|
base_url + "/v2/payments",
|
|
48
54
|
request_body.to_json,
|
|
49
55
|
http_headers,
|
|
@@ -52,35 +58,39 @@ class PiNetwork
|
|
|
52
58
|
parsed_response = handle_http_response(response, "An unknown error occurred while creating a payment")
|
|
53
59
|
|
|
54
60
|
identifier = parsed_response["identifier"]
|
|
55
|
-
@
|
|
61
|
+
@open_payments_mutex.synchronize do
|
|
62
|
+
@open_payments[identifier] = parsed_response
|
|
63
|
+
end
|
|
56
64
|
|
|
57
65
|
return identifier
|
|
58
66
|
end
|
|
59
67
|
|
|
60
68
|
def submit_payment(payment_id)
|
|
61
|
-
|
|
69
|
+
@open_payments_mutex.synchronize do
|
|
70
|
+
payment = @open_payments[payment_id]
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
if payment.nil? || payment["identifier"] != payment_id
|
|
73
|
+
payment = get_payment(payment_id)
|
|
74
|
+
txid = payment["transaction"]&.dig("txid")
|
|
75
|
+
raise Errors::TxidAlreadyLinkedError.new("This payment already has a linked txid", payment_id, txid) if txid.present?
|
|
76
|
+
end
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
set_horizon_client(payment["network"])
|
|
79
|
+
@from_address = payment["from_address"]
|
|
71
80
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
transaction_data = {
|
|
82
|
+
amount: payment["amount"],
|
|
83
|
+
identifier: payment["identifier"],
|
|
84
|
+
recipient: payment["to_address"]
|
|
85
|
+
}
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
transaction = build_a2u_transaction(transaction_data)
|
|
88
|
+
txid = submit_transaction(transaction)
|
|
80
89
|
|
|
81
|
-
|
|
90
|
+
@open_payments.delete(payment_id)
|
|
82
91
|
|
|
83
|
-
|
|
92
|
+
return txid
|
|
93
|
+
end
|
|
84
94
|
end
|
|
85
95
|
|
|
86
96
|
def complete_payment(payment_id, txid)
|
|
@@ -92,7 +102,10 @@ class PiNetwork
|
|
|
92
102
|
http_headers
|
|
93
103
|
)
|
|
94
104
|
|
|
95
|
-
@
|
|
105
|
+
@open_payments_mutex.synchronize do
|
|
106
|
+
@open_payments.delete(payment_id)
|
|
107
|
+
end
|
|
108
|
+
|
|
96
109
|
handle_http_response(response, "An unknown error occurred while completing the payment")
|
|
97
110
|
end
|
|
98
111
|
|
|
@@ -103,7 +116,10 @@ class PiNetwork
|
|
|
103
116
|
http_headers,
|
|
104
117
|
)
|
|
105
118
|
|
|
106
|
-
@
|
|
119
|
+
@open_payments_mutex.synchronize do
|
|
120
|
+
@open_payments.delete(payment_id)
|
|
121
|
+
end
|
|
122
|
+
|
|
107
123
|
handle_http_response(response, "An unknown error occurred while cancelling the payment")
|
|
108
124
|
end
|
|
109
125
|
|
|
@@ -131,7 +147,7 @@ class PiNetwork
|
|
|
131
147
|
|
|
132
148
|
def handle_http_response(response, unknown_error_message = "An unknown error occurred while making an API request")
|
|
133
149
|
unless response.status == 200
|
|
134
|
-
error_message =
|
|
150
|
+
error_message = extract_error_message(response.body, unknown_error_message)
|
|
135
151
|
raise Errors::APIRequestError.new(error_message, response.status, response.body)
|
|
136
152
|
end
|
|
137
153
|
|
|
@@ -148,7 +164,7 @@ class PiNetwork
|
|
|
148
164
|
host = (network.starts_with? "Pi Network") ? @mainnet_host : @testnet_host
|
|
149
165
|
horizon = "https://#{host}"
|
|
150
166
|
|
|
151
|
-
client = Stellar::Client.new(host: host, horizon: horizon)
|
|
167
|
+
client = Stellar::Horizon::Client.new(host: host, horizon: horizon)
|
|
152
168
|
Stellar::default_network = network
|
|
153
169
|
|
|
154
170
|
@client = client
|
|
@@ -169,10 +185,7 @@ class PiNetwork
|
|
|
169
185
|
recipient = Stellar::KeyPair.from_address(transaction_data[:recipient])
|
|
170
186
|
memo = Stellar::Memo.new(:memo_text, transaction_data[:identifier])
|
|
171
187
|
|
|
172
|
-
payment_operation = Stellar::Operation.payment(
|
|
173
|
-
destination: recipient,
|
|
174
|
-
amount: amount.to_payment
|
|
175
|
-
})
|
|
188
|
+
payment_operation = Stellar::Operation.payment(destination: recipient, amount: amount.to_payment)
|
|
176
189
|
|
|
177
190
|
my_public_key = self.account.address
|
|
178
191
|
sequence_number = self.client.account_info(my_public_key).sequence.to_i
|
|
@@ -188,8 +201,13 @@ class PiNetwork
|
|
|
188
201
|
|
|
189
202
|
def submit_transaction(transaction)
|
|
190
203
|
envelope = transaction.to_envelope(self.account.keypair)
|
|
191
|
-
|
|
192
|
-
|
|
204
|
+
begin
|
|
205
|
+
response = self.client.submit_transaction(tx_envelope: envelope)
|
|
206
|
+
txid = response._response.body["id"]
|
|
207
|
+
rescue => error
|
|
208
|
+
result_codes = error.response&.dig(:body, "extras", "result_codes")
|
|
209
|
+
raise Errors::TxSubmissionError.new(result_codes&.dig("transaction"), result_codes&.dig("operations"))
|
|
210
|
+
end
|
|
193
211
|
end
|
|
194
212
|
|
|
195
213
|
def validate_payment_data!(data, options = {})
|
|
@@ -202,7 +220,11 @@ class PiNetwork
|
|
|
202
220
|
end
|
|
203
221
|
|
|
204
222
|
def validate_private_seed_format!(seed)
|
|
205
|
-
raise StandardError.new("Private Seed should start with \"S\"") unless seed.upcase.
|
|
223
|
+
raise StandardError.new("Private Seed should start with \"S\"") unless seed.upcase.start_with?("S")
|
|
206
224
|
raise StandardError.new("Private Seed should be 56 characters") unless seed.length == 56
|
|
207
225
|
end
|
|
226
|
+
|
|
227
|
+
def extract_error_message(response_body, default_message)
|
|
228
|
+
JSON.parse(response_body).dig("error_message") rescue default_message
|
|
229
|
+
end
|
|
208
230
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'minitest/autorun'
|
|
2
|
+
require_relative '../lib/pinetwork'
|
|
3
|
+
|
|
4
|
+
class A2UConcurrencyTest < Minitest::Test
|
|
5
|
+
def test_concurrent_create_payment
|
|
6
|
+
total_threads = 10000
|
|
7
|
+
api_key = "api-key"
|
|
8
|
+
wallet_private_key = "SC2L62EYF7LYF43L4OOSKUKDESRAFJZW3UW6RFZ57UY25VAMHTL2BFER"
|
|
9
|
+
|
|
10
|
+
threads = []
|
|
11
|
+
|
|
12
|
+
faraday_stub = Minitest::Mock.new
|
|
13
|
+
pi = PiNetwork.new(api_key: api_key, wallet_private_key: wallet_private_key, faraday: faraday_stub)
|
|
14
|
+
|
|
15
|
+
total_threads.times do
|
|
16
|
+
threads << Thread.new do
|
|
17
|
+
faraday_response = Faraday::Response.new(
|
|
18
|
+
status: 200,
|
|
19
|
+
body: {identifier: SecureRandom.alphanumeric(12)}.to_json,
|
|
20
|
+
response_headers: {}
|
|
21
|
+
)
|
|
22
|
+
faraday_stub.expect(:post, faraday_response) do |url|
|
|
23
|
+
url == "https://api.minepi.com/v2/payments"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
payment_data = { amount: 1, memo: "test", metadata: {"info": "test"}, uid: "test-uid" }
|
|
27
|
+
payment_id = pi.create_payment(payment_data)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
threads.each(&:join)
|
|
31
|
+
|
|
32
|
+
open_payments_after = pi.instance_variable_get(:@open_payments)
|
|
33
|
+
assert_equal(total_threads, open_payments_after.values.uniq.count, "open_payments got corrupted!")
|
|
34
|
+
end
|
|
35
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pinetwork
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pi Core Team
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2024-04-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: stellar-sdk
|
|
@@ -16,42 +16,44 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 0.
|
|
19
|
+
version: 0.31.0
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.
|
|
26
|
+
version: 0.31.0
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: faraday
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
33
|
+
version: 1.6.0
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
40
|
+
version: 1.6.0
|
|
41
41
|
description: Pi Network backend library for Ruby-based webservers.
|
|
42
42
|
email: support@minepi.com
|
|
43
43
|
executables: []
|
|
44
44
|
extensions: []
|
|
45
45
|
extra_rdoc_files: []
|
|
46
46
|
files:
|
|
47
|
+
- Rakefile
|
|
47
48
|
- lib/errors.rb
|
|
48
49
|
- lib/pinetwork.rb
|
|
50
|
+
- test/a2u_concurrency_test.rb
|
|
49
51
|
homepage: https://github.com/pi-apps/pi-ruby
|
|
50
52
|
licenses:
|
|
51
53
|
- PiOS
|
|
52
54
|
metadata:
|
|
53
55
|
documentation_uri: https://github.com/pi-apps/pi-ruby
|
|
54
|
-
post_install_message:
|
|
56
|
+
post_install_message:
|
|
55
57
|
rdoc_options: []
|
|
56
58
|
require_paths:
|
|
57
59
|
- lib
|
|
@@ -66,8 +68,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
66
68
|
- !ruby/object:Gem::Version
|
|
67
69
|
version: '0'
|
|
68
70
|
requirements: []
|
|
69
|
-
rubygems_version: 3.
|
|
70
|
-
signing_key:
|
|
71
|
+
rubygems_version: 3.4.10
|
|
72
|
+
signing_key:
|
|
71
73
|
specification_version: 4
|
|
72
74
|
summary: Pi Network Ruby
|
|
73
75
|
test_files: []
|