def handle_unlock(mediator_state, state_change: ReceiveUnlock, channelidentifiers_to_channels): """ Handle a ReceiveUnlock state change. """ events = list() balance_proof_sender = state_change.balance_proof.sender channel_identifier = state_change.balance_proof.channel_address for pair in mediator_state.transfers_pair: if pair.payer_transfer.balance_proof.sender == balance_proof_sender: channel_state = channelidentifiers_to_channels.get(channel_identifier) if channel_state: is_valid, channel_events, _ = channel.handle_unlock( channel_state, state_change, ) events.extend(channel_events) if is_valid: unlock = EventUnlockClaimSuccess( pair.payee_transfer.payment_identifier, pair.payee_transfer.lock.secrethash, ) events.append(unlock) send_processed = SendProcessed( balance_proof_sender, b'global', state_change.message_identifier, ) events.append(send_processed) pair.payer_state = 'payer_balance_proof' iteration = TransitionResult(mediator_state, events) return iteration
def handle_unlock(target_state, state_change: ReceiveUnlock, channel_state): """ Handles a ReceiveUnlock state change. """ iteration = TransitionResult(target_state, list()) balance_proof_sender = state_change.balance_proof.sender if balance_proof_sender == target_state.route.node_address: is_valid, events, _ = channel.handle_unlock( channel_state, state_change, ) if is_valid: transfer = target_state.transfer transfer_success = EventTransferReceivedSuccess( transfer.payment_identifier, transfer.lock.amount, transfer.initiator, ) unlock_success = EventUnlockClaimSuccess( transfer.payment_identifier, transfer.lock.secrethash, ) send_processed = SendProcessed( balance_proof_sender, b'global', state_change.message_identifier, ) events.extend([transfer_success, unlock_success, send_processed]) iteration = TransitionResult(None, events) return iteration
def claim_lock(app_chain, identifier, token, secret): """ Unlock a pending transfer. """ secrethash = sha3(secret) for from_, to_ in zip(app_chain[:-1], app_chain[1:]): from_channel = get_channelstate(from_, to_, token) partner_channel = get_channelstate(to_, from_, token) unlock_lock = channel.send_unlock( from_channel, identifier, secret, secrethash, ) secret_message = Secret( unlock_lock.identifier, unlock_lock.balance_proof.nonce, unlock_lock.balance_proof.channel_address, unlock_lock.balance_proof.transferred_amount, unlock_lock.balance_proof.locksroot, unlock_lock.secret, ) from_.raiden.sign(secret_message) balance_proof = balanceproof_from_envelope(secret_message) receive_unlock = ReceiveUnlock( unlock_lock.secret, balance_proof, ) is_valid, msg = channel.handle_unlock( partner_channel, receive_unlock, ) assert is_valid, msg
def handle_unlock(target_state, state_change, channel_state): """ Handles a ReceiveUnlock state change. """ iteration = TransitionResult(target_state, list()) balance_proof_sender = state_change.balance_proof.sender if balance_proof_sender == target_state.route.node_address: is_valid, events, _ = channel.handle_unlock( channel_state, state_change, ) if is_valid: transfer = target_state.transfer transfer_success = EventTransferReceivedSuccess( transfer.payment_identifier, transfer.lock.amount, transfer.initiator, ) unlock_success = EventUnlockClaimSuccess( transfer.payment_identifier, transfer.lock.secrethash, ) send_processed = SendProcessed( balance_proof_sender, b'global', state_change.message_identifier, ) events.extend([transfer_success, unlock_success, send_processed]) iteration = TransitionResult(None, events) return iteration
def handle_unlock(mediator_state, state_change, channelidentifiers_to_channels): """ Handle a ReceiveUnlock state change. """ events = list() balance_proof_sender = state_change.balance_proof.sender channel_identifier = state_change.balance_proof.channel_address for pair in mediator_state.transfers_pair: if pair.payer_transfer.balance_proof.sender == balance_proof_sender: channel_state = channelidentifiers_to_channels.get(channel_identifier) if channel_state: is_valid, channel_events, _ = channel.handle_unlock( channel_state, state_change, ) events.extend(channel_events) if is_valid: unlock = EventUnlockClaimSuccess( pair.payee_transfer.payment_identifier, pair.payee_transfer.lock.secrethash, ) events.append(unlock) send_processed = SendProcessed( balance_proof_sender, b'global', state_change.message_identifier, ) events.append(send_processed) pair.payer_state = 'payer_balance_proof' iteration = TransitionResult(mediator_state, events) return iteration
def handle_unlock(mediator_state, state_change, channelidentifiers_to_channels): """ Handle a ReceiveUnlock state change. """ events = list() balance_proof_sender = state_change.balance_proof.sender channel_identifier = state_change.balance_proof.channel_address for pair in mediator_state.transfers_pair: if pair.payer_transfer.balance_proof.sender == balance_proof_sender: channel_state = channelidentifiers_to_channels.get(channel_identifier) if channel_state: is_valid, _ = channel.handle_unlock( channel_state, state_change, ) if is_valid: withdraw = EventWithdrawSuccess( pair.payee_transfer.payment_identifier, pair.payee_transfer.lock.secrethash, ) events.append(withdraw) pair.payer_state = 'payer_balance_proof' iteration = TransitionResult(mediator_state, events) return iteration
def handle_unlock( target_state: TargetTransferState, state_change: ReceiveUnlock, channel_state: NettingChannelState, ) -> TransitionResult[TargetTransferState]: """ Handles a ReceiveUnlock state change. """ balance_proof_sender = state_change.balance_proof.sender is_valid, events, _ = channel.handle_unlock(channel_state, state_change) next_target_state: Optional[TargetTransferState] = target_state if is_valid: transfer = target_state.transfer payment_received_success = EventPaymentReceivedSuccess( payment_network_identifier=channel_state.payment_network_identifier, token_network_identifier=TokenNetworkID(channel_state.token_network_identifier), identifier=transfer.payment_identifier, amount=TokenAmount(transfer.lock.amount), initiator=transfer.initiator, ) unlock_success = EventUnlockClaimSuccess( transfer.payment_identifier, transfer.lock.secrethash ) send_processed = SendProcessed( recipient=balance_proof_sender, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=state_change.message_identifier, ) events.extend([payment_received_success, unlock_success, send_processed]) next_target_state = None return TransitionResult(next_target_state, events)
def handle_unlock( target_state: TargetTransferState, state_change: ReceiveUnlock, channel_state: NettingChannelState, ): """ Handles a ReceiveUnlock state change. """ iteration = TransitionResult(target_state, list()) balance_proof_sender = state_change.balance_proof.sender if balance_proof_sender == target_state.route.node_address: is_valid, events, _ = channel.handle_unlock( channel_state, state_change, ) if is_valid: transfer = target_state.transfer payment_received_success = EventPaymentReceivedSuccess( payment_network_identifier=channel_state. payment_network_identifier, token_network_identifier=channel_state. token_network_identifier, identifier=transfer.payment_identifier, amount=transfer.lock.amount, initiator=transfer.initiator, ) unlock_success = EventUnlockClaimSuccess( transfer.payment_identifier, transfer.lock.secrethash, ) send_processed = SendProcessed( recipient=balance_proof_sender, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=state_change.message_identifier, ) events.extend( [payment_received_success, unlock_success, send_processed]) iteration = TransitionResult(None, events) return iteration
def claim_lock(app_chain, payment_identifier, token_network_identifier, secret): """ Unlock a pending transfer. """ secrethash = sha3(secret) for from_, to_ in zip(app_chain[:-1], app_chain[1:]): from_channel = get_channelstate(from_, to_, token_network_identifier) partner_channel = get_channelstate(to_, from_, token_network_identifier) unlock_lock = channel.send_unlock( from_channel, random.randint(0, UINT64_MAX), payment_identifier, secret, secrethash, ) secret_message = Secret( chain_id=unlock_lock.balance_proof.chain_id, message_identifier=unlock_lock.message_identifier, payment_identifier=unlock_lock.payment_identifier, nonce=unlock_lock.balance_proof.nonce, token_network_address=partner_channel.token_network_identifier, channel_identifier=unlock_lock.balance_proof.channel_identifier, transferred_amount=unlock_lock.balance_proof.transferred_amount, locked_amount=unlock_lock.balance_proof.locked_amount, locksroot=unlock_lock.balance_proof.locksroot, secret=unlock_lock.secret, ) from_.raiden.sign(secret_message) balance_proof = balanceproof_from_envelope(secret_message) receive_unlock = ReceiveUnlock( message_identifier=random.randint(0, UINT64_MAX), secret=unlock_lock.secret, balance_proof=balance_proof, ) is_valid, _, msg = channel.handle_unlock( partner_channel, receive_unlock, ) assert is_valid, msg
def claim_lock(app_chain, payment_identifier, token_network_identifier, secret): """ Unlock a pending transfer. """ secrethash = sha3(secret) for from_, to_ in zip(app_chain[:-1], app_chain[1:]): from_channel = get_channelstate(from_, to_, token_network_identifier) partner_channel = get_channelstate(to_, from_, token_network_identifier) unlock_lock = channel.send_unlock( from_channel, random.randint(0, UINT64_MAX), payment_identifier, secret, secrethash, ) secret_message = Secret( unlock_lock.balance_proof.chain_id, unlock_lock.message_identifier, unlock_lock.payment_identifier, unlock_lock.balance_proof.nonce, partner_channel.token_network_identifier, unlock_lock.balance_proof.channel_address, unlock_lock.balance_proof.transferred_amount, unlock_lock.balance_proof.locked_amount, unlock_lock.balance_proof.locksroot, unlock_lock.secret, ) from_.raiden.sign(secret_message) balance_proof = balanceproof_from_envelope(secret_message) receive_unlock = ReceiveUnlock( message_identifier=random.randint(0, UINT64_MAX), secret=unlock_lock.secret, balance_proof=balance_proof, ) is_valid, _, msg = channel.handle_unlock( partner_channel, receive_unlock, ) assert is_valid, msg
def handle_unlock( target_state: TargetTransferState, state_change: ReceiveUnlock, channel_state: NettingChannelState, ): """ Handles a ReceiveUnlock state change. """ balance_proof_sender = state_change.balance_proof.sender is_valid, events, _ = channel.handle_unlock( channel_state, state_change, ) if is_valid: transfer = target_state.transfer payment_received_success = EventPaymentReceivedSuccess( payment_network_identifier=channel_state.payment_network_identifier, token_network_identifier=channel_state.token_network_identifier, identifier=transfer.payment_identifier, amount=transfer.lock.amount, initiator=transfer.initiator, ) unlock_success = EventUnlockClaimSuccess( transfer.payment_identifier, transfer.lock.secrethash, ) send_processed = SendProcessed( recipient=balance_proof_sender, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=state_change.message_identifier, ) events.extend([payment_received_success, unlock_success, send_processed]) target_state = None return TransitionResult(target_state, events)
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_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 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)