def _initialize_payment_statuses(self, chain_state: ChainState): """ Re-initialize targets_to_identifiers_to_statuses. Restore the PaymentStatus for any pending payment. This is not tied to a specific protocol message but to the lifecycle of a payment, i.e. the status is re-created if a payment itself has not completed. """ with self.payment_identifier_lock: for task in chain_state.payment_mapping.secrethashes_to_task.values( ): if not isinstance(task, InitiatorTask): continue # Every transfer in the transfers_list must have the same target # and payment_identifier, so using the first transfer is # sufficient. initiator = next( iter(task.manager_state.initiator_transfers.values())) transfer = initiator.transfer transfer_description = initiator.transfer_description target = transfer.target identifier = transfer.payment_identifier balance_proof = transfer.balance_proof self.targets_to_identifiers_to_statuses[target][ identifier] = PaymentStatus( payment_identifier=identifier, amount=transfer_description.amount, token_network_identifier=TokenNetworkID( balance_proof.token_network_identifier), payment_done=AsyncResult(), )
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_message_refundtransfer(raiden: RaidenService, message: RefundTransfer): token_network_address = message.token_network_address from_transfer = lockedtransfersigned_from_message(message) chain_state = views.state_from_raiden(raiden) routes = get_best_routes( chain_state=chain_state, token_network_id=TokenNetworkID(token_network_address), from_address=InitiatorAddress(raiden.address), to_address=from_transfer.target, amount=from_transfer.lock.amount, previous_address=message.sender, config=raiden.config, ) role = views.get_transfer_role( chain_state, from_transfer.lock.secrethash, ) if role == 'initiator': secret = random_secret() state_change = ReceiveTransferRefundCancelRoute( routes=routes, transfer=from_transfer, secret=secret, ) else: state_change = ReceiveTransferRefund( transfer=from_transfer, routes=routes, ) raiden.handle_state_change(state_change)
def handle_cancelpayment( payment_state: InitiatorPaymentState, channelidentifiers_to_channels: ChannelMap ) -> TransitionResult[InitiatorPaymentState]: """ Cancel the payment and all related transfers. """ # Cannot cancel a transfer after the secret is revealed events = list() for initiator_state in payment_state.initiator_transfers.values(): channel_identifier = initiator_state.channel_identifier channel_state = channelidentifiers_to_channels.get(channel_identifier) if not channel_state: continue if can_cancel(initiator_state): transfer_description = initiator_state.transfer_description cancel_events = cancel_current_route(payment_state, initiator_state) initiator_state.transfer_state = "transfer_cancelled" cancel = EventPaymentSentFailed( payment_network_identifier=channel_state. payment_network_identifier, token_network_identifier=TokenNetworkID( channel_state.token_network_identifier), identifier=transfer_description.payment_identifier, target=transfer_description.target, reason="user canceled payment", ) cancel_events.append(cancel) events.extend(cancel_events) return TransitionResult(payment_state, events)
def handle_init_mediator( chain_state: ChainState, state_change: ActionInitMediator) -> TransitionResult[ChainState]: transfer = state_change.from_transfer secrethash = transfer.lock.secrethash token_network_identifier = transfer.balance_proof.token_network_identifier return subdispatch_mediatortask(chain_state, state_change, TokenNetworkID(token_network_identifier), secrethash)
def get_channelstate_by_canonical_identifier( chain_state: ChainState, canonical_identifier: CanonicalIdentifier ) -> Optional[NettingChannelState]: """ Return the NettingChannelState if it exists, None otherwise. """ token_network = get_token_network_by_identifier( chain_state, TokenNetworkID(canonical_identifier.token_network_address)) channel_state = None if token_network: channel_state = token_network.channelidentifiers_to_channels.get( canonical_identifier.channel_identifier) return channel_state
def handle_init_target( chain_state: ChainState, state_change: ActionInitTarget) -> TransitionResult[ChainState]: transfer = state_change.transfer secrethash = transfer.lock.secrethash channel_identifier = transfer.balance_proof.channel_identifier token_network_identifier = transfer.balance_proof.token_network_identifier return subdispatch_targettask( chain_state, state_change, TokenNetworkID(token_network_identifier), channel_identifier, secrethash, )
def get_token_network_by_address( chain_state: ChainState, token_network_address: Union[TokenNetworkID, TokenNetworkAddress] ) -> Optional[TokenNetworkState]: payment_network_identifier = chain_state.tokennetworkaddresses_to_paymentnetworkaddresses.get( TokenNetworkAddress(token_network_address)) payment_network_state = None if payment_network_identifier: payment_network_state = chain_state.identifiers_to_paymentnetworks.get( payment_network_identifier) token_network_state = None if payment_network_state: token_network_state = payment_network_state.tokenidentifiers_to_tokennetworks.get( TokenNetworkID(token_network_address)) return token_network_state
def mediator_init(raiden, transfer: LockedTransfer) -> ActionInitMediator: from_transfer = lockedtransfersigned_from_message(transfer) # Feedback token not used here, will be removed with source routing routes, _ = routing.get_best_routes( chain_state=views.state_from_raiden(raiden), token_network_id=TokenNetworkID( from_transfer.balance_proof.token_network_identifier), one_to_n_address=raiden.default_one_to_n_address, from_address=raiden.address, to_address=from_transfer.target, amount=PaymentAmount(from_transfer.lock.amount ), # FIXME: mypy; deprecated through #3863 previous_address=transfer.sender, config=raiden.config, privkey=raiden.privkey, ) from_route = RouteState(transfer.sender, from_transfer.balance_proof.channel_identifier) return ActionInitMediator(routes, from_route, from_transfer)
def mediator_init(raiden, transfer: LockedTransfer): from_transfer = lockedtransfersigned_from_message(transfer) routes = routing.get_best_routes( chain_state=views.state_from_raiden(raiden), token_network_id=TokenNetworkID( from_transfer.balance_proof.token_network_identifier), from_address=raiden.address, to_address=from_transfer.target, amount=PaymentAmount(from_transfer.lock.amount ), # FIXME: mypy; deprecated through #3863 previous_address=transfer.sender, config=raiden.config, privkey=raiden.privkey, ) from_route = RouteState(transfer.sender, from_transfer.balance_proof.channel_identifier) init_mediator_statechange = ActionInitMediator(routes, from_route, from_transfer) return init_mediator_statechange
def get_channelstate_by_canonical_identifier_and_address( chain_state: ChainState, canonical_identifier: CanonicalIdentifier, address: AddressHex) -> Optional[NettingChannelState]: """ Return the NettingChannelState if it exists, None otherwise. """ token_network = get_token_network_by_identifier( chain_state, TokenNetworkID(canonical_identifier.token_network_address)) channel_state = None if token_network and address in token_network.channelidentifiers_to_channels: channel_state = token_network.channelidentifiers_to_channels[ address].get(canonical_identifier.channel_identifier) else: # Get the channel by the light client associated with the participant and the canonical id lc_address = get_lc_address_by_channel_id_and_partner( token_network, address, canonical_identifier) if lc_address in token_network.channelidentifiers_to_channels: channel_state = token_network.channelidentifiers_to_channels[ lc_address].get(canonical_identifier.channel_identifier) return channel_state
def events_for_unlock_base( initiator_state: InitiatorTransferState, channel_state: NettingChannelState, secret: Secret ) -> List[Event]: transfer_description = initiator_state.transfer_description payment_sent_success = EventPaymentSentSuccess( payment_network_identifier=channel_state.payment_network_identifier, token_network_identifier=TokenNetworkID(channel_state.token_network_identifier), identifier=transfer_description.payment_identifier, amount=transfer_description.amount, target=transfer_description.target, secret=secret, ) unlock_success = EventUnlockSuccess( transfer_description.payment_identifier, transfer_description.secrethash ) return [payment_sent_success, unlock_success]
def handle_message_refundtransfer(raiden: RaidenService, message: RefundTransfer) -> None: token_network_address = message.token_network_address from_transfer = lockedtransfersigned_from_message(message) chain_state = views.state_from_raiden(raiden) # FIXME: Shouldn't request routes here routes, _ = get_best_routes( chain_state=chain_state, token_network_id=TokenNetworkID(token_network_address), one_to_n_address=raiden.default_one_to_n_address, from_address=InitiatorAddress(raiden.address), to_address=from_transfer.target, amount=PaymentAmount( from_transfer.lock.amount), # FIXME: mypy; deprecated by #3863 previous_address=message.sender, config=raiden.config, privkey=raiden.privkey, ) role = views.get_transfer_role( chain_state=chain_state, secrethash=from_transfer.lock.secrethash) state_change: StateChange if role == "initiator": old_secret = views.get_transfer_secret( chain_state, from_transfer.lock.secrethash) # We currently don't allow multi routes if the initiator does not # hold the secret. In such case we remove all other possible routes # which allow the API call to return with with an error message. if old_secret == EMPTY_SECRET: routes = list() secret = random_secret() state_change = ReceiveTransferRefundCancelRoute( routes=routes, transfer=from_transfer, secret=secret) else: state_change = ReceiveTransferRefund(transfer=from_transfer, routes=routes) raiden.handle_and_track_state_change(state_change)
def handle_unlock_light( target_state: TargetTransferState, state_change: ReceiveUnlockLight, channel_state: NettingChannelState, ) -> TransitionResult[TargetTransferState]: """ Handles a ReceiveUnlockLight state change. """ is_valid, events, _ = channel.handle_unlock_light(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 ) store_unlock_message = StoreMessageEvent( state_change.signed_unlock.message_identifier, state_change.signed_unlock.payment_identifier, 11, state_change.signed_unlock, True ) events.extend([payment_received_success, unlock_success, store_unlock_message]) next_target_state = None return TransitionResult(next_target_state, events)
def events_for_unlock_lock( initiator_state: InitiatorTransferState, channel_state: NettingChannelState, secret: Secret, secrethash: SecretHash, pseudo_random_generator: random.Random, ) -> List[Event]: """ Unlocks the lock offchain, and emits the events for the successful payment. """ # next hop learned the secret, unlock the token locally and send the # lock claim message to next hop transfer_description = initiator_state.transfer_description message_identifier = message_identifier_from_prng(pseudo_random_generator) unlock_lock = channel.send_unlock( channel_state=channel_state, message_identifier=message_identifier, payment_identifier=transfer_description.payment_identifier, secret=secret, secrethash=secrethash, ) payment_sent_success = EventPaymentSentSuccess( payment_network_identifier=channel_state.payment_network_identifier, token_network_identifier=TokenNetworkID( channel_state.token_network_identifier), identifier=transfer_description.payment_identifier, amount=transfer_description.amount, target=transfer_description.target, secret=secret, ) unlock_success = EventUnlockSuccess( transfer_description.payment_identifier, transfer_description.secrethash) return [unlock_lock, payment_sent_success, unlock_success]
def handle_contract_send_channelunlock( raiden: "RaidenService", chain_state: ChainState, channel_unlock_event: ContractSendChannelBatchUnlock, ): assert raiden.wal, "The Raiden Service must be initialize to handle events" canonical_identifier = channel_unlock_event.canonical_identifier token_network_identifier = canonical_identifier.token_network_address channel_identifier = canonical_identifier.channel_identifier participant = channel_unlock_event.participant payment_channel: PaymentChannel = raiden.chain.payment_channel( canonical_identifier=canonical_identifier ) channel_state = get_channelstate_by_token_network_and_partner( chain_state=chain_state, token_network_id=TokenNetworkID(token_network_identifier), 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)}, token_network:pex(token_network_identifier)" ) 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 != EMPTY_HASH # we want to unlock, because there are unlocked/unclaimed locks search_state_changes = partner_locksroot != EMPTY_HASH 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, ) state_change_identifier = state_change_record.state_change_identifier if not state_change_identifier: raise RaidenUnrecoverableError( f"Failed to find state that matches the current channel locksroots. " f"chain_id:{raiden.chain.network_id} " f"token_network:{to_checksum_address(token_network_identifier)} " f"channel:{channel_identifier} " f"participant:{to_checksum_address(participant)} " f"our_locksroot:{to_hex(our_locksroot)} " f"partner_locksroot:{to_hex(partner_locksroot)} " ) 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( raiden=raiden, payment_channel=payment_channel, end_state=restored_channel_state.partner_state, participant=our_address, partner=partner_address, ) 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, ) state_change_identifier = event_record.state_change_identifier if not state_change_identifier: raise RaidenUnrecoverableError( f"Failed to find event that match current channel locksroots. " f"chain_id:{raiden.chain.network_id} " f"token_network:{to_checksum_address(token_network_identifier)} " f"channel:{channel_identifier} " f"participant:{to_checksum_address(participant)} " f"our_locksroot:{to_hex(our_locksroot)} " f"partner_locksroot:{to_hex(partner_locksroot)} " ) 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: unlock( raiden=raiden, payment_channel=payment_channel, end_state=restored_channel_state.our_state, participant=partner_address, partner=our_address, )
def token_network_identifier(self) -> TokenNetworkID: return TokenNetworkID(self.canonical_identifier.token_network_address)
def get_channel_state( token_address: TokenAddress, payment_network_identifier: PaymentNetworkID, token_network_address: TokenNetworkAddress, reveal_timeout: BlockTimeout, payment_channel_proxy, opened_block_number: BlockNumber, opened_block_hash: BlockHash, ): channel_details = payment_channel_proxy.detail(opened_block_hash) our_state = NettingChannelEndState( channel_details.participants_data.our_details.address, channel_details.participants_data.our_details.deposit, ) partner_state = NettingChannelEndState( channel_details.participants_data.partner_details.address, channel_details.participants_data.partner_details.deposit, ) identifier = payment_channel_proxy.channel_identifier settle_timeout = payment_channel_proxy.settle_timeout() closed_block_number = payment_channel_proxy.close_block_number() # ignore bad open block numbers if opened_block_number <= 0: return None open_transaction = TransactionExecutionStatus( None, opened_block_number, TransactionExecutionStatus.SUCCESS, ) if closed_block_number: close_transaction = TransactionExecutionStatus( None, closed_block_number, TransactionExecutionStatus.SUCCESS, ) else: close_transaction = None # For the current implementation the channel is a smart contract that # will be killed on settle. settle_transaction = None channel = NettingChannelState( identifier=identifier, chain_id=channel_details.chain_id, token_address=token_address, payment_network_identifier=payment_network_identifier, token_network_identifier=TokenNetworkID(token_network_address), reveal_timeout=reveal_timeout, settle_timeout=settle_timeout, our_state=our_state, partner_state=partner_state, open_transaction=open_transaction, close_transaction=close_transaction, settle_transaction=settle_transaction, ) return channel
def is_transaction_effect_satisfied(chain_state: ChainState, transaction: ContractSendEvent, state_change: StateChange) -> bool: """ True if the side-effect of `transaction` is satisfied by `state_change`. This predicate is used to clear the transaction queue. This should only be done once the expected side effect of a transaction is achieved. This doesn't necessarily mean that the transaction sent by *this* node was mined, but only that *some* transaction which achieves the same side-effect was successfully executed and mined. This distinction is important for restarts and to reduce the number of state changes. On restarts: The state of the on-chain channel could have changed while the node was offline. Once the node learns about the change (e.g. the channel was settled), new transactions can be dispatched by Raiden as a side effect for the on-chain *event* (e.g. do the batch unlock with the latest merkle tree), but the dispatched transaction could have been completed by another agent (e.g. the partner node). For these cases, the transaction from a different address which achieves the same side-effect is sufficient, otherwise unnecessary transactions would be sent by the node. NOTE: The above is not important for transactions sent as a side-effect for a new *block*. On restart the node first synchronizes its state by querying for new events, only after the off-chain state is up-to-date, a Block state change is dispatched. At this point some transactions are not required anymore and therefore are not dispatched. On the number of state changes: Accepting a transaction from another address removes the need for clearing state changes, e.g. when our node's close transaction fails but its partner's close transaction succeeds. """ # These transactions are not made atomic through the WAL. They are sent # exclusively through the external APIs. # # - ContractReceiveChannelNew # - ContractReceiveChannelNewBalance # - ContractReceiveNewPaymentNetwork # - ContractReceiveNewTokenNetwork # - ContractReceiveRouteNew # # Note: Deposits and Withdraws must consider a transaction with a higher # value as sufficient, because the values are monotonically increasing and # the transaction with a lower value will never be executed. # Transactions are used to change the on-chain state of a channel. It # doesn't matter if the sender of the transaction is the local node or # another node authorized to perform the operation. So, for the following # transactions, as long as the side-effects are the same, the local # transaction can be removed from the queue. # # - An update transfer can be done by a trusted third party (i.e. monitoring service) # - A close transaction can be sent by our partner # - A settle transaction can be sent by anyone # - A secret reveal can be done by anyone # - A lower nonce is not a valid replacement, since that is an older balance # proof # - A larger raiden state change nonce is impossible. # That would require the partner node to produce an invalid balance proof, # and this node to accept the invalid balance proof and sign it is_valid_update_transfer = ( isinstance(state_change, ContractReceiveUpdateTransfer) and isinstance(transaction, ContractSendChannelUpdateTransfer) and state_change.token_network_identifier == transaction.token_network_identifier and state_change.channel_identifier == transaction.channel_identifier and state_change.nonce == transaction.balance_proof.nonce) if is_valid_update_transfer: return True # The balance proof data cannot be verified, the local close could have # lost a race against a remote close, and the balance proof data would be # the one provided by this node's partner is_valid_close = (isinstance(state_change, ContractReceiveChannelClosed) and isinstance(transaction, ContractSendChannelClose) and state_change.token_network_identifier == transaction.token_network_identifier and state_change.channel_identifier == transaction.channel_identifier) if is_valid_close: return True is_valid_settle = (isinstance(state_change, ContractReceiveChannelSettled) and isinstance(transaction, ContractSendChannelSettle) and state_change.token_network_identifier == transaction.token_network_identifier and state_change.channel_identifier == transaction.channel_identifier) if is_valid_settle: return True is_valid_secret_reveal = ( isinstance(state_change, ContractReceiveSecretReveal) and isinstance(transaction, ContractSendSecretReveal) and state_change.secret == transaction.secret) if is_valid_secret_reveal: return True is_batch_unlock = isinstance( state_change, ContractReceiveChannelBatchUnlock) and isinstance( transaction, ContractSendChannelBatchUnlock) if is_batch_unlock: assert isinstance(state_change, ContractReceiveChannelBatchUnlock), MYPY_ANNOTATION assert isinstance(transaction, ContractSendChannelBatchUnlock), MYPY_ANNOTATION our_address = chain_state.our_address # Don't assume that because we sent the transaction, we are a # participant partner_address = None if state_change.participant == our_address: partner_address = state_change.partner elif state_change.partner == our_address: partner_address = state_change.participant # Use the second address as the partner address, but check that a # channel exists for our_address and partner_address if partner_address: channel_state = views.get_channelstate_by_token_network_and_partner( chain_state, TokenNetworkID(state_change.token_network_identifier), partner_address) # If the channel was cleared, that means that both # sides of the channel were successfully unlocked. # In this case, we clear the batch unlock # transaction from the queue only in case there # were no more locked funds to unlock. if channel_state is None: return True return False
def __init__( self, canonical_identifier: CanonicalIdentifier, ) -> None: self.token_network_identifier = TokenNetworkID(canonical_identifier.token_network_address) self.channel_identifier = canonical_identifier.channel_identifier