py-near 1.1.36__py3-none-any.whl → 1.1.38__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
@@ -15,6 +15,7 @@ from py_near.dapps.ft.async_client import FT
15
15
  from py_near.dapps.staking.async_client import Staking
16
16
  from py_near.exceptions.provider import (
17
17
  JsonProviderError,
18
+ RPCTimeoutError,
18
19
  )
19
20
  from py_near.models import (
20
21
  TransactionResult,
@@ -151,7 +152,11 @@ class Account(object):
151
152
 
152
153
  try:
153
154
  if included:
154
- await self._provider.send_tx_included(serialized_tx)
155
+ try:
156
+ await self._provider.send_tx_included(serialized_tx)
157
+ except RPCTimeoutError as e:
158
+ if "Transaction not included" in str(e):
159
+ logger.error(f"Transaction not included {trx_hash}")
155
160
  return trx_hash
156
161
  elif nowait:
157
162
  return await self._provider.send_tx(serialized_tx)
@@ -389,6 +394,7 @@ class Account(object):
389
394
  method_name: str,
390
395
  args: dict,
391
396
  block_id: Optional[int] = None,
397
+ threshold: Optional[int] = None,
392
398
  ) -> ViewFunctionResult:
393
399
  """
394
400
  Call view function on smart contract. View function is read only function, it can't change state
@@ -396,10 +402,11 @@ class Account(object):
396
402
  :param method_name: method name to call
397
403
  :param args: json args to call method
398
404
  :param block_id: execution view transaction in block with given id
405
+ :param threshold: minimal amount of nodes with same result
399
406
  :return: result of view function call
400
407
  """
401
408
  result = await self._provider.view_call(
402
- 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
403
410
  )
404
411
  if "error" in result:
405
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
@@ -23,7 +24,8 @@ from py_near.exceptions.provider import (
23
24
  InvalidTransactionError,
24
25
  RPCTimeoutError,
25
26
  UnknownAccessKeyError,
26
- ERROR_CODE_TO_EXCEPTION, InvalidNonce,
27
+ ERROR_CODE_TO_EXCEPTION,
28
+ InvalidNonce,
27
29
  )
28
30
  from py_near.models import TransactionResult
29
31
 
@@ -115,12 +117,13 @@ class JsonProvider(object):
115
117
  self._available_rpcs = available_rpcs
116
118
 
117
119
  async def call_rpc_request(
118
- self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False
120
+ self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False, threshold=None
119
121
  ):
120
122
  await self.check_available_rpcs()
121
123
  j = {"method": method, "params": params, "id": "dontcare", "jsonrpc": "2.0"}
122
124
  res = {}
123
- if broadcast:
125
+ if broadcast or threshold:
126
+
124
127
  async def f(rpc_call_addr):
125
128
  async with aiohttp.ClientSession() as session:
126
129
  r = await session.post(
@@ -128,22 +131,48 @@ class JsonProvider(object):
128
131
  json=j,
129
132
  timeout=timeout,
130
133
  headers={
131
- "Referer": "https://tgapp.herewallet.app/"
134
+ "Referer": "https://tgapp.herewallet.app"
132
135
  }, # NEAR RPC requires Referer header
133
136
  )
134
- r.raise_for_status()
135
- res = json.loads(await r.text())
136
- return res
137
+ if r.status == 200:
138
+ return json.loads(await r.text())
139
+
137
140
  tasks = [
138
141
  asyncio.create_task(f(rpc_addr)) for rpc_addr in self._available_rpcs
139
142
  ]
143
+ responses = []
140
144
  for t in tasks:
141
145
  try:
142
- res = await t
143
- return res
146
+ responses.append(await t)
144
147
  except Exception as e:
145
148
  logger.error(f"Rpc error: {e}")
146
149
  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
+
147
176
  for rpc_addr in self._available_rpcs:
148
177
  try:
149
178
  async with aiohttp.ClientSession() as session:
@@ -191,9 +220,11 @@ class JsonProvider(object):
191
220
  break
192
221
  return error
193
222
 
194
- async def json_rpc(self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False):
223
+ async def json_rpc(
224
+ self, method, params, timeout=TIMEOUT_WAIT_RPC, broadcast=False, threshold=None
225
+ ):
195
226
  content = await self.call_rpc_request(
196
- method, params, timeout, broadcast=broadcast
227
+ method, params, timeout, broadcast=broadcast, threshold=threshold
197
228
  )
198
229
  if not content:
199
230
  raise RpcEmptyResponse("RPC returned empty response")
@@ -236,9 +267,6 @@ class JsonProvider(object):
236
267
  except InvalidNonce:
237
268
  logger.warning("Invalid nonce during broadcast included transaction")
238
269
  return None
239
- except RPCTimeoutError:
240
- raise RPCTimeoutError("Transaction not included")
241
-
242
270
 
243
271
  async def wait_for_trx(self, trx_hash, receiver_id) -> TransactionResult:
244
272
  for _ in range(6):
@@ -356,6 +384,7 @@ class JsonProvider(object):
356
384
  args,
357
385
  finality="optimistic",
358
386
  block_id: Optional[int] = None,
387
+ threshold: Optional[int] = None,
359
388
  ):
360
389
  body = {
361
390
  "request_type": "call_function",
@@ -367,7 +396,7 @@ class JsonProvider(object):
367
396
  body["block_id"] = block_id
368
397
  else:
369
398
  body["finality"] = finality
370
- return await self.json_rpc("query", body)
399
+ return await self.json_rpc("query", body, threshold=threshold)
371
400
 
372
401
  async def get_block(self, block_id):
373
402
  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.36
3
+ Version: 1.1.38
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=z7zjGRJbK8CPkw3b9w7m0M55YUT-kKaMU9JJO0hy14w,17259
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=WT8-hSipTFUATyfZo_YLn8xT8aSkWr3EVq_XZ1elY44,15118
23
+ py_near/providers.py,sha256=A_h587h5e-HXJeko7z2YdDw0SkC3snNJ8hk90POOge8,16190
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.36.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
27
- py_near-1.1.36.dist-info/METADATA,sha256=-RXgeTEdOVUzQuQFGqkOhHMg8BWM8cFSMUZd21VeySs,4713
28
- py_near-1.1.36.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
- py_near-1.1.36.dist-info/RECORD,,
26
+ py_near-1.1.38.dist-info/LICENSE,sha256=I_GOA9xJ35FiL-KnYXZJdATkbO2KcV2dK2enRGVxzKM,1023
27
+ py_near-1.1.38.dist-info/METADATA,sha256=YD1v8lUXSE_IgAcDNcwkEPbY16lL12k3Ri_YORwqmO0,4713
28
+ py_near-1.1.38.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
+ py_near-1.1.38.dist-info/RECORD,,