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
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]