def get_name_puzzle_conditions_rust(generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, safe_mode: bool) -> NPCResult: block_program, block_program_args = setup_generator_args(generator) max_cost -= len(bytes(generator.program)) * cost_per_byte if max_cost < 0: return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) flags = STRICT_MODE if safe_mode else 0 err, result, clvm_cost = GENERATOR_MOD.run_as_generator( max_cost, flags, block_program, block_program_args) if err is not None: return NPCResult(uint16(err), [], uint64(0)) else: npc_list = [] for r in result: conditions = [] for c in r.conditions: cwa = [] for cond_list in c[1]: cwa.append( ConditionWithArgs( ConditionOpcode(bytes([cond_list.opcode])), cond_list.vars)) conditions.append((ConditionOpcode(bytes([c[0]])), cwa)) npc_list.append(NPC(r.coin_name, r.puzzle_hash, conditions)) return NPCResult(None, npc_list, uint64(clvm_cost))
def get_name_puzzle_conditions(generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, safe_mode: bool, rust_checker: bool) -> NPCResult: """ This executes the generator program and returns the coins and their conditions. If the cost of the program (size, CLVM execution and conditions) exceed max_cost, the function fails. In order to accurately take the size of the program into account when calculating cost, cost_per_byte must be specified. safe_mode determines whether the clvm program and conditions are executed in strict mode or not. When in safe/strict mode, unknow operations or conditions are considered failures. This is the mode when accepting transactions into the mempool. """ try: if rust_checker: return get_name_puzzle_conditions_rust(generator, max_cost, cost_per_byte=cost_per_byte, safe_mode=safe_mode) else: return get_name_puzzle_conditions_python( generator, max_cost, cost_per_byte=cost_per_byte, safe_mode=safe_mode) except ValidationError as e: return NPCResult(uint16(e.code.value), [], uint64(0)) except Exception as e: log.debug(f"get_name_puzzle_condition failed: {e}") return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), [], uint64(0))
def get_name_puzzle_conditions( generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, mempool_mode: bool ) -> NPCResult: block_program, block_program_args = setup_generator_args(generator) max_cost -= len(bytes(generator.program)) * cost_per_byte if max_cost < 0: return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) flags = MEMPOOL_MODE if mempool_mode else 0 try: err, result, clvm_cost = GENERATOR_MOD.run_as_generator(max_cost, flags, block_program, block_program_args) if err is not None: return NPCResult(uint16(err), [], uint64(0)) else: npc_list = [] for r in result: conditions = [] for c in r.conditions: cwa = [] for cond_list in c[1]: cwa.append(ConditionWithArgs(ConditionOpcode(bytes([cond_list.opcode])), cond_list.vars)) conditions.append((ConditionOpcode(bytes([c[0]])), cwa)) npc_list.append(NPC(r.coin_name, r.puzzle_hash, conditions)) return NPCResult(None, npc_list, uint64(clvm_cost)) except BaseException as e: log.debug(f"get_name_puzzle_condition failed: {e}") return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), [], uint64(0))
def _run_generator( constants_dict: bytes, unfinished_block_bytes: bytes, block_generator_bytes: bytes, height: uint32, ) -> Optional[bytes]: """ Runs the CLVM generator from bytes inputs. This is meant to be called under a ProcessPoolExecutor, in order to validate the heavy parts of a block (clvm program) in a different process. """ try: constants: ConsensusConstants = dataclass_from_dict( ConsensusConstants, constants_dict) unfinished_block: UnfinishedBlock = UnfinishedBlock.from_bytes( unfinished_block_bytes) assert unfinished_block.transactions_info is not None block_generator: BlockGenerator = BlockGenerator.from_bytes( block_generator_bytes) assert block_generator.program == unfinished_block.transactions_generator npc_result: NPCResult = get_name_puzzle_conditions( block_generator, min(constants.MAX_BLOCK_COST_CLVM, unfinished_block.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, mempool_mode=False, height=height, ) return bytes(npc_result) except ValidationError as e: return bytes(NPCResult(uint16(e.code.value), [], uint64(0))) except Exception: return bytes(NPCResult(uint16(Err.UNKNOWN.value), [], uint64(0)))
async def pre_validate_spendbundle(self, new_spend: SpendBundle, new_spend_bytes: Optional[bytes], spend_name: bytes32) -> NPCResult: """ Errors are included within the cached_result. This runs in another process so we don't block the main thread """ start_time = time.time() if new_spend_bytes is None: new_spend_bytes = bytes(new_spend) err, cached_result_bytes, new_cache_entries = await asyncio.get_running_loop( ).run_in_executor( self.pool, validate_clvm_and_signature, new_spend_bytes, int(self.limit_factor * self.constants.MAX_BLOCK_COST_CLVM), self.constants.COST_PER_BYTE, self.constants.AGG_SIG_ME_ADDITIONAL_DATA, ) if err is not None: raise ValidationError(err) for cache_entry_key, cached_entry_value in new_cache_entries.items(): LOCAL_CACHE.put(cache_entry_key, GTElement.from_bytes(cached_entry_value)) ret = NPCResult.from_bytes(cached_result_bytes) end_time = time.time() log.debug( f"pre_validate_spendbundle took {end_time - start_time:0.4f} seconds for {spend_name}" ) return ret
def get_name_puzzle_conditions(generator: BlockGenerator, max_cost: int, safe_mode: bool) -> NPCResult: try: block_program, block_program_args = setup_generator_args(generator) if safe_mode: cost, result = GENERATOR_MOD.run_safe_with_cost( max_cost, block_program, block_program_args) else: cost, result = GENERATOR_MOD.run_with_cost(max_cost, block_program, block_program_args) npc_list: List[NPC] = [] opcodes: Set[bytes] = set(item.value for item in ConditionOpcode) for res in result.as_iter(): conditions_list: List[ConditionWithArgs] = [] spent_coin_parent_id: bytes32 = res.first().first().as_atom() spent_coin_puzzle_hash: bytes32 = res.first().rest().first( ).as_atom() spent_coin_amount: uint64 = uint64( res.first().rest().rest().first().as_int()) spent_coin: Coin = Coin(spent_coin_parent_id, spent_coin_puzzle_hash, spent_coin_amount) for cond in res.rest().first().as_iter(): if cond.first().as_atom() in opcodes: opcode: ConditionOpcode = ConditionOpcode( cond.first().as_atom()) elif not safe_mode: opcode = ConditionOpcode.UNKNOWN else: return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), [], uint64(0)) cvl = ConditionWithArgs(opcode, cond.rest().as_atom_list()) conditions_list.append(cvl) conditions_dict = conditions_by_opcode(conditions_list) if conditions_dict is None: conditions_dict = {} npc_list.append( NPC(spent_coin.name(), spent_coin.puzzle_hash, [(a, b) for a, b in conditions_dict.items()])) return NPCResult(None, npc_list, uint64(cost)) except Exception: return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), [], uint64(0))
async def pre_validate_spendbundle(self, new_spend: SpendBundle) -> NPCResult: """ Errors are included within the cached_result. This runs in another process so we don't block the main thread """ start_time = time.time() cached_result_bytes = await asyncio.get_running_loop().run_in_executor( self.pool, get_npc_multiprocess, bytes(new_spend), self.constants.MAX_BLOCK_COST_CLVM ) end_time = time.time() log.info(f"It took {end_time - start_time} to pre validate transaction") return NPCResult.from_bytes(cached_result_bytes)
async def run_generator(self, unfinished_block: bytes, generator: BlockGenerator, height: uint32) -> NPCResult: task = asyncio.get_running_loop().run_in_executor( self.pool, _run_generator, self.constants_json, unfinished_block, bytes(generator), height, ) npc_result_bytes = await task if npc_result_bytes is None: raise ConsensusError(Err.UNKNOWN) ret = NPCResult.from_bytes(npc_result_bytes) if ret.error is not None: raise ConsensusError(ret.error) return ret
def batch_pre_validate_blocks( constants_dict: Dict, blocks_pickled: Dict[bytes, bytes], full_blocks_pickled: Optional[List[bytes]], header_blocks_pickled: Optional[List[bytes]], prev_transaction_generators: List[Optional[bytes]], npc_results: Dict[uint32, bytes], check_filter: bool, expected_difficulty: List[uint64], expected_sub_slot_iters: List[uint64], ) -> List[bytes]: blocks = {} for k, v in blocks_pickled.items(): blocks[k] = BlockRecord.from_bytes(v) results: List[PreValidationResult] = [] constants: ConsensusConstants = dataclass_from_dict( ConsensusConstants, constants_dict) if full_blocks_pickled is not None and header_blocks_pickled is not None: assert ValueError("Only one should be passed here") if full_blocks_pickled is not None: for i in range(len(full_blocks_pickled)): try: block: FullBlock = FullBlock.from_bytes(full_blocks_pickled[i]) tx_additions: List[Coin] = [] removals: List[bytes32] = [] npc_result: Optional[NPCResult] = None if block.height in npc_results: npc_result = NPCResult.from_bytes( npc_results[block.height]) assert npc_result is not None if npc_result.npc_list is not None: removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) else: removals, tx_additions = [], [] if block.transactions_generator is not None and npc_result is None: prev_generator_bytes = prev_transaction_generators[i] assert prev_generator_bytes is not None assert block.transactions_info is not None block_generator: BlockGenerator = BlockGenerator.from_bytes( prev_generator_bytes) assert block_generator.program == block.transactions_generator npc_result = get_name_puzzle_conditions( block_generator, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), True) removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) header_block = get_block_header(block, tx_additions, removals) required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int: Optional[uint16] = None if error is not None: error_int = uint16(error.code.value) results.append( PreValidationResult(error_int, required_iters, npc_result)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None)) elif header_blocks_pickled is not None: for i in range(len(header_blocks_pickled)): try: header_block = HeaderBlock.from_bytes(header_blocks_pickled[i]) required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int = None if error is not None: error_int = uint16(error.code.value) results.append( PreValidationResult(error_int, required_iters, None)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None)) return [bytes(r) for r in results]
def get_name_puzzle_conditions_python(generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, safe_mode: bool) -> NPCResult: """ This executes the generator program and returns the coins and their conditions. If the cost of the program (size, CLVM execution and conditions) exceed max_cost, the function fails. In order to accurately take the size of the program into account when calculating cost, cost_per_byte must be specified. safe_mode determines whether the clvm program and conditions are executed in strict mode or not. When in safe/strict mode, unknow operations or conditions are considered failures. This is the mode when accepting transactions into the mempool. """ block_program, block_program_args = setup_generator_args(generator) max_cost -= len(bytes(generator.program)) * cost_per_byte if max_cost < 0: return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) if safe_mode: clvm_cost, result = GENERATOR_MOD.run_safe_with_cost( max_cost, block_program, block_program_args) else: clvm_cost, result = GENERATOR_MOD.run_with_cost( max_cost, block_program, block_program_args) max_cost -= clvm_cost if max_cost < 0: return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) npc_list: List[NPC] = [] for res in result.first().as_iter(): conditions_list: List[ConditionWithArgs] = [] if len(res.first().atom) != 32: raise ValidationError(Err.INVALID_CONDITION) spent_coin_parent_id: bytes32 = res.first().as_atom() res = res.rest() if len(res.first().atom) != 32: raise ValidationError(Err.INVALID_CONDITION) spent_coin_puzzle_hash: bytes32 = res.first().as_atom() res = res.rest() spent_coin_amount: uint64 = uint64(sanitize_int( res.first(), safe_mode)) res = res.rest() spent_coin: Coin = Coin(spent_coin_parent_id, spent_coin_puzzle_hash, spent_coin_amount) for cond in res.first().as_iter(): cost, cvl = parse_condition(cond, safe_mode) max_cost -= cost if max_cost < 0: return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) if cvl is not None: conditions_list.append(cvl) conditions_dict = conditions_by_opcode(conditions_list) if conditions_dict is None: conditions_dict = {} npc_list.append( NPC(spent_coin.name(), spent_coin.puzzle_hash, [(a, b) for a, b in conditions_dict.items()])) return NPCResult(None, npc_list, uint64(clvm_cost))
def get_name_puzzle_conditions(generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, mempool_mode: bool, height: Optional[uint32] = None) -> NPCResult: block_program, block_program_args = setup_generator_args(generator) size_cost = len(bytes(generator.program)) * cost_per_byte max_cost -= size_cost if max_cost < 0: return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) # in mempool mode, the height doesn't matter, because it's always strict. # But otherwise, height must be specified to know which rules to apply assert mempool_mode or height is not None # mempool mode also has these rules apply assert (MEMPOOL_MODE & COND_CANON_INTS) != 0 assert (MEMPOOL_MODE & NO_NEG_DIV) != 0 if mempool_mode: flags = MEMPOOL_MODE elif unwrap(height) >= DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT: # conditions must use integers in canonical encoding (i.e. no redundant # leading zeros) # the division operator may not be used with negative operands flags = COND_CANON_INTS | NO_NEG_DIV else: flags = 0 try: err, result = GENERATOR_MOD.run_as_generator(max_cost, flags, block_program, block_program_args) if err is not None: assert err != 0 return NPCResult(uint16(err), [], uint64(0)) first = True npc_list = [] for r in result.spends: conditions: Dict[ConditionOpcode, List[ConditionWithArgs]] = {} if r.height_relative is not None: add_int_cond(conditions, ConditionOpcode.ASSERT_HEIGHT_RELATIVE, r.height_relative) if r.seconds_relative > 0: add_int_cond(conditions, ConditionOpcode.ASSERT_SECONDS_RELATIVE, r.seconds_relative) for cc in r.create_coin: if cc[2] == b"": add_cond(conditions, ConditionOpcode.CREATE_COIN, [cc[0], int_to_bytes(cc[1])]) else: add_cond(conditions, ConditionOpcode.CREATE_COIN, [cc[0], int_to_bytes(cc[1]), cc[2]]) for sig in r.agg_sig_me: add_cond(conditions, ConditionOpcode.AGG_SIG_ME, [sig[0], sig[1]]) # all conditions that aren't tied to a specific spent coin, we roll into the first one if first: first = False if result.reserve_fee > 0: add_int_cond(conditions, ConditionOpcode.RESERVE_FEE, result.reserve_fee) if result.height_absolute > 0: add_int_cond(conditions, ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE, result.height_absolute) if result.seconds_absolute > 0: add_int_cond(conditions, ConditionOpcode.ASSERT_SECONDS_ABSOLUTE, result.seconds_absolute) for sig in result.agg_sig_unsafe: add_cond(conditions, ConditionOpcode.AGG_SIG_UNSAFE, [sig[0], sig[1]]) npc_list.append( NPC(r.coin_id, r.puzzle_hash, [(op, cond) for op, cond in conditions.items()])) return NPCResult(None, npc_list, uint64(result.cost + size_cost)) except BaseException as e: log.debug(f"get_name_puzzle_condition failed: {e}") return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), [], uint64(0))
def batch_pre_validate_blocks( constants_dict: Dict, blocks_pickled: Dict[bytes, bytes], full_blocks_pickled: Optional[List[bytes]], header_blocks_pickled: Optional[List[bytes]], prev_transaction_generators: List[Optional[bytes]], npc_results: Dict[uint32, bytes], check_filter: bool, expected_difficulty: List[uint64], expected_sub_slot_iters: List[uint64], validate_signatures: bool, ) -> List[bytes]: blocks: Dict[bytes, BlockRecord] = {} for k, v in blocks_pickled.items(): blocks[k] = BlockRecord.from_bytes(v) results: List[PreValidationResult] = [] constants: ConsensusConstants = dataclass_from_dict( ConsensusConstants, constants_dict) if full_blocks_pickled is not None and header_blocks_pickled is not None: assert ValueError("Only one should be passed here") # In this case, we are validating full blocks, not headers if full_blocks_pickled is not None: for i in range(len(full_blocks_pickled)): try: block: FullBlock = FullBlock.from_bytes(full_blocks_pickled[i]) tx_additions: List[Coin] = [] removals: List[bytes32] = [] npc_result: Optional[NPCResult] = None if block.height in npc_results: npc_result = NPCResult.from_bytes( npc_results[block.height]) assert npc_result is not None if npc_result.npc_list is not None: removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) else: removals, tx_additions = [], [] if block.transactions_generator is not None and npc_result is None: prev_generator_bytes = prev_transaction_generators[i] assert prev_generator_bytes is not None assert block.transactions_info is not None block_generator: BlockGenerator = BlockGenerator.from_bytes( prev_generator_bytes) assert block_generator.program == block.transactions_generator npc_result = get_name_puzzle_conditions( block_generator, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, mempool_mode=False, height=block.height, ) removals, tx_additions = tx_removals_and_additions( npc_result.npc_list) if npc_result is not None and npc_result.error is not None: results.append( PreValidationResult(uint16(npc_result.error), None, npc_result, False)) continue header_block = get_block_header(block, tx_additions, removals) # TODO: address hint error and remove ignore # error: Argument 1 to "BlockCache" has incompatible type "Dict[bytes, BlockRecord]"; expected # "Dict[bytes32, BlockRecord]" [arg-type] required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), # type: ignore[arg-type] header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int: Optional[uint16] = None if error is not None: error_int = uint16(error.code.value) successfully_validated_signatures = False # If we failed CLVM, no need to validate signature, the block is already invalid if error_int is None: # If this is False, it means either we don't have a signature (not a tx block) or we have an invalid # signature (which also puts in an error) or we didn't validate the signature because we want to # validate it later. receive_block will attempt to validate the signature later. if validate_signatures: if npc_result is not None and block.transactions_info is not None: pairs_pks, pairs_msgs = pkm_pairs( npc_result.npc_list, constants.AGG_SIG_ME_ADDITIONAL_DATA) pks_objects: List[G1Element] = [ G1Element.from_bytes(pk) for pk in pairs_pks ] if not AugSchemeMPL.aggregate_verify( pks_objects, pairs_msgs, block. transactions_info.aggregated_signature): error_int = uint16( Err.BAD_AGGREGATE_SIGNATURE.value) else: successfully_validated_signatures = True results.append( PreValidationResult(error_int, required_iters, npc_result, successfully_validated_signatures)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None, False)) # In this case, we are validating header blocks elif header_blocks_pickled is not None: for i in range(len(header_blocks_pickled)): try: header_block = HeaderBlock.from_bytes(header_blocks_pickled[i]) # TODO: address hint error and remove ignore # error: Argument 1 to "BlockCache" has incompatible type "Dict[bytes, BlockRecord]"; expected # "Dict[bytes32, BlockRecord]" [arg-type] required_iters, error = validate_finished_header_block( constants, BlockCache(blocks), # type: ignore[arg-type] header_block, check_filter, expected_difficulty[i], expected_sub_slot_iters[i], ) error_int = None if error is not None: error_int = uint16(error.code.value) results.append( PreValidationResult(error_int, required_iters, None, False)) except Exception: error_stack = traceback.format_exc() log.error(f"Exception: {error_stack}") results.append( PreValidationResult(uint16(Err.UNKNOWN.value), None, None, False)) return [bytes(r) for r in results]