def handle_secretreveal( target_state: TargetTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ): """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = state_change.secrethash == target_state.transfer.lock.secrethash waiting_for_secret = target_state.state == 'secret_request' if valid_secret and waiting_for_secret: if isinstance(state_change, ReceiveSecretReveal): channel.register_secret( channel_state, state_change.secret, state_change.secrethash, ) elif isinstance(state_change, ContractReceiveSecretReveal): channel.register_onchain_secret( channel_state=channel_state, secret=state_change.secret, secrethash=state_change.secrethash, secret_reveal_block_number=state_change.block_number, ) else: assert False, 'Got unexpected StateChange' route = target_state.route message_identifier = message_identifier_from_prng( pseudo_random_generator) target_state.state = 'reveal_secret' target_state.secret = state_change.secret recipient = route.node_address # Send the secret reveal message only once, delivery is guaranteed by # the transport and not by the state machine reveal = SendSecretReveal( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=target_state.secret, ) iteration = TransitionResult(target_state, [reveal]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def test_target_task_view(): """Same as above for target tasks.""" secret = factories.make_secret() transfer = factories.create( factories.LockedTransferSignedStateProperties(secret=secret)) secrethash = transfer.lock.secrethash mediator = factories.make_address() mediator_channel = factories.create( factories.NettingChannelStateProperties( partner_state=factories.NettingChannelEndStateProperties( address=mediator, balance=100))) transfer_state = TargetTransferState(route=None, transfer=transfer, secret=secret) task = TargetTask( canonical_identifier=mediator_channel.canonical_identifier, target_state=transfer_state) payment_mapping = {secrethash: task} view = transfer_tasks_view(payment_mapping) assert len(view) == 1 pending_transfer = view[0] assert pending_transfer.get("role") == "target" assert pending_transfer.get("locked_amount") == str( transfer.balance_proof.locked_amount) assert pending_transfer.get("payment_identifier") == str( transfer.payment_identifier)
def test_events_for_onchain_secretreveal(): """ Secret must be registered on-chain when the unsafe region is reached and the secret is known. """ block_number = 10 expiration = block_number + 30 channels = make_channel_set([channel_properties]) from_transfer = make_target_transfer(channels[0], expiration=expiration) channel.handle_receive_lockedtransfer(channels[0], from_transfer) channel.register_offchain_secret(channels[0], UNIT_SECRET, UNIT_SECRETHASH) safe_to_wait = expiration - channels[0].reveal_timeout - 1 unsafe_to_wait = expiration - channels[0].reveal_timeout state = TargetTransferState(channels.get_route(0), from_transfer) events = target.events_for_onchain_secretreveal(state, channels[0], safe_to_wait) assert not events events = target.events_for_onchain_secretreveal(state, channels[0], unsafe_to_wait) assert events assert isinstance(events[0], ContractSendSecretReveal) assert events[0].secret == UNIT_SECRET
def test_target_task_view(): """Same as above for target tasks.""" secret = factories.make_secret() transfer = factories.create( factories.LockedTransferSignedStateProperties( transfer=factories.LockedTransferProperties(secret=secret), )) secrethash = transfer.lock.secrethash mediator = factories.make_address() mediator_channel = factories.create( factories.NettingChannelStateProperties( partner_state=factories.NettingChannelEndStateProperties( address=mediator, balance=100), )) transfer_state = TargetTransferState(route=None, transfer=transfer, secret=secret) task = TargetTask( token_network_identifier=factories.UNIT_TOKEN_NETWORK_ADDRESS, channel_identifier=mediator_channel.identifier, target_state=transfer_state, ) payment_mapping = {secrethash: task} view = transfer_tasks_view(payment_mapping) assert len(view) == 1 pending_transfer = view[0] assert pending_transfer.get('role') == 'target' assert pending_transfer.get('locked_amount') == str( transfer.balance_proof.locked_amount) assert pending_transfer.get('payment_identifier') == str( transfer.payment_identifier)
def test_target_task_view(): """Same as above for target tasks.""" secret = factories.make_secret() transfer = factories.create( factories.LockedTransferSignedStateProperties(secret=secret)) secrethash = transfer.lock.secrethash mediator = factories.make_address() mediator_channel = factories.create( factories.NettingChannelStateProperties( partner_state=factories.NettingChannelEndStateProperties( address=mediator, balance=TokenAmount(100)))) transfer_state = TargetTransferState( from_hop=HopState( channel_identifier=mediator_channel.canonical_identifier. channel_identifier, node_address=mediator, ), transfer=transfer, secret=secret, ) task = TargetTask( canonical_identifier=mediator_channel.canonical_identifier, target_state=transfer_state) payment_mapping = {secrethash: cast(TransferTask, task)} view = transfer_tasks_view(payment_mapping) assert len(view) == 1 pending_transfer = view[0] assert pending_transfer.get("role") == "target" # pylint: disable=no-member assert pending_transfer.get("locked_amount") == str( transfer.balance_proof.locked_amount) assert pending_transfer.get("payment_identifier") == str( transfer.payment_identifier)
def make_target_state(our_address, amount, block_number, initiator, expiration=None): from_channel = factories.make_channel( our_address=our_address, partner_address=UNIT_TRANSFER_SENDER, partner_balance=amount, ) from_route = factories.route_from_channel(from_channel) if expiration is None: expiration = from_channel.reveal_timeout + block_number + 1 from_transfer = factories.make_signed_transfer_for( from_channel, amount, initiator, our_address, expiration, UNIT_SECRET, ) state = TargetTransferState(from_route, from_transfer) return from_channel, state
def test_events_for_close_secret_unknown(): """ Channel must not be closed when the unsafe region is reached and the secret is not known. """ amount = 3 block_number = 10 expiration = block_number + 30 initiator = factories.HOP1 target_address = UNIT_TRANSFER_TARGET from_channel = factories.make_channel( our_address=target_address, partner_address=UNIT_TRANSFER_SENDER, partner_balance=amount, ) from_route = factories.route_from_channel(from_channel) from_transfer = factories.make_signed_transfer_for( from_channel, amount, initiator, target_address, expiration, UNIT_SECRET, ) channel.handle_receive_lockedtransfer( from_channel, from_transfer, ) state = TargetTransferState(from_route, from_transfer) events = target.events_for_close(state, from_channel, expiration) assert not events
def events_for_close( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: typing.BlockNumber, ): """ Emits the event for closing the netting channel if the transfer needs to be settled on-chain. """ transfer = target_state.transfer safe_to_wait, _ = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) if not safe_to_wait and secret_known: target_state.state = 'waiting_close' return channel.events_for_close(channel_state, block_number) return list()
def handle_block( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: typing.BlockNumber, pseudo_random_generator: random.Random, ): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) if not secret_known and block_number > transfer.lock.expiration: if target_state.state != 'expired': failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason='lock expired', ) target_state.state = 'expired' events = [failed] else: events = list() elif target_state.state != 'waiting_close': # only emit the close event once events = events_for_onchain_secretreveal(target_state, channel_state, block_number) else: events = list() iteration = TransitionResult(target_state, events) return iteration
def events_for_onchain_secretreveal( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: BlockNumber, block_hash: BlockHash, ) -> List[Event]: """ Emits the event for revealing the secret on-chain if the transfer can not be settled off-chain. """ transfer = target_state.transfer expiration = transfer.lock.expiration safe_to_wait = is_safe_to_wait(expiration, channel_state.reveal_timeout, block_number) secret_known_offchain = channel.is_secret_known_offchain( channel_state.partner_state, transfer.lock.secrethash ) has_onchain_reveal_started = target_state.state == TargetTransferState.ONCHAIN_SECRET_REVEAL if not safe_to_wait and secret_known_offchain and not has_onchain_reveal_started: target_state.state = TargetTransferState.ONCHAIN_SECRET_REVEAL secret = channel.get_secret(channel_state.partner_state, transfer.lock.secrethash) assert secret, "secret should be known at this point" return secret_registry.events_for_onchain_secretreveal( channel_state=channel_state, secret=secret, expiration=expiration, block_hash=block_hash, ) return list()
def test_events_for_onchain_secretreveal(): """ Secret must be registered on-chain when the unsafe region is reached and the secret is known. """ block_number = 10 expiration = block_number + 30 channels = make_channel_set([channel_properties]) from_transfer = make_target_transfer(channels[0], expiration=expiration) channel.handle_receive_lockedtransfer(channels[0], from_transfer) channel.register_offchain_secret(channels[0], UNIT_SECRET, UNIT_SECRETHASH) safe_to_wait = expiration - channels[0].reveal_timeout - 1 unsafe_to_wait = expiration - channels[0].reveal_timeout state = TargetTransferState(channels.get_route(0), from_transfer) events = target.events_for_onchain_secretreveal(state, channels[0], safe_to_wait) assert not events events = target.events_for_onchain_secretreveal(state, channels[0], unsafe_to_wait) msg = 'when its not safe to wait, the contract send must be emitted' assert search_for_item(events, ContractSendSecretReveal, {'secret': UNIT_SECRET}), msg msg = 'second call must not emit ContractSendSecretReveal again' assert not target.events_for_onchain_secretreveal(state, channels[0], unsafe_to_wait), msg
def handle_block( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: typing.BlockNumber, ): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer events = list() secret_known = channel.is_secret_known( channel_state.partner_state, transfer.lock.secrethash, ) is_lock_expired = (not secret_known and block_number > transfer.lock.expiration) if secret_known: events = events_for_onchain_secretreveal( target_state, channel_state, block_number, ) elif is_lock_expired and target_state.state != 'expired': failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason='lock expired', ) target_state.state = 'expired' events = [failed] return TransitionResult(target_state, events)
def handle_offchain_secretreveal( target_state: TargetTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ): """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = is_valid_secret_reveal( state_change=state_change, transfer_secrethash=target_state.transfer.lock.secrethash, secret=state_change.secret, ) has_transfer_expired = channel.transfer_expired( transfer=target_state.transfer, affected_channel=channel_state, block_number=block_number, ) if valid_secret and not has_transfer_expired: channel.register_offchain_secret( channel_state=channel_state, secret=state_change.secret, secrethash=state_change.secrethash, ) route = target_state.route message_identifier = message_identifier_from_prng( pseudo_random_generator) target_state.state = TargetTransferState.OFFCHAIN_SECRET_REVEAL target_state.secret = state_change.secret recipient = route.node_address reveal = SendSecretReveal( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=target_state.secret, ) iteration = TransitionResult(target_state, [reveal]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def test_subdispatch_to_paymenttask_target(chain_state, netting_channel_state): target_state = TargetTransferState( from_hop=HopState( node_address=netting_channel_state.partner_state.address, channel_identifier=netting_channel_state.canonical_identifier. channel_identifier, ), transfer=factories.create( factories.LockedTransferSignedStateProperties()), secret=UNIT_SECRET, ) subtask = TargetTask( canonical_identifier=netting_channel_state.canonical_identifier, target_state=target_state) chain_state.payment_mapping.secrethashes_to_task[UNIT_SECRETHASH] = subtask lock = factories.HashTimeLockState(amount=0, expiration=2, secrethash=UNIT_SECRETHASH) netting_channel_state.partner_state.secrethashes_to_lockedlocks[ UNIT_SECRETHASH] = lock netting_channel_state.partner_state.pending_locks = PendingLocksState( [bytes(lock.encoded)]) state_change = Block( block_number=chain_state.block_number, gas_limit=GAS_LIMIT, block_hash=chain_state.block_hash, ) transition_result = subdispatch_to_paymenttask(chain_state=chain_state, state_change=state_change, secrethash=UNIT_SECRETHASH) assert transition_result.events == [] assert transition_result.new_state == chain_state chain_state.block_number = 20 balance_proof: BalanceProofSignedState = factories.create( factories.BalanceProofSignedStateProperties( canonical_identifier=netting_channel_state.canonical_identifier, sender=netting_channel_state.partner_state.address, transferred_amount=0, pkey=factories.UNIT_TRANSFER_PKEY, locksroot=LOCKSROOT_OF_NO_LOCKS, )) state_change = ReceiveLockExpired( balance_proof=balance_proof, sender=netting_channel_state.partner_state.address, secrethash=UNIT_SECRETHASH, message_identifier=factories.make_message_identifier(), ) transition_result = subdispatch_to_paymenttask(chain_state=chain_state, state_change=state_change, secrethash=UNIT_SECRETHASH) msg = "ReceiveLockExpired should have cleared the task" assert UNIT_SECRETHASH not in chain_state.payment_mapping.secrethashes_to_task, msg assert len( transition_result.events), "ReceiveLockExpired should generate events" assert transition_result.new_state == chain_state
def handle_offchain_secretreveal( target_state: TargetTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ): """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = is_valid_secret_reveal( state_change=state_change, transfer_secrethash=target_state.transfer.lock.secrethash, secret=state_change.secret, ) has_transfer_expired = channel.transfer_expired( transfer=target_state.transfer, affected_channel=channel_state, block_number=block_number, ) if valid_secret and not has_transfer_expired: channel.register_offchain_secret( channel_state=channel_state, secret=state_change.secret, secrethash=state_change.secrethash, ) route = target_state.route message_identifier = message_identifier_from_prng(pseudo_random_generator) target_state.state = TargetTransferState.OFFCHAIN_SECRET_REVEAL target_state.secret = state_change.secret recipient = route.node_address reveal = SendSecretReveal( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=target_state.secret, ) iteration = TransitionResult(target_state, [reveal]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def handle_inittarget( state_change, channel_state, pseudo_random_generator, block_number, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_address is_valid, _, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) safe_to_wait = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # if there is not enough time to safely withdraw the token on-chain # silently let the transfer expire. if is_valid and safe_to_wait: message_identifier = message_identifier_from_prng( pseudo_random_generator) recipient = transfer.initiator queue_name = b'global' secret_request = SendSecretRequest( recipient, queue_name, message_identifier, transfer.payment_identifier, transfer.lock.amount, transfer.lock.secrethash, ) iteration = TransitionResult(target_state, [secret_request]) else: if not is_valid: failure_reason = errormsg elif not safe_to_wait: failure_reason = 'lock expiration is not safe' withdraw_failed = EventWithdrawFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=failure_reason, ) iteration = TransitionResult(target_state, [withdraw_failed]) return iteration
def handle_inittarget( state_change, channel_state, pseudo_random_generator, block_number, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, _, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) safe_to_wait, unsafe_msg = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # if there is not enough time to safely unlock the token on-chain # silently let the transfer expire. if is_valid and safe_to_wait: message_identifier = message_identifier_from_prng( pseudo_random_generator) recipient = transfer.initiator secret_request = SendSecretRequest( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, ) iteration = TransitionResult(target_state, [secret_request]) else: if not is_valid: failure_reason = errormsg elif not safe_to_wait: failure_reason = unsafe_msg unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=failure_reason, ) iteration = TransitionResult(target_state, [unlock_failed]) return iteration
def handle_onchain_secretreveal( target_state: TargetTransferState, state_change: ContractReceiveSecretReveal, channel_state: NettingChannelState, ): """ Validates and handles a ContractReceiveSecretReveal state change. """ valid_secret = state_change.secrethash == target_state.transfer.lock.secrethash if valid_secret: channel.register_onchain_secret( channel_state=channel_state, secret=state_change.secret, secrethash=state_change.secrethash, secret_reveal_block_number=state_change.block_number, ) target_state.state = 'reveal_secret' target_state.secret = state_change.secret return TransitionResult(target_state, list())
def handle_offchain_secretreveal( target_state: TargetTransferState, state_change: ReceiveSecretReveal, channel_state: NettingChannelState, pseudo_random_generator: random.Random, ): """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = state_change.secrethash == target_state.transfer.lock.secrethash if valid_secret: channel.register_offchain_secret( channel_state=channel_state, secret=state_change.secret, secrethash=state_change.secrethash, ) route = target_state.route message_identifier = message_identifier_from_prng( pseudo_random_generator) target_state.state = 'reveal_secret' target_state.secret = state_change.secret recipient = route.node_address reveal = SendSecretReveal( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=target_state.secret, ) iteration = TransitionResult(target_state, [reveal]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def handle_onchain_secretreveal( target_state: TargetTransferState, state_change: ContractReceiveSecretReveal, channel_state: NettingChannelState, ): """ Validates and handles a ContractReceiveSecretReveal state change. """ valid_secret = is_valid_secret_reveal( state_change=state_change, transfer_secrethash=target_state.transfer.lock.secrethash, secret=state_change.secret, ) if valid_secret: channel.register_onchain_secret( channel_state=channel_state, secret=state_change.secret, secrethash=state_change.secrethash, secret_reveal_block_number=state_change.block_number, ) target_state.state = TargetTransferState.ONCHAIN_UNLOCK target_state.secret = state_change.secret return TransitionResult(target_state, list())
def test_events_for_onchain_secretreveal(): """ Secret must be registered on-chain when the unsafe region is reached and the secret is known. """ amount = 3 block_number = 10 expiration = block_number + 30 initiator = HOP1 target_address = UNIT_TRANSFER_TARGET from_channel = factories.make_channel( our_address=target_address, partner_address=UNIT_TRANSFER_SENDER, partner_balance=amount, ) from_route = factories.route_from_channel(from_channel) from_transfer = factories.make_signed_transfer_for( from_channel, amount, initiator, target_address, expiration, UNIT_SECRET, ) channel.handle_receive_lockedtransfer( from_channel, from_transfer, ) channel.register_offchain_secret(from_channel, UNIT_SECRET, UNIT_SECRETHASH) safe_to_wait = expiration - from_channel.reveal_timeout - 1 unsafe_to_wait = expiration - from_channel.reveal_timeout state = TargetTransferState(from_route, from_transfer) events = target.events_for_onchain_secretreveal(state, from_channel, safe_to_wait) assert not events events = target.events_for_onchain_secretreveal(state, from_channel, unsafe_to_wait) assert events assert isinstance(events[0], ContractSendSecretReveal) assert events[0].secret == UNIT_SECRET
def handle_inittarget(state_change, channel_state, block_number): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route target_state = TargetTransferState( route, transfer, ) assert channel_state.identifier == transfer.balance_proof.channel_address is_valid, errormsg = channel.handle_receive_mediatedtransfer( channel_state, transfer, ) safe_to_wait = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # if there is not enough time to safely withdraw the token on-chain # silently let the transfer expire. if is_valid and safe_to_wait: secret_request = SendSecretRequest( transfer.identifier, transfer.lock.amount, transfer.lock.hashlock, transfer.initiator, ) iteration = TransitionResult(target_state, [secret_request]) else: if not is_valid: failure_reason = errormsg elif not safe_to_wait: failure_reason = 'lock expiration is not safe' withdraw_failed = EventWithdrawFailed( identifier=transfer.identifier, hashlock=transfer.lock.hashlock, reason=failure_reason, ) iteration = TransitionResult(target_state, [withdraw_failed]) return iteration
def test_events_for_close(): """ Channel must be closed when the unsafe region is reached and the secret is known. """ amount = 3 block_number = 10 expiration = block_number + 30 initiator = HOP1 target_address = UNIT_TRANSFER_TARGET from_channel = factories.make_channel( our_address=target_address, partner_address=UNIT_TRANSFER_SENDER, partner_balance=amount, ) from_route = factories.route_from_channel(from_channel) from_transfer = factories.make_signed_transfer_for( from_channel, amount, initiator, target_address, expiration, UNIT_SECRET, ) channel.handle_receive_mediatedtransfer( from_channel, from_transfer, ) channel.register_secret(from_channel, UNIT_SECRET, UNIT_HASHLOCK) safe_to_wait = expiration - from_channel.reveal_timeout - 1 unsafe_to_wait = expiration - from_channel.reveal_timeout state = TargetTransferState(from_route, from_transfer) events = target.events_for_close2(state, from_channel, safe_to_wait) assert not events events = target.events_for_close2(state, from_channel, unsafe_to_wait) assert events assert isinstance(events[0], ContractSendChannelClose) assert events[0].channel_identifier == from_route.channel_identifier
def handle_block( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: BlockNumber, block_hash: BlockHash, ) -> TransitionResult[TargetTransferState]: """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer events = list() lock = transfer.lock secret_known = channel.is_secret_known( channel_state.partner_state, lock.secrethash, ) lock_has_expired, _ = channel.is_lock_expired( end_state=channel_state.our_state, lock=lock, block_number=block_number, lock_expiration_threshold=channel.get_receiver_expiration_threshold( lock), ) if lock_has_expired and target_state.state != 'expired': failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=f'lock expired', ) target_state.state = TargetTransferState.EXPIRED events = [failed] elif secret_known: events = events_for_onchain_secretreveal( target_state=target_state, channel_state=channel_state, block_number=block_number, block_hash=block_hash, ) return TransitionResult(target_state, events)
def handle_block( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: typing.BlockNumber, ): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer events = list() lock = transfer.lock secret_known = channel.is_secret_known( channel_state.partner_state, lock.secrethash, ) lock_has_expired, _ = channel.is_lock_expired( end_state=channel_state.our_state, lock=lock, block_number=block_number, lock_expiration_threshold=typing.BlockNumber( lock.expiration + DEFAULT_NUMBER_OF_BLOCK_CONFIRMATIONS, ), ) if lock_has_expired and target_state.state != 'expired': failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=f'lock expired', ) target_state.state = 'expired' events = [failed] elif secret_known: events = events_for_onchain_secretreveal( target_state, channel_state, block_number, ) return TransitionResult(target_state, events)
def handle_block( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: BlockNumber, ): """ After Raiden learns about a new block this function must be called to handle expiration of the hash time lock. """ transfer = target_state.transfer events = list() lock = transfer.lock secret_known = channel.is_secret_known( channel_state.partner_state, lock.secrethash, ) lock_has_expired, _ = channel.is_lock_expired( end_state=channel_state.our_state, lock=lock, block_number=block_number, lock_expiration_threshold=channel.get_receiver_expiration_threshold(lock), ) if lock_has_expired and target_state.state != 'expired': failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=f'lock expired', ) target_state.state = TargetTransferState.EXPIRED events = [failed] elif secret_known: events = events_for_onchain_secretreveal( target_state, channel_state, block_number, ) return TransitionResult(target_state, events)
def events_for_onchain_secretreveal( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: BlockNumber, ): """ Emits the event for revealing the secret on-chain if the transfer can not be settled off-chain. """ transfer = target_state.transfer expiration = transfer.lock.expiration safe_to_wait, _ = is_safe_to_wait( expiration, channel_state.reveal_timeout, block_number, ) secret_known_offchain = channel.is_secret_known_offchain( channel_state.partner_state, transfer.lock.secrethash, ) has_onchain_reveal_started = ( target_state.state == TargetTransferState.ONCHAIN_SECRET_REVEAL ) if not safe_to_wait and secret_known_offchain and not has_onchain_reveal_started: target_state.state = TargetTransferState.ONCHAIN_SECRET_REVEAL secret = channel.get_secret( channel_state.partner_state, transfer.lock.secrethash, ) return secret_registry.events_for_onchain_secretreveal( channel_state, secret, expiration, ) return list()
def events_for_onchain_secretreveal( target_state: TargetTransferState, channel_state: NettingChannelState, block_number: BlockNumber, ): """ Emits the event for revealing the secret on-chain if the transfer can not be settled off-chain. """ transfer = target_state.transfer expiration = transfer.lock.expiration safe_to_wait, _ = is_safe_to_wait( expiration, channel_state.reveal_timeout, block_number, ) secret_known_offchain = channel.is_secret_known_offchain( channel_state.partner_state, transfer.lock.secrethash, ) has_onchain_reveal_started = ( target_state.state == TargetTransferState.ONCHAIN_SECRET_REVEAL) if not safe_to_wait and secret_known_offchain and not has_onchain_reveal_started: target_state.state = TargetTransferState.ONCHAIN_SECRET_REVEAL secret = channel.get_secret( channel_state.partner_state, transfer.lock.secrethash, ) return secret_registry.events_for_onchain_secretreveal( channel_state, secret, expiration, ) return list()
def handle_offchain_secretreveal_light( target_state: TargetTransferState, state_change: ReceiveSecretRevealLight, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, ) -> TransitionResult[TargetTransferState]: """ Validates and handles a ReceiveSecretReveal state change. """ valid_secret = is_valid_secret_reveal( state_change=state_change, transfer_secrethash=target_state.transfer.lock.secrethash, secret=state_change.secret, ) has_transfer_expired = channel.is_transfer_expired( transfer=target_state.transfer, affected_channel=channel_state, block_number=block_number ) if valid_secret and not has_transfer_expired: # TODO mmarcosmartinez7 this cannot be done without LC interaction # channel.register_offchain_secret( # channel_state=channel_state, # secret=state_change.secret, # secrethash=state_change.secrethash, # ) route = target_state.route message_identifier = message_identifier_from_prng(pseudo_random_generator) target_state.state = TargetTransferState.OFFCHAIN_SECRET_REVEAL target_state.secret = state_change.secret recipient = route.node_address # Store reveal secret 7, create reveal secret 9 and store it for LC signing. received_reveal_secret = state_change.secret_reveal_message reveal_secret_to_send_event = SendSecretReveal( recipient=recipient, channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, secret=target_state.secret, ) reveal_secret_to_send_msg = message_from_sendevent(reveal_secret_to_send_event) store_received_reveal = StoreMessageEvent( received_reveal_secret.message_identifier, target_state.transfer.payment_identifier, 7, received_reveal_secret, True ) store_reveal_to_send = StoreMessageEvent( message_identifier, target_state.transfer.payment_identifier, 9, reveal_secret_to_send_msg, False ) iteration = TransitionResult(target_state, [store_received_reveal, store_reveal_to_send]) else: # TODO: event for byzantine behavior iteration = TransitionResult(target_state, list()) return iteration
def handle_inittarget( state_change: ActionInitTarget, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: typing.BlockNumber, ): """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, channel_events, errormsg = channel.handle_receive_lockedtransfer( channel_state, transfer, ) if is_valid: # A valid balance proof does not mean the payment itself is still valid. # e.g. the lock may be near expiration or have expired. This is fine. The # message with an unusable lock must be handled to properly synchronize the # local view of the partner's channel state, allowing the next balance # proofs to be handled. This however, must only be done once, which is # enforced by the nonce increasing sequentially, which is verified by # the handler handle_receive_lockedtransfer. target_state = TargetTransferState(route, transfer) safe_to_wait, _ = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number, ) # If there is not enough time to safely unlock the lock on-chain # silently let the transfer expire. The target task must be created to # handle the ReceiveLockExpired state change, which will clear the # expired lock. if safe_to_wait: message_identifier = message_identifier_from_prng( pseudo_random_generator) recipient = transfer.initiator secret_request = SendSecretRequest( recipient=typing.Address(recipient), channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, ) channel_events.append(secret_request) iteration = TransitionResult(target_state, channel_events) else: # If the balance proof is not valid, do *not* create a task. Otherwise it's # possible for an attacker to send multiple invalid transfers, and increase # the memory usage of this Node. unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=errormsg, ) channel_events.append(unlock_failed) iteration = TransitionResult(None, channel_events) return iteration
def handle_inittarget_light( state_change: ActionInitTargetLight, channel_state: NettingChannelState, pseudo_random_generator: random.Random, block_number: BlockNumber, storage ) -> TransitionResult[TargetTransferState]: """ Handles an ActionInitTarget state change. """ transfer = state_change.transfer route = state_change.route assert channel_state.identifier == transfer.balance_proof.channel_identifier is_valid, channel_events, errormsg, handle_invoice_result = channel.handle_receive_lockedtransfer_light( channel_state, transfer, storage ) if is_valid: # A valid balance proof does not mean the payment itself is still valid. # e.g. the lock may be near expiration or have expired. This is fine. The # message with an unusable lock must be handled to properly synchronize the # local view of the partner's channel state, allowing the next balance # proofs to be handled. This however, must only be done once, which is # enforced by the nonce increasing sequentially, which is verified by # the handler handle_receive_lockedtransfer. target_state = TargetTransferState(route, transfer) safe_to_wait, _ = is_safe_to_wait( transfer.lock.expiration, channel_state.reveal_timeout, block_number ) # If there is not enough time to safely unlock the lock on-chain # silently let the transfer expire. The target task must be created to # handle the ReceiveLockExpired state change, which will clear the # expired lock. # # We add a new validation. # It is verified that if there was an invoice it was paid successfully, # if it was not, the payment is interrupted # by not generating an event send secret request if safe_to_wait and handle_invoice_result['is_valid']: payment = LightClientPayment( state_change.transfer.target, state_change.transfer.initiator, False, channel_state.token_network_identifier, transfer.lock.amount, str(date.today()), LightClientPaymentStatus.Pending, transfer.payment_identifier ) payment_exists = LightClientService.get_light_client_payment(payment.payment_id, storage) if not payment_exists: LightClientMessageHandler.store_light_client_payment(payment, storage) message_identifier = message_identifier_from_prng(pseudo_random_generator) recipient = transfer.initiator secret_request = SendSecretRequest( recipient=Address(recipient), channel_identifier=CHANNEL_IDENTIFIER_GLOBAL_QUEUE, message_identifier=message_identifier, payment_identifier=transfer.payment_identifier, amount=transfer.lock.amount, expiration=transfer.lock.expiration, secrethash=transfer.lock.secrethash, ) store_locked_transfer_event = StoreMessageEvent(transfer.message_identifier, transfer.payment_identifier, 1, state_change.signed_lockedtransfer, True) secret_request_message = SecretRequest.from_event(secret_request) store_secret_request_event = StoreMessageEvent(message_identifier, transfer.payment_identifier, 5, secret_request_message, False) channel_events.append(store_secret_request_event) channel_events.append(store_locked_transfer_event) iteration = TransitionResult(target_state, channel_events) else: # If the balance proof is not valid, do *not* create a task. Otherwise it's # possible for an attacker to send multiple invalid transfers, and increase # the memory usage of this Node. assert errormsg, "handle_receive_lockedtransfer should return error msg if not valid" unlock_failed = EventUnlockClaimFailed( identifier=transfer.payment_identifier, secrethash=transfer.lock.secrethash, reason=errormsg, ) channel_events.append(unlock_failed) iteration = TransitionResult(None, channel_events) return iteration