def _get_onchain_locksroots( raiden: "RaidenService", storage: SQLiteStorage, token_network: Dict[str, Any], channel: Dict[str, Any], ) -> Tuple[Locksroot, Locksroot]: channel_new_state_change = _find_channel_new_state_change( storage=storage, token_network_address=token_network["address"], channel_identifier=channel["identifier"], ) if not channel_new_state_change: raise RaidenUnrecoverableError( f'Could not find the state change for channel {channel["identifier"]}, ' f'token network address: {token_network["address"]} being created. ' ) canonical_identifier = CanonicalIdentifier( chain_identifier=ChainID(-1), token_network_address=TokenNetworkAddress( to_canonical_address(token_network["address"])), channel_identifier=ChannelID(int(channel["identifier"])), ) our_locksroot, partner_locksroot = get_onchain_locksroots( chain=raiden.chain, canonical_identifier=canonical_identifier, participant1=to_canonical_address(channel["our_state"]["address"]), participant2=to_canonical_address(channel["partner_state"]["address"]), block_identifier="latest", ) return our_locksroot, partner_locksroot
def _get_onchain_locksroots( raiden: RaidenService, storage: SQLiteStorage, token_network: Dict[str, Any], channel: Dict[str, Any], ) -> Tuple[Locksroot, Locksroot]: channel_new_state_change = _find_channel_new_state_change( storage=storage, token_network_address=token_network['address'], channel_identifier=channel['identifier'], ) if not channel_new_state_change: raise RaidenUnrecoverableError( f'Could not find the state change for channel {channel["identifier"]}, ' f'token network address: {token_network["address"]} being created. ', ) canonical_identifier = CanonicalIdentifier( chain_identifier=CHAIN_ID_UNSPECIFIED, token_network_address=to_canonical_address(token_network['address']), channel_identifier=int(channel['identifier']), ) our_locksroot, partner_locksroot = get_onchain_locksroots( chain=raiden.chain, canonical_identifier=canonical_identifier, participant1=to_canonical_address(channel['our_state']['address']), participant2=to_canonical_address(channel['partner_state']['address']), block_identifier='latest', ) return our_locksroot, partner_locksroot
def get_contractreceivechannelsettled_data_from_event( proxy_manager: ProxyManager, chain_state: ChainState, event: DecodedEvent, current_confirmed_head: BlockNumber, ) -> Optional[ChannelSettleState]: data = event.event_data token_network_address = TokenNetworkAddress(event.originating_contract) channel_identifier = data["args"]["channel_identifier"] block_hash = data["block_hash"] canonical_identifier = CanonicalIdentifier( chain_identifier=chain_state.chain_id, token_network_address=token_network_address, channel_identifier=channel_identifier, ) channel_state = views.get_channelstate_by_canonical_identifier( chain_state=chain_state, canonical_identifier=canonical_identifier) # This may happen for two reasons: # - This node is not a participant for the given channel (normal operation, # the event should be ignored). # - Something went wrong in our code and the channel state was cleared # before settle (a bug, this should raise an exception on development # mode). # Because we cannot distinguish the two cases, assume the channel is not of # interest and ignore the event. if not channel_state: return None # Recover the locksroot from the blockchain to fix data races. Check # get_onchain_locksroots for details. try: # First try to query the unblinded state. This way the # ContractReceiveChannelSettled's locksroots will match the values # provided during settle. our_locksroot, partner_locksroot = get_onchain_locksroots( proxy_manager=proxy_manager, channel_state=channel_state, participant1=channel_state.our_state.address, participant2=channel_state.partner_state.address, block_identifier=block_hash, ) except ValueError: # State pruning handling. The block which generate the # ChannelSettled event may have been pruned, because of this the # RPC call raised ValueError. # # The solution is to query the channel's state from the latest # *confirmed* block, this /may/ create a ContractReceiveChannelSettled # with the wrong locksroot (i.e. not the locksroot used during the call # to settle). However this is fine, because at this point the channel # is settled, it is known that the locksroot can not be reverted # without an unlock, and because the unlocks are fair it doesn't matter # who called it, only if there are tokens locked in the settled # channel. our_locksroot, partner_locksroot = get_onchain_locksroots( proxy_manager=proxy_manager, channel_state=channel_state, participant1=channel_state.our_state.address, participant2=channel_state.partner_state.address, block_identifier=current_confirmed_head, ) return ChannelSettleState(canonical_identifier, our_locksroot, partner_locksroot)
def handle_channel_settled(raiden: "RaidenService", event: Event): data = event.event_data token_network_identifier = event.originating_contract channel_identifier = data["args"]["channel_identifier"] block_number = data["block_number"] block_hash = data["block_hash"] transaction_hash = data["transaction_hash"] chain_state = views.state_from_raiden(raiden) # TODO fixme mmartinez7 what about when is a light client? channel_state = views.get_channelstate_by_canonical_identifier_and_address( chain_state=chain_state, canonical_identifier=CanonicalIdentifier( chain_identifier=chain_state.chain_id, token_network_address=token_network_identifier, channel_identifier=channel_identifier, ), address=raiden.address, ) # This may happen for two reasons: # - This node is not a participant for the given channel (normal operation, # the event should be ignored). # - Something went wrong in our code and the channel state was cleared # before settle (a bug, this should raise an exception on development # mode). # Because we cannot distinguish the two cases, assume the channel is not of # interest and ignore the event. if not channel_state: return # Recover the locksroot from the blockchain to fix data races. Check # get_onchain_locksroots for details. try: # First try to query the unblinded state. This way the # ContractReceiveChannelSettled's locksroots will match the values # provided during settle. our_locksroot, partner_locksroot = get_onchain_locksroots( chain=raiden.chain, canonical_identifier=channel_state.canonical_identifier, participant1=channel_state.our_state.address, participant2=channel_state.partner_state.address, block_identifier=block_hash, ) except ValueError: # State pruning handling. The block which generate the ChannelSettled # event may have been pruned, because of this the RPC call will raises # a ValueError. # # The solution is to query the channel's state from the latest block, # this /may/ create a ContractReceiveChannelSettled with the wrong # locksroot (i.e. not the locksroot used during the call to settle). # However this is fine, because at this point the channel is settled, # it is known that the locksroot can not be reverted without an unlock, # and because the unlocks are fare it doesn't matter who called it, # only if there are tokens locked in the settled channel. our_locksroot, partner_locksroot = get_onchain_locksroots( chain=raiden.chain, canonical_identifier=channel_state.canonical_identifier, participant1=channel_state.our_state.address, participant2=channel_state.partner_state.address, block_identifier="latest", ) if raiden.address == channel_state.our_state.address: channel_settled = ContractReceiveChannelSettled( transaction_hash=transaction_hash, canonical_identifier=channel_state.canonical_identifier, our_onchain_locksroot=our_locksroot, partner_onchain_locksroot=partner_locksroot, block_number=block_number, block_hash=block_hash, participant1=channel_state.our_state.address) raiden.handle_and_track_state_change(channel_settled) else: channel_settled = ContractReceiveChannelSettledLight( transaction_hash=transaction_hash, canonical_identifier=channel_state.canonical_identifier, our_onchain_locksroot=our_locksroot, partner_onchain_locksroot=partner_locksroot, block_number=block_number, block_hash=block_hash, participant1=channel_state.our_state.address, participant2=channel_state.partner_state.address) raiden.handle_and_track_state_change(channel_settled)
def _add_onchain_locksroot_to_channel_settled_state_changes( raiden: "RaidenService", storage: SQLiteStorage) -> None: """ Adds `our_onchain_locksroot` and `partner_onchain_locksroot` to ContractReceiveChannelSettled. """ batch_size = 50 batch_query = storage.batch_query_state_changes( batch_size=batch_size, filters=[("_type", "raiden.transfer.state_change.ContractReceiveChannelSettled") ], ) for state_changes_batch in batch_query: updated_state_changes = list() for state_change in state_changes_batch: state_change_data = json.loads(state_change.data) msg = "v18 state changes cant contain our_onchain_locksroot" assert "our_onchain_locksroot" not in state_change_data, msg msg = "v18 state changes cant contain partner_onchain_locksroot" assert "partner_onchain_locksroot" not in state_change_data, msg token_network_identifier = state_change_data[ "token_network_identifier"] channel_identifier = state_change_data["channel_identifier"] channel_new_state_change = _find_channel_new_state_change( storage=storage, token_network_address=token_network_identifier, channel_identifier=channel_identifier, ) if not channel_new_state_change.data: raise RaidenUnrecoverableError( f"Could not find the state change for channel {channel_identifier}, " f"token network address: {token_network_identifier} being created. " ) channel_state_data = json.loads(channel_new_state_change.data) new_channel_state = channel_state_data["channel_state"] canonical_identifier = CanonicalIdentifier( chain_identifier=ChainID(-1), token_network_address=TokenNetworkAddress( to_canonical_address(token_network_identifier)), channel_identifier=ChannelID(int(channel_identifier)), ) our_locksroot, partner_locksroot = get_onchain_locksroots( chain=raiden.chain, canonical_identifier=canonical_identifier, participant1=to_canonical_address( new_channel_state["our_state"]["address"]), participant2=to_canonical_address( new_channel_state["partner_state"]["address"]), block_identifier="latest", ) state_change_data["our_onchain_locksroot"] = serialize_bytes( our_locksroot) state_change_data["partner_onchain_locksroot"] = serialize_bytes( partner_locksroot) updated_state_changes.append( (json.dumps(state_change_data), state_change.state_change_identifier)) storage.update_state_changes(updated_state_changes)
def handle_channel_settled(raiden: 'RaidenService', event: Event): data = event.event_data token_network_identifier = event.originating_contract channel_identifier = data['args']['channel_identifier'] block_number = data['block_number'] block_hash = data['block_hash'] transaction_hash = data['transaction_hash'] chain_state = views.state_from_raiden(raiden) channel_state = views.get_channelstate_by_canonical_identifier( chain_state=chain_state, canonical_identifier=CanonicalIdentifier( chain_identifier=chain_state.chain_id, token_network_address=token_network_identifier, channel_identifier=channel_identifier, ), ) # This may happen for two reasons: # - This node is not a participant for the given channel (normal operation, # the event should be ignored). # - Something went wrong in our code and the channel state was cleared # before settle (a bug, this should raise an exception on development # mode). # Because we cannot distinguish the two cases, assume the channel is not of # interest and ignore the event. if not channel_state: return """ This is resolving a corner case where the current node view of the channel state does not reflect what the blockchain contains. The corner case goes as follows in a setup of nodes: A -> B: - A sends out a LockedTransfer to B - B sends a refund to A - B goes offline - A sends LockExpired to B Here: (1) the lock is removed from A's state (2) B never received the message - A closes the channel with B's refund - B comes back online and calls updateNonClosingBalanceProof with A's LockedTransfer (LockExpired was never processed). - When channel is settled, B unlocks it's refund transfer lock provided that it gains from doing so. - A does NOT try to unlock its lock because its side of the channel state is empty (lock expired and was removed). The above is resolved by providing the state machine with the onchain locksroots for both participants in the channel so that the channel state is updated to store these locksroots. In `raiden_event_handler:handle_contract_send_channelunlock`, those values are used to restore the channel state back to where the locksroots values existed and this channel state is used to calculate the gain and potentially perform unlocks in case there is value to be gained. """ canonical_identifier = CanonicalIdentifier( chain_identifier=CHAIN_ID_UNSPECIFIED, token_network_address=token_network_identifier, channel_identifier=channel_identifier, ) our_locksroot, partner_locksroot = get_onchain_locksroots( chain=raiden.chain, canonical_identifier=canonical_identifier, participant1=channel_state.our_state.address, participant2=channel_state.partner_state.address, block_identifier=block_hash, ) channel_settled = ContractReceiveChannelSettled( transaction_hash=transaction_hash, canonical_identifier=channel_state.canonical_identifier, our_onchain_locksroot=our_locksroot, partner_onchain_locksroot=partner_locksroot, block_number=block_number, block_hash=block_hash, ) raiden.handle_and_track_state_change(channel_settled)
def _add_onchain_locksroot_to_channel_settled_state_changes( raiden: RaidenService, storage: SQLiteStorage, ) -> None: """ Adds `our_onchain_locksroot` and `partner_onchain_locksroot` to ContractReceiveChannelSettled. """ batch_size = 50 batch_query = storage.batch_query_state_changes( batch_size=batch_size, filters=[ ('_type', 'raiden.transfer.state_change.ContractReceiveChannelSettled'), ], ) for state_changes_batch in batch_query: updated_state_changes = list() for state_change in state_changes_batch: state_change_data = json.loads(state_change.data) msg = 'v18 state changes cant contain our_onchain_locksroot' assert 'our_onchain_locksroot' not in state_change_data, msg msg = 'v18 state changes cant contain partner_onchain_locksroot' assert 'partner_onchain_locksroot' not in state_change_data, msg token_network_identifier = state_change_data[ 'token_network_identifier'] channel_identifier = state_change_data['channel_identifier'] channel_new_state_change = _find_channel_new_state_change( storage=storage, token_network_address=token_network_identifier, channel_identifier=channel_identifier, ) if not channel_new_state_change.data: raise RaidenUnrecoverableError( f'Could not find the state change for channel {channel_identifier}, ' f'token network address: {token_network_identifier} being created. ', ) channel_state_data = json.loads(channel_new_state_change.data) new_channel_state = channel_state_data['channel_state'] canonical_identifier = CanonicalIdentifier( chain_identifier=CHAIN_ID_UNSPECIFIED, token_network_address=to_canonical_address( token_network_identifier), channel_identifier=int(channel_identifier), ) our_locksroot, partner_locksroot = get_onchain_locksroots( chain=raiden.chain, canonical_identifier=canonical_identifier, participant1=to_canonical_address( new_channel_state['our_state']['address']), participant2=to_canonical_address( new_channel_state['partner_state']['address']), block_identifier='latest', ) state_change_data['our_onchain_locksroot'] = serialize_bytes( our_locksroot, ) state_change_data['partner_onchain_locksroot'] = serialize_bytes( partner_locksroot, ) updated_state_changes.append(( json.dumps(state_change_data), state_change.state_change_identifier, )) storage.update_state_changes(updated_state_changes)