def serialize_blobs(blobs: Iterable[bytes]) -> Iterator[bytes]: """Serialize a sequence of blobs and return a collation body.""" for i, blob in enumerate(blobs): if len(blob) == 0: raise ValidationError("Cannot serialize blob {} of length 0".format(i)) if len(blob) > MAX_BLOB_SIZE: raise ValidationError("Cannot serialize blob {} of size {}".format(i, len(blob))) for blob_index in range(0, len(blob), CHUNK_DATA_SIZE): remaining_blob_bytes = len(blob) - blob_index if remaining_blob_bytes <= CHUNK_DATA_SIZE: length_bits = remaining_blob_bytes else: length_bits = 0 flag_bits = 0 # TODO: second parameter? blobs as tuple `(flag, blob)`? indicator_byte = int_to_big_endian(length_bits | (flag_bits << 5)) if len(indicator_byte) != 1: raise Exception("Invariant: indicator is not a single byte") yield indicator_byte yield blob[blob_index:blob_index + CHUNK_DATA_SIZE] # end of range(0, N, k) is given by the largest multiple of k smaller than N, i.e., # (ceil(N / k) - 1) * k where ceil(N / k) == -(-N // k) last_blob_index = (-(-len(blob) // CHUNK_DATA_SIZE) - 1) * CHUNK_DATA_SIZE if last_blob_index != blob_index: raise Exception("Invariant: last blob index calculation failed") chunk_filler = b"\x00" * (CHUNK_DATA_SIZE - (len(blob) - last_blob_index)) yield chunk_filler
def validate_header(cls, header, previous_header, check_seal=True): # ignore mypy warnings, because super's validate_header is defined by mixing w/ other class super().validate_header(header, previous_header, check_seal) # type: ignore # The special extra_data is set on the ten headers starting at the fork extra_data_block_nums = range(cls.dao_fork_block_number, cls.dao_fork_block_number + 10) if header.block_number in extra_data_block_nums: if cls.support_dao_fork and header.extra_data != DAO_FORK_MAINNET_EXTRA_DATA: raise ValidationError( "Block {!r} must have extra data {} not {} when supporting DAO fork" .format( header, encode_hex(DAO_FORK_MAINNET_EXTRA_DATA), encode_hex(header.extra_data), )) elif not cls.support_dao_fork and header.extra_data == DAO_FORK_MAINNET_EXTRA_DATA: raise ValidationError( "Block {!r} must not have extra data {} when declining the DAO fork" .format( header, encode_hex(DAO_FORK_MAINNET_EXTRA_DATA), ))
def __init__(self, state_root: bytes, gas_used: int, logs: Iterable[Log], bloom: int=None) -> None: if bloom is None: bloomables = itertools.chain.from_iterable(log.bloomables for log in logs) bloom = int(BloomFilter.from_iterable(bloomables)) super().__init__( state_root=state_root, gas_used=gas_used, bloom=bloom, logs=logs, ) for log_idx, log in enumerate(self.logs): if log.address not in self.bloom_filter: raise ValidationError( "The address from the log entry at position {0} is not " "present in the provided bloom filter.".format(log_idx) ) for topic_idx, topic in enumerate(log.topics): if int32.serialize(topic) not in self.bloom_filter: raise ValidationError( "The topic at position {0} from the log entry at " "position {1} is not present in the provided bloom " "filter.".format(topic_idx, log_idx) )
def validate_gas_limit(gas_limit, parent_gas_limit): if gas_limit < GAS_LIMIT_MINIMUM: raise ValidationError("Gas limit {0} is below minimum {1}".format( gas_limit, GAS_LIMIT_MINIMUM)) if gas_limit > GAS_LIMIT_MAXIMUM: raise ValidationError("Gas limit {0} is above maximum {1}".format( gas_limit, GAS_LIMIT_MAXIMUM)) diff = gas_limit - parent_gas_limit if diff > (parent_gas_limit // GAS_LIMIT_ADJUSTMENT_FACTOR): raise ValidationError( "Gas limit {0} difference to parent {1} is too big {2}".format( gas_limit, parent_gas_limit, diff))
def validate_gaslimit(self, header: BlockHeader) -> None: """ Validate the gas limit on the given header. """ parent_header = self.get_block_header_by_hash(header.parent_hash) low_bound, high_bound = compute_gas_limit_bounds(parent_header) if header.gas_limit < low_bound: raise ValidationError( "The gas limit on block {0} is too low: {1}. It must be at least {2}".format( encode_hex(header.hash), header.gas_limit, low_bound)) elif header.gas_limit > high_bound: raise ValidationError( "The gas limit on block {0} is too high: {1}. It must be at most {2}".format( encode_hex(header.hash), header.gas_limit, high_bound))
def validate_word(value, title="Value"): if not isinstance(value, bytes): raise ValidationError( "{title} is not a valid word. Must be of bytes type: Got: {0}".format( type(value), title=title, ) ) elif not len(value) == 32: raise ValidationError( "{title} is not a valid word. Must be 32 bytes in length: Got: {0}".format( len(value), title=title, ) )
def validate_header_response(start_number: int, count: int, reverse: bool, headers: List[BlockHeader]) -> None: if len(headers) != count: raise ValidationError("Expected {} headers, got: {}".format( count, headers)) if reverse: headers = list(reversed(headers)) if headers[0].block_number != start_number: raise ValidationError( "Expected {} as first header's number, got: {}".format( start_number, headers[0].block_number)) for i, header in enumerate(headers): if header.block_number != start_number + i: raise ValidationError( "Header numbers are not sequential: {}".format(headers))
def _decode_header_to_dict( cls, encoded_header: bytes) -> Iterator[Tuple[str, Any]]: if len(encoded_header) != cls.smc_encoded_size: raise ValidationError( "Expected encoded header to be of size: {0}. Got size {1} instead.\n- {2}" .format( cls.smc_encoded_size, len(encoded_header), encode_hex(encoded_header), )) start_indices = accumulate(lambda i, field: i + field[2], cls.fields_with_sizes, 0) field_bounds = sliding_window(2, start_indices) for byte_range, field in zip(field_bounds, cls._meta.fields): start_index, end_index = byte_range field_name, field_type = field field_bytes = encoded_header[start_index:end_index] if field_type == rlp.sedes.big_endian_int: # remove the leading zeros, to avoid `not minimal length` error in deserialization formatted_field_bytes = field_bytes.lstrip(b'\x00') elif field_type == address: formatted_field_bytes = field_bytes[-20:] else: formatted_field_bytes = field_bytes yield field_name, field_type.deserialize(formatted_field_bytes)
def calc_merkle_tree(items: Sequence[Hashable]) -> MerkleTree: """Calculate the Merkle tree corresponding to a list of items.""" if len(items) == 0: raise ValidationError("No items given") n_layers = math.log2(len(items)) + 1 if not n_layers.is_integer(): raise ValidationError("Item number is not a power of two") n_layers = int(n_layers) leaves = tuple(keccak(item) for item in items) tree = cast(MerkleTree, tuple(take(n_layers, iterate(_hash_layer, leaves)))[::-1]) if len(tree[0]) != 1: raise Exception("Invariant: There must only be one root") return tree
def _validate_changeset(self, changeset_id: uuid.UUID) -> None: """ Checks to be sure the changeset is known by the journal """ if not self.journal.has_changeset(changeset_id): raise ValidationError("Changeset not found in journal: {0}".format( str(changeset_id)))
def persist_block(self, block: 'BaseBlock') -> None: ''' Persist the given block's header and uncles. Assumes all block transactions have been persisted already. ''' new_canonical_headers = self.persist_header(block.header) for header in new_canonical_headers: if header.hash == block.hash: # Most of the time this is called to persist a block whose parent is the current # head, so we optimize for that and read the tx hashes from the block itself. This # is specially important during a fast sync. tx_hashes = [tx.hash for tx in block.transactions] else: tx_hashes = self.get_block_transaction_hashes(header) for index, transaction_hash in enumerate(tx_hashes): self._add_transaction_to_canonical_chain( transaction_hash, header, index) if block.uncles: uncles_hash = self.persist_uncles(block.uncles) else: uncles_hash = EMPTY_UNCLE_HASH if uncles_hash != block.header.uncles_hash: raise ValidationError( "Block's uncles_hash (%s) does not match actual uncles' hash (%s)", block.header.uncles_hash, uncles_hash)
def import_block(self, block: BaseBlock, perform_validation: bool=True) -> BaseBlock: """ Imports a complete block. """ try: parent_header = self.get_block_header_by_hash(block.header.parent_hash) except HeaderNotFound: raise ValidationError( "Attempt to import block #{}. Cannot import block {} before importing " "its parent block at {}".format( block.number, block.hash, block.header.parent_hash, ) ) base_header_for_import = self.create_header_from_parent(parent_header) imported_block = self.get_vm(base_header_for_import).import_block(block) # Validate the imported block. if perform_validation: ensure_imported_block_unchanged(imported_block, block) self.validate_block(imported_block) self.chaindb.persist_block(imported_block) self.logger.debug( 'IMPORTED_BLOCK: number %s | hash %s', imported_block.number, encode_hex(imported_block.hash), ) return imported_block
def apply_all_transactions(self, transactions, base_header): """ Determine the results of applying all transactions to the base header. This does *not* update the current block or header of the VM. :param transactions: an iterable of all transactions to apply :param base_header: the starting header to apply transactions to :return: the final header, the receipts of each transaction, and the computations """ if base_header.block_number != self.block.number: raise ValidationError( "This VM instance must only work on block #{}, " "but the target header has block #{}".format( self.block.number, base_header.block_number, )) receipts = [] computations = [] previous_header = base_header result_header = base_header for transaction in transactions: result_header, receipt, computation = self.apply_transaction( previous_header, transaction, ) previous_header = result_header receipts.append(receipt) computations.append(computation) return result_header, receipts, computations
def validate_point(x: int, y: int) -> Tuple[bn128.FQ, bn128.FQ, bn128.FQ]: FQ = bn128.FQ if x >= bn128.field_modulus: raise ValidationError("Point x value is greater than field modulus") elif y >= bn128.field_modulus: raise ValidationError("Point y value is greater than field modulus") if (x, y) != (0, 0): p1 = (FQ(x), FQ(y), FQ(1)) if not bn128.is_on_curve(p1, bn128.b): raise ValidationError("Point is not on the curve") else: p1 = (FQ(1), FQ(1), FQ(0)) return p1
def validate_frontier_transaction(account_db, transaction): gas_cost = transaction.gas * transaction.gas_price sender_balance = account_db.get_balance(transaction.sender) if sender_balance < gas_cost: raise ValidationError( "Sender account balance cannot afford txn gas: `{0}`".format(transaction.sender) ) total_cost = transaction.value + gas_cost if sender_balance < total_cost: raise ValidationError("Sender account balance cannot afford txn") if account_db.get_nonce(transaction.sender) != transaction.nonce: raise ValidationError("Invalid transaction nonce")
def validate_transaction_signature(transaction: BaseTransaction) -> None: if is_eip_155_signed_transaction(transaction): v = extract_signature_v(transaction.v) else: v = transaction.v canonical_v = v - 27 vrs = (canonical_v, transaction.r, transaction.s) signature = keys.Signature(vrs=vrs) message = transaction.get_message_for_signing() try: public_key = signature.recover_public_key_from_msg(message) except BadSignature as e: raise ValidationError("Bad Signature: {0}".format(str(e))) if not signature.verify_msg(message, public_key): raise ValidationError("Invalid Signature")
def validate(self) -> None: """ Hook called during instantiation to ensure that all transaction parameters pass validation rules. """ if self.gas < self.intrinsic_gas: raise ValidationError("Insufficient gas") self.check_signature_validity()
def validate_block(self, block): """ Validate the the given block. """ if not isinstance(block, self.get_block_class()): raise ValidationError( "This vm ({0!r}) is not equipped to validate a block of type {1!r}" .format( self, block, )) if block.is_genesis: validate_length_lte(block.header.extra_data, 32, title="BlockHeader.extra_data") else: parent_header = get_parent_header(block.header, self.chaindb) self.validate_header(block.header, parent_header) tx_root_hash, _ = make_trie_root_and_nodes(block.transactions) if tx_root_hash != block.header.transaction_root: raise ValidationError( "Block's transaction_root ({0}) does not match expected value: {1}" .format(block.header.transaction_root, tx_root_hash)) if len(block.uncles) > MAX_UNCLES: raise ValidationError( "Blocks may have a maximum of {0} uncles. Found " "{1}.".format(MAX_UNCLES, len(block.uncles))) if not self.chaindb.exists(block.header.state_root): raise ValidationError("`state_root` was not found in the db.\n" "- state_root: {0}".format( block.header.state_root, )) local_uncle_hash = keccak(rlp.encode(block.uncles)) if local_uncle_hash != block.header.uncles_hash: raise ValidationError( "`uncles_hash` and block `uncles` do not match.\n" " - num_uncles : {0}\n" " - block uncle_hash : {1}\n" " - header uncle_hash: {2}".format( len(block.uncles), local_uncle_hash, block.header.uncle_hash, ))
def validate_stack_item(value): if isinstance(value, bytes) and len(value) <= 32: return elif isinstance(value, int) and 0 <= value <= UINT_256_MAX: return raise ValidationError( "Invalid Stack Item: Must be either a length 32 byte " "string or a 256 bit integer. Got {0}".format(value) )
def validate_frontier_transaction_against_header(_vm, base_header, transaction): if base_header.gas_used + transaction.gas > base_header.gas_limit: raise ValidationError( "Transaction exceeds gas limit: using {}, bringing total to {}, but limit is {}".format( transaction.gas, base_header.gas_used + transaction.gas, base_header.gas_limit, ) )
def validate_chain(self, chain: Tuple[BlockHeader, ...]) -> None: parent = self.chaindb.get_block_header_by_hash(chain[0].parent_hash) for header in chain: if header.parent_hash != parent.hash: raise ValidationError( "Invalid header chain; {} has parent {}, but expected {}".format( header, header.parent_hash, parent.hash)) vm_class = self.get_vm_class_for_block_number(header.block_number) vm_class.validate_header(header, parent) parent = header
def complete_work(self, work_completed: Union[int, float]) -> None: if self._last_start is None: raise ValidationError( "Cannot end the ThroughputTracker without starting it") time_elapsed = time.perf_counter() - self._last_start last_throughput = work_completed / time_elapsed self._throughput = (self._throughput * (1 - self._alpha)) + (last_throughput * self._alpha) self._last_start = None
def __init__(self, default_throughput: float, smoothing_factor: float) -> None: self._last_start: float = None self._throughput = default_throughput if 0 < smoothing_factor < 1: self._alpha = smoothing_factor else: raise ValidationError( "Smoothing factor of ThroughputTracker must be between 0 and 1" )
def check_shard_id( shard: Shard, header_or_collation: Union[CollationHeader, Collation]) -> None: if header_or_collation.shard_id != shard.shard_id: raise ValidationError( "Header or collation belongs to shard {} instead of shard {}". format( header_or_collation.shard_id, shard.shard_id, ))
def validate_header_params_for_configuration(header_params): extra_fields = set(header_params.keys()).difference(ALLOWED_HEADER_FIELDS) if extra_fields: raise ValidationError( "The `configure_header` method may only be used with the fields ({0}). " "The provided fields ({1}) are not supported".format( ", ".join(tuple(sorted(ALLOWED_HEADER_FIELDS))), ", ".join(tuple(sorted(extra_fields))), ) )
def validate_length(value, length, title="Value"): if not len(value) == length: raise ValidationError( "{title} must be of length {0}. Got {1} of length {2}".format( length, value, len(value), title=title, ) )
def validate_lte(value, maximum, title="Value"): if value > maximum: raise ValidationError( "{title} {0} is not less than or equal to {1}".format( value, maximum, title=title, ) ) validate_is_integer(value, title=title)
def validate_gte(value, minimum, title="Value"): if value < minimum: raise ValidationError( "{title} {0} is not greater than or equal to {1}".format( value, minimum, title=title, ) ) validate_is_integer(value)
def validate_length_lte(value, maximum_length, title="Value"): if len(value) > maximum_length: raise ValidationError( "{title} must be of length less than or equal to {0}. " "Got {1} of length {2}".format( maximum_length, value, len(value), title=title, ) )
def validate_uncle(cls, block, uncle, uncle_parent): """ Validate the given uncle in the context of the given block. """ if uncle.block_number >= block.number: raise ValidationError( "Uncle number ({0}) is higher than block number ({1})".format( uncle.block_number, block.number)) if uncle.block_number != uncle_parent.block_number + 1: raise ValidationError( "Uncle number ({0}) is not one above ancestor's number ({1})". format(uncle.block_number, uncle_parent.block_number)) if uncle.timestamp < uncle_parent.timestamp: raise ValidationError( "Uncle timestamp ({0}) is before ancestor's timestamp ({1})". format(uncle.timestamp, uncle_parent.timestamp)) if uncle.gas_used > uncle.gas_limit: raise ValidationError( "Uncle's gas usage ({0}) is above the limit ({1})".format( uncle.gas_used, uncle.gas_limit))