dragonchain/dragonchain

View on GitHub
dragonchain/webserver/lib/dragonnet.py

Summary

Maintainability
A
2 hrs
Test Coverage
F
28%
# Copyright 2020 Dragonchain, Inc.
# Licensed under the Apache License, Version 2.0 (the "Apache License")
# with the following modification; you may not use this file except in
# compliance with the Apache License and the following modification to it:
# Section 6. Trademarks. is deleted and replaced with:
#      6. Trademarks. This License does not grant permission to use the trade
#         names, trademarks, service marks, or product names of the Licensor
#         and its affiliates, except as required to comply with Section 4(c) of
#         the License and to reproduce the content of the NOTICE file.
# You may obtain a copy of the Apache License at
#     http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the Apache License with the above modification is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.

import os
from typing import TYPE_CHECKING, cast, Dict, Any


from dragonchain.broadcast_processor import broadcast_functions
from dragonchain.lib.interfaces import storage
from dragonchain.lib.dto import l2_block_model
from dragonchain.lib.dto import l3_block_model
from dragonchain.lib.dto import l4_block_model
from dragonchain.lib.dto import l5_block_model
from dragonchain.lib.dto import api_key_model
from dragonchain.lib.dao import api_key_dao
from dragonchain.lib.dao import block_dao
from dragonchain.lib.database import redisearch
from dragonchain.lib import matchmaking
from dragonchain.lib import keys
from dragonchain.lib import queue
from dragonchain import exceptions
from dragonchain import logger


if TYPE_CHECKING:
    from dragonchain.lib.dto import model  # noqa: F401

REDIS_HOST = os.environ["REDIS_ENDPOINT"]
REDIS_PORT = os.environ["REDIS_PORT"]


_log = logger.get_logger()


def process_receipt_v1(block_dto: Dict[str, Any]) -> None:
    if not block_dto:
        raise exceptions.ValidationException("block_dto missing")
    _log.info(f"[RECEIPT] Got receipt from L{block_dto['header']['level']}: {block_dto}")
    block_model = cast("model.BlockModel", None)  # This will always get defined, or it will raise
    level_received_from: int = block_dto["header"]["level"]
    if level_received_from == 2:
        block_model = l2_block_model.new_from_at_rest(block_dto)
    elif level_received_from == 3:
        block_model = l3_block_model.new_from_at_rest(block_dto)
    elif level_received_from == 4:
        block_model = l4_block_model.new_from_at_rest(block_dto)
    elif level_received_from == 5:
        block_model = l5_block_model.new_from_at_rest(block_dto)
    else:
        raise exceptions.InvalidNodeLevel("Unsupported level receipt")

    _log.debug(f"Block model {block_model.__dict__}")
    l1_block_id_set = block_model.get_associated_l1_block_id()

    _log.info(f"Processing receipt for blocks {l1_block_id_set} from L{level_received_from}")
    for l1_block_id in l1_block_id_set:
        try:
            # Check that the chain which sent this receipt is in our claims, and that this L1 block is accepting receipts for this level
            try:
                validations = matchmaking.get_claim_check(l1_block_id)["validations"][f"l{level_received_from}"]
            except exceptions.NotFound:
                _log.info(f"Block {l1_block_id} has no claim check. Presumably already closed by another L5. Ignoring receipt for this L1 block.")
                continue
            if (block_model.dc_id in validations) and broadcast_functions.is_block_accepting_verifications_from_level(
                l1_block_id, level_received_from
            ):
                _log.info(f"Verified that block {l1_block_id} was sent. Inserting receipt")
                storage_location = broadcast_functions.verification_storage_location(l1_block_id, level_received_from, block_model.dc_id)
                storage.put_object_as_json(storage_location, block_model.export_as_at_rest())
                # Set new receipt for matchmaking claim check
                try:
                    block_id = block_model.block_id
                    proof = block_model.proof
                    dc_id = block_model.dc_id
                    matchmaking.add_receipt(l1_block_id, level_received_from, dc_id, block_id, proof)
                except Exception:
                    _log.exception("matchmaking add_receipt failed!")
                # Update the broadcast system about this receipt
                broadcast_functions.set_receieved_verification_for_block_from_chain_sync(l1_block_id, level_received_from, block_model.dc_id)
                if level_received_from == 5:
                    client = redisearch._get_redisearch_index_client(redisearch.Indexes.verification.value)
                    client.redis.sadd(redisearch.L5_NODES, block_model.dc_id)
                    block_dao.insert_l5_verification(storage_location, block_model)
            else:
                _log.warning(
                    f"Chain {block_model.dc_id} (level {level_received_from}) returned a receipt that wasn't expected (possibly expired?) for block {l1_block_id}. Rejecting receipt"  # noqa: B950
                )
                raise exceptions.NotAcceptingVerifications(f"Not accepting verifications for block {l1_block_id} from {block_model.dc_id}")
        except Exception:
            _log.exception(f"Unknown error occurred processing block {l1_block_id}. Skipping receipt for this block.")


def enqueue_item_for_verification_v1(content: Dict[str, str], deadline: int) -> None:
    queue.enqueue_item(content, deadline)


def get_local_claim_v1(block_id: str) -> dict:
    return matchmaking.get_claim_check(block_id)


def register_interchain_auth_v1(chain_registration_body: Dict[str, str]) -> None:
    dcid = chain_registration_body["dcid"]
    key = chain_registration_body["key"]
    signature = chain_registration_body["signature"]

    # Initialize keys with chain id as public key (dont need to pull from matchmaking)
    requester_keys = keys.DCKeys(pull_keys=False).initialize(public_key_string=dcid, hash_type="sha256")

    if not requester_keys.check_signature(f"{keys.get_public_id()}_{key}".encode("utf-8"), signature):
        _log.info(f"invalid signature for interchain key registration from {dcid}")
        raise exceptions.UnauthorizedException("Invalid signature authorization")

    # Authorization successful, now register actual key
    auth_key = api_key_model.new_from_scratch(interchain_dcid=dcid)
    auth_key.key = key
    try:
        api_key_dao.save_api_key(auth_key)
    except Exception:
        _log.exception("Error saving interchain auth key")
        raise RuntimeError("Authorization registration failure")

    _log.info(f"successfully registered interchain auth key with {dcid}")