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_close(state, from_channel, safe_to_wait) assert not events events = target.events_for_close(state, from_channel, unsafe_to_wait) assert events assert isinstance(events[0], ContractSendChannelClose) assert events[0].channel_identifier == from_route.channel_identifier
def test_channelstate_withdraw(): """Event close must be properly handled if there are no locks to unlock""" our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) payment_network_identifier = factories.make_address() lock_amount = 10 lock_expiration = 100 lock_secret = sha3(b'test_channelstate_mediatedtransfer_overspent') lock_hashlock = sha3(lock_secret) lock = HashTimeLockState( lock_amount, lock_expiration, lock_hashlock, ) nonce = 1 transferred_amount = 0 receive_mediatedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, msg = channel.handle_receive_mediatedtransfer( channel_state, receive_mediatedtransfer, ) assert is_valid, msg channel.register_secret(channel_state, lock_secret, lock_hashlock) # If the channel is closed, withdraw must be done even if the lock is not # at risk of expiring closed_block_number = lock_expiration - channel_state.reveal_timeout - 1 state_change = ContractReceiveChannelClosed( payment_network_identifier, channel_state.token_address, channel_state.identifier, partner_model1.participant_address, closed_block_number, ) iteration = channel.handle_channel_closed(channel_state, state_change) assert must_contain_entry(iteration.events, ContractSendChannelWithdraw, {})
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_channelstate_mediatedtransfer_overspent(): """Receiving a lock with an amount large than distributable must be ignored. """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) distributable = channel.get_distributable(channel_state.partner_state, channel_state.our_state) lock_amount = distributable + 1 lock_expiration = 10 lock_hashlock = sha3(b'test_channelstate_mediatedtransfer_overspent') lock = HashTimeLockState( lock_amount, lock_expiration, lock_hashlock, ) nonce = 1 transferred_amount = 0 receive_mediatedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, _ = channel.handle_receive_mediatedtransfer( channel_state, receive_mediatedtransfer, ) assert not is_valid, 'message is invalid because it is spending more than the distributable' assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model1) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model1)
def test_interwoven_transfers(): """Can keep doing transactions even if not all secrets have been released.""" number_of_transfers = 100 balance_for_all_transfers = 11 * number_of_transfers lock_amounts = cycle([1, 3, 5, 7, 11]) lock_secrets = [ format(i, '>032').encode() for i in range(number_of_transfers) ] our_model, _ = create_model(70) partner_model, privkey2 = create_model(balance_for_all_transfers) channel_state = create_channel_from_models(our_model, partner_model) block_number = 1000 nonce = 0 transferred_amount = 0 our_model_current = our_model partner_model_current = partner_model for i, (lock_amount, lock_secret) in enumerate(zip(lock_amounts, lock_secrets)): nonce += 1 block_number += 1 lock_expiration = block_number + channel_state.settle_timeout - 1 lock_secrethash = sha3(lock_secret) lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) merkletree_leaves = list(partner_model_current.merkletree_leaves) merkletree_leaves.append(lock.lockhash) partner_model_current = partner_model_current._replace( distributable=partner_model_current.distributable - lock_amount, amount_locked=partner_model_current.amount_locked + lock_amount, next_nonce=partner_model_current.next_nonce + 1, merkletree_leaves=merkletree_leaves, ) receive_mediatedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, merkletree_leaves=merkletree_leaves, ) is_valid, msg = channel.handle_receive_mediatedtransfer( channel_state, receive_mediatedtransfer, ) assert is_valid, msg assert_partner_state( channel_state.our_state, channel_state.partner_state, our_model_current, ) assert_partner_state( channel_state.partner_state, channel_state.our_state, partner_model_current, ) # claim a transaction at every other iteration, leaving the current one # in place if i % 2: # Update our model: # - Increase nonce because the secret is a new balance proof # - The lock is removed from the merkle tree, the balance proof must be updated # - The locksroot must have unlocked lock removed # - The transferred amount must be increased by the lock amount # - This changes the balance for both participants: # - the sender balance and locked amount is decremented by the lock amount # - the receiver balance and distributable is incremented by the lock amount nonce += 1 transferred_amount += lock_amount merkletree_leaves = list(partner_model_current.merkletree_leaves) merkletree_leaves.remove(lock.lockhash) tree = compute_layers(merkletree_leaves) locksroot = tree[MERKLEROOT][0] partner_model_current = partner_model_current._replace( amount_locked=partner_model_current.amount_locked - lock_amount, balance=partner_model_current.balance - lock_amount, next_nonce=partner_model_current.next_nonce + 1, merkletree_leaves=merkletree_leaves, ) our_model_current = our_model_current._replace( balance=our_model_current.balance + lock_amount, distributable=our_model_current.distributable + lock_amount, ) secret_message = Secret( identifier=nonce, nonce=nonce, channel=channel_state.identifier, transferred_amount=transferred_amount, locksroot=locksroot, secret=lock_secret, ) secret_message.sign(privkey2, channel_state.partner_state.address) balance_proof = balanceproof_from_envelope(secret_message) unlock_state_change = ReceiveUnlock( lock_secret, balance_proof, ) is_valid, msg = channel.handle_unlock(channel_state, unlock_state_change) assert is_valid, msg assert_partner_state( channel_state.our_state, channel_state.partner_state, our_model_current, ) assert_partner_state( channel_state.partner_state, channel_state.our_state, partner_model_current, )
def test_channelstate_mediatedtransfer_overspend_with_multiple_pending_transfers(): """Receiving a concurrent lock with an amount large than distributable must be ignored. """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) # Step 1: Create a lock with an amount of 1 # - this wont be unlocked lock1_amount = 1 lock1_expiration = 1 + channel_state.settle_timeout lock1_secrethash = sha3(b'test_receive_cannot_overspend_with_multiple_pending_transfers1') lock1 = HashTimeLockState( lock1_amount, lock1_expiration, lock1_secrethash, ) nonce1 = 1 transferred_amount = 0 receive_mediatedtransfer1 = make_receive_transfer_mediated( channel_state, privkey2, nonce1, transferred_amount, lock1, ) is_valid, msg = channel.handle_receive_mediatedtransfer( channel_state, receive_mediatedtransfer1, ) assert is_valid, msg our_model2 = our_model1 partner_model2 = partner_model1._replace( distributable=partner_model1.distributable - lock1.amount, amount_locked=lock1.amount, next_nonce=2, merkletree_leaves=[lock1.lockhash], ) # The valid transfer is handled normally assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2) # Step 2: Create a lock with the current *distributable + 1* # - This must be ignored distributable = channel.get_distributable(channel_state.partner_state, channel_state.our_state) lock2_amount = distributable + 1 lock2_expiration = channel_state.settle_timeout lock2_secrethash = sha3(b'test_receive_cannot_overspend_with_multiple_pending_transfers2') lock2 = HashTimeLockState( lock2_amount, lock2_expiration, lock2_secrethash, ) leaves = [lock1.lockhash, lock2.lockhash] nonce2 = 2 receive_mediatedtransfer2 = make_receive_transfer_mediated( channel_state, privkey2, nonce2, transferred_amount, lock2, merkletree_leaves=leaves, ) is_valid, msg = channel.handle_receive_mediatedtransfer( channel_state, receive_mediatedtransfer2, ) assert not is_valid, 'message is invalid because its expending more than the distributable' # The overspending transfer must be ignored assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2)
def test_channelstate_receive_mediatedtransfer(): """Tests receiving a mediated transfer. The transfer is done in three steps: - a mediated transfer including a lock in its balance proof is sent - the secret is revealed - the unlocked balance proof is sent updating the transferred_amount """ our_model1, _ = create_model(70) partner_model1, privkey2 = create_model(100) channel_state = create_channel_from_models(our_model1, partner_model1) # Step 1: Simulate receiving a transfer # - The receiver end state doesnt change # - The lock must be registered with the sender end lock_amount = 30 lock_expiration = 10 lock_secret = sha3(b'test_end_state') lock_secrethash = sha3(lock_secret) lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) nonce = 1 transferred_amount = 0 receive_mediatedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, msg = channel.handle_receive_mediatedtransfer( channel_state, receive_mediatedtransfer, ) assert is_valid, msg our_model2 = our_model1 partner_model2 = partner_model1._replace( distributable=partner_model1.distributable - lock_amount, amount_locked=lock_amount, next_nonce=2, merkletree_leaves=[lock.lockhash], ) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2) # Step 2: Simulate learning the secret # - Registers the secret, this must not change the balance/locked amount channel.register_secret(channel_state, lock_secret, lock_secrethash) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model2) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model2) # Step 3: Simulate unlocking the lock # - Update the balances transferred_amount = 0 secret_message = Secret( identifier=1, nonce=2, channel=channel_state.identifier, transferred_amount=transferred_amount + lock_amount, locksroot=EMPTY_MERKLE_ROOT, secret=lock_secret, ) secret_message.sign(privkey2, channel_state.partner_state.address) balance_proof = balanceproof_from_envelope(secret_message) unlock_state_change = ReceiveUnlock( lock_secret, balance_proof, ) is_valid, msg = channel.handle_unlock(channel_state, unlock_state_change) assert is_valid, msg our_model3 = our_model2._replace( balance=our_model2.balance + lock_amount, distributable=our_model2.balance + lock_amount, ) partner_model3 = partner_model2._replace( balance=partner_model2.balance - lock_amount, amount_locked=0, next_nonce=3, merkletree_leaves=[], ) assert_partner_state(channel_state.our_state, channel_state.partner_state, our_model3) assert_partner_state(channel_state.partner_state, channel_state.our_state, partner_model3)