py-near 1.1.38__py3-none-any.whl → 1.1.40__py3-none-any.whl
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.
- py_near/account.py +12 -1
- py_near/providers.py +87 -105
- {py_near-1.1.38.dist-info → py_near-1.1.40.dist-info}/METADATA +1 -1
- {py_near-1.1.38.dist-info → py_near-1.1.40.dist-info}/RECORD +6 -6
- {py_near-1.1.38.dist-info → py_near-1.1.40.dist-info}/LICENSE +0 -0
- {py_near-1.1.38.dist-info → py_near-1.1.40.dist-info}/WHEEL +0 -0
py_near/account.py
CHANGED
@@ -93,6 +93,13 @@ class Account(object):
|
|
93
93
|
self._lock_by_pk = collections.defaultdict(asyncio.Lock)
|
94
94
|
self.chain_id = (await self._provider.get_status())["chain_id"]
|
95
95
|
|
96
|
+
async def shutdown(self):
|
97
|
+
"""
|
98
|
+
Close async object
|
99
|
+
:return:
|
100
|
+
"""
|
101
|
+
await self._provider.shutdown()
|
102
|
+
|
96
103
|
async def _update_last_block_hash(self):
|
97
104
|
"""
|
98
105
|
Update last block hash& If it's older than 50 block before, transaction will fail
|
@@ -406,7 +413,11 @@ class Account(object):
|
|
406
413
|
:return: result of view function call
|
407
414
|
"""
|
408
415
|
result = await self._provider.view_call(
|
409
|
-
contract_id,
|
416
|
+
contract_id,
|
417
|
+
method_name,
|
418
|
+
json.dumps(args).encode("utf8"),
|
419
|
+
block_id=block_id,
|
420
|
+
threshold=threshold,
|
410
421
|
)
|
411
422
|
if "error" in result:
|
412
423
|
raise ViewFunctionError(result["error"])
|
py_near/providers.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
import base64
|
3
|
+
import datetime
|
3
4
|
import json
|
4
5
|
from collections import Counter
|
5
6
|
from typing import Optional
|
@@ -7,8 +8,7 @@ from typing import Optional
|
|
7
8
|
import aiohttp
|
8
9
|
from aiohttp import ClientResponseError, ClientConnectorError, ServerDisconnectedError
|
9
10
|
from loguru import logger
|
10
|
-
|
11
|
-
from py_near import constants
|
11
|
+
|
12
12
|
from py_near.constants import TIMEOUT_WAIT_RPC
|
13
13
|
from py_near.exceptions.exceptions import RpcNotAvailableError, RpcEmptyResponse
|
14
14
|
from py_near.exceptions.provider import (
|
@@ -46,7 +46,7 @@ PROVIDER_CODE_TO_EXCEPTION = {
|
|
46
46
|
|
47
47
|
|
48
48
|
class JsonProvider(object):
|
49
|
-
def __init__(self, rpc_addr, allow_broadcast=True):
|
49
|
+
def __init__(self, rpc_addr, allow_broadcast=True, timeout=TIMEOUT_WAIT_RPC):
|
50
50
|
"""
|
51
51
|
:param rpc_addr: str or list of str
|
52
52
|
:param allow_broadcast: bool - submit signed transaction to all RPCs
|
@@ -60,6 +60,11 @@ class JsonProvider(object):
|
|
60
60
|
self._available_rpcs = self._rpc_addresses.copy()
|
61
61
|
self._last_rpc_addr_check = 0
|
62
62
|
self.allow_broadcast = allow_broadcast
|
63
|
+
self._timeout = timeout
|
64
|
+
self.session = aiohttp.ClientSession()
|
65
|
+
|
66
|
+
async def shutdown(self):
|
67
|
+
await self.session.close()
|
63
68
|
|
64
69
|
async def check_available_rpcs(self):
|
65
70
|
if (
|
@@ -88,115 +93,100 @@ class JsonProvider(object):
|
|
88
93
|
"params": {"finality": "final"},
|
89
94
|
"id": 1,
|
90
95
|
}
|
91
|
-
async with
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
data["sync_info"]["latest_block_time"]
|
98
|
-
)
|
99
|
-
diff = (
|
100
|
-
datetime.datetime.utcnow().timestamp()
|
101
|
-
- last_block_ts.timestamp()
|
102
|
-
)
|
103
|
-
is_syncing = diff > 60
|
104
|
-
else:
|
105
|
-
is_syncing = False
|
106
|
-
if is_syncing:
|
107
|
-
logger.error(f"Remove async RPC : {rpc_addr} ({diff})")
|
108
|
-
continue
|
109
|
-
available_rpcs.append(rpc_addr)
|
110
|
-
else:
|
111
|
-
logger.error(
|
112
|
-
f"Remove rpc because of error {r.status}: {rpc_addr}"
|
96
|
+
async with self.session.post(rpc_addr, json=data) as r:
|
97
|
+
if r.status == 200:
|
98
|
+
data = json.loads(await r.text())["result"]
|
99
|
+
if data["sync_info"]["syncing"]:
|
100
|
+
last_block_ts = datetime.datetime.fromisoformat(
|
101
|
+
data["sync_info"]["latest_block_time"]
|
113
102
|
)
|
103
|
+
diff = (
|
104
|
+
datetime.datetime.utcnow().timestamp()
|
105
|
+
- last_block_ts.timestamp()
|
106
|
+
)
|
107
|
+
is_syncing = diff > 60
|
108
|
+
else:
|
109
|
+
is_syncing = False
|
110
|
+
if is_syncing:
|
111
|
+
logger.error(f"Remove async RPC : {rpc_addr} ({diff})")
|
112
|
+
continue
|
113
|
+
available_rpcs.append(rpc_addr)
|
114
|
+
else:
|
115
|
+
logger.error(
|
116
|
+
f"Remove rpc because of error {r.status}: {rpc_addr}"
|
117
|
+
)
|
114
118
|
except Exception as e:
|
115
119
|
if rpc_addr in self._available_rpcs:
|
116
120
|
logger.error(f"Remove rpc: {e}")
|
117
121
|
self._available_rpcs = available_rpcs
|
118
122
|
|
123
|
+
@staticmethod
|
124
|
+
def most_frequent_by_hash(array):
|
125
|
+
counter = Counter(array)
|
126
|
+
most_frequent = counter.most_common(1)[0][0]
|
127
|
+
return most_frequent
|
128
|
+
|
119
129
|
async def call_rpc_request(
|
120
|
-
self, method, params,
|
130
|
+
self, method, params, broadcast=False, threshold: int = 0
|
121
131
|
):
|
122
132
|
await self.check_available_rpcs()
|
123
133
|
j = {"method": method, "params": params, "id": "dontcare", "jsonrpc": "2.0"}
|
124
|
-
res = {}
|
125
|
-
if broadcast or threshold:
|
126
134
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
135
|
+
async def f(rpc_call_addr):
|
136
|
+
auth_key = "py-near"
|
137
|
+
if "@" in rpc_call_addr:
|
138
|
+
auth_key = rpc_call_addr.split("//")[1].split("@")[0]
|
139
|
+
rpc_call_addr = rpc_call_addr.replace(auth_key + "@", "")
|
140
|
+
r = await self.session.post(
|
141
|
+
rpc_call_addr,
|
142
|
+
json=j,
|
143
|
+
timeout=self._timeout,
|
144
|
+
headers={
|
145
|
+
"Referer": "https://tgapp.herewallet.app",
|
146
|
+
"Authorization": f"Bearer {auth_key}",
|
147
|
+
}, # NEAR RPC requires Referer header
|
148
|
+
)
|
149
|
+
if r.status == 200:
|
150
|
+
return json.loads(await r.text())
|
139
151
|
|
140
|
-
|
152
|
+
if broadcast or threshold:
|
153
|
+
pending = [
|
141
154
|
asyncio.create_task(f(rpc_addr)) for rpc_addr in self._available_rpcs
|
142
155
|
]
|
156
|
+
|
143
157
|
responses = []
|
144
|
-
|
158
|
+
while len(pending):
|
159
|
+
done, pending = await asyncio.wait(
|
160
|
+
pending, return_when=asyncio.FIRST_COMPLETED
|
161
|
+
)
|
162
|
+
for task in done:
|
163
|
+
try:
|
164
|
+
result = task.result()
|
165
|
+
if "error" not in result and threshold <= 1:
|
166
|
+
return result
|
167
|
+
responses.append(result)
|
168
|
+
except Exception as e:
|
169
|
+
logger.warning(e)
|
170
|
+
if responses:
|
171
|
+
array = [hash(json.dumps(x)) for x in responses]
|
172
|
+
most_frequent_element = self.most_frequent_by_hash(array)
|
173
|
+
correct_responses = [
|
174
|
+
x for x in responses if hash(json.dumps(x)) == most_frequent_element
|
175
|
+
]
|
176
|
+
if len(correct_responses) >= threshold:
|
177
|
+
for task in pending:
|
178
|
+
task.cancel()
|
179
|
+
return most_frequent_element
|
180
|
+
else:
|
181
|
+
for rpc_addr in self._available_rpcs:
|
145
182
|
try:
|
146
|
-
|
183
|
+
res = await f(rpc_addr)
|
184
|
+
if "error" not in res:
|
185
|
+
return res
|
147
186
|
except Exception as e:
|
148
187
|
logger.error(f"Rpc error: {e}")
|
149
188
|
continue
|
150
|
-
|
151
|
-
def most_frequent_by_hash(array):
|
152
|
-
counter = Counter(array)
|
153
|
-
most_frequent = counter.most_common(1)[0][0]
|
154
|
-
return most_frequent
|
155
|
-
|
156
|
-
if threshold:
|
157
|
-
# return first most frequent response
|
158
|
-
array = [hash(json.dumps(x)) for x in responses]
|
159
|
-
most_frequent_element = most_frequent_by_hash(array)
|
160
|
-
correct_responses = [
|
161
|
-
x for x in responses if hash(json.dumps(x)) == most_frequent_element
|
162
|
-
]
|
163
|
-
if len(correct_responses) >= threshold:
|
164
|
-
return responses[0]
|
165
|
-
raise Exception(
|
166
|
-
f"Threshold not reached: {len(correct_responses)}/{threshold}"
|
167
|
-
)
|
168
|
-
|
169
|
-
if broadcast:
|
170
|
-
# return first response without errors
|
171
|
-
for res in responses:
|
172
|
-
if "error" not in res:
|
173
|
-
return res
|
174
|
-
return responses[0]
|
175
|
-
|
176
|
-
for rpc_addr in self._available_rpcs:
|
177
|
-
try:
|
178
|
-
async with aiohttp.ClientSession() as session:
|
179
|
-
r = await session.post(
|
180
|
-
rpc_addr,
|
181
|
-
json=j,
|
182
|
-
timeout=timeout,
|
183
|
-
headers={
|
184
|
-
"Referer": "https://tgapp.herewallet.app/"
|
185
|
-
}, # NEAR RPC requires Referer header
|
186
|
-
)
|
187
|
-
r.raise_for_status()
|
188
|
-
res = json.loads(await r.text())
|
189
|
-
return res
|
190
|
-
except (
|
191
|
-
RPCTimeoutError,
|
192
|
-
ClientResponseError,
|
193
|
-
ClientConnectorError,
|
194
|
-
ServerDisconnectedError,
|
195
|
-
ConnectionError,
|
196
|
-
) as e:
|
197
|
-
logger.error(f"Rpc error: {e}")
|
198
|
-
continue
|
199
|
-
return res
|
189
|
+
raise RpcEmptyResponse("RPC returned empty response")
|
200
190
|
|
201
191
|
@staticmethod
|
202
192
|
def get_error_from_response(content: dict):
|
@@ -220,11 +210,9 @@ class JsonProvider(object):
|
|
220
210
|
break
|
221
211
|
return error
|
222
212
|
|
223
|
-
async def json_rpc(
|
224
|
-
self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False, threshold=None
|
225
|
-
):
|
213
|
+
async def json_rpc(self, method, params, broadcast=False, threshold=None):
|
226
214
|
content = await self.call_rpc_request(
|
227
|
-
method, params,
|
215
|
+
method, params, broadcast=broadcast, threshold=threshold
|
228
216
|
)
|
229
217
|
if not content:
|
230
218
|
raise RpcEmptyResponse("RPC returned empty response")
|
@@ -234,7 +222,7 @@ class JsonProvider(object):
|
|
234
222
|
raise error
|
235
223
|
return content["result"]
|
236
224
|
|
237
|
-
async def send_tx(self, signed_tx: str
|
225
|
+
async def send_tx(self, signed_tx: str):
|
238
226
|
"""
|
239
227
|
Send a signed transaction to the network and return the hash of the transaction
|
240
228
|
:param signed_tx: base64 encoded signed transaction, str.
|
@@ -244,13 +232,10 @@ class JsonProvider(object):
|
|
244
232
|
return await self.json_rpc(
|
245
233
|
"broadcast_tx_async",
|
246
234
|
[signed_tx],
|
247
|
-
timeout=timeout,
|
248
235
|
broadcast=self.allow_broadcast,
|
249
236
|
)
|
250
237
|
|
251
|
-
async def send_tx_included(
|
252
|
-
self, signed_tx: str, timeout: int = constants.TIMEOUT_WAIT_RPC
|
253
|
-
):
|
238
|
+
async def send_tx_included(self, signed_tx: str):
|
254
239
|
"""
|
255
240
|
Send a signed transaction to the network and return the hash of the transaction
|
256
241
|
:param signed_tx: base64 encoded signed transaction, str.
|
@@ -261,7 +246,6 @@ class JsonProvider(object):
|
|
261
246
|
return await self.json_rpc(
|
262
247
|
"send_tx",
|
263
248
|
{"signed_tx_base64": signed_tx, "wait_until": "INCLUDED"},
|
264
|
-
timeout=timeout,
|
265
249
|
broadcast=self.allow_broadcast,
|
266
250
|
)
|
267
251
|
except InvalidNonce:
|
@@ -285,7 +269,6 @@ class JsonProvider(object):
|
|
285
269
|
async def send_tx_and_wait(
|
286
270
|
self,
|
287
271
|
signed_tx: str,
|
288
|
-
timeout: int = constants.TIMEOUT_WAIT_RPC,
|
289
272
|
trx_hash: Optional[str] = None,
|
290
273
|
receiver_id: Optional[str] = None,
|
291
274
|
) -> TransactionResult:
|
@@ -299,7 +282,6 @@ class JsonProvider(object):
|
|
299
282
|
res = await self.json_rpc(
|
300
283
|
"broadcast_tx_commit",
|
301
284
|
[signed_tx],
|
302
|
-
timeout=timeout,
|
303
285
|
broadcast=self.allow_broadcast,
|
304
286
|
)
|
305
287
|
return TransactionResult(**res)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
py_near/__init__.py,sha256=t5fAxjaU8dN8xpQR2vz0ZGhfTkdVy2RCbkhJhZFglk4,50
|
2
|
-
py_near/account.py,sha256=
|
2
|
+
py_near/account.py,sha256=hz-LZpGvy1cE0_iHX4_TGpOpJ5gfe0P9EAJ_0b-S8E0,17805
|
3
3
|
py_near/constants.py,sha256=inaWIuwmF1EB5JSB0ynnZY5rKY_QsxhF9KuCOhPsM6k,164
|
4
4
|
py_near/dapps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
py_near/dapps/core.py,sha256=LtN9aW2gw2mvEdhzQcQJIidtjv-XL1xjb0LK8DzqtqE,231
|
@@ -20,10 +20,10 @@ py_near/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
|
|
20
20
|
py_near/exceptions/exceptions.py,sha256=DEFipaAHm0y7oCuN2QKzHsiQvUTUQVl-Ce36Ag7n7hs,5509
|
21
21
|
py_near/exceptions/provider.py,sha256=K-wexgjPJ8sw42JePwaP7R5dJEIn9DoFJRvVcURsx6s,7718
|
22
22
|
py_near/models.py,sha256=GZQD1TKGWlwqsJsKRXrVNBjCdAIpk7GQypU-QOtAPFs,11533
|
23
|
-
py_near/providers.py,sha256=
|
23
|
+
py_near/providers.py,sha256=0nv1pnWo0FZ6ZonUbhxVCmnq_gG8vBMiIQ3qhz6toBs,15477
|
24
24
|
py_near/transactions.py,sha256=QAXegv2JpKISk92NaChtIH6-QPHrcWbrwdKH_lH4TsU,3186
|
25
25
|
py_near/utils.py,sha256=FirRH93ydH1cwjn0-sNrZeIn3BRD6QHedrP2VkAdJ6g,126
|
26
|
-
py_near-1.1.
|
27
|
-
py_near-1.1.
|
28
|
-
py_near-1.1.
|
29
|
-
py_near-1.1.
|
26
|
+
py_near-1.1.40.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
|
27
|
+
py_near-1.1.40.dist-info/METADATA,sha256=MLUGiQjTbaNFCyV5qL_IGnHFwBpDlHeJe04WmY37DU4,4713
|
28
|
+
py_near-1.1.40.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
29
|
+
py_near-1.1.40.dist-info/RECORD,,
|
File without changes
|
File without changes
|