def events_for_balanceproof(transfers_pair, block_number): """ Send the balance proof to nodes that know the secret. """ events = list() for pair in reversed(transfers_pair): payee_knows_secret = pair.payee_state in STATE_SECRET_KNOWN payee_payed = pair.payee_state in STATE_TRANSFER_PAID payee_channel_open = pair.payee_route.state == CHANNEL_STATE_OPENED # XXX: All nodes must close the channel and withdraw on-chain if the # lock is nearing it's expiration block, what should be the strategy # for sending a balance proof to a node that knowns the secret but has # not gone on-chain while near the expiration? (The problem is how to # define the unsafe region, since that is a local configuration) lock_valid = is_lock_valid(pair.payee_transfer, block_number) if payee_channel_open and payee_knows_secret and not payee_payed and lock_valid: pair.payee_state = 'payee_balance_proof' balance_proof = SendBalanceProof( pair.payee_transfer.identifier, pair.payee_route.channel_address, pair.payee_transfer.token, pair.payee_route.node_address, pair.payee_transfer.secret, ) unlock_success = EventUnlockSuccess( pair.payer_transfer.identifier, pair.payer_transfer.hashlock, ) events.append(balance_proof) events.append(unlock_success) return events
def events_for_unlock_lock( initiator_state: InitiatorTransferState, channel_state: NettingChannelState, secret: typing.Secret, secrethash: typing.SecretHash, pseudo_random_generator: random.Random, ): # 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=channel_state.token_network_identifier, identifier=transfer_description.payment_identifier, amount=transfer_description.amount, target=transfer_description.target, ) unlock_success = EventUnlockSuccess( transfer_description.payment_identifier, transfer_description.secrethash, ) return [unlock_lock, payment_sent_success, unlock_success]
def handle_secretreveal(state, state_change): """ Send a balance proof to the next hop with the current mediated transfer lock removed and the balance updated. """ if state_change.sender == state.route.node_address: # next hop learned the secret, unlock the token locally and send the # withdraw message to next hop transfer = state.transfer unlock_lock = SendBalanceProof( transfer.identifier, state.route.channel_address, transfer.token, state.route.node_address, transfer.secret, ) transfer_success = EventTransferSentSuccess( transfer.identifier, transfer.amount, transfer.target, ) unlock_success = EventUnlockSuccess( transfer.identifier, transfer.hashlock, ) iteration = TransitionResult(None, [unlock_lock, transfer_success, unlock_success]) else: iteration = TransitionResult(state, list()) return iteration
def events_for_balanceproof( channelidentifiers_to_channels, transfers_pair, pseudo_random_generator, block_number, secret, secrethash, ): """ While it's safe do the off-chain unlock. """ events = list() for pair in reversed(transfers_pair): payee_knows_secret = pair.payee_state in STATE_SECRET_KNOWN payee_payed = pair.payee_state in STATE_TRANSFER_PAID payee_channel = get_payee_channel(channelidentifiers_to_channels, pair) payee_channel_open = channel.get_status( payee_channel) == CHANNEL_STATE_OPENED payer_channel = get_payer_channel(channelidentifiers_to_channels, pair) # The mediator must not send to the payee a balance proof if the lock # is in the danger zone, because the payer may not do the same and the # on-chain unlock may fail. If the lock is nearing it's expiration # block, then on-chain unlock should be done, and if successfull it can # be unlocked off-chain. is_safe_to_send_balanceproof, _ = is_safe_to_wait( pair.payer_transfer.lock.expiration, payer_channel.reveal_timeout, block_number, ) should_send_balanceproof_to_payee = (payee_channel_open and payee_knows_secret and not payee_payed and is_safe_to_send_balanceproof) if should_send_balanceproof_to_payee: pair.payee_state = 'payee_balance_proof' message_identifier = message_identifier_from_prng( pseudo_random_generator) unlock_lock = channel.send_unlock( payee_channel, pair.payee_transfer.payment_identifier, message_identifier, secret, secrethash, ) unlock_success = EventUnlockSuccess( pair.payer_transfer.payment_identifier, pair.payer_transfer.lock.secrethash, ) events.append(unlock_lock) events.append(unlock_success) return events
def handle_contractwithdraw(state, state_change): """ Handle a NettingChannelUnlock state change. """ assert sha3( state.secret ) == state.hashlock, 'secret must be validated by the smart contract' # For all but the last pair in transfer pair a refund transfer ocurred, # meaning the same channel was used twice, once when this node sent the # mediated transfer and once when the refund transfer was received. A # ContractReceiveWithdraw state change may be used for each. events = list() # This node withdrew the refund if state_change.receiver == state.our_address: for previous_pos, pair in enumerate(state.transfers_pair, -1): if pair.payer_route.channel_address == state_change.channel_address: # always set the contract_withdraw regardless of the previous # state (even expired) pair.payer_state = 'payer_contract_withdraw' withdraw = EventWithdrawSuccess( pair.payer_transfer.identifier, pair.payer_transfer.hashlock, ) events.append(withdraw) # if the current pair is backed by a refund set the sent # mediated transfer to a 'secret known' state if previous_pos > -1: previous_pair = state.transfers_pair[previous_pos] if previous_pair.payee_state not in STATE_TRANSFER_FINAL: previous_pair.payee_state = 'payee_refund_withdraw' # A partner withdrew the mediated transfer else: for pair in state.transfers_pair: if pair.payer_route.channel_address == state_change.channel_address: unlock = EventUnlockSuccess( pair.payee_transfer.identifier, pair.payee_transfer.hashlock, ) events.append(unlock) pair.payee_state = 'payee_contract_withdraw' iteration = secret_learned( state, state_change.secret, state_change.receiver, 'payee_contract_withdraw', ) iteration.events.extend(events) return iteration
def handle_secretreveal( initiator_state: InitiatorTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ) -> TransitionResult: """ Send a balance proof to the next hop with the current mediated transfer lock removed and the balance updated. """ is_valid_secret_reveal = ( state_change.sender == channel_state.partner_state.address and state_change.secrethash == initiator_state.transfer_description.secrethash) # If the channel is closed the balance proof must not be sent is_channel_open = channel.get_status(channel_state) == CHANNEL_STATE_OPENED if is_valid_secret_reveal and is_channel_open: # 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, transfer_description.payment_identifier, message_identifier, state_change.secret, state_change.secrethash, ) # TODO: Emit these events after on-chain unlock payment_sent_success = EventPaymentSentSuccess( payment_network_identifier=channel_state. payment_network_identifier, token_network_identifier=channel_state.token_network_identifier, identifier=transfer_description.payment_identifier, amount=transfer_description.amount, target=transfer_description.target, ) unlock_success = EventUnlockSuccess( transfer_description.payment_identifier, transfer_description.secrethash, ) iteration = TransitionResult( None, [payment_sent_success, unlock_success, unlock_lock]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def events_for_balanceproof( channelidentifiers_to_channels, transfers_pair, pseudo_random_generator, block_number, secret, secrethash, ): """ Send the balance proof to nodes that know the secret. """ events = list() for pair in reversed(transfers_pair): payee_knows_secret = pair.payee_state in STATE_SECRET_KNOWN payee_payed = pair.payee_state in STATE_TRANSFER_PAID payee_channel = get_payee_channel(channelidentifiers_to_channels, pair) payee_channel_open = channel.get_status( payee_channel) == CHANNEL_STATE_OPENED # XXX: All nodes must close the channel and unlock on-chain if the # lock is nearing it's expiration block, what should be the strategy # for sending a balance proof to a node that knowns the secret but has # not gone on-chain while near the expiration? (The problem is how to # define the unsafe region, since that is a local configuration) lock_valid = is_lock_valid(pair.payee_transfer.lock.expiration, block_number) if payee_channel_open and payee_knows_secret and not payee_payed and lock_valid: pair.payee_state = 'payee_balance_proof' message_identifier = message_identifier_from_prng( pseudo_random_generator) unlock_lock = channel.send_unlock( payee_channel, pair.payee_transfer.payment_identifier, message_identifier, secret, secrethash, ) unlock_success = EventUnlockSuccess( pair.payer_transfer.payment_identifier, pair.payer_transfer.lock.secrethash, ) events.append(unlock_lock) events.append(unlock_success) return events
def handle_secretreveal( initiator_state: InitiatorTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, ) -> TransitionResult: """ Send a balance proof to the next hop with the current mediated transfer lock removed and the balance updated. """ is_valid_secret_reveal = (state_change.sender == channel_state.partner_state.address and state_change.hashlock == initiator_state.transfer_description.hashlock) # If the channel is closed the balance proof must not be sent is_channel_open = channel.get_status(channel_state) == CHANNEL_STATE_OPENED if is_valid_secret_reveal and is_channel_open: # next hop learned the secret, unlock the token locally and send the # withdraw message to next hop transfer_description = initiator_state.transfer_description unlock_lock = channel.send_unlock( channel_state, transfer_description.identifier, state_change.secret, state_change.hashlock, ) # TODO: Emit these events after on-chain withdraw transfer_success = EventTransferSentSuccess( transfer_description.identifier, transfer_description.amount, transfer_description.target, ) unlock_success = EventUnlockSuccess( transfer_description.identifier, transfer_description.hashlock, ) iteration = TransitionResult( None, [transfer_success, unlock_success, unlock_lock]) else: iteration = TransitionResult(initiator_state, list()) return iteration
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 events_for_unlock_lock( initiator_state: InitiatorTransferState, channel_state: NettingChannelState, secret: Secret, secrethash: SecretHash, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> 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, block_number=block_number, ) payment_sent_success = EventPaymentSentSuccess( token_network_registry_address=channel_state. token_network_registry_address, token_network_address=channel_state.token_network_address, identifier=transfer_description.payment_identifier, amount=transfer_description.amount, target=transfer_description.target, secret=secret, route=initiator_state.route.route, ) unlock_success = EventUnlockSuccess( transfer_description.payment_identifier, transfer_description.secrethash) return [unlock_lock, payment_sent_success, unlock_success]
def handle_contractunlock( state, state_change, channelidentifiers_to_channels, pseudo_random_generator, block_number, ): """ Handle a NettingChannelUnlock state change. """ assert sha3( state.secret ) == state.secrethash, 'secret must be validated by the smart contract' # For all but the last pair in transfer pair a refund transfer ocurred, # meaning the same channel was used twice, once when this node sent the # mediated transfer and once when the refund transfer was received. A # ContractReceiveChannelUnlock state change may be used for each. events = list() # This node withdrew the refund if state_change.receiver == state.our_address: for previous_pos, pair in enumerate(state.transfers_pair, -1): payer_channel = get_payer_channel(channelidentifiers_to_channels, pair) if payer_channel.identifier == state_change.channel_identifier: # always set the contract_unlock regardless of the previous # state (even expired) pair.payer_state = 'payer_contract_unlock' unlock = EventUnlockClaimSuccess( pair.payer_transfer.payment_identifier, pair.payer_transfer.lock.secrethash, ) events.append(unlock) # if the current pair is backed by a refund set the sent # mediated transfer to a 'secret known' state if previous_pos > -1: previous_pair = state.transfers_pair[previous_pos] if previous_pair.payee_state not in STATE_TRANSFER_FINAL: previous_pair.payee_state = 'payee_refund_unlock' # A partner withdrew the mediated transfer else: for pair in state.transfers_pair: payee_channel = get_payee_channel(channelidentifiers_to_channels, pair) if payee_channel.identifier == state_change.channel_identifier: unlock = EventUnlockSuccess( pair.payee_transfer.payment_identifier, pair.payee_transfer.lock.secrethash, ) events.append(unlock) pair.payee_state = 'payee_contract_unlock' iteration = secret_learned( state, channelidentifiers_to_channels, pseudo_random_generator, block_number, state_change.secret, state_change.secrethash, state_change.receiver, 'payee_contract_unlock', False, ) iteration.events.extend(events) return iteration