Beispiel #1
0
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"]
Beispiel #2
0
    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)
Beispiel #3
0
    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)
Beispiel #4
0
def make_transaction_hash() -> TransactionHash:
    return TransactionHash(make_32bytes())
Beispiel #5
0
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()
Beispiel #6
0
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)
Beispiel #7
0
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),
        )
Beispiel #9
0
    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
Beispiel #10
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"]),
    )
Beispiel #11
0
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)
Beispiel #12
0
 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")
     ]
Beispiel #13
0
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)