예제 #1
0
        def inner_wrap(*args, **kwargs):
            message = request.headers.get(MESSAGE_HEADER)
            signature = request.headers.get(SIGNATURE_HEADER)

            authed_user_id = None
            if message and signature:
                web3 = web3_provider.get_web3()
                encoded_to_recover = encode_defunct(text=message)
                wallet = web3.eth.account.recover_message(
                    encoded_to_recover, signature=signature
                )
                db = db_session.get_db_read_replica()
                with db.scoped_session() as session:
                    user = (
                        session.query(User.user_id)
                        .filter(
                            # Convert checksum wallet to lowercase
                            User.wallet == wallet.lower(),
                            User.is_current == True,
                        )
                        # In the case that multiple wallets match (not enforced on the data layer),
                        # pick the user id that is lowest (created first).
                        .order_by(User.user_id.asc())
                        .first()
                    )
                    if user:
                        authed_user_id = user.user_id
                        logger.info(
                            f"auth_middleware.py | authed_user_id: {authed_user_id}"
                        )
            return func(*args, **kwargs, authed_user_id=authed_user_id)
예제 #2
0
    def get(self):
        args = verify_token_parser.parse_args()
        # 1. Break JWT into parts
        token_parts = args["token"].split(".")
        if not len(token_parts) == 3:
            abort_bad_request_param("token", ns)

        # 2. Decode the signature
        try:
            signature = base64.urlsafe_b64decode(token_parts[2] + "==")
        except Exception:
            ns.abort(400, "The JWT signature could not be decoded.")

        signature = signature.decode()
        base64_header = token_parts[0]
        base64_payload = token_parts[1]
        message = f"{base64_header}.{base64_payload}"

        # 3. Recover message from signature
        web3 = web3_provider.get_web3()

        wallet = None
        encoded_message = encode_defunct(text=message)
        try:
            wallet = web3.eth.account.recover_message(
                encoded_message,
                signature=signature,
            )
        except Exception:
            ns.abort(
                404, "The JWT signature is invalid - wallet could not be recovered."
            )
        if not wallet:
            ns.abort(
                404, "The JWT signature is invalid - wallet could not be recovered."
            )

        # 4. Check that user from payload matches the user from the wallet from the signature
        try:
            stringified_payload = base64.urlsafe_b64decode(base64_payload + "==")
            payload = json.loads(stringified_payload)
        except Exception:
            ns.abort(400, "JWT payload could not be decoded.")

        wallet_user_id = get_user_with_wallet(wallet)
        if not wallet_user_id or wallet_user_id != payload["userId"]:
            ns.abort(
                404,
                "The JWT signature is invalid - the wallet does not match the user.",
            )

        # 5. Send back the decoded payload
        return success_response(payload)
예제 #3
0
def get_health(args, use_redis_cache=True):
    """
    Gets health status for the service

    :param args: dictionary
    :param args.verbose: bool
        if True, returns db connection information
    :param args.healthy_block_diff: int
        determines the point at which a block difference is considered unhealthy
    :param args.enforce_block_diff: bool
        if true and the block difference is unhealthy an error is returned

    :rtype: (dictionary, bool)
    :return: tuple of health results and a boolean indicating an error
    """
    redis = redis_connection.get_redis()
    web3 = web3_provider.get_web3()

    verbose = args.get("verbose")
    enforce_block_diff = args.get("enforce_block_diff")
    qs_healthy_block_diff = args.get("healthy_block_diff")

    # If healthy block diff is given in url and positive, override config value
    healthy_block_diff = qs_healthy_block_diff if qs_healthy_block_diff is not None \
        and qs_healthy_block_diff >= 0 else default_healthy_block_diff

    latest_block_num = None
    latest_block_hash = None
    latest_indexed_block_num = None
    latest_indexed_block_hash = None

    if use_redis_cache:
        # get latest blockchain state from redis cache, or fallback to chain if None
        latest_block_num, latest_block_hash = get_latest_chain_block_set_if_nx(
            redis, web3)

        # get latest db state from redis cache
        latest_indexed_block_num = redis.get(
            most_recent_indexed_block_redis_key)
        if latest_indexed_block_num is not None:
            latest_indexed_block_num = int(latest_indexed_block_num)

        latest_indexed_block_hash = redis.get(
            most_recent_indexed_block_hash_redis_key)
        if latest_indexed_block_hash is not None:
            latest_indexed_block_hash = latest_indexed_block_hash.decode(
                "utf-8")

    # fetch latest blockchain state from web3 if:
    # we explicitly don't want to use redis cache or
    # value from redis cache is None
    if not use_redis_cache or latest_block_num is None or latest_block_hash is None:
        # get latest blockchain state from web3
        latest_block = web3.eth.getBlock("latest", True)
        latest_block_num = latest_block.number
        latest_block_hash = latest_block.hash.hex()

    # fetch latest db state if:
    # we explicitly don't want to use redis cache or
    # value from redis cache is None
    if not use_redis_cache or latest_indexed_block_num is None or latest_indexed_block_hash is None:
        db_block_state = _get_db_block_state()
        latest_indexed_block_num = db_block_state["number"] or 0
        latest_indexed_block_hash = db_block_state["blockhash"]

    trending_tracks_age_sec = get_elapsed_time_redis(
        redis, trending_tracks_last_completion_redis_key)
    trending_playlists_age_sec = get_elapsed_time_redis(
        redis, trending_playlists_last_completion_redis_key)

    # Get system information monitor values
    sys_info = monitors.get_monitors([
        MONITORS[monitor_names.database_size],
        MONITORS[monitor_names.database_connections],
        MONITORS[monitor_names.total_memory],
        MONITORS[monitor_names.used_memory],
        MONITORS[monitor_names.filesystem_size],
        MONITORS[monitor_names.filesystem_used],
        MONITORS[monitor_names.received_bytes_per_sec],
        MONITORS[monitor_names.transferred_bytes_per_sec],
        MONITORS[monitor_names.redis_total_memory]
    ])

    health_results = {
        "web": {
            "blocknumber": latest_block_num,
            "blockhash": latest_block_hash,
        },
        "db": {
            "number": latest_indexed_block_num,
            "blockhash": latest_indexed_block_hash
        },
        "git": os.getenv("GIT_SHA"),
        "trending_tracks_age_sec": trending_tracks_age_sec,
        "trending_playlists_age_sec": trending_playlists_age_sec,
        "number_of_cpus": number_of_cpus,
        **sys_info
    }

    block_difference = abs(health_results["web"]["blocknumber"] -
                           health_results["db"]["number"])
    health_results["block_difference"] = block_difference
    health_results[
        "maximum_healthy_block_difference"] = default_healthy_block_diff
    health_results.update(disc_prov_version)

    if verbose:
        # DB connections check
        db_connections_json, error = _get_db_conn_state()
        health_results["db_connections"] = db_connections_json
        if error:
            return health_results, error

    # Return error on unhealthy block diff if requested.
    if enforce_block_diff and health_results[
            "block_difference"] > healthy_block_diff:
        return health_results, True

    return health_results, False
예제 #4
0
def get_health(args: GetHealthArgs,
               use_redis_cache: bool = True) -> Tuple[Dict, bool]:
    """
    Gets health status for the service

    Returns a tuple of health results and a boolean indicating an error
    """
    redis = redis_connection.get_redis()
    web3 = web3_provider.get_web3()

    verbose = args.get("verbose")
    enforce_block_diff = args.get("enforce_block_diff")
    qs_healthy_block_diff = cast(Optional[int], args.get("healthy_block_diff"))
    challenge_events_age_max_drift = args.get("challenge_events_age_max_drift")
    plays_count_max_drift = args.get("plays_count_max_drift")

    # If healthy block diff is given in url and positive, override config value
    healthy_block_diff = (
        qs_healthy_block_diff if qs_healthy_block_diff is not None
        and qs_healthy_block_diff >= 0 else default_healthy_block_diff)

    latest_block_num: Optional[int] = None
    latest_block_hash: Optional[str] = None
    latest_indexed_block_num: Optional[int] = None
    latest_indexed_block_hash: Optional[str] = None

    if use_redis_cache:
        # get latest blockchain state from redis cache, or fallback to chain if None
        latest_block_num, latest_block_hash = get_latest_chain_block_set_if_nx(
            redis, web3)

        # get latest db state from redis cache
        latest_indexed_block_num = redis.get(
            most_recent_indexed_block_redis_key)
        if latest_indexed_block_num is not None:
            latest_indexed_block_num = int(latest_indexed_block_num)

        latest_indexed_block_hash_bytes = redis.get(
            most_recent_indexed_block_hash_redis_key)
        if latest_indexed_block_hash_bytes is not None:
            latest_indexed_block_hash = latest_indexed_block_hash_bytes.decode(
                "utf-8")
    else:
        # Get latest blockchain state from web3
        try:
            latest_block = web3.eth.get_block("latest", True)
            latest_block_num = latest_block.number
            latest_block_hash = latest_block.hash.hex()
        except Exception as e:
            logger.error(f"Could not get latest block from chain: {e}")

    # fetch latest db state if:
    # we explicitly don't want to use redis cache or
    # value from redis cache is None
    if (not use_redis_cache or latest_indexed_block_num is None
            or latest_indexed_block_hash is None):
        db_block_state = _get_db_block_state()
        latest_indexed_block_num = db_block_state["number"] or 0
        latest_indexed_block_hash = db_block_state["blockhash"]

    play_health_info = get_play_health_info(redis, plays_count_max_drift)
    rewards_manager_health_info = get_rewards_manager_health_info(redis)
    user_bank_health_info = get_user_bank_health_info(redis)
    spl_audio_info = get_spl_audio_info(redis)
    reactions_health_info = get_reactions_health_info(
        redis,
        args.get("reactions_max_indexing_drift"),
        args.get("reactions_max_last_reaction_drift"),
    )

    trending_tracks_age_sec = get_elapsed_time_redis(
        redis, trending_tracks_last_completion_redis_key)
    trending_playlists_age_sec = get_elapsed_time_redis(
        redis, trending_playlists_last_completion_redis_key)
    challenge_events_age_sec = get_elapsed_time_redis(
        redis, challenges_last_processed_event_redis_key)
    user_balances_age_sec = get_elapsed_time_redis(
        redis, user_balances_refresh_last_completion_redis_key)
    num_users_in_lazy_balance_refresh_queue = int(
        redis.scard(LAZY_REFRESH_REDIS_PREFIX))
    num_users_in_immediate_balance_refresh_queue = int(
        redis.scard(IMMEDIATE_REFRESH_REDIS_PREFIX))
    last_scanned_block_for_balance_refresh = redis_get_or_restore(
        redis, eth_indexing_last_scanned_block_key)
    index_eth_age_sec = get_elapsed_time_redis(
        redis, index_eth_last_completion_redis_key)
    last_scanned_block_for_balance_refresh = (
        int(last_scanned_block_for_balance_refresh)
        if last_scanned_block_for_balance_refresh else None)

    # Get system information monitor values
    sys_info = monitors.get_monitors([
        MONITORS[monitor_names.database_size],
        MONITORS[monitor_names.database_connections],
        MONITORS[monitor_names.total_memory],
        MONITORS[monitor_names.used_memory],
        MONITORS[monitor_names.filesystem_size],
        MONITORS[monitor_names.filesystem_used],
        MONITORS[monitor_names.received_bytes_per_sec],
        MONITORS[monitor_names.transferred_bytes_per_sec],
        MONITORS[monitor_names.redis_total_memory],
    ])

    health_results = {
        "web": {
            "blocknumber": latest_block_num,
            "blockhash": latest_block_hash,
        },
        "db": {
            "number": latest_indexed_block_num,
            "blockhash": latest_indexed_block_hash,
        },
        "git": os.getenv("GIT_SHA"),
        "trending_tracks_age_sec": trending_tracks_age_sec,
        "trending_playlists_age_sec": trending_playlists_age_sec,
        "challenge_last_event_age_sec": challenge_events_age_sec,
        "user_balances_age_sec": user_balances_age_sec,
        "num_users_in_lazy_balance_refresh_queue":
        num_users_in_lazy_balance_refresh_queue,
        "num_users_in_immediate_balance_refresh_queue":
        num_users_in_immediate_balance_refresh_queue,
        "last_scanned_block_for_balance_refresh":
        last_scanned_block_for_balance_refresh,
        "index_eth_age_sec": index_eth_age_sec,
        "number_of_cpus": number_of_cpus,
        **sys_info,
        "plays": play_health_info,
        "rewards_manager": rewards_manager_health_info,
        "user_bank": user_bank_health_info,
        "openresty_public_key": openresty_public_key,
        "spl_audio_info": spl_audio_info,
        "reactions": reactions_health_info,
        "infra_setup": infra_setup,
    }

    if latest_block_num is not None and latest_indexed_block_num is not None:
        block_difference = abs(latest_block_num - latest_indexed_block_num)
    else:
        # If we cannot get a reading from chain about what the latest block is,
        # we set the difference to be an unhealthy amount
        block_difference = default_healthy_block_diff + 1
    health_results["block_difference"] = block_difference
    health_results[
        "maximum_healthy_block_difference"] = default_healthy_block_diff
    health_results.update(disc_prov_version)

    # Check that this node meets the minimum system requirements
    num_cpus: int = cast(int, health_results["number_of_cpus"] or 0)
    total_memory: int = cast(int, health_results["total_memory"] or 0)
    filesystem_size: int = cast(int, health_results["filesystem_size"] or 0)
    if (num_cpus < min_number_of_cpus or total_memory < min_total_memory
            or filesystem_size < min_filesystem_size):
        health_results["meets_min_requirements"] = False
        # TODO - this will become strictly enforced in upcoming service versions and return with error
    else:
        health_results["meets_min_requirements"] = True

    if verbose:
        # Elasticsearch health
        if esclient:
            health_results["elasticsearch"] = get_elasticsearch_health_info(
                esclient, latest_indexed_block_num)

        # DB connections check
        db_connections_json, db_connections_error = _get_db_conn_state()
        health_results["db_connections"] = db_connections_json
        location = get_location()
        health_results.update(location)

        if db_connections_error:
            return health_results, db_connections_error

        query_insights_json, query_insights_error = _get_query_insights()
        health_results["query_insights"] = query_insights_json

        if query_insights_error:
            return health_results, query_insights_error

        table_size_info_json = monitors.get_monitors([
            MONITORS[monitor_names.table_size_info],
        ])

        health_results["tables"] = table_size_info_json

    unhealthy_blocks = bool(enforce_block_diff
                            and block_difference > healthy_block_diff)
    unhealthy_challenges = bool(
        challenge_events_age_max_drift and challenge_events_age_sec
        and challenge_events_age_sec > challenge_events_age_max_drift)

    is_unhealthy = (unhealthy_blocks or unhealthy_challenges
                    or play_health_info["is_unhealthy"]
                    or reactions_health_info["is_unhealthy"])

    return health_results, is_unhealthy
예제 #5
0
def get_health(args, use_redis_cache=True):
    """
    Gets health status for the service

    :param args: dictionary
    :param args.verbose: bool
        if True, returns db connection information
    :param args.healthy_block_diff: int
        determines the point at which a block difference is considered unhealthy
    :param args.enforce_block_diff: bool
        if true and the block difference is unhealthy an error is returned

    :rtype: (dictionary, bool)
    :return: tuple of health results and a boolean indicating an error
    """
    redis = redis_connection.get_redis()
    web3 = web3_provider.get_web3()

    verbose = args.get("verbose")
    enforce_block_diff = args.get("enforce_block_diff")
    qs_healthy_block_diff = args.get("healthy_block_diff")

    # If healthy block diff is given in url and positive, override config value
    healthy_block_diff = qs_healthy_block_diff if qs_healthy_block_diff is not None \
        and qs_healthy_block_diff >= 0 else default_healthy_block_diff

    latest_block_num = None
    latest_block_hash = None

    # Get latest web block info
    if use_redis_cache:
        stored_latest_block_num = redis.get(latest_block_redis_key)
        if stored_latest_block_num is not None:
            latest_block_num = int(stored_latest_block_num)

        stored_latest_blockhash = redis.get(latest_block_hash_redis_key)
        if stored_latest_blockhash is not None:
            latest_block_hash = stored_latest_blockhash.decode("utf-8")

    if latest_block_num is None or latest_block_hash is None:
        latest_block = web3.eth.getBlock("latest", True)
        latest_block_num = latest_block.number
        latest_block_hash = latest_block.hash.hex()

    latest_indexed_block_num = None
    latest_indexed_block_hash = None

    # Get latest indexed block info
    if use_redis_cache:
        latest_indexed_block_num = redis.get(
            most_recent_indexed_block_redis_key)
        if latest_indexed_block_num is not None:
            latest_indexed_block_num = int(latest_indexed_block_num)

        latest_indexed_block_hash = redis.get(
            most_recent_indexed_block_hash_redis_key)
        if latest_indexed_block_hash is not None:
            latest_indexed_block_hash = latest_indexed_block_hash.decode(
                "utf-8")

    if latest_indexed_block_num is None or latest_indexed_block_hash is None:
        db_block_state = _get_db_block_state()
        latest_indexed_block_num = db_block_state["number"] or 0
        latest_indexed_block_hash = db_block_state["blockhash"]

    health_results = {
        "web": {
            "blocknumber": latest_block_num,
            "blockhash": latest_block_hash,
        },
        "db": {
            "number": latest_indexed_block_num,
            "blockhash": latest_indexed_block_hash
        },
        "git": os.getenv("GIT_SHA"),
    }

    block_difference = abs(
        health_results["web"]["blocknumber"] - health_results["db"]["number"]
    )
    health_results["block_difference"] = block_difference
    health_results["maximum_healthy_block_difference"] = default_healthy_block_diff
    health_results.update(disc_prov_version)

    if verbose:
        # DB connections check
        db_connections_json, error = _get_db_conn_state()
        health_results["db_connections"] = db_connections_json
        if error:
            return health_results, error

    # Return error on unhealthy block diff if requested.
    if enforce_block_diff and health_results["block_difference"] > healthy_block_diff:
        return health_results, True

    return health_results, False
예제 #6
0
# pylint: disable=no-name-in-module
from eth_account.messages import encode_defunct
from flask import jsonify
from src.queries.get_health import get_latest_chain_block_set_if_nx
from src.queries.get_sol_plays import get_sol_play_health_info

# pylint: disable=R0401
from src.utils import helpers, web3_provider
from src.utils.config import shared_config
from src.utils.redis_constants import most_recent_indexed_block_redis_key
from web3 import Web3
from web3.auto import w3

redis_url = shared_config["redis"]["url"]
redis_conn = redis.Redis.from_url(url=redis_url)
web3_connection = web3_provider.get_web3()
logger = logging.getLogger(__name__)
disc_prov_version = helpers.get_discovery_provider_version()


# Subclass JSONEncoder
class DateTimeEncoder(json.JSONEncoder):
    # Override the default method
    def default(self, o):  # pylint: disable=E0202
        if isinstance(o, (datetime.date, datetime.datetime)):
            # the Z is required in JS date format
            return o.isoformat() + " Z"
        return json.JSONEncoder.default(self, o)


def error_response(error, error_code=500):