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))
Beispiel #3
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)
Beispiel #8
0
 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]