py-near 1.1.39__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 +83 -85
- {py_near-1.1.39.dist-info → py_near-1.1.40.dist-info}/METADATA +1 -1
- {py_near-1.1.39.dist-info → py_near-1.1.40.dist-info}/RECORD +6 -6
- {py_near-1.1.39.dist-info → py_near-1.1.40.dist-info}/LICENSE +0 -0
- {py_near-1.1.39.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,36 +93,41 @@ class JsonProvider(object):
|
|
88
93
|
"params": {"finality": "final"},
|
89
94
|
"id": 1,
|
90
95
|
}
|
91
|
-
async with
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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"]
|
102
|
+
)
|
103
|
+
diff = (
|
104
|
+
datetime.datetime.utcnow().timestamp()
|
105
|
+
- last_block_ts.timestamp()
|
113
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"}
|
@@ -127,59 +137,55 @@ class JsonProvider(object):
|
|
127
137
|
if "@" in rpc_call_addr:
|
128
138
|
auth_key = rpc_call_addr.split("//")[1].split("@")[0]
|
129
139
|
rpc_call_addr = rpc_call_addr.replace(auth_key + "@", "")
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
return json.loads(await r.text())
|
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())
|
142
151
|
|
143
152
|
if broadcast or threshold:
|
144
|
-
|
153
|
+
pending = [
|
145
154
|
asyncio.create_task(f(rpc_addr)) for rpc_addr in self._available_rpcs
|
146
155
|
]
|
156
|
+
|
157
|
+
responses = []
|
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
|
147
180
|
else:
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
res = await t
|
153
|
-
if res:
|
154
|
-
responses.append(res)
|
155
|
-
if not (broadcast or threshold):
|
181
|
+
for rpc_addr in self._available_rpcs:
|
182
|
+
try:
|
183
|
+
res = await f(rpc_addr)
|
184
|
+
if "error" not in res:
|
156
185
|
return res
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
if not responses:
|
161
|
-
raise RpcEmptyResponse("RPC returned empty response")
|
162
|
-
def most_frequent_by_hash(array):
|
163
|
-
counter = Counter(array)
|
164
|
-
most_frequent = counter.most_common(1)[0][0]
|
165
|
-
return most_frequent
|
166
|
-
|
167
|
-
if threshold:
|
168
|
-
# return first most frequent response
|
169
|
-
array = [hash(json.dumps(x)) for x in responses]
|
170
|
-
most_frequent_element = most_frequent_by_hash(array)
|
171
|
-
correct_responses = [
|
172
|
-
x for x in responses if hash(json.dumps(x)) == most_frequent_element
|
173
|
-
]
|
174
|
-
if len(correct_responses) >= threshold:
|
175
|
-
return responses[0]
|
176
|
-
raise Exception(
|
177
|
-
f"Threshold not reached: {len(correct_responses)}/{threshold}"
|
178
|
-
)
|
179
|
-
|
180
|
-
for res in responses:
|
181
|
-
if "error" not in res:
|
182
|
-
return res
|
186
|
+
except Exception as e:
|
187
|
+
logger.error(f"Rpc error: {e}")
|
188
|
+
continue
|
183
189
|
raise RpcEmptyResponse("RPC returned empty response")
|
184
190
|
|
185
191
|
@staticmethod
|
@@ -204,11 +210,9 @@ class JsonProvider(object):
|
|
204
210
|
break
|
205
211
|
return error
|
206
212
|
|
207
|
-
async def json_rpc(
|
208
|
-
self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False, threshold=None
|
209
|
-
):
|
213
|
+
async def json_rpc(self, method, params, broadcast=False, threshold=None):
|
210
214
|
content = await self.call_rpc_request(
|
211
|
-
method, params,
|
215
|
+
method, params, broadcast=broadcast, threshold=threshold
|
212
216
|
)
|
213
217
|
if not content:
|
214
218
|
raise RpcEmptyResponse("RPC returned empty response")
|
@@ -218,7 +222,7 @@ class JsonProvider(object):
|
|
218
222
|
raise error
|
219
223
|
return content["result"]
|
220
224
|
|
221
|
-
async def send_tx(self, signed_tx: str
|
225
|
+
async def send_tx(self, signed_tx: str):
|
222
226
|
"""
|
223
227
|
Send a signed transaction to the network and return the hash of the transaction
|
224
228
|
:param signed_tx: base64 encoded signed transaction, str.
|
@@ -228,13 +232,10 @@ class JsonProvider(object):
|
|
228
232
|
return await self.json_rpc(
|
229
233
|
"broadcast_tx_async",
|
230
234
|
[signed_tx],
|
231
|
-
timeout=timeout,
|
232
235
|
broadcast=self.allow_broadcast,
|
233
236
|
)
|
234
237
|
|
235
|
-
async def send_tx_included(
|
236
|
-
self, signed_tx: str, timeout: int = constants.TIMEOUT_WAIT_RPC
|
237
|
-
):
|
238
|
+
async def send_tx_included(self, signed_tx: str):
|
238
239
|
"""
|
239
240
|
Send a signed transaction to the network and return the hash of the transaction
|
240
241
|
:param signed_tx: base64 encoded signed transaction, str.
|
@@ -245,7 +246,6 @@ class JsonProvider(object):
|
|
245
246
|
return await self.json_rpc(
|
246
247
|
"send_tx",
|
247
248
|
{"signed_tx_base64": signed_tx, "wait_until": "INCLUDED"},
|
248
|
-
timeout=timeout,
|
249
249
|
broadcast=self.allow_broadcast,
|
250
250
|
)
|
251
251
|
except InvalidNonce:
|
@@ -269,7 +269,6 @@ class JsonProvider(object):
|
|
269
269
|
async def send_tx_and_wait(
|
270
270
|
self,
|
271
271
|
signed_tx: str,
|
272
|
-
timeout: int = constants.TIMEOUT_WAIT_RPC,
|
273
272
|
trx_hash: Optional[str] = None,
|
274
273
|
receiver_id: Optional[str] = None,
|
275
274
|
) -> TransactionResult:
|
@@ -283,7 +282,6 @@ class JsonProvider(object):
|
|
283
282
|
res = await self.json_rpc(
|
284
283
|
"broadcast_tx_commit",
|
285
284
|
[signed_tx],
|
286
|
-
timeout=timeout,
|
287
285
|
broadcast=self.allow_broadcast,
|
288
286
|
)
|
289
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
|