pinetwork 0.1.2 → 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 +4 -4
- data/Rakefile +9 -0
- data/lib/errors.rb +45 -0
- data/lib/{pi_network.rb → pinetwork.rb} +62 -35
- data/test/a2u_concurrency_test.rb +35 -0
- metadata +10 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9cab0d9673b76e3b49f2ccb041f917b9574ca85dedb1c5d4529fd19ada191b2b
|
4
|
+
data.tar.gz: ee2bf09fd869205f4377f7ad72c79897282b419d3551dc72f88facbd8d33ca0c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f3279cb6518e666ef5ee59de68af1f98d147f1e159c2af1d9c775915f0d22624a640b2f36654c48f8f9a729e4ace10db4f9bedff721769eb220f82a67ca420a
|
7
|
+
data.tar.gz: f6d002eae9fd88cfb6e6d86958558c6f01dbb69b365cd7799ebdbe601d9d6242fb30ded18353f51f7d7f1bd43992e68d763f7687d6453f094f9ccbfe384d58e5
|
data/Rakefile
ADDED
data/lib/errors.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
class ::PiNetwork
|
2
|
+
module Errors
|
3
|
+
class APIRequestError < StandardError
|
4
|
+
attr_reader :response_body
|
5
|
+
attr_reader :response_status
|
6
|
+
|
7
|
+
def initialize(message, response_status, response_body)
|
8
|
+
super(message)
|
9
|
+
@response_status = response_status
|
10
|
+
@response_body = response_body
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class PaymentNotFoundError < StandardError
|
15
|
+
attr_reader :payment_id
|
16
|
+
|
17
|
+
def initialize(message, payment_id)
|
18
|
+
super(message)
|
19
|
+
@payment_id = payment_id
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class TxidAlreadyLinkedError < StandardError
|
24
|
+
attr_reader :payment_id
|
25
|
+
attr_reader :txid
|
26
|
+
|
27
|
+
def initialize(message, payment_id, txid)
|
28
|
+
super(message)
|
29
|
+
@payment_id = payment_id
|
30
|
+
@txid = txid
|
31
|
+
end
|
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
|
44
|
+
end
|
45
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
require "#{gem_dir}/lib/errors"
|
1
|
+
require_relative 'errors'
|
3
2
|
|
4
3
|
class PiNetwork
|
5
4
|
require 'faraday'
|
@@ -12,13 +11,21 @@ class PiNetwork
|
|
12
11
|
attr_reader :base_url
|
13
12
|
attr_reader :from_address
|
14
13
|
|
15
|
-
|
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: {})
|
16
19
|
validate_private_seed_format!(wallet_private_key)
|
17
20
|
@api_key = api_key
|
18
21
|
@account = load_account(wallet_private_key)
|
19
|
-
@base_url = options[:base_url] ||
|
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
|
20
26
|
|
21
27
|
@open_payments = {}
|
28
|
+
@open_payments_mutex = Mutex.new
|
22
29
|
end
|
23
30
|
|
24
31
|
def get_payment(payment_id)
|
@@ -42,7 +49,7 @@ class PiNetwork
|
|
42
49
|
payment: payment_data,
|
43
50
|
}
|
44
51
|
|
45
|
-
response =
|
52
|
+
response = @faraday.post(
|
46
53
|
base_url + "/v2/payments",
|
47
54
|
request_body.to_json,
|
48
55
|
http_headers,
|
@@ -51,58 +58,68 @@ class PiNetwork
|
|
51
58
|
parsed_response = handle_http_response(response, "An unknown error occurred while creating a payment")
|
52
59
|
|
53
60
|
identifier = parsed_response["identifier"]
|
54
|
-
@
|
61
|
+
@open_payments_mutex.synchronize do
|
62
|
+
@open_payments[identifier] = parsed_response
|
63
|
+
end
|
55
64
|
|
56
65
|
return identifier
|
57
66
|
end
|
58
67
|
|
59
68
|
def submit_payment(payment_id)
|
60
|
-
|
69
|
+
@open_payments_mutex.synchronize do
|
70
|
+
payment = @open_payments[payment_id]
|
61
71
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
67
77
|
|
68
|
-
|
69
|
-
|
78
|
+
set_horizon_client(payment["network"])
|
79
|
+
@from_address = payment["from_address"]
|
70
80
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
81
|
+
transaction_data = {
|
82
|
+
amount: payment["amount"],
|
83
|
+
identifier: payment["identifier"],
|
84
|
+
recipient: payment["to_address"]
|
85
|
+
}
|
76
86
|
|
77
|
-
|
78
|
-
|
87
|
+
transaction = build_a2u_transaction(transaction_data)
|
88
|
+
txid = submit_transaction(transaction)
|
79
89
|
|
80
|
-
|
90
|
+
@open_payments.delete(payment_id)
|
81
91
|
|
82
|
-
|
92
|
+
return txid
|
93
|
+
end
|
83
94
|
end
|
84
95
|
|
85
|
-
def complete_payment(
|
96
|
+
def complete_payment(payment_id, txid)
|
86
97
|
body = {"txid": txid}
|
87
98
|
|
88
99
|
response = Faraday.post(
|
89
|
-
base_url + "/v2/payments/#{
|
100
|
+
base_url + "/v2/payments/#{payment_id}/complete",
|
90
101
|
body.to_json,
|
91
102
|
http_headers
|
92
103
|
)
|
93
104
|
|
94
|
-
@
|
105
|
+
@open_payments_mutex.synchronize do
|
106
|
+
@open_payments.delete(payment_id)
|
107
|
+
end
|
108
|
+
|
95
109
|
handle_http_response(response, "An unknown error occurred while completing the payment")
|
96
110
|
end
|
97
111
|
|
98
|
-
def cancel_payment(
|
112
|
+
def cancel_payment(payment_id)
|
99
113
|
response = Faraday.post(
|
100
|
-
base_url + "/v2/payments/#{
|
114
|
+
base_url + "/v2/payments/#{payment_id}/cancel",
|
101
115
|
{}.to_json,
|
102
116
|
http_headers,
|
103
117
|
)
|
104
118
|
|
105
|
-
@
|
119
|
+
@open_payments_mutex.synchronize do
|
120
|
+
@open_payments.delete(payment_id)
|
121
|
+
end
|
122
|
+
|
106
123
|
handle_http_response(response, "An unknown error occurred while cancelling the payment")
|
107
124
|
end
|
108
125
|
|
@@ -130,7 +147,7 @@ class PiNetwork
|
|
130
147
|
|
131
148
|
def handle_http_response(response, unknown_error_message = "An unknown error occurred while making an API request")
|
132
149
|
unless response.status == 200
|
133
|
-
error_message =
|
150
|
+
error_message = extract_error_message(response.body, unknown_error_message)
|
134
151
|
raise Errors::APIRequestError.new(error_message, response.status, response.body)
|
135
152
|
end
|
136
153
|
|
@@ -144,8 +161,9 @@ class PiNetwork
|
|
144
161
|
end
|
145
162
|
|
146
163
|
def set_horizon_client(network)
|
147
|
-
host = network
|
148
|
-
horizon =
|
164
|
+
host = (network.starts_with? "Pi Network") ? @mainnet_host : @testnet_host
|
165
|
+
horizon = "https://#{host}"
|
166
|
+
|
149
167
|
client = Stellar::Client.new(host: host, horizon: horizon)
|
150
168
|
Stellar::default_network = network
|
151
169
|
|
@@ -186,8 +204,13 @@ class PiNetwork
|
|
186
204
|
|
187
205
|
def submit_transaction(transaction)
|
188
206
|
envelope = transaction.to_envelope(self.account.keypair)
|
189
|
-
|
190
|
-
|
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
|
191
214
|
end
|
192
215
|
|
193
216
|
def validate_payment_data!(data, options = {})
|
@@ -200,7 +223,11 @@ class PiNetwork
|
|
200
223
|
end
|
201
224
|
|
202
225
|
def validate_private_seed_format!(seed)
|
203
|
-
raise StandardError.new("Private Seed should start with \"S\"") unless seed.upcase.
|
226
|
+
raise StandardError.new("Private Seed should start with \"S\"") unless seed.upcase.start_with?("S")
|
204
227
|
raise StandardError.new("Private Seed should be 56 characters") unless seed.length == 56
|
205
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
|
206
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.
|
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:
|
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,13 +44,16 @@ executables: []
|
|
44
44
|
extensions: []
|
45
45
|
extra_rdoc_files: []
|
46
46
|
files:
|
47
|
-
-
|
47
|
+
- Rakefile
|
48
|
+
- lib/errors.rb
|
49
|
+
- lib/pinetwork.rb
|
50
|
+
- test/a2u_concurrency_test.rb
|
48
51
|
homepage: https://github.com/pi-apps/pi-ruby
|
49
52
|
licenses:
|
50
53
|
- PiOS
|
51
54
|
metadata:
|
52
55
|
documentation_uri: https://github.com/pi-apps/pi-ruby
|
53
|
-
post_install_message:
|
56
|
+
post_install_message:
|
54
57
|
rdoc_options: []
|
55
58
|
require_paths:
|
56
59
|
- lib
|
@@ -65,8 +68,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
68
|
- !ruby/object:Gem::Version
|
66
69
|
version: '0'
|
67
70
|
requirements: []
|
68
|
-
rubygems_version: 3.
|
69
|
-
signing_key:
|
71
|
+
rubygems_version: 3.4.10
|
72
|
+
signing_key:
|
70
73
|
specification_version: 4
|
71
74
|
summary: Pi Network Ruby
|
72
75
|
test_files: []
|