def confirm_swaps_for_eon(operator_eon_number): checkpoint_created = RootCommitment.objects.filter( eon_number=operator_eon_number).exists() with transaction.atomic(): # Countersign swaps (no matching yet) swaps_pending_operator_confirmation = Transfer.objects \ .filter( processed=False, complete=False, voided=False, cancelled=False, swap=True, eon_number=operator_eon_number, sender_active_state__isnull=False, recipient_active_state__isnull=False, sender_active_state__operator_signature__isnull=True, recipient_active_state__operator_signature__isnull=True) \ .select_for_update() \ .order_by('time') for swap in swaps_pending_operator_confirmation: with swap.lock(auto_renewal=True), swap.wallet.lock( auto_renewal=True), swap.recipient.lock(auto_renewal=True): swap_wallet_view_context = WalletTransferContext( wallet=swap.wallet, transfer=swap) swap_recipient_view_context = WalletTransferContext( wallet=swap.recipient, transfer=swap) if swap_expired(swap, operator_eon_number, checkpoint_created): logger.info('Retiring swap') swap.retire_swap() if should_void_swap(swap, swap_wallet_view_context, swap_recipient_view_context, operator_eon_number, checkpoint_created): logger.info('Voiding swap.') swap.close(voided=True) elif swap.is_fulfilled_swap(): logger.info('Skipping finalized swap.') elif swap.is_signed_by_operator(): logger.info('Skipping signed swap.') else: try: swap.sign_swap(settings.HUB_OWNER_ACCOUNT_ADDRESS, settings.HUB_OWNER_ACCOUNT_KEY) operator_celery.send_task( 'auditor.tasks.on_swap_confirmation', args=[swap.id]) except LookupError as e: logger.error(e)
def to_representation(self, transfer: Transfer): if transfer.final_receipt_hashes is None: return {} # include proof if # 1) context wallet_id is provided and parent is sender wallet # 2) context wallet_id is None include_proof = self.context.get( 'wallet_id') is None or self.context.get( 'wallet_id') == transfer.wallet.id if include_proof: recipient_balance = WalletTransferContext( wallet=transfer.recipient, transfer=transfer).balance_as_of_eon(transfer.eon_number + 1) return { 'merkle_proof': ProofSerializer(recipient_balance, read_only=True).data if include_proof else None, 'transfer_membership_chain': long_string_to_list(transfer.final_receipt_hashes, 64), 'transfer_membership_trail': int(transfer.final_receipt_index), 'transfer_membership_values': csf_to_list(transfer.final_receipt_values) if transfer.passive else None }
def to_representation(self, instance: ExclusiveBalanceAllotment): wallet_context = WalletTransferContext( wallet=instance.wallet, transfer=None) passive_checksum, passive_amount, passive_marker = wallet_context.get_passive_values( eon_number=instance.eon_number - 1) return { 'eon_number': int(instance.eon_number), 'left': str_int(instance.left), 'right': str_int(instance.right), 'allotment_chain': long_string_to_list(instance.merkle_proof_hashes, 64), 'membership_chain': long_string_to_list(instance.merkle_membership_chain(), 64), 'values': csf_to_list(instance.merkle_proof_values, str_int), 'trail': int(instance.merkle_proof_trail), 'active_state_checksum': encode_hex(instance.active_state.checksum()) if instance.active_state else encode_hex(b'\0'*32), 'active_state': ConciseActiveStateSerializer( instance.active_state, read_only=True).data if instance.active_state else None, 'passive_checksum': encode_hex(passive_checksum), 'passive_amount': str_int(passive_amount), 'passive_marker': str_int(passive_marker) }
def delegated_withdrawal(request): signature = request.POST.get("signature") wallet_address = request.POST.get("wallet") amount = int(request.POST.get("amount")) token = request.POST.get("token") expiry = int(request.POST.get("expiry")) message = [ crypto.address(wallet_address), crypto.address(token), crypto.uint256(amount), crypto.uint256(expiry) ] message = crypto.hash_array(message) v, r, s = crypto.decode_signature(signature) trust = crypto.verify_message_signature(crypto.address(wallet_address), message, (v, r, s)) if not trust: return Response(data="Signature is invalid", status=400) wallet = Wallet.objects.filter(address=remove_0x_prefix(wallet_address), token__address=token).get() nc = NOCUSTContractInterface() current_eon = nc.get_current_eon_number() wallet_view_context = WalletTransferContext(wallet, None) available_amount = wallet_view_context.loosely_available_funds_at_eon( current_eon, current_eon, False, False) if available_amount < amount: return Response(data="Not enough funds", status=400) nc.delegated_withdraw(token, wallet_address, amount, expiry, crypto.uint256(r), crypto.uint256(s), v) return Response(data="Ok", status=200)
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 check_active_state_signature(swap, wallet, active_state_signature_data, is_future_state, starting_balance, highest_spendings, highest_gains, signature_type=None): wallet_view_context = WalletTransferContext(wallet=wallet, transfer=swap) # fulfillment active state if signature_type == SignatureType.FULFILLMENT: swap.processed, swap.complete = True, True if is_future_state: # done for all future eons # assumes this is the only TX in set tx_set_tree = WalletTransferContext.optimized_authorized_transfers_tree_from_list( [ swap.shorthand(wallet_view_context, is_last_transfer=True, starting_balance=starting_balance) ]) else: # done once for current eon tx_set_tree = wallet_view_context.optimized_authorized_transfers_tree() # fulfillment active state if signature_type == SignatureType.FULFILLMENT: swap.processed, swap.complete = False, False tx_set_hash = crypto.hex_value(tx_set_tree.root_hash()) transfer_index = tx_set_tree.merkle_tree_nonce_map.get(swap.nonce) transfer_proof = tx_set_tree.proof(transfer_index) # debit active state if signature_type == SignatureType.DEBIT: updated_spendings = highest_spendings + swap.amount updated_gains = highest_gains state_name = "Debit" # credit active state elif signature_type == SignatureType.CREDIT: updated_spendings = highest_spendings updated_gains = highest_gains state_name = "Credit" # fulfillment active state elif signature_type == SignatureType.FULFILLMENT: updated_spendings = highest_spendings updated_gains = highest_gains + swap.amount_swapped state_name = "Fulfillment" active_state = ActiveState(wallet=wallet, updated_spendings=updated_spendings, updated_gains=updated_gains, tx_set_hash=tx_set_hash, tx_set_proof_hashes=transfer_proof, tx_set_index=transfer_index, eon_number=swap.eon_number) active_state_checksum = crypto.hex_value(active_state.checksum()) active_state_signature = Signature( wallet=wallet, checksum=active_state_checksum, value=active_state_signature_data.get('value')) if not active_state_signature.is_valid(): error_code = None if signature_type == SignatureType.CREDIT: error_code = ErrorCode.INVALID_FUTURE_CREDIT_SIGNATURE if is_future_state else ErrorCode.INVALID_CREDIT_SIGNATURE elif signature_type == SignatureType.DEBIT: error_code = ErrorCode.INVALID_FUTURE_DEBIT_SIGNATURE if is_future_state else ErrorCode.INVALID_DEBIT_SIGNATURE elif signature_type == SignatureType.FULFILLMENT: error_code = ErrorCode.INVALID_FUTURE_CREDIT_FULFILLMENT_SIGNATURE if is_future_state else ErrorCode.INVALID_CREDIT_FULFILLMENT_SIGNATURE raise serializers.ValidationError( 'Active state signature failed for eon {}'.format(swap.eon_number), code=error_code) return active_state, active_state_signature, transfer_index, tx_set_tree.merkle_cache_stacks( )
def should_void_swap(swap: Transfer, wallet_view_context: WalletTransferContext, recipient_view_context: WalletTransferContext, operator_eon_number: int, is_checkpoint_created: bool): if not settings.SWAPS_ENABLED: logger.error('Swaps disabled. Voiding {}'.format(swap.id)) return True if swap.amount < 1: logger.error('Swap {} has less than 1 amount'.format(swap.id)) return True if swap.amount_swapped < 1: logger.error('Swap {} has less than 1 amount swapped'.format(swap.id)) return True # Unauthorized transfer if swap.sender_active_state is None: logger.error('Swap {} no authorization'.format(swap.id)) return True # Invalid signature by sender if not swap.sender_active_state.wallet_signature.is_valid(): logger.error('Swap {} invalid sender signature.'.format(swap.id)) return True # Unreceived transaction if swap.recipient_active_state is None: logger.error('Swap receipt for {} not provided.'.format(swap.id)) return True # Invalid signature by recipient if not swap.recipient_active_state.wallet_signature.is_valid(): logger.error('Swap {} invalid receipt signature.'.format(swap.id)) return True # Ensure log consistency can_append_to_sender_log = wallet_view_context.can_append_transfer() if can_append_to_sender_log is not True: logger.error('Sender: {}'.format(can_append_to_sender_log)) return True can_append_to_recipient_log = recipient_view_context.can_append_transfer() if can_append_to_recipient_log is not True: logger.error('Recipient: {}'.format(can_append_to_recipient_log)) return True # Skip consistency checks since they were done at least once before. if swap.appended: return False # Overspending sender_funds_remaining = wallet_view_context.loosely_available_funds_at_eon( eon_number=swap.eon_number, current_eon_number=operator_eon_number, is_checkpoint_created=is_checkpoint_created, only_appended=True) # sender remaining funds should be more than remaining amount in order matched_out, matched_in = swap.matched_amounts(all_eons=True) if sender_funds_remaining < swap.amount - matched_out: logger.error('Swap {} overspending.'.format(swap.id)) return True # Prevent future overdrawing # if swap.sender_balance_marker.amount > sender_funds_remaining - swap.amount: if swap.sender_balance_marker.amount != 0: logger.error('Swap {} invalid concise marker balance.'.format(swap.id)) return True concise_balance_marker = MinimumAvailableBalanceMarker( wallet=swap.wallet, eon_number=swap.eon_number, amount=swap.sender_balance_marker.amount) concise_balance_marker_checksum = crypto.hex_value( concise_balance_marker.checksum()) if swap.sender_balance_marker.signature.checksum != concise_balance_marker_checksum: logger.error('Swap {} invalid concise marker checksum for {}.'.format( swap.id, swap.sender_balance_marker.amount)) return True highest_spendings, highest_gains = wallet_view_context.off_chain_actively_sent_received_amounts( eon_number=swap.eon_number, only_appended=True) # if this is a multi eon swap if Transfer.objects.filter(eon_number=swap.eon_number - 1, tx_id=swap.tx_id).exists(): # set balances to initial fixed balances stored in transfer eon state sender_starting_balance = swap.sender_starting_balance recipient_starting_balance = swap.recipient_starting_balance # make sure this eon's starting balance is exactly the initial stored balance # when matched amount is taken into consideration for both sender and receiver if wallet_view_context.starting_balance_in_eon( swap.eon_number) != sender_starting_balance - matched_out: logger.error( 'Swap {} invalid sender starting balance of future state {} != {} - {}.' .format( swap.id, wallet_view_context.starting_balance_in_eon( swap.eon_number), sender_starting_balance, matched_out)) if recipient_view_context.starting_balance_in_eon( swap.eon_number) != recipient_starting_balance + matched_in: logger.error( 'Swap {} invalid recipient starting balance of future state {} != {} + {}.' .format( swap.id, recipient_view_context.starting_balance_in_eon( swap.eon_number), recipient_starting_balance, matched_out)) assert (wallet_view_context.starting_balance_in_eon( swap.eon_number) == sender_starting_balance - matched_out) assert (recipient_view_context.starting_balance_in_eon( swap.eon_number) == recipient_starting_balance + matched_in) else: sender_starting_balance = int( wallet_view_context.starting_balance_in_eon(swap.eon_number)) recipient_starting_balance = int( recipient_view_context.starting_balance_in_eon(swap.eon_number)) # Debit Authorization tx_set_tree = wallet_view_context.optimized_authorized_transfers_tree( only_appended=True, starting_balance=sender_starting_balance) tx_set_hash = crypto.hex_value(tx_set_tree.root_hash()) transfer_index = tx_set_tree.merkle_tree_nonce_map.get(swap.nonce) transfer_proof = tx_set_tree.proof(transfer_index) highest_spendings, highest_gains = wallet_view_context.off_chain_actively_sent_received_amounts( eon_number=swap.eon_number, only_appended=True) debiting_active_state = ActiveState(wallet=swap.wallet, updated_spendings=highest_spendings + swap.amount, updated_gains=highest_gains, tx_set_hash=tx_set_hash, tx_set_proof_hashes=transfer_proof, tx_set_index=transfer_index, eon_number=swap.eon_number) debiting_active_state_checksum = crypto.hex_value( debiting_active_state.checksum()) if swap.sender_active_state.wallet_signature.checksum != debiting_active_state_checksum: logger.error('Swap {} invalid debit active state checksum.'.format( swap.id)) return True # Credit Authorization tx_set_tree = recipient_view_context.optimized_authorized_transfers_tree( only_appended=True, starting_balance=recipient_starting_balance) tx_set_hash = crypto.hex_value(tx_set_tree.root_hash()) transfer_index = tx_set_tree.merkle_tree_nonce_map.get(swap.nonce) transfer_proof = tx_set_tree.proof(transfer_index) highest_spendings, highest_gains = recipient_view_context.off_chain_actively_sent_received_amounts( eon_number=swap.eon_number, only_appended=True) crediting_active_state = ActiveState(wallet=swap.recipient, updated_spendings=highest_spendings, updated_gains=highest_gains, tx_set_hash=tx_set_hash, tx_set_proof_hashes=transfer_proof, tx_set_index=transfer_index, eon_number=swap.eon_number) crediting_active_state_checksum = crypto.hex_value( crediting_active_state.checksum()) if swap.recipient_active_state.wallet_signature.checksum != crediting_active_state_checksum: logger.error('Swap {} invalid credit active state checksum.'.format( swap.id)) return True # Finality Authorization swap.complete = True tx_set_tree = recipient_view_context.optimized_authorized_transfers_tree( only_appended=True, starting_balance=recipient_starting_balance) swap.complete = False tx_set_hash = crypto.hex_value(tx_set_tree.root_hash()) transfer_index = tx_set_tree.merkle_tree_nonce_map.get(swap.nonce) transfer_proof = tx_set_tree.proof(transfer_index) recipient_fulfillment_active_state = ActiveState( wallet=swap.recipient, updated_spendings=highest_spendings, updated_gains=highest_gains + swap.amount_swapped, tx_set_hash=tx_set_hash, tx_set_proof_hashes=transfer_proof, tx_set_index=transfer_index, eon_number=swap.eon_number) recipient_fulfillment_active_state_checksum = crypto.hex_value( recipient_fulfillment_active_state.checksum()) if swap.recipient_fulfillment_active_state.wallet_signature.checksum != recipient_fulfillment_active_state_checksum: logger.error( 'Swap {} invalid finalization active state checksum.'.format( swap.id)) return True return False
def send_transaction(test_case, eon_number, sender, recipient, amount, nonce, token, expected_status=status.HTTP_201_CREATED): # Sender account sender_wallet = Wallet.objects.get(address=remove_0x_prefix( sender.get('address')), token=token) # Recipient account recipient_wallet = Wallet.objects.get(address=remove_0x_prefix( recipient.get('address')), token=token) transfer = Transfer(wallet=sender_wallet, amount=amount, eon_number=eon_number, recipient=recipient_wallet, nonce=nonce, passive=True) sender_view_context = WalletTransferContext(wallet=sender_wallet, transfer=transfer) sender_highest_spent, sender_highest_gained = sender_view_context.off_chain_actively_sent_received_amounts( eon_number=eon_number, only_appended=False) updated_spendings = sender_highest_spent + amount # Authorize transaction transfer_set_root = hex_value( sender_view_context.authorized_transfers_tree_root(only_appended=False, force_append=True)) active_state = ActiveState(wallet=sender_wallet, updated_spendings=updated_spendings, updated_gains=sender_highest_gained, eon_number=eon_number, tx_set_hash=transfer_set_root) sender_active_state_authorization = sign_message(active_state.checksum(), sender.get('pk')) sender_active_state_signature = Signature( wallet=sender_wallet, checksum=hex_value(active_state.checksum()), value=encode_signature(sender_active_state_authorization)) test_case.assertTrue(sender_active_state_signature.is_valid()) _, available_balance = sender_view_context.can_send_transfer( current_eon_number=eon_number, using_only_appended_funds=False) new_balance = available_balance - transfer.amount concise_balance_marker = MinimumAvailableBalanceMarker( wallet=sender_wallet, eon_number=eon_number, amount=new_balance) sender_concise_balance_marker_authorization = sign_message( concise_balance_marker.checksum(), sender.get('pk')) concise_balance_marker_signature = Signature( wallet=sender_wallet, checksum=hex_value(concise_balance_marker.checksum()), value=encode_signature(sender_concise_balance_marker_authorization)) test_case.assertTrue(concise_balance_marker_signature.is_valid()) print("Sender view:") print("available_balance:", available_balance) print("transfer_set_root:", transfer_set_root) print("active_state.updated_spendings:", active_state.updated_spendings) print("active_state.updated_gains:", active_state.updated_gains) # Make API Request url = reverse('transfer-endpoint') data = { 'debit_signature': { 'value': encode_signature(sender_active_state_authorization), }, 'debit_balance_signature': { 'value': encode_signature(sender_concise_balance_marker_authorization), }, 'debit_balance': new_balance, 'eon_number': eon_number, 'amount': amount, 'nonce': nonce, 'wallet': { 'address': sender_wallet.address, 'token': sender_wallet.token.address, }, 'recipient': { 'address': recipient_wallet.address, 'token': recipient_wallet.token.address, }, } # Send tx to server x = datetime.datetime.now() response = test_case.client.post(url, data, format='json') y = datetime.datetime.now() delta = y - x # Ensure the transaction was recorded test_case.assertEqual(response.status_code, expected_status, '\n'.join([url, str(data), str(response.content)])) print('TX Time: {}s for {}'.format(delta, amount)) # fo rpassive transfer assert that transaction is confirmed tx = json.loads(response.content) transfer = Transfer.objects.get(id=tx['id']) test_case.assertTrue(transfer.complete) test_case.assertTrue(transfer.appended) test_case.assertNotEqual(transfer.sender_active_state.operator_signature, None) # Log time delta return delta
def update(self, swap, validated_data): current_swap = None is_swap_cancelled = False with transaction.atomic(): current_eon = LocalViewInterface.latest().eon_number() swap_set = Transfer.objects.select_for_update().filter( tx_id=swap.tx_id, eon_number__gte=current_eon, swap=True).order_by('eon_number') current_swap = swap_set[0] if not current_swap.cancelled: raise serializers.ValidationError( detail='', code=ErrorCode.SWAP_NOT_FROZEN) elif current_swap.processed: raise serializers.ValidationError( detail='', code=ErrorCode.SWAP_ALREADY_CLOSED) elif current_swap.swap_freezing_signature is None: raise serializers.ValidationError( detail='', code=ErrorCode.MISSING_FREEZING_SIGNATURE) elif None not in [ current_swap.sender_cancellation_active_state, current_swap.recipient_cancellation_active_state ]: raise serializers.ValidationError( detail='', code=ErrorCode.SWAP_ALREADY_CANCELLED) sender_cancellation_signatures = validated_data.get( 'sender_cancellation_signature') recipient_cancellation_signatures = validated_data.get( 'recipient_cancellation_signature') # state to save sender_cancellation_active_state_signature_records = [] sender_cancellation_active_state_records = [] recipient_cancellation_active_state_signature_records = [] recipient_cancellation_active_state_records = [] # make sure appropriate number of signatures was provided if swap_set.count() != len(sender_cancellation_signatures): raise serializers.ValidationError( detail= 'Wrong number of sender cancellation signatures, expected {} but got {}' .format(swap_set.count(), len(sender_cancellation_signatures)), code=ErrorCode.WRONG_NUMBER_OF_DEBIT_SIGNATURES) if swap_set.count() != len(recipient_cancellation_signatures): raise serializers.ValidationError( detail= 'Wrong number of recipient cancellation signatures, expected {} but got {}' .format(swap_set.count(), len(recipient_cancellation_signatures)), code=ErrorCode.WRONG_NUMBER_OF_CREDIT_SIGNATURES) sender_view_context = WalletTransferContext( wallet=current_swap.wallet, transfer=current_swap) debit_tx_set_tree = sender_view_context.optimized_authorized_transfers_tree( force_append=False, assume_active_state_exists=True) tx_set_hash = crypto.hex_value(debit_tx_set_tree.root_hash()) transfer_index = debit_tx_set_tree.merkle_tree_nonce_map.get( current_swap.nonce) transfer_proof = debit_tx_set_tree.proof(transfer_index) sender_highest_spendings, sender_highest_gains = sender_view_context.off_chain_actively_sent_received_amounts( eon_number=current_swap.eon_number, only_appended=False) matched_out, _ = current_swap.matched_amounts() sender_highest_gains += current_swap.amount - matched_out sender_cancellation_active_state = ActiveState( wallet=current_swap.wallet, updated_spendings=sender_highest_spendings, updated_gains=sender_highest_gains, tx_set_hash=tx_set_hash, tx_set_proof_hashes=transfer_proof, tx_set_index=transfer_index, eon_number=current_swap.eon_number) sender_cancellation_active_state_checksum = crypto.hex_value( sender_cancellation_active_state.checksum()) sender_cancellation_active_state_signature = Signature( wallet=current_swap.wallet, checksum=sender_cancellation_active_state_checksum, value=sender_cancellation_signatures[0].get('value')) if not sender_cancellation_active_state_signature.is_valid(): raise serializers.ValidationError( detail='', code=ErrorCode.INVALID_DEBIT_SIGNATURE) sender_cancellation_active_state_records.append( sender_cancellation_active_state) sender_cancellation_active_state_signature_records.append( sender_cancellation_active_state_signature) recipient_view_context = WalletTransferContext( wallet=current_swap.recipient, transfer=current_swap) credit_tx_set_tree = recipient_view_context.optimized_authorized_transfers_tree( force_append=False, assume_active_state_exists=True) tx_set_hash = crypto.hex_value(credit_tx_set_tree.root_hash()) transfer_index = credit_tx_set_tree.merkle_tree_nonce_map.get( current_swap.nonce) transfer_proof = credit_tx_set_tree.proof(transfer_index) recipient_highest_spendings, recipient_highest_gains = recipient_view_context.off_chain_actively_sent_received_amounts( eon_number=current_swap.eon_number, only_appended=False) recipient_cancellation_active_state = ActiveState( wallet=current_swap.recipient, updated_spendings=recipient_highest_spendings + current_swap.amount_swapped, updated_gains=recipient_highest_gains + current_swap.amount_swapped, tx_set_hash=tx_set_hash, tx_set_proof_hashes=transfer_proof, tx_set_index=transfer_index, eon_number=current_swap.eon_number) recipient_cancellation_active_state_checksum = crypto.hex_value( recipient_cancellation_active_state.checksum()) recipient_cancellation_active_state_signature = Signature( wallet=current_swap.recipient, checksum=recipient_cancellation_active_state_checksum, value=recipient_cancellation_signatures[0].get('value')) if not recipient_cancellation_active_state_signature.is_valid(): raise serializers.ValidationError( detail='', code=ErrorCode.INVALID_CREDIT_SIGNATURE) recipient_cancellation_active_state_records.append( recipient_cancellation_active_state) recipient_cancellation_active_state_signature_records.append( recipient_cancellation_active_state_signature) # calculate future spent, gained, empty tx set empty_tx_set_hash = crypto.hex_value(NODE_CACHE[0]['hash']) sender_future_spent_gained = max(sender_highest_spendings, sender_highest_gains) + 1 recipient_future_spent_gained = max( recipient_highest_spendings, recipient_highest_gains) + current_swap.amount_swapped + 1 for index in range(1, len(swap_set)): future_swap = swap_set[index] sender_cancellation_active_state = ActiveState( wallet=future_swap.wallet, updated_spendings=sender_future_spent_gained, updated_gains=sender_future_spent_gained, tx_set_hash=empty_tx_set_hash, # any dummy value tx_set_proof_hashes='', # any dummy value tx_set_index=0, eon_number=future_swap.eon_number) sender_cancellation_active_state_checksum = crypto.hex_value( sender_cancellation_active_state.checksum()) sender_cancellation_active_state_signature = Signature( wallet=future_swap.recipient, checksum=sender_cancellation_active_state_checksum, value=sender_cancellation_signatures[index].get('value')) if not sender_cancellation_active_state_signature.is_valid(): raise serializers.ValidationError( detail='', code=ErrorCode.INVALID_FUTURE_DEBIT_SIGNATURE) sender_cancellation_active_state_signature_records.append( sender_cancellation_active_state_signature) sender_cancellation_active_state_records.append( sender_cancellation_active_state) recipient_cancellation_active_state = ActiveState( wallet=future_swap.recipient, updated_spendings=recipient_future_spent_gained, updated_gains=recipient_future_spent_gained, tx_set_hash=empty_tx_set_hash, # any dummy value tx_set_proof_hashes='', # any dummy value tx_set_index=0, eon_number=future_swap.eon_number) recipient_cancellation_active_state_checksum = crypto.hex_value( recipient_cancellation_active_state.checksum()) recipient_cancellation_active_state_signature = Signature( wallet=future_swap.recipient, checksum=recipient_cancellation_active_state_checksum, value=recipient_cancellation_signatures[index].get( 'value')) if not recipient_cancellation_active_state_signature.is_valid( ): raise serializers.ValidationError( detail='', code=ErrorCode.INVALID_FUTURE_CREDIT_SIGNATURE) recipient_cancellation_active_state_signature_records.append( recipient_cancellation_active_state_signature) recipient_cancellation_active_state_records.append( recipient_cancellation_active_state) assert (len(swap_set) == len( sender_cancellation_active_state_signature_records)) assert (len(swap_set) == len( recipient_cancellation_active_state_signature_records)) Signature.objects.bulk_create( sender_cancellation_active_state_signature_records + recipient_cancellation_active_state_signature_records) with current_swap.lock( auto_renewal=False), current_swap.wallet.lock( auto_renewal=False), current_swap.recipient.lock( auto_renewal=False): for index in range(len(swap_set)): sender_cancellation_active_state_records[ index].wallet_signature = sender_cancellation_active_state_signature_records[ index] recipient_cancellation_active_state_records[ index].wallet_signature = recipient_cancellation_active_state_signature_records[ index] ActiveState.objects.bulk_create( sender_cancellation_active_state_records + recipient_cancellation_active_state_records) for index in range(len(swap_set)): swap_set[ index].sender_cancellation_active_state = sender_cancellation_active_state_records[ index] swap_set[ index].recipient_cancellation_active_state = recipient_cancellation_active_state_records[ index] if index > 0: swap_set[index].voided = True swap_set[index].appended = False swap_set[index].processed = True Transfer.objects.bulk_update(swap_set, [ 'sender_cancellation_active_state', 'recipient_cancellation_active_state', 'voided', 'appended', 'processed' ]) swap_set[0].sign_swap_cancellation( settings.HUB_OWNER_ACCOUNT_ADDRESS, settings.HUB_OWNER_ACCOUNT_KEY) swap_set[0].close(cancelled=True, appended=True) current_swap = swap_set[0] is_swap_cancelled = True if is_swap_cancelled: operator_celery.send_task('auditor.tasks.on_swap_cancellation', args=[current_swap.id]) return current_swap
def should_void_transfer(transfer, wallet_view_context: WalletTransferContext, recipient_view_context: WalletTransferContext, operator_eon_number, is_checkpoint_created): if transfer.eon_number != operator_eon_number and is_checkpoint_created: logger.error('Transfer {} eon mismatch ({}, {})'.format( transfer.id, transfer.eon_number, operator_eon_number)) return True if transfer.amount < 0: logger.error('Transfer {} has negative amount'.format(transfer.id)) return True # Unauthorized transfer if transfer.sender_active_state is None: logger.error('Transfer {} no authorization'.format(transfer.id)) return True # Invalid signature by sender if not transfer.sender_active_state.wallet_signature.is_valid(): logger.error('Transfer {} invalid sender signature.'.format( transfer.id)) return True # Ensure sender log consistency can_append_to_sender_log = wallet_view_context.can_append_transfer() if can_append_to_sender_log is not True: logger.error('Sender: {}'.format(can_append_to_sender_log)) return True # Ensure recipient log consistency can_append_to_recipient_log = recipient_view_context.can_append_transfer() if can_append_to_recipient_log is not True: logger.error('Recipient: {}'.format(can_append_to_recipient_log)) return True # Ensure transfer consistency can_spend, currently_available_funds = wallet_view_context.can_send_transfer( current_eon_number=operator_eon_number, using_only_appended_funds=True) if can_spend is not True: passively_received = wallet_view_context.off_chain_passively_received_amount( eon_number=operator_eon_number, only_appended=True) logger.error(can_spend) logger.info(passively_received) return True last_active_transfer, last_active_transfer_is_outgoing = wallet_view_context.last_appended_active_transfer( operator_eon_number) last_active_state = WalletTransferContext.appropriate_transfer_active_state( transfer=last_active_transfer, is_outgoing=last_active_transfer_is_outgoing) previous_spendings = last_active_state.updated_spendings if last_active_transfer else 0 updated_spendings = transfer.sender_active_state.updated_spendings # Incorrect updated spendings if last_active_transfer: if updated_spendings != previous_spendings + transfer.amount: logger.error( 'Transfer {} invalid updated spendings. Expected {}, found {}.' .format(transfer.id, previous_spendings + transfer.amount, updated_spendings)) return True elif updated_spendings != transfer.amount: logger.error( 'Transfer {} invalid initial spendings. Expected {}, found {}.'. format(transfer.id, transfer.amount, updated_spendings)) return True # Incorrect transfer position last_passively_received = recipient_view_context.last_appended_incoming_passive_transfer( operator_eon_number) if last_passively_received: if transfer.position != last_passively_received.position + last_passively_received.amount: logger.error( 'Transfer {} invalid offset. Expected {}, found {}.'.format( transfer.id, last_passively_received.position + last_passively_received.amount, transfer.position)) return True elif transfer.position != 0: logger.error( 'Transfer {} invalid offset. Expected {}, found {}.'.format( transfer.id, 0, transfer.position)) return True if transfer.sender_balance_marker.amount > currently_available_funds - transfer.amount: logger.error('Transfer {} invalid concise marker balance.'.format( transfer.id)) return True concise_balance_marker = MinimumAvailableBalanceMarker( wallet=transfer.wallet, eon_number=transfer.eon_number, amount=transfer.sender_balance_marker.amount) concise_balance_marker_checksum = hex_value( concise_balance_marker.checksum()) if transfer.sender_balance_marker.signature.checksum != concise_balance_marker_checksum: logger.error( 'Transfer {} invalid concise marker checksum worth: {}'.format( transfer.id, currently_available_funds - transfer.amount)) passively_received = wallet_view_context.off_chain_passively_received_amount( eon_number=operator_eon_number, only_appended=True) logger.info(passively_received) return True return False
def update(self, swap, validated_data): current_swap = None is_swap_finalized = False with transaction.atomic(): current_eon = LocalViewInterface.latest().eon_number() swap_set = Transfer.objects.select_for_update().filter( tx_id=swap.tx_id, eon_number__gte=current_eon, swap=True).order_by('eon_number') current_swap = swap_set[0] if not current_swap.complete: raise serializers.ValidationError( detail='', code=ErrorCode.SWAP_NOT_FULFILLED) elif current_swap.cancelled: raise serializers.ValidationError( detail='', code=ErrorCode.SWAP_ALREADY_FROZEN) elif current_swap.voided: raise serializers.ValidationError( detail='', code=ErrorCode.SWAP_ALREADY_VOIDED) elif current_swap.processed: raise serializers.ValidationError( detail='', code=ErrorCode.SWAP_ALREADY_CLOSED) elif current_swap.recipient_finalization_active_state is not None: raise serializers.ValidationError( detail='', code=ErrorCode.SWAP_ALREADY_FINALIZED) finalization_signatures = validated_data.pop( 'finalization_signature') # state to save finalization_active_state_signature_records = [] finalization_active_state_records = [] if swap_set.count() != len(finalization_signatures): raise serializers.ValidationError( detail= 'Wrong number of finalization signatures, expected {} but got {}' .format(swap_set.count(), len(finalization_signatures)), code=ErrorCode.WRONG_NUMBER_OF_CREDIT_SIGNATURES) recipient_view_context = WalletTransferContext( wallet=current_swap.recipient, transfer=current_swap) tx_set_tree = recipient_view_context.optimized_authorized_transfers_tree( ) tx_set_hash = crypto.hex_value(tx_set_tree.root_hash()) transfer_index = tx_set_tree.merkle_tree_nonce_map.get( current_swap.nonce) transfer_proof = tx_set_tree.proof(transfer_index) highest_spendings, highest_gains = recipient_view_context.off_chain_actively_sent_received_amounts( eon_number=current_swap.eon_number, only_appended=False) finalization_active_state = ActiveState( wallet=current_swap.recipient, updated_spendings=highest_spendings + current_swap.amount_swapped, updated_gains=highest_gains + current_swap.amount_swapped, tx_set_hash=tx_set_hash, tx_set_proof_hashes=transfer_proof, tx_set_index=transfer_index, eon_number=current_swap.eon_number) finalization_active_state_signature_data = finalization_signatures[ 0] finalization_active_state_checksum = crypto.hex_value( finalization_active_state.checksum()) finalization_active_state_signature = Signature( wallet=current_swap.recipient, checksum=finalization_active_state_checksum, value=finalization_active_state_signature_data.get('value')) if not finalization_active_state_signature.is_valid(): raise serializers.ValidationError( detail='', code=ErrorCode.INVALID_CREDIT_SIGNATURE) finalization_active_state_signature_records.append( finalization_active_state_signature) finalization_active_state_records.append(finalization_active_state) # calculate future spent, gained, empty tx set future_spent_gained = max(highest_spendings, highest_gains) + swap.amount_swapped + 1 empty_tx_set_hash = crypto.hex_value(NODE_CACHE[0]['hash']) for index in range(1, len(swap_set)): future_swap = swap_set[index] finalization_active_state = ActiveState( wallet=future_swap.recipient, updated_spendings=future_spent_gained, updated_gains=future_spent_gained, tx_set_hash=empty_tx_set_hash, # any dummy value tx_set_proof_hashes='', # any dummy value tx_set_index=0, eon_number=future_swap.eon_number) finalization_active_state_checksum = crypto.hex_value( finalization_active_state.checksum()) finalization_active_state_signature = Signature( wallet=swap.recipient, checksum=finalization_active_state_checksum, value=finalization_signatures[index].get('value')) if not finalization_active_state_signature.is_valid(): raise serializers.ValidationError( detail='', code=ErrorCode.INVALID_FUTURE_CREDIT_SIGNATURE) finalization_active_state_signature_records.append( finalization_active_state_signature) finalization_active_state_records.append( finalization_active_state) Signature.objects.bulk_create( finalization_active_state_signature_records) with current_swap.lock( auto_renewal=False), current_swap.wallet.lock( auto_renewal=False), current_swap.recipient.lock( auto_renewal=False): for index in range(len(finalization_active_state_records)): finalization_active_state_records[ index].wallet_signature = finalization_active_state_signature_records[ index] ActiveState.objects.bulk_create( finalization_active_state_records) for index in range(len(swap_set)): swap_set[ index].recipient_finalization_active_state = finalization_active_state_records[ index] if index > 0: swap_set[index].voided = True swap_set[index].appended = False swap_set[index].processed = True Transfer.objects.bulk_update(swap_set, [ 'recipient_finalization_active_state', 'voided', 'appended', 'processed' ]) swap_set[0].sign_swap_finalization( settings.HUB_OWNER_ACCOUNT_ADDRESS, settings.HUB_OWNER_ACCOUNT_KEY) swap_set[0].close(complete=True, appended=True) current_swap = swap_set[0] is_swap_finalized = True if is_swap_finalized: operator_celery.send_task('auditor.tasks.on_swap_finalization', args=[current_swap.id]) return current_swap
def init_swap_challenge(test_case: RPCTestCase, swap: Transfer, eon_number): sender_transfer_context = WalletTransferContext(wallet=swap.wallet, transfer=None) if Transfer.objects.filter(eon_number=swap.eon_number - 1, tx_id=swap.tx_id).exists(): starting_balance = int(swap.sender_starting_balance) else: starting_balance = int( sender_transfer_context.starting_balance_in_eon(eon_number)) transfers_list_nonce_index_map = {} transfers_list = sender_transfer_context.authorized_transfers_list_shorthand( only_appended=True, force_append=False, eon_number=eon_number, last_transfer_is_finalized=True, index_map=transfers_list_nonce_index_map, starting_balance=starting_balance) sender_active_state = sender_transfer_context.last_appended_active_state( eon_number=eon_number) transfer_tree = TransactionMerkleTree(transfers_list) transfer_index = transfers_list_nonce_index_map.get(int(swap.nonce)) transfer_node = transfer_tree.merkle_tree_leaf_map.get(transfer_index) transfer_proof = [ node.get('hash') for node in calculate_merkle_proof(transfer_index, transfer_node) ] test_case.assertEqual(sender_active_state.tx_set_hash, crypto.hex_value(transfer_tree.root_hash())) tx_set_root = crypto.zfill( crypto.decode_hex(sender_active_state.tx_set_hash)) deltas = [ int(sender_active_state.updated_spendings), int(sender_active_state.updated_gains) ] test_case.assertTrue( test_case.contract_interface.check_merkle_membership_proof( trail=int(transfer_index), chain=[crypto.zfill(x) for x in transfer_proof], node=transfer_node.get('hash'), merkle_root=tx_set_root)) token_commitment = TokenCommitment.objects.get( token=swap.wallet.token, root_commitment__eon_number=eon_number + 1) v, r, s = sender_active_state.operator_signature.vrs() # swap_sender_balance = sender_transfer_context.balance_as_of_eon( # eon_number) sender_balance = sender_transfer_context.balance_as_of_eon(eon_number + 1) passive_checksum, passive_amount, passive_marker = sender_transfer_context.get_passive_values( eon_number=eon_number + 1) swap_order = [ int(swap.amount), # sell int(swap.amount_swapped), # buy # int(swap_sender_balance.right - swap_sender_balance.left), starting_balance, # balance int(swap.nonce) ] # nonce chain_transition_checksum = test_case.contract_interface.check_proof_of_transition_agreement( token_address=swap.wallet.token.address, holder=swap.wallet.address, trail_identifier=swap.wallet.trail_identifier, eon_number=eon_number, tx_set_root=tx_set_root, deltas=deltas, attester=settings.HUB_OWNER_ACCOUNT_ADDRESS, r=crypto.uint256(r), s=crypto.uint256(s), v=v) test_case.assertEqual(crypto.hex_value(sender_active_state.checksum()), crypto.hex_value(chain_transition_checksum)) node_hash = merkle_tree.leaf_hash( merkle_tree.wallet_leaf_inner_hash, { 'contract': settings.HUB_LQD_CONTRACT_ADDRESS, 'token': swap.wallet.token.address, 'wallet': swap.wallet.address, 'left': sender_balance.left, 'right': sender_balance.right, 'active_state_checksum': sender_active_state.checksum(), 'passive_checksum': passive_checksum, 'passive_amount': passive_amount, 'passive_marker': passive_marker, }) checkpoint = RootCommitment.objects.get(eon_number=eon_number + 1) test_case.contract_interface.check_exclusive_allotment_proof( allotment_trail=int(sender_balance.merkle_proof_trail), membership_trail=swap.wallet.token.trail, node=node_hash, merkle_root=crypto.decode_hex(checkpoint.merkle_root), allotment_chain=[ crypto.zfill(crypto.decode_hex(v)) for v in long_string_to_list( sender_balance.merkle_proof_hashes, 64) ], membership_chain=[ crypto.zfill(crypto.decode_hex(checksum)) for checksum in long_string_to_list( token_commitment.membership_hashes, 64) ], value=csf_to_list(sender_balance.merkle_proof_values, int), left=int(sender_balance.left), right=int(sender_balance.right)) test_case.contract_interface.issue_swap_challenge( token_pair=[swap.wallet.token.address, swap.recipient.token.address], wallet=swap.wallet.address, swap_order=swap_order, sender_tx_recipient_trails=[ swap.wallet.trail_identifier, int(transfer_index), swap.recipient.trail_identifier ], allotment_chain=[ crypto.zfill(crypto.decode_hex(v)) for v in long_string_to_list( sender_balance.merkle_proof_hashes, 64) ], membership_chain=[ crypto.zfill(crypto.decode_hex(checksum)) for checksum in long_string_to_list( token_commitment.membership_hashes, 64) ], tx_chain=[crypto.zfill(x) for x in transfer_proof], values=csf_to_list(sender_balance.merkle_proof_values, int), l_r=[int(sender_balance.left), int(sender_balance.right)], tx_set_root=tx_set_root, deltas=deltas, passive_checksum=passive_checksum, passive_amount=passive_amount, passive_marker=passive_marker)
def test_partial_match_multi_eon_swap(self): commit_eon(test_case=self, eon_number=1) advance_to_next_eon(test_case=self, eon_number=1) commit_eon(test_case=self, eon_number=2) buy_lqd_nonce = random.randint(1, 999999) sell_lqd_nonce = random.randint(1, 999999) total_remaining_eons = 8 # make persistent swap send_swap( # Buy LQD at 0.5 ETH test_case=self, eon_number=2, account=self.testrpc_accounts[1], token=self.eth_token, token_swapped=self.lqd_token, amount=4, amount_swapped=8, nonce=buy_lqd_nonce, eon_count=total_remaining_eons) swap = Transfer.objects.filter( swap=True, wallet__token=self.eth_token).order_by('id')[0] swap_tx_id = swap.tx_id wallet_transfer_context = WalletTransferContext(wallet=swap.wallet, transfer=None) recipient_transfer_context = WalletTransferContext( wallet=swap.recipient, transfer=None) wallet_funds_before = 4 recipient_funds_before = 0 # process swaps confirm_swaps_for_eon(operator_eon_number=2) cancel_finalize_swaps_for_eon(operator_eon_number=2) process_swaps_for_eon(operator_eon_number=2) # make opposite swap send_swap( # Sell LQD at 0.5 ETH test_case=self, eon_number=2, account=self.testrpc_accounts[2], token=self.lqd_token, token_swapped=self.eth_token, amount=2, amount_swapped=1, nonce=sell_lqd_nonce, eon_count=1) # process swaps confirm_swaps_for_eon(operator_eon_number=2) cancel_finalize_swaps_for_eon(operator_eon_number=2) process_swaps_for_eon(operator_eon_number=2) self.assertEqual( wallet_transfer_context.available_funds_at_eon(2, False), wallet_funds_before - 1) self.assertEqual( recipient_transfer_context.available_funds_at_eon(2, False), recipient_funds_before + 2) wallet_funds_before = wallet_funds_before - 1 recipient_funds_before = recipient_funds_before + 2 # skip some eons for i in range(3, 5): # proceed to next eon advance_to_next_eon(test_case=self, eon_number=i - 1) commit_eon(test_case=self, eon_number=i) total_remaining_eons -= 1 swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=i) self.assertEqual(swap.eon_number, i) # process swaps confirm_swaps_for_eon(operator_eon_number=i) cancel_finalize_swaps_for_eon(operator_eon_number=i) process_swaps_for_eon(operator_eon_number=i) self.assertEqual( wallet_transfer_context.available_funds_at_eon(i, False), wallet_funds_before) self.assertEqual( recipient_transfer_context.available_funds_at_eon(i, False), recipient_funds_before) new_wallet_funds = wallet_funds_before new_recipient_funds = recipient_funds_before # partial match across eons for i in range(5, 8): # proceed to next eon advance_to_next_eon(test_case=self, eon_number=i - 1) commit_eon(test_case=self, eon_number=i) total_remaining_eons -= 1 swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=i) self.assertEqual(swap.eon_number, i) # process swaps confirm_swaps_for_eon(operator_eon_number=i) cancel_finalize_swaps_for_eon(operator_eon_number=i) process_swaps_for_eon(operator_eon_number=i) self.assertEqual( wallet_transfer_context.available_funds_at_eon(i, False), new_wallet_funds) self.assertEqual( recipient_transfer_context.available_funds_at_eon(i, False), new_recipient_funds) # make opposite swap send_swap( # Sell LQD at 0.5 ETH test_case=self, eon_number=i, account=self.testrpc_accounts[i - 2], token=self.lqd_token, token_swapped=self.eth_token, amount=2, amount_swapped=1, nonce=sell_lqd_nonce, eon_count=1) # process swaps confirm_swaps_for_eon(operator_eon_number=i) cancel_finalize_swaps_for_eon(operator_eon_number=i) process_swaps_for_eon(operator_eon_number=i) finalize_last_swap(test_case=self, token=self.lqd_token, token_swapped=self.eth_token, account=self.testrpc_accounts[i - 2], eon_count=1) # process swaps confirm_swaps_for_eon(operator_eon_number=i) cancel_finalize_swaps_for_eon(operator_eon_number=i) process_swaps_for_eon(operator_eon_number=i) self.assertEqual( wallet_transfer_context.available_funds_at_eon(i, False), new_wallet_funds - 1) self.assertEqual( recipient_transfer_context.available_funds_at_eon(i, False), new_recipient_funds + 2) new_wallet_funds = new_wallet_funds - 1 new_recipient_funds = new_recipient_funds + 2 swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=7) matched_out, matched_in = swap.matched_amounts(all_eons=True) # self.assertTrue(swap.processed) self.assertTrue(swap.appended) self.assertFalse(swap.voided) self.assertFalse(swap.cancelled) self.assertEqual(matched_out, 4) self.assertEqual(matched_in, 8) self.assertTrue(swap.complete) self.assertEqual( Transfer.objects.filter(tx_id=swap_tx_id, eon_number__gt=7, swap=True, voided=False).count(), 0) finalize_swap(test_case=self, swap=swap, account=self.testrpc_accounts[1], eon_count=total_remaining_eons) # process swaps confirm_swaps_for_eon(operator_eon_number=7) cancel_finalize_swaps_for_eon(operator_eon_number=7) process_swaps_for_eon(operator_eon_number=7) self.assertEqual( wallet_transfer_context.available_funds_at_eon(7, False), wallet_funds_before - 3) self.assertEqual( recipient_transfer_context.available_funds_at_eon(7, False), recipient_funds_before + 6) advance_to_next_eon(test_case=self, eon_number=7) commit_eon(test_case=self, eon_number=8) # process swaps confirm_swaps_for_eon(operator_eon_number=8) cancel_finalize_swaps_for_eon(operator_eon_number=8) process_swaps_for_eon(operator_eon_number=8) self.assertEqual( wallet_transfer_context.available_funds_at_eon(8, False), wallet_funds_before - 3) self.assertEqual( recipient_transfer_context.available_funds_at_eon(8, False), recipient_funds_before + 6)
def finalize_swap(test_case: RPCTestCase, swap: Transfer, account, expected_status=status.HTTP_200_OK, eon_count=1): print('FINALIZING {} ({}/{})'.format(swap.id, int(swap.amount), int(swap.amount_swapped))) finalization_authorizations = [] test_case.assertTrue(swap.complete) recipient_view_context = WalletTransferContext(wallet=swap.recipient, transfer=swap) tx_set_tree = recipient_view_context.authorized_transfers_tree( only_appended=False, force_append=True) tx_set_hash = crypto.hex_value(tx_set_tree.root_hash()) transfer_index = tx_set_tree.merkle_tree_nonce_map.get(swap.nonce) transfer_proof = tx_set_tree.proof(transfer_index) highest_spendings, highest_gains = recipient_view_context.off_chain_actively_sent_received_amounts( eon_number=swap.eon_number, only_appended=False) print("Finalize spent {} gained {}".format(highest_spendings, highest_gains)) for state in ActiveState.objects.filter(wallet=swap.recipient, eon_number=swap.eon_number): print(state.id) print("Finalize spent {} gained {}".format(state.updated_spendings, state.updated_gains)) finalization_active_state = ActiveState( wallet=swap.recipient, updated_spendings=highest_spendings + swap.amount_swapped, updated_gains=highest_gains + swap.amount_swapped, tx_set_hash=tx_set_hash, tx_set_proof_hashes=transfer_proof, tx_set_index=transfer_index, eon_number=swap.eon_number) finalization_authorizations.append({ 'value': encode_signature( sign_message(finalization_active_state.checksum(), account.get('pk'))) }) for i in range(1, eon_count): future_spent_gained = max(highest_spendings, highest_gains) + swap.amount_swapped + 1 empty_tx_set_hash = crypto.hex_value(NODE_CACHE[0]['hash']) finalization_active_state = ActiveState( wallet=swap.recipient, updated_spendings=future_spent_gained, updated_gains=future_spent_gained, tx_set_hash=empty_tx_set_hash, # any dummy value tx_set_proof_hashes='', # any dummy value tx_set_index=0, eon_number=swap.eon_number + i) finalization_authorizations.append({ 'value': encode_signature( sign_message(finalization_active_state.checksum(), account.get('pk'))) }) # Make API Request url = reverse('finalize-swap-endpoint', kwargs={'pk': swap.id}) data = {'finalization_signature': finalization_authorizations} # Send tx to server x = datetime.now() response = test_case.client.put(url, data, format='json') y = datetime.now() delta = y - x # Ensure the transaction was recorded test_case.assertEqual(response.status_code, expected_status, response.content) print('FINALIZE Time: {}s'.format(delta)) # Log time delta return delta
def cancel_swap(test_case: RPCTestCase, swap: Transfer, account, expected_status=status.HTTP_200_OK, eon_count=1): sender_cancellation_authorizations = [] recipient_cancellation_authorizations = [] sender_view_context = WalletTransferContext(wallet=swap.wallet, transfer=swap) tx_set_tree = sender_view_context.authorized_transfers_tree( only_appended=False, force_append=False, assume_active_state_exists=True) tx_set_hash = crypto.hex_value(tx_set_tree.root_hash()) transfer_index = tx_set_tree.merkle_tree_nonce_map.get(swap.nonce) transfer_proof = tx_set_tree.proof(transfer_index) sender_highest_spendings, sender_highest_gains = sender_view_context.off_chain_actively_sent_received_amounts( eon_number=swap.eon_number, only_appended=False) matched_out, _ = swap.matched_amounts() sender_highest_gains += swap.amount - matched_out sender_cancellation_active_state = ActiveState( wallet=swap.wallet, updated_spendings=sender_highest_spendings, updated_gains=sender_highest_gains, tx_set_hash=tx_set_hash, tx_set_proof_hashes=transfer_proof, tx_set_index=transfer_index, eon_number=swap.eon_number) recipient_view_context = WalletTransferContext(wallet=swap.recipient, transfer=swap) tx_set_tree = recipient_view_context.authorized_transfers_tree( only_appended=False, force_append=False, assume_active_state_exists=True) tx_set_hash = crypto.hex_value(tx_set_tree.root_hash()) transfer_index = tx_set_tree.merkle_tree_nonce_map.get(swap.nonce) transfer_proof = tx_set_tree.proof(transfer_index) recipient_highest_spendings, recipient_highest_gains = recipient_view_context.off_chain_actively_sent_received_amounts( eon_number=swap.eon_number, only_appended=False) recipient_cancellation_active_state = ActiveState( wallet=swap.recipient, updated_spendings=recipient_highest_spendings + swap.amount_swapped, updated_gains=recipient_highest_gains + swap.amount_swapped, tx_set_hash=tx_set_hash, tx_set_proof_hashes=transfer_proof, tx_set_index=transfer_index, eon_number=swap.eon_number) sender_cancellation_authorizations.append({ 'value': encode_signature( sign_message(sender_cancellation_active_state.checksum(), account.get('pk'))) }) recipient_cancellation_authorizations.append({ 'value': encode_signature( sign_message(recipient_cancellation_active_state.checksum(), account.get('pk'))) }) for i in range(1, eon_count): empty_tx_set_hash = crypto.hex_value(NODE_CACHE[0]['hash']) sender_future_spent_gained = max(sender_highest_spendings, sender_highest_gains) + 1 recipient_future_spent_gained = max( recipient_highest_spendings, recipient_highest_gains) + swap.amount_swapped + 1 sender_cancellation_active_state = ActiveState( wallet=swap.wallet, updated_spendings=sender_future_spent_gained, updated_gains=sender_future_spent_gained, tx_set_hash=empty_tx_set_hash, # any dummy value tx_set_proof_hashes='', # any dummy value tx_set_index=0, eon_number=swap.eon_number + i) recipient_cancellation_active_state = ActiveState( wallet=swap.recipient, updated_spendings=recipient_future_spent_gained, updated_gains=recipient_future_spent_gained, tx_set_hash=empty_tx_set_hash, # any dummy value tx_set_proof_hashes='', # any dummy value tx_set_index=0, eon_number=swap.eon_number + i) sender_cancellation_authorizations.append({ 'value': encode_signature( sign_message(sender_cancellation_active_state.checksum(), account.get('pk'))) }) recipient_cancellation_authorizations.append({ 'value': encode_signature( sign_message(recipient_cancellation_active_state.checksum(), account.get('pk'))) }) # Make API Request url = reverse('cancel-swap-endpoint', kwargs={'pk': swap.id}) data = { 'sender_cancellation_signature': sender_cancellation_authorizations, 'recipient_cancellation_signature': recipient_cancellation_authorizations } # Send tx to server x = datetime.now() response = test_case.client.put(url, data, format='json') y = datetime.now() delta = y - x # Ensure the transaction was recorded test_case.assertEqual(response.status_code, expected_status, response.content) print('CANCEL Time: {}s'.format(delta)) # Log time delta return delta
def send_swap(test_case: RPCTestCase, eon_number, account, token, token_swapped, amount, amount_swapped, nonce, expected_status=status.HTTP_201_CREATED, eon_count=1, sell_order=True): # Sender account sender_wallet = Wallet.objects.get(address=remove_0x_prefix( account.get('address')), token=token) # Recipient account recipient_wallet = Wallet.objects.get(address=remove_0x_prefix( account.get('address')), token=token_swapped) sender_wallet_context = WalletTransferContext(wallet=sender_wallet, transfer=None) recipient_wallet_context = WalletTransferContext(wallet=recipient_wallet, transfer=None) initial_sender_balance = sender_wallet_context.available_funds_at_eon( eon_number=eon_number, only_appended=False) initial_recipient_balance = recipient_wallet_context.available_funds_at_eon( eon_number=eon_number, only_appended=False) if initial_sender_balance < amount: send_transaction(test_case=test_case, eon_number=eon_number, sender=testrpc_accounts.accounts[0], recipient=account, amount=amount - initial_sender_balance, nonce=random.randint(1, 999999), token=token) if initial_sender_balance > amount: # clear sender account send_transaction(test_case=test_case, eon_number=eon_number, sender=account, recipient=testrpc_accounts.accounts[0], amount=initial_sender_balance - amount, nonce=random.randint(1, 999999), token=token) if initial_recipient_balance > 0: # clear recipient account send_transaction(test_case=test_case, eon_number=eon_number, sender=account, recipient=testrpc_accounts.accounts[0], amount=initial_recipient_balance, nonce=random.randint(1, 999999), token=token_swapped) sender_balance = sender_wallet_context.available_funds_at_eon( eon_number=eon_number, only_appended=False) recipient_balance = recipient_wallet_context.available_funds_at_eon( eon_number=eon_number, only_appended=False) test_case.assertEqual(sender_balance, amount) test_case.assertEqual(recipient_balance, 0) debit_balance_signatures = [] debit_signatures = [] credit_balance_signatures = [] credit_signatures = [] fulfillment_signatures = [] for i in range(eon_count): swap = Transfer(wallet=sender_wallet, amount=amount, eon_number=eon_number + i, recipient=recipient_wallet, amount_swapped=amount_swapped, nonce=nonce, processed=False, complete=False, swap=True) sender_wallet_context = WalletTransferContext(wallet=sender_wallet, transfer=swap) recipient_wallet_context = WalletTransferContext( wallet=recipient_wallet, transfer=swap) sender_highest_spent, sender_highest_gained = sender_wallet_context.off_chain_actively_sent_received_amounts( eon_number=eon_number + i, only_appended=False) if i == 0: tx_set_tree = sender_wallet_context.authorized_transfers_tree( only_appended=False, force_append=True) else: tx_set_tree = WalletTransferContext.authorized_transfers_tree_from_list( [ swap.shorthand(sender_wallet_context, is_last_transfer=True, starting_balance=sender_balance) ]) tx_set_hash = hex_value(tx_set_tree.root_hash()) debiting_active_state = ActiveState( wallet=sender_wallet, updated_spendings=sender_highest_spent + amount, updated_gains=sender_highest_gained, eon_number=eon_number + i, tx_set_hash=tx_set_hash) debiting_active_state_authorization = sign_message( debiting_active_state.checksum(), account.get('pk')) debiting_active_state_signature = Signature( wallet=sender_wallet, checksum=hex_value(debiting_active_state.checksum()), value=encode_signature(debiting_active_state_authorization)) test_case.assertTrue(debiting_active_state_signature.is_valid()) debit_concise_balance_marker = MinimumAvailableBalanceMarker( wallet=sender_wallet, eon_number=eon_number + i, amount=0) debit_concise_balance_marker_authorization = sign_message( debit_concise_balance_marker.checksum(), account.get('pk')) debit_concise_balance_marker_signature = Signature( wallet=sender_wallet, checksum=hex_value(debit_concise_balance_marker.checksum()), value=encode_signature(debit_concise_balance_marker_authorization)) test_case.assertTrue(debit_concise_balance_marker_signature.is_valid()) recipient_highest_spent, recipient_highest_gained = recipient_wallet_context.off_chain_actively_sent_received_amounts( eon_number=eon_number + i, only_appended=False) if i == 0: tx_set_tree = recipient_wallet_context.authorized_transfers_tree( only_appended=False, force_append=True) else: tx_set_tree = WalletTransferContext.authorized_transfers_tree_from_list( [ swap.shorthand(recipient_wallet_context, is_last_transfer=True, starting_balance=recipient_balance) ]) tx_set_hash = hex_value(tx_set_tree.root_hash()) crediting_active_state = ActiveState( wallet=recipient_wallet, updated_spendings=recipient_highest_spent, updated_gains=recipient_highest_gained, eon_number=eon_number + i, tx_set_hash=tx_set_hash) crediting_active_state_authorization = sign_message( crediting_active_state.checksum(), account.get('pk')) crediting_active_state_signature = Signature( wallet=recipient_wallet, checksum=hex_value(crediting_active_state.checksum()), value=encode_signature(crediting_active_state_authorization)) test_case.assertTrue(crediting_active_state_signature.is_valid()) credit_concise_balance_marker = MinimumAvailableBalanceMarker( wallet=recipient_wallet, eon_number=eon_number + i, amount=0) credit_concise_balance_marker_authorization = sign_message( credit_concise_balance_marker.checksum(), account.get('pk')) credit_concise_balance_marker_signature = Signature( wallet=recipient_wallet, checksum=hex_value(credit_concise_balance_marker.checksum()), value=encode_signature( credit_concise_balance_marker_authorization)) test_case.assertTrue( credit_concise_balance_marker_signature.is_valid()) swap.processed, swap.complete = True, True if i == 0: tx_set_tree = recipient_wallet_context.authorized_transfers_tree( only_appended=False, force_append=True) else: tx_set_tree = WalletTransferContext.authorized_transfers_tree_from_list( [ swap.shorthand(recipient_wallet_context, is_last_transfer=True, starting_balance=0) ]) tx_set_hash = hex_value(tx_set_tree.root_hash()) recipient_fulfillment_active_state = ActiveState( wallet=recipient_wallet, updated_spendings=recipient_highest_spent, updated_gains=recipient_highest_gained + amount_swapped, eon_number=eon_number + i, tx_set_hash=tx_set_hash) recipient_fulfillment_active_state_authorization = sign_message( recipient_fulfillment_active_state.checksum(), account.get('pk')) swap.processed, swap.complete = False, False recipient_fulfillment_active_state_signature = Signature( wallet=recipient_wallet, checksum=hex_value(recipient_fulfillment_active_state.checksum()), value=encode_signature( recipient_fulfillment_active_state_authorization)) test_case.assertTrue( recipient_fulfillment_active_state_signature.is_valid()) debit_balance_signatures.append({ 'value': encode_signature(debit_concise_balance_marker_authorization) }) debit_signatures.append( {'value': encode_signature(debiting_active_state_authorization)}) credit_balance_signatures.append({ 'value': encode_signature(credit_concise_balance_marker_authorization) }) credit_signatures.append( {'value': encode_signature(crediting_active_state_authorization)}) fulfillment_signatures.append({ 'value': encode_signature(recipient_fulfillment_active_state_authorization) }) # Make API Request url = reverse('swap-endpoint') data = { 'debit_signature': debit_signatures, 'debit_balance_signature': debit_balance_signatures, 'credit_signature': credit_signatures, 'credit_balance_signature': credit_balance_signatures, 'credit_fulfillment_signature': fulfillment_signatures, 'eon_number': eon_number, 'amount': amount, 'amount_swapped': amount_swapped, 'nonce': nonce, 'wallet': { 'address': sender_wallet.address, 'token': sender_wallet.token.address, }, 'recipient': { 'address': recipient_wallet.address, 'token': recipient_wallet.token.address, }, 'sell_order': sell_order } # Send tx to server x = datetime.now() response = test_case.client.post(url, data, format='json') y = datetime.now() delta = y - x # Ensure the transaction was recorded test_case.assertEqual(response.status_code, expected_status, response.content) print('SWAP Time: {}s for {}/{}'.format(delta, amount, amount_swapped)) # assert that swap created for current eon is confirmed tx = json.loads(response.content) swap = Transfer.objects.get(id=tx['id']) test_case.assertEqual(swap.eon_number, eon_number) test_case.assertTrue(swap.is_signed_by_operator()) # Log time delta return delta
def slash_bad_withdrawals(): contract_interface = NOCUSTContractInterface() current_eon = contract_interface.get_current_eon_number() # This lock is needed because some Withdrawal objects might be mutated, which can affect the checkpoint. with RootCommitment.global_lock(): checkpoint_created = RootCommitment.objects.filter( eon_number=current_eon).exists() # fetch all wallets that issued a pending withdrawal # pending withdrawals that were not slashed yet and issued during this eon or the previous one withdrawal_wallets = WithdrawalRequest.objects \ .filter(slashed=False, eon_number__gte=current_eon-1) \ .values_list('wallet', flat=True).distinct() for wallet_id in withdrawal_wallets: # fetch wallet object wallet = Wallet.objects.get(id=wallet_id) with wallet.lock(auto_renewal=True), transaction.atomic(): pending_withdrawals = WithdrawalRequest.objects \ .filter(wallet=wallet, slashed=False, eon_number__gte=current_eon-1) \ .select_for_update() # skip slashing if no slashable pending withdrawals are found if pending_withdrawals.count() == 0: logger.warning('Skipping withdrawal slash for {}. No Slashable Pending Withdrawals.' .format(wallet.address)) continue withdrawal_aggregate_query = pending_withdrawals.aggregate( Sum('amount'), Min('eon_number')) withdrawal_amount = withdrawal_aggregate_query['amount__sum'] min_eon = withdrawal_aggregate_query['eon_number__min'] withdrawals_in_current_eon = WithdrawalRequest.objects.filter( wallet=wallet, eon_number=current_eon).count() # create a tag for the tuple # (current eon, number of pending withdrawals in this eon, wallet id, total withdrawal amount ) tag = 'withdrawal_request_{}_{}_{}_{}' \ .format(current_eon, withdrawals_in_current_eon, wallet.id, withdrawal_amount) if EthereumTransaction.objects.filter(tag=tag).exists(): logger.warning( 'Skipping withdrawal slash for address {} and token {}. Slashing transaction already enqueued.' .format( wallet.address, wallet.token.address)) continue wallet_transfer_context = WalletTransferContext( wallet=wallet, transfer=None) available_balance = wallet_transfer_context.loosely_available_funds_at_eon( eon_number=current_eon, current_eon_number=current_eon, is_checkpoint_created=checkpoint_created, only_appended=True) if available_balance >= 0: logger.warning( 'Skipping withdrawal slash for address {} and token {}. Available balance covers amount.' .format( wallet.address, wallet.token.address)) continue # find minimum balance marker # start before min possible eon # until current eon minimum_balance = MinimumAvailableBalanceMarker.objects \ .filter( wallet=wallet, eon_number__gte=min_eon-1, eon_number__lte=current_eon) \ .order_by('amount') \ .first() if minimum_balance is None or minimum_balance.amount >= withdrawal_amount: logger.warning( 'Skipping withdrawal slash for address {} and token {}. Minimum balance within two epochs covers amount.' .format( wallet.address, wallet.token.address)) continue logger.warning('{}: Slashing withdrawals of {} > {} >= {}.' .format( wallet.address, withdrawal_amount, available_balance, minimum_balance.amount)) v, r, s = minimum_balance.signature.vrs() # slash all withdrawals for this wallet-token pair contract_interface.queue_slash_withdrawal( token_address=wallet.token.address, wallet_address=wallet.address, eon_number=minimum_balance.eon_number, available=int(minimum_balance.amount), r=crypto.uint256(r), s=crypto.uint256(s), v=v, tag=tag) # mark all related withdrawals as slashed pending_withdrawals.update(slashed=True)
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]
def respond_to_challenges(): contract_interface = NOCUSTContractInterface() contract_interface.get_current_eon_number() latest_root_commitment = RootCommitment.objects\ .all()\ .order_by('eon_number')\ .last() if latest_root_commitment is None: return with Challenge.global_lock(): challenges = Challenge.objects\ .filter(rebuted=False, eon_number__lte=latest_root_commitment.eon_number)\ .order_by('block') for challenge in challenges: token = challenge.wallet.token challenge_entry_token = token.address if challenge.wallet.token != challenge.recipient.token: try: tp = TokenPair.objects.get( token_from=challenge.wallet.token, token_to=challenge.recipient.token) except TokenPair.DoesNotExist: logger.warning( "Skipping challenge for {}. token pair not found!". format(challenge.wallet.address)) continue token = challenge.recipient.token challenge_entry_token = to_checksum_address(tp.conduit) challenge_entry = contract_interface.get_challenge_record( token_address=challenge_entry_token, recipient=challenge.recipient.address, sender=challenge.wallet.address) if not challenge_entry.challengeStage: logger.warning( "Skipping answered challenge for {}. Where is the answer tx_id?" .format(challenge.wallet.address)) continue try: recipient_balance = ExclusiveBalanceAllotment.objects.get( wallet=challenge.recipient, eon_number=challenge.eon_number) except ExclusiveBalanceAllotment.DoesNotExist: logger.error("Could not find balance for {} at eon {}.".format( challenge.wallet.address, challenge.eon_number)) send_admin_email(subject='DISPUTE! NO BALANCE!', content='{}'.format(challenge.wallet.address)) return token_commitment = TokenCommitment.objects.get( token=token, root_commitment__eon_number=challenge.eon_number) # state update challenge if challenge.wallet.token == challenge.recipient.token and challenge.wallet.address == challenge.recipient.address: v_0, r_0, s_0 = recipient_balance.wallet_v_r_s() v_1, r_1, s_1 = recipient_balance.operator_v_r_s() recipient_transfer_context = WalletTransferContext( wallet=challenge.recipient, transfer=None) passive_checksum, passive_amount, passive_marker = recipient_transfer_context.get_passive_values( eon_number=challenge_entry.initialStateEon) logger.info( "Answering challenge for {} with balance {}.".format( challenge.wallet.address, recipient_balance.amount())) logger.info("{}{}{}, {}{}{}".format(v_0, r_0, s_0, v_1, r_1, s_1)) send_admin_email(subject='DISPUTE! Sate Update.', content='{}'.format(challenge.wallet.address)) # TODO signal critical failure if this does not succeed! transaction = contract_interface.queue_answer_state_update_challenge( challenge=challenge, allotment_chain=[ crypto.zfill(crypto.decode_hex(checksum)) for checksum in long_string_to_list( recipient_balance.merkle_proof_hashes, 64) ], membership_chain=[ crypto.zfill(crypto.decode_hex(checksum)) for checksum in long_string_to_list( token_commitment.membership_hashes, 64) ], values=csf_to_list(recipient_balance.merkle_proof_values, int), l_r=[ int(recipient_balance.left), int(recipient_balance.right) ], tx_set_root=crypto.zfill( crypto.decode_hex( recipient_balance.transaction_set_root())), deltas=[d for d in recipient_balance.deltas()], r=[crypto.uint256(r_0), crypto.uint256(r_1)], s=[crypto.uint256(s_0), crypto.uint256(s_1)], v=[v_0, v_1], passive_checksum=passive_checksum, passive_amount=passive_amount, passive_marker=passive_marker) # transfer challenge elif challenge.wallet.token == challenge.recipient.token and challenge.wallet.address != challenge.recipient.address: try: transfer = Transfer.objects.get( recipient=challenge.recipient, eon_number=challenge_entry.initialStateEon, nonce=challenge_entry.deliveredTxNonce) except Transfer.DoesNotExist: logger.error( "Could not find transfer for {} at eon {} with nonce {}." .format(challenge.recipient.address, challenge.eon_number, challenge_entry.deliveredTxNonce)) send_admin_email( subject='DISPUTE! NO TRANSFER!', content= "Could not find transfer for {} at eon {} with nonce {}." .format(challenge.recipient.address, challenge.eon_number, challenge_entry.deliveredTxNonce)) return recipient_transfer_context = WalletTransferContext( wallet=challenge.recipient, transfer=None) transfers_list_nonce_index_map = {} transfers_list = recipient_transfer_context.authorized_transfers_list_shorthand( only_appended=True, force_append=False, eon_number=challenge_entry.initialStateEon, last_transfer_is_finalized=False, index_map=transfers_list_nonce_index_map) transfer_tree = TransactionMerkleTree(transfers_list) transfer_index = transfers_list_nonce_index_map.get( transfer.nonce) transfer_node = transfer_tree.merkle_tree_leaf_map.get( transfer_index) transfer_proof = [ node.get('hash') for node in calculate_merkle_proof( transfer_index, transfer_node) ] passive_checksum, passive_amount, passive_marker = recipient_transfer_context.get_passive_values( eon_number=challenge_entry.initialStateEon) send_admin_email(subject='DISPUTE! Transfer Delivery.', content='{} {} {}'.format( challenge.wallet.address, challenge.recipient.address, transfer_index)) # TODO signal critical failure if this does not succeed! transaction = contract_interface.queue_answer_delivery_challenge( challenge=challenge, tx_trail=transfer_index, allotment_chain=[ crypto.zfill(crypto.decode_hex(v)) for v in long_string_to_list( recipient_balance.merkle_proof_hashes, 64) ], membership_chain=[ crypto.zfill(crypto.decode_hex(checksum)) for checksum in long_string_to_list( token_commitment.membership_hashes, 64) ], values=csf_to_list(recipient_balance.merkle_proof_values, int), l_r=[ int(recipient_balance.left), int(recipient_balance.right) ], deltas=[d for d in recipient_balance.deltas()], tx_set_root=crypto.decode_hex( recipient_balance.transaction_set_root()), tx_chain=[crypto.zfill(x) for x in transfer_proof], passive_checksum=passive_checksum, passive_amount=passive_amount, passive_marker=passive_marker) # swap challenge elif challenge.wallet.token != challenge.recipient.token and challenge.wallet.address == challenge.recipient.address: try: transfer = Transfer.objects.get( recipient=challenge.recipient, eon_number=challenge_entry.initialStateEon, nonce=challenge_entry.deliveredTxNonce) except Transfer.DoesNotExist: logger.error( "Could not find transfer for {} at eon {} with nonce {}." .format(challenge.recipient.address, challenge.eon_number, challenge_entry.deliveredTxNonce)) send_admin_email( subject='DISPUTE! NO SWAP!', content= "Could not find transfer for {} at eon {} with nonce {}." .format(challenge.recipient.address, challenge.eon_number, challenge_entry.deliveredTxNonce)) return recipient_transfer_context = WalletTransferContext( wallet=challenge.recipient, transfer=None) # if not initial transfer in a multi eon swap # override starting balance to cached starting balance if Transfer.objects.filter(eon_number=transfer.eon_number - 1, tx_id=transfer.tx_id).exists(): starting_balance = int(transfer.recipient_starting_balance) else: starting_balance = int( recipient_transfer_context.starting_balance_in_eon( challenge_entry.initialStateEon)) transfers_list_nonce_index_map = {} transfers_list = recipient_transfer_context.authorized_transfers_list_shorthand( only_appended=True, force_append=False, eon_number=challenge_entry.initialStateEon, last_transfer_is_finalized=True, index_map=transfers_list_nonce_index_map, starting_balance=starting_balance) transfer_tree = TransactionMerkleTree(transfers_list) transfer_index = transfers_list_nonce_index_map.get( transfer.nonce) transfer_node = transfer_tree.merkle_tree_leaf_map.get( transfer_index) transfer_proof = [ node.get('hash') for node in calculate_merkle_proof( transfer_index, transfer_node) ] passive_checksum, passive_amount, passive_marker = recipient_transfer_context.get_passive_values( eon_number=challenge_entry.initialStateEon) send_admin_email(subject='DISPUTE! Swap Delivery.', content='{} {} {}'.format( challenge.wallet.address, challenge.recipient.address, transfer_index)) is_cancelled = transfer.cancelled and transfer.recipient_cancellation_active_state is not None if transfer.complete or is_cancelled: starting_balance = 2**256 - 1 # TODO signal critical failure if this does not succeed! transaction = contract_interface.queue_answer_swap_challenge( challenge=challenge, token_pair=[ challenge.wallet.token.address, challenge.recipient.token.address ], balance_at_start_of_eon=starting_balance, tx_trail=int(transfer_index), allotment_chain=[ crypto.zfill(crypto.decode_hex(v)) for v in long_string_to_list( recipient_balance.merkle_proof_hashes, 64) ], membership_chain=[ crypto.zfill(crypto.decode_hex(checksum)) for checksum in long_string_to_list( token_commitment.membership_hashes, 64) ], values=csf_to_list(recipient_balance.merkle_proof_values, int), l_r=[ int(recipient_balance.left), int(recipient_balance.right) ], deltas=[d for d in recipient_balance.deltas()], tx_set_root=crypto.zfill( crypto.decode_hex( recipient_balance.transaction_set_root())), tx_chain=[crypto.zfill(x) for x in transfer_proof], passive_checksum=passive_checksum, passive_amount=passive_amount, passive_marker=passive_marker) challenge.rebuted = True challenge.save() logger.warning(transaction)
def test_unmatched_swap(self): commit_eon(test_case=self, eon_number=1) advance_to_next_eon(test_case=self, eon_number=1) commit_eon(test_case=self, eon_number=2) # make a valid swap send_swap(test_case=self, eon_number=2, account=self.testrpc_accounts[1], token=self.eth_token, token_swapped=self.lqd_token, amount=1, amount_swapped=2, nonce=random.randint(1, 999999)) swap = Transfer.objects.filter(swap=True)[0] wallet_transfer_context = WalletTransferContext(wallet=swap.wallet, transfer=None) funds_before = 1 # process swaps confirm_swaps_for_eon(operator_eon_number=2) cancel_finalize_swaps_for_eon(operator_eon_number=2) process_swaps_for_eon(operator_eon_number=2) # proceed to next eon advance_to_next_eon(test_case=self, eon_number=2) commit_eon(test_case=self, eon_number=3) funds_after = wallet_transfer_context.balance_amount_as_of_eon(3) self.assertEqual(funds_before, funds_after) # make and cancel a swap send_swap(test_case=self, eon_number=3, account=self.testrpc_accounts[1], token=self.eth_token, token_swapped=self.lqd_token, amount=1, amount_swapped=2, nonce=random.randint(1, 999999)) freeze_last_swap(test_case=self, token=self.eth_token, token_swapped=self.lqd_token, account=self.testrpc_accounts[1]) cancel_last_swap(test_case=self, token=self.eth_token, token_swapped=self.lqd_token, account=self.testrpc_accounts[1]) funds_before = wallet_transfer_context.balance_amount_as_of_eon(3) # process swaps confirm_swaps_for_eon(operator_eon_number=3) cancel_finalize_swaps_for_eon(operator_eon_number=3) process_swaps_for_eon(operator_eon_number=3) # proceed to next eon advance_to_next_eon(test_case=self, eon_number=3) commit_eon(test_case=self, eon_number=4) funds_after = wallet_transfer_context.balance_amount_as_of_eon(4) self.assertEqual(funds_before, funds_after)
def process_passive_transfer(transfer, operator_eon_number, checkpoint_created): if transfer.wallet == transfer.recipient: logger.info('Voiding self transfer.') transfer.close(voided=True) return with transfer.lock(auto_renewal=True), transfer.wallet.lock( auto_renewal=True), transfer.recipient.lock(auto_renewal=True): wallet_view_context = WalletTransferContext(wallet=transfer.wallet, transfer=transfer) recipient_view_context = WalletTransferContext( wallet=transfer.recipient, transfer=transfer) if should_void_transfer(transfer, wallet_view_context, recipient_view_context, operator_eon_number, checkpoint_created): logger.info('Voiding transfer.') transfer.close(voided=True) return tx_set_tree = wallet_view_context.optimized_authorized_transfers_tree( only_appended=True) tx_set_hash = hex_value(tx_set_tree.root_hash()) highest_spendings, highest_gains = wallet_view_context.off_chain_actively_sent_received_amounts( eon_number=transfer.eon_number, only_appended=True) active_state = ActiveState( wallet=transfer.wallet, updated_spendings=transfer.sender_active_state.updated_spendings, updated_gains=highest_gains, tx_set_hash=tx_set_hash, eon_number=transfer.eon_number) raw_checksum = active_state.checksum() encoded_checksum = hex_value(raw_checksum) wallet_active_state = transfer.sender_active_state if wallet_active_state.wallet_signature.checksum != encoded_checksum: logger.error( 'Transfer {} invalid sender active state checksum for {}'. format(transfer.id, transfer.wallet.address)) transfer.close(voided=True) return try: wallet_active_state.operator_signature = wallet_active_state.sign_active_state( settings.HUB_OWNER_ACCOUNT_ADDRESS, settings.HUB_OWNER_ACCOUNT_KEY) except LookupError as e: logger.error(e) return transfer.sender_active_state.save() transfer.close(complete=True, appended=True) operator_celery.send_task('auditor.tasks.on_transfer_confirmation', args=[transfer.id]) logger.info('Passive transfer {} processed.'.format(transfer.id))
def test_checkpoint_creation(self): self.eth_token = Token.objects.first() lqd_token_address = deploy_new_test_token(test_case=self) distribute_token_balance_to_addresses( test_case=self, token_address=lqd_token_address, recipients=testrpc_accounts.accounts) self.lqd_token = register_token(token_address=lqd_token_address, name='Liquidity', short_name='LQD', register_on_chain=True) send_queued_transactions() self.tokens = [self.eth_token, self.lqd_token] TokenPair.objects.create(token_from=self.eth_token, token_to=self.lqd_token) TokenPair.objects.create(token_from=self.lqd_token, token_to=self.eth_token) self.registered_accounts = { self.eth_token: register_testrpc_accounts(self, token=self.eth_token), self.lqd_token: register_testrpc_accounts(self, token=self.lqd_token) } for token in self.tokens: self.assertEqual( self.contract_interface.get_unmanaged_funds(token.address, 1), 0) self.assertEqual( self.contract_interface.get_managed_funds(token.address, 1), 0) for token in self.tokens: create_deposits(self, testrpc_accounts.accounts, token) for token in self.tokens: self.assertEqual( self.contract_interface.get_unmanaged_funds(token.address, 1), self.contract_interface.get_total_balance(token.address)) self.assertEqual( self.contract_interface.get_managed_funds(token.address, 1), 0) for account in testrpc_accounts.accounts[1:5]: eth_wallet = Wallet.objects.get(address=remove_0x_prefix( account.get('address')), token=self.eth_token) lqd_wallet = Wallet.objects.get(address=remove_0x_prefix( account.get('address')), token=self.lqd_token) eth_wallet_context = WalletTransferContext(wallet=eth_wallet, transfer=None) lqd_wallet_context = WalletTransferContext(wallet=lqd_wallet, transfer=None) account[ 'reduced_eth_balance'] = eth_wallet_context.available_funds_at_eon( eon_number=1, only_appended=False) - 1 account[ 'reduced_lqd_balance'] = lqd_wallet_context.available_funds_at_eon( eon_number=1, only_appended=False) send_swap( # Buy LQD at 0.5 ETH test_case=self, eon_number=1, account=account, token=self.eth_token, token_swapped=self.lqd_token, amount=1, amount_swapped=2, nonce=random.randint(1, 999999)) for account in testrpc_accounts.accounts[5:9]: eth_wallet = Wallet.objects.get(address=remove_0x_prefix( account.get('address')), token=self.eth_token) lqd_wallet = Wallet.objects.get(address=remove_0x_prefix( account.get('address')), token=self.lqd_token) eth_wallet_context = WalletTransferContext(wallet=eth_wallet, transfer=None) lqd_wallet_context = WalletTransferContext(wallet=lqd_wallet, transfer=None) account[ 'reduced_eth_balance'] = eth_wallet_context.available_funds_at_eon( eon_number=1, only_appended=False) account[ 'reduced_lqd_balance'] = lqd_wallet_context.available_funds_at_eon( eon_number=1, only_appended=False) - 2 send_swap( # Sell LQD at 0.5 ETH test_case=self, eon_number=1, account=account, token=self.lqd_token, token_swapped=self.eth_token, amount=2, amount_swapped=1, nonce=random.randint(1, 999999)) confirm_swaps_for_eon(operator_eon_number=1) cancel_finalize_swaps_for_eon(operator_eon_number=1) process_swaps_for_eon(operator_eon_number=1) for account in testrpc_accounts.accounts[1:5]: finalize_last_swap(test_case=self, token=self.eth_token, token_swapped=self.lqd_token, account=account) for account in testrpc_accounts.accounts[5:9]: finalize_last_swap(test_case=self, token=self.lqd_token, token_swapped=self.eth_token, account=account) confirm_swaps_for_eon(operator_eon_number=1) cancel_finalize_swaps_for_eon(operator_eon_number=1) process_swaps_for_eon(operator_eon_number=1) buyers_sellers = [ testrpc_accounts.accounts[1:5], testrpc_accounts.accounts[5:9] ] for i in range(0, 2): for account in buyers_sellers[i]: eth_wallet = Wallet.objects.get(address=remove_0x_prefix( account.get('address')), token=self.eth_token) lqd_wallet = Wallet.objects.get(address=remove_0x_prefix( account.get('address')), token=self.lqd_token) eth_wallet_context = WalletTransferContext(wallet=eth_wallet, transfer=None) lqd_wallet_context = WalletTransferContext(wallet=lqd_wallet, transfer=None) eth_out, eth_in = eth_wallet_context.off_chain_actively_sent_received_amounts( eon_number=1, only_appended=False) lqd_out, lqd_in = lqd_wallet_context.off_chain_actively_sent_received_amounts( eon_number=1, only_appended=False) eth_out -= account['reduced_eth_balance'] lqd_out -= account['reduced_lqd_balance'] if i == 0: # LQD buyers assert eth_out == 1 and eth_in == 0, '{}/{}'.format( eth_out, eth_in) assert lqd_out - 2 == 0 and lqd_in - 2 == 2, '{}/{}'.format( lqd_out, lqd_in) else: # LQD sellers assert lqd_out == 2 and lqd_in == 0, '{}/{}'.format( lqd_out, lqd_in) assert eth_out - 1 == 0 and eth_in - 1 == 1, '{}/{}'.format( eth_out, eth_in) # Verify transfers were complete swaps = Transfer.objects.filter(swap=True) for transfer in swaps: self.assertTrue(transfer.is_fulfilled_swap()) commit_eon(test_case=self, eon_number=1) advance_to_next_eon(test_case=self, eon_number=1) commit_eon(test_case=self, eon_number=2)
def place_parallel_withdrawals(test_case: RPCTestCase, token, wallet_address, current_eon, dishonest=False): token_commitment = TokenCommitment.objects.get( token=token, root_commitment__eon_number=current_eon - 1) wallet = Wallet.objects.get(token=token, address=remove_0x_prefix(wallet_address)) wallet_transfer_context = WalletTransferContext(wallet=wallet, transfer=None) allotment = wallet_transfer_context.balance_as_of_eon( eon_number=current_eon - 1) passive_checksum, passive_amount, passive_marker = wallet_transfer_context.get_passive_values( eon_number=current_eon - 1) available_balance = wallet_transfer_context.loosely_available_funds_at_eon( eon_number=current_eon, current_eon_number=current_eon, is_checkpoint_created=True, only_appended=True) overdraw = dishonest and available_balance < allotment.amount() if overdraw: total_draw = max(available_balance, allotment.amount()) // 4 else: total_draw = min(available_balance, allotment.amount()) // 4 total_amount = 0 if (total_draw == 0): return (total_amount, [], False) withdrawal_amounts = [total_draw // 4, total_draw // 2, total_draw // 4] for withdrawal_amount in withdrawal_amounts: cyan([ wallet.address, wallet.token.address, withdrawal_amount, available_balance ]) test_case.contract_interface.withdraw( token_address=wallet.token.address, wallet=wallet.address, active_state_checksum=crypto.zfill( allotment.active_state_checksum()), trail=int(allotment.merkle_proof_trail), allotment_chain=[ crypto.zfill(crypto.decode_hex(checksum)) for checksum in long_string_to_list( allotment.merkle_proof_hashes, 64) ], membership_chain=[ crypto.zfill(crypto.decode_hex(checksum)) for checksum in long_string_to_list( token_commitment.membership_hashes, 64) ], values=csf_to_list(allotment.merkle_proof_values, int), exclusive_allotment_interval=[ int(allotment.left), int(allotment.right) ], withdrawal_amount=int(withdrawal_amount), passive_checksum=passive_checksum, passive_amount=passive_amount, passive_marker=passive_marker) total_amount += int(withdrawal_amount) return (total_amount, withdrawal_amounts, overdraw)
def process_swaps_for_eon(operator_eon_number): checkpoint_created = RootCommitment.objects.filter( eon_number=operator_eon_number).exists() notification_queue = [] with transaction.atomic(): default_time = timezone.now() - datetime.timedelta(days=365000) # Match swaps last_unprocessed_swap_time = timezone.make_aware( datetime.datetime.fromtimestamp( cache.get_or_set('last_unprocessed_swap_time', default_time.timestamp()))) unprocessed_swaps = Transfer.objects \ .filter( time__gte=last_unprocessed_swap_time, processed=False, complete=False, voided=False, cancelled=False, swap=True, eon_number=operator_eon_number, sender_active_state__operator_signature__isnull=False, recipient_active_state__operator_signature__isnull=False) \ .select_for_update() \ .order_by('time') order_books_cache = {} for swap in unprocessed_swaps: last_unprocessed_swap_time = max( last_unprocessed_swap_time, swap.time + datetime.timedelta(milliseconds=1)) matched_successfully = False with transaction.atomic(), swap.lock( auto_renewal=True), swap.wallet.lock( auto_renewal=True), swap.recipient.lock( auto_renewal=True): swap_wallet_view_context = WalletTransferContext( wallet=swap.wallet, transfer=swap) swap_recipient_view_context = WalletTransferContext( wallet=swap.recipient, transfer=swap) if swap_expired(swap, operator_eon_number, checkpoint_created): logger.info('Retiring swap') swap.retire_swap() continue if should_void_swap(swap, swap_wallet_view_context, swap_recipient_view_context, operator_eon_number, checkpoint_created): logger.info('Voiding swap.') swap.close(voided=True) continue elif swap.is_fulfilled_swap(): logger.info('Skipping finalized swap.') continue opposite_order_book_name = '{}-{}'.format( swap.recipient.token.short_name, swap.wallet.token.short_name) # If this is a sell order then the opposite orderbook is for buys, which should be sorted # in decremental order by price such that the first element in the list is the highest priced opposite_comparison_function = price_comparison_function( inverse=swap.sell_order, reverse=swap.sell_order) if opposite_order_book_name not in order_books_cache: print("FETCHED") opposite_swaps = Transfer.objects\ .filter( id__lte=swap.id, wallet__token=swap.recipient.token, recipient__token=swap.wallet.token, processed=False, complete=False, voided=False, cancelled=False, swap=True, eon_number=operator_eon_number, sender_active_state__operator_signature__isnull=False, recipient_active_state__operator_signature__isnull=False)\ .select_for_update() order_books_cache[opposite_order_book_name] = SortedList( opposite_swaps, key=cmp_to_key(opposite_comparison_function)) else: print("CACHED") opposite_order_book = SortedList( order_books_cache[opposite_order_book_name], key=cmp_to_key(opposite_comparison_function)) opposite_orders_consumed = 0 if len(opposite_order_book) == 0: print("EMPTY") opposite_swaps = Transfer.objects \ .filter( id__lte=swap.id, wallet__token=swap.recipient.token, recipient__token=swap.wallet.token, processed=False, complete=False, voided=False, cancelled=False, swap=True, eon_number=operator_eon_number, sender_active_state__operator_signature__isnull=False, recipient_active_state__operator_signature__isnull=False) \ .select_for_update() assert (len(opposite_order_book) == opposite_swaps.count()) for opposite in opposite_order_book: # BUY Price: amount / amount_swapped # SELL Price: amount_swapped / amount if swap.sell_order: logger.info('SELL FOR {} VS BUY AT {}'.format( swap.amount_swapped / swap.amount, opposite.amount / opposite.amount_swapped)) else: logger.info('BUY AT {} VS SELL FOR {}'.format( swap.amount / swap.amount_swapped, opposite.amount_swapped / opposite.amount)) # The invariant is that the buy order price is greater than or equal to the sell order price invariant = swap.amount * \ opposite.amount >= opposite.amount_swapped * swap.amount_swapped if not invariant: break with opposite.lock( auto_renewal=True), opposite.wallet.lock( auto_renewal=True), opposite.recipient.lock( auto_renewal=True): opposite_wallet_view_context = WalletTransferContext( wallet=opposite.wallet, transfer=opposite) opposite_recipient_view_context = WalletTransferContext( wallet=opposite.recipient, transfer=opposite) if swap_expired(opposite, operator_eon_number, checkpoint_created): opposite.retire_swap() opposite_orders_consumed += 1 continue if should_void_swap(opposite, opposite_wallet_view_context, opposite_recipient_view_context, operator_eon_number, checkpoint_created): opposite.close(voided=True) opposite_orders_consumed += 1 continue elif opposite.is_fulfilled_swap(): opposite_orders_consumed += 1 continue matched_successfully = match_limit_to_limit( swap, opposite) if opposite.is_fulfilled_swap(): opposite_orders_consumed += 1 try: opposite.sign_swap_fulfillment( settings.HUB_OWNER_ACCOUNT_ADDRESS, settings.HUB_OWNER_ACCOUNT_KEY) except LookupError as e: logger.error(e) if swap.is_fulfilled_swap(): try: swap.sign_swap_fulfillment( settings.HUB_OWNER_ACCOUNT_ADDRESS, settings.HUB_OWNER_ACCOUNT_KEY) except LookupError as e: logger.error(e) if swap.is_fulfilled_swap(): break order_books_cache[ opposite_order_book_name] = opposite_order_book.islice( opposite_orders_consumed) swap_order_book_name = '{}-{}'.format( swap.wallet.token.short_name, swap.recipient.token.short_name) if not swap.is_fulfilled_swap( ) and swap_order_book_name in order_books_cache: swap_comparison_function = price_comparison_function( inverse=not swap.sell_order, reverse=not swap.sell_order) swap_order_book = SortedList( order_books_cache[swap_order_book_name], key=cmp_to_key(swap_comparison_function)) swap_order_book.add(swap) order_books_cache[swap_order_book_name] = swap_order_book if matched_successfully: notification_queue.append((swap.id, opposite.id)) cache.set('last_unprocessed_swap_time', last_unprocessed_swap_time.timestamp()) for swap_id, opposite_id in notification_queue: operator_celery.send_task('auditor.tasks.on_swap_matching', args=[swap_id, opposite_id])
def test_match_multi_eon_swap(self): commit_eon(test_case=self, eon_number=1) advance_to_next_eon(test_case=self, eon_number=1) commit_eon(test_case=self, eon_number=2) buy_lqd_nonce = random.randint(1, 999999) sell_lqd_nonce = random.randint(1, 999999) total_remaining_eons = 5 # make persistent swap send_swap( # Buy LQD at 0.5 ETH test_case=self, eon_number=2, account=self.testrpc_accounts[1], token=self.eth_token, token_swapped=self.lqd_token, amount=1, amount_swapped=2, nonce=buy_lqd_nonce, eon_count=total_remaining_eons) self.assertEqual( Transfer.objects.filter(swap=True).count(), total_remaining_eons) swap = Transfer.objects.filter(swap=True)[0] swap_tx_id = swap.tx_id wallet_transfer_context = WalletTransferContext(wallet=swap.wallet, transfer=None) recipient_transfer_context = WalletTransferContext( wallet=swap.recipient, transfer=None) wallet_funds_before = 1 recipient_funds_before = 0 # process swaps confirm_swaps_for_eon(operator_eon_number=2) cancel_finalize_swaps_for_eon(operator_eon_number=2) process_swaps_for_eon(operator_eon_number=2) # skip some eons for i in range(3, 6): # proceed to next eon advance_to_next_eon(test_case=self, eon_number=i - 1) commit_eon(test_case=self, eon_number=i) total_remaining_eons -= 1 # process swaps confirm_swaps_for_eon(operator_eon_number=i) cancel_finalize_swaps_for_eon(operator_eon_number=i) process_swaps_for_eon(operator_eon_number=i) self.assertEqual( wallet_transfer_context.available_funds_at_eon(i, False), wallet_funds_before) self.assertEqual( recipient_transfer_context.available_funds_at_eon(i, False), recipient_funds_before) # make opposite swap send_swap( # Sell LQD at 0.5 ETH test_case=self, eon_number=5, account=self.testrpc_accounts[2], token=self.lqd_token, token_swapped=self.eth_token, amount=2, amount_swapped=1, nonce=sell_lqd_nonce, eon_count=1) # process swaps confirm_swaps_for_eon(operator_eon_number=5) cancel_finalize_swaps_for_eon(operator_eon_number=5) process_swaps_for_eon(operator_eon_number=5) swap1 = Transfer.objects.filter(eon_number=5).order_by('time')[0] swap2 = Transfer.objects.filter(eon_number=5).order_by('time')[1] self.assertNotEqual(swap1.tx_id, swap2.tx_id) # finalize swaps swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=5) self.assertEqual( Transfer.objects.filter(tx_id=swap_tx_id, eon_number__gt=5, swap=True, voided=False).count(), 0) finalize_swap(test_case=self, swap=swap, account=self.testrpc_accounts[1], eon_count=total_remaining_eons) finalize_last_swap(test_case=self, token=self.lqd_token, token_swapped=self.eth_token, account=self.testrpc_accounts[2], eon_count=1) # process swaps confirm_swaps_for_eon(operator_eon_number=5) cancel_finalize_swaps_for_eon(operator_eon_number=5) process_swaps_for_eon(operator_eon_number=5) self.assertEqual( wallet_transfer_context.available_funds_at_eon(5, False), wallet_funds_before - 1) self.assertEqual( recipient_transfer_context.available_funds_at_eon(5, False), recipient_funds_before + 2)
def test_cancel_multi_eon_swap(self): commit_eon(test_case=self, eon_number=1) advance_to_next_eon(test_case=self, eon_number=1) commit_eon(test_case=self, eon_number=2) buy_lqd_nonce = random.randint(1, 999999) sell_lqd_nonce = random.randint(1, 999999) total_remaining_eons = 5 # make persistent swap buy_lqd = send_swap( # Buy LQD at 0.5 ETH test_case=self, eon_number=2, account=self.testrpc_accounts[1], token=self.eth_token, token_swapped=self.lqd_token, amount=1, amount_swapped=2, nonce=buy_lqd_nonce, eon_count=total_remaining_eons) swap = Transfer.objects.filter( swap=True, wallet__token=self.eth_token).order_by('id')[0] swap_tx_id = swap.tx_id wallet_transfer_context = WalletTransferContext(wallet=swap.wallet, transfer=None) recipient_transfer_context = WalletTransferContext( wallet=swap.recipient, transfer=None) wallet_funds_before = 1 recipient_funds_before = 0 # process swaps confirm_swaps_for_eon(operator_eon_number=2) cancel_finalize_swaps_for_eon(operator_eon_number=2) process_swaps_for_eon(operator_eon_number=2) # skip some eons for i in range(3, 5): # proceed to next eon advance_to_next_eon(test_case=self, eon_number=i - 1) commit_eon(test_case=self, eon_number=i) total_remaining_eons -= 1 # process swaps confirm_swaps_for_eon(operator_eon_number=i) cancel_finalize_swaps_for_eon(operator_eon_number=i) process_swaps_for_eon(operator_eon_number=i) self.assertEqual( wallet_transfer_context.available_funds_at_eon(i, False), wallet_funds_before) self.assertEqual( recipient_transfer_context.available_funds_at_eon(i, False), recipient_funds_before) self.assertEqual( wallet_transfer_context.balance_as_of_eon(i).amount(), wallet_funds_before) swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=4) freeze_swap(test_case=self, swap=swap, account=self.testrpc_accounts[1]) swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=4) cancel_swap(test_case=self, swap=swap, account=self.testrpc_accounts[1], eon_count=total_remaining_eons) self.assertEqual( Transfer.objects.filter(tx_id=swap_tx_id, eon_number__gt=4, swap=True, voided=False).count(), 0) swap = Transfer.objects.filter(swap=True)[0] self.assertTrue(swap.cancelled) self.assertTrue(swap.processed) self.assertTrue(swap.appended) self.assertTrue(swap.sender_cancellation_active_state. operator_signature is not None) # proceed to next eon advance_to_next_eon(test_case=self, eon_number=4) commit_eon(test_case=self, eon_number=5) total_remaining_eons -= 1 # process swaps confirm_swaps_for_eon(operator_eon_number=5) cancel_finalize_swaps_for_eon(operator_eon_number=5) process_swaps_for_eon(operator_eon_number=5) swap = Transfer.objects.filter(swap=True)[0] self.assertTrue(swap.cancelled) self.assertTrue(swap.processed) self.assertTrue(swap.appended) self.assertEqual(swap.eon_number, 4) self.assertEqual( wallet_transfer_context.balance_as_of_eon(5).amount(), wallet_funds_before) self.assertEqual( wallet_transfer_context.available_funds_at_eon(5, False), wallet_funds_before) self.assertEqual( recipient_transfer_context.available_funds_at_eon(5, False), recipient_funds_before) # make opposite swap sell_lqd = send_swap( # Sell LQD at 0.5 ETH test_case=self, eon_number=5, account=self.testrpc_accounts[2], token=self.lqd_token, token_swapped=self.eth_token, amount=2, amount_swapped=1, nonce=sell_lqd_nonce, eon_count=1) # process swaps confirm_swaps_for_eon(operator_eon_number=5) cancel_finalize_swaps_for_eon(operator_eon_number=5) process_swaps_for_eon(operator_eon_number=5) self.assertEqual( wallet_transfer_context.available_funds_at_eon(5, False), wallet_funds_before) self.assertEqual( recipient_transfer_context.available_funds_at_eon(5, False), recipient_funds_before) commit_eon(test_case=self, eon_number=5)
def test_cancel_after_partial_match_multi_eon_swap(self): commit_eon(test_case=self, eon_number=1) advance_to_next_eon(test_case=self, eon_number=1) commit_eon(test_case=self, eon_number=2) buy_lqd_nonce = random.randint(1, 999999) sell_lqd_nonce = random.randint(1, 999999) total_remaining_eons = 5 # make persistent swap send_swap( # Buy LQD at 0.5 ETH test_case=self, eon_number=2, account=self.testrpc_accounts[1], token=self.eth_token, token_swapped=self.lqd_token, amount=2, amount_swapped=4, nonce=buy_lqd_nonce, eon_count=total_remaining_eons) swap = Transfer.objects.filter( swap=True, wallet__token=self.eth_token).order_by('id')[0] swap_tx_id = swap.tx_id wallet_transfer_context = WalletTransferContext(wallet=swap.wallet, transfer=None) recipient_transfer_context = WalletTransferContext( wallet=swap.recipient, transfer=None) wallet_funds_before = 2 recipient_funds_before = 0 # process swaps confirm_swaps_for_eon(operator_eon_number=2) cancel_finalize_swaps_for_eon(operator_eon_number=2) process_swaps_for_eon(operator_eon_number=2) # skip some eons for i in range(3, 5): # proceed to next eon advance_to_next_eon(test_case=self, eon_number=i - 1) commit_eon(test_case=self, eon_number=i) total_remaining_eons -= 1 try: swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=i) self.assertTrue(True) except Transfer.DoesNotExist: self.assertTrue(False) # process swaps confirm_swaps_for_eon(operator_eon_number=i) cancel_finalize_swaps_for_eon(operator_eon_number=i) process_swaps_for_eon(operator_eon_number=i) self.assertEqual( wallet_transfer_context.available_funds_at_eon(i, False), wallet_funds_before) self.assertEqual( recipient_transfer_context.available_funds_at_eon(i, False), recipient_funds_before) # make opposite swap send_swap( # Sell LQD at 0.5 ETH test_case=self, eon_number=4, account=self.testrpc_accounts[2], token=self.lqd_token, token_swapped=self.eth_token, amount=2, amount_swapped=1, nonce=sell_lqd_nonce, eon_count=1) # process swaps confirm_swaps_for_eon(operator_eon_number=4) cancel_finalize_swaps_for_eon(operator_eon_number=4) process_swaps_for_eon(operator_eon_number=4) finalize_last_swap(test_case=self, token=self.lqd_token, token_swapped=self.eth_token, account=self.testrpc_accounts[2], eon_count=1) self.assertEqual( wallet_transfer_context.available_funds_at_eon(4, False), wallet_funds_before - 1) self.assertEqual( recipient_transfer_context.available_funds_at_eon(4, False), recipient_funds_before + 2) # proceed to next eon advance_to_next_eon(test_case=self, eon_number=4) commit_eon(test_case=self, eon_number=5) total_remaining_eons -= 1 swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=5) self.assertFalse(swap.processed) self.assertTrue(swap.appended) self.assertFalse(swap.voided) self.assertFalse(swap.cancelled) self.assertFalse(swap.complete) self.assertEqual( wallet_transfer_context.available_funds_at_eon(5, False), wallet_funds_before - 1) self.assertEqual( recipient_transfer_context.available_funds_at_eon(5, False), recipient_funds_before + 2) # process swaps confirm_swaps_for_eon(operator_eon_number=5) cancel_finalize_swaps_for_eon(operator_eon_number=5) process_swaps_for_eon(operator_eon_number=5) swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=5) freeze_swap(test_case=self, swap=swap, account=self.testrpc_accounts[1]) swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=5) cancel_swap(test_case=self, swap=swap, account=self.testrpc_accounts[1], eon_count=total_remaining_eons) self.assertEqual( Transfer.objects.filter(tx_id=swap_tx_id, eon_number__gt=5, swap=True, voided=False).count(), 0) # process swaps confirm_swaps_for_eon(operator_eon_number=5) cancel_finalize_swaps_for_eon(operator_eon_number=5) process_swaps_for_eon(operator_eon_number=5) swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=5) self.assertTrue(swap.processed) self.assertTrue(swap.appended) self.assertFalse(swap.voided) self.assertTrue(swap.cancelled) self.assertFalse(swap.complete) self.assertEqual( wallet_transfer_context.available_funds_at_eon(5, False), wallet_funds_before - 1) self.assertEqual( recipient_transfer_context.available_funds_at_eon(5, False), recipient_funds_before + 2) # proceed to next eon advance_to_next_eon(test_case=self, eon_number=5) commit_eon(test_case=self, eon_number=6) total_remaining_eons -= 1 # process swaps confirm_swaps_for_eon(operator_eon_number=6) cancel_finalize_swaps_for_eon(operator_eon_number=6) process_swaps_for_eon(operator_eon_number=6) swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=6) self.assertTrue(swap.processed) self.assertFalse(swap.appended) self.assertTrue(swap.voided) self.assertTrue(swap.cancelled) self.assertFalse(swap.complete) self.assertEqual( wallet_transfer_context.available_funds_at_eon(6, False), wallet_funds_before - 1) self.assertEqual( recipient_transfer_context.available_funds_at_eon(6, False), recipient_funds_before + 2) # make opposite swap send_swap( # Sell LQD at 0.5 ETH test_case=self, eon_number=6, account=self.testrpc_accounts[3], token=self.lqd_token, token_swapped=self.eth_token, amount=2, amount_swapped=1, nonce=sell_lqd_nonce, eon_count=1) # process swaps confirm_swaps_for_eon(operator_eon_number=6) cancel_finalize_swaps_for_eon(operator_eon_number=6) process_swaps_for_eon(operator_eon_number=6) self.assertEqual( wallet_transfer_context.available_funds_at_eon(6, False), wallet_funds_before - 1) self.assertEqual( recipient_transfer_context.available_funds_at_eon(6, False), recipient_funds_before + 2) commit_eon(test_case=self, eon_number=6)
def test_cancel_after_partial_match_swap(self): commit_eon(test_case=self, eon_number=1) advance_to_next_eon(test_case=self, eon_number=1) commit_eon(test_case=self, eon_number=2) buy_lqd_nonce = random.randint(1, 999999) sell_lqd_nonce = random.randint(1, 999999) total_remaining_eons = 5 # make persistent swap send_swap( # Buy LQD at 0.5 ETH test_case=self, eon_number=2, account=self.testrpc_accounts[1], token=self.eth_token, token_swapped=self.lqd_token, amount=2, amount_swapped=4, nonce=buy_lqd_nonce, eon_count=total_remaining_eons) swap = Transfer.objects.filter( swap=True, wallet__token=self.eth_token).order_by('id')[0] swap_tx_id = swap.tx_id wallet_transfer_context = WalletTransferContext(wallet=swap.wallet, transfer=None) recipient_transfer_context = WalletTransferContext( wallet=swap.recipient, transfer=None) wallet_funds_before = 2 recipient_funds_before = 0 # process swaps confirm_swaps_for_eon(operator_eon_number=2) cancel_finalize_swaps_for_eon(operator_eon_number=2) process_swaps_for_eon(operator_eon_number=2) # make opposite swap send_swap( # Sell LQD at 0.5 ETH test_case=self, eon_number=2, account=self.testrpc_accounts[2], token=self.lqd_token, token_swapped=self.eth_token, amount=2, amount_swapped=1, nonce=sell_lqd_nonce, eon_count=1) # process swaps confirm_swaps_for_eon(operator_eon_number=2) cancel_finalize_swaps_for_eon(operator_eon_number=2) process_swaps_for_eon(operator_eon_number=2) finalize_last_swap(test_case=self, token=self.lqd_token, token_swapped=self.eth_token, account=self.testrpc_accounts[2], eon_count=1) # process swaps confirm_swaps_for_eon(operator_eon_number=2) cancel_finalize_swaps_for_eon(operator_eon_number=2) process_swaps_for_eon(operator_eon_number=2) swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=2) freeze_swap(test_case=self, swap=swap, account=self.testrpc_accounts[1]) swap = Transfer.objects.get(swap=True, tx_id=swap_tx_id, eon_number=2) cancel_swap(test_case=self, swap=swap, account=self.testrpc_accounts[1], eon_count=total_remaining_eons) self.assertEqual( Transfer.objects.filter(tx_id=swap_tx_id, eon_number__gt=2, swap=True, voided=False).count(), 0) # process swaps confirm_swaps_for_eon(operator_eon_number=2) cancel_finalize_swaps_for_eon(operator_eon_number=2) process_swaps_for_eon(operator_eon_number=2) self.assertEqual( wallet_transfer_context.available_funds_at_eon(2, False), wallet_funds_before - 1) self.assertEqual( recipient_transfer_context.available_funds_at_eon(2, False), recipient_funds_before + 2) advance_to_next_eon(test_case=self, eon_number=2) commit_eon(test_case=self, eon_number=3) # process swaps confirm_swaps_for_eon(operator_eon_number=3) cancel_finalize_swaps_for_eon(operator_eon_number=3) process_swaps_for_eon(operator_eon_number=3) self.assertEqual( wallet_transfer_context.available_funds_at_eon(3, False), wallet_funds_before - 1) self.assertEqual( recipient_transfer_context.available_funds_at_eon(3, False), recipient_funds_before + 2)
def create_token_commitment_for_eon(token: Token, eon_number): logger.info('Creating Token Commitment for {} at {}'.format( token.address, eon_number)) last_eon_number = eon_number - 1 with transaction.atomic(): wallets = Wallet.objects\ .filter( token=token, registration_operator_authorization__isnull=False, trail_identifier__isnull=False)\ .order_by('trail_identifier') new_balances = [] left, right = 0, 0 for wallet in wallets: with wallet.lock(auto_renewal=True): wallet_transfer_context = WalletTransferContext( wallet=wallet, transfer=None) last_transfer, last_transfer_is_outgoing = wallet_transfer_context.last_appended_active_transfer( eon_number=last_eon_number) last_transfer_active_state = None if last_transfer is not None and last_transfer.is_open_swap(): last_transfer.retire_swap() if last_transfer is not None: last_transfer_active_state = WalletTransferContext.appropriate_transfer_active_state( transfer=last_transfer, is_outgoing=last_transfer_is_outgoing) available_funds = wallet_transfer_context.available_funds_at_eon( eon_number=last_eon_number, only_appended=True) right = left + available_funds assert right >= left, 'Wallet {} Token {} Balance {}'.format( wallet.address, token.address, available_funds) passive_checksum, passive_amount, passive_marker = wallet_transfer_context.get_passive_values( eon_number=last_eon_number) new_balances.append({ 'contract': settings.HUB_LQD_CONTRACT_ADDRESS, 'token': token.address, 'wallet': wallet.address, 'left': left, 'right': right, 'active_state_checksum': last_transfer_active_state.checksum() if last_transfer_active_state is not None else b'\0'*32, 'active_state': last_transfer_active_state, 'passive_checksum': passive_checksum, 'passive_amount': passive_amount, 'passive_marker': passive_marker, }) left = right last_incoming_passive_transfer = wallet_transfer_context.last_appended_incoming_passive_transfer( eon_number=last_eon_number) if last_incoming_passive_transfer: wallet_transfer_context = WalletTransferContext( wallet=wallet, transfer=last_incoming_passive_transfer) passive_eon_transfers_list = wallet_transfer_context.incoming_passive_transfers_list( only_appended=True, force_append=False) passive_transfers_merkle_tree = wallet_transfer_context.incoming_passive_transfers_tree( only_appended=True, force_append=False) for index, incoming_passive_transfer in enumerate(passive_eon_transfers_list): final_transfer_index = index final_transfer_membership_proof = passive_transfers_merkle_tree.proof( final_transfer_index) final_transfer_membership_proof_chain = final_transfer_membership_proof.get( "chain") final_transfer_membership_proof_values = final_transfer_membership_proof.get( "values") assert incoming_passive_transfer.final_receipt_hashes is None assert incoming_passive_transfer.final_receipt_index is None assert incoming_passive_transfer.final_receipt_values is None incoming_passive_transfer.final_receipt_hashes = final_transfer_membership_proof_chain incoming_passive_transfer.final_receipt_index = final_transfer_index incoming_passive_transfer.final_receipt_values = final_transfer_membership_proof_values incoming_passive_transfer.save() if last_transfer_active_state is None: continue wallet_transfer_context = WalletTransferContext( wallet=wallet, transfer=last_transfer) starting_balance = int( wallet_transfer_context.starting_balance_in_eon(last_eon_number)) # if last active transfer is a multi eon swap # starting balance included in every tx checksum should be set to the cached starting balance # this way checkpoint state will match signed active state if last_transfer.is_swap() and not last_transfer.cancelled: if Transfer.objects.filter(eon_number=last_transfer.eon_number-1, tx_id=last_transfer.tx_id).exists(): matched_out, matched_in = last_transfer.matched_amounts( all_eons=True) current_matched_out, current_matched_in = last_transfer.matched_amounts( all_eons=False) if last_transfer_is_outgoing: sender_starting_balance = last_transfer.sender_starting_balance # current eon's starting balance should be equal to # cached starting balance - committed matched out amount in past rounds assert(starting_balance == sender_starting_balance - matched_out + current_matched_out) starting_balance = sender_starting_balance else: recipient_starting_balance = last_transfer.recipient_starting_balance # current eon's starting balance should be equal to # cached starting balance + committed matched in amount in past rounds assert( starting_balance == recipient_starting_balance + matched_in - current_matched_in) starting_balance = recipient_starting_balance confirmed_eon_transfers_list = wallet_transfer_context.authorized_transfers_list( only_appended=True, force_append=False) confirmed_eon_transfers_list_shorthand = wallet_transfer_context.authorized_transfers_list_shorthand( only_appended=True, force_append=False, last_transfer_is_finalized=False, starting_balance=starting_balance) transaction_merkle_tree = TransactionMerkleTree( confirmed_eon_transfers_list_shorthand) transaction_merkle_tree_root = hex_value( transaction_merkle_tree.root_hash()) assert transaction_merkle_tree_root == last_transfer_active_state.tx_set_hash,\ '{}/{}'.format(transaction_merkle_tree_root, last_transfer_active_state.tx_set_hash) for confirmed_incoming_transfer in confirmed_eon_transfers_list: if confirmed_incoming_transfer.recipient != wallet: continue final_transfer_index = transaction_merkle_tree.merkle_tree_nonce_map.get( confirmed_incoming_transfer.nonce) final_transfer_membership_proof_chain = transaction_merkle_tree.proof( final_transfer_index) assert confirmed_incoming_transfer.final_receipt_hashes is None assert confirmed_incoming_transfer.final_receipt_index is None confirmed_incoming_transfer.final_receipt_hashes = final_transfer_membership_proof_chain confirmed_incoming_transfer.final_receipt_index = final_transfer_index confirmed_incoming_transfer.save() managed_funds = 0 if eon_number > 1: last_eon = LocalViewInterface.confirmed(eon_number=last_eon_number) pending_withdrawals_until_last_eon = \ WithdrawalRequest.objects\ .filter(wallet__token=token, eon_number__lte=last_eon_number, slashed=False)\ .filter(Q(withdrawal__isnull=True) | Q(withdrawal__block__gt=last_eon.block)) if not pending_withdrawals_until_last_eon.exists(): last_eon_pending_withdrawals = 0 else: last_eon_pending_withdrawals = pending_withdrawals_until_last_eon\ .aggregate(Sum('amount')) \ .get('amount__sum') total_token_balance = last_eon.contractledgerstate_set.get( token=token).total_balance managed_funds = total_token_balance - last_eon_pending_withdrawals if right < managed_funds: logger.warning('UNCLAIMED FUNDS: {} in {}'.format( managed_funds - right, token.address)) send_admin_email( subject='Soft TokenCommitment Warning: Extra funds', content='There are some additional funds in the balance pool that belong to no one: {} of {}' .format(managed_funds - right, token.address)) altered_balances = new_balances + [{ 'contract': settings.HUB_LQD_CONTRACT_ADDRESS, 'token': token.address, 'wallet': settings.HUB_OWNER_ACCOUNT_ADDRESS, 'left': left, 'right': managed_funds, 'active_state_checksum': b'\0'*32, 'active_state': None, 'passive_checksum': b'\0'*32, 'passive_amount': 0, 'passive_marker': 0, }] new_merkle_tree = MerkleTree(altered_balances, managed_funds) right = managed_funds else: if right > managed_funds: logger.error('OVERCLAIMING FUNDS!! {} > {} in {}'.format( right, managed_funds, token.address)) send_admin_email( subject='HARD Checkpoint Error: OVERCLAIMING!', content='OVERCLAIMING FUNDS!! {} > {} in {}'.format(right, managed_funds, token.address)) new_merkle_tree = MerkleTree(new_balances, right) bulk_manager = BulkCreateManager(chunk_size=500) for index, balance in enumerate(new_balances): if not balance.get('wallet') or balance.get('wallet') == '0x0000000000000000000000000000000000000000': continue merkle_proof = new_merkle_tree.proof(index) wallet = Wallet.objects.get( token=token, address=remove_0x_prefix(balance.get('wallet'))) # TODO verify validity through RPC prior to insertion assert(wallet.trail_identifier == index) # create records in batches bulk_manager.add( ExclusiveBalanceAllotment( wallet=wallet, eon_number=eon_number, left=balance.get('left'), right=balance.get('right'), merkle_proof_hashes=merkle_proof.get('chain'), merkle_proof_values=merkle_proof.get('values'), merkle_proof_trail=index, active_state=balance.get('active_state') ) ) # make sure remaining batch is added bulk_manager.done() token_commitment = TokenCommitment.objects.create( token=token, merkle_root=hex_value(new_merkle_tree.root_hash()), upper_bound=right) return token_commitment
def make_random_valid_transactions(test_case: RPCTestCase, eon_number, accounts, token: Token, make_deposits=True): print('Making random valid transactions.') transfer_starting_count = Transfer.objects.count() active_state_starting_count = ActiveState.objects.count() balance_marker_starting_count = MinimumAvailableBalanceMarker.objects.count( ) signature_starting_count = Signature.objects.count() if make_deposits: create_deposits(test_case, accounts, token) transactions = 50 tx_amount = 0 time_to_send = datetime.timedelta(0) ids = [ Wallet.objects.get(address=remove_0x_prefix(account.get('address')), token=token).pk for account in accounts ] passive_transfers = 0 for i in range(transactions): # Randomly choose two wallets a, b = 0, 0 while a == b: a = random.randint(2, len(ids)) b = random.randint(2, len(ids)) # Sender account sender = Wallet.objects.get(pk=ids[a - 1]) available = WalletTransferContext(wallet=sender, transfer=None)\ .available_funds_at_eon(eon_number=eon_number, only_appended=False) amount = min(available, int(random.random() * available)) tx_amount += amount # Recipient account nonce = random.randint(1, 999999) # Send tx to server and log time delta time_to_send += send_transaction(test_case=test_case, eon_number=eon_number, sender=accounts[a - 1], recipient=accounts[b - 1], amount=amount, nonce=nonce, token=token) # Assert expected database state executed_transfer_count = i + 1 test_case.assertEqual( Transfer.objects.count(), transfer_starting_count + executed_transfer_count) test_case.assertEqual( MinimumAvailableBalanceMarker.objects.count(), balance_marker_starting_count + executed_transfer_count) added_active_states_in_previous_iterations = passive_transfers added_active_states_in_current_iteration = 1 test_case.assertEqual( ActiveState.objects.count(), active_state_starting_count + added_active_states_in_previous_iterations + added_active_states_in_current_iteration) added_operator_signatures = passive_transfers synchronously_confirmed_passive = 1 test_case.assertEqual( Signature.objects.count(), signature_starting_count # sender / recipient active state sigs + added_active_states_in_previous_iterations + added_active_states_in_current_iteration + executed_transfer_count # balance marker sigs + added_operator_signatures + synchronously_confirmed_passive) # operator sigs on active states test_case.assertEqual( Signature.objects.count(), signature_starting_count # sender / recipient active state sigs + added_active_states_in_previous_iterations + added_active_states_in_current_iteration + executed_transfer_count # balance marker sigs + added_operator_signatures # operator sigs on active states + added_active_states_in_current_iteration) passive_transfers += 1 average_time_to_send = time_to_send / transactions print("Total wei transferred: ", tx_amount) print("Average time to send: ", average_time_to_send)