Example #1
0
    def create_n_deposits_with_subtasks(self, n=1, amount=2):
        for _ in range(n):
            task_to_compute = self._get_deserialized_task_to_compute(
                provider_public_key=self._get_provider_hex_public_key(),
                requestor_public_key=self._get_requestor_hex_public_key(),
                price=amount,
            )
            store_subtask(
                task_id=task_to_compute.task_id,
                subtask_id=task_to_compute.subtask_id,
                provider_public_key=self.PROVIDER_PUBLIC_KEY,
                requestor_public_key=self.REQUESTOR_PUBLIC_KEY,
                state=Subtask.SubtaskState.ACCEPTED,
                next_deadline=None,
                task_to_compute=task_to_compute,
                report_computed_task=factories.tasks.ReportComputedTaskFactory(
                    task_to_compute=task_to_compute))

            deposit_claim = DepositClaim()
            deposit_claim.subtask_id = task_to_compute.subtask_id
            deposit_claim.payer_deposit_account = self.deposit_account
            deposit_claim.payee_ethereum_address = task_to_compute.provider_ethereum_address
            deposit_claim.concent_use_case = ConcentUseCase.FORCED_ACCEPTANCE
            deposit_claim.amount = amount
            deposit_claim.clean()
            deposit_claim.save()
        return deposit_claim, task_to_compute
Example #2
0
def finalize_payment(deposit_claim: DepositClaim) -> Optional[str]:
    """
    This operation tells Bankster to pay out funds from deposit.
    For each claim, Bankster uses SCI to submit an Ethereum transaction to the Ethereum client which then propagates it
    to the rest of the network.
    Hopefully the transaction is included in one of the upcoming blocks on the blockchain.

    IMPORTANT!: This function must never be called in parallel for the same DepositClaim - otherwise provider might get
    paid twice for the same thing. It's caller responsibility to ensure that.
    """

    assert isinstance(deposit_claim, DepositClaim)
    assert deposit_claim.tx_hash is None

    # Bankster asks SCI about the amount of funds available on the deposit account listed in the DepositClaim.
    available_funds = service.get_deposit_value(  # pylint: disable=no-value-for-parameter
        client_eth_address=deposit_claim.payer_deposit_account.ethereum_address
    )

    # Bankster begins a database transaction and puts a database lock on the DepositAccount object.
    with non_nesting_atomic(using='control'):
        DepositAccount.objects.select_for_update().get(
            pk=deposit_claim.payer_deposit_account_id)

        # Bankster sums the amounts of all existing DepositClaims that have the same payer as the one being processed.
        aggregated_client_claims = DepositClaim.objects.filter(
            payer_deposit_account=deposit_claim.payer_deposit_account).exclude(
                pk=deposit_claim.pk).aggregate(
                    sum_of_existing_claims=Coalesce(Sum('amount'), 0))

        # Bankster subtracts that value from the amount of funds available in the deposit.
        available_funds_without_claims = available_funds - aggregated_client_claims[
            'sum_of_existing_claims']

        # If the result is negative or zero, Bankster removes the DepositClaim object being processed.
        if available_funds_without_claims <= 0:
            deposit_claim.delete()
            return None

        # Otherwise if the result is lower than DepositAccount.amount,
        # Bankster sets this field to the amount that's actually available.
        elif available_funds_without_claims < deposit_claim.amount:
            deposit_claim.amount = available_funds_without_claims
            deposit_claim.save()

    # If the DepositClaim still exists at this point, Bankster uses SCI to create an Ethereum transaction.
    subtask = Subtask.objects.filter(
        subtask_id=deposit_claim.subtask_id).first()  # pylint: disable=no-member
    task_to_compute: TaskToCompute = deserialize_message(
        subtask.task_to_compute.data.tobytes())
    v, r, s = task_to_compute.promissory_note_sig
    if deposit_claim.concent_use_case == ConcentUseCase.FORCED_ACCEPTANCE:
        ethereum_transaction_hash = service.force_subtask_payment(  # pylint: disable=no-value-for-parameter
            requestor_eth_address=deposit_claim.payer_deposit_account.
            ethereum_address,
            provider_eth_address=deposit_claim.payee_ethereum_address,
            value=task_to_compute.price,
            subtask_id=deposit_claim.subtask_id,
            v=v,
            r=r,
            s=s,
            reimburse_amount=deposit_claim.amount_as_int,
        )
    elif deposit_claim.concent_use_case == ConcentUseCase.ADDITIONAL_VERIFICATION:
        if subtask is not None:
            if task_to_compute.requestor_ethereum_address == deposit_claim.payer_deposit_account.ethereum_address:
                ethereum_transaction_hash = service.force_subtask_payment(  # pylint: disable=no-value-for-parameter
                    requestor_eth_address=deposit_claim.payer_deposit_account.
                    ethereum_address,
                    provider_eth_address=deposit_claim.payee_ethereum_address,
                    value=task_to_compute.price,
                    subtask_id=deposit_claim.subtask_id,
                    v=v,
                    r=r,
                    s=s,
                    reimburse_amount=deposit_claim.amount_as_int,
                )
            elif task_to_compute.provider_ethereum_address == deposit_claim.payer_deposit_account.ethereum_address:
                subtask_results_verify: SubtaskResultsVerify = deserialize_message(
                    subtask.subtask_results_verify.data.tobytes())
                (v, r, s) = subtask_results_verify.concent_promissory_note_sig
                ethereum_transaction_hash = service.cover_additional_verification_cost(  # pylint: disable=no-value-for-parameter
                    provider_eth_address=deposit_claim.payer_deposit_account.
                    ethereum_address,
                    value=subtask_results_verify.task_to_compute.price,
                    subtask_id=deposit_claim.subtask_id,
                    v=v,
                    r=r,
                    s=s,
                    reimburse_amount=deposit_claim.amount_as_int,
                )
            else:
                assert False
    else:
        assert False

    with non_nesting_atomic(using='control'):
        # The code below is executed in another transaction, so - in theory - deposit_claim object could be modified in
        # the meantime. Here we are working under assumption that it's not the case and it is coder's responsibility to
        # ensure that.
        ethereum_transaction_hash = adjust_transaction_hash(
            ethereum_transaction_hash)
        deposit_claim.tx_hash = ethereum_transaction_hash
        deposit_claim.full_clean()
        deposit_claim.save()

    service.register_confirmed_transaction_handler(  # pylint: disable=no-value-for-parameter
        tx_hash=deposit_claim.tx_hash,
        callback=lambda _: discard_claim(deposit_claim),
    )

    return deposit_claim.tx_hash
Example #3
0
def finalize_payment(deposit_claim: DepositClaim) -> Optional[str]:
    """
    This operation tells Bankster to pay out funds from deposit.
    For each claim, Bankster uses SCI to submit an Ethereum transaction to the Ethereum client which then propagates it
    to the rest of the network.
    Hopefully the transaction is included in one of the upcoming blocks on the blockchain.
    """

    assert isinstance(deposit_claim, DepositClaim)

    # Bankster asks SCI about the amount of funds available on the deposit account listed in the DepositClaim.
    available_funds = service.get_deposit_value(  # pylint: disable=no-value-for-parameter
        client_eth_address=deposit_claim.payer_deposit_account.ethereum_address
    )

    # Bankster begins a database transaction and puts a database lock on the DepositAccount object.
    with transaction.atomic(using='control'):
        DepositAccount.objects.select_for_update().get(
            pk=deposit_claim.payer_deposit_account_id)

        # Bankster sums the amounts of all existing DepositClaims that have the same payer as the one being processed.
        aggregated_client_claims = DepositClaim.objects.filter(
            payer_deposit_account=deposit_claim.payer_deposit_account).exclude(
                pk=deposit_claim.pk).aggregate(
                    sum_of_existing_claims=Coalesce(Sum('amount'), 0))

        # Bankster subtracts that value from the amount of funds available in the deposit.
        available_funds_without_claims = available_funds - aggregated_client_claims[
            'sum_of_existing_claims']

        # If the result is negative or zero, Bankster removes the DepositClaim object being processed.
        if available_funds_without_claims <= 0:
            deposit_claim.delete()
            return None

        # Otherwise if the result is lower than DepositAccount.amount,
        # Bankster sets this field to the amount that's actually available.
        elif available_funds_without_claims < deposit_claim.amount:
            deposit_claim.amount = available_funds_without_claims

        # If the DepositClaim still exists at this point, Bankster uses SCI to create an Ethereum transaction.
        if deposit_claim.concent_use_case == ConcentUseCase.FORCED_ACCEPTANCE:
            ethereum_transaction_hash = service.force_subtask_payment(  # pylint: disable=no-value-for-parameter
                requestor_eth_address=deposit_claim.payer_deposit_account.
                ethereum_address,
                provider_eth_address=deposit_claim.payee_ethereum_address,
                value=deposit_claim.amount,
                subtask_id=deposit_claim.subtask_id,
            )
        elif deposit_claim.concent_use_case == ConcentUseCase.ADDITIONAL_VERIFICATION:
            subtask = Subtask.objects.filter(
                subtask_id=deposit_claim.subtask_id).first()  # pylint: disable=no-member
            if subtask is not None:
                task_to_compute = deserialize_message(
                    subtask.task_to_compute.data.tobytes())
                if task_to_compute.requestor_ethereum_address == deposit_claim.payer_deposit_account.ethereum_address:
                    ethereum_transaction_hash = service.force_subtask_payment(  # pylint: disable=no-value-for-parameter
                        requestor_eth_address=deposit_claim.
                        payer_deposit_account.ethereum_address,
                        provider_eth_address=deposit_claim.
                        payee_ethereum_address,
                        value=deposit_claim.amount,
                        subtask_id=deposit_claim.subtask_id,
                    )
                elif task_to_compute.provider_ethereum_address == deposit_claim.payer_deposit_account.ethereum_address:
                    ethereum_transaction_hash = service.cover_additional_verification_cost(  # pylint: disable=no-value-for-parameter
                        provider_eth_address=deposit_claim.
                        payer_deposit_account.ethereum_address,
                        value=deposit_claim.amount,
                        subtask_id=deposit_claim.subtask_id,
                    )
                else:
                    assert False
        else:
            assert False

        # Bankster puts transaction ID in DepositClaim.tx_hash.
        ethereum_transaction_hash = adjust_transaction_hash(
            ethereum_transaction_hash)
        deposit_claim.tx_hash = ethereum_transaction_hash
        deposit_claim.full_clean()
        deposit_claim.save()

        service.call_on_confirmed_transaction(  # pylint: disable=no-value-for-parameter
            tx_hash=deposit_claim.tx_hash,
            callback=lambda _: discard_claim(deposit_claim),
        )

    return deposit_claim.tx_hash