def sign_close(self, sender: str, open_block_number: int, balance: int): """Sign an agreement for a channel closing. Returns: channel close signature (str): a signature that can be used client-side to close the channel by directly calling contract's close method on-chain. """ assert is_checksum_address(sender) if (sender, open_block_number) not in self.channels: raise NoOpenChannel('Channel does not exist or has been closed' '(sender=%s, open_block_number=%d)' % (sender, open_block_number)) c = self.channels[sender, open_block_number] if c.is_closed: raise NoOpenChannel('Channel closing has been requested already.') assert balance is not None if c.last_signature is None: raise NoBalanceProofReceived('Payment has not been registered.') if balance != c.balance: raise InvalidBalanceProof('Requested closing balance does not match latest one.') c.is_closed = True c.mtime = time.time() receiver_sig = sign_close( self.private_key, sender, open_block_number, c.balance, self.channel_manager_contract.address ) self.state.set_channel(c) self.log.info('signed cooperative closing message (sender %s, block number %s)', sender, open_block_number) return receiver_sig
def close_channel_cooperatively(channel: Channel, privkey_receiver: str, balance: int = None): if balance is not None: channel.update_balance(balance) closing_sig = sign_close(privkey_receiver, channel.balance_sig) assert channel.close_cooperatively(closing_sig)
def sign_close(self, sender, open_block_number, balance): """Sign an agreement for a channel closing.""" assert is_checksum_address(sender) if (sender, open_block_number) not in self.channels: raise NoOpenChannel('Channel does not exist or has been closed' '(sender=%s, open_block_number=%d)' % (sender, open_block_number)) c = self.channels[sender, open_block_number] if c.is_closed: raise NoOpenChannel('Channel closing has been requested already.') assert balance is not None if c.last_signature is None: raise NoBalanceProofReceived('Payment has not been registered.') if balance != c.balance: raise InvalidBalanceProof( 'Requested closing balance does not match latest one.') c.is_closed = True c.mtime = time.time() receiver_sig = sign_close(self.private_key, sender, open_block_number, c.balance, self.channel_manager_contract.address) self.state.set_channel(c) self.log.info( 'signed cooperative closing message (sender %s, block number %s)', sender, open_block_number) return receiver_sig
def sign_close(self, sender: str, open_block_number: int, balance: int): """Sign an agreement for a channel closing. Returns: channel close signature (str): a signature that can be used client-side to close the channel by directly calling contract's close method on-chain. """ assert is_checksum_address(sender) if (sender, open_block_number) not in self.channels: raise NoOpenChannel('Channel does not exist or has been closed' '(sender=%s, open_block_number=%d)' % (sender, open_block_number)) c = self.channels[sender, open_block_number] # if c.is_closed: # raise NoOpenChannel('Channel closing has been requested already.') assert balance is not None # if balance != 0 and c.last_signature is None: # raise NoBalanceProofReceived('Payment has not been registered.') if balance != c.balance: raise InvalidBalanceProof( 'Requested closing balance does not match latest one.') # c.is_closed = True c.mtime = time.time() receiver_sig = sign_close(self.private_key, sender, open_block_number, c.balance, self.channel_manager_contract.address) self.state.set_channel(c) self.log.info( 'signed cooperative closing message (sender %s, block number %s)', sender, open_block_number) return receiver_sig
def test_sign_close_contract(channel_manager_contract: Contract): sig = sign_close( RECEIVER_PRIVATE_KEY, SENDER_ADDR, 315832, 13, channel_manager_contract.address ) receiver_recovered = channel_manager_contract.call().extractClosingSignature( SENDER_ADDR, 315832, 13, sig ) assert is_same_address(receiver_recovered, RECEIVER_ADDR)
def close_channel_cooperatively(channel: Channel, privkey_receiver: str, contract_address: str, balance: int = None): if balance is not None: channel.update_balance(balance) closing_sig = sign_close(privkey_receiver, channel.sender, channel.block, channel.balance, contract_address) assert channel.close_cooperatively(closing_sig)
def test_cooperative_close(client: Client, receiver_privkey, receiver_address): c = client.get_suitable_channel(receiver_address, 3) c.create_transfer(3) assert c.deposit >= 3 assert c.balance == 3 sig = sign_close(receiver_privkey, c.balance_sig) assert c.close_cooperatively(sig) assert c.state == Channel.State.closed
def test_cooperative_close(client: Client, receiver_privkey, receiver_address): c = client.get_suitable_channel(receiver_address, 3) assert c is not None c.create_transfer(3) assert c.deposit >= 3 assert c.balance == 3 sig = sign_close(receiver_privkey, c.sender, c.block, c.balance, client.context.channel_manager.address) assert c.close_cooperatively(sig) assert c.state == Channel.State.closed
def close_channel_cooperatively( channel: Channel, privkey_receiver: str, contract_address: str, balance: int=None ): if balance is not None: channel.update_balance(balance) closing_sig = sign_close( privkey_receiver, channel.sender, channel.block, channel.balance, contract_address ) assert channel.close_cooperatively(closing_sig)
def close_channel(self, sender: str, open_block_number: int): """Close and settle a channel. Params: sender (str): sender address open_block_number (int): block the channel was open in """ assert is_checksum_address(sender) if not (sender, open_block_number) in self.channels: self.log.warning( "attempt to close a non-registered channel (sender=%s open_block=%s" % (sender, open_block_number) ) return c = self.channels[sender, open_block_number] if c.last_signature is None: raise NoBalanceProofReceived('Cannot close a channel without a balance proof.') # send closing tx closing_sig = sign_close( self.private_key, sender, open_block_number, c.balance, self.channel_manager_contract.address ) raw_tx = create_signed_contract_transaction( self.private_key, self.channel_manager_contract, 'cooperativeClose', [ self.state.receiver, open_block_number, c.balance, decode_hex(c.last_signature), closing_sig ] ) # update local state c.is_closed = True c.mtime = time.time() self.state.set_channel(c) try: txid = self.blockchain.web3.eth.sendRawTransaction(raw_tx) self.log.info('sent channel close(sender %s, block number %s, tx %s)', sender, open_block_number, txid) except InsufficientBalance: c.state = ChannelState.CLOSE_PENDING self.state.set_channel(c) raise
def delete(self, sender_address): parser = reqparse.RequestParser() parser.add_argument('open_block', type=int, help='block the channel was opened') parser.add_argument('signature', help='last balance proof signature') args = parser.parse_args() if args.signature is None: return "Bad signature format", 400 if args.block is None: return "No opening block specified", 400 if sender_address and is_address(sender_address): sender_address = to_checksum_address(sender_address) channel = self.channel_manager.channels[sender_address, args.block] if channel.last_signature != args.signature: return "Invalid or outdated balance signature", 400 ret = sign_close(self.channel_manager.private_key, args.signature) return ret, 200
def delete(self, sender_address): parser = reqparse.RequestParser() parser.add_argument('open_block', type=int, help='block the channel was opened') parser.add_argument('signature', help='last balance proof signature') args = parser.parse_args() if args.signature is None: return "Bad signature format", 400 if args.block is None: return "No opening block specified", 400 channel = self.channel_manager.channels[sender_address, args.block] if channel.last_signature != args.signature: return "Invalid or outdated balance signature", 400 ret = sign_close(self.channel_manager.private_key, args.signature) return ret, 200
def test_cooperative_close(client: Client, receiver_privkey, receiver_address): c = client.get_suitable_channel(receiver_address, 3) assert c is not None c.create_transfer(3) assert c.deposit >= 3 assert c.balance == 3 sig = sign_close( receiver_privkey, c.sender, c.block, c.balance, client.context.channel_manager.address ) assert c.close_cooperatively(sig) assert c.state == Channel.State.closed
def close_open_channels( private_key: str, state: ChannelManagerState, channel_manager_contract: Contract, repetitions=None, wait=lambda: gevent.sleep(1) ): web3 = channel_manager_contract.web3 channels_with_balance_proof = [ c for c in state.channels.values() if c.last_signature is not None ] n_channels = len(state.channels) n_no_balance_proof = len(state.channels) - len(channels_with_balance_proof) n_txs_sent = 0 pending_txs = {} if repetitions: iterator = range(repetitions) else: iterator = count() for _ in iterator: n_non_existant = 0 n_invalid_balance_proof = 0 for channel in channels_with_balance_proof: # lookup channel on block chain channel_id = (channel.sender, channel.receiver, channel.open_block_number) try: channel_info = channel_manager_contract.call().getChannelInfo(*channel_id) except (BadFunctionCallOutput, TransactionFailed): n_non_existant += 1 continue _, deposit, settle_block_number, closing_balance, transferred_tokens = channel_info is_valid = channel.balance <= deposit n_invalid_balance_proof += int(not is_valid) close_sent = (channel.sender, channel.open_block_number) in pending_txs # send close if open or settling with wrong balance, unless already done if not close_sent and is_valid: closing_sig = sign_close( private_key, channel.sender, channel.open_block_number, channel.balance, channel_manager_contract.address ) raw_tx = create_signed_contract_transaction( private_key, channel_manager_contract, 'cooperativeClose', [ channel.receiver, channel.open_block_number, channel.balance, decode_hex(channel.last_signature), closing_sig ] ) tx_hash = web3.eth.sendRawTransaction(raw_tx) log.info('sending close tx (hash: {})'.format(tx_hash)) pending_txs[channel.sender, channel.open_block_number] = tx_hash n_txs_sent += 1 # print status msg_status = 'block: {}, pending txs: {}, total txs sent: {}' msg_progress = ( 'initial channels: {}, settled: {}, pending txs: {}, no BP: {}, invalid BP: {}' ) log.info(msg_status.format(web3.eth.blockNumber, len(pending_txs), n_txs_sent)) log.info(msg_progress.format( n_channels, n_non_existant, len(pending_txs), n_no_balance_proof, n_invalid_balance_proof )) # wait for next block block_before = web3.eth.blockNumber while web3.eth.blockNumber == block_before: wait() # update pending txs confirmed = [] for channel_id, tx_hash in pending_txs.items(): receipt = web3.eth.getTransactionReceipt(tx_hash) if receipt is None: continue tx = web3.eth.getTransaction(tx_hash) if receipt.gasUsed == tx.gas: raise ValueError('Transaction failed, out of gas (hash: {})'.format(tx_hash)) confirmed.append(channel_id) for channel_id in confirmed: pending_txs.pop(channel_id)
def close_open_channels( private_key: str, state: ChannelManagerState, channel_manager_contract: Contract, gas_price: int = None, wait=lambda: gevent.sleep(1) ): """Closes all open channels that belong to a receiver. Args: private_key (str): receiver's private key state (ChannelManagerState): channel manager state channel_manager_contract (str): address of the channel manager contract gas_price (int, optional): gas price you want to use (a network default will be used if not set) wait (callable): pause between checks for a succesfull transaction """ web3 = channel_manager_contract.web3 pending_txs = {} for channel in state.channels.values(): if not channel.last_signature: continue channel_id = (channel.sender, channel.receiver, channel.open_block_number) try: channel_info = channel_manager_contract.call().getChannelInfo(*channel_id) except (BadFunctionCallOutput, TransactionFailed): continue _, deposit, settle_block_number, closing_balance, transferred_tokens = channel_info available_tokens = channel.balance - transferred_tokens if not channel.balance <= deposit: log.info( 'Invalid channel: balance %d > deposit %d', channel.balance, deposit ) continue closing_sig = utils.sign_close( private_key, channel.sender, channel.open_block_number, channel.balance, channel_manager_contract.address ) raw_tx = utils.create_signed_contract_transaction( private_key, channel_manager_contract, 'cooperativeClose', [ channel.receiver, channel.open_block_number, channel.balance, decode_hex(channel.last_signature), closing_sig ], gas_price=gas_price, ) tx_hash = web3.eth.sendRawTransaction(raw_tx) log.info( 'Sending cooperative close tx (hash: %s): %d from %r', encode_hex(tx_hash), available_tokens, channel_id ) pending_txs[channel_id] = (tx_hash, available_tokens) success = 0 total_tokens = 0 total_gas = 0 gas_price = 0 for channel_id, close_info in pending_txs.items(): tx_hash, available_tokens = close_info receipt = None # wait for tx to be mined while True: receipt = web3.eth.getTransactionReceipt(tx_hash) if not receipt or not receipt.blockNumber: wait() else: break tx = web3.eth.getTransaction(tx_hash) total_gas += receipt.gasUsed gas_price = tx.gasPrice if receipt.gasUsed == tx.gas or getattr(receipt, 'status', None) == 0: log.error( 'Transaction failed (hash: %s, tokens: %d, channel: %r)', encode_hex(tx_hash), available_tokens, channel_id ) else: log.info( 'Transaction success (hash: %s, tokens: %d, channel: %r)', encode_hex(tx_hash), available_tokens, channel_id ) success += 1 total_tokens += available_tokens log.info( 'FINISHED Close all channels: total tokens recovered: %d, ' 'transactions succeeded: %d, total gas cost: %s ETH', total_tokens, success, web3.fromWei(total_gas * gas_price, 'ether'), )
def close_open_channels(private_key: str, state: ChannelManagerState, channel_manager_contract: Contract, gas_price: int = None, wait=lambda: gevent.sleep(1)): web3 = channel_manager_contract.web3 pending_txs = {} for channel in state.channels.values(): if not channel.last_signature: continue channel_id = (channel.sender, channel.receiver, channel.open_block_number) try: channel_info = channel_manager_contract.call().getChannelInfo( *channel_id) except (BadFunctionCallOutput, TransactionFailed): continue _, deposit, settle_block_number, closing_balance, transferred_tokens = channel_info available_tokens = channel.balance - transferred_tokens if not channel.balance <= deposit: log.info('Invalid channel: balance %d > deposit %d', channel.balance, deposit) continue closing_sig = utils.sign_close(private_key, channel.sender, channel.open_block_number, channel.balance, channel_manager_contract.address) raw_tx = utils.create_signed_contract_transaction( private_key, channel_manager_contract, 'cooperativeClose', [ channel.receiver, channel.open_block_number, channel.balance, decode_hex(channel.last_signature), closing_sig ], gas_price=gas_price, ) tx_hash = web3.eth.sendRawTransaction(raw_tx) log.info('Sending cooperative close tx (hash: %s): %d from %r', encode_hex(tx_hash), available_tokens, channel_id) pending_txs[channel_id] = (tx_hash, available_tokens) success = 0 total_tokens = 0 total_gas = 0 gas_price = 0 for channel_id, close_info in pending_txs.items(): tx_hash, available_tokens = close_info receipt = None # wait for tx to be mined while True: receipt = web3.eth.getTransactionReceipt(tx_hash) if not receipt or not receipt.blockNumber: wait() else: break tx = web3.eth.getTransaction(tx_hash) total_gas += receipt.gasUsed gas_price = tx.gasPrice if receipt.gasUsed == tx.gas or getattr(receipt, 'status', None) == 0: log.error('Transaction failed (hash: %s, tokens: %d, channel: %r)', encode_hex(tx_hash), available_tokens, channel_id) else: log.info('Transaction success (hash: %s, tokens: %d, channel: %r)', encode_hex(tx_hash), available_tokens, channel_id) success += 1 total_tokens += available_tokens log.info( 'FINISHED Close all channels: total tokens recovered: %d, ' 'transactions succeeded: %d, total gas cost: %s ETH', total_tokens, success, web3.fromWei(total_gas * gas_price, 'ether'), )
def test_verify_closing_sign(channel_manager_address: str): sig = sign_close(RECEIVER_PRIVATE_KEY, SENDER_ADDR, 315832, 13, channel_manager_address) receiver_recovered = verify_closing_sig(SENDER_ADDR, 315832, 13, sig, channel_manager_address) assert is_same_address(receiver_recovered, RECEIVER_ADDR)
def test_sign_close_contract(channel_manager_contract: Contract): sig = sign_close(RECEIVER_PRIVATE_KEY, SENDER_ADDR, 315832, 13, channel_manager_contract.address) receiver_recovered = channel_manager_contract.call( ).extractClosingSignature(SENDER_ADDR, 315832, 13, sig) assert is_same_address(receiver_recovered, RECEIVER_ADDR)
def test_verify_closing_sign(channel_manager_address: str): sig = sign_close( RECEIVER_PRIVATE_KEY, SENDER_ADDR, 315832, 13, channel_manager_address ) receiver_recovered = verify_closing_sig(SENDER_ADDR, 315832, 13, sig, channel_manager_address) assert is_same_address(receiver_recovered, RECEIVER_ADDR)