def test_events_for_onchain_secretreveal(): """ Secret must be registered on-chain when the unsafe region is reached and the secret is known. """ block_number = 10 expiration = block_number + 30 channels = make_channel_set([channel_properties]) from_transfer = make_target_transfer(channels[0], expiration=expiration) channel.handle_receive_lockedtransfer(channels[0], from_transfer) channel.register_offchain_secret(channels[0], UNIT_SECRET, UNIT_SECRETHASH) safe_to_wait = expiration - channels[0].reveal_timeout - 1 unsafe_to_wait = expiration - channels[0].reveal_timeout state = TargetTransferState(channels.get_route(0), from_transfer) events = target.events_for_onchain_secretreveal(state, channels[0], safe_to_wait) assert not events events = target.events_for_onchain_secretreveal(state, channels[0], unsafe_to_wait) assert events assert isinstance(events[0], ContractSendSecretReveal) assert events[0].secret == UNIT_SECRET
def test_handle_inittarget_bad_expiration(): """ Init transfer must do nothing if the expiration is bad. """ block_number = 1 amount = 3 initiator = factories.HOP1 target_address = UNIT_TRANSFER_TARGET payment_network_identifier = factories.make_address() 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_for( from_channel, amount, initiator, target_address, expiration, UNIT_SECRET, ) channel.handle_receive_lockedtransfer( from_channel, from_transfer, ) state_change = ActionInitTarget(payment_network_identifier, from_route, from_transfer) iteration = target.handle_inittarget(state_change, from_channel, block_number) assert must_contain_entry(iteration.events, EventWithdrawFailed, {})
def test_events_for_onchain_secretreveal(): """ Secret must be registered on-chain when the unsafe region is reached and the secret is known. """ block_number = 10 expiration = block_number + 30 channels = make_channel_set([channel_properties]) from_transfer = make_target_transfer(channels[0], expiration=expiration) channel.handle_receive_lockedtransfer(channels[0], from_transfer) channel.register_offchain_secret(channels[0], UNIT_SECRET, UNIT_SECRETHASH) safe_to_wait = expiration - channels[0].reveal_timeout - 1 unsafe_to_wait = expiration - channels[0].reveal_timeout state = TargetTransferState(channels.get_route(0), from_transfer) events = target.events_for_onchain_secretreveal(state, channels[0], safe_to_wait) assert not events events = target.events_for_onchain_secretreveal(state, channels[0], unsafe_to_wait) msg = 'when its not safe to wait, the contract send must be emitted' assert must_contain_entry(events, ContractSendSecretReveal, {'secret': UNIT_SECRET}), msg msg = 'second call must not emit ContractSendSecretReveal again' assert not target.events_for_onchain_secretreveal(state, channels[0], unsafe_to_wait), msg
def test_events_for_close_secret_unknown(): """ Channel must not be closed when the unsafe region is reached and the secret is not known. """ amount = 3 block_number = 10 expiration = block_number + 30 initiator = factories.HOP1 target_address = UNIT_TRANSFER_TARGET 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) from_transfer = factories.make_signed_transfer_for( from_channel, amount, initiator, target_address, expiration, UNIT_SECRET, ) channel.handle_receive_lockedtransfer( from_channel, from_transfer, ) state = TargetTransferState(from_route, from_transfer) events = target.events_for_close(state, from_channel, expiration) assert not events
def test_events_for_onchain_secretreveal(): """ Secret must be registered on-chain when the unsafe region is reached and the secret is known. """ block_number = 10 expiration = block_number + 30 channels = make_channel_set([channel_properties]) from_transfer = make_target_transfer(channels[0], expiration=expiration) channel.handle_receive_lockedtransfer(channels[0], from_transfer) channel.register_offchain_secret(channels[0], UNIT_SECRET, UNIT_SECRETHASH) safe_to_wait = expiration - channels[0].reveal_timeout - 1 unsafe_to_wait = expiration - channels[0].reveal_timeout state = TargetTransferState(channels.get_route(0), from_transfer) events = target.events_for_onchain_secretreveal(state, channels[0], safe_to_wait) assert not events events = target.events_for_onchain_secretreveal(state, channels[0], unsafe_to_wait) msg = 'when its not safe to wait, the contract send must be emitted' assert search_for_item(events, ContractSendSecretReveal, {'secret': UNIT_SECRET}), msg msg = 'second call must not emit ContractSendSecretReveal again' assert not target.events_for_onchain_secretreveal(state, channels[0], unsafe_to_wait), msg
def test_events_for_close_secret_unknown(): """ Channel must not be closed when the unsafe region is reached and the secret is not known. """ amount = 3 block_number = 10 expiration = block_number + 30 initiator = factories.HOP1 target_address = UNIT_TRANSFER_TARGET 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) from_transfer = factories.make_signed_transfer_for( from_channel, amount, initiator, target_address, expiration, UNIT_SECRET, ) channel.handle_receive_lockedtransfer( from_channel, from_transfer, ) state = TargetTransferState(from_route, from_transfer) events = target.events_for_close(state, from_channel, expiration) assert not events
def test_handle_receive_lockedtransfer_enforces_transfer_limit(): state, transfer = _channel_and_transfer(num_pending_locks=MAXIMUM_PENDING_TRANSFERS - 1) is_valid, _, msg = channel.handle_receive_lockedtransfer(state, transfer) assert is_valid, msg state, transfer = _channel_and_transfer(num_pending_locks=MAXIMUM_PENDING_TRANSFERS) is_valid, _, _ = handle_receive_lockedtransfer(state, transfer) assert not is_valid
def test_handle_receive_lockedtransfer_enforces_transfer_limit(): state, transfer = _channel_and_transfer(merkletree_width=MAXIMUM_PENDING_TRANSFERS - 1) is_valid, _, _ = handle_receive_lockedtransfer(state, transfer) assert is_valid state, transfer = _channel_and_transfer(merkletree_width=MAXIMUM_PENDING_TRANSFERS) is_valid, _, _ = handle_receive_lockedtransfer(state, transfer) assert not is_valid
def test_handle_receive_lockedtransfer_enforces_transfer_limit(): state, transfer = _channel_and_transfer(merkletree_width=MAXIMUM_PENDING_TRANSFERS - 1) is_valid, _, msg = channel.handle_receive_lockedtransfer(state, transfer) assert is_valid, msg state, transfer = _channel_and_transfer(merkletree_width=MAXIMUM_PENDING_TRANSFERS) is_valid, _, msg = channel.handle_receive_lockedtransfer(state, transfer) assert not is_valid, msg
def make_mediated_transfer(registry_address, from_channel, partner_channel, initiator, target, lock, pkey, secret=None): """ Helper to create and register a mediated transfer from `from_channel` to `partner_channel`.""" payment_identifier = channel.get_next_nonce(from_channel.our_state) message_identifier = random.randint(0, UINT64_MAX) lockedtransfer = channel.send_lockedtransfer( registry_address, from_channel, initiator, target, lock.amount, message_identifier, payment_identifier, lock.expiration, lock.secrethash, ) mediated_transfer_msg = LockedTransfer.from_event(lockedtransfer) address = privatekey_to_address(pkey) sign_key = PrivateKey(pkey) mediated_transfer_msg.sign(sign_key, address) # compute the signature balance_proof = balanceproof_from_envelope(mediated_transfer_msg) lockedtransfer.balance_proof = balance_proof # if this fails it's not the right key for the current `from_channel` assert mediated_transfer_msg.sender == from_channel.our_state.address receive_lockedtransfer = lockedtransfersigned_from_message( mediated_transfer_msg) channel.handle_receive_lockedtransfer( partner_channel, receive_lockedtransfer, ) if secret is not None: random_sender = make_address() from_secretreveal = ReceiveSecretReveal(secret, random_sender) channel.handle_receive_secretreveal(from_channel, from_secretreveal) partner_secretreveal = ReceiveSecretReveal(secret, random_sender) channel.handle_receive_secretreveal(partner_channel, partner_secretreveal) return mediated_transfer_msg
def make_mediated_transfer( from_channel, partner_channel, initiator, target, lock, pkey, secret=None, ): """ Helper to create and register a mediated transfer from `from_channel` to `partner_channel`.""" payment_identifier = channel.get_next_nonce(from_channel.our_state) message_identifier = random.randint(0, UINT64_MAX) lockedtransfer = channel.send_lockedtransfer( from_channel, initiator, target, lock.amount, message_identifier, payment_identifier, lock.expiration, lock.secrethash, ) mediated_transfer_msg = LockedTransfer.from_event(lockedtransfer) sign_key = PrivateKey(pkey) mediated_transfer_msg.sign(sign_key, NETWORKNAME_TO_ID[TESTS]) # compute the signature balance_proof = balanceproof_from_envelope(mediated_transfer_msg) lockedtransfer.balance_proof = balance_proof # if this fails it's not the right key for the current `from_channel` assert mediated_transfer_msg.sender == from_channel.our_state.address receive_lockedtransfer = lockedtransfersigned_from_message(mediated_transfer_msg) channel.handle_receive_lockedtransfer( partner_channel, receive_lockedtransfer, ) if secret is not None: secrethash = sha3(secret) channel.register_secret(from_channel, secret, secrethash) channel.register_secret(partner_channel, secret, secrethash) return mediated_transfer_msg
def make_mediated_transfer( from_channel, partner_channel, initiator, target, lock, pkey, secret=None, ): """ Helper to create and register a mediated transfer from `from_channel` to `partner_channel`.""" payment_identifier = channel.get_next_nonce(from_channel.our_state) message_identifier = random.randint(0, UINT64_MAX) lockedtransfer = channel.send_lockedtransfer( from_channel, initiator, target, lock.amount, message_identifier, payment_identifier, lock.expiration, lock.secrethash, ) mediated_transfer_msg = LockedTransfer.from_event(lockedtransfer) sign_key = PrivateKey(pkey) mediated_transfer_msg.sign(sign_key) # compute the signature balance_proof = balanceproof_from_envelope(mediated_transfer_msg) lockedtransfer.balance_proof = balance_proof # if this fails it's not the right key for the current `from_channel` assert mediated_transfer_msg.sender == from_channel.our_state.address receive_lockedtransfer = lockedtransfersigned_from_message(mediated_transfer_msg) channel.handle_receive_lockedtransfer( partner_channel, receive_lockedtransfer, ) if secret is not None: secrethash = sha3(secret) channel.register_secret(from_channel, secret, secrethash) channel.register_secret(partner_channel, secret, secrethash) return mediated_transfer_msg
def test_handle_inittarget_bad_expiration(): """ Init transfer must do nothing if the expiration is bad. """ block_number = 1 pseudo_random_generator = random.Random() channels = make_channel_set([channel_properties]) expiration = channels[0].reveal_timeout + block_number + 1 from_transfer = make_target_transfer(channels[0], expiration=expiration) channel.handle_receive_lockedtransfer(channels[0], from_transfer) state_change = ActionInitTarget(channels.get_route(0), from_transfer) iteration = target.handle_inittarget(state_change, channels[0], pseudo_random_generator, block_number) assert search_for_item(iteration.events, EventUnlockClaimFailed, {})
def test_channel_must_accept_expired_locks(): """A node may go offline for an undetermined period of time, and when it comes back online it must accept the messages that are waiting, otherwise the partner node won't make progress with its queue. If a N node goes offline for a number B of blocks, and the partner does not close the channel, when N comes back online some of the messages from its partner may become expired. Nevertheless these messages are ordered and must be accepted for the partner to make progress with its queue. Note: Accepting a message with an expired lock does *not* imply the token transfer happened, and the receiver node must *not* forward the transfer, only accept the message allowing the partner to progress with its message queue. """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) block_number = 100 lock_amount = 10 lock_expiration = block_number - 10 lock_secrethash = sha3(b'test_channel_must_accept_expired_locks') lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, _, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) assert is_valid, msg # the locked amount must increase even though the lock is expired, this # will be removed by an additional synchronization message from the partner our_model2 = our_model1 partner_model2 = partner_model1._replace( amount_locked=lock_amount, distributable=partner_model1.distributable - lock_amount, next_nonce=partner_model1.next_nonce + 1, merkletree_leaves=[lock.lockhash], ) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2)
def test_receive_lockedtransfer_before_deposit(): """Regression test that ensures we accept incoming mediated transfers, even if we don't have any balance on the channel. """ our_model1, _ = create_model(0) # our deposit is 0 partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) lock_amount = 30 lock_expiration = 10 lock_secret = sha3(b'test_end_state') lock_secrethash = sha3(lock_secret) lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) # this node partner has enough balance, the transfer must be accepted assert is_valid, msg
def test_channel_withdraw_must_not_change_merkletree(): our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) payment_network_identifier = factories.make_address() lock_amount = 10 lock_expiration = 100 lock_secret = sha3(b'test_channelstate_lockedtransfer_overspent') lock_secrethash = sha3(lock_secret) lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, _, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) assert is_valid, msg assert merkleroot(channel_state.partner_state.merkletree) == lock.lockhash assert channel.is_lock_pending(channel_state.partner_state, lock.secrethash) closed_block_number = lock_expiration - channel_state.reveal_timeout - 1 state_change = ContractReceiveChannelClosed( payment_network_identifier, channel_state.token_address, channel_state.identifier, partner_model1.participant_address, closed_block_number, ) iteration = channel.handle_channel_closed(channel_state, state_change) new_channel = iteration.new_state withdraw = ContractReceiveChannelWithdraw( payment_network_identifier, channel_state.token_address, channel_state.identifier, lock_secret, channel_state.our_state.address, ) iteration = channel.handle_channel_withdraw(new_channel, withdraw) new_channel = iteration.new_state assert merkleroot(new_channel.partner_state.merkletree) == lock.lockhash assert not channel.is_lock_pending(new_channel.partner_state, lock.secrethash)
def handle_init(state_change, channelidentifiers_to_channels, block_number): routes = state_change.routes from_route = state_change.from_route from_transfer = state_change.from_transfer payer_channel = channelidentifiers_to_channels.get( from_route.channel_identifier) # There is no corresponding channel for the message, ignore it if not payer_channel: return TransitionResult(None, []) mediator_state = MediatorTransferState(from_transfer.lock.secrethash) is_valid, _ = channel.handle_receive_lockedtransfer( payer_channel, from_transfer, ) if not is_valid: return TransitionResult(None, []) iteration = mediate_transfer( mediator_state, routes, payer_channel, channelidentifiers_to_channels, from_transfer, block_number, ) return iteration
def test_events_for_onchain_secretreveal(): """ Secret must be registered on-chain when the unsafe region is reached and the secret is known. """ amount = 3 block_number = 10 expiration = block_number + 30 initiator = HOP1 target_address = UNIT_TRANSFER_TARGET 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) from_transfer = factories.make_signed_transfer_for( from_channel, amount, initiator, target_address, expiration, UNIT_SECRET, ) channel.handle_receive_lockedtransfer( from_channel, from_transfer, ) channel.register_offchain_secret(from_channel, UNIT_SECRET, UNIT_SECRETHASH) safe_to_wait = expiration - from_channel.reveal_timeout - 1 unsafe_to_wait = expiration - from_channel.reveal_timeout state = TargetTransferState(from_route, from_transfer) events = target.events_for_onchain_secretreveal(state, from_channel, safe_to_wait) assert not events events = target.events_for_onchain_secretreveal(state, from_channel, unsafe_to_wait) assert events assert isinstance(events[0], ContractSendSecretReveal) assert events[0].secret == UNIT_SECRET
def handle_inittarget( state_change, channel_state, pseudo_random_generator, block_number, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, _, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) safe_to_wait, unsafe_msg = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # if there is not enough time to safely unlock the token on-chain # silently let the transfer expire. if is_valid and safe_to_wait: message_identifier = message_identifier_from_prng( pseudo_random_generator) recipient = transfer.initiator secret_request = SendSecretRequest( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, ) iteration = TransitionResult(target_state, [secret_request]) else: if not is_valid: failure_reason = errormsg elif not safe_to_wait: failure_reason = unsafe_msg unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=failure_reason, ) iteration = TransitionResult(target_state, [unlock_failed]) return iteration
def handle_inittarget( state_change, channel_state, pseudo_random_generator, block_number, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_address is_valid, _, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) safe_to_wait = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # if there is not enough time to safely withdraw the token on-chain # silently let the transfer expire. if is_valid and safe_to_wait: message_identifier = message_identifier_from_prng( pseudo_random_generator) recipient = transfer.initiator queue_name = b'global' secret_request = SendSecretRequest( recipient, queue_name, message_identifier, transfer.payment_identifier, transfer.lock.amount, transfer.lock.secrethash, ) iteration = TransitionResult(target_state, [secret_request]) else: if not is_valid: failure_reason = errormsg elif not safe_to_wait: failure_reason = 'lock expiration is not safe' withdraw_failed = EventWithdrawFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=failure_reason, ) iteration = TransitionResult(target_state, [withdraw_failed]) return iteration
def handle_inittarget( state_change, channel_state, pseudo_random_generator, block_number, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_address is_valid, _, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) safe_to_wait = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # if there is not enough time to safely unlock the token on-chain # silently let the transfer expire. if is_valid and safe_to_wait: message_identifier = message_identifier_from_prng(pseudo_random_generator) recipient = transfer.initiator queue_name = b'global' secret_request = SendSecretRequest( recipient, queue_name, message_identifier, transfer.payment_identifier, transfer.lock.amount, transfer.lock.secrethash, ) iteration = TransitionResult(target_state, [secret_request]) else: if not is_valid: failure_reason = errormsg elif not safe_to_wait: failure_reason = 'lock expiration is not safe' unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=failure_reason, ) iteration = TransitionResult(target_state, [unlock_failed]) return iteration
def test_events_for_onchain_secretreveal(): """ Secret must be registered on-chain when the unsafe region is reached and the secret is known. """ amount = 3 block_number = 10 expiration = block_number + 30 initiator = HOP1 target_address = UNIT_TRANSFER_TARGET 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) from_transfer = factories.make_signed_transfer_for( from_channel, amount, initiator, target_address, expiration, UNIT_SECRET, ) channel.handle_receive_lockedtransfer( from_channel, from_transfer, ) channel.register_secret(from_channel, UNIT_SECRET, UNIT_SECRETHASH) safe_to_wait = expiration - from_channel.reveal_timeout - 1 unsafe_to_wait = expiration - from_channel.reveal_timeout state = TargetTransferState(from_route, from_transfer) events = target.events_for_onchain_secretreveal(state, from_channel, safe_to_wait) assert not events events = target.events_for_onchain_secretreveal(state, from_channel, unsafe_to_wait) assert events assert isinstance(events[0], ContractSendSecretReveal) assert events[0].secret == UNIT_SECRET
def test_channelstate_unlock(): """The node must call unlock after the channel is settled""" our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) lock_amount = 10 lock_expiration = 100 lock_secret = sha3(b'test_channelstate_lockedtransfer_overspent') lock_secrethash = sha3(lock_secret) lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, _, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) assert is_valid, msg channel.register_secret(channel_state, lock_secret, lock_secrethash) closed_block_number = lock_expiration - channel_state.reveal_timeout - 1 close_state_change = ContractReceiveChannelClosed( channel_state.token_network_identifier, channel_state.identifier, partner_model1.participant_address, closed_block_number, ) iteration = channel.handle_channel_closed(channel_state, close_state_change) assert not must_contain_entry(iteration.events, ContractSendChannelBatchUnlock, {}) settle_block_number = lock_expiration + channel_state.reveal_timeout + 1 settle_state_change = ContractReceiveChannelSettled( channel_state.token_network_identifier, channel_state.identifier, settle_block_number, ) iteration = channel.handle_channel_settled( channel_state, settle_state_change, settle_block_number, ) assert must_contain_entry(iteration.events, ContractSendChannelBatchUnlock, {})
def test_handle_inittarget_bad_expiration(): """ Init transfer must do nothing if the expiration is bad. """ block_number = 1 pseudo_random_generator = random.Random() channels = make_channel_set([channel_properties]) expiration = channels[0].reveal_timeout + block_number + 1 from_transfer = make_target_transfer(channels[0], expiration=expiration) channel.handle_receive_lockedtransfer(channels[0], from_transfer) state_change = ActionInitTarget(channels.get_route(0), from_transfer) iteration = target.handle_inittarget( state_change, channels[0], pseudo_random_generator, block_number, ) assert must_contain_entry(iteration.events, EventUnlockClaimFailed, {})
def test_handle_inittarget_bad_expiration(): """ Init transfer must do nothing if the expiration is bad. """ block_number = 1 amount = 3 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_for( from_channel, amount, initiator, target_address, expiration, UNIT_SECRET, ) channel.handle_receive_lockedtransfer( from_channel, from_transfer, ) 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, EventUnlockClaimFailed, {})
def test_channelstate_withdraw(): """Event close must be properly handled if there are no locks to unlock""" our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) payment_network_identifier = factories.make_address() lock_amount = 10 lock_expiration = 100 lock_secret = sha3(b'test_channelstate_lockedtransfer_overspent') lock_secrethash = sha3(lock_secret) lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, _, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) assert is_valid, msg channel.register_secret(channel_state, lock_secret, lock_secrethash) # If the channel is closed, withdraw must be done even if the lock is not # at risk of expiring closed_block_number = lock_expiration - channel_state.reveal_timeout - 1 state_change = ContractReceiveChannelClosed( payment_network_identifier, channel_state.token_address, channel_state.identifier, partner_model1.participant_address, closed_block_number, ) iteration = channel.handle_channel_closed(channel_state, state_change) assert must_contain_entry(iteration.events, ContractSendChannelWithdraw, {})
def handle_init( state_change: ActionInitMediator, channelidentifiers_to_channels: typing.ChannelMap, pseudo_random_generator: random.Random, block_number: typing.BlockNumber, ): routes = state_change.routes from_route = state_change.from_route from_transfer = state_change.from_transfer payer_channel = channelidentifiers_to_channels.get( from_route.channel_identifier) # There is no corresponding channel for the message, ignore it if not payer_channel: return TransitionResult(None, []) mediator_state = MediatorTransferState(from_transfer.lock.secrethash) is_valid, events, _ = channel.handle_receive_lockedtransfer( payer_channel, from_transfer, ) if not is_valid: # If the balance proof is not valid, do *not* create a task. Otherwise it's # possible for an attacker to send multiple invalid transfers, and increase # the memory usage of this Node. return TransitionResult(None, events) iteration = mediate_transfer( mediator_state, routes, payer_channel, channelidentifiers_to_channels, pseudo_random_generator, from_transfer, block_number, ) events.extend(iteration.events) return TransitionResult(iteration.new_state, events)
def test_channelstate_lockedtransfer_invalid_chainid(): """Receiving a locked transfer with chain_id different from the channel's chain_id should be ignored """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) distributable = channel.get_distributable(channel_state.partner_state, channel_state.our_state) lock_amount = distributable - 1 lock_expiration = 10 lock_secrethash = sha3(b'test_channelstate_lockedtransfer_overspent') lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, chain_id=UNIT_CHAIN_ID + 1, ) is_valid, _, _ = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) assert not is_valid, ( 'message is invalid because it uses different chain_id than the channel' ) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model1) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model1)
def test_channelstate_lockedtransfer_overspent(): """Receiving a lock with an amount large than distributable must be ignored. """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) distributable = channel.get_distributable(channel_state.partner_state, channel_state.our_state) lock_amount = distributable + 1 lock_expiration = 10 lock_secrethash = sha3(b'test_channelstate_lockedtransfer_overspent') lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, _, _ = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) assert not is_valid, 'message is invalid because it is spending more than the distributable' assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model1) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model1)
def handle_init( state_change, channelidentifiers_to_channels, pseudo_random_generator, block_number, ): routes = state_change.routes from_route = state_change.from_route from_transfer = state_change.from_transfer payer_channel = channelidentifiers_to_channels.get( from_route.channel_identifier) # There is no corresponding channel for the message, ignore it if not payer_channel: return TransitionResult(None, []) mediator_state = MediatorTransferState(from_transfer.lock.secrethash) is_valid, events, _ = channel.handle_receive_lockedtransfer( payer_channel, from_transfer, ) if not is_valid: return TransitionResult(None, events) iteration = mediate_transfer( state_change.payment_network_identifier, mediator_state, routes, payer_channel, channelidentifiers_to_channels, pseudo_random_generator, from_transfer, block_number, ) events.extend(iteration.events) return TransitionResult(iteration.new_state, events)
def handle_init( state_change, channelidentifiers_to_channels, pseudo_random_generator, block_number, ): routes = state_change.routes from_route = state_change.from_route from_transfer = state_change.from_transfer payer_channel = channelidentifiers_to_channels.get(from_route.channel_identifier) # There is no corresponding channel for the message, ignore it if not payer_channel: return TransitionResult(None, []) mediator_state = MediatorTransferState(from_transfer.lock.secrethash) is_valid, events, _ = channel.handle_receive_lockedtransfer( payer_channel, from_transfer, ) if not is_valid: return TransitionResult(None, events) iteration = mediate_transfer( mediator_state, routes, payer_channel, channelidentifiers_to_channels, pseudo_random_generator, from_transfer, block_number, ) events.extend(iteration.events) return TransitionResult(iteration.new_state, events)
def handle_inittarget( state_change: ActionInitTarget, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: typing.BlockNumber, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, channel_events, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) if is_valid: # A valid balance proof does not mean the payment itself is still valid. # e.g. the lock may be near expiration or have expired. This is fine. The # message with an unusable lock must be handled to properly synchronize the # local view of the partner's channel state, allowing the next balance # proofs to be handled. This however, must only be done once, which is # enforced by the nonce increasing sequentially, which is verified by # the handler handle_receive_lockedtransfer. target_state = TargetTransferState(route, transfer) safe_to_wait, _ = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # If there is not enough time to safely unlock the lock on-chain # silently let the transfer expire. The target task must be created to # handle the ReceiveLockExpired state change, which will clear the # expired lock. if safe_to_wait: message_identifier = message_identifier_from_prng( pseudo_random_generator) recipient = transfer.initiator secret_request = SendSecretRequest( recipient=typing.Address(recipient), channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, ) channel_events.append(secret_request) iteration = TransitionResult(target_state, channel_events) else: # If the balance proof is not valid, do *not* create a task. Otherwise it's # possible for an attacker to send multiple invalid transfers, and increase # the memory usage of this Node. unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=errormsg, ) channel_events.append(unlock_failed) iteration = TransitionResult(None, channel_events) return iteration
def make_transfers_pair(number_of_channels: int, amount: int = UNIT_TRANSFER_AMOUNT, block_number: int = 5) -> MediatorTransfersPair: deposit = 5 * amount defaults = create_properties( NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=deposit), partner_state=NettingChannelEndStateProperties(balance=deposit), open_transaction=TransactionExecutionStatusProperties( finished_block_number=10), )) properties_list = [ NettingChannelStateProperties( canonical_identifier=make_canonical_identifier( channel_identifier=i), our_state=NettingChannelEndStateProperties( address=ChannelSet.ADDRESSES[0], privatekey=ChannelSet.PKEYS[0]), partner_state=NettingChannelEndStateProperties( address=ChannelSet.ADDRESSES[i + 1], privatekey=ChannelSet.PKEYS[i + 1]), ) for i in range(number_of_channels) ] channels = make_channel_set(properties_list, defaults) lock_expiration = block_number + UNIT_REVEAL_TIMEOUT * 2 pseudo_random_generator = random.Random() transfers_pairs = list() for payer_index in range(number_of_channels - 1): payee_index = payer_index + 1 receiver_channel = channels[payer_index] received_transfer = create( LockedTransferSignedStateProperties( amount=amount, expiration=lock_expiration, payment_identifier=UNIT_TRANSFER_IDENTIFIER, canonical_identifier=receiver_channel.canonical_identifier, sender=channels.partner_address(payer_index), pkey=channels.partner_privatekeys[payer_index], )) is_valid, _, msg = channel.handle_receive_lockedtransfer( receiver_channel, received_transfer) assert is_valid, msg message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = channel.send_lockedtransfer( channel_state=channels[payee_index], initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, amount=amount, message_identifier=message_identifier, payment_identifier=UNIT_TRANSFER_IDENTIFIER, expiration=lock_expiration, secrethash=UNIT_SECRETHASH, ) assert lockedtransfer_event lock_timeout = lock_expiration - block_number assert mediator.is_channel_usable( candidate_channel_state=channels[payee_index], transfer_amount=amount, lock_timeout=lock_timeout, ) sent_transfer = lockedtransfer_event.transfer pair = MediationPairState(received_transfer, lockedtransfer_event.recipient, sent_transfer) transfers_pairs.append(pair) return MediatorTransfersPair( channels=channels, transfers_pair=transfers_pairs, amount=amount, block_number=block_number, block_hash=make_block_hash(), )
def test_interwoven_transfers(): """Can keep doing transactions even if not all secrets have been released.""" number_of_transfers = 100 balance_for_all_transfers = 11 * number_of_transfers lock_amounts = cycle([1, 3, 5, 7, 11]) lock_secrets = [ format(i, '>032').encode() for i in range(number_of_transfers) ] our_model, _ = create_model(70) partner_model, privkey2 = create_model(balance_for_all_transfers) channel_state = create_channel_from_models(our_model, partner_model) block_number = 1000 nonce = 0 transferred_amount = 0 our_model_current = our_model partner_model_current = partner_model for i, (lock_amount, lock_secret) in enumerate(zip(lock_amounts, lock_secrets)): nonce += 1 block_number += 1 lock_expiration = block_number + channel_state.settle_timeout - 1 lock_secrethash = sha3(lock_secret) lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) merkletree_leaves = list(partner_model_current.merkletree_leaves) merkletree_leaves.append(lock.lockhash) partner_model_current = partner_model_current._replace( distributable=partner_model_current.distributable - lock_amount, amount_locked=partner_model_current.amount_locked + lock_amount, next_nonce=partner_model_current.next_nonce + 1, merkletree_leaves=merkletree_leaves, ) receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, merkletree_leaves=merkletree_leaves, ) is_valid, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) assert is_valid, msg assert_partner_state( channel_state.our_state, channel_state.partner_state, our_model_current, ) assert_partner_state( channel_state.partner_state, channel_state.our_state, partner_model_current, ) # claim a transaction at every other iteration, leaving the current one # in place if i % 2: # Update our model: # - Increase nonce because the secret is a new balance proof # - The lock is removed from the merkle tree, the balance proof must be updated # - The locksroot must have unlocked lock removed # - The transferred amount must be increased by the lock amount # - This changes the balance for both participants: # - the sender balance and locked amount is decremented by the lock amount # - the receiver balance and distributable is incremented by the lock amount nonce += 1 transferred_amount += lock_amount merkletree_leaves = list(partner_model_current.merkletree_leaves) merkletree_leaves.remove(lock.lockhash) tree = compute_layers(merkletree_leaves) locksroot = tree[MERKLEROOT][0] partner_model_current = partner_model_current._replace( amount_locked=partner_model_current.amount_locked - lock_amount, balance=partner_model_current.balance - lock_amount, next_nonce=partner_model_current.next_nonce + 1, merkletree_leaves=merkletree_leaves, ) our_model_current = our_model_current._replace( balance=our_model_current.balance + lock_amount, distributable=our_model_current.distributable + lock_amount, ) message_identifier = random.randint(0, UINT64_MAX) secret_message = Secret( message_identifier=message_identifier, payment_identifier=nonce, nonce=nonce, channel=channel_state.identifier, transferred_amount=transferred_amount, locksroot=locksroot, secret=lock_secret, ) secret_message.sign(privkey2, channel_state.partner_state.address) balance_proof = balanceproof_from_envelope(secret_message) unlock_state_change = ReceiveUnlock( lock_secret, balance_proof, ) is_valid, msg = channel.handle_unlock(channel_state, unlock_state_change) assert is_valid, msg assert_partner_state( channel_state.our_state, channel_state.partner_state, our_model_current, ) assert_partner_state( channel_state.partner_state, channel_state.our_state, partner_model_current, )
def test_channelstate_lockedtransfer_overspend_with_multiple_pending_transfers(): """Receiving a concurrent lock with an amount large than distributable must be ignored. """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) # Step 1: Create a lock with an amount of 1 # - this wont be unlocked lock1_amount = 1 lock1_expiration = 1 + channel_state.settle_timeout lock1_secrethash = sha3(b'test_receive_cannot_overspend_with_multiple_pending_transfers1') lock1 = HashTimeLockState( lock1_amount, lock1_expiration, lock1_secrethash, ) nonce1 = 1 transferred_amount = 0 receive_lockedtransfer1 = make_receive_transfer_mediated( channel_state, privkey2, nonce1, transferred_amount, lock1, ) is_valid, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer1, ) assert is_valid, msg our_model2 = our_model1 partner_model2 = partner_model1._replace( distributable=partner_model1.distributable - lock1.amount, amount_locked=lock1.amount, next_nonce=2, merkletree_leaves=[lock1.lockhash], ) # The valid transfer is handled normally assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2) # Step 2: Create a lock with the current *distributable + 1* # - This must be ignored distributable = channel.get_distributable(channel_state.partner_state, channel_state.our_state) lock2_amount = distributable + 1 lock2_expiration = channel_state.settle_timeout lock2_secrethash = sha3(b'test_receive_cannot_overspend_with_multiple_pending_transfers2') lock2 = HashTimeLockState( lock2_amount, lock2_expiration, lock2_secrethash, ) leaves = [lock1.lockhash, lock2.lockhash] nonce2 = 2 receive_lockedtransfer2 = make_receive_transfer_mediated( channel_state, privkey2, nonce2, transferred_amount, lock2, merkletree_leaves=leaves, ) is_valid, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer2, ) assert not is_valid, 'message is invalid because its expending more than the distributable' # The overspending transfer must be ignored assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2)
def test_channelstate_receive_lockedtransfer(): """Tests receiving a mediated transfer. The transfer is done in three steps: - a mediated transfer including a lock in its balance proof is sent - the secret is revealed - the unlocked balance proof is sent updating the transferred_amount """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) # Step 1: Simulate receiving a transfer # - The receiver end state doesnt change # - The lock must be registered with the sender end lock_amount = 30 lock_expiration = 10 lock_secret = sha3(b'test_end_state') lock_secrethash = sha3(lock_secret) lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) assert is_valid, msg our_model2 = our_model1 partner_model2 = partner_model1._replace( distributable=partner_model1.distributable - lock_amount, amount_locked=lock_amount, next_nonce=2, merkletree_leaves=[lock.lockhash], ) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2) # Step 2: Simulate learning the secret # - Registers the secret, this must not change the balance/locked amount channel.register_secret(channel_state, lock_secret, lock_secrethash) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2) # Step 3: Simulate unlocking the lock # - Update the balances transferred_amount = 0 message_identifier = random.randint(0, UINT64_MAX) secret_message = Secret( message_identifier=message_identifier, payment_identifier=1, nonce=2, channel=channel_state.identifier, transferred_amount=transferred_amount + lock_amount, locksroot=EMPTY_MERKLE_ROOT, secret=lock_secret, ) secret_message.sign(privkey2, channel_state.partner_state.address) balance_proof = balanceproof_from_envelope(secret_message) unlock_state_change = ReceiveUnlock( lock_secret, balance_proof, ) is_valid, msg = channel.handle_unlock(channel_state, unlock_state_change) assert is_valid, msg our_model3 = our_model2._replace( balance=our_model2.balance + lock_amount, distributable=our_model2.balance + lock_amount, ) partner_model3 = partner_model2._replace( balance=partner_model2.balance - lock_amount, amount_locked=0, next_nonce=3, merkletree_leaves=[], ) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model3) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model3)
def make_transfers_pair(privatekeys, amount, block_number): transfers_pair = list() channel_map = dict() pseudo_random_generator = random.Random() addresses = list() for pkey in privatekeys: pubkey = pkey.public_key.format(compressed=False) address = publickey_to_address(pubkey) addresses.append(address) key_address = list(zip(privatekeys, addresses)) deposit_amount = amount * 5 channels_state = { address: make_channel( our_address=HOP1, our_balance=deposit_amount, partner_balance=deposit_amount, partner_address=address, token_address=UNIT_TOKEN_ADDRESS, token_network_identifier=UNIT_TOKEN_NETWORK_ADDRESS, ) for address in addresses } lock_expiration = block_number + UNIT_REVEAL_TIMEOUT * 2 for (payer_key, payer_address), payee_address in zip(key_address[:-1], addresses[1:]): pay_channel = channels_state[payee_address] receive_channel = channels_state[payer_address] received_transfer = make_signed_transfer( amount=amount, initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, expiration=lock_expiration, secret=UNIT_SECRET, payment_identifier=UNIT_TRANSFER_IDENTIFIER, channel_identifier=receive_channel.identifier, pkey=payer_key, sender=payer_address, ) is_valid, _, msg = channel.handle_receive_lockedtransfer( receive_channel, received_transfer, ) assert is_valid, msg message_identifier = message_identifier_from_prng( pseudo_random_generator) lockedtransfer_event = channel.send_lockedtransfer( channel_state=pay_channel, initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, amount=amount, message_identifier=message_identifier, payment_identifier=UNIT_TRANSFER_IDENTIFIER, expiration=lock_expiration, secrethash=UNIT_SECRETHASH, ) assert lockedtransfer_event lock_timeout = lock_expiration - block_number assert mediator.is_channel_usable( candidate_channel_state=pay_channel, transfer_amount=amount, lock_timeout=lock_timeout, ) sent_transfer = lockedtransfer_event.transfer pair = MediationPairState( received_transfer, lockedtransfer_event.recipient, sent_transfer, ) transfers_pair.append(pair) channel_map[receive_channel.identifier] = receive_channel channel_map[pay_channel.identifier] = pay_channel assert channel.is_lock_locked(receive_channel.partner_state, UNIT_SECRETHASH) assert channel.is_lock_locked(pay_channel.our_state, UNIT_SECRETHASH) return channel_map, transfers_pair
def handle_transferreroute( payment_state: InitiatorPaymentState, state_change: ActionTransferReroute, channelidentifiers_to_channels: Dict[ChannelID, NettingChannelState], addresses_to_channel: Dict[Tuple[TokenNetworkAddress, Address], NettingChannelState], nodeaddresses_to_networkstates: NodeNetworkStateMap, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[InitiatorPaymentState]: try: initiator_state = payment_state.initiator_transfers[ state_change.transfer.lock.secrethash] channel_identifier = initiator_state.channel_identifier channel_state = channelidentifiers_to_channels[channel_identifier] except KeyError: return TransitionResult(payment_state, list()) refund_transfer = state_change.transfer original_transfer = initiator_state.transfer is_valid_lock = ( refund_transfer.lock.secrethash == original_transfer.lock.secrethash and refund_transfer.lock.amount == original_transfer.lock.amount and refund_transfer.lock.expiration == original_transfer.lock.expiration) is_valid_refund = channel.refund_transfer_matches_transfer( refund_transfer, original_transfer) is_valid, channel_events, _ = channel.handle_receive_lockedtransfer( channel_state, refund_transfer) if not is_valid_lock or not is_valid_refund or not is_valid: return TransitionResult(payment_state, list()) events: List[Event] = [] events.extend(channel_events) old_description = initiator_state.transfer_description filtered_route_states = routes.filter_acceptable_routes( route_states=payment_state.routes, blacklisted_channel_ids=payment_state.cancelled_channels, addresses_to_channel=addresses_to_channel, token_network_address=old_description.token_network_address, ) transfer_description = TransferDescriptionWithSecretState( token_network_registry_address=old_description. token_network_registry_address, payment_identifier=old_description.payment_identifier, amount=old_description.amount, token_network_address=old_description.token_network_address, initiator=old_description.initiator, target=old_description.target, secret=state_change.secret, secrethash=state_change.secrethash, ) sub_iteration = initiator.try_new_route( addresses_to_channel=addresses_to_channel, nodeaddresses_to_networkstates=nodeaddresses_to_networkstates, candidate_route_states=filtered_route_states, transfer_description=transfer_description, pseudo_random_generator=pseudo_random_generator, block_number=block_number, ) events.extend(sub_iteration.events) if sub_iteration.new_state is None: # Here we don't delete the initiator state, but instead let it live. # It will be deleted when the lock expires. We do that so that we # still have an initiator payment task around to process the # LockExpired message that our partner will send us. # https://github.com/raiden-network/raiden/issues/3146#issuecomment-447378046 return TransitionResult(payment_state, events) new_transfer = sub_iteration.new_state.transfer payment_state.initiator_transfers[ new_transfer.lock.secrethash] = sub_iteration.new_state return TransitionResult(payment_state, events)
def handle_inittarget( state_change: ActionInitTarget, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, channel_events, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) if is_valid: # A valid balance proof does not mean the payment itself is still valid. # e.g. the lock may be near expiration or have expired. This is fine. The # message with an unusable lock must be handled to properly synchronize the # local view of the partner's channel state, allowing the next balance # proofs to be handled. This however, must only be done once, which is # enforced by the nonce increasing sequentially, which is verified by # the handler handle_receive_lockedtransfer. target_state = TargetTransferState(route, transfer) safe_to_wait, _ = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # If there is not enough time to safely unlock the lock on-chain # silently let the transfer expire. The target task must be created to # handle the ReceiveLockExpired state change, which will clear the # expired lock. if safe_to_wait: message_identifier = message_identifier_from_prng(pseudo_random_generator) recipient = transfer.initiator secret_request = SendSecretRequest( recipient=Address(recipient), channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, ) channel_events.append(secret_request) iteration = TransitionResult(target_state, channel_events) else: # If the balance proof is not valid, do *not* create a task. Otherwise it's # possible for an attacker to send multiple invalid transfers, and increase # the memory usage of this Node. unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=errormsg, ) channel_events.append(unlock_failed) iteration = TransitionResult(None, channel_events) return iteration
def assert_balance_proof( token_network_address: TokenNetworkAddress, app0: App, app1: App, saved_state0: SavedState, saved_state1: SavedState, ) -> None: """Assert app0 and app1 agree on the latest balance proof from app0. Notes: - The other direction of the channel does not have to be synchronized, it can be checked with another call. - It is important to do the validation on a fixed state, that is why saved_state0 is used. """ assert app0.raiden.wal assert app1.raiden.wal assert app0.raiden.address == saved_state0.state.our_address assert app1.raiden.address == saved_state1.state.our_address channel0 = views.get_channelstate_by_token_network_and_partner( saved_state0.state, token_network_address, app1.raiden.address) channel1 = views.get_channelstate_by_token_network_and_partner( saved_state1.state, token_network_address, app0.raiden.address) assert channel0 assert channel1 balanceproof0 = cast(BalanceProofUnsignedState, channel0.our_state.balance_proof) balanceproof1 = cast(BalanceProofSignedState, channel1.partner_state.balance_proof) if balanceproof0 is None: msg = "Bug detected. The sender does not have a balance proof, but the recipient does." assert balanceproof1 is None, msg # nothing to compare return # Handle the case when the recipient didn't receive the message yet. if balanceproof1 is not None: nonce1 = balanceproof1.nonce else: nonce1 = 0 if balanceproof0.nonce < nonce1: msg = ( "This is a bug, it should never happen. The nonce updates **always** " "start with the owner of the channel's end. This means for a channel " "A-B, only A can increase its nonce, same thing with B. At this " "point, the assertion is failling because this rule was broken, and " "the partner node has a larger nonce than the sending partner.") raise AssertionError(msg) if balanceproof0.nonce > nonce1: # TODO: Only consider the records up to saved state's state_change_id. # ATM this has a race condition where this utility could be called # before the alarm task fetches the corresponding event but while it # runs it does fetch it. sent_balance_proof = get_event_with_balance_proof_by_balance_hash( storage=app0.raiden.wal.storage, canonical_identifier=balanceproof0.canonical_identifier, balance_hash=balanceproof0.balance_hash, recipient=app1.raiden.address, ) received_balance_proof = get_state_change_with_balance_proof_by_locksroot( storage=app1.raiden.wal.storage, canonical_identifier=balanceproof0.canonical_identifier, locksroot=balanceproof0.locksroot, sender=app0.raiden.address, ) if received_balance_proof is not None: if type(received_balance_proof) == ReceiveTransferRefund: msg = ( f"Node1 received a refund from node0 and rejected it. This " f"is likely a Raiden bug. state_change={received_balance_proof}" ) elif type(received_balance_proof) in ( ActionInitMediator, ActionInitTarget, ReceiveUnlock, ReceiveLockExpired, ): if type(received_balance_proof) == ReceiveUnlock: assert isinstance(received_balance_proof, ReceiveUnlock), MYPY_ANNOTATION is_valid, _, innermsg = channel.handle_unlock( channel_state=channel1, unlock=received_balance_proof) elif type(received_balance_proof) == ReceiveLockExpired: assert isinstance(received_balance_proof, ReceiveLockExpired), MYPY_ANNOTATION is_valid, innermsg, _ = channel.is_valid_lock_expired( state_change=received_balance_proof, channel_state=channel1, sender_state=channel1.partner_state, receiver_state=channel1.our_state, block_number=saved_state1.state.block_number, ) else: assert isinstance(received_balance_proof, (ActionInitMediator, ActionInitTarget)), MYPY_ANNOTATION is_valid, _, innermsg = channel.handle_receive_lockedtransfer( channel_state=channel1, mediated_transfer=received_balance_proof.from_transfer, ) if not is_valid: msg = ( f"Node1 received the node0's message but rejected it. This " f"is likely a Raiden bug. reason={innermsg} " f"state_change={received_balance_proof}") else: msg = ( f"Node1 received the node0's message at that time it " f"was rejected, this is likely a race condition, node1 " f"has to process the message again. reason={innermsg} " f"state_change={received_balance_proof}") else: msg = ( f"Node1 received the node0's message but rejected it. This " f"is likely a Raiden bug. state_change={received_balance_proof}" ) elif sent_balance_proof is None: msg = ( "Node0 did not send a message with the latest balanceproof, " "this is likely a Raiden bug.") else: msg = ( "Node0 sent the latest balanceproof but Node1 didn't receive, " "likely the test is missing proper synchronization amongst the " "nodes.") msg = (f"{msg}. " f"node1={to_checksum_address(app1.raiden.address)} " f"node0={to_checksum_address(app0.raiden.address)} " f"state_change_id0={saved_state0.state_change_id} " f"state_change_id1={saved_state1.state_change_id}.") raise AssertionError(msg) is_equal = (balanceproof0.nonce == balanceproof1.nonce and balanceproof0.transferred_amount == balanceproof1.transferred_amount and balanceproof0.locked_amount == balanceproof1.locked_amount and balanceproof0.locksroot == balanceproof1.locksroot and balanceproof0.canonical_identifier == balanceproof1.canonical_identifier and balanceproof0.balance_hash == balanceproof1.balance_hash) if not is_equal: msg = ( f"The balance proof seems corrupted, the recipient has different " f"values than the sender. " f"node1={to_checksum_address(app1.raiden.address)} " f"node0={to_checksum_address(app0.raiden.address)} " f"state_change_id0={saved_state0.state_change_id} " f"state_change_id1={saved_state1.state_change_id}.") raise AssertionError(msg)
def make_transfers_pair( number_of_channels: int, amount: int = UNIT_TRANSFER_AMOUNT, block_number: int = 5, ) -> MediatorTransfersPair: deposit = 5 * amount defaults = create_properties(NettingChannelStateProperties( our_state=NettingChannelEndStateProperties(balance=deposit), partner_state=NettingChannelEndStateProperties(balance=deposit), open_transaction=TransactionExecutionStatusProperties(finished_block_number=10), )) properties_list = [ NettingChannelStateProperties( identifier=i, our_state=NettingChannelEndStateProperties( address=ChannelSet.ADDRESSES[0], privatekey=ChannelSet.PKEYS[0], ), partner_state=NettingChannelEndStateProperties( address=ChannelSet.ADDRESSES[i + 1], privatekey=ChannelSet.PKEYS[i + 1], ), ) for i in range(number_of_channels) ] channels = make_channel_set(properties_list, defaults) lock_expiration = block_number + UNIT_REVEAL_TIMEOUT * 2 pseudo_random_generator = random.Random() transfers_pairs = list() for payer_index in range(number_of_channels - 1): payee_index = payer_index + 1 receiver_channel = channels[payer_index] received_transfer = make_signed_transfer( amount=amount, initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, expiration=lock_expiration, secret=UNIT_SECRET, payment_identifier=UNIT_TRANSFER_IDENTIFIER, channel_identifier=receiver_channel.identifier, pkey=channels.partner_privatekeys[payer_index], sender=channels.partner_address(payer_index), ) is_valid, _, msg = channel.handle_receive_lockedtransfer( receiver_channel, received_transfer, ) assert is_valid, msg message_identifier = message_identifier_from_prng(pseudo_random_generator) lockedtransfer_event = channel.send_lockedtransfer( channel_state=channels[payee_index], initiator=UNIT_TRANSFER_INITIATOR, target=UNIT_TRANSFER_TARGET, amount=amount, message_identifier=message_identifier, payment_identifier=UNIT_TRANSFER_IDENTIFIER, expiration=lock_expiration, secrethash=UNIT_SECRETHASH, ) assert lockedtransfer_event lock_timeout = lock_expiration - block_number assert mediator.is_channel_usable( candidate_channel_state=channels[payee_index], transfer_amount=amount, lock_timeout=lock_timeout, ) sent_transfer = lockedtransfer_event.transfer pair = MediationPairState( received_transfer, lockedtransfer_event.recipient, sent_transfer, ) transfers_pairs.append(pair) return MediatorTransfersPair(channels, transfers_pairs, amount, block_number)