def compareConstructedTX(self): self.op = operations.Balance_claim( **{ "fee": {"amount": 0, "asset_id": "1.3.0"}, "deposit_to_account": "1.2.0", "balance_to_claim": "1.15.0", "balance_owner_key": prefix + "1111111111111111111111111111111114T1Anm", "total_claimed": {"amount": 0, "asset_id": "1.3.0"}, "prefix": prefix, } ) ops = [Operation(self.op)] tx = Signed_Transaction( ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops, ) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], prefix) txWire = hexlify(bytes(tx)).decode("ascii") print("=" * 80) pprint(tx.json()) print("=" * 80) from grapheneapi.grapheneapi import GrapheneAPI rpc = GrapheneAPI("localhost", 8092) self.cm = rpc.serialize_transaction(tx.json()) print("soll: %s" % self.cm) print("ist: %s" % txWire) print(txWire[:-130] == self.cm[:-130]) self.assertEqual(self.cm[:-130], txWire[:-130])
def doit(self, printWire=False): ops = [Operation(self.op)] tx = Signed_Transaction( ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops, ) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], prefix) txWire = hexlify(bytes(tx)).decode("ascii") if printWire: print() print(txWire) print() self.assertEqual(self.cm[:-130], txWire[:-130]) if TEST_AGAINST_CLI_WALLET: from grapheneapi.grapheneapi import GrapheneAPI rpc = GrapheneAPI("localhost", 8092) self.cm = rpc.serialize_transaction(tx.json()) # print("soll: %s" % self.cm[:-130]) # print("ist: %s" % txWire[:-130]) # print(txWire[:-130] == self.cm[:-130]) self.assertEqual(self.cm[:-130], txWire[:-130]) # Test against Bitshares backened live = peerplays.rpc.get_transaction_hex(tx.json()) # Compare expected result with online result self.assertEqual(live[:-130], txWire[:-130]) # Compare expected result with online backend self.assertEqual(live[:-130], self.cm[:-130])
def constructTx(self): """ Construct the actual transaction and store it in the class's dict store """ if self.peerplays.proposer: ops = [operations.Op_wrapper(op=o) for o in list(self.ops)] proposer = Account( self.peerplays.proposer, peerplays_instance=self.peerplays ) ops = operations.Proposal_create(**{ "fee": {"amount": 0, "asset_id": "1.3.0"}, "fee_paying_account": proposer["id"], "expiration_time": transactions.formatTimeFromNow( self.peerplays.proposal_expiration), "proposed_ops": [o.json() for o in ops], "extensions": [] }) ops = [Operation(ops)] else: ops = [Operation(o) for o in list(self.ops)] ops = transactions.addRequiredFees(self.peerplays.rpc, ops) expiration = transactions.formatTimeFromNow(self.peerplays.expiration) ref_block_num, ref_block_prefix = transactions.getBlockParams(self.peerplays.rpc) tx = Signed_Transaction( ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops ) super(TransactionBuilder, self).__init__(tx.json())
def compareConstructedTX(self): op = operations.Proposal_update( **{ 'fee_paying_account': "1.2.1", 'proposal': "1.10.90", 'active_approvals_to_add': ["1.2.5"], "fee": { "amount": 0, "asset_id": "1.3.0" }, }) """ op = operations.Betting_market_resolve(**{ "fee": {"amount": 0, "asset_id": "1.3.0"}, "betting_market_id": "1.21.1", "resolution": "win", "prefix": prefix, }) op = operations.Bet_cancel_operation(**{ "fee": {"amount": 0, "asset_id": "1.3.0"}, "bettor_id": "1.2.1241", "bet_to_cancel": "1.22.10", "prefix": prefix, }) """ ops = [Operation(op)] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], prefix) txWire = hexlify(bytes(tx)).decode("ascii") print("=" * 80) pprint(tx.json()) print("=" * 80) from grapheneapi.grapheneapi import GrapheneAPI rpc = GrapheneAPI("localhost", 8092) compare = rpc.serialize_transaction(tx.json()) print(compare[:-130]) print(txWire[:-130]) print(txWire[:-130] == compare[:-130]) self.assertEqual(compare[:-130], txWire[:-130])
def compareConstructedTX(self): self.op = operations.Bet_place( **{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "bettor_id": "1.2.1241", "betting_market_id": "1.21.1", "amount_to_bet": { "amount": 1000, "asset_id": "1.3.1" }, "backer_multiplier": 2 * GRAPHENE_BETTING_ODDS_PRECISION, "back_or_lay": "lay", "prefix": prefix, }) ops = [Operation(self.op)] """ from peerplays import PeerPlays ppy = PeerPlays() ops = transactions.addRequiredFees(ppy.rpc, ops) """ tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], prefix) txWire = hexlify(bytes(tx)).decode("ascii") print("=" * 80) pprint(tx.json()) print("=" * 80) from grapheneapi.grapheneapi import GrapheneAPI rpc = GrapheneAPI("localhost", 8092) self.cm = rpc.serialize_transaction(tx.json()) print("soll: %s" % self.cm[:-130]) print("ist: %s" % txWire[:-130]) print(txWire[:-130] == self.cm[:-130]) self.assertEqual(self.cm[:-130], txWire[:-130])
def sign(self): """ Sign a provided transaction with the provided key(s) :param dict tx: The transaction to be signed and returned :param string wifs: One or many wif keys to use for signing a transaction. If not present, the keys will be loaded from the wallet as defined in "missing_signatures" key of the transactions. """ self.constructTx() if "operations" not in self or not self["operations"]: return # Legacy compatibility! # If we are doing a proposal, obtain the account from the proposer_id if self.blockchain.proposer: proposer = Account(self.blockchain.proposer, blockchain_instance=self.blockchain) self.wifs = set() self.signing_accounts = list() self.appendSigner(proposer["id"], "active") # We need to set the default prefix, otherwise pubkeys are # presented wrongly! if self.blockchain.rpc: operations.default_prefix = ( self.blockchain.rpc.chain_params["prefix"]) elif "blockchain" in self: operations.default_prefix = self["blockchain"]["prefix"] try: signedtx = Signed_Transaction(**self.json()) except: raise ValueError("Invalid TransactionBuilder Format") if not any(self.wifs): raise MissingKeyError signedtx.sign(self.wifs, chain=self.blockchain.rpc.chain_params) self["signatures"].extend(signedtx.json().get("signatures")) return signedtx
def doit(self, printWire=False): ops = [Operation(self.op)] tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], prefix) txWire = hexlify(bytes(tx)).decode("ascii") if printWire: print() print(txWire) print() self.assertEqual(self.cm[:-130], txWire[:-130]) if TEST_AGAINST_CLI_WALLET: from grapheneapi.grapheneapi import GrapheneAPI rpc = GrapheneAPI("localhost", 8092) self.cm = rpc.serialize_transaction(tx.json()) # print("soll: %s" % self.cm[:-130]) # print("ist: %s" % txWire[:-130]) # print(txWire[:-130] == self.cm[:-130]) self.assertEqual(self.cm[:-130], txWire[:-130])
class TransactionBuilder(dict): """ This class simplifies the creation of transactions by adding operations and signers. """ def __init__(self, tx={}, peerplays_instance=None): self.peerplays = peerplays_instance or shared_peerplays_instance() self.clear() if not isinstance(tx, dict): raise ValueError("Invalid TransactionBuilder Format") super(TransactionBuilder, self).__init__(tx) def appendOps(self, ops): """ Append op(s) to the transaction builder :param list ops: One or a list of operations """ if isinstance(ops, list): self.ops.extend(ops) else: self.ops.append(ops) def appendSigner(self, account, permission): """ Try to obtain the wif key from the wallet by telling which account and permission is supposed to sign the transaction """ assert permission in ["active", "owner"], "Invalid permission" account = Account(account, peerplays_instance=self.peerplays) required_treshold = account[permission]["weight_threshold"] def fetchkeys(account, level=0): if level > 2: return [] r = [] for authority in account[permission]["key_auths"]: wif = self.peerplays.wallet.getPrivateKeyForPublicKey( authority[0]) if wif: r.append([wif, authority[1]]) if sum([x[1] for x in r]) < required_treshold: # go one level deeper for authority in account[permission]["account_auths"]: auth_account = Account(authority[0], peerplays_instance=self.peerplays) r.extend(fetchkeys(auth_account, level + 1)) return r keys = fetchkeys(account) self.wifs.extend([x[0] for x in keys]) def appendWif(self, wif): """ Add a wif that should be used for signing of the transaction. """ if wif: try: PrivateKey(wif) self.wifs.append(wif) except: raise InvalidWifError def constructTx(self): """ Construct the actual transaction and store it in the class's dict store """ if self.peerplays.proposer: ops = [operations.Op_wrapper(op=o) for o in list(self.ops)] proposer = Account(self.peerplays.proposer, peerplays_instance=self.peerplays) ops = operations.Proposal_create( **{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "fee_paying_account": proposer["id"], "expiration_time": transactions.formatTimeFromNow( self.peerplays.proposal_expiration), "proposed_ops": [o.json() for o in ops], "extensions": [] }) ops = [Operation(ops)] else: ops = [Operation(o) for o in list(self.ops)] ops = transactions.addRequiredFees(self.peerplays.rpc, ops) expiration = transactions.formatTimeFromNow(self.peerplays.expiration) ref_block_num, ref_block_prefix = transactions.getBlockParams( self.peerplays.rpc) self.tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) super(TransactionBuilder, self).__init__(self.tx.json()) def sign(self): """ Sign a provided transaction witht he provided key(s) :param dict tx: The transaction to be signed and returned :param string wifs: One or many wif keys to use for signing a transaction. If not present, the keys will be loaded from the wallet as defined in "missing_signatures" key of the transactions. """ self.constructTx() # If we are doing a proposal, obtain the account from the proposer_id if self.peerplays.proposer: proposer = Account(self.peerplays.proposer, peerplays_instance=self.peerplays) self.wifs = [] self.appendSigner(proposer["id"], "active") # We need to set the default prefix, otherwise pubkeys are # presented wrongly! if self.peerplays.rpc: operations.default_prefix = self.peerplays.rpc.chain_params[ "prefix"] elif "blockchain" in self: operations.default_prefix = self["blockchain"]["prefix"] try: signedtx = Signed_Transaction(**self.json()) except: raise ValueError("Invalid TransactionBuilder Format") if not any(self.wifs): raise MissingKeyError signedtx.sign(self.wifs, chain=self.peerplays.rpc.chain_params) self["signatures"].extend(signedtx.json().get("signatures")) def verify_authority(self): """ Verify the authority of the signed transaction """ try: if not self.peerplays.rpc.verify_authority(self.json()): raise InsufficientAuthorityError except Exception as e: raise e def broadcast(self): """ Broadcast a transaction to the PeerPlays network :param tx tx: Signed transaction to broadcast """ if not "signatures" in self or not self["signatures"]: self.sign() if self.peerplays.nobroadcast: log.warning("Not broadcasting anything!") return self # Broadcast try: self.peerplays.rpc.broadcast_transaction(self.json(), api="network_broadcast") except Exception as e: raise e self.clear() return self def clear(self): """ Clear the transaction builder and start from scratch """ self.ops = [] self.wifs = [] super(TransactionBuilder, self).__init__({}) def addSigningInformation(self, account, permission): """ This is a private method that adds side information to a unsigned/partial transaction in order to simplify later signing (e.g. for multisig or coldstorage) """ self.constructTx() accountObj = Account(account) authority = accountObj[permission] # We add a required_authorities to be able to identify # how to sign later. This is an array, because we # may later want to allow multiple operations per tx self.update({"required_authorities": {accountObj["name"]: authority}}) for account_auth in authority["account_auths"]: account_auth_account = Account(account_auth[0]) self["required_authorities"].update( {account_auth[0]: account_auth_account.get(permission)}) # Try to resolve required signatures for offline signing self["missing_signatures"] = [x[0] for x in authority["key_auths"]] # Add one recursion of keys from account_auths: for account_auth in authority["account_auths"]: account_auth_account = Account(account_auth[0]) self["missing_signatures"].extend( [x[0] for x in account_auth_account[permission]["key_auths"]]) self["blockchain"] = self.peerplays.rpc.chain_params def json(self): """ Show the transaction as plain json """ return dict(self) def appendMissingSignatures(self): """ Store which accounts/keys are supposed to sign the transaction This method is used for an offline-signer! """ missing_signatures = self.get("missing_signatures", []) for pub in missing_signatures: wif = self.peerplays.wallet.getPrivateKeyForPublicKey(pub) if wif: self.appendWif(wif)
class TransactionBuilder(dict): """ This class simplifies the creation of transactions by adding operations and signers. """ def __init__(self, tx={}, proposer=None, **kwargs): BlockchainInstance.__init__(self, **kwargs) self.clear() if tx and isinstance(tx, dict): super(TransactionBuilder, self).__init__(tx) # Load operations self.ops = tx["operations"] self._require_reconstruction = False else: self._require_reconstruction = True self.set_fee_asset(kwargs.get("fee_asset", None)) self.set_expiration( kwargs.get("expiration", self.blockchain.expiration) or 30) def set_expiration(self, p): self.expiration = p def is_empty(self): return not (len(self.ops) > 0) def list_operations(self): return [Operation(o) for o in self.ops] def _is_signed(self): return "signatures" in self and self["signatures"] def _is_constructed(self): return "expiration" in self and self["expiration"] def _is_require_reconstruction(self): return self._require_reconstruction def _set_require_reconstruction(self): self._require_reconstruction = True def _unset_require_reconstruction(self): self._require_reconstruction = False def __repr__(self): return str(self) def __str__(self): return str(self.json()) def __getitem__(self, key): if key not in self: self.constructTx() return dict(self).__getitem__(key) def get_parent(self): """ TransactionBuilders don't have parents, they are their own parent """ return self def json(self): """ Show the transaction as plain json """ if not self._is_constructed() or self._is_require_reconstruction(): self.constructTx() return dict(self) def appendOps(self, ops, append_to=None): """ Append op(s) to the transaction builder :param list ops: One or a list of operations """ if isinstance(ops, list): self.ops.extend(ops) else: self.ops.append(ops) self._set_require_reconstruction() def appendSigner(self, accounts, permission): """ Try to obtain the wif key from the wallet by telling which accounts and permission is supposed to sign the transaction """ # Let's define a helper function for recursion def fetchkeys(account, perm, level=0): if level > 2: return [] r = [] for authority in account[perm]["key_auths"]: try: wif = self.blockchain.wallet.getPrivateKeyForPublicKey( authority[0]) r.append([wif, authority[1]]) except Exception: pass if sum([x[1] for x in r]) < required_treshold: # go one level deeper for authority in account[perm]["account_auths"]: auth_account = Account(authority[0], blockchain_instance=self.blockchain) r.extend(fetchkeys(auth_account, perm, level + 1)) return r assert permission in ["active", "owner"], "Invalid permission" if self.blockchain.wallet.locked(): raise WalletLocked() if not isinstance(accounts, (list, tuple, set)): accounts = [accounts] for account in accounts: # Now let's actually deal with the accounts if account not in self.signing_accounts: # is the account an instance of public key? if isinstance(account, PublicKey): self.wifs.add( self.blockchain.wallet.getPrivateKeyForPublicKey( str(account))) # ... or should we rather obtain the keys from an account name else: account = Account(account, blockchain_instance=self.blockchain) required_treshold = account[permission]["weight_threshold"] keys = fetchkeys(account, permission) # If we couldn't find an active key, let's try overwrite it # with an owner key if not keys and permission != "owner": keys.extend(fetchkeys(account, "owner")) for x in keys: self.wifs.add(x[0]) self.signing_accounts.append(account) def appendWif(self, wif): """ Add a wif that should be used for signing of the transaction. """ if wif: try: PrivateKey(wif) self.wifs.add(wif) except: raise InvalidWifError def set_fee_asset(self, fee_asset): """ Set asset to fee """ from .amount import Asset if isinstance(fee_asset, Asset): self.fee_asset_id = fee_asset["id"] elif fee_asset: self.fee_asset_id = fee_asset else: self.fee_asset_id = "1.3.0" def constructTx(self): """ Construct the actual transaction and store it in the class's dict store """ ops = list() for op in self.ops: if isinstance(op, ProposalBuilder): # This operation is a proposal an needs to be deal with # differently proposals = op.get_raw() if proposals: ops.append(proposals) else: # otherwise, we simply wrap ops into Operations ops.extend([Operation(op)]) # We now wrap everything into an actual transaction ops = transactions.addRequiredFees(self.blockchain.rpc, ops, asset_id=self.fee_asset_id) expiration = transactions.formatTimeFromNow( self.expiration or self.blockchain.expiration) ref_block_num, ref_block_prefix = transactions.getBlockParams( self.blockchain.rpc) self.tx = Signed_Transaction(ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops) super(TransactionBuilder, self).update(self.tx.json()) self._unset_require_reconstruction() def sign(self): """ Sign a provided transaction with the provided key(s) :param dict tx: The transaction to be signed and returned :param string wifs: One or many wif keys to use for signing a transaction. If not present, the keys will be loaded from the wallet as defined in "missing_signatures" key of the transactions. """ self.constructTx() if "operations" not in self or not self["operations"]: return # Legacy compatibility! # If we are doing a proposal, obtain the account from the proposer_id if self.blockchain.proposer: proposer = Account(self.blockchain.proposer, blockchain_instance=self.blockchain) self.wifs = set() self.signing_accounts = list() self.appendSigner(proposer["id"], "active") # We need to set the default prefix, otherwise pubkeys are # presented wrongly! if self.blockchain.rpc: operations.default_prefix = ( self.blockchain.rpc.chain_params["prefix"]) elif "blockchain" in self: operations.default_prefix = self["blockchain"]["prefix"] try: signedtx = Signed_Transaction(**self.json()) except: raise ValueError("Invalid TransactionBuilder Format") if not any(self.wifs): raise MissingKeyError signedtx.sign(self.wifs, chain=self.blockchain.rpc.chain_params) self["signatures"].extend(signedtx.json().get("signatures")) return signedtx def verify_authority(self): """ Verify the authority of the signed transaction """ try: if not self.blockchain.rpc.verify_authority(self.json()): raise InsufficientAuthorityError except Exception as e: raise e def broadcast(self): """ Broadcast a transaction to the blockchain network :param tx tx: Signed transaction to broadcast """ # Cannot broadcast an empty transaction if not self._is_signed(): self.sign() if "operations" not in self or not self["operations"]: return ret = self.json() if self.blockchain.nobroadcast: log.warning("Not broadcasting anything!") self.clear() return ret # Broadcast try: if self.blockchain.blocking: ret = self.blockchain.rpc.broadcast_transaction_synchronous( ret, api="network_broadcast") ret.update(**ret.get("trx")) else: self.blockchain.rpc.broadcast_transaction( ret, api="network_broadcast") except Exception as e: raise e finally: self.clear() return ret def clear(self): """ Clear the transaction builder and start from scratch """ self.ops = [] self.wifs = set() self.signing_accounts = [] # This makes sure that _is_constructed will return False afterwards self["expiration"] = None super(TransactionBuilder, self).__init__({}) def addSigningInformation(self, accounts, permission): """ This is a private method that adds side information to a unsigned/partial transaction in order to simplify later signing (e.g. for multisig or coldstorage) FIXME: Does not work with owner keys! """ self.constructTx() self["blockchain"] = self.blockchain.rpc.chain_params if isinstance(accounts, PublicKey): self["missing_signatures"] = [str(accounts)] else: if not isinstance(accounts, list): accounts = [accounts] for account in accounts: accountObj = Account(account) authority = accountObj[permission] # We add a required_authorities to be able to identify # how to sign later. This is an array, because we # may later want to allow multiple operations per tx if "required_authorities" not in self: self["required_authorities"] = dict() if "missing_signatures" not in self: self["missing_signatures"] = list() self["required_authorities"].update( {accountObj["name"]: authority}) for account_auth in authority["account_auths"]: account_auth_account = Account(account_auth[0]) self["required_authorities"].update({ account_auth[0]: account_auth_account.get(permission) }) # Try to resolve required signatures for offline signing self["missing_signatures"].extend( [x[0] for x in authority["key_auths"]]) # Add one recursion of keys from account_auths: for account_auth in authority["account_auths"]: account_auth_account = Account(account_auth[0]) self["missing_signatures"].extend([ x[0] for x in account_auth_account[permission]["key_auths"] ]) def appendMissingSignatures(self): """ Store which accounts/keys are supposed to sign the transaction This method is used for an offline-signer! """ missing_signatures = self.get("missing_signatures", []) for pub in missing_signatures: wif = self.blockchain.wallet.getPrivateKeyForPublicKey(pub) if wif: self.appendWif(wif)