Esempio n. 1
0
def make_transfer_pair(payer,
                       payee,
                       initiator,
                       target,
                       amount,
                       expiration,
                       secret=None,
                       reveal_timeout=factories.UNIT_REVEAL_TIMEOUT):

    payer_expiration = expiration
    payee_expiration = expiration - reveal_timeout

    return MediationPairState(
        factories.make_route(payer, amount),
        factories.make_transfer(amount,
                                initiator,
                                target,
                                payer_expiration,
                                secret=secret),
        factories.make_route(payee, amount),
        factories.make_transfer(amount,
                                initiator,
                                target,
                                payee_expiration,
                                secret=secret),
    )
Esempio n. 2
0
def test_get_timeout_blocks():
    amount = 10
    initiator = factories.HOP1
    next_hop = factories.HOP2

    settle_timeout = 30
    block_number = 5

    route = factories.make_route(
        next_hop,
        amount,
        settle_timeout=settle_timeout,
    )

    early_expire = 10
    early_transfer = factories.make_transfer(amount, initiator, next_hop,
                                             early_expire)
    early_block = mediator.get_timeout_blocks(route, early_transfer,
                                              block_number)
    assert early_block == 5 - mediator.TRANSIT_BLOCKS, 'must use the lock expiration'

    equal_expire = 30
    equal_transfer = factories.make_transfer(amount, initiator, next_hop,
                                             equal_expire)
    equal_block = mediator.get_timeout_blocks(route, equal_transfer,
                                              block_number)
    assert equal_block == 25 - mediator.TRANSIT_BLOCKS

    large_expire = 70
    large_transfer = factories.make_transfer(amount, initiator, next_hop,
                                             large_expire)
    large_block = mediator.get_timeout_blocks(route, large_transfer,
                                              block_number)
    assert large_block == 30 - mediator.TRANSIT_BLOCKS, 'must use the settle timeout'

    closed_route = factories.make_route(
        next_hop,
        amount,
        settle_timeout=settle_timeout,
        closed_block=2,
    )

    large_block = mediator.get_timeout_blocks(closed_route, large_transfer,
                                              block_number)
    assert large_block == 27 - mediator.TRANSIT_BLOCKS, 'must use the close block'

    # the computed timeout may be negative, in which case the calling code must /not/ use it
    negative_block_number = large_expire
    negative_block = mediator.get_timeout_blocks(route, large_transfer,
                                                 negative_block_number)
    assert negative_block == -mediator.TRANSIT_BLOCKS
def test_events_for_withdraw():
    """ On-chain withdraw must be done if the channel is closed, regardless of
    the unsafe region.
    """
    amount = 3
    expire = 10
    initiator = factories.HOP1

    transfer = factories.make_transfer(
        amount,
        initiator,
        factories.ADDR,
        expire,
        secret=factories.UNIT_SECRET,
    )
    route = factories.make_route(
        initiator,
        amount,
    )

    events = target.events_for_withdraw(
        transfer,
        route,
    )
    assert len(events) == 0

    route.state = CHANNEL_STATE_CLOSED
    events = target.events_for_withdraw(
        transfer,
        route,
    )
    assert isinstance(events[0], ContractSendWithdraw)
    assert events[0].channel_address == route.channel_address
Esempio n. 4
0
def test_init_without_routes():
    amount = factories.UNIT_TRANSFER_AMOUNT
    block_number = 1
    our_address, target_address = factories.HOP1, factories.HOP3
    routes = []

    transfer = factories.make_transfer(
        amount,
        initiator=our_address,
        target=target_address,
        secret=None,
        hashlock=None,
        expiration=None,
    )
    init_state_change = ActionInitInitiator(
        our_address,
        transfer,
        RoutesState(routes),
        SequenceGenerator(),
        block_number,
    )

    initiator_state_machine = StateManager(
        initiator.state_transition,
        None,
    )

    assert initiator_state_machine.current_state is None

    events = initiator_state_machine.dispatch(init_state_change, )

    assert len(events) == 1
    assert any(isinstance(e, EventTransferSentFailed) for e in events)
    assert initiator_state_machine.current_state is None
def make_init_statechange(
        routes,
        target,
        amount=factories.UNIT_TRANSFER_AMOUNT,
        block_number=1,
        our_address=factories.ADDR,
        secret_generator=None,
        identifier=0,
        token=factories.UNIT_TOKEN_ADDRESS):

    if secret_generator is None:
        secret_generator = SequenceGenerator()

    transfer = factories.make_transfer(
        amount,
        initiator=our_address,
        target=target,
        identifier=identifier,
        token=token,
        secret=None,
        hashlock=None,
        expiration=None,
    )

    init_state_change = ActionInitInitiator(
        our_address,
        transfer,
        RoutesState(routes),
        secret_generator,
        block_number,
    )

    return init_state_change
Esempio n. 6
0
def make_init_statechange(routes,
                          target,
                          amount=factories.UNIT_TRANSFER_AMOUNT,
                          block_number=1,
                          our_address=factories.ADDR,
                          secret_generator=None,
                          identifier=0,
                          token=factories.UNIT_TOKEN_ADDRESS):

    if secret_generator is None:
        secret_generator = SequenceGenerator()

    transfer = factories.make_transfer(
        amount,
        initiator=our_address,
        target=target,
        identifier=identifier,
        token=token,
        secret=None,
        hashlock=None,
        expiration=None,
    )

    init_state_change = ActionInitInitiator(
        our_address,
        transfer,
        RoutesState(routes),
        secret_generator,
        block_number,
    )

    return init_state_change
Esempio n. 7
0
def test_events_for_withdraw():
    """ On-chain withdraw must be done if the channel is closed, regardless of
    the unsafe region.
    """
    amount = 3
    expire = 10
    initiator = factories.HOP1

    transfer = factories.make_transfer(
        amount,
        initiator,
        factories.ADDR,
        expire,
        secret=factories.UNIT_SECRET,
    )
    route = factories.make_route(
        initiator,
        amount,
    )

    events = target.events_for_withdraw(
        transfer,
        route,
    )
    assert len(events) == 0

    route.state = CHANNEL_STATE_CLOSED
    events = target.events_for_withdraw(
        transfer,
        route,
    )
    assert isinstance(events[0], ContractSendWithdraw)
    assert events[0].channel_address == route.channel_address
Esempio n. 8
0
def test_refund_transfer_matches_received():
    amount = 30
    expiration = 50

    transfer = factories.make_transfer(
        amount,
        UNIT_TRANSFER_INITIATOR,
        UNIT_TRANSFER_TARGET,
        expiration,
        UNIT_SECRET,
    )

    refund_lower_expiration = factories.make_signed_transfer(
        amount,
        UNIT_TRANSFER_INITIATOR,
        UNIT_TRANSFER_TARGET,
        expiration - 1,
        UNIT_SECRET,
    )

    assert channel.refund_transfer_matches_received(refund_lower_expiration, transfer) is True

    refund_same_expiration = factories.make_signed_transfer(
        amount,
        UNIT_TRANSFER_INITIATOR,
        UNIT_TRANSFER_TARGET,
        expiration,
        UNIT_SECRET,
    )
    assert channel.refund_transfer_matches_received(refund_same_expiration, transfer) is False
Esempio n. 9
0
def make_transfer_from_counter(counter):
    return factories.make_transfer(
        amount=next(counter),
        initiator=factories.make_address(),
        target=factories.make_address(),
        expiration=next(counter),
        secret=factories.make_secret(next(counter)),
    )
Esempio n. 10
0
def make_transfer_from_counter(counter):
    return factories.make_transfer(
        amount=next(counter),
        initiator=factories.make_address(),
        target=factories.make_address(),
        expiration=next(counter),
        secret=factories.make_secret(next(counter)),
    )
def test_get_timeout_blocks():
    amount = 10
    initiator = factories.HOP1
    next_hop = factories.HOP2

    settle_timeout = 30
    block_number = 5

    route = factories.make_route(
        next_hop,
        amount,
        settle_timeout=settle_timeout,
    )

    early_expire = 10
    early_transfer = factories.make_transfer(amount, initiator, next_hop, early_expire)
    early_block = mediator.get_timeout_blocks(route, early_transfer, block_number)
    assert early_block == 5 - mediator.TRANSIT_BLOCKS, 'must use the lock expiration'

    equal_expire = 30
    equal_transfer = factories.make_transfer(amount, initiator, next_hop, equal_expire)
    equal_block = mediator.get_timeout_blocks(route, equal_transfer, block_number)
    assert equal_block == 25 - mediator.TRANSIT_BLOCKS

    large_expire = 70
    large_transfer = factories.make_transfer(amount, initiator, next_hop, large_expire)
    large_block = mediator.get_timeout_blocks(route, large_transfer, block_number)
    assert large_block == 30 - mediator.TRANSIT_BLOCKS, 'must use the settle timeout'

    closed_route = factories.make_route(
        next_hop,
        amount,
        settle_timeout=settle_timeout,
        closed_block=2,
    )

    large_block = mediator.get_timeout_blocks(closed_route, large_transfer, block_number)
    assert large_block == 27 - mediator.TRANSIT_BLOCKS, 'must use the close block'

    # the computed timeout may be negative, in which case the calling code must /not/ use it
    negative_block_number = large_expire
    negative_block = mediator.get_timeout_blocks(route, large_transfer, negative_block_number)
    assert negative_block == -mediator.TRANSIT_BLOCKS
def make_transfer_pair(
        payer,
        payee,
        initiator,
        target,
        amount,
        expiration,
        secret=None,
        reveal_timeout=factories.UNIT_REVEAL_TIMEOUT):

    payer_expiration = expiration
    payee_expiration = expiration - reveal_timeout

    return MediationPairState(
        factories.make_route(payer, amount),
        factories.make_transfer(amount, initiator, target, payer_expiration, secret=secret),
        factories.make_route(payee, amount),
        factories.make_transfer(amount, initiator, target, payee_expiration, secret=secret),
    )
def test_is_lock_valid():
    """ A hash time lock is valid up to the expiraiton block. """
    amount = 10
    expiration = 10
    initiator = factories.HOP1
    target = factories.HOP2
    transfer = factories.make_transfer(amount, initiator, target, expiration)

    assert mediator.is_lock_valid(transfer, 5) is True
    assert mediator.is_lock_valid(transfer, 10) is True, 'lock is expired at the next block'
    assert mediator.is_lock_valid(transfer, 11) is False
def test_is_lock_valid():
    """ A hash time lock is valid up to the expiraiton block. """
    amount = 10
    expiration = 10
    initiator = factories.HOP1
    target = factories.HOP2
    transfer = factories.make_transfer(amount, initiator, target, expiration)

    assert mediator.is_lock_valid(transfer, 5) is True
    assert mediator.is_lock_valid(transfer, 10) is True, 'lock is expired at the next block'
    assert mediator.is_lock_valid(transfer, 11) is False
Esempio n. 15
0
def test_send_refund_transfer_contains_balance_proof():
    recipient = make_address()
    transfer = make_transfer()
    message_identifier = 1
    channel_identifier = make_channel_identifier()
    event = SendRefundTransfer(
        recipient=recipient,
        channel_identifier=channel_identifier,
        message_identifier=message_identifier,
        transfer=transfer,
    )

    assert hasattr(event, 'balance_proof')
    assert SendRefundTransfer.from_dict(event.to_dict()) == event
Esempio n. 16
0
def test_refund_transfer_next_route():
    identifier = 1
    amount = factories.UNIT_TRANSFER_AMOUNT
    block_number = 1
    mediator_address = factories.HOP1
    target_address = factories.HOP2
    our_address = factories.ADDR

    routes = [
        factories.make_route(mediator_address, available_balance=amount),
        factories.make_route(factories.HOP2, available_balance=amount),
    ]
    current_state = make_initiator_state(
        routes,
        target_address,
        block_number=block_number,
        our_address=our_address,
        secret_generator=SequenceGenerator(),
        identifier=identifier,
    )

    transfer = factories.make_transfer(
        amount,
        our_address,
        target_address,
        block_number + factories.UNIT_SETTLE_TIMEOUT,
    )

    state_change = ReceiveTransferRefund(
        sender=mediator_address,
        transfer=transfer,
    )

    prior_state = deepcopy(current_state)

    initiator_state_machine = StateManager(
        initiator.state_transition,
        current_state,
    )
    assert initiator_state_machine.current_state is not None

    events = initiator_state_machine.dispatch(state_change)
    assert len(events) == 1
    assert any(
        isinstance(e, SendMediatedTransfer) for e in events
    ), 'No mediated transfer event emitted, should have tried a new route'

    assert initiator_state_machine.current_state is not None
    assert initiator_state_machine.current_state.routes.canceled_routes[
        0] == prior_state.route
def test_refund_transfer_next_route():
    identifier = 1
    amount = factories.UNIT_TRANSFER_AMOUNT
    block_number = 1
    mediator_address = factories.HOP1
    target_address = factories.HOP2
    our_address = factories.ADDR

    routes = [
        factories.make_route(mediator_address, available_balance=amount),
        factories.make_route(factories.HOP2, available_balance=amount),
    ]
    current_state = make_initiator_state(
        routes,
        target_address,
        block_number=block_number,
        our_address=our_address,
        secret_generator=SequenceGenerator(),
        identifier=identifier,
    )

    transfer = factories.make_transfer(
        amount,
        our_address,
        target_address,
        block_number + factories.UNIT_SETTLE_TIMEOUT,
    )

    state_change = ReceiveTransferRefund(
        sender=mediator_address,
        transfer=transfer,
    )

    prior_state = deepcopy(current_state)

    initiator_state_machine = StateManager(
        initiator.state_transition,
        current_state,
    )
    assert initiator_state_machine.current_state is not None

    events = initiator_state_machine.dispatch(state_change)
    assert len(events) == 1
    assert any(
        isinstance(e, SendMediatedTransfer) for e in events
    ), 'No mediated transfer event emitted, should have tried a new route'

    assert initiator_state_machine.current_state is not None
    assert initiator_state_machine.current_state.routes.canceled_routes[0] == prior_state.route
Esempio n. 18
0
def test_next_transfer_pair():
    timeout_blocks = 47
    block_number = 3
    balance = 10
    initiator = factories.HOP1
    target = factories.ADDR

    payer_route = factories.make_route(initiator, balance)
    payer_transfer = factories.make_transfer(balance,
                                             initiator,
                                             target,
                                             expiration=50)

    routes = [
        factories.make_route(factories.HOP2, available_balance=balance),
    ]
    routes_state = RoutesState(
        list(routes))  # copy because the list will be modified inplace

    pair, events = mediator.next_transfer_pair(
        payer_route,
        payer_transfer,
        routes_state,
        timeout_blocks,
        block_number,
    )

    assert pair.payer_route == payer_route
    assert pair.payer_transfer == payer_transfer
    assert pair.payee_route == routes[0]
    assert pair.payee_transfer.expiration < pair.payer_transfer.expiration

    assert isinstance(events[0], SendMediatedTransfer)
    transfer = events[0]
    assert transfer.identifier == payer_transfer.identifier
    assert transfer.token == payer_transfer.token
    assert transfer.amount == payer_transfer.amount
    assert transfer.hashlock == payer_transfer.hashlock
    assert transfer.initiator == payer_transfer.initiator
    assert transfer.target == payer_transfer.target
    assert transfer.expiration < payer_transfer.expiration
    assert transfer.receiver == pair.payee_route.node_address

    assert len(routes_state.available_routes) == 0
def test_refund_transfer_invalid_sender():
    identifier = 1
    amount = factories.UNIT_TRANSFER_AMOUNT
    block_number = 1
    mediator_address = factories.HOP1
    target_address = factories.HOP2
    our_address = factories.ADDR

    routes = [
        factories.make_route(mediator_address, available_balance=amount),
    ]
    current_state = make_initiator_state(
        routes,
        target_address,
        block_number=block_number,
        our_address=our_address,
        secret_generator=SequenceGenerator(),
        identifier=identifier,
    )

    transfer = factories.make_transfer(
        amount,
        our_address,
        target_address,
        block_number + factories.UNIT_SETTLE_TIMEOUT,
    )

    state_change = ReceiveTransferRefund(
        sender=our_address,
        transfer=transfer,
    )

    prior_state = deepcopy(current_state)

    initiator_state_machine = StateManager(
        initiator.state_transition,
        current_state,
    )
    assert initiator_state_machine.current_state is not None

    events = initiator_state_machine.dispatch(state_change)
    assert len(events) == 0
    assert initiator_state_machine.current_state is not None
    assert_state_equal(initiator_state_machine.current_state, prior_state)
Esempio n. 20
0
def test_refund_transfer_invalid_sender():
    identifier = 1
    amount = factories.UNIT_TRANSFER_AMOUNT
    block_number = 1
    mediator_address = factories.HOP1
    target_address = factories.HOP2
    our_address = factories.ADDR

    routes = [
        factories.make_route(mediator_address, available_balance=amount),
    ]
    current_state = make_initiator_state(
        routes,
        target_address,
        block_number=block_number,
        our_address=our_address,
        secret_generator=SequenceGenerator(),
        identifier=identifier,
    )

    transfer = factories.make_transfer(
        amount,
        our_address,
        target_address,
        block_number + factories.UNIT_SETTLE_TIMEOUT,
    )

    state_change = ReceiveTransferRefund(
        sender=our_address,
        transfer=transfer,
    )

    prior_state = deepcopy(current_state)

    initiator_state_machine = StateManager(
        initiator.state_transition,
        current_state,
    )
    assert initiator_state_machine.current_state is not None

    events = initiator_state_machine.dispatch(state_change)
    assert len(events) == 0
    assert initiator_state_machine.current_state is not None
    assert_state_equal(initiator_state_machine.current_state, prior_state)
Esempio n. 21
0
def test_is_safe_to_wait():
    """ It's safe to wait for a secret while there are more than reveal timeout
    blocks until the lock expiration.
    """
    amount = 10
    expiration = 40
    initiator = factories.HOP1
    target = factories.HOP2
    transfer = factories.make_transfer(amount, initiator, target, expiration)

    # expiration is in 30 blocks, 19 blocks safe for waiting
    block_number = 10
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout,
                                    block_number) is True

    # expiration is in 30 blocks, 09 blocks safe for waiting
    block_number = 20
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout,
                                    block_number) is True

    # expiration is in 30 blocks, 1 block safe for waiting
    block_number = 29
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout,
                                    block_number) is True

    # at the block 30 it's not safe to wait anymore
    block_number = 30
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout,
                                    block_number) is False

    block_number = 40
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout,
                                    block_number) is False

    block_number = 50
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout,
                                    block_number) is False
def test_events_for_refund():
    amount = 10
    expiration = 30
    reveal_timeout = 17
    timeout_blocks = expiration
    block_number = 1
    initiator = factories.HOP1
    target = factories.HOP6

    refund_route = factories.make_route(
        initiator,
        amount,
        reveal_timeout=reveal_timeout,
    )

    refund_transfer = factories.make_transfer(
        amount,
        initiator,
        target,
        expiration,
    )

    small_timeout_blocks = reveal_timeout
    small_refund_events = mediator.events_for_refund_transfer(
        refund_route,
        refund_transfer,
        small_timeout_blocks,
        block_number,
    )
    assert len(small_refund_events) == 0

    refund_events = mediator.events_for_refund_transfer(
        refund_route,
        refund_transfer,
        timeout_blocks,
        block_number,
    )
    assert refund_events[0].expiration < block_number + timeout_blocks
    assert refund_events[0].amount == amount
    assert refund_events[0].hashlock == refund_transfer.hashlock
    assert refund_events[0].receiver == refund_route.node_address
def test_events_for_refund():
    amount = 10
    expiration = 30
    reveal_timeout = 17
    timeout_blocks = expiration
    block_number = 1
    initiator = factories.HOP1
    target = factories.HOP6

    refund_route = factories.make_route(
        initiator,
        amount,
        reveal_timeout=reveal_timeout,
    )

    refund_transfer = factories.make_transfer(
        amount,
        initiator,
        target,
        expiration,
    )

    small_timeout_blocks = reveal_timeout
    small_refund_events = mediator.events_for_refund_transfer(
        refund_route,
        refund_transfer,
        small_timeout_blocks,
        block_number,
    )
    assert len(small_refund_events) == 0

    refund_events = mediator.events_for_refund_transfer(
        refund_route,
        refund_transfer,
        timeout_blocks,
        block_number,
    )
    assert refund_events[0].expiration < block_number + timeout_blocks
    assert refund_events[0].amount == amount
    assert refund_events[0].hashlock == refund_transfer.hashlock
    assert refund_events[0].receiver == refund_route.node_address
Esempio n. 24
0
def test_refund_transfer_does_not_match_received():
    amount = 30
    expiration = 50
    target = UNIT_TRANSFER_SENDER
    transfer = factories.make_transfer(
        amount,
        UNIT_TRANSFER_INITIATOR,
        target,
        expiration,
        UNIT_SECRET,
    )

    refund_from_target = factories.make_signed_transfer(
        amount,
        UNIT_TRANSFER_INITIATOR,
        UNIT_TRANSFER_TARGET,
        expiration - 1,
        UNIT_SECRET,
    )
    # target cannot refund
    assert not channel.refund_transfer_matches_received(refund_from_target, transfer)
def test_next_transfer_pair():
    timeout_blocks = 47
    block_number = 3
    balance = 10
    initiator = factories.HOP1
    target = factories.ADDR

    payer_route = factories.make_route(initiator, balance)
    payer_transfer = factories.make_transfer(balance, initiator, target, expiration=50)

    routes = [
        factories.make_route(factories.HOP2, available_balance=balance),
    ]
    routes_state = RoutesState(list(routes))  # copy because the list will be modified inplace

    pair, events = mediator.next_transfer_pair(
        payer_route,
        payer_transfer,
        routes_state,
        timeout_blocks,
        block_number,
    )

    assert pair.payer_route == payer_route
    assert pair.payer_transfer == payer_transfer
    assert pair.payee_route == routes[0]
    assert pair.payee_transfer.expiration < pair.payer_transfer.expiration

    assert isinstance(events[0], SendMediatedTransfer)
    transfer = events[0]
    assert transfer.identifier == payer_transfer.identifier
    assert transfer.token == payer_transfer.token
    assert transfer.amount == payer_transfer.amount
    assert transfer.hashlock == payer_transfer.hashlock
    assert transfer.initiator == payer_transfer.initiator
    assert transfer.target == payer_transfer.target
    assert transfer.expiration < payer_transfer.expiration
    assert transfer.receiver == pair.payee_route.node_address

    assert len(routes_state.available_routes) == 0
def test_is_safe_to_wait():
    """ It's safe to wait for a secret while there are more than reveal timeout
    blocks until the lock expiration.
    """
    amount = 10
    expiration = 40
    initiator = factories.HOP1
    target = factories.HOP2
    transfer = factories.make_transfer(amount, initiator, target, expiration)

    # expiration is in 30 blocks, 19 blocks safe for waiting
    block_number = 10
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is True

    # expiration is in 30 blocks, 09 blocks safe for waiting
    block_number = 20
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is True

    # expiration is in 30 blocks, 1 block safe for waiting
    block_number = 29
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is True

    # at the block 30 it's not safe to wait anymore
    block_number = 30
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is False

    block_number = 40
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is False

    block_number = 50
    reveal_timeout = 10
    assert mediator.is_safe_to_wait(transfer, reveal_timeout, block_number) is False
def test_init_without_routes():
    amount = factories.UNIT_TRANSFER_AMOUNT
    block_number = 1
    our_address, target_address = factories.HOP1, factories.HOP3
    routes = []

    transfer = factories.make_transfer(
        amount,
        initiator=our_address,
        target=target_address,
        secret=None,
        hashlock=None,
        expiration=None,
    )
    init_state_change = ActionInitInitiator(
        our_address,
        transfer,
        RoutesState(routes),
        SequenceGenerator(),
        block_number,
    )

    initiator_state_machine = StateManager(
        initiator.state_transition,
        None,
    )

    assert initiator_state_machine.current_state is None

    events = initiator_state_machine.dispatch(
        init_state_change,
    )

    assert len(events) == 1
    assert any(isinstance(e, EventTransferSentFailed) for e in events)
    assert initiator_state_machine.current_state is None