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
@@ -0,0 +1,622 @@
1
+ # -*- coding: utf-8 -*-
2
+ import logging
3
+ import struct
4
+ from binascii import unhexlify
5
+
6
+ # import time (not currently used)
7
+ from datetime import timedelta
8
+
9
+ from nectar.instance import shared_blockchain_instance
10
+ from nectarbase import operations # removed deprecated transactions module
11
+ from nectarbase.ledgertransactions import Ledger_Transaction
12
+ from nectarbase.objects import Operation
13
+ from nectarbase.signedtransactions import Signed_Transaction
14
+ from nectargraphenebase.account import PrivateKey, PublicKey
15
+ from nectarstorage.exceptions import WalletLocked
16
+
17
+ from .account import Account
18
+ from .exceptions import (
19
+ InsufficientAuthorityError,
20
+ InvalidWifError,
21
+ MissingKeyError,
22
+ OfflineHasNoRPCException,
23
+ )
24
+ from .utils import formatTimeFromNow, formatTimeString
25
+
26
+ log = logging.getLogger(__name__)
27
+
28
+
29
+ class TransactionBuilder(dict):
30
+ """This class simplifies the creation of transactions by adding
31
+ operations and signers.
32
+ To build your own transactions and sign them
33
+
34
+ :param dict tx: transaction (Optional). If not set, the new transaction is created.
35
+ :param int expiration: Delay in seconds until transactions are supposed
36
+ to expire *(optional)* (default is 30)
37
+ :param Hive/Steem blockchain_instance: If not set, shared_blockchain_instance() is used
38
+
39
+ .. testcode::
40
+
41
+ from nectar.transactionbuilder import TransactionBuilder
42
+ from nectarbase.operations import Transfer
43
+ from nectar import Hive
44
+ wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"
45
+ hive = Hive(nobroadcast=True, keys={'active': wif})
46
+ tx = TransactionBuilder(blockchain_instance=hive)
47
+ transfer = {"from": "test", "to": "test1", "amount": "1 HIVE", "memo": ""}
48
+ tx.appendOps(Transfer(transfer))
49
+ tx.appendSigner("test", "active") # or tx.appendWif(wif)
50
+ signed_tx = tx.sign()
51
+ broadcast_tx = tx.broadcast()
52
+
53
+ """
54
+
55
+ def __init__(self, tx={}, blockchain_instance=None, **kwargs):
56
+ if blockchain_instance is None:
57
+ if kwargs.get("steem_instance"):
58
+ blockchain_instance = kwargs["steem_instance"]
59
+ elif kwargs.get("hive_instance"):
60
+ blockchain_instance = kwargs["hive_instance"]
61
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
62
+ self.clear()
63
+ if tx and isinstance(tx, dict):
64
+ super(TransactionBuilder, self).__init__(tx)
65
+ # Load operations
66
+ self.ops = tx["operations"]
67
+ self._require_reconstruction = False
68
+ else:
69
+ self._require_reconstruction = True
70
+ self._use_ledger = self.blockchain.use_ledger
71
+ self.path = self.blockchain.path
72
+ self._use_condenser_api = bool(self.blockchain.config["use_condenser"])
73
+ self.set_expiration(kwargs.get("expiration", self.blockchain.expiration))
74
+
75
+ def set_expiration(self, p):
76
+ """Set expiration date"""
77
+ self.expiration = p
78
+
79
+ def is_empty(self):
80
+ """Check if ops is empty"""
81
+ return not (len(self.ops) > 0)
82
+
83
+ def list_operations(self):
84
+ """List all ops"""
85
+ if self.blockchain.is_connected() and self.blockchain.rpc.get_use_appbase():
86
+ # appbase disabled by now
87
+ appbase = not self._use_condenser_api
88
+ else:
89
+ appbase = False
90
+ return [Operation(o, appbase=appbase, prefix=self.blockchain.prefix) for o in self.ops]
91
+
92
+ def _is_signed(self):
93
+ """Check if signatures exists"""
94
+ return "signatures" in self and bool(self["signatures"])
95
+
96
+ def _is_constructed(self):
97
+ """Check if tx is already constructed"""
98
+ return "expiration" in self and bool(self["expiration"])
99
+
100
+ def _is_require_reconstruction(self):
101
+ return self._require_reconstruction
102
+
103
+ def _set_require_reconstruction(self):
104
+ self._require_reconstruction = True
105
+
106
+ def _unset_require_reconstruction(self):
107
+ self._require_reconstruction = False
108
+
109
+ def _get_auth_field(self, permission):
110
+ return permission
111
+
112
+ def __repr__(self):
113
+ return "<Transaction num_ops={}, ops={}>".format(
114
+ len(self.ops), [op.__class__.__name__ for op in self.ops]
115
+ )
116
+
117
+ def __str__(self):
118
+ return str(self.json())
119
+
120
+ def __getitem__(self, key):
121
+ if key not in self:
122
+ self.constructTx()
123
+ return dict(self).__getitem__(key)
124
+
125
+ def get_parent(self):
126
+ """TransactionBuilders don't have parents, they are their own parent"""
127
+ return self
128
+
129
+ def json(self, with_prefix=False):
130
+ """Show the transaction as plain json"""
131
+ if not self._is_constructed() or self._is_require_reconstruction():
132
+ self.constructTx()
133
+ json_dict = dict(self)
134
+ if with_prefix:
135
+ json_dict["prefix"] = self.blockchain.prefix
136
+ return json_dict
137
+
138
+ def appendOps(self, ops, append_to=None):
139
+ """Append op(s) to the transaction builder
140
+
141
+ :param list ops: One or a list of operations
142
+ """
143
+ if isinstance(ops, list):
144
+ self.ops.extend(ops)
145
+ else:
146
+ self.ops.append(ops)
147
+ self._set_require_reconstruction()
148
+
149
+ def _fetchkeys(self, account, perm, level=0, required_treshold=1):
150
+ # Do not travel recursion more than 2 levels
151
+ if level > 2:
152
+ return []
153
+
154
+ r = []
155
+ wif = None
156
+ # Let's go through all *keys* of the account
157
+ for authority in account[perm]["key_auths"]:
158
+ try:
159
+ # Try obtain the private key from wallet
160
+ wif = self.blockchain.wallet.getPrivateKeyForPublicKey(authority[0])
161
+ except ValueError:
162
+ pass
163
+ except MissingKeyError:
164
+ pass
165
+
166
+ if wif:
167
+ r.append([wif, authority[1]])
168
+ # If we found a key for account, we add it
169
+ # to signing_accounts to be sure we do not resign
170
+ # another operation with the same account/wif
171
+ self.signing_accounts.append(account)
172
+
173
+ # Test if we reached threshold already
174
+ if sum([x[1] for x in r]) >= required_treshold:
175
+ break
176
+
177
+ # Let's see if we still need to go through accounts
178
+ if sum([x[1] for x in r]) < required_treshold:
179
+ # go one level deeper
180
+ for authority in account[perm]["account_auths"]:
181
+ # Let's see if we can find keys for an account in
182
+ # account_auths
183
+ # This is recursive with a limit at level 2 (see above)
184
+ auth_account = Account(authority[0], blockchain_instance=self.blockchain)
185
+ required_treshold = auth_account[perm]["weight_threshold"]
186
+ keys = self._fetchkeys(auth_account, perm, level + 1, required_treshold)
187
+
188
+ for key in keys:
189
+ r.append(key)
190
+
191
+ # Test if we reached threshold already and break
192
+ if sum([x[1] for x in r]) >= required_treshold:
193
+ break
194
+
195
+ return r
196
+
197
+ def appendSigner(self, account, permission):
198
+ """Try to obtain the wif key from the wallet by telling which account
199
+ and permission is supposed to sign the transaction
200
+ It is possible to add more than one signer.
201
+
202
+ :param str account: account to sign transaction with
203
+ :param str permission: type of permission, e.g. "active", "owner" etc
204
+ """
205
+ if not self.blockchain.is_connected():
206
+ return
207
+ if permission not in ["active", "owner", "posting"]:
208
+ raise AssertionError("Invalid permission")
209
+ account = Account(account, blockchain_instance=self.blockchain)
210
+ auth_field = self._get_auth_field(permission)
211
+ if auth_field not in account:
212
+ account = Account(account, blockchain_instance=self.blockchain, lazy=False, full=True)
213
+ account.clear_cache()
214
+ account.refresh()
215
+ if auth_field not in account:
216
+ account = Account(account, blockchain_instance=self.blockchain)
217
+ if auth_field not in account:
218
+ raise AssertionError("Could not access permission")
219
+
220
+ if self._use_ledger:
221
+ if not self._is_constructed() or self._is_require_reconstruction():
222
+ self.constructTx()
223
+
224
+ key_found = False
225
+ if self.path is not None:
226
+ current_pubkey = self.ledgertx.get_pubkey(self.path)
227
+ for authority in account[auth_field]["key_auths"]:
228
+ if str(current_pubkey) == authority[0]:
229
+ key_found = True
230
+ if permission == "posting" and not key_found:
231
+ for authority in account["active"]["key_auths"]:
232
+ if str(current_pubkey) == authority[0]:
233
+ key_found = True
234
+ if not key_found:
235
+ for authority in account["owner"]["key_auths"]:
236
+ if str(current_pubkey) == authority[0]:
237
+ key_found = True
238
+ if not key_found:
239
+ raise AssertionError(
240
+ "Could not find pubkey from %s in path: %s!" % (account["name"], self.path)
241
+ )
242
+ return
243
+
244
+ if self.blockchain.wallet.locked():
245
+ raise WalletLocked()
246
+ if self.blockchain.use_sc2 and self.blockchain.steemconnect is not None:
247
+ self.blockchain.steemconnect.set_username(account["name"], permission)
248
+ return
249
+
250
+ if account["name"] not in self.signing_accounts:
251
+ # is the account an instance of public key?
252
+ if isinstance(account, PublicKey):
253
+ self.wifs.add(self.blockchain.wallet.getPrivateKeyForPublicKey(str(account)))
254
+ else:
255
+ if auth_field not in account:
256
+ raise AssertionError("Could not access permission")
257
+ required_treshold = account[auth_field]["weight_threshold"]
258
+ keys = self._fetchkeys(account, permission, required_treshold=required_treshold)
259
+ # If keys are empty, try again with active key
260
+ if not keys and permission == "posting":
261
+ _keys = self._fetchkeys(account, "active", required_treshold=required_treshold)
262
+ keys.extend(_keys)
263
+ # If keys are empty, try again with owner key
264
+ if not keys and permission != "owner":
265
+ _keys = self._fetchkeys(account, "owner", required_treshold=required_treshold)
266
+ keys.extend(_keys)
267
+ for x in keys:
268
+ self.appendWif(x[0])
269
+
270
+ self.signing_accounts.append(account["name"])
271
+
272
+ def appendWif(self, wif):
273
+ """Add a wif that should be used for signing of the transaction.
274
+
275
+ :param string wif: One wif key to use for signing
276
+ a transaction.
277
+ """
278
+ if wif:
279
+ try:
280
+ PrivateKey(wif, prefix=self.blockchain.prefix)
281
+ self.wifs.add(wif)
282
+ except:
283
+ raise InvalidWifError
284
+
285
+ def clearWifs(self):
286
+ """Clear all stored wifs"""
287
+ self.wifs = set()
288
+
289
+ def setPath(self, path):
290
+ self.path = path
291
+
292
+ def searchPath(self, account, perm):
293
+ if not self.blockchain.use_ledger:
294
+ return
295
+ if not self._is_constructed() or self._is_require_reconstruction():
296
+ self.constructTx()
297
+ key_found = False
298
+ path = None
299
+ current_account_index = 0
300
+ current_key_index = 0
301
+ while not key_found and current_account_index < 5:
302
+ path = self.ledgertx.build_path(perm, current_account_index, current_key_index)
303
+ current_pubkey = self.ledgertx.get_pubkey(path)
304
+ key_found = False
305
+ for authority in account[perm]["key_auths"]:
306
+ if str(current_pubkey) == authority[1]:
307
+ key_found = True
308
+ if not key_found and current_key_index < 5:
309
+ current_key_index += 1
310
+ elif not key_found and current_key_index >= 5:
311
+ current_key_index = 0
312
+ current_account_index += 1
313
+ if not key_found:
314
+ return None
315
+ else:
316
+ return path
317
+
318
+ def constructTx(self, ref_block_num=None, ref_block_prefix=None):
319
+ """Construct the actual transaction and store it in the class's dict
320
+ store
321
+
322
+ """
323
+ ops = list()
324
+ if self.blockchain.is_connected() and self.blockchain.rpc.get_use_appbase():
325
+ # appbase disabled by now
326
+ # broadcasting does not work at the moment
327
+ appbase = not self._use_condenser_api
328
+ else:
329
+ appbase = False
330
+ for op in self.ops:
331
+ # otherwise, we simply wrap ops into Operations
332
+ ops.extend([Operation(op, appbase=appbase, prefix=self.blockchain.prefix)])
333
+
334
+ # calculation expiration time from last block time not system time
335
+ # it fixes transaction expiration error when pushing transactions
336
+ # when blocks are moved forward with debug_produce_block*
337
+ if self.blockchain.is_connected():
338
+ now = formatTimeString(
339
+ self.blockchain.get_dynamic_global_properties(use_stored_data=False).get("time")
340
+ ).replace(tzinfo=None)
341
+ expiration = now + timedelta(seconds=int(self.expiration or self.blockchain.expiration))
342
+ expiration = expiration.replace(microsecond=0).isoformat()
343
+ else:
344
+ expiration = formatTimeFromNow(self.expiration or self.blockchain.expiration)
345
+
346
+ # We now wrap everything into an actual transaction
347
+ if ref_block_num is None or ref_block_prefix is None:
348
+ ref_block_num, ref_block_prefix = self.get_block_params()
349
+ if self._use_ledger:
350
+ self.ledgertx = Ledger_Transaction(
351
+ ref_block_prefix=ref_block_prefix,
352
+ expiration=expiration,
353
+ operations=ops,
354
+ ref_block_num=ref_block_num,
355
+ custom_chains=self.blockchain.custom_chains,
356
+ prefix=self.blockchain.prefix,
357
+ )
358
+
359
+ self.tx = Signed_Transaction(
360
+ ref_block_prefix=ref_block_prefix,
361
+ expiration=expiration,
362
+ operations=ops,
363
+ ref_block_num=ref_block_num,
364
+ custom_chains=self.blockchain.custom_chains,
365
+ prefix=self.blockchain.prefix,
366
+ )
367
+
368
+ super(TransactionBuilder, self).update(self.tx.json())
369
+ self._unset_require_reconstruction()
370
+
371
+ def get_block_params(self, use_head_block=False):
372
+ """Auxiliary method to obtain ``ref_block_num`` and
373
+ ``ref_block_prefix``. Requires a connection to a
374
+ node!
375
+ """
376
+
377
+ dynBCParams = self.blockchain.get_dynamic_global_properties(use_stored_data=False)
378
+ # fix for corner case where last_irreversible_block_num == head_block_number
379
+ # then int(dynBCParams["last_irreversible_block_num"]) + 1 does not exists
380
+ # and BlockHeader throws error
381
+ if use_head_block or int(dynBCParams["last_irreversible_block_num"]) == int(
382
+ dynBCParams["head_block_number"]
383
+ ):
384
+ ref_block_num = dynBCParams["head_block_number"] & 0xFFFF
385
+ ref_block_prefix = struct.unpack_from("<I", unhexlify(dynBCParams["head_block_id"]), 4)[
386
+ 0
387
+ ]
388
+ else:
389
+ # need to get subsequent block because block head doesn't return 'id' - stupid
390
+ from .block import BlockHeader
391
+
392
+ block = BlockHeader(
393
+ int(dynBCParams["last_irreversible_block_num"]) + 1,
394
+ blockchain_instance=self.blockchain,
395
+ )
396
+ ref_block_num = dynBCParams["last_irreversible_block_num"] & 0xFFFF
397
+ ref_block_prefix = struct.unpack_from("<I", unhexlify(block["previous"]), 4)[0]
398
+ return ref_block_num, ref_block_prefix
399
+
400
+ def sign(self, reconstruct_tx=True):
401
+ """Sign a provided transaction with the provided key(s)
402
+ One or many wif keys to use for signing a transaction.
403
+ The wif keys can be provided by "appendWif" or the
404
+ signer can be defined "appendSigner". The wif keys
405
+ from all signer that are defined by "appendSigner
406
+ will be loaded from the wallet.
407
+
408
+ :param bool reconstruct_tx: when set to False and tx
409
+ is already contructed, it will not reconstructed
410
+ and already added signatures remain
411
+
412
+ """
413
+ if not self._is_constructed() or (self._is_constructed() and reconstruct_tx):
414
+ self.constructTx()
415
+ if "operations" not in self or not self["operations"]:
416
+ return
417
+ if self.blockchain.use_sc2:
418
+ return
419
+ # We need to set the default prefix, otherwise pubkeys are
420
+ # presented wrongly!
421
+ if self.blockchain.rpc is not None:
422
+ operations.default_prefix = self.blockchain.chain_params["prefix"]
423
+ elif "blockchain" in self:
424
+ operations.default_prefix = self["blockchain"]["prefix"]
425
+
426
+ if self._use_ledger:
427
+ # try:
428
+ # ledgertx = Ledger_Transaction(**self.json(with_prefix=True))
429
+ # ledgertx.add_custom_chains(self.blockchain.custom_chains)
430
+ # except:
431
+ # raise ValueError("Invalid TransactionBuilder Format")
432
+ # ledgertx.sign(self.path, chain=self.blockchain.chain_params)
433
+ self.ledgertx.sign(self.path, chain=self.blockchain.chain_params)
434
+ self["signatures"].extend(self.ledgertx.json().get("signatures"))
435
+ return self.ledgertx
436
+ else:
437
+ if not any(self.wifs):
438
+ raise MissingKeyError
439
+
440
+ self.tx.sign(self.wifs, chain=self.blockchain.chain_params)
441
+ self["signatures"].extend(self.tx.json().get("signatures"))
442
+ return self.tx
443
+
444
+ def verify_authority(self):
445
+ """Verify the authority of the signed transaction"""
446
+ try:
447
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
448
+ if self.blockchain.rpc.get_use_appbase():
449
+ args = {"trx": self.json()}
450
+ else:
451
+ args = self.json()
452
+ ret = self.blockchain.rpc.verify_authority(args, api="database")
453
+ if not ret:
454
+ raise InsufficientAuthorityError
455
+ elif isinstance(ret, dict) and "valid" in ret and not ret["valid"]:
456
+ raise InsufficientAuthorityError
457
+ except Exception as e:
458
+ raise e
459
+
460
+ def get_potential_signatures(self):
461
+ """Returns public key from signature"""
462
+ if not self.blockchain.is_connected():
463
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
464
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
465
+ if self.blockchain.rpc.get_use_appbase():
466
+ args = {"trx": self.json()}
467
+ else:
468
+ args = self.json()
469
+ ret = self.blockchain.rpc.get_potential_signatures(args, api="database")
470
+ if "keys" in ret:
471
+ ret = ret["keys"]
472
+ return ret
473
+
474
+ def get_transaction_hex(self):
475
+ """Returns a hex value of the transaction"""
476
+ if not self.blockchain.is_connected():
477
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
478
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
479
+ if self.blockchain.rpc.get_use_appbase():
480
+ args = {"trx": self.json()}
481
+ else:
482
+ args = self.json()
483
+ ret = self.blockchain.rpc.get_transaction_hex(args, api="database")
484
+ if "hex" in ret:
485
+ ret = ret["hex"]
486
+ return ret
487
+
488
+ def get_required_signatures(self, available_keys=list()):
489
+ """Returns public key from signature"""
490
+ if not self.blockchain.is_connected():
491
+ raise OfflineHasNoRPCException("No RPC available in offline mode!")
492
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
493
+ if self.blockchain.rpc.get_use_appbase():
494
+ args = {"trx": self.json(), "available_keys": available_keys}
495
+ ret = self.blockchain.rpc.get_required_signatures(args, api="database")
496
+ else:
497
+ ret = self.blockchain.rpc.get_required_signatures(
498
+ self.json(), available_keys, api="database"
499
+ )
500
+
501
+ return ret
502
+
503
+ def broadcast(self, max_block_age=-1, trx_id=True):
504
+ """Broadcast a transaction to the steem network
505
+ Returns the signed transaction and clears itself
506
+ after broadast
507
+
508
+ Clears itself when broadcast was not successfully.
509
+
510
+ :param int max_block_age: parameter only used
511
+ for appbase ready nodes
512
+ :param bool trx_id: When True, trx_id is return
513
+
514
+ """
515
+ # Cannot broadcast an empty transaction
516
+ if not self._is_signed():
517
+ sign_ret = self.sign()
518
+ else:
519
+ sign_ret = None
520
+
521
+ if "operations" not in self or not self["operations"]:
522
+ return
523
+ ret = self.json()
524
+
525
+ # Returns an internal Error at the moment
526
+ if not self._use_condenser_api:
527
+ args = {"trx": self.json(), "max_block_age": max_block_age}
528
+ broadcast_api = "network_broadcast"
529
+ else:
530
+ args = self.json()
531
+ broadcast_api = "condenser"
532
+
533
+ if self.blockchain.nobroadcast:
534
+ log.info("Not broadcasting anything!")
535
+ self.clear()
536
+ return ret
537
+ # Broadcast
538
+ try:
539
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
540
+ if self.blockchain.use_sc2:
541
+ ret = self.blockchain.steemconnect.broadcast(self["operations"])
542
+ elif self.blockchain.blocking and self._use_condenser_api:
543
+ ret = self.blockchain.rpc.broadcast_transaction_synchronous(args, api=broadcast_api)
544
+ if "trx" in ret:
545
+ ret.update(**ret.get("trx"))
546
+ else:
547
+ self.blockchain.rpc.broadcast_transaction(args, api=broadcast_api)
548
+ except Exception as e:
549
+ # log.error("Could Not broadcasting anything!")
550
+ self.clear()
551
+ raise e
552
+ if sign_ret is not None and "trx_id" not in ret and trx_id:
553
+ ret["trx_id"] = sign_ret.id
554
+ self.clear()
555
+ return ret
556
+
557
+ def clear(self):
558
+ """Clear the transaction builder and start from scratch"""
559
+ self.ops = []
560
+ self.wifs = set()
561
+ self.signing_accounts = []
562
+ self.ref_block_num = None
563
+ self.ref_block_prefix = None
564
+ # This makes sure that _is_constructed will return False afterwards
565
+ self["expiration"] = None
566
+ super(TransactionBuilder, self).__init__({})
567
+
568
+ def addSigningInformation(self, account, permission, reconstruct_tx=False):
569
+ """This is a private method that adds side information to a
570
+ unsigned/partial transaction in order to simplify later
571
+ signing (e.g. for multisig or coldstorage)
572
+
573
+ Not needed when "appendWif" was already or is going to be used
574
+
575
+ FIXME: Does not work with owner keys!
576
+
577
+ :param bool reconstruct_tx: when set to False and tx
578
+ is already contructed, it will not reconstructed
579
+ and already added signatures remain
580
+
581
+ """
582
+ if not self._is_constructed() or (self._is_constructed() and reconstruct_tx):
583
+ self.constructTx()
584
+ self["blockchain"] = self.blockchain.chain_params
585
+
586
+ if isinstance(account, PublicKey):
587
+ self["missing_signatures"] = [str(account)]
588
+ else:
589
+ accountObj = Account(account, blockchain_instance=self.blockchain)
590
+ authority = accountObj[permission]
591
+ # We add a required_authorities to be able to identify
592
+ # how to sign later. This is an array, because we
593
+ # may later want to allow multiple operations per tx
594
+ self.update({"required_authorities": {accountObj["name"]: authority}})
595
+ for account_auth in authority["account_auths"]:
596
+ account_auth_account = Account(account_auth[0], blockchain_instance=self.blockchain)
597
+ self["required_authorities"].update(
598
+ {account_auth[0]: account_auth_account.get(permission)}
599
+ )
600
+
601
+ # Try to resolve required signatures for offline signing
602
+ self["missing_signatures"] = [x[0] for x in authority["key_auths"]]
603
+ # Add one recursion of keys from account_auths:
604
+ for account_auth in authority["account_auths"]:
605
+ account_auth_account = Account(account_auth[0], blockchain_instance=self.blockchain)
606
+ self["missing_signatures"].extend(
607
+ [x[0] for x in account_auth_account[permission]["key_auths"]]
608
+ )
609
+
610
+ def appendMissingSignatures(self):
611
+ """Store which accounts/keys are supposed to sign the transaction
612
+
613
+ This method is used for an offline-signer!
614
+ """
615
+ missing_signatures = self.get("missing_signatures", [])
616
+ for pub in missing_signatures:
617
+ try:
618
+ wif = self.blockchain.wallet.getPrivateKeyForPublicKey(pub)
619
+ if wif:
620
+ self.appendWif(wif)
621
+ except MissingKeyError:
622
+ wif = None