Esempio n. 1
0
def validate_registry_uri(uri: str) -> None:
    """
    Raise an exception if the URI does not conform to the registry URI scheme.
    """
    parsed = parse.urlparse(uri)
    scheme, authority, pkg_path = (
        parsed.scheme,
        parsed.netloc,
        parsed.path,
    )
    pkg_id = pkg_path.strip("/")

    if "@" in pkg_id:
        if len(pkg_id.split("@")) != 2:
            raise EthPMValidationError(
                "Registry URI: {pkg_id} is not properly escaped")
        pkg_name, pkg_version = pkg_id.split("@")
    else:
        pkg_name, pkg_version = (pkg_id, None)

    validate_registry_uri_scheme(scheme)
    validate_registry_uri_authority(authority)
    if pkg_name:
        validate_package_name(pkg_name)
    if not pkg_name and pkg_version:
        raise EthPMValidationError(
            "Registry URIs cannot provide a version without a package name.")
    if pkg_version:
        validate_escaped_string(pkg_version)
Esempio n. 2
0
def validate_build_dependencies_are_present(manifest: Dict[str, Any]) -> None:
    if "build_dependencies" not in manifest:
        raise EthPMValidationError(
            "Manifest doesn't have any build dependencies.")

    if not manifest["build_dependencies"]:
        raise EthPMValidationError(
            "Manifest's build dependencies key is empty.")
Esempio n. 3
0
def validate_deployments_tx_receipt(
    deployments: Dict[str, Any], w3: "Web3", allow_missing_data: bool = False
) -> None:
    """
    Validate that address and block hash found in deployment data match what is found on-chain.
    :allow_missing_data: by default, enforces validation of address and blockHash.
    """
    # todo: provide hook to lazily look up tx receipt via binary search if missing data
    for name, data in deployments.items():
        if "transaction" in data:
            tx_hash = data["transaction"]
            tx_receipt = w3.eth.get_transaction_receipt(tx_hash)
            # tx_address will be None if contract created via contract factory
            tx_address = tx_receipt["contractAddress"]

            if tx_address is None and allow_missing_data is False:
                raise EthPMValidationError(
                    "No contract address found in tx receipt. Unable to verify "
                    "address found in tx receipt matches address in manifest's deployment data. "
                    "If this validation is not necessary, please enable `allow_missing_data` arg. "
                )

            if tx_address is not None and not is_same_address(
                tx_address, data["address"]
            ):
                raise EthPMValidationError(
                    f"Error validating tx_receipt for {name} deployment. "
                    f"Address found in manifest's deployment data: {data['address']} "
                    f"Does not match address found on tx_receipt: {tx_address}."
                )

            if "block" in data:
                if tx_receipt["blockHash"] != to_bytes(hexstr=data["block"]):
                    raise EthPMValidationError(
                        f"Error validating tx_receipt for {name} deployment. "
                        f"Block found in manifest's deployment data: {data['block']!r} does not "
                        f"Does not match block found on tx_receipt: {tx_receipt['blockHash']!r}."
                    )
            elif allow_missing_data is False:
                raise EthPMValidationError(
                    "No block hash found in deployment data. "
                    "Unable to verify block hash on tx receipt. "
                    "If this validation is not necessary, please enable `allow_missing_data` arg."
                )
        elif allow_missing_data is False:
            raise EthPMValidationError(
                "No transaction hash found in deployment data. "
                "Unable to validate tx_receipt. "
                "If this validation is not necessary, please enable `allow_missing_data` arg."
            )
Esempio n. 4
0
def validate_raw_manifest_format(raw_manifest: str) -> None:
    """
    Raise a EthPMValidationError if a manifest ...
    - is not tightly packed (i.e. no linebreaks or extra whitespace)
    - does not have alphabetically sorted keys
    - has duplicate keys
    - is not UTF-8 encoded
    - has a trailing newline
    """
    try:
        manifest_dict = json.loads(raw_manifest, encoding="UTF-8")
    except json.JSONDecodeError as err:
        raise json.JSONDecodeError(
            "Failed to load package data. File is not a valid JSON document.",
            err.doc,
            err.pos,
        )
    compact_manifest = json.dumps(manifest_dict,
                                  sort_keys=True,
                                  separators=(",", ":"))
    if raw_manifest != compact_manifest:
        raise EthPMValidationError(
            "The manifest appears to be malformed. Please ensure that it conforms to the "
            "EthPM-Spec for document format. "
            "http://ethpm.github.io/ethpm-spec/package-spec.html#document-format "
        )
Esempio n. 5
0
def validate_manifest_exists(manifest_id: str) -> None:
    """
    Validate that manifest with manifest_id exists in ASSETS_DIR
    """
    if not (ASSETS_DIR / manifest_id).is_file():
        raise EthPMValidationError(
            f"Manifest not found in ASSETS_DIR with id: {manifest_id}")
Esempio n. 6
0
def validate_escaped_string(string: str) -> None:
    unsafe = parse.unquote(string)
    safe = parse.quote(unsafe)
    if string != safe:
        raise EthPMValidationError(
            f"String: {string} is not properly escaped, and contains url unsafe characters."
        )
Esempio n. 7
0
def validate_registry_uri_scheme(scheme: str) -> None:
    """
    Raise an exception if the scheme is not the valid registry URI scheme ('ercXXX').
    """
    if scheme != REGISTRY_URI_SCHEME:
        raise EthPMValidationError(
            f"{scheme} is not a valid registry URI scheme.")
Esempio n. 8
0
    def __init__(
            self, manifest: Dict[str, Any], w3: Web3, uri: Optional[str] = None
    ) -> None:
        """
        A package should be created using one of the available
        classmethods and a valid w3 instance.
        """
        if not isinstance(manifest, dict):
            raise TypeError(
                "Package object must be initialized with a dictionary. "
                f"Got {type(manifest)}"
            )

        if "manifest" not in manifest or manifest["manifest"] != "ethpm/3":
            raise EthPMValidationError(
                "Py-Ethpm currently only supports v3 ethpm manifests. "
                "Please use the CLI to update or re-generate a v3 manifest. "
            )

        validate_manifest_against_schema(manifest)
        validate_manifest_deployments(manifest)
        validate_w3_instance(w3)

        self.w3 = w3
        self.w3.eth.defaultContractFactory = cast(Type[Contract], LinkableContract)
        self.manifest = manifest
        self._uri = uri
Esempio n. 9
0
def validate_package_name(pkg_name: str) -> None:
    """
    Raise an exception if the value is not a valid package name
    as defined in the EthPM-Spec.
    """
    if not bool(re.match(PACKAGE_NAME_REGEX, pkg_name)):
        raise EthPMValidationError(f"{pkg_name} is not a valid package name.")
Esempio n. 10
0
def _build_dependency(package_name: str, uri: URI,
                      manifest: Manifest) -> Manifest:
    validate_package_name(package_name)
    if not is_supported_content_addressed_uri(uri):
        raise EthPMValidationError(
            f"{uri} is not a supported content-addressed URI. "
            "Currently only IPFS and Github blob uris are supported.")
    return assoc_in(manifest, ("buildDependencies", package_name), uri)
Esempio n. 11
0
def validate_registry_uri_version(query: str) -> None:
    """
    Raise an exception if the version param is malformed.
    """
    query_dict = parse.parse_qs(query, keep_blank_values=True)
    if "version" not in query_dict:
        raise EthPMValidationError(
            f"{query} is not a correctly formatted version param.")
Esempio n. 12
0
def validate_manifest_version(version: str) -> None:
    """
    Raise an exception if the version is not "ethpm/3".
    """
    if not version == "ethpm/3":
        raise EthPMValidationError(
            f"Py-EthPM does not support the provided specification version: {version}"
        )
Esempio n. 13
0
def validate_package_version(version: Any) -> None:
    """
    Validates that a package version is of text type.
    """
    if not is_text(version):
        raise EthPMValidationError(
            f"Expected a version of text type, instead received {type(version)}."
        )
Esempio n. 14
0
def validate_link_ref(offset: int, length: int, bytecode: str) -> str:
    slot_length = offset + length
    slot = bytecode[offset:slot_length]
    if slot[:2] != "__" and slot[-2:] != "__":
        raise EthPMValidationError(
            f"Slot: {slot}, at offset: {offset} of length: {length} is not a valid "
            "link_ref that can be replaced.")
    return bytecode
Esempio n. 15
0
def validate_build_dependency(key: str, uri: str) -> None:
    """
    Raise an exception if the key in dependencies is not a valid package name,
    or if the value is not a valid IPFS URI.
    """
    validate_package_name(key)
    # validate is supported content-addressed uri
    if not is_ipfs_uri(uri):
        raise EthPMValidationError(f"URI: {uri} is not a valid IPFS URI.")
Esempio n. 16
0
def validate_registry_uri_scheme(scheme: str) -> None:
    """
    Raise an exception if the scheme is not a valid registry URI scheme:
     - 'erc1319'
     - 'ethpm'
    """
    if scheme not in REGISTRY_URI_SCHEMES:
        raise EthPMValidationError(
            f"{scheme} is not a valid registry URI scheme. "
            f"Valid schemes include: {REGISTRY_URI_SCHEMES}")
Esempio n. 17
0
def validate_meta_object(meta: Dict[str, Any],
                         allow_extra_meta_fields: bool) -> None:
    """
    Validates that every key is one of `META_FIELDS` and has a value of the expected type.
    """
    for key, value in meta.items():
        if key in META_FIELDS:
            if type(value) is not META_FIELDS[key]:
                raise EthPMValidationError(
                    f"Values for {key} are expected to have the type {META_FIELDS[key]}, "
                    f"instead got {type(value)}.")
        elif allow_extra_meta_fields:
            if key[:2] != "x-":
                raise EthPMValidationError(
                    "Undefined meta fields need to begin with 'x-', "
                    f"{key} is not a valid undefined meta field.")
        else:
            raise EthPMValidationError(
                f"{key} is not a permitted meta field. To allow undefined fields, "
                "set `allow_extra_meta_fields` to True.")
Esempio n. 18
0
def validate_single_matching_uri(all_blockchain_uris: List[str],
                                 w3: "Web3") -> str:
    """
    Return a single block URI after validating that it is the *only* URI in
    all_blockchain_uris that matches the w3 instance.
    """
    from ethpm.uri import check_if_chain_matches_chain_uri

    matching_uris = [
        uri for uri in all_blockchain_uris
        if check_if_chain_matches_chain_uri(w3, uri)
    ]

    if not matching_uris:
        raise EthPMValidationError("Package has no matching URIs on chain.")
    elif len(matching_uris) != 1:
        raise EthPMValidationError(
            f"Package has too many ({len(matching_uris)}) matching URIs: {matching_uris}."
        )
    return matching_uris[0]
Esempio n. 19
0
def validate_empty_bytes(offset: int, length: int, bytecode: bytes) -> None:
    """
    Validates that segment [`offset`:`offset`+`length`] of
    `bytecode` is comprised of empty bytes (b'\00').
    """
    slot_length = offset + length
    slot = bytecode[offset:slot_length]
    if slot != bytearray(length):
        raise EthPMValidationError(
            f"Bytecode segment: [{offset}:{slot_length}] is not comprised of empty bytes, "
            f"rather: {slot}.")
Esempio n. 20
0
 def _get_all_contract_instances(self, deployments):
     for deployment_name, deployment_data in deployments.items():
         if deployment_data['contract_type'] not in self.contract_types:
             raise EthPMValidationError(
                 f"Contract type: {deployment_data['contract_type']} for alias: "
                 f"{deployment_name} not found. Available contract types include: "
                 f"{self.contract_types}.")
         contract_instance = self.get_contract_instance(
             deployment_data['contract_type'],
             deployment_data['address'],
         )
         yield deployment_name, contract_instance
Esempio n. 21
0
 def fetch_uri_contents(self, uri: str) -> bytes:
     ipfs_hash = extract_ipfs_path_from_uri(uri)
     contents = self.client.cat(ipfs_hash)
     # Local validation of hashed contents only works for non-chunked files ~< 256kb
     # Improved validation WIP @ https://github.com/ethpm/py-ethpm/pull/165
     if len(contents) <= 262144:
         validation_hash = generate_file_hash(contents)
         if validation_hash != ipfs_hash:
             raise EthPMValidationError(
                 f"Hashed IPFS contents retrieved from uri: {uri} do not match its content hash."
             )
     return contents
Esempio n. 22
0
def validate_manifest_against_schema(manifest: Dict[str, Any]) -> None:
    """
    Load and validate manifest against schema
    located at MANIFEST_SCHEMA_PATH.
    """
    schema_data = _load_schema_data()
    try:
        validate(manifest, schema_data)
    except jsonValidationError as e:
        raise EthPMValidationError(
            f"Manifest invalid for schema version {schema_data['version']}. "
            f"Reason: {e.message}")
Esempio n. 23
0
def validate_registry_uri_authority(auth: str) -> None:
    """
    Raise an exception if the authority is not a valid ENS domain
    or a valid checksummed contract address.
    """
    try:
        address, chain_id = auth.split(':')
    except ValueError:
        raise EthPMValidationError(
            f"{auth} is not a valid registry URI authority. "
            "Please try again with a valid registry URI.")

    if is_ens_domain(address) is False and not is_checksum_address(address):
        raise EthPMValidationError(
            f"{address} is not a valid registry address. "
            "Please try again with a valid registry URI.")

    if not is_supported_chain_id(to_int(text=chain_id)):
        raise EthPMValidationError(
            f"Chain ID: {chain_id} is not supported. Supported chain ids include: "
            "1 (mainnet), 3 (ropsten), 4 (rinkeby), 5 (goerli) and 42 (kovan). "
            "Please try again with a valid registry URI.")
Esempio n. 24
0
def _process_pkg_path(
        raw_pkg_path: str
) -> Tuple[Optional[str], Optional[str], Optional[str]]:
    pkg_path = raw_pkg_path.strip("/")
    if not pkg_path:
        return None, None, None

    pkg_id, namespaced_asset = _parse_pkg_path(pkg_path)
    pkg_name, pkg_version = _parse_pkg_id(pkg_id)
    if not pkg_version and namespaced_asset:
        raise EthPMValidationError(
            "Invalid registry URI, missing package version."
            "Version is required if namespaced assets are defined.")
    return pkg_name, pkg_version, namespaced_asset
Esempio n. 25
0
 def _get_all_contract_instances(
         self, deployments: Dict[str, DeploymentData]
 ) -> Iterable[Tuple[str, Contract]]:
     for deployment_name, deployment_data in deployments.items():
         if deployment_data['contractType'] not in self.contract_types:
             raise EthPMValidationError(
                 f"Contract type: {deployment_data['contractType']} for alias: "
                 f"{deployment_name} not found. Available contract types include: "
                 f"{self.contract_types}."
             )
         contract_instance = self.get_contract_instance(
             ContractName(deployment_data['contractType']),
             deployment_data['address'],
         )
         yield deployment_name, contract_instance
Esempio n. 26
0
def validate_manifest_against_schema(manifest: Dict[str, Any]) -> None:
    """
    Load and validate manifest against schema
    located at v3_schema_path.
    """
    schema_data = _load_schema_data()
    try:
        validate(manifest,
                 schema_data,
                 cls=validator_for(schema_data, Draft7Validator))
    except jsonValidationError as e:
        raise EthPMValidationError(
            f"Manifest invalid for schema version {schema_data['version']}. "
            f"Reason: {e.message}"
            f"{e}")
Esempio n. 27
0
def validate_manifest_deployments(manifest: Dict[str, Any]) -> None:
    """
    Validate that a manifest's deployments contracts reference existing contract_types.
    """
    if set(("contract_types", "deployments")).issubset(manifest):
        all_contract_types = list(manifest["contract_types"].keys())
        all_deployments = list(manifest["deployments"].values())
        all_deployment_names = extract_contract_types_from_deployments(all_deployments)
        missing_contract_types = set(all_deployment_names).difference(
            all_contract_types
        )
        if missing_contract_types:
            raise EthPMValidationError(
                f"Manifest missing references to contracts: {missing_contract_types}."
            )
    def _validate_name_and_references(self, name: str) -> None:
        validate_contract_name(name)

        if name not in self.deployment_data:
            raise KeyError(
                "Contract name not found in deployment data. "
                f"Available deployments include: {list(sorted(self.deployment_data.keys()))}."
            )

        contract_type = self.deployment_data[name]["contract_type"]
        if contract_type not in self.contract_factories:
            raise EthPMValidationError(
                f"Contract type: {contract_type} for alias: {name} not found. "
                f"Available contract types include: {list(sorted(self.contract_factories.keys()))}."
            )
Esempio n. 29
0
def validate_blob_uri_contents(contents: bytes, blob_uri: str) -> None:
    """
    Raises an exception if the sha1 hash of the contents does not match the hash found in te
    blob_uri. Formula for how git calculates the hash found here:
    http://alblue.bandlem.com/2011/08/git-tip-of-week-objects.html
    """
    blob_path = parse.urlparse(blob_uri).path
    blob_hash = blob_path.split("/")[-1]
    contents_str = to_text(contents)
    content_length = len(contents_str)
    hashable_contents = "blob " + str(content_length) + "\0" + contents_str
    hash_object = hashlib.sha1(to_bytes(text=hashable_contents))
    if hash_object.hexdigest() != blob_hash:
        raise EthPMValidationError(
            f"Hash of contents fetched from {blob_uri} do not match its hash: {blob_hash}."
        )
Esempio n. 30
0
def validate_linked_references(link_deps: Tuple[Tuple[int, bytes], ...],
                               bytecode: HexBytes) -> None:
    """
    Validates that normalized linked_references (offset, expected_bytes)
    match the corresponding bytecode.
    """
    offsets, values = zip(*link_deps)
    for idx, offset in enumerate(offsets):
        value = values[idx]
        # https://github.com/python/mypy/issues/4975
        offset_value = int(offset)
        dep_length = len(value)
        end_of_bytes = offset_value + dep_length
        # Ignore b/c whitespace around ':' conflict b/w black & flake8
        actual_bytes = bytecode[offset_value:end_of_bytes]  # noqa: E203
        if actual_bytes != values[idx]:
            raise EthPMValidationError("Error validating linked reference. "
                                       f"Offset: {offset} "
                                       f"Value: {values[idx]} "
                                       f"Bytecode: {bytecode} .")