예제 #1
0
    def create(self, validated_data):
        active_state_signature_data = validated_data.pop('debit_signature')
        wallet = validated_data.pop('wallet')
        recipient = validated_data.pop('recipient')

        # get current eon
        current_eon = LocalViewInterface.latest().eon_number()

        # transfer eon should be the current eon number
        if validated_data.pop('eon_number') != current_eon:
            raise serializers.ValidationError(
                detail='', code=ErrorCode.EON_NUMBER_OUT_OF_SYNC)

        # TODO refactor this such that the recipient is only locked after the sender's details are verified
        wallets = sorted([wallet, recipient], key=lambda w: w.trail_identifier)
        with RootCommitment.read_write_lock(
                suffix=current_eon, auto_renewal=False), wallets[0].lock(
                    auto_renewal=False), wallets[1].lock(auto_renewal=False):
            if RootCommitment.objects.filter(eon_number=current_eon +
                                             1).exists():
                raise serializers.ValidationError(
                    detail='', code=ErrorCode.EON_NUMBER_OUT_OF_SYNC)

            transfer = Transfer(wallet=wallet,
                                amount=validated_data.pop('amount'),
                                eon_number=current_eon,
                                recipient=recipient,
                                nonce=validated_data.pop('nonce'),
                                passive=True)

            wallet_view_context = WalletTransferContext(wallet=wallet,
                                                        transfer=transfer)
            recipient_view_context = WalletTransferContext(wallet=recipient,
                                                           transfer=transfer)

            # Minimal SLA
            if not wallet.is_sla_exempt() and not recipient.is_sla_exempt():
                if not wallet.has_valid_sla():
                    sender_transfers_list = wallet_view_context.authorized_transfers_list(
                        only_appended=False, force_append=True)
                    if len(sender_transfers_list) > settings.SLA_THRESHOLD:
                        raise serializers.ValidationError(
                            detail='',
                            code=ErrorCode.DEBIT_WALLET_EXCEEDED_SLA)
                elif not recipient.has_valid_sla():
                    recipient_transfers_list = recipient_view_context.authorized_transfers_list(
                        only_appended=False, force_append=True)
                    if len(recipient_transfers_list) > settings.SLA_THRESHOLD:
                        raise serializers.ValidationError(
                            detail='',
                            code=ErrorCode.CREDIT_WALLET_EXCEEDED_SLA)

            # Ensure sender log consistency
            can_append_to_sender_log = wallet_view_context.can_schedule_transfer(
            )
            if can_append_to_sender_log is not True:
                raise serializers.ValidationError(
                    detail='Sender: {}'.format(can_append_to_sender_log),
                    code=ErrorCode.DEBIT_WALLET_CANNOT_ADD_TRANSACTION)

            # Ensure recipient log consistency
            can_append_to_recipient_log = recipient_view_context.can_schedule_transfer(
            )
            if can_append_to_recipient_log is not True:
                raise serializers.ValidationError(
                    detail='Recipient: {}'.format(can_append_to_recipient_log),
                    code=ErrorCode.CREDIT_WALLET_CANNOT_ADD_TRANSACTION)

            # Ensure transfer consistency
            can_spend, currently_available_funds = wallet_view_context.can_send_transfer(
                current_eon_number=current_eon,
                using_only_appended_funds=False)
            if can_spend is not True:
                raise serializers.ValidationError(
                    detail=can_spend, code=ErrorCode.DEBIT_WALLET_OVERSPENDING)

            # Validate data
            concise_balance_marker_signature_data = validated_data.pop(
                'debit_balance_signature')
            concise_balance_marker_amount = validated_data.pop('debit_balance')

            if concise_balance_marker_amount > currently_available_funds - transfer.amount:
                raise serializers.ValidationError(
                    detail='',
                    code=ErrorCode.DEBIT_WALLET_BALANCE_MARKER_EXCEED_BALANCE)

            concise_balance_marker = MinimumAvailableBalanceMarker(
                wallet=wallet,
                eon_number=transfer.eon_number,
                amount=concise_balance_marker_amount)
            concise_balance_marker_checksum = hex_value(
                concise_balance_marker.checksum())
            concise_balance_marker_signature = Signature(
                wallet=transfer.wallet,
                checksum=concise_balance_marker_checksum,
                value=concise_balance_marker_signature_data.get('value'))
            if not concise_balance_marker_signature.is_valid():
                raise serializers.ValidationError(
                    detail='', code=ErrorCode.INVALID_DEBIT_BALANCE_SIGNATURE)

            tx_set_tree = wallet_view_context.optimized_authorized_transfers_tree(
            )
            tx_set_hash = hex_value(tx_set_tree.root_hash())
            transfer_index = tx_set_tree.merkle_tree_nonce_map.get(
                transfer.nonce)
            transfer_proof = tx_set_tree.proof(transfer_index)

            highest_spendings, highest_gains = wallet_view_context.off_chain_actively_sent_received_amounts(
                eon_number=transfer.eon_number, only_appended=False)
            active_state = ActiveState(wallet=wallet,
                                       updated_spendings=highest_spendings +
                                       transfer.amount,
                                       updated_gains=highest_gains,
                                       tx_set_hash=tx_set_hash,
                                       tx_set_proof_hashes=transfer_proof,
                                       tx_set_index=transfer_index,
                                       eon_number=transfer.eon_number)

            checksum = hex_value(active_state.checksum())
            active_state_signature = Signature(
                wallet=transfer.wallet,
                checksum=checksum,
                value=active_state_signature_data.get('value'))
            if not active_state_signature.is_valid():
                raise serializers.ValidationError(
                    detail='', code=ErrorCode.INVALID_DEBIT_SIGNATURE)

            transfer.position = recipient_view_context.off_chain_passively_received_amount(
                eon_number=transfer.eon_number, only_appended=False)

            # locking context covers saving the state as well to make sure checkpoint creation is consistent
            with transaction.atomic():
                Signature.objects.bulk_create(
                    [concise_balance_marker_signature, active_state_signature])

                concise_balance_marker.signature = concise_balance_marker_signature
                concise_balance_marker.save()

                active_state.wallet_signature = active_state_signature
                active_state.operator_signature = active_state.sign_active_state(
                    settings.HUB_OWNER_ACCOUNT_ADDRESS,
                    settings.HUB_OWNER_ACCOUNT_KEY)
                active_state.save()

                transfer.sender_active_state = active_state
                transfer.sender_balance_marker = concise_balance_marker
                # cache transfer index in sender active set
                transfer.sender_merkle_index = transfer_index
                # transfer.sender_merkle_root_cache = tx_set_hash
                # cache active set merkle mountains height array and hash array for recipient active set
                transfer.sender_merkle_hash_cache, transfer.sender_merkle_height_cache = tx_set_tree.merkle_cache_stacks(
                )
                transfer.complete = True
                transfer.appended = True
                transfer.processed = True
                transfer.save()

        if transfer.appended:
            operator_celery.send_task('auditor.tasks.on_transfer_confirmation',
                                      args=[transfer.id])

        return transfer
예제 #2
0
    def create(self, validated_data):
        wallet = validated_data.pop('wallet')
        recipient = validated_data.pop('recipient')

        if not TokenPair.objects.filter(token_from=wallet.token,
                                        token_to=recipient.token).exists():
            raise serializers.ValidationError(
                detail='', code=ErrorCode.TOKEN_PAIR_BLOCKED)

        # swap data
        valid_eons = validated_data.pop('valid_eons')
        swap_amount = validated_data.pop('amount')
        swap_nonce = validated_data.pop('nonce')
        sell_order = validated_data.pop('sell_order', True)
        swap_amount_swapped = validated_data.pop('amount_swapped')
        debit_signatures = validated_data.pop('debit_signature')
        debit_balance_signatures = validated_data.pop(
            'debit_balance_signature')
        credit_balance_signatures = validated_data.pop(
            'credit_balance_signature')
        credit_signatures = validated_data.pop('credit_signature')
        recipient_fulfillment_signatures = validated_data.pop(
            'credit_fulfillment_signature')

        # common transaction id
        tx_id = uuid.uuid4()
        tx_time = timezone.now()

        # cached items to be used later
        sender_available_balance = 0
        recipient_available_balance = 0

        swap_set = []

        debit_tx_set_index = []
        credit_tx_set_index = []
        # recipient_fulfillment_tx_set_index = []

        debit_tx_set_cache = []
        credit_tx_set_cache = []
        # recipient_fulfillment_tx_set_cache = []

        debit_balance_signature_records = []
        credit_balance_signature_records = []
        debit_signature_records = []
        credit_signature_records = []
        recipient_fulfillment_signature_records = []

        debit_balance_records = []
        credit_balance_records = []
        debit_active_state_records = []
        credit_active_state_records = []
        recipient_fulfillment_active_state_records = []

        initial_swap_confirmed = False

        # get current eon
        current_eon = LocalViewInterface.latest().eon_number()

        # initial swap eon should be the current eon number
        if validated_data.pop('eon_number') != current_eon:
            raise serializers.ValidationError(
                detail='', code=ErrorCode.EON_NUMBER_OUT_OF_SYNC)

        wallets = sorted([wallet, recipient], key=lambda w: w.token.id)
        with RootCommitment.read_write_lock(
                suffix=current_eon, auto_renewal=False), wallets[0].lock(
                    auto_renewal=False), wallets[1].lock(auto_renewal=False):
            if RootCommitment.objects.filter(eon_number=current_eon +
                                             1).exists():
                raise serializers.ValidationError(
                    detail='Checkpoint was already created for this eon.',
                    code=ErrorCode.EON_NUMBER_OUT_OF_SYNC)

            for eon_number in range(current_eon, current_eon + valid_eons):
                swap = Transfer(tx_id=tx_id,
                                wallet=wallet,
                                amount=swap_amount,
                                eon_number=eon_number,
                                recipient=recipient,
                                nonce=swap_nonce,
                                amount_swapped=swap_amount_swapped,
                                swap=True,
                                sell_order=sell_order,
                                time=tx_time)

                wallet_view_context = WalletTransferContext(wallet=wallet,
                                                            transfer=swap)
                recipient_view_context = WalletTransferContext(
                    wallet=recipient, transfer=swap)

                if eon_number == current_eon:
                    # Ensure sender log consistency
                    can_append_to_sender_log = wallet_view_context.can_schedule_transfer(
                    )
                    if can_append_to_sender_log is not True:
                        raise serializers.ValidationError(
                            detail='Sender: {}'.format(
                                can_append_to_sender_log),
                            code=ErrorCode.DEBIT_WALLET_CANNOT_ADD_TRANSACTION)

                    # Ensure recipient log consistency
                    can_append_to_recipient_log = recipient_view_context.can_schedule_transfer(
                    )
                    if can_append_to_recipient_log is not True:
                        raise serializers.ValidationError(
                            detail='Recipient: {}'.format(
                                can_append_to_recipient_log),
                            code=ErrorCode.CREDIT_WALLET_CANNOT_ADD_TRANSACTION
                        )

                    # Ensure swap consistency
                    can_spend, sender_available_balance = wallet_view_context.can_send_transfer(
                        current_eon_number=current_eon,
                        using_only_appended_funds=False)
                    if can_spend is not True:
                        raise serializers.ValidationError(
                            detail=can_spend,
                            code=ErrorCode.DEBIT_WALLET_OVERSPENDING)

                    # Ensure that sender balance is exactly equal to total outgoing amount
                    if sender_available_balance != swap.amount:
                        raise serializers.ValidationError(
                            detail=
                            'Sender balance should be exactly equal to outgoing swap amount, {} != {}.'
                            .format(sender_available_balance, swap.amount),
                            code=ErrorCode.DEBIT_WALLET_BALANCE_AMOUNT_MISMATCH
                        )

                    # Ensure that recipient balance is zero
                    recipient_available_balance = recipient_view_context.available_funds_at_eon(
                        eon_number=eon_number, only_appended=False)
                    if recipient_available_balance != 0:
                        raise serializers.ValidationError(
                            detail='Recipient balance should be exactly zero.',
                            code=ErrorCode.CREDIT_WALLET_BALANCE_NOT_ZERO)

                    current_eon_swap = swap
                    sender_highest_spendings, sender_highest_gains = wallet_view_context.off_chain_actively_sent_received_amounts(
                        eon_number=swap.eon_number, only_appended=False)
                    recipient_highest_spendings, recipient_highest_gains = recipient_view_context.off_chain_actively_sent_received_amounts(
                        eon_number=swap.eon_number, only_appended=False)
                else:
                    sender_highest_spendings, sender_highest_gains = 0, 0
                    recipient_highest_spendings, recipient_highest_gains = 0, 0

                # Minimum Debit Balance Marker
                debit_balance_marker_signature_data = debit_balance_signatures[
                    eon_number - current_eon]
                debit_balance_marker = MinimumAvailableBalanceMarker(
                    wallet=wallet, eon_number=eon_number, amount=0)

                debit_balance_marker_checksum = crypto.hex_value(
                    debit_balance_marker.checksum())
                debit_balance_marker_signature = Signature(
                    wallet=wallet,
                    checksum=debit_balance_marker_checksum,
                    value=debit_balance_marker_signature_data.get('value'))
                if not debit_balance_marker_signature.is_valid():
                    raise serializers.ValidationError(
                        detail='',
                        code=ErrorCode.INVALID_DEBIT_BALANCE_SIGNATURE)

                # Minimum Credit Balance Marker
                credit_balance_marker_signature_data = credit_balance_signatures[
                    eon_number - current_eon]
                credit_balance_marker = MinimumAvailableBalanceMarker(
                    wallet=recipient, eon_number=eon_number, amount=0)

                credit_balance_marker_checksum = crypto.hex_value(
                    credit_balance_marker.checksum())
                credit_balance_marker_signature = Signature(
                    wallet=recipient,
                    checksum=credit_balance_marker_checksum,
                    value=credit_balance_marker_signature_data.get('value'))
                if not credit_balance_marker_signature.is_valid():
                    raise serializers.ValidationError(
                        detail='',
                        code=ErrorCode.INVALID_CREDIT_BALANCE_SIGNATURE)
                assert (sender_available_balance == swap.amount)
                # Debit Authorization
                debit_active_state_signature_data = debit_signatures[
                    eon_number - current_eon]
                debit_active_state, \
                    debit_active_state_signature, \
                    debit_transfer_index, \
                    debit_transfer_cache = check_active_state_signature(
                        swap,
                        wallet,
                        debit_active_state_signature_data,
                        eon_number > current_eon,
                        sender_available_balance,
                        sender_highest_spendings,
                        sender_highest_gains,
                        signature_type=SignatureType.DEBIT)

                # Credit Authorization
                credit_active_state_signature_data = credit_signatures[
                    eon_number - current_eon]
                credit_active_state, \
                    credit_active_state_signature, \
                    credit_transfer_index, \
                    credit_transfer_cache = check_active_state_signature(
                        swap,
                        recipient,
                        credit_active_state_signature_data,
                        eon_number > current_eon,
                        recipient_available_balance,
                        recipient_highest_spendings,
                        recipient_highest_gains,
                        signature_type=SignatureType.CREDIT)

                # Finality Authorization
                recipient_fulfillment_active_state_signature_data = recipient_fulfillment_signatures[
                    eon_number - current_eon]
                recipient_fulfillment_active_state, \
                    recipient_fulfillment_active_state_signature, \
                    recipient_fulfillment_transfer_index, \
                    recipient_fulfillment_transfer_cache = check_active_state_signature(
                        swap,
                        recipient,
                        recipient_fulfillment_active_state_signature_data,
                        eon_number > current_eon,
                        recipient_available_balance,
                        recipient_highest_spendings,
                        recipient_highest_gains,
                        signature_type=SignatureType.FULFILLMENT)

                # accumulate records to be saved
                debit_balance_signature_records.append(
                    debit_balance_marker_signature)
                credit_balance_signature_records.append(
                    credit_balance_marker_signature)
                debit_signature_records.append(debit_active_state_signature)
                credit_signature_records.append(credit_active_state_signature)
                recipient_fulfillment_signature_records.append(
                    recipient_fulfillment_active_state_signature)

                debit_balance_records.append(debit_balance_marker)
                credit_balance_records.append(credit_balance_marker)
                debit_active_state_records.append(debit_active_state)
                credit_active_state_records.append(credit_active_state)
                recipient_fulfillment_active_state_records.append(
                    recipient_fulfillment_active_state)

                debit_tx_set_index.append(debit_transfer_index)
                credit_tx_set_index.append(credit_transfer_index)
                # recipient_fulfillment_tx_set_index.append(recipient_fulfillment_transfer_index)

                debit_tx_set_cache.append(debit_transfer_cache)
                credit_tx_set_cache.append(credit_transfer_cache)
                # recipient_fulfillment_tx_set_cache.append(recipient_fulfillment_transfer_cache)
                swap_set.append(swap)

            assert (swap_set[0] is not None
                    and swap_set[0].eon_number == current_eon)
            assert (len(swap_set) == valid_eons)

            # locking context covers saving the state as well to make sure checkpoint creation is consistent
            with transaction.atomic():
                Signature.objects.bulk_create(
                    debit_balance_signature_records +
                    credit_balance_signature_records +
                    debit_signature_records + credit_signature_records +
                    recipient_fulfillment_signature_records)

                for index in range(valid_eons):
                    debit_balance_records[
                        index].signature = debit_balance_signature_records[
                            index]
                    credit_balance_records[
                        index].signature = credit_balance_signature_records[
                            index]
                    debit_active_state_records[
                        index].wallet_signature = debit_signature_records[
                            index]
                    credit_active_state_records[
                        index].wallet_signature = credit_signature_records[
                            index]
                    recipient_fulfillment_active_state_records[
                        index].wallet_signature = recipient_fulfillment_signature_records[
                            index]

                ActiveState.objects.bulk_create(
                    debit_active_state_records + credit_active_state_records +
                    recipient_fulfillment_active_state_records)

                MinimumAvailableBalanceMarker.objects.bulk_create(
                    debit_balance_records + credit_balance_records)

                for index in range(valid_eons):
                    swap_set[
                        index].sender_active_state = debit_active_state_records[
                            index]
                    swap_set[
                        index].recipient_active_state = credit_active_state_records[
                            index]
                    swap_set[
                        index].recipient_fulfillment_active_state = recipient_fulfillment_active_state_records[
                            index]
                    swap_set[
                        index].sender_balance_marker = debit_balance_records[
                            index]

                    swap_set[
                        index].sender_starting_balance = sender_available_balance
                    swap_set[
                        index].recipient_starting_balance = recipient_available_balance

                    # cache swap index in sender active set
                    swap_set[index].sender_merkle_index = debit_tx_set_index[
                        index]
                    # cache swap index in recipient active set
                    swap_set[
                        index].recipient_merkle_index = credit_tx_set_index[
                            index]
                    # cache active set merkle mountains height array and hash array for sender active set
                    swap_set[index].sender_merkle_hash_cache, swap_set[
                        index].sender_merkle_height_cache = debit_tx_set_cache[
                            index]
                    # cache active set merkle mountains height array and hash array for recipient active set
                    swap_set[index].recipient_merkle_hash_cache, swap_set[
                        index].recipient_merkle_height_cache = debit_tx_set_cache[
                            index]

                Transfer.objects.bulk_create(swap_set)

                swap_set[0].sign_swap(settings.HUB_OWNER_ACCOUNT_ADDRESS,
                                      settings.HUB_OWNER_ACCOUNT_KEY)
                initial_swap_confirmed = True

        if initial_swap_confirmed:
            operator_celery.send_task('auditor.tasks.on_swap_confirmation',
                                      args=[swap_set[0].id])

        return swap_set[0]