def get_contractreceivechannelbatchunlock_data_from_event( chain_state: ChainState, storage: SerializedSQLiteStorage, event: DecodedEvent) -> Optional[CanonicalIdentifier]: token_network_address = TokenNetworkAddress(event.originating_contract) data = event.event_data args = data["args"] participant1 = args["receiver"] participant2 = args["sender"] locksroot = args["locksroot"] token_network_state = views.get_token_network_by_address( chain_state, token_network_address) msg = f"Could not find token network for address {to_checksum_address(token_network_address)}" assert token_network_state is not None, msg if participant1 == chain_state.our_address: partner = participant2 elif participant2 == chain_state.our_address: partner = participant1 else: return None channel_identifiers = token_network_state.partneraddresses_to_channelidentifiers[ partner] canonical_identifier = None for channel_identifier in channel_identifiers: if partner == args["sender"]: state_change_record = get_state_change_with_balance_proof_by_locksroot( storage=storage, canonical_identifier=CanonicalIdentifier( chain_identifier=chain_state.chain_id, token_network_address=token_network_address, channel_identifier=channel_identifier, ), locksroot=locksroot, sender=partner, ) if state_change_record is not None: canonical_identifier = ( state_change_record.data.balance_proof. canonical_identifier # type: ignore ) break elif partner == args["receiver"]: event_record = get_event_with_balance_proof_by_locksroot( storage=storage, canonical_identifier=CanonicalIdentifier( chain_identifier=chain_state.chain_id, token_network_address=token_network_address, channel_identifier=channel_identifier, ), locksroot=locksroot, recipient=partner, ) if event_record is not None: canonical_identifier = ( event_record.data.balance_proof. canonical_identifier # type: ignore ) break msg = ( f"Can not resolve channel_id for unlock with locksroot {to_hex(locksroot)} and " f"partner {to_checksum_address(partner)}.") if canonical_identifier is None: raise RaidenUnrecoverableError(msg) return canonical_identifier
def handle_contract_send_channelunlock( raiden: "RaidenService", chain_state: ChainState, channel_unlock_event: ContractSendChannelBatchUnlock, ) -> None: assert raiden.wal, "The Raiden Service must be initialize to handle events" canonical_identifier = channel_unlock_event.canonical_identifier token_network_address = canonical_identifier.token_network_address channel_identifier = canonical_identifier.channel_identifier participant = channel_unlock_event.sender payment_channel: PaymentChannel = raiden.proxy_manager.payment_channel( canonical_identifier=canonical_identifier ) channel_state = get_channelstate_by_token_network_and_partner( chain_state=chain_state, token_network_address=token_network_address, partner_address=participant, ) if not channel_state: # channel was cleaned up already due to an unlock raise RaidenUnrecoverableError( f"Failed to find channel state with partner:" f"{to_checksum_address(participant)}, " f"token_network:{to_checksum_address(token_network_address)}" ) our_address = channel_state.our_state.address our_locksroot = channel_state.our_state.onchain_locksroot partner_address = channel_state.partner_state.address partner_locksroot = channel_state.partner_state.onchain_locksroot # we want to unlock because there are on-chain unlocked locks search_events = our_locksroot != LOCKSROOT_OF_NO_LOCKS # we want to unlock, because there are unlocked/unclaimed locks search_state_changes = partner_locksroot != LOCKSROOT_OF_NO_LOCKS if not search_events and not search_state_changes: # In the case that someone else sent the unlock we do nothing # Check https://github.com/raiden-network/raiden/issues/3152 # for more details log.warning( "Onchain unlock already mined", canonical_identifier=canonical_identifier, channel_identifier=canonical_identifier.channel_identifier, participant=to_checksum_address(participant), ) return if search_state_changes: state_change_record = get_state_change_with_balance_proof_by_locksroot( storage=raiden.wal.storage, canonical_identifier=canonical_identifier, locksroot=partner_locksroot, sender=partner_address, ) if state_change_record is None: raise RaidenUnrecoverableError( f"Failed to find state that matches the current channel locksroots. " f"chain_id:{raiden.rpc_client.chain_id} " f"token_network:{to_checksum_address(token_network_address)} " f"channel:{channel_identifier} " f"participant:{to_checksum_address(participant)} " f"our_locksroot:{to_hex(our_locksroot)} " f"partner_locksroot:{to_hex(partner_locksroot)} " ) state_change_identifier = state_change_record.state_change_identifier restored_channel_state = channel_state_until_state_change( raiden=raiden, canonical_identifier=canonical_identifier, state_change_identifier=state_change_identifier, ) assert restored_channel_state is not None gain = get_batch_unlock_gain(restored_channel_state) skip_unlock = ( restored_channel_state.partner_state.address == participant and gain.from_partner_locks == 0 ) if not skip_unlock: unlock( payment_channel=payment_channel, end_state=restored_channel_state.partner_state, sender=partner_address, receiver=our_address, given_block_identifier=channel_unlock_event.triggered_by_block_hash, ) if search_events: event_record = get_event_with_balance_proof_by_locksroot( storage=raiden.wal.storage, canonical_identifier=canonical_identifier, locksroot=our_locksroot, recipient=partner_address, ) if event_record is None: raise RaidenUnrecoverableError( f"Failed to find event that match current channel locksroots. " f"chain_id:{raiden.rpc_client.chain_id} " f"token_network:{to_checksum_address(token_network_address)} " f"channel:{channel_identifier} " f"participant:{to_checksum_address(participant)} " f"our_locksroot:{to_hex(our_locksroot)} " f"partner_locksroot:{to_hex(partner_locksroot)} " ) state_change_identifier = event_record.state_change_identifier restored_channel_state = channel_state_until_state_change( raiden=raiden, canonical_identifier=canonical_identifier, state_change_identifier=state_change_identifier, ) assert restored_channel_state is not None gain = get_batch_unlock_gain(restored_channel_state) skip_unlock = ( restored_channel_state.our_state.address == participant and gain.from_our_locks == 0 ) if not skip_unlock: try: unlock( payment_channel=payment_channel, end_state=restored_channel_state.our_state, sender=our_address, receiver=partner_address, given_block_identifier=channel_unlock_event.triggered_by_block_hash, ) except InsufficientEth as e: raise RaidenUnrecoverableError(str(e)) from e
def test_get_state_change_with_balance_proof(): """ All state changes which contain a balance proof must be found when querying the database. """ serializer = JSONSerializer() storage = SerializedSQLiteStorage(":memory:", serializer) counter = itertools.count() balance_proof = make_signed_balance_proof_from_counter(counter) lock_expired = ReceiveLockExpired( sender=balance_proof.sender, balance_proof=balance_proof, secrethash=factories.make_secret_hash(next(counter)), message_identifier=MessageID(next(counter)), ) received_balance_proof = make_signed_balance_proof_from_counter(counter) unlock = ReceiveUnlock( sender=received_balance_proof.sender, message_identifier=MessageID(next(counter)), secret=factories.make_secret(next(counter)), balance_proof=received_balance_proof, ) transfer = make_signed_transfer_from_counter(counter) transfer_refund = ReceiveTransferRefund( transfer=transfer, balance_proof=transfer.balance_proof, sender=transfer.balance_proof.sender, # pylint: disable=no-member ) transfer = make_signed_transfer_from_counter(counter) transfer_reroute = ActionTransferReroute( transfer=transfer, balance_proof=transfer.balance_proof, sender=transfer.balance_proof.sender, # pylint: disable=no-member secret=sha3(factories.make_secret(next(counter))), ) mediator_from_route, mediator_signed_transfer = make_from_route_from_counter( counter) action_init_mediator = ActionInitMediator( route_states=[ RouteState( route=[factories.make_address(), factories.make_address()], forward_channel_id=factories.make_channel_identifier(), ) ], from_hop=mediator_from_route, from_transfer=mediator_signed_transfer, balance_proof=mediator_signed_transfer.balance_proof, sender=mediator_signed_transfer.balance_proof.sender, # pylint: disable=no-member ) target_from_route, target_signed_transfer = make_from_route_from_counter( counter) action_init_target = ActionInitTarget( from_hop=target_from_route, transfer=target_signed_transfer, balance_proof=target_signed_transfer.balance_proof, sender=target_signed_transfer.balance_proof.sender, # pylint: disable=no-member ) statechanges_balanceproofs = [ (lock_expired, lock_expired.balance_proof), (unlock, unlock.balance_proof), (transfer_refund, transfer_refund.transfer.balance_proof), (transfer_reroute, transfer_reroute.transfer.balance_proof), (action_init_mediator, action_init_mediator.from_transfer.balance_proof), (action_init_target, action_init_target.transfer.balance_proof), ] assert storage.count_state_changes() == 0 state_change_ids = storage.write_state_changes( [state_change for state_change, _ in statechanges_balanceproofs]) assert storage.count_state_changes() == len(statechanges_balanceproofs) msg_in_order = "Querying must return state changes in order" stored_statechanges_records = storage.get_statechanges_records_by_range( RANGE_ALL_STATE_CHANGES) assert len(stored_statechanges_records) == 6, msg_in_order pair_elements = zip(statechanges_balanceproofs, state_change_ids, stored_statechanges_records) for statechange_balanceproof, statechange_id, record in pair_elements: assert record.data == statechange_balanceproof[0], msg_in_order assert record.state_change_identifier == statechange_id, msg_in_order # Make sure state changes are returned in the correct order in which they were stored stored_statechanges = storage.get_statechanges_by_range( Range( stored_statechanges_records[1].state_change_identifier, stored_statechanges_records[2].state_change_identifier, )) assert len(stored_statechanges) == 2 assert isinstance(stored_statechanges[0], ReceiveUnlock) assert isinstance(stored_statechanges[1], ReceiveTransferRefund) for state_change, balance_proof in statechanges_balanceproofs: state_change_record = get_state_change_with_balance_proof_by_balance_hash( storage=storage, canonical_identifier=balance_proof.canonical_identifier, sender=balance_proof.sender, balance_hash=balance_proof.balance_hash, ) assert state_change_record assert state_change_record.data == state_change state_change_record = get_state_change_with_balance_proof_by_locksroot( storage=storage, canonical_identifier=balance_proof.canonical_identifier, sender=balance_proof.sender, locksroot=balance_proof.locksroot, ) assert state_change_record assert state_change_record.data == state_change storage.close()
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)