def handle_contract_send_channelsettle( raiden: "RaidenService", channel_settle_event: ContractSendChannelSettle ) -> None: assert raiden.wal, "The Raiden Service must be initialize to handle events" canonical_identifier = CanonicalIdentifier( chain_identifier=raiden.rpc_client.chain_id, token_network_address=channel_settle_event.token_network_address, channel_identifier=channel_settle_event.channel_identifier, ) triggered_by_block_hash = channel_settle_event.triggered_by_block_hash payment_channel: PaymentChannel = raiden.proxy_manager.payment_channel( canonical_identifier=canonical_identifier ) token_network_proxy: TokenNetwork = payment_channel.token_network try: participants_details = token_network_proxy.detail_participants( participant1=payment_channel.participant1, participant2=payment_channel.participant2, block_identifier=triggered_by_block_hash, channel_identifier=channel_settle_event.channel_identifier, ) except ValueError: # The triggered_by_block_hash block was pruned. participants_details = token_network_proxy.detail_participants( participant1=payment_channel.participant1, participant2=payment_channel.participant2, block_identifier="latest", channel_identifier=channel_settle_event.channel_identifier, ) our_details = participants_details.our_details partner_details = participants_details.partner_details log_details = { "chain_id": canonical_identifier.chain_identifier, "token_network_address": canonical_identifier.token_network_address, "channel_identifier": canonical_identifier.channel_identifier, "node": to_checksum_address(raiden.address), "partner": to_checksum_address(partner_details.address), "our_deposit": our_details.deposit, "our_withdrawn": our_details.withdrawn, "our_is_closer": our_details.is_closer, "our_balance_hash": to_hex(our_details.balance_hash), "our_nonce": our_details.nonce, "our_locksroot": to_hex(our_details.locksroot), "our_locked_amount": our_details.locked_amount, "partner_deposit": partner_details.deposit, "partner_withdrawn": partner_details.withdrawn, "partner_is_closer": partner_details.is_closer, "partner_balance_hash": to_hex(partner_details.balance_hash), "partner_nonce": partner_details.nonce, "partner_locksroot": to_hex(partner_details.locksroot), "partner_locked_amount": partner_details.locked_amount, } if our_details.balance_hash != EMPTY_HASH: event_record = get_event_with_balance_proof_by_balance_hash( storage=raiden.wal.storage, canonical_identifier=canonical_identifier, balance_hash=our_details.balance_hash, recipient=participants_details.partner_details.address, ) if event_record is None: log.critical("our balance proof not found", **log_details) raise RaidenUnrecoverableError( "Our balance proof could not be found in the database" ) our_balance_proof = event_record.data.balance_proof # type: ignore our_transferred_amount = our_balance_proof.transferred_amount our_locked_amount = our_balance_proof.locked_amount our_locksroot = our_balance_proof.locksroot else: our_transferred_amount = 0 our_locked_amount = 0 our_locksroot = LOCKSROOT_OF_NO_LOCKS if partner_details.balance_hash != EMPTY_HASH: state_change_record = get_state_change_with_balance_proof_by_balance_hash( storage=raiden.wal.storage, canonical_identifier=canonical_identifier, balance_hash=partner_details.balance_hash, sender=participants_details.partner_details.address, ) if state_change_record is None: log.critical("partner balance proof not found", **log_details) raise RaidenUnrecoverableError( "Partner balance proof could not be found in the database" ) partner_balance_proof = state_change_record.data.balance_proof # type: ignore partner_transferred_amount = partner_balance_proof.transferred_amount partner_locked_amount = partner_balance_proof.locked_amount partner_locksroot = partner_balance_proof.locksroot else: partner_transferred_amount = 0 partner_locked_amount = 0 partner_locksroot = LOCKSROOT_OF_NO_LOCKS try: payment_channel.settle( transferred_amount=our_transferred_amount, locked_amount=our_locked_amount, locksroot=our_locksroot, partner_transferred_amount=partner_transferred_amount, partner_locked_amount=partner_locked_amount, partner_locksroot=partner_locksroot, block_identifier=triggered_by_block_hash, ) except InsufficientEth as e: raise RaidenUnrecoverableError(str(e)) from e
def test_get_event_with_balance_proof(): """ All events which contain a balance proof must be found by when querying the database. """ serializer = JSONSerializer() storage = SerializedSQLiteStorage(":memory:", serializer) counter = itertools.count(1) partner_address = factories.make_address() balance_proof = make_balance_proof_from_counter(counter) lock_expired = SendLockExpired( recipient=partner_address, message_identifier=MessageID(next(counter)), balance_proof=balance_proof, secrethash=factories.make_secret_hash(next(counter)), canonical_identifier=balance_proof.canonical_identifier, ) locked_transfer = SendLockedTransfer( recipient=partner_address, message_identifier=MessageID(next(counter)), transfer=make_transfer_from_counter(counter), canonical_identifier=factories.make_canonical_identifier(), ) send_balance_proof = SendBalanceProof( recipient=partner_address, message_identifier=MessageID(next(counter)), payment_identifier=factories.make_payment_id(), token_address=factories.make_token_address(), secret=factories.make_secret(next(counter)), balance_proof=make_balance_proof_from_counter(counter), canonical_identifier=factories.make_canonical_identifier(), ) refund_transfer = SendRefundTransfer( recipient=partner_address, message_identifier=MessageID(next(counter)), transfer=make_transfer_from_counter(counter), canonical_identifier=factories.make_canonical_identifier(), ) events_balanceproofs = [ (lock_expired, lock_expired.balance_proof), (locked_transfer, locked_transfer.balance_proof), (send_balance_proof, send_balance_proof.balance_proof), (refund_transfer, refund_transfer.transfer.balance_proof), ] state_change = Block(BlockNumber(1), BlockGasLimit(1), factories.make_block_hash()) for event, _ in events_balanceproofs: state_change_identifiers = storage.write_state_changes([state_change]) storage.write_events(events=[(state_change_identifiers[0], event)]) for event, balance_proof in events_balanceproofs: event_record = get_event_with_balance_proof_by_balance_hash( storage=storage, canonical_identifier=balance_proof.canonical_identifier, balance_hash=balance_proof.balance_hash, recipient=partner_address, ) assert event_record assert event_record.data == event event_record = get_event_with_balance_proof_by_locksroot( storage=storage, canonical_identifier=balance_proof.canonical_identifier, recipient=event.recipient, locksroot=balance_proof.locksroot, ) assert event_record assert event_record.data == event # Checking that balance proof attribute can be accessed for all events. # Issue https://github.com/raiden-network/raiden/issues/3179 assert event_record.data.balance_proof == event.balance_proof 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)