def assert_mirror(channel0, channel1): """ Assert that `channel0` has a correct `partner_state` to represent `channel1` and vice-versa. """ unclaimed0 = merkleroot(channel0.our_state.merkletree) unclaimed1 = merkleroot(channel1.partner_state.merkletree) assert unclaimed0 == unclaimed1 assert channel0.our_state.amount_locked == channel1.partner_state.amount_locked assert channel0.transferred_amount == channel1.partner_state.transferred_amount balance0 = channel0.our_state.balance(channel0.partner_state) balance1 = channel1.partner_state.balance(channel1.our_state) assert balance0 == balance1 assert channel0.distributable == channel0.our_state.distributable(channel0.partner_state) assert channel0.distributable == channel1.partner_state.distributable(channel1.our_state) unclaimed1 = merkleroot(channel1.our_state.merkletree) unclaimed0 = merkleroot(channel0.partner_state.merkletree) assert unclaimed1 == unclaimed0 assert channel1.our_state.amount_locked == channel0.partner_state.amount_locked assert channel1.transferred_amount == channel0.partner_state.transferred_amount balance1 = channel1.our_state.balance(channel1.partner_state) balance0 = channel0.partner_state.balance(channel0.our_state) assert balance1 == balance0 assert channel1.distributable == channel1.our_state.distributable(channel1.partner_state) assert channel1.distributable == channel0.partner_state.distributable(channel0.our_state)
def test_channel_withdraw_must_not_change_merkletree(): 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_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 assert merkleroot(channel_state.partner_state.merkletree) == lock.lockhash assert channel.is_lock_pending(channel_state.partner_state, lock.secrethash) 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) new_channel = iteration.new_state withdraw = ContractReceiveChannelWithdraw( payment_network_identifier, channel_state.token_address, channel_state.identifier, lock_secret, channel_state.our_state.address, ) iteration = channel.handle_channel_withdraw(new_channel, withdraw) new_channel = iteration.new_state assert merkleroot(new_channel.partner_state.merkletree) == lock.lockhash assert not channel.is_lock_pending(new_channel.partner_state, lock.secrethash)
def register_direct_transfer(self, direct_transfer): """ Register a direct_transfer. Raises: InvalidLocksRoot: If the merkleroot of `direct_transfer` does not match the current value. """ balance_proof = direct_transfer.to_balanceproof() if balance_proof.locksroot != merkleroot(self.merkletree): raise InvalidLocksRoot(merkleroot(self.merkletree), balance_proof.locksroot) self.balance_proof = balance_proof
def compute_merkleroot_with(self, include): """ Compute the resulting merkle root if the lock `include` is added in the tree. """ if not self.is_known(include.hashlock): leaves = list(self.merkletree.layers[LEAVES]) leaves.append(sha3(include.as_bytes)) tree_with = MerkleTreeState(compute_layers(leaves)) locksroot = merkleroot(tree_with) else: locksroot = merkleroot(self.merkletree) return locksroot
def test_channelstate_get_unlock_proof(): number_of_transfers = 100 lock_amounts = cycle([1, 3, 5, 7, 11]) lock_secrets = [ make_secret(i) for i in range(number_of_transfers) ] block_number = 1000 locked_amount = 0 settle_timeout = 8 merkletree_leaves = [] locked_locks = {} unlocked_locks = {} for lock_amount, lock_secret in zip(lock_amounts, lock_secrets): block_number += 1 locked_amount += lock_amount lock_expiration = block_number + settle_timeout lock_secrethash = sha3(lock_secret) lock = HashTimeLockState( lock_amount, lock_expiration, lock_secrethash, ) merkletree_leaves.append(lock.lockhash) if random.randint(0, 1) == 0: locked_locks[lock_secrethash] = lock else: unlocked_locks[lock_secrethash] = UnlockPartialProofState(lock, lock_secret) end_state = NettingChannelEndState(HOP1, 300) end_state.secrethashes_to_lockedlocks = locked_locks end_state.secrethashes_to_unlockedlocks = unlocked_locks end_state.merkletree = MerkleTreeState(compute_layers(merkletree_leaves)) unlock_proof = channel.get_batch_unlock(end_state) assert len(unlock_proof) == len(end_state.merkletree.layers[LEAVES]) leaves_packed = b''.join(lock.encoded for lock in unlock_proof) recomputed_merkle_tree = MerkleTreeState(compute_layers( merkle_leaves_from_packed_data(leaves_packed), )) assert len(recomputed_merkle_tree.layers[LEAVES]) == len(end_state.merkletree.layers[LEAVES]) computed_merkleroot = merkleroot(recomputed_merkle_tree) assert merkleroot(end_state.merkletree) == computed_merkleroot
def test_many(tree_up_to=10): for number_of_leaves in range(1, tree_up_to): # skipping the empty tree leaves = [sha3(str(value)) for value in range(number_of_leaves)] layers = compute_layers(leaves) tree = MerkleTreeState(layers) root = merkleroot(tree) for value in leaves: proof = compute_merkleproof_for(tree, value) assert validate_proof(proof, root, value) reversed_tree = MerkleTreeState(compute_layers(reversed(leaves))) assert root == merkleroot(reversed_tree)
def test_three(): hash_0 = b'a' * 32 hash_1 = b'b' * 32 hash_2 = b'c' * 32 leaves = [hash_0, hash_1, hash_2] layers = compute_layers(leaves) tree = MerkleTreeState(layers) root = merkleroot(tree) hash_01 = ( b'me\xef\x9c\xa9=5\x16\xa4\xd3\x8a\xb7\xd9\x89\xc2\xb5\x00' b'\xe2\xfc\x89\xcc\xdc\xf8x\xf9\xc4m\xaa\xf6\xad\r[' ) assert sha3(hash_0 + hash_1) == hash_01 calculated_root = sha3(hash_2 + hash_01) proof0 = compute_merkleproof_for(tree, hash_0) proof1 = compute_merkleproof_for(tree, hash_1) proof2 = compute_merkleproof_for(tree, hash_2) assert proof0 == [hash_1, hash_2] assert root == calculated_root assert validate_proof(proof0, root, hash_0) assert proof1 == [hash_0, hash_2] assert root == calculated_root assert validate_proof(proof1, root, hash_1) # with an odd number of values, the last value wont appear by itself in the # proof since it isn't hashed with another value assert proof2 == [sha3(hash_0 + hash_1)] assert root == calculated_root assert validate_proof(proof2, root, hash_2)
def test_compute_layers_single_entry(): hash_0 = sha3(b'x') layers = compute_layers([hash_0]) assert layers[MERKLEROOT][0] == hash_0 tree = MerkleTreeState(layers) assert merkleroot(tree) == hash_0
def test_three(): hash_0 = b'a' * 32 hash_1 = b'b' * 32 hash_2 = b'c' * 32 leaves = [hash_0, hash_1, hash_2] layers = compute_layers(leaves) tree = MerkleTreeState(layers) root = merkleroot(tree) hash_01 = (b'me\xef\x9c\xa9=5\x16\xa4\xd3\x8a\xb7\xd9\x89\xc2\xb5\x00' b'\xe2\xfc\x89\xcc\xdc\xf8x\xf9\xc4m\xaa\xf6\xad\r[') assert sha3(hash_0 + hash_1) == hash_01 calculated_root = sha3(hash_2 + hash_01) proof0 = compute_merkleproof_for(tree, hash_0) proof1 = compute_merkleproof_for(tree, hash_1) proof2 = compute_merkleproof_for(tree, hash_2) assert proof0 == [hash_1, hash_2] assert root == calculated_root assert validate_proof(proof0, root, hash_0) assert proof1 == [hash_0, hash_2] assert root == calculated_root assert validate_proof(proof1, root, hash_1) # with an odd number of values, the last value wont appear by itself in the # proof since it isn't hashed with another value assert proof2 == [sha3(hash_0 + hash_1)] assert root == calculated_root assert validate_proof(proof2, root, hash_2)
def test_merkle_proof_one_lock(tester_chain, tester_nettingchannel_library_address): """ computeMerkleRoot and the python implementation must compute the same value for a merkle tree with a single lock.""" auxiliary = deploy_auxiliary_tester(tester_chain, tester_nettingchannel_library_address) amount = 10 expiration = 77 secret = sha3(b'test_merkle_proof_one_lock') secrethash = sha3(secret) lock = Lock(amount, expiration, secrethash) layers = compute_layers([lock.lockhash]) merkletree = MerkleTreeState(layers) proof = compute_merkleproof_for(merkletree, lock.lockhash) assert len(proof) == 0, 'with only one element the proof is empty' smart_contact_root = auxiliary.computeMerkleRoot( lock.as_bytes, b''.join(proof), ) assert smart_contact_root == merkleroot(merkletree)
def create_sendmediatedtransfer(channel_state, initiator, target, amount, identifier, expiration, secrethash): our_state = channel_state.our_state partner_state = channel_state.partner_state our_balance_proof = our_state.balance_proof msg = 'caller must make sure there is enough balance' assert amount <= get_distributable(our_state, partner_state), msg msg = 'caller must make sure the channel is open' assert get_status(channel_state) == CHANNEL_STATE_OPENED, msg lock = HashTimeLockState( amount, expiration, secrethash, ) merkletree = compute_merkletree_with( channel_state.our_state.merkletree, lock.lockhash, ) # The caller must ensure the same lock is not being used twice assert merkletree, 'lock is already registered' locksroot = merkleroot(merkletree) if our_balance_proof: transferred_amount = our_balance_proof.transferred_amount else: transferred_amount = 0 token = channel_state.token_address nonce = get_next_nonce(channel_state.our_state) recipient = channel_state.partner_state.address balance_proof = BalanceProofUnsignedState( nonce, transferred_amount, locksroot, channel_state.identifier, ) locked_transfer = LockedTransferUnsignedState( identifier, token, balance_proof, lock, initiator, target, ) mediatedtransfer = SendMediatedTransfer( locked_transfer, recipient, ) return mediatedtransfer, merkletree
def test_withdraw_fails_with_partial_merkle_proof(tree, tester_channels, tester_chain, settle_timeout): """ withdraw must fail if informed proof is not complete. """ pkey0, pkey1, nettingchannel, channel0, _ = tester_channels[0] current_block = tester_chain.block.number expiration = current_block + settle_timeout - 1 locks = [ make_lock( hashlock=hashlock, expiration=expiration, ) for hashlock in tree ] leaves = [sha3(lock.as_bytes) for lock in locks] layers = compute_layers(leaves) merkle_tree = MerkleTreeState(layers) opened_block = nettingchannel.opened(sender=pkey0) nonce = 1 + (opened_block * (2**32)) direct_transfer = make_direct_transfer( nonce=nonce, channel=channel0.identifier, locksroot=merkleroot(merkle_tree), recipient=privatekey_to_address(pkey1)) address = privatekey_to_address(pkey0) sign_key = PrivateKey(pkey0) direct_transfer.sign(sign_key, address) direct_transfer_hash = sha3(direct_transfer.packed().data[:-65]) nettingchannel.close( direct_transfer.nonce, direct_transfer.transferred_amount, direct_transfer.locksroot, direct_transfer_hash, direct_transfer.signature, sender=pkey1, ) for lock in locks: secret = HASHLOCKS_SECRESTS[lock.hashlock] lock_encoded = lock.as_bytes merkle_proof = compute_merkleproof_for(merkle_tree, sha3(lock_encoded)) # withdraw must fail regardless of which part of the proof is removed for hash_ in merkle_proof: tampered_proof = list(merkle_proof) tampered_proof.remove(hash_) with pytest.raises(TransactionFailed): nettingchannel.withdraw( lock_encoded, b''.join(tampered_proof), secret, sender=pkey1, )
def test_many(tree_up_to=10): for number_of_leaves in range(1, tree_up_to): # skipping the empty tree leaves = [ sha3(str(value).encode()) for value in range(number_of_leaves) ] layers = compute_layers(leaves) tree = MerkleTreeState(layers) root = merkleroot(tree) for value in leaves: proof = compute_merkleproof_for(tree, value) assert validate_proof(proof, root, value) reversed_tree = MerkleTreeState(compute_layers(reversed(leaves))) assert root == merkleroot(reversed_tree)
def update(self, amount, lockhash): self._merkletree = channel.compute_merkletree_with( self._merkletree, lockhash) if self.properties: self.properties = factories.replace( self.properties, locked_amount=self.properties.locked_amount + amount, locksroot=merkleroot(self._merkletree), nonce=self.properties.nonce + 1, ) else: self.properties = factories.BalanceProofProperties( transferred_amount=0, locked_amount=amount, nonce=1, locksroot=merkleroot(self._merkletree), canonical_identifier=self._canonical_identifier, )
def test_channel_cleared_after_our_unlock(): our_model, _ = create_model(balance=700, merkletree_width=1) partner_model, partner_key1 = create_model(balance=700, merkletree_width=0) channel_state = create_channel_from_models(our_model, partner_model, partner_key1) block_number = 1 block_hash = make_block_hash() def make_unlock(unlock_end, partner_end): batch_unlock = ContractReceiveChannelBatchUnlock( transaction_hash=make_transaction_hash(), canonical_identifier=channel_state.canonical_identifier, participant=partner_end.address, partner=unlock_end.address, locksroot=unlock_end.balance_proof.locksroot, unlocked_amount=10, returned_tokens=0, block_number=block_number, block_hash=block_hash, ) return batch_unlock settle_channel = ContractReceiveChannelSettled( transaction_hash=make_transaction_hash(), canonical_identifier=channel_state.canonical_identifier, our_onchain_locksroot=merkleroot(channel_state.our_state.merkletree), partner_onchain_locksroot=merkleroot( channel_state.partner_state.merkletree), block_number=1, block_hash=make_block_hash(), ) assert settle_channel.our_onchain_locksroot is not EMPTY_MERKLE_ROOT assert settle_channel.partner_onchain_locksroot is EMPTY_MERKLE_ROOT iteration = channel.state_transition(channel_state, settle_channel, block_number, block_hash) batch_unlock = make_unlock(channel_state.our_state, channel_state.partner_state) iteration = channel.state_transition(iteration.new_state, batch_unlock, block_number, block_hash) msg = "partner did not have any locks in the merkletree, channel should have been cleaned" assert iteration.new_state is None, msg
def assert_locked(from_channel, pending_locks): """ Assert the locks created from `from_channel`. """ # a locked transfer is registered in the _partner_ state if pending_locks: leaves = [sha3(lock.as_bytes) for lock in pending_locks] layers = compute_layers(leaves) tree = MerkleTreeState(layers) root = merkleroot(tree) else: root = EMPTY_MERKLE_ROOT assert len( from_channel.our_state.hashlocks_to_pendinglocks) == len(pending_locks) assert merkleroot(from_channel.our_state.merkletree) == root assert from_channel.our_state.amount_locked == sum( lock.amount for lock in pending_locks) assert from_channel.locked == sum(lock.amount for lock in pending_locks) for lock in pending_locks: assert lock.hashlock in from_channel.our_state.hashlocks_to_pendinglocks
def assert_locked(from_channel, pending_locks): """ Assert the locks created from `from_channel`. """ # a locked transfer is registered in the _partner_ state if pending_locks: leaves = [sha3(lock.as_bytes) for lock in pending_locks] layers = compute_layers(leaves) tree = MerkleTreeState(layers) root = merkleroot(tree) else: root = EMPTY_MERKLE_ROOT assert len(from_channel.our_state.hashlocks_to_pendinglocks) == len( pending_locks ) assert merkleroot(from_channel.our_state.merkletree) == root assert from_channel.our_state.amount_locked == sum(lock.amount for lock in pending_locks) assert from_channel.locked == sum(lock.amount for lock in pending_locks) for lock in pending_locks: assert lock.hashlock in from_channel.our_state.hashlocks_to_pendinglocks
def create_unlock( channel_state: NettingChannelState, message_identifier: typing.MessageID, payment_identifier: typing.PaymentID, secret: typing.Secret, lock: HashTimeLockState, ) -> SendUnlockAndMerkleTree: our_state = channel_state.our_state msg = 'caller must make sure the lock is known' assert is_lock_pending(our_state, lock.secrethash), msg msg = 'caller must make sure the channel is open' assert get_status(channel_state) == CHANNEL_STATE_OPENED, msg our_balance_proof = our_state.balance_proof if our_balance_proof: transferred_amount = lock.amount + our_balance_proof.transferred_amount else: transferred_amount = lock.amount merkletree = compute_merkletree_without( our_state.merkletree, lock.lockhash, ) locksroot = merkleroot(merkletree) token = channel_state.token_address nonce = get_next_nonce(our_state) recipient = channel_state.partner_state.address locked_amount = get_amount_locked( our_state) - lock.amount # the lock is still registered balance_proof = BalanceProofUnsignedState( nonce, transferred_amount, locked_amount, locksroot, channel_state.token_network_identifier, channel_state.identifier, ) queue_name = channel_state.identifier unlock_lock = SendBalanceProof( recipient, queue_name, message_identifier, payment_identifier, token, secret, balance_proof, ) return unlock_lock, merkletree
def test_one(): hash_0 = b'a' * 32 leaves = [hash_0] layers = compute_layers(leaves) tree = MerkleTreeState(layers) root = merkleroot(tree) proof = compute_merkleproof_for(tree, hash_0) assert proof == [] assert root == hash_0 assert validate_proof(proof, root, hash_0) is True
def compute_merkleroot_without(self, without): """ Compute the resulting merkle root if the lock `include` is added in the tree. """ if not self.is_known(without.hashlock): raise ValueError('unknown lock', lock=without) leaves = list(self.merkletree.layers[LEAVES]) leaves.remove(sha3(without.as_bytes)) if leaves: tree_without = MerkleTreeState(compute_layers(leaves)) locksroot = merkleroot(tree_without) else: locksroot = EMPTY_MERKLE_ROOT return locksroot
def create_BP(self, w3, cr, initiator, target, secrethash, amount, expiration, s_contract, a_contract, start_time): # self.lock.acquire() # try: # self.locked_amount[self.i] += amount + s_contract[0] # finally: # self.lock.release() # print("locked_amount ", self.locked_amount[self.i]) # uint = 32bytes, address = 20bytes, bytes32 = 32bytes leaf = pack_data(['uint256', 'uint256', 'bytes32', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'address'], [amount+s_contract[0], expiration, secrethash, s_contract[0], s_contract[1], a_contract[0][0], a_contract[1][1], start_time, target]) self.leaves[self.i][cr] = leaf temp = [] for i in list(self.leaves[self.i].values()) : temp.append(sha3(i)) layer = compute_layers(temp) tree = MerkleTreeState(layer) locksroot = "0x" + merkleroot(tree).hex() # print("locksroot ", locksroot) self.locksroot[self.i] = locksroot self.nonce +=1 message_data = LockedTransfer_structure(self.nonce, self.chain_id, cr, expiration, self.token_network.address, self.channel_identifier, self.addrs[1-self.i], target, initiator, locksroot, secrethash, self.transferred_amount[self.i], self.locked_amount[self.i], amount, s_contract[0], s_contract[1], a_contract[0][0], a_contract[1][1], start_time) packed_message_data = message_data.pack() additional_hash = '0x' + sha3(packed_message_data).hex() # print("additional_hash ", additional_hash) packed_balance = pack_data(['uint256', 'uint256', 'bytes32'], [self.transferred_amount[self.i], self.locked_amount[self.i], locksroot]) balance_hash = '0x' + sha3(packed_balance).hex() # print("balance_hash ", balance_hash) packed_balance_proof = pack_data(['uint256', 'bytes32', 'uint256', 'bytes32'], [self.channel_identifier, balance_hash, self.nonce, additional_hash]) hashBP = '0x' + sha3(packed_balance_proof).hex() signature = w3.eth.account.signHash(message_hash=hashBP, private_key=self.sk) # print("signature", signature) BP = balanceProof(message_data, additional_hash, balance_hash, signature['signature'].hex()) self.BP[self.i] = BP return BP
def test_two(): hash_0 = b'a' * 32 hash_1 = b'b' * 32 leaves = [hash_0, hash_1] layers = compute_layers(leaves) tree = MerkleTreeState(layers) root = merkleroot(tree) proof0 = compute_merkleproof_for(tree, hash_0) proof1 = compute_merkleproof_for(tree, hash_1) assert proof0 == [hash_1] assert root == sha3(hash_0 + hash_1) assert validate_proof(proof0, root, hash_0) assert proof1 == [hash_0] assert root == sha3(hash_0 + hash_1) assert validate_proof(proof1, root, hash_1)
def test_new_end_state(): """Test the defaults for an end state object.""" balance1 = 101 node_address = factories.make_address() end_state = NettingChannelEndState(node_address, balance1) lock_secret = sha3(b'test_end_state') lock_secrethash = sha3(lock_secret) assert channel.is_lock_pending(end_state, lock_secrethash) is False assert channel.is_lock_locked(end_state, lock_secrethash) is False assert channel.get_next_nonce(end_state) == 1 assert channel.get_amount_locked(end_state) == 0 assert not channel.get_known_unlocks(end_state) assert merkleroot(end_state.merkletree) == EMPTY_MERKLE_ROOT assert not end_state.secrethashes_to_lockedlocks assert not end_state.secrethashes_to_unlockedlocks
def register_locked_transfer(self, locked_transfer): """ Register the latest known transfer. The sender needs to use this method before sending a locked transfer, otherwise the calculate locksroot of the transfer message will be invalid and the transfer will be rejected by the partner. Since the sender wants the transfer to be accepted by the receiver otherwise the transfer won't proceed and the sender won't receive their fee. The receiver needs to use this method to update the container with a _valid_ transfer, otherwise the locksroot will not contain the pending transfer. The receiver needs to ensure that the merkle root has the hashlock included, otherwise it won't be able to claim it. Args: transfer (LockedTransfer): The transfer to be added. Raises: InvalidLocksRoot: If the merkleroot of `locked_transfer` does not match with the expected value. ValueError: If the transfer contains a lock that was registered previously. """ balance_proof = locked_transfer.to_balanceproof() lock = locked_transfer.lock lockhashed = sha3(lock.as_bytes) if self.is_known(lock.hashlock): raise ValueError('hashlock is already registered') leaves = list(self.merkletree.layers[LEAVES]) leaves.append(lockhashed) newtree = MerkleTreeState(compute_layers(leaves)) locksroot = merkleroot(newtree) if balance_proof.locksroot != locksroot: raise InvalidLocksRoot(locksroot, balance_proof.locksroot) self.hashlocks_to_pendinglocks[lock.hashlock] = PendingLock( lock, lockhashed) self.balance_proof = balance_proof self.merkletree = newtree
def create_unlock(channel_state, message_identifier, payment_identifier, secret, lock): msg = 'caller must make sure the lock is known' assert is_lock_pending(channel_state.our_state, lock.secrethash), msg msg = 'caller must make sure the channel is open' assert get_status(channel_state) == CHANNEL_STATE_OPENED, msg our_balance_proof = channel_state.our_state.balance_proof if our_balance_proof: transferred_amount = lock.amount + our_balance_proof.transferred_amount else: transferred_amount = lock.amount merkletree = compute_merkletree_without( channel_state.our_state.merkletree, lock.lockhash, ) locksroot = merkleroot(merkletree) token = channel_state.token_address nonce = get_next_nonce(channel_state.our_state) recipient = channel_state.partner_state.address balance_proof = BalanceProofUnsignedState( nonce, transferred_amount, locksroot, channel_state.identifier, ) queue_name = channel_state.identifier unlock_lock = SendBalanceProof( recipient, queue_name, message_identifier, payment_identifier, token, secret, balance_proof, ) return unlock_lock, merkletree
def test_merkle_proof(tree, tester_chain, tester_nettingchannel_library_address): """ computeMerkleRoot and the python implementation must compute the same value. """ auxiliary = deploy_auxiliary_tester(tester_chain, tester_nettingchannel_library_address) hashes = [sha3(element) for element in tree] layers = compute_layers(hashes) merkletree = MerkleTreeState(layers) for element in tree: proof = compute_merkleproof_for(merkletree, sha3(element)) smart_contact_root = auxiliary.computeMerkleRoot( element, b''.join(proof), ) assert smart_contact_root == merkleroot(merkletree)
def test_merkle_proof( tree, tester_chain, tester_nettingchannel_library_address): """ computeMerkleRoot and the python implementation must compute the same value. """ auxiliary = deploy_auxiliary_tester(tester_chain, tester_nettingchannel_library_address) hashes = [sha3(element) for element in tree] layers = compute_layers(hashes) merkletree = MerkleTreeState(layers) for element in tree: proof = compute_merkleproof_for(merkletree, sha3(element)) smart_contact_root = auxiliary.computeMerkleRoot( element, b''.join(proof), ) assert smart_contact_root == merkleroot(merkletree)
def increase_transferred_amount(from_channel, to_channel, amount): # increasing the transferred amount by a value larger than distributable # would put one end of the channel in a negative balance, which is # forbidden assert from_channel.distributable >= amount, 'operation would end up in a incosistent state' identifier = 1 nonce = from_channel.get_next_nonce() direct_transfer_message = DirectTransfer( identifier=identifier, nonce=nonce, token=from_channel.token_address, channel=from_channel.channel_address, transferred_amount=from_channel.transferred_amount + amount, recipient=from_channel.partner_state.address, locksroot=merkleroot(from_channel.partner_state.merkletree), ) # skipping the netting channel register_transfer because the message is not # signed from_channel.our_state.register_direct_transfer(direct_transfer_message) to_channel.partner_state.register_direct_transfer(direct_transfer_message)
def create_directtransfer(self, amount, identifier): """ Return a DirectTransfer message. This message needs to be signed and registered with the channel before sent. """ if not self.can_transfer: raise ValueError( 'Transfer not possible, no funding or channel closed.') from_ = self.our_state to_ = self.partner_state distributable = from_.distributable(to_) if amount <= 0 or amount > distributable: log.debug( 'Insufficient funds', amount=amount, distributable=distributable, ) raise ValueError('Insufficient funds') transferred_amount = from_.transferred_amount + amount current_locksroot = merkleroot(to_.merkletree) nonce = self.get_next_nonce() return DirectTransfer( identifier=identifier, nonce=nonce, token=self.token_address, channel=self.channel_address, transferred_amount=transferred_amount, recipient=to_.address, locksroot=current_locksroot, )
def register_secretmessage(self, message_secret): balance_proof = message_secret.to_balanceproof() hashlock = sha3(message_secret.secret) pendinglock = self.hashlocks_to_pendinglocks.get(hashlock) if not pendinglock: pendinglock = self.hashlocks_to_unclaimedlocks[hashlock] lock = pendinglock.lock lockhashed = sha3(lock.as_bytes) if not isinstance(balance_proof, BalanceProofState): raise ValueError('balance_proof must be a BalanceProof instance') if not self.is_known(lock.hashlock): raise ValueError('hashlock is not registered') leaves = list(self.merkletree.layers[LEAVES]) leaves.remove(lockhashed) if leaves: layers = compute_layers(leaves) new_merkletree = MerkleTreeState(layers) new_locksroot = merkleroot(new_merkletree) else: new_merkletree = EMPTY_MERKLE_TREE new_locksroot = EMPTY_MERKLE_ROOT if balance_proof.locksroot != new_locksroot: raise InvalidLocksRoot(new_locksroot, balance_proof.locksroot) if lock.hashlock in self.hashlocks_to_pendinglocks: del self.hashlocks_to_pendinglocks[lock.hashlock] else: del self.hashlocks_to_unclaimedlocks[lock.hashlock] self.merkletree = new_merkletree self.balance_proof = balance_proof
def create_directtransfer(self, amount, identifier): """ Return a DirectTransfer message. This message needs to be signed and registered with the channel before sent. """ if not self.can_transfer: raise ValueError('Transfer not possible, no funding or channel closed.') from_ = self.our_state to_ = self.partner_state distributable = from_.distributable(to_) if amount <= 0 or amount > distributable: log.debug( 'Insufficient funds', amount=amount, distributable=distributable, ) raise ValueError('Insufficient funds') transferred_amount = from_.transferred_amount + amount current_locksroot = merkleroot(to_.merkletree) nonce = self.get_next_nonce() return DirectTransfer( identifier=identifier, nonce=nonce, token=self.token_address, channel=self.channel_address, transferred_amount=transferred_amount, recipient=to_.address, locksroot=current_locksroot, )
def create_unlock(channel_state, identifier, secret, lock): msg = 'caller must make sure the lock is known' assert is_known(channel_state.our_state, lock.hashlock), msg our_balance_proof = channel_state.our_state.balance_proof if our_balance_proof: transferred_amount = lock.amount + our_balance_proof.transferred_amount else: transferred_amount = lock.amount merkletree = compute_merkletree_without( channel_state.our_state.merkletree, lock.lockhash, ) locksroot = merkleroot(merkletree) token = channel_state.token_address nonce = get_next_nonce(channel_state.our_state) recipient = channel_state.partner_state.address balance_proof = BalanceProofUnsignedState( nonce, transferred_amount, locksroot, channel_state.identifier, ) unlock_lock = SendBalanceProof2( identifier, token, recipient, secret, balance_proof, ) return unlock_lock, merkletree
def test_empty(): tree = MerkleTreeState([[EMPTY_MERKLE_ROOT]]) assert merkleroot(tree) == EMPTY_MERKLE_ROOT
def make_signed_transfer_for( channel_state: NettingChannelState = EMPTY, properties: LockedTransferSignedStateProperties = None, defaults: LockedTransferSignedStateProperties = None, compute_locksroot: bool = False, allow_invalid: bool = False, only_transfer: bool = True, ) -> LockedTransferSignedState: properties: LockedTransferSignedStateProperties = create_properties( properties or LockedTransferSignedStateProperties(), defaults or SIGNED_TRANSFER_FOR_CHANNEL_DEFAULTS, ) channel_state = if_empty(channel_state, create(NettingChannelStateProperties())) if not allow_invalid: expiration = properties.transfer.expiration valid = channel_state.reveal_timeout < expiration < channel_state.settle_timeout assert valid, 'Expiration must be between reveal_timeout and settle_timeout.' pubkey = properties.pkey.public_key.format(compressed=False) assert publickey_to_address(pubkey) == properties.sender if properties.sender == channel_state.our_state.address: recipient = channel_state.partner_state.address elif properties.sender == channel_state.partner_state.address: recipient = channel_state.our_state.address else: assert False, 'Given sender does not participate in given channel.' if compute_locksroot: lock = Lock( amount=properties.transfer.amount, expiration=properties.transfer.expiration, secrethash=sha3(properties.transfer.secret), ) locksroot = merkleroot(channel.compute_merkletree_with( merkletree=channel_state.partner_state.merkletree, lockhash=sha3(lock.as_bytes), )) else: locksroot = properties.transfer.balance_proof.locksroot if only_transfer: balance_proof_properties = BalanceProofProperties( locksroot=locksroot, channel_identifier=channel_state.identifier, transferred_amount=0, locked_amount=properties.transfer.amount, ) else: balance_proof_properties = BalanceProofProperties( locksroot=locksroot, channel_identifier=channel_state.identifier, ) transfer = create( LockedTransferSignedStateProperties( recipient=recipient, transfer=LockedTransferProperties( balance_proof=balance_proof_properties, ), ), defaults=properties, ) if not allow_invalid: is_valid, msg, _ = channel.is_valid_lockedtransfer( transfer_state=transfer, channel_state=channel_state, sender_state=channel_state.partner_state, receiver_state=channel_state.our_state, ) assert is_valid, msg return transfer
def register_transfer_from_to(self, block_number, transfer, from_state, to_state): # noqa pylint: disable=too-many-branches,too-many-statements """ Validates and register a signed transfer, updating the channel's state accordingly. Note: The transfer must be registered before it is sent, not on acknowledgement. That is necessary for two reasons: - Guarantee that the transfer is valid. - Avoid sending a new transaction without funds. Raises: InsufficientBalance: If the transfer is negative or above the distributable amount. InvalidLocksRoot: If locksroot check fails. InvalidNonce: If the expected nonce does not match. ValueError: If there is an address mismatch (token or node address). """ if transfer.channel != self.channel_address: raise ValueError('Channel address mismatch') if transfer.sender != from_state.address: raise ValueError('Unsigned transfer') # nonce is changed only when a transfer is un/registered, if the test # fails either we are out of sync, a message out of order, or it's a # forged transfer is_invalid_nonce = (transfer.nonce < 1 or (from_state.nonce is not None and transfer.nonce != from_state.nonce + 1)) if is_invalid_nonce: # this may occur on normal operation if log.isEnabledFor(logging.INFO): log.info( 'INVALID NONCE', node=pex(self.our_address), from_=pex(transfer.sender), to=pex(to_state.address), expected_nonce=from_state.nonce, nonce=transfer.nonce, ) raise InvalidNonce(transfer) # if the locksroot is out-of-sync (because a transfer was created while # a Secret was in traffic) the balance _will_ be wrong, so first check # the locksroot and then the balance if isinstance(transfer, LockedTransfer): if from_state.is_known(transfer.lock.hashlock): # this may occur on normal operation if log.isEnabledFor(logging.INFO): lockhashes = list( from_state.hashlocks_to_unclaimedlocks.values()) lockhashes.extend( from_state.hashlocks_to_pendinglocks.values()) log.info( 'duplicated lock', node=pex(self.our_address), from_=pex(from_state.address), to=pex(to_state.address), hashlock=pex(transfer.lock.hashlock), lockhash=pex(sha3(transfer.lock.as_bytes)), lockhashes=lpex(lockhashes), received_locksroot=pex(transfer.locksroot), ) raise ValueError('hashlock is already registered') # As a receiver: Check that all locked transfers are registered in # the locksroot, if any hashlock is missing there is no way to # claim it while the channel is closing expected_locksroot = from_state.compute_merkleroot_with( transfer.lock) if expected_locksroot != transfer.locksroot: # this should not happen if log.isEnabledFor(logging.WARN): lockhashes = list( from_state.hashlocks_to_unclaimedlocks.values()) lockhashes.extend( from_state.hashlocks_to_pendinglocks.values()) log.warn( 'LOCKSROOT MISMATCH', node=pex(self.our_address), from_=pex(from_state.address), to=pex(to_state.address), lockhash=pex(sha3(transfer.lock.as_bytes)), lockhashes=lpex(lockhashes), expected_locksroot=pex(expected_locksroot), received_locksroot=pex(transfer.locksroot), ) raise InvalidLocksRoot(expected_locksroot, transfer.locksroot) # For mediators: This is registering the *mediator* paying # transfer. The expiration of the lock must be `reveal_timeout` # blocks smaller than the *received* paying transfer. This cannot # be checked by the paying channel alone. # # For the initiators: As there is no backing transfer, the # expiration is arbitrary, using the channel settle_timeout as an # upper limit because the node receiving the transfer will use it # as an upper bound while mediating. # # For the receiver: A lock that expires after the settle period # just means there is more time to withdraw it. end_settle_period = self.get_settle_expiration(block_number) expires_after_settle = transfer.lock.expiration > end_settle_period is_sender = transfer.sender == self.our_address if is_sender and expires_after_settle: if log.isEnabledFor(logging.ERROR): log.error( 'Lock expires after the settlement period.', node=pex(self.our_address), from_=pex(from_state.address), to=pex(to_state.address), lock_expiration=transfer.lock.expiration, current_block=block_number, end_settle_period=end_settle_period, ) raise ValueError('Lock expires after the settlement period.') # only check the balance if the locksroot matched if transfer.transferred_amount < from_state.transferred_amount: if log.isEnabledFor(logging.ERROR): log.error( 'NEGATIVE TRANSFER', node=pex(self.our_state.address), from_=pex(from_state.address), to=pex(to_state.address), transfer=transfer, ) raise ValueError('Negative transfer') amount = transfer.transferred_amount - from_state.transferred_amount distributable = from_state.distributable(to_state) if isinstance(transfer, DirectTransfer): if amount > distributable: raise InsufficientBalance(transfer) elif isinstance(transfer, LockedTransfer): if amount + transfer.lock.amount > distributable: raise InsufficientBalance(transfer) elif isinstance(transfer, Secret): hashlock = sha3(transfer.secret) lock = from_state.get_lock_by_hashlock(hashlock) transferred_amount = from_state.transferred_amount + lock.amount # transfer.transferred_amount could be larger than the previous # transferred_amount + lock.amount, that scenario is a bug of the # payer if transfer.transferred_amount != transferred_amount: raise ValueError( 'invalid transferred_amount, expected: {} got: {}'.format( transferred_amount, transfer.transferred_amount, )) # all checks need to be done before the internal state of the channel # is changed, otherwise if a check fails and the state was changed the # channel will be left trashed if isinstance(transfer, LockedTransfer): if log.isEnabledFor(logging.DEBUG): lockhashes = list( from_state.hashlocks_to_unclaimedlocks.values()) lockhashes.extend( from_state.hashlocks_to_pendinglocks.values()) log.debug( 'REGISTERED LOCK', node=pex(self.our_state.address), from_=pex(from_state.address), to=pex(to_state.address), currentlocksroot=pex(merkleroot(from_state.merkletree)), lockhashes=lpex(lockhashes), lock_amount=transfer.lock.amount, lock_expiration=transfer.lock.expiration, lock_hashlock=pex(transfer.lock.hashlock), lockhash=pex(sha3(transfer.lock.as_bytes)), ) from_state.register_locked_transfer(transfer) # register this channel as waiting for the secret (the secret can # be revealed through a message or a blockchain log) self.external_state.register_channel_for_hashlock( self, transfer.lock.hashlock, ) if isinstance(transfer, DirectTransfer): from_state.register_direct_transfer(transfer) elif isinstance(transfer, Secret): from_state.register_secretmessage(transfer) if log.isEnabledFor(logging.DEBUG): log.debug( 'REGISTERED TRANSFER', node=pex(self.our_state.address), from_=pex(from_state.address), to=pex(to_state.address), transfer=repr(transfer), transferred_amount=from_state.transferred_amount, nonce=from_state.nonce, current_locksroot=pex(merkleroot(from_state.merkletree)), )
def test_unlock(raiden_network, token_addresses, deposit): """Unlock can be called on a closed channel.""" alice_app, bob_app = raiden_network registry_address = alice_app.raiden.default_registry.address token_address = token_addresses[0] token_proxy = alice_app.raiden.chain.token(token_address) token_network_identifier = views.get_token_network_identifier_by_token_address( views.state_from_app(alice_app), alice_app.raiden.default_registry.address, token_address, ) alice_initial_balance = token_proxy.balance_of(alice_app.raiden.address) bob_initial_balance = token_proxy.balance_of(bob_app.raiden.address) alice_to_bob_amount = 10 identifier = 1 secret = pending_mediated_transfer( raiden_network, token_network_identifier, alice_to_bob_amount, identifier, ) secrethash = sha3(secret) # This is the current state of the protocol: # # A -> B LockedTransfer # B -> A SecretRequest # - protocol didn't continue alice_bob_channel = get_channelstate(alice_app, bob_app, token_network_identifier) bob_alice_channel = get_channelstate(bob_app, alice_app, token_network_identifier) lock = channel.get_lock(alice_bob_channel.our_state, secrethash) assert lock assert_synched_channel_state( token_network_identifier, alice_app, deposit, [lock], bob_app, deposit, [], ) # get proof, that locked transfermessage was in merkle tree, with locked.root unlock_proof = channel.compute_proof_for_lock( alice_bob_channel.our_state, secret, lock, ) assert validate_proof( unlock_proof.merkle_proof, merkleroot(bob_alice_channel.partner_state.merkletree), sha3(lock.encoded), ) assert unlock_proof.lock_encoded == lock.encoded assert unlock_proof.secret == secret # A ChannelClose event will be generated, this will be polled by both apps # and each must start a task for calling settle RaidenAPI(bob_app.raiden).channel_close( registry_address, token_address, alice_app.raiden.address, ) # Unlock will not be called because the secret was not revealed assert lock.expiration > alice_app.raiden.chain.block_number() assert lock.secrethash == sha3(secret) nettingchannel_proxy = bob_app.raiden.chain.netting_channel( bob_alice_channel.identifier, ) nettingchannel_proxy.unlock(unlock_proof) waiting.wait_for_settle( alice_app.raiden, registry_address, token_address, [alice_bob_channel.identifier], alice_app.raiden.alarm.wait_time, ) alice_bob_channel = get_channelstate(alice_app, bob_app, token_network_identifier) bob_alice_channel = get_channelstate(bob_app, alice_app, token_network_identifier) alice_netted_balance = alice_initial_balance + deposit - alice_to_bob_amount bob_netted_balance = bob_initial_balance + deposit + alice_to_bob_amount assert token_proxy.balance_of(alice_app.raiden.address) == alice_netted_balance assert token_proxy.balance_of(bob_app.raiden.address) == bob_netted_balance # Now let's query the WAL to see if the state changes were logged as expected state_changes = alice_app.raiden.wal.storage.get_statechanges_by_identifier( from_identifier=0, to_identifier='latest', ) alice_bob_channel = get_channelstate(alice_app, bob_app, token_network_identifier) bob_alice_channel = get_channelstate(bob_app, alice_app, token_network_identifier) assert must_contain_entry(state_changes, ContractReceiveChannelUnlock, { 'payment_network_identifier': registry_address, 'token_address': token_address, 'channel_identifier': alice_bob_channel.identifier, 'secrethash': secrethash, 'secret': secret, 'receiver': bob_app.raiden.address, })
def test_withdraw_tampered_lock_amount( tree, tester_channels, tester_chain, tester_token, settle_timeout): """ withdraw must fail if the lock amonut is tampered. """ pkey0, pkey1, nettingchannel, channel0, _ = tester_channels[0] current_block = tester_chain.block.number expiration = current_block + settle_timeout - 1 locks = [ make_lock( hashlock=hashlock, expiration=expiration, ) for hashlock in tree ] leaves = [sha3(lock.as_bytes) for lock in locks] layers = compute_layers(leaves) merkle_tree = MerkleTreeState(layers) opened_block = nettingchannel.opened(sender=pkey0) nonce = 1 + (opened_block * (2 ** 32)) direct_transfer = make_direct_transfer( nonce=nonce, channel=channel0.channel_address, locksroot=merkleroot(merkle_tree), token=tester_token.address, recipient=privatekey_to_address(pkey1) ) address = privatekey_to_address(pkey0) sign_key = PrivateKey(pkey0) direct_transfer.sign(sign_key, address) direct_transfer_hash = sha3(direct_transfer.packed().data[:-65]) nettingchannel.close( direct_transfer.nonce, direct_transfer.transferred_amount, direct_transfer.locksroot, direct_transfer_hash, direct_transfer.signature, sender=pkey1, ) for lock in locks: secret = HASHLOCKS_SECRESTS[lock.hashlock] lock_encoded = lock.as_bytes merkle_proof = compute_merkleproof_for(merkle_tree, sha3(lock_encoded)) tampered_lock = make_lock( amount=lock.amount * 100, hashlock=lock.hashlock, expiration=lock.expiration, ) tampered_lock_encoded = sha3(tampered_lock.as_bytes) with pytest.raises(TransactionFailed): nettingchannel.withdraw( tampered_lock_encoded, b''.join(merkle_proof), secret, sender=pkey1, )
def create_sendlockedtransfer( channel_state: NettingChannelState, initiator: typing.InitiatorAddress, target: typing.TargetAddress, amount: typing.PaymentAmount, message_identifier: typing.MessageID, payment_identifier: typing.PaymentID, expiration: typing.BlockExpiration, secrethash: typing.SecretHash, ) -> SendLockedTransfer: our_state = channel_state.our_state partner_state = channel_state.partner_state our_balance_proof = our_state.balance_proof msg = 'caller must make sure there is enough balance' assert amount <= get_distributable(our_state, partner_state), msg msg = 'caller must make sure the channel is open' assert get_status(channel_state) == CHANNEL_STATE_OPENED, msg lock = HashTimeLockState( amount, expiration, secrethash, ) merkletree = compute_merkletree_with( channel_state.our_state.merkletree, lock.lockhash, ) # The caller must ensure the same lock is not being used twice assert merkletree, 'lock is already registered' locksroot = merkleroot(merkletree) if our_balance_proof: transferred_amount = our_balance_proof.transferred_amount else: transferred_amount = 0 token = channel_state.token_address nonce = get_next_nonce(channel_state.our_state) recipient = channel_state.partner_state.address locked_amount = get_amount_locked( our_state) + amount # the new lock is not registered yet balance_proof = BalanceProofUnsignedState( nonce, transferred_amount, locked_amount, locksroot, channel_state.token_network_identifier, channel_state.identifier, ) locked_transfer = LockedTransferUnsignedState( payment_identifier, token, balance_proof, lock, initiator, target, ) queue_name = channel_state.identifier lockedtransfer = SendLockedTransfer( recipient, queue_name, message_identifier, locked_transfer, ) return lockedtransfer, merkletree
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(b'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 register_transfer_from_to( self, block_number, transfer, from_state, to_state): # noqa pylint: disable=too-many-branches,too-many-statements """ Validates and register a signed transfer, updating the channel's state accordingly. Note: The transfer must be registered before it is sent, not on acknowledgement. That is necessary for two reasons: - Guarantee that the transfer is valid. - Avoid sending a new transaction without funds. Raises: InsufficientBalance: If the transfer is negative or above the distributable amount. InvalidLocksRoot: If locksroot check fails. InvalidNonce: If the expected nonce does not match. ValueError: If there is an address mismatch (token or node address). """ if transfer.channel != self.channel_address: raise ValueError('Channel address mismatch') if transfer.sender != from_state.address: raise ValueError('Unsigned transfer') # nonce is changed only when a transfer is un/registered, if the test # fails either we are out of sync, a message out of order, or it's a # forged transfer is_invalid_nonce = ( transfer.nonce < 1 or ( from_state.nonce is not None and transfer.nonce != from_state.nonce + 1 ) ) if is_invalid_nonce: # this may occur on normal operation if log.isEnabledFor(logging.INFO): log.info( 'INVALID NONCE', node=pex(self.our_address), from_=pex(transfer.sender), to=pex(to_state.address), expected_nonce=from_state.nonce, nonce=transfer.nonce, ) raise InvalidNonce(transfer) # if the locksroot is out-of-sync (because a transfer was created while # a Secret was in traffic) the balance _will_ be wrong, so first check # the locksroot and then the balance if isinstance(transfer, LockedTransfer): if from_state.is_known(transfer.lock.hashlock): # this may occur on normal operation if log.isEnabledFor(logging.INFO): lockhashes = list(from_state.hashlocks_to_unclaimedlocks.values()) lockhashes.extend(from_state.hashlocks_to_pendinglocks.values()) log.info( 'duplicated lock', node=pex(self.our_address), from_=pex(from_state.address), to=pex(to_state.address), hashlock=pex(transfer.lock.hashlock), lockhash=pex(sha3(transfer.lock.as_bytes)), lockhashes=lpex(str(l).encode() for l in lockhashes), received_locksroot=pex(transfer.locksroot), ) raise ValueError('hashlock is already registered') # As a receiver: Check that all locked transfers are registered in # the locksroot, if any hashlock is missing there is no way to # claim it while the channel is closing expected_locksroot = from_state.compute_merkleroot_with(transfer.lock) if expected_locksroot != transfer.locksroot: # this should not happen if log.isEnabledFor(logging.WARN): lockhashes = list(from_state.hashlocks_to_unclaimedlocks.values()) lockhashes.extend(from_state.hashlocks_to_pendinglocks.values()) log.warn( 'LOCKSROOT MISMATCH', node=pex(self.our_address), from_=pex(from_state.address), to=pex(to_state.address), lockhash=pex(sha3(transfer.lock.as_bytes)), lockhashes=lpex(str(l).encode() for l in lockhashes), expected_locksroot=pex(expected_locksroot), received_locksroot=pex(transfer.locksroot), ) raise InvalidLocksRoot(expected_locksroot, transfer.locksroot) # For mediators: This is registering the *mediator* paying # transfer. The expiration of the lock must be `reveal_timeout` # blocks smaller than the *received* paying transfer. This cannot # be checked by the paying channel alone. # # For the initiators: As there is no backing transfer, the # expiration is arbitrary, using the channel settle_timeout as an # upper limit because the node receiving the transfer will use it # as an upper bound while mediating. # # For the receiver: A lock that expires after the settle period # just means there is more time to withdraw it. end_settle_period = self.get_settle_expiration(block_number) expires_after_settle = transfer.lock.expiration > end_settle_period is_sender = transfer.sender == self.our_address if is_sender and expires_after_settle: if log.isEnabledFor(logging.ERROR): log.error( 'Lock expires after the settlement period.', node=pex(self.our_address), from_=pex(from_state.address), to=pex(to_state.address), lock_expiration=transfer.lock.expiration, current_block=block_number, end_settle_period=end_settle_period, ) raise ValueError('Lock expires after the settlement period.') # only check the balance if the locksroot matched if transfer.transferred_amount < from_state.transferred_amount: if log.isEnabledFor(logging.ERROR): log.error( 'NEGATIVE TRANSFER', node=pex(self.our_state.address), from_=pex(from_state.address), to=pex(to_state.address), transfer=transfer, ) raise ValueError('Negative transfer') amount = transfer.transferred_amount - from_state.transferred_amount distributable = from_state.distributable(to_state) if isinstance(transfer, DirectTransfer): if amount > distributable: raise InsufficientBalance(transfer) elif isinstance(transfer, LockedTransfer): if amount + transfer.lock.amount > distributable: raise InsufficientBalance(transfer) elif isinstance(transfer, Secret): hashlock = sha3(transfer.secret) lock = from_state.get_lock_by_hashlock(hashlock) transferred_amount = from_state.transferred_amount + lock.amount # transfer.transferred_amount could be larger than the previous # transferred_amount + lock.amount, that scenario is a bug of the # payer if transfer.transferred_amount != transferred_amount: raise ValueError( 'invalid transferred_amount, expected: {} got: {}'.format( transferred_amount, transfer.transferred_amount, ) ) # all checks need to be done before the internal state of the channel # is changed, otherwise if a check fails and the state was changed the # channel will be left trashed if isinstance(transfer, LockedTransfer): if log.isEnabledFor(logging.DEBUG): lockhashes = list(from_state.hashlocks_to_unclaimedlocks.values()) lockhashes.extend(from_state.hashlocks_to_pendinglocks.values()) log.debug( 'REGISTERED LOCK', node=pex(self.our_state.address), from_=pex(from_state.address), to=pex(to_state.address), currentlocksroot=pex(merkleroot(from_state.merkletree)), lockhashes=lpex(str(l).encode() for l in lockhashes), lock_amount=transfer.lock.amount, lock_expiration=transfer.lock.expiration, lock_hashlock=pex(transfer.lock.hashlock), lockhash=pex(sha3(transfer.lock.as_bytes)), ) from_state.register_locked_transfer(transfer) # register this channel as waiting for the secret (the secret can # be revealed through a message or a blockchain log) self.external_state.register_channel_for_hashlock( self, transfer.lock.hashlock, ) if isinstance(transfer, DirectTransfer): from_state.register_direct_transfer(transfer) elif isinstance(transfer, Secret): from_state.register_secretmessage(transfer) if log.isEnabledFor(logging.DEBUG): log.debug( 'REGISTERED TRANSFER', node=pex(self.our_state.address), from_=pex(from_state.address), to=pex(to_state.address), transfer=repr(transfer), transferred_amount=from_state.transferred_amount, nonce=from_state.nonce, current_locksroot=pex(merkleroot(from_state.merkletree)), )
def test_receiver_cannot_spend_locked_amount(): token_address = make_address() privkey1, address1 = make_privkey_address() privkey2, address2 = make_privkey_address() balance1 = 33 balance2 = 11 reveal_timeout = 7 settle_timeout = 21 block_number = 7 our_state = ChannelEndState(address1, balance1, None, EMPTY_MERKLE_TREE) partner_state = ChannelEndState(address2, balance2, None, EMPTY_MERKLE_TREE) external_state = make_external_state() test_channel = Channel( our_state, partner_state, external_state, token_address, reveal_timeout, settle_timeout, ) amount1 = balance2 expiration = block_number + settle_timeout receive_mediated_transfer0 = test_channel.create_mediatedtransfer( address1, address2, fee=0, amount=amount1, identifier=1, expiration=expiration, hashlock=sha3(b'test_locked_amount_cannot_be_spent'), ) receive_mediated_transfer0.sign(privkey2, address2) test_channel.register_transfer( block_number, receive_mediated_transfer0, ) # trying to send one unit of the locked token amount2 = balance1 + 1 lock2 = Lock( amount=amount2, expiration=expiration, hashlock=sha3(b'test_locked_amount_cannot_be_spent2'), ) layers = compute_layers([sha3(lock2.as_bytes)]) tree2 = MerkleTreeState(layers) locksroot2 = merkleroot(tree2) send_mediated_transfer0 = MediatedTransfer( identifier=1, nonce=1, token=token_address, channel=test_channel.channel_address, transferred_amount=0, recipient=address2, locksroot=locksroot2, lock=lock2, target=address2, initiator=address1, fee=0, ) send_mediated_transfer0.sign(privkey1, address1) # address1 balance is all locked with pytest.raises(InsufficientBalance): test_channel.register_transfer( block_number, send_mediated_transfer0, )
def test_sender_cannot_overspend(): token_address = make_address() privkey1, address1 = make_privkey_address() address2 = make_address() balance1 = 70 balance2 = 110 reveal_timeout = 5 settle_timeout = 15 block_number = 10 our_state = ChannelEndState(address1, balance1, None, EMPTY_MERKLE_TREE) partner_state = ChannelEndState(address2, balance2, None, EMPTY_MERKLE_TREE) external_state = make_external_state() test_channel = Channel( our_state, partner_state, external_state, token_address, reveal_timeout, settle_timeout, ) amount = balance1 expiration = block_number + settle_timeout sent_mediated_transfer0 = test_channel.create_mediatedtransfer( address1, address2, fee=0, amount=amount, identifier=1, expiration=expiration, hashlock=sha3(b'test_locked_amount_cannot_be_spent'), ) sent_mediated_transfer0.sign(privkey1, address1) test_channel.register_transfer( block_number, sent_mediated_transfer0, ) lock2 = Lock( amount=amount, expiration=expiration, hashlock=sha3(b'test_locked_amount_cannot_be_spent2'), ) leaves = [ sha3(sent_mediated_transfer0.lock.as_bytes), sha3(lock2.as_bytes), ] tree2 = MerkleTreeState(compute_layers(leaves)) locksroot2 = merkleroot(tree2) sent_mediated_transfer1 = MediatedTransfer( identifier=2, nonce=sent_mediated_transfer0.nonce + 1, token=token_address, channel=test_channel.channel_address, transferred_amount=0, recipient=address2, locksroot=locksroot2, lock=lock2, target=address2, initiator=address1, fee=0, ) sent_mediated_transfer1.sign(privkey1, address1) # address1 balance is all locked with pytest.raises(InsufficientBalance): test_channel.register_transfer( block_number, sent_mediated_transfer1, )
def make_signed_transfer_for( channel_state: NettingChannelState = EMPTY, properties: LockedTransferSignedStateProperties = None, defaults: LockedTransferSignedStateProperties = None, compute_locksroot: bool = False, allow_invalid: bool = False, only_transfer: bool = True, ) -> LockedTransferSignedState: properties: LockedTransferSignedStateProperties = create_properties( properties or LockedTransferSignedStateProperties(), defaults or SIGNED_TRANSFER_FOR_CHANNEL_DEFAULTS, ) channel_state = if_empty(channel_state, create(NettingChannelStateProperties())) if not allow_invalid: ok = channel_state.reveal_timeout < properties.expiration < channel_state.settle_timeout assert ok, "Expiration must be between reveal_timeout and settle_timeout." assert privatekey_to_address(properties.pkey) == properties.sender if properties.sender == channel_state.our_state.address: recipient = channel_state.partner_state.address elif properties.sender == channel_state.partner_state.address: recipient = channel_state.our_state.address else: assert False, "Given sender does not participate in given channel." if compute_locksroot: lock = Lock( amount=properties.amount, expiration=properties.expiration, secrethash=sha3(properties.secret), ) locksroot = merkleroot( channel.compute_merkletree_with( merkletree=channel_state.partner_state.merkletree, lockhash=sha3(lock.as_bytes))) else: locksroot = properties.locksroot if only_transfer: transfer_properties = LockedTransferUnsignedStateProperties( locksroot=locksroot, canonical_identifier=channel_state.canonical_identifier, locked_amount=properties.amount, ) else: transfer_properties = LockedTransferUnsignedStateProperties( locksroot=locksroot, canonical_identifier=channel_state.canonical_identifier) transfer = create( LockedTransferSignedStateProperties(recipient=recipient, **transfer_properties.__dict__), defaults=properties, ) if not allow_invalid: is_valid, msg, _ = channel.is_valid_lockedtransfer( transfer_state=transfer, channel_state=channel_state, sender_state=channel_state.partner_state, receiver_state=channel_state.our_state, ) assert is_valid, msg return transfer
def test_settlement(raiden_network, settle_timeout, reveal_timeout): alice_app, bob_app = raiden_network # pylint: disable=unbalanced-tuple-unpacking setup_messages_cb() alice_graph = list(alice_app.raiden.token_to_channelgraph.values())[0] bob_graph = list(bob_app.raiden.token_to_channelgraph.values())[0] assert alice_graph.token_address == bob_graph.token_address alice_bob_channel = alice_graph.partneraddress_to_channel[bob_app.raiden.address] bob_alice_channel = bob_graph.partneraddress_to_channel[alice_app.raiden.address] alice_deposit = alice_bob_channel.balance bob_deposit = bob_alice_channel.balance token = alice_app.raiden.chain.token(alice_bob_channel.token_address) alice_balance = token.balance_of(alice_app.raiden.address) bob_balance = token.balance_of(bob_app.raiden.address) alice_chain = alice_app.raiden.chain alice_to_bob_amount = 10 expiration = alice_app.raiden.chain.block_number() + reveal_timeout + 1 secret = b'secretsecretsecretsecretsecretse' hashlock = sha3(secret) assert bob_app.raiden.address in alice_graph.partneraddress_to_channel nettingaddress0 = alice_bob_channel.external_state.netting_channel.address nettingaddress1 = bob_alice_channel.external_state.netting_channel.address assert nettingaddress0 == nettingaddress1 identifier = 1 fee = 0 transfermessage = alice_bob_channel.create_mediatedtransfer( alice_app.raiden.address, bob_app.raiden.address, fee, alice_to_bob_amount, identifier, expiration, hashlock, ) alice_app.raiden.sign(transfermessage) alice_bob_channel.register_transfer( alice_app.raiden.get_block_number(), transfermessage, ) bob_alice_channel.register_transfer( bob_app.raiden.get_block_number(), transfermessage, ) assert_synched_channels( alice_bob_channel, alice_deposit, [], bob_alice_channel, bob_deposit, [transfermessage.lock], ) # At this point we are assuming the following: # # A -> B MediatedTransfer # B -> A SecretRequest # A -> B RevealSecret # - protocol didn't continue # # B knowns the secret but doesn't have an updated balance proof, B needs to # call settle. # get proof, that locked transfermessage was in merkle tree, with locked.root lock = bob_alice_channel.partner_state.get_lock_by_hashlock(hashlock) assert sha3(secret) == hashlock unlock_proof = bob_alice_channel.partner_state.compute_proof_for_lock(secret, lock) root = merkleroot(bob_alice_channel.partner_state.merkletree) assert validate_proof( unlock_proof.merkle_proof, root, sha3(transfermessage.lock.as_bytes), ) assert unlock_proof.lock_encoded == transfermessage.lock.as_bytes assert unlock_proof.secret == secret # a ChannelClose event will be generated, this will be polled by both apps # and each must start a task for calling settle balance_proof = transfermessage.to_balanceproof() bob_alice_channel.external_state.close(balance_proof) wait_until_block(alice_chain, alice_chain.block_number() + 1) assert alice_bob_channel.external_state.close_event.wait(timeout=15) assert bob_alice_channel.external_state.close_event.wait(timeout=15) assert alice_bob_channel.external_state.closed_block != 0 assert bob_alice_channel.external_state.closed_block != 0 assert alice_bob_channel.external_state.settled_block == 0 assert bob_alice_channel.external_state.settled_block == 0 # unlock will not be called by Channel.channel_closed because we did not # register the secret assert lock.expiration > alice_app.raiden.chain.block_number() assert lock.hashlock == sha3(secret) bob_alice_channel.external_state.netting_channel.withdraw([unlock_proof]) settle_expiration = alice_chain.block_number() + settle_timeout + 2 wait_until_block(alice_chain, settle_expiration) assert alice_bob_channel.external_state.settle_event.wait(timeout=15) assert bob_alice_channel.external_state.settle_event.wait(timeout=15) # settle must be called by the apps triggered by the ChannelClose event, # and the channels must update it's state based on the ChannelSettled event assert alice_bob_channel.external_state.settled_block != 0 assert bob_alice_channel.external_state.settled_block != 0 address0 = alice_app.raiden.address address1 = bob_app.raiden.address alice_netted_balance = alice_balance + alice_deposit - alice_to_bob_amount bob_netted_balance = bob_balance + bob_deposit + alice_to_bob_amount assert token.balance_of(address0) == alice_netted_balance assert token.balance_of(address1) == bob_netted_balance # Now let's query the WAL to see if the state changes were logged as expected state_changes = [ change[1] for change in get_all_state_changes(alice_app.raiden.transaction_log) if not isinstance(change[1], Block) ] assert must_contain_entry(state_changes, ContractReceiveClosed, { 'channel_address': nettingaddress0, 'closing_address': bob_app.raiden.address, 'block_number': alice_bob_channel.external_state.closed_block, }) assert must_contain_entry(state_changes, ReceiveSecretReveal, { 'secret': secret, 'sender': bob_app.raiden.address, }) assert must_contain_entry(state_changes, ContractReceiveWithdraw, { 'channel_address': nettingaddress0, 'secret': secret, 'receiver': bob_app.raiden.address, }) assert must_contain_entry(state_changes, ContractReceiveSettled, { 'channel_address': nettingaddress0, 'block_number': bob_alice_channel.external_state.settled_block, })
def test_withdraw_tampered_merkle_proof(tree, tester_channels, tester_chain, settle_timeout): """ withdraw must fail if the proof is tampered. """ pkey0, pkey1, nettingchannel, channel0, _ = tester_channels[0] current_block = tester_chain.block.number expiration = current_block + settle_timeout - 1 locks = [ make_lock( hashlock=hashlock, expiration=expiration, ) for hashlock in tree ] leaves = [sha3(lock.as_bytes) for lock in locks] layers = compute_layers(leaves) merkle_tree = MerkleTreeState(layers) opened_block = nettingchannel.opened(sender=pkey0) nonce = 1 + (opened_block * (2 ** 32)) direct_transfer = make_direct_transfer( nonce=nonce, channel=channel0.channel_address, locksroot=merkleroot(merkle_tree), recipient=privatekey_to_address(pkey1) ) address = privatekey_to_address(pkey0) sign_key = PrivateKey(pkey0) direct_transfer.sign(sign_key, address) direct_transfer_hash = sha3(direct_transfer.packed().data[:-65]) nettingchannel.close( direct_transfer.nonce, direct_transfer.transferred_amount, direct_transfer.locksroot, direct_transfer_hash, direct_transfer.signature, sender=pkey1, ) for lock in locks: secret = HASHLOCKS_SECRESTS[lock.hashlock] lock_encoded = lock.as_bytes merkle_proof = compute_merkleproof_for(merkle_tree, sha3(lock_encoded)) # withdraw must fail regardless of which part of the proof is tampered for pos, hash_ in enumerate(merkle_proof): # changing arbitrary bytes from the proof tampered_hash = bytearray(hash_) tampered_hash[6], tampered_hash[7] = tampered_hash[7], tampered_hash[6] tampered_proof = list(merkle_proof) tampered_proof[pos] = tampered_hash joiner = b'' with pytest.raises(TransactionFailed): nettingchannel.withdraw( lock_encoded, joiner.join(tampered_proof), secret, sender=pkey1, )
def __repr__(self): return '<MerkleTreeState root:{}>'.format( pex(merkleroot(self)), )