hive-nectar 0.0.2__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.
Potentially problematic release.
This version of hive-nectar might be problematic. Click here for more details.
- hive_nectar-0.0.2.dist-info/METADATA +182 -0
- hive_nectar-0.0.2.dist-info/RECORD +86 -0
- hive_nectar-0.0.2.dist-info/WHEEL +4 -0
- hive_nectar-0.0.2.dist-info/entry_points.txt +2 -0
- hive_nectar-0.0.2.dist-info/licenses/LICENSE.txt +23 -0
- nectar/__init__.py +32 -0
- nectar/account.py +4371 -0
- nectar/amount.py +475 -0
- nectar/asciichart.py +270 -0
- nectar/asset.py +82 -0
- nectar/block.py +446 -0
- nectar/blockchain.py +1178 -0
- nectar/blockchaininstance.py +2284 -0
- nectar/blockchainobject.py +221 -0
- nectar/blurt.py +563 -0
- nectar/cli.py +6285 -0
- nectar/comment.py +1217 -0
- nectar/community.py +513 -0
- nectar/constants.py +111 -0
- nectar/conveyor.py +309 -0
- nectar/discussions.py +1709 -0
- nectar/exceptions.py +149 -0
- nectar/hive.py +546 -0
- nectar/hivesigner.py +420 -0
- nectar/imageuploader.py +72 -0
- nectar/instance.py +129 -0
- nectar/market.py +1013 -0
- nectar/memo.py +449 -0
- nectar/message.py +357 -0
- nectar/nodelist.py +444 -0
- nectar/price.py +557 -0
- nectar/profile.py +65 -0
- nectar/rc.py +308 -0
- nectar/snapshot.py +726 -0
- nectar/steem.py +582 -0
- nectar/storage.py +53 -0
- nectar/transactionbuilder.py +622 -0
- nectar/utils.py +545 -0
- nectar/version.py +2 -0
- nectar/vote.py +557 -0
- nectar/wallet.py +472 -0
- nectar/witness.py +617 -0
- nectarapi/__init__.py +11 -0
- nectarapi/exceptions.py +123 -0
- nectarapi/graphenerpc.py +589 -0
- nectarapi/node.py +178 -0
- nectarapi/noderpc.py +229 -0
- nectarapi/rpcutils.py +97 -0
- nectarapi/version.py +2 -0
- nectarbase/__init__.py +14 -0
- nectarbase/ledgertransactions.py +75 -0
- nectarbase/memo.py +243 -0
- nectarbase/objects.py +429 -0
- nectarbase/objecttypes.py +22 -0
- nectarbase/operationids.py +102 -0
- nectarbase/operations.py +1297 -0
- nectarbase/signedtransactions.py +48 -0
- nectarbase/transactions.py +11 -0
- nectarbase/version.py +2 -0
- nectargrapheneapi/__init__.py +6 -0
- nectargraphenebase/__init__.py +27 -0
- nectargraphenebase/account.py +846 -0
- nectargraphenebase/aes.py +52 -0
- nectargraphenebase/base58.py +192 -0
- nectargraphenebase/bip32.py +494 -0
- nectargraphenebase/bip38.py +134 -0
- nectargraphenebase/chains.py +149 -0
- nectargraphenebase/dictionary.py +3 -0
- nectargraphenebase/ecdsasig.py +326 -0
- nectargraphenebase/objects.py +123 -0
- nectargraphenebase/objecttypes.py +6 -0
- nectargraphenebase/operationids.py +3 -0
- nectargraphenebase/operations.py +23 -0
- nectargraphenebase/prefix.py +11 -0
- nectargraphenebase/py23.py +38 -0
- nectargraphenebase/signedtransactions.py +201 -0
- nectargraphenebase/types.py +419 -0
- nectargraphenebase/unsignedtransactions.py +283 -0
- nectargraphenebase/version.py +2 -0
- nectarstorage/__init__.py +38 -0
- nectarstorage/base.py +306 -0
- nectarstorage/exceptions.py +16 -0
- nectarstorage/interfaces.py +237 -0
- nectarstorage/masterpassword.py +239 -0
- nectarstorage/ram.py +30 -0
- nectarstorage/sqlite.py +334 -0
nectarapi/graphenerpc.py
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
import ssl
|
|
6
|
+
|
|
7
|
+
from nectargraphenebase.chains import known_chains
|
|
8
|
+
from nectargraphenebase.version import version as nectar_version
|
|
9
|
+
|
|
10
|
+
from .exceptions import (
|
|
11
|
+
CallRetriesReached,
|
|
12
|
+
RPCConnection,
|
|
13
|
+
RPCError,
|
|
14
|
+
RPCErrorDoRetry,
|
|
15
|
+
UnauthorizedError,
|
|
16
|
+
WorkingNodeMissing,
|
|
17
|
+
)
|
|
18
|
+
from .node import Nodes
|
|
19
|
+
from .rpcutils import get_api_name, get_query, is_network_appbase_ready
|
|
20
|
+
|
|
21
|
+
WEBSOCKET_MODULE = None
|
|
22
|
+
if not WEBSOCKET_MODULE:
|
|
23
|
+
try:
|
|
24
|
+
import websocket
|
|
25
|
+
from websocket._exceptions import (
|
|
26
|
+
WebSocketConnectionClosedException,
|
|
27
|
+
WebSocketTimeoutException,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
WEBSOCKET_MODULE = "websocket"
|
|
31
|
+
except ImportError:
|
|
32
|
+
WEBSOCKET_MODULE = None
|
|
33
|
+
REQUEST_MODULE = None
|
|
34
|
+
if not REQUEST_MODULE:
|
|
35
|
+
try:
|
|
36
|
+
import requests
|
|
37
|
+
from requests.adapters import HTTPAdapter
|
|
38
|
+
from requests.exceptions import ConnectionError
|
|
39
|
+
from requests.packages.urllib3.util.retry import Retry
|
|
40
|
+
|
|
41
|
+
REQUEST_MODULE = "requests"
|
|
42
|
+
except ImportError:
|
|
43
|
+
REQUEST_MODULE = None
|
|
44
|
+
|
|
45
|
+
log = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SessionInstance(object):
|
|
49
|
+
"""Singelton for the Session Instance"""
|
|
50
|
+
|
|
51
|
+
instance = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def set_session_instance(instance):
|
|
55
|
+
"""Set session instance"""
|
|
56
|
+
SessionInstance.instance = instance
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def shared_session_instance():
|
|
60
|
+
"""Get session instance"""
|
|
61
|
+
if REQUEST_MODULE is None:
|
|
62
|
+
raise Exception()
|
|
63
|
+
if not SessionInstance.instance:
|
|
64
|
+
SessionInstance.instance = requests.Session()
|
|
65
|
+
return SessionInstance.instance
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def create_ws_instance(use_ssl=True, enable_multithread=True):
|
|
69
|
+
"""Get websocket instance"""
|
|
70
|
+
if WEBSOCKET_MODULE is None:
|
|
71
|
+
raise Exception()
|
|
72
|
+
if use_ssl:
|
|
73
|
+
ssl_defaults = ssl.get_default_verify_paths()
|
|
74
|
+
sslopt_ca_certs = {"ca_certs": ssl_defaults.cafile}
|
|
75
|
+
return websocket.WebSocket(sslopt=sslopt_ca_certs, enable_multithread=enable_multithread)
|
|
76
|
+
else:
|
|
77
|
+
return websocket.WebSocket(enable_multithread=enable_multithread)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class GrapheneRPC(object):
|
|
81
|
+
"""
|
|
82
|
+
This class allows to call API methods synchronously, without callbacks.
|
|
83
|
+
|
|
84
|
+
It logs warnings and errors.
|
|
85
|
+
|
|
86
|
+
:param str urls: Either a single Websocket/Http URL, or a list of URLs
|
|
87
|
+
:param str user: Username for Authentication
|
|
88
|
+
:param str password: Password for Authentication
|
|
89
|
+
:param int num_retries: Try x times to num_retries to a node on disconnect, -1 for indefinitely (default is 100)
|
|
90
|
+
:param int num_retries_call: Repeat num_retries_call times a rpc call on node error (default is 5)
|
|
91
|
+
:param int timeout: Timeout setting for https nodes (default is 60)
|
|
92
|
+
:param bool autoconnect: When set to false, connection is performed on the first rpc call (default is True)
|
|
93
|
+
:param bool use_condenser: Use the old condenser_api rpc protocol on nodes with version
|
|
94
|
+
0.19.4 or higher. The settings has no effect on nodes with version of 0.19.3 or lower.
|
|
95
|
+
:param bool use_tor: When set to true, 'socks5h://localhost:9050' is set as proxy
|
|
96
|
+
:param dict custom_chains: custom chain which should be added to the known chains
|
|
97
|
+
|
|
98
|
+
Available APIs:
|
|
99
|
+
|
|
100
|
+
* database
|
|
101
|
+
* network_node
|
|
102
|
+
* network_broadcast
|
|
103
|
+
|
|
104
|
+
Usage:
|
|
105
|
+
|
|
106
|
+
.. code-block:: python
|
|
107
|
+
|
|
108
|
+
from nectarapi.graphenerpc import GrapheneRPC
|
|
109
|
+
ws = GrapheneRPC("wss://steemd.pevo.science","","")
|
|
110
|
+
print(ws.get_account_count())
|
|
111
|
+
|
|
112
|
+
ws = GrapheneRPC("https://api.steemit.com","","")
|
|
113
|
+
print(ws.get_account_count())
|
|
114
|
+
|
|
115
|
+
.. note:: This class allows to call methods available via
|
|
116
|
+
websocket. If you want to use the notification
|
|
117
|
+
subsystem, please use ``GrapheneWebsocket`` instead.
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(self, urls, user=None, password=None, **kwargs):
|
|
122
|
+
"""Init."""
|
|
123
|
+
self.rpc_methods = {"offline": -1, "ws": 0, "jsonrpc": 1, "wsappbase": 2, "appbase": 3}
|
|
124
|
+
self.current_rpc = self.rpc_methods["ws"]
|
|
125
|
+
self._request_id = 0
|
|
126
|
+
self.timeout = kwargs.get("timeout", 60)
|
|
127
|
+
num_retries = kwargs.get("num_retries", 100)
|
|
128
|
+
num_retries_call = kwargs.get("num_retries_call", 5)
|
|
129
|
+
self.use_condenser = kwargs.get("use_condenser", False)
|
|
130
|
+
self.use_tor = kwargs.get("use_tor", False)
|
|
131
|
+
self.disable_chain_detection = kwargs.get("disable_chain_detection", False)
|
|
132
|
+
self.known_chains = known_chains
|
|
133
|
+
custom_chain = kwargs.get("custom_chains", {})
|
|
134
|
+
if len(custom_chain) > 0:
|
|
135
|
+
for c in custom_chain:
|
|
136
|
+
if c not in self.known_chains:
|
|
137
|
+
self.known_chains[c] = custom_chain[c]
|
|
138
|
+
|
|
139
|
+
self.nodes = Nodes(urls, num_retries, num_retries_call)
|
|
140
|
+
if self.nodes.working_nodes_count == 0:
|
|
141
|
+
self.current_rpc = self.rpc_methods["offline"]
|
|
142
|
+
|
|
143
|
+
self.user = user
|
|
144
|
+
self.password = password
|
|
145
|
+
self.ws = None
|
|
146
|
+
self.url = None
|
|
147
|
+
self.session = None
|
|
148
|
+
self.rpc_queue = []
|
|
149
|
+
if kwargs.get("autoconnect", True):
|
|
150
|
+
self.rpcconnect()
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def num_retries(self):
|
|
154
|
+
return self.nodes.num_retries
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def num_retries_call(self):
|
|
158
|
+
return self.nodes.num_retries_call
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def error_cnt_call(self):
|
|
162
|
+
return self.nodes.error_cnt_call
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def error_cnt(self):
|
|
166
|
+
return self.nodes.error_cnt
|
|
167
|
+
|
|
168
|
+
def get_request_id(self):
|
|
169
|
+
"""Get request id."""
|
|
170
|
+
self._request_id += 1
|
|
171
|
+
return self._request_id
|
|
172
|
+
|
|
173
|
+
def next(self):
|
|
174
|
+
"""Switches to the next node url"""
|
|
175
|
+
if self.ws:
|
|
176
|
+
try:
|
|
177
|
+
self.rpcclose()
|
|
178
|
+
except Exception as e:
|
|
179
|
+
log.warning(str(e))
|
|
180
|
+
self.rpcconnect()
|
|
181
|
+
|
|
182
|
+
def is_appbase_ready(self):
|
|
183
|
+
"""Check if node is appbase ready"""
|
|
184
|
+
return self.current_rpc in [self.rpc_methods["wsappbase"], self.rpc_methods["appbase"]]
|
|
185
|
+
|
|
186
|
+
def get_use_appbase(self):
|
|
187
|
+
"""Returns True if appbase ready and appbase calls are set"""
|
|
188
|
+
return not self.use_condenser and self.is_appbase_ready()
|
|
189
|
+
|
|
190
|
+
def rpcconnect(self, next_url=True):
|
|
191
|
+
"""Connect to next url in a loop."""
|
|
192
|
+
if self.nodes.working_nodes_count == 0:
|
|
193
|
+
return
|
|
194
|
+
while True:
|
|
195
|
+
if next_url:
|
|
196
|
+
self.url = next(self.nodes)
|
|
197
|
+
self.nodes.reset_error_cnt_call()
|
|
198
|
+
log.debug("Trying to connect to node %s" % self.url)
|
|
199
|
+
if self.url[:3] == "wss":
|
|
200
|
+
self.ws = create_ws_instance(use_ssl=True)
|
|
201
|
+
self.ws.settimeout(self.timeout)
|
|
202
|
+
self.current_rpc = self.rpc_methods["wsappbase"]
|
|
203
|
+
elif self.url[:2] == "ws":
|
|
204
|
+
self.ws = create_ws_instance(use_ssl=False)
|
|
205
|
+
self.ws.settimeout(self.timeout)
|
|
206
|
+
self.current_rpc = self.rpc_methods["wsappbase"]
|
|
207
|
+
else:
|
|
208
|
+
self.ws = None
|
|
209
|
+
self.session = shared_session_instance()
|
|
210
|
+
if self.use_tor:
|
|
211
|
+
self.session.proxies = {}
|
|
212
|
+
self.session.proxies["http"] = "socks5h://localhost:9050"
|
|
213
|
+
self.session.proxies["https"] = "socks5h://localhost:9050"
|
|
214
|
+
self.current_rpc = self.rpc_methods["appbase"]
|
|
215
|
+
self.headers = {
|
|
216
|
+
"User-Agent": "nectar v%s" % (nectar_version),
|
|
217
|
+
"content-type": "application/json; charset=utf-8",
|
|
218
|
+
}
|
|
219
|
+
try:
|
|
220
|
+
if self.ws:
|
|
221
|
+
self.ws.connect(self.url)
|
|
222
|
+
self.rpclogin(self.user, self.password)
|
|
223
|
+
if self.disable_chain_detection:
|
|
224
|
+
# Set to appbase rpc format
|
|
225
|
+
if self.current_rpc == self.rpc_methods["ws"]:
|
|
226
|
+
self.current_rpc = self.rpc_methods["wsappbase"]
|
|
227
|
+
else:
|
|
228
|
+
self.current_rpc = self.rpc_methods["appbase"]
|
|
229
|
+
break
|
|
230
|
+
try:
|
|
231
|
+
props = None
|
|
232
|
+
if not self.use_condenser:
|
|
233
|
+
props = self.get_config(api="database")
|
|
234
|
+
else:
|
|
235
|
+
props = self.get_config()
|
|
236
|
+
except Exception as e:
|
|
237
|
+
if re.search("Bad Cast:Invalid cast from type", str(e)):
|
|
238
|
+
# retry with not appbase
|
|
239
|
+
if self.current_rpc == self.rpc_methods["wsappbase"]:
|
|
240
|
+
self.current_rpc = self.rpc_methods["ws"]
|
|
241
|
+
else:
|
|
242
|
+
self.current_rpc = self.rpc_methods["appbase"]
|
|
243
|
+
props = self.get_config(api="database")
|
|
244
|
+
if props is None:
|
|
245
|
+
raise RPCError("Could not receive answer for get_config")
|
|
246
|
+
if is_network_appbase_ready(props):
|
|
247
|
+
if self.ws:
|
|
248
|
+
self.current_rpc = self.rpc_methods["wsappbase"]
|
|
249
|
+
else:
|
|
250
|
+
self.current_rpc = self.rpc_methods["appbase"]
|
|
251
|
+
break
|
|
252
|
+
except KeyboardInterrupt:
|
|
253
|
+
raise
|
|
254
|
+
except Exception as e:
|
|
255
|
+
self.nodes.increase_error_cnt()
|
|
256
|
+
do_sleep = not next_url or (next_url and self.nodes.working_nodes_count == 1)
|
|
257
|
+
self.nodes.sleep_and_check_retries(str(e), sleep=do_sleep)
|
|
258
|
+
next_url = True
|
|
259
|
+
|
|
260
|
+
def rpclogin(self, user, password):
|
|
261
|
+
"""Login into Websocket"""
|
|
262
|
+
if self.ws and self.current_rpc == self.rpc_methods["ws"] and user and password:
|
|
263
|
+
self.login(user, password, api="login_api")
|
|
264
|
+
|
|
265
|
+
def rpcclose(self):
|
|
266
|
+
"""Close Websocket"""
|
|
267
|
+
if self.ws is None:
|
|
268
|
+
return
|
|
269
|
+
# if self.ws.connected:
|
|
270
|
+
self.ws.close()
|
|
271
|
+
|
|
272
|
+
def request_send(self, payload):
|
|
273
|
+
if self.user is not None and self.password is not None:
|
|
274
|
+
response = self.session.post(
|
|
275
|
+
self.url,
|
|
276
|
+
data=payload,
|
|
277
|
+
headers=self.headers,
|
|
278
|
+
timeout=self.timeout,
|
|
279
|
+
auth=(self.user, self.password),
|
|
280
|
+
)
|
|
281
|
+
else:
|
|
282
|
+
response = self.session.post(
|
|
283
|
+
self.url, data=payload, headers=self.headers, timeout=self.timeout
|
|
284
|
+
)
|
|
285
|
+
if response.status_code == 401:
|
|
286
|
+
raise UnauthorizedError
|
|
287
|
+
return response
|
|
288
|
+
|
|
289
|
+
def ws_send(self, payload):
|
|
290
|
+
if self.ws is None:
|
|
291
|
+
raise RPCConnection("No websocket available!")
|
|
292
|
+
self.ws.send(payload)
|
|
293
|
+
reply = self.ws.recv()
|
|
294
|
+
return reply
|
|
295
|
+
|
|
296
|
+
def version_string_to_int(self, network_version):
|
|
297
|
+
version_list = network_version.split(".")
|
|
298
|
+
return int(int(version_list[0]) * 1e8 + int(version_list[1]) * 1e4 + int(version_list[2]))
|
|
299
|
+
|
|
300
|
+
def get_network(self, props=None):
|
|
301
|
+
"""Identify the connected network. This call returns a
|
|
302
|
+
dictionary with keys chain_id, core_symbol and prefix
|
|
303
|
+
"""
|
|
304
|
+
if props is None:
|
|
305
|
+
props = self.get_config(api="database")
|
|
306
|
+
chain_id = None
|
|
307
|
+
network_version = None
|
|
308
|
+
blockchain_name = None
|
|
309
|
+
chain_config = None
|
|
310
|
+
prefix = None
|
|
311
|
+
symbols = []
|
|
312
|
+
chain_assets = []
|
|
313
|
+
|
|
314
|
+
prefix_count = {}
|
|
315
|
+
for key in props:
|
|
316
|
+
if key.split("_")[0] in prefix_count:
|
|
317
|
+
prefix_count[key.split("_")[0]] += 1
|
|
318
|
+
else:
|
|
319
|
+
prefix_count[key.split("_")[0]] = 1
|
|
320
|
+
if len(prefix_count) > 0:
|
|
321
|
+
sorted_prefix_count = sorted(prefix_count.items(), key=lambda x: x[1], reverse=True)
|
|
322
|
+
if sorted_prefix_count[0][1] > 1:
|
|
323
|
+
blockchain_name = sorted_prefix_count[0][0]
|
|
324
|
+
if blockchain_name is None and "HIVE_CHAIN_ID" in props and "STEEM_CHAIN_ID" in props:
|
|
325
|
+
del props["STEEM_CHAIN_ID"]
|
|
326
|
+
|
|
327
|
+
for key in props:
|
|
328
|
+
if key[-8:] == "CHAIN_ID" and blockchain_name is None:
|
|
329
|
+
chain_id = props[key]
|
|
330
|
+
blockchain_name = key.split("_")[0]
|
|
331
|
+
elif key[-8:] == "CHAIN_ID" and key.split("_")[0] == blockchain_name:
|
|
332
|
+
chain_id = props[key]
|
|
333
|
+
elif key[-13:] == "CHAIN_VERSION" and blockchain_name is None:
|
|
334
|
+
network_version = props[key]
|
|
335
|
+
elif key[-13:] == "CHAIN_VERSION" and key.split("_")[0] == blockchain_name:
|
|
336
|
+
network_version = props[key]
|
|
337
|
+
elif key[-14:] == "ADDRESS_PREFIX" and blockchain_name is None:
|
|
338
|
+
prefix = props[key]
|
|
339
|
+
elif key[-14:] == "ADDRESS_PREFIX" and key.split("_")[0] == blockchain_name:
|
|
340
|
+
prefix = props[key]
|
|
341
|
+
elif key[-6:] == "SYMBOL":
|
|
342
|
+
value = {}
|
|
343
|
+
value["asset"] = props[key]["nai"]
|
|
344
|
+
value["precision"] = props[key]["decimals"]
|
|
345
|
+
if (
|
|
346
|
+
"IS_TEST_NET" in props
|
|
347
|
+
and props["IS_TEST_NET"]
|
|
348
|
+
and "nai" in props[key]
|
|
349
|
+
and props[key]["nai"] == "@@000000013"
|
|
350
|
+
):
|
|
351
|
+
value["symbol"] = "TBD"
|
|
352
|
+
elif (
|
|
353
|
+
"IS_TEST_NET" in props
|
|
354
|
+
and props["IS_TEST_NET"]
|
|
355
|
+
and "nai" in props[key]
|
|
356
|
+
and props[key]["nai"] == "@@000000021"
|
|
357
|
+
):
|
|
358
|
+
value["symbol"] = "TESTS"
|
|
359
|
+
else:
|
|
360
|
+
value["symbol"] = key[:-7]
|
|
361
|
+
value["id"] = -1
|
|
362
|
+
symbols.append(value)
|
|
363
|
+
symbol_id = 0
|
|
364
|
+
if len(symbols) == 2:
|
|
365
|
+
symbol_id = 1
|
|
366
|
+
for s in sorted(symbols, key=lambda self: self["asset"], reverse=False):
|
|
367
|
+
s["id"] = symbol_id
|
|
368
|
+
symbol_id += 1
|
|
369
|
+
chain_assets.append(s)
|
|
370
|
+
if (
|
|
371
|
+
chain_id is not None
|
|
372
|
+
and network_version is not None
|
|
373
|
+
and len(chain_assets) > 0
|
|
374
|
+
and prefix is not None
|
|
375
|
+
):
|
|
376
|
+
chain_config = {
|
|
377
|
+
"prefix": prefix,
|
|
378
|
+
"chain_id": chain_id,
|
|
379
|
+
"min_version": network_version,
|
|
380
|
+
"chain_assets": chain_assets,
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if chain_id is None:
|
|
384
|
+
raise RPCError("Connecting to unknown network!")
|
|
385
|
+
highest_version_chain = None
|
|
386
|
+
for k, v in list(self.known_chains.items()):
|
|
387
|
+
if (
|
|
388
|
+
blockchain_name is not None
|
|
389
|
+
and blockchain_name not in k
|
|
390
|
+
and blockchain_name != "STEEMIT"
|
|
391
|
+
and blockchain_name != "CHAIN"
|
|
392
|
+
):
|
|
393
|
+
continue
|
|
394
|
+
if v["chain_id"] == chain_id and self.version_string_to_int(
|
|
395
|
+
v["min_version"]
|
|
396
|
+
) <= self.version_string_to_int(network_version):
|
|
397
|
+
if highest_version_chain is None:
|
|
398
|
+
highest_version_chain = v
|
|
399
|
+
elif self.version_string_to_int(v["min_version"]) > self.version_string_to_int(
|
|
400
|
+
highest_version_chain["min_version"]
|
|
401
|
+
):
|
|
402
|
+
highest_version_chain = v
|
|
403
|
+
if highest_version_chain is None and chain_config is not None:
|
|
404
|
+
return chain_config
|
|
405
|
+
elif highest_version_chain is None:
|
|
406
|
+
raise RPCError("Connecting to unknown network!")
|
|
407
|
+
else:
|
|
408
|
+
return highest_version_chain
|
|
409
|
+
|
|
410
|
+
def _check_for_server_error(self, reply):
|
|
411
|
+
"""Checks for server error message in reply"""
|
|
412
|
+
if re.search("Internal Server Error", reply) or re.search("500", reply):
|
|
413
|
+
raise RPCErrorDoRetry("Internal Server Error")
|
|
414
|
+
elif re.search("Not Implemented", reply) or re.search("501", reply):
|
|
415
|
+
raise RPCError("Not Implemented")
|
|
416
|
+
elif re.search("Bad Gateway", reply) or re.search("502", reply):
|
|
417
|
+
raise RPCErrorDoRetry("Bad Gateway")
|
|
418
|
+
elif re.search("Too Many Requests", reply) or re.search("429", reply):
|
|
419
|
+
raise RPCErrorDoRetry("Too Many Requests")
|
|
420
|
+
elif (
|
|
421
|
+
re.search("Service Temporarily Unavailable", reply)
|
|
422
|
+
or re.search("Service Unavailable", reply)
|
|
423
|
+
or re.search("503", reply)
|
|
424
|
+
):
|
|
425
|
+
raise RPCErrorDoRetry("Service Temporarily Unavailable")
|
|
426
|
+
elif (
|
|
427
|
+
re.search("Gateway Time-out", reply)
|
|
428
|
+
or re.search("Gateway Timeout", reply)
|
|
429
|
+
or re.search("504", reply)
|
|
430
|
+
):
|
|
431
|
+
raise RPCErrorDoRetry("Gateway Time-out")
|
|
432
|
+
elif re.search("HTTP Version not supported", reply) or re.search("505", reply):
|
|
433
|
+
raise RPCError("HTTP Version not supported")
|
|
434
|
+
elif re.search("Variant Also Negotiates", reply) or re.search("506", reply):
|
|
435
|
+
raise RPCError("Variant Also Negotiates")
|
|
436
|
+
elif re.search("Insufficient Storage", reply) or re.search("507", reply):
|
|
437
|
+
raise RPCError("Insufficient Storage")
|
|
438
|
+
elif re.search("Loop Detected", reply) or re.search("508", reply):
|
|
439
|
+
raise RPCError("Loop Detected")
|
|
440
|
+
elif re.search("Bandwidth Limit Exceeded", reply) or re.search("509", reply):
|
|
441
|
+
raise RPCError("Bandwidth Limit Exceeded")
|
|
442
|
+
elif re.search("Not Extended", reply) or re.search("510", reply):
|
|
443
|
+
raise RPCError("Not Extended")
|
|
444
|
+
elif re.search("Network Authentication Required", reply) or re.search("511", reply):
|
|
445
|
+
raise RPCError("Network Authentication Required")
|
|
446
|
+
else:
|
|
447
|
+
raise RPCError("Client returned invalid format. Expected JSON!")
|
|
448
|
+
|
|
449
|
+
def rpcexec(self, payload):
|
|
450
|
+
"""
|
|
451
|
+
Execute a call by sending the payload.
|
|
452
|
+
|
|
453
|
+
:param json payload: Payload data
|
|
454
|
+
:raises ValueError: if the server does not respond in proper JSON format
|
|
455
|
+
:raises RPCError: if the server returns an error
|
|
456
|
+
"""
|
|
457
|
+
log.debug(json.dumps(payload))
|
|
458
|
+
if self.nodes.working_nodes_count == 0:
|
|
459
|
+
raise WorkingNodeMissing
|
|
460
|
+
if self.url is None:
|
|
461
|
+
raise RPCConnection("RPC is not connected!")
|
|
462
|
+
reply = {}
|
|
463
|
+
response = None
|
|
464
|
+
while True:
|
|
465
|
+
self.nodes.increase_error_cnt_call()
|
|
466
|
+
try:
|
|
467
|
+
if (
|
|
468
|
+
self.current_rpc == self.rpc_methods["ws"]
|
|
469
|
+
or self.current_rpc == self.rpc_methods["wsappbase"]
|
|
470
|
+
):
|
|
471
|
+
reply = self.ws_send(json.dumps(payload, ensure_ascii=False).encode("utf8"))
|
|
472
|
+
else:
|
|
473
|
+
response = self.request_send(
|
|
474
|
+
json.dumps(payload, ensure_ascii=False).encode("utf8")
|
|
475
|
+
)
|
|
476
|
+
reply = response.text
|
|
477
|
+
if not bool(reply):
|
|
478
|
+
try:
|
|
479
|
+
self.nodes.sleep_and_check_retries("Empty Reply", call_retry=True)
|
|
480
|
+
except CallRetriesReached:
|
|
481
|
+
self.nodes.increase_error_cnt()
|
|
482
|
+
self.nodes.sleep_and_check_retries(
|
|
483
|
+
"Empty Reply", sleep=False, call_retry=False
|
|
484
|
+
)
|
|
485
|
+
self.rpcconnect()
|
|
486
|
+
else:
|
|
487
|
+
break
|
|
488
|
+
except KeyboardInterrupt:
|
|
489
|
+
raise
|
|
490
|
+
except WebSocketConnectionClosedException as e:
|
|
491
|
+
if self.nodes.num_retries_call_reached:
|
|
492
|
+
self.nodes.increase_error_cnt()
|
|
493
|
+
self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
|
|
494
|
+
self.rpcconnect()
|
|
495
|
+
else:
|
|
496
|
+
# self.nodes.sleep_and_check_retries(str(e), sleep=True, call_retry=True)
|
|
497
|
+
self.rpcconnect(next_url=False)
|
|
498
|
+
except ConnectionError as e:
|
|
499
|
+
self.nodes.increase_error_cnt()
|
|
500
|
+
self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
|
|
501
|
+
self.rpcconnect()
|
|
502
|
+
except WebSocketTimeoutException as e:
|
|
503
|
+
self.nodes.increase_error_cnt()
|
|
504
|
+
self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
|
|
505
|
+
self.rpcconnect()
|
|
506
|
+
except Exception as e:
|
|
507
|
+
self.nodes.increase_error_cnt()
|
|
508
|
+
self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
|
|
509
|
+
self.rpcconnect()
|
|
510
|
+
|
|
511
|
+
ret = {}
|
|
512
|
+
try:
|
|
513
|
+
if response is None:
|
|
514
|
+
ret = json.loads(reply, strict=False, encoding="utf-8")
|
|
515
|
+
else:
|
|
516
|
+
ret = response.json()
|
|
517
|
+
except ValueError:
|
|
518
|
+
self._check_for_server_error(reply)
|
|
519
|
+
|
|
520
|
+
log.debug(json.dumps(reply))
|
|
521
|
+
|
|
522
|
+
if isinstance(ret, dict) and "error" in ret:
|
|
523
|
+
if "detail" in ret["error"]:
|
|
524
|
+
raise RPCError(ret["error"]["detail"])
|
|
525
|
+
else:
|
|
526
|
+
raise RPCError(ret["error"]["message"])
|
|
527
|
+
else:
|
|
528
|
+
if isinstance(ret, list):
|
|
529
|
+
ret_list = []
|
|
530
|
+
for r in ret:
|
|
531
|
+
if isinstance(r, dict) and "error" in r:
|
|
532
|
+
if "detail" in r["error"]:
|
|
533
|
+
raise RPCError(r["error"]["detail"])
|
|
534
|
+
else:
|
|
535
|
+
raise RPCError(r["error"]["message"])
|
|
536
|
+
elif isinstance(r, dict) and "result" in r:
|
|
537
|
+
ret_list.append(r["result"])
|
|
538
|
+
else:
|
|
539
|
+
ret_list.append(r)
|
|
540
|
+
self.nodes.reset_error_cnt_call()
|
|
541
|
+
return ret_list
|
|
542
|
+
elif isinstance(ret, dict) and "result" in ret:
|
|
543
|
+
self.nodes.reset_error_cnt_call()
|
|
544
|
+
return ret["result"]
|
|
545
|
+
elif isinstance(ret, int):
|
|
546
|
+
raise RPCError(
|
|
547
|
+
"Client returned invalid format. Expected JSON! Output: %s" % (str(ret))
|
|
548
|
+
)
|
|
549
|
+
else:
|
|
550
|
+
self.nodes.reset_error_cnt_call()
|
|
551
|
+
return ret
|
|
552
|
+
return ret
|
|
553
|
+
|
|
554
|
+
# End of Deprecated methods
|
|
555
|
+
####################################################################
|
|
556
|
+
def __getattr__(self, name):
|
|
557
|
+
"""Map all methods to RPC calls and pass through the arguments."""
|
|
558
|
+
|
|
559
|
+
def method(*args, **kwargs):
|
|
560
|
+
api_name = get_api_name(self.is_appbase_ready(), *args, **kwargs)
|
|
561
|
+
if self.is_appbase_ready() and self.use_condenser and api_name != "bridge":
|
|
562
|
+
api_name = "condenser_api"
|
|
563
|
+
if api_name is None:
|
|
564
|
+
api_name = "database_api"
|
|
565
|
+
|
|
566
|
+
# let's be able to define the num_retries per query
|
|
567
|
+
stored_num_retries_call = self.nodes.num_retries_call
|
|
568
|
+
self.nodes.num_retries_call = kwargs.get("num_retries_call", stored_num_retries_call)
|
|
569
|
+
add_to_queue = kwargs.get("add_to_queue", False)
|
|
570
|
+
query = get_query(
|
|
571
|
+
self.is_appbase_ready() and not self.use_condenser or api_name == "bridge",
|
|
572
|
+
self.get_request_id(),
|
|
573
|
+
api_name,
|
|
574
|
+
name,
|
|
575
|
+
args,
|
|
576
|
+
)
|
|
577
|
+
if add_to_queue:
|
|
578
|
+
self.rpc_queue.append(query)
|
|
579
|
+
self.nodes.num_retries_call = stored_num_retries_call
|
|
580
|
+
return None
|
|
581
|
+
elif len(self.rpc_queue) > 0:
|
|
582
|
+
self.rpc_queue.append(query)
|
|
583
|
+
query = self.rpc_queue
|
|
584
|
+
self.rpc_queue = []
|
|
585
|
+
r = self.rpcexec(query)
|
|
586
|
+
self.nodes.num_retries_call = stored_num_retries_call
|
|
587
|
+
return r
|
|
588
|
+
|
|
589
|
+
return method
|