def test_event_operators(): a = EventTransferSentSuccess(2) b = EventTransferSentSuccess(2) c = EventTransferSentSuccess(3) assert a == b assert not a != b assert a != c assert not a == c a = EventTransferSentFailed(2, 'BECAUSE') b = EventTransferSentFailed(2, 'BECAUSE') c = EventTransferSentFailed(3, 'UNKNOWN') assert a == b assert not a != b assert a != c assert not a == c a = EventTransferReceivedSuccess(2, 5, sha3('initiator')) b = EventTransferReceivedSuccess(2, 5, sha3('initiator')) c = EventTransferReceivedSuccess(3, 5, sha3('initiator')) d = EventTransferReceivedSuccess(3, 5, sha3('other initiator')) assert a == b assert not a != b assert a != c assert not a == c assert c != d assert not c == d
def test_event_operators(): a = EventTransferSentSuccess(2) b = EventTransferSentSuccess(2) c = EventTransferSentSuccess(3) assert a == b assert not a != b assert a != c assert not a == c a = EventTransferSentFailed(2, 'BECAUSE') b = EventTransferSentFailed(2, 'BECAUSE') c = EventTransferSentFailed(3, 'UNKNOWN') assert a == b assert not a != b assert a != c assert not a == c a = EventTransferReceivedSuccess(2) b = EventTransferReceivedSuccess(2) c = EventTransferReceivedSuccess(3) assert a == b assert not a != b assert a != c assert not a == c
def test_event_operators(): a = EventTransferSentSuccess(2, 5, sha3(b'target')) b = EventTransferSentSuccess(2, 5, sha3(b'target')) c = EventTransferSentSuccess(3, 4, sha3(b'target')) d = EventTransferSentSuccess(3, 4, sha3(b'differenttarget')) # pylint: disable=unneeded-not assert a == b assert not a != b assert a != c assert not a == c assert not c == d a = EventTransferSentFailed(2, 'BECAUSE') b = EventTransferSentFailed(2, 'BECAUSE') c = EventTransferSentFailed(3, 'UNKNOWN') assert a == b assert not a != b assert a != c assert not a == c a = EventTransferReceivedSuccess(2, 5, sha3(b'initiator')) b = EventTransferReceivedSuccess(2, 5, sha3(b'initiator')) c = EventTransferReceivedSuccess(3, 5, sha3(b'initiator')) d = EventTransferReceivedSuccess(3, 5, sha3(b'other initiator')) assert a == b assert not a != b assert a != c assert not a == c assert c != d assert not c == d
def handle_send_directtransfer( channel_state, state_change, pseudo_random_generator, ): events = list() amount = state_change.amount payment_identifier = state_change.payment_identifier distributable_amount = get_distributable(channel_state.our_state, channel_state.partner_state) is_open = get_status(channel_state) == CHANNEL_STATE_OPENED is_valid = amount > 0 can_pay = amount <= distributable_amount if is_open and is_valid and can_pay: message_identifier = message_identifier_from_prng( pseudo_random_generator) direct_transfer = send_directtransfer( state_change.payment_network_identifier, channel_state, amount, message_identifier, payment_identifier, ) events.append(direct_transfer) else: if not is_open: failure = EventTransferSentFailed(payment_identifier, 'Channel is not opened') events.append(failure) elif not is_valid: msg = 'Transfer amount is invalid. Transfer: {}'.format(amount) failure = EventTransferSentFailed(payment_identifier, msg) events.append(failure) elif not can_pay: msg = ('Transfer amount exceeds the available capacity. ' 'Capacity: {}, Transfer: {}').format( distributable_amount, amount, ) failure = EventTransferSentFailed(payment_identifier, msg) events.append(failure) return TransitionResult(channel_state, events)
def handle_action_transfer_direct( token_network_state, state_change, pseudo_random_generator, block_number, ): receiver_address = state_change.receiver_address channel_state = token_network_state.partneraddresses_to_channels.get( receiver_address) if channel_state: iteration = channel.state_transition( channel_state, state_change, pseudo_random_generator, block_number, ) events = iteration.events else: failure = EventTransferSentFailed( state_change.identifier, 'Unknown partner channel', ) events = [failure] return TransitionResult(token_network_state, events)
def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, pseudo_random_generator: random.Random, ) -> TransitionResult: request_from_target = ( state_change.sender == initiator_state.transfer_description.target and state_change.secrethash == initiator_state.transfer_description.secrethash ) is_valid_payment_id = ( state_change.payment_identifier == initiator_state.transfer_description.payment_identifier ) valid_secretrequest = ( request_from_target and is_valid_payment_id and state_change.amount == initiator_state.transfer_description.amount ) invalid_secretrequest = request_from_target and ( is_valid_payment_id or state_change.amount != initiator_state.transfer_description.amount ) if valid_secretrequest: # Reveal the secret to the target node and wait for its confirmation. # At this point the transfer is not cancellable anymore as either the lock # timeouts or a secret reveal is received. # # Note: The target might be the first hop # message_identifier = message_identifier_from_prng(pseudo_random_generator) transfer_description = initiator_state.transfer_description recipient = transfer_description.target queue_name = b'global' revealsecret = SendRevealSecret( recipient, queue_name, message_identifier, transfer_description.secret, transfer_description.token, ) initiator_state.revealsecret = revealsecret iteration = TransitionResult(initiator_state, [revealsecret]) elif invalid_secretrequest: cancel = EventTransferSentFailed( identifier=initiator_state.transfer_description.payment_identifier, reason='bad secret request message from target', ) iteration = TransitionResult(None, [cancel]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def test_write_read_events(tmpdir, in_memory_database): log = init_database(tmpdir, in_memory_database) event = EventTransferSentFailed(1, 'whatever') with pytest.raises(sqlite3.IntegrityError): log.storage.write_state_events(1, [(None, 1, 1, log.serializer.serialize(event))]) assert(len(get_all_state_events(log)) == 0) log.storage.write_state_change('statechangedata') log.storage.write_state_events(1, [(None, 1, 1, log.serializer.serialize(event))]) logged_events = get_all_state_events(log) assert(len(logged_events) == 1) assert(logged_events[0].identifier == 1) assert(logged_events[0].state_change_id == 1) assert(isinstance(logged_events[0].event_object, EventTransferSentFailed))
def handle_secretrequest( initiator_state: InitiatorTransferState, state_change: ReceiveSecretRequest, ) -> TransitionResult: request_from_target = (state_change.sender == initiator_state.transfer_description.target and state_change.hashlock == initiator_state.transfer_description.hashlock) valid_secretrequest = (request_from_target and state_change.identifier == initiator_state.transfer_description.identifier and state_change.amount == initiator_state.transfer_description.amount) invalid_secretrequest = request_from_target and ( state_change.identifier != initiator_state.transfer_description.identifier or state_change.amount != initiator_state.transfer_description.amount) if valid_secretrequest: # Reveal the secret to the target node and wait for its confirmation. # At this point the transfer is not cancellable anymore as either the lock # timeouts or a secret reveal is received. # # Note: The target might be the first hop # transfer_description = initiator_state.transfer_description reveal_secret = SendRevealSecret( transfer_description.identifier, transfer_description.secret, transfer_description.token, transfer_description.target, ) initiator_state.revealsecret = reveal_secret iteration = TransitionResult(initiator_state, [reveal_secret]) elif invalid_secretrequest: cancel = EventTransferSentFailed( identifier=initiator_state.transfer_description.identifier, reason='bad secret request message from target', ) iteration = TransitionResult(None, [cancel]) else: iteration = TransitionResult(initiator_state, list()) return iteration
def try_new_route( registry_address: typing.Address, channelidentifiers_to_channels: ChannelMap, available_routes: typing.List[RouteState], transfer_description: TransferDescriptionWithSecretState, pseudo_random_generator: random.Random, block_number: typing.BlockNumber, ) -> TransitionResult: channel_state = next_channel_from_routes( available_routes, channelidentifiers_to_channels, transfer_description.amount, ) events = list() if channel_state is None: if not available_routes: reason = 'there is no route available' else: reason = 'none of the available routes could be used' transfer_failed = EventTransferSentFailed( identifier=transfer_description.payment_identifier, reason=reason, ) events.append(transfer_failed) initiator_state = None else: initiator_state = InitiatorTransferState( transfer_description, channel_state.identifier, ) message_identifier = message_identifier_from_prng(pseudo_random_generator) lockedtransfer_event = send_lockedtransfer( registry_address, initiator_state, channel_state, message_identifier, block_number, ) assert lockedtransfer_event events.append(lockedtransfer_event) return TransitionResult(initiator_state, events)
def handle_cancelpayment(payment_state): """ Cancel the payment. """ assert can_cancel( payment_state), 'Cannot cancel a transfer after the secret is revealed' transfer_description = payment_state.initiator.transfer_description cancel_events = cancel_current_route(payment_state) cancel = EventTransferSentFailed( identifier=transfer_description.identifier, reason='user canceled transfer', ) cancel_events.append(cancel) return TransitionResult(None, cancel_events)
def user_cancel_transfer(state): """ Cancel the current in-transit message. """ assert state.revealsecret is None, 'cannot cancel a transfer with a RevealSecret in flight' state.transfer.secret = None state.transfer.hashlock = None state.message = None state.route = None state.secretrequest = None state.revealsecret = None cancel = EventTransferSentFailed( identifier=state.transfer.identifier, reason='user canceled transfer', ) iteration = TransitionResult(None, [cancel]) return iteration
def try_new_route( channelidentifiers_to_channels: ChannelMap, available_routes: typing.List[RouteState], transfer_description: TransferDescriptionWithSecretState, block_number: typing.BlockNumber, ) -> TransitionResult: channel_state = next_channel_from_routes( available_routes, channelidentifiers_to_channels, transfer_description.amount, ) events = list() if channel_state is None: if not available_routes: reason = 'there is no route available' else: reason = 'none of the available routes could be used' transfer_failed = EventTransferSentFailed( identifier=transfer_description.identifier, reason=reason, ) events.append(transfer_failed) initiator_state = None else: initiator_state = InitiatorTransferState( transfer_description, channel_state.identifier, ) mediatedtransfer_event = send_mediatedtransfer( initiator_state, channel_state, block_number, ) assert mediatedtransfer_event events.append(mediatedtransfer_event) return TransitionResult(initiator_state, events)
def test_write_read_events(): wal = new_wal() event = EventTransferSentFailed(1, 'whatever') event_list = [event] block_number = 10 with pytest.raises(sqlite3.IntegrityError): unexisting_state_change_id = 1 wal.storage.write_events( unexisting_state_change_id, block_number, event_list, ) previous_events = wal.storage.get_events_by_identifier( from_identifier=0, to_identifier='latest', ) state_change_id = wal.storage.write_state_change('statechangedata') wal.storage.write_events( state_change_id, block_number, event_list, ) new_events = wal.storage.get_events_by_identifier( from_identifier=0, to_identifier='latest', ) assert len(previous_events) + 1 == len(new_events) latest_event = new_events[-1] assert latest_event[0] == block_number assert isinstance(latest_event[1], EventTransferSentFailed)
def try_new_route(state): assert state.route is None, 'cannot try a new route while one is being used' # TODO: # - Route ranking. An upper layer should rate each route to optimize # the fee price/quality of each route and add a rate from in the range # [0.0,1.0]. # - Add in a policy per route: # - filtering, e.g. so the user may have a per route maximum transfer # value based on fixed value or reputation. # - reveal time computation # - These policy details are better hidden from this implementation and # changes should be applied through the use of Route state changes. # Find a single route that may fulfill the request, this uses a single # route intentionally try_route = None while state.routes.available_routes: route = state.routes.available_routes.pop(0) if route.available_balance < state.transfer.amount: state.routes.ignored_routes.append(route) else: try_route = route break unlock_failed = None if state.message: unlock_failed = EventUnlockFailed( identifier=state.transfer.identifier, hashlock=state.transfer.hashlock, reason='route was canceled', ) if try_route is None: # No available route has sufficient balance for the current transfer, # cancel it. # # At this point we can just discard all the state data, this is only # valid because we are the initiator and we know that the secret was # not released. transfer_failed = EventTransferSentFailed( identifier=state.transfer.identifier, reason='no route available', ) events = [transfer_failed] if unlock_failed: events.append(unlock_failed) iteration = TransitionResult(None, events) else: state.route = try_route secret = next(state.random_generator) hashlock = sha3(secret) # The initiator doesn't need to learn the secret, so there is no need # to decrement reveal_timeout from the lock timeout. # # The lock_expiration could be set to a value larger than # settle_timeout, this is not useful since the next hop will take this # channel settle_timeout as an upper limit for expiration. # # The two nodes will most likely disagree on latest block, as far as # the expiration goes this is no problem. lock_expiration = state.block_number + try_route.settle_timeout identifier = state.transfer.identifier transfer = LockedTransferState( identifier, state.transfer.amount, state.transfer.token, state.transfer.initiator, state.transfer.target, lock_expiration, hashlock, secret, ) message = SendMediatedTransfer( transfer.identifier, transfer.token, transfer.amount, transfer.hashlock, state.our_address, transfer.target, lock_expiration, try_route.node_address, ) state.transfer = transfer state.message = message events = [message] if unlock_failed: events.append(unlock_failed) iteration = TransitionResult(state, events) return iteration