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.

Files changed (86) hide show
  1. hive_nectar-0.0.2.dist-info/METADATA +182 -0
  2. hive_nectar-0.0.2.dist-info/RECORD +86 -0
  3. hive_nectar-0.0.2.dist-info/WHEEL +4 -0
  4. hive_nectar-0.0.2.dist-info/entry_points.txt +2 -0
  5. hive_nectar-0.0.2.dist-info/licenses/LICENSE.txt +23 -0
  6. nectar/__init__.py +32 -0
  7. nectar/account.py +4371 -0
  8. nectar/amount.py +475 -0
  9. nectar/asciichart.py +270 -0
  10. nectar/asset.py +82 -0
  11. nectar/block.py +446 -0
  12. nectar/blockchain.py +1178 -0
  13. nectar/blockchaininstance.py +2284 -0
  14. nectar/blockchainobject.py +221 -0
  15. nectar/blurt.py +563 -0
  16. nectar/cli.py +6285 -0
  17. nectar/comment.py +1217 -0
  18. nectar/community.py +513 -0
  19. nectar/constants.py +111 -0
  20. nectar/conveyor.py +309 -0
  21. nectar/discussions.py +1709 -0
  22. nectar/exceptions.py +149 -0
  23. nectar/hive.py +546 -0
  24. nectar/hivesigner.py +420 -0
  25. nectar/imageuploader.py +72 -0
  26. nectar/instance.py +129 -0
  27. nectar/market.py +1013 -0
  28. nectar/memo.py +449 -0
  29. nectar/message.py +357 -0
  30. nectar/nodelist.py +444 -0
  31. nectar/price.py +557 -0
  32. nectar/profile.py +65 -0
  33. nectar/rc.py +308 -0
  34. nectar/snapshot.py +726 -0
  35. nectar/steem.py +582 -0
  36. nectar/storage.py +53 -0
  37. nectar/transactionbuilder.py +622 -0
  38. nectar/utils.py +545 -0
  39. nectar/version.py +2 -0
  40. nectar/vote.py +557 -0
  41. nectar/wallet.py +472 -0
  42. nectar/witness.py +617 -0
  43. nectarapi/__init__.py +11 -0
  44. nectarapi/exceptions.py +123 -0
  45. nectarapi/graphenerpc.py +589 -0
  46. nectarapi/node.py +178 -0
  47. nectarapi/noderpc.py +229 -0
  48. nectarapi/rpcutils.py +97 -0
  49. nectarapi/version.py +2 -0
  50. nectarbase/__init__.py +14 -0
  51. nectarbase/ledgertransactions.py +75 -0
  52. nectarbase/memo.py +243 -0
  53. nectarbase/objects.py +429 -0
  54. nectarbase/objecttypes.py +22 -0
  55. nectarbase/operationids.py +102 -0
  56. nectarbase/operations.py +1297 -0
  57. nectarbase/signedtransactions.py +48 -0
  58. nectarbase/transactions.py +11 -0
  59. nectarbase/version.py +2 -0
  60. nectargrapheneapi/__init__.py +6 -0
  61. nectargraphenebase/__init__.py +27 -0
  62. nectargraphenebase/account.py +846 -0
  63. nectargraphenebase/aes.py +52 -0
  64. nectargraphenebase/base58.py +192 -0
  65. nectargraphenebase/bip32.py +494 -0
  66. nectargraphenebase/bip38.py +134 -0
  67. nectargraphenebase/chains.py +149 -0
  68. nectargraphenebase/dictionary.py +3 -0
  69. nectargraphenebase/ecdsasig.py +326 -0
  70. nectargraphenebase/objects.py +123 -0
  71. nectargraphenebase/objecttypes.py +6 -0
  72. nectargraphenebase/operationids.py +3 -0
  73. nectargraphenebase/operations.py +23 -0
  74. nectargraphenebase/prefix.py +11 -0
  75. nectargraphenebase/py23.py +38 -0
  76. nectargraphenebase/signedtransactions.py +201 -0
  77. nectargraphenebase/types.py +419 -0
  78. nectargraphenebase/unsignedtransactions.py +283 -0
  79. nectargraphenebase/version.py +2 -0
  80. nectarstorage/__init__.py +38 -0
  81. nectarstorage/base.py +306 -0
  82. nectarstorage/exceptions.py +16 -0
  83. nectarstorage/interfaces.py +237 -0
  84. nectarstorage/masterpassword.py +239 -0
  85. nectarstorage/ram.py +30 -0
  86. nectarstorage/sqlite.py +334 -0
nectar/blockchain.py ADDED
@@ -0,0 +1,1178 @@
1
+ # -*- coding: utf-8 -*-
2
+ import hashlib
3
+ import json
4
+ import logging
5
+ import math
6
+ import time
7
+ from datetime import timedelta
8
+ from threading import Event, Thread
9
+ from time import sleep
10
+
11
+ import nectar as stm
12
+ from nectar.instance import shared_blockchain_instance
13
+ from nectarapi.exceptions import UnknownTransaction
14
+ from nectargraphenebase.py23 import py23_bytes
15
+
16
+ from .block import Block, BlockHeader
17
+ from .exceptions import (
18
+ BatchedCallsNotSupported,
19
+ BlockDoesNotExistsException,
20
+ BlockWaitTimeExceeded,
21
+ OfflineHasNoRPCException,
22
+ )
23
+ from .utils import addTzInfo
24
+
25
+ log = logging.getLogger(__name__)
26
+ from queue import Queue
27
+
28
+ FUTURES_MODULE = None
29
+ if not FUTURES_MODULE:
30
+ try:
31
+ from concurrent.futures import ThreadPoolExecutor, as_completed, wait
32
+
33
+ FUTURES_MODULE = "futures"
34
+ # FUTURES_MODULE = None
35
+ except ImportError:
36
+ FUTURES_MODULE = None
37
+
38
+
39
+ # default exception handler. if you want to take some action on failed tasks
40
+ # maybe add the task back into the queue, then make your own handler and pass it in
41
+ def default_handler(name, exception, *args, **kwargs):
42
+ log.warn(
43
+ "%s raised %s with args %s and kwargs %s" % (name, str(exception), repr(args), repr(kwargs))
44
+ )
45
+ pass
46
+
47
+
48
+ class Worker(Thread):
49
+ """Thread executing tasks from a given tasks queue"""
50
+
51
+ def __init__(self, name, queue, results, abort, idle, exception_handler):
52
+ Thread.__init__(self)
53
+ self.name = name
54
+ self.queue = queue
55
+ self.results = results
56
+ self.abort = abort
57
+ self.idle = idle
58
+ self.exception_handler = exception_handler
59
+ self.daemon = True
60
+ self.start()
61
+
62
+ def run(self):
63
+ """Thread work loop calling the function with the params"""
64
+ # keep running until told to abort
65
+ while not self.abort.is_set():
66
+ try:
67
+ # get a task and raise immediately if none available
68
+ func, args, kwargs = self.queue.get(False)
69
+ self.idle.clear()
70
+ except:
71
+ # no work to do
72
+ # if not self.idle.is_set():
73
+ # print >> stdout, '%s is idle' % self.name
74
+ self.idle.set()
75
+ # time.sleep(1)
76
+ continue
77
+
78
+ try:
79
+ # the function may raise
80
+ result = func(*args, **kwargs)
81
+ # print(result)
82
+ if result is not None:
83
+ self.results.put(result)
84
+ except Exception as e:
85
+ # so we move on and handle it in whatever way the caller wanted
86
+ self.exception_handler(self.name, e, args, kwargs)
87
+ finally:
88
+ # task complete no matter what happened
89
+ self.queue.task_done()
90
+
91
+
92
+ # class for thread pool
93
+ class Pool:
94
+ """Pool of threads consuming tasks from a queue"""
95
+
96
+ def __init__(self, thread_count, batch_mode=True, exception_handler=default_handler):
97
+ # batch mode means block when adding tasks if no threads available to process
98
+ self.queue = Queue(thread_count if batch_mode else 0)
99
+ self.resultQueue = Queue(0)
100
+ self.thread_count = thread_count
101
+ self.exception_handler = exception_handler
102
+ self.aborts = []
103
+ self.idles = []
104
+ self.threads = []
105
+
106
+ def __del__(self):
107
+ """Tell my threads to quit"""
108
+ self.abort()
109
+
110
+ def run(self, block=False):
111
+ """Start the threads, or restart them if you've aborted"""
112
+ # either wait for them to finish or return false if some arent
113
+ if block:
114
+ while self.alive():
115
+ sleep(1)
116
+ elif self.alive():
117
+ return False
118
+
119
+ # go start them
120
+ self.aborts = []
121
+ self.idles = []
122
+ self.threads = []
123
+ for n in range(self.thread_count):
124
+ abort = Event()
125
+ idle = Event()
126
+ self.aborts.append(abort)
127
+ self.idles.append(idle)
128
+ self.threads.append(
129
+ Worker(
130
+ "thread-%d" % n,
131
+ self.queue,
132
+ self.resultQueue,
133
+ abort,
134
+ idle,
135
+ self.exception_handler,
136
+ )
137
+ )
138
+ return True
139
+
140
+ def enqueue(self, func, *args, **kargs):
141
+ """Add a task to the queue"""
142
+ self.queue.put((func, args, kargs))
143
+
144
+ def join(self):
145
+ """Wait for completion of all the tasks in the queue"""
146
+ self.queue.join()
147
+
148
+ def abort(self, block=False):
149
+ """Tell each worker that its done working"""
150
+ # tell the threads to stop after they are done with what they are currently doing
151
+ for a in self.aborts:
152
+ a.set()
153
+ # wait for them to finish if requested
154
+ while block and self.alive():
155
+ sleep(1)
156
+
157
+ def alive(self):
158
+ """Returns True if any threads are currently running"""
159
+ return True in [t.is_alive() for t in self.threads]
160
+
161
+ def idle(self):
162
+ """Returns True if all threads are waiting for work"""
163
+ return False not in [i.is_set() for i in self.idles]
164
+
165
+ def done(self):
166
+ """Returns True if not tasks are left to be completed"""
167
+ return self.queue.empty()
168
+
169
+ def results(self, sleep_time=0):
170
+ """Get the set of results that have been processed, repeatedly call until done"""
171
+ sleep(sleep_time)
172
+ results = []
173
+ try:
174
+ while True:
175
+ # get a result, raises empty exception immediately if none available
176
+ results.append(self.resultQueue.get(False))
177
+ self.resultQueue.task_done()
178
+ except:
179
+ return results
180
+ return results
181
+
182
+
183
+ class Blockchain(object):
184
+ """This class allows to access the blockchain and read data
185
+ from it
186
+
187
+ :param Steem/Hive blockchain_instance: Steem or Hive instance
188
+ :param str mode: (default) Irreversible block (``irreversible``) or
189
+ actual head block (``head``)
190
+ :param int max_block_wait_repetition: maximum wait repetition for next block
191
+ where each repetition is block_interval long (default is 3)
192
+
193
+ This class let's you deal with blockchain related data and methods.
194
+ Read blockchain related data:
195
+
196
+ .. testsetup::
197
+
198
+ from nectar.blockchain import Blockchain
199
+ chain = Blockchain()
200
+
201
+ Read current block and blockchain info
202
+
203
+ .. testcode::
204
+
205
+ print(chain.get_current_block())
206
+ print(chain.blockchain.info())
207
+
208
+ Monitor for new blocks. When ``stop`` is not set, monitoring will never stop.
209
+
210
+ .. testcode::
211
+
212
+ blocks = []
213
+ current_num = chain.get_current_block_num()
214
+ for block in chain.blocks(start=current_num - 99, stop=current_num):
215
+ blocks.append(block)
216
+ len(blocks)
217
+
218
+ .. testoutput::
219
+
220
+ 100
221
+
222
+ or each operation individually:
223
+
224
+ .. testcode::
225
+
226
+ ops = []
227
+ current_num = chain.get_current_block_num()
228
+ for operation in chain.ops(start=current_num - 99, stop=current_num):
229
+ ops.append(operation)
230
+
231
+ """
232
+
233
+ def __init__(
234
+ self,
235
+ blockchain_instance=None,
236
+ mode="irreversible",
237
+ max_block_wait_repetition=None,
238
+ data_refresh_time_seconds=900,
239
+ **kwargs,
240
+ ):
241
+ if blockchain_instance is None:
242
+ if kwargs.get("steem_instance"):
243
+ blockchain_instance = kwargs["steem_instance"]
244
+ elif kwargs.get("hive_instance"):
245
+ blockchain_instance = kwargs["hive_instance"]
246
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
247
+
248
+ if mode == "irreversible":
249
+ self.mode = "last_irreversible_block_num"
250
+ elif mode == "head":
251
+ self.mode = "head_block_number"
252
+ else:
253
+ raise ValueError("invalid value for 'mode'!")
254
+ if max_block_wait_repetition:
255
+ self.max_block_wait_repetition = max_block_wait_repetition
256
+ else:
257
+ self.max_block_wait_repetition = 3
258
+ self.block_interval = self.blockchain.get_block_interval()
259
+
260
+ def is_irreversible_mode(self):
261
+ return self.mode == "last_irreversible_block_num"
262
+
263
+ def is_transaction_existing(self, transaction_id):
264
+ """Returns true, if the transaction_id is valid"""
265
+ try:
266
+ self.get_transaction(transaction_id)
267
+ return True
268
+ except UnknownTransaction:
269
+ return False
270
+
271
+ def get_transaction(self, transaction_id):
272
+ """Returns a transaction from the blockchain
273
+
274
+ :param str transaction_id: transaction_id
275
+ """
276
+ if not self.blockchain.is_connected():
277
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
278
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
279
+ if self.blockchain.rpc.get_use_appbase():
280
+ ret = self.blockchain.rpc.get_transaction({"id": transaction_id}, api="account_history")
281
+ else:
282
+ ret = self.blockchain.rpc.get_transaction(transaction_id, api="database")
283
+ return ret
284
+
285
+ def get_transaction_hex(self, transaction):
286
+ """Returns a hexdump of the serialized binary form of a transaction.
287
+
288
+ :param dict transaction: transaction
289
+ """
290
+ if not self.blockchain.is_connected():
291
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
292
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
293
+ if self.blockchain.rpc.get_use_appbase():
294
+ ret = self.blockchain.rpc.get_transaction_hex({"trx": transaction}, api="database")[
295
+ "hex"
296
+ ]
297
+ else:
298
+ ret = self.blockchain.rpc.get_transaction_hex(transaction, api="database")
299
+ return ret
300
+
301
+ def get_current_block_num(self):
302
+ """This call returns the current block number
303
+
304
+ .. note:: The block number returned depends on the ``mode`` used
305
+ when instantiating from this class.
306
+ """
307
+ props = self.blockchain.get_dynamic_global_properties(False)
308
+ if props is None:
309
+ raise ValueError("Could not receive dynamic_global_properties!")
310
+ if self.mode not in props:
311
+ raise ValueError(self.mode + " is not in " + str(props))
312
+ return int(props.get(self.mode))
313
+
314
+ def get_current_block(self, only_ops=False, only_virtual_ops=False):
315
+ """This call returns the current block
316
+
317
+ :param bool only_ops: Returns block with operations only, when set to True (default: False)
318
+ :param bool only_virtual_ops: Includes only virtual operations (default: False)
319
+
320
+ .. note:: The block number returned depends on the ``mode`` used
321
+ when instantiating from this class.
322
+ """
323
+ return Block(
324
+ self.get_current_block_num(),
325
+ only_ops=only_ops,
326
+ only_virtual_ops=only_virtual_ops,
327
+ blockchain_instance=self.blockchain,
328
+ )
329
+
330
+ def get_estimated_block_num(self, date, estimateForwards=False, accurate=True):
331
+ """This call estimates the block number based on a given date
332
+
333
+ :param datetime date: block time for which a block number is estimated
334
+
335
+ .. note:: The block number returned depends on the ``mode`` used
336
+ when instantiating from this class.
337
+
338
+ .. code-block:: python
339
+
340
+ >>> from nectar.blockchain import Blockchain
341
+ >>> from datetime import datetime
342
+ >>> blockchain = Blockchain()
343
+ >>> block_num = blockchain.get_estimated_block_num(datetime(2019, 6, 18, 5 ,8, 27))
344
+ >>> block_num == 33898184
345
+ True
346
+
347
+ """
348
+ last_block = self.get_current_block()
349
+ date = addTzInfo(date)
350
+ if estimateForwards:
351
+ block_offset = 10
352
+ first_block = BlockHeader(block_offset, blockchain_instance=self.blockchain)
353
+ time_diff = date - first_block.time()
354
+ block_number = math.floor(
355
+ time_diff.total_seconds() / self.block_interval + block_offset
356
+ )
357
+ else:
358
+ time_diff = last_block.time() - date
359
+ block_number = math.floor(
360
+ last_block.identifier - time_diff.total_seconds() / self.block_interval
361
+ )
362
+ if block_number < 1:
363
+ block_number = 1
364
+
365
+ if accurate:
366
+ if block_number > last_block.identifier:
367
+ block_number = last_block.identifier
368
+ block_time_diff = timedelta(seconds=10)
369
+
370
+ last_block_time_diff_seconds = 10
371
+ second_last_block_time_diff_seconds = 10
372
+
373
+ while (
374
+ block_time_diff.total_seconds() > self.block_interval
375
+ or block_time_diff.total_seconds() < -self.block_interval
376
+ ):
377
+ block = BlockHeader(block_number, blockchain_instance=self.blockchain)
378
+ second_last_block_time_diff_seconds = last_block_time_diff_seconds
379
+ last_block_time_diff_seconds = block_time_diff.total_seconds()
380
+ block_time_diff = date - block.time()
381
+ if (
382
+ second_last_block_time_diff_seconds == block_time_diff.total_seconds()
383
+ and second_last_block_time_diff_seconds < 10
384
+ ):
385
+ return int(block_number)
386
+ delta = block_time_diff.total_seconds() // self.block_interval
387
+ if delta == 0 and block_time_diff.total_seconds() < 0:
388
+ delta = -1
389
+ elif delta == 0 and block_time_diff.total_seconds() > 0:
390
+ delta = 1
391
+ block_number += delta
392
+ if block_number < 1:
393
+ break
394
+ if block_number > last_block.identifier:
395
+ break
396
+
397
+ return int(block_number)
398
+
399
+ def block_time(self, block_num):
400
+ """Returns a datetime of the block with the given block
401
+ number.
402
+
403
+ :param int block_num: Block number
404
+ """
405
+ return Block(block_num, blockchain_instance=self.blockchain).time()
406
+
407
+ def block_timestamp(self, block_num):
408
+ """Returns the timestamp of the block with the given block
409
+ number as integer.
410
+
411
+ :param int block_num: Block number
412
+ """
413
+ block_time = Block(block_num, blockchain_instance=self.blockchain).time()
414
+ return int(time.mktime(block_time.timetuple()))
415
+
416
+ @property
417
+ def participation_rate(self):
418
+ """Returns the witness participation rate in a range from 0 to 1"""
419
+ return (
420
+ bin(
421
+ int(
422
+ self.blockchain.get_dynamic_global_properties(use_stored_data=False)[
423
+ "recent_slots_filled"
424
+ ]
425
+ )
426
+ ).count("1")
427
+ / 128
428
+ )
429
+
430
+ def blocks(
431
+ self,
432
+ start=None,
433
+ stop=None,
434
+ max_batch_size=None,
435
+ threading=False,
436
+ thread_num=8,
437
+ only_ops=False,
438
+ only_virtual_ops=False,
439
+ ):
440
+ """Yields blocks starting from ``start``.
441
+
442
+ :param int start: Starting block
443
+ :param int stop: Stop at this block
444
+ :param int max_batch_size: only for appbase nodes. When not None, batch calls of are used.
445
+ Cannot be combined with threading
446
+ :param bool threading: Enables threading. Cannot be combined with batch calls
447
+ :param int thread_num: Defines the number of threads, when `threading` is set.
448
+ :param bool only_ops: Only yield operations (default: False).
449
+ Cannot be combined with ``only_virtual_ops=True``.
450
+ :param bool only_virtual_ops: Only yield virtual operations (default: False)
451
+
452
+ .. note:: If you want instant confirmation, you need to instantiate
453
+ class:`nectar.blockchain.Blockchain` with
454
+ ``mode="head"``, otherwise, the call will wait until
455
+ confirmed in an irreversible block.
456
+
457
+ """
458
+ # Let's find out how often blocks are generated!
459
+ current_block = self.get_current_block()
460
+ current_block_num = current_block.block_num
461
+ if not start:
462
+ start = current_block_num
463
+ head_block_reached = False
464
+ if threading and FUTURES_MODULE is not None:
465
+ pool = ThreadPoolExecutor(max_workers=thread_num)
466
+ elif threading:
467
+ pool = Pool(thread_num, batch_mode=True)
468
+ if threading:
469
+ blockchain_instance = [self.blockchain]
470
+ nodelist = self.blockchain.rpc.nodes.export_working_nodes()
471
+ for i in range(thread_num - 1):
472
+ blockchain_instance.append(
473
+ stm.Steem(
474
+ node=nodelist,
475
+ num_retries=self.blockchain.rpc.num_retries,
476
+ num_retries_call=self.blockchain.rpc.num_retries_call,
477
+ timeout=self.blockchain.rpc.timeout,
478
+ )
479
+ )
480
+ # We are going to loop indefinitely
481
+ latest_block = 0
482
+ while True:
483
+ if stop:
484
+ head_block = stop
485
+ else:
486
+ current_block_num = self.get_current_block_num()
487
+ head_block = current_block_num
488
+ if threading and not head_block_reached:
489
+ latest_block = start - 1
490
+ result_block_nums = []
491
+ for blocknum in range(start, head_block + 1, thread_num):
492
+ # futures = []
493
+ i = 0
494
+ if FUTURES_MODULE is not None:
495
+ futures = []
496
+ block_num_list = []
497
+ # freeze = self.blockchain.rpc.nodes.freeze_current_node
498
+ num_retries = self.blockchain.rpc.nodes.num_retries
499
+ # self.blockchain.rpc.nodes.freeze_current_node = True
500
+ self.blockchain.rpc.nodes.num_retries = thread_num
501
+ error_cnt = self.blockchain.rpc.nodes.node.error_cnt
502
+ while i < thread_num and blocknum + i <= head_block:
503
+ block_num_list.append(blocknum + i)
504
+ results = []
505
+ if FUTURES_MODULE is not None:
506
+ futures.append(
507
+ pool.submit(
508
+ Block,
509
+ blocknum + i,
510
+ only_ops=only_ops,
511
+ only_virtual_ops=only_virtual_ops,
512
+ blockchain_instance=blockchain_instance[i],
513
+ )
514
+ )
515
+ else:
516
+ pool.enqueue(
517
+ Block,
518
+ blocknum + i,
519
+ only_ops=only_ops,
520
+ only_virtual_ops=only_virtual_ops,
521
+ blockchain_instance=blockchain_instance[i],
522
+ )
523
+ i += 1
524
+ if FUTURES_MODULE is not None:
525
+ try:
526
+ results = [r.result() for r in as_completed(futures)]
527
+ except Exception as e:
528
+ log.error(str(e))
529
+ else:
530
+ pool.run(True)
531
+ pool.join()
532
+ for result in pool.results():
533
+ results.append(result)
534
+ pool.abort()
535
+ self.blockchain.rpc.nodes.num_retries = num_retries
536
+ # self.blockchain.rpc.nodes.freeze_current_node = freeze
537
+ new_error_cnt = self.blockchain.rpc.nodes.node.error_cnt
538
+ self.blockchain.rpc.nodes.node.error_cnt = error_cnt
539
+ if new_error_cnt > error_cnt:
540
+ self.blockchain.rpc.nodes.node.error_cnt += 1
541
+ # self.blockchain.rpc.next()
542
+
543
+ checked_results = []
544
+ for b in results:
545
+ if b.block_num is not None and int(b.block_num) not in result_block_nums:
546
+ b["id"] = b.block_num
547
+ b.identifier = b.block_num
548
+ checked_results.append(b)
549
+ result_block_nums.append(int(b.block_num))
550
+
551
+ missing_block_num = list(set(block_num_list).difference(set(result_block_nums)))
552
+ while len(missing_block_num) > 0:
553
+ for blocknum in missing_block_num:
554
+ try:
555
+ block = Block(
556
+ blocknum,
557
+ only_ops=only_ops,
558
+ only_virtual_ops=only_virtual_ops,
559
+ blockchain_instance=self.blockchain,
560
+ )
561
+ checked_results.append(block)
562
+ result_block_nums.append(int(block.block_num))
563
+ except Exception as e:
564
+ log.error(str(e))
565
+ missing_block_num = list(
566
+ set(block_num_list).difference(set(result_block_nums))
567
+ )
568
+ from operator import itemgetter
569
+
570
+ blocks = sorted(checked_results, key=itemgetter("id"))
571
+ for b in blocks:
572
+ if latest_block < int(b.block_num):
573
+ latest_block = int(b.block_num)
574
+ yield b
575
+
576
+ if latest_block <= head_block:
577
+ for blocknum in range(latest_block + 1, head_block + 1):
578
+ if blocknum not in result_block_nums:
579
+ block = Block(
580
+ blocknum,
581
+ only_ops=only_ops,
582
+ only_virtual_ops=only_virtual_ops,
583
+ blockchain_instance=self.blockchain,
584
+ )
585
+ result_block_nums.append(blocknum)
586
+ yield block
587
+ elif (
588
+ max_batch_size is not None
589
+ and (head_block - start) >= max_batch_size
590
+ and not head_block_reached
591
+ ):
592
+ if not self.blockchain.is_connected():
593
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
594
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
595
+ latest_block = start - 1
596
+ batches = max_batch_size
597
+ for blocknumblock in range(start, head_block + 1, batches):
598
+ # Get full block
599
+ if (head_block - blocknumblock) < batches:
600
+ batches = head_block - blocknumblock + 1
601
+ # add up to 'batches' calls to the queue
602
+ block_batch = []
603
+ for blocknum in range(blocknumblock, blocknumblock + batches):
604
+ if blocknum == blocknumblock + batches - 1:
605
+ add_to_queue = False # execute the call with the last request
606
+ else:
607
+ add_to_queue = True # append request to the queue w/o executing
608
+ if only_virtual_ops:
609
+ if self.blockchain.rpc.get_use_appbase():
610
+ block_batch = self.blockchain.rpc.get_ops_in_block(
611
+ {"block_num": blocknum, "only_virtual": only_virtual_ops},
612
+ api="account_history",
613
+ add_to_queue=add_to_queue,
614
+ )
615
+ else:
616
+ block_batch = self.blockchain.rpc.get_ops_in_block(
617
+ blocknum, only_virtual_ops, add_to_queue=add_to_queue
618
+ )
619
+ else:
620
+ if self.blockchain.rpc.get_use_appbase():
621
+ block_batch = self.blockchain.rpc.get_block(
622
+ {"block_num": blocknum}, api="block", add_to_queue=add_to_queue
623
+ )
624
+ else:
625
+ block_batch = self.blockchain.rpc.get_block(
626
+ blocknum, add_to_queue=add_to_queue
627
+ )
628
+
629
+ if not bool(block_batch):
630
+ raise BatchedCallsNotSupported()
631
+ if not isinstance(block_batch, list):
632
+ block_batch = [block_batch]
633
+ for block in block_batch:
634
+ if not bool(block):
635
+ continue
636
+ if self.blockchain.rpc.get_use_appbase():
637
+ if only_virtual_ops:
638
+ block = {
639
+ "block": block["ops"][0]["block"],
640
+ "timestamp": block["ops"][0]["timestamp"],
641
+ "id": block["ops"][0]["block"],
642
+ "operations": block["ops"],
643
+ }
644
+ else:
645
+ block = block["block"]
646
+ block = Block(
647
+ block,
648
+ only_ops=only_ops,
649
+ only_virtual_ops=only_virtual_ops,
650
+ blockchain_instance=self.blockchain,
651
+ )
652
+ block["id"] = block.block_num
653
+ block.identifier = block.block_num
654
+ yield block
655
+ else:
656
+ # Blocks from start until head block
657
+ for blocknum in range(start, head_block + 1):
658
+ # Get full block
659
+ block = self.wait_for_and_get_block(
660
+ blocknum,
661
+ only_ops=only_ops,
662
+ only_virtual_ops=only_virtual_ops,
663
+ block_number_check_cnt=5,
664
+ last_current_block_num=current_block_num,
665
+ )
666
+ yield block
667
+ # Set new start
668
+ start = head_block + 1
669
+ head_block_reached = True
670
+
671
+ if stop and start > stop:
672
+ return
673
+
674
+ # Sleep for one block
675
+ time.sleep(self.block_interval)
676
+
677
+ def wait_for_and_get_block(
678
+ self,
679
+ block_number,
680
+ blocks_waiting_for=None,
681
+ only_ops=False,
682
+ only_virtual_ops=False,
683
+ block_number_check_cnt=-1,
684
+ last_current_block_num=None,
685
+ ):
686
+ """Get the desired block from the chain, if the current head block is smaller (for both head and irreversible)
687
+ then we wait, but a maxmimum of blocks_waiting_for * max_block_wait_repetition time before failure.
688
+
689
+ :param int block_number: desired block number
690
+ :param int blocks_waiting_for: difference between block_number and current head and defines
691
+ how many blocks we are willing to wait, positive int (default: None)
692
+ :param bool only_ops: Returns blocks with operations only, when set to True (default: False)
693
+ :param bool only_virtual_ops: Includes only virtual operations (default: False)
694
+ :param int block_number_check_cnt: limit the number of retries when greater than -1
695
+ :param int last_current_block_num: can be used to reduce the number of get_current_block_num() api calls
696
+
697
+ """
698
+ if last_current_block_num is None:
699
+ last_current_block_num = self.get_current_block_num()
700
+ elif last_current_block_num - block_number < 50:
701
+ last_current_block_num = self.get_current_block_num()
702
+
703
+ if not blocks_waiting_for:
704
+ blocks_waiting_for = max(1, block_number - last_current_block_num)
705
+
706
+ repetition = 0
707
+ # can't return the block before the chain has reached it (support future block_num)
708
+ while last_current_block_num < block_number:
709
+ repetition += 1
710
+ time.sleep(self.block_interval)
711
+ if last_current_block_num - block_number < 50:
712
+ last_current_block_num = self.get_current_block_num()
713
+ if repetition > blocks_waiting_for * self.max_block_wait_repetition:
714
+ raise BlockWaitTimeExceeded(
715
+ "Already waited %d s"
716
+ % (
717
+ blocks_waiting_for
718
+ * self.max_block_wait_repetition
719
+ * self.block_interval
720
+ )
721
+ )
722
+ # block has to be returned properly
723
+ repetition = 0
724
+ cnt = 0
725
+ block = None
726
+ while (
727
+ block is None or block.block_num is None or int(block.block_num) != block_number
728
+ ) and (block_number_check_cnt < 0 or cnt < block_number_check_cnt):
729
+ try:
730
+ block = Block(
731
+ block_number,
732
+ only_ops=only_ops,
733
+ only_virtual_ops=only_virtual_ops,
734
+ blockchain_instance=self.blockchain,
735
+ )
736
+ cnt += 1
737
+ except BlockDoesNotExistsException:
738
+ block = None
739
+ if repetition > blocks_waiting_for * self.max_block_wait_repetition:
740
+ raise BlockWaitTimeExceeded(
741
+ "Already waited %d s"
742
+ % (
743
+ blocks_waiting_for
744
+ * self.max_block_wait_repetition
745
+ * self.block_interval
746
+ )
747
+ )
748
+ repetition += 1
749
+ time.sleep(self.block_interval)
750
+
751
+ return block
752
+
753
+ def ops(self, start=None, stop=None, only_virtual_ops=False, **kwargs):
754
+ """Blockchain.ops() is deprecated. Please use Blockchain.stream() instead."""
755
+ raise DeprecationWarning(
756
+ "Blockchain.ops() is deprecated. Please use Blockchain.stream() instead."
757
+ )
758
+
759
+ def ops_statistics(
760
+ self, start, stop=None, add_to_ops_stat=None, with_virtual_ops=True, verbose=False
761
+ ):
762
+ """Generates statistics for all operations (including virtual operations) starting from
763
+ ``start``.
764
+
765
+ :param int start: Starting block
766
+ :param int stop: Stop at this block, if set to None, the current_block_num is taken
767
+ :param dict add_to_ops_stat: if set, the result is added to add_to_ops_stat
768
+ :param bool verbose: if True, the current block number and timestamp is printed
769
+
770
+ This call returns a dict with all possible operations and their occurrence.
771
+
772
+ """
773
+ if add_to_ops_stat is None:
774
+ import nectarbase.operationids
775
+
776
+ ops_stat = nectarbase.operationids.operations.copy()
777
+ for key in ops_stat:
778
+ ops_stat[key] = 0
779
+ else:
780
+ ops_stat = add_to_ops_stat.copy()
781
+ current_block = self.get_current_block_num()
782
+ if start > current_block:
783
+ return
784
+ if stop is None:
785
+ stop = current_block
786
+ for block in self.blocks(start=start, stop=stop, only_ops=False, only_virtual_ops=False):
787
+ if verbose:
788
+ print(block["identifier"] + " " + block["timestamp"])
789
+ ops_stat = block.ops_statistics(add_to_ops_stat=ops_stat)
790
+ if with_virtual_ops:
791
+ for block in self.blocks(start=start, stop=stop, only_ops=True, only_virtual_ops=True):
792
+ if verbose:
793
+ print(block["identifier"] + " " + block["timestamp"])
794
+ ops_stat = block.ops_statistics(add_to_ops_stat=ops_stat)
795
+ return ops_stat
796
+
797
+ def stream(self, opNames=[], raw_ops=False, *args, **kwargs):
798
+ """Yield specific operations (e.g. comments) only
799
+
800
+ :param array opNames: List of operations to filter for
801
+ :param bool raw_ops: When set to True, it returns the unmodified operations (default: False)
802
+ :param int start: Start at this block
803
+ :param int stop: Stop at this block
804
+ :param int max_batch_size: only for appbase nodes. When not None, batch calls of are used.
805
+ Cannot be combined with threading
806
+ :param bool threading: Enables threading. Cannot be combined with batch calls
807
+ :param int thread_num: Defines the number of threads, when `threading` is set.
808
+ :param bool only_ops: Only yield operations (default: False)
809
+ Cannot be combined with ``only_virtual_ops=True``
810
+ :param bool only_virtual_ops: Only yield virtual operations (default: False)
811
+
812
+ The dict output is formated such that ``type`` carries the
813
+ operation type. Timestamp and block_num are taken from the
814
+ block the operation was stored in and the other keys depend
815
+ on the actual operation.
816
+
817
+ .. note:: If you want instant confirmation, you need to instantiate
818
+ class:`nectar.blockchain.Blockchain` with
819
+ ``mode="head"``, otherwise, the call will wait until
820
+ confirmed in an irreversible block.
821
+
822
+ output when `raw_ops=False` is set:
823
+
824
+ .. code-block:: js
825
+
826
+ {
827
+ 'type': 'transfer',
828
+ 'from': 'johngreenfield',
829
+ 'to': 'thundercurator',
830
+ 'amount': '0.080 SBD',
831
+ 'memo': 'https://steemit.com/lofi/@johngreenfield/lofi-joji-yeah-right',
832
+ '_id': '6d4c5f2d4d8ef1918acaee4a8dce34f9da384786',
833
+ 'timestamp': datetime.datetime(2018, 5, 9, 11, 23, 6, tzinfo=<UTC>),
834
+ 'block_num': 22277588, 'trx_num': 35, 'trx_id': 'cf11b2ac8493c71063ec121b2e8517ab1e0e6bea'
835
+ }
836
+
837
+ output when `raw_ops=True` is set:
838
+
839
+ .. code-block:: js
840
+
841
+ {
842
+ 'block_num': 22277588,
843
+ 'op':
844
+ [
845
+ 'transfer',
846
+ {
847
+ 'from': 'johngreenfield', 'to': 'thundercurator',
848
+ 'amount': '0.080 SBD',
849
+ 'memo': 'https://steemit.com/lofi/@johngreenfield/lofi-joji-yeah-right'
850
+ }
851
+ ],
852
+ 'timestamp': datetime.datetime(2018, 5, 9, 11, 23, 6, tzinfo=<UTC>)
853
+ }
854
+
855
+ """
856
+ for block in self.blocks(**kwargs):
857
+ if "transactions" in block:
858
+ trx = block["transactions"]
859
+ else:
860
+ trx = [block]
861
+ block_num = 0
862
+ trx_id = ""
863
+ _id = ""
864
+ timestamp = ""
865
+ for trx_nr in range(len(trx)):
866
+ if "operations" not in trx[trx_nr]:
867
+ continue
868
+ for event in trx[trx_nr]["operations"]:
869
+ if isinstance(event, list):
870
+ op_type, op = event
871
+ trx_id = block["transaction_ids"][trx_nr]
872
+ block_num = block.get("id")
873
+ _id = self.hash_op(event)
874
+ timestamp = block.get("timestamp")
875
+ elif isinstance(event, dict) and "type" in event and "value" in event:
876
+ op_type = event["type"]
877
+ if len(op_type) > 10 and op_type[len(op_type) - 10 :] == "_operation":
878
+ op_type = op_type[:-10]
879
+ op = event["value"]
880
+ trx_id = block["transaction_ids"][trx_nr]
881
+ block_num = block.get("id")
882
+ _id = self.hash_op(event)
883
+ timestamp = block.get("timestamp")
884
+ elif (
885
+ "op" in event
886
+ and isinstance(event["op"], dict)
887
+ and "type" in event["op"]
888
+ and "value" in event["op"]
889
+ ):
890
+ op_type = event["op"]["type"]
891
+ if len(op_type) > 10 and op_type[len(op_type) - 10 :] == "_operation":
892
+ op_type = op_type[:-10]
893
+ op = event["op"]["value"]
894
+ trx_id = event.get("trx_id")
895
+ block_num = event.get("block")
896
+ _id = self.hash_op(event["op"])
897
+ timestamp = event.get("timestamp")
898
+ else:
899
+ op_type, op = event["op"]
900
+ trx_id = event.get("trx_id")
901
+ block_num = event.get("block")
902
+ _id = self.hash_op(event["op"])
903
+ timestamp = event.get("timestamp")
904
+ if not bool(opNames) or op_type in opNames and block_num > 0:
905
+ if raw_ops:
906
+ yield {
907
+ "block_num": block_num,
908
+ "trx_num": trx_nr,
909
+ "op": [op_type, op],
910
+ "timestamp": timestamp,
911
+ }
912
+ else:
913
+ updated_op = {"type": op_type}
914
+ updated_op.update(op.copy())
915
+ updated_op.update(
916
+ {
917
+ "_id": _id,
918
+ "timestamp": timestamp,
919
+ "block_num": block_num,
920
+ "trx_num": trx_nr,
921
+ "trx_id": trx_id,
922
+ }
923
+ )
924
+ yield updated_op
925
+
926
+ def awaitTxConfirmation(self, transaction, limit=10):
927
+ """Returns the transaction as seen by the blockchain after being
928
+ included into a block
929
+
930
+ :param dict transaction: transaction to wait for
931
+ :param int limit: (optional) number of blocks to wait for the transaction (default: 10)
932
+
933
+ .. note:: If you want instant confirmation, you need to instantiate
934
+ class:`nectar.blockchain.Blockchain` with
935
+ ``mode="head"``, otherwise, the call will wait until
936
+ confirmed in an irreversible block.
937
+
938
+ .. note:: This method returns once the blockchain has included a
939
+ transaction with the **same signature**. Even though the
940
+ signature is not usually used to identify a transaction,
941
+ it still cannot be forfeited and is derived from the
942
+ transaction contented and thus identifies a transaction
943
+ uniquely.
944
+ """
945
+ counter = 0
946
+ for block in self.blocks():
947
+ counter += 1
948
+ for tx in block["transactions"]:
949
+ if sorted(tx["signatures"]) == sorted(transaction["signatures"]):
950
+ return tx
951
+ if counter > limit:
952
+ raise Exception("The operation has not been added after %d blocks!" % (limit))
953
+
954
+ @staticmethod
955
+ def hash_op(event):
956
+ """This method generates a hash of blockchain operation."""
957
+ if isinstance(event, dict) and "type" in event and "value" in event:
958
+ op_type = event["type"]
959
+ if len(op_type) > 10 and op_type[len(op_type) - 10 :] == "_operation":
960
+ op_type = op_type[:-10]
961
+ op = event["value"]
962
+ event = [op_type, op]
963
+ data = json.dumps(event, sort_keys=True)
964
+ return hashlib.sha1(py23_bytes(data, "utf-8")).hexdigest()
965
+
966
+ def get_all_accounts(self, start="", stop="", steps=1e3, limit=-1, **kwargs):
967
+ """Yields account names between start and stop.
968
+
969
+ :param str start: Start at this account name
970
+ :param str stop: Stop at this account name
971
+ :param int steps: Obtain ``steps`` ret with a single call from RPC
972
+ """
973
+ cnt = 1
974
+ if not self.blockchain.is_connected():
975
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
976
+ if self.blockchain.rpc.get_use_appbase() and start == "":
977
+ lastname = None
978
+ else:
979
+ lastname = start
980
+ self.blockchain.rpc.set_next_node_on_empty_reply(self.blockchain.rpc.get_use_appbase())
981
+ while True:
982
+ if self.blockchain.rpc.get_use_appbase():
983
+ ret = self.blockchain.rpc.list_accounts(
984
+ {"start": lastname, "limit": steps, "order": "by_name"}, api="database"
985
+ )["accounts"]
986
+ else:
987
+ ret = self.blockchain.rpc.lookup_accounts(lastname, steps)
988
+ for account in ret:
989
+ if isinstance(account, dict):
990
+ account_name = account["name"]
991
+ else:
992
+ account_name = account
993
+ if account_name != lastname:
994
+ yield account_name
995
+ cnt += 1
996
+ if account_name == stop or (limit > 0 and cnt > limit):
997
+ return
998
+ if lastname == account_name:
999
+ return
1000
+ lastname = account_name
1001
+ if len(ret) < steps:
1002
+ return
1003
+
1004
+ def get_account_count(self):
1005
+ """Returns the number of accounts"""
1006
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1007
+ if self.blockchain.rpc.get_use_appbase():
1008
+ ret = self.blockchain.rpc.get_account_count(api="condenser")
1009
+ else:
1010
+ ret = self.blockchain.rpc.get_account_count()
1011
+ return ret
1012
+
1013
+ def get_account_reputations(self, start="", stop="", steps=1e3, limit=-1, **kwargs):
1014
+ """Yields account reputation between start and stop.
1015
+
1016
+ :param str start: Start at this account name
1017
+ :param str stop: Stop at this account name
1018
+ :param int steps: Obtain ``steps`` ret with a single call from RPC
1019
+ """
1020
+ cnt = 1
1021
+ if not self.blockchain.is_connected():
1022
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
1023
+ if self.blockchain.rpc.get_use_appbase() and start == "":
1024
+ lastname = None
1025
+ else:
1026
+ lastname = start
1027
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1028
+ skip_first = False
1029
+ while True:
1030
+ if self.blockchain.rpc.get_use_appbase():
1031
+ ret = self.blockchain.rpc.get_account_reputations(
1032
+ {"account_lower_bound": lastname, "limit": steps}, api="condenser"
1033
+ )["reputations"]
1034
+ else:
1035
+ ret = self.blockchain.rpc.get_account_reputations(lastname, steps, api="condenser")
1036
+ for account in ret:
1037
+ if isinstance(account, dict):
1038
+ account_name = account["name"]
1039
+ else:
1040
+ account_name = account
1041
+ if account_name != lastname or skip_first is False:
1042
+ yield account
1043
+ cnt += 1
1044
+ if account_name == stop or (limit > 0 and cnt > limit):
1045
+ return
1046
+ if lastname == account_name:
1047
+ return
1048
+ lastname = account_name
1049
+ if len(ret) < steps:
1050
+ return
1051
+ # skip the first result for all follow-up requests because
1052
+ # this was already included in the previous iteration.
1053
+ skip_first = True
1054
+
1055
+ def get_similar_account_names(self, name, limit=5):
1056
+ """Returns limit similar accounts with name as list
1057
+
1058
+ :param str name: account name to search similars for
1059
+ :param int limit: limits the number of accounts, which will be returned
1060
+ :returns: Similar account names as list
1061
+ :rtype: list
1062
+
1063
+ .. code-block:: python
1064
+
1065
+ >>> from nectar.blockchain import Blockchain
1066
+ >>> from nectar import Steem
1067
+ >>> stm = Steem("https://api.steemit.com")
1068
+ >>> blockchain = Blockchain(blockchain_instance=stm)
1069
+ >>> ret = blockchain.get_similar_account_names("test", limit=5)
1070
+ >>> len(ret) == 5
1071
+ True
1072
+
1073
+ """
1074
+ if not self.blockchain.is_connected():
1075
+ return None
1076
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1077
+ if self.blockchain.rpc.get_use_appbase():
1078
+ account = self.blockchain.rpc.list_accounts(
1079
+ {"start": name, "limit": limit, "order": "by_name"}, api="database"
1080
+ )
1081
+ if bool(account):
1082
+ return account["accounts"]
1083
+ else:
1084
+ return self.blockchain.rpc.lookup_accounts(name, limit)
1085
+
1086
+ def find_rc_accounts(self, name):
1087
+ """Returns the RC parameters of one or more accounts.
1088
+
1089
+ :param str name: account name to search rc params for (can also be a list of accounts)
1090
+ :returns: RC params
1091
+ :rtype: list
1092
+
1093
+ .. code-block:: python
1094
+
1095
+ >>> from nectar.blockchain import Blockchain
1096
+ >>> from nectar import Steem
1097
+ >>> stm = Steem("https://api.steemit.com")
1098
+ >>> blockchain = Blockchain(blockchain_instance=stm)
1099
+ >>> ret = blockchain.find_rc_accounts(["test"])
1100
+ >>> len(ret) == 1
1101
+ True
1102
+
1103
+ """
1104
+ if not self.blockchain.is_connected():
1105
+ return None
1106
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1107
+ if isinstance(name, list):
1108
+ account = self.blockchain.rpc.find_rc_accounts({"accounts": name}, api="rc")
1109
+ if bool(account):
1110
+ return account["rc_accounts"]
1111
+ else:
1112
+ account = self.blockchain.rpc.find_rc_accounts({"accounts": [name]}, api="rc")
1113
+ if bool(account):
1114
+ return account["rc_accounts"][0]
1115
+
1116
+ def list_change_recovery_account_requests(self, start="", limit=1000, order="by_account"):
1117
+ """List pending `change_recovery_account` requests.
1118
+
1119
+ :param str/list start: Start the listing from this entry.
1120
+ Leave empty to start from the beginning. If `order` is set
1121
+ to `by_account`, `start` has to be an account name. If
1122
+ `order` is set to `by_effective_date`, `start` has to be a
1123
+ list of [effective_on, account_to_recover],
1124
+ e.g. `start=['2018-12-18T01:46:24', 'bott']`.
1125
+ :param int limit: maximum number of results to return (default
1126
+ and maximum: 1000).
1127
+ :param str order: valid values are "by_account" (default) or
1128
+ "by_effective_date".
1129
+ :returns: list of `change_recovery_account` requests.
1130
+ :rtype: list
1131
+
1132
+ .. code-block:: python
1133
+
1134
+ >>> from nectar.blockchain import Blockchain
1135
+ >>> from nectar import Steem
1136
+ >>> stm = Steem("https://api.steemit.com")
1137
+ >>> blockchain = Blockchain(blockchain_instance=stm)
1138
+ >>> ret = blockchain.list_change_recovery_account_requests(limit=1)
1139
+
1140
+ """
1141
+ if not self.blockchain.is_connected():
1142
+ return None
1143
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1144
+ requests = self.blockchain.rpc.list_change_recovery_account_requests(
1145
+ {"start": start, "limit": limit, "order": order}, api="database"
1146
+ )
1147
+ if bool(requests):
1148
+ return requests["requests"]
1149
+
1150
+ def find_change_recovery_account_requests(self, accounts):
1151
+ """Find pending `change_recovery_account` requests for one or more
1152
+ specific accounts.
1153
+
1154
+ :param str/list accounts: account name or list of account
1155
+ names to find `change_recovery_account` requests for.
1156
+ :returns: list of `change_recovery_account` requests for the
1157
+ given account(s).
1158
+ :rtype: list
1159
+
1160
+ .. code-block:: python
1161
+
1162
+ >>> from nectar.blockchain import Blockchain
1163
+ >>> from nectar import Steem
1164
+ >>> stm = Steem("https://api.steemit.com")
1165
+ >>> blockchain = Blockchain(blockchain_instance=stm)
1166
+ >>> ret = blockchain.find_change_recovery_account_requests('bott')
1167
+
1168
+ """
1169
+ if not self.blockchain.is_connected():
1170
+ return None
1171
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1172
+ if isinstance(accounts, str):
1173
+ accounts = [accounts]
1174
+ requests = self.blockchain.rpc.find_change_recovery_account_requests(
1175
+ {"accounts": accounts}, api="database"
1176
+ )
1177
+ if bool(requests):
1178
+ return requests["requests"]