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 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
- res = None
143
- for t in tasks:
144
- try:
145
- res = await t
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
- async with aiohttp.ClientSession() as session:
155
- r = await session.post(
156
- rpc_addr,
157
- json=j,
158
- timeout=timeout,
159
- headers={
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
- return res
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(self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False):
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-near
3
- Version: 1.1.37
3
+ Version: 1.1.39
4
4
  Summary: Pretty simple and fully asynchronous framework for working with NEAR blockchain
5
5
  Author: pvolnov
6
6
  Author-email: petr@herewallet.app
@@ -1,5 +1,5 @@
1
1
  py_near/__init__.py,sha256=t5fAxjaU8dN8xpQR2vz0ZGhfTkdVy2RCbkhJhZFglk4,50
2
- py_near/account.py,sha256=kg3Nhq1vaOh10amO8gadBlGCpOADWbL1H8zm5rXBQJ8,17488
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=fUEMTpy-LadTZ32lVmGjKKPrTgm3cCFVGatMeWaAa9c,15120
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.37.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
27
- py_near-1.1.37.dist-info/METADATA,sha256=DA8ZhE1CgjLDaxvKImj1ASgwUXwYSYxvvJelUwiov_Y,4713
28
- py_near-1.1.37.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
- py_near-1.1.37.dist-info/RECORD,,
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,,