def test_write_read_events(tmpdir, in_memory_database): log = init_database(tmpdir, in_memory_database) event = EventTransferFailed(1, 'whatever') with pytest.raises(sqlite3.IntegrityError): log.storage.write_state_events( 1, [(None, 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, log.serializer.serialize(event))]) logged_events = get_all_state_events(log) assert (len(logged_events) == 1) assert (logged_events[0][0] == 1) assert (logged_events[0][1] == 1) assert (isinstance(logged_events[0][2], EventTransferFailed))
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 = EventTransferFailed( identifier=state.transfer.identifier, reason='user canceled transfer', ) iteration = TransitionResult(None, [cancel]) return iteration
def clear_if_finalized(iteration): """ Clear the state if the transfer was either completed or failed. """ state = iteration.state if state.from_transfer.secret is None and state.block_number > state.from_transfer.expiration: failed = EventTransferFailed( identifier=state.transfer.identifier, reason='lock expired', ) iteration = TransitionResult(None, [failed]) elif state.state == 'balance_proof': completed = EventTransferCompleted( state.from_transfer.identifier, state.from_transfer.secret, state.from_transfer.hashlock, ) iteration = TransitionResult(None, completed) return iteration
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 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. cancel = EventTransferFailed( identifier=state.transfer.identifier, reason='no route available', ) iteration = TransitionResult(None, [cancel]) else: state.route = try_route secret = state.random_generator.next() 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. # # A value larger than settle_timeout could be used but wouldn't # improve, since the next hop will take settle_timeout as an upper # limit for expiration. lock_expiration = state.block_number + try_route.settle_timeout identifier = state.transfer.identifier transfer = LockedTransferState( identifier, state.transfer.amount, state.transfer.token, state.transfer.target, lock_expiration, hashlock, secret, ) message = SendMediatedTransfer( transfer.identifier, transfer.token, transfer.amount, transfer.hashlock, transfer.target, lock_expiration, try_route.node_address, ) state.transfer = transfer state.message = message iteration = TransitionResult(state, [message]) return iteration