def test_waiting_transactions(ms_database: Database): assert ms_database.get_waiting_transactions() == [] ms_database.add_waiting_transaction(TransactionHash(b"A")) assert ms_database.get_waiting_transactions() == [b"A"] ms_database.add_waiting_transaction(TransactionHash(b"B")) assert ms_database.get_waiting_transactions() == [b"A", b"B"] ms_database.remove_waiting_transaction(TransactionHash(b"A")) assert ms_database.get_waiting_transactions() == [b"B"]
def send_transaction(self, to: Address, startgas: int, value: int = 0, data: bytes = b"") -> TransactionHash: """ Locally sign the transaction and send it to the network. """ with self._sent_lock: if self._sent: raise RaidenUnrecoverableError( f"A transaction for this slot has been sent already! " f"Reusing the nonce is a synchronization problem.") if to == to_canonical_address(NULL_ADDRESS_HEX): warnings.warn( "For contract creation the empty string must be used.") gas_price = self._client.gas_price() transaction = { "data": data, "gas": startgas, "nonce": self.nonce, "value": value, "gasPrice": gas_price, } node_gas_price = self._client.web3.eth.gasPrice log.debug( "Calculated gas price for transaction", node=to_checksum_address(self._client.address), calculated_gas_price=gas_price, node_gas_price=node_gas_price, ) # add the to address if not deploying a contract if to != b"": transaction["to"] = to_checksum_address(to) signed_txn = self._client.web3.eth.account.signTransaction( transaction, self._client.privkey) log_details = { "node": to_checksum_address(self._client.address), "nonce": transaction["nonce"], "gasLimit": transaction["gas"], "gasPrice": transaction["gasPrice"], } log.debug("send_raw_transaction called", **log_details) tx_hash = self._client.web3.eth.sendRawTransaction( signed_txn.rawTransaction) log.debug("send_raw_transaction returned", tx_hash=encode_hex(tx_hash), **log_details) self._sent = True return TransactionHash(tx_hash)
def send_transaction(self, to: Address, startgas: int, value: int = 0, data: bytes = b"") -> TransactionHash: """ Helper to send signed messages. This method will use the `privkey` provided in the constructor to locally sign the transaction. This requires an extended server implementation that accepts the variables v, r, and s. """ if to == to_canonical_address(constants.NULL_ADDRESS): warnings.warn( "For contract creation the empty string must be used.") with self._nonce_lock: nonce = self._available_nonce gas_price = self.gas_price() transaction = { "data": data, "gas": startgas, "nonce": nonce, "value": value, "gasPrice": gas_price, } node_gas_price = self.web3.eth.gasPrice log.debug( "Calculated gas price for transaction", node=to_checksum_address(self.address), calculated_gas_price=gas_price, node_gas_price=node_gas_price, ) # add the to address if not deploying a contract if to != b"": transaction["to"] = to_checksum_address(to) signed_txn = self.web3.eth.account.signTransaction( transaction, self.privkey) log_details = { "node": to_checksum_address(self.address), "nonce": transaction["nonce"], "gasLimit": transaction["gas"], "gasPrice": transaction["gasPrice"], } log.debug("send_raw_transaction called", **log_details) tx_hash = self.web3.eth.sendRawTransaction( signed_txn.rawTransaction) self._available_nonce += 1 log.debug("send_raw_transaction returned", tx_hash=encode_hex(tx_hash), **log_details) return TransactionHash(tx_hash)
def make_transaction_hash() -> TransactionHash: return TransactionHash(make_32bytes())
def action_claim_reward_triggered_event_handler(event: Event, context: Context) -> None: assert isinstance(event, ActionClaimRewardTriggeredEvent) log.info("Triggering reward claim") monitor_request = context.database.get_monitor_request( token_network_address=event.token_network_address, channel_id=event.channel_identifier, non_closing_signer=event.non_closing_participant, ) if monitor_request is None: return channel = context.database.get_channel( token_network_address=monitor_request.token_network_address, channel_id=monitor_request.channel_identifier, ) if channel is None: return # check that the latest update was ours and that we didn't send a transaction yet can_claim = (channel.claim_tx_hash is None and channel.update_status is not None and channel.update_status.update_sender_address == context.ms_state.address) log.info("Checking if eligible for reward", reward_available=can_claim) # check if claiming will produce a reward has_reward = monitor_request.reward_amount > 0 if not has_reward: log.warning( "MonitorRequest has no reward. Skipping reward claim.", reward_amount=monitor_request.reward_amount, monitor_request=monitor_request, ) if can_claim and has_reward: try: # The gas estimation will prevent us from wasting gas on failing # transactions. Attackers shouldn't be able to exploit this # transaction to waste gas anyway unless there is a bug in the smart # contract. It should not be possible to bring the contract into a # state where MS has a reward, the contract says it is time to # claim it, but the claim will fail. tx_hash = TransactionHash( bytes( context.monitoring_service_contract.functions.claimReward( monitor_request.channel_identifier, monitor_request.token_network_address, monitor_request.signer, monitor_request.non_closing_signer, ).transact({"from": context.ms_state.address}))) log.info( "Sent transaction calling `claimReward` for channel", token_network_address=channel.token_network_address, channel_identifier=channel.identifier, transaction_hash=encode_hex(tx_hash), ) assert tx_hash is not None with context.database.conn: # Add tx hash to list of waiting transactions context.database.add_waiting_transaction(tx_hash) channel.claim_tx_hash = tx_hash context.database.upsert_channel(channel) except Exception as exc: # pylint: disable=broad-except log.error("Sending tx failed", exc_info=True, err=exc) metrics.get_metrics_for_label( metrics.ERRORS_LOGGED, metrics.ErrorCategory.PROTOCOL).inc() # manually increase exception counter here because # exception is not visible from outside metrics.EVENTS_EXCEPTIONS_RAISED.labels( event_type=event.__class__.__name__).inc()
def action_monitoring_triggered_event_handler(event: Event, context: Context) -> None: assert isinstance(event, ActionMonitoringTriggeredEvent) log.info("Triggering channel monitoring") monitor_request = context.database.get_monitor_request( token_network_address=event.token_network_address, channel_id=event.channel_identifier, non_closing_signer=event.non_closing_participant, ) if monitor_request is None: log.error( "MonitorRequest cannot be found", token_network_address=event.token_network_address, channel_id=event.channel_identifier, ) metrics.get_metrics_for_label(metrics.ERRORS_LOGGED, metrics.ErrorCategory.STATE).inc() return channel = context.database.get_channel( token_network_address=monitor_request.token_network_address, channel_id=monitor_request.channel_identifier, ) if channel is None: log.error("Channel cannot be found", monitor_request=monitor_request) metrics.get_metrics_for_label(metrics.ERRORS_LOGGED, metrics.ErrorCategory.STATE).inc() return if not _is_mr_valid(monitor_request, channel): log.error( "MonitorRequest lost its validity", monitor_request=monitor_request, channel=channel, ) metrics.get_metrics_for_label(metrics.ERRORS_LOGGED, metrics.ErrorCategory.PROTOCOL).inc() return last_onchain_nonce = 0 if channel.update_status: last_onchain_nonce = channel.update_status.nonce if monitor_request.nonce <= last_onchain_nonce: log.info( "Another MS submitted the last known channel state", monitor_request=monitor_request, ) return latest_block = context.web3.eth.blockNumber last_confirmed_block = context.latest_confirmed_block user_address = monitor_request.non_closing_signer user_deposit = get_pessimistic_udc_balance( udc=context.user_deposit_contract, address=user_address, from_block=last_confirmed_block, to_block=latest_block, ) if monitor_request.reward_amount < context.min_reward: log.info( "Monitor request not executed due to insufficient reward amount", monitor_request=monitor_request, min_reward=context.min_reward, ) return if user_deposit < monitor_request.reward_amount * UDC_SECURITY_MARGIN_FACTOR_MS: log.debug( "User deposit is insufficient -> try monitoring again later", monitor_request=monitor_request, min_reward=context.min_reward, ) context.database.upsert_scheduled_event( ScheduledEvent( trigger_block_number=BlockNumber(last_confirmed_block + 1), event=event)) return assert (channel.monitor_tx_hash is None ), "This MS already monitored this channel. Should be impossible." try: # Attackers might be able to construct MRs that make this fail. # Since we execute a gas estimation before doing the `transact`, # the gas estimation will fail before any gas is used. # If we stop doing a gas estimation, a `call` has to be done before # the `transact` to prevent attackers from wasting the MS's gas. tx_hash = TransactionHash( bytes( context.monitoring_service_contract.functions.monitor( monitor_request.signer, monitor_request.non_closing_signer, monitor_request.balance_hash, monitor_request.nonce, monitor_request.additional_hash, monitor_request.closing_signature, monitor_request.non_closing_signature, monitor_request.reward_amount, monitor_request.token_network_address, monitor_request.reward_proof_signature, ).transact({"from": context.ms_state.address}))) except Exception as exc: # pylint: disable=broad-except first_allowed = BlockNumber( _first_allowed_block_to_monitor(event.token_network_address, channel, context)) failed_at = context.web3.eth.blockNumber log.error( "Sending tx failed", exc_info=True, err=exc, first_allowed=first_allowed, failed_at=failed_at, ) metrics.get_metrics_for_label(metrics.ERRORS_LOGGED, metrics.ErrorCategory.BLOCKCHAIN).inc() return log.info( "Sent transaction calling `monitor` for channel", token_network_address=channel.token_network_address, channel_identifier=channel.identifier, transaction_hash=encode_hex(tx_hash), ) assert tx_hash is not None with context.database.conn: # Add tx hash to list of waiting transactions context.database.add_waiting_transaction(tx_hash) channel.monitor_tx_hash = tx_hash context.database.upsert_channel(channel)
def deserialize_transactionhash(data: str) -> TransactionHash: return TransactionHash(deserialize_bytes(data))
def _add_token( self, token_address: TokenAddress, channel_participant_deposit_limit: TokenAmount, token_network_deposit_limit: TokenAmount, log_details: Dict[Any, Any], ) -> Tuple[TransactionHash, TokenNetworkAddress]: token_network_address = None kwargs = { "_token_address": token_address, "_channel_participant_deposit_limit": channel_participant_deposit_limit, "_token_network_deposit_limit": token_network_deposit_limit, } estimated_transaction = self.rpc_client.estimate_gas( self.proxy, "createERC20TokenNetwork", log_details, **kwargs) if estimated_transaction is not None: estimated_transaction.estimated_gas = safe_gas_limit( estimated_transaction.estimated_gas, self.gas_measurements[ "TokenNetworkRegistry createERC20TokenNetwork"], ) transaction_sent = self.rpc_client.transact(estimated_transaction) transaction_mined = self.rpc_client.poll_transaction( transaction_sent) receipt = transaction_mined.receipt if not was_transaction_successfully_mined(transaction_mined): failed_at_blocknumber = BlockNumber(receipt["blockNumber"]) max_token_networks = self.get_max_token_networks( block_identifier=failed_at_blocknumber) token_networks_created = self.get_token_network_created( block_identifier=failed_at_blocknumber) already_registered = self.get_token_network( token_address=token_address, block_identifier=failed_at_blocknumber) deprecation_executor = self.get_deprecation_executor( block_identifier=failed_at_blocknumber) settlement_timeout_min = self.settlement_timeout_min( block_identifier=failed_at_blocknumber) settlement_timeout_max = self.settlement_timeout_max( block_identifier=failed_at_blocknumber) chain_id = self.get_chain_id( block_identifier=failed_at_blocknumber) secret_registry_address = self.get_secret_registry_address( block_identifier=failed_at_blocknumber) try: # Creating a new instance to run the constructor checks. token_proxy = Token( jsonrpc_client=self.rpc_client, token_address=token_address, contract_manager=self.proxy_manager.contract_manager, block_identifier=failed_at_blocknumber, ) except AddressWithoutCode: # This cannot be an unrecoverable error, since the ERC20 # code is external. raise RaidenRecoverableError( "Token disappeared! The address " f"{to_checksum_address(token_address)} did have code at " f"block {log_details['given_block_identifier']}, however " f"at block {failed_at_blocknumber} when the registration " "transaction was mined the address didn't have code " "anymore.") check_transaction_failure(transaction_mined, self.rpc_client) check_address_has_code_handle_pruned_block( client=self.rpc_client, address=Address(secret_registry_address), contract_name=CONTRACT_SECRET_REGISTRY, expected_code=decode_hex( self.proxy_manager.contract_manager. get_runtime_hexcode(CONTRACT_SECRET_REGISTRY)), given_block_identifier=failed_at_blocknumber, ) if token_networks_created >= max_token_networks: raise RaidenRecoverableError( "The number of existing token networks reached the maximum allowed" ) if already_registered: # Race condition lost, the token network was created in a different # transaction which got mined first. raise RaidenRecoverableError( "The token was already registered in the TokenNetworkRegistry." ) if deprecation_executor == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The deprecation executor property for the " "TokenNetworkRegistry is invalid.") if chain_id == 0: raise RaidenUnrecoverableError( "The chain ID property for the TokenNetworkRegistry is invalid." ) if chain_id != self.rpc_client.chain_id: raise RaidenUnrecoverableError( f"The provided chain ID {chain_id} does not match the " f"network Raiden is running on: {self.rpc_client.chain_id}." ) if secret_registry_address == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The secret registry address for the token network is invalid." ) if settlement_timeout_min == 0: raise RaidenUnrecoverableError( "The minimum settlement timeout for the token network " "should be larger than zero.") if settlement_timeout_max <= settlement_timeout_min: raise RaidenUnrecoverableError( "The maximum settlement timeout for the token network " "should be larger than the minimum settlement timeout." ) total_supply = token_proxy.total_supply( block_identifier=failed_at_blocknumber) if not total_supply or total_supply <= 0: raise RaidenRecoverableError( f"The given token address is not a valid ERC20 token, " f"total_supply() returned an invalid value {total_supply}." ) # At this point, the TokenNetworkRegistry fails to instantiate # a new TokenNetwork. raise RaidenUnrecoverableError( "createERC20TokenNetwork failed for an unknown reason, even " "though the gas estimation succeeded.") succeeded_at_blockhash = receipt["blockHash"] token_network_address = self.get_token_network( token_address, succeeded_at_blockhash) if token_network_address is None: msg = "createERC20TokenNetwork succeeded but token network address is Null" raise RaidenUnrecoverableError(msg) else: # `estimated_transaction` is None # The latest block can not be used reliably because of reorgs, # therefore every call using this block has to handle pruned data. failed_at_block = self.rpc_client.get_block(BLOCK_ID_LATEST) failed_at_blockhash = failed_at_block["hash"].hex() failed_at_blocknumber = failed_at_block["number"] max_token_networks = self.get_max_token_networks( block_identifier=failed_at_blocknumber) token_networks_created = self.get_token_network_created( block_identifier=failed_at_blocknumber) already_registered = self.get_token_network( token_address=token_address, block_identifier=failed_at_blocknumber) deprecation_executor = self.get_deprecation_executor( block_identifier=failed_at_blocknumber) settlement_timeout_min = self.settlement_timeout_min( block_identifier=failed_at_blocknumber) settlement_timeout_max = self.settlement_timeout_max( block_identifier=failed_at_blocknumber) chain_id = self.get_chain_id( block_identifier=failed_at_blocknumber) secret_registry_address = self.get_secret_registry_address( block_identifier=failed_at_blocknumber) try: # Creating a new instance to run the constructor checks. token_proxy = Token( jsonrpc_client=self.rpc_client, token_address=token_address, contract_manager=self.proxy_manager.contract_manager, block_identifier=failed_at_blocknumber, ) except AddressWithoutCode: # This cannot be an unrecoverable error, since the ERC20 # code is external. raise RaidenRecoverableError( "Token disappeared! The address " "{to_checksum_address(token_address)} did have code at " "block {log_details['given_block_identifier']}, however " "at block {failed_at_blocknumber} when the registration " "transaction was mined the address didn't have code " "anymore.") check_address_has_code_handle_pruned_block( client=self.rpc_client, address=Address(secret_registry_address), contract_name=CONTRACT_SECRET_REGISTRY, expected_code=decode_hex( self.proxy_manager.contract_manager.get_runtime_hexcode( CONTRACT_SECRET_REGISTRY)), given_block_identifier=failed_at_blocknumber, ) required_gas = self.gas_measurements[ "TokenNetworkRegistry createERC20TokenNetwork"] self.rpc_client.check_for_insufficient_eth( transaction_name="createERC20TokenNetwork", transaction_executed=False, required_gas=required_gas, block_identifier=failed_at_blocknumber, ) if token_networks_created >= max_token_networks: raise RaidenRecoverableError( "The number of existing token networks reached the maximum allowed" ) if already_registered: # Race condition lost, the token network was created in a different # transaction which got mined first. raise RaidenRecoverableError( "The token was already registered in the TokenNetworkRegistry." ) if deprecation_executor == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The deprecation executor property for the TokenNetworkRegistry is invalid." ) if chain_id == 0: raise RaidenUnrecoverableError( "The chain ID property for the TokenNetworkRegistry is invalid." ) if chain_id != self.rpc_client.chain_id: raise RaidenUnrecoverableError( f"The provided chain ID {chain_id} does not match the " f"network Raiden is running on: {self.rpc_client.chain_id}." ) if secret_registry_address == NULL_ADDRESS_BYTES: raise RaidenUnrecoverableError( "The secret registry address for the token network is invalid." ) if settlement_timeout_min <= 0: raise RaidenUnrecoverableError( "The minimum settlement timeout for the token network " "should be larger than zero.") if settlement_timeout_max <= settlement_timeout_min: raise RaidenUnrecoverableError( "The maximum settlement timeout for the token network " "should be larger than the minimum settlement timeout.") total_supply = token_proxy.total_supply( block_identifier=failed_at_blocknumber) if not total_supply or total_supply <= 0: raise RaidenRecoverableError( f"The given token address is not a valid ERC20 token, " f"total_supply() returned an invalid value {total_supply}." ) # At this point, the TokenNetworkRegistry fails to instantiate # a new TokenNetwork. raise RaidenUnrecoverableError( f"createERC20TokenNetwork gas estimation failed for an unknown " f"reason. Reference block {failed_at_blockhash} " f"{failed_at_blocknumber}.") return ( TransactionHash(transaction_mined.transaction_hash), TokenNetworkAddress(token_network_address), )
GOERLI = 5 KOVAN = 42 SMOKETEST = 627 # Set at 64 since parity's default is 64 and Geth's default is 128 # TODO: Make this configurable. Since in parity this is also a configurable value STATE_PRUNING_AFTER_BLOCKS = 64 STATE_PRUNING_SAFETY_MARGIN = 8 NO_STATE_QUERY_AFTER_BLOCKS = STATE_PRUNING_AFTER_BLOCKS - STATE_PRUNING_SAFETY_MARGIN NULL_ADDRESS_BYTES = bytes(20) NULL_ADDRESS = to_checksum_address(NULL_ADDRESS_BYTES) EMPTY_HASH = BlockHash(bytes(32)) EMPTY_TRANSACTION_HASH = TransactionHash(bytes(32)) EMPTY_BALANCE_HASH = BalanceHash(bytes(32)) EMPTY_MESSAGE_HASH = AdditionalHash(bytes(32)) EMPTY_SIGNATURE = Signature(bytes(65)) EMPTY_SECRET = Secret(bytes(32)) EMPTY_SECRETHASH = SecretHash(bytes(32)) EMPTY_SECRET_SHA256 = sha256_secrethash(EMPTY_SECRET) LOCKSROOT_OF_NO_LOCKS = Locksroot(keccak(b"")) ZERO_TOKENS = TokenAmount(0) ABSENT_SECRET = Secret(b"") SECRET_LENGTH = 32 SECRETHASH_LENGTH = 32 RECEIPT_FAILURE_CODE = 0
def decode_raiden_event_to_internal( abi: ABI, chain_id: ChainID, log_event: LogReceipt ) -> DecodedEvent: """Enforce the sandwich encoding. Converts the JSON RPC/web3 data types to the internal representation. Note:: This function must only on confirmed data. """ # Note: All addresses inside the event_data must be decoded. decoded_event = decode_event(abi, log_event) if not decoded_event: raise UnknownRaidenEventType() # copy the attribute dict because that data structure is immutable data = dict(decoded_event) args = dict(decoded_event["args"]) data["args"] = args # translate from web3's to raiden's name convention data["block_number"] = log_event["blockNumber"] data["transaction_hash"] = log_event["transactionHash"] data["block_hash"] = bytes(log_event["blockHash"]) # Remove the old names del data["blockNumber"] del data["transactionHash"] del data["blockHash"] assert data["block_number"], "The event must have the block_number" assert data["transaction_hash"], "The event must have the transaction hash field" assert data["block_hash"], "The event must have the block_hash" event = data["event"] if event == EVENT_TOKEN_NETWORK_CREATED: args["token_network_address"] = to_canonical_address(args["token_network_address"]) args["token_address"] = to_canonical_address(args["token_address"]) elif event == ChannelEvent.OPENED: args["participant1"] = to_canonical_address(args["participant1"]) args["participant2"] = to_canonical_address(args["participant2"]) elif event == ChannelEvent.DEPOSIT: args["participant"] = to_canonical_address(args["participant"]) elif event == ChannelEvent.WITHDRAW: args["participant"] = to_canonical_address(args["participant"]) elif event == ChannelEvent.BALANCE_PROOF_UPDATED: args["closing_participant"] = to_canonical_address(args["closing_participant"]) elif event == ChannelEvent.CLOSED: args["closing_participant"] = to_canonical_address(args["closing_participant"]) elif event == ChannelEvent.UNLOCKED: args["receiver"] = to_canonical_address(args["receiver"]) args["sender"] = to_canonical_address(args["sender"]) return DecodedEvent( chain_id=chain_id, originating_contract=to_canonical_address(log_event["address"]), event_data=data, block_number=log_event["blockNumber"], block_hash=BlockHash(log_event["blockHash"]), transaction_hash=TransactionHash(log_event["transactionHash"]), )
def action_monitoring_triggered_event_handler(event: Event, context: Context) -> None: assert isinstance(event, ActionMonitoringTriggeredEvent) log.info("Triggering channel monitoring") monitor_request = context.db.get_monitor_request( token_network_address=event.token_network_address, channel_id=event.channel_identifier, non_closing_signer=event.non_closing_participant, ) if monitor_request is None: return channel = context.db.get_channel( token_network_address=monitor_request.token_network_address, channel_id=monitor_request.channel_identifier, ) if channel is None: return if not _is_mr_valid(monitor_request, channel): return last_onchain_nonce = 0 if channel.update_status: last_onchain_nonce = channel.update_status.nonce user_address = monitor_request.non_closing_signer user_deposit = context.user_deposit_contract.functions.effectiveBalance( user_address).call() if monitor_request.reward_amount < context.min_reward: log.info( "Monitor request not executed due to insufficient reward amount", monitor_request=monitor_request, min_reward=context.min_reward, ) call_monitor = (channel.closing_tx_hash is None and monitor_request.nonce > last_onchain_nonce and user_deposit >= monitor_request.reward_amount * DEFAULT_PAYMENT_RISK_FAKTOR and monitor_request.reward_amount >= context.min_reward) if call_monitor: try: # Attackers might be able to construct MRs that make this fail. # Since we execute a gas estimation before doing the `transact`, # the gas estimation will fail before any gas is used. # If we stop doing a gas estimation, a `call` has to be done before # the `transact` to prevent attackers from wasting the MS's gas. tx_hash = TransactionHash( bytes( context.monitoring_service_contract.functions.monitor( monitor_request.signer, monitor_request.non_closing_signer, monitor_request.balance_hash, monitor_request.nonce, monitor_request.additional_hash, monitor_request.closing_signature, monitor_request.non_closing_signature, monitor_request.reward_amount, monitor_request.token_network_address, monitor_request.reward_proof_signature, ).transact({"from": context.ms_state.address}))) log.info( "Sent transaction calling `monitor` for channel", token_network_address=channel.token_network_address, channel_identifier=channel.identifier, transaction_hash=encode_hex(tx_hash), ) assert tx_hash is not None with context.db.conn: # Add tx hash to list of waiting transactions context.db.add_waiting_transaction(tx_hash) channel.closing_tx_hash = tx_hash context.db.upsert_channel(channel) except Exception as exc: # pylint: disable=broad-except log.error("Sending tx failed", exc_info=True, err=exc)
def get_waiting_transactions(self) -> List[TransactionHash]: return [ TransactionHash(decode_hex(row[0])) for row in self.conn.execute( "SELECT transaction_hash FROM waiting_transactions") ]
def action_claim_reward_triggered_event_handler(event: Event, context: Context) -> None: assert isinstance(event, ActionClaimRewardTriggeredEvent) log.info("Triggering reward claim") monitor_request = context.db.get_monitor_request( token_network_address=event.token_network_address, channel_id=event.channel_identifier, non_closing_signer=event.non_closing_participant, ) if monitor_request is None: return channel = context.db.get_channel( token_network_address=monitor_request.token_network_address, channel_id=monitor_request.channel_identifier, ) if channel is None: return # check that the latest update was ours and that we didn't send a transaction yet can_claim = (channel is not None and channel.claim_tx_hash is None and channel.update_status is not None and channel.update_status.update_sender_address == context.ms_state.address) log.info("Checking if eligible for reward", reward_available=can_claim) # check if claiming will produce a reward has_reward = monitor_request.reward_amount > 0 if not has_reward: log.warning( "MonitorRequest has no reward. Skipping reward claim.", reward_amount=monitor_request.reward_amount, monitor_request=monitor_request, ) if can_claim and has_reward: try: tx_hash = TransactionHash( bytes( context.monitoring_service_contract.functions.claimReward( monitor_request.channel_identifier, monitor_request.token_network_address, monitor_request.signer, monitor_request.non_closing_signer, ).transact({"from": context.ms_state.address}))) log.info( "Sent transaction calling `claimReward` for channel", token_network_address=channel.token_network_address, channel_identifier=channel.identifier, transaction_hash=encode_hex(tx_hash), ) assert tx_hash is not None with context.db.conn: # Add tx hash to list of waiting transactions context.db.add_waiting_transaction(tx_hash) channel.claim_tx_hash = tx_hash context.db.upsert_channel(channel) except Exception as exc: # pylint: disable=broad-except log.error("Sending tx failed", exc_info=True, err=exc)