def _funds_remaining(self) -> TokenAmount: """The remaining funds after subtracting the already deposited amounts. Note: - This attribute must be accessed with the lock held. """ if self.funds > 0: confirmed_block_identifier = views.get_confirmed_blockhash( self.raiden) token = self.raiden.proxy_manager.token( self.token_address, confirmed_block_identifier) token_balance = token.balance_of(self.raiden.address) sum_deposits = views.get_our_deposits_for_token_network( views.state_from_raiden(self.raiden), self.registry_address, self.token_address) return TokenAmount(min(self.funds - sum_deposits, token_balance)) return TokenAmount(0)
def join_channel(self, partner_address: Address, partner_deposit: TokenAmount) -> None: """Will be called, when we were selected as channel partner by another node. It will fund the channel with up to the partners deposit, but not more than remaining funds or the initial funding per channel. If the connection manager has no funds, this is a noop. """ # Consider this race condition: # # - Partner opens the channel and starts the deposit. # - This nodes learns about the new channel, starts ConnectionManager's # retry_connect, which will start a deposit for this half of the # channel. # - This node learns about the partner's deposit before its own. # join_channel is called which will try to deposit again. # # To fix this race, first the node must wait for the pending operations # to finish, because in them could be a deposit, and then deposit must # be called only if the channel is still not funded. confirmed_block_identifier = views.get_confirmed_blockhash(self.raiden) token_network_proxy = self.raiden.proxy_manager.token_network( self.token_network_address, block_identifier=confirmed_block_identifier) # Wait for any pending operation in the channel to complete, before # deciding on the deposit with self.lock, token_network_proxy.channel_operations_lock[ partner_address]: channel_state = views.get_channelstate_for( chain_state=views.state_from_raiden(self.raiden), token_network_registry_address=self.registry_address, token_address=self.token_address, partner_address=partner_address, ) if not channel_state: return joining_funds = min(partner_deposit, self._funds_remaining, self._initial_funding_per_partner) if joining_funds <= 0 or self._leaving_state: return if joining_funds <= channel_state.our_state.contract_balance: return try: self.api.set_total_channel_deposit(self.registry_address, self.token_address, partner_address, joining_funds) except RaidenRecoverableError: log.info("Channel not in opened state", node=to_checksum_address(self.raiden.address)) except InvalidDBData: raise except RaidenUnrecoverableError as e: should_crash = ( self.raiden.config.environment_type != Environment.PRODUCTION or self.raiden.config.unrecoverable_error_should_crash) if should_crash: raise log.critical(str(e), node=to_checksum_address(self.raiden.address)) else: log.info( "Joined a channel", node=to_checksum_address(self.raiden.address), partner=to_checksum_address(partner_address), funds=joining_funds, )
def payment_channel_open_and_deposit( app0: App, app1: App, token_address: TokenAddress, deposit: TokenAmount, settle_timeout: BlockTimeout, ) -> None: """ Open a new channel with app0 and app1 as participants """ assert token_address block_identifier: BlockIdentifier if app0.raiden.wal: block_identifier = views.get_confirmed_blockhash(app0.raiden) else: block_identifier = BLOCK_ID_LATEST token_network_address = app0.raiden.default_registry.get_token_network( token_address=token_address, block_identifier=block_identifier) assert token_network_address, "request a channel for an unregistered token" token_network_proxy = app0.raiden.proxy_manager.token_network( token_network_address, block_identifier=BLOCK_ID_LATEST) channel_identifier, _, _ = token_network_proxy.new_netting_channel( partner=app1.raiden.address, settle_timeout=settle_timeout, given_block_identifier=block_identifier, ) assert channel_identifier if deposit != 0: for app, partner in [(app0, app1), (app1, app0)]: waiting.wait_for_newchannel( raiden=app.raiden, token_network_registry_address=app.raiden.default_registry. address, token_address=token_address, partner_address=partner.raiden.address, retry_timeout=0.5, ) chain_state = state_from_raiden(app.raiden) canonical_identifier = CanonicalIdentifier( chain_identifier=chain_state.chain_id, token_network_address=token_network_proxy.address, channel_identifier=channel_identifier, ) channel_state = get_channelstate_by_canonical_identifier( chain_state=chain_state, canonical_identifier=canonical_identifier) assert channel_state, "nodes dont share a channel" # Use each app's own chain because of the private key / local signing token = app.raiden.proxy_manager.token(token_address, BLOCK_ID_LATEST) payment_channel_proxy = app.raiden.proxy_manager.payment_channel( channel_state=channel_state, block_identifier=BLOCK_ID_LATEST) # This check can succeed and the deposit still fail, if channels are # openned in parallel previous_balance = token.balance_of(app.raiden.address) assert previous_balance >= deposit # the payment channel proxy will call approve # token.approve(token_network_proxy.address, deposit) payment_channel_proxy.approve_and_set_total_deposit( total_deposit=deposit, block_identifier=BLOCK_ID_LATEST) # Balance must decrease by at least but not exactly `deposit` amount, # because channels can be openned in parallel new_balance = token.balance_of(app.raiden.address) assert new_balance <= previous_balance - deposit check_channel(app0, app1, token_network_proxy.address, settle_timeout, deposit)
def connect( self, funds: typing.TokenAmount, initial_channel_target: int = 3, joinable_funds_target: float = 0.4, ) -> None: """Connect to the network. Subsequent calls to `connect` are allowed, but will only affect the spendable funds and the connection strategy parameters for the future. `connect` will not close any channels. Note: the ConnectionManager does not discriminate manually opened channels from automatically opened ones. If the user manually opened channels, those deposit amounts will affect the funding per channel and the number of new channels opened. Args: funds: Target amount of tokens spendable to join the network. initial_channel_target: Target number of channels to open. joinable_funds_target: Amount of funds not initially assigned. """ confirmed_block_identifier = views.get_confirmed_blockhash(self.raiden) token = self.raiden.proxy_manager.token(self.token_address, confirmed_block_identifier) token_balance = token.balance_of(self.raiden.address) if token_balance < funds: raise InvalidAmount( f"Insufficient balance for token {to_checksum_address(self.token_address)}" ) if funds <= 0: raise InvalidAmount( "The funds to use in the connection need to be a positive integer" ) if joinable_funds_target < 0 or joinable_funds_target > 1: raise InvalidAmount( f"joinable_funds_target should be between 0 and 1. Given: {joinable_funds_target}" ) with self.lock: self.funds = funds self.initial_channel_target = initial_channel_target self.joinable_funds_target = joinable_funds_target log_open_channels(self.raiden, self.registry_address, self.token_address, funds) have_online_partners, potential_partners = self._have_online_channels_to_connect_to( ) if not have_online_partners: bootstrap_address = (self.BOOTSTRAP_ADDR if len(potential_partners) == 0 else random.choice(potential_partners)) log.info( "Bootstrapping token network.", node=to_checksum_address(self.raiden.address), network_id=to_checksum_address(self.registry_address), token_id=to_checksum_address(self.token_address), bootstrap_address=to_checksum_address(bootstrap_address), ) try: self.api.channel_open( registry_address=self.registry_address, token_address=self.token_address, partner_address=bootstrap_address, ) except DuplicatedChannelError: # If we have none else to connect to and connect got called twice # then it's possible to already have channel with the bootstrap node. # In that case do nothing pass else: self._open_channels()
def channel_open( self, registry_address: TokenNetworkRegistryAddress, token_address: TokenAddress, partner_address: Address, settle_timeout: BlockTimeout = None, reveal_timeout: BlockTimeout = None, retry_timeout: NetworkTimeout = DEFAULT_RETRY_TIMEOUT, ) -> ChannelID: """ Open a channel with the peer at `partner_address` with the given `token_address`. """ if settle_timeout is None: settle_timeout = self.raiden.config.settle_timeout if reveal_timeout is None: reveal_timeout = self.raiden.config.reveal_timeout if reveal_timeout <= 0: raise InvalidRevealTimeout( "reveal_timeout should be larger than zero") if settle_timeout < reveal_timeout * 2: raise InvalidSettleTimeout( "`settle_timeout` can not be smaller than double the " "`reveal_timeout`.\n " "\n " "The setting `reveal_timeout` determines the maximum number of " "blocks it should take a transaction to be mined when the " "blockchain is under congestion. This setting determines the " "when a node must go on-chain to register a secret, and it is " "therefore the lower bound of the lock expiration. The " "`settle_timeout` determines when a channel can be settled " "on-chain, for this operation to be safe all locks must have " "been resolved, for this reason the `settle_timeout` has to be " "larger than `reveal_timeout`.") if not is_binary_address(registry_address): raise InvalidBinaryAddress( "Expected binary address format for registry in channel open") if not is_binary_address(token_address): raise InvalidBinaryAddress( "Expected binary address format for token in channel open") if not is_binary_address(partner_address): raise InvalidBinaryAddress( "Expected binary address format for partner in channel open") confirmed_block_identifier = views.get_confirmed_blockhash(self.raiden) registry = self.raiden.proxy_manager.token_network_registry( registry_address, block_identifier=confirmed_block_identifier) settlement_timeout_min = registry.settlement_timeout_min( block_identifier=confirmed_block_identifier) settlement_timeout_max = registry.settlement_timeout_max( block_identifier=confirmed_block_identifier) if settle_timeout < settlement_timeout_min: raise InvalidSettleTimeout( f"Settlement timeout should be at least {settlement_timeout_min}" ) if settle_timeout > settlement_timeout_max: raise InvalidSettleTimeout( f"Settlement timeout exceeds max of {settlement_timeout_max}") token_network_address = registry.get_token_network( token_address=token_address, block_identifier=confirmed_block_identifier) if token_network_address is None: raise TokenNotRegistered( "Token network for token %s does not exist" % to_checksum_address(token_address)) token_network = self.raiden.proxy_manager.token_network( address=token_network_address, block_identifier=confirmed_block_identifier) safety_deprecation_switch = token_network.safety_deprecation_switch( block_identifier=confirmed_block_identifier) if safety_deprecation_switch: msg = ( "This token_network has been deprecated. New channels cannot be " "open for this network, usage of the newly deployed token " "network contract is highly encouraged.") raise TokenNetworkDeprecated(msg) duplicated_channel = self.is_already_existing_channel( token_network_address=token_network_address, partner_address=partner_address, block_identifier=confirmed_block_identifier, ) if duplicated_channel: raise DuplicatedChannelError( f"A channel with {to_checksum_address(partner_address)} for token " f"{to_checksum_address(token_address)} already exists. " f"(At blockhash: {confirmed_block_identifier.hex()})") has_enough_reserve, estimated_required_reserve = has_enough_gas_reserve( self.raiden, channels_to_open=1) if not has_enough_reserve: raise InsufficientGasReserve( "The account balance is below the estimated amount necessary to " "finish the lifecycles of all active channels. A balance of at " f"least {estimated_required_reserve} wei is required.") try: token_network.new_netting_channel( partner=partner_address, settle_timeout=settle_timeout, given_block_identifier=confirmed_block_identifier, ) except DuplicatedChannelError: log.info("partner opened channel first") except RaidenRecoverableError: # The channel may have been created in the pending block. duplicated_channel = self.is_already_existing_channel( token_network_address=token_network_address, partner_address=partner_address) if duplicated_channel: log.info("Channel has already been opened") else: raise waiting.wait_for_newchannel( raiden=self.raiden, token_network_registry_address=registry_address, token_address=token_address, partner_address=partner_address, retry_timeout=retry_timeout, ) chain_state = views.state_from_raiden(self.raiden) channel_state = views.get_channelstate_for( chain_state=chain_state, token_network_registry_address=registry_address, token_address=token_address, partner_address=partner_address, ) assert channel_state, f"channel {channel_state} is gone" self.raiden.set_channel_reveal_timeout( canonical_identifier=channel_state.canonical_identifier, reveal_timeout=reveal_timeout) return channel_state.identifier