From dc5fadf21c031ccb06212fc1260625a592a52d1b Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Thu, 22 Jun 2023 17:31:26 +0300 Subject: [PATCH 1/8] started to integrate Transaction class from sdk-core --- multiversx_sdk_cli/accounts.py | 4 +- multiversx_sdk_cli/cli_contracts.py | 15 +- multiversx_sdk_cli/cli_output.py | 4 +- multiversx_sdk_cli/cli_shared.py | 8 +- multiversx_sdk_cli/contracts.py | 110 +++---- multiversx_sdk_cli/dns.py | 5 +- multiversx_sdk_cli/interfaces.py | 65 ++-- multiversx_sdk_cli/transactions.py | 473 +++++++++++++++++----------- requirements.txt | 2 +- 9 files changed, 406 insertions(+), 280 deletions(-) diff --git a/multiversx_sdk_cli/accounts.py b/multiversx_sdk_cli/accounts.py index 9656f1ae..26037d3c 100644 --- a/multiversx_sdk_cli/accounts.py +++ b/multiversx_sdk_cli/accounts.py @@ -91,8 +91,8 @@ def sign_transaction(self, transaction: ITransaction) -> str: ledger_version = do_get_ledger_version() should_use_hash_signing = compare_versions(ledger_version, SIGN_USING_HASH_VERSION) >= 0 if should_use_hash_signing: - transaction.set_version(TX_HASH_SIGN_VERSION) - transaction.set_options(TX_HASH_SIGN_OPTIONS) + transaction.version = TX_HASH_SIGN_VERSION + transaction.options = TX_HASH_SIGN_OPTIONS signature = do_sign_transaction_with_ledger( transaction.serialize_for_signing(), diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index c9396db3..1a78e3e5 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -3,11 +3,10 @@ from pathlib import Path from typing import Any, Dict, List +from multiversx_sdk_core import Address, Transaction from multiversx_sdk_network_providers.proxy_network_provider import \ ProxyNetworkProvider -from multiversx_sdk_core import Address - from multiversx_sdk_cli import cli_shared, errors, projects, utils from multiversx_sdk_cli.accounts import Account, LedgerAccount from multiversx_sdk_cli.cli_output import CLIOutputBuilder @@ -15,11 +14,10 @@ from multiversx_sdk_cli.contract_verification import \ trigger_contract_verification from multiversx_sdk_cli.contracts import CodeMetadata, SmartContract +from multiversx_sdk_cli.cosign_transaction import cosign_transaction from multiversx_sdk_cli.docker import is_docker_installed, run_docker from multiversx_sdk_cli.errors import DockerMissingError, NoWalletProvided from multiversx_sdk_cli.projects.core import get_project_paths_recursively -from multiversx_sdk_cli.transactions import Transaction -from multiversx_sdk_cli.cosign_transaction import cosign_transaction logger = logging.getLogger("cli.contracts") @@ -356,8 +354,9 @@ def _prepare_signer(args: Any) -> Account: def _sign_guarded_tx(args: Any, tx: Transaction) -> Transaction: - signature = tx.signature - tx.signature = "" + # not needed anymore (i think) + # signature = tx.signature + # tx.signature = bytes() try: guardian_account = cli_shared.prepare_guardian_account(args) @@ -365,11 +364,11 @@ def _sign_guarded_tx(args: Any, tx: Transaction) -> Transaction: guardian_account = None if guardian_account: - tx.guardianSignature = guardian_account.sign_transaction(tx) + tx.guardian_signature = bytes.fromhex(guardian_account.sign_transaction(tx)) elif args.guardian: tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) # type: ignore - tx.signature = signature + # tx.signature = signature return tx diff --git a/multiversx_sdk_cli/cli_output.py b/multiversx_sdk_cli/cli_output.py index 42e7113f..62c30fbe 100644 --- a/multiversx_sdk_cli/cli_output.py +++ b/multiversx_sdk_cli/cli_output.py @@ -47,8 +47,8 @@ def build(self) -> Dict[str, Any]: if self.emitted_transaction: emitted_transaction_dict = self.emitted_transaction.to_dictionary() - emitted_transaction_hash = self.emitted_transaction.get_hash() or "" - emitted_transaction_data = self.emitted_transaction.get_data() or "" + emitted_transaction_hash = self.emitted_transaction.hash if hasattr(self.emitted_transaction, "hash") else "" + emitted_transaction_data = str(self.emitted_transaction.data) utils.omit_fields(emitted_transaction_dict, self.emitted_transaction_omitted_fields) output["emittedTransaction"] = emitted_transaction_dict diff --git a/multiversx_sdk_cli/cli_shared.py b/multiversx_sdk_cli/cli_shared.py index c7ef7c0a..b8b0f8bf 100644 --- a/multiversx_sdk_cli/cli_shared.py +++ b/multiversx_sdk_cli/cli_shared.py @@ -4,7 +4,7 @@ from argparse import FileType from typing import Any, List, Text, cast -from multiversx_sdk_core import Address +from multiversx_sdk_core import Address, Transaction from multiversx_sdk_network_providers.proxy_network_provider import \ ProxyNetworkProvider @@ -16,7 +16,7 @@ from multiversx_sdk_cli.constants import TRANSACTION_OPTIONS_TX_GUARDED from multiversx_sdk_cli.ledger.ledger_functions import do_get_ledger_address from multiversx_sdk_cli.simulation import Simulator -from multiversx_sdk_cli.transactions import Transaction +from multiversx_sdk_cli.transactions import send_and_wait_for_result def wider_help_formatter(prog: Text): @@ -235,10 +235,10 @@ def send_or_simulate(tx: Transaction, args: Any, dump_output: bool = True) -> CL try: if send_wait_result: - transaction_on_network = tx.send_wait_result(proxy, args.timeout) + transaction_on_network = send_and_wait_for_result(tx, proxy, args.timeout) output_builder.set_awaited_transaction(transaction_on_network) elif send_only: - tx.send(proxy) + proxy.send_transaction(tx) elif simulate: simulation = Simulator(proxy).run(tx) output_builder.set_simulation_results(simulation) diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index a202cbbf..2c6634d2 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -3,13 +3,12 @@ from typing import Any, List, Optional, Protocol, Sequence, Tuple from Cryptodome.Hash import keccak -from multiversx_sdk_core import Address +from multiversx_sdk_core import Address, Transaction, TransactionPayload from multiversx_sdk_network_providers.interface import IContractQuery from multiversx_sdk_cli import config, constants, errors -from multiversx_sdk_cli.accounts import Account +from multiversx_sdk_cli.accounts import Account, EmptyAddress from multiversx_sdk_cli.constants import ADDRESS_ZERO, DEFAULT_HRP -from multiversx_sdk_cli.transactions import Transaction from multiversx_sdk_cli.utils import Object logger = logging.getLogger("contracts") @@ -77,29 +76,30 @@ def deploy(self, owner: Account, arguments: List[Any], gas_price: int, gas_limit gas_limit = int(gas_limit) value = value or 0 - tx = Transaction() - tx.nonce = owner.nonce - tx.value = str(value) - tx.sender = owner.address.bech32() - tx.receiver = ADDRESS_ZERO - tx.gasPrice = gas_price - tx.gasLimit = gas_limit - tx.data = self.prepare_deploy_transaction_data(arguments) - tx.chainID = chain - tx.version = version - tx.guardian = guardian - tx.options = options - - tx.sign(owner) + tx = Transaction( + chain_id=chain, + sender=owner.address, + receiver=Address.from_bech32(ADDRESS_ZERO), + gas_limit=gas_limit, + gas_price=gas_price, + nonce=owner.nonce, + value=value, + data=self.prepare_deploy_transaction_data(arguments), + version=version, + options=options, + guardian=Address.from_bech32(guardian) + ) + + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) return tx - def prepare_deploy_transaction_data(self, arguments: List[Any]): + def prepare_deploy_transaction_data(self, arguments: List[Any]) -> TransactionPayload: tx_data = f"{self.bytecode}@{constants.VM_TYPE_WASM_VM}@{self.metadata.to_hex()}" for arg in arguments: tx_data += f"@{_prepare_argument(arg)}" - return tx_data + return TransactionPayload.from_str(tx_data) def compute_address(self): """ @@ -119,30 +119,32 @@ def execute(self, caller: Account, function: str, arguments: List[str], gas_pric gas_price = int(gas_price) gas_limit = int(gas_limit) value = value or 0 - - tx = Transaction() - tx.nonce = caller.nonce - tx.value = str(value) - tx.sender = caller.address.bech32() - tx.receiver = self.address.bech32() - tx.gasPrice = gas_price - tx.gasLimit = gas_limit - tx.data = self.prepare_execute_transaction_data(function, arguments) - tx.chainID = chain - tx.version = version - tx.guardian = guardian - tx.options = options - - tx.sign(caller) + receiver = self.address if self.address else EmptyAddress() + + tx = Transaction( + chain_id=chain, + sender=caller.address, + receiver=receiver, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=caller.nonce, + value=value, + data=self.prepare_execute_transaction_data(function, arguments), + version=version, + options=options, + guardian=Address.from_bech32(guardian) + ) + + tx.signature = bytes.fromhex(caller.sign_transaction(tx)) return tx - def prepare_execute_transaction_data(self, function: str, arguments: List[Any]): + def prepare_execute_transaction_data(self, function: str, arguments: List[Any]) -> TransactionPayload: tx_data = function for arg in arguments: tx_data += f"@{_prepare_argument(arg)}" - return tx_data + return TransactionPayload.from_str(tx_data) def upgrade(self, owner: Account, arguments: List[Any], gas_price: int, gas_limit: int, value: int, chain: str, version: int, guardian: str, options: int) -> Transaction: self.owner = owner @@ -151,30 +153,32 @@ def upgrade(self, owner: Account, arguments: List[Any], gas_price: int, gas_limi gas_price = int(gas_price or config.DEFAULT_GAS_PRICE) gas_limit = int(gas_limit) value = value or 0 - - tx = Transaction() - tx.nonce = owner.nonce - tx.value = str(value) - tx.sender = owner.address.bech32() - tx.receiver = self.address.bech32() - tx.gasPrice = gas_price - tx.gasLimit = gas_limit - tx.data = self.prepare_upgrade_transaction_data(arguments) - tx.chainID = chain - tx.version = version - tx.guardian = guardian - tx.options = options - - tx.sign(owner) + receiver = self.address if self.address else EmptyAddress() + + tx = Transaction( + chain_id=chain, + sender=owner.address, + receiver=receiver, + gas_limit=gas_limit, + gas_price=gas_price, + nonce=owner.nonce, + value=value, + data=self.prepare_upgrade_transaction_data(arguments), + version=version, + options=options, + guardian=Address.from_bech32(guardian) + ) + + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) return tx - def prepare_upgrade_transaction_data(self, arguments: List[Any]): + def prepare_upgrade_transaction_data(self, arguments: List[Any]) -> TransactionPayload: tx_data = f"upgradeContract@{self.bytecode}@{self.metadata.to_hex()}" for arg in arguments: tx_data += f"@{_prepare_argument(arg)}" - return tx_data + return TransactionPayload.from_str(tx_data) def query( self, diff --git a/multiversx_sdk_cli/dns.py b/multiversx_sdk_cli/dns.py index 285cb2fd..3b93d335 100644 --- a/multiversx_sdk_cli/dns.py +++ b/multiversx_sdk_cli/dns.py @@ -7,7 +7,8 @@ from multiversx_sdk_cli.accounts import Account from multiversx_sdk_cli.constants import ADDRESS_ZERO, DEFAULT_HRP from multiversx_sdk_cli.contracts import SmartContract -from multiversx_sdk_cli.transactions import do_prepare_transaction +from multiversx_sdk_cli.transactions import (do_prepare_transaction, + tx_to_dictionary_as_inner) MaxNumShards = 256 ShardIdentiferLen = 2 @@ -48,7 +49,7 @@ def register(args: Any): tx = do_prepare_transaction(args) if hasattr(args, "relay") and args.relay: - args.outfile.write(tx.serialize_as_inner()) + args.outfile.write(tx_to_dictionary_as_inner(tx)) return cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/interfaces.py b/multiversx_sdk_cli/interfaces.py index 8e12eee2..88c7fab8 100644 --- a/multiversx_sdk_cli/interfaces.py +++ b/multiversx_sdk_cli/interfaces.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Protocol -from multiversx_sdk_cli.utils import ISerializable +# from multiversx_sdk_cli.utils import ISerializable class IAddress(Protocol): @@ -11,33 +11,58 @@ def bech32(self) -> str: ... -class ITransaction(ISerializable): - def serialize(self) -> bytes: - return bytes() +# class ITransaction(ISerializable): +# def serialize(self) -> bytes: +# return bytes() - def serialize_for_signing(self) -> bytes: - return bytes() +# def serialize_for_signing(self) -> bytes: +# return bytes() - def serialize_as_inner(self) -> str: - return '' +# def serialize_as_inner(self) -> str: +# return '' - def to_dictionary(self) -> Dict[str, Any]: - return {} +# def to_dictionary(self) -> Dict[str, Any]: +# return {} + +# def to_dictionary_as_inner(self) -> Dict[str, Any]: +# return {} + +# def set_version(self, version: int): +# return + +# def set_options(self, options: int): +# return + +# def get_hash(self) -> str: +# return "" + +# def get_data(self) -> str: +# return "" - def to_dictionary_as_inner(self) -> Dict[str, Any]: - return {} - def set_version(self, version: int): - return +ITransactionOptions = int +ITransactionVersion = int +ISignature = bytes - def set_options(self, options: int): - return - def get_hash(self) -> str: - return "" +class ITransactionPayload(Protocol): + data: bytes + def encoded(self) -> str: ... + def length(self) -> int: ... - def get_data(self) -> str: - return "" + +class ITransaction(Protocol): + version: ITransactionVersion + options: ITransactionOptions + data: ITransactionPayload + signature: ISignature + guardian_signature: ISignature + + def serialize_for_signing(self) -> bytes: + ... + + def to_dictionary(self, with_signature: bool = True) -> Dict[str, Any]: + ... class IAccount(Protocol): diff --git a/multiversx_sdk_cli/transactions.py b/multiversx_sdk_cli/transactions.py index 47d294b8..f32031b7 100644 --- a/multiversx_sdk_cli/transactions.py +++ b/multiversx_sdk_cli/transactions.py @@ -2,13 +2,15 @@ import json import logging import time +from ast import arg from collections import OrderedDict +from itertools import chain from typing import Any, Dict, List, Protocol, Sequence, TextIO, Tuple -from multiversx_sdk_core import Address +from multiversx_sdk_core import Address, Transaction, TransactionPayload from multiversx_sdk_cli import config, errors, utils -from multiversx_sdk_cli.accounts import Account, LedgerAccount +from multiversx_sdk_cli.accounts import Account, EmptyAddress, LedgerAccount from multiversx_sdk_cli.cli_password import (load_guardian_password, load_password) from multiversx_sdk_cli.cosign_transaction import cosign_transaction @@ -38,189 +40,208 @@ def get_transaction(self, tx_hash: str) -> ITransactionOnNetwork: ... -class Transaction(ITransaction): - def __init__(self): - self.hash: str = "" - self.nonce = 0 - self.value = "0" - self.receiver = "" - self.sender = "" - self.senderUsername = "" - self.receiverUsername = "" - self.gasPrice = 0 - self.gasLimit = 0 - self.data: str = "" - self.chainID = "" - self.version = 0 - self.options = 0 - self.signature = "" - self.guardian = "" - self.guardianSignature = "" +# class Transaction(ITransaction): +# def __init__(self): +# self.hash: str = "" +# self.nonce = 0 +# self.value = "0" +# self.receiver = "" +# self.sender = "" +# self.senderUsername = "" +# self.receiverUsername = "" +# self.gasPrice = 0 +# self.gasLimit = 0 +# self.data: str = "" +# self.chainID = "" +# self.version = 0 +# self.options = 0 +# self.signature = "" +# self.guardian = "" +# self.guardianSignature = "" + +# # The data field is base64-encoded. Here, we only support utf-8 "data" at this moment. +# def data_encoded(self) -> str: +# return self._field_encoded("data") + +# # Useful when loading a tx from a file (when data is already encoded in base64) +# def data_decoded(self) -> str: +# return self._field_decoded("data") + +# def sender_username_encoded(self) -> str: +# return self._field_encoded("senderUsername") + +# def sender_username_decoded(self) -> str: +# return self._field_decoded("senderUsername") + +# def receiver_username_encoded(self) -> str: +# return self._field_encoded("receiverUsername") + +# def receiver_username_decoded(self) -> str: +# return self._field_decoded("receiverUsername") + +# def _field_encoded(self, field: str) -> str: +# field_bytes = self.__dict__.get(field, "").encode("utf-8") +# encoded = base64.b64encode(field_bytes).decode() +# return encoded + +# def _field_decoded(self, field: str) -> str: +# return base64.b64decode(self.__dict__.get(field, None)).decode() + +# def sign(self, account: Account): +# self.validate() +# self.signature = account.sign_transaction(self) + +# def validate(self) -> None: +# if self.gasLimit > config.MAX_GAS_LIMIT: +# raise errors.GasLimitTooLarge(self.gasLimit, config.MAX_GAS_LIMIT) + +# def serialize(self) -> bytes: +# dictionary = self.to_dictionary() +# serialized = self._dict_to_json(dictionary) +# return serialized - # The data field is base64-encoded. Here, we only support utf-8 "data" at this moment. - def data_encoded(self) -> str: - return self._field_encoded("data") +# def serialize_for_signing(self) -> bytes: +# return self.serialize() + +# def _dict_to_json(self, dictionary: Dict[str, Any]) -> bytes: +# serialized = json.dumps(dictionary, separators=(',', ':')).encode("utf8") +# return serialized + +# def serialize_as_inner(self) -> str: +# inner_dictionary = self.to_dictionary_as_inner() +# serialized = self._dict_to_json(inner_dictionary) +# serialized_hex = serialized.hex() +# return f"relayedTx@{serialized_hex}" - # Useful when loading a tx from a file (when data is already encoded in base64) - def data_decoded(self) -> str: - return self._field_decoded("data") +# @classmethod +# def load_from_file(cls, f: TextIO): +# data_json: bytes = f.read().encode() +# fields = json.loads(data_json).get("tx") or json.loads(data_json).get("emittedTransaction") +# instance = cls() +# instance.__dict__.update(fields) +# instance.data = instance.data_decoded() +# instance.senderUsername = instance.sender_username_decoded() +# instance.receiverUsername = instance.receiver_username_encoded() +# return instance - def sender_username_encoded(self) -> str: - return self._field_encoded("senderUsername") +# def send(self, proxy: INetworkProvider): +# if not self.signature: +# raise errors.TransactionIsNotSigned() - def sender_username_decoded(self) -> str: - return self._field_decoded("senderUsername") +# logger.info(f"Transaction.send: nonce={self.nonce}") - def receiver_username_encoded(self) -> str: - return self._field_encoded("receiverUsername") +# self.hash = proxy.send_transaction(self) +# logger.info(f"Hash: {self.hash}") +# utils.log_explorer_transaction(self.chainID, self.hash) +# return self.hash - def receiver_username_decoded(self) -> str: - return self._field_decoded("receiverUsername") +# def send_wait_result(self, proxy: INetworkProvider, timeout: int) -> ITransactionOnNetwork: +# if not self.signature: +# raise errors.TransactionIsNotSigned() - def _field_encoded(self, field: str) -> str: - field_bytes = self.__dict__.get(field, "").encode("utf-8") - encoded = base64.b64encode(field_bytes).decode() - return encoded +# txOnNetwork = self.__send_transaction_and_wait_for_result(proxy, self, timeout) +# self.hash = txOnNetwork.hash +# return txOnNetwork - def _field_decoded(self, field: str) -> str: - return base64.b64decode(self.__dict__.get(field, None)).decode() +# def __send_transaction_and_wait_for_result(self, proxy: INetworkProvider, payload: Any, num_seconds_timeout: int = 100) -> ITransactionOnNetwork: +# AWAIT_TRANSACTION_PERIOD = 5 - def sign(self, account: Account): - self.validate() - self.signature = account.sign_transaction(self) +# tx_hash = proxy.send_transaction(payload) +# num_periods_to_wait = int(num_seconds_timeout / AWAIT_TRANSACTION_PERIOD) - def validate(self) -> None: - if self.gasLimit > config.MAX_GAS_LIMIT: - raise errors.GasLimitTooLarge(self.gasLimit, config.MAX_GAS_LIMIT) +# for _ in range(0, num_periods_to_wait): +# time.sleep(AWAIT_TRANSACTION_PERIOD) - def serialize(self) -> bytes: - dictionary = self.to_dictionary() - serialized = self._dict_to_json(dictionary) - return serialized +# tx = proxy.get_transaction(tx_hash) +# if tx.is_completed: +# return tx +# else: +# logger.info("Transaction not yet done.") - def serialize_for_signing(self) -> bytes: - return self.serialize() +# raise errors.KnownError("Took too long to get transaction.") - def _dict_to_json(self, dictionary: Dict[str, Any]) -> bytes: - serialized = json.dumps(dictionary, separators=(',', ':')).encode("utf8") - return serialized +# def to_dictionary(self) -> Dict[str, Any]: +# dictionary: Dict[str, Any] = OrderedDict() +# dictionary["nonce"] = self.nonce +# dictionary["value"] = self.value - def serialize_as_inner(self) -> str: - inner_dictionary = self.to_dictionary_as_inner() - serialized = self._dict_to_json(inner_dictionary) - serialized_hex = serialized.hex() - return f"relayedTx@{serialized_hex}" +# dictionary["receiver"] = self.receiver +# dictionary["sender"] = self.sender - @classmethod - def load_from_file(cls, f: TextIO): - data_json: bytes = f.read().encode() - fields = json.loads(data_json).get("tx") or json.loads(data_json).get("emittedTransaction") - instance = cls() - instance.__dict__.update(fields) - instance.data = instance.data_decoded() - instance.senderUsername = instance.sender_username_decoded() - instance.receiverUsername = instance.receiver_username_encoded() - return instance +# if self.senderUsername: +# dictionary["senderUsername"] = self.sender_username_encoded() +# if self.receiverUsername: +# dictionary["receiverUsername"] = self.receiver_username_encoded() - def send(self, proxy: INetworkProvider): - if not self.signature: - raise errors.TransactionIsNotSigned() +# dictionary["gasPrice"] = self.gasPrice +# dictionary["gasLimit"] = self.gasLimit - logger.info(f"Transaction.send: nonce={self.nonce}") +# if self.data: +# dictionary["data"] = self.data_encoded() - self.hash = proxy.send_transaction(self) - logger.info(f"Hash: {self.hash}") - utils.log_explorer_transaction(self.chainID, self.hash) - return self.hash +# dictionary["chainID"] = self.chainID - def send_wait_result(self, proxy: INetworkProvider, timeout: int) -> ITransactionOnNetwork: - if not self.signature: - raise errors.TransactionIsNotSigned() +# if self.version: +# dictionary["version"] = int(self.version) - txOnNetwork = self.__send_transaction_and_wait_for_result(proxy, self, timeout) - self.hash = txOnNetwork.hash - return txOnNetwork +# if self.options: +# dictionary["options"] = int(self.options) - def __send_transaction_and_wait_for_result(self, proxy: INetworkProvider, payload: Any, num_seconds_timeout: int = 100) -> ITransactionOnNetwork: - AWAIT_TRANSACTION_PERIOD = 5 +# if self.guardian: +# dictionary["guardian"] = self.guardian - tx_hash = proxy.send_transaction(payload) - num_periods_to_wait = int(num_seconds_timeout / AWAIT_TRANSACTION_PERIOD) +# if self.signature: +# dictionary["signature"] = self.signature - for _ in range(0, num_periods_to_wait): - time.sleep(AWAIT_TRANSACTION_PERIOD) +# if self.guardianSignature: +# dictionary["guardianSignature"] = self.guardianSignature - tx = proxy.get_transaction(tx_hash) - if tx.is_completed: - return tx - else: - logger.info("Transaction not yet done.") +# return dictionary - raise errors.KnownError("Took too long to get transaction.") +# # Creates the payload for a "user" / "inner" transaction +# def to_dictionary_as_inner(self) -> Dict[str, Any]: +# dictionary = self.to_dictionary() +# dictionary["receiver"] = base64.b64encode(bytes.fromhex(Address.from_bech32(self.receiver).hex())).decode() +# dictionary["sender"] = base64.b64encode(bytes.fromhex(Address.from_bech32(self.sender).hex())).decode() +# dictionary["chainID"] = base64.b64encode(self.chainID.encode()).decode() +# dictionary["signature"] = base64.b64encode(bytes(bytearray.fromhex(self.signature))).decode() +# dictionary["value"] = int(self.value) - def to_dictionary(self) -> Dict[str, Any]: - dictionary: Dict[str, Any] = OrderedDict() - dictionary["nonce"] = self.nonce - dictionary["value"] = self.value - - dictionary["receiver"] = self.receiver - dictionary["sender"] = self.sender - - if self.senderUsername: - dictionary["senderUsername"] = self.sender_username_encoded() - if self.receiverUsername: - dictionary["receiverUsername"] = self.receiver_username_encoded() - - dictionary["gasPrice"] = self.gasPrice - dictionary["gasLimit"] = self.gasLimit - - if self.data: - dictionary["data"] = self.data_encoded() - - dictionary["chainID"] = self.chainID - - if self.version: - dictionary["version"] = int(self.version) - - if self.options: - dictionary["options"] = int(self.options) +# return dictionary - if self.guardian: - dictionary["guardian"] = self.guardian +# def wrap_inner(self, inner: ITransaction) -> None: +# self.data = inner.serialize_as_inner() - if self.signature: - dictionary["signature"] = self.signature +# def set_version(self, version: int): +# self.version = version - if self.guardianSignature: - dictionary["guardianSignature"] = self.guardianSignature +# def set_options(self, options: int): +# self.options = options - return dictionary +# def get_data(self) -> str: +# return self.data - # Creates the payload for a "user" / "inner" transaction - def to_dictionary_as_inner(self) -> Dict[str, Any]: - dictionary = self.to_dictionary() - dictionary["receiver"] = base64.b64encode(bytes.fromhex(Address.from_bech32(self.receiver).hex())).decode() - dictionary["sender"] = base64.b64encode(bytes.fromhex(Address.from_bech32(self.sender).hex())).decode() - dictionary["chainID"] = base64.b64encode(self.chainID.encode()).decode() - dictionary["signature"] = base64.b64encode(bytes(bytearray.fromhex(self.signature))).decode() - dictionary["value"] = int(self.value) +# def get_hash(self) -> str: +# return self.hash - return dictionary - def wrap_inner(self, inner: ITransaction) -> None: - self.data = inner.serialize_as_inner() - - def set_version(self, version: int): - self.version = version - - def set_options(self, options: int): - self.options = options - - def get_data(self) -> str: - return self.data - - def get_hash(self) -> str: - return self.hash +class JSONTransaction: + def __init__(self) -> None: + self.nonce = 0 + self.value = "0" + self.receiver = "" + self.sender = "" + self.senderUsername = "" + self.receiverUsername = "" + self.gasPrice = 0 + self.gasLimit = 0 + self.data: str = "" + self.chainID = "" + self.version = 0 + self.options = 0 + self.signature = "" + self.guardian = "" + self.guardianSignature = "" class BunchOfTransactions: @@ -231,23 +252,25 @@ def add_prepared(self, transaction: Transaction): self.transactions.append(transaction) def add(self, sender: Account, receiver_address: str, nonce: Any, value: Any, data: str, gas_price: int, - gas_limit: int, chain: str, version: int, options: int): - tx = Transaction() - tx.nonce = int(nonce) - tx.value = str(value) - tx.receiver = receiver_address - tx.sender = sender.address.bech32() - tx.gasPrice = gas_price - tx.gasLimit = gas_limit - tx.data = data - tx.chainID = chain - tx.version = version - tx.options = options - - tx.sign(sender) + gas_limit: int, chain: str, version: int, options: int, guardian_address: str): + tx = Transaction( + chain_id=chain, + sender=sender.address, + receiver=Address.from_bech32(receiver_address), + gas_limit=gas_limit, + gas_price=gas_price, + nonce=nonce, + value=value, + data=TransactionPayload.from_str(data), + version=version, + options=options, + guardian=Address.from_bech32(guardian_address) + ) + + tx.signature = bytes.fromhex(sender.sign_transaction(tx)) self.transactions.append(tx) - def add_tx(self, tx: Transaction): + def add_tx(self, tx: Transaction): # duplicated; same as `add_prepared()` self.transactions.append(tx) def send(self, proxy: INetworkProvider): @@ -268,22 +291,23 @@ def do_prepare_transaction(args: Any) -> Transaction: password = load_password(args) account = Account(key_file=args.keyfile, password=password) - tx = Transaction() - tx.nonce = int(args.nonce) - tx.value = args.value - tx.receiver = args.receiver - tx.sender = account.address.bech32() - tx.senderUsername = getattr(args, "sender_username", "") - tx.receiverUsername = getattr(args, "receiver_username", "") - tx.gasPrice = int(args.gas_price) - tx.gasLimit = int(args.gas_limit) - tx.data = args.data - tx.chainID = args.chain - tx.version = int(args.version) - tx.options = int(args.options) - tx.guardian = args.guardian - - tx.sign(account) + tx = Transaction( + chain_id=args.chain, + sender=account.address, + receiver=Address.from_bech32(args.receiver), + gas_limit=int(args.gas_limit), + sender_username=getattr(args, "sender_username", ""), + receiver_username=getattr(args, "receiver_username", ""), + gas_price=int(args.gas_price), + data=TransactionPayload.from_str(args.data), + nonce=int(args.nonce), + value=int(args.value), + version=int(args.version), + options=int(args.options), + guardian=Address.from_bech32(args.guardian) + ) + + tx.signature = bytes.fromhex(account.sign_transaction(tx)) tx = sign_tx_by_guardian(args, tx) return tx @@ -295,16 +319,17 @@ def sign_tx_by_guardian(args: Any, tx: Transaction) -> Transaction: except NoWalletProvided: guardian_account = None - # empty sender signature - sender_signature = tx.signature - tx.signature = "" + # TODO: might not be needed, will test + # # empty sender signature + # sender_signature = tx.signature + # tx.signature = b"" if guardian_account: - tx.guardianSignature = guardian_account.sign_transaction(tx) + tx.guardian_signature = bytes.fromhex(guardian_account.sign_transaction(tx)) elif args.guardian: tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) # type: ignore - tx.signature = sender_signature + # tx.signature = sender_signature return tx @@ -323,3 +348,75 @@ def get_guardian_account_from_args(args: Any): raise errors.NoWalletProvided() return account + + +def send_and_wait_for_result(transaction: Transaction, proxy: INetworkProvider, timeout: int) -> ITransactionOnNetwork: + if not transaction.signature: + raise errors.TransactionIsNotSigned() + + txOnNetwork = _send_transaction_and_wait_for_result(proxy, transaction, timeout) + return txOnNetwork + + +def _send_transaction_and_wait_for_result(proxy: INetworkProvider, payload: Transaction, num_seconds_timeout: int = 100) -> ITransactionOnNetwork: + AWAIT_TRANSACTION_PERIOD = 5 + + tx_hash = proxy.send_transaction(payload) + num_periods_to_wait = int(num_seconds_timeout / AWAIT_TRANSACTION_PERIOD) + + for _ in range(0, num_periods_to_wait): + time.sleep(AWAIT_TRANSACTION_PERIOD) + + tx = proxy.get_transaction(tx_hash) + if tx.is_completed: + return tx + else: + logger.info("Transaction not yet done.") + + raise errors.KnownError("Took too long to get transaction.") + + +def tx_to_dictionary_as_inner(tx: Transaction) -> Dict[str, Any]: + dictionary = tx.to_dictionary() + dictionary["receiver"] = base64.b64encode(tx.receiver.hex()).decode() # type: ignore + dictionary["sender"] = base64.b64encode(bytes.fromhex(tx.sender.hex())).decode() # type: ignore + dictionary["chainID"] = base64.b64encode(tx.chainID.encode()).decode() + dictionary["signature"] = base64.b64encode(bytes(bytearray(tx.signature))).decode() + dictionary["value"] = tx.value + + return dictionary + + +def load__transaction_from_file(f: TextIO) -> Transaction: + data_json: bytes = f.read().encode() + fields = json.loads(data_json).get("tx") or json.loads(data_json).get("emittedTransaction") + + instance = JSONTransaction() + instance.__dict__.update(fields) + + loaded_tx = Transaction( + chain_id=instance.chainID, + sender=Address.from_bech32(instance.sender), + receiver=Address.from_bech32(instance.receiver), + sender_username=decode_field_value(instance.senderUsername), + receiver_username=decode_field_value(instance.receiverUsername), + gas_limit=instance.gasLimit, + gas_price=instance.gasPrice, + value=int(instance.value), + data=TransactionPayload.from_encoded_str(instance.data), + version=instance.version, + options=instance.options, + guardian=Address.from_bech32(instance.guardian) + ) + + if instance.signature: + loaded_tx.signature = bytes.fromhex(instance.signature) + + if instance.guardianSignature: + loaded_tx.guardian_signature = bytes.fromhex(instance.guardianSignature) + + return loaded_tx + + +def decode_field_value(value: str) -> str: + return base64.b64decode(value).decode() diff --git a/requirements.txt b/requirements.txt index 214bd039..8074630c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,6 @@ semver requests-cache rich==13.3.4 -multiversx-sdk-core>=0.5.0 +multiversx-sdk-core==0.6.0b1 multiversx-sdk-network-providers==0.6.* multiversx-sdk-wallet==0.7.* From 5181741e4181d5ac711fbe6520a783fd30d1b459 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 23 Jun 2023 11:54:24 +0300 Subject: [PATCH 2/8] fully replace the `Transaction` class --- multiversx_sdk_cli/cli_password.py | 6 ++-- multiversx_sdk_cli/cli_transactions.py | 30 ++++++++++-------- multiversx_sdk_cli/contracts.py | 18 +++++++---- multiversx_sdk_cli/transactions.py | 42 +++++++++++++++++++------- multiversx_sdk_cli/validators/core.py | 4 +-- 5 files changed, 66 insertions(+), 34 deletions(-) diff --git a/multiversx_sdk_cli/cli_password.py b/multiversx_sdk_cli/cli_password.py index 227e5abf..d7fff56b 100644 --- a/multiversx_sdk_cli/cli_password.py +++ b/multiversx_sdk_cli/cli_password.py @@ -1,15 +1,15 @@ -from typing import Any from getpass import getpass +from typing import Any -def load_password(args: Any): +def load_password(args: Any) -> str: if args.passfile: with open(args.passfile) as pass_file: return pass_file.read().strip() return getpass("Keyfile's password: ") -def load_guardian_password(args: Any): +def load_guardian_password(args: Any) -> str: if args.guardian_passfile: with open(args.guardian_passfile) as pass_file: return pass_file.read().strip() diff --git a/multiversx_sdk_cli/cli_transactions.py b/multiversx_sdk_cli/cli_transactions.py index a25d2ceb..a3f643a5 100644 --- a/multiversx_sdk_cli/cli_transactions.py +++ b/multiversx_sdk_cli/cli_transactions.py @@ -1,12 +1,16 @@ from pathlib import Path from typing import Any, List +from multiversx_sdk_network_providers.proxy_network_provider import \ + ProxyNetworkProvider + from multiversx_sdk_cli import cli_shared, utils from multiversx_sdk_cli.cli_output import CLIOutputBuilder -from multiversx_sdk_network_providers.proxy_network_provider import ProxyNetworkProvider -from multiversx_sdk_cli.errors import NoWalletProvided -from multiversx_sdk_cli.transactions import Transaction, do_prepare_transaction from multiversx_sdk_cli.cosign_transaction import cosign_transaction +from multiversx_sdk_cli.errors import NoWalletProvided +from multiversx_sdk_cli.transactions import (do_prepare_transaction, + load__transaction_from_file, + serialize_as_inner) def setup_parser(args: List[str], subparsers: Any) -> Any: @@ -72,7 +76,7 @@ def create_transaction(args: Any): tx = do_prepare_transaction(args) if hasattr(args, "relay") and args.relay: - args.outfile.write(tx.serialize_as_inner()) + args.outfile.write(serialize_as_inner(tx)) return cli_shared.send_or_simulate(tx, args) @@ -81,10 +85,12 @@ def create_transaction(args: Any): def send_transaction(args: Any): args = utils.as_object(args) - tx = Transaction.load_from_file(args.infile) + tx = load__transaction_from_file(args.infile) try: - tx.send(ProxyNetworkProvider(args.proxy)) + proxy = ProxyNetworkProvider(args.proxy) + tx_hash = proxy.send_transaction(tx) + tx.hash = tx_hash finally: output = CLIOutputBuilder().set_emitted_transaction(tx).build() utils.dump_out_json(output, outfile=args.outfile) @@ -106,16 +112,16 @@ def sign_transaction(args: Any): cli_shared.check_guardian_args(args) cli_shared.check_broadcast_args(args) - tx = Transaction.load_from_file(args.infile) + tx = load__transaction_from_file(args.infile) if args.guardian: cli_shared.check_options_for_guarded_tx(tx.options) # clear existing signatures, if any - tx.guardianSignature = "" - tx.signature = "" + # tx.guardianSignature = "" + # tx.signature = "" account = cli_shared.prepare_account(args) - signature = account.sign_transaction(tx) + tx.signature = bytes.fromhex(account.sign_transaction(tx)) try: guardian_account = cli_shared.prepare_guardian_account(args) @@ -123,10 +129,10 @@ def sign_transaction(args: Any): guardian_account = None if guardian_account: - tx.guardianSignature = guardian_account.sign_transaction(tx) + tx.guardian_signature = bytes.fromhex(guardian_account.sign_transaction(tx)) elif args.guardian: tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) - tx.signature = signature + # tx.signature = signature cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 2c6634d2..fd800b7e 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -86,10 +86,12 @@ def deploy(self, owner: Account, arguments: List[Any], gas_price: int, gas_limit value=value, data=self.prepare_deploy_transaction_data(arguments), version=version, - options=options, - guardian=Address.from_bech32(guardian) + options=options ) + if guardian: + tx.guardian = Address.from_bech32(guardian) + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) return tx @@ -131,10 +133,12 @@ def execute(self, caller: Account, function: str, arguments: List[str], gas_pric value=value, data=self.prepare_execute_transaction_data(function, arguments), version=version, - options=options, - guardian=Address.from_bech32(guardian) + options=options ) + if guardian: + tx.guardian = Address.from_bech32(guardian) + tx.signature = bytes.fromhex(caller.sign_transaction(tx)) return tx @@ -165,10 +169,12 @@ def upgrade(self, owner: Account, arguments: List[Any], gas_price: int, gas_limi value=value, data=self.prepare_upgrade_transaction_data(arguments), version=version, - options=options, - guardian=Address.from_bech32(guardian) + options=options ) + if guardian: + tx.guardian = Address.from_bech32(guardian) + tx.signature = bytes.fromhex(owner.sign_transaction(tx)) return tx diff --git a/multiversx_sdk_cli/transactions.py b/multiversx_sdk_cli/transactions.py index f32031b7..e1ab9362 100644 --- a/multiversx_sdk_cli/transactions.py +++ b/multiversx_sdk_cli/transactions.py @@ -2,15 +2,12 @@ import json import logging import time -from ast import arg -from collections import OrderedDict -from itertools import chain -from typing import Any, Dict, List, Protocol, Sequence, TextIO, Tuple +from typing import Any, Dict, List, Optional, Protocol, Sequence, TextIO, Tuple from multiversx_sdk_core import Address, Transaction, TransactionPayload -from multiversx_sdk_cli import config, errors, utils -from multiversx_sdk_cli.accounts import Account, EmptyAddress, LedgerAccount +from multiversx_sdk_cli import errors +from multiversx_sdk_cli.accounts import Account, LedgerAccount from multiversx_sdk_cli.cli_password import (load_guardian_password, load_password) from multiversx_sdk_cli.cosign_transaction import cosign_transaction @@ -227,6 +224,7 @@ def get_transaction(self, tx_hash: str) -> ITransactionOnNetwork: class JSONTransaction: def __init__(self) -> None: + self.hash = "" self.nonce = 0 self.value = "0" self.receiver = "" @@ -252,7 +250,7 @@ def add_prepared(self, transaction: Transaction): self.transactions.append(transaction) def add(self, sender: Account, receiver_address: str, nonce: Any, value: Any, data: str, gas_price: int, - gas_limit: int, chain: str, version: int, options: int, guardian_address: str): + gas_limit: int, chain: str, version: int, options: int, guardian_address: Optional[str] = None): tx = Transaction( chain_id=chain, sender=sender.address, @@ -264,9 +262,11 @@ def add(self, sender: Account, receiver_address: str, nonce: Any, value: Any, da data=TransactionPayload.from_str(data), version=version, options=options, - guardian=Address.from_bech32(guardian_address) ) + if guardian_address: + tx.guardian = Address.from_bech32(guardian_address) + tx.signature = bytes.fromhex(sender.sign_transaction(tx)) self.transactions.append(tx) @@ -303,10 +303,12 @@ def do_prepare_transaction(args: Any) -> Transaction: nonce=int(args.nonce), value=int(args.value), version=int(args.version), - options=int(args.options), - guardian=Address.from_bech32(args.guardian) + options=int(args.options) ) + if args.guardian: + tx.guardian = Address.from_bech32(args.guardian) + tx.signature = bytes.fromhex(account.sign_transaction(tx)) tx = sign_tx_by_guardian(args, tx) @@ -387,6 +389,18 @@ def tx_to_dictionary_as_inner(tx: Transaction) -> Dict[str, Any]: return dictionary +def _dict_to_json(dictionary: Dict[str, Any]) -> bytes: + serialized = json.dumps(dictionary, separators=(',', ':')).encode("utf8") + return serialized + + +def serialize_as_inner(tx: Transaction) -> str: + inner_dictionary = tx_to_dictionary_as_inner(tx) + serialized = _dict_to_json(inner_dictionary) + serialized_hex = serialized.hex() + return f"relayedTx@{serialized_hex}" + + def load__transaction_from_file(f: TextIO) -> Transaction: data_json: bytes = f.read().encode() fields = json.loads(data_json).get("tx") or json.loads(data_json).get("emittedTransaction") @@ -406,15 +420,21 @@ def load__transaction_from_file(f: TextIO) -> Transaction: data=TransactionPayload.from_encoded_str(instance.data), version=instance.version, options=instance.options, - guardian=Address.from_bech32(instance.guardian) + nonce=instance.nonce ) + if instance.guardian: + loaded_tx.guardian = Address.from_bech32(instance.guardian) + if instance.signature: loaded_tx.signature = bytes.fromhex(instance.signature) if instance.guardianSignature: loaded_tx.guardian_signature = bytes.fromhex(instance.guardianSignature) + if instance.hash: + loaded_tx.hash: str = instance.hash + return loaded_tx diff --git a/multiversx_sdk_cli/validators/core.py b/multiversx_sdk_cli/validators/core.py index 69fd85a0..bc327ad6 100644 --- a/multiversx_sdk_cli/validators/core.py +++ b/multiversx_sdk_cli/validators/core.py @@ -71,9 +71,9 @@ def prepare_transaction_data_for_stake(node_operator_address: Address, validator call_arguments.append(f"0x{reward_address.hex()}") data = SmartContract().prepare_execute_transaction_data("stake", call_arguments) - gas_limit = estimate_system_sc_call(data, MetaChainSystemSCsCost.STAKE, num_of_nodes) + gas_limit = estimate_system_sc_call(str(data), MetaChainSystemSCsCost.STAKE, num_of_nodes) - return data, gas_limit + return str(data), gas_limit def prepare_args_for_top_up(args: Any): From 8c93d98beb7dd16eaafdecf978bc4d67bd9bf9f9 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 23 Jun 2023 11:54:43 +0300 Subject: [PATCH 3/8] fix tests --- multiversx_sdk_cli/tests/test_accounts.py | 56 +++++------ multiversx_sdk_cli/tests/test_transactions.py | 98 ------------------- 2 files changed, 28 insertions(+), 126 deletions(-) delete mode 100644 multiversx_sdk_cli/tests/test_transactions.py diff --git a/multiversx_sdk_cli/tests/test_accounts.py b/multiversx_sdk_cli/tests/test_accounts.py index 996b3007..c466d9d0 100644 --- a/multiversx_sdk_cli/tests/test_accounts.py +++ b/multiversx_sdk_cli/tests/test_accounts.py @@ -1,11 +1,10 @@ from pathlib import Path import pytest +from multiversx_sdk_core import Address, Transaction, TransactionPayload from multiversx_sdk_cli.accounts import Account -from multiversx_sdk_cli.transactions import Transaction from multiversx_sdk_cli.constants import DEFAULT_HRP -from multiversx_sdk_core import Address def test_address(): @@ -35,34 +34,35 @@ def test_sign_transaction(): alice = Account(pem_file=str(alice_pem)) # With data - transaction = Transaction() - transaction.nonce = 0 - transaction.value = "0" - transaction.sender = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - transaction.receiver = "erd188nydpkagtpwvfklkl2tn0w6g40zdxkwfgwpjqc2a2m2n7ne9g8q2t22sr" - transaction.gasPrice = 200000000000000 - transaction.gasLimit = 500000000 - transaction.data = "foo" - transaction.chainID = "chainID" - transaction.version = 1 - transaction.signature = alice.sign_transaction(transaction) - - assert "0e69f27e24aba2f3b7a8842dc7e7c085a0bfb5b29112b258318eed73de9c8809889756f8afaa74c7b3c7ce20a028b68ba90466a249aaf999a1a78dcf7f4eb40c" == transaction.signature + transaction = Transaction( + chain_id="chainID", + sender=Address.from_bech32("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"), + receiver=Address.from_bech32("erd188nydpkagtpwvfklkl2tn0w6g40zdxkwfgwpjqc2a2m2n7ne9g8q2t22sr"), + gas_limit=500000000, + gas_price=200000000000000, + nonce=0, + value=0, + data=TransactionPayload.from_str("foo"), + version=1 + ) + transaction.signature = bytes.fromhex(alice.sign_transaction(transaction)) + + assert "0e69f27e24aba2f3b7a8842dc7e7c085a0bfb5b29112b258318eed73de9c8809889756f8afaa74c7b3c7ce20a028b68ba90466a249aaf999a1a78dcf7f4eb40c" == transaction.signature.hex() # Without data - transaction = Transaction() - transaction.nonce = 0 - transaction.value = "0" - transaction.sender = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - transaction.receiver = "erd188nydpkagtpwvfklkl2tn0w6g40zdxkwfgwpjqc2a2m2n7ne9g8q2t22sr" - transaction.gasPrice = 200000000000000 - transaction.gasLimit = 500000000 - transaction.data = "" - transaction.chainID = "chainID" - transaction.version = 1 - transaction.signature = alice.sign_transaction(transaction) - - assert "83efd1bc35790ecc220b0ed6ddd1fcb44af6653dd74e37b3a49dcc1f002a1b98b6f79779192cca68bdfefd037bc81f4fa606628b751023122191f8c062362805" == transaction.signature + transaction = Transaction( + chain_id="chainID", + sender=Address.from_bech32("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"), + receiver=Address.from_bech32("erd188nydpkagtpwvfklkl2tn0w6g40zdxkwfgwpjqc2a2m2n7ne9g8q2t22sr"), + gas_limit=500000000, + gas_price=200000000000000, + nonce=0, + value=0, + version=1 + ) + transaction.signature = bytes.fromhex(alice.sign_transaction(transaction)) + + assert "83efd1bc35790ecc220b0ed6ddd1fcb44af6653dd74e37b3a49dcc1f002a1b98b6f79779192cca68bdfefd037bc81f4fa606628b751023122191f8c062362805" == transaction.signature.hex() def test_sign_message(): diff --git a/multiversx_sdk_cli/tests/test_transactions.py b/multiversx_sdk_cli/tests/test_transactions.py deleted file mode 100644 index 7396ba0c..00000000 --- a/multiversx_sdk_cli/tests/test_transactions.py +++ /dev/null @@ -1,98 +0,0 @@ -import logging - -from multiversx_sdk_cli.accounts import Account -from multiversx_sdk_cli.errors import GasLimitTooLarge -from multiversx_sdk_cli.tests.utils import MyTestCase -from multiversx_sdk_cli.transactions import Transaction - -logging.basicConfig(level=logging.INFO) - - -class TransactionsTestCase(MyTestCase): - def setUp(self): - super().setUp() - self.alice = Account(pem_file=str(self.devnet_wallets.joinpath("users", "alice.pem"))) - - def test_serialize_transaction_payload(self): - # With data field - transaction = Transaction() - transaction.nonce = 0 - transaction.value = "42" - transaction.sender = "alice" - transaction.receiver = "bob" - transaction.gasPrice = 43 - transaction.gasLimit = 44 - transaction.data = "foobar" - transaction.chainID = "BoN" - transaction.version = 1 - serialized = transaction.serialize().decode() - self.assertEqual("""{"nonce":0,"value":"42","receiver":"bob","sender":"alice","gasPrice":43,"gasLimit":44,"data":"Zm9vYmFy","chainID":"BoN","version":1}""", serialized) - - # Without data field - transaction.data = "" - serialized = transaction.serialize().decode() - self.assertEqual("""{"nonce":0,"value":"42","receiver":"bob","sender":"alice","gasPrice":43,"gasLimit":44,"chainID":"BoN","version":1}""", serialized) - - # With actual addresses - transaction = Transaction() - transaction.nonce = 0 - transaction.value = "0" - transaction.sender = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - transaction.receiver = "erd188nydpkagtpwvfklkl2tn0w6g40zdxkwfgwpjqc2a2m2n7ne9g8q2t22sr" - transaction.gasPrice = 200000000000000 - transaction.gasLimit = 500000000 - transaction.data = "foo" - transaction.chainID = "BoN" - transaction.version = 1 - serialized = transaction.serialize().decode() - self.assertEqual("""{"nonce":0,"value":"0","receiver":"erd188nydpkagtpwvfklkl2tn0w6g40zdxkwfgwpjqc2a2m2n7ne9g8q2t22sr","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":200000000000000,"gasLimit":500000000,"data":"Zm9v","chainID":"BoN","version":1}""", serialized) - - def test_serialize_transaction_with_usernames(self): - transaction = Transaction() - transaction.nonce = 89 - transaction.value = "0" - transaction.sender = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" - transaction.receiver = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" - transaction.senderUsername = "alice" - transaction.receiverUsername = "bob" - transaction.gasPrice = 1000000000 - transaction.gasLimit = 50000 - transaction.data = "" - transaction.chainID = "localnet" - transaction.version = 1 - - serialized = transaction.serialize().decode() - self.assertEqual("""{"nonce":89,"value":"0","receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","senderUsername":"YWxpY2U=","receiverUsername":"Ym9i","gasPrice":1000000000,"gasLimit":50000,"chainID":"localnet","version":1}""", serialized) - - transaction.sign(self.alice) - self.assertEqual("f872ea6d43b86ba6b22a7f62319a972124fc6dbd6b0743fa6465819a14d7a90100be865affb030bddf7ed86e648236cd20b1d5078e805b5419d43f3c5032b006", transaction.signature) - - def test_serialize_transaction_as_inner(self): - transaction = Transaction() - transaction.nonce = 0 - transaction.value = "0" - transaction.sender = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - transaction.receiver = "erd188nydpkagtpwvfklkl2tn0w6g40zdxkwfgwpjqc2a2m2n7ne9g8q2t22sr" - transaction.gasPrice = 200000000000000 - transaction.gasLimit = 500000000 - transaction.data = "foo" - transaction.chainID = "BoN" - transaction.version = 1 - transaction.sign(self.alice) - serialized = transaction.serialize_as_inner() - - self.assertEqual("relayedTx@7b226e6f6e6365223a302c2276616c7565223a302c227265636569766572223a224f655a47687431437775596d33376655756233615256346d6d73354b48426b4443757132716670354b67343d222c2273656e646572223a222f576b62746568644543614832424235332f7a6f517454634d6f4a323074544744592f5277304d384d704d3d222c226761735072696365223a3230303030303030303030303030302c226761734c696d6974223a3530303030303030302c2264617461223a225a6d3976222c22636861696e4944223a22516d394f222c2276657273696f6e223a312c227369676e6174757265223a224a38433741664278376f594e79746b713542564845367a4f6f7563596a796a4e79375a47484961485139674f3354384a75442f325147507650724f694f58444b5031744274643338596a667a37384f736a624a7743673d3d227d", serialized) - - def test_sign_when_gas_limit_too_large(self): - transaction = Transaction() - transaction.nonce = 0 - transaction.value = "0" - transaction.sender = "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" - transaction.receiver = "erd188nydpkagtpwvfklkl2tn0w6g40zdxkwfgwpjqc2a2m2n7ne9g8q2t22sr" - transaction.gasPrice = 200000000000000 - transaction.gasLimit = 1500000000 - transaction.data = "foo" - transaction.chainID = "chainID" - transaction.version = 1 - - self.assertRaises(GasLimitTooLarge, lambda: transaction.sign(self.alice)) From cf9f6174f1579c761668c7aba15312e2be6b1f5c Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Fri, 23 Jun 2023 16:48:39 +0300 Subject: [PATCH 4/8] code cleanup --- multiversx_sdk_cli/cli_contracts.py | 6 - multiversx_sdk_cli/cli_output.py | 9 +- multiversx_sdk_cli/cli_shared.py | 8 +- multiversx_sdk_cli/cli_transactions.py | 11 +- multiversx_sdk_cli/contracts.py | 10 +- multiversx_sdk_cli/cosign_transaction.py | 14 +- multiversx_sdk_cli/interfaces.py | 36 +--- multiversx_sdk_cli/tests/test_cli_dns.sh | 12 +- multiversx_sdk_cli/transactions.py | 199 +---------------------- 9 files changed, 37 insertions(+), 268 deletions(-) diff --git a/multiversx_sdk_cli/cli_contracts.py b/multiversx_sdk_cli/cli_contracts.py index 1a78e3e5..c4e78704 100644 --- a/multiversx_sdk_cli/cli_contracts.py +++ b/multiversx_sdk_cli/cli_contracts.py @@ -354,10 +354,6 @@ def _prepare_signer(args: Any) -> Account: def _sign_guarded_tx(args: Any, tx: Transaction) -> Transaction: - # not needed anymore (i think) - # signature = tx.signature - # tx.signature = bytes() - try: guardian_account = cli_shared.prepare_guardian_account(args) except NoWalletProvided: @@ -368,8 +364,6 @@ def _sign_guarded_tx(args: Any, tx: Transaction) -> Transaction: elif args.guardian: tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) # type: ignore - # tx.signature = signature - return tx diff --git a/multiversx_sdk_cli/cli_output.py b/multiversx_sdk_cli/cli_output.py index 62c30fbe..bbdc5e02 100644 --- a/multiversx_sdk_cli/cli_output.py +++ b/multiversx_sdk_cli/cli_output.py @@ -1,7 +1,7 @@ import json import logging from collections import OrderedDict -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Optional, Union from multiversx_sdk_core import Address @@ -14,6 +14,7 @@ class CLIOutputBuilder: def __init__(self) -> None: + self.emitted_transaction_hash: Optional[str] = None self.emitted_transaction: Union[ITransaction, None] = None self.emitted_transaction_omitted_fields: List[str] = [] self.contract_address: Union[Address, None] = None @@ -21,6 +22,10 @@ def __init__(self) -> None: self.transaction_on_network_omitted_fields: List[str] = [] self.simulation_results: Union[ISerializable, None] = None + def set_emitted_transaction_hash(self, hash: str): + self.emitted_transaction_hash = hash + return self + def set_emitted_transaction(self, emitted_transaction: ITransaction, omitted_fields: List[str] = []): self.emitted_transaction = emitted_transaction self.emitted_transaction_omitted_fields = omitted_fields @@ -47,7 +52,7 @@ def build(self) -> Dict[str, Any]: if self.emitted_transaction: emitted_transaction_dict = self.emitted_transaction.to_dictionary() - emitted_transaction_hash = self.emitted_transaction.hash if hasattr(self.emitted_transaction, "hash") else "" + emitted_transaction_hash = self.emitted_transaction_hash or "" emitted_transaction_data = str(self.emitted_transaction.data) utils.omit_fields(emitted_transaction_dict, self.emitted_transaction_omitted_fields) diff --git a/multiversx_sdk_cli/cli_shared.py b/multiversx_sdk_cli/cli_shared.py index b8b0f8bf..34aa4ccb 100644 --- a/multiversx_sdk_cli/cli_shared.py +++ b/multiversx_sdk_cli/cli_shared.py @@ -4,7 +4,7 @@ from argparse import FileType from typing import Any, List, Text, cast -from multiversx_sdk_core import Address, Transaction +from multiversx_sdk_core import Address from multiversx_sdk_network_providers.proxy_network_provider import \ ProxyNetworkProvider @@ -14,6 +14,7 @@ from multiversx_sdk_cli.cli_password import (load_guardian_password, load_password) from multiversx_sdk_cli.constants import TRANSACTION_OPTIONS_TX_GUARDED +from multiversx_sdk_cli.interfaces import ITransaction from multiversx_sdk_cli.ledger.ledger_functions import do_get_ledger_address from multiversx_sdk_cli.simulation import Simulator from multiversx_sdk_cli.transactions import send_and_wait_for_result @@ -218,7 +219,7 @@ def check_options_for_guarded_tx(options: int): raise errors.BadUsage("Invalid guarded transaction's options. The second least significant bit must be set.") -def send_or_simulate(tx: Transaction, args: Any, dump_output: bool = True) -> CLIOutputBuilder: +def send_or_simulate(tx: ITransaction, args: Any, dump_output: bool = True) -> CLIOutputBuilder: proxy = ProxyNetworkProvider(args.proxy) is_set_wait_result = hasattr(args, "wait_result") and args.wait_result @@ -238,7 +239,8 @@ def send_or_simulate(tx: Transaction, args: Any, dump_output: bool = True) -> CL transaction_on_network = send_and_wait_for_result(tx, proxy, args.timeout) output_builder.set_awaited_transaction(transaction_on_network) elif send_only: - proxy.send_transaction(tx) + hash = proxy.send_transaction(tx) + output_builder.set_emitted_transaction_hash(hash) elif simulate: simulation = Simulator(proxy).run(tx) output_builder.set_simulation_results(simulation) diff --git a/multiversx_sdk_cli/cli_transactions.py b/multiversx_sdk_cli/cli_transactions.py index a3f643a5..48a5c822 100644 --- a/multiversx_sdk_cli/cli_transactions.py +++ b/multiversx_sdk_cli/cli_transactions.py @@ -86,13 +86,14 @@ def send_transaction(args: Any): args = utils.as_object(args) tx = load__transaction_from_file(args.infile) + output = CLIOutputBuilder() try: proxy = ProxyNetworkProvider(args.proxy) tx_hash = proxy.send_transaction(tx) - tx.hash = tx_hash + output.set_emitted_transaction_hash(tx_hash) finally: - output = CLIOutputBuilder().set_emitted_transaction(tx).build() + output.set_emitted_transaction(tx).build() utils.dump_out_json(output, outfile=args.outfile) @@ -116,10 +117,6 @@ def sign_transaction(args: Any): if args.guardian: cli_shared.check_options_for_guarded_tx(tx.options) - # clear existing signatures, if any - # tx.guardianSignature = "" - # tx.signature = "" - account = cli_shared.prepare_account(args) tx.signature = bytes.fromhex(account.sign_transaction(tx)) @@ -133,6 +130,4 @@ def sign_transaction(args: Any): elif args.guardian: tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) - # tx.signature = signature - cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index fd800b7e..5bae3f92 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -4,7 +4,7 @@ from Cryptodome.Hash import keccak from multiversx_sdk_core import Address, Transaction, TransactionPayload -from multiversx_sdk_network_providers.interface import IContractQuery +from multiversx_sdk_network_providers.interface import IAddress, IContractQuery from multiversx_sdk_cli import config, constants, errors from multiversx_sdk_cli.accounts import Account, EmptyAddress @@ -32,14 +32,14 @@ def __init__(self, as_base64: str, as_hex: str, as_number: Optional[int]): class ContractQuery(IContractQuery): - def __init__(self, address: Address, function: str, value: int, arguments: List[bytes], caller: Optional[Address] = None): + def __init__(self, address: IAddress, function: str, value: int, arguments: List[bytes], caller: Optional[IAddress] = None): self.contract = address self.function = function self.caller = caller self.value = value self.encoded_arguments = [item.hex() for item in arguments] - def get_contract(self) -> Address: + def get_contract(self) -> IAddress: return self.contract def get_function(self) -> str: @@ -48,7 +48,7 @@ def get_function(self) -> str: def get_encoded_arguments(self) -> Sequence[str]: return self.encoded_arguments - def get_caller(self) -> Optional[Address]: + def get_caller(self) -> Optional[IAddress]: return self.caller def get_value(self) -> int: @@ -62,7 +62,7 @@ class IContractQueryResponse(Protocol): class SmartContract: - def __init__(self, address: Optional[Address] = None, bytecode=None, metadata=None): + def __init__(self, address: Optional[IAddress] = EmptyAddress(), bytecode=None, metadata=None): self.address = address self.bytecode = bytecode self.metadata = metadata or CodeMetadata() diff --git a/multiversx_sdk_cli/cosign_transaction.py b/multiversx_sdk_cli/cosign_transaction.py index bb715a12..6c5383dc 100644 --- a/multiversx_sdk_cli/cosign_transaction.py +++ b/multiversx_sdk_cli/cosign_transaction.py @@ -1,13 +1,9 @@ -import requests -from typing import Dict, Any, Protocol -from multiversx_sdk_cli.errors import GuardianServiceError - +from typing import Any, Dict -class ITransaction(Protocol): - guardianSignature: str +import requests - def to_dictionary(self) -> Dict[str, Any]: - ... +from multiversx_sdk_cli.errors import GuardianServiceError +from multiversx_sdk_cli.interfaces import ITransaction def cosign_transaction(transaction: ITransaction, service_url: str, guardian_code: str) -> ITransaction: @@ -21,7 +17,7 @@ def cosign_transaction(transaction: ITransaction, service_url: str, guardian_cod check_for_guardian_error(response.json()) tx_as_dict = response.json()["data"]["transaction"] - transaction.guardianSignature = tx_as_dict["guardianSignature"] + transaction.guardian_signature = bytes.fromhex(tx_as_dict["guardianSignature"]) return transaction diff --git a/multiversx_sdk_cli/interfaces.py b/multiversx_sdk_cli/interfaces.py index 88c7fab8..947433b2 100644 --- a/multiversx_sdk_cli/interfaces.py +++ b/multiversx_sdk_cli/interfaces.py @@ -1,7 +1,5 @@ from typing import Any, Dict, Protocol -# from multiversx_sdk_cli.utils import ISerializable - class IAddress(Protocol): def hex(self) -> str: @@ -11,35 +9,6 @@ def bech32(self) -> str: ... -# class ITransaction(ISerializable): -# def serialize(self) -> bytes: -# return bytes() - -# def serialize_for_signing(self) -> bytes: -# return bytes() - -# def serialize_as_inner(self) -> str: -# return '' - -# def to_dictionary(self) -> Dict[str, Any]: -# return {} - -# def to_dictionary_as_inner(self) -> Dict[str, Any]: -# return {} - -# def set_version(self, version: int): -# return - -# def set_options(self, options: int): -# return - -# def get_hash(self) -> str: -# return "" - -# def get_data(self) -> str: -# return "" - - ITransactionOptions = int ITransactionVersion = int ISignature = bytes @@ -54,10 +23,13 @@ def length(self) -> int: ... class ITransaction(Protocol): version: ITransactionVersion options: ITransactionOptions - data: ITransactionPayload signature: ISignature guardian_signature: ISignature + @property + def data(self) -> ITransactionPayload: + ... + def serialize_for_signing(self) -> bytes: ... diff --git a/multiversx_sdk_cli/tests/test_cli_dns.sh b/multiversx_sdk_cli/tests/test_cli_dns.sh index 9af261a0..fa3c9340 100644 --- a/multiversx_sdk_cli/tests/test_cli_dns.sh +++ b/multiversx_sdk_cli/tests/test_cli_dns.sh @@ -76,32 +76,32 @@ testRegistrationOffline() { } testTransactionsWithUsernamesOffline() { - ${CLI} --verbose tx new --pem=${TestUser} --receiver=${TestUser2} \ + ${CLI} --verbose tx new --pem=${TestUser} --receiver="erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" \ --value="1${DENOMINATION}" --nonce=42 --gas-limit=50000 --gas-price=2000000000 --chain=${CHAIN_ID} \ --outfile=${SANDBOX}/txA.txt || return 1 assertFileExists ${SANDBOX}/txA.txt || return 1 - ${CLI} --verbose tx new --pem=${TestUser} --receiver=${TestUser2} --receiver-username="testuser2" \ + ${CLI} --verbose tx new --pem=${TestUser} --receiver="erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" --receiver-username="testuser2" \ --value="1${DENOMINATION}" --nonce=43 --gas-limit=50000 --gas-price=2000000000 --chain=${CHAIN_ID} \ --outfile=${SANDBOX}/txB.txt || return 1 assertFileExists ${SANDBOX}/txB.txt || return 1 - ${CLI} --verbose tx new --pem=${TestUser} --receiver=${TestUser2} --receiver-username="testuser2foo" \ + ${CLI} --verbose tx new --pem=${TestUser} --receiver="erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" --receiver-username="testuser2foo" \ --value="1${DENOMINATION}" --nonce=44 --gas-limit=50000 --gas-price=2000000000 --chain=${CHAIN_ID} \ --outfile=${SANDBOX}/txC.txt || return 1 assertFileExists ${SANDBOX}/txC.txt || return 1 - ${CLI} --verbose tx new --pem=${TestUser} --sender-username="testuser" --receiver=${TestUser2} --receiver-username="testuser2" \ + ${CLI} --verbose tx new --pem=${TestUser} --sender-username="testuser" --receiver="erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" --receiver-username="testuser2" \ --value="1${DENOMINATION}" --nonce=45 --gas-limit=50000 --gas-price=2000000000 --chain=${CHAIN_ID} \ --outfile=${SANDBOX}/txD.txt || return 1 assertFileExists ${SANDBOX}/txD.txt || return 1 - ${CLI} --verbose tx new --pem=${TestUser} --sender-username="testuser" --receiver=${TestUser2} \ + ${CLI} --verbose tx new --pem=${TestUser} --sender-username="testuser" --receiver="erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" \ --value="1${DENOMINATION}" --nonce=46 --gas-limit=50000 --gas-price=2000000000 --chain=${CHAIN_ID} \ --outfile=${SANDBOX}/txF.txt || return 1 assertFileExists ${SANDBOX}/txF.txt || return 1 - ${CLI} --verbose tx new --pem=${TestUser} --sender-username="testuserfoo" --receiver=${TestUser2} \ + ${CLI} --verbose tx new --pem=${TestUser} --sender-username="testuserfoo" --receiver="erd1ssmsc9022udc8pdw7wk3hxw74jr900xg28vwpz3z60gep66fasasl2nkm4" \ --value="1${DENOMINATION}" --nonce=47 --gas-limit=50000 --gas-price=2000000000 --chain=${CHAIN_ID} \ --outfile=${SANDBOX}/txG.txt || return 1 assertFileExists ${SANDBOX}/txG.txt || return 1 diff --git a/multiversx_sdk_cli/transactions.py b/multiversx_sdk_cli/transactions.py index e1ab9362..99127493 100644 --- a/multiversx_sdk_cli/transactions.py +++ b/multiversx_sdk_cli/transactions.py @@ -37,191 +37,6 @@ def get_transaction(self, tx_hash: str) -> ITransactionOnNetwork: ... -# class Transaction(ITransaction): -# def __init__(self): -# self.hash: str = "" -# self.nonce = 0 -# self.value = "0" -# self.receiver = "" -# self.sender = "" -# self.senderUsername = "" -# self.receiverUsername = "" -# self.gasPrice = 0 -# self.gasLimit = 0 -# self.data: str = "" -# self.chainID = "" -# self.version = 0 -# self.options = 0 -# self.signature = "" -# self.guardian = "" -# self.guardianSignature = "" - -# # The data field is base64-encoded. Here, we only support utf-8 "data" at this moment. -# def data_encoded(self) -> str: -# return self._field_encoded("data") - -# # Useful when loading a tx from a file (when data is already encoded in base64) -# def data_decoded(self) -> str: -# return self._field_decoded("data") - -# def sender_username_encoded(self) -> str: -# return self._field_encoded("senderUsername") - -# def sender_username_decoded(self) -> str: -# return self._field_decoded("senderUsername") - -# def receiver_username_encoded(self) -> str: -# return self._field_encoded("receiverUsername") - -# def receiver_username_decoded(self) -> str: -# return self._field_decoded("receiverUsername") - -# def _field_encoded(self, field: str) -> str: -# field_bytes = self.__dict__.get(field, "").encode("utf-8") -# encoded = base64.b64encode(field_bytes).decode() -# return encoded - -# def _field_decoded(self, field: str) -> str: -# return base64.b64decode(self.__dict__.get(field, None)).decode() - -# def sign(self, account: Account): -# self.validate() -# self.signature = account.sign_transaction(self) - -# def validate(self) -> None: -# if self.gasLimit > config.MAX_GAS_LIMIT: -# raise errors.GasLimitTooLarge(self.gasLimit, config.MAX_GAS_LIMIT) - -# def serialize(self) -> bytes: -# dictionary = self.to_dictionary() -# serialized = self._dict_to_json(dictionary) -# return serialized - -# def serialize_for_signing(self) -> bytes: -# return self.serialize() - -# def _dict_to_json(self, dictionary: Dict[str, Any]) -> bytes: -# serialized = json.dumps(dictionary, separators=(',', ':')).encode("utf8") -# return serialized - -# def serialize_as_inner(self) -> str: -# inner_dictionary = self.to_dictionary_as_inner() -# serialized = self._dict_to_json(inner_dictionary) -# serialized_hex = serialized.hex() -# return f"relayedTx@{serialized_hex}" - -# @classmethod -# def load_from_file(cls, f: TextIO): -# data_json: bytes = f.read().encode() -# fields = json.loads(data_json).get("tx") or json.loads(data_json).get("emittedTransaction") -# instance = cls() -# instance.__dict__.update(fields) -# instance.data = instance.data_decoded() -# instance.senderUsername = instance.sender_username_decoded() -# instance.receiverUsername = instance.receiver_username_encoded() -# return instance - -# def send(self, proxy: INetworkProvider): -# if not self.signature: -# raise errors.TransactionIsNotSigned() - -# logger.info(f"Transaction.send: nonce={self.nonce}") - -# self.hash = proxy.send_transaction(self) -# logger.info(f"Hash: {self.hash}") -# utils.log_explorer_transaction(self.chainID, self.hash) -# return self.hash - -# def send_wait_result(self, proxy: INetworkProvider, timeout: int) -> ITransactionOnNetwork: -# if not self.signature: -# raise errors.TransactionIsNotSigned() - -# txOnNetwork = self.__send_transaction_and_wait_for_result(proxy, self, timeout) -# self.hash = txOnNetwork.hash -# return txOnNetwork - -# def __send_transaction_and_wait_for_result(self, proxy: INetworkProvider, payload: Any, num_seconds_timeout: int = 100) -> ITransactionOnNetwork: -# AWAIT_TRANSACTION_PERIOD = 5 - -# tx_hash = proxy.send_transaction(payload) -# num_periods_to_wait = int(num_seconds_timeout / AWAIT_TRANSACTION_PERIOD) - -# for _ in range(0, num_periods_to_wait): -# time.sleep(AWAIT_TRANSACTION_PERIOD) - -# tx = proxy.get_transaction(tx_hash) -# if tx.is_completed: -# return tx -# else: -# logger.info("Transaction not yet done.") - -# raise errors.KnownError("Took too long to get transaction.") - -# def to_dictionary(self) -> Dict[str, Any]: -# dictionary: Dict[str, Any] = OrderedDict() -# dictionary["nonce"] = self.nonce -# dictionary["value"] = self.value - -# dictionary["receiver"] = self.receiver -# dictionary["sender"] = self.sender - -# if self.senderUsername: -# dictionary["senderUsername"] = self.sender_username_encoded() -# if self.receiverUsername: -# dictionary["receiverUsername"] = self.receiver_username_encoded() - -# dictionary["gasPrice"] = self.gasPrice -# dictionary["gasLimit"] = self.gasLimit - -# if self.data: -# dictionary["data"] = self.data_encoded() - -# dictionary["chainID"] = self.chainID - -# if self.version: -# dictionary["version"] = int(self.version) - -# if self.options: -# dictionary["options"] = int(self.options) - -# if self.guardian: -# dictionary["guardian"] = self.guardian - -# if self.signature: -# dictionary["signature"] = self.signature - -# if self.guardianSignature: -# dictionary["guardianSignature"] = self.guardianSignature - -# return dictionary - -# # Creates the payload for a "user" / "inner" transaction -# def to_dictionary_as_inner(self) -> Dict[str, Any]: -# dictionary = self.to_dictionary() -# dictionary["receiver"] = base64.b64encode(bytes.fromhex(Address.from_bech32(self.receiver).hex())).decode() -# dictionary["sender"] = base64.b64encode(bytes.fromhex(Address.from_bech32(self.sender).hex())).decode() -# dictionary["chainID"] = base64.b64encode(self.chainID.encode()).decode() -# dictionary["signature"] = base64.b64encode(bytes(bytearray.fromhex(self.signature))).decode() -# dictionary["value"] = int(self.value) - -# return dictionary - -# def wrap_inner(self, inner: ITransaction) -> None: -# self.data = inner.serialize_as_inner() - -# def set_version(self, version: int): -# self.version = version - -# def set_options(self, options: int): -# self.options = options - -# def get_data(self) -> str: -# return self.data - -# def get_hash(self) -> str: -# return self.hash - - class JSONTransaction: def __init__(self) -> None: self.hash = "" @@ -321,18 +136,11 @@ def sign_tx_by_guardian(args: Any, tx: Transaction) -> Transaction: except NoWalletProvided: guardian_account = None - # TODO: might not be needed, will test - # # empty sender signature - # sender_signature = tx.signature - # tx.signature = b"" - if guardian_account: tx.guardian_signature = bytes.fromhex(guardian_account.sign_transaction(tx)) elif args.guardian: tx = cosign_transaction(tx, args.guardian_service_url, args.guardian_2fa_code) # type: ignore - # tx.signature = sender_signature - return tx @@ -352,7 +160,7 @@ def get_guardian_account_from_args(args: Any): return account -def send_and_wait_for_result(transaction: Transaction, proxy: INetworkProvider, timeout: int) -> ITransactionOnNetwork: +def send_and_wait_for_result(transaction: ITransaction, proxy: INetworkProvider, timeout: int) -> ITransactionOnNetwork: if not transaction.signature: raise errors.TransactionIsNotSigned() @@ -360,7 +168,7 @@ def send_and_wait_for_result(transaction: Transaction, proxy: INetworkProvider, return txOnNetwork -def _send_transaction_and_wait_for_result(proxy: INetworkProvider, payload: Transaction, num_seconds_timeout: int = 100) -> ITransactionOnNetwork: +def _send_transaction_and_wait_for_result(proxy: INetworkProvider, payload: ITransaction, num_seconds_timeout: int = 100) -> ITransactionOnNetwork: AWAIT_TRANSACTION_PERIOD = 5 tx_hash = proxy.send_transaction(payload) @@ -432,9 +240,6 @@ def load__transaction_from_file(f: TextIO) -> Transaction: if instance.guardianSignature: loaded_tx.guardian_signature = bytes.fromhex(instance.guardianSignature) - if instance.hash: - loaded_tx.hash: str = instance.hash - return loaded_tx From a33e3894509dda7eb120ee99873c285e53df7a52 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 10 Jul 2023 13:37:54 +0300 Subject: [PATCH 5/8] return `TransactionPayload` from `prepare_deploy_tx_data` --- multiversx_sdk_cli/contracts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multiversx_sdk_cli/contracts.py b/multiversx_sdk_cli/contracts.py index 1e9dce37..267d9922 100644 --- a/multiversx_sdk_cli/contracts.py +++ b/multiversx_sdk_cli/contracts.py @@ -101,7 +101,7 @@ def prepare_deploy_transaction_data(self, arguments: List[Any]) -> TransactionPa for arg in arguments: tx_data += f"@{_prepare_argument(arg)}" - return tx_data + return TransactionPayload.from_str(tx_data) def execute(self, caller: Account, function: str, arguments: List[str], gas_price: int, gas_limit: int, value: int, chain: str, version: int, guardian: str, options: int) -> Transaction: self.caller = caller From ba304c8d9be8a990c53cee6fd6d4c15ffc7bfffb Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 10 Jul 2023 14:14:24 +0300 Subject: [PATCH 6/8] fixes --- multiversx_sdk_cli/cli_transactions.py | 8 ++++---- multiversx_sdk_cli/transactions.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/multiversx_sdk_cli/cli_transactions.py b/multiversx_sdk_cli/cli_transactions.py index 48a5c822..693571f6 100644 --- a/multiversx_sdk_cli/cli_transactions.py +++ b/multiversx_sdk_cli/cli_transactions.py @@ -9,7 +9,7 @@ from multiversx_sdk_cli.cosign_transaction import cosign_transaction from multiversx_sdk_cli.errors import NoWalletProvided from multiversx_sdk_cli.transactions import (do_prepare_transaction, - load__transaction_from_file, + load_transaction_from_file, serialize_as_inner) @@ -85,7 +85,7 @@ def create_transaction(args: Any): def send_transaction(args: Any): args = utils.as_object(args) - tx = load__transaction_from_file(args.infile) + tx = load_transaction_from_file(args.infile) output = CLIOutputBuilder() try: @@ -93,7 +93,7 @@ def send_transaction(args: Any): tx_hash = proxy.send_transaction(tx) output.set_emitted_transaction_hash(tx_hash) finally: - output.set_emitted_transaction(tx).build() + output = output.set_emitted_transaction(tx).build() utils.dump_out_json(output, outfile=args.outfile) @@ -113,7 +113,7 @@ def sign_transaction(args: Any): cli_shared.check_guardian_args(args) cli_shared.check_broadcast_args(args) - tx = load__transaction_from_file(args.infile) + tx = load_transaction_from_file(args.infile) if args.guardian: cli_shared.check_options_for_guarded_tx(tx.options) diff --git a/multiversx_sdk_cli/transactions.py b/multiversx_sdk_cli/transactions.py index 99127493..c635d297 100644 --- a/multiversx_sdk_cli/transactions.py +++ b/multiversx_sdk_cli/transactions.py @@ -188,7 +188,7 @@ def _send_transaction_and_wait_for_result(proxy: INetworkProvider, payload: ITra def tx_to_dictionary_as_inner(tx: Transaction) -> Dict[str, Any]: dictionary = tx.to_dictionary() - dictionary["receiver"] = base64.b64encode(tx.receiver.hex()).decode() # type: ignore + dictionary["receiver"] = base64.b64encode(bytes.fromhex(tx.receiver.hex())).decode() # type: ignore dictionary["sender"] = base64.b64encode(bytes.fromhex(tx.sender.hex())).decode() # type: ignore dictionary["chainID"] = base64.b64encode(tx.chainID.encode()).decode() dictionary["signature"] = base64.b64encode(bytes(bytearray(tx.signature))).decode() @@ -209,7 +209,7 @@ def serialize_as_inner(tx: Transaction) -> str: return f"relayedTx@{serialized_hex}" -def load__transaction_from_file(f: TextIO) -> Transaction: +def load_transaction_from_file(f: TextIO) -> Transaction: data_json: bytes = f.read().encode() fields = json.loads(data_json).get("tx") or json.loads(data_json).get("emittedTransaction") From dd0bee1f127483e1e37f3015f7b65e45e2585e49 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Mon, 10 Jul 2023 15:31:38 +0300 Subject: [PATCH 7/8] remove BunchOfTransactions --- multiversx_sdk_cli/tests/test_accounts.py | 23 ------------- multiversx_sdk_cli/transactions.py | 41 +---------------------- 2 files changed, 1 insertion(+), 63 deletions(-) diff --git a/multiversx_sdk_cli/tests/test_accounts.py b/multiversx_sdk_cli/tests/test_accounts.py index c466d9d0..5c66d6cf 100644 --- a/multiversx_sdk_cli/tests/test_accounts.py +++ b/multiversx_sdk_cli/tests/test_accounts.py @@ -4,29 +4,6 @@ from multiversx_sdk_core import Address, Transaction, TransactionPayload from multiversx_sdk_cli.accounts import Account -from multiversx_sdk_cli.constants import DEFAULT_HRP - - -def test_address(): - address = Address.from_bech32("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz") - address_cloned = Address.from_bech32(address.bech32()) - assert "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" == address.hex() - assert "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" == address.bech32() - assert address.hex() == address_cloned.hex() - assert address.bech32() == address_cloned.bech32() - - address = Address.from_hex("fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293", DEFAULT_HRP) - address_cloned = Address.from_bech32(address.bech32()) - assert "fd691bb5e85d102687d81079dffce842d4dc328276d2d4c60d8fd1c3433c3293" == address.hex() - assert "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz" == address.bech32() - assert address.hex() == address_cloned.hex() - assert address.bech32() == address_cloned.bech32() - - with pytest.raises(Exception): - address = Address(b"", DEFAULT_HRP) - - with pytest.raises(Exception): - address = Address.from_bech32("bad") def test_sign_transaction(): diff --git a/multiversx_sdk_cli/transactions.py b/multiversx_sdk_cli/transactions.py index c635d297..928d6f1d 100644 --- a/multiversx_sdk_cli/transactions.py +++ b/multiversx_sdk_cli/transactions.py @@ -2,7 +2,7 @@ import json import logging import time -from typing import Any, Dict, List, Optional, Protocol, Sequence, TextIO, Tuple +from typing import Any, Dict, Protocol, Sequence, TextIO, Tuple from multiversx_sdk_core import Address, Transaction, TransactionPayload @@ -57,45 +57,6 @@ def __init__(self) -> None: self.guardianSignature = "" -class BunchOfTransactions: - def __init__(self): - self.transactions: List[Transaction] = [] - - def add_prepared(self, transaction: Transaction): - self.transactions.append(transaction) - - def add(self, sender: Account, receiver_address: str, nonce: Any, value: Any, data: str, gas_price: int, - gas_limit: int, chain: str, version: int, options: int, guardian_address: Optional[str] = None): - tx = Transaction( - chain_id=chain, - sender=sender.address, - receiver=Address.from_bech32(receiver_address), - gas_limit=gas_limit, - gas_price=gas_price, - nonce=nonce, - value=value, - data=TransactionPayload.from_str(data), - version=version, - options=options, - ) - - if guardian_address: - tx.guardian = Address.from_bech32(guardian_address) - - tx.signature = bytes.fromhex(sender.sign_transaction(tx)) - self.transactions.append(tx) - - def add_tx(self, tx: Transaction): # duplicated; same as `add_prepared()` - self.transactions.append(tx) - - def send(self, proxy: INetworkProvider): - logger.info(f"BunchOfTransactions.send: {len(self.transactions)} transactions") - num_sent, hashes = proxy.send_transactions(self.transactions) - logger.info(f"Sent: {num_sent}") - logger.info(f"TxsHashes: {hashes}") - return num_sent, hashes - - def do_prepare_transaction(args: Any) -> Transaction: account = Account() if args.ledger: From 7ae35fa3d96aa90bedfaddd56fa5dbad0d53a77e Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 12 Jul 2023 17:54:27 +0300 Subject: [PATCH 8/8] fix after review --- multiversx_sdk_cli/cli_transactions.py | 8 ++++---- multiversx_sdk_cli/transactions.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/multiversx_sdk_cli/cli_transactions.py b/multiversx_sdk_cli/cli_transactions.py index 693571f6..6c303342 100644 --- a/multiversx_sdk_cli/cli_transactions.py +++ b/multiversx_sdk_cli/cli_transactions.py @@ -8,9 +8,9 @@ from multiversx_sdk_cli.cli_output import CLIOutputBuilder from multiversx_sdk_cli.cosign_transaction import cosign_transaction from multiversx_sdk_cli.errors import NoWalletProvided -from multiversx_sdk_cli.transactions import (do_prepare_transaction, - load_transaction_from_file, - serialize_as_inner) +from multiversx_sdk_cli.transactions import (compute_relayed_v1_data, + do_prepare_transaction, + load_transaction_from_file) def setup_parser(args: List[str], subparsers: Any) -> Any: @@ -76,7 +76,7 @@ def create_transaction(args: Any): tx = do_prepare_transaction(args) if hasattr(args, "relay") and args.relay: - args.outfile.write(serialize_as_inner(tx)) + args.outfile.write(compute_relayed_v1_data(tx)) return cli_shared.send_or_simulate(tx, args) diff --git a/multiversx_sdk_cli/transactions.py b/multiversx_sdk_cli/transactions.py index 928d6f1d..6805a95c 100644 --- a/multiversx_sdk_cli/transactions.py +++ b/multiversx_sdk_cli/transactions.py @@ -163,7 +163,7 @@ def _dict_to_json(dictionary: Dict[str, Any]) -> bytes: return serialized -def serialize_as_inner(tx: Transaction) -> str: +def compute_relayed_v1_data(tx: Transaction) -> str: inner_dictionary = tx_to_dictionary_as_inner(tx) serialized = _dict_to_json(inner_dictionary) serialized_hex = serialized.hex()