def test_refund_transfer_matches_received(): amount = 30 expiration = 50 transfer = factories.make_transfer( amount, UNIT_TRANSFER_INITIATOR, UNIT_TRANSFER_TARGET, expiration, UNIT_SECRET, ) refund_lower_expiration = factories.make_signed_transfer( amount, UNIT_TRANSFER_INITIATOR, UNIT_TRANSFER_TARGET, expiration - 1, UNIT_SECRET, ) assert channel.refund_transfer_matches_received(refund_lower_expiration, transfer) is True refund_same_expiration = factories.make_signed_transfer( amount, UNIT_TRANSFER_INITIATOR, UNIT_TRANSFER_TARGET, expiration, UNIT_SECRET, ) assert channel.refund_transfer_matches_received(refund_same_expiration, transfer) is False
def make_signed_transfer_from_counter(counter): lock = Lock( amount=next(counter), expiration=next(counter), secrethash=sha3(factories.make_secret(next(counter))), ) signed_transfer = factories.make_signed_transfer( amount=next(counter), initiator=factories.make_address(), target=factories.make_address(), expiration=next(counter), secret=factories.make_secret(next(counter)), payment_identifier=next(counter), message_identifier=next(counter), nonce=next(counter), transferred_amount=next(counter), locked_amount=next(counter), locksroot=sha3(lock.as_bytes), recipient=factories.make_address(), channel_identifier=next(counter), token_network_address=factories.make_address(), token=factories.make_address(), pkey=factories.HOP1_KEY, sender=factories.HOP1, ) return signed_transfer
def test_refund_transfer_no_more_routes(): amount = UNIT_TRANSFER_AMOUNT block_number = 1 refund_pkey, refund_address = factories.make_privkey_address() channel1 = factories.make_channel( our_balance=amount, partner_balance=amount, our_address=UNIT_TRANSFER_INITIATOR, partner_address=refund_address, token_address=UNIT_TOKEN_ADDRESS, ) channelmap = {channel1.identifier: channel1} available_routes = [factories.route_from_channel(channel1)] current_state = make_initiator_state( available_routes, factories.UNIT_TRANSFER_DESCRIPTION, channelmap, block_number, ) original_transfer = current_state.initiator.transfer channel_identifier = current_state.initiator.channel_identifier channel_state = channelmap[channel_identifier] expiration = original_transfer.lock.expiration - channel_state.reveal_timeout - TRANSIT_BLOCKS refund_transfer = factories.make_signed_transfer( amount, original_transfer.initiator, original_transfer.target, expiration, UNIT_SECRET, identifier=original_transfer.identifier, channel_identifier=channel1.identifier, pkey=refund_pkey, sender=refund_address, ) state_change = ReceiveTransferRefundCancelRoute( sender=channel_state.partner_state.address, routes=available_routes, transfer=refund_transfer, secret=random_secret(), ) iteration = initiator_manager.state_transition( current_state, state_change, channelmap, block_number, ) assert iteration.new_state is None assert len(iteration.events) == 2 unlocked_failed = next(e for e in iteration.events if isinstance(e, EventUnlockFailed)) sent_failed = next(e for e in iteration.events if isinstance(e, EventTransferSentFailed)) assert unlocked_failed assert sent_failed
def make_signed_transfer_from_counter(counter): lock = Lock( amount=next(counter), expiration=next(counter), secrethash=sha3(factories.make_secret(next(counter))), ) signed_transfer = factories.make_signed_transfer( amount=next(counter), initiator=factories.make_address(), target=factories.make_address(), expiration=next(counter), secret=factories.make_secret(next(counter)), payment_identifier=next(counter), message_identifier=next(counter), nonce=next(counter), transferred_amount=next(counter), locked_amount=next(counter), locksroot=sha3(lock.as_bytes), recipient=factories.make_address(), channel_identifier=next(counter), token_network_address=factories.make_address(), token=factories.make_address(), pkey=factories.HOP1_KEY, sender=factories.HOP1, ) return signed_transfer
def test_locked_transfer_secret_registered_onchain( raiden_network, token_addresses, secret_registry_address, ): app0 = raiden_network[0] token_address = token_addresses[0] chain_state = views.state_from_app(app0) payment_network_id = app0.raiden.default_registry.address token_network_identifier = views.get_token_network_identifier_by_token_address( chain_state, payment_network_id, token_address, ) amount = 1 target = UNIT_TRANSFER_INITIATOR identifier = 1 transfer_secret = sha3(target + b'1') secret_registry_proxy = app0.raiden.chain.secret_registry( secret_registry_address, ) secret_registry_proxy.register_secret(transfer_secret) # Test that sending a transfer with a secret already registered on-chain fails with pytest.raises(RaidenUnrecoverableError): app0.raiden.start_mediated_transfer_with_secret( token_network_identifier, amount, target, identifier, transfer_secret, ) expiration = 9999 transfer = make_signed_transfer( amount, UNIT_TRANSFER_INITIATOR, app0.raiden.address, expiration, transfer_secret, ) message_handler = MessageHandler() message_handler.handle_message_lockedtransfer( app0.raiden, transfer, ) state_changes = app0.raiden.wal.storage.get_statechanges_by_identifier(0, 'latest') transfer_statechange_dispatched = ( must_contain_entry(state_changes, ActionInitMediator, {}) or must_contain_entry(state_changes, ActionInitTarget, {}) ) assert not transfer_statechange_dispatched
def test_locked_transfer_secret_registered_onchain( raiden_network, token_addresses, secret_registry_address, ): app0 = raiden_network[0] token_address = token_addresses[0] chain_state = views.state_from_app(app0) payment_network_id = app0.raiden.default_registry.address token_network_identifier = views.get_token_network_identifier_by_token_address( chain_state, payment_network_id, token_address, ) amount = 1 target = UNIT_TRANSFER_INITIATOR identifier = 1 transfer_secret = sha3(target + b'1') secret_registry_proxy = app0.raiden.chain.secret_registry( secret_registry_address, ) secret_registry_proxy.register_secret(transfer_secret) # Test that sending a transfer with a secret already registered on-chain fails with pytest.raises(RaidenUnrecoverableError): app0.raiden.start_mediated_transfer_with_secret( token_network_identifier, amount, target, identifier, transfer_secret, ) expiration = 9999 transfer = make_signed_transfer( amount, UNIT_TRANSFER_INITIATOR, app0.raiden.address, expiration, transfer_secret, ) message_handler = MessageHandler() message_handler.handle_message_lockedtransfer( app0.raiden, transfer, ) state_changes = app0.raiden.wal.storage.get_statechanges_by_identifier(0, 'latest') transfer_statechange_dispatched = ( must_contain_entry(state_changes, ActionInitMediator, {}) or must_contain_entry(state_changes, ActionInitTarget, {}) ) assert not transfer_statechange_dispatched
def test_refund_transfer_invalid_sender(): amount = UNIT_TRANSFER_AMOUNT block_number = 1 pseudo_random_generator = random.Random() channel1 = factories.make_channel( our_balance=amount, our_address=UNIT_TRANSFER_INITIATOR, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) channelmap = {channel1.identifier: channel1} available_routes = [factories.route_from_channel(channel1)] current_state = make_initiator_state( available_routes, factories.UNIT_TRANSFER_DESCRIPTION, channelmap, pseudo_random_generator, block_number, ) original_transfer = current_state.initiator.transfer channel_identifier = current_state.initiator.channel_identifier channel_state = channelmap[channel_identifier] expiration = original_transfer.lock.expiration - channel_state.reveal_timeout - TRANSIT_BLOCKS refund_transfer = factories.make_signed_transfer( amount, original_transfer.initiator, original_transfer.target, expiration, UNIT_SECRET, ) wrong_sender_address = factories.HOP3 state_change = ReceiveTransferRefundCancelRoute( sender=wrong_sender_address, routes=available_routes, transfer=refund_transfer, secret=random_secret(), ) before_state = deepcopy(current_state) iteration = initiator_manager.state_transition( current_state, state_change, channelmap, pseudo_random_generator, block_number, ) assert iteration.new_state is not None assert not iteration.events assert iteration.new_state == before_state
def run_test_mediated_transfer_calls_pfs(raiden_network, token_addresses): app0, = raiden_network token_address = token_addresses[0] chain_state = views.state_from_app(app0) payment_network_id = app0.raiden.default_registry.address token_network_id = views.get_token_network_identifier_by_token_address( chain_state, payment_network_id, token_address, ) with patch('raiden.routing.query_paths', return_value=[]) as patched: app0.raiden.start_mediated_transfer_with_secret( token_network_identifier=token_network_id, amount=10, fee=0, target=factories.HOP1, identifier=1, secret=b'1' * 32, ) assert not patched.called config_patch = dict( pathfinding_service_address='mock-address', pathfinding_eth_address=factories.make_checksum_address(), ) with patch.dict(app0.raiden.config['services'], config_patch): app0.raiden.start_mediated_transfer_with_secret( token_network_identifier=token_network_id, amount=11, fee=0, target=factories.HOP2, identifier=2, secret=b'2' * 32, ) assert patched.call_count == 1 locked_transfer = factories.make_signed_transfer( amount=5, initiator=factories.HOP1, target=factories.HOP2, sender=factories.HOP1, pkey=factories.HOP1_KEY, token_network_address=token_network_id, token=token_address, ) app0.raiden.mediate_mediated_transfer(locked_transfer) assert patched.call_count == 2
def test_handle_inittarget(): """ Init transfer must send a secret request if the expiration is valid. """ amount = 3 block_number = 1 initiator = factories.HOP1 target_address = UNIT_TRANSFER_TARGET payment_network_identifier = factories.make_address() pseudo_random_generator = random.Random() from_channel = factories.make_channel( our_address=target_address, partner_address=UNIT_TRANSFER_SENDER, partner_balance=amount, ) from_route = factories.route_from_channel(from_channel) expiration = from_channel.reveal_timeout + block_number + 1 from_transfer = factories.make_signed_transfer( amount, initiator, target_address, expiration, UNIT_SECRET, channel_identifier=from_channel.identifier, ) state_change = ActionInitTarget( payment_network_identifier, from_route, from_transfer, ) iteration = target.handle_inittarget( state_change, from_channel, pseudo_random_generator, block_number, ) events = iteration.events assert events assert isinstance(events[0], SendSecretRequest) assert events[0].payment_identifier == from_transfer.payment_identifier assert events[0].amount == from_transfer.lock.amount assert events[0].secrethash == from_transfer.lock.secrethash assert events[0].recipient == initiator
def test_handle_inittarget(): """ Init transfer must send a secret request if the expiration is valid. """ amount = 3 block_number = 1 initiator = factories.HOP1 target_address = UNIT_TRANSFER_TARGET pseudo_random_generator = random.Random() from_channel = factories.make_channel( our_address=target_address, partner_address=UNIT_TRANSFER_SENDER, partner_balance=amount, ) from_route = factories.route_from_channel(from_channel) expiration = from_channel.reveal_timeout + block_number + 1 from_transfer = factories.make_signed_transfer( amount, initiator, target_address, expiration, UNIT_SECRET, channel_identifier=from_channel.identifier, token_network_address=from_channel.token_network_identifier, ) state_change = ActionInitTarget( from_route, from_transfer, ) iteration = target.handle_inittarget( state_change, from_channel, pseudo_random_generator, block_number, ) assert must_contain_entry( iteration.events, SendSecretRequest, { 'payment_identifier': from_transfer.payment_identifier, 'amount': from_transfer.lock.amount, 'secrethash': from_transfer.lock.secrethash, 'recipient': initiator, }) assert must_contain_entry(iteration.events, SendProcessed, {})
def test_handle_inittarget(): """ Init transfer must send a secret request if the expiration is valid. """ amount = 3 block_number = 1 initiator = factories.HOP1 target_address = UNIT_TRANSFER_TARGET pseudo_random_generator = random.Random() from_channel = factories.make_channel( our_address=target_address, partner_address=UNIT_TRANSFER_SENDER, partner_balance=amount, ) from_route = factories.route_from_channel(from_channel) expiration = from_channel.reveal_timeout + block_number + 1 from_transfer = factories.make_signed_transfer( amount, initiator, target_address, expiration, UNIT_SECRET, channel_identifier=from_channel.identifier, ) state_change = ActionInitTarget( from_route, from_transfer, ) iteration = target.handle_inittarget( state_change, from_channel, pseudo_random_generator, block_number, ) events = iteration.events assert events assert isinstance(events[0], SendSecretRequest) assert events[0].payment_identifier == from_transfer.payment_identifier assert events[0].amount == from_transfer.lock.amount assert events[0].secrethash == from_transfer.lock.secrethash assert events[0].recipient == initiator
def test_refund_transfer_no_more_routes(): amount = UNIT_TRANSFER_AMOUNT refund_pkey, refund_address = factories.make_privkey_address() setup = setup_initiator_tests( amount=amount, partner_balance=amount, our_address=UNIT_TRANSFER_INITIATOR, partner_address=refund_address, ) original_transfer = setup.current_state.initiator.transfer refund_transfer = factories.make_signed_transfer( amount, original_transfer.initiator, original_transfer.target, original_transfer.lock.expiration, UNIT_SECRET, payment_identifier=original_transfer.payment_identifier, channel_identifier=setup.channel.identifier, pkey=refund_pkey, sender=refund_address, ) state_change = ReceiveTransferRefundCancelRoute( routes=setup.available_routes, transfer=refund_transfer, secret=random_secret(), ) iteration = initiator_manager.state_transition( setup.current_state, state_change, setup.channel_map, setup.prng, setup.block_number, ) assert iteration.new_state is None unlocked_failed = next(e for e in iteration.events if isinstance(e, EventUnlockFailed)) sent_failed = next(e for e in iteration.events if isinstance(e, EventPaymentSentFailed)) assert unlocked_failed assert sent_failed
def test_refund_transfer_does_not_match_received(): amount = 30 expiration = 50 target = UNIT_TRANSFER_SENDER transfer = factories.make_transfer( amount, UNIT_TRANSFER_INITIATOR, target, expiration, UNIT_SECRET, ) refund_from_target = factories.make_signed_transfer( amount, UNIT_TRANSFER_INITIATOR, UNIT_TRANSFER_TARGET, expiration - 1, UNIT_SECRET, ) # target cannot refund assert not channel.refund_transfer_matches_received(refund_from_target, transfer)
def test_refund_transfer_next_route(): amount = UNIT_TRANSFER_AMOUNT our_address = factories.ADDR refund_pkey, refund_address = factories.make_privkey_address() pseudo_random_generator = random.Random() channel1 = factories.make_channel( our_balance=amount, partner_balance=amount, our_address=our_address, partner_address=refund_address, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) channel2 = factories.make_channel( our_balance=0, our_address=our_address, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) channel3 = factories.make_channel( our_balance=amount, our_address=our_address, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) channelmap = { channel1.identifier: channel1, channel2.identifier: channel2, channel3.identifier: channel3, } available_routes = [ factories.route_from_channel(channel1), factories.route_from_channel(channel2), factories.route_from_channel(channel3), ] block_number = 10 current_state = make_initiator_state( available_routes, factories.UNIT_TRANSFER_DESCRIPTION, channelmap, pseudo_random_generator, block_number, ) original_transfer = current_state.initiator.transfer channel_identifier = current_state.initiator.channel_identifier channel_state = channelmap[channel_identifier] expiration = original_transfer.lock.expiration - channel_state.reveal_timeout - TRANSIT_BLOCKS refund_transfer = factories.make_signed_transfer( amount, our_address, original_transfer.target, expiration, UNIT_SECRET, payment_identifier=original_transfer.payment_identifier, channel_identifier=channel1.identifier, pkey=refund_pkey, sender=refund_address, ) assert channel_state.partner_state.address == refund_address state_change = ReceiveTransferRefundCancelRoute( sender=refund_address, routes=available_routes, transfer=refund_transfer, secret=random_secret(), ) iteration = initiator_manager.state_transition( current_state, state_change, channelmap, pseudo_random_generator, block_number, ) assert iteration.new_state is not None route_cancelled = next(e for e in iteration.events if isinstance(e, EventUnlockFailed)) new_transfer = next(e for e in iteration.events if isinstance(e, SendLockedTransfer)) assert route_cancelled, 'The previous transfer must be cancelled' assert new_transfer, 'No mediated transfer event emitted, should have tried a new route' msg = 'the new transfer must use a new secret / secrethash' assert new_transfer.transfer.lock.secrethash != refund_transfer.lock.secrethash, msg assert iteration.new_state.initiator is not None
def test_refund_transfer_no_more_routes(): amount = UNIT_TRANSFER_AMOUNT refund_pkey, refund_address = factories.make_privkey_address() setup = setup_initiator_tests( amount=amount, partner_balance=amount, our_address=UNIT_TRANSFER_INITIATOR, partner_address=refund_address, ) original_transfer = setup.current_state.initiator.transfer refund_transfer = factories.make_signed_transfer( amount, original_transfer.initiator, original_transfer.target, original_transfer.lock.expiration, UNIT_SECRET, payment_identifier=original_transfer.payment_identifier, channel_identifier=setup.channel.identifier, pkey=refund_pkey, sender=refund_address, ) state_change = ReceiveTransferRefundCancelRoute( routes=setup.available_routes, transfer=refund_transfer, secret=random_secret(), ) iteration = initiator_manager.state_transition( setup.current_state, state_change, setup.channel_map, setup.prng, setup.block_number, ) current_state = iteration.new_state # As per the description of the issue here: # https://github.com/raiden-network/raiden/issues/3146#issuecomment-447378046 # We can fail the payment but can't delete the payment task if there are no # more routes, but we have to wait for the lock expiration assert iteration.new_state is not None unlocked_failed = next(e for e in iteration.events if isinstance(e, EventUnlockFailed)) sent_failed = next(e for e in iteration.events if isinstance(e, EventPaymentSentFailed)) assert unlocked_failed assert sent_failed invalid_balance_proof = factories.make_signed_balance_proof( nonce=2, transferred_amount=original_transfer.balance_proof.transferred_amount, locked_amount=0, token_network_address=original_transfer.balance_proof.token_network_identifier, channel_identifier=setup.channel.identifier, locksroot=EMPTY_MERKLE_ROOT, extra_hash=original_transfer.lock.secrethash, sender_address=refund_address, ) balance_proof = factories.make_signed_balance_proof( nonce=2, transferred_amount=original_transfer.balance_proof.transferred_amount, locked_amount=0, token_network_address=original_transfer.balance_proof.token_network_identifier, channel_identifier=setup.channel.identifier, locksroot=EMPTY_MERKLE_ROOT, extra_hash=original_transfer.lock.secrethash, sender_address=refund_address, private_key=refund_pkey, ) invalid_lock_expired_state_change = ReceiveLockExpired( invalid_balance_proof, secrethash=original_transfer.lock.secrethash, message_identifier=5, ) lock_expired_state_change = ReceiveLockExpired( balance_proof, secrethash=original_transfer.lock.secrethash, message_identifier=5, ) before_expiry_block = original_transfer.lock.expiration - 1 expiry_block = channel.get_sender_expiration_threshold(original_transfer.lock) # a block before lock expiration, no events should be emitted current_state = iteration.new_state state_change = Block( block_number=before_expiry_block, gas_limit=1, block_hash=factories.make_transaction_hash(), ) iteration = initiator_manager.state_transition( current_state, state_change, setup.channel_map, setup.prng, expiry_block, ) assert not iteration.events assert iteration.new_state, 'payment task should not be deleted at this block' # process an invalid lock expired message before lock expiration current_state = iteration.new_state iteration = initiator_manager.state_transition( current_state, invalid_lock_expired_state_change, setup.channel_map, setup.prng, before_expiry_block, ) assert iteration.new_state, 'payment task should not be deleted at this lock expired' # should not be accepted assert not events.must_contain_entry(iteration.events, SendProcessed, {}) assert events.must_contain_entry(iteration.events, EventInvalidReceivedLockExpired, {}) # process a valid lock expired message before lock expiration current_state = iteration.new_state iteration = initiator_manager.state_transition( current_state, lock_expired_state_change, setup.channel_map, setup.prng, before_expiry_block, ) assert iteration.new_state, 'payment task should not be deleted at this lock expired' # should not be accepted assert not events.must_contain_entry(iteration.events, SendProcessed, {}) # now we get to the lock expiration block current_state = iteration.new_state state_change = Block( block_number=expiry_block, gas_limit=1, block_hash=factories.make_transaction_hash(), ) iteration = initiator_manager.state_transition( current_state, state_change, setup.channel_map, setup.prng, expiry_block, ) assert events.must_contain_entry(iteration.events, SendLockExpired, {}) # Since there was a refund transfer the payment task must not have been deleted assert iteration.new_state is not None # process the lock expired message after lock expiration current_state = iteration.new_state iteration = initiator_manager.state_transition( current_state, lock_expired_state_change, setup.channel_map, setup.prng, expiry_block, ) # should be accepted assert events.must_contain_entry(iteration.events, SendProcessed, {}) assert iteration.new_state, 'payment task should be there waiting for next block' # process the the block after lock expiration current_state = iteration.new_state state_change = Block( block_number=expiry_block + 1, gas_limit=1, block_hash=factories.make_transaction_hash(), ) iteration = initiator_manager.state_transition( current_state, state_change, setup.channel_map, setup.prng, expiry_block + 1, ) assert iteration.new_state is None, 'from this point on the payment task should go'
def test_regression_send_refund(): """Regression test for discarded refund transfer. The handle_refundtransfer used to discard events from the channel state machine, which led to the state being updated but the message to the partner was never sent. """ pseudo_random_generator = random.Random() setup = factories.make_transfers_pair(3) mediator_state = MediatorTransferState(UNIT_SECRETHASH) mediator_state.transfers_pair = setup.transfers_pair last_pair = setup.transfers_pair[-1] channel_identifier = last_pair.payee_transfer.balance_proof.channel_identifier lock_expiration = last_pair.payee_transfer.lock.expiration received_transfer = factories.make_signed_transfer( amount=UNIT_TRANSFER_AMOUNT, initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, expiration=lock_expiration, secret=UNIT_SECRET, payment_identifier=UNIT_TRANSFER_IDENTIFIER, channel_identifier=channel_identifier, pkey=setup.channels.partner_privatekeys[2], sender=setup.channels.partner_address(2), ) # All three channels have been used routes = [] refund_state_change = ReceiveTransferRefund( transfer=received_transfer, routes=routes, ) iteration = mediator.handle_refundtransfer( mediator_state=mediator_state, mediator_state_change=refund_state_change, channelidentifiers_to_channels=setup.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=setup.block_number, ) first_pair = setup.transfers_pair[0] first_payer_transfer = first_pair.payer_transfer payer_channel = mediator.get_payer_channel(setup.channel_map, first_pair) lock = channel.get_lock( end_state=payer_channel.partner_state, secrethash=UNIT_SECRETHASH, ) token_network_identifier = first_payer_transfer.balance_proof.token_network_identifier assert must_contain_entry( iteration.events, SendRefundTransfer, { 'recipient': setup.channels.partner_address(0), 'queue_identifier': { 'recipient': setup.channels.partner_address(0), 'channel_identifier': first_payer_transfer.balance_proof.channel_identifier, }, 'transfer': { 'payment_identifier': UNIT_TRANSFER_IDENTIFIER, 'token': UNIT_TOKEN_ADDRESS, 'balance_proof': { 'transferred_amount': 0, 'locked_amount': 10, 'locksroot': lock.lockhash, 'token_network_identifier': token_network_identifier, 'channel_identifier': first_payer_transfer.balance_proof.channel_identifier, 'chain_id': first_payer_transfer.balance_proof.chain_id, }, 'lock': { 'amount': lock.amount, 'expiration': lock.expiration, 'secrethash': lock.secrethash, }, 'initiator': UNIT_TRANSFER_INITIATOR, 'target': UNIT_TRANSFER_TARGET, }, })
def test_regression_send_refund(): """Regression test for discarded refund transfer. The handle_refundtransfer used to discard events from the channel state machine, which led to the state being updated but the message to the partner was never sent. Also, for issue: https://github.com/raiden-network/raiden/issues/3170 It was noticed that when receiving the same refund transfer twice, the mediator would detect an invalid refund and clear the mediator state. So the test also checks that mediator rejects the duplicate transfer and keeps the mediator state unchanged. """ pseudo_random_generator = random.Random() setup = factories.make_transfers_pair(3) mediator_state = MediatorTransferState(UNIT_SECRETHASH) mediator_state.transfers_pair = setup.transfers_pair last_pair = setup.transfers_pair[-1] channel_identifier = last_pair.payee_transfer.balance_proof.channel_identifier lock_expiration = last_pair.payee_transfer.lock.expiration received_transfer = factories.make_signed_transfer( amount=UNIT_TRANSFER_AMOUNT, initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, expiration=lock_expiration, secret=UNIT_SECRET, payment_identifier=UNIT_TRANSFER_IDENTIFIER, channel_identifier=channel_identifier, pkey=setup.channels.partner_privatekeys[2], sender=setup.channels.partner_address(2), ) # All three channels have been used routes = [] refund_state_change = ReceiveTransferRefund( transfer=received_transfer, routes=routes, ) iteration = mediator.handle_refundtransfer( mediator_state=mediator_state, mediator_state_change=refund_state_change, channelidentifiers_to_channels=setup.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=setup.block_number, ) first_pair = setup.transfers_pair[0] first_payer_transfer = first_pair.payer_transfer payer_channel = mediator.get_payer_channel(setup.channel_map, first_pair) lock = channel.get_lock( end_state=payer_channel.partner_state, secrethash=UNIT_SECRETHASH, ) token_network_identifier = first_payer_transfer.balance_proof.token_network_identifier assert search_for_item(iteration.events, SendRefundTransfer, { 'recipient': setup.channels.partner_address(0), 'queue_identifier': { 'recipient': setup.channels.partner_address(0), 'channel_identifier': first_payer_transfer.balance_proof.channel_identifier, }, 'transfer': { 'payment_identifier': UNIT_TRANSFER_IDENTIFIER, 'token': UNIT_TOKEN_ADDRESS, 'balance_proof': { 'transferred_amount': 0, 'locked_amount': 10, 'locksroot': lock.lockhash, 'token_network_identifier': token_network_identifier, 'channel_identifier': first_payer_transfer.balance_proof.channel_identifier, 'chain_id': first_payer_transfer.balance_proof.chain_id, }, 'lock': { 'amount': lock.amount, 'expiration': lock.expiration, 'secrethash': lock.secrethash, }, 'initiator': UNIT_TRANSFER_INITIATOR, 'target': UNIT_TRANSFER_TARGET, }, }) duplicate_iteration = mediator.handle_refundtransfer( mediator_state=iteration.new_state, mediator_state_change=refund_state_change, channelidentifiers_to_channels=setup.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=setup.block_number, ) assert search_for_item(duplicate_iteration.events, SendRefundTransfer, {}) is None assert duplicate_iteration.new_state is not None assert duplicate_iteration.new_state == iteration.new_state
def test_refund_transfer_no_more_routes(): amount = UNIT_TRANSFER_AMOUNT refund_pkey, refund_address = factories.make_privkey_address() setup = setup_initiator_tests( amount=amount, partner_balance=amount, our_address=UNIT_TRANSFER_INITIATOR, partner_address=refund_address, ) original_transfer = setup.current_state.initiator.transfer refund_transfer = factories.make_signed_transfer( amount, original_transfer.initiator, original_transfer.target, original_transfer.lock.expiration, UNIT_SECRET, payment_identifier=original_transfer.payment_identifier, channel_identifier=setup.channel.identifier, pkey=refund_pkey, sender=refund_address, ) state_change = ReceiveTransferRefundCancelRoute( routes=setup.available_routes, transfer=refund_transfer, secret=random_secret(), ) iteration = initiator_manager.state_transition( setup.current_state, state_change, setup.channel_map, setup.prng, setup.block_number, ) current_state = iteration.new_state # As per the description of the issue here: # https://github.com/raiden-network/raiden/issues/3146#issuecomment-447378046 # We can fail the payment but can't delete the payment task if there are no # more routes, but we have to wait for the lock expiration assert iteration.new_state is not None unlocked_failed = next(e for e in iteration.events if isinstance(e, EventUnlockFailed)) sent_failed = next(e for e in iteration.events if isinstance(e, EventPaymentSentFailed)) assert unlocked_failed assert sent_failed invalid_balance_proof = factories.make_signed_balance_proof( nonce=2, transferred_amount=original_transfer.balance_proof.transferred_amount, locked_amount=0, token_network_address=original_transfer.balance_proof. token_network_identifier, channel_identifier=setup.channel.identifier, locksroot=EMPTY_MERKLE_ROOT, extra_hash=original_transfer.lock.secrethash, sender_address=refund_address, ) balance_proof = factories.make_signed_balance_proof( nonce=2, transferred_amount=original_transfer.balance_proof.transferred_amount, locked_amount=0, token_network_address=original_transfer.balance_proof. token_network_identifier, channel_identifier=setup.channel.identifier, locksroot=EMPTY_MERKLE_ROOT, extra_hash=original_transfer.lock.secrethash, sender_address=refund_address, private_key=refund_pkey, ) invalid_lock_expired_state_change = ReceiveLockExpired( invalid_balance_proof, secrethash=original_transfer.lock.secrethash, message_identifier=5, ) lock_expired_state_change = ReceiveLockExpired( balance_proof, secrethash=original_transfer.lock.secrethash, message_identifier=5, ) before_expiry_block = original_transfer.lock.expiration - 1 expiry_block = original_transfer.lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS * 2 # a block before lock expiration, no events should be emitted current_state = iteration.new_state state_change = Block( block_number=before_expiry_block, gas_limit=1, block_hash=factories.make_transaction_hash(), ) iteration = initiator_manager.state_transition( current_state, state_change, setup.channel_map, setup.prng, expiry_block, ) assert not iteration.events assert iteration.new_state, 'payment task should not be deleted at this block' # process an invalid lock expired message before lock expiration current_state = iteration.new_state iteration = initiator_manager.state_transition( current_state, invalid_lock_expired_state_change, setup.channel_map, setup.prng, before_expiry_block, ) assert iteration.new_state, 'payment task should not be deleted at this lock expired' # should not be accepted assert not events.must_contain_entry(iteration.events, SendProcessed, {}) assert events.must_contain_entry(iteration.events, EventInvalidReceivedLockExpired, {}) # process a valid lock expired message before lock expiration current_state = iteration.new_state iteration = initiator_manager.state_transition( current_state, lock_expired_state_change, setup.channel_map, setup.prng, before_expiry_block, ) assert iteration.new_state, 'payment task should not be deleted at this lock expired' # should not be accepted assert not events.must_contain_entry(iteration.events, SendProcessed, {}) # now we get to the lock expiration block current_state = iteration.new_state state_change = Block( block_number=expiry_block, gas_limit=1, block_hash=factories.make_transaction_hash(), ) iteration = initiator_manager.state_transition( current_state, state_change, setup.channel_map, setup.prng, expiry_block, ) assert events.must_contain_entry(iteration.events, SendLockExpired, {}) # Since there was a refund transfer the payment task must not have been deleted assert iteration.new_state is not None # process the lock expired message after lock expiration current_state = iteration.new_state iteration = initiator_manager.state_transition( current_state, lock_expired_state_change, setup.channel_map, setup.prng, expiry_block, ) # should be accepted assert events.must_contain_entry(iteration.events, SendProcessed, {}) assert iteration.new_state, 'payment task should be there waiting for next block' # process the the block after lock expiration current_state = iteration.new_state state_change = Block( block_number=expiry_block + 1, gas_limit=1, block_hash=factories.make_transaction_hash(), ) iteration = initiator_manager.state_transition( current_state, state_change, setup.channel_map, setup.prng, expiry_block + 1, ) assert iteration.new_state is None, 'from this point on the payment task should go'
def test_refund_transfer_next_route(): amount = UNIT_TRANSFER_AMOUNT our_address = factories.ADDR refund_pkey, refund_address = factories.make_privkey_address() pseudo_random_generator = random.Random() channel1 = factories.make_channel( our_balance=amount, partner_balance=amount, our_address=our_address, partner_address=refund_address, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) channel2 = factories.make_channel( our_balance=0, our_address=our_address, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) channel3 = factories.make_channel( our_balance=amount, our_address=our_address, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) channel_map = { channel1.identifier: channel1, channel2.identifier: channel2, channel3.identifier: channel3, } available_routes = [ factories.route_from_channel(channel1), factories.route_from_channel(channel2), factories.route_from_channel(channel3), ] block_number = 10 current_state = make_initiator_manager_state( available_routes, factories.UNIT_TRANSFER_DESCRIPTION, channel_map, pseudo_random_generator, block_number, ) original_transfer = current_state.initiator.transfer refund_transfer = factories.make_signed_transfer( amount, our_address, original_transfer.target, original_transfer.lock.expiration, UNIT_SECRET, payment_identifier=original_transfer.payment_identifier, channel_identifier=channel1.identifier, pkey=refund_pkey, sender=refund_address, ) assert channel1.partner_state.address == refund_address state_change = ReceiveTransferRefundCancelRoute( routes=available_routes, transfer=refund_transfer, secret=random_secret(), ) iteration = initiator_manager.state_transition( current_state, state_change, channel_map, pseudo_random_generator, block_number, ) assert iteration.new_state is not None route_cancelled = next(e for e in iteration.events if isinstance(e, EventUnlockFailed)) new_transfer = next(e for e in iteration.events if isinstance(e, SendLockedTransfer)) assert route_cancelled, 'The previous transfer must be cancelled' assert new_transfer, 'No mediated transfer event emitted, should have tried a new route' msg = 'the new transfer must use a new secret / secrethash' assert new_transfer.transfer.lock.secrethash != refund_transfer.lock.secrethash, msg assert iteration.new_state.initiator is not None
def run_test_locked_transfer_secret_registered_onchain( raiden_network, token_addresses, secret_registry_address, retry_timeout, ): app0 = raiden_network[0] token_address = token_addresses[0] chain_state = views.state_from_app(app0) payment_network_id = app0.raiden.default_registry.address token_network_identifier = views.get_token_network_identifier_by_token_address( chain_state, payment_network_id, token_address, ) amount = 1 target = factories.UNIT_TRANSFER_INITIATOR identifier = 1 transfer_secret = sha3(target + b'1') secret_registry_proxy = app0.raiden.chain.secret_registry( secret_registry_address, ) secret_registry_proxy.register_secret(secret=transfer_secret, given_block_identifier='latest') # Wait until our node has processed the block that the secret registration was mined at block_number = app0.raiden.get_block_number() wait_for_block( raiden=app0.raiden, block_number=block_number + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS, retry_timeout=retry_timeout, ) # Test that sending a transfer with a secret already registered on-chain fails with pytest.raises(RaidenUnrecoverableError): app0.raiden.start_mediated_transfer_with_secret( token_network_identifier=token_network_identifier, amount=amount, fee=0, target=target, identifier=identifier, secret=transfer_secret, ) # Test that receiving a transfer with a secret already registered on chain fails expiration = 9999 locked_transfer = factories.make_signed_transfer( amount, factories.UNIT_TRANSFER_INITIATOR, app0.raiden.address, expiration, transfer_secret, ) message_handler = MessageHandler() message_handler.handle_message_lockedtransfer( app0.raiden, locked_transfer, ) state_changes = app0.raiden.wal.storage.get_statechanges_by_identifier( 0, 'latest') transfer_statechange_dispatched = ( search_for_item(state_changes, ActionInitMediator, {}) or search_for_item(state_changes, ActionInitTarget, {})) assert not transfer_statechange_dispatched
def test_regression_send_refund(): """Regression test for discarded refund transfer. The handle_refundtransfer used to discard events from the channel state machine, which led to the state being updated but the message to the partner was never sent. Also, for issue: https://github.com/raiden-network/raiden/issues/3170 It was noticed that when receiving the same refund transfer twice, the mediator would detect an invalid refund and clear the mediator state. So the test also checks that mediator rejects the duplicate transfer and keeps the mediator state unchanged. """ pseudo_random_generator = random.Random() setup = factories.make_transfers_pair(3) mediator_state = MediatorTransferState(UNIT_SECRETHASH) mediator_state.transfers_pair = setup.transfers_pair last_pair = setup.transfers_pair[-1] channel_identifier = last_pair.payee_transfer.balance_proof.channel_identifier lock_expiration = last_pair.payee_transfer.lock.expiration received_transfer = factories.make_signed_transfer( amount=UNIT_TRANSFER_AMOUNT, initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, expiration=lock_expiration, secret=UNIT_SECRET, payment_identifier=UNIT_TRANSFER_IDENTIFIER, channel_identifier=channel_identifier, pkey=setup.channels.partner_privatekeys[2], sender=setup.channels.partner_address(2), ) # All three channels have been used routes = [] refund_state_change = ReceiveTransferRefund( transfer=received_transfer, routes=routes, ) iteration = mediator.handle_refundtransfer( mediator_state=mediator_state, mediator_state_change=refund_state_change, channelidentifiers_to_channels=setup.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=setup.block_number, ) first_pair = setup.transfers_pair[0] first_payer_transfer = first_pair.payer_transfer payer_channel = mediator.get_payer_channel(setup.channel_map, first_pair) lock = channel.get_lock( end_state=payer_channel.partner_state, secrethash=UNIT_SECRETHASH, ) token_network_identifier = first_payer_transfer.balance_proof.token_network_identifier assert must_contain_entry(iteration.events, SendRefundTransfer, { 'recipient': setup.channels.partner_address(0), 'queue_identifier': { 'recipient': setup.channels.partner_address(0), 'channel_identifier': first_payer_transfer.balance_proof.channel_identifier, }, 'transfer': { 'payment_identifier': UNIT_TRANSFER_IDENTIFIER, 'token': UNIT_TOKEN_ADDRESS, 'balance_proof': { 'transferred_amount': 0, 'locked_amount': 10, 'locksroot': lock.lockhash, 'token_network_identifier': token_network_identifier, 'channel_identifier': first_payer_transfer.balance_proof.channel_identifier, 'chain_id': first_payer_transfer.balance_proof.chain_id, }, 'lock': { 'amount': lock.amount, 'expiration': lock.expiration, 'secrethash': lock.secrethash, }, 'initiator': UNIT_TRANSFER_INITIATOR, 'target': UNIT_TRANSFER_TARGET, }, }) duplicate_iteration = mediator.handle_refundtransfer( mediator_state=iteration.new_state, mediator_state_change=refund_state_change, channelidentifiers_to_channels=setup.channel_map, pseudo_random_generator=pseudo_random_generator, block_number=setup.block_number, ) assert must_contain_entry(duplicate_iteration.events, SendRefundTransfer, {}) is None assert duplicate_iteration.new_state is not None assert duplicate_iteration.new_state == iteration.new_state