Exemplo n.º 1
0
    def _create_escrow(self, gas: int = GAS_LIMIT) -> bool:
        """Launches a new escrow contract to the ethereum network.

        >>> credentials = {
        ... 	"gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92",
        ... 	"gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"
        ... }
        >>> job = Job(credentials, manifest)
        >>> job._create_escrow()
        True

        Args:
            gas (int): maximum amount of gas the caller is ready to pay.

        Returns:
            bool: returns True if a new job was successfully launched to the network.

        Raises:
            TimeoutError: if wait_on_transaction times out.

        """
        txn_func = self.factory_contract.functions.createEscrow
        txn_info = {
            "gas_payer": self.gas_payer,
            "gas_payer_priv": self.gas_payer_priv,
            "gas": gas
        }

        handle_transaction(txn_func, *[], **txn_info)
        return True
Exemplo n.º 2
0
    def abort(self, gas: int = GAS_LIMIT) -> bool:
        """Kills the contract and returns the HMT back to the gas payer.
        The contract cannot be aborted if the contract is in Partial, Paid or Complete state.

        >>> credentials = {
        ... 	"gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92",
        ... 	"gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"
        ... }
        >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d"
        >>> job = Job(credentials, manifest)

        The escrow contract is in Pending state after setup so it can be aborted.
        >>> job.launch(rep_oracle_pub_key)
        True
        >>> job.setup()
        True
        >>> job.abort()
        True

        The escrow contract is in Partial state after the first payout and it can't be aborted.
        >>> job = Job(credentials, manifest)
        >>> job.launch(rep_oracle_pub_key)
        True
        >>> job.setup()
        True
        >>> payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0'))]
        >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key)
        True
        >>> job.abort()
        False
        >>> job.status()
        <Status.Partial: 3>

        The escrow contract is in Paid state after the second payout and it can't be aborted.
        >>> payouts = [("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('80.0'))]
        >>> job.bulk_payout(payouts, {'results': 0}, rep_oracle_pub_key)
        True
        >>> job.abort()
        False
        >>> job.status()
        <Status.Paid: 4>

        Returns:
            bool: returns True if contract has been destroyed successfully.

        """
        txn_func = self.job_contract.functions.abort
        txn_info = {
            "gas_payer": self.gas_payer,
            "gas_payer_priv": self.gas_payer_priv,
            "gas": gas
        }

        handle_transaction(txn_func, *[], **txn_info)

        # After abort the contract should be destroyed
        w3 = get_w3()
        contract_code = w3.eth.getCode(self.job_contract.address)
        return contract_code == b"\x00"
Exemplo n.º 3
0
    def cancel(self, gas: int = GAS_LIMIT) -> bool:
        """Returns the HMT back to the gas payer. It's the softer version of abort as the contract is not destroyed.

        >>> credentials = {
        ... 	"gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92",
        ... 	"gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"
        ... }
        >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d"
        >>> job = Job(credentials, manifest)

        The escrow contract is in Pending state after setup so it can be cancelled.
        >>> job.launch(rep_oracle_pub_key)
        True
        >>> job.setup()
        True
        >>> job.cancel()
        True

        Contract balance is zero and status is "Cancelled".
        >>> job.balance()
        0
        >>> job.status()
        <Status.Cancelled: 6>

        The escrow contract is in Partial state after the first payout and it can't be cancelled.
        >>> job = Job(credentials, manifest)
        >>> job.launch(rep_oracle_pub_key)
        True
        >>> job.setup()
        True
        >>> payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0'))]
        >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key)
        True
        >>> job.status()
        <Status.Partial: 3>

        The escrow contract is in Paid state after the second payout and it can't be cancelled.
        >>> payouts = [("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('80.0'))]
        >>> job.bulk_payout(payouts, {'results': 0}, rep_oracle_pub_key)
        True
        >>> job.cancel()
        False
        >>> job.status()
        <Status.Paid: 4>

        Returns:
            bool: returns True if gas payer has been paid back and contract is in "Cancelled" state.

        """
        txn_func = self.job_contract.functions.cancel
        txn_info = {
            "gas_payer": self.gas_payer,
            "gas_payer_priv": self.gas_payer_priv,
            "gas": gas
        }

        handle_transaction(txn_func, *[], **txn_info)
        return self.status() == Status.Cancelled
Exemplo n.º 4
0
    def complete(self, gas: int = GAS_LIMIT) -> bool:
        """Completes the Job if it has been paid.

        >>> credentials = {
        ... 	"gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92",
        ... 	"gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"
        ... }
        >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d"
        >>> job = Job(credentials, manifest)
        >>> job.launch(rep_oracle_pub_key)
        True
        >>> job.setup()
        True
        >>> payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0'))]
        >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key)
        True

        A Job can't be completed when it is still in partially paid state.
        >>> job.status()
        <Status.Partial: 3>
        >>> job.complete()
        False

        Job completes in paid state correctly.
        >>> payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('80.0'))]
        >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key)
        True
        >>> job.complete()
        True
        >>> job.status()
        <Status.Complete: 5>

        Returns:
            bool: returns True if the contract has been completed.

        """
        txn_func = self.job_contract.functions.complete
        txn_info = {
            "gas_payer": self.gas_payer,
            "gas_payer_priv": self.gas_payer_priv,
            "gas": gas
        }

        handle_transaction(txn_func, *[], **txn_info)
        return self.status() == Status.Complete
Exemplo n.º 5
0
    def store_intermediate_results(self,
                                   results: Dict,
                                   pub_key: bytes,
                                   gas: int = GAS_LIMIT) -> bool:
        """Recording Oracle stores intermediate results with Reputation Oracle's public key to IPFS
        and updates the contract's state.

        >>> credentials = {
        ... 	"gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92",
        ... 	"gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"
        ... }
        >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d"
        >>> job = Job(credentials, manifest)
        >>> job.launch(rep_oracle_pub_key)
        True
        >>> job.setup()
        True

        Storing intermediate results uploads and updates results url correctly.
        >>> results = {"results": True}
        >>> job.store_intermediate_results(results, rep_oracle_pub_key)
        True
        >>> rep_oracle_priv_key = b"28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"
        >>> job.intermediate_results(rep_oracle_priv_key)
        {'results': True}

        Args:
            results (Dict): intermediate results of the Recording Oracle.
            pub_key (bytes): public key of the Reputation Oracle.

        Returns:
            returns True if contract's state is updated and IPFS upload succeeds.

        """
        (hash_, url) = upload(results, pub_key)
        txn_func = self.job_contract.functions.storeResults
        func_args = [url, hash_]
        txn_info = {
            "gas_payer": self.gas_payer,
            "gas_payer_priv": self.gas_payer_priv,
            "gas": gas
        }

        handle_transaction(txn_func, *func_args, **txn_info)
        return True
Exemplo n.º 6
0
    def test_handle_transaction(self):
        from web3.datastructures import AttributeDict as Web3AttributeDict

        self.assertTrue(self.job.launch(self.rep_oracle_pub_key))
        gas = 4712388
        hmt_amount = int(self.job.amount * 10**18)
        hmtoken_contract = get_hmtoken()
        txn_func = hmtoken_contract.functions.transfer
        func_args = [self.job.job_contract.address, hmt_amount]
        txn_info = {
            "gas_payer": self.job.gas_payer,
            "gas_payer_priv": self.job.gas_payer_priv,
            "gas": gas,
        }
        txn_receipt = handle_transaction(txn_func, *func_args, **txn_info)
        self.assertIs(type(txn_receipt), Web3AttributeDict)
Exemplo n.º 7
0
    def bulk_payout(self,
                    payouts: List[Tuple[str, Decimal]],
                    results: Dict,
                    pub_key: bytes,
                    gas: int = GAS_LIMIT) -> bool:
        """Performs a payout to multiple ethereum addresses. When the payout happens,
        final results are uploaded to IPFS and contract's state is updated to Partial or Paid
        depending on contract's balance.

        >>> credentials = {
        ... 	"gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92",
        ... 	"gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"
        ... }
        >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d"
        >>> job = Job(credentials, manifest)
        >>> job.launch(rep_oracle_pub_key)
        True
        >>> job.setup()
        True
        >>> payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0')), ("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('50.0'))]
        >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key)
        True

        The escrow contract is still in Partial state as there's still balance left.
        >>> job.balance()
        30000000000000000000
        >>> job.status()
        <Status.Partial: 3>

        Trying to pay more than the contract balance results in failure.
        >>> payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal('40.0'))]
        >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key)
        False

        Paying the remaining amount empties the escrow and updates the status correctly.
        >>> payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal('30.0'))]
        >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key)
        True
        >>> job.balance()
        0
        >>> job.status()
        <Status.Paid: 4>

        Args:
            payouts (List[Tuple[str, int]]): a list of tuples with ethereum addresses and amounts.
            results (Dict): the final answer results stored by the Reputation Oracle.
            pub_key (bytes): the public key of the Reputation Oracle.

        Returns:
            bool: returns True if paying to ethereum addresses and oracles succeeds.

        """
        (hash_, url) = upload(results, pub_key)
        eth_addrs = [eth_addr for eth_addr, amount in payouts]
        hmt_amounts = [int(amount * 10**18) for eth_addr, amount in payouts]

        txn_func = self.job_contract.functions.bulkPayOut
        func_args = [eth_addrs, hmt_amounts, url, hash_, 1]
        txn_info = {
            "gas_payer": self.gas_payer,
            "gas_payer_priv": self.gas_payer_priv,
            "gas": gas
        }

        handle_transaction(txn_func, *func_args, **txn_info)
        return self._bulk_paid() == True
Exemplo n.º 8
0
    def setup(self, gas: int = GAS_LIMIT) -> bool:
        """Sets the escrow contract to be ready to receive answers from the Recording Oracle.
        The contract needs to be deployed and funded first.

        >>> credentials = {
        ... 	"gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92",
        ... 	"gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"
        ... }
        >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d"
        >>> job = Job(credentials, manifest)

        A Job can't be setup without deploying it first.
        >>> job.setup()
        Traceback (most recent call last):
        AttributeError: 'Job' object has no attribute 'job_contract'

        >>> job.launch(rep_oracle_pub_key)
        True
        >>> job.setup()
        True

        Returns:
            bool: returns True if Job is in Pending state.

        Raises:
            AttributeError: if trying to setup the job before deploying it.

        """
        # Prepare setup arguments for the escrow contract.
        reputation_oracle_stake = int(
            Decimal(self.serialized_manifest["oracle_stake"]) * 100)
        recording_oracle_stake = int(
            Decimal(self.serialized_manifest["oracle_stake"]) * 100)
        reputation_oracle = str(
            self.serialized_manifest["reputation_oracle_addr"])
        recording_oracle = str(
            self.serialized_manifest["recording_oracle_addr"])
        hmt_amount = int(self.amount * 10**18)

        # Fund the escrow contract with HMT.
        hmtoken_contract = get_hmtoken()
        txn_func = hmtoken_contract.functions.transfer
        func_args = [self.job_contract.address, hmt_amount]
        txn_info = {
            "gas_payer": self.gas_payer,
            "gas_payer_priv": self.gas_payer_priv,
            "gas": gas
        }
        handle_transaction(txn_func, *func_args, **txn_info)

        # Setup the escrow contract with manifest and IPFS data.
        txn_func = self.job_contract.functions.setup
        func_args = [
            reputation_oracle, recording_oracle, reputation_oracle_stake,
            recording_oracle_stake, self.manifest_url, self.manifest_hash
        ]
        txn_info = {
            "gas_payer": self.gas_payer,
            "gas_payer_priv": self.gas_payer_priv,
            "gas": gas
        }
        handle_transaction(txn_func, *func_args, **txn_info)
        return self.status() == Status.Pending and self.balance() == hmt_amount
Exemplo n.º 9
0
    def store_intermediate_results(self,
                                   results: Dict,
                                   pub_key: bytes,
                                   gas: int = GAS_LIMIT,
                                   store_onchain: bool = True) -> bool:
        """Recording Oracle stores intermediate results with Reputation Oracle's public key to IPFS
        and updates the contract's state.

        Args:
            results (Dict): intermediate results of the Recording Oracle.
            pub_key (bytes): public key of the Reputation Oracle.
            gas (int): gas limit
            store_onchain (bool): false is don't run the blockchain fn.

        Returns:
            returns True if contract's state is updated and IPFS upload succeeds.

        >>> credentials = {
        ... 	"gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92",
        ... 	"gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"
        ... }
        >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d"
        >>> job = Job(credentials, manifest)
        >>> job.launch(rep_oracle_pub_key)
        True
        >>> job.setup()
        True

        >>> rep_oracle_priv_key = b"28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"

        Store intermediate results in IPNS
        >>> results = {"results": True}
        >>> job.store_intermediate_results(results, rep_oracle_pub_key)
        True
        >>> job.intermediate_results(rep_oracle_priv_key)
        {'results': True}

        Store intermediate results in IPNS. This time we see if IPNS link will update.
        >>> results = {"results": False}
        >>> job.store_intermediate_results(results, rep_oracle_pub_key)
        True
        >>> job.intermediate_results(rep_oracle_priv_key)
        {'results': False}
        """
        (hash_, url) = upload(
            results,
            pub_key,
            ipns_keypair_name=
            f'intermediate-results-{self.job_contract.address}')

        if store_onchain:
            txn_func = self.job_contract.functions.storeResults
            func_args = [url, hash_]
            txn_info = {
                "gas_payer": self.gas_payer,
                "gas_payer_priv": self.gas_payer_priv,
                "gas": gas
            }
            handle_transaction(txn_func, *func_args, **txn_info)

        return True