Exemplo n.º 1
0
def main(
    private_key: str,
    state_db: str,
    web3: Web3,
    contracts: Dict[str, Contract],
    start_block: BlockNumber,
    rdn_per_eth: float,
    expire_within: Timestamp,
) -> None:
    pfs_address = private_key_to_address(private_key)
    chain_id = ChainID(web3.eth.chain_id)
    database = PFSDatabase(filename=state_db,
                           chain_id=chain_id,
                           pfs_address=pfs_address,
                           sync_start_block=start_block)

    claim_cost_rdn = calc_claim_cost_rdn(web3, rdn_per_eth)
    time_now = get_posix_utc_time_now()
    ious = list(
        get_claimable_ious(
            database,
            claimable_until_after=time_now,
            claimable_until_before=Timestamp(time_now + expire_within),
            claim_cost_rdn=claim_cost_rdn,
        ))
    print(f"Found {len(ious)} claimable IOUs")
    _, failures = claim_ious(ious, claim_cost_rdn,
                             contracts[CONTRACT_ONE_TO_N], web3, database)
    if failures:
        sys.exit(1)
def test_channel_closed_event_handler_closes_existing_channel(context: Context):
    context = setup_state_with_open_channel(context)
    current_block = get_posix_utc_time_now() // 15

    event = ReceiveChannelClosedEvent(
        token_network_address=DEFAULT_TOKEN_NETWORK_ADDRESS,
        channel_identifier=DEFAULT_CHANNEL_IDENTIFIER,
        closing_participant=DEFAULT_PARTICIPANT2,
        block_number=BlockNumber(current_block + 1),
    )

    channel_closed_event_handler(event, context)

    assert context.database.channel_count() == 1
    assert_channel_state(context, ChannelState.CLOSED)
    event = ReceiveChannelClosedEvent(
        token_network_address=DEFAULT_TOKEN_NETWORK_ADDRESS,
        channel_identifier=DEFAULT_CHANNEL_IDENTIFIER,
        closing_participant=DEFAULT_PARTICIPANT2,
        block_number=BlockNumber(current_block),
    )
    channel_closed_event_handler(event, context)

    # ActionMonitoringTriggeredEvent has been triggered
    assert context.database.scheduled_event_count() == 1

    assert context.database.channel_count() == 1
    assert_channel_state(context, ChannelState.CLOSED)
Exemplo n.º 3
0
def test_trigger_scheduled_events(monitoring_service: MonitoringService):
    monitoring_service.context.required_confirmations = 5

    create_default_token_network(monitoring_service.context)

    triggered_event = ActionMonitoringTriggeredEvent(
        token_network_address=DEFAULT_TOKEN_NETWORK_ADDRESS,
        channel_identifier=make_channel_identifier(),
        non_closing_participant=make_address(),
    )

    trigger_timestamp = Timestamp(get_posix_utc_time_now())

    assert len(
        monitoring_service.database.get_scheduled_events(
            trigger_timestamp)) == 0
    monitoring_service.context.database.upsert_scheduled_event(
        ScheduledEvent(trigger_timestamp=trigger_timestamp,
                       event=triggered_event))
    assert len(
        monitoring_service.database.get_scheduled_events(
            trigger_timestamp)) == 1

    # Now run `_trigger_scheduled_events` and see if the event is removed
    monitoring_service._trigger_scheduled_events()  # pylint: disable=protected-access
    assert len(
        monitoring_service.database.get_scheduled_events(
            trigger_timestamp)) == 0
Exemplo n.º 4
0
def test_payment_with_new_iou_rejected(  # pylint: disable=too-many-locals
    api_sut,
    api_url: str,
    addresses: List[Address],
    token_network_model: TokenNetwork,
    make_iou: Callable,
):
    """Regression test for https://github.com/raiden-network/raiden-services/issues/624"""

    initiator_address = to_checksum_address(addresses[0])
    target_address = to_checksum_address(addresses[1])
    url = api_url + "/v1/" + to_checksum_address(token_network_model.address) + "/paths"
    default_params = {"from": initiator_address, "to": target_address, "value": 5, "max_paths": 3}

    def request_path_with(status_code=400, **kwargs):
        params = default_params.copy()
        params.update(kwargs)
        response = requests.post(url, json=params)
        assert response.status_code == status_code, response.json()
        return response

    # test with payment
    api_sut.service_fee = 100
    sender = PrivateKey(get_random_privkey())
    iou = make_iou(
        sender,
        api_sut.pathfinding_service.address,
        one_to_n_address=api_sut.one_to_n_address,
        amount=100,
        claimable_until=Timestamp(get_posix_utc_time_now() + 1_234_567),
    )
    first_iou_dict = iou.Schema().dump(iou)
    second_iou = make_iou(
        sender,
        api_sut.pathfinding_service.address,
        one_to_n_address=api_sut.one_to_n_address,
        amount=200,
        claimable_until=Timestamp(get_posix_utc_time_now() + 1_234_568),
    )
    second_iou_dict = second_iou.Schema().dump(second_iou)

    response = request_path_with(status_code=200, iou=first_iou_dict)
    assert response.status_code == 200

    response = request_path_with(iou=second_iou_dict)
    assert response.json()["error_code"] == exceptions.UseThisIOU.error_code
def test_action_monitoring_rescheduling_when_user_lacks_funds(context: Context):
    reward_amount = TokenAmount(10)
    context = setup_state_with_closed_channel(context)
    context.database.upsert_monitor_request(
        create_signed_monitor_request(nonce=Nonce(6), reward_amount=reward_amount)
    )

    event = ActionMonitoringTriggeredEvent(
        token_network_address=DEFAULT_TOKEN_NETWORK_ADDRESS,
        channel_identifier=DEFAULT_CHANNEL_IDENTIFIER,
        non_closing_participant=DEFAULT_PARTICIPANT2,
    )
    scheduled_events_before = context.database.get_scheduled_events(
        max_trigger_timestamp=Timestamp(get_posix_utc_time_now())
    )

    # Try to call monitor when the user has insufficient funds
    with patch("monitoring_service.handlers.get_pessimistic_udc_balance", Mock(return_value=0)):
        action_monitoring_triggered_event_handler(event, context)
    assert not context.monitoring_service_contract.functions.monitor.called

    # Now the event must have been rescheduled
    # TODO: check that the event is rescheduled to trigger at the right block
    scheduled_events_after = context.database.get_scheduled_events(
        max_trigger_timestamp=Timestamp(get_posix_utc_time_now())
    )
    new_events = set(scheduled_events_after) - set(scheduled_events_before)
    assert len(new_events) == 1
    assert new_events.pop().event == event

    # With sufficient funds it must succeed
    with patch(
        "monitoring_service.handlers.get_pessimistic_udc_balance",
        Mock(return_value=reward_amount * UDC_SECURITY_MARGIN_FACTOR_MS),
    ):
        action_monitoring_triggered_event_handler(event, context)
    assert context.monitoring_service_contract.functions.monitor.called
def test_channel_closed_event_handler_trigger_action_monitor_event_without_monitor_request(
    context: Context,
):
    context = setup_state_with_open_channel(context)
    current_block_number = get_posix_utc_time_now() // 15

    event = ReceiveChannelClosedEvent(
        token_network_address=DEFAULT_TOKEN_NETWORK_ADDRESS,
        channel_identifier=DEFAULT_CHANNEL_IDENTIFIER,
        closing_participant=DEFAULT_PARTICIPANT2,
        block_number=BlockNumber(current_block_number + 1),
    )

    channel_closed_event_handler(event, context)
    assert context.database.scheduled_event_count() == 1
Exemplo n.º 7
0
def process_payment(  # pylint: disable=too-many-branches
    iou: Optional[IOU],
    pathfinding_service: PathfindingService,
    service_fee: TokenAmount,
    one_to_n_address: Address,
) -> None:
    if service_fee == 0:
        if iou is not None:
            log.debug(
                "Discarding IOU, service fee is 0",
                sender=to_checksum_address(iou.sender),
                total_amount=iou.amount,
                claimable_until=iou.claimable_until,
            )
        else:
            log.debug("No IOU and service fee is 0")
        return

    if iou is None:
        raise exceptions.MissingIOU

    log.debug(
        "Checking IOU",
        sender=to_checksum_address(iou.sender),
        total_amount=iou.amount,
        claimable_until=iou.claimable_until,
    )

    # Basic IOU validity checks
    if not is_same_address(iou.receiver, pathfinding_service.address):
        raise exceptions.WrongIOURecipient(expected=pathfinding_service.address)
    if iou.chain_id != pathfinding_service.chain_id:
        raise exceptions.UnsupportedChainID(expected=pathfinding_service.chain_id)
    if iou.one_to_n_address != one_to_n_address:
        raise exceptions.WrongOneToNAddress(expected=one_to_n_address, got=iou.one_to_n_address)
    if not iou.is_signature_valid():
        raise exceptions.InvalidSignature

    # Compare with known IOU
    latest_block = pathfinding_service.blockchain_state.latest_committed_block
    active_iou = pathfinding_service.database.get_iou(sender=iou.sender, claimed=False)

    if active_iou:
        if active_iou.claimable_until != iou.claimable_until:
            raise exceptions.UseThisIOU(iou=active_iou.Schema().dump(active_iou))

        expected_amount = active_iou.amount + service_fee
    else:
        claimed_iou = pathfinding_service.database.get_iou(
            sender=iou.sender, claimable_until=iou.claimable_until, claimed=True
        )
        if claimed_iou:
            raise exceptions.IOUAlreadyClaimed

        min_expiry = get_posix_utc_time_now() + MIN_IOU_EXPIRY
        if iou.claimable_until < min_expiry:
            raise exceptions.IOUExpiredTooEarly(
                min_expiry=min_expiry, claimable_until_received=iou.claimable_until
            )
        expected_amount = service_fee
    if iou.amount < expected_amount:
        raise exceptions.InsufficientServicePayment(
            expected_amount=expected_amount, actual_amount=iou.amount
        )

    # Check client's deposit in UserDeposit contract
    udc = pathfinding_service.user_deposit_contract
    udc_balance = get_pessimistic_udc_balance(
        udc=udc,
        address=iou.sender,
        from_block=BlockNumber(latest_block - pathfinding_service.required_confirmations),
        to_block=latest_block,
    )
    required_deposit = round(expected_amount * UDC_SECURITY_MARGIN_FACTOR_PFS)
    if udc_balance < required_deposit:
        raise exceptions.DepositTooLow(
            required_deposit=required_deposit, seen_deposit=udc_balance, block_number=latest_block
        )

    log.info(
        "Received service fee",
        sender=iou.sender,
        expected_amount=expected_amount,
        total_amount=iou.amount,
        added_amount=expected_amount - service_fee,
    )

    # Save latest IOU
    iou.claimed = False
    pathfinding_service.database.upsert_iou(iou)
Exemplo n.º 8
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

    user_deposit = get_pessimistic_udc_balance(
        udc=context.user_deposit_contract,
        address=monitor_request.non_closing_signer,
        from_block=context.latest_confirmed_block,
        to_block=context.get_latest_unconfirmed_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_timestamp=get_posix_utc_time_now(),
                           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
        error_message = exc.args[0] if len(exc.args) > 0 else ""
        if "not allowed to monitor" in error_message:
            raise TransactionTooEarlyException
        first_allowed = _first_allowed_timestamp_to_monitor(
            event.token_network_address, channel, context)
        failed_at = context.web3.eth.block_number
        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)
Exemplo n.º 9
0
def channel_closed_event_handler(event: Event, context: Context) -> None:
    assert isinstance(event, ReceiveChannelClosedEvent)
    channel = context.database.get_channel(event.token_network_address,
                                           event.channel_identifier)

    if channel is None:
        log.error(
            "Channel not in database",
            token_network_address=event.token_network_address,
            identifier=event.channel_identifier,
        )
        metrics.get_metrics_for_label(metrics.ERRORS_LOGGED,
                                      metrics.ErrorCategory.STATE).inc()
        return

    # Check if the settle timeout is already over.
    # This is important when starting up the MS.
    timestamp_of_closing_block = Timestamp(
        context.web3.eth.get_block(
            event.block_number).timestamp  # type: ignore
    )
    settle_timeout = context.database.get_token_network_settle_timeout(
        event.token_network_address)
    settleable_after = Timestamp(timestamp_of_closing_block + settle_timeout)
    update_balance_proof_period_is_over = settleable_after < get_posix_utc_time_now(
    )

    if not update_balance_proof_period_is_over:
        # Trigger the monitoring action event handler, this will check if a
        # valid MR is available.
        # This enables the client to send a late MR
        # also see https://github.com/raiden-network/raiden-services/issues/29
        if channel.participant1 == event.closing_participant:
            non_closing_participant = channel.participant2
        else:
            non_closing_participant = channel.participant1

        # Transactions go into the next mined block, so we could trigger one block
        # before the `monitor` call is allowed to succeed to include it in the
        # first possible block.
        # Unfortunately, parity does the gas estimation on the current block
        # instead of the next one, so we have to wait for the first allowed
        # block to be finished to send the transaction successfully on parity.
        trigger_timestamp = _first_allowed_timestamp_to_monitor(
            event.token_network_address, channel, context)

        triggered_event = ActionMonitoringTriggeredEvent(
            token_network_address=channel.token_network_address,
            channel_identifier=channel.identifier,
            non_closing_participant=non_closing_participant,
        )

        log.info(
            "Channel closed, triggering monitoring check",
            token_network_address=event.token_network_address,
            identifier=channel.identifier,
            scheduled_event=triggered_event,
            trigger_timestamp=trigger_timestamp,
        )

        # Add scheduled event if it not exists yet. If the event is already
        # scheduled (e.g. after a restart) the DB takes care that it is only
        # stored once.
        context.database.upsert_scheduled_event(
            ScheduledEvent(trigger_timestamp=trigger_timestamp,
                           event=triggered_event))
    else:
        log.warning(
            "Update balance proof period is in the past, skipping",
            token_network_address=event.token_network_address,
            identifier=channel.identifier,
            settleable_after=settleable_after,
            latest_committed_block=context.latest_committed_block,
            latest_confirmed_block=context.latest_confirmed_block,
        )

    channel.state = ChannelState.CLOSED
    channel.closing_block = event.block_number
    channel.closing_participant = event.closing_participant
    context.database.upsert_channel(channel)