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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e55714fc1d6fbd13ff97e4d78e32d918a7926bc29e2e10cba93c8c9797ff9d5
4
- data.tar.gz: f5dc2d60e6f0b835d677504e6ea70d0b78e21e48f3ad09a692287e5feacd7d71
3
+ metadata.gz: 8a84d8ec60d970ce532eaa91719ad9489df92a4d705b0fd0e9e729ec787f2b87
4
+ data.tar.gz: 18f1f89e4bc966b552556d8bd6c4a1569e15b6fb29612a55f41cb8aa47284ede
5
5
  SHA512:
6
- metadata.gz: 68b9a5f54bab85dd97c1e358a8d7ae428e7c04b5029ebdcfe66bfb1057fc53c59126236528aaada6c8017a25050fc3bd251287eb4d132f8ebf8ece6cad467506
7
- data.tar.gz: 469f3afd74061b371b27118172bc3f10955fd24e89f68c00fe26e2677e9ca3b09a69949e70babfd9d10679784c62619b44420cb50b40dc13fdf26cf1298abc05
6
+ metadata.gz: 5e3149e0c3bffeaf65dd395b57c51fdb87ff3667e915aa0fe9ab0cf8e3f7ec0f9d6770d0c62cf1ca855d28ed73974d83c84e38917f4c77b85e7c0ff62f9aae46
7
+ data.tar.gz: 6e561e7f72fafde97d68245f01ad4296b2b9c182e497978d0899ba4e827879e5e8884b74862bbac6acdae9ba3c06f0c31d27247f71a40416b60bce55ba59f338
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ t.test_files = FileList["test/*.rb"]
6
+ end
7
+
8
+ desc "Run tests"
9
+ task default: :test
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
- def initialize(api_key, wallet_private_key, options = {})
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] || "https://api.minepi.com"
19
- @mainnet_host = options[:mainnet_host] || "api.mainnet.minepi.com"
20
- @testnet_host = options[:testnet_host] || "api.testnet.minepi.com"
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 = Faraday.post(
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
- @open_payments[identifier] = parsed_response
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
- payment = @open_payments[payment_id]
69
+ @open_payments_mutex.synchronize do
70
+ payment = @open_payments[payment_id]
62
71
 
63
- if payment.nil? || payment["identifier"] != payment_id
64
- payment = get_payment(payment_id)
65
- txid = payment["transaction"]&.dig("txid")
66
- raise Errors::TxidAlreadyLinkedError.new("This payment already has a linked txid", payment_id, txid) if txid.present?
67
- end
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
- set_horizon_client(payment["network"])
70
- @from_address = payment["from_address"]
78
+ set_horizon_client(payment["network"])
79
+ @from_address = payment["from_address"]
71
80
 
72
- transaction_data = {
73
- amount: payment["amount"],
74
- identifier: payment["identifier"],
75
- recipient: payment["to_address"]
76
- }
81
+ transaction_data = {
82
+ amount: payment["amount"],
83
+ identifier: payment["identifier"],
84
+ recipient: payment["to_address"]
85
+ }
77
86
 
78
- transaction = build_a2u_transaction(transaction_data)
79
- txid = submit_transaction(transaction)
87
+ transaction = build_a2u_transaction(transaction_data)
88
+ txid = submit_transaction(transaction)
80
89
 
81
- @open_payments.delete(payment_id)
90
+ @open_payments.delete(payment_id)
82
91
 
83
- return txid
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
- @open_payments.delete(payment_id)
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
- @open_payments.delete(payment_id)
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 = JSON.parse(response.body).dig("error_message") rescue unknown_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
- response = self.client.submit_transaction(tx_envelope: envelope)
192
- txid = response._response.body["id"]
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.starts_with?("S")
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.3
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: 2023-10-12 00:00:00.000000000 Z
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.29.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.29.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: '0'
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: '0'
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.0.3.1
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: []