py-near 1.1.37__py3-none-any.whl → 1.1.39__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 +3 -1
- py_near/providers.py +61 -50
- {py_near-1.1.37.dist-info → py_near-1.1.39.dist-info}/METADATA +1 -1
- {py_near-1.1.37.dist-info → py_near-1.1.39.dist-info}/RECORD +6 -6
- {py_near-1.1.37.dist-info → py_near-1.1.39.dist-info}/LICENSE +0 -0
- {py_near-1.1.37.dist-info → py_near-1.1.39.dist-info}/WHEEL +0 -0
py_near/account.py
CHANGED
@@ -394,6 +394,7 @@ class Account(object):
|
|
394
394
|
method_name: str,
|
395
395
|
args: dict,
|
396
396
|
block_id: Optional[int] = None,
|
397
|
+
threshold: Optional[int] = None,
|
397
398
|
) -> ViewFunctionResult:
|
398
399
|
"""
|
399
400
|
Call view function on smart contract. View function is read only function, it can't change state
|
@@ -401,10 +402,11 @@ class Account(object):
|
|
401
402
|
:param method_name: method name to call
|
402
403
|
:param args: json args to call method
|
403
404
|
:param block_id: execution view transaction in block with given id
|
405
|
+
:param threshold: minimal amount of nodes with same result
|
404
406
|
:return: result of view function call
|
405
407
|
"""
|
406
408
|
result = await self._provider.view_call(
|
407
|
-
contract_id, method_name, json.dumps(args).encode("utf8"), block_id=block_id
|
409
|
+
contract_id, method_name, json.dumps(args).encode("utf8"), block_id=block_id, threshold=threshold
|
408
410
|
)
|
409
411
|
if "error" in result:
|
410
412
|
raise ViewFunctionError(result["error"])
|
py_near/providers.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
import base64
|
3
3
|
import json
|
4
|
+
from collections import Counter
|
4
5
|
from typing import Optional
|
5
6
|
|
6
7
|
import aiohttp
|
@@ -116,63 +117,70 @@ class JsonProvider(object):
|
|
116
117
|
self._available_rpcs = available_rpcs
|
117
118
|
|
118
119
|
async def call_rpc_request(
|
119
|
-
self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False
|
120
|
+
self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False, threshold=None
|
120
121
|
):
|
121
122
|
await self.check_available_rpcs()
|
122
123
|
j = {"method": method, "params": params, "id": "dontcare", "jsonrpc": "2.0"}
|
123
|
-
res = {}
|
124
|
-
if broadcast:
|
125
|
-
async def f(rpc_call_addr):
|
126
|
-
async with aiohttp.ClientSession() as session:
|
127
|
-
r = await session.post(
|
128
|
-
rpc_call_addr,
|
129
|
-
json=j,
|
130
|
-
timeout=timeout,
|
131
|
-
headers={
|
132
|
-
"Referer": "https://tgapp.herewallet.app"
|
133
|
-
}, # NEAR RPC requires Referer header
|
134
|
-
)
|
135
|
-
r.raise_for_status()
|
136
|
-
res = json.loads(await r.text())
|
137
|
-
return res
|
138
124
|
|
125
|
+
async def f(rpc_call_addr):
|
126
|
+
auth_key = "py-near"
|
127
|
+
if "@" in rpc_call_addr:
|
128
|
+
auth_key = rpc_call_addr.split("//")[1].split("@")[0]
|
129
|
+
rpc_call_addr = rpc_call_addr.replace(auth_key + "@", "")
|
130
|
+
async with aiohttp.ClientSession() as session:
|
131
|
+
r = await session.post(
|
132
|
+
rpc_call_addr,
|
133
|
+
json=j,
|
134
|
+
timeout=timeout,
|
135
|
+
headers={
|
136
|
+
"Referer": "https://tgapp.herewallet.app",
|
137
|
+
"Authorization": f"Bearer {auth_key}",
|
138
|
+
}, # NEAR RPC requires Referer header
|
139
|
+
)
|
140
|
+
if r.status == 200:
|
141
|
+
return json.loads(await r.text())
|
142
|
+
|
143
|
+
if broadcast or threshold:
|
139
144
|
tasks = [
|
140
145
|
asyncio.create_task(f(rpc_addr)) for rpc_addr in self._available_rpcs
|
141
146
|
]
|
142
|
-
|
143
|
-
for
|
144
|
-
|
145
|
-
|
146
|
-
if "error" not in res:
|
147
|
-
return res
|
148
|
-
except Exception as e:
|
149
|
-
logger.error(f"Rpc error: {e}")
|
150
|
-
continue
|
151
|
-
return res
|
152
|
-
for rpc_addr in self._available_rpcs:
|
147
|
+
else:
|
148
|
+
tasks = [f(rpc_addr) for rpc_addr in self._available_rpcs]
|
149
|
+
responses = []
|
150
|
+
for t in tasks:
|
153
151
|
try:
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
"Referer": "https://tgapp.herewallet.app/"
|
161
|
-
}, # NEAR RPC requires Referer header
|
162
|
-
)
|
163
|
-
r.raise_for_status()
|
164
|
-
res = json.loads(await r.text())
|
165
|
-
return res
|
166
|
-
except (
|
167
|
-
RPCTimeoutError,
|
168
|
-
ClientResponseError,
|
169
|
-
ClientConnectorError,
|
170
|
-
ServerDisconnectedError,
|
171
|
-
ConnectionError,
|
172
|
-
) as e:
|
152
|
+
res = await t
|
153
|
+
if res:
|
154
|
+
responses.append(res)
|
155
|
+
if not (broadcast or threshold):
|
156
|
+
return res
|
157
|
+
except Exception as e:
|
173
158
|
logger.error(f"Rpc error: {e}")
|
174
159
|
continue
|
175
|
-
|
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
|
183
|
+
raise RpcEmptyResponse("RPC returned empty response")
|
176
184
|
|
177
185
|
@staticmethod
|
178
186
|
def get_error_from_response(content: dict):
|
@@ -196,9 +204,11 @@ class JsonProvider(object):
|
|
196
204
|
break
|
197
205
|
return error
|
198
206
|
|
199
|
-
async def json_rpc(
|
207
|
+
async def json_rpc(
|
208
|
+
self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False, threshold=None
|
209
|
+
):
|
200
210
|
content = await self.call_rpc_request(
|
201
|
-
method, params, timeout, broadcast=broadcast
|
211
|
+
method, params, timeout, broadcast=broadcast, threshold=threshold
|
202
212
|
)
|
203
213
|
if not content:
|
204
214
|
raise RpcEmptyResponse("RPC returned empty response")
|
@@ -358,6 +368,7 @@ class JsonProvider(object):
|
|
358
368
|
args,
|
359
369
|
finality="optimistic",
|
360
370
|
block_id: Optional[int] = None,
|
371
|
+
threshold: Optional[int] = None,
|
361
372
|
):
|
362
373
|
body = {
|
363
374
|
"request_type": "call_function",
|
@@ -369,7 +380,7 @@ class JsonProvider(object):
|
|
369
380
|
body["block_id"] = block_id
|
370
381
|
else:
|
371
382
|
body["finality"] = finality
|
372
|
-
return await self.json_rpc("query", body)
|
383
|
+
return await self.json_rpc("query", body, threshold=threshold)
|
373
384
|
|
374
385
|
async def get_block(self, block_id):
|
375
386
|
return await self.json_rpc("block", [block_id])
|
@@ -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=CX0fYvWJj8g7QANLUr3G5eUvO347UW1jlunphN47Heg,17617
|
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=W9jquOitiBtW1ezvcpuH5xs-1z0nQYOgvNbMHyevTSA,15646
|
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.39.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
|
27
|
+
py_near-1.1.39.dist-info/METADATA,sha256=bBtdADCm44pJN3z7ZyutWqepQGLoeaAsqX9MfuTa8Gk,4713
|
28
|
+
py_near-1.1.39.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
29
|
+
py_near-1.1.39.dist-info/RECORD,,
|
File without changes
|
File without changes
|