def create_secret(self, identifier, secret): hashlock = sha3(secret) from_ = self.our_state lock = from_.balance_proof.get_lock_by_hashlock(hashlock) lockhashed = sha3(lock.as_bytes) leafs = from_.balance_proof.unclaimed_merkletree() leafs.remove(lockhashed) locksroot_with_pending_lock_removed = Merkletree( leafs).merkleroot or EMPTY_MERKLE_ROOT transferred_amount = from_.transferred_amount + lock.amount nonce = self.get_next_nonce() secret = Secret( identifier, nonce, self.channel_address, transferred_amount, locksroot_with_pending_lock_removed, secret, ) return secret
def register_secret(self, secret): """ Handle a secret that could be received from a Secret message or a ChannelSecretRevealed event. """ hashlock = sha3(secret) channels_reveal = self.hashlock_channel[hashlock] secret_message = Secret(secret) self.raiden.sign(secret_message) while channels_reveal: reveal_to = channels_reveal.pop() # send the secret to all channels registered, including the next # hop that might be the node that informed us about the secret self.raiden.send(reveal_to.partner_state.address, secret_message) # update the channel by claiming the locked transfers try: reveal_to.claim_locked(secret) except InvalidSecret: log.error('claiming lock failed') assert len(self.hashlock_channel[hashlock]) == 0 del self.hashlock_channel[hashlock]
def claim_lock(app_chain, identifier, token, secret): """ Unlock a pending transfer. """ secrethash = sha3(secret) for from_, to_ in zip(app_chain[:-1], app_chain[1:]): from_channel = get_channelstate(from_, to_, token) partner_channel = get_channelstate(to_, from_, token) unlock_lock = channel.send_unlock( from_channel, identifier, secret, secrethash, ) secret_message = Secret( unlock_lock.identifier, unlock_lock.balance_proof.nonce, unlock_lock.balance_proof.channel_address, unlock_lock.balance_proof.transferred_amount, unlock_lock.balance_proof.locksroot, unlock_lock.secret, ) from_.raiden.sign(secret_message) balance_proof = balanceproof_from_envelope(secret_message) receive_unlock = ReceiveUnlock( unlock_lock.secret, balance_proof, ) is_valid, msg = channel.handle_unlock( partner_channel, receive_unlock, ) assert is_valid, msg
def test_receive_hashlocktransfer_unknown(raiden_network): app0 = raiden_network[0] # pylint: disable=unbalanced-tuple-unpacking token_manager0 = app0.raiden.managers_by_token_address.values()[0] other_key = PrivateKey(HASH2, ctx=GLOBAL_CTX, raw=True) other_address = privatekey_to_address(other_key.private_key) amount = 10 lock = Lock(amount, 1, HASH) refund_transfer = RefundTransfer(identifier=1, nonce=1, token=token_manager0.token_address, transferred_amount=amount, recipient=app0.raiden.address, locksroot=HASH, lock=lock) sign_and_send(refund_transfer, other_key, other_address, app0) secret = Secret(1, HASH, token_manager0.token_address) sign_and_send(secret, other_key, other_address, app0) secret_request = SecretRequest(1, HASH, 1) sign_and_send(secret_request, other_key, other_address, app0) reveal_secret = RevealSecret(HASH) sign_and_send(reveal_secret, other_key, other_address, app0)
def test_receive_hashlocktransfer_unknown(raiden_network): app0 = raiden_network[0] # pylint: disable=unbalanced-tuple-unpacking token_manager0 = app0.raiden.managers_by_token_address.values()[0] other_key = PrivateKey(HASH2, ctx=GLOBAL_CTX, raw=True) other_address = privatekey_to_address(other_key.private_key) amount = 10 lock = Lock(amount, 1, HASH) refund_transfer = RefundTransfer(identifier=1, nonce=1, token=token_manager0.token_address, transferred_amount=amount, recipient=app0.raiden.address, locksroot=HASH, lock=lock) sign_and_send(refund_transfer, other_key, other_address, app0) transfer_timeout = TransferTimeout(HASH, HASH) sign_and_send(transfer_timeout, other_key, other_address, app0) secret = Secret(1, HASH, token_manager0.token_address) sign_and_send(secret, other_key, other_address, app0) secret_request = SecretRequest(1, HASH, 1) sign_and_send(secret_request, other_key, other_address, app0) reveal_secret = RevealSecret(HASH) sign_and_send(reveal_secret, other_key, other_address, app0) # Whenever processing of ConfirmTransfer is implemented test it here # too by removing the expectation of an exception with pytest.raises(KeyError): confirm_transfer = ConfirmTransfer(HASH) sign_and_send(confirm_transfer, other_key, other_address, app0)
def test_receive_hashlocktransfer_unknown(raiden_network): app0 = raiden_network[0] # pylint: disable=unbalanced-tuple-unpacking graph0 = app0.raiden.channelgraphs.values()[0] other_key = PrivateKey(HASH2) other_address = privatekey_to_address(HASH2) amount = 10 lock = Lock(amount, 1, HASH) refund_transfer = RefundTransfer( identifier=1, nonce=1, token=graph0.token_address, transferred_amount=amount, recipient=app0.raiden.address, locksroot=HASH, lock=lock ) sign_and_send(refund_transfer, other_key, other_address, app0) secret = Secret(1, HASH, graph0.token_address) sign_and_send(secret, other_key, other_address, app0) secret_request = SecretRequest(1, HASH, 1) sign_and_send(secret_request, other_key, other_address, app0) reveal_secret = RevealSecret(HASH) sign_and_send(reveal_secret, other_key, other_address, app0)
def test_receive_hashlocktransfer_unknown(raiden_network, token_addresses): app0 = raiden_network[0] token_address = token_addresses[0] other_key = HOP1_KEY other_address = HOP1 amount = 10 refund_transfer_message = make_refund_transfer( identifier=1, nonce=1, token=token_address, channel=other_address, transferred_amount=amount, recipient=app0.raiden.address, locksroot=UNIT_HASHLOCK, amount=amount, hashlock=UNIT_HASHLOCK, ) sign_and_inject(refund_transfer_message, other_key, other_address, app0) secret = Secret( identifier=1, nonce=1, channel=make_address(), transferred_amount=amount, locksroot=UNIT_HASHLOCK, secret=UNIT_SECRET, ) sign_and_inject(secret, other_key, other_address, app0) secret_request_message = SecretRequest(1, UNIT_HASHLOCK, 1) sign_and_inject(secret_request_message, other_key, other_address, app0) reveal_secret_message = RevealSecret(UNIT_SECRET) sign_and_inject(reveal_secret_message, other_key, other_address, app0)
def test_receive_secrethashtransfer_unknown(raiden_network, token_addresses): app0 = raiden_network[0] token_address = token_addresses[0] token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(app0), app0.raiden.default_registry.address, token_address, ) other_key = HOP1_KEY other_address = HOP1 channel_identifier = make_channel_identifier() amount = 10 refund_transfer_message = make_refund_transfer( payment_identifier=1, nonce=1, token_network_address=token_network_identifier, token=token_address, channel_identifier=channel_identifier, transferred_amount=amount, recipient=app0.raiden.address, locksroot=UNIT_SECRETHASH, amount=amount, secrethash=UNIT_SECRETHASH, ) sign_and_inject(refund_transfer_message, other_key, other_address, app0) secret = Secret( chain_id=UNIT_CHAIN_ID, message_identifier=random.randint(0, UINT64_MAX), payment_identifier=1, nonce=1, channel_identifier=channel_identifier, token_network_address=token_network_identifier, transferred_amount=amount, locked_amount=0, locksroot=UNIT_SECRETHASH, secret=UNIT_SECRET, ) sign_and_inject(secret, other_key, other_address, app0) secret_request_message = SecretRequest( message_identifier=random.randint(0, UINT64_MAX), payment_identifier=1, secrethash=UNIT_SECRETHASH, amount=1, expiration=refund_transfer_message.lock.expiration, ) sign_and_inject(secret_request_message, other_key, other_address, app0) reveal_secret_message = RevealSecret( message_identifier=random.randint(0, UINT64_MAX), secret=UNIT_SECRET, ) sign_and_inject(reveal_secret_message, other_key, other_address, app0)
def handle_secret(self, secret): """ Handle a secret that could be received from a Secret message or a ChannelSecretRevealed event. """ hashlock = sha3(secret) channels_reveal = self.hashlock_channel[hashlock] secret_message = Secret(secret) self.raiden.sign(secret_message) while channels_reveal: reveal_to = channels_reveal.pop() # When a secret is revealed a message could be in-transit # containing the older lockroot, for this reason the recipient # cannot update it's locksroot at the moment a secret was revealed. # # The protocol is to register the secret so that it can compute a # proof of balance, if necessary, forward the secret to the sender # and wait for the update from it. It's the sender duty to order # the current in-transit (and possible the transfers in queue) # transfers and the secret/locksroot update. # # The channel and it's queue must be changed in sync, a transfer # must not be created and while we update the balance_proof. # critical read/write section # (relying on the GIL and non-blocking apis instead of an explicit # lock). # we are the sender, so we can claim the lock/update the locksroot # and add the update message into the end of the message queue, all # the messages will remain consistent (including the messages # in-transit and the ones that are already in the queue) if reveal_to.partner_state.balance_proof.is_pending(hashlock): reveal_to.claim_lock(secret) self.raiden.send_async(reveal_to.partner_state.address, secret_message) # we are the recipient, register the secret so that a balance proof # can be generate and reveal the secret to the sender. the asset # will be claimed once the secret is received from the sender in # the MediatedTransferTask elif reveal_to.our_state.balance_proof.is_pending(hashlock): reveal_to.register_secret(secret) self.raiden.send_async(reveal_to.partner_state.address, secret_message) else: log.error('No corresponding hashlock for the given secret.') # /critical read/write section # delete the list it wont ever be used again (unless we have a sha3 # collision) del self.hashlock_channel[hashlock]
def test_secret(iterations=ITERATIONS): identifier = 1 secret = HASH amount = 1 msg = Secret( identifier, secret, amount, ) msg.sign(PRIVKEY, ADDRESS) run_timeit('Secret', msg, iterations=iterations)
def _run(self): # pylint: disable=method-hidden mediated_transfer = self.originating_transfer assetmanager = self.transfermanager.assetmanager originating_channel = assetmanager.get_channel_by_partner_address( mediated_transfer.sender) raiden = assetmanager.raiden log.debug( 'END MEDIATED TRANSFER %s -> %s msghash:%s hashlock:%s', pex(mediated_transfer.target), pex(mediated_transfer.initiator), pex(mediated_transfer.hash), pex(mediated_transfer.lock.hashlock), ) secret_request = SecretRequest(mediated_transfer.lock.hashlock) raiden.sign(secret_request) response = self.send_and_wait_valid(raiden, mediated_transfer, secret_request) if response is None: timeout_message = originating_channel.create_timeouttransfer_for( mediated_transfer) raiden.send_async(mediated_transfer.sender, timeout_message) self.transfermanager.on_hashlock_result( mediated_transfer.lock.hashlock, False) return # register the secret so that a balance proof can be created but don't # claim until our partner has informed us that it's internal state is # updated originating_channel.register_secret(response.secret) secret_message = Secret(response.secret) raiden.sign(secret_message) raiden.send_async(mediated_transfer.sender, secret_message) # wait for the secret from `sender` to claim the lock while True: response = self.response_message.wait() # critical write section self.response_message = AsyncResult() # /critical write section if isinstance( response, Secret) and response.sender == mediated_transfer.sender: originating_channel.claim_lock(response.secret) self.transfermanager.on_hashlock_result( mediated_transfer.lock.hashlock, True) return
def check_path(self, msg, channel): if isinstance(msg, CancelTransfer): return None # try with next path elif isinstance(msg, TransferTimeout): # stale hashlock if not self.isinitiator: self.raiden.send(self.originating_transfer.sender, msg) return False elif isinstance(msg, Secret): assert self.originating_transfer assert msg.hashlock == self.hashlock if self.originating_transfer.sender != self.originating_transfer.initiator: fwd = Secret(msg.secret) self.raiden.sign(fwd) self.raiden.send(self.originating_transfer.sender, fwd) else: log.warning('NOT FORWARDING SECRET TO ININTIATOR') return True elif isinstance(msg, SecretRequest): assert self.isinitiator # lock.target can easilly be tampered, ensure that we are receiving # the SecretRequest from the correct node if msg.sender != self.target: log.error('Tampered SecretRequest', secret_request=msg) return None # try the next available path # TODO: the lock.amount can easily be tampered, check the `target` # locked transfer has the correct `amount` msg = Secret(self.secret) self.raiden.sign(msg) self.raiden.send(self.target, msg) # TODO: Guarantee that `target` received the secret, otherwise we # updated the channel and the first hop will receive the asset, but # none of the other channels will make the transfer channel.claim_locked(self.secret) return True return None
def _run(self): # pylint: disable=method-hidden self.event = AsyncResult() # http://www.gevent.org/gevent.event.html timeout = self.timeout msg = self.event.wait(timeout) if not msg: log.error('TIMEOUT! ' * 5) # TransferTimeout is of no use, SecretRequest was for sender return self.on_completion(False) assert isinstance(msg, Secret) assert msg.hashlock == self.hashlock fwd = Secret(msg.secret) self.raiden.sign(fwd) self.raiden.send(self.recipient, fwd) return self.on_completion(True)
def create_secret(self, identifier, secret): from_ = self.our_state to_ = self.partner_state self.release_lock(secret) locksroot_with_pending_lock_removed = to_.balance_proof.merkleroot_for_unclaimed( ) secret = Secret( identifier, from_.nonce, self.channel_address, from_.transferred_amount, locksroot_with_pending_lock_removed, secret, ) return secret
def test_secret(iterations=ITERATIONS): identifier = 1 nonce = 1 channel = HASH transferred_amount = 1 secret = HASH locksroot = '' msg = Secret( identifier, nonce, channel, transferred_amount, locksroot, secret, ) msg.sign(PRIVKEY, ADDRESS) run_timeit('Secret', msg, iterations=iterations)
def test_receive_secrethashtransfer_unknown(raiden_network, token_addresses): app0 = raiden_network[0] token_address = token_addresses[0] other_key = HOP1_KEY other_address = HOP1 amount = 10 refund_transfer_message = make_refund_transfer( payment_identifier=1, nonce=1, registry_address=app0.raiden.default_registry.address, token=token_address, channel=other_address, transferred_amount=amount, recipient=app0.raiden.address, locksroot=UNIT_SECRETHASH, amount=amount, secrethash=UNIT_SECRETHASH, ) sign_and_inject(refund_transfer_message, other_key, other_address, app0) secret = Secret( message_identifier=random.randint(0, UINT64_MAX), payment_identifier=1, nonce=1, channel=make_address(), transferred_amount=amount, locked_amount=0, locksroot=UNIT_SECRETHASH, secret=UNIT_SECRET, ) sign_and_inject(secret, other_key, other_address, app0) secret_request_message = SecretRequest( message_identifier=random.randint(0, UINT64_MAX), payment_identifier=1, secrethash=UNIT_SECRETHASH, amount=1, ) sign_and_inject(secret_request_message, other_key, other_address, app0) reveal_secret_message = RevealSecret( message_identifier=random.randint(0, UINT64_MAX), secret=UNIT_SECRET, ) sign_and_inject(reveal_secret_message, other_key, other_address, app0)
def handle_secret(self, secret): """ Handle a secret that could be received from a Secret message or a ChannelSecretRevealed event. """ hashlock = sha3(secret) channels_reveal = self.hashlock_channel[hashlock] secret_message = Secret(secret) self.raiden.sign(secret_message) while channels_reveal: reveal_to = channels_reveal.pop() # critical read/write section # The channel and it's queue must be changed in sync, a transfer # must not be created and the balance_proof must not be changed # while we update the state (relaying on the GIL and non-blocing # apis instead of an explicit lock). # If we created the mediated transfer update our local state and # notify our partner to do the same, this operation needs to be # synchronized with the merkletree of locks to inhibit locksroot # conflicts. if reveal_to.partner_state.balance_proof.is_pending(hashlock): reveal_to.claim_lock(secret) self.raiden.send_async(reveal_to.partner_state.address, secret_message) # Otherwise we received the transfer, reveal it to the # originating_channel so that it can update it's internal state and # allow us to update too. elif reveal_to.our_state.balance_proof.is_pending(hashlock): # register the secret so that a balance proof can be generated reveal_to.register_secret(secret) self.raiden.send_async(reveal_to.partner_state.address, secret_message) else: log.error('No corresponding hashlock for the given secret.') # delete the list it wont ever be used again (unless we have a sha3 # colision) del self.hashlock_channel[hashlock]
def claim_lock(app_chain, payment_identifier, token_network_identifier, secret): """ Unlock a pending transfer. """ secrethash = sha3(secret) for from_, to_ in zip(app_chain[:-1], app_chain[1:]): from_channel = get_channelstate(from_, to_, token_network_identifier) partner_channel = get_channelstate(to_, from_, token_network_identifier) unlock_lock = channel.send_unlock( from_channel, random.randint(0, UINT64_MAX), payment_identifier, secret, secrethash, ) secret_message = Secret( chain_id=unlock_lock.balance_proof.chain_id, message_identifier=unlock_lock.message_identifier, payment_identifier=unlock_lock.payment_identifier, nonce=unlock_lock.balance_proof.nonce, token_network_address=partner_channel.token_network_identifier, channel_identifier=unlock_lock.balance_proof.channel_identifier, transferred_amount=unlock_lock.balance_proof.transferred_amount, locked_amount=unlock_lock.balance_proof.locked_amount, locksroot=unlock_lock.balance_proof.locksroot, secret=unlock_lock.secret, ) from_.raiden.sign(secret_message) balance_proof = balanceproof_from_envelope(secret_message) receive_unlock = ReceiveUnlock( message_identifier=random.randint(0, UINT64_MAX), secret=unlock_lock.secret, balance_proof=balance_proof, ) is_valid, _, msg = channel.handle_unlock( partner_channel, receive_unlock, ) assert is_valid, msg
def create_secret(self, identifier, secret): hashlock = sha3(secret) from_ = self.our_state lock = from_.get_lock_by_hashlock(hashlock) locksroot_with_pending_lock_removed = from_.compute_merkleroot_without( lock) transferred_amount = from_.transferred_amount + lock.amount nonce = self.get_next_nonce() secret = Secret( identifier, nonce, self.channel_address, transferred_amount, locksroot_with_pending_lock_removed, secret, ) return secret
def test_receive_hashlocktransfer_unknown(raiden_network): app0 = raiden_network[0] # pylint: disable=unbalanced-tuple-unpacking graph0 = app0.raiden.token_to_channelgraph.values()[0] other_key = PrivateKey(HASH2) other_address = privatekey_to_address(HASH2) amount = 10 refund_transfer = make_refund_transfer( identifier=1, nonce=1, token=graph0.token_address, channel=other_address, transferred_amount=amount, recipient=app0.raiden.address, locksroot=UNIT_HASHLOCK, amount=amount, hashlock=UNIT_HASHLOCK, ) sign_and_send(refund_transfer, other_key, other_address, app0) secret = Secret( identifier=1, nonce=1, channel=make_address(), transferred_amount=amount, locksroot=UNIT_HASHLOCK, secret=UNIT_SECRET, ) sign_and_send(secret, other_key, other_address, app0) secret_request = SecretRequest(1, UNIT_HASHLOCK, 1) sign_and_send(secret_request, other_key, other_address, app0) reveal_secret = RevealSecret(UNIT_SECRET) sign_and_send(reveal_secret, other_key, other_address, app0)
def test_regression_multiple_revealsecret(raiden_network, token_addresses): """ Multiple RevealSecret messages arriving at the same time must be handled properly. Secret handling followed these steps: The Secret message arrives The secret is registered The channel is updated and the correspoding lock is removed * A balance proof for the new channel state is created and sent to the payer The channel is unregistered for the given hashlock The step marked with an asterisk above introduced a context-switch, this allowed a second Reveal Secret message to be handled before the channel was unregistered, because the channel was already updated an exception was raised for an unknown secret. """ app0, app1 = raiden_network token = token_addresses[0] identifier = 1 secret = sha3(b'test_regression_multiple_revealsecret') hashlock = sha3(secret) expiration = app0.raiden.get_block_number() + 100 amount = 10 mediated_transfer = channel(app0, app1, token).create_mediatedtransfer( transfer_initiator=app0.raiden.address, transfer_target=app1.raiden.address, fee=0, amount=amount, identifier=identifier, expiration=expiration, hashlock=hashlock, ) app0.raiden.sign(mediated_transfer) message_data = mediated_transfer.encode() app1.raiden.protocol.receive(message_data) reveal_secret = RevealSecret(secret) app0.raiden.sign(reveal_secret) reveal_secret_data = reveal_secret.encode() secret = Secret( identifier=identifier, nonce=mediated_transfer.nonce + 1, channel=channel(app0, app1, token).channel_address, transferred_amount=amount, locksroot=EMPTY_MERKLE_ROOT, secret=secret, ) app0.raiden.sign(secret) secret_data = secret.encode() messages = [ secret_data, reveal_secret_data, ] wait = [ gevent.spawn_later( .1, app1.raiden.protocol.receive, data, ) for data in messages ] gevent.joinall(wait)
def _secret(self, identifier, secret, partner_secret_message, hashlock): channels_list = self.hashlock_channel[hashlock] channels_to_remove = list() # Dont use the partner_secret_message.token since it might not match with the # current token manager our_secret_message = Secret(identifier, secret, self.token_address) self.raiden.sign(our_secret_message) revealsecret_message = RevealSecret(secret) self.raiden.sign(revealsecret_message) for channel in channels_list: # critical read/write section # - the `release_lock` might raise if the `balance_proof` changes # after the check # - a message created before the lock is release must be added in # the message queue before `our_secret_message` # We are relying on the GIL and non-blocking apis instead of an # explicit lock. if channel.partner_state.balance_proof.is_unclaimed(hashlock): # we are the sender, so we can release the lock once the secret # is known and add the update message into the end of the # message queue, all the messages will remain consistent # (including the messages in-transit and the ones that are # already in the queue) channel.release_lock(secret) # notify our partner that our state is updated and it can # withdraw the token self.raiden.send_async(channel.partner_state.address, our_secret_message) channels_to_remove.append(channel) # we are the recipient, we can only withdraw the token if a secret # message is received from the correct sender and token address, so # withdraw if a valid message is received otherwise register the # secret and reveal the secret to channel patner. if channel.our_state.balance_proof.is_unclaimed(hashlock): # partner_secret_message might be None if partner_secret_message: valid_sender = partner_secret_message.sender == channel.partner_state.address valid_token = partner_secret_message.token == channel.token_address if valid_sender and valid_token: channel.withdraw_lock(secret) channels_to_remove.append(channel) else: # assume our partner does not know the secret and reveal it channel.register_secret(secret) self.raiden.send_async(channel.partner_state.address, revealsecret_message) else: channel.register_secret(secret) self.raiden.send_async(channel.partner_state.address, revealsecret_message) # /critical read/write section for channel in channels_to_remove: channels_list.remove(channel) # delete the list from defaultdict, it wont be used again if len(channels_list) == 0: del self.hashlock_channel[hashlock]
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_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, merkletree_leaves=merkletree_leaves, ) is_valid, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) 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, ) message_identifier = random.randint(0, UINT64_MAX) secret_message = Secret( message_identifier=message_identifier, payment_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_receive_lockedtransfer(): """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_lockedtransfer = make_receive_transfer_mediated( channel_state, privkey2, nonce, transferred_amount, lock, ) is_valid, msg = channel.handle_receive_lockedtransfer( channel_state, receive_lockedtransfer, ) 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 message_identifier = random.randint(0, UINT64_MAX) secret_message = Secret( message_identifier=message_identifier, payment_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)
def test_regression_multiple_revealsecret(raiden_network, token_addresses): """ Multiple RevealSecret messages arriving at the same time must be handled properly. Secret handling followed these steps: The Secret message arrives The secret is registered The channel is updated and the correspoding lock is removed * A balance proof for the new channel state is created and sent to the payer The channel is unregistered for the given secrethash The step marked with an asterisk above introduced a context-switch. This allowed a second Reveal Secret message to be handled before the channel was unregistered. And because the channel was already updated an exception was raised for an unknown secret. """ app0, app1 = raiden_network token = token_addresses[0] channelstate_0_1 = get_channelstate(app0, app1, token) payment_identifier = 1 secret = sha3(b'test_regression_multiple_revealsecret') secrethash = sha3(secret) expiration = app0.raiden.get_block_number() + 100 amount = 10 lock = Lock( amount, expiration, secrethash, ) nonce = 1 transferred_amount = 0 mediated_transfer = LockedTransfer( random.randint(0, UINT64_MAX), payment_identifier, nonce, token, channelstate_0_1.identifier, transferred_amount, app1.raiden.address, lock.secrethash, lock, app1.raiden.address, app0.raiden.address, ) app0.raiden.sign(mediated_transfer) message_data = mediated_transfer.encode() app1.raiden.protocol.receive(message_data) reveal_secret = RevealSecret( random.randint(0, UINT64_MAX), secret, ) app0.raiden.sign(reveal_secret) reveal_secret_data = reveal_secret.encode() secret = Secret( message_identifier=random.randint(0, UINT64_MAX), payment_identifier=payment_identifier, nonce=mediated_transfer.nonce + 1, channel=channelstate_0_1.identifier, transferred_amount=amount, locksroot=EMPTY_MERKLE_ROOT, secret=secret, ) app0.raiden.sign(secret) secret_data = secret.encode() messages = [ secret_data, reveal_secret_data, ] wait = [ gevent.spawn_later( .1, app1.raiden.protocol.receive, data, ) for data in messages ] gevent.joinall(wait)
def _run(self): # pylint: disable=method-hidden,too-many-locals amount = self.amount target = self.target raiden = self.transfermanager.assetmanager.raiden fee = 0 # there are no guarantees that the next_hop will follow the same route routes = self.transfermanager.assetmanager.get_best_routes( amount, target, lock_timeout=None, ) if log.isEnabledFor(logging.DEBUG): log.debug( 'START MEDIATED TRANSFER initiator:%s target:%s', pex(self.address), pex(self.target), ) for path, forward_channel in routes: # try a new secret secret = sha3(hex(random.getrandbits(256))) hashlock = sha3(secret) next_hop = path[1] if log.isEnabledFor(logging.DEBUG): log.debug( 'START MEDIATED TRANSFER NEW PATH path:%s hashlock:%s', lpex(path), pex(hashlock), ) self.transfermanager.register_task_for_hashlock(self, hashlock) lock_expiration = (raiden.chain.block_number() + forward_channel.settle_timeout - raiden.config['reveal_timeout']) mediated_transfer = forward_channel.create_mediatedtransfer( raiden.address, target, fee, amount, lock_expiration, hashlock, ) raiden.sign(mediated_transfer) forward_channel.register_transfer(mediated_transfer) response = self.send_and_wait_valid(raiden, path, mediated_transfer) # `next_hop` timedout if response is None: self.transfermanager.on_hashlock_result(hashlock, False) # someone down the line timedout / couldn't proceed elif isinstance(response, (RefundTransfer, TransferTimeout)): self.transfermanager.on_hashlock_result(hashlock, False) # `target` received the MediatedTransfer elif response.sender == target and isinstance( response, SecretRequest): secret_message = Secret(secret) raiden.sign(secret_message) raiden.send_async(target, secret_message) # register the secret now and just incur with the additional # overhead of retrying until the `next_hop` receives the secret # forward_channel.register_secret(secret) # wait until `next_hop` received the secret to syncronize our # state (otherwise we can send a new transfer with an invalid # locksroot while the secret is in transit that will incur into # additional retry/timeout latency) next_hop = path[1] while True: response = self.response_message.wait() # critical write section self.response_message = AsyncResult() # /critical write section if isinstance(response, Secret) and response.sender == next_hop: # critical read/write section # The channel and it's queue must be locked, a transfer # must not be created while we update the balance_proof. forward_channel.claim_lock(secret) raiden.send_async(next_hop, secret_message) # /critical write section self.transfermanager.on_hashlock_result(hashlock, True) self.done_result.set(True) return log.error( 'Invalid message ignoring. %s', repr(response), ) else: log.error( 'Unexpected response %s', repr(response), ) self.transfermanager.on_hashlock_result(hashlock, False) if log.isEnabledFor(logging.DEBUG): log.debug( 'START MEDIATED TRANSFER FAILED initiator:%s target:%s', pex(self.address), pex(self.target), ) self.done_result.set(False) # all paths failed
def handle_secret( # pylint: disable=too-many-arguments self, identifier, token_address, secret, partner_secret_message, hashlock): """ Unlock/Witdraws locks, register the secret, and send Secret messages as necessary. This function will: - Unlock the locks created by this node and send a Secret message to the corresponding partner so that she can withdraw the token. - Withdraw the lock from sender. - Register the secret for the locks received and reveal the secret to the senders Note: The channel needs to be registered with `raiden.register_channel_for_hashlock`. """ # handling the secret needs to: # - unlock the token for all `forward_channel` (the current one # and the ones that failed with a refund) # - send a message to each of the forward nodes allowing them # to withdraw the token # - register the secret for the `originating_channel` so that a # proof can be made, if necessary # - reveal the secret to the `sender` node (otherwise we # cannot withdraw the token) channels_list = self.tokens_hashlocks_channels[token_address][hashlock] channels_to_remove = list() # Dont use the partner_secret_message.token since it might not match # the current token manager our_secret_message = Secret( identifier, secret, token_address, ) self.sign(our_secret_message) revealsecret_message = RevealSecret(secret) self.sign(revealsecret_message) for channel in channels_list: # unlock a sent lock if channel.partner_state.balance_proof.is_unclaimed(hashlock): channel.release_lock(secret) self.send_async( channel.partner_state.address, our_secret_message, ) channels_to_remove.append(channel) # withdraw a pending lock if channel.our_state.balance_proof.is_unclaimed(hashlock): if partner_secret_message: matching_sender = ( partner_secret_message.sender == channel.partner_state.address ) matching_token = partner_secret_message.token == channel.token_address if matching_sender and matching_token: channel.withdraw_lock(secret) channels_to_remove.append(channel) else: channel.register_secret(secret) self.send_async( channel.partner_state.address, revealsecret_message, ) else: channel.register_secret(secret) self.send_async( channel.partner_state.address, revealsecret_message, ) for channel in channels_to_remove: channels_list.remove(channel) if len(channels_list) == 0: del self.tokens_hashlocks_channels[token_address][hashlock]
def test_secret(iterations=ITERATIONS): secret = HASH msg = Secret(secret) msg.sign(PRIVKEY) run_timeit('Secret', msg, iterations=iterations)
def test_end_state(): token_address = make_address() privkey1, address1 = make_privkey_address() address2 = make_address() channel_address = make_address() balance1 = 70 balance2 = 110 lock_secret = sha3('test_end_state') lock_amount = 30 lock_expiration = 10 lock_hashlock = sha3(lock_secret) state1 = ChannelEndState(address1, balance1, None, EMPTY_MERKLE_TREE) state2 = ChannelEndState(address2, balance2, None, EMPTY_MERKLE_TREE) assert state1.contract_balance == balance1 assert state2.contract_balance == balance2 assert state1.balance(state2) == balance1 assert state2.balance(state1) == balance2 assert state1.is_locked(lock_hashlock) is False assert state2.is_locked(lock_hashlock) is False assert merkleroot(state1.merkletree) == EMPTY_MERKLE_ROOT assert merkleroot(state2.merkletree) == EMPTY_MERKLE_ROOT assert state1.nonce is None assert state2.nonce is None lock = Lock( lock_amount, lock_expiration, lock_hashlock, ) lock_hash = sha3(lock.as_bytes) transferred_amount = 0 locksroot = state2.compute_merkleroot_with(lock) locked_transfer = LockedTransfer( 1, nonce=1, token=token_address, channel=channel_address, transferred_amount=transferred_amount, recipient=state2.address, locksroot=locksroot, lock=lock, ) transfer_target = make_address() transfer_initiator = make_address() fee = 0 mediated_transfer = locked_transfer.to_mediatedtransfer( transfer_target, transfer_initiator, fee, ) mediated_transfer.sign(privkey1, address1) state1.register_locked_transfer(mediated_transfer) assert state1.contract_balance == balance1 assert state2.contract_balance == balance2 assert state1.balance(state2) == balance1 assert state2.balance(state1) == balance2 assert state1.distributable(state2) == balance1 - lock_amount assert state2.distributable(state1) == balance2 assert state1.amount_locked == lock_amount assert state2.amount_locked == 0 assert state1.is_locked(lock_hashlock) is True assert state2.is_locked(lock_hashlock) is False assert merkleroot(state1.merkletree) == lock_hash assert merkleroot(state2.merkletree) == EMPTY_MERKLE_ROOT assert state1.nonce is 1 assert state2.nonce is None with pytest.raises(ValueError): state1.update_contract_balance(balance1 - 10) state1.update_contract_balance(balance1 + 10) assert state1.contract_balance == balance1 + 10 assert state2.contract_balance == balance2 assert state1.balance(state2) == balance1 + 10 assert state2.balance(state1) == balance2 assert state1.distributable(state2) == balance1 - lock_amount + 10 assert state2.distributable(state1) == balance2 assert state1.amount_locked == lock_amount assert state2.amount_locked == 0 assert state1.is_locked(lock_hashlock) is True assert state2.is_locked(lock_hashlock) is False assert merkleroot(state1.merkletree) == lock_hash assert merkleroot(state2.merkletree) == EMPTY_MERKLE_ROOT assert state1.nonce is 1 assert state2.nonce is None # registering the secret should not change the locked amount state1.register_secret(lock_secret) assert state1.contract_balance == balance1 + 10 assert state2.contract_balance == balance2 assert state1.balance(state2) == balance1 + 10 assert state2.balance(state1) == balance2 assert state1.is_locked(lock_hashlock) is False assert state2.is_locked(lock_hashlock) is False assert merkleroot(state1.merkletree) == lock_hash assert merkleroot(state2.merkletree) == EMPTY_MERKLE_ROOT assert state1.nonce is 1 assert state2.nonce is None secret_message = Secret( identifier=1, nonce=2, channel=channel_address, transferred_amount=transferred_amount + lock_amount, locksroot=EMPTY_MERKLE_ROOT, secret=lock_secret, ) secret_message.sign(privkey1, address1) state1.register_secretmessage(secret_message) assert state1.contract_balance == balance1 + 10 assert state2.contract_balance == balance2 assert state1.balance(state2) == balance1 + 10 - lock_amount assert state2.balance(state1) == balance2 + lock_amount assert state1.distributable(state2) == balance1 + 10 - lock_amount assert state2.distributable(state1) == balance2 + lock_amount assert state1.amount_locked == 0 assert state2.amount_locked == 0 assert state1.is_locked(lock_hashlock) is False assert state2.is_locked(lock_hashlock) is False assert merkleroot(state1.merkletree) == EMPTY_MERKLE_ROOT assert merkleroot(state2.merkletree) == EMPTY_MERKLE_ROOT assert state1.nonce is 2 assert state2.nonce is None
def test_regression_multiple_revealsecret(raiden_network, token_addresses, transport_config): """ Multiple RevealSecret messages arriving at the same time must be handled properly. Secret handling followed these steps: The Secret message arrives The secret is registered The channel is updated and the correspoding lock is removed * A balance proof for the new channel state is created and sent to the payer The channel is unregistered for the given secrethash The step marked with an asterisk above introduced a context-switch. This allowed a second Reveal Secret message to be handled before the channel was unregistered. And because the channel was already updated an exception was raised for an unknown secret. """ app0, app1 = raiden_network token = token_addresses[0] token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(app0), app0.raiden.default_registry.address, token, ) channelstate_0_1 = get_channelstate(app0, app1, token_network_identifier) payment_identifier = 1 secret = sha3(b'test_regression_multiple_revealsecret') secrethash = sha3(secret) expiration = app0.raiden.get_block_number() + 100 lock_amount = 10 lock = Lock( lock_amount, expiration, secrethash, ) nonce = 1 transferred_amount = 0 mediated_transfer = LockedTransfer( chain_id=UNIT_CHAIN_ID, message_identifier=random.randint(0, UINT64_MAX), payment_identifier=payment_identifier, nonce=nonce, token_network_address=app0.raiden.default_registry.address, token=token, channel_identifier=channelstate_0_1.identifier, transferred_amount=transferred_amount, locked_amount=lock_amount, recipient=app1.raiden.address, locksroot=lock.secrethash, lock=lock, target=app1.raiden.address, initiator=app0.raiden.address, ) app0.raiden.sign(mediated_transfer) if transport_config.protocol is TransportProtocol.UDP: message_data = mediated_transfer.encode() host_port = None app1.raiden.transport.receive(message_data, host_port) elif transport_config.protocol is TransportProtocol.MATRIX: app1.raiden.transport._receive_message(mediated_transfer) else: raise TypeError('Unknown TransportProtocol') reveal_secret = RevealSecret( random.randint(0, UINT64_MAX), secret, ) app0.raiden.sign(reveal_secret) token_network_identifier = channelstate_0_1.token_network_identifier secret = Secret( chain_id=UNIT_CHAIN_ID, message_identifier=random.randint(0, UINT64_MAX), payment_identifier=payment_identifier, nonce=mediated_transfer.nonce + 1, token_network_address=token_network_identifier, channel_identifier=channelstate_0_1.identifier, transferred_amount=lock_amount, locked_amount=0, locksroot=EMPTY_MERKLE_ROOT, secret=secret, ) app0.raiden.sign(secret) if transport_config.protocol is TransportProtocol.UDP: messages = [ secret.encode(), reveal_secret.encode(), ] host_port = None receive_method = app1.raiden.transport.receive wait = [ gevent.spawn_later( .1, receive_method, data, host_port, ) for data in messages ] elif transport_config.protocol is TransportProtocol.MATRIX: messages = [ secret, reveal_secret, ] receive_method = app1.raiden.transport._receive_message wait = [ gevent.spawn_later( .1, receive_method, data, ) for data in messages ] else: raise TypeError('Unknown TransportProtocol') gevent.joinall(wait)