async def test_clvm_max_cost(self):

        block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR.program))
        disassembly = binutils.disassemble(block)
        # this is a valid generator program except the first clvm
        # if-condition, that depends on executing an unknown operator
        # ("0xfe"). In strict mode, this should fail, but in non-strict
        # mode, the unknown operator should be treated as if it returns ().
        # the CLVM program has a cost of 391969
        program = SerializedProgram.from_bytes(
            binutils.assemble(
                f"(i (softfork (q . 10000000)) (q . ()) {disassembly})").
            as_bin())

        # ensure we fail if the program exceeds the cost
        generator = BlockGenerator(program, [])
        npc_result: NPCResult = get_name_puzzle_conditions(
            generator, 10000000, False)

        assert npc_result.error is not None
        assert npc_result.clvm_cost == 0

        # raise the max cost to make sure this passes
        # ensure we pass if the program does not exceeds the cost
        npc_result: NPCResult = get_name_puzzle_conditions(
            generator, 20000000, False)

        assert npc_result.error is None
        assert npc_result.clvm_cost > 10000000
Пример #2
0
 async def test_clvm_strict_mode(self, rust_checker: bool):
     block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR.program))
     disassembly = binutils.disassemble(block)
     # this is a valid generator program except the first clvm
     # if-condition, that depends on executing an unknown operator
     # ("0xfe"). In strict mode, this should fail, but in non-strict
     # mode, the unknown operator should be treated as if it returns ().
     program = SerializedProgram.from_bytes(
         binutils.assemble(
             f"(i (0xfe (q . 0)) (q . ()) {disassembly})").as_bin())
     generator = BlockGenerator(program, [])
     npc_result: NPCResult = get_name_puzzle_conditions(
         generator,
         test_constants.MAX_BLOCK_COST_CLVM,
         cost_per_byte=test_constants.COST_PER_BYTE,
         safe_mode=True,
         rust_checker=rust_checker,
     )
     assert npc_result.error is not None
     npc_result = get_name_puzzle_conditions(
         generator,
         test_constants.MAX_BLOCK_COST_CLVM,
         cost_per_byte=test_constants.COST_PER_BYTE,
         safe_mode=False,
         rust_checker=rust_checker,
     )
     assert npc_result.error is None
Пример #3
0
    async def test_strict_mode(self, rust_checker: bool):
        wallet_tool = bt.get_pool_wallet_tool()
        ph = wallet_tool.get_new_puzzlehash()

        num_blocks = 3
        blocks = bt.get_consecutive_blocks(num_blocks, [],
                                           guarantee_transaction_block=True,
                                           pool_reward_puzzle_hash=ph,
                                           farmer_reward_puzzle_hash=ph)

        coinbase = None
        for coin in blocks[2].get_included_reward_coins():
            if coin.puzzle_hash == ph:
                coinbase = coin
                break
        assert coinbase is not None
        spend_bundle = wallet_tool.generate_signed_transaction(
            coinbase.amount,
            BURN_PUZZLE_HASH,
            coinbase,
        )
        assert spend_bundle is not None

        pk = bytes.fromhex(
            "88bc9360319e7c54ab42e19e974288a2d7a817976f7633f4b43f36ce72074e59c4ab8ddac362202f3e366f0aebbb6280"
        )
        puzzle = p2_delegated_puzzle_or_hidden_puzzle.puzzle_for_pk(pk)
        disassembly = binutils.disassemble(puzzle)
        program = SerializedProgram.from_bytes(
            binutils.assemble(
                f"(q ((0x3d2331635a58c0d49912bc1427d7db51afe3f20a7b4bcaffa17ee250dcbcbfaa {disassembly} 300"
                f"  (() (q . ((65 '00000000000000000000000000000000' 0x0cbba106e000))) ()))))"
            ).as_bin())
        generator = BlockGenerator(program, [])
        npc_result: NPCResult = get_name_puzzle_conditions(
            generator,
            test_constants.MAX_BLOCK_COST_CLVM,
            cost_per_byte=test_constants.COST_PER_BYTE,
            safe_mode=True,
            rust_checker=rust_checker,
        )
        assert npc_result.error is not None
        npc_result = get_name_puzzle_conditions(
            generator,
            test_constants.MAX_BLOCK_COST_CLVM,
            cost_per_byte=test_constants.COST_PER_BYTE,
            safe_mode=False,
            rust_checker=rust_checker,
        )
        assert npc_result.error is None

        coin_name = npc_result.npc_list[0].coin_name
        error, puzzle, solution = get_puzzle_and_solution_for_coin(
            generator, coin_name, test_constants.MAX_BLOCK_COST_CLVM)
        assert error is None
Пример #4
0
 async def test_clvm_strict_mode(self):
     block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR))
     disassembly = binutils.disassemble(block)
     # this is a valid generator program except the first clvm
     # if-condition, that depends on executing an unknown operator
     # ("0xfe"). In strict mode, this should fail, but in non-strict
     # mode, the unknown operator should be treated as if it returns ().
     program = SerializedProgram.from_bytes(
         binutils.assemble(
             f"(i (0xfe (q . 0)) (q . ()) {disassembly})").as_bin())
     error, npc_list, cost = get_name_puzzle_conditions(program, True)
     assert error is not None
     error, npc_list, cost = get_name_puzzle_conditions(program, False)
     assert error is None
Пример #5
0
def run_and_get_removals_and_additions(
        block: FullBlock,
        max_cost: int,
        cost_per_byte: int,
        rust_checker: bool,
        safe_mode=False) -> Tuple[List[bytes32], List[Coin]]:
    removals: List[bytes32] = []
    additions: List[Coin] = []

    assert len(block.transactions_generator_ref_list) == 0
    if not block.is_transaction_block():
        return [], []

    if block.transactions_generator is not None:
        npc_result = get_name_puzzle_conditions(
            BlockGenerator(block.transactions_generator, []),
            max_cost,
            cost_per_byte=cost_per_byte,
            safe_mode=safe_mode,
            rust_checker=rust_checker,
        )
        # build removals list
        for npc in npc_result.npc_list:
            removals.append(npc.coin_name)
        additions.extend(additions_for_npc(npc_result.npc_list))

    rewards = block.get_included_reward_coins()
    additions.extend(rewards)
    return removals, additions
Пример #6
0
    async def get_max_send_amount(self, records=None):
        spendable: List[WalletCoinRecord] = list(
            await self.wallet_state_manager.get_spendable_coins_for_wallet(self.id(), records)
        )
        if len(spendable) == 0:
            return 0
        spendable.sort(reverse=True, key=lambda record: record.coin.amount)
        if self.cost_of_single_tx is None:
            coin = spendable[0].coin
            tx = await self.generate_signed_transaction(
                [coin.amount], [coin.puzzle_hash], coins={coin}, ignore_max_send_amount=True
            )
            program: BlockGenerator = simple_solution_generator(tx.spend_bundle)
            # npc contains names of the coins removed, puzzle_hashes and their spend conditions
            result: NPCResult = get_name_puzzle_conditions(program, True)
            cost_result: uint64 = calculate_cost_of_program(
                program.program, result, self.wallet_state_manager.constants.COST_PER_BYTE
            )
            self.cost_of_single_tx = cost_result
            self.log.info(f"Cost of a single tx for standard wallet: {self.cost_of_single_tx}")

        max_cost = self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM / 2  # avoid full block TXs
        current_cost = 0
        total_amount = 0
        total_coin_count = 0

        for record in spendable:
            current_cost += self.cost_of_single_tx
            total_amount += record.coin.amount
            total_coin_count += 1
            if current_cost + self.cost_of_single_tx > max_cost:
                break

        return total_amount
Пример #7
0
    def test_get_name_puzzle_conditions(self):
        # this tests that extra block or coin data doesn't confuse `get_name_puzzle_conditions`

        gen = block_generator()
        cost, r = run_generator(gen, max_cost=MAX_COST)
        print(r)

        npc_result = get_name_puzzle_conditions(gen,
                                                max_cost=MAX_COST,
                                                cost_per_byte=COST_PER_BYTE,
                                                safe_mode=False)
        assert npc_result.error is None
        assert npc_result.clvm_cost == EXPECTED_COST
        cond_1 = ConditionWithArgs(
            ConditionOpcode.CREATE_COIN,
            [bytes([0] * 31 + [1]), int_to_bytes(500)])
        CONDITIONS = [
            (ConditionOpcode.CREATE_COIN, [cond_1]),
        ]

        npc = NPC(
            coin_name=bytes32.fromhex(
                "e8538c2d14f2a7defae65c5c97f5d4fae7ee64acef7fec9d28ad847a0880fd03"
            ),
            puzzle_hash=bytes32.fromhex(
                "9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2"
            ),
            conditions=CONDITIONS,
        )

        assert npc_result.npc_list == [npc]
Пример #8
0
def calculate_cost_of_program(program: SerializedProgram,
                              clvm_cost_ratio_constant: int,
                              strict_mode: bool = False) -> CostResult:
    """
    This function calculates the total cost of either a block or a spendbundle
    """
    total_clvm_cost = 0
    error, npc_list, cost = get_name_puzzle_conditions(program, strict_mode)
    if error or cost is None or npc_list is None:
        raise Exception("get_name_puzzle_conditions raised error:" +
                        str(error))
    total_clvm_cost += cost

    # Add cost of conditions
    npc: NPC
    total_vbyte_cost = 0
    for npc in npc_list:
        for condition, cvp_list in npc.condition_dict.items():
            if condition is ConditionOpcode.AGG_SIG or condition is ConditionOpcode.AGG_SIG_ME:
                total_vbyte_cost += len(cvp_list) * ConditionCost.AGG_SIG.value
            elif condition is ConditionOpcode.CREATE_COIN:
                total_vbyte_cost += len(
                    cvp_list) * ConditionCost.CREATE_COIN.value
            elif condition is ConditionOpcode.ASSERT_SECONDS_NOW_EXCEEDS:
                total_vbyte_cost += len(
                    cvp_list) * ConditionCost.ASSERT_SECONDS_NOW_EXCEEDS.value
            elif condition is ConditionOpcode.ASSERT_HEIGHT_AGE_EXCEEDS:
                total_vbyte_cost += len(
                    cvp_list) * ConditionCost.ASSERT_HEIGHT_AGE_EXCEEDS.value
            elif condition is ConditionOpcode.ASSERT_HEIGHT_NOW_EXCEEDS:
                total_vbyte_cost += len(
                    cvp_list) * ConditionCost.ASSERT_HEIGHT_NOW_EXCEEDS.value
            elif condition is ConditionOpcode.ASSERT_MY_COIN_ID:
                total_vbyte_cost += len(
                    cvp_list) * ConditionCost.ASSERT_MY_COIN_ID.value
            elif condition is ConditionOpcode.RESERVE_FEE:
                total_vbyte_cost += len(
                    cvp_list) * ConditionCost.RESERVE_FEE.value
            elif condition is ConditionOpcode.CREATE_COIN_ANNOUNCEMENT:
                total_vbyte_cost += len(
                    cvp_list) * ConditionCost.CREATE_COIN_ANNOUNCEMENT.value
            elif condition is ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT:
                total_vbyte_cost += len(
                    cvp_list) * ConditionCost.ASSERT_COIN_ANNOUNCEMENT.value
            elif condition is ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT:
                total_vbyte_cost += len(
                    cvp_list) * ConditionCost.CREATE_PUZZLE_ANNOUNCEMENT.value
            elif condition is ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT:
                total_vbyte_cost += len(
                    cvp_list) * ConditionCost.ASSERT_PUZZLE_ANNOUNCEMENT.value
            else:
                # We ignore unknown conditions in order to allow for future soft forks
                pass

    # Add raw size of the program
    total_vbyte_cost += len(bytes(program))

    total_clvm_cost += total_vbyte_cost * clvm_cost_ratio_constant

    return CostResult(None, npc_list, uint64(total_clvm_cost))
Пример #9
0
 async def get_tx_removals_and_additions(
     self,
     block: FullBlock,
     npc_result: Optional[NPCResult] = None
 ) -> Tuple[List[bytes32], List[Coin]]:
     if block.is_transaction_block():
         if block.transactions_generator is not None:
             if npc_result is None:
                 block_generator: Optional[
                     BlockGenerator] = await self.get_block_generator(block)
                 assert block_generator is not None
                 npc_result = get_name_puzzle_conditions(
                     block_generator,
                     self.constants.MAX_BLOCK_COST_CLVM,
                     cost_per_byte=self.constants.COST_PER_BYTE,
                     safe_mode=False,
                     rust_checker=block.height >
                     self.constants.RUST_CONDITION_CHECKER,
                 )
             tx_removals, tx_additions = tx_removals_and_additions(
                 npc_result.npc_list)
             return tx_removals, tx_additions
         else:
             return [], []
     else:
         return [], []
Пример #10
0
def _run_generator(
    constants_dict: bytes,
    unfinished_block_bytes: bytes,
    block_generator_bytes: bytes,
) -> Tuple[Optional[Err], 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,
        )
        if npc_result.error is not None:
            return Err(npc_result.error), None
    except ValidationError as e:
        return e.code, None
    except Exception:
        return Err.UNKNOWN, None

    return None, bytes(npc_result)
Пример #11
0
def get_npc_multiprocess(spend_bundle_bytes: bytes, max_cost: int,
                         cost_per_byte: int) -> bytes:
    program = simple_solution_generator(
        SpendBundle.from_bytes(spend_bundle_bytes))
    # npc contains names of the coins removed, puzzle_hashes and their spend conditions
    return bytes(
        get_name_puzzle_conditions(program,
                                   max_cost,
                                   cost_per_byte=cost_per_byte,
                                   safe_mode=True,
                                   rust_checker=True))
Пример #12
0
 async def get_removals_and_additions(self, block: FullBlock) -> Tuple[List[bytes32], List[Coin]]:
     if block.is_transaction_block():
         if block.transactions_generator is not None:
             block_generator: Optional[BlockGenerator] = await self.get_block_generator(block)
             assert block_generator is not None
             npc_result = get_name_puzzle_conditions(block_generator, self.constants.MAX_BLOCK_COST_CLVM, False)
             removals, additions = block_removals_and_additions(block, npc_result.npc_list)
             return removals, additions
         else:
             return [], list(block.get_included_reward_coins())
     else:
         return [], []
Пример #13
0
    def additions(self) -> List[Coin]:
        additions: List[Coin] = []

        if self.transactions_generator is not None:
            # This should never throw here, block must be valid if it comes to here
            err, npc_list, cost = get_name_puzzle_conditions(
                self.transactions_generator, False)
            # created coins
            if npc_list is not None:
                additions.extend(additions_for_npc(npc_list))

        additions.extend(self.get_included_reward_coins())

        return additions
Пример #14
0
    async def test_tx_generator_speed(self):
        LARGE_BLOCK_COIN_CONSUMED_COUNT = 687
        generator = large_block_generator(LARGE_BLOCK_COIN_CONSUMED_COUNT)
        program = SerializedProgram.from_bytes(generator)

        start_time = time.time()
        err, npc, cost = get_name_puzzle_conditions(program, False)
        end_time = time.time()
        duration = end_time - start_time
        assert err is None
        assert len(npc) == LARGE_BLOCK_COIN_CONSUMED_COUNT
        log.info(f"Time spent: {duration}")

        assert duration < 3
    async def test_basics(self, rust_checker: bool):
        wallet_tool = bt.get_pool_wallet_tool()
        ph = wallet_tool.get_new_puzzlehash()
        num_blocks = 3
        blocks = bt.get_consecutive_blocks(num_blocks, [],
                                           guarantee_transaction_block=True,
                                           pool_reward_puzzle_hash=ph,
                                           farmer_reward_puzzle_hash=ph)
        coinbase = None
        for coin in blocks[2].get_included_reward_coins():
            if coin.puzzle_hash == ph and coin.amount == 250000000000:
                coinbase = coin
                break
        assert coinbase is not None
        spend_bundle = wallet_tool.generate_signed_transaction(
            coinbase.amount,
            BURN_PUZZLE_HASH,
            coinbase,
        )
        assert spend_bundle is not None
        program: BlockGenerator = simple_solution_generator(spend_bundle)

        npc_result: NPCResult = get_name_puzzle_conditions(
            program,
            test_constants.MAX_BLOCK_COST_CLVM,
            cost_per_byte=test_constants.COST_PER_BYTE,
            safe_mode=False,
            rust_checker=rust_checker,
        )

        cost = calculate_cost_of_program(program.program, npc_result,
                                         test_constants.COST_PER_BYTE)

        assert npc_result.error is None
        assert len(bytes(program.program)) == 433

        coin_name = npc_result.npc_list[0].coin_name
        error, puzzle, solution = get_puzzle_and_solution_for_coin(
            program, coin_name, test_constants.MAX_BLOCK_COST_CLVM)
        assert error is None

        assert npc_result.clvm_cost == 404560

        # Create condition + agg_sig_condition + length + cpu_cost
        assert (cost == ConditionCost.CREATE_COIN.value +
                ConditionCost.AGG_SIG.value +
                len(bytes(program.program)) * test_constants.COST_PER_BYTE +
                404560  # clvm_cost
                )
Пример #16
0
    async def test_tx_generator_speed(self):
        LARGE_BLOCK_COIN_CONSUMED_COUNT = 687
        generator_bytes = large_block_generator(LARGE_BLOCK_COIN_CONSUMED_COUNT)
        program = SerializedProgram.from_bytes(generator_bytes)

        start_time = time.time()
        generator = BlockGenerator(program, [])
        npc_result = get_name_puzzle_conditions(generator, False)
        end_time = time.time()
        duration = end_time - start_time
        assert npc_result.error is None
        assert len(npc_result.npc_list) == LARGE_BLOCK_COIN_CONSUMED_COUNT
        log.info(f"Time spent: {duration}")

        assert duration < 3
Пример #17
0
 async def get_tx_removals_and_additions(
     self, block: FullBlock, npc_result: Optional[NPCResult] = None
 ) -> Tuple[List[bytes32], List[Coin]]:
     if block.is_transaction_block():
         if block.transactions_generator is not None:
             if npc_result is None:
                 block_generator: Optional[BlockGenerator] = await self.get_block_generator(block)
                 assert block_generator is not None
                 npc_result = get_name_puzzle_conditions(block_generator, self.constants.MAX_BLOCK_COST_CLVM, False)
             tx_removals, tx_additions = tx_removals_and_additions(npc_result.npc_list)
             return tx_removals, tx_additions
         else:
             return [], []
     else:
         return [], []
Пример #18
0
    def validate_spend_bundle(
        self,
        spend_bundle: SpendBundle,
        now: CoinTimestamp,
        max_cost: int,
        cost_per_byte: int,
    ) -> int:
        # this should use blockchain consensus code

        program = simple_solution_generator(spend_bundle)
        result: NPCResult = get_name_puzzle_conditions(
            program, max_cost, cost_per_byte=cost_per_byte, mempool_mode=True
        )
        if result.error is not None:
            raise BadSpendBundleError(f"condition validation failure {Err(result.error)}")

        ephemeral_db = dict(self._db)
        for npc in result.npc_list:
            for coin in created_outputs_for_conditions_dict(npc.condition_dict, npc.coin_name):
                name = coin.name()
                ephemeral_db[name] = CoinRecord(
                    coin,
                    uint32(now.height),
                    uint32(0),
                    False,
                    uint64(now.seconds),
                )

        for npc in result.npc_list:
            prev_transaction_block_height = uint32(now.height)
            timestamp = uint64(now.seconds)
            coin_record = ephemeral_db.get(npc.coin_name)
            if coin_record is None:
                raise BadSpendBundleError(f"coin not found for id 0x{npc.coin_name.hex()}")  # noqa
            err = mempool_check_conditions_dict(
                coin_record,
                npc.condition_dict,
                prev_transaction_block_height,
                timestamp,
            )
            if err is not None:
                raise BadSpendBundleError(f"condition validation failure {Err(err)}")

        return 0
Пример #19
0
def validate_clvm_and_signature(
        spend_bundle_bytes: bytes, max_cost: int, cost_per_byte: int,
        additional_data: bytes
) -> Tuple[Optional[Err], bytes, Dict[bytes, bytes]]:
    """
    Validates CLVM and aggregate signature for a spendbundle. This is meant to be called under a ProcessPoolExecutor
    in order to validate the heavy parts of a transction in a different thread. Returns an optional error,
    the NPCResult and a cache of the new pairings validated (if not error)
    """
    try:
        bundle: SpendBundle = SpendBundle.from_bytes(spend_bundle_bytes)
        program = simple_solution_generator(bundle)
        # npc contains names of the coins removed, puzzle_hashes and their spend conditions
        result: NPCResult = get_name_puzzle_conditions(
            program, max_cost, cost_per_byte=cost_per_byte, mempool_mode=True)

        if result.error is not None:
            return Err(result.error), b"", {}

        pks: List[G1Element] = []
        msgs: List[bytes32] = []
        # TODO: address hint error and remove ignore
        #       error: Incompatible types in assignment (expression has type "List[bytes]", variable has type
        #       "List[bytes32]")  [assignment]
        pks, msgs = pkm_pairs(result.npc_list,
                              additional_data)  # type: ignore[assignment]

        # Verify aggregated signature
        cache: LRUCache = LRUCache(10000)
        if not cached_bls.aggregate_verify(
                pks, msgs, bundle.aggregated_signature, True, cache):
            return Err.BAD_AGGREGATE_SIGNATURE, b"", {}
        new_cache_entries: Dict[bytes, bytes] = {}
        for k, v in cache.cache.items():
            new_cache_entries[k] = bytes(v)
    except ValidationError as e:
        return e.code, b"", {}
    except Exception:
        return Err.UNKNOWN, b"", {}

    return None, bytes(result), new_cache_entries
Пример #20
0
def compute_coin_hints(cs: CoinSpend) -> List[bytes]:

    bundle = SpendBundle([cs], G2Element())
    generator = simple_solution_generator(bundle)

    npc_result = get_name_puzzle_conditions(
        generator,
        INFINITE_COST,
        cost_per_byte=DEFAULT_CONSTANTS.COST_PER_BYTE,
        mempool_mode=False,
        height=DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT,
    )
    h_list = []
    for npc in npc_result.npc_list:
        for opcode, conditions in npc.conditions:
            if opcode == ConditionOpcode.CREATE_COIN:
                for condition in conditions:
                    if len(condition.vars) > 2 and condition.vars[2] != b"":
                        h_list.append(condition.vars[2])

    return h_list
Пример #21
0
    def tx_removals_and_additions(self) -> Tuple[List[bytes32], List[Coin]]:
        """
        Doesn't return farmer and pool reward.
        This call assumes that this block has been validated already,
        get_name_puzzle_conditions should not return error here
        """
        removals: List[bytes32] = []
        additions: List[Coin] = []

        if self.transactions_generator is not None:
            # This should never throw here, block must be valid if it comes to here
            err, npc_list, cost = get_name_puzzle_conditions(
                self.transactions_generator, False)
            # build removals list
            if npc_list is None:
                return [], []
            for npc in npc_list:
                removals.append(npc.coin_name)

            additions.extend(additions_for_npc(npc_list))

        return removals, additions
Пример #22
0
    async def test_tx_generator_speed(self, rust_checker: bool):
        LARGE_BLOCK_COIN_CONSUMED_COUNT = 687
        generator_bytes = large_block_generator(
            LARGE_BLOCK_COIN_CONSUMED_COUNT)
        program = SerializedProgram.from_bytes(generator_bytes)

        start_time = time.time()
        generator = BlockGenerator(program, [])
        npc_result = get_name_puzzle_conditions(
            generator,
            test_constants.MAX_BLOCK_COST_CLVM,
            cost_per_byte=test_constants.COST_PER_BYTE,
            safe_mode=False,
            rust_checker=rust_checker,
        )
        end_time = time.time()
        duration = end_time - start_time
        assert npc_result.error is None
        assert len(npc_result.npc_list) == LARGE_BLOCK_COIN_CONSUMED_COUNT
        log.info(f"Time spent: {duration}")

        assert duration < 1
Пример #23
0
    async def test_basics(self):
        wallet_tool = bt.get_pool_wallet_tool()
        ph = wallet_tool.get_new_puzzlehash()
        num_blocks = 3
        blocks = bt.get_consecutive_blocks(num_blocks, [],
                                           guarantee_transaction_block=True,
                                           pool_reward_puzzle_hash=ph,
                                           farmer_reward_puzzle_hash=ph)
        coinbase = None
        for coin in blocks[2].get_included_reward_coins():
            if coin.puzzle_hash == ph:
                coinbase = coin
                break
        assert coinbase is not None
        spend_bundle = wallet_tool.generate_signed_transaction(
            coinbase.amount,
            BURN_PUZZLE_HASH,
            coinbase,
        )
        assert spend_bundle is not None
        program = best_solution_program(spend_bundle)

        result: CostResult = calculate_cost_of_program(
            program, test_constants.COST_PER_BYTE)
        clvm_cost = result.cost

        error, npc_list, cost = get_name_puzzle_conditions(program, False)
        assert error is None
        coin_name = npc_list[0].coin_name
        error, puzzle, solution = get_puzzle_and_solution_for_coin(
            program, coin_name)
        assert error is None

        # Create condition + agg_sig_condition + length + cpu_cost
        assert (clvm_cost == ConditionCost.CREATE_COIN.value +
                ConditionCost.AGG_SIG.value +
                len(bytes(program)) * test_constants.COST_PER_BYTE + cost)
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]
Пример #25
0
    async def test_basic_coin_store(self, rust_checker: bool):
        wallet_a = WALLET_A
        reward_ph = wallet_a.get_new_puzzlehash()

        for cache_size in [0]:
            # Generate some coins
            blocks = bt.get_consecutive_blocks(
                10,
                [],
                farmer_reward_puzzle_hash=reward_ph,
                pool_reward_puzzle_hash=reward_ph,
            )

            coins_to_spend: List[Coin] = []
            for block in blocks:
                if block.is_transaction_block():
                    for coin in block.get_included_reward_coins():
                        if coin.puzzle_hash == reward_ph:
                            coins_to_spend.append(coin)

            spend_bundle = wallet_a.generate_signed_transaction(
                uint64(1000), wallet_a.get_new_puzzlehash(), coins_to_spend[0])

            db_path = Path("fndb_test.db")
            if db_path.exists():
                db_path.unlink()
            connection = await aiosqlite.connect(db_path)
            db_wrapper = DBWrapper(connection)
            coin_store = await CoinStore.create(db_wrapper,
                                                cache_size=uint32(cache_size))

            blocks = bt.get_consecutive_blocks(
                10,
                blocks,
                farmer_reward_puzzle_hash=reward_ph,
                pool_reward_puzzle_hash=reward_ph,
                transaction_data=spend_bundle,
            )

            # Adding blocks to the coin store
            should_be_included_prev: Set[Coin] = set()
            should_be_included: Set[Coin] = set()
            for block in blocks:
                farmer_coin, pool_coin = get_future_reward_coins(block)
                should_be_included.add(farmer_coin)
                should_be_included.add(pool_coin)
                if block.is_transaction_block():
                    if block.transactions_generator is not None:
                        block_gen: BlockGenerator = BlockGenerator(
                            block.transactions_generator, [])
                        npc_result = get_name_puzzle_conditions(
                            block_gen,
                            bt.constants.MAX_BLOCK_COST_CLVM,
                            cost_per_byte=bt.constants.COST_PER_BYTE,
                            safe_mode=False,
                            rust_checker=rust_checker,
                        )
                        tx_removals, tx_additions = tx_removals_and_additions(
                            npc_result.npc_list)
                    else:
                        tx_removals, tx_additions = [], []

                    assert block.get_included_reward_coins(
                    ) == should_be_included_prev

                    await coin_store.new_block(block, tx_additions,
                                               tx_removals)

                    if block.height != 0:
                        with pytest.raises(Exception):
                            await coin_store.new_block(block, tx_additions,
                                                       tx_removals)

                    for expected_coin in should_be_included_prev:
                        # Check that the coinbase rewards are added
                        record = await coin_store.get_coin_record(
                            expected_coin.name())
                        assert record is not None
                        assert not record.spent
                        assert record.coin == expected_coin
                    for coin_name in tx_removals:
                        # Check that the removed coins are set to spent
                        record = await coin_store.get_coin_record(coin_name)
                        assert record.spent
                    for coin in tx_additions:
                        # Check that the added coins are added
                        record = await coin_store.get_coin_record(coin.name())
                        assert not record.spent
                        assert coin == record.coin

                    should_be_included_prev = should_be_included.copy()
                    should_be_included = set()

            await connection.close()
            Path("fndb_test.db").unlink()
Пример #26
0
def create_foliage(
    constants: ConsensusConstants,
    reward_block_unfinished: RewardChainBlockUnfinished,
    block_generator: Optional[BlockGenerator],
    aggregate_sig: G2Element,
    additions: List[Coin],
    removals: List[Coin],
    prev_block: Optional[BlockRecord],
    blocks: BlockchainInterface,
    total_iters_sp: uint128,
    timestamp: uint64,
    farmer_reward_puzzlehash: bytes32,
    pool_target: PoolTarget,
    get_plot_signature: Callable[[bytes32, G1Element], G2Element],
    get_pool_signature: Callable[[PoolTarget, Optional[G1Element]], Optional[G2Element]],
    seed: bytes32 = b"",
) -> Tuple[Foliage, Optional[FoliageTransactionBlock], Optional[TransactionsInfo]]:
    """
    Creates a foliage for a given reward chain block. This may or may not be a tx block. In the case of a tx block,
    the return values are not None. This is called at the signage point, so some of this information may be
    tweaked at the infusion point.

    Args:
        constants: consensus constants being used for this chain
        reward_block_unfinished: the reward block to look at, potentially at the signage point
        block_generator: transactions to add to the foliage block, if created
        aggregate_sig: aggregate of all transctions (or infinity element)
        prev_block: the previous block at the signage point
        blocks: dict from header hash to blocks, of all ancestor blocks
        total_iters_sp: total iters at the signage point
        timestamp: timestamp to put into the foliage block
        farmer_reward_puzzlehash: where to pay out farming reward
        pool_target: where to pay out pool reward
        get_plot_signature: retrieve the signature corresponding to the plot public key
        get_pool_signature: retrieve the signature corresponding to the pool public key
        seed: seed to randomize block

    """

    if prev_block is not None:
        res = get_prev_transaction_block(prev_block, blocks, total_iters_sp)
        is_transaction_block: bool = res[0]
        prev_transaction_block: Optional[BlockRecord] = res[1]
    else:
        # Genesis is a transaction block
        prev_transaction_block = None
        is_transaction_block = True

    random.seed(seed)
    # Use the extension data to create different blocks based on header hash
    extension_data: bytes32 = random.randint(0, 100000000).to_bytes(32, "big")
    if prev_block is None:
        height: uint32 = uint32(0)
    else:
        height = uint32(prev_block.height + 1)

    # Create filter
    byte_array_tx: List[bytes32] = []
    tx_additions: List[Coin] = []
    tx_removals: List[bytes32] = []

    pool_target_signature: Optional[G2Element] = get_pool_signature(
        pool_target, reward_block_unfinished.proof_of_space.pool_public_key
    )

    foliage_data = FoliageBlockData(
        reward_block_unfinished.get_hash(),
        pool_target,
        pool_target_signature,
        farmer_reward_puzzlehash,
        extension_data,
    )

    foliage_block_data_signature: G2Element = get_plot_signature(
        foliage_data.get_hash(),
        reward_block_unfinished.proof_of_space.plot_public_key,
    )

    prev_block_hash: bytes32 = constants.GENESIS_CHALLENGE
    if height != 0:
        assert prev_block is not None
        prev_block_hash = prev_block.header_hash

    generator_block_heights_list: List[uint32] = []

    if is_transaction_block:
        cost = uint64(0)

        # Calculate the cost of transactions
        if block_generator is not None:
            generator_block_heights_list = block_generator.block_height_list()
            result: NPCResult = get_name_puzzle_conditions(block_generator, constants.MAX_BLOCK_COST_CLVM, True)
            cost = calculate_cost_of_program(block_generator.program, result, constants.COST_PER_BYTE)

            removal_amount = 0
            addition_amount = 0
            for coin in removals:
                removal_amount += coin.amount
            for coin in additions:
                addition_amount += coin.amount
            spend_bundle_fees = removal_amount - addition_amount
        else:
            spend_bundle_fees = 0

        reward_claims_incorporated = []
        if height > 0:
            assert prev_transaction_block is not None
            assert prev_block is not None
            curr: BlockRecord = prev_block
            while not curr.is_transaction_block:
                curr = blocks.block_record(curr.prev_hash)

            assert curr.fees is not None
            pool_coin = create_pool_coin(
                curr.height, curr.pool_puzzle_hash, calculate_pool_reward(curr.height), constants.GENESIS_CHALLENGE
            )

            farmer_coin = create_farmer_coin(
                curr.height,
                curr.farmer_puzzle_hash,
                uint64(calculate_base_farmer_reward(curr.height) + curr.fees),
                constants.GENESIS_CHALLENGE,
            )
            assert curr.header_hash == prev_transaction_block.header_hash
            reward_claims_incorporated += [pool_coin, farmer_coin]

            if curr.height > 0:
                curr = blocks.block_record(curr.prev_hash)
                # Prev block is not genesis
                while not curr.is_transaction_block:
                    pool_coin = create_pool_coin(
                        curr.height,
                        curr.pool_puzzle_hash,
                        calculate_pool_reward(curr.height),
                        constants.GENESIS_CHALLENGE,
                    )
                    farmer_coin = create_farmer_coin(
                        curr.height,
                        curr.farmer_puzzle_hash,
                        calculate_base_farmer_reward(curr.height),
                        constants.GENESIS_CHALLENGE,
                    )
                    reward_claims_incorporated += [pool_coin, farmer_coin]
                    curr = blocks.block_record(curr.prev_hash)
        additions.extend(reward_claims_incorporated.copy())
        for coin in additions:
            tx_additions.append(coin)
            byte_array_tx.append(bytearray(coin.puzzle_hash))
        for coin in removals:
            tx_removals.append(coin.name())
            byte_array_tx.append(bytearray(coin.name()))

        bip158: PyBIP158 = PyBIP158(byte_array_tx)
        encoded = bytes(bip158.GetEncoded())

        removal_merkle_set = MerkleSet()
        addition_merkle_set = MerkleSet()

        # Create removal Merkle set
        for coin_name in tx_removals:
            removal_merkle_set.add_already_hashed(coin_name)

        # Create addition Merkle set
        puzzlehash_coin_map: Dict[bytes32, List[Coin]] = {}

        for coin in tx_additions:
            if coin.puzzle_hash in puzzlehash_coin_map:
                puzzlehash_coin_map[coin.puzzle_hash].append(coin)
            else:
                puzzlehash_coin_map[coin.puzzle_hash] = [coin]

        # Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
        for puzzle, coins in puzzlehash_coin_map.items():
            addition_merkle_set.add_already_hashed(puzzle)
            addition_merkle_set.add_already_hashed(hash_coin_list(coins))

        additions_root = addition_merkle_set.get_root()
        removals_root = removal_merkle_set.get_root()

        generator_hash = bytes32([0] * 32)
        if block_generator is not None:
            generator_hash = std_hash(block_generator.program)

        generator_refs_hash = bytes32([1] * 32)
        if generator_block_heights_list not in (None, []):
            generator_ref_list_bytes = b"".join([bytes(i) for i in generator_block_heights_list])
            generator_refs_hash = std_hash(generator_ref_list_bytes)

        filter_hash: bytes32 = std_hash(encoded)

        transactions_info: Optional[TransactionsInfo] = TransactionsInfo(
            generator_hash,
            generator_refs_hash,
            aggregate_sig,
            uint64(spend_bundle_fees),
            cost,
            reward_claims_incorporated,
        )
        if prev_transaction_block is None:
            prev_transaction_block_hash: bytes32 = constants.GENESIS_CHALLENGE
        else:
            prev_transaction_block_hash = prev_transaction_block.header_hash

        assert transactions_info is not None
        foliage_transaction_block: Optional[FoliageTransactionBlock] = FoliageTransactionBlock(
            prev_transaction_block_hash,
            timestamp,
            filter_hash,
            additions_root,
            removals_root,
            transactions_info.get_hash(),
        )
        assert foliage_transaction_block is not None

        foliage_transaction_block_hash: Optional[bytes32] = foliage_transaction_block.get_hash()
        foliage_transaction_block_signature: Optional[G2Element] = get_plot_signature(
            foliage_transaction_block_hash, reward_block_unfinished.proof_of_space.plot_public_key
        )
        assert foliage_transaction_block_signature is not None
    else:
        foliage_transaction_block_hash = None
        foliage_transaction_block_signature = None
        foliage_transaction_block = None
        transactions_info = None
    assert (foliage_transaction_block_hash is None) == (foliage_transaction_block_signature is None)

    foliage = Foliage(
        prev_block_hash,
        reward_block_unfinished.get_hash(),
        foliage_data,
        foliage_block_data_signature,
        foliage_transaction_block_hash,
        foliage_transaction_block_signature,
    )

    return foliage, foliage_transaction_block, transactions_info
Пример #27
0
async def validate_block_body(
    constants: ConsensusConstants,
    blocks: BlockchainInterface,
    block_store: BlockStore,
    coin_store: CoinStore,
    peak: Optional[BlockRecord],
    block: Union[FullBlock, UnfinishedBlock],
    height: uint32,
    npc_result: Optional[NPCResult],
    fork_point_with_peak: Optional[uint32],
    get_block_generator: Callable,
    validate_signature=True,
) -> Tuple[Optional[Err], Optional[NPCResult]]:
    """
    This assumes the header block has been completely validated.
    Validates the transactions and body of the block. Returns None for the first value if everything
    validates correctly, or an Err if something does not validate. For the second value, returns a CostResult
    only if validation succeeded, and there are transactions. In other cases it returns None. The NPC result is
    the result of running the generator with the previous generators refs. It is only present for transaction
    blocks which have spent coins.
    """
    if isinstance(block, FullBlock):
        assert height == block.height
    prev_transaction_block_height: uint32 = uint32(0)

    # 1. For non transaction-blocs: foliage block, transaction filter, transactions info, and generator must
    # be empty. If it is a block but not a transaction block, there is no body to validate. Check that all fields are
    # None
    if block.foliage.foliage_transaction_block_hash is None:
        if (block.foliage_transaction_block is not None
                or block.transactions_info is not None
                or block.transactions_generator is not None):
            return Err.NOT_BLOCK_BUT_HAS_DATA, None

        prev_tb: BlockRecord = blocks.block_record(block.prev_header_hash)
        while not prev_tb.is_transaction_block:
            prev_tb = blocks.block_record(prev_tb.prev_hash)
        assert prev_tb.timestamp is not None
        if len(block.transactions_generator_ref_list) > 0:
            return Err.NOT_BLOCK_BUT_HAS_DATA, None

        return None, None  # This means the block is valid

    # All checks below this point correspond to transaction blocks
    # 2. For blocks, foliage block, transactions info must not be empty
    if block.foliage_transaction_block is None or block.transactions_info is None:
        return Err.IS_TRANSACTION_BLOCK_BUT_NO_DATA, None
    assert block.foliage_transaction_block is not None

    # keeps track of the reward coins that need to be incorporated
    expected_reward_coins: Set[Coin] = set()

    # 3. The transaction info hash in the Foliage block must match the transaction info
    if block.foliage_transaction_block.transactions_info_hash != std_hash(
            block.transactions_info):
        return Err.INVALID_TRANSACTIONS_INFO_HASH, None

    # 4. The foliage block hash in the foliage block must match the foliage block
    if block.foliage.foliage_transaction_block_hash != std_hash(
            block.foliage_transaction_block):
        return Err.INVALID_FOLIAGE_BLOCK_HASH, None

    # 5. The reward claims must be valid for the previous blocks, and current block fees
    # If height == 0, expected_reward_coins will be left empty
    if height > 0:
        # Add reward claims for all blocks from the prev prev block, until the prev block (including the latter)
        prev_transaction_block = blocks.block_record(
            block.foliage_transaction_block.prev_transaction_block_hash)
        prev_transaction_block_height = prev_transaction_block.height
        assert prev_transaction_block.fees is not None
        pool_coin = create_pool_coin(
            prev_transaction_block_height,
            prev_transaction_block.pool_puzzle_hash,
            calculate_pool_reward(prev_transaction_block.height),
            constants.GENESIS_CHALLENGE,
        )
        farmer_coin = create_farmer_coin(
            prev_transaction_block_height,
            prev_transaction_block.farmer_puzzle_hash,
            uint64(
                calculate_base_farmer_reward(prev_transaction_block.height) +
                prev_transaction_block.fees),
            constants.GENESIS_CHALLENGE,
        )
        # Adds the previous block
        expected_reward_coins.add(pool_coin)
        expected_reward_coins.add(farmer_coin)

        # For the second block in the chain, don't go back further
        if prev_transaction_block.height > 0:
            curr_b = blocks.block_record(prev_transaction_block.prev_hash)
            while not curr_b.is_transaction_block:
                expected_reward_coins.add(
                    create_pool_coin(
                        curr_b.height,
                        curr_b.pool_puzzle_hash,
                        calculate_pool_reward(curr_b.height),
                        constants.GENESIS_CHALLENGE,
                    ))
                expected_reward_coins.add(
                    create_farmer_coin(
                        curr_b.height,
                        curr_b.farmer_puzzle_hash,
                        calculate_base_farmer_reward(curr_b.height),
                        constants.GENESIS_CHALLENGE,
                    ))
                curr_b = blocks.block_record(curr_b.prev_hash)

    if set(block.transactions_info.reward_claims_incorporated
           ) != expected_reward_coins:
        return Err.INVALID_REWARD_COINS, None

    if len(block.transactions_info.reward_claims_incorporated) != len(
            expected_reward_coins):
        return Err.INVALID_REWARD_COINS, None

    removals: List[bytes32] = []
    coinbase_additions: List[Coin] = list(expected_reward_coins)
    additions: List[Coin] = []
    npc_list: List[NPC] = []
    removals_puzzle_dic: Dict[bytes32, bytes32] = {}
    cost: uint64 = uint64(0)

    # In header validation we check that timestamp is not more that 10 minutes into the future
    # 6. No transactions before INITIAL_TRANSACTION_FREEZE timestamp
    # (this test has been removed)

    # 7a. The generator root must be the hash of the serialized bytes of
    #     the generator for this block (or zeroes if no generator)
    if block.transactions_generator is not None:
        if std_hash(bytes(block.transactions_generator)
                    ) != block.transactions_info.generator_root:
            return Err.INVALID_TRANSACTIONS_GENERATOR_HASH, None
    else:
        if block.transactions_info.generator_root != bytes([0] * 32):
            return Err.INVALID_TRANSACTIONS_GENERATOR_HASH, None

    # 8a. The generator_ref_list must be the hash of the serialized bytes of
    #     the generator ref list for this block (or 'one' bytes [0x01] if no generator)
    # 8b. The generator ref list length must be less than or equal to MAX_GENERATOR_REF_LIST_SIZE entries
    # 8c. The generator ref list must not point to a height >= this block's height
    if block.transactions_generator_ref_list in (None, []):
        if block.transactions_info.generator_refs_root != bytes([1] * 32):
            return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
    else:
        # If we have a generator reference list, we must have a generator
        if block.transactions_generator is None:
            return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None

        # The generator_refs_root must be the hash of the concatenation of the List[uint32]
        generator_refs_hash = std_hash(b"".join(
            [bytes(i) for i in block.transactions_generator_ref_list]))
        if block.transactions_info.generator_refs_root != generator_refs_hash:
            return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
        if len(block.transactions_generator_ref_list
               ) > constants.MAX_GENERATOR_REF_LIST_SIZE:
            return Err.TOO_MANY_GENERATOR_REFS, None
        if any([
                index >= height
                for index in block.transactions_generator_ref_list
        ]):
            return Err.FUTURE_GENERATOR_REFS, None

    if block.transactions_generator is not None:
        # Get List of names removed, puzzles hashes for removed coins and conditions created

        assert npc_result is not None
        cost = calculate_cost_of_program(block.transactions_generator,
                                         npc_result, constants.COST_PER_BYTE)
        npc_list = npc_result.npc_list

        # 7. Check that cost <= MAX_BLOCK_COST_CLVM
        log.debug(
            f"Cost: {cost} max: {constants.MAX_BLOCK_COST_CLVM} "
            f"percent full: {round(100 * (cost / constants.MAX_BLOCK_COST_CLVM), 2)}%"
        )
        if cost > constants.MAX_BLOCK_COST_CLVM:
            return Err.BLOCK_COST_EXCEEDS_MAX, None

        # 8. The CLVM program must not return any errors
        if npc_result.error is not None:
            return Err(npc_result.error), None

        for npc in npc_list:
            removals.append(npc.coin_name)
            removals_puzzle_dic[npc.coin_name] = npc.puzzle_hash

        additions = additions_for_npc(npc_list)
    else:
        assert npc_result is None

    # 9. Check that the correct cost is in the transactions info
    if block.transactions_info.cost != cost:
        return Err.INVALID_BLOCK_COST, None

    additions_dic: Dict[bytes32, Coin] = {}
    # 10. Check additions for max coin amount
    # Be careful to check for 64 bit overflows in other languages. This is the max 64 bit unsigned integer
    # We will not even reach here because Coins do type checking (uint64)
    for coin in additions + coinbase_additions:
        additions_dic[coin.name()] = coin
        if coin.amount < 0:
            return Err.COIN_AMOUNT_NEGATIVE, None

        if coin.amount > constants.MAX_COIN_AMOUNT:
            return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None

    # 11. Validate addition and removal roots
    root_error = validate_block_merkle_roots(
        block.foliage_transaction_block.additions_root,
        block.foliage_transaction_block.removals_root,
        additions + coinbase_additions,
        removals,
    )
    if root_error:
        return root_error, None

    # 12. The additions and removals must result in the correct filter
    byte_array_tx: List[bytes32] = []

    for coin in additions + coinbase_additions:
        # TODO: address hint error and remove ignore
        #       error: Argument 1 to "append" of "list" has incompatible type "bytearray"; expected "bytes32"
        #       [arg-type]
        byte_array_tx.append(bytearray(
            coin.puzzle_hash))  # type: ignore[arg-type]
    for coin_name in removals:
        # TODO: address hint error and remove ignore
        #       error: Argument 1 to "append" of "list" has incompatible type "bytearray"; expected "bytes32"
        #       [arg-type]
        byte_array_tx.append(bytearray(coin_name))  # type: ignore[arg-type]

    bip158: PyBIP158 = PyBIP158(byte_array_tx)
    encoded_filter = bytes(bip158.GetEncoded())
    filter_hash = std_hash(encoded_filter)

    if filter_hash != block.foliage_transaction_block.filter_hash:
        return Err.INVALID_TRANSACTIONS_FILTER_HASH, None

    # 13. Check for duplicate outputs in additions
    addition_counter = collections.Counter(
        _.name() for _ in additions + coinbase_additions)
    for k, v in addition_counter.items():
        if v > 1:
            return Err.DUPLICATE_OUTPUT, None

    # 14. Check for duplicate spends inside block
    removal_counter = collections.Counter(removals)
    for k, v in removal_counter.items():
        if v > 1:
            return Err.DOUBLE_SPEND, None

    # 15. Check if removals exist and were not previously spent. (unspent_db + diff_store + this_block)
    # The fork point is the last block in common between the peak chain and the chain of `block`
    if peak is None or height == 0:
        fork_h: int = -1
    elif fork_point_with_peak is not None:
        fork_h = fork_point_with_peak
    else:
        fork_h = find_fork_point_in_chain(
            blocks, peak, blocks.block_record(block.prev_header_hash))

    # Get additions and removals since (after) fork_h but not including this block
    # The values include: the coin that was added, the height of the block in which it was confirmed, and the
    # timestamp of the block in which it was confirmed
    additions_since_fork: Dict[bytes32, Tuple[Coin, uint32, uint64]] = {
    }  # This includes coinbase additions
    removals_since_fork: Set[bytes32] = set()

    # For height 0, there are no additions and removals before this block, so we can skip
    if height > 0:
        # First, get all the blocks in the fork > fork_h, < block.height
        prev_block: Optional[FullBlock] = await block_store.get_full_block(
            block.prev_header_hash)
        reorg_blocks: Dict[uint32, FullBlock] = {}
        curr: Optional[FullBlock] = prev_block
        assert curr is not None
        while curr.height > fork_h:
            if curr.height == 0:
                break
            curr = await block_store.get_full_block(curr.prev_header_hash)
            assert curr is not None
            reorg_blocks[curr.height] = curr
        if fork_h != -1:
            assert len(reorg_blocks) == height - fork_h - 1

        curr = prev_block
        assert curr is not None
        while curr.height > fork_h:
            # Coin store doesn't contain coins from fork, we have to run generator for each block in fork
            if curr.transactions_generator is not None:
                # These blocks are in the past and therefore assumed to be valid, so get_block_generator won't raise
                curr_block_generator: Optional[
                    BlockGenerator] = await get_block_generator(curr)
                assert curr_block_generator is not None and curr.transactions_info is not None
                curr_npc_result = get_name_puzzle_conditions(
                    curr_block_generator,
                    min(constants.MAX_BLOCK_COST_CLVM,
                        curr.transactions_info.cost),
                    cost_per_byte=constants.COST_PER_BYTE,
                    mempool_mode=False,
                )
                removals_in_curr, additions_in_curr = tx_removals_and_additions(
                    curr_npc_result.npc_list)
            else:
                removals_in_curr = []
                additions_in_curr = []

            for c_name in removals_in_curr:
                assert c_name not in removals_since_fork
                removals_since_fork.add(c_name)
            for c in additions_in_curr:
                assert c.name() not in additions_since_fork
                assert curr.foliage_transaction_block is not None
                additions_since_fork[c.name()] = (
                    c, curr.height, curr.foliage_transaction_block.timestamp)

            for coinbase_coin in curr.get_included_reward_coins():
                assert coinbase_coin.name() not in additions_since_fork
                assert curr.foliage_transaction_block is not None
                additions_since_fork[coinbase_coin.name()] = (
                    coinbase_coin,
                    curr.height,
                    curr.foliage_transaction_block.timestamp,
                )
            if curr.height == 0:
                break
            curr = reorg_blocks[curr.height - 1]
            assert curr is not None

    removal_coin_records: Dict[bytes32, CoinRecord] = {}
    for rem in removals:
        if rem in additions_dic:
            # Ephemeral coin
            rem_coin: Coin = additions_dic[rem]
            new_unspent: CoinRecord = CoinRecord(
                rem_coin,
                height,
                height,
                False,
                block.foliage_transaction_block.timestamp,
            )
            removal_coin_records[new_unspent.name] = new_unspent
        else:
            unspent = await coin_store.get_coin_record(rem)
            if unspent is not None and unspent.confirmed_block_index <= fork_h:
                # Spending something in the current chain, confirmed before fork
                # (We ignore all coins confirmed after fork)
                if unspent.spent == 1 and unspent.spent_block_index <= fork_h:
                    # Check for coins spent in an ancestor block
                    return Err.DOUBLE_SPEND, None
                removal_coin_records[unspent.name] = unspent
            else:
                # This coin is not in the current heaviest chain, so it must be in the fork
                if rem not in additions_since_fork:
                    # Check for spending a coin that does not exist in this fork
                    log.error(
                        f"Err.UNKNOWN_UNSPENT: COIN ID: {rem} NPC RESULT: {npc_result}"
                    )
                    return Err.UNKNOWN_UNSPENT, None
                new_coin, confirmed_height, confirmed_timestamp = additions_since_fork[
                    rem]
                new_coin_record: CoinRecord = CoinRecord(
                    new_coin,
                    confirmed_height,
                    uint32(0),
                    False,
                    confirmed_timestamp,
                )
                removal_coin_records[new_coin_record.name] = new_coin_record

            # This check applies to both coins created before fork (pulled from coin_store),
            # and coins created after fork (additions_since_fork)
            if rem in removals_since_fork:
                # This coin was spent in the fork
                return Err.DOUBLE_SPEND_IN_FORK, None

    removed = 0
    for unspent in removal_coin_records.values():
        removed += unspent.coin.amount

    added = 0
    for coin in additions:
        added += coin.amount

    # 16. Check that the total coin amount for added is <= removed
    if removed < added:
        return Err.MINTING_COIN, None

    fees = removed - added
    assert fees >= 0
    assert_fee_sum: uint128 = uint128(0)

    for npc in npc_list:
        if ConditionOpcode.RESERVE_FEE in npc.condition_dict:
            fee_list: List[ConditionWithArgs] = npc.condition_dict[
                ConditionOpcode.RESERVE_FEE]
            for cvp in fee_list:
                fee = int_from_bytes(cvp.vars[0])
                if fee < 0:
                    return Err.RESERVE_FEE_CONDITION_FAILED, None
                assert_fee_sum = uint128(assert_fee_sum + fee)

    # 17. Check that the assert fee sum <= fees, and that each reserved fee is non-negative
    if fees < assert_fee_sum:
        return Err.RESERVE_FEE_CONDITION_FAILED, None

    # 18. Check that the fee amount + farmer reward < maximum coin amount
    if fees + calculate_base_farmer_reward(height) > constants.MAX_COIN_AMOUNT:
        return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None

    # 19. Check that the computed fees are equal to the fees in the block header
    if block.transactions_info.fees != fees:
        return Err.INVALID_BLOCK_FEE_AMOUNT, None

    # 20. Verify that removed coin puzzle_hashes match with calculated puzzle_hashes
    for unspent in removal_coin_records.values():
        if unspent.coin.puzzle_hash != removals_puzzle_dic[unspent.name]:
            return Err.WRONG_PUZZLE_HASH, None

    # 21. Verify conditions
    for npc in npc_list:
        assert height is not None
        unspent = removal_coin_records[npc.coin_name]
        error = mempool_check_conditions_dict(
            unspent,
            npc.condition_dict,
            prev_transaction_block_height,
            block.foliage_transaction_block.timestamp,
        )
        if error:
            return error, None

    # create hash_key list for aggsig check
    pairs_pks, pairs_msgs = pkm_pairs(npc_list,
                                      constants.AGG_SIG_ME_ADDITIONAL_DATA)

    # 22. Verify aggregated signature
    # TODO: move this to pre_validate_blocks_multiprocessing so we can sync faster
    if not block.transactions_info.aggregated_signature:
        return Err.BAD_AGGREGATE_SIGNATURE, None

    # The pairing cache is not useful while syncing as each pairing is seen
    # only once, so the extra effort of populating it is not justified.
    # However, we force caching of pairings just for unfinished blocks
    # as the cache is likely to be useful when validating the corresponding
    # finished blocks later.
    if validate_signature:
        force_cache: bool = isinstance(block, UnfinishedBlock)
        if not cached_bls.aggregate_verify(
                pairs_pks, pairs_msgs,
                block.transactions_info.aggregated_signature, force_cache):
            return Err.BAD_AGGREGATE_SIGNATURE, None

    return None, npc_result
Пример #28
0
async def validate_block_body(
    constants: ConsensusConstants,
    blocks: BlockchainInterface,
    block_store: BlockStore,
    coin_store: CoinStore,
    peak: Optional[BlockRecord],
    block: Union[FullBlock, UnfinishedBlock],
    height: uint32,
    npc_result: Optional[NPCResult],
    fork_point_with_peak: Optional[uint32],
    get_block_generator: Callable,
) -> Tuple[Optional[Err], Optional[NPCResult]]:
    """
    This assumes the header block has been completely validated.
    Validates the transactions and body of the block. Returns None for the first value if everything
    validates correctly, or an Err if something does not validate. For the second value, returns a CostResult
    if validation succeeded, and there are transactions
    """
    if isinstance(block, FullBlock):
        assert height == block.height
    prev_transaction_block_height: uint32 = uint32(0)

    # 1. For non block blocks, foliage block, transaction filter, transactions info, and generator must be empty
    # If it is a block but not a transaction block, there is no body to validate. Check that all fields are None
    if block.foliage.foliage_transaction_block_hash is None:
        if (
            block.foliage_transaction_block is not None
            or block.transactions_info is not None
            or block.transactions_generator is not None
        ):
            return Err.NOT_BLOCK_BUT_HAS_DATA, None
        return None, None  # This means the block is valid

    # 2. For blocks, foliage block, transaction filter, transactions info must not be empty
    if (
        block.foliage_transaction_block is None
        or block.foliage_transaction_block.filter_hash is None
        or block.transactions_info is None
    ):
        return Err.IS_TRANSACTION_BLOCK_BUT_NO_DATA, None

    # keeps track of the reward coins that need to be incorporated
    expected_reward_coins: Set[Coin] = set()

    # 3. The transaction info hash in the Foliage block must match the transaction info
    if block.foliage_transaction_block.transactions_info_hash != std_hash(block.transactions_info):
        return Err.INVALID_TRANSACTIONS_INFO_HASH, None

    # 4. The foliage block hash in the foliage block must match the foliage block
    if block.foliage.foliage_transaction_block_hash != std_hash(block.foliage_transaction_block):
        return Err.INVALID_FOLIAGE_BLOCK_HASH, None

    # 7. The reward claims must be valid for the previous blocks, and current block fees
    if height > 0:
        # Add reward claims for all blocks from the prev prev block, until the prev block (including the latter)
        prev_transaction_block = blocks.block_record(block.foliage_transaction_block.prev_transaction_block_hash)
        prev_transaction_block_height = prev_transaction_block.height
        assert prev_transaction_block.fees is not None
        pool_coin = create_pool_coin(
            prev_transaction_block_height,
            prev_transaction_block.pool_puzzle_hash,
            calculate_pool_reward(prev_transaction_block.height),
            constants.GENESIS_CHALLENGE,
        )
        farmer_coin = create_farmer_coin(
            prev_transaction_block_height,
            prev_transaction_block.farmer_puzzle_hash,
            uint64(calculate_base_farmer_reward(prev_transaction_block.height) + prev_transaction_block.fees),
            constants.GENESIS_CHALLENGE,
        )
        # Adds the previous block
        expected_reward_coins.add(pool_coin)
        expected_reward_coins.add(farmer_coin)

        # For the second block in the chain, don't go back further
        if prev_transaction_block.height > 0:
            curr_b = blocks.block_record(prev_transaction_block.prev_hash)
            while not curr_b.is_transaction_block:
                expected_reward_coins.add(
                    create_pool_coin(
                        curr_b.height,
                        curr_b.pool_puzzle_hash,
                        calculate_pool_reward(curr_b.height),
                        constants.GENESIS_CHALLENGE,
                    )
                )
                expected_reward_coins.add(
                    create_farmer_coin(
                        curr_b.height,
                        curr_b.farmer_puzzle_hash,
                        calculate_base_farmer_reward(curr_b.height),
                        constants.GENESIS_CHALLENGE,
                    )
                )
                curr_b = blocks.block_record(curr_b.prev_hash)

    if set(block.transactions_info.reward_claims_incorporated) != expected_reward_coins:
        return Err.INVALID_REWARD_COINS, None

    removals: List[bytes32] = []
    coinbase_additions: List[Coin] = list(expected_reward_coins)
    additions: List[Coin] = []
    coin_announcement_names: Set[bytes32] = set()
    puzzle_announcement_names: Set[bytes32] = set()
    npc_list: List[NPC] = []
    removals_puzzle_dic: Dict[bytes32, bytes32] = {}
    cost: uint64 = uint64(0)

    if height <= constants.INITIAL_FREEZE_PERIOD and block.transactions_generator is not None:
        return Err.INITIAL_TRANSACTION_FREEZE, None

    if height > constants.INITIAL_FREEZE_PERIOD and constants.NETWORK_TYPE == NetworkType.MAINNET:
        return Err.INITIAL_TRANSACTION_FREEZE, None
    else:
        # 6a. The generator root must be the hash of the serialized bytes of
        #     the generator for this block (or zeroes if no generator)
        if block.transactions_generator is not None:
            if std_hash(bytes(block.transactions_generator)) != block.transactions_info.generator_root:
                return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT, None
        else:
            if block.transactions_info.generator_root != bytes([0] * 32):
                return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT, None

        # 6b. The generator_ref_list must be the hash of the serialized bytes of
        #     the generator ref list for this block (or 'one' bytes [0x01] if no generator)
        # 6c. The generator ref list length must be less than or equal to MAX_GENERATOR_REF_LIST_SIZE entries
        if block.transactions_generator_ref_list in (None, []):
            if block.transactions_info.generator_refs_root != bytes([1] * 32):
                return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
        else:
            # If we have a generator reference list, we must have a generator
            if block.transactions_generator is None:
                return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None

            # The generator_refs_root must be the hash of the concatenation of the List[uint32]
            generator_refs_hash = std_hash(b"".join([bytes(i) for i in block.transactions_generator_ref_list]))
            if block.transactions_info.generator_refs_root != generator_refs_hash:
                return Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT, None
            if len(block.transactions_generator_ref_list) > constants.MAX_GENERATOR_REF_LIST_SIZE:
                return Err.PRE_SOFT_FORK_TOO_MANY_GENERATOR_REFS, None

        if block.transactions_generator is not None:
            # Get List of names removed, puzzles hashes for removed coins and conditions created

            assert npc_result is not None
            cost = calculate_cost_of_program(block.transactions_generator, npc_result, constants.COST_PER_BYTE)
            npc_list = npc_result.npc_list

            # 8. Check that cost <= MAX_BLOCK_COST_CLVM
            log.warning(f"Cost: {cost} max: {constants.MAX_BLOCK_COST_CLVM}")
            if cost > constants.MAX_BLOCK_COST_CLVM:
                return Err.BLOCK_COST_EXCEEDS_MAX, None
            if npc_result.error is not None:
                return Err.GENERATOR_RUNTIME_ERROR, None

            for npc in npc_list:
                removals.append(npc.coin_name)
                removals_puzzle_dic[npc.coin_name] = npc.puzzle_hash

            additions = additions_for_npc(npc_list)
            coin_announcement_names = coin_announcements_names_for_npc(npc_list)
            puzzle_announcement_names = puzzle_announcements_names_for_npc(npc_list)
        else:
            assert npc_result is None

        # 9. Check that the correct cost is in the transactions info
        if block.transactions_info.cost != cost:
            return Err.INVALID_BLOCK_COST, None

        additions_dic: Dict[bytes32, Coin] = {}
        # 10. Check additions for max coin amount
        # Be careful to check for 64 bit overflows in other languages. This is the max 64 bit unsigned integer
        for coin in additions + coinbase_additions:
            additions_dic[coin.name()] = coin
            if coin.amount > constants.MAX_COIN_AMOUNT:
                return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None

        # 11. Validate addition and removal roots
        root_error = validate_block_merkle_roots(
            block.foliage_transaction_block.additions_root,
            block.foliage_transaction_block.removals_root,
            additions + coinbase_additions,
            removals,
        )
        if root_error:
            return root_error, None

        # 12. The additions and removals must result in the correct filter
        byte_array_tx: List[bytes32] = []

        for coin in additions + coinbase_additions:
            byte_array_tx.append(bytearray(coin.puzzle_hash))
        for coin_name in removals:
            byte_array_tx.append(bytearray(coin_name))

        bip158: PyBIP158 = PyBIP158(byte_array_tx)
        encoded_filter = bytes(bip158.GetEncoded())
        filter_hash = std_hash(encoded_filter)

        if filter_hash != block.foliage_transaction_block.filter_hash:
            return Err.INVALID_TRANSACTIONS_FILTER_HASH, None

        # 13. Check for duplicate outputs in additions
        addition_counter = collections.Counter(_.name() for _ in additions + coinbase_additions)
        for k, v in addition_counter.items():
            if v > 1:
                return Err.DUPLICATE_OUTPUT, None

        # 14. Check for duplicate spends inside block
        removal_counter = collections.Counter(removals)
        for k, v in removal_counter.items():
            if v > 1:
                return Err.DOUBLE_SPEND, None

        # 15. Check if removals exist and were not previously spent. (unspent_db + diff_store + this_block)
        if peak is None or height == 0:
            fork_h: int = -1
        elif fork_point_with_peak is not None:
            fork_h = fork_point_with_peak
        else:
            fork_h = find_fork_point_in_chain(blocks, peak, blocks.block_record(block.prev_header_hash))

        if fork_h == -1:
            coin_store_reorg_height = -1
        else:
            last_block_in_common = await blocks.get_block_record_from_db(blocks.height_to_hash(uint32(fork_h)))
            assert last_block_in_common is not None
            coin_store_reorg_height = last_block_in_common.height

        # Get additions and removals since (after) fork_h but not including this block
        additions_since_fork: Dict[bytes32, Tuple[Coin, uint32]] = {}
        removals_since_fork: Set[bytes32] = set()
        coinbases_since_fork: Dict[bytes32, uint32] = {}

        if height > 0:
            prev_block: Optional[FullBlock] = await block_store.get_full_block(block.prev_header_hash)
            reorg_blocks: Dict[int, FullBlock] = {}
            curr: Optional[FullBlock] = prev_block
            assert curr is not None
            reorg_blocks[curr.height] = curr
            while curr.height > fork_h:
                if curr.height == 0:
                    break
                curr = await block_store.get_full_block(curr.prev_header_hash)
                assert curr is not None
                reorg_blocks[curr.height] = curr

            curr = prev_block
            assert curr is not None
            while curr.height > fork_h:
                # Coin store doesn't contain coins from fork, we have to run generator for each block in fork
                if curr.transactions_generator is not None:
                    curr_block_generator: Optional[BlockGenerator] = await get_block_generator(curr)
                    assert curr_block_generator is not None
                    npc_result = get_name_puzzle_conditions(curr_block_generator, False)
                    removals_in_curr, additions_in_curr = tx_removals_and_additions(npc_result.npc_list)
                else:
                    removals_in_curr = []
                    additions_in_curr = []

                for c_name in removals_in_curr:
                    removals_since_fork.add(c_name)
                for c in additions_in_curr:
                    additions_since_fork[c.name()] = (c, curr.height)

                for coinbase_coin in curr.get_included_reward_coins():
                    additions_since_fork[coinbase_coin.name()] = (coinbase_coin, curr.height)
                    coinbases_since_fork[coinbase_coin.name()] = curr.height
                if curr.height == 0:
                    break
                curr = reorg_blocks[curr.height - 1]
                assert curr is not None

        removal_coin_records: Dict[bytes32, CoinRecord] = {}
        for rem in removals:
            if rem in additions_dic:
                # Ephemeral coin
                rem_coin: Coin = additions_dic[rem]
                new_unspent: CoinRecord = CoinRecord(
                    rem_coin,
                    height,
                    uint32(0),
                    False,
                    (rem in coinbases_since_fork),
                    block.foliage_transaction_block.timestamp,
                )
                removal_coin_records[new_unspent.name] = new_unspent
            else:
                unspent = await coin_store.get_coin_record(rem)
                if unspent is not None and unspent.confirmed_block_index <= coin_store_reorg_height:
                    # Spending something in the current chain, confirmed before fork
                    # (We ignore all coins confirmed after fork)
                    if unspent.spent == 1 and unspent.spent_block_index <= coin_store_reorg_height:
                        # Check for coins spent in an ancestor block
                        return Err.DOUBLE_SPEND, None
                    removal_coin_records[unspent.name] = unspent
                else:
                    # This coin is not in the current heaviest chain, so it must be in the fork
                    if rem not in additions_since_fork:
                        # Check for spending a coin that does not exist in this fork
                        # TODO: fix this, there is a consensus bug here
                        return Err.UNKNOWN_UNSPENT, None
                    new_coin, confirmed_height = additions_since_fork[rem]
                    new_coin_record: CoinRecord = CoinRecord(
                        new_coin,
                        confirmed_height,
                        uint32(0),
                        False,
                        (rem in coinbases_since_fork),
                        block.foliage_transaction_block.timestamp,
                    )
                    removal_coin_records[new_coin_record.name] = new_coin_record

                # This check applies to both coins created before fork (pulled from coin_store),
                # and coins created after fork (additions_since_fork)>
                if rem in removals_since_fork:
                    # This coin was spent in the fork
                    return Err.DOUBLE_SPEND, None

        removed = 0
        for unspent in removal_coin_records.values():
            removed += unspent.coin.amount

        added = 0
        for coin in additions:
            added += coin.amount

        # 16. Check that the total coin amount for added is <= removed
        if removed < added:
            return Err.MINTING_COIN, None

        fees = removed - added
        assert_fee_sum: uint64 = uint64(0)

        for npc in npc_list:
            if ConditionOpcode.RESERVE_FEE in npc.condition_dict:
                fee_list: List[ConditionWithArgs] = npc.condition_dict[ConditionOpcode.RESERVE_FEE]
                for cvp in fee_list:
                    fee = int_from_bytes(cvp.vars[0])
                    assert_fee_sum = assert_fee_sum + fee

        # 17. Check that the assert fee sum <= fees
        if fees < assert_fee_sum:
            return Err.RESERVE_FEE_CONDITION_FAILED, None

        # 18. Check that the assert fee amount < maximum coin amount
        if fees > constants.MAX_COIN_AMOUNT:
            return Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, None

        # 19. Check that the computed fees are equal to the fees in the block header
        if block.transactions_info.fees != fees:
            return Err.INVALID_BLOCK_FEE_AMOUNT, None

        # 20. Verify that removed coin puzzle_hashes match with calculated puzzle_hashes
        for unspent in removal_coin_records.values():
            if unspent.coin.puzzle_hash != removals_puzzle_dic[unspent.name]:
                return Err.WRONG_PUZZLE_HASH, None

        # 21. Verify conditions
        # create hash_key list for aggsig check
        pairs_pks = []
        pairs_msgs = []
        for npc in npc_list:
            assert height is not None
            unspent = removal_coin_records[npc.coin_name]
            error = mempool_check_conditions_dict(
                unspent,
                coin_announcement_names,
                puzzle_announcement_names,
                npc.condition_dict,
                prev_transaction_block_height,
                block.foliage_transaction_block.timestamp,
            )
            if error:
                return error, None
            for pk, m in pkm_pairs_for_conditions_dict(
                npc.condition_dict, npc.coin_name, constants.AGG_SIG_ME_ADDITIONAL_DATA
            ):
                pairs_pks.append(pk)
                pairs_msgs.append(m)

        # 22. Verify aggregated signature
        # TODO: move this to pre_validate_blocks_multiprocessing so we can sync faster
        if not block.transactions_info.aggregated_signature:
            return Err.BAD_AGGREGATE_SIGNATURE, None

            # noinspection PyTypeChecker
        if not AugSchemeMPL.aggregate_verify(pairs_pks, pairs_msgs, block.transactions_info.aggregated_signature):
            return Err.BAD_AGGREGATE_SIGNATURE, None

        return None, npc_result
Пример #29
0
    async def validate_unfinished_block(
        self, block: UnfinishedBlock, skip_overflow_ss_validation=True
    ) -> PreValidationResult:
        if (
            not self.contains_block(block.prev_header_hash)
            and not block.prev_header_hash == self.constants.GENESIS_CHALLENGE
        ):
            return PreValidationResult(uint16(Err.INVALID_PREV_BLOCK_HASH.value), None, None)

        unfinished_header_block = UnfinishedHeaderBlock(
            block.finished_sub_slots,
            block.reward_chain_block,
            block.challenge_chain_sp_proof,
            block.reward_chain_sp_proof,
            block.foliage,
            block.foliage_transaction_block,
            b"",
        )
        prev_b = self.try_block_record(unfinished_header_block.prev_header_hash)
        sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty(
            self.constants, len(unfinished_header_block.finished_sub_slots) > 0, prev_b, self
        )
        required_iters, error = validate_unfinished_header_block(
            self.constants,
            self,
            unfinished_header_block,
            False,
            difficulty,
            sub_slot_iters,
            skip_overflow_ss_validation,
        )

        if error is not None:
            return PreValidationResult(uint16(error.code.value), None, None)

        prev_height = (
            -1
            if block.prev_header_hash == self.constants.GENESIS_CHALLENGE
            else self.block_record(block.prev_header_hash).height
        )

        npc_result = None
        if block.transactions_generator is not None:
            assert block.transactions_info is not None
            try:
                block_generator: Optional[BlockGenerator] = await self.get_block_generator(block)
            except ValueError:
                return PreValidationResult(uint16(Err.GENERATOR_REF_HAS_NO_GENERATOR.value), None, None)
            if block_generator is None:
                return PreValidationResult(uint16(Err.GENERATOR_REF_HAS_NO_GENERATOR.value), None, None)
            npc_result = get_name_puzzle_conditions(
                block_generator, min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), False
            )
        error_code, cost_result = await validate_block_body(
            self.constants,
            self,
            self.block_store,
            self.coin_store,
            self.get_peak(),
            block,
            uint32(prev_height + 1),
            npc_result,
            None,
            self.get_block_generator,
        )

        if error_code is not None:
            return PreValidationResult(uint16(error_code.value), None, None)

        return PreValidationResult(None, required_iters, cost_result)
Пример #30
0
    async def receive_block(
        self,
        block: FullBlock,
        pre_validation_result: Optional[PreValidationResult] = None,
        fork_point_with_peak: Optional[uint32] = None,
        summaries_to_check: List[SubEpochSummary] = None,  # passed only on long sync
    ) -> Tuple[ReceiveBlockResult, Optional[Err], Optional[uint32]]:
        """
        This method must be called under the blockchain lock
        Adds a new block into the blockchain, if it's valid and connected to the current
        blockchain, regardless of whether it is the child of a head, or another block.
        Returns a header if block is added to head. Returns an error if the block is
        invalid. Also returns the fork height, in the case of a new peak.
        """
        genesis: bool = block.height == 0

        if self.contains_block(block.header_hash):
            return ReceiveBlockResult.ALREADY_HAVE_BLOCK, None, None

        if not self.contains_block(block.prev_header_hash) and not genesis:
            return (
                ReceiveBlockResult.DISCONNECTED_BLOCK,
                Err.INVALID_PREV_BLOCK_HASH,
                None,
            )

        if not genesis and (self.block_record(block.prev_header_hash).height + 1) != block.height:
            return ReceiveBlockResult.INVALID_BLOCK, Err.INVALID_HEIGHT, None

        npc_result: Optional[NPCResult] = None
        if pre_validation_result is None:
            if block.height == 0:
                prev_b: Optional[BlockRecord] = None
            else:
                prev_b = self.block_record(block.prev_header_hash)
            sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty(
                self.constants, len(block.finished_sub_slots) > 0, prev_b, self
            )

            if block.is_transaction_block():
                if block.transactions_generator is not None:
                    try:
                        block_generator: Optional[BlockGenerator] = await self.get_block_generator(block)
                    except ValueError:
                        return ReceiveBlockResult.INVALID_BLOCK, Err.GENERATOR_REF_HAS_NO_GENERATOR, None
                    assert block_generator is not None and block.transactions_info is not None
                    npc_result = get_name_puzzle_conditions(
                        block_generator, min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), False
                    )
                    removals, tx_additions = tx_removals_and_additions(npc_result.npc_list)
                else:
                    removals, tx_additions = [], []
                header_block = get_block_header(block, tx_additions, removals)
            else:
                npc_result = None
                header_block = get_block_header(block, [], [])

            required_iters, error = validate_finished_header_block(
                self.constants,
                self,
                header_block,
                False,
                difficulty,
                sub_slot_iters,
            )

            if error is not None:
                return ReceiveBlockResult.INVALID_BLOCK, error.code, None
        else:
            npc_result = pre_validation_result.npc_result
            required_iters = pre_validation_result.required_iters
            assert pre_validation_result.error is None
        assert required_iters is not None
        error_code, _ = await validate_block_body(
            self.constants,
            self,
            self.block_store,
            self.coin_store,
            self.get_peak(),
            block,
            block.height,
            npc_result,
            fork_point_with_peak,
            self.get_block_generator,
        )
        if error_code is not None:
            return ReceiveBlockResult.INVALID_BLOCK, error_code, None

        block_record = block_to_block_record(
            self.constants,
            self,
            required_iters,
            block,
            None,
        )
        # Always add the block to the database
        async with self.block_store.db_wrapper.lock:
            try:
                # Perform the DB operations to update the state, and rollback if something goes wrong
                await self.block_store.db_wrapper.begin_transaction()
                await self.block_store.add_full_block(block, block_record)
                fork_height, peak_height, records = await self._reconsider_peak(
                    block_record, genesis, fork_point_with_peak, npc_result
                )
                await self.block_store.db_wrapper.commit_transaction()

                # Then update the memory cache. It is important that this task is not cancelled and does not throw
                self.add_block_record(block_record)
                for fetched_block_record in records:
                    self.__height_to_hash[fetched_block_record.height] = fetched_block_record.header_hash
                    if fetched_block_record.sub_epoch_summary_included is not None:
                        self.__sub_epoch_summaries[
                            fetched_block_record.height
                        ] = fetched_block_record.sub_epoch_summary_included
                if peak_height is not None:
                    self._peak_height = peak_height
                self.block_store.cache_block(block)
            except BaseException:
                await self.block_store.db_wrapper.rollback_transaction()
                raise
        if fork_height is not None:
            return ReceiveBlockResult.NEW_PEAK, None, fork_height
        else:
            return ReceiveBlockResult.ADDED_AS_ORPHAN, None, None