def compareConstructedTX(self): # def test_online(self): # self.maxDiff = None op = operations.Asset_reserve( **{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "payer": "1.2.0", "amount_to_reserve": { "amount": 1234567890, "asset_id": "1.3.0" }, "extensions": [] }) 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], "BTS") 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 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, ) pprint(tx.json()) tx = tx.sign([wif], chain=prefix) tx.verify([PrivateKey(wif).pubkey], prefix) txWire = hexlify(bytes(tx)).decode("ascii") if printWire: print() print(txWire) print() # Test against Bitshares backened live = bitshares.rpc.get_transaction_hex(tx.json()) # Compare expected result with online backend self.assertEqual(live[:-130], self.cm[:-130]) # Compare expected result with online result self.assertEqual(live[:-130], txWire[:-130]) # Compare expected result with test unit self.assertEqual(self.cm[:-130], txWire[:-130])
def compareConstructedTX(self): self.maxDiff = None self.op = operations.Call_order_update( **{ "fee": {"amount": 100, "asset_id": "1.3.0"}, "delta_debt": {"amount": 10000, "asset_id": "1.3.22"}, "delta_collateral": {"amount": 100000000, "asset_id": "1.3.0"}, "funding_account": "1.2.29", "extensions": {"target_collateral_ratio": 12345}, } ) 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) # Test against Bitshares backened self.cm = bitshares.rpc.get_transaction_hex(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 compareConstructedTX(self): self.maxDiff = None self.op = operations.Committee_member_create(**{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "committee_member_account": "1.2.0", "url": "foobar" }) 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[:-130]) print("ist: %s" % txWire[:-130]) print(txWire[:-130] == self.cm[:-130]) self.assertEqual(self.cm[:-130], txWire[:-130])
def compareConstructedTX(self): self.maxDiff = None self.op = operations.Custom( **{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "payer": "1.2.0", "required_auths": ["1.2.100", "1.2.101"], "id": "35235", "data": hexlify(b"Foobar").decode("ascii") }) 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) # Test against Bitshares backened self.cm = bitshares.rpc.get_transaction_hex(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 compareConstructedTX(self): self.maxDiff = None self.op = operations.Bid_collateral(**{ 'fee': {'amount': 100, 'asset_id': '1.3.0'}, 'additional_collateral': { 'amount': 10000, 'asset_id': '1.3.22'}, 'debt_covered': { 'amount': 100000000, 'asset_id': '1.3.0'}, 'bidder': '1.2.29', 'extensions': [] }) 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], "BTS") 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 constructTx(self): """ Construct the actual transaction and store it in the class's dict store """ if self.bitshares.proposer: ops = [operations.Op_wrapper(op=o) for o in list(self.ops)] proposer = Account(self.bitshares.proposer, bitshares_instance=self.bitshares) ops = operations.Proposal_create( **{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "fee_paying_account": proposer["id"], "expiration_time": transactions.formatTimeFromNow( self.bitshares.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.bitshares.rpc, ops) expiration = transactions.formatTimeFromNow(self.bitshares.expiration) ref_block_num, ref_block_prefix = transactions.getBlockParams( self.bitshares.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): # def test_online(self): # self.maxDiff = None op = operations.Worker_create( **{ "fee": { "amount": 0, "asset_id": "1.3.0" }, "owner": "1.2.0", "work_begin_date": "1970-01-01T00:00:00", "work_end_date": "1970-01-01T00:00:00", "daily_pay": 0, "name": "Myname", "url": "myURL", "initializer": [1, { "pay_vesting_period_days": 125 }] }) 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], "BTS") 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("soll: %s" % compare[:-130]) print("ist: %s" % txWire[:-130]) print(txWire[:-130] == compare[:-130]) self.assertEqual(compare[:-130], txWire[:-130])
async def __construct_tx(self, fee_asset_id, expiration): """ 构造转账操作 """ ops = [Operation(o) for o in list(self.ops)] expiration = transactions.formatTimeFromNow(expiration) ops = await transactions.add_required_fees(self.node_api, ops, fee_asset_id) ref_block_num, ref_block_prefix = await transactions.get_block_params(self.node_api) tx = Signed_Transaction( ref_block_num=ref_block_num, ref_block_prefix=ref_block_prefix, expiration=expiration, operations=ops ) super(Builder, self).__init__(tx.json())
async def sign(self, fee_asset_id, expiration=30): """ 执行签名 """ await self.__construct_tx(fee_asset_id, expiration) operations.default_prefix = self.node_api.chain_params['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.node_api.chain_params) self['signatures'].extend(signedtx.json().get('signatures'))
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]) # Test against Bitshares backened bitshares = BitShares() self.cm = bitshares.rpc.get_transaction_hex(tx.json()) 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={}, proposer=None, bitshares_instance=None): self.bitshares = bitshares_instance or shared_bitshares_instance() self.clear() if not isinstance(tx, dict): raise ValueError("Invalid TransactionBuilder Format") super(TransactionBuilder, self).__init__(tx) # Do we need to reconstruct the tx from self.ops? self._require_reconstruction = True 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, 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, bitshares_instance=self.bitshares) required_treshold = account[permission]["weight_threshold"] if self.bitshares.wallet.locked(): raise WalletLocked() def fetchkeys(account, perm, level=0): if level > 2: return [] r = [] for authority in account[perm]["key_auths"]: try: wif = self.bitshares.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], bitshares_instance=self.bitshares) r.extend(fetchkeys(auth_account, perm, level + 1)) return r if account not in self.signing_accounts: # is the account an instance of public key? if isinstance(account, PublicKey): self.wifs.add( self.bitshares.wallet.getPrivateKeyForPublicKey( str(account))) else: account = Account(account, bitshares_instance=self.bitshares) required_treshold = account[permission]["weight_threshold"] keys = fetchkeys(account, permission) if 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 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 no wrap everything into an actual transaction ops = transactions.addRequiredFees(self.bitshares.rpc, ops) expiration = transactions.formatTimeFromNow(self.bitshares.expiration) ref_block_num, ref_block_prefix = transactions.getBlockParams( self.bitshares.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()) self._unset_require_reconstruction() 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 "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.bitshares.proposer: proposer = Account(self.bitshares.proposer, bitshares_instance=self.bitshares) 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.bitshares.rpc: operations.default_prefix = ( self.bitshares.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.bitshares.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.bitshares.rpc.verify_authority(self.json()): raise InsufficientAuthorityError except Exception as e: raise e def broadcast(self): """ Broadcast a transaction to the bitshares 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.bitshares.nobroadcast: log.warning("Not broadcasting anything!") self.clear() return ret # Broadcast try: if self.bitshares.blocking: ret = self.bitshares.rpc.broadcast_transaction_synchronous( ret, api="network_broadcast") ret.update(**ret["trx"]) else: self.bitshares.rpc.broadcast_transaction( ret, api="network_broadcast") except Exception as e: raise e 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, 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) FIXME: Does not work with owner keys! """ self.constructTx() self["blockchain"] = self.bitshares.rpc.chain_params if isinstance(account, PublicKey): self["missing_signatures"] = [str(account)] else: 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"] ]) 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.bitshares.wallet.getPrivateKeyForPublicKey(pub) if wif: self.appendWif(wif)
def test_jsonLoading(self): data1 = { "expiration": expiration, "ref_block_num": ref_block_num, "ref_block_prefix": ref_block_prefix, "extensions": [], "operations": [[ 0, { "amount": { "amount": 1000000, "asset_id": "1.3.4" }, "extensions": [], "fee": { "amount": 0, "asset_id": "1.3.0" }, "from": "1.2.0", "memo": { "from": "BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "message": "fa5b6e83079a878e499e2e52a76a7739e9de40986a8e3bd8a68ce316cee50b21", "nonce": 5862723643998573708, "to": "BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" }, "to": "1.2.1" } ]], "signatures": [ "1f6c1e8df5faf18c3b057ce713ec92f9" + "b487c1ba58138daabc0038741b402c93" + "0d63d8d63861740215b4f65eb8ac9185" + "a3987f8239b962181237f47189e21102" + "af" ] } a = Signed_Transaction(data1.copy()) data2 = a.json() check1 = data1 check2 = data2 for key in [ "expiration", "extensions", "ref_block_num", "ref_block_prefix", "signatures" ]: self.assertEqual(check1[key], check2[key]) check1 = data1["operations"][0][1] check2 = data2["operations"][0][1] for key in ["from", "to"]: self.assertEqual(check1[key], check2[key]) check1 = data1["operations"][0][1]["memo"] check2 = data2["operations"][0][1]["memo"] for key in check1: self.assertEqual(check1[key], check2[key])