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_valid_topic_array(topics): return True else: raise ValidationError(invalid_topics_message)
def validate_positive_integer(value): error_message = "Value must be positive integers. Got: {0}".format( value, ) if not is_integer(value) or is_boolean(value): raise ValidationError(error_message) elif value < 0: raise ValidationError(error_message)
def lock_account(self, account): self.validator.validate_inbound_account(account) raw_account = self.normalizer.normalize_inbound_account(account) if raw_account not in self._account_passwords: raise ValidationError("Unknown account") elif self._account_passwords[raw_account] is None: raise ValidationError("Account does not have a password") self._account_unlock[raw_account] = False
def validate_account(value): if not is_text(value): raise ValidationError( "Address must be 20 bytes encoded as hexidecimal") elif not is_hex_address(value): raise ValidationError( "Address must be 20 bytes encoded as hexidecimal") elif is_checksum_formatted_address( value) and not is_checksum_address(value): raise ValidationError("Address does not validate EIP55 checksum")
def validate_block_number(value): error_message = ( "Block number must be a positive integer or one of the strings " "'latest', 'earliest', or 'pending'. Got: {0}".format(value)) if is_string(value): validate_text(value) if value not in BLOCK_NUMBER_META_VALUES: raise ValidationError(error_message) elif not is_integer(value) or is_boolean(value): raise ValidationError(error_message) elif value < 0: raise ValidationError(error_message)
def validate_uint(max_val, value): validate_positive_integer(value) if value > max_val: bitsize = int(math.log2(max_val)) raise ValidationError( "Value exceeds maximum {:d} bit integer size: {}".format( bitsize, value))
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 '{0}' are allowed. Got extra keys: '{1}'".format( "/".join(tuple(sorted(allowed_keys))), "/".join(extra_keys), ))
def validate_any(value, validators): errors = _accumulate_errors(value, validators) if len(errors) == len(validators): item_error_messages = tuple(" - [{0}]: {1}".format(idx, str(err)) for idx, err in errors) error_message = ("Value did not pass any of the provided validators:\n" "{0}".format("\n".join(item_error_messages))) raise ValidationError(error_message)
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 '{0}'. Missing the keys: '{1}'" .format( "/".join(tuple(sorted(required_keys))), "/".join(missing_keys), ))
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, ))
def validate_array(value, validator): validate_is_list_like(value) item_errors = _accumulate_array_errors(value, validator) if item_errors: item_messages = tuple("[{0}]: {1}".format(index, str(err)) for index, err in sorted(item_errors)) error_message = ("The following items failed to validate\n" "- {0}".format("\n - ".join(item_messages))) raise ValidationError(error_message)
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
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("{0}: {1}".format(key, str(err)) for key, err in sorted(key_errors.items())) error_message = ("The following keys failed to validate\n" "- {0}".format("\n - ".join(key_messages))) raise ValidationError(error_message)
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: # no change, return immediately return elif to_timestamp < current_timestamp: raise ValidationError( "Space time continuum distortion detected. Traveling backwards " "in time violates interdimensional ordinance 31415-926.") else: self.backend.time_travel(to_timestamp)
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)
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
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
def validate_is_dict(value): if not is_dict(value): raise ValidationError("Value must be a dictionary. Got: {0}".format( type(value)))
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)
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: " "{0}".format(raw_transaction))
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_TYPE_INFO[txn_type], ))) if unknown_keys: raise ValidationError( "Only the keys '{0}' are allowed. Got extra keys: '{1}'".format( "/".join(tuple(sorted(TRANSACTION_TYPE_INFO[txn_type]))), "/".join(unknown_keys), )) if txn_type == 'send': required_keys = {'from', 'gas'} elif txn_type == 'send_signed': required_keys = {'from', 'gas'} | SIGNED_TRANSACTION_KEYS 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']) elif 'to' in value and value['to'] == '': validate_text(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 'nonce' in value: validate_uint256(value['nonce']) 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) if txn_type == 'send_signed': validate_uint256(value['r']) validate_uint256(value['s']) validate_uint8(value['v'])
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")
def validate_text(value): if not is_text(value): raise ValidationError( "Value must be a text string. Got type: {0}".format(type(value)))
def validate_32_byte_string(value): validate_bytes(value) if len(value) != 32: raise ValidationError( "Must be of length 32. Got: {0} of length {1}".format(value, len(value)) )
def validate_nonce(value): validate_bytes(value) if len(value) != 8: raise ValidationError( "Must be of length 8. Got: {0} of lenght {1}".format(value, len(value)) )
def validate_log_entry_type(value): if value not in {"pending", "mined"}: raise ValidationError("Log entry type must be one of 'pending' or 'mined'")
def validate_canonical_address(value): validate_bytes(value) if not is_canonical_address(value): raise ValidationError("Value must be a 20 byte string")
def validate_logs_bloom(value): validate_positive_integer(value) if value > UINT2048_MAX: raise ValidationError("Value exceeds 2048 bit integer size: {0}".format(value))
def validate_signature_v(value): validate_positive_integer(value) if value not in [0, 1, 27, 28] and value not in range(35, UINT256_MAX + 1): raise ValidationError("The `v` portion of the signature must be 0, 1, 27, 28 or >= 35")
def validate_is_list_like(value): if not is_list_like(value): raise ValidationError( "Value must be a sequence type. Got: {0}".format(type(value)))