Exemple #1
0
    def build_contract_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: int = None,
    ) -> dict:

        payload = self.build_payload(
            sender_address=sender_address,
            payload=payload,
            transaction_gas_limit=transaction_gas_limit)
        self.__log_transaction(transaction_dict=payload,
                               contract_function=contract_function)
        try:
            transaction_dict = contract_function.buildTransaction(
                payload)  # Gas estimation occurs here
        except (TestTransactionFailed, ValidationError, ValueError) as error:
            # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed" during gas estimation.
            raise self._handle_failed_transaction(
                exception=error,
                transaction_dict=payload,
                contract_function=contract_function,
                logger=self.log)
        return transaction_dict
Exemple #2
0
    def execute_transaction(self, contract_function: ContractFunction, **kwargs) -> str:
        """
        This function WILL result in immediate network calls (e.g. to get the gas price, nonce and gas cost), even
        though it is written in sync manner.

        :param contract_function:
        :param kwargs:
        :return:
        """
        if self._network_status is not NetworkStatus.CONNECTED:
            raise EnvironmentError("Cannot send transactions when network status is not connected.")

        gas_price: int = self.gas_price
        transaction_args: Dict[str, Any] = {
            "from": self.address,
            "nonce": self.nonce,
            "chainId": self.chain.value,
            "gasPrice": gas_price,
        }
        transaction_args.update(kwargs)
        transaction: Dict[str, Any] = contract_function.buildTransaction(transaction_args)
        if "gas" not in transaction:
            estimate_gas: int = 1000000
            try:
                estimate_gas = self._w3.eth.estimateGas(transaction)
            except ValueError:
                self.logger().error(f"Failed to estimate gas. Using default of 1000000.")
            transaction["gas"] = estimate_gas
        signed_transaction: AttributeDict = self._account.signTransaction(transaction)
        tx_hash: str = signed_transaction.hash.hex()
        self.schedule_eth_transaction(signed_transaction, gas_price)
        return tx_hash
 def execute_transaction(self, contract_function: ContractFunction,
                         **kwargs) -> str:
     gas_price: int = self.gas_price
     transaction_args: Dict[str, Any] = {
         "from": self.address,
         "nonce": self.nonce,
         "chainId": self.chain.value,
         "gasPrice": gas_price,
     }
     transaction_args.update(kwargs)
     transaction: Dict[str, Any] = contract_function.buildTransaction(
         transaction_args)
     if "gas" not in transaction:
         estimate_gas: int = 1000000
         try:
             estimate_gas = self._w3.eth.estimateGas(transaction)
         except ValueError:
             self.logger().error(
                 f"Failed to estimate gas. Using default of 1000000.")
         transaction["gas"] = estimate_gas
     signed_transaction: AttributeDict = self._account.signTransaction(
         transaction)
     tx_hash: str = signed_transaction.hash.hex()
     self.schedule_eth_transaction(signed_transaction, gas_price)
     return tx_hash
Exemple #4
0
    def build_contract_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: Optional[int] = None,
        gas_estimation_multiplier: Optional[float] = None,
        use_pending_nonce: Optional[bool] = None,
    ) -> dict:

        if transaction_gas_limit is not None:
            self.log.warn(
                "The transaction gas limit of {transaction_gas_limit} will override gas estimation attempts"
            )

        # Sanity checks for the gas estimation multiplier
        if gas_estimation_multiplier is not None:
            if not 1 <= gas_estimation_multiplier <= 3:  # Arbitrary upper bound.
                raise ValueError(
                    f"The gas estimation multiplier should be a float between 1 and 3, "
                    f"but we received {gas_estimation_multiplier}.")

        payload = self.build_payload(
            sender_address=sender_address,
            payload=payload,
            transaction_gas_limit=transaction_gas_limit,
            use_pending_nonce=use_pending_nonce)
        self.__log_transaction(transaction_dict=payload,
                               contract_function=contract_function)
        try:
            if 'gas' not in payload:  # i.e., transaction_gas_limit is not None
                # As web3 buildTransaction() will estimate gas with block identifier "pending" by default,
                # explicitly estimate gas here with block identifier 'latest' if not otherwise specified
                # as a pending transaction can cause gas estimation to fail, notably in case of worklock refunds.
                payload['gas'] = contract_function.estimateGas(
                    payload, block_identifier='latest')
            transaction_dict = contract_function.buildTransaction(payload)
        except (TestTransactionFailed, ValidationError, ValueError) as error:
            # Note: Geth (1.9.15) raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed" during gas estimation.
            raise self._handle_failed_transaction(
                exception=error,
                transaction_dict=payload,
                contract_function=contract_function,
                logger=self.log)

        # Increase the estimated gas limit according to the gas estimation multiplier, if any.
        if gas_estimation_multiplier and not transaction_gas_limit:
            gas_estimation = transaction_dict['gas']
            overestimation = int(
                math.ceil(gas_estimation * gas_estimation_multiplier))
            self.log.debug(
                f"Gas limit for this TX was increased from {gas_estimation} to {overestimation}, "
                f"using a multiplier of {gas_estimation_multiplier}.")
            transaction_dict['gas'] = overestimation
            # TODO: What if we're going over the block limit? Not likely, but perhaps worth checking (NRN)

        return transaction_dict
Exemple #5
0
    def build_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: int = None,
    ) -> dict:

        #
        # Build
        #

        if not payload:
            payload = {}

        nonce = self.client.w3.eth.getTransactionCount(sender_address,
                                                       'pending')
        payload.update({
            'chainId': int(self.client.chain_id),
            'nonce': nonce,
            'from': sender_address,
            'gasPrice': self.client.gas_price
        })

        if transaction_gas_limit:
            payload['gas'] = int(transaction_gas_limit)

        # Get transaction type
        deployment = isinstance(contract_function, ContractConstructor)
        try:
            transaction_name = contract_function.fn_name.upper()
        except AttributeError:
            transaction_name = 'DEPLOY' if deployment else 'UNKNOWN'

        payload_pprint = dict(payload)
        payload_pprint['from'] = to_checksum_address(payload['from'])
        payload_pprint = ', '.join("{}: {}".format(k, v)
                                   for k, v in payload_pprint.items())
        self.log.debug(f"[TX-{transaction_name}] | {payload_pprint}")

        # Build transaction payload
        try:
            unsigned_transaction = contract_function.buildTransaction(payload)
        except (ValidationError, ValueError) as e:
            # TODO: #1504 - Handle validation failures for gas limits, invalid fields, etc.
            # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed".
            error = str(e).replace("{", "").replace("}", "")  # See #724
            self.log.critical(f"Validation error: {error}")
            raise
        else:
            if deployment:
                self.log.info(
                    f"Deploying contract: {len(unsigned_transaction['data'])} bytes"
                )

        return unsigned_transaction
Exemple #6
0
 def build_and_send_tx(self,
                       func: ContractFunction,
                       tx_params: Optional[TxParams] = None) -> HexBytes:
     if not tx_params:
         tx_params = self.get_tx_params()
     transaction = func.buildTransaction(tx_params)
     signed_tx = self.w3.eth.account.sign_transaction(
         transaction, private_key=self.secrets._pk)
     try:
         return self.w3.eth.send_raw_transaction(signed_tx.rawTransaction)
     finally:
         self.last_nonce = Nonce(tx_params["nonce"] + 1)
Exemple #7
0
    def build_contract_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: Optional[int] = None,
        gas_estimation_multiplier: Optional[float] = None,
        use_pending_nonce: Optional[bool] = None,
    ) -> dict:

        # Sanity checks for the gas estimation multiplier
        if gas_estimation_multiplier is not None:
            if not 1 <= gas_estimation_multiplier <= 3:  # TODO: Arbitrary upper bound.
                raise ValueError(
                    f"The gas estimation multiplier should be a float between 1 and 3, "
                    f"but we received {gas_estimation_multiplier}.")
            elif transaction_gas_limit is not None:
                raise ValueError(
                    "'transaction_gas_limit' and 'gas_estimation_multiplier' can't be used together."
                )

        payload = self.build_payload(
            sender_address=sender_address,
            payload=payload,
            transaction_gas_limit=transaction_gas_limit,
            use_pending_nonce=use_pending_nonce)
        self.__log_transaction(transaction_dict=payload,
                               contract_function=contract_function)
        try:
            transaction_dict = contract_function.buildTransaction(
                payload)  # Gas estimation occurs here
        except (TestTransactionFailed, ValidationError, ValueError) as error:
            # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed" during gas estimation.
            raise self._handle_failed_transaction(
                exception=error,
                transaction_dict=payload,
                contract_function=contract_function,
                logger=self.log)

        # Overestimate the transaction gas limit according to the gas estimation multiplier, if any
        if gas_estimation_multiplier:
            gas_estimation = transaction_dict['gas']
            overestimation = int(
                math.ceil(gas_estimation * gas_estimation_multiplier))
            self.log.debug(
                f"Gas limit for this TX was increased from {gas_estimation} to {overestimation}, "
                f"using a multiplier of {gas_estimation_multiplier}.")
            transaction_dict['gas'] = overestimation
            # TODO: What if we're going over the block limit? Not likely, but perhaps worth checking (NRN)

        return transaction_dict
 def estimate_transaction_cost(self, contract_function: ContractFunction,
                               **kwargs) -> int:
     transaction_args: Dict[str, Any] = {
         "from": self.address,
         "nonce": self.nonce,
         "chainId": self.chain.value,
         "gasPrice": self.gas_price,
     }
     transaction_args.update(kwargs)
     transaction: Dict[str, Any] = contract_function.buildTransaction(
         transaction_args)
     gas_estimate = self._w3.eth.estimateGas(transaction)
     return gas_estimate * self.gas_price
Exemple #9
0
    def build_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: int = None,
    ) -> dict:

        #
        # Build Payload
        #

        base_payload = {
            'chainId':
            int(self.client.chain_id),
            'nonce':
            self.client.w3.eth.getTransactionCount(sender_address, 'pending'),
            'from':
            sender_address,
            'gasPrice':
            self.client.gas_price
        }

        # Aggregate
        if not payload:
            payload = {}
        payload.update(base_payload)
        # Explicit gas override - will skip gas estimation in next operation.
        if transaction_gas_limit:
            payload['gas'] = int(transaction_gas_limit)

        #
        # Build Transaction
        #

        self.__log_transaction(transaction_dict=payload,
                               contract_function=contract_function)
        try:
            transaction_dict = contract_function.buildTransaction(
                payload)  # Gas estimation occurs here
        except (TestTransactionFailed, ValidationError, ValueError) as error:
            # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed" during gas estimation.
            raise self.__transaction_failed(
                exception=error,
                transaction_dict=payload,
                contract_function=contract_function)
        return transaction_dict
Exemple #10
0
 def _build_and_send_tx(self,
                        function: ContractFunction,
                        tx_params: Optional[TxParams] = None) -> HexBytes:
     """Build and send a transaction."""
     if not tx_params:
         tx_params = self._get_tx_params()
     transaction = function.buildTransaction(tx_params)
     signed_txn = self.w3.eth.account.sign_transaction(
         transaction, private_key=self.private_key)
     # TODO: This needs to get more complicated if we want to support replacing a transaction
     # FIXME: This does not play nice if transactions are sent from other places using the same wallet.
     try:
         return self.w3.eth.sendRawTransaction(signed_txn.rawTransaction)
     finally:
         logger.debug(f"nonce: {tx_params['nonce']}")
         self.last_nonce = Nonce(tx_params["nonce"] + 1)
def build_and_send_tx(function: ContractFunction,
                      tx_params: Optional[TxParams] = None) -> HexBytes:
    """
    Build and send a transaction.
    """
    if not tx_params:
        tx_params = get_tx_params()

    transaction = function.buildTransaction(tx_params)
    signed_txn = w3.eth.account.sign_transaction(transaction,
                                                 private_key=PRIVATE_KEY)

    logging.info(signed_txn.rawTransaction)

    # !--- Commented for test.
    # return w3.eth.sendRawTransaction(signed_txn.rawTransaction)
    return w3.eth.send_raw_transaction(signed_txn.rawTransaction)
Exemple #12
0
    def _build_and_send_tx(self,
                           function: ContractFunction,
                           tx_params: Optional[TxParams] = None) -> HexBytes:
        """Build and send a transaction."""
        if not tx_params:
            tx_params = self._get_tx_params()
        transaction = function.buildTransaction(tx_params)
        signed_txn = self.w3.eth.account.sign_transaction(
            transaction, private_key=self.private_key)
        try:
            return self.w3.eth.sendRawTransaction(signed_txn.rawTransaction)
        finally:
            print("Print TX = ",
                  self.w3.toHex(self.w3.keccak(signed_txn.rawTransaction)))
            logger.debug(f"nonce: {tx_params['nonce']}")
            self.last_nonce = Nonce(tx_params["nonce"] + 1)
            #LOGG TX TO JSON
            with open('transactions.json', 'r') as fp:
                data = json.load(fp)
            tx_input = tx_params
            data.append(tx_input)
            with open('transactions.json', 'w') as fp:
                json.dump(data, fp, indent=2)
            fp.close()
            last_tx = self.w3.toHex(self.w3.keccak(signed_txn.rawTransaction))

            hash = [{'hash': last_tx}]
            with open('hash.json', 'w') as fp:
                json.dump(hash, fp, indent=2)
            fp.close()

            print("Waiting for TX to Confirm")
            while True:
                try:
                    if self.w3.eth.getTransactionReceipt(last_tx) != None:
                        print("Transaction Confirmed!")
                        break
                    else:
                        print("waiting for tx to confirm")
                except Exception:
                    pass
                time.sleep(5)
    def send_transaction(
        self,
        transaction_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
    ) -> dict:

        if self.transacting_power is READ_ONLY_INTERFACE:
            raise self.InterfaceError(str(READ_ONLY_INTERFACE))

        #
        # Build
        #

        if not payload:
            payload = {}

        nonce = self.client.w3.eth.getTransactionCount(sender_address)
        payload.update({
            'chainId': int(self.client.chain_id),
            'nonce': nonce,
            'from': sender_address,
            'gasPrice': self.client.w3.eth.gasPrice
        })

        unsigned_transaction = transaction_function.buildTransaction(payload)

        #
        # Broadcast
        #

        signed_raw_transaction = self.transacting_power.sign_transaction(
            unsigned_transaction)
        txhash = self.client.send_raw_transaction(signed_raw_transaction)

        try:
            receipt = self.client.wait_for_receipt(txhash,
                                                   timeout=self.TIMEOUT)
        except TimeExhausted:
            raise

        #
        # Confirm
        #

        # Primary check
        deployment_status = receipt.get('status', UNKNOWN_TX_STATUS)
        if deployment_status is 0:
            failure = f"Transaction transmitted, but receipt returned status code 0. " \
                      f"Full receipt: \n {pprint.pformat(receipt, indent=2)}"
            raise self.InterfaceError(failure)

        if deployment_status is UNKNOWN_TX_STATUS:
            self.log.info(
                f"Unknown transaction status for {txhash} (receipt did not contain a status field)"
            )

            # Secondary check TODO: Is this a sensible check?
            tx = self.client.get_transaction(txhash)
            if tx["gas"] == receipt["gasUsed"]:
                raise self.InterfaceError(
                    f"Transaction consumed 100% of transaction gas."
                    f"Full receipt: \n {pprint.pformat(receipt, indent=2)}")

        return receipt
Exemple #14
0
    def send_transaction(
        self,
        contract_function: ContractFunction,
        sender_address: str,
        payload: dict = None,
        transaction_gas_limit: int = None,
    ) -> dict:

        if self.transacting_power is READ_ONLY_INTERFACE:
            raise self.InterfaceError(str(READ_ONLY_INTERFACE))

        #
        # Build
        #

        if not payload:
            payload = {}

        nonce = self.client.w3.eth.getTransactionCount(sender_address,
                                                       'pending')
        payload.update({
            'chainId': int(self.client.chain_id),
            'nonce': nonce,
            'from': sender_address,
            'gasPrice': self.client.gas_price
        })

        if transaction_gas_limit:
            payload['gas'] = int(transaction_gas_limit)

        # Get interface name
        deployment = True if isinstance(contract_function,
                                        ContractConstructor) else False

        try:
            transaction_name = contract_function.fn_name.upper()
        except AttributeError:
            if deployment:
                transaction_name = 'DEPLOY'
            else:
                transaction_name = 'UNKNOWN'

        payload_pprint = dict(payload)
        payload_pprint['from'] = to_checksum_address(payload['from'])
        payload_pprint = ', '.join("{}: {}".format(k, v)
                                   for k, v in payload_pprint.items())
        self.log.debug(f"[TX-{transaction_name}] | {payload_pprint}")

        # Build transaction payload
        try:
            unsigned_transaction = contract_function.buildTransaction(payload)
        except (ValidationError, ValueError) as e:
            # TODO: #1504 - Handle validation failures for gas limits, invalid fields, etc.
            # Note: Geth raises ValueError in the same condition that pyevm raises ValidationError here.
            # Treat this condition as "Transaction Failed".
            error = str(e).replace("{", "{{").replace("}", "}}")  # See #724
            self.log.critical(f"Validation error: {error}")
            raise
        else:
            if deployment:
                self.log.info(
                    f"Deploying contract: {len(unsigned_transaction['data'])} bytes"
                )

        #
        # Broadcast
        #

        signed_raw_transaction = self.transacting_power.sign_transaction(
            unsigned_transaction)
        txhash = self.client.send_raw_transaction(signed_raw_transaction)

        try:
            receipt = self.client.wait_for_receipt(txhash,
                                                   timeout=self.TIMEOUT)
        except TimeExhausted:
            # TODO: #1504 - Handle transaction timeout
            raise
        else:
            self.log.debug(
                f"[RECEIPT-{transaction_name}] | txhash: {receipt['transactionHash'].hex()}"
            )

        #
        # Confirm
        #

        # Primary check
        deployment_status = receipt.get('status', UNKNOWN_TX_STATUS)
        if deployment_status == 0:
            failure = f"Transaction transmitted, but receipt returned status code 0. " \
                      f"Full receipt: \n {pprint.pformat(receipt, indent=2)}"
            raise self.InterfaceError(failure)

        if deployment_status is UNKNOWN_TX_STATUS:
            self.log.info(
                f"Unknown transaction status for {txhash} (receipt did not contain a status field)"
            )

            # Secondary check
            tx = self.client.get_transaction(txhash)
            if tx["gas"] == receipt["gasUsed"]:
                raise self.InterfaceError(
                    f"Transaction consumed 100% of transaction gas."
                    f"Full receipt: \n {pprint.pformat(receipt, indent=2)}")

        return receipt
Exemple #15
0
    def send_transaction(self,
                         contract_function: ContractFunction,
                         sender_address: str,
                         payload: dict = None,
                         ) -> dict:

        if self.transacting_power is READ_ONLY_INTERFACE:
            raise self.InterfaceError(str(READ_ONLY_INTERFACE))

        #
        # Build
        #

        if not payload:
            payload = {}

        nonce = self.client.w3.eth.getTransactionCount(sender_address)
        payload.update({'chainId': int(self.client.chain_id),
                        'nonce': nonce,
                        'from': sender_address,
                        'gasPrice': self.client.w3.eth.gasPrice})

        # Get interface name
        try:
            transaction_name = contract_function.fn_name.upper()
        except AttributeError:
            if isinstance(contract_function, ContractConstructor):
                transaction_name = 'DEPLOY'
            else:
                transaction_name = 'UNKNOWN'

        # Build transaction payload
        try:
            unsigned_transaction = contract_function.buildTransaction(payload)
        except ValidationError:
            # TODO: Handle validation failures for gas limits, invalid fields, etc.
            raise

        #
        # Broadcast
        #

        signed_raw_transaction = self.transacting_power.sign_transaction(unsigned_transaction)
        txhash = self.client.send_raw_transaction(signed_raw_transaction)
        self.log.debug(f"[TX-{transaction_name}] | {to_checksum_address(payload['from'])}")

        try:
            receipt = self.client.wait_for_receipt(txhash, timeout=self.TIMEOUT)
        except TimeExhausted:
            # TODO: Handle transaction timeout
            raise
        else:
            self.log.debug(f"[RECEIPT-{transaction_name}] | {receipt['transactionHash'].hex()}")

        #
        # Confirm
        #

        # Primary check
        deployment_status = receipt.get('status', UNKNOWN_TX_STATUS)
        if deployment_status is 0:
            failure = f"Transaction transmitted, but receipt returned status code 0. " \
                      f"Full receipt: \n {pprint.pformat(receipt, indent=2)}"
            raise self.InterfaceError(failure)

        if deployment_status is UNKNOWN_TX_STATUS:
            self.log.info(f"Unknown transaction status for {txhash} (receipt did not contain a status field)")

            # Secondary check TODO: Is this a sensible check?
            tx = self.client.get_transaction(txhash)
            if tx["gas"] == receipt["gasUsed"]:
                raise self.InterfaceError(f"Transaction consumed 100% of transaction gas."
                                          f"Full receipt: \n {pprint.pformat(receipt, indent=2)}")

        return receipt