def open_channel(self, receiver_address: str, deposit: int): """ Attempts to open a new channel to the receiver with the given deposit. Blocks until the creation transaction is found in a pending block or timeout is reached. The new channel state is returned. """ assert isinstance(receiver_address, str) assert isinstance(deposit, int) assert deposit > 0 balance = self.context.web3.eth.getBalance(self.context.address) # token_balance = self.context.token.call().balanceOf(self.context.address) if balance < deposit: log.error( 'Insufficient balance available for the specified deposit ({}/{})' .format(balance, deposit)) return None current_block = self.context.web3.eth.blockNumber log.info( 'Creating channel to {} with an initial deposit of {} @{}'.format( receiver_address, deposit, current_block)) tx = create_signed_contract_transaction(self.context.private_key, self.context.channel_manager, 'createChannel', [receiver_address], deposit) ret = self.context.web3.eth.sendRawTransaction(tx) log.warning(str(ret)) log.debug('Waiting for channel creation event on the blockchain...') filters = { '_sender_address': self.context.address, '_receiver_address': receiver_address } event = get_event_blocking(self.context.channel_manager, 'ChannelCreated', from_block=current_block + 1, to_block='latest', argument_filters=filters) if event: log.debug('Event received. Channel created in block {}.'.format( event['blockNumber'])) assert is_same_address(event['args']['_sender_address'], self.context.address) assert is_same_address(event['args']['_receiver_address'], receiver_address) channel = Channel(self.context, self.context.address, receiver_address, event['blockNumber'], event['args']['_deposit'], on_settle=lambda c: self.channels.remove(c)) self.channels.append(channel) else: log.error('Error: No event received.') channel = None return channel
def sweep_account(private_key: str, faucet_address: str, token_contract: Contract, web3: Web3, wait_for_transaction): address = privkey_to_addr(private_key) log.info('Sweeping account {}'.format(address)) token_balance = token_contract.call().balanceOf(address) if token_balance > 0: tx = create_signed_contract_transaction( private_key, token_contract, 'transfer', [faucet_address, token_balance]) try: tx_hash = web3.eth.sendRawTransaction(tx) except ValueError as e: if e.args[0]['message'].startswith('Insufficient funds.'): pass else: raise else: wait_for_transaction(tx_hash) assert token_contract.call().balanceOf(address) == 0 balance = web3.eth.getBalance(address) if balance < POT_GAS_LIMIT * GAS_PRICE: return tx = create_signed_transaction(private_key, web3, to=faucet_address, value=balance - POT_GAS_LIMIT * GAS_PRICE, gas_limit=POT_GAS_LIMIT) tx_hash = web3.eth.sendRawTransaction(tx) wait_for_transaction(tx_hash) assert web3.eth.getBalance(address) == 0, ( 'Sweeping of account {} (private key {}) failed.'.format( address, private_key))
def sweep_account(private_key: str, token_contract: Contract, web3: Web3, wait_for_transaction): address = privkey_to_addr(private_key) token_balance = token_contract.call().balanceOf(address) if token_balance > 0: tx = create_signed_contract_transaction( private_key, token_contract, 'transfer', [FAUCET_ADDRESS, token_balance]) try: tx_hash = web3.eth.sendRawTransaction(tx) except ValueError as e: if e.args[0]['message'].startswith('Insufficient funds.'): pass else: raise else: wait_for_transaction(tx_hash) balance = web3.eth.getBalance(address, 'pending') if balance < 21000 * GAS_PRICE: return tx = create_signed_transaction(private_key, web3, to=FAUCET_ADDRESS, value=balance - 21000 * GAS_PRICE, gas_limit=21000) tx_hash = web3.eth.sendRawTransaction(tx) wait_for_transaction(tx_hash) assert web3.eth.getBalance(address, 'pending') == 0, ( 'Sweeping of account {} (private key {}) failed.'.format( address, private_key))
def close_channel(self, sender, open_block_number): """Close and settle a channel.""" 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 raw_tx = create_signed_contract_transaction( self.private_key, self.channel_manager_contract, 'uncooperativeClose', [ self.state.receiver, open_block_number, c.balance, decode_hex(c.last_signature) ]) # 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 fund_account( address: str, eth_allowance: int, token_allowance: int, token_contract: Contract, web3: Web3, wait_for_transaction, faucet_private_key: str, ): log.info('Funding account {}'.format(address)) tx = create_signed_transaction( faucet_private_key, web3, to=address, value=eth_allowance ) tx_hash = web3.eth.sendRawTransaction(tx) wait_for_transaction(tx_hash) if token_allowance > 0: tx = create_signed_contract_transaction( faucet_private_key, token_contract, 'transfer', [ address, token_allowance ] ) tx_hash = web3.eth.sendRawTransaction(tx) wait_for_transaction(tx_hash)
def event_channel_close_requested(self, sender: str, open_block_number: int, balance, timeout): sender = to_checksum_address(sender) self.log.info( 'channel close requested (\n\tsender %s, \n\tblock number %s, \n\tbalance %s, \n\ttimeout %s)', sender, open_block_number, balance, timeout) job = self.jobs[self.state.receiver, sender, open_block_number] if not job.respond: self.log.info( "Didn't finish fair exchange to so don't respond to close request\n" ) return if job.interfered: self.log.info( 'Already responded to a close (\n\tsender %s \n\topen_block_number %s, \n\tbalance %s)\n', sender, open_block_number, balance) return assert job.last_hash == job.all_hashes[-1] assert job.last_signature == job.all_signatures[-1] assert job.last_customer_sig == job.all_customer_signatures[-1] assert job.last_round_number == job.all_round_numbers[-1] if self.try_to_make_some_money and len(job.all_hashes) > 1: evidence_hash = job.all_hashes[-2] evidence_sig = job.all_signatures[-2] evidence_customer_sig = job.all_customer_signatures[-2] evidence_round_number = job.all_round_numbers[-2] else: evidence_hash = job.last_hash evidence_sig = job.last_signature evidence_customer_sig = job.last_customer_sig evidence_round_number = job.last_round_number print('\nevidence') debug_print([ evidence_hash, decode_hex(evidence_sig), decode_hex(evidence_customer_sig), evidence_round_number ]) raw_tx = create_signed_contract_transaction( self.private_key, self.channel_manager_contract, 'monitorEvidence', [ self.state.receiver, open_block_number, evidence_hash, decode_hex(evidence_sig), decode_hex(evidence_customer_sig), evidence_round_number ]) txid = self.blockchain.web3.eth.sendRawTransaction(raw_tx) self.log.info( 'sent monitor intereference (\n\tsender %s, \n\tblock number %d, \n\ttxid %s)\n', sender, open_block_number, encode_hex(txid))
def close_cooperatively(self, closing_sig: bytes): """ Attempts to close the channel immediately by providing a hash of the channel's balance proof signed by the receiver. This signature must correspond to the balance proof stored in the passed channel state. """ if self.state == Channel.State.closed: log.error('Channel must not be closed already to be closed cooperatively.') return None log.info('Attempting to cooperatively close channel to {} created at block #{}.'.format( self.receiver, self.block )) current_block = self.core.web3.eth.blockNumber receiver_recovered = verify_closing_sig( self.sender, self.block, self.balance, closing_sig, self.core.channel_manager.address ) if not is_same_address(receiver_recovered, self.receiver): log.error('Invalid closing signature.') return None tx = create_signed_contract_transaction( self.core.private_key, self.core.channel_manager, 'cooperativeClose', [ self.receiver, self.block, self.balance, self.balance_sig, closing_sig ] ) self.core.web3.eth.sendRawTransaction(tx) log.debug('Waiting for settle confirmation event...') event = get_event_blocking( self.core.channel_manager, 'ChannelSettled', from_block=current_block + 1, argument_filters={ '_sender_address': self.sender, '_receiver_address': self.receiver, '_open_block_number': self.block } ) if event: log.debug('Successfully closed channel in block {}.'.format(event['blockNumber'])) self.state = Channel.State.closed return event else: log.error('No event received.') return None
def drain_rdn(self, to: Address) -> TXHashHex: assert self.web3 assert self.token assert self.unlocked, 'Account is locked.' rei = self.token.call().balanceOf(self.address) tx = create_signed_contract_transaction(self.private_key, self.token, 'transfer', [to, rei]) return self.web3.eth.sendRawTransaction(tx)
def close_cooperatively(self, closing_sig: bytes): """ Attempts to close the channel immediately by providing a hash of the channel's balance proof signed by the receiver. This signature must correspond to the balance proof stored in the passed channel state. """ if self.state == Channel.State.closed: log.error('Channel must not be closed already to be closed cooperatively.') return None log.info('Attempting to cooperatively close channel to {} created at block #{}.'.format( self.receiver, self.block )) current_block = self.core.web3.eth.blockNumber receiver_recovered = verify_closing_sig( self.sender, self.block, self.balance, closing_sig, self.core.channel_manager.address ) if not is_same_address(receiver_recovered, self.receiver): log.error('Invalid closing signature.') return None tx = create_signed_contract_transaction( self.core.private_key, self.core.channel_manager, 'cooperativeClose', [ self.receiver, self.block, self.balance, self.balance_sig, closing_sig ] ) self.core.web3.eth.sendRawTransaction(tx) log.debug('Waiting for settle confirmation event...') event = get_event_blocking( self.core.channel_manager, 'ChannelSettled', from_block=current_block + 1, argument_filters={ '_sender_address': self.sender, '_receiver_address': self.receiver, '_open_block_number': self.block } ) if event: log.debug('Successfully closed channel in block {}.'.format(event['blockNumber'])) self.state = Channel.State.closed return event else: log.error('No event received.') return None
def settle(self): """ Attempts to settle a channel that has passed its settlement period. If a channel cannot be settled yet, the call is ignored with a warning. Blocks until a confirmation event is received or timeout. """ if self.state != Channel.State.settling: log.error('Channel must be in the settlement period to settle.') return None log.info('Attempting to settle channel to {} created at block #{}.'.format( self.receiver, self.block )) _, _, settle_block, _, _ = self.core.channel_manager.call().getChannelInfo( self.sender, self.receiver, self.block ) current_block = self.core.web3.eth.blockNumber wait_remaining = settle_block - current_block if wait_remaining > 0: log.warning('{} more blocks until this channel can be settled. Aborting.'.format( wait_remaining )) return None tx = create_signed_contract_transaction( self.core.private_key, self.core.channel_manager, 'settle', [ self.receiver, self.block ] ) self.core.web3.eth.sendRawTransaction(tx) log.debug('Waiting for settle confirmation event...') event = get_event_blocking( self.core.channel_manager, 'ChannelSettled', from_block=current_block + 1, argument_filters={ '_sender_address': self.sender, '_receiver_address': self.receiver, '_open_block_number': self.block } ) if event: log.debug('Successfully settled channel in block {}.'.format(event['blockNumber'])) self.state = Channel.State.closed self.on_settle(self) return event else: log.error('No event received.') return None
def settle(self): """ Attempts to settle a channel that has passed its settlement period. If a channel cannot be settled yet, the call is ignored with a warning. Blocks until a confirmation event is received or timeout. """ if self.state != Channel.State.settling: log.error('Channel must be in the settlement period to settle.') return None log.info('Attempting to settle channel to {} created at block #{}.'.format( self.receiver, self.block )) _, _, settle_block, _, _ = self.core.channel_manager.call().getChannelInfo( self.sender, self.receiver, self.block ) current_block = self.core.web3.eth.blockNumber wait_remaining = settle_block - current_block if wait_remaining > 0: log.warning('{} more blocks until this channel can be settled. Aborting.'.format( wait_remaining )) return None tx = create_signed_contract_transaction( self.core.private_key, self.core.channel_manager, 'settle', [ self.receiver, self.block ] ) self.core.web3.eth.sendRawTransaction(tx) log.debug('Waiting for settle confirmation event...') event = get_event_blocking( self.core.channel_manager, 'ChannelSettled', from_block=current_block + 1, argument_filters={ '_sender_address': self.sender, '_receiver_address': self.receiver, '_open_block_number': self.block } ) if event: log.debug('Successfully settled channel in block {}.'.format(event['blockNumber'])) self.state = Channel.State.closed self.on_settle(self) return event else: log.error('No event received.') return None
def topup(self, deposit): """ Attempts to increase the deposit in an existing channel. Block until confirmation. """ if self.state != Channel.State.open: log.error('Channel must be open to be topped up.') return _balance = self.core.web3.eth.getBalance(self.core.address) if _balance < deposit: log.error( 'Insufficient tokens available for the specified topup ({}/{})' .format(_balance, deposit) ) return None log.info('Topping up channel to {} created at block #{} by {} tokens.'.format( self.receiver, self.block, deposit )) current_block = self.core.web3.eth.blockNumber data = (decode_hex(self.sender) + decode_hex(self.receiver) + self.block.to_bytes(4, byteorder='big')) tx = create_signed_contract_transaction( self.core.private_key, self.core.channel_manager, 'topUp', [ self.receiver, self.block ], deposit ) self.core.web3.eth.sendRawTransaction(tx) log.debug('Waiting for topup confirmation event...') event = get_event_blocking( self.core.channel_manager, 'ChannelToppedUp', from_block=current_block + 1, argument_filters={ '_sender_address': self.sender, '_receiver_address': self.receiver, '_open_block_number': self.block } ) if event: log.debug('Successfully topped up channel in block {}.'.format(event['blockNumber'])) self.deposit += deposit return event else: log.error('No event received.') return None
def topup(self, deposit): """ Attempts to increase the deposit in an existing channel. Block until confirmation. """ if self.state != Channel.State.open: log.error('Channel must be open to be topped up.') return token_balance = self.core.token.call().balanceOf(self.core.address) if token_balance < deposit: log.error( 'Insufficient tokens available for the specified topup ({}/{})' .format(token_balance, deposit) ) return None log.info('Topping up channel to {} created at block #{} by {} tokens.'.format( self.receiver, self.block, deposit )) current_block = self.core.web3.eth.blockNumber data = (decode_hex(self.sender) + decode_hex(self.receiver) + self.block.to_bytes(4, byteorder='big')) tx = create_signed_contract_transaction( self.core.private_key, self.core.token, 'transfer', [ self.core.channel_manager.address, deposit, data ] ) self.core.web3.eth.sendRawTransaction(tx) log.debug('Waiting for topup confirmation event...') event = get_event_blocking( self.core.channel_manager, 'ChannelToppedUp', from_block=current_block + 1, argument_filters={ '_sender_address': self.sender, '_receiver_address': self.receiver, '_open_block_number': self.block } ) if event: log.debug('Successfully topped up channel in block {}.'.format(event['blockNumber'])) self.deposit += deposit return event else: log.error('No event received.') return None
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 event_customer_dispute(self, customer: str, sender: str, open_block_number: int): customer = to_checksum_address(customer) sender = to_checksum_address(sender) try: job = self.jobs[customer, sender, open_block_number] except KeyError: self.log.info('Customer disputed a channel not being watched.') return if self.redeem_payment: p = job.conditional_payment #debug_print([p.payout, job.last_image, job.last_preimage, p.pay_sig, customer]) print(bcolors.OKBLUE + '\nMonitor: Redeeming payment on-chain instead...\n' + bcolors.ENDC) self.log.info('Redeeming payment on-chain (customer %s)', customer) # self.log.info('calling setstate to redeem payment \n\tpayout %d, \n\tlast_image %s, \n\tlast preimage %d, \n\tpay_sig %s, \n\tcustomer %s)', # p.payout, # encode_hex(job.last_image), # job.last_preimage, # encode_hex(p.pay_sig), # customer # ) raw_tx = create_signed_contract_transaction( self.private_key, self.channel_monitor_contract, 'setstate', [ sender, open_block_number, p.payout, p.conditional_transfer, job.last_image, job.last_preimage, p.pay_sig, customer ]) txid = self.blockchain.web3.eth.sendRawTransaction(raw_tx) self.log.debug('setstate transaction (txid %s)\n', encode_hex(txid)) print( bcolors.OKBLUE + "\nMonitor: Customer raised dispute in contract. No action required." + bcolors.ENDC) if self.redeem_payment and self.cheat_with_receipt: job.respond = False elif self.redeem_payment: job.respond = True else: job.respond = False
def get_tokens(): web3 = Web3(HTTPProvider(config['web3path'])) private_key = get_private_key('./dragonstone-rinkeby-02-03') log.info('This private key %s', private_key) log.info('This address %s', privkey_to_addr(private_key)) token_address = config['token'] token_abi = constants.CONTRACT_METADATA[constants.TOKEN_ABI_NAME]['abi'] token_contract = web3.eth.contract(abi=token_abi, address=token_address) raw_tx = create_signed_contract_transaction(private_key, token_contract, 'mint', [], 100000000000000000) web3.eth.sendRawTransaction(raw_tx) log.info('bought tokens from custom token')
def close(self, balance=None): """ Attempts to request close on a channel. An explicit balance can be given to override the locally stored balance signature. Blocks until a confirmation event is received or timeout. """ if self.state != Channel.State.open: log.error('Channel must be open to request a close.') return log.info('Requesting close of channel to {} created at block #{}.'.format( self.receiver, self.block )) current_block = self.core.web3.eth.blockNumber if balance is not None: self.update_balance(balance) tx = create_signed_contract_transaction( self.core.private_key, self.core.channel_manager, 'uncooperativeClose', [ self.receiver, self.block, self.balance ] ) self.core.web3.eth.sendRawTransaction(tx) log.debug('Waiting for close confirmation event...') event = get_event_blocking( self.core.channel_manager, 'ChannelCloseRequested', from_block=current_block + 1, argument_filters={ '_sender_address': self.sender, '_receiver_address': self.receiver, '_open_block_number': self.block } ) if event: log.debug('Successfully sent channel close request in block {}.'.format( event['blockNumber'] )) self.state = Channel.State.settling return event else: log.error('No event received.') return None
def close(self, balance=None): """ Attempts to request close on a channel. An explicit balance can be given to override the locally stored balance signature. Blocks until a confirmation event is received or timeout. """ if self.state != Channel.State.open: log.error('Channel must be open to request a close.') return log.info('Requesting close of channel to {} created at block #{}.'.format( self.receiver, self.block )) current_block = self.core.web3.eth.blockNumber if balance is not None: self.update_balance(balance) tx = create_signed_contract_transaction( self.core.private_key, self.core.channel_manager, 'uncooperativeClose', [ self.receiver, self.block, self.balance ] ) self.core.web3.eth.sendRawTransaction(tx) log.debug('Waiting for close confirmation event...') event = get_event_blocking( self.core.channel_manager, 'ChannelCloseRequested', from_block=current_block + 1, argument_filters={ '_sender_address': self.sender, '_receiver_address': self.receiver, '_open_block_number': self.block } ) if event: log.debug('Successfully sent channel close request in block {}.'.format( event['blockNumber'] )) self.state = Channel.State.settling return event else: log.error('No event received.') return None
def fund_account(address: str, eth_allowance: int, token_allowance: int, token_contract: Contract, web3: Web3, wait_for_transaction): tx = create_signed_transaction(FAUCET_PRIVKEY, web3, to=address, value=eth_allowance, gas_limit=21000) tx_hash = web3.eth.sendRawTransaction(tx) wait_for_transaction(tx_hash) if token_allowance > 0: tx = create_signed_contract_transaction(FAUCET_PRIVKEY, token_contract, 'transfer', [address, token_allowance]) tx_hash = web3.eth.sendRawTransaction(tx) wait_for_transaction(tx_hash)
def sweep_account( private_key: str, faucet_address: str, token_contract: Contract, web3: Web3, wait_for_transaction ): address = privkey_to_addr(private_key) log.info('Sweeping account {}'.format(address)) token_balance = token_contract.call().balanceOf(address) if token_balance > 0: tx = create_signed_contract_transaction( private_key, token_contract, 'transfer', [ faucet_address, token_balance ] ) try: tx_hash = web3.eth.sendRawTransaction(tx) except ValueError as e: if e.args[0]['message'].startswith('Insufficient funds.'): pass else: raise else: wait_for_transaction(tx_hash) assert token_contract.call().balanceOf(address) == 0 balance = web3.eth.getBalance(address) if balance < NETWORK_CFG.POT_GAS_LIMIT * NETWORK_CFG.GAS_PRICE: return tx = create_signed_transaction( private_key, web3, to=faucet_address, value=balance - NETWORK_CFG.POT_GAS_LIMIT * NETWORK_CFG.GAS_PRICE, gas_limit=NETWORK_CFG.POT_GAS_LIMIT ) tx_hash = web3.eth.sendRawTransaction(tx) wait_for_transaction(tx_hash) assert web3.eth.getBalance(address) == 0, ( 'Sweeping of account {} (private key {}) failed.'.format(address, private_key) )
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 open_channel(self, receiver_address: str, deposit: int): """ Attempts to open a new channel to the receiver with the given deposit. Blocks until the creation transaction is found in a pending block or timeout is reached. The new channel state is returned. """ assert isinstance(receiver_address, str) assert isinstance(deposit, int) assert deposit > 0 token_balance = self.context.token.call().balanceOf(self.context.address) if token_balance < deposit: log.error( 'Insufficient tokens available for the specified deposit ({}/{})' .format(token_balance, deposit) ) return None current_block = self.context.web3.eth.blockNumber log.info('Creating channel to {} with an initial deposit of {} @{}'.format( receiver_address, deposit, current_block )) data = decode_hex(self.context.address) + decode_hex(receiver_address) tx = create_signed_contract_transaction( self.context.private_key, self.context.token, 'transfer', [ self.context.channel_manager.address, deposit, data ] ) self.context.web3.eth.sendRawTransaction(tx) log.debug('Waiting for channel creation event on the blockchain...') filters = { '_sender_address': self.context.address, '_receiver_address': receiver_address } event = get_event_blocking( self.context.channel_manager, 'ChannelCreated', from_block=current_block + 1, to_block='latest', argument_filters=filters ) if event: log.debug('Event received. Channel created in block {}.'.format(event['blockNumber'])) assert is_same_address(event['args']['_sender_address'], self.context.address) assert is_same_address(event['args']['_receiver_address'], receiver_address) channel = Channel( self.context, self.context.address, receiver_address, event['blockNumber'], event['args']['_deposit'], on_settle=lambda c: self.channels.remove(c) ) self.channels.append(channel) else: log.error('Error: No event received.') channel = None return channel
def withdraw_from_channels( private_key: str, state: ChannelManagerState, channel_manager_contract: Contract, minimum: int = 1, 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 or available_tokens < minimum: log.info( 'Not enough available balance: %d - %d = %d < %d', channel.balance, transferred_tokens, available_tokens, minimum ) continue raw_tx = utils.create_signed_contract_transaction( private_key, channel_manager_contract, 'withdraw', [ channel.open_block_number, channel.balance, decode_hex(channel.last_signature) ], gas_price=gas_price, ) tx_hash = web3.eth.sendRawTransaction(raw_tx) log.info( 'Sending withdraw 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, withdraw_info in pending_txs.items(): tx_hash, available_tokens = withdraw_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 Withdraw: 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 open_channel(self, receiver_address: str, deposit: int) -> Optional[Channel]: """Open a channel with a receiver and deposit Attempts to open a new channel to the receiver with the given deposit. Blocks until the creation transaction is found in a pending block or timeout is reached. Args: receiver_address: the partner with whom the channel should be opened deposit: the initial deposit for the channel (Should be > 0) Returns: The opened channel, if successful. Otherwise `None` """ assert isinstance(receiver_address, str) assert isinstance(deposit, int) assert deposit > 0 token_balance = self.context.token.call().balanceOf( self.context.address) if token_balance < deposit: log.error( 'Insufficient tokens available for the specified deposit ({}/{})' .format(token_balance, deposit)) return None current_block = self.context.web3.eth.blockNumber log.info( 'Creating channel to {} with an initial deposit of {} @{}'.format( receiver_address, deposit, current_block)) data = decode_hex(self.context.address) + decode_hex(receiver_address) tx = create_signed_contract_transaction( self.context.private_key, self.context.token, 'transfer', [self.context.channel_manager.address, deposit, data]) self.context.web3.eth.sendRawTransaction(tx) log.debug('Waiting for channel creation event on the blockchain...') filters = { '_sender_address': self.context.address, '_receiver_address': receiver_address } event = get_event_blocking(self.context.channel_manager, 'ChannelCreated', from_block=current_block + 1, to_block='latest', argument_filters=filters) if event: log.debug('Event received. Channel created in block {}.'.format( event['blockNumber'])) assert is_same_address(event['args']['_sender_address'], self.context.address) assert is_same_address(event['args']['_receiver_address'], receiver_address) channel = Channel(self.context, self.context.address, receiver_address, event['blockNumber'], event['args']['_deposit'], on_settle=lambda c: self.channels.remove(c)) self.channels.append(channel) else: log.error('Error: No event received.') channel = None return channel