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_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
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
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
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
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 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_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
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
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)
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_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_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