pinetwork 0.1.3 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e55714fc1d6fbd13ff97e4d78e32d918a7926bc29e2e10cba93c8c9797ff9d5
4
- data.tar.gz: f5dc2d60e6f0b835d677504e6ea70d0b78e21e48f3ad09a692287e5feacd7d71
3
+ metadata.gz: 9cab0d9673b76e3b49f2ccb041f917b9574ca85dedb1c5d4529fd19ada191b2b
4
+ data.tar.gz: ee2bf09fd869205f4377f7ad72c79897282b419d3551dc72f88facbd8d33ca0c
5
5
  SHA512:
6
- metadata.gz: 68b9a5f54bab85dd97c1e358a8d7ae428e7c04b5029ebdcfe66bfb1057fc53c59126236528aaada6c8017a25050fc3bd251287eb4d132f8ebf8ece6cad467506
7
- data.tar.gz: 469f3afd74061b371b27118172bc3f10955fd24e89f68c00fe26e2677e9ca3b09a69949e70babfd9d10679784c62619b44420cb50b40dc13fdf26cf1298abc05
6
+ metadata.gz: 9f3279cb6518e666ef5ee59de68af1f98d147f1e159c2af1d9c775915f0d22624a640b2f36654c48f8f9a729e4ace10db4f9bedff721769eb220f82a67ca420a
7
+ data.tar.gz: f6d002eae9fd88cfb6e6d86958558c6f01dbb69b365cd7799ebdbe601d9d6242fb30ded18353f51f7d7f1bd43992e68d763f7687d6453f094f9ccbfe384d58e5
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
 
@@ -188,8 +204,13 @@ class PiNetwork
188
204
 
189
205
  def submit_transaction(transaction)
190
206
  envelope = transaction.to_envelope(self.account.keypair)
191
- response = self.client.submit_transaction(tx_envelope: envelope)
192
- txid = response._response.body["id"]
207
+ begin
208
+ response = self.client.submit_transaction(tx_envelope: envelope)
209
+ txid = response._response.body["id"]
210
+ rescue => error
211
+ result_codes = error.response&.dig(:body, "extras", "result_codes")
212
+ raise Errors::TxSubmissionError.new(result_codes&.dig("transaction"), result_codes&.dig("operations"))
213
+ end
193
214
  end
194
215
 
195
216
  def validate_payment_data!(data, options = {})
@@ -202,7 +223,11 @@ class PiNetwork
202
223
  end
203
224
 
204
225
  def validate_private_seed_format!(seed)
205
- raise StandardError.new("Private Seed should start with \"S\"") unless seed.upcase.starts_with?("S")
226
+ raise StandardError.new("Private Seed should start with \"S\"") unless seed.upcase.start_with?("S")
206
227
  raise StandardError.new("Private Seed should be 56 characters") unless seed.length == 56
207
228
  end
229
+
230
+ def extract_error_message(response_body, default_message)
231
+ JSON.parse(response_body).dig("error_message") rescue default_message
232
+ end
208
233
  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.4
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-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: stellar-sdk
@@ -44,14 +44,16 @@ 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: []