def make_signed_balance_proof( nonce: typing.Nonce = EMPTY, transferred_amount: typing.TokenAmount = EMPTY, locked_amount: typing.TokenAmount = EMPTY, token_network_address: typing.TokenNetworkID = EMPTY, channel_identifier: typing.ChannelID = EMPTY, locksroot: typing.Locksroot = EMPTY, extra_hash: typing.Keccak256 = EMPTY, private_key: PrivateKey = EMPTY, sender_address: typing.Address = EMPTY, ) -> BalanceProofSignedState: nonce = if_empty(nonce, make_uint256()) transferred_amount = if_empty(transferred_amount, make_uint256()) locked_amount = if_empty(locked_amount, make_uint256()) token_network_address = if_empty(token_network_address, make_address()) channel_identifier = if_empty(channel_identifier, make_uint256()) locksroot = if_empty(locksroot, make_32bytes()) extra_hash = if_empty(extra_hash, make_keccak_hash()) private_key = if_empty(private_key, make_privatekey()) sender_address = if_empty(sender_address, make_address()) balance_hash = hash_balance_data( transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=locksroot, ) data_to_sign = balance_proof.pack_balance_proof( nonce=nonce, balance_hash=balance_hash, additional_hash=extra_hash, channel_identifier=channel_identifier, token_network_identifier=token_network_address, chain_id=UNIT_CHAIN_ID, ) signature = eth_sign(privkey=private_key, data=data_to_sign) return BalanceProofSignedState( nonce=nonce, transferred_amount=transferred_amount, locked_amount=locked_amount, locksroot=locksroot, token_network_identifier=token_network_address, channel_identifier=channel_identifier, message_hash=extra_hash, signature=signature, sender=sender_address, chain_id=UNIT_CHAIN_ID, )
def _data_to_sign(self) -> bytes: balance_hash = hash_balance_data( self.transferred_amount, self.locked_amount, self.locksroot, ) balance_proof_packed = pack_balance_proof( nonce=self.nonce, balance_hash=balance_hash, additional_hash=self.message_hash, channel_identifier=self.channel_identifier, token_network_identifier=typing.TokenNetworkID(self.token_network_address), chain_id=self.chain_id, ) return balance_proof_packed
def _(properties: BalanceProofSignedStateProperties, defaults=None) -> BalanceProofSignedState: defaults = defaults or BALANCE_PROOF_SIGNED_STATE_DEFAULTS params = _properties_to_dict(properties, defaults) params.update( _properties_to_dict(params.pop('balance_proof'), defaults.balance_proof), ) if params['signature'] is EMPTY: keys = ('transferred_amount', 'locked_amount', 'locksroot') balance_hash = hash_balance_data(**_partial_dict(params, *keys)) keys = ('nonce', 'channel_identifier', 'token_network_identifier') data_to_sign = balance_proof.pack_balance_proof( balance_hash=balance_hash, additional_hash=params['message_hash'], chain_id=UNIT_CHAIN_ID, **_partial_dict(params, *keys), ) params['signature'] = eth_sign(privkey=params.pop('pkey'), data=data_to_sign) return BalanceProofSignedState(**params)
def _(properties: BalanceProofSignedStateProperties, defaults=None) -> BalanceProofSignedState: defaults = defaults or BALANCE_PROOF_SIGNED_STATE_DEFAULTS params = _properties_to_dict(properties, defaults) params.update( _properties_to_dict(params.pop('balance_proof'), defaults.balance_proof), ) if params['signature'] is EMPTY: keys = ('transferred_amount', 'locked_amount', 'locksroot') balance_hash = hash_balance_data(**_partial_dict(params, *keys)) keys = ('nonce', 'channel_identifier', 'token_network_identifier') data_to_sign = balance_proof.pack_balance_proof( balance_hash=balance_hash, additional_hash=params['message_hash'], chain_id=UNIT_CHAIN_ID, **_partial_dict(params, *keys), ) params['signature'] = eth_sign(privkey=params.pop('pkey'), data=data_to_sign) return BalanceProofSignedState(**params)
def find_max_pending_transfers(gas_limit): """Measure gas consumption of TokenNetwork.unlock() depending on number of pending transfers and find the maximum number of pending transfers so gas_limit is not exceeded.""" tester = ContractTester(generate_keys=2) tester.deploy_contract('SecretRegistry') tester.deploy_contract( 'HumanStandardToken', _initialAmount=100000, _decimalUnits=3, _tokenName='SomeToken', _tokenSymbol='SMT', ) tester.deploy_contract( 'TokenNetwork', _token_address=tester.contract_address('HumanStandardToken'), _secret_registry=tester.contract_address('SecretRegistry'), _chain_id=1, _settlement_timeout_min=100, _settlement_timeout_max=200, ) tester.call_transaction( 'HumanStandardToken', 'transfer', _to=tester.accounts[1], _value=10000, ) receipt = tester.call_transaction( 'TokenNetwork', 'openChannel', participant1=tester.accounts[0], participant2=tester.accounts[1], settle_timeout=150, ) channel_identifier = int(hexlify(receipt['logs'][0]['topics'][1]), 16) tester.call_transaction( 'HumanStandardToken', 'approve', sender=tester.accounts[0], _spender=tester.contract_address('TokenNetwork'), _value=10000, ) tester.call_transaction( 'HumanStandardToken', 'approve', sender=tester.accounts[1], _spender=tester.contract_address('TokenNetwork'), _value=5000, ) tester.call_transaction( 'TokenNetwork', 'setTotalDeposit', channel_identifier=channel_identifier, participant=tester.accounts[0], total_deposit=5000, partner=tester.accounts[1], ) tester.call_transaction( 'TokenNetwork', 'setTotalDeposit', channel_identifier=channel_identifier, participant=tester.accounts[1], total_deposit=2000, partner=tester.accounts[0], ) print( "Measuring unlock()'s gas cost for different Merkle tree widths, can take a while..." ) before_closing = tester.tester.take_snapshot() enough = 0 too_much = 1024 nonce = 10 additional_hash = urandom(32) token_network_identifier = tester.contract_address('TokenNetwork') while enough + 1 < too_much: tree_size = (enough + too_much) // 2 tester.tester.revert_to_snapshot(before_closing) pending_transfers_tree = get_pending_transfers_tree( tester.web3, unlockable_amounts=[1] * tree_size, ) balance_hash = hash_balance_data(3000, 2000, pending_transfers_tree.merkle_root) data_to_sign = pack_balance_proof( nonce=nonce, balance_hash=balance_hash, additional_hash=additional_hash, channel_identifier=channel_identifier, token_network_identifier=token_network_identifier, chain_id=1, ) signature = eth_sign(privkey=tester.private_keys[1], data=data_to_sign) tester.call_transaction( 'TokenNetwork', 'closeChannel', channel_identifier=channel_identifier, partner=tester.accounts[1], balance_hash=balance_hash, nonce=nonce, additional_hash=additional_hash, signature=signature, ) tester.tester.mine_blocks(160) # close settlement window tester.call_transaction( 'TokenNetwork', 'settleChannel', channel_identifier=channel_identifier, participant1=tester.accounts[0], participant1_transferred_amount=0, participant1_locked_amount=0, participant1_locksroot=b'\x00' * 32, participant2=tester.accounts[1], participant2_transferred_amount=3000, participant2_locked_amount=2000, participant2_locksroot=pending_transfers_tree.merkle_root, ) receipt = tester.call_transaction( 'TokenNetwork', 'unlock', channel_identifier=channel_identifier, participant=tester.accounts[0], partner=tester.accounts[1], merkle_tree_leaves=pending_transfers_tree.packed_transfers, ) gas_used = receipt['gasUsed'] if gas_used <= gas_limit: enough = tree_size print( f'{tree_size} pending transfers work ({gas_used} gas needed to unlock)' ) else: too_much = tree_size print( f'{tree_size} pending transfers are too much ({gas_used} gas needed to unlock)' )
def test_request_monitoring(): partner_signer = LocalSigner(PARTNER_PRIVKEY) balance_proof = make_balance_proof(signer=partner_signer, amount=1) partner_signed_balance_proof = SignedBlindedBalanceProof.from_balance_proof_signed_state( balance_proof, ) request_monitoring = RequestMonitoring( onchain_balance_proof=partner_signed_balance_proof, reward_amount=55, ) assert request_monitoring with pytest.raises(ValueError): request_monitoring.to_dict() request_monitoring.sign(signer) as_dict = request_monitoring.to_dict() assert RequestMonitoring.from_dict(as_dict) == request_monitoring packed = request_monitoring.pack(request_monitoring.packed()) assert RequestMonitoring.unpack(packed) == request_monitoring # RequestMonitoring can be created directly from BalanceProofSignedState direct_created = RequestMonitoring.from_balance_proof_signed_state( balance_proof, reward_amount=55, ) with pytest.raises(ValueError): # equality test uses `validated` packed format assert direct_created == request_monitoring direct_created.sign(signer) # Instances created from same balance proof are equal assert direct_created == request_monitoring other_balance_proof = make_balance_proof(signer=partner_signer, amount=2) other_instance = RequestMonitoring.from_balance_proof_signed_state( other_balance_proof, reward_amount=55, ) other_instance.sign(signer) # different balance proof ==> non-equality assert other_instance != request_monitoring # test signature verification reward_proof_data = pack_reward_proof( request_monitoring.balance_proof.channel_identifier, request_monitoring.reward_amount, request_monitoring.balance_proof.token_network_address, request_monitoring.balance_proof.chain_id, request_monitoring.balance_proof.nonce, ) assert recover(reward_proof_data, request_monitoring.reward_proof_signature) == ADDRESS blinded_data = pack_balance_proof_update( nonce=request_monitoring.balance_proof.nonce, balance_hash=request_monitoring.balance_proof.balance_hash, additional_hash=request_monitoring.balance_proof.additional_hash, canonical_identifier=CanonicalIdentifier( chain_identifier=request_monitoring.balance_proof.chain_id, token_network_address=request_monitoring.balance_proof. token_network_address, channel_identifier=request_monitoring.balance_proof. channel_identifier, ), partner_signature=request_monitoring.balance_proof.signature, ) assert recover(blinded_data, request_monitoring.non_closing_signature) == ADDRESS balance_proof_data = pack_balance_proof( nonce=request_monitoring.balance_proof.nonce, balance_hash=request_monitoring.balance_proof.balance_hash, additional_hash=request_monitoring.balance_proof.additional_hash, canonical_identifier=CanonicalIdentifier( chain_identifier=request_monitoring.balance_proof.chain_id, token_network_address=request_monitoring.balance_proof. token_network_address, channel_identifier=request_monitoring.balance_proof. channel_identifier, ), ) assert recover( balance_proof_data, request_monitoring.balance_proof.signature, ) == PARTNER_ADDRESS assert request_monitoring.verify_request_monitoring( PARTNER_ADDRESS, ADDRESS)
def find_max_pending_transfers(gas_limit): """Measure gas consumption of TokenNetwork.unlock() depending on number of pending transfers and find the maximum number of pending transfers so gas_limit is not exceeded.""" tester = ContractTester(generate_keys=2) tester.deploy_contract('SecretRegistry') tester.deploy_contract( 'HumanStandardToken', _initialAmount=100000, _decimalUnits=3, _tokenName='SomeToken', _tokenSymbol='SMT', ) tester.deploy_contract( 'TokenNetwork', _token_address=tester.contract_address('HumanStandardToken'), _secret_registry=tester.contract_address('SecretRegistry'), _chain_id=1, _settlement_timeout_min=100, _settlement_timeout_max=200, ) tester.call_transaction( 'HumanStandardToken', 'transfer', _to=tester.accounts[1], _value=10000, ) receipt = tester.call_transaction( 'TokenNetwork', 'openChannel', participant1=tester.accounts[0], participant2=tester.accounts[1], settle_timeout=150, ) channel_identifier = int(encode_hex(receipt['logs'][0]['topics'][1]), 16) tester.call_transaction( 'HumanStandardToken', 'approve', sender=tester.accounts[0], _spender=tester.contract_address('TokenNetwork'), _value=10000, ) tester.call_transaction( 'HumanStandardToken', 'approve', sender=tester.accounts[1], _spender=tester.contract_address('TokenNetwork'), _value=5000, ) tester.call_transaction( 'TokenNetwork', 'setTotalDeposit', channel_identifier=channel_identifier, participant=tester.accounts[0], total_deposit=5000, partner=tester.accounts[1], ) tester.call_transaction( 'TokenNetwork', 'setTotalDeposit', channel_identifier=channel_identifier, participant=tester.accounts[1], total_deposit=2000, partner=tester.accounts[0], ) print("Measuring unlock()'s gas cost for different Merkle tree widths, can take a while...") before_closing = tester.tester.take_snapshot() enough = 0 too_much = 1024 nonce = 10 additional_hash = urandom(32) token_network_identifier = tester.contract_address('TokenNetwork') while enough + 1 < too_much: tree_size = (enough + too_much) // 2 tester.tester.revert_to_snapshot(before_closing) pending_transfers_tree = get_pending_transfers_tree( tester.web3, unlockable_amounts=[1] * tree_size, ) balance_hash = hash_balance_data(3000, 2000, pending_transfers_tree.merkle_root) data_to_sign = pack_balance_proof( nonce=nonce, balance_hash=balance_hash, additional_hash=additional_hash, channel_identifier=channel_identifier, token_network_identifier=token_network_identifier, chain_id=1, ) signature = eth_sign(privkey=tester.private_keys[1], data=data_to_sign) tester.call_transaction( 'TokenNetwork', 'closeChannel', channel_identifier=channel_identifier, partner=tester.accounts[1], balance_hash=balance_hash, nonce=nonce, additional_hash=additional_hash, signature=signature, ) tester.tester.mine_blocks(160) # close settlement window tester.call_transaction( 'TokenNetwork', 'settleChannel', channel_identifier=channel_identifier, participant1=tester.accounts[0], participant1_transferred_amount=0, participant1_locked_amount=0, participant1_locksroot=b'\x00' * 32, participant2=tester.accounts[1], participant2_transferred_amount=3000, participant2_locked_amount=2000, participant2_locksroot=pending_transfers_tree.merkle_root, ) receipt = tester.call_transaction( 'TokenNetwork', 'unlock', channel_identifier=channel_identifier, participant=tester.accounts[0], partner=tester.accounts[1], merkle_tree_leaves=pending_transfers_tree.packed_transfers, ) gas_used = receipt['gasUsed'] if gas_used <= gas_limit: enough = tree_size print(f'{tree_size} pending transfers work ({gas_used} gas needed to unlock)') else: too_much = tree_size print(f'{tree_size} pending transfers are too much ({gas_used} gas needed to unlock)')
def update_transfer( self, channel_identifier: typing.ChannelID, partner: typing.Address, balance_hash: typing.BalanceHash, nonce: typing.Nonce, additional_hash: typing.AdditionalHash, closing_signature: typing.Signature, non_closing_signature: typing.Signature, ): log_details = { 'token_network': pex(self.address), 'node': pex(self.node_address), 'partner': pex(partner), 'nonce': nonce, 'balance_hash': encode_hex(balance_hash), 'additional_hash': encode_hex(additional_hash), 'closing_signature': encode_hex(closing_signature), 'non_closing_signature': encode_hex(non_closing_signature), } log.debug('updateNonClosingBalanceProof called', **log_details) data_that_was_signed = pack_balance_proof( nonce=nonce, balance_hash=balance_hash, additional_hash=additional_hash, channel_identifier=channel_identifier, token_network_identifier=typing.TokenNetworkID(self.address), chain_id=self.proxy.contract.functions.chain_id().call(), ) try: signer_address = eth_recover( data=data_that_was_signed, signature=closing_signature, ) # InvalidSignature is raised by eth_utils.eth_recover if signature # is not bytes or has the incorrect length # # ValueError is raised if the PublicKey instantiation failed, let it # propagate because it's a memory pressure problem. # # Exception is raised if the public key recovery failed. except Exception: # pylint: disable=broad-except msg = "Couldn't verify the balance proof signature" log.critical(f'updateNonClosingBalanceProof failed, {msg}', **log_details) raise RaidenUnrecoverableError(msg) if signer_address != partner: msg = 'Invalid balance proof signature' log.critical(f'updateNonClosingBalanceProof failed, {msg}', **log_details) raise RaidenUnrecoverableError(msg) self._check_for_outdated_channel( self.node_address, partner, channel_identifier, ) detail = self.detail_channel( participant1=self.node_address, participant2=partner, channel_identifier=channel_identifier, ) if detail.state != ChannelState.CLOSED: msg = 'Channel is not in a closed state' log.critical(f'updateNonClosingBalanceProof failed, {msg}', **log_details) raise RaidenUnrecoverableError(msg) if detail.settle_block_number < self.client.block_number(): msg = ('updateNonClosingBalanceProof cannot be called ' 'because the settlement period is over') log.critical(f'updateNonClosingBalanceProof failed, {msg}', **log_details) raise RaidenRecoverableError(msg) self._check_channel_state_for_update( channel_identifier=channel_identifier, closer=partner, update_nonce=nonce, log_details=log_details, ) transaction_hash = self.proxy.transact( 'updateNonClosingBalanceProof', safe_gas_limit(GAS_REQUIRED_FOR_UPDATE_BALANCE_PROOF), channel_identifier, partner, self.node_address, balance_hash, nonce, additional_hash, closing_signature, non_closing_signature, ) self.client.poll(transaction_hash) receipt_or_none = check_transaction_threw(self.client, transaction_hash) if receipt_or_none: if detail.settle_block_number < receipt_or_none['blockNumber']: msg = ('updateNonClosingBalanceProof transaction ' 'was mined after settlement finished') log.critical(f'updateNonClosingBalanceProof failed, {msg}', **log_details) raise RaidenRecoverableError(msg) self._check_channel_state_for_update( channel_identifier=channel_identifier, closer=partner, update_nonce=nonce, log_details=log_details, ) # This should never happen if the settlement window and gas price # estimation is done properly channel_settled = self.channel_is_settled( participant1=self.node_address, participant2=partner, channel_identifier=channel_identifier, ) if channel_settled is True: msg = 'Channel is settled' log.critical(f'updateNonClosingBalanceProof failed, {msg}', **log_details) raise RaidenRecoverableError(msg) msg = 'Update NonClosing balance proof' log.critical(f'updateNonClosingBalanceProof failed, {msg}', **log_details) raise TransactionThrew(msg, receipt_or_none) log.info('updateNonClosingBalanceProof successful', **log_details)
def find_max_pending_transfers(gas_limit): """Measure gas consumption of TokenNetwork.unlock() depending on number of pending transfers and find the maximum number of pending transfers so gas_limit is not exceeded.""" tester = ContractTester(generate_keys=2) tester.deploy_contract("SecretRegistry") tester.deploy_contract( "HumanStandardToken", _initialAmount=100000, _decimalUnits=3, _tokenName="SomeToken", _tokenSymbol="SMT", ) tester.deploy_contract( "TokenNetwork", _token_address=tester.contract_address("HumanStandardToken"), _secret_registry=tester.contract_address("SecretRegistry"), _chain_id=1, _settlement_timeout_min=100, _settlement_timeout_max=200, ) tester.call_transaction("HumanStandardToken", "transfer", _to=tester.accounts[1], _value=10000) receipt = tester.call_transaction( "TokenNetwork", "openChannel", participant1=tester.accounts[0], participant2=tester.accounts[1], settle_timeout=150, ) channel_identifier = int(encode_hex(receipt["logs"][0]["topics"][1]), 16) tester.call_transaction( "HumanStandardToken", "approve", sender=tester.accounts[0], _spender=tester.contract_address("TokenNetwork"), _value=10000, ) tester.call_transaction( "HumanStandardToken", "approve", sender=tester.accounts[1], _spender=tester.contract_address("TokenNetwork"), _value=5000, ) tester.call_transaction( "TokenNetwork", "setTotalDeposit", channel_identifier=channel_identifier, participant=tester.accounts[0], total_deposit=5000, partner=tester.accounts[1], ) tester.call_transaction( "TokenNetwork", "setTotalDeposit", channel_identifier=channel_identifier, participant=tester.accounts[1], total_deposit=2000, partner=tester.accounts[0], ) print( "Measuring unlock()'s gas cost for different Merkle tree widths, can take a while..." ) before_closing = tester.tester.take_snapshot() enough = 0 too_much = 1024 nonce = 10 additional_hash = urandom(32) token_network_identifier = tester.contract_address("TokenNetwork") while enough + 1 < too_much: tree_size = (enough + too_much) // 2 tester.tester.revert_to_snapshot(before_closing) pending_transfers_tree = get_pending_transfers_tree( tester.web3, unlockable_amounts=[1] * tree_size) balance_hash = hash_balance_data(3000, 2000, pending_transfers_tree.merkle_root) # FIXME: outdated data_to_sign = pack_balance_proof( nonce=nonce, balance_hash=balance_hash, additional_hash=additional_hash, canonical_identifier=CanonicalIdentifier( chain_identifier=1, token_network_address=token_network_identifier, channel_identifier=channel_identifier, ), ) signature = LocalSigner(tester.private_keys[1]).sign(data=data_to_sign) tester.call_transaction( "TokenNetwork", "closeChannel", channel_identifier=channel_identifier, partner=tester.accounts[1], balance_hash=balance_hash, nonce=nonce, additional_hash=additional_hash, signature=signature, ) tester.tester.mine_blocks(160) # close settlement window tester.call_transaction( "TokenNetwork", "settleChannel", channel_identifier=channel_identifier, participant1=tester.accounts[0], participant1_transferred_amount=0, participant1_locked_amount=0, participant1_locksroot=b"\x00" * 32, participant2=tester.accounts[1], participant2_transferred_amount=3000, participant2_locked_amount=2000, participant2_locksroot=pending_transfers_tree.merkle_root, ) receipt = tester.call_transaction( "TokenNetwork", "unlock", channel_identifier=channel_identifier, participant=tester.accounts[0], partner=tester.accounts[1], merkle_tree_leaves=pending_transfers_tree.packed_transfers, ) gas_used = receipt["gasUsed"] if gas_used <= gas_limit: enough = tree_size print( f"{tree_size} pending transfers work ({gas_used} gas needed to unlock)" ) else: too_much = tree_size print( f"{tree_size} pending transfers are too much ({gas_used} gas needed to unlock)" )