Пример #1
0
def validate_no_extra_keys(value, allowed_keys):
    extra_keys = tuple(sorted(set(value.keys()).difference(allowed_keys)))
    if extra_keys:
        raise ValidationError(
            "Only the keys '{}' are allowed.  Got extra keys: '{}'".format(
                "/".join(tuple(sorted(allowed_keys))),
                "/".join(extra_keys),
            )
        )
Пример #2
0
 def time_travel(self, to_timestamp):
     self.validator.validate_inbound_timestamp(to_timestamp)
     # make sure we are not traveling back in time as this is not possible.
     current_timestamp = self.get_block_by_number('pending')['timestamp']
     if to_timestamp <= current_timestamp:
         raise ValidationError(
             "Space time continuum distortion detected.  Traveling backwards "
             "in time violates interdimensional ordinance 31415-926.")
     self.backend.time_travel(to_timestamp)
Пример #3
0
def validate_timestamp(value):
    validate_positive_integer(value)

    if value >= MAX_TIMESTAMP:
        raise ValidationError(
            "Timestamp values must be less than {0}.  Got {1}".format(
                MAX_TIMESTAMP,
                value,
            ))
Пример #4
0
def validate_has_required_keys(value, required_keys):
    missing_keys = tuple(sorted(set(required_keys).difference(value.keys())))
    if missing_keys:
        raise ValidationError(
            "Blocks must contain all of the keys '{}'.  Missing the keys: '{}'"
            .format(
                "/".join(tuple(sorted(required_keys))),
                "/".join(missing_keys),
            ))
Пример #5
0
 def _get_normalized_and_signed_evm_transaction(self, transaction, block_number='latest'):
     if transaction['from'] not in self._key_lookup:
         raise ValidationError(
             'No valid "from" key was provided in the transaction '
             'which is required for transaction signing.'
         )
     signing_key = self._key_lookup[transaction['from']]
     normalized_transaction = self._normalize_transaction(transaction, block_number)
     evm_transaction = self.chain.create_unsigned_transaction(**normalized_transaction)
     return evm_transaction.as_signed_transaction(signing_key)
Пример #6
0
def validate_array(value, validator):
    validate_is_list_like(value)

    item_errors = _accumulate_array_errors(value, validator)
    if item_errors:
        item_messages = tuple("[{}]: {}".format(index, str(err))
                              for index, err in sorted(item_errors))
        error_message = ("The following items failed to validate\n"
                         "- {}".format("\n - ".join(item_messages)))
        raise ValidationError(error_message)
Пример #7
0
def _validate_outbound_access_list(access_list):
    if not is_list_like(access_list):
        raise ValidationError('access_list is not list-like.')
    for entry in access_list:
        if not is_list_like(entry) and len(entry) != 2:
            raise ValidationError(
                f'access_list entry not properly formatted: {entry}')
        address = entry[0]
        storage_keys = entry[1]
        if not (is_bytes(address) and len(address) == 20):
            raise ValidationError(
                f'access_list address not properly formatted: {address}')
        if not is_list_like(storage_keys):
            raise ValidationError(
                f'access_list storage keys are not list-like: {storage_keys}')
        if len(storage_keys) > 0 and (not all(
                is_integer(k) for k in storage_keys)):
            raise ValidationError(
                f'one or more access list storage keys not formatted properly: {storage_keys}'
            )
Пример #8
0
    def unlock_account(self, account, password, unlock_seconds=None):
        self.validator.validate_inbound_account(account)
        raw_account = self.normalizer.normalize_inbound_account(account)
        try:
            account_password = self._account_passwords[raw_account]
        except KeyError:
            raise ValidationError("Unknown account")

        if account_password is None:
            raise ValidationError("Account does not have a password")

        if account_password != password:
            raise ValidationError("Wrong password")

        if unlock_seconds is None:
            unlock_until = None
        else:
            unlock_until = time.time() + unlock_seconds

        self._account_unlock[raw_account] = unlock_until
Пример #9
0
def validate_dict(value, key_validators):
    validate_is_dict(value)
    validate_no_extra_keys(value, key_validators.keys())
    validate_has_required_keys(value, key_validators.keys())

    key_errors = _accumulate_dict_errors(value, key_validators)
    if key_errors:
        key_messages = tuple("{}: {}".format(key, str(err))
                             for key, err in sorted(key_errors.items()))
        error_message = ("The following keys failed to validate\n"
                         "- {}".format("\n - ".join(key_messages)))
        raise ValidationError(error_message)
Пример #10
0
def validate_transaction(transaction):
    # TODO: refactor this so that it's not gross.
    if 'from' not in transaction:
        raise ValidationError("Transactions must specify a 'from' address")
    elif not is_address(transaction['from']):
        raise ValidationError(
            "transaction[from]: Unrecognized address format: {0}".format(
                transaction['from'], ))
    elif 'to' in transaction and not is_address(transaction['to']):
        raise ValidationError(
            "transaction[to]: Unrecognized address format: {0}".format(
                transaction['to'], ))

    extra_keys = set(transaction.keys()).difference(TRANSACTION_KEYS)
    if extra_keys:
        raise ValidationError(
            "Transactions may only include the keys {0}.  The following extra "
            "keys were found: {1}".format(
                ",".join(sorted(TRANSACTION_KEYS)),
                ",".join(sorted(extra_keys)),
            ))
Пример #11
0
def serialize_transaction(block, transaction, transaction_index, is_pending):
    txn_type = _extract_transaction_type(transaction)

    common_transaction_params = {
        "type": txn_type,
        "hash": transaction.hash,
        "nonce": transaction.nonce,
        "block_hash": None if is_pending else block.hash,
        "block_number": None if is_pending else block.number,
        "transaction_index": None if is_pending else transaction_index,
        "from": transaction.sender,
        "to": transaction.to,
        "value": transaction.value,
        "gas": transaction.gas,
        "data": transaction.data,
        "r": transaction.r,
        "s": transaction.s,
        "v": transaction.v if _field_in_transaction(transaction, 'v') else transaction.y_parity,
    }
    if _field_in_transaction(transaction, 'gas_price'):
        type_specific_params = {'gas_price': transaction.gas_price}

        if _field_in_transaction(transaction, 'access_list'):
            # access list transaction
            type_specific_params = merge(
                type_specific_params,
                {
                    'chain_id': transaction.chain_id,
                    'access_list': transaction.access_list or (),
                }
            )
    elif any(_field_in_transaction(transaction, _) for _ in (
        'max_fee_per_gas' and 'max_priority_fee_per_gas'
    )):
        # dynamic fee transaction
        type_specific_params = {
            'chain_id': transaction.chain_id,
            'max_fee_per_gas': transaction.max_fee_per_gas,
            'max_priority_fee_per_gas': transaction.max_priority_fee_per_gas,
            'access_list': transaction.access_list or (),

            # TODO: Sometime in 2022 the inclusion of gas_price may be removed from dynamic fee
            #  transactions and we can get rid of this behavior.
            #  https://github.com/ethereum/execution-specs/pull/251
            'gas_price': (
                transaction.max_fee_per_gas if is_pending
                else _calculate_effective_gas_price(transaction, block, txn_type)
            ),
        }
    else:
        raise ValidationError('Invariant: code path should be unreachable')

    return merge(common_transaction_params, type_specific_params)
Пример #12
0
def _validate_inbound_access_list(access_list):
    """
    Validates the structure of an inbound access list. This is similar to the JSON-RPC structure
    for an access list only with `under_score` keys rather than `camelCase`.

    >>> _access_list = (
    ...     {
    ...         'address': '0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae',
    ...         'storage_keys': (
    ...             '0x0000000000000000000000000000000000000000000000000000000000000003',
    ...             '0x0000000000000000000000000000000000000000000000000000000000000007',
    ...         )
    ...     },
    ...     {
    ...         'address': '0xbb9bc244d798123fde783fcc1c72d3bb8c189413',
    ...         'storage_keys': ()
    ...     },
    ... )
    """
    if not is_list_like(access_list):
        raise ValidationError('access_list is not list-like')
    for entry in access_list:
        if not is_dict(entry) and len(entry) != 2:
            raise ValidationError(
                f'access_list entry not properly formatted: {entry}')
        address = entry.get('address')
        storage_keys = entry.get('storage_keys')
        if not is_hex_address(address):
            raise ValidationError(
                f'access_list address must be a hexadecimal address: {address}'
            )
        if not is_list_like(storage_keys):
            raise ValidationError(
                f'access_list storage keys are not list-like: {storage_keys}')
        if len(storage_keys) > 0 and not all(
                is_32byte_hex_string(k) for k in storage_keys):
            raise ValidationError(
                f'one or more access list storage keys not formatted properly: {storage_keys}'
            )
Пример #13
0
    def add_account(self, private_key, password=None):
        # TODO: validation
        self.validator.validate_inbound_private_key(private_key)
        raw_private_key = self.normalizer.normalize_inbound_private_key(private_key)
        raw_account = private_key_to_address(raw_private_key)
        account = self.normalizer.normalize_outbound_account(raw_account)
        if any((is_same_address(account, value) for value in self.get_accounts())):
            raise ValidationError("Account already present in account list")

        self.backend.add_account(raw_private_key)
        self._account_passwords[raw_account] = password
        # TODO: outbound normalization
        return account
Пример #14
0
    def get_transaction_receipt(self, transaction_hash):
        self.validator.validate_inbound_transaction_hash(transaction_hash)
        raw_transaction_hash = self.normalizer.normalize_inbound_transaction_hash(
            transaction_hash,
        )
        raw_receipt = self.backend.get_transaction_receipt(raw_transaction_hash)
        self.validator.validate_outbound_receipt(raw_receipt)
        receipt = self.normalizer.normalize_outbound_receipt(raw_receipt)

        # Assume backend supports Byzantium
        status = to_int(receipt.pop('state_root'))
        if status > 1:
            raise ValidationError('Invalid status value: only 0 or 1 are valid')
        return assoc(receipt, 'status', status)
Пример #15
0
def validate_transaction(value, txn_type):
    if txn_type not in ALLOWED_TRANSACTION_TYPES:
        raise TypeError("the `txn_type` parameter must be one of send/call/estimate")
    if not is_dict(value):
        raise ValidationError("Transaction must be a dictionary.  Got: {0}".format(type(value)))

    unknown_keys = tuple(sorted(set(value.keys()).difference(TRANSACTION_KEYS)))
    if unknown_keys:
        raise ValidationError(
            "Only the keys '{0}' are allowed.  Got extra keys: '{1}'".format(
                "/".join(tuple(sorted(TRANSACTION_KEYS))),
                "/".join(unknown_keys),
            )
        )

    if txn_type == 'send':
        required_keys = {'from', 'gas'}
    elif txn_type in {'estimate', 'call'}:
        required_keys = set(['from'])
    else:
        raise Exception("Invariant: code path should be unreachable")

    missing_required_keys = tuple(sorted(required_keys.difference(value.keys())))
    if missing_required_keys:
        raise ValidationError(
            "Transaction is missing the required keys: '{0}'".format(
                "/".join(missing_required_keys),
            )
        )

    if 'from' in value:
        validate_account(value['from'])
    if 'to' in value and value['to'] != '':
        validate_account(value['to'])
    if 'gas' in value:
        validate_uint256(value['gas'])
    if 'gas_price' in value:
        validate_uint256(value['gas_price'])
    if 'value' in value:
        validate_uint256(value['value'])
    if 'data' in value:
        bad_data_message = (
            "Transaction data must be a hexidecimal encoded string.  Got: "
            "{0}".format(value['data'])
        )
        if not is_text(value['data']):
            raise ValidationError(bad_data_message)
        elif not remove_0x_prefix(value['data']):
            pass
        elif not is_hex(value['data']):
            raise ValidationError(bad_data_message)
        try:
            decode_hex(value['data'])
        except (binascii.Error, TypeError):
            # TypeError is for python2
            # binascii.Error is for python3
            raise ValidationError(bad_data_message)
Пример #16
0
def validate_any(value, validators):
    errors = _accumulate_errors(value, validators)
    if len(errors) == len(validators):
        item_error_messages = tuple(
            " - [{}]: {}".format(idx, str(err))
            for idx, err
            in errors
        )
        error_message = (
            "Value did not pass any of the provided validators:\n"
            "{}".format(
                "\n".join(item_error_messages)
            )
        )
        raise ValidationError(error_message)
Пример #17
0
def validate_filter_params(from_block, to_block, address, topics):
    # blocks
    if from_block is not None:
        validate_block_number(from_block)
    if to_block is not None:
        validate_block_number(to_block)

    # address
    if address is None:
        pass
    elif is_list_like(address):
        if not address:
            raise ValidationError(
                "Address must be either a single hexidecimal encoded address or "
                "a non-empty list of hexidecimal encoded addresses"
            )
        for sub_address in address:
            validate_account(sub_address)
    elif not is_hex_address(address):
        validate_account(address)

    invalid_topics_message = (
        "Topics must be one of `None`, an array of 32 byte hexidecimal encoded "
        "strings, or an array of arrays of 32 byte hexidecimal strings"
    )
    # topics
    if topics is None:
        pass
    elif not is_list_like(topics):
        raise ValidationError(invalid_topics_message)
    elif _is_flat_topic_array(topics):
        return True
    elif all(_is_flat_topic_array(item) for item in topics):
        return True
    else:
        raise ValidationError(invalid_topics_message)
Пример #18
0
    def mine_blocks(self, num_blocks=1, coinbase=None):
        if coinbase is None:
            raw_coinbase = None
        else:
            self.validator.validate_inbound_account(coinbase)
            raw_coinbase = self.normalizer.normalize_inbound_account(coinbase)

        if not self.auto_mine_transactions:
            self._pop_pending_transactions_to_pending_block()

        raw_block_hashes = self.backend.mine_blocks(num_blocks, raw_coinbase)

        if len(raw_block_hashes) != num_blocks:
            raise ValidationError(
                "Invariant: tried to mine {0} blocks.  Got {1} mined block hashes.".format(
                    num_blocks,
                    len(raw_block_hashes),
                )
            )

        for raw_block_hash in raw_block_hashes:
            self.validator.validate_outbound_block_hash(raw_block_hash)
        block_hashes = [
            self.normalizer.normalize_outbound_block_hash(raw_block_hash)
            for raw_block_hash
            in raw_block_hashes
        ]

        # feed the block hashes to any block filters
        for block_hash in block_hashes:
            block = self.get_block_by_hash(block_hash)

            for _, block_filter in self._block_filters.items():
                raw_block_hash = self.normalizer.normalize_inbound_block_hash(block_hash)
                block_filter.add(raw_block_hash)

            self._process_block_logs(block)

        return block_hashes
Пример #19
0
    def get_transaction_receipt(self, transaction_hash):
        self.validator.validate_inbound_transaction_hash(transaction_hash)
        raw_transaction_hash = self.normalizer.normalize_inbound_transaction_hash(
            transaction_hash,
        )
        raw_receipt = self.backend.get_transaction_receipt(raw_transaction_hash)
        self.validator.validate_outbound_receipt(raw_receipt)
        receipt = self.normalizer.normalize_outbound_receipt(raw_receipt)

        if 'FORK_BYZANTIUM' in self.get_supported_forks():
            byzantium_fork_block = self.get_fork_block('FORK_BYZANTIUM')
            transaction = self.get_transaction_by_hash(transaction_hash)
        else:
            byzantium_fork_block = None
        if((byzantium_fork_block is not None) and
           (transaction['block_number'] is not None) and
           (transaction['block_number'] >= byzantium_fork_block)
           ):
            status = to_int(receipt.pop('state_root'))
            if status > 1:
                raise ValidationError('Invalid status value: only 0 or 1 are valid')
            return assoc(receipt, 'status', status)
        return receipt
Пример #20
0
def validate_is_list_like(value):
    if not is_list_like(value):
        raise ValidationError("Value must be a sequence type.  Got: {}".format(
            type(value)))
Пример #21
0
def validate_is_dict(value):
    if not is_dict(value):
        raise ValidationError("Value must be a dictionary.  Got: {}".format(
            type(value)))
Пример #22
0
def validate_text(value):
    if not is_text(value):
        raise ValidationError(
            "Value must be a text string.  Got type: {}".format(type(value)))
Пример #23
0
def validate_bytes(value):
    if not is_bytes(value):
        raise ValidationError(
            "Value must be a byte string.  Got type: {}".format(type(value)))
Пример #24
0
def validate_uint(max_val, value):
    validate_positive_integer(value)
    if value > max_val:
        bitsize = int(math.log2(max_val))
        raise ValidationError(
            f"Value exceeds maximum {bitsize:d} bit integer size:  {value}")
Пример #25
0
def validate_32_byte_hex_value(value, name):
    error_message = ("{0} must be a hexidecimal encoded 32 byte string.  Got: "
                     "{1}".format(name, value))
    if not is_32byte_hex_string(value):
        raise ValidationError(error_message)
Пример #26
0
def validate_step(step):
    if step < 0:
        raise ValidationError("Step number must be non-negative")
Пример #27
0
def validate_raw_transaction(raw_transaction):
    if not is_text(raw_transaction) or not is_hex(raw_transaction):
        raise ValidationError(
            "Raw Transaction must be a hexidecimal encoded string.  Got: "
            "{}".format(raw_transaction)
        )
Пример #28
0
def validate_private_key(value):
    if not is_text(value) or not is_hex(value) or not len(remove_0x_prefix(value)) == 64:
        raise ValidationError("Private keys must be 32 bytes encoded as hexidecimal")
Пример #29
0
def validate_positive_integer(value):
    error_message = "Value must be positive integers.  Got: {}".format(value, )
    if not is_integer(value) or is_boolean(value):
        raise ValidationError(error_message)
    elif value < 0:
        raise ValidationError(error_message)
Пример #30
0
def patched_validate_signature_v(value):
    validate_positive_integer(value)
    if value not in {0, 1, 27, 28, 37, 38}:
        raise ValidationError(
            "The `v` portion of the signature must be 0, 1, 27, 28, 37 or 38, not %d"
            % value)